Merge pull request #1004 from MCSManager/chino

Feat: user list
This commit is contained in:
unitwk 2023-09-03 12:06:44 +08:00 committed by GitHub
commit 73750b1070
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 313 additions and 82 deletions

View File

@ -1,5 +1,5 @@
import { useDefineApi } from "@/stores/useDefineApi";
import type { InstanceDetail, NodeStatus, Settings } from "@/types";
import type { InstanceDetail, NodeStatus, Settings, UserInfo } from "@/types";
import type { BaseUserInfo } from "@/types/user";
import type { IPanelOverviewResponse } from "../../../../common/global";
@ -71,6 +71,48 @@ export const setSettingInfo = useDefineApi<
method: "PUT"
});
// 用户管理
// 用户管理 获取信息
export const getUserInfo = useDefineApi<
{
params: {
userName: string;
page: number;
page_size: number;
};
},
{ total: number; pageSize: number; page: number; maxPage: number; data: UserInfo[] }
>({
url: "/api/auth/search",
method: "GET"
});
// 用户管理 删除用户
export const deleteUser = useDefineApi<
{
data: string[];
},
any
>({
url: "/api/auth",
method: "DELETE"
});
// 用户管理 新增用户
export const addUser = useDefineApi<
{
data: {
username: string;
password: string;
permission: number;
};
},
boolean
>({
url: "/api/auth",
method: "POST"
});
// 获取总览
// 获取设置信息

View File

@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
import type { IGlobalInstanceConfig } from "./../../../common/global";
import type { LayoutCardHeight } from "@/config/originLayoutConfig";
@ -85,3 +86,15 @@ export interface Settings {
quickInstallAddr: string;
redisUrl: string;
}
export interface UserInfo {
uuid: string;
userName: string;
registerTime: string;
permission: number;
passWordType: number;
loginTime: string;
isInit: boolean;
instances: any[];
apiKey: string;
}

View File

