Feat: edit user info

This commit is contained in:
Lazy 2023-12-01 11:58:57 +08:00
parent 6f41802c9a
commit 994d540b9e
6 changed files with 177 additions and 94 deletions

View File

@ -1,6 +1,6 @@
import { useDefineApi } from "@/stores/useDefineApi";
import type { InstanceDetail, NodeStatus, Settings } from "@/types";
import type { BaseUserInfo, UserInstance } from "@/types/user";
import type { BaseUserInfo, LoginUserInfo, EditUserInfo, UserInstance } from "@/types/user";
import type { IPanelOverviewResponse } from "../../../../common/global";
export const panelInstall = useDefineApi<
@ -63,7 +63,7 @@ export const logoutUser = useDefineApi<any, any>({
method: "GET"
});
export const userInfoApi = useDefineApi<any, BaseUserInfo>({
export const userInfoApi = useDefineApi<any, LoginUserInfo>({
url: "/api/auth/"
});
@ -155,6 +155,19 @@ export const addUser = useDefineApi<
method: "POST"
});
export const editUserInfo = useDefineApi<
{
data: {
config: EditUserInfo;
uuid: string;
};
},
boolean
>({
url: "/api/auth",
method: "PUT"
});
export const updateUserInstance = useDefineApi<
{
data: {

View File

@ -2,10 +2,10 @@ import { computed, reactive } from "vue";
import { createGlobalState } from "@vueuse/core";
import _ from "lodash";
import { userInfoApi } from "@/services/apis";
import type { BaseUserInfo } from "@/types/user";
import type { LoginUserInfo } from "@/types/user";
interface AppStateInfo {
userInfo: BaseUserInfo | null;
userInfo: LoginUserInfo | null;
language: string;
isInstall: boolean;
versionChanged: boolean;
@ -28,7 +28,7 @@ export const useAppStateStore = createGlobalState(() => {
const isAdmin = computed(() => state.userInfo?.permission === 10);
const updateUserInfo = async (userInfo?: BaseUserInfo) => {
const updateUserInfo = async (userInfo?: LoginUserInfo) => {
if (userInfo) {
state.userInfo = userInfo;
} else {

View File

@ -13,7 +13,14 @@ export interface BaseUserInfo {
registerTime: string;
instances: UserInstance[];
permission: number;
token: string;
apiKey: string;
isInit: boolean;
}
export interface EditUserInfo extends BaseUserInfo {
password?: string;
}
export interface LoginUserInfo extends BaseUserInfo {
token: string;
}

View File

@ -1,17 +1,24 @@
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { t } from "@/lang/i18n";
import { message } from "ant-design-vue";
import { message, type FormInstance } from "ant-design-vue";
import { DownOutlined, UserOutlined, SearchOutlined } from "@ant-design/icons-vue";
import type { Rule } from "ant-design-vue/es/form";
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";
import {
getUserInfo,
deleteUser as deleteUserApi,
addUser as addUserApi,
editUserInfo
} from "@/services/apis";
import type { LayoutCard } from "@/types/index";
import type { BaseUserInfo } from "@/types/user";
import type { BaseUserInfo, EditUserInfo } from "@/types/user";
import _ from "lodash";
defineProps<{
card: LayoutCard;
@ -90,12 +97,6 @@ const data = ref<dataType>();
const dataSource = computed(() => data?.value?.data);
const selectedUsers = ref<string[]>([]);
const handleEditUserConfig = (user: BaseUserInfo) => {
console.log(user);
// TXT_CODE_dbc9f7b2
};
const handleToUserResources = (user: BaseUserInfo) => {
toPage({
path: "/users/resources",
@ -155,116 +156,180 @@ const handleBatchDelete = async () => {
await deleteUser(selectedUsers.value);
};
const { execute: addUserExecute, isLoading: addUserIsLoading } = addUserApi();
const newUserDialog = ref({
const isAddMode = ref(true);
const userDialog = ref({
status: false,
title: t("TXT_CODE_e83ffa03"),
permissionList: [
{
lable: t("TXT_CODE_a778451f"),
value: 1
},
{
lable: t("TXT_CODE_b438b517"),
value: 10
}
],
data: {
username: "",
password: "",
permission: 1
},
confirmBtnLoading: false,
show: () => {
newUserDialog.value.status = true;
},
hidden: () => {
newUserDialog.value.clear();
newUserDialog.value.status = false;
},
clear: () => {
newUserDialog.value.data = {
username: "",
password: "",
permission: 1
};
userDialog.value.status = true;
},
resolve: async () => {
if (newUserDialog.value.data.username == "" || newUserDialog.value.data.password == "")
return message.error(t("TXT_CODE_633415e2"));
try {
await addUserExecute({
data: newUserDialog.value.data
});
message.success(t("TXT_CODE_c855fc29"));
newUserDialog.value.hidden();
await formRef.value?.validateFields();
userDialog.value.confirmBtnLoading = true;
if (isAddMode.value) {
await addUserApi().execute({
data: {
username: formData.value.userName,
password: formData.value.password!,
permission: formData.value.permission
}
});
message.success(t("TXT_CODE_c855fc29"));
} else {
await editUserInfo().execute({
data: {
config: formData.value,
uuid: formData.value.uuid
}
});
message.success(t("编辑成功"));
}
userDialog.value.status = false;
formData.value = _.cloneDeep(formDataOrigin);
} catch (error: any) {
message.error(t("TXT_CODE_5246d704") + error.message);
return message.error(error.message);
} finally {
fetchData();
userDialog.value.confirmBtnLoading = false;
}
fetchData();
}
});
const formDataOrigin: EditUserInfo = {
uuid: "",
userName: "",
password: "",
loginTime: "",
registerTime: "",
instances: [],
permission: 1,
apiKey: "",
isInit: false
};
const formRef = ref<FormInstance>();
const formData = ref<EditUserInfo>(_.cloneDeep(formDataOrigin));
const baseRules: Record<string, Rule[]> = {
userName: [
{ required: true, message: t("请输入用户名") },
{ min: 3, max: 12, message: t("长度只能 3 ~ 12 个字符"), trigger: "blur" }
],
permission: [{ required: true, message: t("TXT_CODE_3bb646e4") }]
};
const addUserRules: Record<string, Rule[]> = {
...baseRules,
password: [
{
required: true,
message: t("请输入密码")
},
{
min: 9,
max: 36,
validator: async (_rule: Rule, value: string) => {
if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[0-9A-Za-z]{12,}$/.test(value))
throw new Error(t("9 到 36 个字符,必须包含大小写字母和数字"));
},
trigger: "blur"
}
]
};
const editUserRules: Record<string, Rule[]> = {
...baseRules,
password: [
{
required: false
},
{
min: 9,
max: 36,
validator: async (_rule: Rule, value: string) => {
if (value && !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[0-9A-Za-z]{12,}$/.test(value))
throw new Error(t("9 到 36 个字符,必须包含大小写字母和数字"));
},
trigger: "blur"
}
]
};
// Add user
const handleAddUser = async () => {
userDialog.value.title = t("TXT_CODE_e83ffa03");
formData.value = _.cloneDeep(formDataOrigin);
isAddMode.value = true;
userDialog.value.show();
};
// Edit user
const handleEditUser = (user: BaseUserInfo) => {
userDialog.value.title = t("编辑用户");
formData.value = _.cloneDeep(user);
isAddMode.value = false;
userDialog.value.show();
};
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()"
<a-modal
v-model:open="userDialog.status"
centered
:destroy-on-close="true"
:title="userDialog.title"
:confirm-loading="userDialog.confirmBtnLoading"
@ok="userDialog.resolve()"
>
<a-form
ref="formRef"
:rules="isAddMode ? addUserRules : editUserRules"
:model="formData"
layout="vertical"
>
<div class="mb-20">
<a-form-item name="userName">
<a-typography-title :level="5">{{ t("TXT_CODE_eb9fcdad") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_1987587b") }}
</a-typography-text>
</a-typography-paragraph>
<a-input
v-model:value="newUserDialog.data.username"
:placeholder="t('TXT_CODE_4ea93630')"
/>
</div>
<a-input v-model:value="formData.userName" :placeholder="t('TXT_CODE_4ea93630')" />
</a-form-item>
<div class="mb-20">
<a-form-item name="password">
<a-typography-title :level="5">{{ t("TXT_CODE_5c605130") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_1f2062c7") }}
{{ !isAddMode ? t("不填写则不更变原有值") : t("TXT_CODE_1f2062c7") }}
</a-typography-text>
</a-typography-paragraph>
<a-input
v-model:value="newUserDialog.data.password"
:placeholder="t('TXT_CODE_4ea93630')"
/>
</div>
<a-input v-model:value="formData.password" :placeholder="t('TXT_CODE_4ea93630')" />
</a-form-item>
<div class="mb-20">
<a-form-item name="permission">
<a-typography-title :level="5">{{ t("TXT_CODE_511aea70") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_21b8b71a") }}
</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 v-model:value="formData.permission">
<a-select-option v-for="(item, key, i) in permissionList" :key="i" :value="Number(key)">
{{ item }}
</a-select-option>
</a-select>
</div>
</a-form-item>
<div class="mb-20">
<a-form-item v-if="!isAddMode">
<a-typography-title :level="5">APIKEY</a-typography-title>
<a-input v-model:value="formData.apiKey" :placeholder="t('TXT_CODE_d7dbc7c2')" />
</a-form-item>
<a-form-item v-if="isAddMode">
<a-typography-title :level="5">{{ t("TXT_CODE_ef0ce2e") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
@ -275,9 +340,9 @@ onMounted(async () => {
</a>
</a-typography-text>
</a-typography-paragraph>
</div>
</a-modal>
</div>
</a-form-item>
</a-form>
</a-modal>
<div style="height: 100%" class="container">
<a-row :gutter="[24, 24]" style="height: 100%">
@ -293,7 +358,7 @@ onMounted(async () => {
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="newUserDialog.show()">
<a-menu-item key="1" @click="handleAddUser">
{{ t("TXT_CODE_e83ffa03") }}
</a-menu-item>
<a-menu-item key="2" @click="handleBatchDelete()">
@ -352,7 +417,7 @@ onMounted(async () => {
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="handleEditUserConfig(record)">
<a-menu-item key="1" @click="handleEditUser(record)">
{{ t("TXT_CODE_236f70aa") }}
</a-menu-item>
<a-menu-item key="2" @click="handleToUserResources(record)">

View File

@ -235,7 +235,7 @@
"TXT_CODE_175b570d": "starting",
"TXT_CODE_181f2f08": "Unable to get remote node information",
"TXT_CODE_190ecd56": "Linux load",
"TXT_CODE_1987587b": "Required, 6 to 12 characters, supports Chinese, English and characters",
"TXT_CODE_1987587b": "Required, 3 to 12 characters, supports Chinese, English and characters",
"TXT_CODE_1d67c9c6": "Limit on the number of logins with the same IP",
"TXT_CODE_1d9d0746": "User ID",
"TXT_CODE_1deaa2dd": "user",
@ -1325,7 +1325,6 @@
"TXT_CODE_da8bb476": "Plug-in update folder, put the folder name of the new version of the plug-in, this folder will automatically update the plug-in when the server restarts",
"TXT_CODE_db04e635": "When there is only one default server, the server will be shown to the player's Motd. \nThis has no effect when ping_passthrough is enabled",
"TXT_CODE_db815f4a": "Text filtering rules configuration file (may not work properly)",
"TXT_CODE_dbc9f7b2": "User permission settings",
"TXT_CODE_dc570cf2": "Leave blank to make no changes",
"TXT_CODE_dce87e42": "Filling in 50 means that the usage of all cores is limited to 50%, and filling in 200 means that the total usage of all cores is allowed to be 200%.",
"TXT_CODE_dd002fae": "Whether to enable whitelist",

View File

@ -447,7 +447,7 @@
"TXT_CODE_b438b517": "最高权限",
"TXT_CODE_c855fc29": "新增用户成功",
"TXT_CODE_5246d704": "新增用户失败:",
"TXT_CODE_1987587b": "必填,6 到 12 个字符,支持任何语言",
"TXT_CODE_1987587b": "必填,3 到 12 个字符,支持任何语言",
"TXT_CODE_5c605130": "用户密码",
"TXT_CODE_1f2062c7": "必填9 到 36 个字符,必须包含大小写字母和数字",
"TXT_CODE_21b8b71a": "普通权限适用于商业用户,最高权限适用于管理人员",
@ -1475,7 +1475,6 @@
"TXT_CODE_ecf17071": "暂时不可使用的实例数",
"TXT_CODE_1c45f7fe": "编辑服务端配置文件",
"TXT_CODE_97d17cce": "用户列表",
"TXT_CODE_dbc9f7b2": "用户权限设定",
"TXT_CODE_3fe97dcc": "系统设置",
"TXT_CODE_7411336e": "实例总计",
"TXT_CODE_d655beec": "实例列表",