2026-05-15 09:31:00 +08:00
|
|
|
<script setup lang="tsx">
|
|
|
|
|
import { computed, nextTick, onActivated, reactive, ref } from 'vue';
|
|
|
|
|
import type { TableInstance } from 'element-plus';
|
|
|
|
|
import { ElTag } from 'element-plus';
|
|
|
|
|
import { useBoolean } from '@sa/hooks';
|
|
|
|
|
import { OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
|
|
|
|
import {
|
|
|
|
|
fetchBatchDeleteObjectStatusModel,
|
|
|
|
|
fetchDeleteObjectStatusModel,
|
|
|
|
|
fetchGetObjectStatusModelPage
|
|
|
|
|
} from '@/service/api';
|
|
|
|
|
import { useAuth } from '@/hooks/business/auth';
|
|
|
|
|
import { useDict } from '@/hooks/business/dict';
|
|
|
|
|
import { useUIPaginatedTable } from '@/hooks/common/table';
|
|
|
|
|
import BusinessTableActionCell, { type BusinessTableAction } from '@/components/custom/business-table-action-cell';
|
|
|
|
|
import StateMachineOperateDialog from './modules/state-machine-operate-dialog.vue';
|
|
|
|
|
import StateMachineSearch from './modules/state-machine-search.vue';
|
|
|
|
|
import StateTransitionDialog from './modules/state-transition-dialog.vue';
|
|
|
|
|
import { formatDateTime, getBooleanLabel, getBooleanTagType, getStatusLabel, getStatusTagType } from './shared';
|
2026-05-22 10:46:46 +08:00
|
|
|
import IconMdiDeleteOutline from '~icons/mdi/delete-outline';
|
|
|
|
|
import IconMdiPencilOutline from '~icons/mdi/pencil-outline';
|
|
|
|
|
import IconMdiSourceBranch from '~icons/mdi/source-branch';
|
2026-05-15 09:31:00 +08:00
|
|
|
|
|
|
|
|
defineOptions({ name: 'StateMachineManage' });
|
|
|
|
|
|
|
|
|
|
function getInitSearchParams(): Api.Infra.ObjectStatusModelSearchParams {
|
|
|
|
|
return {
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
keyword: undefined,
|
|
|
|
|
objectType: undefined,
|
|
|
|
|
status: undefined,
|
|
|
|
|
initialFlag: undefined,
|
|
|
|
|
terminalFlag: undefined
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function transformPageResult(
|
|
|
|
|
response: Awaited<ReturnType<typeof fetchGetObjectStatusModelPage>>,
|
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const searchParams = reactive(getInitSearchParams());
|
|
|
|
|
const stateTableRef = ref<TableInstance>();
|
|
|
|
|
const checkedRowKeys = ref<string[]>([]);
|
|
|
|
|
const { getLabel: getObjectTypeLabel } = useDict(OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE);
|
|
|
|
|
const { hasAuth } = useAuth();
|
|
|
|
|
|
|
|
|
|
const canDeleteStateMachine = computed(() => hasAuth('infra:state-machine:delete'));
|
|
|
|
|
const canUpdateStateMachine = computed(() => hasAuth('infra:state-machine:update'));
|
|
|
|
|
const canManageStateTransition = computed(() => hasAuth('infra:state-transition:manage'));
|
|
|
|
|
|
|
|
|
|
function getStatusModelActions(row: Api.Infra.ObjectStatusModel): BusinessTableAction[] {
|
|
|
|
|
const actions: BusinessTableAction[] = [];
|
|
|
|
|
|
|
|
|
|
if (canManageStateTransition.value) {
|
|
|
|
|
actions.push({
|
|
|
|
|
key: 'transition',
|
|
|
|
|
label: '状态流转',
|
2026-05-22 10:46:46 +08:00
|
|
|
icon: IconMdiSourceBranch,
|
2026-05-15 09:31:00 +08:00
|
|
|
buttonType: 'primary',
|
|
|
|
|
onClick: () => openTransitionDialog(row)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (canUpdateStateMachine.value) {
|
|
|
|
|
actions.push({
|
|
|
|
|
key: 'edit',
|
|
|
|
|
label: '编辑',
|
2026-05-22 10:46:46 +08:00
|
|
|
icon: IconMdiPencilOutline,
|
2026-05-15 09:31:00 +08:00
|
|
|
buttonType: 'primary',
|
|
|
|
|
onClick: () => openEdit(row)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (canDeleteStateMachine.value) {
|
|
|
|
|
actions.push({
|
|
|
|
|
key: 'delete',
|
|
|
|
|
label: '删除',
|
2026-05-22 10:46:46 +08:00
|
|
|
icon: IconMdiDeleteOutline,
|
2026-05-15 09:31:00 +08:00
|
|
|
buttonType: 'danger',
|
|
|
|
|
onClick: () => handleDeleteAction(row)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return actions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useUIPaginatedTable({
|
|
|
|
|
paginationProps: {
|
|
|
|
|
currentPage: searchParams.pageNo,
|
|
|
|
|
pageSize: searchParams.pageSize
|
|
|
|
|
},
|
|
|
|
|
api: () => fetchGetObjectStatusModelPage(searchParams),
|
|
|
|
|
transform: response => transformPageResult(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 10),
|
|
|
|
|
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: 'objectType',
|
|
|
|
|
label: '对象类型',
|
|
|
|
|
minWidth: 130,
|
|
|
|
|
formatter: row => getObjectTypeLabel(row.objectType)
|
|
|
|
|
},
|
|
|
|
|
{ prop: 'statusName', label: '状态名称', minWidth: 140, showOverflowTooltip: true },
|
|
|
|
|
{ prop: 'statusCode', label: '状态编码', minWidth: 160, showOverflowTooltip: true },
|
|
|
|
|
{
|
|
|
|
|
prop: 'status',
|
|
|
|
|
label: '配置状态',
|
|
|
|
|
width: 110,
|
|
|
|
|
align: 'center',
|
|
|
|
|
formatter: row => <ElTag type={getStatusTagType(row.status)}>{getStatusLabel(row.status)}</ElTag>
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'initialFlag',
|
|
|
|
|
label: '初始状态',
|
|
|
|
|
width: 110,
|
|
|
|
|
align: 'center',
|
|
|
|
|
formatter: row => <ElTag type={getBooleanTagType(row.initialFlag)}>{getBooleanLabel(row.initialFlag)}</ElTag>
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'terminalFlag',
|
|
|
|
|
label: '终态',
|
|
|
|
|
width: 90,
|
|
|
|
|
align: 'center',
|
|
|
|
|
formatter: row => <ElTag type={getBooleanTagType(row.terminalFlag)}>{getBooleanLabel(row.terminalFlag)}</ElTag>
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'allowEdit',
|
|
|
|
|
label: '允许编辑主数据',
|
|
|
|
|
width: 140,
|
|
|
|
|
align: 'center',
|
|
|
|
|
formatter: row => <ElTag type={getBooleanTagType(row.allowEdit)}>{getBooleanLabel(row.allowEdit)}</ElTag>
|
|
|
|
|
},
|
2026-05-18 14:57:48 +08:00
|
|
|
// {
|
|
|
|
|
// prop: 'progressExcludedFlag',
|
|
|
|
|
// label: '不参与上层进度统计',
|
|
|
|
|
// width: 160,
|
|
|
|
|
// align: 'center',
|
|
|
|
|
// formatter: row => (
|
|
|
|
|
// <ElTag type={getBooleanTagType(row.progressExcludedFlag)}>{getBooleanLabel(row.progressExcludedFlag)}</ElTag>
|
|
|
|
|
// )
|
|
|
|
|
// },
|
|
|
|
|
// {
|
|
|
|
|
// prop: 'allowCreateProject',
|
|
|
|
|
// label: '允许新建项目',
|
|
|
|
|
// width: 130,
|
|
|
|
|
// align: 'center',
|
|
|
|
|
// formatter: row => (
|
|
|
|
|
// <ElTag type={getBooleanTagType(row.allowCreateProject)}>{getBooleanLabel(row.allowCreateProject)}</ElTag>
|
|
|
|
|
// )
|
|
|
|
|
// },
|
|
|
|
|
// {
|
|
|
|
|
// prop: 'allowCreateRequirement',
|
|
|
|
|
// label: '允许新增需求',
|
|
|
|
|
// width: 130,
|
|
|
|
|
// align: 'center',
|
|
|
|
|
// formatter: row => (
|
|
|
|
|
// <ElTag type={getBooleanTagType(row.allowCreateRequirement)}>
|
|
|
|
|
// {getBooleanLabel(row.allowCreateRequirement)}
|
|
|
|
|
// </ElTag>
|
|
|
|
|
// )
|
|
|
|
|
// },
|
2026-05-15 09:31:00 +08:00
|
|
|
{ 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 => formatDateTime(row.createTime)
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prop: 'operate',
|
|
|
|
|
label: '操作',
|
|
|
|
|
width: 220,
|
|
|
|
|
align: 'center',
|
|
|
|
|
fixed: 'right',
|
|
|
|
|
formatter: row => {
|
|
|
|
|
const actions = getStatusModelActions(row);
|
|
|
|
|
|
|
|
|
|
if (!actions.length) {
|
|
|
|
|
return <span>--</span>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 10:46:46 +08:00
|
|
|
return <BusinessTableActionCell actions={actions} variant="icon" />;
|
2026-05-15 09:31:00 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean();
|
|
|
|
|
const operateType = ref<UI.TableOperateType>('add');
|
|
|
|
|
const editingData = ref<Api.Infra.ObjectStatusModel | null>(null);
|
|
|
|
|
|
|
|
|
|
const { bool: transitionVisible, setTrue: openTransitionModal, setFalse: closeTransitionModal } = useBoolean();
|
|
|
|
|
const transitionRow = ref<Api.Infra.ObjectStatusModel | null>(null);
|
|
|
|
|
|
|
|
|
|
function openAdd() {
|
|
|
|
|
operateType.value = 'add';
|
|
|
|
|
editingData.value = null;
|
|
|
|
|
openOperateModal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openEdit(item: Api.Infra.ObjectStatusModel) {
|
|
|
|
|
operateType.value = 'edit';
|
|
|
|
|
editingData.value = item;
|
|
|
|
|
openOperateModal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openTransitionDialog(item: Api.Infra.ObjectStatusModel) {
|
|
|
|
|
transitionRow.value = item;
|
|
|
|
|
openTransitionModal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleDelete(item: Api.Infra.ObjectStatusModel) {
|
|
|
|
|
const { error } = await fetchDeleteObjectStatusModel(item.id);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.$message?.success('删除成功');
|
|
|
|
|
await reloadStatusTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleDeleteAction(row: Api.Infra.ObjectStatusModel) {
|
|
|
|
|
try {
|
|
|
|
|
await window.$messageBox?.confirm('确认删除当前状态模型吗?', '警告', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await handleDelete(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleBatchDelete() {
|
|
|
|
|
if (!checkedRowKeys.value.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { error } = await fetchBatchDeleteObjectStatusModel(checkedRowKeys.value);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.$message?.success('删除成功');
|
|
|
|
|
await reloadStatusTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSelectionChange(rows: Api.Infra.ObjectStatusModel[]) {
|
|
|
|
|
checkedRowKeys.value = rows.map(item => item.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function reloadStatusTable(page = searchParams.pageNo) {
|
|
|
|
|
checkedRowKeys.value = [];
|
|
|
|
|
await getDataByPage(page);
|
|
|
|
|
await nextTick();
|
|
|
|
|
stateTableRef.value?.clearSelection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetSearchParams() {
|
|
|
|
|
Object.assign(searchParams, getInitSearchParams());
|
|
|
|
|
reloadStatusTable(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSearch() {
|
|
|
|
|
reloadStatusTable(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSubmitted() {
|
|
|
|
|
closeOperateModal();
|
|
|
|
|
reloadStatusTable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onActivated(() => {
|
|
|
|
|
resetSearchParams();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
<template>
|
2026-05-15 09:31:00 +08:00
|
|
|
<div class="flex-col-stretch gap-16px overflow-hidden">
|
|
|
|
|
<StateMachineSearch v-model:model="searchParams" @reset="resetSearchParams" @search="handleSearch" />
|
|
|
|
|
|
|
|
|
|
<ElCard class="flex-1-hidden card-wrapper" body-class="business-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="checkedRowKeys.length === 0"
|
|
|
|
|
:loading="loading"
|
|
|
|
|
@refresh="getData"
|
|
|
|
|
>
|
|
|
|
|
<template #default>
|
|
|
|
|
<ElButton v-auth="'infra:state-machine:create'" plain type="primary" @click="openAdd">
|
|
|
|
|
<template #icon>
|
|
|
|
|
<icon-ic-round-plus class="text-icon" />
|
|
|
|
|
</template>
|
|
|
|
|
新增
|
|
|
|
|
</ElButton>
|
|
|
|
|
<ElPopconfirm
|
|
|
|
|
v-if="canDeleteStateMachine"
|
|
|
|
|
title="确认删除选中的状态模型吗?"
|
|
|
|
|
@confirm="handleBatchDelete"
|
|
|
|
|
>
|
|
|
|
|
<template #reference>
|
|
|
|
|
<ElButton type="danger" plain :disabled="checkedRowKeys.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="stateTableRef"
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
height="100%"
|
|
|
|
|
border
|
|
|
|
|
row-key="id"
|
|
|
|
|
:data="data"
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
>
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
<StateMachineOperateDialog
|
|
|
|
|
v-model:visible="operateVisible"
|
|
|
|
|
:operate-type="operateType"
|
|
|
|
|
:row-data="editingData"
|
|
|
|
|
@submitted="handleSubmitted"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<StateTransitionDialog
|
|
|
|
|
v-model:visible="transitionVisible"
|
|
|
|
|
:current-status="transitionRow"
|
|
|
|
|
@update:visible="value => !value && closeTransitionModal()"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-05-14 09:05:08 +08:00
|
|
|
</template>
|
2026-05-15 09:31:00 +08:00
|
|
|
|
|
|
|
|
<style scoped></style>
|