Files
lgp-admin-plus/playground/src/views/system/user/list.vue
xingyu 04fbb7a556 chore: 升级 shadcn-vue 组件到v4最新版 (#7972)
* fix: useStore is deprecated

* chore: update deps

* feat: 升级shadcn-ui v4

* fix: workbench style

* feat: 升级shadcn-ui v4 step2

* feat: 升级shadcn-ui v4 step3

* chore: 升级shadcn v4

* fix: pagination

* fix: dark style

* fix: doc import

* feat: 增加详情组件,参考 antdv-next

* docs: descriptions docs

* docs: Browser Support

* feat: add table action

* feat: icon use vbenIcon

* fix: type error

* fix: dropdown popConfirm

* feat: 使用默认的文字交互

* feat: 优化渲染性能
2026-05-31 15:18:46 +08:00

247 lines
6.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import type { Recordable } from '@vben/types';
import type { 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, VbenTableAction } from '#/adapter/vxe-table';
import { deleteUser, getDeptList, getUserList, updateUser } from '#/api';
import { $t } from '#/locales';
import { useColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
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 [DetailDrawer, detailDrawerApi] = useVbenDrawer({
connectedComponent: Detail,
destroyOnClose: true,
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
fieldMappingTime: [['createTime', ['startTime', 'endTime']]],
schema: useGridFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useColumns(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>,
});
/**
* 将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 onDetail(row: SystemUserApi.SystemUser) {
detailDrawerApi.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" />
<DetailDrawer @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>
<template #action="{ row }">
<VbenTableAction
:actions="[
{
text: $t('common.detail'),
icon: 'lucide:eye',
onClick: () => onDetail(row),
},
{
text: $t('common.edit'),
icon: 'lucide:edit',
onClick: () => onEdit(row),
},
]"
:dropdown-actions="[
{
text: $t('common.delete'),
icon: 'lucide:trash-2',
danger: true,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: () => onDelete(row),
},
auth: ['AC_100100'],
},
]"
align="center"
/>
</template>
</Grid>
</div>
</div>
</Page>
</template>