@ -1,103 +1,80 @@
<script setup lang="ts">
import CardPanel from "@/components/CardPanel.vue";
import type { LayoutCard } from "@/types/index";
import { ref, computed } from "vue";
import { ref, computed, onMounted } from "vue";
import { t } from "@/lang/i18n";
import { DownOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons-vue";
import { message } from "ant-design-vue";
import { DownOutlined } from "@ant-design/icons-vue";
import { throttle } from "lodash";
import CardPanel from "@/components/CardPanel.vue";
import BetweenMenus from "@/components/BetweenMenus.vue";
import { useScreen } from "../hooks/useScreen";
import { arrayFilter } from "../tools/array";
import { useAppRouters } from "@/hooks/useAppRouters";
import { getUserInfo, deleteUser as deleteUserApi, addUser as addUserApi } from "@/services/apis";
export interface UserInfo {
key?: string | number;
name: string;
level: number;
time: number;
registerTime: number;
}
import type { LayoutCard, UserInfo } from "@/types/index";
defineProps<{
card: LayoutCard;
}>();
interface dataType {
total: number;
pageSize: number;
page: number;
maxPage: number;
data: UserInfo[];
}
const { execute } = getUserInfo();
// eslint-disable-next-line no-unused-vars
const { getRouteParamsUrl, toPage } = useAppRouters();
const screen = useScreen();
const operationForm = ref({
name: ""
name: "",
currentPage: 1,
pageSize: 20
});
const handleToUserConfig = (user: UserInfo) => {
console.log(user);
toPage({
path: "/users/config",
query: {
uuid: "XXXZZZ123"
}
});
const permissionList = {
"1": t("普通用户"),
"10": t("管理员"),
"-1": t("被封禁")
};
const handleDeleteUser = (user: UserInfo) => {};
const dataSource: UserInfo[] = [
{
key: "1",
name: "Admin",
level: 10,
time: new Date().getTime(),
registerTime: new Date().getTime()
},
{
key: "2",
name: "Admin",
level: 10,
time: new Date().getTime(),
registerTime: new Date().getTime()
},
{
key: "3",
name: "Admin",
level: 10,
time: new Date().getTime(),
registerTime: new Date().getTime()
},
{
key: "4",
name: "Admin",
level: 10,
time: new Date().getTime(),
registerTime: new Date().getTime()
}
];
const columns = computed(() => {
return arrayFilter([
{
align: "center",
title: "用户名",
dataIndex: "name",
key: "name",
title: t("用户名"),
dataIndex: "userName",
key: "userName",
minWidth: "200px"
},
{
align: "center",
title: "角色",
dataIndex: "level",
key: "level",
minWidth: "200px"
title: t("权限"),
dataIndex: "permission",
key: "permission",
minWidth: "200px",
customRender: (e: { text: "1" | "10" | "-1" }) => {
return permissionList[e.text] || e.text;
}
},
{
align: "center",
title: "最后上线时间",
dataIndex: "time",
key: "time",
title: t("最后登录时间"),
dataIndex: "loginTime",
key: "loginTime",
minWidth: "200px",
condition: () => !screen.isPhone.value
},
{
align: "center",
title: "注册时间",
title: t("注册时间"),
dataIndex: "registerTime",
key: "registerTime",
minWidth: "200px",
@ -105,17 +82,187 @@ const columns = computed(() => {
},
{
align: "center",
title: "操作",
title: t("操作"),
key: "action",
minWidth: "200px"
}
]);
});
const rowSelection = () => {};
const total = ref(0);
const data = ref<dataType>();
const dataSource = computed(() => data?.value?.data);
const selectedUsers = ref<string[]>([]);
const handleToUserConfig = (user: any) => {
toPage({
path: "/users/config",
query: {
uuid: user.uuid
}
});
};
const fetchData = async () => {
if (operationForm.value.currentPage < 1) {
operationForm.value.currentPage = 1;
}
const res = await execute({
params: {
userName: operationForm.value.name,
page: operationForm.value.currentPage,
page_size: operationForm.value.pageSize
}
});
data.value = res.value!;
total.value = res.value?.total ?? 0;
};
const reload = throttle(() => {
fetchData();
}, 2000);
const deleteUser = async (userList: string[]) => {
try {
const { execute } = deleteUserApi();
await execute({
data: userList
});
} catch (error: any) {
message.error(error.message);
}
};
const handleDeleteUser = async (user: UserInfo) => {
await deleteUser([user.uuid]);
};
const handleBatchDelete = async () => {
if (selectedUsers.value.length === 0) {
return message.warn(t("请选择要删除的用户"));
}
await deleteUser(selectedUsers.value);
};
const { execute: addUserExecute, isLoading: addUserIsLoading } = addUserApi();
const newUserDialog = ref({
status: false,
title: t("新增用户"),
permissionList: [
{
lable: t("普通权限"),
value: 1
},
{
lable: t("最高权限"),
value: 10
}
],
data: {
username: "",
password: "",
permission: 1
},
show: () => {
newUserDialog.value.status = true;
},
hidden: () => {
newUserDialog.value.clear();
newUserDialog.value.status = false;
},
clear: () => {
newUserDialog.value.data = {
username: "",
password: "",
permission: 1
};
},
resolve: async () => {
if (newUserDialog.value.data.username == "" || newUserDialog.value.data.password == "")
return message.error(t("请正确填写表单"));
try {
await addUserExecute({
data: newUserDialog.value.data
});
message.success(t("新增用户成功"));
newUserDialog.value.hidden();
} catch (error: any) {
message.error(t("新增用户失败:") + error.message);
}
fetchData();
}
});
onMounted(async () => {
fetchData();
});
</script>
<template>
<div class="new">
<a-modal
v-model:open="newUserDialog.status"
destroy-on-close="true"
:title="newUserDialog.title"
:confirm-loading="addUserIsLoading"
@ok="newUserDialog.resolve()"
>
<div class="mb-20">
<a-typography-title :level="5">{{ t("用户名") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("必填6 到 12 个字符,支持中文,英文和字符") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="newUserDialog.data.username" :placeholder="t('请输入内容')" />
</div>
<div class="mb-20">
<a-typography-title :level="5">{{ t("用户密码") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("必填9 到 36 个字符,必须包含大小写字母和数字") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="newUserDialog.data.password" :placeholder="t('请输入内容')" />
</div>
<div class="mb-20">
<a-typography-title :level="5">{{ t("权限") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("普通权限适用于商业用户,最高权限适用于管理人员") }}
</a-typography-text>
</a-typography-paragraph>
<a-select v-model:value="newUserDialog.data.permission" style="max-width: 320px">
<a-select-option
v-for="item in newUserDialog.permissionList"
:key="item"
:value="item.value"
>
{{ item.lable }}
</a-select-option>
</a-select>
</div>
<div class="mb-20">
<a-typography-title :level="5">{{ t("注意事项") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{
t(
"若您从事出租商业活动,请务必保证应用实例运行在 Linux 的 Docker 虚拟容器中,否则将有安全隐患。"
)
}}
<br />
<a href="https://docs.mcsmanager.com/" target="_blank">具体信息参考</a>
</a-typography-text>
</a-typography-paragraph>
</div>
</a-modal>
</div>
<div style="height: 100%" class="container">
<a-row :gutter="[24, 24]" style="height: 100%">
<a-col :span="24">
@ -129,20 +276,29 @@ const rowSelection = () => {};
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1">新增用户</a-menu-item>
<a-menu-item key="2">删除用户</a-menu-item>
<a-menu-item key="3">封禁用户</a-menu-item>
<a-menu-item key="1" @click="newUserDialog.show()">
{{ t("新增用户") }}
</a-menu-item>
<a-menu-item key="2" @click="handleBatchDelete()">
{{ t("删除用户") }}
</a-menu-item>
<!-- <a-menu-item key="3">{{ t("封禁用户") }}</a-menu-item> -->
</a-menu>
</template>
<a-button type="primary">
用户操作
{{ t("用户操作") }}
<DownOutlined />
</a-button>
</a-dropdown>
</template>
<template #center>
<div class="search-input">
<a-input v-model:value="operationForm.name" :placeholder="t('根据用户名搜索')">
<a-input
v-model:value="operationForm.name"
:placeholder="t('根据用户名搜索')"
@change="reload()"
@press-enter="fetchData()"
>
<template #prefix>
<search-outlined />
</template>
@ -155,7 +311,19 @@ const rowSelection = () => {};
<a-col :span="24">
<CardPanel style="height: 100%">
<template #body>
<a-table :row-selection="rowSelection" :data-source="dataSource" :columns="columns">
<a-table
:row-selection="{
selectedRowKeys: selectedUsers,
onChange: (selectedRowKeys: string[], selectedRows: UserInfo[]) => {
selectedUsers = selectedRowKeys;
}
}"
:data-source="dataSource"
:columns="columns"
:pagination="false"
:preserve-selected-row-keys="true"
:row-key="(record: UserInfo) => record.uuid"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-dropdown>
@ -167,6 +335,9 @@ const rowSelection = () => {};
<a-menu-item key="2" @click="handleDeleteUser(record)">
{{ t("删除用户") }}
</a-menu-item>
<!-- <a-menu-item key="3">
{{ t("封禁用户") }}
</a-menu-item> -->
</a-menu>
</template>
<a-button>
@ -177,6 +348,15 @@ const rowSelection = () => {};
</template>
</template>
</a-table>
<div class="flex justify-end mt-24">
<a-pagination
v-model:current="operationForm.currentPage"
v-model:pageSize="operationForm.pageSize"
:total="total"
show-size-changer
@change="fetchData"
/>
</div>
</template>
</CardPanel>
</a-col>
@ -189,17 +369,13 @@ const rowSelection = () => {};
transition: all 0.4s;
text-align: center;
width: 50%;
}
@media (max-width: 992px) {
.search-input {
transition: all 0.4s;
text-align: center;
&:hover {
width: 100%;
}
@media (max-width: 992px) {
width: 100% !important;
}
}
.search-input:hover {
width: 100%;
}
</style>