2026-05-18 02:51:25 +00:00
|
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
2026-05-11 06:32:38 +00:00
|
|
|
|
|
2026-05-13 03:35:44 +00:00
|
|
|
|
import ConfirmDialog from '../../components/shared/ConfirmDialog.vue'
|
2026-05-18 09:42:23 +00:00
|
|
|
|
import { fetchEmployees } from '../../services/employees.js'
|
2026-05-23 19:54:42 +08:00
|
|
|
|
import RiskRuleFlowDiagram from '../../components/shared/RiskRuleFlowDiagram.vue'
|
2026-05-24 21:44:17 +08:00
|
|
|
|
import RiskRuleTestDialog from '../../components/shared/RiskRuleTestDialog.vue'
|
2026-05-20 14:21:56 +08:00
|
|
|
|
import TableLoadingState from '../../components/shared/TableLoadingState.vue'
|
2026-05-13 06:52:30 +00:00
|
|
|
|
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
|
2026-05-11 06:32:38 +00:00
|
|
|
|
import { useSystemState } from '../../composables/useSystemState.js'
|
|
|
|
|
|
import { useToast } from '../../composables/useToast.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
activateAgentAsset,
|
|
|
|
|
|
createAgentAssetReview,
|
|
|
|
|
|
createAgentAssetVersion,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
deleteAgentAsset,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
fetchAgentAssetDetail,
|
|
|
|
|
|
fetchAgentAssets,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
fetchAgentAssetSpreadsheetBlob,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
fetchAgentAssetSpreadsheetChangeRecords,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
fetchAgentAssetSpreadsheetOnlyOfficeConfig,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
fetchAgentAssetRuleJson,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
fetchAgentAssetVersionTimeline,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
fetchAgentRuns,
|
2026-05-23 19:54:42 +08:00
|
|
|
|
generateRiskRuleAsset,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
publishRiskRuleAsset,
|
|
|
|
|
|
returnRiskRuleAsset,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
saveAgentAssetRuleJson,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
importAgentAssetSpreadsheetContent,
|
|
|
|
|
|
restoreAgentAssetVersion,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
setRiskRuleAssetEnabled,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
updateAgentAsset
|
2026-05-11 06:32:38 +00:00
|
|
|
|
} from '../../services/agentAssets.js'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
import { loadOnlyOfficeApi } from '../../services/onlyoffice.js'
|
|
|
|
|
|
import { isFinanceUser, isManagerUser } from '../../utils/accessControl.js'
|
|
|
|
|
|
import { buildOnlyOfficeEditorConfig } from './onlyOfficePreviewConfig.js'
|
2026-05-21 23:53:03 +08:00
|
|
|
|
import {
|
|
|
|
|
|
buildReviewNote,
|
|
|
|
|
|
buildRuleConfigPayload,
|
|
|
|
|
|
buildSpreadsheetChangeRecordKey,
|
|
|
|
|
|
filterAuditAssets,
|
|
|
|
|
|
incrementVersion
|
|
|
|
|
|
} from './auditViewRuntimeModel.js'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
|
2026-05-21 23:53:03 +08:00
|
|
|
|
import {
|
|
|
|
|
|
TAB_META,
|
|
|
|
|
|
STATUS_OPTIONS,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
ENABLED_STATE_OPTIONS,
|
|
|
|
|
|
ONLINE_STATE_OPTIONS,
|
2026-05-21 23:53:03 +08:00
|
|
|
|
RISK_SCENARIO_OPTIONS,
|
|
|
|
|
|
normalizeText,
|
|
|
|
|
|
readConfigJson,
|
|
|
|
|
|
resolveRuleTemplateLabel,
|
|
|
|
|
|
resolveTimelineEventMeta,
|
|
|
|
|
|
formatDateTime,
|
|
|
|
|
|
formatSpreadsheetChangeSummary,
|
|
|
|
|
|
resolveDiffChangeMeta,
|
|
|
|
|
|
resolveDomainLabel,
|
|
|
|
|
|
resolveStatusMeta,
|
|
|
|
|
|
resolveReviewMeta,
|
|
|
|
|
|
buildListItem,
|
|
|
|
|
|
buildDetailViewModel,
|
|
|
|
|
|
applyRiskRuleJsonState,
|
|
|
|
|
|
resolveRiskRuleDescription,
|
|
|
|
|
|
buildPreviewRuleDetail,
|
|
|
|
|
|
buildDefaultRuntimeRule,
|
|
|
|
|
|
stringifyRuntimeRule,
|
|
|
|
|
|
parseRuntimeRuleText,
|
|
|
|
|
|
buildMarkdownVersionContent
|
|
|
|
|
|
} from './auditViewModel.js'
|
2026-05-23 19:54:42 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createDefaultRiskRuleForm,
|
2026-05-26 09:15:14 +08:00
|
|
|
|
RISK_RULE_EXPENSE_CATEGORY_OPTIONS
|
2026-05-23 19:54:42 +08:00
|
|
|
|
} from './auditViewRiskRuleModel.js'
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
2026-05-09 15:46:16 +00:00
|
|
|
|
name: 'AuditView',
|
2026-05-13 03:35:44 +00:00
|
|
|
|
components: {
|
2026-05-13 06:52:30 +00:00
|
|
|
|
ConfirmDialog,
|
2026-05-23 19:54:42 +08:00
|
|
|
|
RiskRuleFlowDiagram,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
RiskRuleTestDialog,
|
2026-05-20 14:21:56 +08:00
|
|
|
|
TableLoadingState,
|
2026-05-13 06:52:30 +00:00
|
|
|
|
TableEmptyState
|
2026-05-13 03:35:44 +00:00
|
|
|
|
},
|
2026-05-09 16:16:56 +00:00
|
|
|
|
emits: ['detail-open-change'],
|
2026-05-11 06:32:38 +00:00
|
|
|
|
setup(_, { emit }) {
|
|
|
|
|
|
const { toast } = useToast()
|
|
|
|
|
|
const { currentUser } = useSystemState()
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const tabs = Object.entries(TAB_META).map(([id, meta]) => ({
|
2026-05-11 06:32:38 +00:00
|
|
|
|
id,
|
|
|
|
|
|
label: meta.label
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const activeType = ref('financialRules')
|
2026-05-06 11:00:38 +08:00
|
|
|
|
const selectedSkill = ref(null)
|
2026-05-09 15:46:16 +00:00
|
|
|
|
const versionSwitchTarget = ref(null)
|
2026-05-11 01:53:30 +00:00
|
|
|
|
const keyword = ref('')
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const activeFilterPopover = ref('')
|
|
|
|
|
|
const selectedDomain = ref('')
|
|
|
|
|
|
const selectedOwner = ref('')
|
|
|
|
|
|
const selectedStatus = ref('')
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const selectedRiskScenario = ref('')
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const selectedOnlineState = ref('')
|
|
|
|
|
|
const selectedEnabledState = ref('')
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const errorMessage = ref('')
|
|
|
|
|
|
const detailLoading = ref(false)
|
|
|
|
|
|
const detailError = ref('')
|
|
|
|
|
|
const actionState = ref('')
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const reviewSubmitOpen = ref(false)
|
|
|
|
|
|
const reviewSubmitVersion = ref('')
|
|
|
|
|
|
const reviewSubmitReviewer = ref('')
|
|
|
|
|
|
const reviewSubmitReviewerLoading = ref(false)
|
|
|
|
|
|
const reviewSubmitReviewerOptions = ref([])
|
2026-05-23 19:54:42 +08:00
|
|
|
|
const riskRuleCreateOpen = ref(false)
|
|
|
|
|
|
const riskRuleCreateForm = ref(createDefaultRiskRuleForm())
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const riskRuleTestOpen = ref(false)
|
|
|
|
|
|
const riskRuleDeleteOpen = ref(false)
|
|
|
|
|
|
const riskRuleReturnOpen = ref(false)
|
|
|
|
|
|
const riskRulePublishOpen = ref(false)
|
|
|
|
|
|
const riskRuleReturnNote = ref('')
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const runLoading = ref(false)
|
|
|
|
|
|
const runs = ref([])
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const spreadsheetUploadInput = ref(null)
|
|
|
|
|
|
const spreadsheetOnlyOfficeLoading = ref(false)
|
|
|
|
|
|
const spreadsheetOnlyOfficeError = ref('')
|
|
|
|
|
|
const spreadsheetOnlyOfficeEditor = ref(null)
|
|
|
|
|
|
const spreadsheetOnlyOfficeReady = ref(false)
|
|
|
|
|
|
const spreadsheetOnlyOfficeHostId = ref('audit-rule-onlyoffice')
|
|
|
|
|
|
const versionTimelineOpen = ref(false)
|
|
|
|
|
|
const versionTimelineLoading = ref(false)
|
|
|
|
|
|
const versionTimelineError = ref('')
|
|
|
|
|
|
const versionTimelineItems = ref([])
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const spreadsheetChangeRecordsByAsset = ref({})
|
|
|
|
|
|
const spreadsheetChangeDetailOpen = ref(false)
|
|
|
|
|
|
const selectedSpreadsheetChangeRecord = ref(null)
|
|
|
|
|
|
let spreadsheetOnlyOfficeMountSeq = 0
|
|
|
|
|
|
let spreadsheetOnlyOfficeLoadTimer = null
|
|
|
|
|
|
let spreadsheetOnlyOfficeHadLocalEdits = false
|
|
|
|
|
|
let spreadsheetOnlyOfficeSyncSeq = 0
|
2026-05-19 16:19:03 +00:00
|
|
|
|
let spreadsheetOnlyOfficeChangePollTimer = null
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const riskRuleGenerationPollTimers = new Map()
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const assetBuckets = ref({
|
2026-05-18 02:51:25 +00:00
|
|
|
|
financialRules: [],
|
|
|
|
|
|
riskRules: [],
|
2026-05-11 06:32:38 +00:00
|
|
|
|
skills: [],
|
|
|
|
|
|
mcp: [],
|
|
|
|
|
|
tasks: []
|
|
|
|
|
|
})
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const isAdmin = computed(() => isManagerUser(currentUser.value))
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const isFinance = computed(() => isFinanceUser(currentUser.value))
|
|
|
|
|
|
const activeMeta = computed(() => TAB_META[activeType.value])
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const activeTabLabel = computed(() => activeMeta.value.label)
|
|
|
|
|
|
const currentAssets = computed(() => assetBuckets.value[activeType.value] || [])
|
|
|
|
|
|
const searchPlaceholder = computed(() => activeMeta.value.searchPlaceholder)
|
2026-05-09 15:46:16 +00:00
|
|
|
|
const createButtonLabel = computed(() => activeMeta.value.createButtonLabel)
|
|
|
|
|
|
const hintText = computed(() => activeMeta.value.hintText)
|
|
|
|
|
|
const tableColumns = computed(() => activeMeta.value.tableColumns)
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const showRuntimeColumn = computed(() => activeMeta.value.showRuntimeColumn !== false)
|
2026-05-11 06:33:46 +00:00
|
|
|
|
const showMetricColumn = computed(() => activeMeta.value.showMetricColumn !== false)
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const showVersionColumn = computed(() => activeMeta.value.showVersionColumn !== false)
|
|
|
|
|
|
const showStatusColumn = computed(() => activeMeta.value.showStatusColumn !== false)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const showOnlineColumn = computed(() => false)
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const showEnabledColumn = computed(() => activeType.value === 'riskRules')
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const selectedSkillIsRule = computed(() => selectedSkill.value?.type === 'rules')
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const selectedSkillUsesSpreadsheet = computed(
|
|
|
|
|
|
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesSpreadsheetRule)
|
|
|
|
|
|
)
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const selectedSkillUsesJsonRisk = computed(
|
|
|
|
|
|
() => selectedSkillIsRule.value && Boolean(selectedSkill.value?.usesJsonRiskRule)
|
|
|
|
|
|
)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const canManageSelected = computed(
|
|
|
|
|
|
() => isAdmin.value && Boolean(selectedSkill.value) && !selectedSkill.value?.isPreviewMock
|
|
|
|
|
|
)
|
|
|
|
|
|
const canEditSelected = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
Boolean(selectedSkill.value) &&
|
|
|
|
|
|
!selectedSkill.value?.isPreviewMock &&
|
|
|
|
|
|
(isAdmin.value || isFinance.value)
|
|
|
|
|
|
)
|
2026-05-23 19:54:42 +08:00
|
|
|
|
const canCreateRiskRule = computed(
|
|
|
|
|
|
() => activeType.value === 'riskRules' && (isAdmin.value || isFinance.value) && !detailBusy.value
|
|
|
|
|
|
)
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const latestRiskRuleTestSummary = computed(() => selectedSkill.value?.latestTestSummary || null)
|
|
|
|
|
|
const riskRuleTestPassed = computed(() => Boolean(latestRiskRuleTestSummary.value?.test_passed))
|
|
|
|
|
|
const riskRuleInReview = computed(
|
|
|
|
|
|
() => selectedSkillUsesJsonRisk.value && selectedSkill.value?.statusValue === 'review'
|
|
|
|
|
|
)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const riskRuleGenerationBusy = computed(
|
|
|
|
|
|
() => selectedSkillUsesJsonRisk.value && selectedSkill.value?.statusValue === 'generating'
|
|
|
|
|
|
)
|
|
|
|
|
|
const riskRuleGenerationFailed = computed(
|
|
|
|
|
|
() => selectedSkillUsesJsonRisk.value && selectedSkill.value?.statusValue === 'failed'
|
|
|
|
|
|
)
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const canOpenRiskRuleTest = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
selectedSkillUsesJsonRisk.value &&
|
|
|
|
|
|
canEditSelected.value &&
|
|
|
|
|
|
Boolean(selectedSkill.value?.id) &&
|
2026-05-26 09:15:14 +08:00
|
|
|
|
!riskRuleGenerationBusy.value &&
|
|
|
|
|
|
!riskRuleGenerationFailed.value &&
|
2026-05-24 21:44:17 +08:00
|
|
|
|
!detailBusy.value
|
|
|
|
|
|
)
|
|
|
|
|
|
const canDeleteRiskRule = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
selectedSkillUsesJsonRisk.value &&
|
|
|
|
|
|
canEditSelected.value &&
|
|
|
|
|
|
Boolean(selectedSkill.value?.id) &&
|
|
|
|
|
|
!normalizeText(selectedSkill.value?.publishedVersion).replace('-', '') &&
|
|
|
|
|
|
!detailBusy.value
|
|
|
|
|
|
)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const canOpenRiskRuleReviewSubmit = computed(
|
2026-05-24 21:44:17 +08:00
|
|
|
|
() =>
|
|
|
|
|
|
selectedSkillUsesJsonRisk.value &&
|
|
|
|
|
|
canSubmitReview.value &&
|
|
|
|
|
|
!riskRuleInReview.value &&
|
2026-05-26 09:15:14 +08:00
|
|
|
|
!riskRuleGenerationBusy.value &&
|
|
|
|
|
|
!riskRuleGenerationFailed.value
|
|
|
|
|
|
)
|
|
|
|
|
|
const canSubmitRiskRuleReview = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
canOpenRiskRuleReviewSubmit.value &&
|
2026-05-24 21:44:17 +08:00
|
|
|
|
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
|
|
|
|
|
|
)
|
2026-05-23 19:54:42 +08:00
|
|
|
|
const riskRuleCreateBusy = computed(() => actionState.value === 'generate-risk-rule')
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const canEditMarkdown = computed(() => canEditSelected.value && selectedSkillIsRule.value)
|
|
|
|
|
|
const isDisplayingWorkingVersion = computed(
|
|
|
|
|
|
() => selectedSkill.value?.displayVersion === selectedSkill.value?.workingVersion
|
|
|
|
|
|
)
|
|
|
|
|
|
const canSubmitReview = computed(
|
|
|
|
|
|
() => canEditSelected.value && selectedSkillIsRule.value && isDisplayingWorkingVersion.value
|
|
|
|
|
|
)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const hasReviewSubmitReviewers = computed(() => reviewSubmitReviewerOptions.value.length > 0)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const canReviewSelected = computed(
|
|
|
|
|
|
() => canManageSelected.value && selectedSkillIsRule.value && isDisplayingWorkingVersion.value
|
|
|
|
|
|
)
|
|
|
|
|
|
const canUploadSpreadsheet = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
canEditSelected.value &&
|
|
|
|
|
|
selectedSkillUsesSpreadsheet.value &&
|
2026-05-19 16:19:03 +00:00
|
|
|
|
!detailBusy.value
|
2026-05-18 02:51:25 +00:00
|
|
|
|
)
|
|
|
|
|
|
const canDownloadSpreadsheet = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
selectedSkillUsesSpreadsheet.value &&
|
|
|
|
|
|
Boolean(selectedSkill.value?.id) &&
|
|
|
|
|
|
!detailBusy.value
|
|
|
|
|
|
)
|
|
|
|
|
|
const canEditSpreadsheetInline = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
selectedSkillUsesSpreadsheet.value &&
|
|
|
|
|
|
(selectedSkill.value?.isPreviewMock || canEditSelected.value)
|
|
|
|
|
|
)
|
|
|
|
|
|
const selectedSpreadsheetFileName = computed(
|
|
|
|
|
|
() =>
|
2026-05-19 16:19:03 +00:00
|
|
|
|
normalizeText(selectedSkill.value?.ruleDocument?.file_name) || '未上传规则表'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
)
|
2026-05-19 16:19:03 +00:00
|
|
|
|
const selectedSpreadsheetModeLabel = computed(() => {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (selectedSkill.value?.isPreviewMock) {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
return canEditSpreadsheetInline.value ? '可编辑' : '只读'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
}
|
2026-05-19 16:19:03 +00:00
|
|
|
|
return canEditSpreadsheetInline.value ? '在线可编辑' : '只读'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
})
|
|
|
|
|
|
const selectedVersionTimelineItems = computed(() =>
|
|
|
|
|
|
versionTimelineItems.value.map((item) => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
meta: resolveTimelineEventMeta(item.event_type),
|
|
|
|
|
|
timeLabel: formatDateTime(item.event_time)
|
|
|
|
|
|
}))
|
|
|
|
|
|
)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const selectedSpreadsheetChangeRecords = computed(() => {
|
|
|
|
|
|
if (!selectedSkillUsesSpreadsheet.value || !selectedSkill.value?.id) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
return (spreadsheetChangeRecordsByAsset.value[selectedSkill.value.id] || [])
|
|
|
|
|
|
.filter((item) => item?.changed_at)
|
2026-05-19 15:41:53 +00:00
|
|
|
|
.map((item) => {
|
|
|
|
|
|
const sheetNames = [
|
|
|
|
|
|
...(Array.isArray(item.sheet_changes)
|
|
|
|
|
|
? item.sheet_changes.map((change) => normalizeText(change.sheet_name))
|
|
|
|
|
|
: []),
|
|
|
|
|
|
...(Array.isArray(item.cell_changes)
|
|
|
|
|
|
? item.cell_changes.map((change) => normalizeText(change.sheet_name))
|
|
|
|
|
|
: [])
|
|
|
|
|
|
].filter(Boolean)
|
|
|
|
|
|
const changedSheetNames = [...new Set(sheetNames)]
|
|
|
|
|
|
const previewChanges = Array.isArray(item.cell_changes) ? item.cell_changes.slice(0, 3) : []
|
|
|
|
|
|
return {
|
|
|
|
|
|
...item,
|
|
|
|
|
|
time: formatDateTime(item.changed_at),
|
2026-05-19 16:19:03 +00:00
|
|
|
|
summary: formatSpreadsheetChangeSummary(item.summary),
|
2026-05-19 15:41:53 +00:00
|
|
|
|
changeCountLabel: item.changed_cell_count
|
|
|
|
|
|
? `${item.changed_cell_count} 处改动`
|
|
|
|
|
|
: `${item.changed_sheet_count || changedSheetNames.length || 0} 个工作表`,
|
|
|
|
|
|
changedSheetNames,
|
|
|
|
|
|
sheetPreview: changedSheetNames.slice(0, 4),
|
|
|
|
|
|
remainingSheetCount: Math.max(changedSheetNames.length - 4, 0),
|
|
|
|
|
|
previewChanges,
|
|
|
|
|
|
remainingChangeCount: Math.max((item.changed_cell_count || 0) - previewChanges.length, 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-05-18 09:42:23 +00:00
|
|
|
|
})
|
|
|
|
|
|
const selectedSpreadsheetChangeSheetRows = computed(() =>
|
|
|
|
|
|
Array.isArray(selectedSpreadsheetChangeRecord.value?.sheet_changes)
|
|
|
|
|
|
? selectedSpreadsheetChangeRecord.value.sheet_changes.map((item) => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
meta: resolveDiffChangeMeta(item.change_type)
|
|
|
|
|
|
}))
|
|
|
|
|
|
: []
|
|
|
|
|
|
)
|
|
|
|
|
|
const selectedSpreadsheetChangeCellRows = computed(() =>
|
|
|
|
|
|
Array.isArray(selectedSpreadsheetChangeRecord.value?.cell_changes)
|
|
|
|
|
|
? selectedSpreadsheetChangeRecord.value.cell_changes.map((item) => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
meta: resolveDiffChangeMeta(item.change_type)
|
|
|
|
|
|
}))
|
|
|
|
|
|
: []
|
2026-05-18 02:51:25 +00:00
|
|
|
|
)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const detailBusy = computed(() => Boolean(actionState.value))
|
|
|
|
|
|
const showReviewNote = computed(
|
|
|
|
|
|
() => selectedSkillIsRule.value && (selectedSkill.value?.reviewNote || selectedSkill.value?.reviewTimeLabel)
|
|
|
|
|
|
)
|
|
|
|
|
|
const domainOptions = computed(() => {
|
|
|
|
|
|
const uniqueValues = [...new Set(currentAssets.value.map((item) => item.domainValue).filter(Boolean))]
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ value: '', label: '全部业务域' },
|
|
|
|
|
|
...uniqueValues.map((value) => ({
|
|
|
|
|
|
value,
|
|
|
|
|
|
label: resolveDomainLabel(value)
|
|
|
|
|
|
}))
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
const ownerOptions = computed(() => {
|
|
|
|
|
|
const uniqueOwners = [...new Set(currentAssets.value.map((item) => item.owner).filter(Boolean))]
|
|
|
|
|
|
return [
|
2026-05-24 21:44:17 +08:00
|
|
|
|
{ value: '', label: activeType.value === 'riskRules' ? '全部审核人' : '全部负责人' },
|
2026-05-11 06:32:38 +00:00
|
|
|
|
...uniqueOwners.map((value) => ({
|
|
|
|
|
|
value,
|
|
|
|
|
|
label: value
|
|
|
|
|
|
}))
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
const selectedDomainLabel = computed(
|
|
|
|
|
|
() => domainOptions.value.find((item) => item.value === selectedDomain.value)?.label || '业务域'
|
|
|
|
|
|
)
|
|
|
|
|
|
const selectedOwnerLabel = computed(
|
2026-05-24 21:44:17 +08:00
|
|
|
|
() =>
|
|
|
|
|
|
ownerOptions.value.find((item) => item.value === selectedOwner.value)?.label ||
|
|
|
|
|
|
(activeType.value === 'riskRules' ? '审核人' : '负责人')
|
2026-05-11 06:32:38 +00:00
|
|
|
|
)
|
|
|
|
|
|
const selectedStatusLabel = computed(
|
|
|
|
|
|
() => STATUS_OPTIONS.find((item) => item.value === selectedStatus.value)?.label || '状态'
|
|
|
|
|
|
)
|
2026-05-20 09:36:01 +08:00
|
|
|
|
const showRiskScenarioFilter = computed(() =>
|
|
|
|
|
|
['financialRules', 'riskRules'].includes(activeType.value)
|
|
|
|
|
|
)
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const showStatusFilter = computed(() => true)
|
|
|
|
|
|
const showOnlineFilter = computed(() => false)
|
2026-05-24 21:44:17 +08:00
|
|
|
|
const showEnabledFilter = computed(() => activeType.value === 'riskRules')
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const selectedRiskScenarioLabel = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
RISK_SCENARIO_OPTIONS.find((item) => item.value === selectedRiskScenario.value)?.label ||
|
|
|
|
|
|
'使用场景'
|
|
|
|
|
|
)
|
2026-05-24 21:44:17 +08:00
|
|
|
|
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 ||
|
|
|
|
|
|
'是否启用'
|
|
|
|
|
|
)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const activeFilterTokens = computed(() => {
|
|
|
|
|
|
const tokens = []
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedDomain.value) {
|
|
|
|
|
|
tokens.push(`业务域:${resolveDomainLabel(selectedDomain.value)}`)
|
|
|
|
|
|
}
|
2026-05-19 20:23:58 +08:00
|
|
|
|
if (showRiskScenarioFilter.value && selectedRiskScenario.value) {
|
|
|
|
|
|
tokens.push(`使用场景:${selectedRiskScenario.value}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (showStatusFilter.value && selectedStatus.value) {
|
2026-05-11 06:32:38 +00:00
|
|
|
|
tokens.push(`状态:${resolveStatusMeta(selectedStatus.value).label}`)
|
|
|
|
|
|
}
|
2026-05-24 21:44:17 +08:00
|
|
|
|
if (showOnlineFilter.value && selectedOnlineState.value) {
|
|
|
|
|
|
tokens.push(`是否上线:${selectedOnlineStateLabel.value}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (showEnabledFilter.value && selectedEnabledState.value) {
|
|
|
|
|
|
tokens.push(`是否启用:${selectedEnabledStateLabel.value}`)
|
|
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
if (selectedOwner.value) {
|
2026-05-24 21:44:17 +08:00
|
|
|
|
tokens.push(`${activeType.value === 'riskRules' ? '审核人' : '负责人'}:${selectedOwner.value}`)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (keyword.value.trim()) {
|
|
|
|
|
|
tokens.push(`搜索:${keyword.value.trim()}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return tokens
|
|
|
|
|
|
})
|
2026-05-13 06:52:30 +00:00
|
|
|
|
const auditEmptyState = computed(() => {
|
|
|
|
|
|
const hasFilters = activeFilterTokens.value.length > 0
|
2026-05-20 09:36:01 +08:00
|
|
|
|
const supportedFilters = [
|
|
|
|
|
|
'业务域',
|
2026-05-24 21:44:17 +08:00
|
|
|
|
activeType.value === 'riskRules' ? '审核人' : '负责人',
|
2026-05-20 09:36:01 +08:00
|
|
|
|
...(showRiskScenarioFilter.value ? ['使用场景'] : []),
|
|
|
|
|
|
...(showStatusFilter.value ? ['状态'] : []),
|
2026-05-24 21:44:17 +08:00
|
|
|
|
...(showOnlineFilter.value ? ['是否上线'] : []),
|
|
|
|
|
|
...(showEnabledFilter.value ? ['是否启用'] : []),
|
2026-05-20 09:36:01 +08:00
|
|
|
|
'关键词'
|
|
|
|
|
|
]
|
2026-05-13 06:52:30 +00:00
|
|
|
|
|
|
|
|
|
|
if (!currentAssets.value.length) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
eyebrow: `${activeTabLabel.value}资产`,
|
|
|
|
|
|
title: `${activeTabLabel.value}列表暂时还是空的`,
|
|
|
|
|
|
desc: `当前环境里还没有可展示的${activeTabLabel.value}资产。完成接入或同步后,会统一展示在这里。`,
|
|
|
|
|
|
icon: 'mdi mdi-database-search-outline',
|
2026-05-18 02:51:25 +00:00
|
|
|
|
actionLabel: '',
|
|
|
|
|
|
actionIcon: '',
|
2026-05-13 06:52:30 +00:00
|
|
|
|
tone: 'amber',
|
|
|
|
|
|
artLabel: 'ASSET',
|
2026-05-20 09:36:01 +08:00
|
|
|
|
tips: [
|
|
|
|
|
|
'切换页签可查看其他资产类型',
|
|
|
|
|
|
`支持按${supportedFilters.slice(0, -1).join('、')}和关键词做过滤`
|
|
|
|
|
|
]
|
2026-05-13 06:52:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
eyebrow: '筛选结果为空',
|
|
|
|
|
|
title: `没有找到匹配的${activeTabLabel.value}`,
|
|
|
|
|
|
desc: hasFilters
|
2026-05-20 09:36:01 +08:00
|
|
|
|
? `试试清空${supportedFilters.join('、')}筛选,再重新查看。`
|
2026-05-13 06:52:30 +00:00
|
|
|
|
: `当前列表中还没有满足展示条件的${activeTabLabel.value}资产。`,
|
|
|
|
|
|
icon: hasFilters ? 'mdi mdi-tune-variant' : 'mdi mdi-view-grid-outline',
|
2026-05-18 02:51:25 +00:00
|
|
|
|
actionLabel: hasFilters ? '清空筛选' : '',
|
|
|
|
|
|
actionIcon: hasFilters ? 'mdi mdi-filter-remove-outline' : '',
|
2026-05-13 06:52:30 +00:00
|
|
|
|
tone: hasFilters ? 'emerald' : 'slate',
|
|
|
|
|
|
artLabel: hasFilters ? 'FILTER' : 'QUEUE',
|
|
|
|
|
|
tips: hasFilters
|
2026-05-20 09:36:01 +08:00
|
|
|
|
? [
|
|
|
|
|
|
`${supportedFilters.join('、')}会叠加过滤`,
|
|
|
|
|
|
showRiskScenarioFilter.value
|
|
|
|
|
|
? '可以换个规则名称或场景分类继续搜索'
|
|
|
|
|
|
: '可以换个编码、名称或负责人关键词继续搜索'
|
|
|
|
|
|
]
|
2026-05-13 06:52:30 +00:00
|
|
|
|
: ['列表展示来自真实资产 API', '切换资产类型后会自动重新拉取数据']
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const canActivateSelected = computed(() => {
|
|
|
|
|
|
if (!selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
return (
|
|
|
|
|
|
isDisplayingWorkingVersion.value &&
|
|
|
|
|
|
selectedSkill.value?.reviewStatusValue === 'approved' &&
|
|
|
|
|
|
selectedSkill.value?.workingVersion !== selectedSkill.value?.publishedVersion
|
|
|
|
|
|
)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
})
|
|
|
|
|
|
const activateBlockedReason = computed(() => {
|
|
|
|
|
|
if (!selectedSkillIsRule.value) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (selectedSkill.value?.isPreviewMock) {
|
|
|
|
|
|
return '当前为页面预览态,暂不执行真实审核和上线。'
|
|
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
if (!canManageSelected.value) {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
return '仅高级管理人员可执行审核和上线。'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isDisplayingWorkingVersion.value) {
|
|
|
|
|
|
return '请先切回当前工作版本,再执行审核或上线。'
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (selectedSkill.value?.workingVersion === selectedSkill.value?.publishedVersion) {
|
|
|
|
|
|
return '当前工作版本已经是线上版本。'
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (selectedSkill.value?.reviewStatusValue !== 'approved') {
|
|
|
|
|
|
return '当前规则版本未审核通过,不能上线。'
|
|
|
|
|
|
}
|
|
|
|
|
|
return ''
|
2026-05-11 01:53:30 +00:00
|
|
|
|
})
|
2026-05-21 23:53:03 +08:00
|
|
|
|
const visibleSkills = computed(() =>
|
|
|
|
|
|
filterAuditAssets(currentAssets.value, {
|
|
|
|
|
|
keyword: keyword.value,
|
|
|
|
|
|
selectedDomain: selectedDomain.value,
|
|
|
|
|
|
selectedOwner: selectedOwner.value,
|
|
|
|
|
|
selectedStatus: selectedStatus.value,
|
|
|
|
|
|
selectedRiskScenario: selectedRiskScenario.value,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
selectedOnlineState: selectedOnlineState.value,
|
|
|
|
|
|
selectedEnabledState: selectedEnabledState.value,
|
2026-05-21 23:53:03 +08:00
|
|
|
|
showStatusFilter: showStatusFilter.value,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
showRiskScenarioFilter: showRiskScenarioFilter.value,
|
|
|
|
|
|
showOnlineFilter: showOnlineFilter.value,
|
|
|
|
|
|
showEnabledFilter: showEnabledFilter.value
|
2026-05-11 06:32:38 +00:00
|
|
|
|
})
|
2026-05-21 23:53:03 +08:00
|
|
|
|
)
|
2026-05-09 15:46:16 +00:00
|
|
|
|
|
2026-05-09 16:16:56 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
selectedSkill,
|
|
|
|
|
|
(value) => {
|
|
|
|
|
|
emit('detail-open-change', Boolean(value))
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => [
|
|
|
|
|
|
selectedSkill.value?.id || '',
|
|
|
|
|
|
selectedSkill.value?.loading ? '1' : '0',
|
|
|
|
|
|
selectedSkill.value?.usesSpreadsheetRule ? '1' : '0'
|
|
|
|
|
|
],
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
if (!selectedSkillUsesSpreadsheet.value || selectedSkill.value?.loading) {
|
|
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
spreadsheetOnlyOfficeError.value = ''
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
await mountSpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
watch(activeType, () => {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
2026-05-11 06:32:38 +00:00
|
|
|
|
selectedSkill.value = null
|
|
|
|
|
|
versionSwitchTarget.value = null
|
|
|
|
|
|
resetFilters()
|
|
|
|
|
|
loadAssets({ force: true }).catch((error) => {
|
|
|
|
|
|
errorMessage.value = error?.message || '资产数据加载失败,请稍后重试。'
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
function resetFilters() {
|
|
|
|
|
|
keyword.value = ''
|
|
|
|
|
|
selectedDomain.value = ''
|
|
|
|
|
|
selectedOwner.value = ''
|
|
|
|
|
|
selectedStatus.value = ''
|
2026-05-19 20:23:58 +08:00
|
|
|
|
selectedRiskScenario.value = ''
|
2026-05-24 21:44:17 +08:00
|
|
|
|
selectedOnlineState.value = ''
|
|
|
|
|
|
selectedEnabledState.value = ''
|
2026-05-11 06:32:38 +00:00
|
|
|
|
activeFilterPopover.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 06:52:30 +00:00
|
|
|
|
function handleAuditEmptyAction() {
|
|
|
|
|
|
if (!currentAssets.value.length || !activeFilterTokens.value.length) {
|
|
|
|
|
|
loadAssets({ force: true }).catch(() => {})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resetFilters()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
function toggleFilterPopover(name) {
|
|
|
|
|
|
activeFilterPopover.value = activeFilterPopover.value === name ? '' : name
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeFilterPopover() {
|
|
|
|
|
|
activeFilterPopover.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function selectFilter(name, value) {
|
|
|
|
|
|
if (name === 'domain') {
|
|
|
|
|
|
selectedDomain.value = value
|
|
|
|
|
|
}
|
|
|
|
|
|
if (name === 'owner') {
|
|
|
|
|
|
selectedOwner.value = value
|
|
|
|
|
|
}
|
|
|
|
|
|
if (name === 'status') {
|
|
|
|
|
|
selectedStatus.value = value
|
|
|
|
|
|
}
|
2026-05-19 20:23:58 +08:00
|
|
|
|
if (name === 'riskScenario') {
|
|
|
|
|
|
selectedRiskScenario.value = value
|
|
|
|
|
|
}
|
2026-05-24 21:44:17 +08:00
|
|
|
|
if (name === 'online') {
|
|
|
|
|
|
selectedOnlineState.value = value
|
|
|
|
|
|
}
|
|
|
|
|
|
if (name === 'enabled') {
|
|
|
|
|
|
selectedEnabledState.value = value
|
|
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
closeFilterPopover()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleDocumentClick(event) {
|
|
|
|
|
|
const target = event.target
|
|
|
|
|
|
if (!(target instanceof Element)) {
|
|
|
|
|
|
closeFilterPopover()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!target.closest('.picker-filter')) {
|
|
|
|
|
|
closeFilterPopover()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveActor() {
|
|
|
|
|
|
return currentUser.value?.name || currentUser.value?.username || 'system'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 19:54:42 +08:00
|
|
|
|
function openRiskRuleCreateDialog() {
|
|
|
|
|
|
if (activeType.value !== 'riskRules') {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
riskRuleCreateForm.value = createDefaultRiskRuleForm()
|
|
|
|
|
|
riskRuleCreateOpen.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeRiskRuleCreateDialog() {
|
|
|
|
|
|
if (riskRuleCreateBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
riskRuleCreateOpen.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitRiskRuleCreate() {
|
|
|
|
|
|
if (!canCreateRiskRule.value || riskRuleCreateBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const naturalLanguage = String(riskRuleCreateForm.value.natural_language || '').trim()
|
2026-05-26 09:15:14 +08:00
|
|
|
|
const ruleTitle = String(riskRuleCreateForm.value.rule_title || '').trim()
|
|
|
|
|
|
if (ruleTitle.length < 2) {
|
|
|
|
|
|
toast('请输入至少 2 个字的规则标题。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-23 19:54:42 +08:00
|
|
|
|
if (naturalLanguage.length < 8) {
|
|
|
|
|
|
toast('请至少输入 8 个字的风险规则描述。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = 'generate-risk-rule'
|
|
|
|
|
|
try {
|
|
|
|
|
|
const detail = await generateRiskRuleAsset(
|
|
|
|
|
|
{
|
2026-05-26 09:15:14 +08:00
|
|
|
|
business_domain: 'expense',
|
|
|
|
|
|
expense_category: riskRuleCreateForm.value.expense_category,
|
|
|
|
|
|
rule_title: ruleTitle,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
requires_attachment: Boolean(riskRuleCreateForm.value.requires_attachment),
|
2026-05-23 19:54:42 +08:00
|
|
|
|
natural_language: naturalLanguage
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
|
|
|
|
|
riskRuleCreateOpen.value = false
|
|
|
|
|
|
await refreshCurrentAssets()
|
2026-05-26 09:15:14 +08:00
|
|
|
|
scheduleRiskRuleGenerationPoll(detail.id)
|
|
|
|
|
|
toast('风险规则已进入后台生成,列表会先显示生成中。')
|
2026-05-23 19:54:42 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '风险规则生成失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
|
function stopRiskRuleGenerationPoll(assetId) {
|
|
|
|
|
|
const timer = riskRuleGenerationPollTimers.get(assetId)
|
|
|
|
|
|
if (timer) {
|
|
|
|
|
|
window.clearTimeout(timer)
|
|
|
|
|
|
riskRuleGenerationPollTimers.delete(assetId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scheduleRiskRuleGenerationPoll(assetId, attempt = 0) {
|
|
|
|
|
|
const normalizedAssetId = normalizeText(assetId)
|
|
|
|
|
|
if (!normalizedAssetId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
stopRiskRuleGenerationPoll(normalizedAssetId)
|
|
|
|
|
|
const timer = window.setTimeout(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
const latest = (assetBuckets.value.riskRules || []).find((item) => item.id === normalizedAssetId)
|
|
|
|
|
|
if (!latest || latest.statusValue !== 'generating' || attempt >= 59) {
|
|
|
|
|
|
riskRuleGenerationPollTimers.delete(normalizedAssetId)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
scheduleRiskRuleGenerationPoll(normalizedAssetId, attempt + 1)
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
if (attempt < 59) {
|
|
|
|
|
|
scheduleRiskRuleGenerationPoll(normalizedAssetId, attempt + 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
riskRuleGenerationPollTimers.delete(normalizedAssetId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, attempt === 0 ? 1200 : 3000)
|
|
|
|
|
|
riskRuleGenerationPollTimers.set(normalizedAssetId, timer)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 06:57:07 +00:00
|
|
|
|
async function persistRuleRuntimeConfig(asset, runtimeRule) {
|
|
|
|
|
|
await updateAgentAsset(
|
|
|
|
|
|
asset.id,
|
|
|
|
|
|
{
|
|
|
|
|
|
config_json: buildRuleConfigPayload(asset, runtimeRule)
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
function destroySpreadsheetOnlyOfficeEditor() {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
if (spreadsheetOnlyOfficeLoadTimer) {
|
|
|
|
|
|
window.clearTimeout(spreadsheetOnlyOfficeLoadTimer)
|
|
|
|
|
|
spreadsheetOnlyOfficeLoadTimer = null
|
|
|
|
|
|
}
|
2026-05-19 16:19:03 +00:00
|
|
|
|
stopSpreadsheetOnlyOfficeChangeSync()
|
2026-05-18 09:42:23 +00:00
|
|
|
|
spreadsheetOnlyOfficeHadLocalEdits = false
|
|
|
|
|
|
spreadsheetOnlyOfficeSyncSeq += 1
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (spreadsheetOnlyOfficeEditor.value?.destroyEditor) {
|
|
|
|
|
|
spreadsheetOnlyOfficeEditor.value.destroyEditor()
|
|
|
|
|
|
}
|
|
|
|
|
|
spreadsheetOnlyOfficeEditor.value = null
|
|
|
|
|
|
spreadsheetOnlyOfficeReady.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 16:19:03 +00:00
|
|
|
|
function stopSpreadsheetOnlyOfficeChangeSync() {
|
|
|
|
|
|
if (spreadsheetOnlyOfficeChangePollTimer) {
|
|
|
|
|
|
window.clearTimeout(spreadsheetOnlyOfficeChangePollTimer)
|
|
|
|
|
|
spreadsheetOnlyOfficeChangePollTimer = null
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getLatestSpreadsheetChangeKey(assetId) {
|
2026-05-21 23:53:03 +08:00
|
|
|
|
return buildSpreadsheetChangeRecordKey(spreadsheetChangeRecordsByAsset.value[assetId] || [])
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshSpreadsheetChangeRecordsAfterSave(assetId, previousLatestKey = '', attempt = 0) {
|
|
|
|
|
|
const normalizedAssetId = normalizeText(assetId)
|
|
|
|
|
|
if (!normalizedAssetId || selectedSkill.value?.id !== normalizedAssetId) {
|
2026-05-19 20:23:58 +08:00
|
|
|
|
return false
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await loadSpreadsheetChangeRecords(normalizedAssetId)
|
|
|
|
|
|
const nextLatestKey = getLatestSpreadsheetChangeKey(normalizedAssetId)
|
|
|
|
|
|
if (nextLatestKey && nextLatestKey !== previousLatestKey) {
|
2026-05-19 20:23:58 +08:00
|
|
|
|
return true
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (attempt >= 9) {
|
2026-05-19 20:23:58 +08:00
|
|
|
|
return false
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
await new Promise((resolve) => window.setTimeout(resolve, 800))
|
|
|
|
|
|
return refreshSpreadsheetChangeRecordsAfterSave(normalizedAssetId, previousLatestKey, attempt + 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 16:19:03 +00:00
|
|
|
|
function scheduleSpreadsheetOnlyOfficeChangeSync(assetId, attempt = 0) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const normalizedAssetId = normalizeText(assetId)
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (!normalizedAssetId) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const syncSeq = ++spreadsheetOnlyOfficeSyncSeq
|
2026-05-19 16:19:03 +00:00
|
|
|
|
stopSpreadsheetOnlyOfficeChangeSync()
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const previousLatestChangeKey = getLatestSpreadsheetChangeKey(normalizedAssetId)
|
|
|
|
|
|
|
|
|
|
|
|
const runSync = async () => {
|
|
|
|
|
|
if (syncSeq !== spreadsheetOnlyOfficeSyncSeq || selectedSkill.value?.id !== normalizedAssetId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-19 20:23:58 +08:00
|
|
|
|
const changeRecordRefreshed = await refreshSpreadsheetChangeRecordsAfterSave(
|
|
|
|
|
|
normalizedAssetId,
|
|
|
|
|
|
previousLatestChangeKey
|
|
|
|
|
|
)
|
|
|
|
|
|
if (changeRecordRefreshed) {
|
|
|
|
|
|
await refreshCurrentAssets()
|
2026-05-19 16:19:03 +00:00
|
|
|
|
stopSpreadsheetOnlyOfficeChangeSync()
|
2026-05-19 20:23:58 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 09:42:23 +00:00
|
|
|
|
} catch {
|
|
|
|
|
|
// Ignore transient polling failures and continue retrying within the window.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (syncSeq !== spreadsheetOnlyOfficeSyncSeq || selectedSkill.value?.id !== normalizedAssetId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (attempt >= 29) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-19 16:19:03 +00:00
|
|
|
|
spreadsheetOnlyOfficeChangePollTimer = window.setTimeout(() => {
|
|
|
|
|
|
scheduleSpreadsheetOnlyOfficeChangeSync(normalizedAssetId, attempt + 1)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}, 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 16:19:03 +00:00
|
|
|
|
spreadsheetOnlyOfficeChangePollTimer = window.setTimeout(() => {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
runSync().catch(() => {})
|
|
|
|
|
|
}, attempt === 0 ? 800 : 2000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 16:19:03 +00:00
|
|
|
|
function isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return (
|
|
|
|
|
|
mountSeq !== spreadsheetOnlyOfficeMountSeq ||
|
|
|
|
|
|
!selectedSkillUsesSpreadsheet.value ||
|
|
|
|
|
|
selectedSkill.value?.id !== assetId ||
|
|
|
|
|
|
selectedSkill.value?.loading
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function mountSpreadsheetOnlyOfficeEditor(retryAttempt = 0) {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (!selectedSkillUsesSpreadsheet.value || !selectedSkill.value?.id || selectedSkill.value?.loading) {
|
|
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const mountSeq = ++spreadsheetOnlyOfficeMountSeq
|
|
|
|
|
|
const assetId = selectedSkill.value.id
|
|
|
|
|
|
const editable = canEditSpreadsheetInline.value
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
spreadsheetOnlyOfficeLoading.value = true
|
|
|
|
|
|
spreadsheetOnlyOfficeError.value = ''
|
|
|
|
|
|
spreadsheetOnlyOfficeReady.value = false
|
|
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
const payload = await fetchAgentAssetSpreadsheetOnlyOfficeConfig(assetId)
|
|
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
await loadOnlyOfficeApi(payload.documentServerUrl)
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (!window.DocsAPI?.DocEditor) {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
throw new Error('表格编辑器未正确加载。')
|
2026-05-18 02:51:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 09:42:23 +00:00
|
|
|
|
// Host id must be unique for every mount. ONLYOFFICE mutates its host DOM
|
|
|
|
|
|
// during lifecycle teardown; reusing the same element can leave the next
|
|
|
|
|
|
// DocEditor instance with a dead container even though config loading succeeds.
|
2026-05-19 16:19:03 +00:00
|
|
|
|
spreadsheetOnlyOfficeHostId.value = `audit-rule-onlyoffice-${assetId}-${mountSeq}`
|
2026-05-18 02:51:25 +00:00
|
|
|
|
await nextTick()
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const config = buildOnlyOfficeEditorConfig(payload.config, {
|
|
|
|
|
|
viewportHeight: window.innerHeight,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
editable,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
fillContainer: true
|
|
|
|
|
|
})
|
|
|
|
|
|
const upstreamEvents = config.events || {}
|
2026-05-18 09:42:23 +00:00
|
|
|
|
spreadsheetOnlyOfficeLoadTimer = window.setTimeout(() => {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (retryAttempt < 1) {
|
|
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = true
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
|
|
mountSpreadsheetOnlyOfficeEditor(retryAttempt + 1).catch(() => {})
|
|
|
|
|
|
}, 600)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-19 16:19:03 +00:00
|
|
|
|
spreadsheetOnlyOfficeError.value = '表格加载超时,请退出详情后重试。'
|
2026-05-18 09:42:23 +00:00
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
}, 15000)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
config.events = {
|
|
|
|
|
|
...upstreamEvents,
|
|
|
|
|
|
onAppReady(event) {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (spreadsheetOnlyOfficeLoadTimer) {
|
|
|
|
|
|
window.clearTimeout(spreadsheetOnlyOfficeLoadTimer)
|
|
|
|
|
|
spreadsheetOnlyOfficeLoadTimer = null
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
spreadsheetOnlyOfficeReady.value = true
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
upstreamEvents.onAppReady?.(event)
|
|
|
|
|
|
},
|
|
|
|
|
|
onError(event) {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (spreadsheetOnlyOfficeLoadTimer) {
|
|
|
|
|
|
window.clearTimeout(spreadsheetOnlyOfficeLoadTimer)
|
|
|
|
|
|
spreadsheetOnlyOfficeLoadTimer = null
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const errorCode = event?.data?.errorCode
|
|
|
|
|
|
const errorDescription = event?.data?.errorDescription
|
|
|
|
|
|
spreadsheetOnlyOfficeError.value = errorDescription
|
2026-05-19 16:19:03 +00:00
|
|
|
|
? `表格加载失败:${errorDescription}`
|
|
|
|
|
|
: `表格加载失败${errorCode ? `(错误码 ${errorCode})` : '。'}`
|
2026-05-18 02:51:25 +00:00
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
upstreamEvents.onError?.(event)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
onDocumentStateChange(event) {
|
|
|
|
|
|
const hasChanges = Boolean(event?.data)
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
|
spreadsheetOnlyOfficeHadLocalEdits = true
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (!spreadsheetOnlyOfficeChangePollTimer) {
|
|
|
|
|
|
scheduleSpreadsheetOnlyOfficeChangeSync(assetId)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
spreadsheetOnlyOfficeHadLocalEdits &&
|
|
|
|
|
|
editable &&
|
2026-05-19 16:19:03 +00:00
|
|
|
|
!isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
) {
|
|
|
|
|
|
spreadsheetOnlyOfficeHadLocalEdits = false
|
2026-05-19 16:19:03 +00:00
|
|
|
|
scheduleSpreadsheetOnlyOfficeChangeSync(assetId)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
upstreamEvents.onDocumentStateChange?.(event)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
spreadsheetOnlyOfficeEditor.value = new window.DocsAPI.DocEditor(
|
|
|
|
|
|
spreadsheetOnlyOfficeHostId.value,
|
|
|
|
|
|
config
|
|
|
|
|
|
)
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
} catch (error) {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (isSpreadsheetOnlyOfficeMountStale(mountSeq, assetId)) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
spreadsheetOnlyOfficeError.value = error?.message || '规则表加载失败,请稍后重试。'
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
toast(spreadsheetOnlyOfficeError.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function triggerSpreadsheetUpload() {
|
|
|
|
|
|
if (!canUploadSpreadsheet.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
spreadsheetUploadInput.value?.click()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function downloadSpreadsheetFile() {
|
|
|
|
|
|
if (!canDownloadSpreadsheet.value || !selectedSkill.value?.id) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = 'download-spreadsheet'
|
|
|
|
|
|
try {
|
|
|
|
|
|
const blob = await fetchAgentAssetSpreadsheetBlob(
|
|
|
|
|
|
selectedSkill.value.id,
|
|
|
|
|
|
'attachment'
|
|
|
|
|
|
)
|
|
|
|
|
|
const objectUrl = URL.createObjectURL(blob)
|
|
|
|
|
|
const anchor = document.createElement('a')
|
|
|
|
|
|
anchor.href = objectUrl
|
|
|
|
|
|
anchor.download = selectedSpreadsheetFileName.value || '规则表.xlsx'
|
|
|
|
|
|
document.body.appendChild(anchor)
|
|
|
|
|
|
anchor.click()
|
|
|
|
|
|
anchor.remove()
|
|
|
|
|
|
URL.revokeObjectURL(objectUrl)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则表下载失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function uploadSpreadsheetFile(file) {
|
|
|
|
|
|
if (!file || !selectedSkill.value?.id || !canUploadSpreadsheet.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = 'upload-spreadsheet'
|
|
|
|
|
|
try {
|
|
|
|
|
|
await importAgentAssetSpreadsheetContent(selectedSkill.value.id, file, {
|
|
|
|
|
|
actor: resolveActor()
|
|
|
|
|
|
})
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
2026-05-18 09:42:23 +00:00
|
|
|
|
await loadSpreadsheetChangeRecords(selectedSkill.value.id)
|
2026-05-19 16:19:03 +00:00
|
|
|
|
toast(`已导入 ${file.name} 的表格内容,右侧会记录本次修改。`)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则表内容导入失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
if (spreadsheetUploadInput.value) {
|
|
|
|
|
|
spreadsheetUploadInput.value.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleSpreadsheetFileInput(event) {
|
|
|
|
|
|
await uploadSpreadsheetFile(event?.target?.files?.[0] || null)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
async function loadRuns(options = {}) {
|
|
|
|
|
|
if (runLoading.value && !options.force) {
|
2026-05-09 15:46:16 +00:00
|
|
|
|
return
|
2026-05-06 11:00:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
runLoading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = await fetchAgentRuns({ limit: 50 })
|
|
|
|
|
|
runs.value = Array.isArray(payload) ? payload : []
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
runLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadAssets(options = {}) {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
errorMessage.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = await fetchAgentAssets({ assetType: activeMeta.value.assetType })
|
2026-05-18 02:51:25 +00:00
|
|
|
|
const items = Array.isArray(payload) ? payload.map(buildListItem).filter(Boolean) : []
|
|
|
|
|
|
|
|
|
|
|
|
if (activeMeta.value.assetType === 'rule') {
|
|
|
|
|
|
const nextBuckets = {
|
|
|
|
|
|
financialRules: [],
|
|
|
|
|
|
riskRules: []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items.forEach((item) => {
|
|
|
|
|
|
if (item?.tabId === 'financialRules' || item?.tabId === 'riskRules') {
|
|
|
|
|
|
nextBuckets[item.tabId].push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
assetBuckets.value = {
|
|
|
|
|
|
...assetBuckets.value,
|
|
|
|
|
|
...nextBuckets
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
assetBuckets.value = {
|
|
|
|
|
|
...assetBuckets.value,
|
|
|
|
|
|
[activeType.value]: items
|
|
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (activeMeta.value.assetType === 'rule') {
|
|
|
|
|
|
assetBuckets.value = {
|
|
|
|
|
|
...assetBuckets.value,
|
|
|
|
|
|
financialRules:
|
|
|
|
|
|
activeType.value === 'financialRules' ? [] : assetBuckets.value.financialRules,
|
|
|
|
|
|
riskRules: []
|
|
|
|
|
|
}
|
|
|
|
|
|
errorMessage.value = error?.message || '资产数据加载失败,请稍后重试。'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
assetBuckets.value = {
|
|
|
|
|
|
...assetBuckets.value,
|
|
|
|
|
|
[activeType.value]: []
|
|
|
|
|
|
}
|
|
|
|
|
|
errorMessage.value = error?.message || '资产数据加载失败,请稍后重试。'
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (!options.silent) {
|
|
|
|
|
|
toast(errorMessage.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshCurrentAssets() {
|
|
|
|
|
|
await loadAssets({ force: true, silent: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadSelectedAssetDetail(assetId) {
|
|
|
|
|
|
detailLoading.value = true
|
|
|
|
|
|
detailError.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!runs.value.length) {
|
|
|
|
|
|
await loadRuns()
|
|
|
|
|
|
}
|
|
|
|
|
|
const detail = await fetchAgentAssetDetail(assetId)
|
|
|
|
|
|
selectedSkill.value = buildDetailViewModel(detail, runs.value)
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (selectedSkill.value?.type === 'rules') {
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (!selectedSkill.value.usesSpreadsheetRule && !selectedSkill.value.usesJsonRiskRule) {
|
2026-05-19 20:23:58 +08:00
|
|
|
|
loadVersionTimeline(assetId, { silent: true }).catch(() => {})
|
|
|
|
|
|
}
|
2026-05-18 09:42:23 +00:00
|
|
|
|
if (selectedSkill.value.usesSpreadsheetRule) {
|
|
|
|
|
|
loadSpreadsheetChangeRecords(assetId).catch(() => {})
|
|
|
|
|
|
}
|
2026-05-19 20:23:58 +08:00
|
|
|
|
if (selectedSkill.value.usesJsonRiskRule) {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
if (selectedSkill.value.riskRuleGenerationFailed || selectedSkill.value.riskRuleGenerationBusy) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-19 15:41:53 +00:00
|
|
|
|
try {
|
|
|
|
|
|
await loadRiskRuleJson(assetId)
|
|
|
|
|
|
} catch (jsonError) {
|
|
|
|
|
|
console.warn('Failed to load risk rule JSON:', jsonError)
|
|
|
|
|
|
const jsonMessage =
|
|
|
|
|
|
jsonError?.message || '风险规则 JSON 文件缺失或无法读取,请同步规则库后重试。'
|
|
|
|
|
|
toast(jsonMessage)
|
|
|
|
|
|
selectedSkill.value = {
|
|
|
|
|
|
...selectedSkill.value,
|
|
|
|
|
|
riskRuleJsonText: '{}',
|
|
|
|
|
|
riskRuleDescription:
|
|
|
|
|
|
selectedSkill.value.riskRuleDescription ||
|
|
|
|
|
|
'规则 JSON 尚未就绪,请联系管理员执行平台风险规则同步。'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-19 20:23:58 +08:00
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
detailError.value = error?.message || '资产详情加载失败,请稍后重试。'
|
|
|
|
|
|
toast(detailError.value)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
detailLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 20:23:58 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 09:42:23 +00:00
|
|
|
|
async function loadSpreadsheetChangeRecords(assetId) {
|
|
|
|
|
|
if (!assetId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const payload = await fetchAgentAssetSpreadsheetChangeRecords(assetId, 30)
|
|
|
|
|
|
spreadsheetChangeRecordsByAsset.value = {
|
|
|
|
|
|
...spreadsheetChangeRecordsByAsset.value,
|
|
|
|
|
|
[assetId]: Array.isArray(payload) ? payload : []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openSpreadsheetChangeDetail(item) {
|
|
|
|
|
|
if (!item?.changed_at) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
selectedSpreadsheetChangeRecord.value = item
|
|
|
|
|
|
spreadsheetChangeDetailOpen.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSpreadsheetChangeDetail() {
|
|
|
|
|
|
spreadsheetChangeDetailOpen.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
function openAssetDetail(asset) {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
if (asset?.usesJsonRiskRule && asset.statusValue === 'generating') {
|
|
|
|
|
|
toast('规则仍在后台生成中,生成完成后才能进入详情。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 02:51:25 +00:00
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
spreadsheetOnlyOfficeError.value = ''
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
|
|
|
|
|
if (asset?.isPreviewMock) {
|
|
|
|
|
|
selectedSkill.value = buildPreviewRuleDetail()
|
|
|
|
|
|
detailError.value = ''
|
|
|
|
|
|
detailLoading.value = false
|
|
|
|
|
|
versionSwitchTarget.value = null
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-19 16:19:03 +00:00
|
|
|
|
const opensSpreadsheetRule = Boolean(asset?.usesSpreadsheetRule)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
selectedSkill.value = {
|
|
|
|
|
|
...asset,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
configJson: {},
|
2026-05-18 02:51:25 +00:00
|
|
|
|
isPreviewMock: false,
|
2026-05-19 16:19:03 +00:00
|
|
|
|
usesSpreadsheetRule: opensSpreadsheetRule,
|
|
|
|
|
|
usesJsonRiskRule: Boolean(asset?.usesJsonRiskRule),
|
2026-05-19 20:23:58 +08:00
|
|
|
|
riskRuleJsonText: '{}',
|
|
|
|
|
|
riskRuleSummary: null,
|
|
|
|
|
|
riskRuleDescription: '',
|
|
|
|
|
|
riskRuleSourceRef: '',
|
2026-05-19 16:19:03 +00:00
|
|
|
|
ruleDocument: asset?.ruleDocument || null,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
scenarioList: [],
|
2026-05-11 06:32:38 +00:00
|
|
|
|
fields: [],
|
|
|
|
|
|
promptSections: [],
|
|
|
|
|
|
outputRules: [],
|
|
|
|
|
|
tests: [],
|
|
|
|
|
|
triggers: [],
|
|
|
|
|
|
tools: [],
|
|
|
|
|
|
history: [],
|
|
|
|
|
|
markdownContent: '',
|
2026-05-15 06:57:07 +00:00
|
|
|
|
runtimeRuleText: '',
|
|
|
|
|
|
ruleTemplateKey: '',
|
|
|
|
|
|
ruleTemplateLabel: '',
|
|
|
|
|
|
runtimeKind: 'policy_rule_draft',
|
2026-05-11 06:32:38 +00:00
|
|
|
|
displayVersion: asset.version,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
displayVersionChangeNote: '无版本说明',
|
2026-05-19 16:19:03 +00:00
|
|
|
|
loading: !opensSpreadsheetRule,
|
|
|
|
|
|
reviewStatusLabel: opensSpreadsheetRule ? '' : '加载中',
|
2026-05-11 06:32:38 +00:00
|
|
|
|
reviewStatusTone: 'draft'
|
|
|
|
|
|
}
|
|
|
|
|
|
versionSwitchTarget.value = null
|
2026-05-19 16:19:03 +00:00
|
|
|
|
if (opensSpreadsheetRule) {
|
|
|
|
|
|
loadSpreadsheetChangeRecords(asset.id).catch(() => {})
|
|
|
|
|
|
}
|
2026-05-11 06:32:38 +00:00
|
|
|
|
loadSelectedAssetDetail(asset.id).catch(() => {})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeDetail() {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
|
|
|
|
|
spreadsheetOnlyOfficeError.value = ''
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading.value = false
|
2026-05-11 06:32:38 +00:00
|
|
|
|
selectedSkill.value = null
|
|
|
|
|
|
detailError.value = ''
|
|
|
|
|
|
detailLoading.value = false
|
|
|
|
|
|
versionSwitchTarget.value = null
|
2026-05-18 02:51:25 +00:00
|
|
|
|
versionTimelineOpen.value = false
|
|
|
|
|
|
versionTimelineItems.value = []
|
2026-05-24 21:44:17 +08:00
|
|
|
|
riskRuleTestOpen.value = false
|
|
|
|
|
|
riskRuleDeleteOpen.value = false
|
|
|
|
|
|
riskRuleReturnOpen.value = false
|
|
|
|
|
|
riskRulePublishOpen.value = false
|
|
|
|
|
|
riskRuleReturnNote.value = ''
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openVersionSwitch(version) {
|
|
|
|
|
|
if (!selectedSkill.value || version.version === selectedSkill.value.displayVersion) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-09 15:46:16 +00:00
|
|
|
|
versionSwitchTarget.value = version
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function cancelVersionSwitch() {
|
|
|
|
|
|
versionSwitchTarget.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function confirmVersionSwitch() {
|
|
|
|
|
|
if (!selectedSkill.value || !versionSwitchTarget.value) {
|
|
|
|
|
|
return
|
2026-05-06 11:00:38 +08:00
|
|
|
|
}
|
2026-05-09 15:46:16 +00:00
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
selectedSkill.value.displayVersion = versionSwitchTarget.value.version
|
2026-05-15 06:57:07 +00:00
|
|
|
|
selectedSkill.value.displayVersionChangeNote = versionSwitchTarget.value.note || '无版本说明'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (selectedSkill.value.usesSpreadsheetRule) {
|
|
|
|
|
|
versionSwitchTarget.value = null
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-15 06:57:07 +00:00
|
|
|
|
if (typeof versionSwitchTarget.value.markdownContent === 'string') {
|
|
|
|
|
|
selectedSkill.value.markdownContent = versionSwitchTarget.value.markdownContent
|
2026-05-11 06:32:38 +00:00
|
|
|
|
}
|
2026-05-15 06:57:07 +00:00
|
|
|
|
const runtimeRule = versionSwitchTarget.value.runtimeRule || buildDefaultRuntimeRule(selectedSkill.value)
|
|
|
|
|
|
selectedSkill.value.runtimeRuleText = stringifyRuntimeRule(runtimeRule)
|
|
|
|
|
|
selectedSkill.value.runtimeKind =
|
|
|
|
|
|
normalizeText(runtimeRule.kind) || selectedSkill.value.runtimeKind || 'policy_rule_draft'
|
|
|
|
|
|
selectedSkill.value.ruleTemplateKey =
|
|
|
|
|
|
normalizeText(runtimeRule.template_key) || selectedSkill.value.ruleTemplateKey
|
|
|
|
|
|
selectedSkill.value.ruleTemplateLabel = resolveRuleTemplateLabel(selectedSkill.value.ruleTemplateKey)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
versionSwitchTarget.value = null
|
|
|
|
|
|
}
|
2026-05-09 15:46:16 +00:00
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
async function saveRuleMarkdown() {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (
|
|
|
|
|
|
!selectedSkill.value ||
|
|
|
|
|
|
!selectedSkillIsRule.value ||
|
|
|
|
|
|
selectedSkill.value.usesSpreadsheetRule ||
|
|
|
|
|
|
!canEditMarkdown.value ||
|
|
|
|
|
|
detailBusy.value
|
|
|
|
|
|
) {
|
2026-05-11 06:32:38 +00:00
|
|
|
|
return
|
2026-05-09 15:46:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
if (!normalizeText(selectedSkill.value.markdownContent)) {
|
|
|
|
|
|
toast('规则 Markdown 内容不能为空。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 06:57:07 +00:00
|
|
|
|
const runtimeRule = parseRuntimeRuleText(selectedSkill.value.runtimeRuleText)
|
|
|
|
|
|
if (!runtimeRule) {
|
|
|
|
|
|
toast('运行时 JSON 必须是合法的对象。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
const nextVersion = incrementVersion(selectedSkill.value.currentVersion)
|
|
|
|
|
|
actionState.value = 'save-markdown'
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await createAgentAssetVersion(
|
|
|
|
|
|
selectedSkill.value.id,
|
|
|
|
|
|
{
|
|
|
|
|
|
version: nextVersion,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
content: buildMarkdownVersionContent(selectedSkill.value.markdownContent, runtimeRule),
|
2026-05-11 06:32:38 +00:00
|
|
|
|
content_type: 'markdown',
|
2026-05-15 06:57:07 +00:00
|
|
|
|
change_note: '通过任务规则中心保存 Markdown 规则内容,并同步运行时 JSON。',
|
2026-05-11 06:32:38 +00:00
|
|
|
|
created_by: resolveActor()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
2026-05-15 06:57:07 +00:00
|
|
|
|
await persistRuleRuntimeConfig(selectedSkill.value, runtimeRule)
|
2026-05-11 06:32:38 +00:00
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast(`规则 Markdown 已保存为 ${nextVersion}。`)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则 Markdown 保存失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-15 06:57:07 +00:00
|
|
|
|
async function saveRuleRuntimeJson() {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (
|
|
|
|
|
|
!selectedSkill.value ||
|
|
|
|
|
|
!selectedSkillIsRule.value ||
|
|
|
|
|
|
selectedSkill.value.usesSpreadsheetRule ||
|
|
|
|
|
|
!canEditMarkdown.value ||
|
|
|
|
|
|
detailBusy.value
|
|
|
|
|
|
) {
|
2026-05-15 06:57:07 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!normalizeText(selectedSkill.value.markdownContent)) {
|
|
|
|
|
|
toast('规则 Markdown 模板不能为空。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const runtimeRule = parseRuntimeRuleText(selectedSkill.value.runtimeRuleText)
|
|
|
|
|
|
if (!runtimeRule) {
|
|
|
|
|
|
toast('运行时 JSON 必须是合法的对象。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const nextVersion = incrementVersion(selectedSkill.value.currentVersion)
|
|
|
|
|
|
actionState.value = 'save-runtime-json'
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await createAgentAssetVersion(
|
|
|
|
|
|
selectedSkill.value.id,
|
|
|
|
|
|
{
|
|
|
|
|
|
version: nextVersion,
|
|
|
|
|
|
content: buildMarkdownVersionContent(selectedSkill.value.markdownContent, runtimeRule),
|
|
|
|
|
|
content_type: 'markdown',
|
|
|
|
|
|
change_note: '通过任务规则中心保存运行时 JSON 配置。',
|
|
|
|
|
|
created_by: resolveActor()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
|
|
|
|
|
await persistRuleRuntimeConfig(selectedSkill.value, runtimeRule)
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast(`规则 JSON 已保存为 ${nextVersion}。`)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则 JSON 保存失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
async function reviewSelectedRule(reviewStatus) {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (!selectedSkill.value || !selectedSkillIsRule.value || detailBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (reviewStatus === 'pending' && !canSubmitReview.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (reviewStatus !== 'pending' && !canReviewSelected.value) {
|
2026-05-11 06:32:38 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = `review-${reviewStatus}`
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await createAgentAssetReview(
|
|
|
|
|
|
selectedSkill.value.id,
|
|
|
|
|
|
{
|
2026-05-18 02:51:25 +00:00
|
|
|
|
version: selectedSkill.value.workingVersion,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
reviewer: resolveActor(),
|
|
|
|
|
|
review_status: reviewStatus,
|
|
|
|
|
|
review_note: buildReviewNote(reviewStatus)
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast(`当前规则版本已标记为${resolveReviewMeta(reviewStatus).label}。`)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则审核提交失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 09:42:23 +00:00
|
|
|
|
async function loadReviewSubmitReviewers() {
|
|
|
|
|
|
reviewSubmitReviewerLoading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const employees = await fetchEmployees()
|
|
|
|
|
|
reviewSubmitReviewerOptions.value = (Array.isArray(employees) ? employees : [])
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
item.status === '在职' &&
|
|
|
|
|
|
Array.isArray(item.roleCodes) &&
|
|
|
|
|
|
item.roleCodes.includes('manager')
|
|
|
|
|
|
)
|
|
|
|
|
|
.map((item) => ({
|
|
|
|
|
|
value: item.name,
|
|
|
|
|
|
label: `${item.name} · ${item.position || '高级管理员'}`
|
|
|
|
|
|
}))
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
reviewSubmitReviewerOptions.value = []
|
|
|
|
|
|
toast(error?.message || '审核人列表加载失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
reviewSubmitReviewerLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function openSubmitReviewDialog() {
|
2026-05-26 09:15:14 +08:00
|
|
|
|
if (
|
|
|
|
|
|
selectedSkillUsesJsonRisk.value &&
|
|
|
|
|
|
!canOpenRiskRuleReviewSubmit.value
|
|
|
|
|
|
) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
|
if (!selectedSkill.value || !canSubmitReview.value || detailBusy.value) {
|
2026-05-24 21:44:17 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 09:42:23 +00:00
|
|
|
|
reviewSubmitVersion.value = selectedSkill.value.workingVersion || selectedSkill.value.displayVersion || ''
|
|
|
|
|
|
reviewSubmitReviewer.value = selectedSkill.value.reviewer || ''
|
|
|
|
|
|
reviewSubmitOpen.value = true
|
|
|
|
|
|
await loadReviewSubmitReviewers()
|
|
|
|
|
|
if (
|
|
|
|
|
|
!reviewSubmitReviewerOptions.value.some(
|
|
|
|
|
|
(item) => item.value === reviewSubmitReviewer.value
|
|
|
|
|
|
)
|
|
|
|
|
|
) {
|
|
|
|
|
|
reviewSubmitReviewer.value = reviewSubmitReviewerOptions.value[0]?.value || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeSubmitReviewDialog() {
|
|
|
|
|
|
if (detailBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
reviewSubmitOpen.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitSelectedRuleForReview() {
|
|
|
|
|
|
if (!selectedSkill.value || !canSubmitReview.value || detailBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-24 21:44:17 +08:00
|
|
|
|
if (selectedSkillUsesJsonRisk.value && !riskRuleTestPassed.value) {
|
|
|
|
|
|
toast('当前规则版本尚未确认测试通过,不能提交审核。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-05-18 09:42:23 +00:00
|
|
|
|
const version = normalizeText(reviewSubmitVersion.value)
|
|
|
|
|
|
const reviewer = normalizeText(reviewSubmitReviewer.value)
|
|
|
|
|
|
if (!version) {
|
|
|
|
|
|
toast('请输入送审版本号。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!reviewer) {
|
|
|
|
|
|
toast('请选择审核人。')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = 'review-pending'
|
|
|
|
|
|
try {
|
|
|
|
|
|
await createAgentAssetReview(
|
|
|
|
|
|
selectedSkill.value.id,
|
|
|
|
|
|
{
|
|
|
|
|
|
version,
|
|
|
|
|
|
reviewer,
|
|
|
|
|
|
review_status: 'pending',
|
|
|
|
|
|
review_note: buildReviewNote('pending')
|
|
|
|
|
|
},
|
|
|
|
|
|
{ actor: resolveActor() }
|
|
|
|
|
|
)
|
|
|
|
|
|
reviewSubmitOpen.value = false
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast(`规则版本 ${version} 已提交给 ${reviewer} 审核。`)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则审核提交失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-24 21:44:17 +08:00
|
|
|
|
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 = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
async function activateSelectedRule() {
|
|
|
|
|
|
if (!selectedSkill.value || !selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = 'activate'
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await activateAgentAsset(selectedSkill.value.id, { actor: resolveActor() })
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast('规则已正式上线。')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '规则上线失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
2026-05-09 15:46:16 +00:00
|
|
|
|
}
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
2026-05-18 02:51:25 +00:00
|
|
|
|
async function restoreSelectedVersion(version) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!selectedSkill.value ||
|
|
|
|
|
|
!selectedSkillIsRule.value ||
|
|
|
|
|
|
!canManageSelected.value ||
|
|
|
|
|
|
detailBusy.value ||
|
|
|
|
|
|
!version
|
|
|
|
|
|
) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionState.value = `restore-${version}`
|
|
|
|
|
|
try {
|
|
|
|
|
|
await restoreAgentAssetVersion(selectedSkill.value.id, version, { actor: resolveActor() })
|
|
|
|
|
|
await refreshCurrentAssets()
|
|
|
|
|
|
await loadSelectedAssetDetail(selectedSkill.value.id)
|
|
|
|
|
|
toast(`已基于 ${version} 生成新的工作版本。`)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast(error?.message || '历史版本恢复失败,请稍后重试。')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
actionState.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadVersionTimeline(assetId = selectedSkill.value?.id, options = {}) {
|
|
|
|
|
|
if (!assetId) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
versionTimelineLoading.value = true
|
|
|
|
|
|
versionTimelineError.value = ''
|
|
|
|
|
|
try {
|
|
|
|
|
|
const payload = await fetchAgentAssetVersionTimeline(assetId)
|
|
|
|
|
|
versionTimelineItems.value = Array.isArray(payload) ? payload : []
|
|
|
|
|
|
} catch (error) {
|
2026-05-18 09:42:23 +00:00
|
|
|
|
versionTimelineError.value = error?.message || '操作记录加载失败,请稍后重试。'
|
2026-05-18 02:51:25 +00:00
|
|
|
|
if (!options.silent) {
|
|
|
|
|
|
toast(versionTimelineError.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
versionTimelineLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function openVersionTimeline() {
|
|
|
|
|
|
if (!selectedSkill.value?.id) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
versionTimelineOpen.value = true
|
|
|
|
|
|
await loadVersionTimeline(selectedSkill.value.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeVersionTimeline() {
|
|
|
|
|
|
versionTimelineOpen.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-11 06:32:38 +00:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
document.addEventListener('click', handleDocumentClick)
|
|
|
|
|
|
loadAssets({ force: true }).catch(() => {})
|
|
|
|
|
|
loadRuns().catch(() => {})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
2026-05-18 02:51:25 +00:00
|
|
|
|
destroySpreadsheetOnlyOfficeEditor()
|
2026-05-26 09:15:14 +08:00
|
|
|
|
riskRuleGenerationPollTimers.forEach((timer) => window.clearTimeout(timer))
|
|
|
|
|
|
riskRuleGenerationPollTimers.clear()
|
2026-05-11 06:32:38 +00:00
|
|
|
|
document.removeEventListener('click', handleDocumentClick)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
|
return {
|
|
|
|
|
|
tabs,
|
2026-05-09 15:46:16 +00:00
|
|
|
|
activeType,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
activeTabLabel,
|
|
|
|
|
|
selectedSkill,
|
|
|
|
|
|
versionSwitchTarget,
|
|
|
|
|
|
keyword,
|
2026-05-09 15:46:16 +00:00
|
|
|
|
createButtonLabel,
|
|
|
|
|
|
hintText,
|
2026-05-11 01:53:30 +00:00
|
|
|
|
searchPlaceholder,
|
2026-05-09 15:46:16 +00:00
|
|
|
|
tableColumns,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
showRuntimeColumn,
|
|
|
|
|
|
showVersionColumn,
|
2026-05-11 06:33:46 +00:00
|
|
|
|
showMetricColumn,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
showStatusColumn,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
showOnlineColumn,
|
|
|
|
|
|
showEnabledColumn,
|
2026-05-09 15:46:16 +00:00
|
|
|
|
visibleSkills,
|
2026-05-13 06:52:30 +00:00
|
|
|
|
auditEmptyState,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
loading,
|
|
|
|
|
|
errorMessage,
|
|
|
|
|
|
detailLoading,
|
|
|
|
|
|
detailError,
|
|
|
|
|
|
selectedDomain,
|
|
|
|
|
|
selectedOwner,
|
|
|
|
|
|
selectedStatus,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
selectedRiskScenario,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
selectedOnlineState,
|
|
|
|
|
|
selectedEnabledState,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
selectedDomainLabel,
|
|
|
|
|
|
selectedOwnerLabel,
|
|
|
|
|
|
selectedStatusLabel,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
selectedRiskScenarioLabel,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
selectedOnlineStateLabel,
|
|
|
|
|
|
selectedEnabledStateLabel,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
showRiskScenarioFilter,
|
|
|
|
|
|
showStatusFilter,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
showOnlineFilter,
|
|
|
|
|
|
showEnabledFilter,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
domainOptions,
|
|
|
|
|
|
ownerOptions,
|
|
|
|
|
|
statusOptions: STATUS_OPTIONS,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
riskScenarioOptions: RISK_SCENARIO_OPTIONS,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
onlineStateOptions: ONLINE_STATE_OPTIONS,
|
|
|
|
|
|
enabledStateOptions: ENABLED_STATE_OPTIONS,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
activeFilterPopover,
|
|
|
|
|
|
activeFilterTokens,
|
|
|
|
|
|
canManageSelected,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
canEditSelected,
|
2026-05-23 19:54:42 +08:00
|
|
|
|
canCreateRiskRule,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
canOpenRiskRuleTest,
|
|
|
|
|
|
canDeleteRiskRule,
|
2026-05-26 09:15:14 +08:00
|
|
|
|
canOpenRiskRuleReviewSubmit,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
canSubmitRiskRuleReview,
|
|
|
|
|
|
canReturnRiskRule,
|
|
|
|
|
|
canPublishRiskRule,
|
|
|
|
|
|
canToggleRiskRuleEnabled,
|
|
|
|
|
|
riskRuleTestPassed,
|
|
|
|
|
|
riskRuleInReview,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
canSubmitReview,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
hasReviewSubmitReviewers,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
canReviewSelected,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
canEditMarkdown,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
canUploadSpreadsheet,
|
|
|
|
|
|
canDownloadSpreadsheet,
|
|
|
|
|
|
canEditSpreadsheetInline,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
canActivateSelected,
|
|
|
|
|
|
activateBlockedReason,
|
|
|
|
|
|
selectedSkillIsRule,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
selectedSkillUsesSpreadsheet,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
selectedSkillUsesJsonRisk,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
selectedSpreadsheetFileName,
|
2026-05-19 16:19:03 +00:00
|
|
|
|
selectedSpreadsheetModeLabel,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
selectedVersionTimelineItems,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
selectedSpreadsheetChangeRecords,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
detailBusy,
|
|
|
|
|
|
actionState,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
reviewSubmitOpen,
|
|
|
|
|
|
reviewSubmitVersion,
|
|
|
|
|
|
reviewSubmitReviewer,
|
|
|
|
|
|
reviewSubmitReviewerLoading,
|
|
|
|
|
|
reviewSubmitReviewerOptions,
|
2026-05-23 19:54:42 +08:00
|
|
|
|
riskRuleCreateOpen,
|
|
|
|
|
|
riskRuleCreateForm,
|
|
|
|
|
|
riskRuleCreateBusy,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
riskRuleTestOpen,
|
|
|
|
|
|
riskRuleDeleteOpen,
|
|
|
|
|
|
riskRuleReturnOpen,
|
|
|
|
|
|
riskRulePublishOpen,
|
|
|
|
|
|
riskRuleReturnNote,
|
|
|
|
|
|
riskRuleExpenseCategoryOptions: RISK_RULE_EXPENSE_CATEGORY_OPTIONS,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
showReviewNote,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
spreadsheetUploadInput,
|
|
|
|
|
|
spreadsheetOnlyOfficeLoading,
|
|
|
|
|
|
spreadsheetOnlyOfficeError,
|
|
|
|
|
|
spreadsheetOnlyOfficeReady,
|
|
|
|
|
|
spreadsheetOnlyOfficeHostId,
|
|
|
|
|
|
versionTimelineOpen,
|
|
|
|
|
|
versionTimelineLoading,
|
|
|
|
|
|
versionTimelineError,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
spreadsheetChangeDetailOpen,
|
|
|
|
|
|
selectedSpreadsheetChangeRecord,
|
|
|
|
|
|
selectedSpreadsheetChangeSheetRows,
|
|
|
|
|
|
selectedSpreadsheetChangeCellRows,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
openAssetDetail,
|
|
|
|
|
|
closeDetail,
|
|
|
|
|
|
resetFilters,
|
2026-05-13 06:52:30 +00:00
|
|
|
|
handleAuditEmptyAction,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
toggleFilterPopover,
|
|
|
|
|
|
selectFilter,
|
|
|
|
|
|
closeFilterPopover,
|
2026-05-23 19:54:42 +08:00
|
|
|
|
openRiskRuleCreateDialog,
|
|
|
|
|
|
closeRiskRuleCreateDialog,
|
|
|
|
|
|
submitRiskRuleCreate,
|
2026-05-09 15:46:16 +00:00
|
|
|
|
openVersionSwitch,
|
|
|
|
|
|
cancelVersionSwitch,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
confirmVersionSwitch,
|
|
|
|
|
|
saveRuleMarkdown,
|
2026-05-15 06:57:07 +00:00
|
|
|
|
saveRuleRuntimeJson,
|
2026-05-19 20:23:58 +08:00
|
|
|
|
saveRiskRuleJson,
|
|
|
|
|
|
formatRiskRuleJson,
|
|
|
|
|
|
downloadRiskRuleJson,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
triggerSpreadsheetUpload,
|
|
|
|
|
|
downloadSpreadsheetFile,
|
|
|
|
|
|
handleSpreadsheetFileInput,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
reviewSelectedRule,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
openSubmitReviewDialog,
|
|
|
|
|
|
closeSubmitReviewDialog,
|
|
|
|
|
|
submitSelectedRuleForReview,
|
2026-05-24 21:44:17 +08:00
|
|
|
|
openRiskRuleTestDialog,
|
|
|
|
|
|
closeRiskRuleTestDialog,
|
|
|
|
|
|
handleRiskRuleReportSaved,
|
|
|
|
|
|
openDeleteRiskRuleDialog,
|
|
|
|
|
|
closeDeleteRiskRuleDialog,
|
|
|
|
|
|
deleteSelectedRiskRule,
|
|
|
|
|
|
openReturnRiskRuleDialog,
|
|
|
|
|
|
closeReturnRiskRuleDialog,
|
|
|
|
|
|
returnSelectedRiskRule,
|
|
|
|
|
|
openPublishRiskRuleDialog,
|
|
|
|
|
|
closePublishRiskRuleDialog,
|
|
|
|
|
|
publishSelectedRiskRule,
|
|
|
|
|
|
toggleSelectedRiskRuleEnabled,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
activateSelectedRule,
|
2026-05-18 02:51:25 +00:00
|
|
|
|
restoreSelectedVersion,
|
|
|
|
|
|
openVersionTimeline,
|
|
|
|
|
|
closeVersionTimeline,
|
2026-05-18 09:42:23 +00:00
|
|
|
|
openSpreadsheetChangeDetail,
|
|
|
|
|
|
closeSpreadsheetChangeDetail,
|
2026-05-11 06:32:38 +00:00
|
|
|
|
loadAssets
|
2026-05-06 11:00:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|