feat(infra): 新增状态机管理功能模块
- 新增状态机模型和状态流转的完整 CRUD 功能 - 添加字典编码 OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE 用于对象类型下拉选择 - 实现状态机列表页、搜索组件、操作对话框和状态流转管理 - 新增 infra API 接口封装和类型定义 - 遵循项目规范:使用 TableSearchFields 搜索组件、BusinessTableActionCell 操作列、统一的状态标签展示 涉及文件: - src/constants/dict.ts: 新增对象类型字典编码 - src/service/api/infra.ts: 新增状态机和状态流转相关 API - src/typings/api/infra.d.ts: 新增状态机相关类型定义 - src/views/infra/state-machine/: 新增状态机管理页面及子组件
This commit is contained in:
@@ -76,6 +76,14 @@ export const RDMS_PROJECT_TYPE_DICT_CODE = 'rdms_project_type';
|
||||
*/
|
||||
export const RDMS_PROJECT_EXECUTION_TYPE_DICT_CODE = 'rdms_project_execution_type';
|
||||
|
||||
/**
|
||||
* 状态机对象类型字典编码
|
||||
*
|
||||
* 对应业务字段:状态机管理中的 objectType / 对象类型
|
||||
* 来源口径:用户明确指定对象类型下拉来自运行时字典 object_status_model_object_type
|
||||
*/
|
||||
export const OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE = 'object_status_model_object_type';
|
||||
|
||||
/**
|
||||
* 需求允许删除的状态字典编码
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './auth';
|
||||
export * from './dict';
|
||||
export * from './file';
|
||||
export * from './infra';
|
||||
export * from './object-context';
|
||||
export * from './product';
|
||||
export * from './project';
|
||||
|
||||
208
src/service/api/infra.ts
Normal file
208
src/service/api/infra.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { WEB_SERVICE_PREFIX } from '@/constants/service';
|
||||
import { request } from '../request';
|
||||
import { type ServiceRequestResult, mapServiceResult, normalizeStringId, safeJsonRequestConfig } from './shared';
|
||||
|
||||
const OBJECT_STATUS_MODEL_PREFIX = `${WEB_SERVICE_PREFIX}/project/status/model`;
|
||||
const OBJECT_STATUS_TRANSITION_PREFIX = `${WEB_SERVICE_PREFIX}/project/status/transition`;
|
||||
|
||||
type ObjectStatusModelResponse = Omit<
|
||||
Api.Infra.ObjectStatusModel,
|
||||
| 'id'
|
||||
| 'initialFlag'
|
||||
| 'terminalFlag'
|
||||
| 'allowEdit'
|
||||
| 'progressExcludedFlag'
|
||||
| 'allowCreateProject'
|
||||
| 'allowCreateRequirement'
|
||||
> & {
|
||||
id: string | number;
|
||||
initialFlag: boolean | number | string | null | undefined;
|
||||
terminalFlag: boolean | number | string | null | undefined;
|
||||
allowEdit: boolean | number | string | null | undefined;
|
||||
progressExcludedFlag: boolean | number | string | null | undefined;
|
||||
allowCreateProject: boolean | number | string | null | undefined;
|
||||
allowCreateRequirement: boolean | number | string | null | undefined;
|
||||
};
|
||||
|
||||
type ObjectStatusTransitionResponse = Omit<Api.Infra.ObjectStatusTransition, 'id' | 'needReason'> & {
|
||||
id: string | number;
|
||||
needReason: boolean | number | string | null | undefined;
|
||||
};
|
||||
|
||||
type ObjectStatusModelPageResponse = Api.Infra.PageResult<ObjectStatusModelResponse>;
|
||||
|
||||
type ObjectStatusTransitionPageResponse = Api.Infra.PageResult<ObjectStatusTransitionResponse>;
|
||||
|
||||
function createBatchDeleteQuery(ids: string[]) {
|
||||
const query = new URLSearchParams();
|
||||
|
||||
ids.forEach(id => {
|
||||
query.append('ids', id);
|
||||
});
|
||||
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
function normalizeBooleanFlag(value: boolean | number | string | null | undefined) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value === 1;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
|
||||
if (!normalized || normalized === '0' || normalized === 'false' || normalized === 'n') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeObjectStatusModel(model: ObjectStatusModelResponse): Api.Infra.ObjectStatusModel {
|
||||
return {
|
||||
...model,
|
||||
id: normalizeStringId(model.id),
|
||||
initialFlag: normalizeBooleanFlag(model.initialFlag),
|
||||
terminalFlag: normalizeBooleanFlag(model.terminalFlag),
|
||||
allowEdit: normalizeBooleanFlag(model.allowEdit),
|
||||
progressExcludedFlag: normalizeBooleanFlag(model.progressExcludedFlag),
|
||||
allowCreateProject: normalizeBooleanFlag(model.allowCreateProject),
|
||||
allowCreateRequirement: normalizeBooleanFlag(model.allowCreateRequirement)
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeObjectStatusTransition(transition: ObjectStatusTransitionResponse): Api.Infra.ObjectStatusTransition {
|
||||
return {
|
||||
...transition,
|
||||
id: normalizeStringId(transition.id),
|
||||
needReason: normalizeBooleanFlag(transition.needReason)
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchGetObjectStatusModelPage(params?: Api.Infra.ObjectStatusModelSearchParams) {
|
||||
const result = await request<ObjectStatusModelPageResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/page`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<ObjectStatusModelPageResponse>, data => ({
|
||||
...data,
|
||||
list: data.list.map(normalizeObjectStatusModel)
|
||||
}));
|
||||
}
|
||||
|
||||
export async function fetchGetObjectStatusModel(id: string) {
|
||||
const result = await request<ObjectStatusModelResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/get`,
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<ObjectStatusModelResponse>, normalizeObjectStatusModel);
|
||||
}
|
||||
|
||||
export async function fetchCreateObjectStatusModel(data: Api.Infra.SaveObjectStatusModelParams) {
|
||||
const result = await request<string | number>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/create`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
|
||||
}
|
||||
|
||||
export function fetchUpdateObjectStatusModel(data: { id: string } & Api.Infra.SaveObjectStatusModelParams) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/update`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDeleteObjectStatusModel(id: string) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/delete`,
|
||||
method: 'delete',
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchBatchDeleteObjectStatusModel(ids: string[]) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_MODEL_PREFIX}/delete-list?${createBatchDeleteQuery(ids)}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchGetObjectStatusTransitionPage(params?: Api.Infra.ObjectStatusTransitionSearchParams) {
|
||||
const result = await request<ObjectStatusTransitionPageResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/page`,
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<ObjectStatusTransitionPageResponse>, data => ({
|
||||
...data,
|
||||
list: data.list.map(normalizeObjectStatusTransition)
|
||||
}));
|
||||
}
|
||||
|
||||
export async function fetchGetObjectStatusTransition(id: string) {
|
||||
const result = await request<ObjectStatusTransitionResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/get`,
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
|
||||
return mapServiceResult(
|
||||
result as ServiceRequestResult<ObjectStatusTransitionResponse>,
|
||||
normalizeObjectStatusTransition
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchCreateObjectStatusTransition(data: Api.Infra.SaveObjectStatusTransitionParams) {
|
||||
const result = await request<string | number>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/create`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
|
||||
}
|
||||
|
||||
export function fetchUpdateObjectStatusTransition(data: { id: string } & Api.Infra.SaveObjectStatusTransitionParams) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/update`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDeleteObjectStatusTransition(id: string) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/delete`,
|
||||
method: 'delete',
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchBatchDeleteObjectStatusTransition(ids: string[]) {
|
||||
return request<boolean>({
|
||||
url: `${OBJECT_STATUS_TRANSITION_PREFIX}/delete-list?${createBatchDeleteQuery(ids)}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
101
src/typings/api/infra.d.ts
vendored
Normal file
101
src/typings/api/infra.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
declare namespace Api {
|
||||
/**
|
||||
* namespace Infra
|
||||
*
|
||||
* backend api module: "project/status/*"
|
||||
*/
|
||||
namespace Infra {
|
||||
type CommonStatus = 0 | 1;
|
||||
|
||||
interface PageParams {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
interface PageResult<T = any> {
|
||||
total: number;
|
||||
list: T[];
|
||||
}
|
||||
|
||||
interface ObjectStatusModel {
|
||||
id: string;
|
||||
objectType: string;
|
||||
statusCode: string;
|
||||
statusName: string;
|
||||
sort: number;
|
||||
status: CommonStatus;
|
||||
initialFlag: boolean;
|
||||
terminalFlag: boolean;
|
||||
allowEdit: boolean;
|
||||
progressExcludedFlag: boolean;
|
||||
allowCreateProject: boolean;
|
||||
allowCreateRequirement: boolean;
|
||||
remark?: string | null;
|
||||
creator?: string | null;
|
||||
createTime: string;
|
||||
updater?: string | null;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
type ObjectStatusModelSearchParams = CommonType.RecordNullable<
|
||||
Pick<PageParams, 'pageNo' | 'pageSize'> &
|
||||
Pick<ObjectStatusModel, 'objectType' | 'status' | 'initialFlag' | 'terminalFlag'> & {
|
||||
keyword?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
type SaveObjectStatusModelParams = Pick<
|
||||
ObjectStatusModel,
|
||||
| 'objectType'
|
||||
| 'statusCode'
|
||||
| 'statusName'
|
||||
| 'sort'
|
||||
| 'status'
|
||||
| 'initialFlag'
|
||||
| 'terminalFlag'
|
||||
| 'allowEdit'
|
||||
| 'progressExcludedFlag'
|
||||
| 'allowCreateProject'
|
||||
| 'allowCreateRequirement'
|
||||
> & {
|
||||
remark?: string | null;
|
||||
};
|
||||
|
||||
type ObjectStatusModelList = PageResult<ObjectStatusModel>;
|
||||
|
||||
interface ObjectStatusTransition {
|
||||
id: string;
|
||||
objectType: string;
|
||||
actionCode: string;
|
||||
actionName: string;
|
||||
fromStatusCode: string;
|
||||
fromStatusName?: string | null;
|
||||
toStatusCode: string;
|
||||
toStatusName?: string | null;
|
||||
needReason: boolean;
|
||||
status: CommonStatus;
|
||||
remark?: string | null;
|
||||
creator?: string | null;
|
||||
createTime: string;
|
||||
updater?: string | null;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
type ObjectStatusTransitionSearchParams = CommonType.RecordNullable<
|
||||
Pick<PageParams, 'pageNo' | 'pageSize'> &
|
||||
Pick<
|
||||
ObjectStatusTransition,
|
||||
'objectType' | 'fromStatusCode' | 'toStatusCode' | 'status' | 'actionCode' | 'actionName'
|
||||
>
|
||||
>;
|
||||
|
||||
type SaveObjectStatusTransitionParams = Pick<
|
||||
ObjectStatusTransition,
|
||||
'objectType' | 'actionCode' | 'actionName' | 'fromStatusCode' | 'toStatusCode' | 'needReason' | 'status'
|
||||
> & {
|
||||
remark?: string | null;
|
||||
};
|
||||
|
||||
type ObjectStatusTransitionList = PageResult<ObjectStatusTransition>;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,389 @@
|
||||
<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';
|
||||
|
||||
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: '状态流转',
|
||||
buttonType: 'primary',
|
||||
onClick: () => openTransitionDialog(row)
|
||||
});
|
||||
}
|
||||
|
||||
if (canUpdateStateMachine.value) {
|
||||
actions.push({
|
||||
key: 'edit',
|
||||
label: '编辑',
|
||||
buttonType: 'primary',
|
||||
onClick: () => openEdit(row)
|
||||
});
|
||||
}
|
||||
|
||||
if (canDeleteStateMachine.value) {
|
||||
actions.push({
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
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>
|
||||
},
|
||||
{
|
||||
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>
|
||||
)
|
||||
},
|
||||
{ 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>;
|
||||
}
|
||||
|
||||
return <BusinessTableActionCell actions={actions} />;
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<LookForward title="状态机管理" subtitle="功能建设中,敬请期待" />
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import { fetchCreateObjectStatusModel, fetchGetObjectStatusModel, fetchUpdateObjectStatusModel } from '@/service/api';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import { statusOptions } from '../shared';
|
||||
|
||||
defineOptions({ name: 'StateMachineOperateDialog' });
|
||||
|
||||
interface Props {
|
||||
operateType: UI.TableOperateType;
|
||||
rowData?: Api.Infra.ObjectStatusModel | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
submitted: [statusModelId: string];
|
||||
}>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate } = useForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
const { dictOptions: objectTypeOptions } = useDict(OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE);
|
||||
|
||||
const detailLoading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const isEdit = computed(() => props.operateType === 'edit');
|
||||
|
||||
const title = computed(() => {
|
||||
const titleMap: Record<UI.TableOperateType, string> = {
|
||||
add: '新增状态模型',
|
||||
edit: '编辑状态模型'
|
||||
};
|
||||
|
||||
return titleMap[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Infra.SaveObjectStatusModelParams;
|
||||
|
||||
const model = ref(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
objectType: 'product',
|
||||
statusCode: '',
|
||||
statusName: '',
|
||||
sort: 0,
|
||||
status: 0,
|
||||
initialFlag: false,
|
||||
terminalFlag: false,
|
||||
allowEdit: false,
|
||||
progressExcludedFlag: false,
|
||||
allowCreateProject: false,
|
||||
allowCreateRequirement: false,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
const rules = {
|
||||
objectType: createRequiredRule('请选择对象类型'),
|
||||
statusCode: createRequiredRule('请输入状态编码'),
|
||||
statusName: createRequiredRule('请输入状态名称'),
|
||||
sort: createRequiredRule('请输入排序值'),
|
||||
status: createRequiredRule('请选择配置状态')
|
||||
} satisfies Record<string, App.Global.FormRule>;
|
||||
|
||||
function closeModal() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function initModel() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (!isEdit.value || !props.rowData) {
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
return;
|
||||
}
|
||||
|
||||
detailLoading.value = true;
|
||||
|
||||
const { error, data } = await fetchGetObjectStatusModel(props.rowData.id);
|
||||
|
||||
detailLoading.value = false;
|
||||
|
||||
if (!error) {
|
||||
model.value = {
|
||||
objectType: data.objectType,
|
||||
statusCode: data.statusCode,
|
||||
statusName: data.statusName,
|
||||
sort: data.sort ?? 0,
|
||||
status: data.status,
|
||||
initialFlag: data.initialFlag,
|
||||
terminalFlag: data.terminalFlag,
|
||||
allowEdit: data.allowEdit,
|
||||
progressExcludedFlag: data.progressExcludedFlag,
|
||||
allowCreateProject: data.allowCreateProject,
|
||||
allowCreateRequirement: data.allowCreateRequirement,
|
||||
remark: data.remark ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
const submitData: Api.Infra.SaveObjectStatusModelParams = {
|
||||
...model.value,
|
||||
statusCode: model.value.statusCode.trim(),
|
||||
statusName: model.value.statusName.trim(),
|
||||
remark: model.value.remark?.trim() || null
|
||||
};
|
||||
|
||||
let statusModelId = props.rowData?.id ?? '';
|
||||
|
||||
if (isEdit.value && props.rowData) {
|
||||
const { error } = await fetchUpdateObjectStatusModel({ id: props.rowData.id, ...submitData });
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const { error, data } = await fetchCreateObjectStatusModel(submitData);
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
statusModelId = data;
|
||||
}
|
||||
|
||||
window.$message?.success(isEdit.value ? '修改成功' : '新增成功');
|
||||
|
||||
closeModal();
|
||||
emit('submitted', statusModelId);
|
||||
}
|
||||
|
||||
watch(visible, value => {
|
||||
if (value) {
|
||||
initModel();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BusinessFormDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
preset="lg"
|
||||
:loading="detailLoading"
|
||||
:confirm-loading="submitting"
|
||||
@confirm="handleSubmit"
|
||||
>
|
||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="对象类型" prop="objectType">
|
||||
<ElSelect
|
||||
v-model="model.objectType"
|
||||
class="w-full"
|
||||
placeholder="请选择或输入对象类型"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
clearable
|
||||
:reserve-keyword="false"
|
||||
>
|
||||
<ElOption v-for="item in objectTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="状态编码" prop="statusCode">
|
||||
<ElInput v-model="model.statusCode" placeholder="请输入状态编码" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="状态名称" prop="statusName">
|
||||
<ElInput v-model="model.statusName" placeholder="请输入状态名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="排序值" prop="sort">
|
||||
<ElInputNumber v-model="model.sort" class="w-full" :min="0" placeholder="请输入排序值" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="配置状态" prop="status">
|
||||
<ElRadioGroup v-model="model.status" class="business-form-radio-group">
|
||||
<ElRadio v-for="{ label, value } in statusOptions" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="是否初始状态" prop="initialFlag">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.initialFlag" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.initialFlag ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="是否终态" prop="terminalFlag">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.terminalFlag" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.terminalFlag ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="允许编辑主数据" prop="allowEdit">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.allowEdit" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.allowEdit ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="不参与上层进度统计" prop="progressExcludedFlag">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.progressExcludedFlag" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.progressExcludedFlag ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="允许新建项目" prop="allowCreateProject">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.allowCreateProject" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.allowCreateProject ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="允许新增需求" prop="allowCreateRequirement">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.allowCreateRequirement" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.allowCreateRequirement ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="model.remark" type="textarea" :rows="4" placeholder="请输入备注" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElForm>
|
||||
</BusinessFormDialog>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
142
src/views/infra/state-machine/modules/state-machine-search.vue
Normal file
142
src/views/infra/state-machine/modules/state-machine-search.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import TableSearchFields, { type SearchField } from '@/components/custom/table-search-fields.vue';
|
||||
import { statusOptions } from '../shared';
|
||||
|
||||
defineOptions({ name: 'StateMachineSearch' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
reset: [];
|
||||
search: [];
|
||||
}>();
|
||||
|
||||
const model = defineModel<Api.Infra.ObjectStatusModelSearchParams>('model', { required: true });
|
||||
|
||||
const booleanOptions = [
|
||||
{ label: '是', value: 1 },
|
||||
{ label: '否', value: 0 }
|
||||
];
|
||||
|
||||
const searchModel = reactive<{
|
||||
keyword: string;
|
||||
objectType?: string;
|
||||
status?: Api.Infra.CommonStatus;
|
||||
initialFlag?: number;
|
||||
terminalFlag?: number;
|
||||
}>({
|
||||
keyword: '',
|
||||
objectType: undefined,
|
||||
status: undefined,
|
||||
initialFlag: undefined,
|
||||
terminalFlag: undefined
|
||||
});
|
||||
|
||||
let syncingFromSource = false;
|
||||
|
||||
watch(
|
||||
() =>
|
||||
[
|
||||
model.value.keyword,
|
||||
model.value.objectType,
|
||||
model.value.status,
|
||||
model.value.initialFlag,
|
||||
model.value.terminalFlag
|
||||
] as const,
|
||||
([keyword, objectType, status, initialFlag, terminalFlag]) => {
|
||||
syncingFromSource = true;
|
||||
searchModel.keyword = keyword ?? '';
|
||||
searchModel.objectType = objectType;
|
||||
searchModel.status = status;
|
||||
|
||||
if (initialFlag === undefined) {
|
||||
searchModel.initialFlag = undefined;
|
||||
} else {
|
||||
searchModel.initialFlag = initialFlag ? 1 : 0;
|
||||
}
|
||||
|
||||
if (terminalFlag === undefined) {
|
||||
searchModel.terminalFlag = undefined;
|
||||
} else {
|
||||
searchModel.terminalFlag = terminalFlag ? 1 : 0;
|
||||
}
|
||||
|
||||
syncingFromSource = false;
|
||||
},
|
||||
{ immediate: true, flush: 'sync' }
|
||||
);
|
||||
|
||||
watch(
|
||||
() =>
|
||||
[
|
||||
searchModel.keyword,
|
||||
searchModel.objectType,
|
||||
searchModel.status,
|
||||
searchModel.initialFlag,
|
||||
searchModel.terminalFlag
|
||||
] as const,
|
||||
([keywordValue, objectType, status, initialFlag, terminalFlag]) => {
|
||||
if (syncingFromSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.value.keyword = keywordValue.trim() || undefined;
|
||||
model.value.objectType = objectType;
|
||||
model.value.status = status;
|
||||
model.value.initialFlag = initialFlag === undefined ? undefined : initialFlag === 1;
|
||||
model.value.terminalFlag = terminalFlag === undefined ? undefined : terminalFlag === 1;
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
const fields = computed<SearchField[]>(() => [
|
||||
{
|
||||
key: 'objectType',
|
||||
label: '对象类型',
|
||||
type: 'dict',
|
||||
placeholder: '请选择对象类型',
|
||||
dictCode: OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE
|
||||
},
|
||||
{
|
||||
key: 'keyword',
|
||||
label: '关键字',
|
||||
type: 'input',
|
||||
placeholder: '请输入状态名称或状态编码'
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: '配置状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择配置状态',
|
||||
options: statusOptions
|
||||
},
|
||||
{
|
||||
key: 'initialFlag',
|
||||
label: '初始状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择是否初始状态',
|
||||
options: booleanOptions
|
||||
},
|
||||
{
|
||||
key: 'terminalFlag',
|
||||
label: '终态',
|
||||
type: 'select',
|
||||
placeholder: '请选择是否终态',
|
||||
options: booleanOptions
|
||||
}
|
||||
]);
|
||||
|
||||
function reset() {
|
||||
emit('reset');
|
||||
}
|
||||
|
||||
function search() {
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableSearchFields v-model="searchModel" :fields="fields" :columns="4" @reset="reset" @search="search" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,406 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
import type { TableInstance } from 'element-plus';
|
||||
import { ElButton, ElTag } from 'element-plus';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import {
|
||||
fetchBatchDeleteObjectStatusTransition,
|
||||
fetchDeleteObjectStatusTransition,
|
||||
fetchGetObjectStatusModelPage,
|
||||
fetchGetObjectStatusTransitionPage
|
||||
} from '@/service/api';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { useUIPaginatedTable } from '@/hooks/common/table';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
|
||||
import { formatDateTime, getBooleanLabel, getBooleanTagType, getStatusLabel, getStatusTagType } from '../shared';
|
||||
import StateTransitionOperateDialog from './state-transition-operate-dialog.vue';
|
||||
import StateTransitionSearch from './state-transition-search.vue';
|
||||
|
||||
defineOptions({ name: 'StateTransitionDialog' });
|
||||
|
||||
interface Props {
|
||||
currentStatus?: Api.Infra.ObjectStatusModel | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
function getInitSearchParams(): Api.Infra.ObjectStatusTransitionSearchParams {
|
||||
return {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
objectType: props.currentStatus?.objectType,
|
||||
fromStatusCode: props.currentStatus?.statusCode,
|
||||
actionCode: undefined,
|
||||
actionName: undefined,
|
||||
toStatusCode: undefined,
|
||||
status: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function transformPageResult(
|
||||
response: Awaited<ReturnType<typeof fetchGetObjectStatusTransitionPage>>,
|
||||
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 transitionTableRef = ref<TableInstance>();
|
||||
const checkedRowKeys = ref<string[]>([]);
|
||||
const statusModelOptions = ref<Api.Infra.ObjectStatusModel[]>([]);
|
||||
const loadingOptions = ref(false);
|
||||
const { getLabel: getObjectTypeLabel } = useDict(OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE);
|
||||
|
||||
const targetStatusOptions = computed(() =>
|
||||
statusModelOptions.value.map(item => ({
|
||||
label: `${item.statusName} (${item.statusCode})`,
|
||||
value: item.statusCode
|
||||
}))
|
||||
);
|
||||
|
||||
const currentStatusLabel = computed(() => {
|
||||
if (!props.currentStatus) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
return `${props.currentStatus.statusName} (${props.currentStatus.statusCode})`;
|
||||
});
|
||||
|
||||
const currentObjectTypeLabel = computed(() => getObjectTypeLabel(props.currentStatus?.objectType));
|
||||
|
||||
const { columns, columnChecks, data, loading, getData, getDataByPage, mobilePagination } = useUIPaginatedTable({
|
||||
paginationProps: {
|
||||
currentPage: searchParams.pageNo,
|
||||
pageSize: searchParams.pageSize
|
||||
},
|
||||
api: () => fetchGetObjectStatusTransitionPage(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: 'actionName', label: '动作名称', minWidth: 150, showOverflowTooltip: true },
|
||||
{ prop: 'actionCode', label: '动作编码', minWidth: 150, showOverflowTooltip: true },
|
||||
{
|
||||
prop: 'toStatusCode',
|
||||
label: '目标状态',
|
||||
minWidth: 180,
|
||||
formatter: row => row.toStatusName?.trim() || row.toStatusCode
|
||||
},
|
||||
{
|
||||
prop: 'needReason',
|
||||
label: '必须填写原因',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
formatter: row => <ElTag type={getBooleanTagType(row.needReason)}>{getBooleanLabel(row.needReason)}</ElTag>
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '配置状态',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
formatter: row => <ElTag type={getStatusTagType(row.status)}>{getStatusLabel(row.status)}</ElTag>
|
||||
},
|
||||
{
|
||||
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: 180,
|
||||
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.Infra.ObjectStatusTransition | null>(null);
|
||||
|
||||
function openAdd() {
|
||||
operateType.value = 'add';
|
||||
editingData.value = null;
|
||||
openOperateModal();
|
||||
}
|
||||
|
||||
function openEdit(item: Api.Infra.ObjectStatusTransition) {
|
||||
operateType.value = 'edit';
|
||||
editingData.value = item;
|
||||
openOperateModal();
|
||||
}
|
||||
|
||||
async function handleDelete(item: Api.Infra.ObjectStatusTransition) {
|
||||
const { error } = await fetchDeleteObjectStatusTransition(item.id);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success('删除成功');
|
||||
await reloadTable();
|
||||
}
|
||||
|
||||
async function handleDeleteAction(row: Api.Infra.ObjectStatusTransition) {
|
||||
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 fetchBatchDeleteObjectStatusTransition(checkedRowKeys.value);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.success('删除成功');
|
||||
await reloadTable();
|
||||
}
|
||||
|
||||
function handleSelectionChange(rows: Api.Infra.ObjectStatusTransition[]) {
|
||||
checkedRowKeys.value = rows.map(item => item.id);
|
||||
}
|
||||
|
||||
async function reloadTable(page = searchParams.pageNo) {
|
||||
checkedRowKeys.value = [];
|
||||
await getDataByPage(page);
|
||||
await nextTick();
|
||||
transitionTableRef.value?.clearSelection();
|
||||
}
|
||||
|
||||
function resetSearchParams() {
|
||||
Object.assign(searchParams, getInitSearchParams());
|
||||
reloadTable(1);
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
reloadTable(1);
|
||||
}
|
||||
|
||||
function handleSubmitted() {
|
||||
closeOperateModal();
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
async function loadStatusModelOptions() {
|
||||
if (!props.currentStatus?.objectType) {
|
||||
statusModelOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
loadingOptions.value = true;
|
||||
|
||||
const { error, data: page } = await fetchGetObjectStatusModelPage({
|
||||
pageNo: 1,
|
||||
pageSize: 200,
|
||||
keyword: undefined,
|
||||
objectType: props.currentStatus.objectType,
|
||||
status: undefined,
|
||||
initialFlag: undefined,
|
||||
terminalFlag: undefined
|
||||
});
|
||||
|
||||
loadingOptions.value = false;
|
||||
|
||||
statusModelOptions.value = error ? [] : page.list;
|
||||
}
|
||||
|
||||
async function initDialog() {
|
||||
if (!props.currentStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(searchParams, getInitSearchParams(), {
|
||||
objectType: props.currentStatus.objectType,
|
||||
fromStatusCode: props.currentStatus.statusCode
|
||||
});
|
||||
|
||||
checkedRowKeys.value = [];
|
||||
|
||||
await Promise.all([loadStatusModelOptions(), reloadTable(1)]);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [visible.value, props.currentStatus?.id] as const,
|
||||
([opened]) => {
|
||||
if (opened) {
|
||||
initDialog();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BusinessFormDialog
|
||||
v-model="visible"
|
||||
title="状态流转配置"
|
||||
width="1200px"
|
||||
:loading="loadingOptions"
|
||||
:show-footer="false"
|
||||
:scrollbar="false"
|
||||
>
|
||||
<div v-if="currentStatus" class="state-transition-dialog">
|
||||
<StateTransitionSearch
|
||||
v-model:model="searchParams"
|
||||
:target-status-options="targetStatusOptions"
|
||||
@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="min-w-0 flex flex-wrap items-center gap-8px">
|
||||
<p>状态流转列表</p>
|
||||
<ElTag type="primary" effect="light">
|
||||
{{ currentObjectTypeLabel }}
|
||||
</ElTag>
|
||||
<ElTag type="success" effect="light">
|
||||
{{ currentStatusLabel }}
|
||||
</ElTag>
|
||||
<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 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="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="transitionTableRef"
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div v-else class="h-full flex items-center justify-center">
|
||||
<ElEmpty description="请选择状态模型" />
|
||||
</div>
|
||||
|
||||
<StateTransitionOperateDialog
|
||||
v-model:visible="operateVisible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:current-status="currentStatus"
|
||||
:target-status-options="targetStatusOptions"
|
||||
append-to-body
|
||||
@submitted="handleSubmitted"
|
||||
/>
|
||||
</BusinessFormDialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.state-transition-dialog {
|
||||
display: flex;
|
||||
min-height: 560px;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,234 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import {
|
||||
fetchCreateObjectStatusTransition,
|
||||
fetchGetObjectStatusTransition,
|
||||
fetchUpdateObjectStatusTransition
|
||||
} from '@/service/api';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import { statusOptions } from '../shared';
|
||||
|
||||
defineOptions({ name: 'StateTransitionOperateDialog' });
|
||||
|
||||
interface Props {
|
||||
operateType: UI.TableOperateType;
|
||||
rowData?: Api.Infra.ObjectStatusTransition | null;
|
||||
currentStatus?: Api.Infra.ObjectStatusModel | null;
|
||||
targetStatusOptions: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
submitted: [transitionId: string];
|
||||
}>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate } = useForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
const { getLabel: getObjectTypeLabel } = useDict(OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE);
|
||||
|
||||
const detailLoading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const isEdit = computed(() => props.operateType === 'edit');
|
||||
|
||||
const title = computed(() => {
|
||||
const titleMap: Record<UI.TableOperateType, string> = {
|
||||
add: '新增状态流转',
|
||||
edit: '编辑状态流转'
|
||||
};
|
||||
|
||||
return titleMap[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Api.Infra.SaveObjectStatusTransitionParams;
|
||||
|
||||
const model = ref(createDefaultModel());
|
||||
|
||||
const currentObjectTypeLabel = computed(() => getObjectTypeLabel(model.value.objectType));
|
||||
|
||||
const currentFromStatusLabel = computed(() => {
|
||||
if (!props.currentStatus) {
|
||||
return model.value.fromStatusCode || '--';
|
||||
}
|
||||
|
||||
return `${props.currentStatus.statusName} (${props.currentStatus.statusCode})`;
|
||||
});
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
objectType: props.currentStatus?.objectType ?? 'product',
|
||||
actionCode: '',
|
||||
actionName: '',
|
||||
fromStatusCode: props.currentStatus?.statusCode ?? '',
|
||||
toStatusCode: '',
|
||||
needReason: false,
|
||||
status: 0,
|
||||
remark: ''
|
||||
};
|
||||
}
|
||||
|
||||
const rules = {
|
||||
actionCode: createRequiredRule('请输入动作编码'),
|
||||
actionName: createRequiredRule('请输入动作名称'),
|
||||
toStatusCode: createRequiredRule('请选择目标状态'),
|
||||
status: createRequiredRule('请选择配置状态')
|
||||
} satisfies Record<string, App.Global.FormRule>;
|
||||
|
||||
function closeModal() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function initModel() {
|
||||
model.value = createDefaultModel();
|
||||
|
||||
if (!isEdit.value || !props.rowData) {
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
return;
|
||||
}
|
||||
|
||||
detailLoading.value = true;
|
||||
|
||||
const { error, data } = await fetchGetObjectStatusTransition(props.rowData.id);
|
||||
|
||||
detailLoading.value = false;
|
||||
|
||||
if (!error) {
|
||||
model.value = {
|
||||
objectType: data.objectType,
|
||||
actionCode: data.actionCode,
|
||||
actionName: data.actionName,
|
||||
fromStatusCode: data.fromStatusCode,
|
||||
toStatusCode: data.toStatusCode,
|
||||
needReason: data.needReason,
|
||||
status: data.status,
|
||||
remark: data.remark ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
const submitData: Api.Infra.SaveObjectStatusTransitionParams = {
|
||||
...model.value,
|
||||
objectType: props.currentStatus?.objectType ?? model.value.objectType,
|
||||
fromStatusCode: props.currentStatus?.statusCode ?? model.value.fromStatusCode,
|
||||
actionCode: model.value.actionCode.trim(),
|
||||
actionName: model.value.actionName.trim(),
|
||||
remark: model.value.remark?.trim() || null
|
||||
};
|
||||
|
||||
let transitionId = props.rowData?.id ?? '';
|
||||
|
||||
if (isEdit.value && props.rowData) {
|
||||
const { error } = await fetchUpdateObjectStatusTransition({ id: props.rowData.id, ...submitData });
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const { error, data } = await fetchCreateObjectStatusTransition(submitData);
|
||||
|
||||
submitting.value = false;
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
transitionId = data;
|
||||
}
|
||||
|
||||
window.$message?.success(isEdit.value ? '修改成功' : '新增成功');
|
||||
|
||||
closeModal();
|
||||
emit('submitted', transitionId);
|
||||
}
|
||||
|
||||
watch(visible, value => {
|
||||
if (value) {
|
||||
initModel();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BusinessFormDialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
preset="md"
|
||||
:loading="detailLoading"
|
||||
:confirm-loading="submitting"
|
||||
@confirm="handleSubmit"
|
||||
>
|
||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="对象类型">
|
||||
<ElInput :model-value="currentObjectTypeLabel" readonly />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="起始状态">
|
||||
<ElInput :model-value="currentFromStatusLabel" readonly />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="动作编码" prop="actionCode">
|
||||
<ElInput v-model="model.actionCode" placeholder="请输入动作编码" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="动作名称" prop="actionName">
|
||||
<ElInput v-model="model.actionName" placeholder="请输入动作名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="目标状态" prop="toStatusCode">
|
||||
<ElSelect v-model="model.toStatusCode" class="w-full" placeholder="请选择目标状态">
|
||||
<ElOption v-for="{ label, value } in targetStatusOptions" :key="value" :label="label" :value="value" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="配置状态" prop="status">
|
||||
<ElRadioGroup v-model="model.status" class="business-form-radio-group">
|
||||
<ElRadio v-for="{ label, value } in statusOptions" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="必须填写原因" prop="needReason">
|
||||
<div class="business-form-switch-field">
|
||||
<ElSwitch v-model="model.needReason" />
|
||||
<span class="ml-8px text-12px text-[#606266]">{{ model.needReason ? '是' : '否' }}</span>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="model.remark" type="textarea" :rows="4" placeholder="请输入备注" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElForm>
|
||||
</BusinessFormDialog>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import TableSearchFields, { type SearchField } from '@/components/custom/table-search-fields.vue';
|
||||
import { statusOptions } from '../shared';
|
||||
|
||||
defineOptions({ name: 'StateTransitionSearch' });
|
||||
|
||||
interface Props {
|
||||
targetStatusOptions: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
reset: [];
|
||||
search: [];
|
||||
}>();
|
||||
|
||||
const model = defineModel<Api.Infra.ObjectStatusTransitionSearchParams>('model', { required: true });
|
||||
|
||||
const searchModel = reactive<{
|
||||
keyword: string;
|
||||
toStatusCode?: string;
|
||||
status?: Api.Infra.CommonStatus;
|
||||
}>({
|
||||
keyword: '',
|
||||
toStatusCode: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
let syncingFromSource = false;
|
||||
|
||||
watch(
|
||||
() => [model.value.actionName, model.value.actionCode, model.value.toStatusCode, model.value.status] as const,
|
||||
([actionName, actionCode, toStatusCode, status]) => {
|
||||
syncingFromSource = true;
|
||||
searchModel.keyword = actionName ?? actionCode ?? '';
|
||||
searchModel.toStatusCode = toStatusCode;
|
||||
searchModel.status = status;
|
||||
syncingFromSource = false;
|
||||
},
|
||||
{ immediate: true, flush: 'sync' }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [searchModel.keyword, searchModel.toStatusCode, searchModel.status] as const,
|
||||
([keywordValue, toStatusCode, status]) => {
|
||||
if (syncingFromSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const keywordText = keywordValue.trim() || undefined;
|
||||
model.value.actionName = keywordText;
|
||||
model.value.actionCode = keywordText;
|
||||
model.value.toStatusCode = toStatusCode;
|
||||
model.value.status = status;
|
||||
},
|
||||
{ flush: 'sync' }
|
||||
);
|
||||
|
||||
const fields = computed<SearchField[]>(() => [
|
||||
{
|
||||
key: 'keyword',
|
||||
label: '动作名称',
|
||||
type: 'input',
|
||||
placeholder: '请输入动作名称或动作编码'
|
||||
},
|
||||
{
|
||||
key: 'toStatusCode',
|
||||
label: '目标状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择目标状态',
|
||||
options: props.targetStatusOptions
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: '配置状态',
|
||||
type: 'select',
|
||||
placeholder: '请选择配置状态',
|
||||
options: statusOptions
|
||||
}
|
||||
]);
|
||||
|
||||
function reset() {
|
||||
emit('reset');
|
||||
}
|
||||
|
||||
function search() {
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TableSearchFields v-model="searchModel" :fields="fields" :columns="4" @reset="reset" @search="search" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
38
src/views/infra/state-machine/shared.ts
Normal file
38
src/views/infra/state-machine/shared.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const statusOptions: Array<{ label: string; value: Api.Infra.CommonStatus }> = [
|
||||
{ label: '启用', value: 0 },
|
||||
{ label: '停用', value: 1 }
|
||||
];
|
||||
|
||||
export function getStatusLabel(value?: Api.Infra.CommonStatus | null) {
|
||||
if (value === 0) {
|
||||
return '启用';
|
||||
}
|
||||
|
||||
if (value === 1) {
|
||||
return '停用';
|
||||
}
|
||||
|
||||
return '--';
|
||||
}
|
||||
|
||||
export function getStatusTagType(value?: Api.Infra.CommonStatus | null): UI.ThemeColor {
|
||||
return value === 0 ? 'success' : 'warning';
|
||||
}
|
||||
|
||||
export function getBooleanLabel(value?: boolean | null) {
|
||||
return value ? '是' : '否';
|
||||
}
|
||||
|
||||
export function getBooleanTagType(value?: boolean | null): UI.ThemeColor {
|
||||
return value ? 'success' : 'info';
|
||||
}
|
||||
|
||||
export function formatDateTime(value?: string | number | null) {
|
||||
if (!value) {
|
||||
return '--';
|
||||
}
|
||||
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
Reference in New Issue
Block a user