refactor: 重构 AuditView 和 TravelReimbursementCreateView 相关代码
- 优化 agent_assets、agent_foundation、user_agent 服务层结构 - 更新 AuditView 视图和脚本 - 更新 TravelReimbursementCreateView 脚本 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,10 @@ import {
|
||||
fetchAgentAssetSpreadsheetBlob,
|
||||
fetchAgentAssetSpreadsheetChangeRecords,
|
||||
fetchAgentAssetSpreadsheetOnlyOfficeConfig,
|
||||
fetchAgentAssetRuleJson,
|
||||
fetchAgentAssetVersionTimeline,
|
||||
fetchAgentRuns,
|
||||
saveAgentAssetRuleJson,
|
||||
importAgentAssetSpreadsheetContent,
|
||||
restoreAgentAssetVersion,
|
||||
updateAgentAsset
|
||||
@@ -30,9 +32,8 @@ const RULE_TABLE_COLUMNS = {
|
||||
category: '业务域',
|
||||
owner: '负责人',
|
||||
scope: '适用场景',
|
||||
runtime: '风险等级',
|
||||
version: '当前版本',
|
||||
metric: '审核状态'
|
||||
version: '修改次数',
|
||||
metric: '修改人'
|
||||
}
|
||||
|
||||
const TYPE_META = {
|
||||
@@ -106,6 +107,8 @@ const TAB_META = {
|
||||
hintText: '仅展示 tag 为“财务规则”的规则资产;未打新 tag 的旧规则已从规则中心隐藏。',
|
||||
searchPlaceholder: '搜索财务规则名称、编码或负责人',
|
||||
tableColumns: RULE_TABLE_COLUMNS,
|
||||
showRuntimeColumn: false,
|
||||
showStatusColumn: false,
|
||||
badgeTone: 'emerald'
|
||||
},
|
||||
riskRules: {
|
||||
@@ -114,9 +117,12 @@ const TAB_META = {
|
||||
label: '风险规则',
|
||||
typeLabel: '风险规则',
|
||||
createButtonLabel: '风险规则已接入',
|
||||
hintText: '仅展示 tag 为“风险规则”的规则资产;当前未打标签的规则不会显示在这里。',
|
||||
hintText: '仅展示平台风险规则;适用场景按差旅、发票、餐饮招待等分类,可用「使用场景」筛选。',
|
||||
searchPlaceholder: '搜索风险规则名称、编码或负责人',
|
||||
tableColumns: RULE_TABLE_COLUMNS,
|
||||
showRuntimeColumn: false,
|
||||
showVersionColumn: false,
|
||||
showStatusColumn: false,
|
||||
badgeTone: 'rose'
|
||||
},
|
||||
skills: {
|
||||
@@ -287,7 +293,32 @@ const RULE_TAB_TAG_ALIASES = {
|
||||
riskRules: new Set(['风险规则', '风险', '风控', 'riskrule', 'riskrules', 'risk'])
|
||||
}
|
||||
|
||||
const RISK_SCENARIO_OPTIONS = [
|
||||
{ value: '', label: '全部场景' },
|
||||
{ value: '差旅', label: '差旅' },
|
||||
{ value: '发票', label: '发票' },
|
||||
{ value: '餐饮招待', label: '餐饮招待' },
|
||||
{ value: '交通出行', label: '交通出行' },
|
||||
{ value: '办公物料', label: '办公物料' },
|
||||
{ value: '费用科目', label: '费用科目' },
|
||||
{ value: '通用', label: '通用' }
|
||||
]
|
||||
|
||||
const LEGACY_RISK_SCENARIO_KEYS = new Set([
|
||||
'expense',
|
||||
'risk_check',
|
||||
'travel',
|
||||
'meal',
|
||||
'invoice',
|
||||
'travel_policy',
|
||||
'travel_standard',
|
||||
'attachment_policy',
|
||||
'scene_policy',
|
||||
'invoice_anomaly'
|
||||
])
|
||||
|
||||
const SPREADSHEET_DETAIL_MODE = 'spreadsheet'
|
||||
const JSON_RISK_DETAIL_MODE = 'json_risk'
|
||||
const PREVIEW_RULE_ID = 'preview-rule-expense-company-travel-expense'
|
||||
const PREVIEW_RULE_CODE = 'rule.expense.company_travel_expense_reimbursement'
|
||||
const PREVIEW_RULE_VERSION_SPECS = [
|
||||
@@ -461,6 +492,11 @@ function isSpreadsheetRuleSource(value) {
|
||||
return normalizeText(configJson.detail_mode || configJson.rule_detail_mode).toLowerCase() === SPREADSHEET_DETAIL_MODE
|
||||
}
|
||||
|
||||
function isJsonRiskRuleSource(value) {
|
||||
const configJson = readConfigJson(value)
|
||||
return normalizeText(configJson.detail_mode || configJson.rule_detail_mode).toLowerCase() === JSON_RISK_DETAIL_MODE
|
||||
}
|
||||
|
||||
function normalizeRuleTagValue(value) {
|
||||
return normalizeText(value).toLowerCase().replace(/[\s_-]+/g, '')
|
||||
}
|
||||
@@ -478,6 +514,14 @@ function collectRuleTagValues(source) {
|
||||
}
|
||||
|
||||
function resolveRuleTabId(source) {
|
||||
const code = normalizeText(source?.code || '').toLowerCase()
|
||||
if (code.startsWith('risk.')) {
|
||||
return 'riskRules'
|
||||
}
|
||||
if (isJsonRiskRuleSource(source)) {
|
||||
return 'riskRules'
|
||||
}
|
||||
|
||||
const normalizedTags = collectRuleTagValues(source).map((item) => normalizeRuleTagValue(item))
|
||||
|
||||
if (normalizedTags.some((item) => RULE_TAB_TAG_ALIASES.riskRules.has(item))) {
|
||||
@@ -510,6 +554,108 @@ function resolveTabMeta(tabId, typeKey) {
|
||||
return TAB_META[typeKey]
|
||||
}
|
||||
|
||||
function resolveRiskRuleDescription(payload) {
|
||||
if (!isPlainObject(payload)) {
|
||||
return ''
|
||||
}
|
||||
return normalizeText(payload.description)
|
||||
}
|
||||
|
||||
function resolveRiskRuleSourceRef(payload) {
|
||||
if (!isPlainObject(payload)) {
|
||||
return ''
|
||||
}
|
||||
const metadata = isPlainObject(payload.metadata) ? payload.metadata : {}
|
||||
return normalizeText(metadata.source_ref)
|
||||
}
|
||||
|
||||
function inferRiskCategoryFromCode(code) {
|
||||
const normalized = normalizeText(code).toLowerCase()
|
||||
if (normalized.startsWith('risk.travel.')) {
|
||||
return '差旅'
|
||||
}
|
||||
if (normalized.startsWith('risk.invoice.')) {
|
||||
return '发票'
|
||||
}
|
||||
if (normalized.includes('entertainment') || normalized.includes('meal_localized')) {
|
||||
return '餐饮招待'
|
||||
}
|
||||
if (normalized.includes('consecutive_transport')) {
|
||||
return '交通出行'
|
||||
}
|
||||
if (normalized.startsWith('risk.expense.')) {
|
||||
return '费用科目'
|
||||
}
|
||||
return '通用'
|
||||
}
|
||||
|
||||
function resolveRiskRuleCategory(source) {
|
||||
const configJson = readConfigJson(source)
|
||||
const explicit = normalizeText(configJson.risk_category)
|
||||
if (explicit) {
|
||||
return explicit
|
||||
}
|
||||
|
||||
const payloadCategory = normalizeText(source?.risk_category)
|
||||
if (payloadCategory) {
|
||||
return payloadCategory
|
||||
}
|
||||
|
||||
const scenarioItems = Array.isArray(source?.scenario_json)
|
||||
? source.scenario_json
|
||||
: Array.isArray(source?.scenarioList)
|
||||
? source.scenarioList
|
||||
: []
|
||||
const businessScenario = scenarioItems
|
||||
.map((item) => normalizeText(item))
|
||||
.find((item) => item && !LEGACY_RISK_SCENARIO_KEYS.has(item))
|
||||
if (businessScenario) {
|
||||
return businessScenario
|
||||
}
|
||||
|
||||
return inferRiskCategoryFromCode(source?.code)
|
||||
}
|
||||
|
||||
function buildRiskListSubtitle(text, maxLength = 42) {
|
||||
const normalized = normalizeText(text)
|
||||
if (!normalized) {
|
||||
return '平台内置风险规则'
|
||||
}
|
||||
const firstSentence = normalized.split(/[。;;!?\n]/)[0] || normalized
|
||||
if (firstSentence.length <= maxLength) {
|
||||
return firstSentence
|
||||
}
|
||||
return `${firstSentence.slice(0, maxLength)}…`
|
||||
}
|
||||
|
||||
function applyRiskRuleJsonState(target, payload, apiPayload) {
|
||||
const rulePayload = isPlainObject(payload) ? payload : {}
|
||||
const fullDescription =
|
||||
resolveRiskRuleDescription(rulePayload) ||
|
||||
normalizeText(apiPayload?.description) ||
|
||||
normalizeText(target.riskRuleDescription)
|
||||
const riskCategory =
|
||||
normalizeText(rulePayload.risk_category) ||
|
||||
resolveRiskRuleCategory({ ...target, risk_category: rulePayload.risk_category, config_json: rulePayload })
|
||||
|
||||
return {
|
||||
...target,
|
||||
riskRuleDescription: fullDescription,
|
||||
riskRuleSubtitle: buildRiskListSubtitle(fullDescription, 48),
|
||||
riskCategory,
|
||||
scope: riskCategory,
|
||||
riskRuleSourceRef: resolveRiskRuleSourceRef(rulePayload),
|
||||
riskRuleSummary: {
|
||||
name: apiPayload?.name || target.name,
|
||||
evaluator: apiPayload?.evaluator || rulePayload.evaluator || '',
|
||||
ontologySignal: apiPayload?.ontology_signal || rulePayload.ontology_signal || '',
|
||||
inputs: apiPayload?.inputs || rulePayload.inputs || {},
|
||||
outcomes: apiPayload?.outcomes || rulePayload.outcomes || {}
|
||||
},
|
||||
riskRuleJsonText: JSON.stringify(rulePayload, null, 2)
|
||||
}
|
||||
}
|
||||
|
||||
function cloneJsonObject(value) {
|
||||
if (!isPlainObject(value)) {
|
||||
return null
|
||||
@@ -812,7 +958,7 @@ function buildRowRuntime(asset, typeKey) {
|
||||
|
||||
function buildRowMetric(asset, typeKey) {
|
||||
if (typeKey === 'rules') {
|
||||
return asset.reviewer ? `审核人:${asset.reviewer}` : '待分配审核人'
|
||||
return normalizeText(asset.modified_by) || '未记录'
|
||||
}
|
||||
if (typeKey === 'skills') {
|
||||
return '进入详情查看输出'
|
||||
@@ -832,6 +978,25 @@ function buildListItem(asset) {
|
||||
|
||||
const tabMeta = resolveTabMeta(tabId, typeKey)
|
||||
const statusMeta = resolveStatusMeta(asset.status)
|
||||
const workingVersion = asset.working_version || asset.current_version || '-'
|
||||
const changeCount =
|
||||
typeof asset.change_count === 'number'
|
||||
? asset.change_count
|
||||
: Array.isArray(asset.recent_versions)
|
||||
? Math.max(asset.recent_versions.length - 1, 0)
|
||||
: 0
|
||||
const modifiedBy =
|
||||
normalizeText(asset.modified_by) ||
|
||||
normalizeText(
|
||||
Array.isArray(asset.recent_versions)
|
||||
? asset.recent_versions.find((item) => item.version === workingVersion)?.created_by
|
||||
: ''
|
||||
)
|
||||
const isRiskRule = tabId === 'riskRules'
|
||||
const riskCategory = isRiskRule ? resolveRiskRuleCategory(asset) : ''
|
||||
const listSubtitle = isRiskRule
|
||||
? buildRiskListSubtitle(asset.description)
|
||||
: normalizeText(asset.description)
|
||||
|
||||
return {
|
||||
id: asset.id,
|
||||
@@ -842,19 +1007,24 @@ function buildListItem(asset) {
|
||||
short: makeShort(asset.name),
|
||||
name: asset.name,
|
||||
code: asset.code,
|
||||
summary: asset.description,
|
||||
summary: listSubtitle,
|
||||
listSubtitle,
|
||||
category: resolveDomainLabel(asset.domain),
|
||||
owner: asset.owner,
|
||||
reviewer: asset.reviewer || '待分配',
|
||||
scope: formatScenarioList(asset.scenario_json),
|
||||
scope: isRiskRule ? riskCategory || '通用' : formatScenarioList(asset.scenario_json),
|
||||
riskCategory,
|
||||
model: buildRowRuntime(asset, typeKey),
|
||||
version: asset.working_version || asset.current_version || '-',
|
||||
version: workingVersion,
|
||||
versionDisplay: typeKey === 'rules' ? `${changeCount} 次` : workingVersion,
|
||||
publishedVersion: asset.published_version || '-',
|
||||
workingVersion: asset.working_version || asset.current_version || '-',
|
||||
workingVersion,
|
||||
status: statusMeta.label,
|
||||
statusValue: asset.status,
|
||||
statusTone: statusMeta.tone,
|
||||
hitRate: buildRowMetric(asset, typeKey),
|
||||
hitRate: buildRowMetric({ ...asset, modified_by: modifiedBy }, typeKey),
|
||||
modifiedBy,
|
||||
changeCount,
|
||||
updatedAt: formatDateTime(asset.updated_at),
|
||||
badgeTone: tabMeta.badgeTone,
|
||||
spotlight: asset.status === 'active',
|
||||
@@ -1214,6 +1384,7 @@ function buildDetailViewModel(detail, runs) {
|
||||
const history = buildHistory(detail.recent_versions || [], detail)
|
||||
const previewVersion = history.find((item) => item.isWorking) || history[0] || null
|
||||
const usesSpreadsheetRule = typeKey === 'rules' && isSpreadsheetRuleSource(detail)
|
||||
const usesJsonRiskRule = typeKey === 'rules' && isJsonRiskRuleSource(detail)
|
||||
const ruleDocument = readRuleDocumentMeta(detail)
|
||||
const previewRawMarkdown =
|
||||
detail.current_version_content_type === 'markdown'
|
||||
@@ -1239,11 +1410,12 @@ function buildDetailViewModel(detail, runs) {
|
||||
short: makeShort(detail.name),
|
||||
name: detail.name,
|
||||
code: detail.code,
|
||||
summary: detail.description,
|
||||
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: formatScenarioList(detail.scenario_json),
|
||||
scope: usesJsonRiskRule ? resolveRiskRuleCategory(detail) || '通用' : formatScenarioList(detail.scenario_json),
|
||||
version: detail.working_version || detail.current_version || '-',
|
||||
currentVersion: detail.current_version || '-',
|
||||
publishedVersion: detail.published_version || '-',
|
||||
@@ -1257,6 +1429,13 @@ function buildDetailViewModel(detail, runs) {
|
||||
badgeTone: tabMeta.badgeTone,
|
||||
configJson,
|
||||
usesSpreadsheetRule,
|
||||
usesJsonRiskRule,
|
||||
riskRuleJsonText: '{}',
|
||||
riskRuleSummary: null,
|
||||
riskRuleDescription: '',
|
||||
riskRuleSubtitle: usesJsonRiskRule ? buildRiskListSubtitle(detail.description) : '',
|
||||
riskRuleSourceRef: '',
|
||||
riskCategory: usesJsonRiskRule ? resolveRiskRuleCategory(detail) : '',
|
||||
ruleDocument,
|
||||
scenarioList: Array.isArray(detail.scenario_json) ? [...detail.scenario_json] : [],
|
||||
markdownContent: previewMarkdown,
|
||||
@@ -1380,6 +1559,7 @@ export default {
|
||||
const selectedDomain = ref('')
|
||||
const selectedOwner = ref('')
|
||||
const selectedStatus = ref('')
|
||||
const selectedRiskScenario = ref('')
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const detailLoading = ref(false)
|
||||
@@ -1434,11 +1614,17 @@ export default {
|
||||
const createButtonLabel = computed(() => activeMeta.value.createButtonLabel)
|
||||
const hintText = computed(() => activeMeta.value.hintText)
|
||||
const tableColumns = computed(() => activeMeta.value.tableColumns)
|
||||
const showRuntimeColumn = computed(() => activeMeta.value.showRuntimeColumn !== false)
|
||||
const showMetricColumn = computed(() => activeMeta.value.showMetricColumn !== false)
|
||||
const showVersionColumn = computed(() => activeMeta.value.showVersionColumn !== false)
|
||||
const showStatusColumn = computed(() => activeMeta.value.showStatusColumn !== false)
|
||||
const selectedSkillIsRule = computed(() => selectedSkill.value?.type === 'rules')
|
||||
const selectedSkillUsesSpreadsheet = computed(
|
||||
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesSpreadsheetRule)
|
||||
)
|
||||
const selectedSkillUsesJsonRisk = computed(
|
||||
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesJsonRiskRule)
|
||||
)
|
||||
const canManageSelected = computed(
|
||||
() => isAdmin.value && Boolean(selectedSkill.value) && !selectedSkill.value?.isPreviewMock
|
||||
)
|
||||
@@ -1581,13 +1767,23 @@ export default {
|
||||
const selectedStatusLabel = computed(
|
||||
() => STATUS_OPTIONS.find((item) => item.value === selectedStatus.value)?.label || '状态'
|
||||
)
|
||||
const showRiskScenarioFilter = computed(() => activeType.value === 'riskRules')
|
||||
const showStatusFilter = computed(() => activeType.value !== 'riskRules')
|
||||
const selectedRiskScenarioLabel = computed(
|
||||
() =>
|
||||
RISK_SCENARIO_OPTIONS.find((item) => item.value === selectedRiskScenario.value)?.label ||
|
||||
'使用场景'
|
||||
)
|
||||
const activeFilterTokens = computed(() => {
|
||||
const tokens = []
|
||||
|
||||
if (selectedDomain.value) {
|
||||
tokens.push(`业务域:${resolveDomainLabel(selectedDomain.value)}`)
|
||||
}
|
||||
if (selectedStatus.value) {
|
||||
if (showRiskScenarioFilter.value && selectedRiskScenario.value) {
|
||||
tokens.push(`使用场景:${selectedRiskScenario.value}`)
|
||||
}
|
||||
if (showStatusFilter.value && selectedStatus.value) {
|
||||
tokens.push(`状态:${resolveStatusMeta(selectedStatus.value).label}`)
|
||||
}
|
||||
if (selectedOwner.value) {
|
||||
@@ -1612,7 +1808,10 @@ export default {
|
||||
actionIcon: '',
|
||||
tone: 'amber',
|
||||
artLabel: 'ASSET',
|
||||
tips: ['切换页签可查看其他资产类型', '支持按业务域、负责人和状态做过滤']
|
||||
tips:
|
||||
activeType.value === 'riskRules'
|
||||
? ['切换页签可查看其他资产类型', '支持按业务域、负责人和使用场景做过滤']
|
||||
: ['切换页签可查看其他资产类型', '支持按业务域、负责人和状态做过滤']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1620,7 +1819,9 @@ export default {
|
||||
eyebrow: '筛选结果为空',
|
||||
title: `没有找到匹配的${activeTabLabel.value}`,
|
||||
desc: hasFilters
|
||||
? '试试清空业务域、负责人、状态或关键词筛选,再重新查看。'
|
||||
? showRiskScenarioFilter.value
|
||||
? '试试清空业务域、负责人、使用场景或关键词筛选,再重新查看。'
|
||||
: '试试清空业务域、负责人、状态或关键词筛选,再重新查看。'
|
||||
: `当前列表中还没有满足展示条件的${activeTabLabel.value}资产。`,
|
||||
icon: hasFilters ? 'mdi mdi-tune-variant' : 'mdi mdi-view-grid-outline',
|
||||
actionLabel: hasFilters ? '清空筛选' : '',
|
||||
@@ -1628,7 +1829,9 @@ export default {
|
||||
tone: hasFilters ? 'emerald' : 'slate',
|
||||
artLabel: hasFilters ? 'FILTER' : 'QUEUE',
|
||||
tips: hasFilters
|
||||
? ['业务域、负责人、状态与关键词会叠加过滤', '可以换个编码、名称或负责人关键词继续搜索']
|
||||
? showRiskScenarioFilter.value
|
||||
? ['业务域、负责人、使用场景与关键词会叠加过滤', '可以换个规则名称或场景分类继续搜索']
|
||||
: ['业务域、负责人、状态与关键词会叠加过滤', '可以换个编码、名称或负责人关键词继续搜索']
|
||||
: ['列表展示来自真实资产 API', '切换资产类型后会自动重新拉取数据']
|
||||
}
|
||||
})
|
||||
@@ -1675,9 +1878,18 @@ export default {
|
||||
: true
|
||||
const matchesDomain = selectedDomain.value ? item.domainValue === selectedDomain.value : true
|
||||
const matchesOwner = selectedOwner.value ? item.owner === selectedOwner.value : true
|
||||
const matchesStatus = selectedStatus.value ? item.statusValue === selectedStatus.value : true
|
||||
const matchesStatus = showStatusFilter.value
|
||||
? selectedStatus.value
|
||||
? item.statusValue === selectedStatus.value
|
||||
: true
|
||||
: true
|
||||
const matchesRiskScenario = showRiskScenarioFilter.value
|
||||
? selectedRiskScenario.value
|
||||
? item.riskCategory === selectedRiskScenario.value
|
||||
: true
|
||||
: true
|
||||
|
||||
return matchesKeyword && matchesDomain && matchesOwner && matchesStatus
|
||||
return matchesKeyword && matchesDomain && matchesOwner && matchesStatus && matchesRiskScenario
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1723,6 +1935,7 @@ export default {
|
||||
selectedDomain.value = ''
|
||||
selectedOwner.value = ''
|
||||
selectedStatus.value = ''
|
||||
selectedRiskScenario.value = ''
|
||||
activeFilterPopover.value = ''
|
||||
}
|
||||
|
||||
@@ -1753,6 +1966,9 @@ export default {
|
||||
if (name === 'status') {
|
||||
selectedStatus.value = value
|
||||
}
|
||||
if (name === 'riskScenario') {
|
||||
selectedRiskScenario.value = value
|
||||
}
|
||||
closeFilterPopover()
|
||||
}
|
||||
|
||||
@@ -1840,7 +2056,7 @@ export default {
|
||||
)
|
||||
spreadsheetChangeRecordsByAsset.value = {
|
||||
...spreadsheetChangeRecordsByAsset.value,
|
||||
[assetId]: [nextRecord, ...deduped].slice(0, 5)
|
||||
[assetId]: [nextRecord, ...deduped].slice(0, 30)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,22 +2114,51 @@ export default {
|
||||
function getLatestSpreadsheetChangeKey(assetId) {
|
||||
const records = spreadsheetChangeRecordsByAsset.value[assetId] || []
|
||||
const latest = records.find((item) => item?.changed_at)
|
||||
return latest ? `${latest.changed_at}-${latest.actor}-${latest.summary}` : ''
|
||||
if (!latest) {
|
||||
return ''
|
||||
}
|
||||
const previewSignature = Array.isArray(latest.cell_changes)
|
||||
? latest.cell_changes
|
||||
.slice(0, 8)
|
||||
.map((item) =>
|
||||
[
|
||||
item?.sheet_name,
|
||||
item?.cell,
|
||||
item?.change_type,
|
||||
item?.before_value,
|
||||
item?.after_value
|
||||
]
|
||||
.map((value) => normalizeText(value))
|
||||
.join(':')
|
||||
)
|
||||
.join('|')
|
||||
: ''
|
||||
return [
|
||||
latest.id,
|
||||
latest.changed_at,
|
||||
latest.actor,
|
||||
latest.summary,
|
||||
latest.changed_sheet_count,
|
||||
latest.changed_cell_count,
|
||||
previewSignature
|
||||
]
|
||||
.map((value) => normalizeText(value))
|
||||
.join('-')
|
||||
}
|
||||
|
||||
async function refreshSpreadsheetChangeRecordsAfterSave(assetId, previousLatestKey = '', attempt = 0) {
|
||||
const normalizedAssetId = normalizeText(assetId)
|
||||
if (!normalizedAssetId || selectedSkill.value?.id !== normalizedAssetId) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
await loadSpreadsheetChangeRecords(normalizedAssetId)
|
||||
const nextLatestKey = getLatestSpreadsheetChangeKey(normalizedAssetId)
|
||||
if (nextLatestKey && nextLatestKey !== previousLatestKey) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
if (attempt >= 9) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 800))
|
||||
return refreshSpreadsheetChangeRecordsAfterSave(normalizedAssetId, previousLatestKey, attempt + 1)
|
||||
@@ -1973,6 +2218,17 @@ export default {
|
||||
stopSpreadsheetOnlyOfficeVersionSync()
|
||||
return
|
||||
}
|
||||
|
||||
const changeRecordRefreshed = await refreshSpreadsheetChangeRecordsAfterSave(
|
||||
normalizedAssetId,
|
||||
previousLatestChangeKey
|
||||
)
|
||||
if (changeRecordRefreshed) {
|
||||
clearSpreadsheetPendingChangeRecord(normalizedAssetId, normalizedVersion)
|
||||
await refreshCurrentAssets()
|
||||
stopSpreadsheetOnlyOfficeVersionSync()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// Ignore transient polling failures and continue retrying within the window.
|
||||
}
|
||||
@@ -2275,10 +2531,15 @@ export default {
|
||||
const detail = await fetchAgentAssetDetail(assetId)
|
||||
selectedSkill.value = buildDetailViewModel(detail, runs.value)
|
||||
if (selectedSkill.value?.type === 'rules') {
|
||||
loadVersionTimeline(assetId, { silent: true }).catch(() => {})
|
||||
if (!selectedSkill.value.usesJsonRiskRule) {
|
||||
loadVersionTimeline(assetId, { silent: true }).catch(() => {})
|
||||
}
|
||||
if (selectedSkill.value.usesSpreadsheetRule) {
|
||||
loadSpreadsheetChangeRecords(assetId).catch(() => {})
|
||||
}
|
||||
if (selectedSkill.value.usesJsonRiskRule) {
|
||||
await loadRiskRuleJson(assetId)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
detailError.value = error?.message || '资产详情加载失败,请稍后重试。'
|
||||
@@ -2288,6 +2549,67 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRiskRuleJson(assetId) {
|
||||
if (!assetId || !selectedSkill.value?.usesJsonRiskRule) {
|
||||
return
|
||||
}
|
||||
const payload = await fetchAgentAssetRuleJson(assetId)
|
||||
const rulePayload = payload?.payload && typeof payload.payload === 'object' ? payload.payload : payload
|
||||
selectedSkill.value = applyRiskRuleJsonState(selectedSkill.value, rulePayload, payload)
|
||||
}
|
||||
|
||||
async function saveRiskRuleJson() {
|
||||
if (!selectedSkill.value?.id || !canEditMarkdown.value) {
|
||||
return
|
||||
}
|
||||
actionState.value = 'save-risk-json'
|
||||
detailBusy.value = true
|
||||
try {
|
||||
const parsed = JSON.parse(String(selectedSkill.value.riskRuleJsonText || '{}'))
|
||||
const saved = await saveAgentAssetRuleJson(selectedSkill.value.id, { payload: parsed })
|
||||
const rulePayload = saved?.payload && typeof saved.payload === 'object' ? saved.payload : saved
|
||||
selectedSkill.value = applyRiskRuleJsonState(selectedSkill.value, rulePayload, saved)
|
||||
toast('风险规则 JSON 已保存。')
|
||||
} catch (error) {
|
||||
toast(error?.message || '风险规则 JSON 保存失败。')
|
||||
} finally {
|
||||
detailBusy.value = false
|
||||
actionState.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function formatRiskRuleJson() {
|
||||
if (!selectedSkill.value?.usesJsonRiskRule) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(String(selectedSkill.value.riskRuleJsonText || '{}'))
|
||||
selectedSkill.value = applyRiskRuleJsonState(selectedSkill.value, parsed, {
|
||||
name: selectedSkill.value.name,
|
||||
description: resolveRiskRuleDescription(parsed)
|
||||
})
|
||||
} catch (error) {
|
||||
toast(error?.message || 'JSON 格式无效,无法格式化。')
|
||||
}
|
||||
}
|
||||
|
||||
function downloadRiskRuleJson() {
|
||||
if (!selectedSkill.value?.usesJsonRiskRule) {
|
||||
return
|
||||
}
|
||||
const blob = new Blob([String(selectedSkill.value.riskRuleJsonText || '{}')], {
|
||||
type: 'application/json;charset=utf-8'
|
||||
})
|
||||
const fileName =
|
||||
selectedSkill.value.ruleDocument?.file_name ||
|
||||
`${selectedSkill.value.code || 'risk-rule'}.json`
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
async function loadSpreadsheetChangeRecords(assetId) {
|
||||
if (!assetId) {
|
||||
return
|
||||
@@ -2328,6 +2650,11 @@ export default {
|
||||
configJson: {},
|
||||
isPreviewMock: false,
|
||||
usesSpreadsheetRule: false,
|
||||
usesJsonRiskRule: false,
|
||||
riskRuleJsonText: '{}',
|
||||
riskRuleSummary: null,
|
||||
riskRuleDescription: '',
|
||||
riskRuleSourceRef: '',
|
||||
ruleDocument: null,
|
||||
scenarioList: [],
|
||||
fields: [],
|
||||
@@ -2775,7 +3102,10 @@ export default {
|
||||
hintText,
|
||||
searchPlaceholder,
|
||||
tableColumns,
|
||||
showRuntimeColumn,
|
||||
showVersionColumn,
|
||||
showMetricColumn,
|
||||
showStatusColumn,
|
||||
visibleSkills,
|
||||
auditEmptyState,
|
||||
loading,
|
||||
@@ -2785,12 +3115,17 @@ export default {
|
||||
selectedDomain,
|
||||
selectedOwner,
|
||||
selectedStatus,
|
||||
selectedRiskScenario,
|
||||
selectedDomainLabel,
|
||||
selectedOwnerLabel,
|
||||
selectedStatusLabel,
|
||||
selectedRiskScenarioLabel,
|
||||
showRiskScenarioFilter,
|
||||
showStatusFilter,
|
||||
domainOptions,
|
||||
ownerOptions,
|
||||
statusOptions: STATUS_OPTIONS,
|
||||
riskScenarioOptions: RISK_SCENARIO_OPTIONS,
|
||||
activeFilterPopover,
|
||||
activeFilterTokens,
|
||||
canManageSelected,
|
||||
@@ -2806,6 +3141,7 @@ export default {
|
||||
activateBlockedReason,
|
||||
selectedSkillIsRule,
|
||||
selectedSkillUsesSpreadsheet,
|
||||
selectedSkillUsesJsonRisk,
|
||||
selectedSpreadsheetFileName,
|
||||
selectedSpreadsheetVersionModeLabel,
|
||||
selectedVersionTimelineItems,
|
||||
@@ -2850,6 +3186,9 @@ export default {
|
||||
confirmVersionSwitch,
|
||||
saveRuleMarkdown,
|
||||
saveRuleRuntimeJson,
|
||||
saveRiskRuleJson,
|
||||
formatRiskRuleJson,
|
||||
downloadRiskRuleJson,
|
||||
triggerSpreadsheetUpload,
|
||||
downloadSpreadsheetFile,
|
||||
handleSpreadsheetFileInput,
|
||||
|
||||
Reference in New Issue
Block a user