Files
cn-rdms-web/src/views/system/post/index.vue
dk c5911ea34b feat(user): 用户页面搜索区新增用户昵称搜索框
- 在用户组件中将用户名搜索框替换为用户昵称搜索框。
- 给岗位设置最小宽度,防止内容缩起来。
fix(user-management-relation): 管理链路树显示问题
- 当组织类型不为公司时(为部门、方向、团队),为防止根节点变更导致下面的子节点出现紊乱,此时隐藏编辑按钮。
- 把用margin扩充宽度改为用min-width。
fix(role): 选中某角色后的操作列的透明显示问题
- 选中某角色后,当页面分辨率不够时(出现滚动条),创建时间等字段的内容会透过操作列
fix(post): 岗位的名称、编码字段的显示问题
- 给这两个字段设置最小宽度,防止它们缩起来。
2026-04-22 14:34:26 +08:00

320 lines
8.6 KiB
Vue

<script setup lang="tsx">
import { nextTick, reactive, ref } from 'vue';
import type { TableInstance } from 'element-plus';
import { ElButton, ElPopconfirm, ElTag } from 'element-plus';
import dayjs from 'dayjs';
import { useBoolean } from '@sa/hooks';
import { fetchBatchDeletePost, fetchDeletePost, fetchGetPostPage } from '@/service/api';
import { useUIPaginatedTable } from '@/hooks/common/table';
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
import PostOperateDialog from './modules/post-operate-dialog.vue';
import PostSearch from './modules/post-search.vue';
defineOptions({ name: 'PostManage' });
function getInitSearchParams(): Api.SystemManage.PostSearchParams {
return {
pageNo: 1,
pageSize: 10,
name: undefined,
code: undefined,
postType: undefined,
status: undefined
};
}
function transformPageResult(response: Awaited<ReturnType<typeof fetchGetPostPage>>, pageNo: number, pageSize: number) {
if (!response.error) {
return {
data: response.data.list,
pageNum: pageNo,
pageSize,
total: response.data.total
};
}
return {
data: [],
pageNum: pageNo,
pageSize,
total: 0
};
}
function formatTime(value?: number | null) {
if (!value) {
return '--';
}
return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
}
function getStatusTagType(status: Api.SystemManage.CommonStatus): UI.ThemeColor {
return status === 0 ? 'success' : 'warning';
}
function getStatusLabel(status: Api.SystemManage.CommonStatus) {
return status === 0 ? '启用' : '停用';
}
function getPostTypeLabel(type?: Api.SystemManage.PostType | null) {
if (!type) {
return '--';
}
const postTypeLabelMap: Record<Api.SystemManage.PostType, string> = {
management: '管理岗',
technical: '技术岗',
business: '业务岗'
};
return postTypeLabelMap[type];
}
const searchParams = reactive(getInitSearchParams());
const postTableRef = ref<TableInstance>();
const postCheckedRowKeys = ref<number[]>([]);
const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useUIPaginatedTable({
paginationProps: {
currentPage: searchParams.pageNo,
pageSize: searchParams.pageSize
},
api: () => fetchGetPostPage(searchParams),
transform: response => transformPageResult(response, searchParams.pageNo, searchParams.pageSize),
onPaginationParamsChange: params => {
searchParams.pageNo = params.currentPage ?? 1;
searchParams.pageSize = params.pageSize ?? 10;
},
columns: () => [
{ prop: 'selection', type: 'selection', width: 48 },
{ prop: 'index', type: 'index', label: '序号', width: 64 },
{ prop: 'name', label: '岗位名称', minWidth: 200, showOverflowTooltip: true },
{ prop: 'code', label: '岗位编码', minWidth: 330, showOverflowTooltip: true },
{
prop: 'postType',
label: '岗位类型',
width: 120,
align: 'center',
formatter: row => getPostTypeLabel(row.postType)
},
{
prop: 'status',
label: '岗位状态',
width: 110,
align: 'center',
formatter: row => <ElTag type={getStatusTagType(row.status)}>{getStatusLabel(row.status)}</ElTag>
},
{
prop: 'levelRank',
label: '岗位职级',
width: 100,
align: 'center',
formatter: row => String(row.levelRank ?? '--')
},
{ prop: 'sort', label: '排序', width: 90, align: 'center' },
{
prop: 'remark',
label: '备注',
minWidth: 180,
showOverflowTooltip: true,
formatter: row => row.remark || '--'
},
{
prop: 'createTime',
label: '创建时间',
minWidth: 170,
formatter: row => formatTime(row.createTime)
},
{
prop: 'operate',
label: '操作',
width: 196,
align: 'center',
fixed: 'right',
formatter: row => (
<BusinessTableActionCell
actions={[
{
key: 'edit',
label: '编辑',
buttonType: 'primary',
onClick: () => openEdit(row)
},
{
key: 'delete',
label: '删除',
buttonType: 'danger',
onClick: () => handleDeleteAction(row)
}
]}
/>
)
}
]
});
const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean();
const operateType = ref<UI.TableOperateType>('add');
const editingData = ref<Api.SystemManage.Post | null>(null);
function openAdd() {
operateType.value = 'add';
editingData.value = null;
openOperateModal();
}
function openEdit(item: Api.SystemManage.Post) {
operateType.value = 'edit';
editingData.value = item;
openOperateModal();
}
async function handleDelete(item: Api.SystemManage.Post) {
const { error } = await fetchDeletePost(item.id);
if (error) {
return;
}
window.$message?.success('删除成功');
await reloadPostTable();
}
async function handleDeleteAction(row: Api.SystemManage.Post) {
try {
await window.$messageBox?.confirm('确认删除当前岗位吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
await handleDelete(row);
}
async function handleBatchDelete() {
if (!postCheckedRowKeys.value.length) {
return;
}
const { error } = await fetchBatchDeletePost(postCheckedRowKeys.value);
if (error) {
return;
}
window.$message?.success('删除成功');
await reloadPostTable();
}
function handlePostSelectionChange(rows: Api.SystemManage.Post[]) {
postCheckedRowKeys.value = rows.map(item => item.id);
}
async function reloadPostTable(page = searchParams.pageNo) {
postCheckedRowKeys.value = [];
await getDataByPage(page);
await nextTick();
postTableRef.value?.clearSelection();
}
function resetSearchParams() {
Object.assign(searchParams, getInitSearchParams());
reloadPostTable(1);
}
function handleSearch() {
reloadPostTable(1);
}
function handleSubmitted(postId: number) {
closeOperateModal();
reloadPostTable();
}
</script>
<template>
<div class="flex-col-stretch gap-16px overflow-hidden">
<PostSearch v-model:model="searchParams" @reset="resetSearchParams" @search="handleSearch" />
<ElCard class="flex-1-hidden card-wrapper" body-class="post-table-card-body">
<template #header>
<div class="flex items-center justify-between gap-12px">
<div class="flex items-center gap-10px">
<p>岗位列表</p>
<ElTag effect="plain">{{ mobilePagination.total || data.length }}</ElTag>
</div>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="true"
:loading="loading"
@refresh="reloadPostTable"
>
<template #default>
<ElButton plain type="primary" @click="openAdd">
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>
新增
</ElButton>
<ElPopconfirm title="确认删除选中的岗位吗?" @confirm="handleBatchDelete">
<template #reference>
<ElButton type="danger" plain :disabled="postCheckedRowKeys.length === 0">
<template #icon>
<icon-ic-round-delete class="text-icon" />
</template>
批量删除
</ElButton>
</template>
</ElPopconfirm>
</template>
</TableHeaderOperation>
</div>
</template>
<div class="flex-1">
<ElTable
ref="postTableRef"
v-loading="loading"
height="100%"
border
row-key="id"
:data="data"
@selection-change="handlePostSelectionChange"
>
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col" />
</ElTable>
</div>
<div class="mt-20px flex justify-end">
<ElPagination
v-if="mobilePagination.total"
layout="total,prev,pager,next,sizes"
v-bind="mobilePagination"
@current-change="mobilePagination['current-change']"
@size-change="mobilePagination['size-change']"
/>
</div>
</ElCard>
<PostOperateDialog
v-model:visible="operateVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="handleSubmitted"
/>
</div>
</template>
<style lang="scss" scoped>
:deep(.post-table-card-body) {
height: calc(100% - 56px);
display: flex;
flex-direction: column;
}
</style>