refactor(projects): refactor page: user-management [重构用户管理页面]

This commit is contained in:
Soybean
2022-09-29 00:24:59 +08:00
parent 88e535f63c
commit 468b4bb0e1
26 changed files with 340 additions and 351 deletions

View File

@ -0,0 +1,69 @@
<template>
<n-popover placement="bottom" trigger="click">
<template #trigger>
<n-button size="small" type="primary">
<icon-ant-design-setting-outlined class="mr-4px text-16px" />
表格列设置
</n-button>
</template>
<div class="w-180px">
<vue-draggable v-model="list" item-key="key">
<template #item="{ element }">
<div v-if="element.key" class="flex-y-center h-36px px-12px hover:bg-primary_active">
<icon-mdi-drag class="mr-8px text-20px cursor-move" />
<n-checkbox v-model:checked="element.checked">
{{ element.title }}
</n-checkbox>
</div>
</template>
</vue-draggable>
</div>
</n-popover>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import type { DataTableColumn } from 'naive-ui';
import VueDraggable from 'vuedraggable';
type Column = DataTableColumn<UserManagement.User>;
interface Props {
columns: Column[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'update:columns', columns: Column[]): void;
}
const emit = defineEmits<Emits>();
type List = Column & { checked?: boolean };
const list = ref(initList());
function initList(): List[] {
return props.columns.map(item => ({ ...item, checked: true }));
}
watch(
list,
newValue => {
const newColumns = newValue.filter(item => item.checked);
const columns: Column[] = newColumns.map(item => {
const column = { ...item };
delete column.checked;
return column;
}) as Column[];
emit('update:columns', columns);
},
{ deep: true }
);
</script>
<style scoped></style>

View File

