Restore full post management page

This commit is contained in:
2026-06-10 08:43:13 +08:00
parent 28437074dc
commit 3f75e96260
5 changed files with 686 additions and 369 deletions
+1
View File
@@ -32,6 +32,7 @@ const modules = import.meta.glob([
'./langs/*/message.json',
'./langs/*/page.json',
'./langs/*/permission.json',
'./langs/*/post.json',
'./langs/*/role.json',
'./langs/*/system-config.json',
'./langs/*/system.json',
@@ -0,0 +1,187 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn } from '#/adapter/vxe-table';
import type { Post } from '#/api/core/post';
import { $t } from '@vben/locales';
import { z } from '#/adapter/form';
/**
* 获取搜索表单的字段配置
*/
export function useSearchFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'name',
label: $t('system.user.userName'),
},
{
component: 'Input',
fieldName: 'username',
label: $t('system.user.account'),
},
];
}
/**
* 获取岗位类型选项
*/
export function getPostTypeOptions() {
return [
{ label: $t('post.types.management'), value: 0 },
{ label: $t('post.types.technical'), value: 1 },
{ label: $t('post.types.business'), value: 2 },
{ label: $t('post.types.functional'), value: 3 },
{ label: $t('post.types.other'), value: 4 },
];
}
/**
* 获取岗位级别选项
*/
export function getPostLevelOptions() {
return [
{ label: $t('post.levels.senior'), value: 0 },
{ label: $t('post.levels.middle'), value: 1 },
{ label: $t('post.levels.basic'), value: 2 },
{ label: $t('post.levels.staff'), value: 3 },
];
}
/**
* 获取岗位树列配置
*/
export function usePostTreeColumns(
onActionClick?: OnActionClickFn<Post>,
): VxeTableGridOptions<Post>['columns'] {
return [
{
field: 'name',
title: $t('post.postName'),
minWidth: 150,
},
];
}
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'name',
label: $t('post.postName'),
rules: z
.string()
.min(2, $t('ui.formRules.minLength', [$t('post.postName'), 2]))
.max(64, $t('ui.formRules.maxLength', [$t('post.postName'), 64])),
},
{
component: 'Input',
fieldName: 'code',
label: $t('post.postCode'),
rules: z
.string()
.min(2, $t('ui.formRules.minLength', [$t('post.postCode'), 2]))
.max(32, $t('ui.formRules.maxLength', [$t('post.postCode'), 32]))
.regex(/^[\w-]+$/, $t('post.codeFormatError')),
},
{
component: 'Select',
componentProps: {
options: getPostTypeOptions(),
},
defaultValue: 4,
fieldName: 'post_type',
label: $t('post.postType'),
},
{
component: 'Select',
componentProps: {
options: getPostLevelOptions(),
},
defaultValue: 3,
fieldName: 'post_level',
label: $t('post.postLevel'),
},
{
component: 'DeptSelector',
componentProps: {
placeholder: $t('post.selectDepartment'),
},
fieldName: 'dept_id',
label: $t('post.department'),
},
{
component: 'Textarea',
componentProps: {
placeholder: $t('post.descriptionPlaceholder'),
rows: 3,
},
fieldName: 'description',
label: $t('post.description'),
},
{
component: 'RadioGroup',
componentProps: {
options: [
{ label: $t('common.enabled'), value: true },
{ label: $t('common.disabled'), value: false },
],
},
defaultValue: true,
fieldName: 'status',
label: $t('post.status'),
},
];
}
/**
* 获取用户表格列配置
*/
export function useUserColumns(
onActionClick?: OnActionClickFn<Post>,
): VxeTableGridOptions<Post>['columns'] {
return [
{
type: 'checkbox',
minWidth: 60,
align: 'center',
fixed: 'left',
},
{
field: 'username',
title: $t('system.user.account'),
minWidth: 120,
},
{
field: 'name',
title: $t('system.user.userName'),
minWidth: 120,
},
{
field: 'email',
title: $t('system.user.email'),
minWidth: 180,
},
{
align: 'right',
cellRender: {
attrs: {
nameField: 'name',
nameTitle: $t('system.user.userName'),
onClick: onActionClick,
},
name: 'CellOperation',
options: ['edit', 'delete'],
},
field: 'operation',
fixed: 'right',
headerAlign: 'center',
showOverflow: false,
title: $t('system.user.operation'),
minWidth: 150,
},
];
}
+127 -373
View File
@@ -1,406 +1,160 @@
<script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import type { Post, PostCreateInput } from '#/api/core/post';
import { computed, onMounted, reactive, ref } from 'vue';
import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Edit, Plus, RefreshCw, Search, Trash2 } from '@vben/icons';
import { $t } from '@vben/locales';
import {
ElButton,
ElDialog,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElMessage,
ElMessageBox,
ElOption,
ElPagination,
ElSelect,
ElSwitch,
ElTable,
ElTableColumn,
ElTag,
} from 'element-plus';
import { ElButton, ElMessage, ElMessageBox } from 'element-plus';
import {
createPostApi,
deletePostApi,
getPostListApi,
updatePostApi,
} from '#/api/core/post';
import { addPostUsersApi, removePostUsersApi } from '#/api/core/post';
import { UserListPanel } from '#/components/user-list-panel';
import { UserSelector } from '#/components/zq-form/user-selector';
import PostList from './modules/post-list.vue';
defineOptions({ name: 'SystemPost' });
type NullableStatus = '' | boolean;
const currentPostId = ref<string>();
const tempSelectedUsers = ref<Set<string>>(new Set());
const userListPanelRef = ref<InstanceType<typeof UserListPanel>>();
const postTypeOptions = [
{ label: '管理岗', value: 0 },
{ label: '技术岗', value: 1 },
{ label: '业务岗', value: 2 },
{ label: '职能岗', value: 3 },
{ label: '其他', value: 4 },
];
const postLevelOptions = [
{ label: '高层', value: 0 },
{ label: '中层', value: 1 },
{ label: '基层', value: 2 },
{ label: '一般员工', value: 3 },
];
const loading = ref(false);
const saving = ref(false);
const dialogVisible = ref(false);
const editingPostId = ref<string>();
const tableData = ref<Post[]>([]);
const formRef = ref<FormInstance>();
const query = reactive({
code: '',
name: '',
page: 1,
pageSize: 20,
status: '' as NullableStatus,
});
const total = ref(0);
const form = reactive<PostCreateInput>({
code: '',
description: '',
name: '',
post_level: 3,
post_type: 4,
sort: 0,
status: true,
});
const rules: FormRules = {
code: [
{ message: '请输入岗位编码', required: true, trigger: 'blur' },
{
message: '岗位编码只能包含字母、数字、下划线和横线',
pattern: /^[\w-]+$/,
trigger: 'blur',
},
],
name: [{ message: '请输入岗位名称', required: true, trigger: 'blur' }],
};
const dialogTitle = computed(() =>
editingPostId.value ? '编辑岗位' : '新增岗位',
);
function getOptionLabel(
options: Array<{ label: string; value: number }>,
value?: number,
) {
return options.find((item) => item.value === value)?.label ?? '-';
/**
* 岗位选择事件
*/
function onPostSelect(postId: string | undefined) {
currentPostId.value = postId;
tempSelectedUsers.value.clear();
}
function resetForm() {
editingPostId.value = undefined;
Object.assign(form, {
code: '',
description: '',
name: '',
post_level: 3,
post_type: 4,
sort: 0,
status: true,
});
formRef.value?.clearValidate();
}
async function loadPosts() {
loading.value = true;
try {
const res = await getPostListApi({
code: query.code || undefined,
name: query.name || undefined,
page: query.page,
pageSize: query.pageSize,
status: query.status === '' ? undefined : query.status,
});
tableData.value = res.items ?? [];
total.value = res.total ?? 0;
} finally {
loading.value = false;
}
}
function onSearch() {
query.page = 1;
loadPosts();
}
function onReset() {
Object.assign(query, {
code: '',
name: '',
page: 1,
status: '',
});
loadPosts();
}
function onCreate() {
resetForm();
dialogVisible.value = true;
}
function onEdit(row: Post) {
resetForm();
editingPostId.value = row.id;
Object.assign(form, {
code: row.code,
description: row.description ?? '',
name: row.name,
post_level: row.post_level ?? 3,
post_type: row.post_type ?? 4,
sort: row.sort ?? 0,
status: row.status,
});
dialogVisible.value = true;
}
async function onSave() {
await formRef.value?.validate();
saving.value = true;
try {
const payload = { ...form };
if (editingPostId.value) {
await updatePostApi(editingPostId.value, payload);
ElMessage.success('岗位已更新');
/**
* 处理用户选择
*/
function handleUserSelect(userId: string, _user: any) {
if (tempSelectedUsers.value.has(userId)) {
tempSelectedUsers.value.delete(userId);
} else {
await createPostApi(payload);
ElMessage.success('岗位已新增');
}
dialogVisible.value = false;
await loadPosts();
} finally {
saving.value = false;
tempSelectedUsers.value.add(userId);
}
}
async function onDelete(row: Post) {
await ElMessageBox.confirm(
`确定删除岗位「${row.name}」吗?`,
'删除岗位',
{
cancelButtonText: '取消',
confirmButtonText: '删除',
type: 'warning',
},
);
await deletePostApi(row.id);
ElMessage.success('岗位已删除');
await loadPosts();
/**
* 处理移除用户
*/
function handleRemoveUser(userId: string) {
tempSelectedUsers.value.delete(userId);
}
async function onStatusChange(row: Post, status: boolean) {
/**
* 新增用户到岗位(作为 UserSelector 的 onConfirm 回调)
*/
async function handleAddUsers(userIds: string | string[]) {
if (!currentPostId.value) {
ElMessage.warning($t('post.selectPostFirst') || '请先选择岗位');
throw new Error('请先选择岗位');
}
const userIdsArray = Array.isArray(userIds) ? userIds : [userIds];
if (userIdsArray.length === 0) {
ElMessage.warning($t('post.selectUsersFirst') || '请先选择用户');
throw new Error('请先选择用户');
}
await addPostUsersApi(currentPostId.value, {
user_ids: userIdsArray,
});
ElMessage.success($t('post.addUsersSuccess') || '添加成功');
// 刷新用户列表
userListPanelRef.value?.reload();
}
/**
* 从岗位删除用户
*/
async function handleRemoveUsers() {
if (!currentPostId.value) {
ElMessage.warning($t('post.selectPostFirst') || '请先选择岗位');
return;
}
if (tempSelectedUsers.value.size === 0) {
ElMessage.warning($t('post.selectUsersFirst') || '请先选择用户');
return;
}
const userIds = [...tempSelectedUsers.value];
const confirmMessage =
$t('post.removeUsersConfirm', [tempSelectedUsers.value.size]) ||
`确定要删除选中的 ${tempSelectedUsers.value.size} 个用户吗?`;
try {
await updatePostApi(row.id, { status });
ElMessage.success(status ? '岗位已启用' : '岗位已禁用');
} catch (error) {
row.status = !status;
throw error;
}
}
await ElMessageBox.confirm(confirmMessage, $t('common.delete') || '删除', {
confirmButtonText: $t('common.confirm') || '确定',
cancelButtonText: $t('common.cancel') || '取消',
type: 'warning',
});
onMounted(loadPosts);
await removePostUsersApi(currentPostId.value, {
user_ids: userIds,
});
ElMessage.success($t('post.removeUsersSuccess') || '删除成功');
tempSelectedUsers.value.clear();
// 刷新用户列表
userListPanelRef.value?.reload();
} catch (error) {
if (error !== 'cancel') {
console.error('Failed to remove users:', error);
ElMessage.error($t('post.removeUsersFailed') || '删除失败');
}
}
}
</script>
<template>
<Page auto-content-height>
<div class="flex h-full min-h-0 flex-col gap-3">
<div
class="bg-background flex flex-wrap items-end gap-3 rounded-lg border border-border px-4 py-3"
>
<ElForm inline :model="query" class="post-search-form">
<ElFormItem label="岗位名称">
<ElInput
v-model="query.name"
clearable
placeholder="请输入岗位名称"
@keyup.enter="onSearch"
/>
</ElFormItem>
<ElFormItem label="岗位编码">
<ElInput
v-model="query.code"
clearable
placeholder="请输入岗位编码"
@keyup.enter="onSearch"
/>
</ElFormItem>
<ElFormItem label="状态">
<ElSelect
v-model="query.status"
clearable
placeholder="全部"
class="w-[120px]"
>
<ElOption label="启用" :value="true" />
<ElOption label="禁用" :value="false" />
</ElSelect>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" :icon="Search" @click="onSearch">
查询
</ElButton>
<ElButton :icon="RefreshCw" @click="onReset">重置</ElButton>
</ElFormItem>
</ElForm>
<div class="ml-auto">
<ElButton type="primary" :icon="Plus" @click="onCreate">
新增岗位
</ElButton>
</div>
<div class="flex h-full">
<!-- 岗位列表 -->
<div class="mr-3 w-1/6">
<PostList @select="onPostSelect" />
</div>
<div
class="bg-background flex min-h-0 flex-1 flex-col rounded-lg border border-border"
<!-- 主内容区用户列表 -->
<div class="w-5/6">
<UserListPanel
ref="userListPanelRef"
:data-source="currentPostId ? 'post' : 'all'"
:source-id="currentPostId"
:temp-selected-users="tempSelectedUsers"
:filterable="true"
:multiple="true"
:selectable="true"
:show-selected-tags="false"
:show-border="false"
@user-select="handleUserSelect"
@remove-user="handleRemoveUser"
>
<ElTable
v-loading="loading"
:data="tableData"
height="100%"
row-key="id"
stripe
<template #title>
<div class="flex items-center gap-2">
<UserSelector
:multiple="true"
:disabled="!currentPostId"
display-mode="button"
:placeholder="$t('common.add') || '新增'"
:on-confirm="handleAddUsers"
/>
<ElButton
type="danger"
plain
:disabled="!currentPostId || tempSelectedUsers.size === 0"
@click="handleRemoveUsers"
>
<ElTableColumn type="index" width="56" label="#" />
<ElTableColumn prop="name" label="岗位名称" min-width="150" />
<ElTableColumn prop="code" label="岗位编码" min-width="140" />
<ElTableColumn label="岗位类型" min-width="110">
<template #default="{ row }">
{{ getOptionLabel(postTypeOptions, row.post_type) }}
</template>
</ElTableColumn>
<ElTableColumn label="岗位级别" min-width="110">
<template #default="{ row }">
{{ getOptionLabel(postLevelOptions, row.post_level) }}
</template>
</ElTableColumn>
<ElTableColumn prop="dept_name" label="所属部门" min-width="140">
<template #default="{ row }">
{{ row.dept_name || '-' }}
</template>
</ElTableColumn>
<ElTableColumn label="状态" width="110">
<template #default="{ row }">
<ElSwitch
v-model="row.status"
inline-prompt
active-text=""
inactive-text=""
@change="(value) => onStatusChange(row, value as boolean)"
/>
</template>
</ElTableColumn>
<ElTableColumn label="用户数" width="90" align="center">
<template #default="{ row }">
<ElTag type="info" effect="plain">{{ row.user_count ?? 0 }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="sort" label="排序" width="90" align="center" />
<ElTableColumn
prop="sys_create_datetime"
label="创建时间"
min-width="170"
/>
<ElTableColumn label="操作" width="150" fixed="right">
<template #default="{ row }">
<ElButton link type="primary" :icon="Edit" @click="onEdit(row)">
编辑
</ElButton>
<ElButton link type="danger" :icon="Trash2" @click="onDelete(row)">
删除
{{ $t('common.delete') || '删除' }}
</ElButton>
</div>
</template>
</ElTableColumn>
</ElTable>
<div class="flex justify-end border-t border-border px-4 py-3">
<ElPagination
v-model:current-page="query.page"
v-model:page-size="query.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
background
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadPosts"
@size-change="onSearch"
/>
</UserListPanel>
</div>
</div>
</div>
<ElDialog v-model="dialogVisible" :title="dialogTitle" width="520px">
<ElForm ref="formRef" :model="form" :rules="rules" label-width="92px">
<ElFormItem label="岗位名称" prop="name">
<ElInput v-model="form.name" maxlength="64" placeholder="请输入岗位名称" />
</ElFormItem>
<ElFormItem label="岗位编码" prop="code">
<ElInput v-model="form.code" maxlength="32" placeholder="请输入岗位编码" />
</ElFormItem>
<ElFormItem label="岗位类型">
<ElSelect v-model="form.post_type" class="w-full">
<ElOption
v-for="item in postTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="岗位级别">
<ElSelect v-model="form.post_level" class="w-full">
<ElOption
v-for="item in postLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="排序">
<ElInputNumber v-model="form.sort" :min="0" class="w-full" />
</ElFormItem>
<ElFormItem label="状态">
<ElSwitch
v-model="form.status"
inline-prompt
active-text="启用"
inactive-text="禁用"
/>
</ElFormItem>
<ElFormItem label="岗位描述">
<ElInput
v-model="form.description"
maxlength="200"
placeholder="请输入岗位描述"
show-word-limit
type="textarea"
:rows="3"
/>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" :loading="saving" @click="onSave">
保存
</ElButton>
</template>
</ElDialog>
</Page>
</template>
<style scoped>
.post-search-form {
margin-bottom: -18px;
}
</style>
@@ -0,0 +1,84 @@
<script lang="ts" setup>
import type { Post } from '#/api/core/post';
import { computed, ref } from 'vue';
import { $t } from '@vben/locales';
import { ElButton } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { createPostApi, updatePostApi } from '#/api/core/post';
import { ZqDialog } from '#/components/zq-dialog';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Post>();
const visible = ref(false);
const confirmLoading = ref(false);
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', [$t('post.name')])
: $t('ui.actionTitle.create', [$t('post.name')]);
});
const [Form, formApi] = useVbenForm({
layout: 'vertical',
schema: useFormSchema(),
showDefaultActions: false,
});
function resetForm() {
formApi.resetForm();
formApi.setValues(formData.value || {});
}
async function onSubmit() {
const { valid } = await formApi.validate();
if (valid) {
confirmLoading.value = true;
const data = await formApi.getValues();
try {
await (formData.value?.id
? updatePostApi(formData.value.id, data)
: createPostApi(data));
visible.value = false;
emit('success');
} finally {
confirmLoading.value = false;
}
}
}
function open(data?: Post) {
visible.value = true;
if (data) {
formData.value = data;
formApi.setValues(formData.value);
} else {
formData.value = undefined;
formApi.resetForm();
}
}
defineExpose({
open,
});
</script>
<template>
<ZqDialog
v-model="visible"
:title="getTitle"
:confirm-loading="confirmLoading"
@confirm="onSubmit"
>
<Form class="mx-4" />
<template #footer-left>
<ElButton type="primary" @click="resetForm">
{{ $t('common.reset') }}
</ElButton>
</template>
</ZqDialog>
</template>
@@ -0,0 +1,291 @@
<script lang="ts" setup>
import type { Post } from '#/api/core/post';
import type { CardListOptions } from '#/components/card-list';
import { onMounted, ref } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import {
ElButton,
ElMessage,
ElMessageBox,
ElPopover,
ElTooltip,
} from 'element-plus';
import { deletePostApi, getPostListApi } from '#/api/core/post';
import { CardList } from '#/components/card-list';
import PostFormModal from './post-form-modal.vue';
const emit = defineEmits<{
select: [postId: string | undefined];
}>();
const postList = ref<Post[]>([]);
const loading = ref(false);
const selectedPostId = ref<string>();
const searchKeyword = ref<string>('');
const hoveredPostId = ref<string>();
const postFormModalRef = ref<InstanceType<typeof PostFormModal>>();
// 卡片列表配置
const cardListOptions: CardListOptions<Post> = {
searchFields: [{ field: 'name' }, { field: 'code' }],
titleField: 'name',
};
async function fetchPostList() {
try {
loading.value = true;
const response = await getPostListApi({ page: 1, pageSize: 100 });
postList.value = response.items || [];
// 自动选中第一个岗位
if (postList.value.length > 0 && !selectedPostId.value) {
const firstPost = postList.value.at(0);
if (firstPost) {
selectedPostId.value = firstPost.id;
emit('select', firstPost.id);
}
}
} finally {
loading.value = false;
}
}
/**
* 处理岗位选择
*/
function onPostSelect(postId: string | undefined) {
selectedPostId.value = postId;
emit('select', postId);
}
/**
* 打开添加岗位对话框
*/
function onAddPost() {
postFormModalRef.value?.open();
}
/**
* 打开编辑岗位对话框
*/
function onEditPost(post: Post, e?: Event) {
e?.stopPropagation();
postFormModalRef.value?.open(post);
}
/**
* 删除岗位
*/
async function onDeletePost(post: Post, e?: Event) {
e?.stopPropagation();
ElMessageBox.confirm(
$t('ui.actionMessage.deleteConfirm', [post.name]),
$t('common.delete'),
{
confirmButtonText: $t('common.confirm'),
cancelButtonText: $t('common.cancel'),
type: 'warning',
showClose: false,
},
)
.then(async () => {
try {
await deletePostApi(post.id);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [post.name]));
// 如果删除的是当前选中的岗位,清除选中状态
if (selectedPostId.value === post.id) {
selectedPostId.value = undefined;
emit('select', undefined);
}
await fetchPostList();
} catch {
ElMessage.error($t('ui.actionMessage.deleteError'));
}
})
.catch(() => {
// 用户取消了操作
});
}
/**
* 添加岗位成功后的回调
*/
async function onPostFormSuccess() {
ElMessage.success($t('ui.actionMessage.createSuccess', [$t('post.name')]));
await fetchPostList();
}
onMounted(() => {
fetchPostList();
});
</script>
<template>
<CardList
:items="postList"
:loading="loading"
:selected-id="selectedPostId"
:hovered-id="hoveredPostId"
:search-keyword="searchKeyword"
:options="cardListOptions"
@select="onPostSelect"
@update:search-keyword="(v) => (searchKeyword = v)"
@update:hovered-id="(v) => (hoveredPostId = v)"
@add="onAddPost"
@edit="onEditPost"
@delete="onDeletePost"
>
<!-- 自定义项目渲染 -->
<template #item="{ item }">
<div class="truncate text-sm" :title="item.name">
{{ item.name }}
</div>
</template>
<!-- 详细信息 -->
<template #details="{ item }">
<div class="flex items-center gap-2 text-xs opacity-70">
<!-- 岗位编码 -->
<span class="truncate" :title="item.code">
{{ item.code }}
</span>
<!-- 分隔符 -->
<span class="text-gray-400">|</span>
<!-- 岗位类型 -->
<span v-if="item.post_type_display" class="flex-shrink-0">
{{ item.post_type_display }}
</span>
<!-- 部门 -->
<span
v-if="item.dept_name"
class="flex-1 truncate"
:title="item.dept_name"
>
{{ item.dept_name }}
</span>
</div>
</template>
<!-- 操作按钮 -->
<template #actions="{ item }">
<div class="flex flex-shrink-0" @click.stop>
<!-- 编辑按钮 -->
<ElTooltip :content="$t('post.edit')" placement="top">
<ElButton
type="primary"
text
size="small"
circle
@click="onEditPost(item, $event)"
>
<IconifyIcon icon="ep:edit" class="size-4" />
</ElButton>
</ElTooltip>
<!-- 删除按钮 -->
<ElButton
type="danger"
text
size="small"
circle
style="margin-left: 0"
:title="$t('common.delete')"
@click="onDeletePost(item, $event)"
>
<IconifyIcon icon="ep:delete" class="size-4" />
</ElButton>
<!-- 详情按钮 -->
<ElPopover placement="right" :width="300">
<template #reference>
<ElButton
type="info"
text
size="small"
style="margin-left: 0"
circle
>
<IconifyIcon icon="ep:info-filled" class="size-4" />
</ElButton>
</template>
<!-- Popover 内容:详细信息 -->
<div class="space-y-2 p-3 text-sm">
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">岗位名称:</span>
<span class="font-medium">{{ item.name || '-' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">岗位编码:</span>
<span class="font-medium">{{ item.code || '-' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">岗位类型:</span>
<span class="font-medium">{{
item.post_type_display || '-'
}}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">岗位级别:</span>
<span class="font-medium">{{
item.post_level_display || '-'
}}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">所属部门:</span>
<span class="font-medium">{{ item.dept_name || '-' }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-600 dark:text-gray-400">状态:</span>
<span class="font-medium">{{
item.status ? '启用' : '禁用'
}}</span>
</div>
<div
v-if="item.description"
class="border-t border-gray-200 pt-2 dark:border-gray-700"
>
<span class="text-gray-600 dark:text-gray-400">描述:</span>
<div
class="mt-1 max-h-32 overflow-y-auto break-words rounded bg-gray-100 p-2 text-xs dark:bg-gray-800"
>
{{ item.description }}
</div>
</div>
</div>
</ElPopover>
</div>
</template>
<!-- Modal 组件 -->
<template #modal>
<PostFormModal ref="postFormModalRef" @success="onPostFormSuccess" />
</template>
</CardList>
</template>
<style scoped>
/* 输入框前置图标样式 */
:deep(.el-input__icon) {
cursor: pointer;
}
/* 文本按钮样式 */
:deep(.el-button--text) {
padding: 0 4px;
}
:deep(.el-popover__reference) {
padding: 0;
}
</style>