feat: 新增数字员工管理页面与工作台首页重构

后端优化 agent 资产种子初始化和常量配置,前端新增数字员工
视图和调度对话框组件,重构个人工作台首页布局和洞察面板,
完善审计页面数字员工详情和运行时模型,优化侧边栏导航和图
标配置,新增工作台摘要和工作台数据模块,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-28 09:30:34 +08:00
parent d4d5d40569
commit 04cd6d0f81
38 changed files with 3413 additions and 1301 deletions

View File

@@ -154,7 +154,6 @@ export default {
const assetBuckets = ref({
financialRules: [],
riskRules: [],
skills: [],
mcp: []
})
@@ -173,7 +172,7 @@ export default {
const showVersionColumn = computed(() => activeMeta.value.showVersionColumn !== false)
const showStatusColumn = computed(() => activeMeta.value.showStatusColumn !== false)
const showOnlineColumn = computed(() => false)
const showEnabledColumn = computed(() => false)
const showEnabledColumn = computed(() => activeMeta.value.showEnabledColumn === true)
const selectedSkillIsRule = computed(() => selectedSkill.value?.type === 'rules')
const selectedSkillUsesSpreadsheet = computed(
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesSpreadsheetRule)
@@ -241,7 +240,7 @@ export default {
() => selectedSkillUsesJsonRisk.value && canManageSelected.value
)
const riskRuleCreateBusy = computed(() => actionState.value === 'generate-risk-rule')
const canEditMarkdown = computed(() => canEditSelected.value && selectedSkillIsRule.value)
const canEditMarkdown = computed(() => selectedSkillIsRule.value && canEditSelected.value)
const isDisplayingWorkingVersion = computed(
() => selectedSkill.value?.displayVersion === selectedSkill.value?.workingVersion
)
@@ -1401,7 +1400,7 @@ export default {
version: nextVersion,
content: buildMarkdownVersionContent(selectedSkill.value.markdownContent, runtimeRule),
content_type: 'markdown',
change_note: '通过任务规则中心保存 Markdown 规则内容,并同步运行时 JSON。',
change_note: '通过规则中心保存 Markdown 规则内容,并同步运行时 JSON。',
created_by: resolveActor()
},
{ actor: resolveActor() }
@@ -1449,7 +1448,7 @@ export default {
version: nextVersion,
content: buildMarkdownVersionContent(selectedSkill.value.markdownContent, runtimeRule),
content_type: 'markdown',
change_note: '通过任务规则中心保存运行时 JSON 配置。',
change_note: '通过规则中心保存运行时 JSON 配置。',
created_by: resolveActor()
},
{ actor: resolveActor() }

View File

@@ -1,4 +1,5 @@
const DIGITAL_EMPLOYEE_AGENT = 'hermes'
export const DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS = ['积累', '升级', '整理', '评估']
const TASK_TYPE_LABELS = {
daily_risk_scan: '每日风险巡检',
@@ -7,11 +8,25 @@ const TASK_TYPE_LABELS = {
weekly_expense_report: '周度费用洞察',
rule_review_digest: '规则待审摘要',
knowledge_index_sync: '知识库归集',
llm_wiki_rule_formation: '知识库归集',
x_financial_callback: '任务回调上报'
}
const TASK_TYPE_SKILL_CATEGORIES = {
daily_risk_scan: '评估',
global_risk_scan: '评估',
weekly_ar_summary: '整理',
weekly_expense_report: '整理',
rule_review_digest: '升级',
knowledge_index_sync: '积累',
llm_wiki_rule_formation: '积累',
x_financial_callback: '升级'
}
const CONTENT_LABELS = {
task_type: '技能类型',
task_type: '任务类型',
skill_category: '技能类型',
skill_category_options: '技能类型范围',
schedule: '执行计划',
cron: '调度表达式',
folder: '归集范围',
@@ -45,6 +60,14 @@ export function sanitizeDigitalEmployeeText(value, fallback = '') {
return text || fallback
}
export function sanitizeDigitalEmployeeSource(value, fallback = '') {
const text = normalizeDigitalEmployeeText(value)
.replace(/hermes/gi, '数字员工')
.replace(/赫尔墨斯/g, '数字员工')
.trim()
return text || fallback
}
export function sanitizeDigitalEmployeeName(value, fallback = '数字员工技能') {
const text = sanitizeDigitalEmployeeText(value, fallback)
.replace(/^数字员工[\s·:-]*/i, '')
@@ -80,6 +103,22 @@ export function resolveDigitalEmployeeTaskType(source = {}, content = {}) {
return raw.replace(/[-.]/g, '_')
}
export function resolveDigitalEmployeeSkillCategory(source = {}, content = {}) {
const config = source.config_json || source.configJson || {}
const taskType = resolveDigitalEmployeeTaskType(source, content)
const explicitCategory =
normalizeDigitalEmployeeText(config.skill_category) ||
normalizeDigitalEmployeeText(config.skillCategory) ||
normalizeDigitalEmployeeText(content.skill_category) ||
normalizeDigitalEmployeeText(content.skillCategory)
if (DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS.includes(explicitCategory)) {
return explicitCategory
}
return TASK_TYPE_SKILL_CATEGORIES[taskType] || '整理'
}
export function isDigitalEmployeeAsset(source = {}) {
const config = source.config_json || source.configJson || {}
const haystack = [
@@ -145,10 +184,10 @@ export function formatDigitalEmployeeCron(value) {
export function resolveDigitalEmployeeSchedule(source = {}, content = {}) {
const config = source.config_json || source.configJson || {}
const raw =
normalizeDigitalEmployeeText(content.schedule) ||
normalizeDigitalEmployeeText(config.cron) ||
normalizeDigitalEmployeeText(config.schedule) ||
normalizeDigitalEmployeeText(config.cron_expression)
normalizeDigitalEmployeeText(config.cron_expression) ||
normalizeDigitalEmployeeText(content.schedule)
return {
value: raw,
label: formatDigitalEmployeeCron(raw)
@@ -205,9 +244,85 @@ export function buildDigitalEmployeeContentPreview(content = {}) {
return sanitizeDigitalEmployeeText(JSON.stringify(visiblePayload, null, 2))
}
function resolveDigitalEmployeeMarkdownFromContent(content = {}, config = {}) {
const candidates = [
content.skill_markdown,
content.skills_markdown,
content.source_markdown,
content.markdown,
content.skill_source,
config.skill_markdown,
config.skills_markdown,
config.source_markdown,
config.skill_source
]
return candidates.find((item) => normalizeDigitalEmployeeText(item)) || ''
}
function buildDefaultDigitalEmployeeSource(source = {}, listMeta = {}, schedule = {}) {
const name = listMeta.name || '数字员工技能'
const description =
listMeta.summary ||
sanitizeDigitalEmployeeText(source.description, '该技能用于后台自动执行指定任务。')
return [
'---',
`name: ${listMeta.code || 'digital.skill'}`,
`description: ${description}`,
'---',
'',
`# ${name}`,
'',
'## 功能说明',
'',
description,
'',
'## 执行方式',
'',
`- 技能类型:${listMeta.skillCategory || '整理'}`,
`- 可选类型:${DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS.join('、')}`,
`- 执行计划:${schedule.label || '手动触发'}`,
`- 触发方式:${listMeta.executionMode || '手动触发'}`,
'',
'## 操作要求',
'',
'- 按任务参数读取业务数据。',
'- 运行完成后写回业务结果或运行日志。'
].join('\n')
}
export function buildDigitalEmployeeSourceMarkdown(source = {}, content = {}, listMeta = {}) {
const config = source.config_json || source.configJson || {}
if (
normalizeDigitalEmployeeText(source.current_version_content_type) === 'markdown' &&
typeof source.current_version_content === 'string'
) {
return sanitizeDigitalEmployeeSource(source.current_version_content)
}
const schedule = resolveDigitalEmployeeSchedule(source, content)
const sourceMarkdown = resolveDigitalEmployeeMarkdownFromContent(content, config)
return sanitizeDigitalEmployeeSource(
sourceMarkdown,
buildDefaultDigitalEmployeeSource(source, listMeta, schedule)
)
}
function buildDigitalEmployeeBasicRows(source = {}, listMeta = {}, schedule = {}) {
return [
{ label: '技能编号', value: listMeta.code },
{ label: '技能类型', value: listMeta.skillCategory },
{ label: '维护人', value: listMeta.owner },
{ label: '执行计划', value: schedule.label },
{ label: '当前版本', value: source.working_version || source.current_version || '-' },
{ label: '最近更新', value: source.updated_at || '-' }
]
}
export function buildDigitalEmployeeListMeta(source = {}) {
const content = parseDigitalEmployeeContent(source.current_version_content)
const taskType = resolveDigitalEmployeeTaskType(source, content)
const skillCategory = resolveDigitalEmployeeSkillCategory(source, content)
const schedule = resolveDigitalEmployeeSchedule(source, content)
const enabled = resolveDigitalEmployeeEnabled(source)
const fallbackName = TASK_TYPE_LABELS[taskType] || '数字员工技能'
@@ -217,6 +332,8 @@ export function buildDigitalEmployeeListMeta(source = {}) {
code: resolveDigitalEmployeeDisplayCode(source, content),
summary: sanitizeDigitalEmployeeText(source.description, '面向后台自动执行的数字员工技能。'),
category: '数字员工',
skillCategory,
skillCategoryOptions: DIGITAL_EMPLOYEE_SKILL_CATEGORY_OPTIONS,
owner: sanitizeDigitalEmployeeText(source.owner, '平台运营'),
reviewer: sanitizeDigitalEmployeeText(source.reviewer, '系统'),
scope: schedule.label,
@@ -237,6 +354,7 @@ export function buildDigitalEmployeeDetailMeta(source = {}) {
})
const schedule = resolveDigitalEmployeeSchedule(source, content)
const contentRows = buildDigitalEmployeeContentRows(content)
const sourceMarkdown = buildDigitalEmployeeSourceMarkdown(source, content, listMeta)
return {
...listMeta,
@@ -245,13 +363,16 @@ export function buildDigitalEmployeeDetailMeta(source = {}) {
source.description,
'该技能由后台数字员工按计划执行,并把结果沉淀到对应业务资产或运行日志中。'
),
sourceMarkdown,
basicRows: buildDigitalEmployeeBasicRows(source, listMeta, schedule),
contentRows,
contentPreview: buildDigitalEmployeeContentPreview(content),
scheduleRows: [
{ label: '执行计划', value: schedule.label },
{ label: '调度表达式', value: schedule.value || '手动触发' },
{ label: '启动状态', value: listMeta.enabledLabel, tone: listMeta.enabledTone },
{ label: '执行方式', value: listMeta.executionMode }
{ label: '执行方式', value: listMeta.executionMode },
{ label: '技能类型', value: listMeta.skillCategory }
],
overviewRows: [
{ label: '能力编号', value: listMeta.code },

View File

@@ -22,24 +22,6 @@ export const TYPE_META = {
typeLabel: '规则',
tableColumns: RULE_TABLE_COLUMNS
},
skills: {
assetType: 'skill',
label: '技能',
typeLabel: '技能',
createButtonLabel: '技能已接入',
hintText: '技能页签已接到真实资产 API可查看输入、输出、依赖和场景信息。',
searchPlaceholder: '搜索技能名称、编码或负责人',
showMetricColumn: false,
tableColumns: {
name: '技能名称',
category: '业务域',
owner: '负责人',
scope: '适用场景',
runtime: '输入摘要',
version: '当前版本',
metric: ''
}
},
mcp: {
assetType: 'mcp',
label: 'MCP',
@@ -87,41 +69,10 @@ export const TAB_META = {
showStatusColumn: true,
badgeTone: 'rose'
},
skills: {
...TYPE_META.skills,
typeKey: 'skills',
badgeTone: 'blue'
},
mcp: {
...TYPE_META.mcp,
typeKey: 'mcp',
badgeTone: 'amber'
},
digitalWorkers: {
assetType: 'task',
typeKey: 'digitalWorkers',
label: '数字员工',
typeLabel: '数字员工',
createButtonLabel: '数字员工已接入',
hintText: '归集后台自动执行的数字员工技能,可查看技能内容、执行计划、启动状态和最近版本。',
searchPlaceholder: '搜索数字员工技能、编号、执行计划或维护人',
showMetricColumn: true,
showRuntimeColumn: true,
showVersionColumn: true,
showStatusColumn: true,
showEnabledColumn: true,
tableColumns: {
name: '技能名称',
category: '归集标签',
owner: '维护归口',
scope: '执行计划',
runtime: '触发方式',
version: '当前版本',
status: '资产状态',
metric: '运行方式',
updatedAt: '最近更新'
},
badgeTone: 'violet'
}
}
@@ -199,24 +150,6 @@ export const DETAIL_TITLES = {
publishTitle: '上线控制',
publishDesc: '正式上线会调用后端激活接口,审核未通过时会被拦截。'
},
skills: {
configTitle: '技能配置',
configDesc: '展示技能编码、输入摘要、版本和业务域。',
detailTitle: '技能结构',
detailDesc: '按输入、输出和依赖组织技能定义。',
outputTitle: '输出契约',
outputDesc: '技能详情重点展示输入参数、输出参数和依赖能力。',
ruleListTitle: '输出要求',
checkListTitle: '当前快照',
triggerTitle: '适用场景',
triggerDesc: '当前技能注册到的场景标签',
toolTitle: '依赖能力',
toolDesc: '技能当前依赖的数据库或其他能力',
historyTitle: '版本历史',
historyDesc: '最近版本记录',
publishTitle: '发布状态',
publishDesc: '技能当前状态由资产中心统一管理。'
},
mcp: {
configTitle: 'MCP 连接配置',
configDesc: '展示服务地址、超时和调用方式。',
@@ -234,24 +167,6 @@ export const DETAIL_TITLES = {
historyDesc: '最近版本记录',
publishTitle: '服务状态',
publishDesc: 'MCP 资产已接入规则中心,但真实外部调用仍以后续链路集成为准。'
},
digitalWorkers: {
configTitle: '技能档案',
configDesc: '展示数字员工技能的编号、归口、执行计划和启停状态。',
detailTitle: '技能内容',
detailDesc: '展示当前版本记录的任务类型、调度范围和执行参数。',
outputTitle: '执行安排',
outputDesc: '展示什么时候执行、是否启动,以及当前运行方式。',
ruleListTitle: '技能参数',
checkListTitle: '启动状态',
triggerTitle: '执行计划',
triggerDesc: '当前技能的计划执行时间或触发方式。',
toolTitle: '运行归口',
toolDesc: '数字员工技能由后台调度执行,运行结果进入对应日志或业务资产。',
historyTitle: '版本记录',
historyDesc: '最近的技能配置快照。',
publishTitle: '启动状态',
publishDesc: '数字员工技能由资产状态和调度配置共同决定是否启动。'
}
}

View File

@@ -34,13 +34,6 @@ import {
resolveRiskRuleSeverity,
resolveRiskRuleSeverityLabel
} from './auditViewRiskRuleModel.js'
import {
buildDigitalEmployeeContentRows,
buildDigitalEmployeeDetailMeta,
buildDigitalEmployeeListMeta,
isDigitalEmployeeAsset,
sanitizeDigitalEmployeeText
} from './auditViewDigitalEmployeeModel.js'
const EXPENSE_TYPE_SCENARIO_LABELS = {
travel: '差旅费',
@@ -342,9 +335,6 @@ export function resolveTabId(source, typeKey) {
if (typeKey === 'rules') {
return resolveRuleTabId(source)
}
if (typeKey === 'digitalWorkers') {
return isDigitalEmployeeAsset(source) ? 'digitalWorkers' : ''
}
return typeKey
}
@@ -899,15 +889,9 @@ export function resolveTypeKey(assetType) {
if (assetType === 'rule') {
return 'rules'
}
if (assetType === 'skill') {
return 'skills'
}
if (assetType === 'mcp') {
return 'mcp'
}
if (assetType === 'task') {
return 'digitalWorkers'
}
return ''
}
@@ -965,15 +949,9 @@ export function buildRowRuntime(asset, typeKey) {
if (typeKey === 'rules') {
return formatSeverity(asset.config_json?.severity)
}
if (typeKey === 'skills') {
return formatInputSummary(asset.config_json?.input_schema)
}
if (typeKey === 'mcp') {
return normalizeText(asset.config_json?.endpoint) || '未配置地址'
}
if (typeKey === 'digitalWorkers') {
return buildDigitalEmployeeListMeta(asset).executionMode
}
return ''
}
@@ -981,15 +959,9 @@ export function buildRowMetric(asset, typeKey) {
if (typeKey === 'rules') {
return normalizeText(asset.modified_by) || '未记录'
}
if (typeKey === 'skills') {
return '进入详情查看输出'
}
if (typeKey === 'mcp') {
return asset.config_json?.timeout_ms ? `${asset.config_json.timeout_ms} ms` : '未配置超时'
}
if (typeKey === 'digitalWorkers') {
return buildDigitalEmployeeListMeta(asset).executionMode
}
return ''
}
@@ -1061,19 +1033,16 @@ export function buildListItem(asset) {
? resolveRiskRuleScoreLabel(asset.config_json, asset.config_json) || resolveRiskRuleSeverityLabel(asset.config_json)
: resolveRiskRuleSeverityLabel(asset.config_json)
: ''
const digitalMeta = typeKey === 'digitalWorkers' ? buildDigitalEmployeeListMeta(asset) : null
const displayName = digitalMeta?.name || asset.name
const displayCode = digitalMeta?.code || asset.code
const displaySummary = digitalMeta?.summary || listSubtitle
const displayOwner = digitalMeta?.owner || (isRiskRule ? creator : asset.owner)
const displayReviewer = digitalMeta?.reviewer || reviewer
const displayCategory = digitalMeta?.category || resolveDomainLabel(asset.domain)
const displayScope =
digitalMeta?.scope ||
(typeKey === 'rules' ? ruleScenarioCategory || '閫氱敤' : formatScenarioList(asset.scenario_json))
const displayEnabledValue = digitalMeta ? digitalMeta.enabled : isEnabledValue
const displayEnabledLabel = digitalMeta?.enabledLabel || (isEnabledValue ? '鏄? : '?)
const displayEnabledTone = digitalMeta?.enabledTone || (isEnabledValue ? 'success' : 'disabled')
const displayName = asset.name
const displayCode = asset.code
const displaySummary = listSubtitle
const displayOwner = isRiskRule ? creator : asset.owner
const displayReviewer = reviewer
const displayCategory = resolveDomainLabel(asset.domain)
const displayScope = typeKey === 'rules' ? ruleScenarioCategory || '通用' : formatScenarioList(asset.scenario_json)
const displayEnabledValue = isEnabledValue
const displayEnabledLabel = isEnabledValue ? '是' : '否'
const displayEnabledTone = isEnabledValue ? 'success' : 'disabled'
return {
id: asset.id,
@@ -1093,7 +1062,6 @@ export function buildListItem(asset) {
category: displayCategory,
owner: displayOwner,
reviewer: displayReviewer,
scope: typeKey === 'rules' ? ruleScenarioCategory || '通用' : formatScenarioList(asset.scenario_json),
scope: displayScope,
riskCategory: ruleScenarioCategory,
scenarioList: ruleScenarioList,
@@ -1117,9 +1085,6 @@ export function buildListItem(asset) {
isOnlineValue,
isOnlineLabel: onlineMeta.label,
isOnlineTone: onlineMeta.tone,
isEnabledValue,
isEnabledLabel: isEnabledValue ? '是' : '否',
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
isEnabledValue: displayEnabledValue,
isEnabledLabel: displayEnabledLabel,
isEnabledTone: displayEnabledTone,
@@ -1163,22 +1128,6 @@ export function buildRuleFields(detail) {
]
}
export function buildSkillFields(detail) {
const content = detail.current_version_content || {}
return [
{ label: '技能编码', value: detail.code },
{ label: '业务域', value: resolveDomainLabel(detail.domain) },
{
label: '输入参数',
value: Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('、') : '未配置'
},
{
label: '输出参数',
value: Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('、') : '未配置'
}
]
}
export function buildMcpFields(detail, latestCall) {
const content = detail.current_version_content || {}
return [
@@ -1196,9 +1145,6 @@ export function buildFields(detail, typeKey, latestCall) {
if (typeKey === 'rules') {
return buildRuleFields(detail)
}
if (typeKey === 'skills') {
return buildSkillFields(detail)
}
if (typeKey === 'mcp') {
return buildMcpFields(detail, latestCall)
}
@@ -1208,29 +1154,6 @@ export function buildFields(detail, typeKey, latestCall) {
export function buildPromptSections(detail, typeKey) {
const content = detail.current_version_content || {}
if (typeKey === 'skills') {
return [
{
title: '输入参数',
intent: '技能入口',
content: Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('\n') : '未配置输入参数。'
},
{
title: '输出参数',
intent: '技能产出',
content: Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('\n') : '未配置输出参数。'
},
{
title: '依赖能力',
intent: '外部依赖',
content:
Array.isArray(content.dependencies) && content.dependencies.length
? content.dependencies.join('\n')
: '当前技能未声明外部依赖。'
}
]
}
if (typeKey === 'mcp') {
return [
{
@@ -1274,14 +1197,6 @@ export function buildOutputRules(detail, typeKey) {
]
}
if (typeKey === 'skills') {
return [
`输入参数:${Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('、') : '未配置'}`,
`输出参数:${Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('、') : '未配置'}`,
`依赖能力:${Array.isArray(content.dependencies) && content.dependencies.length ? content.dependencies.join('、') : '未声明'}`
]
}
if (typeKey === 'mcp') {
return [
`服务地址:${normalizeText(detail.config_json?.endpoint) || '未配置'}`,
@@ -1312,24 +1227,6 @@ export function buildTests(detail, typeKey, latestCall) {
]
}
if (typeKey === 'skills') {
const content = detail.current_version_content || {}
return [
{
name: '输入数量',
input: detail.current_version || '暂无版本',
result: `${content.inputs?.length || 0}`,
tone: 'success'
},
{
name: '输出数量',
input: detail.current_version || '暂无版本',
result: `${content.outputs?.length || 0}`,
tone: 'success'
}
]
}
if (typeKey === 'mcp') {
return [
{
@@ -1356,15 +1253,6 @@ export function buildTests(detail, typeKey, latestCall) {
export function buildTools(detail, typeKey, latestCall) {
const content = detail.current_version_content || {}
if (typeKey === 'skills') {
return (content.dependencies || []).map((item) => ({
name: item,
scope: '技能依赖',
mode: '读取',
tone: 'safe'
}))
}
if (typeKey === 'mcp') {
return [
{
@@ -1454,40 +1342,32 @@ export function buildDetailViewModel(detail, runs) {
const initialRiskRuleScore = resolveRiskRuleScore(configJson, configJson)
const initialRiskRuleScoreLevel = resolveRiskRuleScoreLevel(configJson, configJson)
const initialRiskRuleSeverity = initialRiskRuleScoreLevel || resolveRiskRuleSeverity(configJson)
const digitalMeta = typeKey === 'digitalWorkers'
? buildDigitalEmployeeDetailMeta({
...detail,
updated_at: formatDateTime(detail.updated_at)
})
: null
const detailName = digitalMeta?.name || detail.name
const detailCode = digitalMeta?.code || detail.code
const detailSummary = digitalMeta?.description ||
(usesJsonRiskRule ? buildRiskListSubtitle(detail.description) : detail.description)
const detailOwner = digitalMeta?.owner || detail.owner
const detailReviewer = digitalMeta?.reviewer || detail.reviewer || detail.latest_review?.reviewer || '寰呭垎閰?
const detailCategory = digitalMeta?.category || resolveDomainLabel(detail.domain)
const detailScope =
digitalMeta?.scope ||
(typeKey === 'rules' ? ruleScenarioCategory || '閫氱敤' : formatScenarioList(detail.scenario_json))
const detailEnabledValue = digitalMeta ? digitalMeta.enabled : isEnabledValue
const detailEnabledLabel = digitalMeta?.enabledLabel || (isEnabledValue ? '? : '鍚?)
const detailEnabledTone = digitalMeta?.enabledTone || (isEnabledValue ? 'success' : 'disabled')
const detailName = detail.name
const detailCode = detail.code
const detailSummary = usesJsonRiskRule ? buildRiskListSubtitle(detail.description) : detail.description
const detailOwner = detail.owner
const detailReviewer = detail.reviewer || detail.latest_review?.reviewer || '待分配'
const detailCategory = resolveDomainLabel(detail.domain)
const detailScope = typeKey === 'rules' ? ruleScenarioCategory || '通用' : formatScenarioList(detail.scenario_json)
const detailEnabledValue = isEnabledValue
const detailEnabledLabel = isEnabledValue ? '是' : '否'
const detailEnabledTone = isEnabledValue ? 'success' : 'disabled'
return {
id: detail.id,
tabId,
type: typeKey,
typeLabel: tabMeta.typeLabel,
short: makeShort(detail.name),
name: detail.name,
code: detail.code,
summary: usesJsonRiskRule ? buildRiskListSubtitle(detail.description) : detail.description,
listSubtitle: usesJsonRiskRule ? buildRiskListSubtitle(detail.description) : normalizeText(detail.description),
owner: detail.owner,
reviewer: detail.reviewer || detail.latest_review?.reviewer || '待分配',
category: resolveDomainLabel(detail.domain),
scope: typeKey === 'rules' ? ruleScenarioCategory || '通用' : formatScenarioList(detail.scenario_json),
short: makeShort(detailName),
name: detailName,
code: detailCode,
rawCode: detail.code,
summary: detailSummary,
listSubtitle: normalizeText(detailSummary),
owner: detailOwner,
reviewer: detailReviewer,
category: detailCategory,
scope: detailScope,
businessStageValue: businessStage.value,
businessStageLabel: businessStage.label,
version: detail.working_version || detail.current_version || '-',
@@ -1524,9 +1404,9 @@ export function buildDetailViewModel(detail, runs) {
isOnlineValue: onlineMeta.online,
isOnlineLabel: onlineMeta.label,
isOnlineTone: onlineMeta.tone,
isEnabledValue,
isEnabledLabel: isEnabledValue ? '' : '',
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
isEnabledValue: detailEnabledValue,
isEnabledLabel: detailEnabledLabel,
isEnabledTone: detailEnabledTone,
publisher:
detail.status === 'active'
? normalizeText(detail.published_by) ||

View File

@@ -16,12 +16,12 @@ export function incrementVersion(version) {
export function buildReviewNote(status) {
if (status === 'approved') {
return '通过任务规则中心审核。'
return '通过规则中心审核。'
}
if (status === 'rejected') {
return '在任务规则中心驳回当前版本。'
return '在规则中心驳回当前版本。'
}
return '提交任务规则中心待审核。'
return '提交规则中心待审核。'
}
export function buildRuleConfigPayload(asset, runtimeRule) {

View File

@@ -0,0 +1,122 @@
const DEFAULT_SCHEDULE_TIME = '00:00'
const CRON_TOKEN_PATTERN = /^[\d*/,?\-]+$/
export const DIGITAL_EMPLOYEE_SCHEDULE_MODES = [
{ value: 'manual', label: '手动触发' },
{ value: 'daily', label: '每天执行' },
{ value: 'weekly', label: '每周执行' },
{ value: 'custom', label: 'Cron 表达式' }
]
export const DIGITAL_EMPLOYEE_WEEKDAY_OPTIONS = [
{ value: '1', label: '周一' },
{ value: '2', label: '周二' },
{ value: '3', label: '周三' },
{ value: '4', label: '周四' },
{ value: '5', label: '周五' },
{ value: '6', label: '周六' },
{ value: '0', label: '周日' }
]
function normalizeText(value) {
return String(value ?? '').trim()
}
function normalizeTime(value) {
const raw = normalizeText(value)
return /^\d{2}:\d{2}$/.test(raw) ? raw : DEFAULT_SCHEDULE_TIME
}
function toTime(hour, minute) {
const hourNumber = Number(hour)
const minuteNumber = Number(minute)
if (!Number.isInteger(hourNumber) || hourNumber < 0 || hourNumber > 23) {
return DEFAULT_SCHEDULE_TIME
}
if (!Number.isInteger(minuteNumber) || minuteNumber < 0 || minuteNumber > 59) {
return DEFAULT_SCHEDULE_TIME
}
return `${String(hourNumber).padStart(2, '0')}:${String(minuteNumber).padStart(2, '0')}`
}
export function createDigitalEmployeeScheduleForm(cron = '') {
const normalizedCron = normalizeText(cron)
if (!normalizedCron) {
return { mode: 'manual', time: DEFAULT_SCHEDULE_TIME, weekday: '1', cron: '' }
}
const parts = normalizedCron.split(/\s+/)
if (parts.length !== 5) {
return { mode: 'custom', time: DEFAULT_SCHEDULE_TIME, weekday: '1', cron: normalizedCron }
}
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts
const time = toTime(hour, minute)
if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
return { mode: 'daily', time, weekday: '1', cron: normalizedCron }
}
if (dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') {
return { mode: 'weekly', time, weekday: dayOfWeek || '1', cron: normalizedCron }
}
return { mode: 'custom', time, weekday: '1', cron: normalizedCron }
}
export function resolveDigitalEmployeeScheduleValue(employee = {}) {
const config = employee.configJson || {}
return (
normalizeText(employee.digitalEmployee?.scheduleValue) ||
normalizeText(config.cron) ||
normalizeText(config.schedule) ||
normalizeText(config.cron_expression)
)
}
export function buildDigitalEmployeeScheduleCron(form = {}) {
const mode = normalizeText(form.mode) || 'manual'
if (mode === 'manual') {
return ''
}
if (mode === 'custom') {
const cron = normalizeText(form.cron)
const parts = cron.split(/\s+/).filter(Boolean)
if (parts.length !== 5 || !parts.every((part) => CRON_TOKEN_PATTERN.test(part))) {
throw new Error('请输入 5 段 Cron 表达式。')
}
return parts.join(' ')
}
const [hour, minute] = normalizeTime(form.time).split(':')
if (mode === 'daily') {
return `${Number(minute)} ${Number(hour)} * * *`
}
if (mode === 'weekly') {
const weekday = normalizeText(form.weekday) || '1'
return `${Number(minute)} ${Number(hour)} * * ${weekday}`
}
throw new Error('请选择有效的执行方式。')
}
export function buildDigitalEmployeeScheduleConfig(config = {}, cron = '') {
const nextConfig = config && typeof config === 'object' && !Array.isArray(config)
? { ...config }
: {}
const normalizedCron = normalizeText(cron)
if (normalizedCron) {
nextConfig.cron = normalizedCron
nextConfig.schedule = normalizedCron
nextConfig.cron_expression = normalizedCron
return nextConfig
}
delete nextConfig.cron
delete nextConfig.schedule
delete nextConfig.cron_expression
return nextConfig
}