Merge pull request #34 from unitwk/abstract

Feat: user list filter
This commit is contained in:
unitwk 2023-12-15 11:46:16 +08:00 committed by GitHub
commit cfd16fdf8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 51 deletions

View File

@ -26,7 +26,6 @@ declare module 'vue' {
AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']

View File

@ -182,7 +182,7 @@ const appMenus = computed(() => {
setTheme(key as THEME);
},
conditions: !containerState.isDesignMode,
onlyPC: true,
onlyPC: false,
menus: [
{
title: $t("TXT_CODE_673eac8e"),
@ -300,7 +300,11 @@ const openPhoneMenu = (b = false) => {
@click="openPhoneMenu(true)"
></a-button>
<div v-for="(item, index) in appMenus" :key="index">
<a-dropdown v-if="item.menus && item.conditions && !item.onlyPC" placement="bottom">
<a-dropdown
v-if="item.menus && item.conditions && !item.onlyPC"
class="phone-nav-button"
placement="bottom"
>
<a-button type="text" :icon="h(item.icon)" size="small" @click.prevent></a-button>
<template #overlay>
<a-menu @click="(e: any) => item.click(String(e.key))">

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import BetweenMenus from "../BetweenMenus.vue";
import type { MountComponent, NodeStatus } from "@/types";
import type { UserInstance } from "@/types/user";
import { t } from "@/lang/i18n";
@ -53,6 +54,7 @@ const instancesList = computed(() => {
const initNodes = async () => {
await getNodes();
nodes?.value?.sort((a, b) => (a.available === b.available ? 0 : a.available ? -1 : 1));
if (!nodes.value?.length) {
return message.error(t("TXT_CODE_e3d96a26"));
}
@ -125,6 +127,7 @@ const handleQueryInstance = throttle(async () => {
const handleChangeNode = async (item: NodeStatus) => {
try {
operationForm.value.currentPage = 1;
currentRemoteNode.value = item;
await initInstancesData();
localStorage.setItem("pageSelectedRemote", JSON.stringify(item));
@ -152,46 +155,55 @@ const handleChangeNode = async (item: NodeStatus) => {
</a-typography-paragraph>
<a-row :gutter="[24, 24]" style="height: 100%">
<a-col :span="24">
<div class="flex-between flex-wrap align-center">
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item v-for="item in nodes" :key="item.uuid" @click="handleChangeNode(item)">
<DatabaseOutlined v-if="item.available" />
<FrownOutlined v-else />
{{ computeNodeName(item.ip, item.available, item.remarks) }}
</a-menu-item>
<a-menu-divider />
<a-menu-item key="toNodesPage">
<FormOutlined />
{{ t("TXT_CODE_28e53fed") }}
</a-menu-item>
</a-menu>
</template>
<a-button :class="isPhone && 'mb-10 w-100'">
{{
computeNodeName(
currentRemoteNode?.ip || "",
currentRemoteNode?.available || true,
currentRemoteNode?.remarks
)
}}
<DownOutlined />
</a-button>
</a-dropdown>
<div class="search-input" :class="isPhone && 'w-100'">
<a-input
v-model:value="operationForm.instanceName"
:placeholder="t('TXT_CODE_ce132192')"
@press-enter="handleQueryInstance"
@change="handleQueryInstance"
>
<template #prefix>
<search-outlined />
<BetweenMenus>
<template #left>
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item
v-for="item in nodes"
:key="item.uuid"
:disabled="!item.available"
@click="handleChangeNode(item)"
>
<DatabaseOutlined v-if="item.available" />
<FrownOutlined v-else />
{{ computeNodeName(item.ip, item.available, item.remarks) }}
</a-menu-item>
<a-menu-divider />
<a-menu-item key="toNodesPage">
<FormOutlined />
{{ t("TXT_CODE_28e53fed") }}
</a-menu-item>
</a-menu>
</template>
</a-input>
</div>
</div>
<a-button :class="isPhone && 'mb-10 w-100'">
{{
computeNodeName(
currentRemoteNode?.ip || "",
currentRemoteNode?.available || true,
currentRemoteNode?.remarks
)
}}
<DownOutlined />
</a-button>
</a-dropdown>
</template>
<template #right>
<div class="search-input" :class="isPhone && 'w-100'">
<a-input
v-model:value="operationForm.instanceName"
:placeholder="t('TXT_CODE_ce132192')"
@press-enter="handleQueryInstance"
@change="handleQueryInstance"
>
<template #prefix>
<search-outlined />
</template>
</a-input>
</div>
</template>
</BetweenMenus>
</a-col>
<a-col :span="24">
<div v-if="instances" class="flex-between align-center">

View File

@ -16,7 +16,8 @@ import {
ExclamationCircleOutlined,
DeleteOutlined,
WarningOutlined,
InfoCircleOutlined
InfoCircleOutlined,
FrownOutlined
} from "@ant-design/icons-vue";
import BetweenMenus from "@/components/BetweenMenus.vue";
import { router } from "@/config/router";
@ -294,7 +295,8 @@ onMounted(async () => {
:disabled="!item.available"
@click="handleChangeNode(item)"
>
<DatabaseOutlined />
<DatabaseOutlined v-if="item.available" />
<FrownOutlined v-else />
{{ computeNodeName(item.ip, item.available, item.remarks) }}
</a-menu-item>
<a-menu-divider />

View File

@ -223,7 +223,7 @@ const formData = ref<EditUserInfo>(_.cloneDeep(formDataOrigin));
const baseRules: Record<string, Rule[]> = {
userName: [
{ required: true, message: t("TXT_CODE_2695488c") },
{ min: 3, max: 12, message: t("TXT_CODE_3f477ec"), trigger: "blur" }
{ min: 3, max: 20, message: t("TXT_CODE_3f477ec"), trigger: "blur" }
],
permission: [{ required: true, message: t("TXT_CODE_3bb646e4") }]
};
@ -279,6 +279,11 @@ const handleEditUser = (user: BaseUserInfo) => {
userDialog.value.show();
};
const search = throttle(async () => {
operationForm.value.currentPage = 1;
await fetchData();
}, 600);
onMounted(async () => {
fetchData();
});
@ -364,7 +369,7 @@ onMounted(async () => {
</a-typography-title>
</template>
<template #right>
<a-button class="mr-8" type="default" :loading="getUserInfoLoading" @click="fetchData">
<a-button class="mr-8" type="default" :loading="getUserInfoLoading" @click="reload">
{{ t("TXT_CODE_b76d94e0") }}
</a-button>
<a-dropdown>
@ -387,7 +392,7 @@ onMounted(async () => {
<template #center>
<div class="search-input">
<a-input-group compact>
<a-select v-model:value="currentRole" style="width: 100px" @change="reload()">
<a-select v-model:value="currentRole" style="width: 100px" @change="search()">
<a-select-option value="">
{{ t("TXT_CODE_c48f6f64") }}
</a-select-option>
@ -399,7 +404,7 @@ onMounted(async () => {
v-model:value.trim="operationForm.name"
:placeholder="t('TXT_CODE_2471b9c')"
style="width: calc(100% - 100px)"
@change="reload()"
@change="search()"
>
<template #suffix>
<search-outlined />

View File

@ -1663,7 +1663,7 @@
"TXT_CODE_c1320e08": "你可以通过 “重置布局” 来清空你上传的所有文件",
"TXT_CODE_ba42d467": "网络URL",
"TXT_CODE_27efac3b": "编辑成功",
"TXT_CODE_3f477ec": "长度只能 3 ~ 12 个字符",
"TXT_CODE_3f477ec": "长度只能 3 ~ 20 个字符",
"TXT_CODE_25c895d5": "请输入密码",
"TXT_CODE_6032f5a3": "9 到 36 个字符,必须包含大小写字母和数字",
"TXT_CODE_79f9a172": "编辑用户",

View File

@ -45,15 +45,17 @@ router.get(
permission({ level: ROLE.ADMIN }),
validator({ query: { page: Number, page_size: Number } }),
async (ctx: Koa.ParameterizedContext) => {
const userName = ctx.query.userName as string;
const userName = ctx.query.userName;
const role = ctx.query.role;
const page = Number(ctx.query.page);
const pageSize = Number(ctx.query.page_size);
const condition: any = {};
if (userName) condition["userName"] = `%${userName}%`;
if (role) condition["permission"] = Number(role);
let resultPage = userSystem.getQueryWrapper().selectPage(condition, page, pageSize);
// make a copy, delete redundant
resultPage = JSON.parse(JSON.stringify(resultPage));
resultPage.data.forEach((v: any) => {
resultPage.data.forEach((v) => {
delete v.passWord;
delete v.salt;
});