diff --git a/src/constants/dict.ts b/src/constants/dict.ts index f61f04e..63d753d 100644 --- a/src/constants/dict.ts +++ b/src/constants/dict.ts @@ -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'; + /** * 需求允许删除的状态字典编码 * diff --git a/src/service/api/index.ts b/src/service/api/index.ts index b73862c..308d494 100644 --- a/src/service/api/index.ts +++ b/src/service/api/index.ts @@ -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'; diff --git a/src/service/api/infra.ts b/src/service/api/infra.ts new file mode 100644 index 0000000..7c13e90 --- /dev/null +++ b/src/service/api/infra.ts @@ -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 & { + id: string | number; + needReason: boolean | number | string | null | undefined; +}; + +type ObjectStatusModelPageResponse = Api.Infra.PageResult; + +type ObjectStatusTransitionPageResponse = Api.Infra.PageResult; + +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({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_MODEL_PREFIX}/page`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + ...data, + list: data.list.map(normalizeObjectStatusModel) + })); +} + +export async function fetchGetObjectStatusModel(id: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_MODEL_PREFIX}/get`, + method: 'get', + params: { id } + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeObjectStatusModel); +} + +export async function fetchCreateObjectStatusModel(data: Api.Infra.SaveObjectStatusModelParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_MODEL_PREFIX}/create`, + method: 'post', + data + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeStringId); +} + +export function fetchUpdateObjectStatusModel(data: { id: string } & Api.Infra.SaveObjectStatusModelParams) { + return request({ + url: `${OBJECT_STATUS_MODEL_PREFIX}/update`, + method: 'put', + data + }); +} + +export function fetchDeleteObjectStatusModel(id: string) { + return request({ + url: `${OBJECT_STATUS_MODEL_PREFIX}/delete`, + method: 'delete', + params: { id } + }); +} + +export function fetchBatchDeleteObjectStatusModel(ids: string[]) { + return request({ + url: `${OBJECT_STATUS_MODEL_PREFIX}/delete-list?${createBatchDeleteQuery(ids)}`, + method: 'delete' + }); +} + +export async function fetchGetObjectStatusTransitionPage(params?: Api.Infra.ObjectStatusTransitionSearchParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/page`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + ...data, + list: data.list.map(normalizeObjectStatusTransition) + })); +} + +export async function fetchGetObjectStatusTransition(id: string) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/get`, + method: 'get', + params: { id } + }); + + return mapServiceResult( + result as ServiceRequestResult, + normalizeObjectStatusTransition + ); +} + +export async function fetchCreateObjectStatusTransition(data: Api.Infra.SaveObjectStatusTransitionParams) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/create`, + method: 'post', + data + }); + + return mapServiceResult(result as ServiceRequestResult, normalizeStringId); +} + +export function fetchUpdateObjectStatusTransition(data: { id: string } & Api.Infra.SaveObjectStatusTransitionParams) { + return request({ + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/update`, + method: 'put', + data + }); +} + +export function fetchDeleteObjectStatusTransition(id: string) { + return request({ + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/delete`, + method: 'delete', + params: { id } + }); +} + +export function fetchBatchDeleteObjectStatusTransition(ids: string[]) { + return request({ + url: `${OBJECT_STATUS_TRANSITION_PREFIX}/delete-list?${createBatchDeleteQuery(ids)}`, + method: 'delete' + }); +} diff --git a/src/typings/api/infra.d.ts b/src/typings/api/infra.d.ts new file mode 100644 index 0000000..d71fffb --- /dev/null +++ b/src/typings/api/infra.d.ts @@ -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 { + 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 & + Pick & { + keyword?: string; + } + >; + + type SaveObjectStatusModelParams = Pick< + ObjectStatusModel, + | 'objectType' + | 'statusCode' + | 'statusName' + | 'sort' + | 'status' + | 'initialFlag' + | 'terminalFlag' + | 'allowEdit' + | 'progressExcludedFlag' + | 'allowCreateProject' + | 'allowCreateRequirement' + > & { + remark?: string | null; + }; + + type ObjectStatusModelList = PageResult; + + 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 & + 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; + } +} diff --git a/src/views/infra/state-machine/index.vue b/src/views/infra/state-machine/index.vue index 1450eab..0c6dbd6 100644 --- a/src/views/infra/state-machine/index.vue +++ b/src/views/infra/state-machine/index.vue @@ -1,3 +1,389 @@ + + + + diff --git a/src/views/infra/state-machine/modules/state-machine-operate-dialog.vue b/src/views/infra/state-machine/modules/state-machine-operate-dialog.vue new file mode 100644 index 0000000..10b1f5a --- /dev/null +++ b/src/views/infra/state-machine/modules/state-machine-operate-dialog.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/src/views/infra/state-machine/modules/state-machine-search.vue b/src/views/infra/state-machine/modules/state-machine-search.vue new file mode 100644 index 0000000..bdfb931 --- /dev/null +++ b/src/views/infra/state-machine/modules/state-machine-search.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/views/infra/state-machine/modules/state-transition-dialog.vue b/src/views/infra/state-machine/modules/state-transition-dialog.vue new file mode 100644 index 0000000..f400a9d --- /dev/null +++ b/src/views/infra/state-machine/modules/state-transition-dialog.vue @@ -0,0 +1,406 @@ + + + + + diff --git a/src/views/infra/state-machine/modules/state-transition-operate-dialog.vue b/src/views/infra/state-machine/modules/state-transition-operate-dialog.vue new file mode 100644 index 0000000..e439137 --- /dev/null +++ b/src/views/infra/state-machine/modules/state-transition-operate-dialog.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/src/views/infra/state-machine/modules/state-transition-search.vue b/src/views/infra/state-machine/modules/state-transition-search.vue new file mode 100644 index 0000000..76a4af5 --- /dev/null +++ b/src/views/infra/state-machine/modules/state-transition-search.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/views/infra/state-machine/shared.ts b/src/views/infra/state-machine/shared.ts new file mode 100644 index 0000000..84763db --- /dev/null +++ b/src/views/infra/state-machine/shared.ts @@ -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'); +}