规则展示,bug修复
This commit is contained in:
@@ -58,4 +58,12 @@ export const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import('@/views/decision/rule/management.vue'),
|
||||
},
|
||||
{
|
||||
name: 'decision-rule-config',
|
||||
path: '/app/decision/rule-config',
|
||||
meta: {
|
||||
title: '规则聚合测试',
|
||||
},
|
||||
component: () => import('@/views/decision/rule-config/management.vue'),
|
||||
},
|
||||
]
|
||||
44
modeler/src/views/decision/rule-config/api.ts
Normal file
44
modeler/src/views/decision/rule-config/api.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the kernelstudio package.
|
||||
*
|
||||
* (c) 2014-2026 zlin <admin@kernelstudio.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file
|
||||
* that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import { HttpRequestClient } from '@/utils/request';
|
||||
import type { ApiDataResponse, BasicResponse } from '@/types';
|
||||
import type { RuleConfig, RuleConfigPageableResponse, RuleConfigRequest, RuleDictItem, RuleParamMeta } from './types';
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
baseURL: '/api',
|
||||
});
|
||||
|
||||
export const findRuleConfigByQuery = (query: Partial<RuleConfigRequest> = {}): Promise<RuleConfigPageableResponse> => {
|
||||
return req.get('/system/rule/config/list', query);
|
||||
};
|
||||
|
||||
export const findRuleConfigByCode = (ruleCode: string): Promise<ApiDataResponse<RuleConfig>> => {
|
||||
return req.get(`/system/rule/config/${ruleCode}`);
|
||||
};
|
||||
|
||||
export const createRuleConfig = (ruleConfig: RuleConfig): Promise<BasicResponse> => {
|
||||
return req.postJson('/system/rule/config', ruleConfig);
|
||||
};
|
||||
|
||||
export const updateRuleConfig = (ruleConfig: RuleConfig): Promise<BasicResponse> => {
|
||||
return req.putJson('/system/rule/config', ruleConfig);
|
||||
};
|
||||
|
||||
export const deleteRuleConfig = (ruleCode: string): Promise<BasicResponse> => {
|
||||
return req.delete(`/system/rule/config/${ruleCode}`);
|
||||
};
|
||||
|
||||
export const findRuleDictByType = (dictType: string): Promise<ApiDataResponse<RuleDictItem[]>> => {
|
||||
return req.get(`/system/rule/config/dict/${dictType}`);
|
||||
};
|
||||
|
||||
export const findRuleParamMeta = (): Promise<ApiDataResponse<RuleParamMeta[]>> => {
|
||||
return req.get('/system/rule/config/param-meta');
|
||||
};
|
||||
557
modeler/src/views/decision/rule-config/management.vue
Normal file
557
modeler/src/views/decision/rule-config/management.vue
Normal file
@@ -0,0 +1,557 @@
|
||||
<template>
|
||||
<Layout>
|
||||
<template #sidebar>
|
||||
<div class="ks-sidebar-header">
|
||||
<a-flex class="ks-sidebar-title">
|
||||
<span class="icon"></span>
|
||||
<span class="text">规则聚合测试</span>
|
||||
</a-flex>
|
||||
<a-button class="ks-sidebar-add" size="small" @click="handleCreate">
|
||||
<PlusOutlined />
|
||||
新增
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<a-tree
|
||||
class="ks-sidebar-list"
|
||||
:tree-data="treeData"
|
||||
v-model:expandedKeys="expandedTreeKeys"
|
||||
v-model:selectedKeys="selectedTreeKeys"
|
||||
@select="handleTreeSelect"
|
||||
/>
|
||||
|
||||
<a-pagination
|
||||
v-model:current="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
:total="datasourceTotal"
|
||||
simple
|
||||
size="small"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="w-full h-full">
|
||||
<a-card class="ks-page-card ks-algorithm-card">
|
||||
<template #title>
|
||||
<a-space>
|
||||
<span class="point"></span>
|
||||
<span class="text">规则聚合配置</span>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<div class="ks-scrollable" style="height: 80.5vh;overflow-y: auto;padding-right: 10px">
|
||||
<a-row :gutter="15">
|
||||
<a-col :span="16">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:model="selectedRuleConfig"
|
||||
autocomplete="off"
|
||||
layout="horizontal"
|
||||
name="rule-config"
|
||||
>
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 12px;"
|
||||
message="仅保留业务参数:参数作用、参数Key、参数值"
|
||||
/>
|
||||
|
||||
<a-form-item
|
||||
label="参数配置"
|
||||
:rules="[{ required: true, message: '请至少配置一条参数', trigger: ['change'] }]"
|
||||
name="params"
|
||||
>
|
||||
<a-form-item-rest>
|
||||
<div class="ks-sidebar-list-param-list">
|
||||
<div class="ks-sidebar-list-param-item" v-for="(item,index) in selectedRuleConfig.params" :key="`param-${index}`">
|
||||
<a-row :gutter="10">
|
||||
<a-col :span="6">
|
||||
<a-input v-model:value="item.paramName" placeholder="参数作用(业务说明)" />
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-select
|
||||
v-model:value="item.paramKey"
|
||||
placeholder="参数Key"
|
||||
:options="paramMetaOptions"
|
||||
show-search
|
||||
:filter-option="filterParamKey"
|
||||
@change="() => handleParamKeyChange(item)"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="9">
|
||||
<template v-if="resolveMeta(item.paramKey)?.valueType === 'bool'">
|
||||
<a-select
|
||||
v-model:value="item.paramVal"
|
||||
placeholder="参数值"
|
||||
:options="boolOptions"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="resolveMeta(item.paramKey)?.valueType === 'enum'">
|
||||
<a-select
|
||||
v-model:value="item.paramVal"
|
||||
placeholder="参数值"
|
||||
:options="toEnumOptions(resolveMeta(item.paramKey)?.enumOptions)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="resolveMeta(item.paramKey)?.valueType === 'number'">
|
||||
<a-input-number
|
||||
v-model:value="item.paramVal"
|
||||
placeholder="参数值"
|
||||
style="width: 100%;"
|
||||
:min="resolveMeta(item.paramKey)?.min ?? undefined"
|
||||
:max="resolveMeta(item.paramKey)?.max ?? undefined"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-input v-model:value="item.paramVal" placeholder="参数值" />
|
||||
</template>
|
||||
<div class="text-xs text-red-400 mt-1" v-if="!resolveMeta(item.paramKey) && item.paramKey">
|
||||
未知参数键,当前为兼容展示。请联系后端补充参数元数据后再保存。
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mt-1" v-if="resolveMeta(item.paramKey)?.description">
|
||||
{{ resolveMeta(item.paramKey)?.description }}<span v-if="resolveMeta(item.paramKey)?.example">,示例:{{ resolveMeta(item.paramKey)?.example }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a-space class="ks-sidebar-list-param-actions">
|
||||
<MinusCircleOutlined @click.stop="() => handleMinusParam(index)" />
|
||||
<PlusCircleOutlined @click.stop="() => handleAddParam()" v-if="index === 0" />
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ offset: 6 }">
|
||||
<a-space>
|
||||
<a-button @click="handleSave" type="primary">保存规则</a-button>
|
||||
<a-popconfirm
|
||||
v-if="selectedRuleConfig && selectedRuleConfig.id > 0 && selectedRuleConfig.ruleCode"
|
||||
title="确定删除?"
|
||||
@confirm="handleDelete"
|
||||
>
|
||||
<a-button danger>删除规则</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
import { type FormInstance, message } from 'ant-design-vue';
|
||||
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import Layout from '../layout.vue';
|
||||
import { createRuleConfig, deleteRuleConfig, findRuleConfigByCode, findRuleConfigByQuery, findRuleParamMeta, updateRuleConfig } from './api';
|
||||
import type { RuleConfig, RuleConfigParam, RuleConfigRequest, RuleParamMeta } from './types';
|
||||
|
||||
const query = ref<RuleConfigRequest>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const defaultParam = (): RuleConfigParam => ({
|
||||
ruleCode: null,
|
||||
paramKey: null,
|
||||
paramVal: null,
|
||||
valType: 'string',
|
||||
paramName: null,
|
||||
sortNo: 0,
|
||||
enabled: 1,
|
||||
remark: null,
|
||||
});
|
||||
|
||||
const defaultRuleConfig: RuleConfig = {
|
||||
id: 0,
|
||||
ruleCode: null,
|
||||
ruleName: null,
|
||||
levelCode: null,
|
||||
kindCode: null,
|
||||
moduleCode: null,
|
||||
priorityNo: 0,
|
||||
conditionExpr: null,
|
||||
actionExpr: null,
|
||||
versionNo: 1,
|
||||
enabled: 1,
|
||||
remark: null,
|
||||
params: [defaultParam()],
|
||||
taskTypes: [],
|
||||
};
|
||||
|
||||
const datasource = ref<RuleConfig[]>([]);
|
||||
const datasourceTotal = ref<number>(0);
|
||||
const selectedRuleConfig = ref<RuleConfig>(JSON.parse(JSON.stringify(defaultRuleConfig)));
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const treeData = ref<any[]>([]);
|
||||
const selectedTreeKeys = ref<string[]>([]);
|
||||
const expandedTreeKeys = ref<string[]>([]);
|
||||
const detailMap = ref<Record<string, RuleConfig>>({});
|
||||
const paramMetaList = ref<RuleParamMeta[]>([]);
|
||||
const paramMetaMap = ref<Record<string, RuleParamMeta>>({});
|
||||
|
||||
const boolOptions = [
|
||||
{ label: 'true', value: 'true' },
|
||||
{ label: 'false', value: 'false' },
|
||||
];
|
||||
|
||||
const paramMetaOptions = ref<{ label: string; value: string }[]>([]);
|
||||
|
||||
const levelOptions = [
|
||||
{ code: 'task', name: '任务级' },
|
||||
{ code: 'platform', name: '平台级' },
|
||||
{ code: 'action', name: '行动级' },
|
||||
];
|
||||
|
||||
const kindOptions = [
|
||||
{ code: 'select', name: '选择' },
|
||||
{ code: 'assign', name: '分配' },
|
||||
{ code: 'deploy', name: '部署' },
|
||||
{ code: 'config', name: '配置' },
|
||||
{ code: 'mode', name: '工作模式' },
|
||||
{ code: 'spacetime', name: '时空约束' },
|
||||
{ code: 'relation', name: '关联关系' },
|
||||
{ code: 'limit', name: '限制条件' },
|
||||
];
|
||||
|
||||
const buildTreeData = () => {
|
||||
const group: Record<string, Record<string, any[]>> = {};
|
||||
|
||||
datasource.value.forEach((rule) => {
|
||||
const detail = rule.ruleCode ? detailMap.value[rule.ruleCode] : null;
|
||||
const levelCode = rule.levelCode;
|
||||
const kindCode = rule.kindCode;
|
||||
if (!levelCode || !kindCode) return;
|
||||
const params = detail?.params?.filter((item) => item.paramKey) ?? [];
|
||||
if (params.length === 0) return;
|
||||
|
||||
group[levelCode] = group[levelCode] ?? {};
|
||||
group[levelCode][kindCode] = group[levelCode][kindCode] ?? [];
|
||||
|
||||
params.forEach((param, index) => {
|
||||
const paramTitle = param.paramName || param.paramKey || `参数${index + 1}`;
|
||||
group[levelCode][kindCode].push({
|
||||
title: paramTitle,
|
||||
key: `param:${rule.ruleCode}:${param.paramKey || index}`,
|
||||
isLeaf: true,
|
||||
nodeType: 'param',
|
||||
ruleCode: rule.ruleCode,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
treeData.value = levelOptions
|
||||
.map((level) => {
|
||||
const levelGroup = group[level.code] ?? {};
|
||||
const kindChildren = kindOptions
|
||||
.map((kind) => {
|
||||
const children = levelGroup[kind.code] ?? [];
|
||||
if (children.length === 0) return null;
|
||||
return {
|
||||
title: kind.name,
|
||||
key: `kind:${level.code}:${kind.code}`,
|
||||
nodeType: 'kind',
|
||||
children,
|
||||
};
|
||||
})
|
||||
.filter((item) => !!item);
|
||||
if (kindChildren.length === 0) return null;
|
||||
return {
|
||||
title: level.name,
|
||||
key: `level:${level.code}`,
|
||||
nodeType: 'level',
|
||||
children: kindChildren,
|
||||
};
|
||||
})
|
||||
.filter((item) => !!item);
|
||||
};
|
||||
|
||||
const load = async () => {
|
||||
datasource.value = [];
|
||||
datasourceTotal.value = 0;
|
||||
detailMap.value = {};
|
||||
|
||||
if (selectedRuleConfig.value.id <= 0) {
|
||||
selectedRuleConfig.value = JSON.parse(JSON.stringify(defaultRuleConfig));
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
|
||||
const r = await findRuleConfigByQuery(query.value as Partial<RuleConfigRequest>);
|
||||
datasource.value = r.rows ?? [];
|
||||
datasourceTotal.value = r.total ?? 0;
|
||||
|
||||
await Promise.all(
|
||||
datasource.value
|
||||
.filter((item) => !!item.ruleCode)
|
||||
.map(async (item) => {
|
||||
if (!item.ruleCode) return;
|
||||
const detailResp = await findRuleConfigByCode(item.ruleCode);
|
||||
if (detailResp.code === 200 && detailResp.data) {
|
||||
detailMap.value[item.ruleCode] = detailResp.data;
|
||||
}
|
||||
}),
|
||||
);
|
||||
buildTreeData();
|
||||
};
|
||||
|
||||
const loadParamMeta = async () => {
|
||||
const r = await findRuleParamMeta();
|
||||
if (r.code !== 200 || !Array.isArray(r.data)) {
|
||||
message.error(r.msg ?? '加载参数元数据失败');
|
||||
return;
|
||||
}
|
||||
paramMetaList.value = r.data;
|
||||
const map: Record<string, RuleParamMeta> = {};
|
||||
const options: { label: string; value: string }[] = [];
|
||||
r.data.forEach((item) => {
|
||||
if (!item.paramKey) return;
|
||||
map[item.paramKey] = item;
|
||||
options.push({
|
||||
label: item.label ? `${item.label} (${item.paramKey})` : String(item.paramKey),
|
||||
value: String(item.paramKey),
|
||||
});
|
||||
});
|
||||
paramMetaMap.value = map;
|
||||
paramMetaOptions.value = options;
|
||||
};
|
||||
|
||||
const resolveMeta = (paramKey: string | null): RuleParamMeta | null => {
|
||||
if (!paramKey) return null;
|
||||
return paramMetaMap.value[paramKey] ?? null;
|
||||
};
|
||||
|
||||
const toEnumOptions = (enumOptions: string[] | null | undefined) => {
|
||||
if (!Array.isArray(enumOptions)) return [];
|
||||
return enumOptions.map((v) => ({ label: v, value: v }));
|
||||
};
|
||||
|
||||
const filterParamKey = (input: string, option: { label: string; value: string }) => {
|
||||
const text = `${option.label ?? ''}${option.value ?? ''}`.toLowerCase();
|
||||
return text.includes(input.toLowerCase());
|
||||
};
|
||||
|
||||
const ensureUnknownParamOptions = (params: RuleConfigParam[]) => {
|
||||
const exists = new Set(paramMetaOptions.value.map((item) => item.value));
|
||||
const append: { label: string; value: string }[] = [];
|
||||
params.forEach((item) => {
|
||||
if (!item?.paramKey) return;
|
||||
if (!exists.has(item.paramKey)) {
|
||||
const opt = { label: `[未知] ${item.paramKey}`, value: item.paramKey };
|
||||
append.push(opt);
|
||||
exists.add(item.paramKey);
|
||||
}
|
||||
});
|
||||
if (append.length > 0) {
|
||||
paramMetaOptions.value = [...paramMetaOptions.value, ...append];
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
formRef.value?.resetFields();
|
||||
selectedRuleConfig.value = JSON.parse(JSON.stringify(defaultRuleConfig));
|
||||
};
|
||||
|
||||
const handleSelect = (item: RuleConfig) => {
|
||||
if (!item.ruleCode) {
|
||||
return;
|
||||
}
|
||||
findRuleConfigByCode(item.ruleCode).then((r) => {
|
||||
if (r.code === 200 && r.data) {
|
||||
formRef.value?.resetFields();
|
||||
nextTick(() => {
|
||||
const detail = JSON.parse(JSON.stringify(r.data)) as RuleConfig;
|
||||
if (!Array.isArray(detail.params) || detail.params.length === 0) {
|
||||
detail.params = [defaultParam()];
|
||||
}
|
||||
if (!Array.isArray(detail.taskTypes)) {
|
||||
detail.taskTypes = [];
|
||||
}
|
||||
ensureUnknownParamOptions(detail.params ?? []);
|
||||
selectedRuleConfig.value = detail;
|
||||
});
|
||||
} else {
|
||||
message.error(r.msg ?? '查询详情失败');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleTreeSelect = (_keys: (string | number)[], e: any) => {
|
||||
const node = e?.node;
|
||||
if (!node || node.nodeType !== 'param') {
|
||||
return;
|
||||
}
|
||||
const ruleCode = node.ruleCode as string | null;
|
||||
if (!ruleCode) {
|
||||
return;
|
||||
}
|
||||
selectedTreeKeys.value = [String(node.key)];
|
||||
handleSelect({
|
||||
...defaultRuleConfig,
|
||||
ruleCode,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedRuleConfig.value?.id > 0 && selectedRuleConfig.value.ruleCode) {
|
||||
deleteRuleConfig(selectedRuleConfig.value.ruleCode).then((r) => {
|
||||
if (r.code === 200) {
|
||||
load();
|
||||
message.success(r.msg ?? '删除成功');
|
||||
} else {
|
||||
message.error(r.msg ?? '删除失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
const savedValue: RuleConfig = JSON.parse(JSON.stringify(selectedRuleConfig.value));
|
||||
savedValue.params = (savedValue.params ?? []).filter((item) => item.paramKey);
|
||||
if (savedValue.params.length === 0) {
|
||||
message.error('请至少填写一个参数键');
|
||||
return;
|
||||
}
|
||||
savedValue.params = savedValue.params.map((item, index) => ({
|
||||
...item,
|
||||
ruleCode: savedValue.ruleCode,
|
||||
sortNo: item.sortNo ?? index,
|
||||
enabled: item.enabled ?? 1,
|
||||
valType: resolveMeta(item.paramKey)?.valueType === 'bool'
|
||||
? 'bool'
|
||||
: resolveMeta(item.paramKey)?.valueType === 'number'
|
||||
? 'number'
|
||||
: 'string',
|
||||
paramVal: normalizeParamVal(item),
|
||||
}));
|
||||
const validMsg = validateParamsBeforeSubmit(savedValue.params);
|
||||
if (validMsg) {
|
||||
message.error(validMsg);
|
||||
return;
|
||||
}
|
||||
// 保留后端必填字段,前端不展示时统一补默认值
|
||||
savedValue.ruleCode = savedValue.ruleCode ?? `rule_${Date.now()}`;
|
||||
savedValue.ruleName = savedValue.ruleName ?? savedValue.ruleCode;
|
||||
savedValue.levelCode = savedValue.levelCode ?? 'task';
|
||||
savedValue.kindCode = savedValue.kindCode ?? 'select';
|
||||
savedValue.moduleCode = savedValue.moduleCode ?? 'equipment';
|
||||
savedValue.priorityNo = savedValue.priorityNo ?? 100;
|
||||
savedValue.versionNo = savedValue.versionNo ?? 1;
|
||||
savedValue.enabled = savedValue.enabled ?? 1;
|
||||
savedValue.taskTypes = Array.isArray(savedValue.taskTypes) ? savedValue.taskTypes : [];
|
||||
const request = savedValue.id > 0
|
||||
? updateRuleConfig(savedValue)
|
||||
: createRuleConfig(savedValue);
|
||||
|
||||
request.then(async (r) => {
|
||||
if (r.code === 200) {
|
||||
await load();
|
||||
message.success(r.msg ?? '操作成功');
|
||||
} else {
|
||||
message.error(r.msg ?? '操作失败');
|
||||
}
|
||||
}).catch((err) => {
|
||||
message.error('请求失败:' + err.message);
|
||||
});
|
||||
}).catch((err) => {
|
||||
message.error('表单验证失败:' + err.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = async (page: number, pageSize: number) => {
|
||||
query.value.pageNum = page;
|
||||
query.value.pageSize = pageSize;
|
||||
await load();
|
||||
};
|
||||
|
||||
const handleAddParam = () => {
|
||||
const row = defaultParam();
|
||||
if (paramMetaOptions.value.length > 0) {
|
||||
row.paramKey = paramMetaOptions.value[0].value;
|
||||
handleParamKeyChange(row);
|
||||
}
|
||||
selectedRuleConfig.value.params.push(row);
|
||||
};
|
||||
|
||||
const handleMinusParam = (index: number) => {
|
||||
const params = [...selectedRuleConfig.value.params];
|
||||
if (params.length <= 1) {
|
||||
selectedRuleConfig.value.params = [defaultParam()];
|
||||
return;
|
||||
}
|
||||
params.splice(index, 1);
|
||||
selectedRuleConfig.value.params = params;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadParamMeta().then(() => load());
|
||||
});
|
||||
|
||||
const handleParamKeyChange = (item: RuleConfigParam) => {
|
||||
const meta = resolveMeta(item.paramKey);
|
||||
if (!meta) {
|
||||
item.valType = 'string';
|
||||
return;
|
||||
}
|
||||
if (meta.valueType === 'bool') {
|
||||
item.valType = 'bool';
|
||||
if (item.paramVal !== 'true' && item.paramVal !== 'false') {
|
||||
item.paramVal = 'true';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (meta.valueType === 'number') {
|
||||
item.valType = 'number';
|
||||
return;
|
||||
}
|
||||
item.valType = 'string';
|
||||
if (meta.valueType === 'enum' && Array.isArray(meta.enumOptions) && meta.enumOptions.length > 0) {
|
||||
if (!meta.enumOptions.includes(String(item.paramVal ?? ''))) {
|
||||
item.paramVal = meta.enumOptions[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeParamVal = (item: RuleConfigParam): string | null => {
|
||||
if (item.paramVal === null || item.paramVal === undefined) return null;
|
||||
return String(item.paramVal);
|
||||
};
|
||||
|
||||
const validateParamsBeforeSubmit = (params: RuleConfigParam[]): string | null => {
|
||||
for (const item of params) {
|
||||
const key = item.paramKey;
|
||||
if (!key) return '参数Key不能为空';
|
||||
const meta = resolveMeta(key);
|
||||
if (!meta) return `不支持的参数Key: ${key}`;
|
||||
const val = String(item.paramVal ?? '');
|
||||
if (meta.required && !val) return `参数值不能为空: ${key}`;
|
||||
if (meta.valueType === 'bool' && val !== 'true' && val !== 'false') {
|
||||
return `布尔参数仅支持 true/false: ${key}`;
|
||||
}
|
||||
if (meta.valueType === 'enum' && Array.isArray(meta.enumOptions) && !meta.enumOptions.includes(val)) {
|
||||
return `参数值不在可选范围内: ${key}`;
|
||||
}
|
||||
if (meta.valueType === 'number') {
|
||||
const num = Number(val);
|
||||
if (Number.isNaN(num)) return `数值参数格式错误: ${key}`;
|
||||
if (meta.min !== null && meta.min !== undefined && num < meta.min) return `参数值小于最小值: ${key}`;
|
||||
if (meta.max !== null && meta.max !== undefined && num > meta.max) return `参数值大于最大值: ${key}`;
|
||||
}
|
||||
if (meta.pattern) {
|
||||
const regex = new RegExp(meta.pattern);
|
||||
if (!regex.test(val)) return `参数格式错误: ${key}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
</script>
|
||||
88
modeler/src/views/decision/rule-config/types.ts
Normal file
88
modeler/src/views/decision/rule-config/types.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of the kernelstudio package.
|
||||
*
|
||||
* (c) 2014-2026 zlin <admin@kernelstudio.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file
|
||||
* that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import type { NullableString, PageableResponse } from '@/types';
|
||||
|
||||
export interface RuleConfigParam {
|
||||
// 规则编码
|
||||
ruleCode: NullableString,
|
||||
// 参数键
|
||||
paramKey: NullableString,
|
||||
// 参数值
|
||||
paramVal: NullableString | number,
|
||||
// 值类型(string/number/bool/json)
|
||||
valType: NullableString,
|
||||
// 参数名称
|
||||
paramName: NullableString,
|
||||
// 排序号
|
||||
sortNo: number | null,
|
||||
// 是否启用(1是0否)
|
||||
enabled: number | null,
|
||||
// 备注
|
||||
remark: NullableString,
|
||||
}
|
||||
|
||||
export interface RuleDictItem {
|
||||
dictType: NullableString,
|
||||
dictCode: NullableString,
|
||||
dictName: NullableString,
|
||||
sortNo: number | null,
|
||||
enabled: number | null,
|
||||
remark: NullableString,
|
||||
}
|
||||
|
||||
export interface RuleParamMeta {
|
||||
paramKey: NullableString,
|
||||
label: NullableString,
|
||||
valueType: NullableString,
|
||||
required: boolean | null,
|
||||
enumOptions: string[] | null,
|
||||
min: number | null,
|
||||
max: number | null,
|
||||
pattern: NullableString,
|
||||
example: NullableString,
|
||||
description: NullableString,
|
||||
}
|
||||
|
||||
export interface RuleConfig {
|
||||
id: number,
|
||||
// 规则编码
|
||||
ruleCode: NullableString,
|
||||
// 规则名称
|
||||
ruleName: NullableString,
|
||||
// 层级编码
|
||||
levelCode: NullableString,
|
||||
// 种类编码
|
||||
kindCode: NullableString,
|
||||
// 模块编码
|
||||
moduleCode: NullableString,
|
||||
// 优先级(数值越小优先级越高)
|
||||
priorityNo: number | null,
|
||||
// 条件表达式
|
||||
conditionExpr: NullableString,
|
||||
// 动作表达式
|
||||
actionExpr: NullableString,
|
||||
// 版本号
|
||||
versionNo: number | null,
|
||||
// 是否启用(1是0否)
|
||||
enabled: number | null,
|
||||
// 备注
|
||||
remark: NullableString,
|
||||
// 参数列表
|
||||
params: RuleConfigParam[],
|
||||
// 适用任务类型编码列表
|
||||
taskTypes: string[],
|
||||
}
|
||||
|
||||
export interface RuleConfigRequest extends Partial<RuleConfig> {
|
||||
pageNum: number,
|
||||
pageSize: number,
|
||||
}
|
||||
|
||||
export interface RuleConfigPageableResponse extends PageableResponse<RuleConfig> {}
|
||||
8
modeler/types/components.d.ts
vendored
8
modeler/types/components.d.ts
vendored
@@ -12,6 +12,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
@@ -40,6 +41,8 @@ declare module 'vue' {
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
@@ -49,6 +52,7 @@ declare module 'vue' {
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
ATree: typeof import('ant-design-vue/es')['Tree']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
@@ -56,6 +60,7 @@ declare module 'vue' {
|
||||
|
||||
// For TSX support
|
||||
declare global {
|
||||
const AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
const ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
const AButton: typeof import('ant-design-vue/es')['Button']
|
||||
const ACard: typeof import('ant-design-vue/es')['Card']
|
||||
@@ -84,6 +89,8 @@ declare global {
|
||||
const AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
const APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
const APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
const ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
const ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
const ARow: typeof import('ant-design-vue/es')['Row']
|
||||
const ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
const ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
@@ -93,6 +100,7 @@ declare global {
|
||||
const ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
const ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
const ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
const ATree: typeof import('ant-design-vue/es')['Tree']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
Reference in New Issue
Block a user