@ -1,49 +1,59 @@
<template>
<n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
<n-form label-placement="left" :label-width="80" :model="formModel">
<n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item :span="12" label="用户名" path="userName">
<n-input v-model:value="formModel.userName" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="年龄" path="userAge">
<n-input v-model:value="formModel.userAge" />
<n-form-item-grid-item :span="12" label="年龄" path="age">
<n-input-number v-model:value="formModel.age" clearable />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="性别" path="userGender">
<n-input v-model:value="formModel.userGender" />
<n-form-item-grid-item :span="12" label="性别" path="gender">
<n-radio-group v-model:value="formModel.gender">
<n-radio v-for="item in genderOptions" :key="item.value" :value="item.value">{{ item.label }}</n-radio>
</n-radio-group>
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="手机号" path="userPhone">
<n-input v-model:value="formModel.userPhone" />
<n-form-item-grid-item :span="12" label="手机号" path="phone">
<n-input v-model:value="formModel.phone" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="邮箱" path="userEmail">
<n-input v-model:value="formModel.userEmail" />
<n-form-item-grid-item :span="12" label="邮箱" path="email">
<n-input v-model:value="formModel.email" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="角色" path="userRole">
<n-input v-model:value="formModel.userRole" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="12" label="状态" path="disabled">
<n-switch v-model:value="formModel.disabled" />
<n-form-item-grid-item :span="12" label="状态" path="userStatus">
<n-select v-model:value="formModel.userStatus" :options="userStatusOptions" />
</n-form-item-grid-item>
</n-grid>
<n-space class="w-full pt-16px" :size="24" justify="end">
<n-button class="w-72px" @click="closeModal">取消</n-button>
<n-button class="w-72px" type="primary" @click="handleSubmit">确定</n-button>
</n-space>
</n-form>
</n-modal>
</template>
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { ref, computed, reactive, watch } from 'vue';
import type { FormInst, FormItemRule } from 'naive-ui';
import { formRules, createRequiredFormRule } from '@/utils';
import { genderOptions, userStatusOptions } from '@/constants';
defineOptions({ name: 'TableActionModal' });
type ModalType = 'add' | 'edit';
interface Props {
export interface Props {
/** 弹窗可见性 */
visible: boolean;
/** 弹窗类型 */
type?: ModalType;
/**
* 弹窗类型
* add: 新增
* edit: 编辑
*/
type?: 'add' | 'edit';
/** 编辑的表格行数据 */
editData?: UserManagement.UserTable | null;
editData?: UserManagement.User | null;
}
export type ModalType = NonNullable<Props['type']>;
defineOptions({ name: 'TableActionModal' });
const props = withDefaults(defineProps<Props>(), {
type: 'add',
editData: null
@ -63,6 +73,9 @@ const modalVisible = computed({
emit('update:visible', visible);
}
});
const closeModal = () => {
modalVisible.value = false;
};
const title = computed(() => {
const titles: Record<ModalType, string> = {
@ -72,22 +85,29 @@ const title = computed(() => {
return titles[props.type];
});
type FormModel = Pick<
UserManagement.UserTable,
'userName' | 'userAge' | 'userGender' | 'userPhone' | 'userEmail' | 'userRole' | 'disabled'
>;
const formRef = ref<HTMLElement & FormInst>();
type FormModel = Pick<UserManagement.User, 'userName' | 'age' | 'gender' | 'phone' | 'email' | 'userStatus'>;
const formModel = reactive<FormModel>(createDefaultFormModel());
const rules: Record<keyof FormModel, FormItemRule | FormItemRule[]> = {
userName: createRequiredFormRule('请输入用户名'),
age: createRequiredFormRule('请输入年龄'),
gender: createRequiredFormRule('请选择性别'),
phone: formRules.phone,
email: formRules.email,
userStatus: createRequiredFormRule('请选择用户状态')
};
function createDefaultFormModel(): FormModel {
return {
userName: '',
userAge: '',
userGender: 'null',
userPhone: '',
userEmail: '',
userRole: 'user',
disabled: true
age: null,
gender: null,
phone: '',
email: null,
userStatus: null
};
}
@ -111,6 +131,12 @@ function handleUpdateFormModelByModalType() {
handlers[props.type]();
}
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('新增成功!');
closeModal();
}
watch(
() => props.visible,
newValue => {

View File

@ -1,3 +0,0 @@
import TableActionModal from './TableActionModal.vue';
export { TableActionModal };

View File

@ -15,9 +15,12 @@
导出Excel
</n-button>
</n-space>
<n-space>
<n-switch />
<icon-mdi-refresh class="text-20px" />
<n-space align="center" :size="18">
<n-button size="small" type="primary" @click="getTableData">
<icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
刷新表格
</n-button>
<column-setting v-model:columns="columns" />
</n-space>
</n-space>
<n-data-table :columns="columns" :data="tableData" :loading="loading" :pagination="pagination" />
@ -27,23 +30,27 @@
<script setup lang="tsx">
import { reactive, ref } from 'vue';
import { NButton, NPopconfirm, NSpace, NSwitch, NTag } from 'naive-ui';
import type { Ref } from 'vue';
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui';
import type { DataTableColumns, PaginationProps } from 'naive-ui';
import { fetchUserManagementList } from '@/service';
import { fetchUserList } from '@/service';
import { useBoolean, useLoading } from '@/hooks';
import { TableActionModal } from './components';
import { genderLabels, userStatusLabels } from '@/constants';
import TableActionModal from './components/TableActionModal.vue';
import type { ModalType } from './components/TableActionModal.vue';
import ColumnSetting from './components/ColumnSetting.vue';
const { loading, startLoading, endLoading } = useLoading(false);
const { bool: visible, setTrue: openModal } = useBoolean();
const tableData = ref<UserManagement.UserTable[]>([]);
function setTableData(data: UserManagement.UserTable[]) {
const tableData = ref<UserManagement.User[]>([]);
function setTableData(data: UserManagement.User[]) {
tableData.value = data;
}
async function getTableData() {
startLoading();
const { data } = await fetchUserManagementList();
const { data } = await fetchUserList();
if (data) {
setTimeout(() => {
setTableData(data);
@ -52,7 +59,7 @@ async function getTableData() {
}
}
const columns: DataTableColumns = [
const columns: Ref<DataTableColumns<UserManagement.User>> = ref([
{
type: 'selection',
align: 'center'
@ -68,66 +75,53 @@ const columns: DataTableColumns = [
align: 'center'
},
{
key: 'userAge',
key: 'age',
title: '用户年龄',
align: 'center'
},
{
key: 'userGenderLabel',
key: 'gender',
title: '性别',
align: 'center',
render: row => {
const rowData = row as unknown as UserManagement.UserTable;
if (row.gender) {
const tagTypes: Record<UserManagement.GenderKey, NaiveUI.ThemeColor> = {
'0': 'success',
'1': 'warning'
};
if (rowData.userGender !== 'null') {
const tagType = {
male: 'success',
female: 'warning'
} as const;
return <NTag type={tagType[rowData.userGender]}>{rowData.userGenderLabel}</NTag>;
return <NTag type={tagTypes[row.gender]}>{genderLabels[row.gender]}</NTag>;
}
return <span></span>;
}
},
{
key: 'userPhone',
key: 'phone',
title: '手机号码',
align: 'center'
},
{
key: 'userEmail',
key: 'email',
title: '邮箱',
align: 'center'
},
{
key: 'userRole',
title: '角色',
align: 'center',
render: row => {
const rowData = row as unknown as UserManagement.UserTable;
const tagType = {
super: 'primary',
admin: 'warning',
user: 'success'
} as const;
return <NTag type={tagType[rowData.userRole]}>{rowData.userRole}</NTag>;
}
},
{
key: 'disabled',
key: 'userStatus',
title: '状态',
align: 'center',
render: row => {
const rowData = row as unknown as UserManagement.UserTable;
if (row.userStatus) {
const tagTypes: Record<UserManagement.UserStatusKey, NaiveUI.ThemeColor> = {
'1': 'success',
'2': 'error',
'3': 'warning',
'4': 'default'
};
return (
<NSwitch value={rowData.disabled} onUpdateValue={disabled => handleUpdateDisabled(disabled, rowData.id)}>
{{ checked: () => '启用', unchecked: () => '禁用' }}
</NSwitch>
);
return <NTag type={tagTypes[row.userStatus]}>{userStatusLabels[row.userStatus]}</NTag>;
}
return <span></span>;
}
},
{
@ -135,13 +129,12 @@ const columns: DataTableColumns = [
title: '操作',
align: 'center',
render: row => {
const rowData = row as unknown as UserManagement.UserTable;
return (
<NSpace justify={'center'}>
<NButton size={'small'} onClick={() => handleEditTable(rowData.id)}>
<NButton size={'small'} onClick={() => handleEditTable(row.id)}>
编辑
</NButton>
<NPopconfirm onPositiveClick={() => handleDeleteTable(rowData.id)}>
<NPopconfirm onPositiveClick={() => handleDeleteTable(row.id)}>
{{
default: () => '确认删除',
trigger: () => <NButton size={'small'}>删除</NButton>
@ -151,24 +144,17 @@ const columns: DataTableColumns = [
);
}
}
];
function handleUpdateDisabled(disabled: boolean, rowId: string) {
const index = tableData.value.findIndex(item => item.id === rowId);
if (index > -1) {
tableData.value[index].disabled = disabled;
}
}
type ModalType = 'add' | 'edit';
]) as Ref<DataTableColumns<UserManagement.User>>;
const modalType = ref<ModalType>('add');
function setModalType(type: ModalType) {
modalType.value = type;
}
const editData = ref<UserManagement.UserTable | null>(null);
function setEditData(data: UserManagement.UserTable | null) {
const editData = ref<UserManagement.User | null>(null);
function setEditData(data: UserManagement.User | null) {
editData.value = data;
}
@ -185,6 +171,7 @@ function handleEditTable(rowId: string) {
setModalType('edit');
openModal();
}
function handleDeleteTable(rowId: string) {
window.$message?.info(`点击了删除rowId为${rowId}`);
}

View File

@ -29,12 +29,14 @@ import { formRules } from '@/utils';
const { toLoginModule } = useRouterPush();
const { label, isCounting, loading: smsLoading, getSmsCode } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const formRef = ref<HTMLElement & FormInst>();
const model = reactive({
phone: '',
code: '',
imgCode: ''
});
const rules = {
phone: formRules.phone,
code: formRules.code
@ -44,17 +46,9 @@ function handleSmsCode() {
getSmsCode(model.phone);
}
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
formRef.value.validate(errors => {
if (!errors) {
window.$message?.success('验证成功!');
} else {
window.$message?.error('验证失败!');
}
});
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('验证成功!');
}
</script>

View File

@ -46,13 +46,16 @@ const auth = useAuthStore();
const { toLoginModule } = useRouterPush();
const { label, isCounting, loading: smsLoading, getSmsCode } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const formRef = ref<HTMLElement & FormInst>();
const model = reactive({
phone: '',
code: '',
imgCode: ''
});
const imgCode = ref('');
const rules = {
phone: formRules.phone,
code: formRules.code,
@ -63,17 +66,9 @@ function handleSmsCode() {
getSmsCode(model.phone);
}
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
formRef.value.validate(errors => {
if (!errors) {
window.$message?.success('验证通过!');
} else {
window.$message?.error('验证失败');
}
});
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('验证成功!');
}
</script>

View File

@ -48,26 +48,25 @@ const auth = useAuthStore();
const { login } = useAuthStore();
const { toLoginModule } = useRouterPush();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const formRef = ref<HTMLElement & FormInst>();
const model = reactive({
userName: 'Soybean',
password: 'soybean123'
});
const rules: FormRules = {
password: formRules.pwd
};
const rememberMe = ref(false);
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
async function handleSubmit() {
await formRef.value?.validate();
formRef.value.validate(errors => {
if (!errors) {
const { userName, password } = model;
login(userName, password);
}
});
const { userName, password } = model;
login(userName, password);
}
function handleLoginOtherAccount(param: { userName: string; password: string }) {

View File

@ -36,13 +36,15 @@ import { formRules, getConfirmPwdRule } from '@/utils';
const { toLoginModule } = useRouterPush();
const { label, isCounting, loading: smsLoading, start } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const formRef = ref<HTMLElement & FormInst>();
const model = reactive({
phone: '',
code: '',
pwd: '',
confirmPwd: ''
});
const rules: FormRules = {
phone: formRules.phone,
code: formRules.code,
@ -56,18 +58,9 @@ function handleSmsCode() {
start();
}
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
formRef.value.validate(errors => {
if (!errors) {
if (!agreement.value) return;
window.$message?.success('验证成功!');
} else {
window.$message?.error('验证失败!');
}
});
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('验证成功!');
}
</script>

View File

@ -35,13 +35,15 @@ import { formRules, getConfirmPwdRule } from '@/utils';
const { toLoginModule } = useRouterPush();
const { label, isCounting, loading: smsLoading, start } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const formRef = ref<HTMLElement & FormInst>();
const model = reactive({
phone: '',
code: '',
pwd: '',
confirmPwd: ''
});
const rules: FormRules = {
phone: formRules.phone,
code: formRules.code,
@ -53,17 +55,9 @@ function handleSmsCode() {
start();
}
function handleSubmit(e: MouseEvent) {
if (!formRef.value) return;
e.preventDefault();
formRef.value.validate(errors => {
if (!errors) {
window.$message?.success('验证成功');
} else {
window.$message?.error('验证失败');
}
});
async function handleSubmit() {
await formRef.value?.validate();
window.$message?.success('验证成功!');
}
</script>