feat: 设置模块用户列表功能90%

This commit is contained in:
Zzz1zz
2025-07-06 00:12:32 +08:00
parent 1cfa0b6e4c
commit 293beb9f5d
17 changed files with 1758 additions and 147 deletions
+84 -46
View File
@@ -4,43 +4,46 @@
<a-card class="general-card">
<template #title>
<div class="card-title_between">
<p class="title">{{ $t('menu.business.project') }}</p>
<p class="title">{{ t('menu.business.project') }}</p>
<div class="flex_center_between">
<a-space>
<a-input-search
:style="{ width: '320px' }"
:placeholder="$t('search.placeholder')"
:placeholder="t('please.enter', { placeholder: t('content') })"
/>
<a-button>
<template #icon>
<icon-filter />
</template>
{{ $t('table.filter') }}
{{ t('table.filter') }}
</a-button>
<a-button>
<template #icon>
<icon-download />
</template>
{{ $t('table.export') }}
{{ t('table.export') }}
</a-button>
<a-button type="primary">
<template #icon>
<icon-plus />
</template>
{{ $t('add-project') }}
{{ t('add-project') }}
</a-button>
</a-space>
</div>
</div>
</template>
<a-table
row-key="id"
:loading="loading"
:pagination="pagination"
:columns="projectColumn"
<!-- 使用新的 DataTable 组件 -->
<DataTable
:data="renderData"
:bordered="false"
:columns="projectColumn"
:loading="loading"
:total="total"
:current="current"
:page-size="pageSize"
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #type="{ record }">
<a-tag checkable :color="getTypeColor(record.type)">{{
@@ -61,15 +64,16 @@
<a-button>更多</a-button>
</a-space>
</template>
</a-table>
</DataTable>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, reactive, watch, nextTick } from 'vue';
import { onMounted } from 'vue';
import useLoading from '@/hooks/loading';
import { Pagination } from '@/types/global';
import { useI18n } from 'vue-i18n';
import type { TableColumnData } from '@arco-design/web-vue';
import {
ProjectRecord,
ProjectType,
@@ -77,11 +81,35 @@
PROJECT_TYPE_COLORS,
PROJECT_TYPE_LABELS,
} from '@/types/business';
import { ProjectParams, getAllProjectList } from '@/api/business';
// import { getAllProjectList } from '@/api/business';
import useDataTable from '@/composables/useDataTable';
const { loading, setLoading } = useLoading(false);
const renderData = ref<ProjectRecord[]>([
const { t } = useI18n();
const { loading } = useLoading(false);
// setLoading
// 使用 DataTable composable
const {
data: renderData,
columns: projectColumn,
total,
current,
pageSize,
setData,
setColumns,
setTotal,
setCurrent,
setPageSize,
} = useDataTable({
initialPage: 1,
initialPageSize: 10,
pageSizeOptions: [10, 20, 50, 100],
});
// 模拟数据
const mockData: ProjectRecord[] = [
{
id: '1',
name: '星河商务中心',
type: PROJECT_TYPE.COMMERCIAL_COMPLEX,
size: '1200m²',
@@ -91,6 +119,7 @@
create_time: '2025-06-24 00:51',
},
{
id: '2',
name: '南山科学科学院',
type: PROJECT_TYPE.OFFICE_BUILDING,
size: '120m²',
@@ -100,6 +129,7 @@
create_time: '2025-06-24 00:58',
},
{
id: '3',
name: '前海科学科学院',
type: PROJECT_TYPE.INDUSTRIAL_PARK,
size: '1520m²',
@@ -108,24 +138,25 @@
count: '120',
create_time: '2025-06-24 01:16',
},
]);
const basePagination: Pagination = {
current: 1,
pageSize: 20,
total: 3,
};
const projectColumn = [
{ title: '项目信息', dataIndex: 'name' },
{ title: '项目类型', dataIndex: 'type', slotName: 'type' },
{ title: '管理面积', dataIndex: 'size' },
{ title: '可租面积', dataIndex: 'kezu' },
{ title: '出租率', dataIndex: 'rate', slotName: 'rate' },
{ title: '房源数量', dataIndex: 'count' },
{ title: '创建时间', dataIndex: 'create_time' },
{ title: '操作', dataIndex: 'operate', slotName: 'operate' },
];
const pagination = reactive({
...basePagination,
// 列配置
const columns: TableColumnData[] = [
{ title: '项目信息', dataIndex: 'name', key: 'name' },
{ title: '项目类型', dataIndex: 'type', key: 'type', slotName: 'type' },
{ title: '管理面积', dataIndex: 'size', key: 'size' },
{ title: '可租面积', dataIndex: 'kezu', key: 'kezu' },
{ title: '出租率', dataIndex: 'rate', key: 'rate', slotName: 'rate' },
{ title: '房源数量', dataIndex: 'count', key: 'count' },
{ title: '创建时间', dataIndex: 'create_time', key: 'create_time' },
{ title: '操作', key: 'operate', slotName: 'operate', width: 200 },
];
// 初始化数据
onMounted(() => {
setColumns(columns);
setData(mockData);
setTotal(mockData.length);
});
const getTypeColor = (type: ProjectType): string => {
@@ -135,21 +166,28 @@
const getTypeText = (type: ProjectType): string => {
return PROJECT_TYPE_LABELS[type] || '未知';
};
const onPageChange = (current: number) => {
pagination.current = current;
// 分页事件处理
const onPageChange = (page: number) => {
setCurrent(page);
// TODO: 这里应该调用API获取对应页码的数据
};
const getAllProjectListData = async () => {
setLoading(true);
try {
const { data } = await getAllProjectList();
console.log(data, 'getAllProjectList');
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
const onPageSizeChange = (newPageSize: number) => {
setPageSize(newPageSize);
// TODO: 这里应该调用API获取对应页码的数据
};
// const getAllProjectListData = async () => {
// setLoading(true);
// try {
// await getAllProjectList();
// // 处理数据
// } catch (err) {
// // you can report use errorHandler or other
// } finally {
// setLoading(false);
// }
// };
// getAllProjectListData()
</script>
@@ -1,78 +0,0 @@
import { ref, onMounted } from 'vue';
import {
getDepartmentList,
DepartmentItem,
getDepartmentDetail,
} from '@/api/setting';
export default function useDepartmentList(autoFetch = true) {
const departmentListData = ref<DepartmentItem[]>([]);
const allDepartmentKeys = ref<number[]>([]);
function markNonLevel1Departments(list: DepartmentItem[]): DepartmentItem[] {
const traverse = (items: DepartmentItem[]) => {
return items.map((item) => {
const newItem: DepartmentItem = {
...item,
isShowEditIcon: false,
children: [],
};
// 收集 ID
allDepartmentKeys.value.push(item.id);
if (item.children && item.children.length > 0) {
newItem.children = traverse(item.children);
}
return newItem;
});
};
return traverse(list);
}
const getDepartmentListData = async () => {
try {
const { code, data } = await getDepartmentList();
if (code === 200) {
if (data?.departmentList?.length === 0) return;
allDepartmentKeys.value = []; // 清空再收集
const fetchDepartmentList = markNonLevel1Departments(
data?.departmentList
);
departmentListData.value = fetchDepartmentList;
}
} catch (error) {
console.error('获取部门列表失败:', error);
}
};
const getDepartmentDetailData = async (
id: number
): Promise<DepartmentItem | null> => {
try {
const { code, data } = await getDepartmentDetail(id);
if (code === 200 && data?.department) {
return data.department;
}
return null;
} catch (error) {
console.error('获取部门详情失败:', error);
return null;
}
};
if (autoFetch) {
onMounted(() => {
getDepartmentListData();
});
}
return {
departmentListData,
allDepartmentKeys,
getDepartmentListData,
getDepartmentDetailData,
};
}
@@ -1,7 +1,7 @@
<template>
<a-modal
:visible="visible"
:title="isEdit ? '编辑部门' : '新增部门'"
:title="isEdit ? $t('edit-department') : $t('add-department')"
unmount-on-close
@ok="handleSubmit"
@cancel="handleCancel"
@@ -14,36 +14,48 @@
layout="vertical"
>
<!-- 部门名称 -->
<a-form-item :label="t('department-name')" field="name" required>
<a-input v-model="form.name" placeholder="请输入部门名称" />
<a-form-item :label="$t('department-name')" field="name" required>
<a-input
v-model="form.name"
:placeholder="
$t('please.enter', { placeholder: $t('department-name') })
"
/>
</a-form-item>
<!-- 所属部门 -->
<a-form-item :label="t('belongto-department')" field="parent_id" required>
<a-form-item
:label="$t('belongto-department')"
field="parent_id"
required
>
<a-tree-select
v-model="form.parent_id"
:data="departmentListData"
:field-names="{ key: 'id', title: 'name', children: 'children' }"
placeholder="请选择所属部门"
:placeholder="
$t('please.select', { placeholder: $t('belongto-department') })
"
allow-clear
/>
</a-form-item>
<!-- 部门主管 -->
<a-form-item :label="t('department-head')" field="user_id">
<a-form-item :label="$t('department-head')" field="user_id">
<a-select
v-model="form.user_id"
:options="props.userListData"
placeholder="请选择部门主管"
:placeholder="
$t('please.select', { placeholder: $t('department-head') })
"
allow-clear
/>
</a-form-item>
<!-- 备注 -->
<a-form-item :label="t('remark')" field="remark">
<a-form-item :label="$t('remark')" field="remark">
<a-textarea
v-model="form.remark"
placeholder="请输入备注"
:placeholder="$t('please.enter', { placeholder: $t('remark') })"
allow-clear
/>
</a-form-item>
@@ -75,17 +87,26 @@
user_id?: number;
remark?: string;
}
interface TreeNode {
id: string | number;
name: string;
children?: TreeNode[];
}
const props = defineProps<{
visible: boolean;
isEdit: boolean;
record?: DepartmentItem | null;
departmentListData: any[]; // 新增
departmentListData: TreeNode[]; // 修复类型
userListData: UserList;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'submitted'): void;
}>();
// 表单数据
const formRef = ref<FormInstance>();
const form = reactive<DepartmentForm>({
@@ -99,7 +120,12 @@
const { t } = useI18n();
// 校验规则
const rules = {
name: [{ required: true, message: '请输入部门名称' }],
name: [
{
required: true,
message: t('please.enter', { placeholder: t('department') }),
},
],
parent_id: [{ required: true, message: '请选择所属部门' }],
};
+21 -2
View File
@@ -33,8 +33,9 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import {
IconSettings,
IconSafe,
@@ -45,7 +46,23 @@
import organization from './organization.vue';
// import { CreateDepartmentParams, createDepartment, getDepartmentList, DepartmentItem } from '@/api/setting'
const { t } = useI18n();
const currentActive = ref(4);
const route = useRoute();
const currentActive = ref(1);
// 监听路由query参数中的active参数
watch(
() => route.query.active,
(newActive) => {
if (newActive) {
const activeValue = parseInt(newActive as string, 10);
if (!Number.isNaN(activeValue)) {
currentActive.value = activeValue;
}
}
},
{ immediate: true }
);
const systemSettingList = [
{ id: 1, name: t('general-setting'), icon: IconSettings },
{ id: 2, name: t('security-setting'), icon: IconSafe },
@@ -56,6 +73,8 @@
const handleChooseSystemItem = (id: number) => {
currentActive.value = id;
};
// watch()
</script>
<style lang="less" scoped>
+50 -32
View File
@@ -93,9 +93,9 @@
</template>
<script lang="ts" setup>
import { reactive, ref, watch, onMounted } from 'vue';
import { ref, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
import useDepartmentList from '@/composables/useDepartmentList';
import {
DepartmentItem,
getAllUserList,
@@ -114,9 +114,10 @@
getDepartmentListData,
getDepartmentDetailData,
} = useDepartmentList();
const emit = defineEmits(['onSelectTree']);
const expandedKeys = ref<(string | number)[]>([]);
const { t } = useI18n();
watch(
allDepartmentKeys,
(val) => {
@@ -129,7 +130,14 @@
const isEdit = ref<boolean>(false);
const departmentDetailInfo = ref<DepartmentItem | null>(null);
const resetEditIcon = (nodes: any[]) => {
interface TreeNode {
id: string | number;
isShowEditIcon?: boolean;
children?: TreeNode[];
level?: number;
}
const resetEditIcon = (nodes: TreeNode[]) => {
nodes.forEach((node) => {
node.isShowEditIcon = false;
if (node.children && node.children.length > 0) {
@@ -137,21 +145,26 @@
}
});
};
const currentNode = ref<any>(null);
const onSelect = debounce(async (_: (string | number)[], event: any) => {
const { node } = event;
if (node.id === currentNode.value?.id) return;
currentNode.value = node;
// 递归重置所有节点的 isShowEditIcon
resetEditIcon(departmentListData.value);
// 只设置当前节点
if (currentNode.value.level !== 1) {
node.isShowEditIcon = true;
}
departmentDetailInfo.value = await getDepartmentDetailData(
currentNode.value.id
);
}, 300);
const currentNode = ref<TreeNode | null>(null);
const onSelect = debounce(
async (_: (string | number)[], event: { node: TreeNode }) => {
const { node } = event;
if (node.id === currentNode.value?.id) return;
currentNode.value = node;
// 递归重置所有节点的 isShowEditIcon
resetEditIcon(departmentListData.value);
// 只设置当前节点
if (currentNode.value.level !== 1) {
node.isShowEditIcon = true;
}
departmentDetailInfo.value = await getDepartmentDetailData(
Number(currentNode.value.id)
);
},
300
);
// 所有用户数据
const allUserList = ref<UserList>([]);
@@ -162,39 +175,43 @@
const { code, data } = await getAllUserList({
keyword: userKeyword.value,
});
if (code === 200 && data?.length) {
allUserList.value = data;
if (code === 200) {
const { user } = data;
allUserList.value = Array.isArray(user) ? user : [];
}
} catch (error) {
console.error('获取所以用户数据失败:', error);
// 处理错误
}
};
const handleAdd = () => {
showModal.value = true;
isEdit.value = false;
departmentDetailInfo.value = null;
showModal.value = true;
};
const handleEdit = () => {
showModal.value = true;
isEdit.value = true;
};
const handleDelete = async (item) => {
const handleDelete = async (item: { id: string | number }) => {
const { id } = item;
try {
const { code, message } = await delDepartment(id);
const { code, message } = await delDepartment(Number(id));
if (code === 200) {
Message.success(message);
getDepartmentListData();
}
} catch (error) {
console.log('删除失败:', error);
// 处理错误
}
};
const handleMoveUp = async (item) => {
const handleMoveUp = async (item: { id: string | number }) => {
const { id } = item;
try {
const { code, message } = await departmentSetUp(id);
const { code, message } = await departmentSetUp(Number(id));
if (code === 200) {
Message.success(message);
getDepartmentListData();
@@ -202,13 +219,14 @@
Message.info(message);
}
} catch (error) {
console.log('向上移动失败:', error);
// 处理错误
}
};
const handleMoveDown = async (item) => {
const handleMoveDown = async (item: { id: string | number }) => {
const { id } = item;
try {
const { code, message } = await departmentSetDown(id);
const { code, message } = await departmentSetDown(Number(id));
if (code === 200) {
Message.success(message);
getDepartmentListData();
@@ -216,7 +234,7 @@
Message.info(message);
}
} catch (error) {
console.log('向下移动失败:', error);
// 处理错误
}
};
@@ -0,0 +1,300 @@
<template>
<a-drawer
:width="340"
:visible="visible"
:title="isEdit ? $t('edit-user') : $t('add-user')"
unmount-on-close
@ok="handleOk"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
auto-label-width
layout="vertical"
>
<!-- 用户名 -->
<a-form-item :label="$t('username')" field="username" required>
<a-input
v-model="form.username"
:placeholder="$t('please.enter', { placeholder: $t('username') })"
/>
</a-form-item>
<!-- 姓名 -->
<a-form-item :label="$t('name')" field="realname" required>
<a-input
v-model="form.realname"
:placeholder="$t('please.enter', { placeholder: $t('name') })"
/>
</a-form-item>
<a-form-item field="password" :label="$t('password')" class="form-item">
<a-input-password
v-model="form.password"
:disabled="isEdit"
:placeholder="$t('please.enter', { placeholder: $t('password') })"
allow-clear
class="input-field"
>
</a-input-password>
</a-form-item>
<!-- 确认密码 -->
<a-form-item
field="repassword"
:label="$t('confirm.password')"
class="form-item"
>
<a-input-password
v-model="form.repassword"
:disabled="isEdit"
:placeholder="$t('please.confirm', { placeholder: $t('password') })"
allow-clear
class="input-field"
>
</a-input-password>
</a-form-item>
<!-- 手机号 -->
<a-form-item :label="$t('tel')" field="tel" required>
<a-input
v-model="form.tel"
:placeholder="$t('please.enter', { placeholder: $t('tel') })"
/>
</a-form-item>
<!-- 邮箱 -->
<a-form-item :label="$t('email')" field="email">
<a-input
v-model="form.email"
:placeholder="$t('please.enter', { placeholder: $t('email') })"
/>
</a-form-item>
<!-- 角色 -->
<a-form-item :label="$t('role')" field="role_id" required>
<a-select
v-model="form.role_id"
:options="props.roleList"
:field-names="{ value: 'id', label: 'name' }"
:placeholder="$t('please.select', { placeholder: $t('role') })"
/>
</a-form-item>
<!-- 部门主管 -->
<a-form-item field="department_id">
<template #label>
<div class="label_wrap">
<div class="left">
<span class="required">*</span>
{{ $t('department') }}
</div>
<div class="right">
<a-checkbox
:default-checked="form.is_manager === 1"
:checked="form.is_manager === 1"
@change="form.is_manager = $event ? 1 : 2"
>
{{ $t('department-head') }}
</a-checkbox>
<icon-exclamation-circle-fill />
</div>
</div>
</template>
<a-tree-select
v-model="form.department_id"
:data="props.departmentListData"
:field-names="{ key: 'id', title: 'name', children: 'children' }"
:placeholder="$t('please.select', { placeholder: $t('department') })"
/>
</a-form-item>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, watch, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import type { FormInstance } from '@arco-design/web-vue';
import {
DepartmentItem,
RoleItem,
FormUserItem,
createUser,
updateUser,
} from '@/api/setting';
const props = defineProps<{
visible: boolean;
isEdit: boolean;
record?: FormUserItem | null;
departmentListData?: DepartmentItem[];
roleList?: RoleItem[];
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'submitted'): void;
}>();
// 表单数据
const formRef = ref<FormInstance>();
const defaultForm: FormUserItem = {
username: '',
realname: '',
tel: '',
password: '',
repassword: '',
role_id: null,
department_id: null,
email: '',
is_manager: 1,
};
const form = reactive<FormUserItem>({ ...defaultForm });
const { t } = useI18n();
// 校验规则
const rules = reactive({
username: [
{
required: true,
message: t('please.enter', { placeholder: t('username') }),
},
],
realname: [
{
required: true,
message: t('please.enter', { placeholder: t('name') }),
},
],
password: [
{
required: true,
message: t('please.enter', { placeholder: t('password') }),
},
],
repassword: [
{
required: true,
message: t('please.confirm', { placeholder: t('password') }),
},
{
validator: (value, cb) => {
if (value !== form.password) {
cb('two passwords do not match');
} else {
cb();
}
},
},
],
tel: [
{ required: true, message: t('please.enter', { placeholder: t('tel') }) },
{
validator: (value, cb) => {
if (!/^1\d{10}$/.test(String(value))) {
cb(t('please.enter.correct', { field: t('tel') }));
} else {
cb();
}
},
},
],
email: [
{
validator: (value, cb) => {
if (!value) {
cb();
return;
}
const emailReg = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
if (!emailReg.test(value)) {
cb(t('please.enter.correct', { field: t('email') }));
} else {
cb();
}
},
},
],
role_id: [
{
required: true,
message: t('please.select', { placeholder: t('role') }),
},
],
department_id: [
{ message: t('please.select', { placeholder: t('department-head') }) },
],
});
watch(
() => props.visible,
(val) => {
if (val) {
if (props.isEdit && props.record) {
const record = props.record as FormUserItem & {
roleId?: number;
departmentId?: number;
isManager?: number;
};
Object.assign(form, {
...record,
});
form.role_id = record?.roleId ?? null;
form.department_id = record?.departmentId ?? null;
form.password = record?.password;
form.repassword = record?.password;
form.is_manager =
record?.isManager === 1 || record?.isManager === 2
? record.isManager
: 1;
} else {
Object.assign(form, defaultForm);
}
}
},
{ immediate: true }
);
const handleOk = async () => {
try {
const valid = await formRef.value.validate();
if (valid) return;
const params = { ...form };
if (props.isEdit) {
await updateUser(params);
} else {
await createUser(params);
}
emit('submitted');
emit('update:visible', false);
} catch (err) {
// 处理错误
}
};
const handleCancel = () => {
emit('update:visible', false);
};
</script>
<style lang="less" scoped>
:deep(label.arco-form-item-label) {
width: 100% !important;
}
.label_wrap {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
align-items: center;
.required {
color: #f53f3f;
margin-right: 4px;
font-size: 16px;
line-height: 1;
}
}
.right {
display: flex;
align-items: center;
gap: 4px;
}
}
</style>
+327 -23
View File
@@ -3,17 +3,21 @@
<Breadcrumb
:items="['menu.setting', 'menu.setting.user', 'menu.setting.user-list']"
/>
<a-row class="" :gutter="12">
<a-row :gutter="12" :wrap="false">
<a-col flex="310px">
<a-card :bordered="false">
<a-card class="full_height" :bordered="false">
<template #title>
<div class="card-title_between">
<p class="title">{{ $t('organization') }}</p>
<a-button @click="onClick"> {{ $t('watch-detail') }} </a-button>
<a-button @click="toOrganization">
{{ $t('watch-detail') }}
</a-button>
</div>
</template>
<a-tree
:field-names="{ key: 'ID', title: 'Name' }"
v-model:expanded-keys="expandedKeys"
v-model:selected-keys="selectedKeys"
:field-names="{ key: 'id', title: 'name' }"
:data="departmentListData"
>
<template #icon>
@@ -23,35 +27,335 @@
</a-card>
</a-col>
<a-col flex="auto">
<a-card title="Arco Card" :bordered="false">
<template #extra>
<a-link>More</a-link>
<a-card class="full_height" title="Arco Card" :bordered="false">
<template #title>
<div class="card-title_between">
<p class="title">{{ $t('menu.business.project') }}</p>
<div class="flex_center_between">
<a-space>
<a-input-search
v-model="searchKeyWord"
allow-clear
:style="{ width: '320px' }"
:placeholder="$t('search.username.name')"
@clear="handleSearchUser"
@search="handleSearchUser"
@press-enter="handleSearchUser"
/>
<a-button>
<template #icon>
<icon-filter />
</template>
{{ $t('table.filter') }}
</a-button>
<a-button type="primary" @click="handleAddUser">
<template #icon>
<icon-plus />
</template>
{{ $t('add-user') }}
</a-button>
</a-space>
</div>
</div>
</template>
Card content
<br />
Card content
<!-- 使用新的 DataTable 组件 -->
<DataTable
:data="renderData"
:columns="projectColumn"
:loading="loading"
:total="total"
:current="current"
:page-size="pageSize"
pagination
@page-change="onPageChange"
@page-size-change="onPageSizeChange"
>
<template #realname="{ record }">
<span>{{ record.realname }}</span>
<icon-idcard v-if="record.isManager === 1" />
</template>
<template #status="{ record }">
<a-tag :color="record.status == 1 ? 'green' : 'red'">
{{ record.status == 1 ? $t('enable') : $t('disable') }}</a-tag
>
</template>
<template #operate="{ record }">
<a-space>
<!-- <a-button>查看</a-button> -->
<a-button type="primary" @click="handleEditUser(record.id)">{{
$t('edit')
}}</a-button>
<a-popover trigger="click" position="bottom">
<a-button>{{ $t('more') }}</a-button>
<template #content>
<a-space direction="vertical">
<a-button
@click="handleDisableUser(record.id, record.status)"
>
{{ record.status == 1 ? $t('disable') : $t('enable') }}
</a-button>
<a-button
status="danger"
@click="handleDelUser(record.id)"
>{{ $t('delete') }}</a-button
>
</a-space>
</template>
</a-popover>
</a-space>
</template>
</DataTable>
</a-card>
<userFormDrawer
v-model:visible="showDrawer"
:record="userInfo"
:is-edit="isEdit"
:department-list-data="departmentListData"
:role-list="roleList"
@submitted="getUserListData"
/>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { DepartmentFormParams, createDepartment } from '@/api/setting';
import useDepartmentList from '@/views/setting/composables/useDepartmentList';
// import { ref } from 'vue'
const onClick = async () => {
const params: DepartmentFormParams = {
name: '冰一下很开心', // 部门名称
parent_id: 34, // 上一级id
};
import { ref, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import type { TableColumnData } from '@arco-design/web-vue';
import {
getUserList,
UserList,
getRoleList,
RoleItem,
getUserInfo,
FormUserItem,
disableUser,
delUser,
} from '@/api/setting';
import useDepartmentList from '@/composables/useDepartmentList';
import useDataTable from '@/composables/useDataTable';
import { Message } from '@arco-design/web-vue';
import userFormDrawer from './components/user-form-drawer.vue';
const { t } = useI18n();
const router = useRouter();
const { departmentListData, allDepartmentKeys } = useDepartmentList();
const expandedKeys = ref<(string | number)[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
const showDrawer = ref<boolean>(false);
const isEdit = ref<boolean>(false);
const userInfo = ref<FormUserItem | null>(null);
const roleList = ref<RoleItem[]>([]);
const roleListLoaded = ref(false);
// 使用 DataTable composable
const {
data: renderData,
columns: projectColumn,
loading,
total,
current,
pageSize,
setData,
setColumns,
setLoading,
setTotal,
setCurrent,
setPageSize,
} = useDataTable({
initialPage: 1,
initialPageSize: 10,
pageSizeOptions: [10, 20, 50, 100],
});
const userListData = ref<UserList>([]);
// 列配置
const columns: TableColumnData[] = [
{
title: t('username'),
dataIndex: 'username',
key: 'username',
width: 120,
ellipsis: true,
},
{
title: t('name'),
dataIndex: 'realname',
key: 'realname',
width: 150,
ellipsis: true,
slotName: 'realname',
},
{
title: t('department'),
dataIndex: 'deptName',
key: 'departmentId',
},
{
title: t('role'),
dataIndex: 'roleName',
key: 'roleName',
},
{
title: t('status'),
dataIndex: 'status',
key: 'status',
slotName: 'status',
},
{
title: t('lastLogin'),
dataIndex: 'lastLoginTime',
key: 'lastLoginTime',
render: ({ record }) => record.lastLoginTime || '-',
},
{
title: '操作',
key: 'operate',
slotName: 'operate',
width: 200,
},
];
watch(
allDepartmentKeys,
(val) => {
expandedKeys.value = val;
},
{ immediate: true }
);
const toOrganization = () => {
router.push({ path: '/setting/system', query: { active: 4 } });
};
const searchKeyWord = ref<string>('');
// const pageNo = ref<number>(1)
const getUserListData = async () => {
try {
const res = await createDepartment(params);
console.log(res, 'res');
const params = {
dept_id: selectedKeys.value[0],
keyword: searchKeyWord.value,
page: current.value,
page_size: pageSize.value,
};
setLoading(true);
const { code, data } = await getUserList(params);
if (code === 200) {
const { page, user } = data;
userListData.value = Array.isArray(user) ? user : [];
setData(userListData.value);
setTotal(typeof page.total === 'number' ? page.total : 0);
}
} catch (error) {
console.log(error, 'res');
// 处理错误
} finally {
setLoading(false);
}
};
const { departmentListData, getDepartmentListData } = useDepartmentList();
getDepartmentListData();
const openDrawer = async () => {
if (!roleListLoaded.value) {
try {
const { code, data } = await getRoleList();
if (code === 200) {
roleList.value = Array.isArray(data?.rolesList) ? data.rolesList : [];
roleListLoaded.value = true;
}
} catch (error) {
// 处理错误
}
}
showDrawer.value = true;
};
watch(
departmentListData,
(val) => {
if (Array.isArray(val) && val.length > 0) {
const [firstRoot] = val;
if (firstRoot.children && firstRoot.children.length > 0) {
selectedKeys.value = [firstRoot.children[0].id];
} else {
selectedKeys.value = [];
}
}
},
{ immediate: true, deep: true }
);
let lastSelectedKey: string | number = '';
watch(selectedKeys, (val) => {
// 只处理有选中项的情况
if (val.length > 0) {
// 判断新旧 key 是否相同
if (val[0] !== lastSelectedKey) {
lastSelectedKey = val[0];
getUserListData();
}
}
});
// 分页事件处理
const onPageChange = (page: number) => {
setCurrent(page);
current.value = page;
getUserListData();
};
const onPageSizeChange = (size: number) => {
setPageSize(size);
pageSize.value = size;
getUserListData();
};
const handleSearchUser = () => {
getUserListData();
};
const handleAddUser = async () => {
isEdit.value = false;
userInfo.value = null;
openDrawer();
};
const getUserInfoData = async (id: number) => {
try {
const { code, data } = await getUserInfo(id);
if (code === 200) userInfo.value = data?.user;
} catch (error) {
// 处理错误
}
};
const handleEditUser = async (id: number) => {
isEdit.value = true;
userInfo.value = null;
await getUserInfoData(id);
openDrawer();
};
const handleDisableUser = async (id: number, currentStatus: number) => {
try {
const param = { id, status: currentStatus === 1 ? 2 : 1 };
const { code, message } = await disableUser(param);
if (code === 200) {
Message.success(message);
getUserListData();
} else {
Message.error(message);
}
} catch (error) {
// 处理错误
}
};
const handleDelUser = async (id: number) => {
try {
const { code, message } = await delUser(id);
if (code === 200) {
Message.success(message);
getUserListData();
} else {
Message.error(message);
}
} catch (error) {
// 处理错误
}
};
// 初始化数据
onMounted(() => {
setColumns(columns);
});
</script>