feat: 增强规则资产管理与审计页面运行时调试

后端新增规则资产版本管理和规则文件 CRUD 接口,优化风险
规则生成模板执行和员工数据模型字段,知识库 RAG 增强本
地回退和文档提取能力,清理旧风险规则文件统一由生成引擎
管理,前端审计页面增加运行时调试面板和规则资产编辑交互,
补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-24 21:44:17 +08:00
parent 575f093c74
commit 50b1c3f9a9
113 changed files with 13896 additions and 5044 deletions

View File

@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
import { fetchEmployees } from '../../services/employees.js'
import RiskRuleFlowDiagram from '../../components/shared/RiskRuleFlowDiagram.vue'
import RiskRuleTestDialog from '../../components/shared/RiskRuleTestDialog.vue'
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
import { useSystemState } from '../../composables/useSystemState.js'
@@ -11,6 +12,7 @@ import {
activateAgentAsset,
createAgentAssetReview,
createAgentAssetVersion,
deleteAgentAsset,
fetchAgentAssetDetail,
fetchAgentAssets,
fetchAgentAssetSpreadsheetBlob,
@@ -20,9 +22,12 @@ import {
fetchAgentAssetVersionTimeline,
fetchAgentRuns,
generateRiskRuleAsset,
publishRiskRuleAsset,
returnRiskRuleAsset,
saveAgentAssetRuleJson,
importAgentAssetSpreadsheetContent,
restoreAgentAssetVersion,
setRiskRuleAssetEnabled,
updateAgentAsset
} from '../../services/agentAssets.js'
import { loadOnlyOfficeApi } from '../../services/onlyoffice.js'
@@ -39,6 +44,8 @@ import {
import {
TAB_META,
STATUS_OPTIONS,
ENABLED_STATE_OPTIONS,
ONLINE_STATE_OPTIONS,
RISK_SCENARIO_OPTIONS,
normalizeText,
readConfigJson,
@@ -63,6 +70,7 @@ import {
import {
createDefaultRiskRuleForm,
RISK_RULE_CREATE_DOMAIN_OPTIONS,
RISK_RULE_EXPENSE_CATEGORY_OPTIONS,
RISK_RULE_LEVEL_OPTIONS
} from './auditViewRiskRuleModel.js'
@@ -71,6 +79,7 @@ export default {
components: {
ConfirmDialog,
RiskRuleFlowDiagram,
RiskRuleTestDialog,
TableLoadingState,
TableEmptyState
},
@@ -93,6 +102,8 @@ export default {
const selectedOwner = ref('')
const selectedStatus = ref('')
const selectedRiskScenario = ref('')
const selectedOnlineState = ref('')
const selectedEnabledState = ref('')
const loading = ref(false)
const errorMessage = ref('')
const detailLoading = ref(false)
@@ -105,6 +116,11 @@ export default {
const reviewSubmitReviewerOptions = ref([])
const riskRuleCreateOpen = ref(false)
const riskRuleCreateForm = ref(createDefaultRiskRuleForm())
const riskRuleTestOpen = ref(false)
const riskRuleDeleteOpen = ref(false)
const riskRuleReturnOpen = ref(false)
const riskRulePublishOpen = ref(false)
const riskRuleReturnNote = ref('')
const runLoading = ref(false)
const runs = ref([])
const spreadsheetUploadInput = ref(null)
@@ -146,6 +162,8 @@ export default {
const showMetricColumn = computed(() => activeMeta.value.showMetricColumn !== false)
const showVersionColumn = computed(() => activeMeta.value.showVersionColumn !== false)
const showStatusColumn = computed(() => activeMeta.value.showStatusColumn !== false)
const showOnlineColumn = computed(() => activeType.value === 'riskRules')
const showEnabledColumn = computed(() => activeType.value === 'riskRules')
const selectedSkillIsRule = computed(() => selectedSkill.value?.type === 'rules')
const selectedSkillUsesSpreadsheet = computed(
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesSpreadsheetRule)
@@ -165,6 +183,46 @@ export default {
const canCreateRiskRule = computed(
() => activeType.value === 'riskRules' && (isAdmin.value || isFinance.value) && !detailBusy.value
)
const latestRiskRuleTestSummary = computed(() => selectedSkill.value?.latestTestSummary || null)
const riskRuleTestPassed = computed(() => Boolean(latestRiskRuleTestSummary.value?.test_passed))
const riskRuleInReview = computed(
() => selectedSkillUsesJsonRisk.value && selectedSkill.value?.statusValue === 'review'
)
const canOpenRiskRuleTest = computed(
() =>
selectedSkillUsesJsonRisk.value &&
canEditSelected.value &&
Boolean(selectedSkill.value?.id) &&
!detailBusy.value
)
const canDeleteRiskRule = computed(
() =>
selectedSkillUsesJsonRisk.value &&
canEditSelected.value &&
Boolean(selectedSkill.value?.id) &&
!normalizeText(selectedSkill.value?.publishedVersion).replace('-', '') &&
!detailBusy.value
)
const canSubmitRiskRuleReview = computed(
() =>
selectedSkillUsesJsonRisk.value &&
canSubmitReview.value &&
!riskRuleInReview.value &&
riskRuleTestPassed.value
)
const canReturnRiskRule = computed(
() => selectedSkillUsesJsonRisk.value && canManageSelected.value && riskRuleInReview.value
)
const canPublishRiskRule = computed(
() =>
selectedSkillUsesJsonRisk.value &&
canManageSelected.value &&
riskRuleInReview.value &&
riskRuleTestPassed.value
)
const canToggleRiskRuleEnabled = computed(
() => selectedSkillUsesJsonRisk.value && canManageSelected.value && !detailBusy.value
)
const riskRuleCreateBusy = computed(() => actionState.value === 'generate-risk-rule')
const canEditMarkdown = computed(() => canEditSelected.value && selectedSkillIsRule.value)
const isDisplayingWorkingVersion = computed(
@@ -276,7 +334,7 @@ export default {
const ownerOptions = computed(() => {
const uniqueOwners = [...new Set(currentAssets.value.map((item) => item.owner).filter(Boolean))]
return [
{ value: '', label: '全部负责人' },
{ value: '', label: activeType.value === 'riskRules' ? '全部审核人' : '全部负责人' },
...uniqueOwners.map((value) => ({
value,
label: value
@@ -287,7 +345,9 @@ export default {
() => domainOptions.value.find((item) => item.value === selectedDomain.value)?.label || '业务域'
)
const selectedOwnerLabel = computed(
() => ownerOptions.value.find((item) => item.value === selectedOwner.value)?.label || '负责人'
() =>
ownerOptions.value.find((item) => item.value === selectedOwner.value)?.label ||
(activeType.value === 'riskRules' ? '审核人' : '负责人')
)
const selectedStatusLabel = computed(
() => STATUS_OPTIONS.find((item) => item.value === selectedStatus.value)?.label || '状态'
@@ -296,11 +356,23 @@ export default {
['financialRules', 'riskRules'].includes(activeType.value)
)
const showStatusFilter = computed(() => activeType.value !== 'riskRules')
const showOnlineFilter = computed(() => activeType.value === 'riskRules')
const showEnabledFilter = computed(() => activeType.value === 'riskRules')
const selectedRiskScenarioLabel = computed(
() =>
RISK_SCENARIO_OPTIONS.find((item) => item.value === selectedRiskScenario.value)?.label ||
'使用场景'
)
const selectedOnlineStateLabel = computed(
() =>
ONLINE_STATE_OPTIONS.find((item) => item.value === selectedOnlineState.value)?.label ||
'是否上线'
)
const selectedEnabledStateLabel = computed(
() =>
ENABLED_STATE_OPTIONS.find((item) => item.value === selectedEnabledState.value)?.label ||
'是否启用'
)
const activeFilterTokens = computed(() => {
const tokens = []
@@ -313,8 +385,14 @@ export default {
if (showStatusFilter.value && selectedStatus.value) {
tokens.push(`状态:${resolveStatusMeta(selectedStatus.value).label}`)
}
if (showOnlineFilter.value && selectedOnlineState.value) {
tokens.push(`是否上线:${selectedOnlineStateLabel.value}`)
}
if (showEnabledFilter.value && selectedEnabledState.value) {
tokens.push(`是否启用:${selectedEnabledStateLabel.value}`)
}
if (selectedOwner.value) {
tokens.push(`负责人${selectedOwner.value}`)
tokens.push(`${activeType.value === 'riskRules' ? '审核人' : '负责人'}${selectedOwner.value}`)
}
if (keyword.value.trim()) {
tokens.push(`搜索:${keyword.value.trim()}`)
@@ -326,9 +404,11 @@ export default {
const hasFilters = activeFilterTokens.value.length > 0
const supportedFilters = [
'业务域',
'负责人',
activeType.value === 'riskRules' ? '审核人' : '负责人',
...(showRiskScenarioFilter.value ? ['使用场景'] : []),
...(showStatusFilter.value ? ['状态'] : []),
...(showOnlineFilter.value ? ['是否上线'] : []),
...(showEnabledFilter.value ? ['是否启用'] : []),
'关键词'
]
@@ -409,8 +489,12 @@ export default {
selectedOwner: selectedOwner.value,
selectedStatus: selectedStatus.value,
selectedRiskScenario: selectedRiskScenario.value,
selectedOnlineState: selectedOnlineState.value,
selectedEnabledState: selectedEnabledState.value,
showStatusFilter: showStatusFilter.value,
showRiskScenarioFilter: showRiskScenarioFilter.value
showRiskScenarioFilter: showRiskScenarioFilter.value,
showOnlineFilter: showOnlineFilter.value,
showEnabledFilter: showEnabledFilter.value
})
)
@@ -455,6 +539,8 @@ export default {
selectedOwner.value = ''
selectedStatus.value = ''
selectedRiskScenario.value = ''
selectedOnlineState.value = ''
selectedEnabledState.value = ''
activeFilterPopover.value = ''
}
@@ -488,6 +574,12 @@ export default {
if (name === 'riskScenario') {
selectedRiskScenario.value = value
}
if (name === 'online') {
selectedOnlineState.value = value
}
if (name === 'enabled') {
selectedEnabledState.value = value
}
closeFilterPopover()
}
@@ -536,7 +628,11 @@ export default {
const detail = await generateRiskRuleAsset(
{
business_domain: riskRuleCreateForm.value.business_domain,
expense_category: riskRuleCreateForm.value.business_domain === 'expense'
? riskRuleCreateForm.value.expense_category
: null,
risk_level: riskRuleCreateForm.value.risk_level,
requires_attachment: Boolean(riskRuleCreateForm.value.requires_attachment),
natural_language: naturalLanguage
},
{ actor: resolveActor() }
@@ -1105,6 +1201,11 @@ export default {
versionSwitchTarget.value = null
versionTimelineOpen.value = false
versionTimelineItems.value = []
riskRuleTestOpen.value = false
riskRuleDeleteOpen.value = false
riskRuleReturnOpen.value = false
riskRulePublishOpen.value = false
riskRuleReturnNote.value = ''
}
function openVersionSwitch(version) {
@@ -1299,6 +1400,10 @@ export default {
if (!selectedSkill.value || !canSubmitReview.value || detailBusy.value) {
return
}
if (selectedSkillUsesJsonRisk.value && !riskRuleTestPassed.value) {
toast('请先在“测试规则”中保存测试通过报告,再提交审核。')
return
}
reviewSubmitVersion.value = selectedSkill.value.workingVersion || selectedSkill.value.displayVersion || ''
reviewSubmitReviewer.value = selectedSkill.value.reviewer || ''
reviewSubmitOpen.value = true
@@ -1323,6 +1428,10 @@ export default {
if (!selectedSkill.value || !canSubmitReview.value || detailBusy.value) {
return
}
if (selectedSkillUsesJsonRisk.value && !riskRuleTestPassed.value) {
toast('当前规则版本尚未确认测试通过,不能提交审核。')
return
}
const version = normalizeText(reviewSubmitVersion.value)
const reviewer = normalizeText(reviewSubmitReviewer.value)
if (!version) {
@@ -1357,6 +1466,155 @@ export default {
}
}
function openRiskRuleTestDialog() {
if (!canOpenRiskRuleTest.value) {
if (!selectedSkill.value?.id) {
toast('规则详情还没有加载完成,请稍后再测试。')
}
return
}
riskRuleTestOpen.value = true
}
function closeRiskRuleTestDialog() {
riskRuleTestOpen.value = false
}
async function handleRiskRuleReportSaved(summary) {
if (selectedSkill.value) {
selectedSkill.value.latestTestSummary = summary
}
await refreshCurrentAssets()
if (selectedSkill.value?.id) {
await loadSelectedAssetDetail(selectedSkill.value.id)
}
}
function openDeleteRiskRuleDialog() {
if (!canDeleteRiskRule.value) {
return
}
riskRuleDeleteOpen.value = true
}
function closeDeleteRiskRuleDialog() {
if (detailBusy.value) {
return
}
riskRuleDeleteOpen.value = false
}
async function deleteSelectedRiskRule() {
if (!selectedSkill.value || !canDeleteRiskRule.value || detailBusy.value) {
return
}
actionState.value = 'delete-risk-rule'
try {
await deleteAgentAsset(selectedSkill.value.id, { actor: resolveActor() })
riskRuleDeleteOpen.value = false
const deletedName = selectedSkill.value.name
closeDetail()
await refreshCurrentAssets()
toast(`风险规则“${deletedName}”已删除。`)
} catch (error) {
toast(error?.message || '风险规则删除失败,请稍后重试。')
} finally {
actionState.value = ''
}
}
function openReturnRiskRuleDialog() {
if (!canReturnRiskRule.value) {
return
}
riskRuleReturnNote.value = ''
riskRuleReturnOpen.value = true
}
function closeReturnRiskRuleDialog() {
if (detailBusy.value) {
return
}
riskRuleReturnOpen.value = false
}
async function returnSelectedRiskRule() {
if (!selectedSkill.value || !canReturnRiskRule.value || detailBusy.value) {
return
}
const note = normalizeText(riskRuleReturnNote.value)
if (!note) {
toast('请填写回退原因。')
return
}
actionState.value = 'return-risk-rule'
try {
await returnRiskRuleAsset(selectedSkill.value.id, { note }, { actor: resolveActor() })
riskRuleReturnOpen.value = false
await refreshCurrentAssets()
await loadSelectedAssetDetail(selectedSkill.value.id)
toast('风险规则已回退到草稿。')
} catch (error) {
toast(error?.message || '风险规则回退失败,请稍后重试。')
} finally {
actionState.value = ''
}
}
function openPublishRiskRuleDialog() {
if (!canPublishRiskRule.value) {
if (!riskRuleTestPassed.value) {
toast('请先确认测试报告通过,再发布上线。')
}
return
}
riskRulePublishOpen.value = true
}
function closePublishRiskRuleDialog() {
if (detailBusy.value) {
return
}
riskRulePublishOpen.value = false
}
async function publishSelectedRiskRule() {
if (!selectedSkill.value || !canPublishRiskRule.value || detailBusy.value) {
return
}
actionState.value = 'publish-risk-rule'
try {
await publishRiskRuleAsset(selectedSkill.value.id, { actor: resolveActor() })
riskRulePublishOpen.value = false
await refreshCurrentAssets()
await loadSelectedAssetDetail(selectedSkill.value.id)
toast('风险规则已发布上线。')
} catch (error) {
toast(error?.message || '风险规则发布失败,请稍后重试。')
} finally {
actionState.value = ''
}
}
async function toggleSelectedRiskRuleEnabled() {
if (!selectedSkill.value || !canToggleRiskRuleEnabled.value || detailBusy.value) {
return
}
const assetId = selectedSkill.value.id
const nextEnabled = !selectedSkill.value.isEnabledValue
actionState.value = 'toggle-risk-rule-enabled'
try {
await setRiskRuleAssetEnabled(assetId, nextEnabled, { actor: resolveActor() })
await refreshCurrentAssets()
await loadSelectedAssetDetail(assetId)
toast(nextEnabled ? '风险规则已启用。' : '风险规则已停用,不会进入业务扫描。')
} catch (error) {
toast(error?.message || '风险规则启用状态更新失败,请稍后重试。')
} finally {
actionState.value = ''
}
}
async function activateSelectedRule() {
if (!selectedSkill.value || !selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
return
@@ -1458,6 +1716,8 @@ export default {
showVersionColumn,
showMetricColumn,
showStatusColumn,
showOnlineColumn,
showEnabledColumn,
visibleSkills,
auditEmptyState,
loading,
@@ -1468,21 +1728,37 @@ export default {
selectedOwner,
selectedStatus,
selectedRiskScenario,
selectedOnlineState,
selectedEnabledState,
selectedDomainLabel,
selectedOwnerLabel,
selectedStatusLabel,
selectedRiskScenarioLabel,
selectedOnlineStateLabel,
selectedEnabledStateLabel,
showRiskScenarioFilter,
showStatusFilter,
showOnlineFilter,
showEnabledFilter,
domainOptions,
ownerOptions,
statusOptions: STATUS_OPTIONS,
riskScenarioOptions: RISK_SCENARIO_OPTIONS,
onlineStateOptions: ONLINE_STATE_OPTIONS,
enabledStateOptions: ENABLED_STATE_OPTIONS,
activeFilterPopover,
activeFilterTokens,
canManageSelected,
canEditSelected,
canCreateRiskRule,
canOpenRiskRuleTest,
canDeleteRiskRule,
canSubmitRiskRuleReview,
canReturnRiskRule,
canPublishRiskRule,
canToggleRiskRuleEnabled,
riskRuleTestPassed,
riskRuleInReview,
canSubmitReview,
hasReviewSubmitReviewers,
canReviewSelected,
@@ -1509,7 +1785,13 @@ export default {
riskRuleCreateOpen,
riskRuleCreateForm,
riskRuleCreateBusy,
riskRuleTestOpen,
riskRuleDeleteOpen,
riskRuleReturnOpen,
riskRulePublishOpen,
riskRuleReturnNote,
riskRuleCreateDomainOptions: RISK_RULE_CREATE_DOMAIN_OPTIONS,
riskRuleExpenseCategoryOptions: RISK_RULE_EXPENSE_CATEGORY_OPTIONS,
riskRuleLevelOptions: RISK_RULE_LEVEL_OPTIONS,
showReviewNote,
spreadsheetUploadInput,
@@ -1549,6 +1831,19 @@ export default {
openSubmitReviewDialog,
closeSubmitReviewDialog,
submitSelectedRuleForReview,
openRiskRuleTestDialog,
closeRiskRuleTestDialog,
handleRiskRuleReportSaved,
openDeleteRiskRuleDialog,
closeDeleteRiskRuleDialog,
deleteSelectedRiskRule,
openReturnRiskRuleDialog,
closeReturnRiskRuleDialog,
returnSelectedRiskRule,
openPublishRiskRuleDialog,
closePublishRiskRuleDialog,
publishSelectedRiskRule,
toggleSelectedRiskRuleEnabled,
activateSelectedRule,
restoreSelectedVersion,
openVersionTimeline,

View File

@@ -7,6 +7,13 @@ export const RULE_TABLE_COLUMNS = {
metric: '修改人'
}
export const RISK_RULE_TABLE_COLUMNS = {
...RULE_TABLE_COLUMNS,
owner: '审核人',
metric: '发布者',
updatedAt: '发布时间'
}
export const TYPE_META = {
rules: {
assetType: 'rule',
@@ -89,8 +96,8 @@ export const TAB_META = {
typeLabel: '风险规则',
createButtonLabel: '新建风险规则',
hintText: '仅展示平台风险规则;适用场景按差旅、发票、餐饮招待等分类,可用「使用场景」筛选。',
searchPlaceholder: '搜索风险规则名称、编码或负责人',
tableColumns: RULE_TABLE_COLUMNS,
searchPlaceholder: '搜索风险规则名称、编码或审核人',
tableColumns: RISK_RULE_TABLE_COLUMNS,
showRuntimeColumn: false,
showVersionColumn: false,
showStatusColumn: false,
@@ -249,6 +256,18 @@ export const STATUS_OPTIONS = [
{ value: 'disabled', label: '已停用' }
]
export const ONLINE_STATE_OPTIONS = [
{ value: '', label: '全部上线状态' },
{ value: 'online', label: '已上线' },
{ value: 'offline', label: '未上线' }
]
export const ENABLED_STATE_OPTIONS = [
{ value: '', label: '全部启用状态' },
{ value: 'enabled', label: '已启用' },
{ value: 'disabled', label: '已停用' }
]
export const EXPENSE_RULE_BLOCK_PATTERN = /```expense-rule\s*([\s\S]*?)\s*```/i
export const RULE_SPREADSHEET_BLOCK_PATTERN = /```rule-spreadsheet\s*([\s\S]*?)\s*```/i

View File

@@ -34,6 +34,7 @@ import {
export {
DETAIL_TITLES,
DOMAIN_LABELS,
ENABLED_STATE_OPTIONS,
EXPENSE_RULE_BLOCK_PATTERN,
JSON_RISK_DETAIL_MODE,
LEGACY_RISK_SCENARIO_KEYS,
@@ -43,6 +44,7 @@ export {
REVIEW_META,
RISK_SCENARIO_OPTIONS,
RISK_SCENARIO_VALUES,
RISK_RULE_TABLE_COLUMNS,
RULE_SPREADSHEET_BLOCK_PATTERN,
RULE_TABLE_COLUMNS,
RULE_TAB_TAG_ALIASES,
@@ -51,6 +53,7 @@ export {
SPREADSHEET_DETAIL_MODE,
STATUS_META,
STATUS_OPTIONS,
ONLINE_STATE_OPTIONS,
TAB_META,
TYPE_META,
VERSION_STATE_META
@@ -189,6 +192,17 @@ export function readConfigJson(value) {
return {}
}
export function resolveRiskRuleEnabled(source, rulePayload = null) {
const configJson = readConfigJson(source)
if (isPlainObject(rulePayload) && rulePayload.enabled === false) {
return false
}
if (source?.enabled === false || configJson.enabled === false) {
return false
}
return true
}
export function readRuleDocumentMeta(value) {
const configJson = readConfigJson(value)
return isPlainObject(configJson.rule_document) ? configJson.rule_document : null
@@ -417,6 +431,12 @@ export function buildRiskListSubtitle(text, maxLength = 42) {
export function applyRiskRuleJsonState(target, payload, apiPayload) {
const rulePayload = isPlainObject(payload) ? payload : {}
const metadata = rulePayload.metadata && typeof rulePayload.metadata === 'object'
? rulePayload.metadata
: {}
const apiConfig = apiPayload?.config_json && typeof apiPayload.config_json === 'object'
? apiPayload.config_json
: {}
const fullDescription =
resolveRiskRuleDescription(rulePayload) ||
normalizeText(apiPayload?.description) ||
@@ -427,6 +447,21 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
const riskRuleFields = resolveRiskRuleFields(rulePayload)
const riskRuleCreatedAt = resolveRiskRuleCreatedAt(rulePayload, target.createdAt || target.updatedAt)
const statusValue = apiPayload?.status || target.statusValue || 'draft'
const isOnlineLabel = statusValue === 'active' ? '是' : '否'
const isEnabledValue = resolveRiskRuleEnabled(target, rulePayload)
const publisher = apiPayload?.created_by || target.publisher || (apiPayload?.recent_versions && apiPayload.recent_versions[0]?.created_by) || '系统管理员'
let publishedAt = target.publishedAt || '-'
if (apiPayload?.recent_versions) {
const history = buildHistory(apiPayload.recent_versions, { ...target, config_json: payload })
const publishedVersionObj = history.find((item) => item.isPublished || item.lifecycleState === 'published')
publishedAt = publishedVersionObj ? publishedVersionObj.time : (apiPayload?.latest_review?.reviewed_at ? formatDateTime(apiPayload.latest_review.reviewed_at) : '-')
} else if (apiPayload?.latest_review?.reviewed_at) {
publishedAt = formatDateTime(apiPayload.latest_review.reviewed_at)
}
return {
...target,
riskRuleDescription: fullDescription,
@@ -444,6 +479,12 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
riskRuleFlow: resolveRiskRuleFlow(rulePayload, riskRuleFields),
riskRuleFlowDiagramSvg:
normalizeText(apiPayload?.flow_diagram_svg) || resolveRiskRuleFlowDiagramSvg(rulePayload),
riskRuleRequiresAttachment: Boolean(
rulePayload.requires_attachment ||
metadata.requires_attachment ||
apiConfig.requires_attachment ||
target.configJson?.requires_attachment
),
riskRuleSummary: {
name: apiPayload?.name || target.name,
evaluator: apiPayload?.evaluator || rulePayload.evaluator || '',
@@ -451,7 +492,13 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) {
inputs: apiPayload?.inputs || rulePayload.inputs || {},
outcomes: apiPayload?.outcomes || rulePayload.outcomes || {}
},
riskRuleJsonText: JSON.stringify(rulePayload, null, 2)
riskRuleJsonText: JSON.stringify(rulePayload, null, 2),
isOnlineLabel,
isEnabledValue,
isEnabledLabel: isEnabledValue ? '是' : '否',
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
publisher,
publishedAt
}
}
@@ -810,6 +857,15 @@ export function buildListItem(asset) {
const listSubtitle = isRiskRule
? buildRiskListSubtitle(asset.description)
: normalizeText(asset.description)
const isOnlineValue = asset.status === 'active'
const isEnabledValue = usesJsonRiskRule ? resolveRiskRuleEnabled(asset) : true
const reviewer = normalizeText(asset.reviewer) || '待分配'
const publisher = isRiskRule
? isOnlineValue
? normalizeText(asset.published_by) || reviewer || modifiedBy || '系统管理员'
: '-'
: ''
const publishedAt = isRiskRule && isOnlineValue ? formatDateTime(asset.published_at || asset.updated_at) : '-'
return {
id: asset.id,
@@ -826,8 +882,8 @@ export function buildListItem(asset) {
summary: listSubtitle,
listSubtitle,
category: resolveDomainLabel(asset.domain),
owner: asset.owner,
reviewer: asset.reviewer || '待分配',
owner: isRiskRule ? reviewer : asset.owner,
reviewer,
scope: typeKey === 'rules' ? ruleScenarioCategory || '通用' : formatScenarioList(asset.scenario_json),
riskCategory: ruleScenarioCategory,
model: buildRowRuntime(asset, typeKey),
@@ -838,10 +894,18 @@ export function buildListItem(asset) {
status: statusMeta.label,
statusValue: asset.status,
statusTone: statusMeta.tone,
hitRate: buildRowMetric({ ...asset, modified_by: modifiedBy }, typeKey),
hitRate: isRiskRule ? publisher : buildRowMetric({ ...asset, modified_by: modifiedBy }, typeKey),
publisher,
publishedAt,
isOnlineValue,
isOnlineLabel: isOnlineValue ? '是' : '否',
isOnlineTone: isOnlineValue ? 'success' : 'disabled',
isEnabledValue,
isEnabledLabel: isEnabledValue ? '是' : '否',
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
modifiedBy,
changeCount,
updatedAt: formatDateTime(asset.updated_at),
updatedAt: isRiskRule ? publishedAt : formatDateTime(asset.updated_at),
badgeTone: tabMeta.badgeTone,
domainValue: asset.domain
}
@@ -1218,6 +1282,7 @@ export function buildDetailViewModel(detail, runs) {
const ruleTemplateLabel = normalizeText(configJson.rule_template_label) || resolveRuleTemplateLabel(ruleTemplateKey)
const runtimeKind = normalizeText(configJson.runtime_kind || previewRuntimeRule.kind) || 'policy_rule_draft'
const ruleScenarioCategory = typeKey === 'rules' ? resolveRuleScenarioCategory(detail, tabId) : ''
const isEnabledValue = usesJsonRiskRule ? resolveRiskRuleEnabled(detail) : true
return {
id: detail.id,
@@ -1258,10 +1323,28 @@ export function buildDetailViewModel(detail, runs) {
riskRuleSeverityLabel: '中风险',
riskRuleCreatedAt: formatDateTime(detail.created_at),
riskRuleAgeLabel: formatRiskRuleAge(detail.created_at),
isOnlineLabel: detail.status === 'active' ? '是' : '否',
isEnabledValue,
isEnabledLabel: isEnabledValue ? '是' : '否',
isEnabledTone: isEnabledValue ? 'success' : 'disabled',
publisher:
detail.status === 'active'
? normalizeText(detail.published_by) ||
detail.latest_review?.reviewer ||
detail.reviewer ||
(detail.recent_versions && detail.recent_versions[0]?.created_by) ||
'系统管理员'
: '-',
publishedAt:
history.find((item) => item.isPublished || item.lifecycleState === 'published')?.time ||
(detail.published_at ? formatDateTime(detail.published_at) : '') ||
(detail.latest_review?.reviewed_at ? formatDateTime(detail.latest_review.reviewed_at) : '-'),
riskRuleFields: [],
riskRuleFieldSummary: '未识别字段',
riskRuleFlow: resolveRiskRuleFlow({}, []),
riskRuleFlowDiagramSvg: normalizeText(configJson.flow_diagram_svg),
riskRuleRequiresAttachment: Boolean(configJson.requires_attachment),
latestTestSummary: detail.latest_test_summary || detail.latestTestSummary || null,
riskCategory: typeKey === 'rules' ? ruleScenarioCategory : '',
ruleDocument,
scenarioList: typeKey === 'rules' && ruleScenarioCategory

View File

@@ -4,6 +4,18 @@ export const RISK_RULE_CREATE_DOMAIN_OPTIONS = [
{ value: 'ap', label: '应付' }
]
export const RISK_RULE_EXPENSE_CATEGORY_OPTIONS = [
{ value: 'travel', label: '差旅费' },
{ value: 'hotel', label: '住宿费' },
{ value: 'transport', label: '交通费' },
{ value: 'meal', label: '业务招待费' },
{ value: 'meeting', label: '会务费' },
{ value: 'office', label: '办公用品费' },
{ value: 'training', label: '培训费' },
{ value: 'communication', label: '通讯费' },
{ value: 'welfare', label: '福利费' }
]
export const RISK_RULE_LEVEL_OPTIONS = [
{ value: 'medium', label: '中风险' },
{ value: 'high', label: '高风险' },
@@ -19,7 +31,9 @@ const RISK_LEVEL_LABELS = {
export function createDefaultRiskRuleForm() {
return {
business_domain: 'expense',
expense_category: 'travel',
risk_level: 'medium',
requires_attachment: false,
natural_language: ''
}
}

View File

@@ -103,7 +103,25 @@ export function filterAuditAssets(assets = [], filters = {}) {
? item.riskCategory === filters.selectedRiskScenario
: true
: true
const matchesOnline = filters.showOnlineFilter
? filters.selectedOnlineState
? (filters.selectedOnlineState === 'online') === Boolean(item.isOnlineValue)
: true
: true
const matchesEnabled = filters.showEnabledFilter
? filters.selectedEnabledState
? (filters.selectedEnabledState === 'enabled') === Boolean(item.isEnabledValue)
: true
: true
return matchesKeyword && matchesDomain && matchesOwner && matchesStatus && matchesRiskScenario
return (
matchesKeyword &&
matchesDomain &&
matchesOwner &&
matchesStatus &&
matchesRiskScenario &&
matchesOnline &&
matchesEnabled
)
})
}