feat: 完成用户列表

This commit is contained in:
xlsea
2024-09-12 16:16:38 +08:00
parent 0a62e4dcaa
commit 85f6c31878
19 changed files with 618 additions and 37 deletions

View File

@ -1,6 +1,8 @@
<script setup lang="tsx">
import { NButton, NPopconfirm } from 'naive-ui';
import { fetchBatchDeleteUser, fetchGetUserList } from '@/service/api/system';
import { ref } from 'vue';
import { useLoading } from '@sa/hooks';
import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList } from '@/service/api/system';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
@ -143,10 +145,66 @@ async function handleDelete(userId: CommonType.IdType) {
async function edit(userId: CommonType.IdType) {
handleEdit('userId', userId);
}
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
const deptPattern = ref<string>();
const deptData = ref<Api.Common.CommonTreeRecord>([]);
async function getTreeData() {
startTreeLoading();
const { data: tree, error } = await fetchGetDeptTree();
if (!error) {
deptData.value = tree;
}
endTreeLoading();
}
getTreeData();
function handleClickTree(keys: string[]) {
searchParams.deptId = keys.length ? keys[0] : null;
checkedRowKeys.value = [];
getDataByPage();
}
function handleResetTreeData() {
deptPattern.value = undefined;
getTreeData();
}
</script>
<template>
<TableSiderLayout sider-title="部门列表">
<template #header-extra>
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
<template #icon>
<SvgIcon icon="ic:round-refresh" />
</template>
</NButton>
</template>
<template #sider>
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
<NSpin class="dept-tree" :show="treeLoading">
<NTree
block-node
show-line
:data="deptData as []"
:default-expanded-keys="deptData?.length ? [deptData[0].id!] : []"
:show-irrelevant-nodes="false"
:pattern="deptPattern"
block-line
class="infinite-scroll h-full min-h-200px py-3"
key-field="id"
label-field="label"
virtual-scroll
@update:selected-keys="handleClickTree"
>
<template #empty>
<NEmpty description="暂无部门信息" class="h-full min-h-200px justify-center" />
</template>
</NTree>
</NSpin>
</template>
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
<UserSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
@ -178,6 +236,7 @@ async function edit(userId: CommonType.IdType) {
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
:dept-data="deptData"
@submitted="getDataByPage"
/>
</NCard>
@ -185,4 +244,67 @@ async function edit(userId: CommonType.IdType) {
</TableSiderLayout>
</template>
<style scoped></style>
<style scoped lang="scss">
.dept-tree {
.n-button {
--n-padding: 8px !important;
}
:deep(.n-tree__empty) {
height: 100%;
justify-content: center;
}
:deep(.n-spin-content) {
height: 100%;
}
:deep(.infinite-scroll) {
height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
}
@media screen and (max-width: 1024px) {
:deep(.infinite-scroll) {
height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
}
}
:deep(.n-tree-node) {
height: 33px;
}
:deep(.n-tree-node-switcher) {
height: 33px;
}
:deep(.n-tree-node-switcher__icon) {
font-size: 16px !important;
height: 16px !important;
width: 16px !important;
}
}
:deep(.n-data-table-wrapper),
:deep(.n-data-table-base-table),
:deep(.n-data-table-base-table-body) {
height: 100%;
}
@media screen and (max-width: 800px) {
:deep(.n-data-table-base-table-body) {
max-height: calc(100vh - 400px - var(--calc-footer-height, 0px));
}
}
@media screen and (max-width: 802px) {
:deep(.n-data-table-base-table-body) {
max-height: calc(100vh - 473px - var(--calc-footer-height, 0px));
}
}
:deep(.n-card-header__main) {
min-width: 69px !important;
}
</style>

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { useLoading } from '@sa/hooks';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { fetchCreateUser, fetchUpdateUser } from '@/service/api/system';
import { fetchCreateUser, fetchGetUserInfo, fetchUpdateUser } from '@/service/api/system';
defineOptions({
name: 'UserOperateDrawer'
@ -13,6 +14,8 @@ interface Props {
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.System.User | null;
/** the dept tree data */
deptData?: Api.Common.CommonTreeRecord;
}
const props = defineProps<Props>();
@ -27,8 +30,9 @@ const visible = defineModel<boolean>('visible', {
default: false
});
const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules();
const { createRequiredRule, patternRules } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
@ -51,20 +55,31 @@ function createDefaultModel(): Model {
phonenumber: '',
sex: '',
password: '',
status: '',
status: '0',
roleIds: [],
postIds: [],
remark: ''
};
}
type RuleKey = Extract<keyof Model, 'userName' | 'nickName' | 'password' | 'status'>;
type RuleKey = Extract<keyof Model, 'userName' | 'nickName' | 'password' | 'status' | 'phonenumber'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
userName: createRequiredRule('用户名称不能为空'),
nickName: createRequiredRule('用户昵称不能为空'),
password: createRequiredRule('密码不能为空'),
status: createRequiredRule('帐号状态不能为空')
const rules: Record<RuleKey, App.Global.FormRule[]> = {
userName: [createRequiredRule('用户名称不能为空')],
nickName: [createRequiredRule('用户昵称不能为空')],
password: [createRequiredRule('密码不能为空'), patternRules.pwd],
phonenumber: [patternRules.phone],
status: [createRequiredRule('帐号状态不能为空')]
};
async function getUserInfo() {
const { error, data } = await fetchGetUserInfo(props.rowData?.userId);
if (!error) {
model.roleIds = data.roleIds;
model.postIds = data.postIds;
}
}
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
@ -72,7 +87,10 @@ function handleUpdateModelWhenEdit() {
}
if (props.operateType === 'edit' && props.rowData) {
startDeptLoading();
Object.assign(model, props.rowData);
getUserInfo();
endDeptLoading();
}
}
@ -134,26 +152,46 @@ watch(visible, () => {
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="部门" path="deptId">
<!-- <NInput v-model:value="model.deptId" placeholder="请输入部门" /> -->
</NFormItem>
<NFormItem label="用户名称" path="userName">
<NInput v-model:value="model.userName" placeholder="请输入用户名称" />
</NFormItem>
<NFormItem label="用户昵称" path="nickName">
<NInput v-model:value="model.nickName" placeholder="请输入用户昵称" />
</NFormItem>
<NFormItem label="用户邮箱" path="email">
<NInput v-model:value="model.email" placeholder="请输入用户邮箱" />
<NFormItem label="归属部门" path="deptId">
<NTreeSelect
v-model:value="model.deptId"
:loading="deptLoading"
clearable
:options="deptData as []"
label-field="label"
key-field="id"
:default-expanded-keys="deptData?.length ? [deptData[0].id] : []"
placeholder="请选择归属部门"
/>
</NFormItem>
<NFormItem label="手机号码" path="phonenumber">
<NInput v-model:value="model.phonenumber" placeholder="请输入手机号码" />
</NFormItem>
<NFormItem label="用户性别" path="sex">
<DictRadio v-model:value="model.sex" dict-code="sys_user_sex" />
<NFormItem label="邮箱" path="email">
<NInput v-model:value="model.email" placeholder="请输入邮箱" />
</NFormItem>
<NFormItem label="密码" path="password">
<NInput v-model:value="model.password" placeholder="请输入密码" />
<NFormItem v-if="operateType === 'add'" label="用户名称" path="userName">
<NInput v-model:value="model.userName" placeholder="请输入用户名称" />
</NFormItem>
<NFormItem v-if="operateType === 'add'" label="用户密码" path="password">
<NInput
v-model:value="model.password"
type="password"
show-password-on="click"
placeholder="请输入用户密码"
/>
</NFormItem>
<NFormItem label="用户性别" path="sex">
<DictRadio v-model:value="model.sex" dict-code="sys_user_sex" placeholder="请选择用户性别" />
</NFormItem>
<NFormItem label="岗位" path="postIds">
<PostSelect v-model:value="model.postIds" :dept-id="model.deptId" multiple clearable />
</NFormItem>
<NFormItem label="角色" path="roleIds">
<RoleSelect v-model:value="model.roleIds" multiple clearable />
</NFormItem>
<NFormItem label="状态" path="status">
<DictRadio v-model:value="model.status" dict-code="sys_normal_disable" />