Files
cn-rdms-web/src/views/infra/state-machine/modules/state-transition-operate-dialog.vue
caozehui 3a064eb09f 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/: 新增状态机管理页面及子组件
2026-05-15 09:31:00 +08:00

235 lines
6.8 KiB
Vue

<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>