|
|
@@ -0,0 +1,222 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import type { Recordable } from '@vben/types';
|
|
|
+
|
|
|
+import type {
|
|
|
+ OnActionClickParams,
|
|
|
+ VxeTableGridOptions,
|
|
|
+} from '#/adapter/vxe-table';
|
|
|
+import type { SystemDeptApi, SystemUserApi } from '#/api';
|
|
|
+
|
|
|
+import { onMounted, ref, watch } from 'vue';
|
|
|
+
|
|
|
+import { Page, Tree, useVbenDrawer } from '@vben/common-ui';
|
|
|
+import { Plus } from '@vben/icons';
|
|
|
+
|
|
|
+import { Button, Card, InputSearch, message, Modal } from 'antdv-next';
|
|
|
+
|
|
|
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
|
+import { deleteUser, getDeptList, getUserList, updateUser } from '#/api';
|
|
|
+import { $t } from '#/locales';
|
|
|
+
|
|
|
+import { useColumns, useGridFormSchema } from './data';
|
|
|
+import Form from './modules/form.vue';
|
|
|
+
|
|
|
+const deptList = ref<SystemDeptApi.SystemDept[]>([]);
|
|
|
+const inputSearchValue = ref('');
|
|
|
+const selectedDeptId = ref<string>('');
|
|
|
+
|
|
|
+const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
|
|
+ connectedComponent: Form,
|
|
|
+ destroyOnClose: true,
|
|
|
+});
|
|
|
+
|
|
|
+const [Grid, gridApi] = useVbenVxeGrid({
|
|
|
+ formOptions: {
|
|
|
+ fieldMappingTime: [['createTime', ['startTime', 'endTime']]],
|
|
|
+ schema: useGridFormSchema(),
|
|
|
+ submitOnChange: true,
|
|
|
+ },
|
|
|
+ gridOptions: {
|
|
|
+ columns: useColumns(onActionClick, onStatusChange),
|
|
|
+ height: 'auto',
|
|
|
+ keepSource: true,
|
|
|
+ proxyConfig: {
|
|
|
+ ajax: {
|
|
|
+ query: async ({ page }, formValues) => {
|
|
|
+ return await getUserList({
|
|
|
+ page: page.currentPage,
|
|
|
+ pageSize: page.pageSize,
|
|
|
+ ...formValues,
|
|
|
+ deptId: selectedDeptId.value,
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ rowConfig: {
|
|
|
+ keyField: 'id',
|
|
|
+ },
|
|
|
+
|
|
|
+ toolbarConfig: {
|
|
|
+ custom: true,
|
|
|
+ export: false,
|
|
|
+ refresh: true,
|
|
|
+ search: true,
|
|
|
+ zoom: true,
|
|
|
+ },
|
|
|
+ } as VxeTableGridOptions<SystemUserApi.SystemUser>,
|
|
|
+});
|
|
|
+
|
|
|
+function onActionClick(e: OnActionClickParams<SystemUserApi.SystemUser>) {
|
|
|
+ switch (e.code) {
|
|
|
+ case 'delete': {
|
|
|
+ onDelete(e.row);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 'edit': {
|
|
|
+ onEdit(e.row);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将Antd的Modal.confirm封装为promise,方便在异步函数中调用。
|
|
|
+ * @param content 提示内容
|
|
|
+ * @param title 提示标题
|
|
|
+ */
|
|
|
+function confirm(content: string, title: string) {
|
|
|
+ return new Promise((reslove, reject) => {
|
|
|
+ Modal.confirm({
|
|
|
+ content,
|
|
|
+ onCancel() {
|
|
|
+ reject(new Error('已取消'));
|
|
|
+ },
|
|
|
+ onOk() {
|
|
|
+ reslove(true);
|
|
|
+ },
|
|
|
+ title,
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 状态开关即将改变
|
|
|
+ * @param newStatus 期望改变的状态值
|
|
|
+ * @param row 行数据
|
|
|
+ * @returns 返回false则中止改变,返回其他值(undefined、true)则允许改变
|
|
|
+ */
|
|
|
+async function onStatusChange(
|
|
|
+ newStatus: number,
|
|
|
+ row: SystemUserApi.SystemUser,
|
|
|
+) {
|
|
|
+ const status: Recordable<string> = {
|
|
|
+ 0: '禁用',
|
|
|
+ 1: '启用',
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ await confirm(
|
|
|
+ `你要将${row.name}的状态切换为 【${status[newStatus.toString()]}】 吗?`,
|
|
|
+ `切换状态`,
|
|
|
+ );
|
|
|
+ await updateUser(row.id, { status: newStatus });
|
|
|
+ return true;
|
|
|
+ } catch {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onEdit(row: SystemUserApi.SystemUser) {
|
|
|
+ formDrawerApi.setData(row).open();
|
|
|
+}
|
|
|
+
|
|
|
+function onDelete(row: SystemUserApi.SystemUser) {
|
|
|
+ const hideLoading = message.loading({
|
|
|
+ content: $t('ui.actionMessage.deleting', [row.name]),
|
|
|
+ duration: 0,
|
|
|
+ key: 'action_process_msg',
|
|
|
+ });
|
|
|
+ deleteUser(row.id)
|
|
|
+ .then(() => {
|
|
|
+ message.success({
|
|
|
+ content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
|
|
+ key: 'action_process_msg',
|
|
|
+ });
|
|
|
+ onRefresh();
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ hideLoading();
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function onRefresh() {
|
|
|
+ gridApi.query();
|
|
|
+}
|
|
|
+
|
|
|
+function onCreate() {
|
|
|
+ formDrawerApi.setData({}).open();
|
|
|
+}
|
|
|
+
|
|
|
+async function loadDeptList() {
|
|
|
+ try {
|
|
|
+ const res = await getDeptList();
|
|
|
+ deptList.value = res;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Failed to load department list:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function selectDept(v: string) {
|
|
|
+ selectedDeptId.value = v;
|
|
|
+ gridApi.query();
|
|
|
+}
|
|
|
+
|
|
|
+function searchDept(value: string) {
|
|
|
+ if (!value) {
|
|
|
+ loadDeptList();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const filtered = deptList.value.filter((dept) =>
|
|
|
+ dept.name.toLowerCase().includes(value.toLowerCase()),
|
|
|
+ );
|
|
|
+ deptList.value = filtered;
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadDeptList();
|
|
|
+});
|
|
|
+
|
|
|
+watch(inputSearchValue, (value) => {
|
|
|
+ searchDept(value);
|
|
|
+});
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <Page auto-content-height>
|
|
|
+ <FormDrawer @success="onRefresh" />
|
|
|
+ <div class="flex size-full">
|
|
|
+ <Card class="w-1/6">
|
|
|
+ <InputSearch
|
|
|
+ v-model:value="inputSearchValue"
|
|
|
+ :placeholder="$t('system.user.placeholder')"
|
|
|
+ />
|
|
|
+ <Tree
|
|
|
+ label-field="name"
|
|
|
+ value-field="id"
|
|
|
+ :tree-data="deptList"
|
|
|
+ :default-expanded-level="2"
|
|
|
+ @select="selectDept"
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ <div class="w-5/6 ml-4">
|
|
|
+ <Grid :table-title="$t('system.user.list')">
|
|
|
+ <template #toolbar-tools>
|
|
|
+ <Button type="primary" @click="onCreate">
|
|
|
+ <Plus class="size-5" />
|
|
|
+ {{ $t('ui.actionTitle.create', [$t('system.user.name')]) }}
|
|
|
+ </Button>
|
|
|
+ </template>
|
|
|
+ </Grid>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Page>
|
|
|
+</template>
|