fix: 字段显示和订单详情按钮
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m47s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m47s
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="操作审计日志"
|
title="操作审计日志"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
size="60%"
|
size="70%"
|
||||||
:before-close="handleClose"
|
:before-close="handleClose"
|
||||||
>
|
>
|
||||||
<OperationLogsCard :asset-identifier="identifier" />
|
<OperationLogsCard :asset-identifier="identifier" />
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export interface AssetResolveResponse {
|
|||||||
first_commission_paid?: boolean // 一次性佣金是否已发放
|
first_commission_paid?: boolean // 一次性佣金是否已发放
|
||||||
|
|
||||||
// ===== 卡专属字段 (asset_type === 'card' 时) =====
|
// ===== 卡专属字段 (asset_type === 'card' 时) =====
|
||||||
iccid?: string // 卡 ICCID
|
iccid?: string // ICCID
|
||||||
bound_device_id?: number // 绑定设备 ID
|
bound_device_id?: number // 绑定设备 ID
|
||||||
bound_device_no?: string // 绑定设备虚拟号
|
bound_device_no?: string // 绑定设备虚拟号
|
||||||
bound_device_name?: string // 绑定设备名称
|
bound_device_name?: string // 绑定设备名称
|
||||||
|
|||||||
@@ -11,22 +11,12 @@
|
|||||||
style="width: 180px"
|
style="width: 180px"
|
||||||
@change="handleFilterChange"
|
@change="handleFilterChange"
|
||||||
>
|
>
|
||||||
<ElOption label="设备WiFi设置" value="device_set_wifi" />
|
<ElOption
|
||||||
<ElOption label="设备切卡" value="device_switch_card" />
|
v-for="option in operationTypeOptions"
|
||||||
<ElOption label="设备切卡模式" value="device_switch_mode" />
|
:key="option.value"
|
||||||
<ElOption label="设备停机" value="device_stop" />
|
:label="option.label"
|
||||||
<ElOption label="设备复机" value="device_start" />
|
:value="option.value"
|
||||||
<ElOption label="设备重启" value="device_reboot" />
|
/>
|
||||||
<ElOption label="设备重置" value="device_reset" />
|
|
||||||
<ElOption label="卡分配" value="card_allocate" />
|
|
||||||
<ElOption label="卡回收" value="card_recall" />
|
|
||||||
<ElOption label="卡停机" value="card_stop" />
|
|
||||||
<ElOption label="卡复机" value="card_start" />
|
|
||||||
<ElOption label="设备分配" value="device_allocate" />
|
|
||||||
<ElOption label="设备回收" value="device_recall" />
|
|
||||||
<ElOption label="实名策略" value="asset_realname_policy" />
|
|
||||||
<ElOption label="实名状态" value="asset_realname_status" />
|
|
||||||
<ElOption label="轮询开关" value="asset_polling" />
|
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="filterForm.result_status"
|
v-model="filterForm.result_status"
|
||||||
@@ -67,7 +57,7 @@
|
|||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="操作人类型" width="110" align="center">
|
<ElTableColumn label="操作人类型" width="110" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ getOperatorTypeText(row.operator_type) }}
|
{{ getOperatorTypeText(row) }}
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="执行结果" width="100" align="center">
|
<ElTableColumn label="执行结果" width="100" align="center">
|
||||||
@@ -77,23 +67,25 @@
|
|||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="变更内容" min-width="250">
|
<ElTableColumn label="变更内容" min-width="320">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-if="getChangeContent(row).length" class="change-content">
|
<div v-if="getDisplayContent(row).length" class="change-content">
|
||||||
<span
|
<span
|
||||||
v-for="(change, index) in getChangeContent(row)"
|
v-for="(item, index) in getDisplayContent(row)"
|
||||||
:key="index"
|
:key="`${item.type}-${item.key}-${index}`"
|
||||||
class="change-item"
|
class="change-item"
|
||||||
>
|
>
|
||||||
<span class="field-name">{{ change.fieldName }}</span>
|
<span class="field-name">{{ item.fieldName }}</span>
|
||||||
<span class="field-value">
|
<span class="field-value">
|
||||||
<span class="new-value">{{ formatFieldValue(change.key, change.afterValue) }}</span>
|
<template v-if="item.type === 'change'">
|
||||||
|
<span class="new-value">{{ formatFieldValue(item.key, item.afterValue) }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else :class="['message-value', item.level === 'error' ? 'is-error' : '']">
|
||||||
|
{{ item.message }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-else-if="row.fail_count > 0 && row.failed_items?.length" class="fail-reason">
|
|
||||||
失败 {{ row.fail_count }} 项
|
|
||||||
</span>
|
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
@@ -135,16 +127,154 @@
|
|||||||
assetType?: string
|
assetType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TagType = 'primary' | 'success' | 'warning' | 'info' | 'danger'
|
||||||
|
type ResultStatus = '' | 'success' | 'failed' | 'denied'
|
||||||
|
|
||||||
|
type OperationLogRow = AssetOperationLogItem & {
|
||||||
|
operator_type_code?: string
|
||||||
|
error_code?: string
|
||||||
|
error_msg?: string
|
||||||
|
request_id?: string
|
||||||
|
request_path?: string
|
||||||
|
request_method?: string
|
||||||
|
ip_address?: string
|
||||||
|
user_agent?: string
|
||||||
|
}
|
||||||
|
|
||||||
interface ChangeItem {
|
interface ChangeItem {
|
||||||
|
type: 'change'
|
||||||
key: string
|
key: string
|
||||||
fieldName: string
|
fieldName: string
|
||||||
|
beforeValue?: any
|
||||||
afterValue?: any
|
afterValue?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MessageItem {
|
||||||
|
type: 'message'
|
||||||
|
key: string
|
||||||
|
fieldName: string
|
||||||
|
message: string
|
||||||
|
level: 'info' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
type DisplayItem = ChangeItem | MessageItem
|
||||||
|
|
||||||
|
const operationTypeOptions = [
|
||||||
|
{ label: '设备WiFi设置', value: 'device_set_wifi' },
|
||||||
|
{ label: '设备切卡', value: 'device_switch_card' },
|
||||||
|
{ label: '设备切卡模式', value: 'device_switch_mode' },
|
||||||
|
{ label: '设备停机', value: 'device_stop' },
|
||||||
|
{ label: '设备复机', value: 'device_start' },
|
||||||
|
{ label: '设备重启', value: 'device_reboot' },
|
||||||
|
{ label: '设备重置', value: 'device_reset' },
|
||||||
|
{ label: '设备分配', value: 'device_allocate' },
|
||||||
|
{ label: '设备回收', value: 'device_recall' },
|
||||||
|
{ label: '卡分配', value: 'card_allocate' },
|
||||||
|
{ label: '卡回收', value: 'card_recall' },
|
||||||
|
{ label: '卡停机', value: 'card_stop' },
|
||||||
|
{ label: '卡复机', value: 'card_start' },
|
||||||
|
{ label: '手动停卡', value: 'card_manual_stop' },
|
||||||
|
{ label: '手动复机', value: 'card_manual_start' },
|
||||||
|
{ label: '自动停卡', value: 'card_auto_stop' },
|
||||||
|
{ label: '自动复机', value: 'card_auto_start' },
|
||||||
|
{ label: '卡轮询状态', value: 'card_polling_status' },
|
||||||
|
{ label: '资产轮询状态', value: 'asset_polling_status' },
|
||||||
|
{ label: '实名策略', value: 'asset_realname_policy' },
|
||||||
|
{ label: '实名状态', value: 'asset_realname_status' },
|
||||||
|
{ label: '轮询开关', value: 'asset_polling' },
|
||||||
|
{ label: 'IoT卡导入任务', value: 'iot_card_import_task_create' }
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const operationTypeTagMap: Record<string, TagType> = {
|
||||||
|
device_set_wifi: 'primary',
|
||||||
|
device_switch_card: 'success',
|
||||||
|
device_switch_mode: 'success',
|
||||||
|
device_stop: 'danger',
|
||||||
|
device_start: 'success',
|
||||||
|
device_reboot: 'warning',
|
||||||
|
device_reset: 'warning',
|
||||||
|
device_allocate: 'primary',
|
||||||
|
device_recall: 'warning',
|
||||||
|
card_allocate: 'primary',
|
||||||
|
card_recall: 'warning',
|
||||||
|
card_stop: 'danger',
|
||||||
|
card_start: 'success',
|
||||||
|
card_manual_stop: 'danger',
|
||||||
|
card_manual_start: 'success',
|
||||||
|
card_auto_stop: 'danger',
|
||||||
|
card_auto_start: 'success',
|
||||||
|
card_polling_status: 'info',
|
||||||
|
asset_realname_policy: 'info',
|
||||||
|
asset_realname_status: 'info',
|
||||||
|
asset_polling: 'info',
|
||||||
|
asset_polling_status: 'info',
|
||||||
|
iot_card_import_task_create: 'primary'
|
||||||
|
}
|
||||||
|
|
||||||
|
const operatorTypeMap: Record<string, string> = {
|
||||||
|
system: '系统',
|
||||||
|
admin_user: '平台用户',
|
||||||
|
agent_user: '代理账号',
|
||||||
|
enterprise_user: '企业用户',
|
||||||
|
api_user: 'API用户'
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldNameMap: Record<string, string> = {
|
||||||
|
asset_type: '资产类型',
|
||||||
|
batch_no: '批次号',
|
||||||
|
card_category: '卡类型',
|
||||||
|
enable_polling: '轮询开关',
|
||||||
|
file_key: '文件存储键',
|
||||||
|
first_realname_at: '首次实名时间',
|
||||||
|
iccid: 'ICCID',
|
||||||
|
network_status: '网络状态',
|
||||||
|
real_name_status: '实名状态',
|
||||||
|
realname_policy: '实名认证策略',
|
||||||
|
source_service: '来源服务',
|
||||||
|
status: '资产状态',
|
||||||
|
stop_reason: '停机原因',
|
||||||
|
switch_mode: '切卡模式'
|
||||||
|
}
|
||||||
|
|
||||||
|
const realnamePolicyMap: Record<string, string> = {
|
||||||
|
none: '无需实名',
|
||||||
|
before_order: '先实名后充值/购买',
|
||||||
|
after_order: '先充值/购买后实名'
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopReasonMap: Record<string, string> = {
|
||||||
|
'': '无',
|
||||||
|
no_package: '无可用套餐',
|
||||||
|
carrier_stopped: '运营商停机',
|
||||||
|
not_realname: '未实名'
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetTypeMap: Record<string, string> = {
|
||||||
|
iot_card: 'IoT卡',
|
||||||
|
device: '设备'
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardCategoryMap: Record<string, string> = {
|
||||||
|
normal: '普通卡',
|
||||||
|
industry: '行业卡'
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetStatusMap: Record<string, string> = {
|
||||||
|
'1': '在库',
|
||||||
|
'2': '已分销',
|
||||||
|
'3': '已激活',
|
||||||
|
'4': '已停用'
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStatusMap: Record<string, string> = {
|
||||||
|
allocated: '已分配',
|
||||||
|
recalled: '已回收'
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const logList = ref<AssetOperationLogItem[]>([])
|
const logList = ref<OperationLogRow[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const pagination = reactive({
|
const pagination = reactive({
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -153,7 +283,7 @@
|
|||||||
|
|
||||||
const filterForm = reactive({
|
const filterForm = reactive({
|
||||||
operation_type: '',
|
operation_type: '',
|
||||||
result_status: '' as '' | 'success' | 'failed' | 'denied'
|
result_status: '' as ResultStatus
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadLogs = async () => {
|
const loadLogs = async () => {
|
||||||
@@ -204,30 +334,12 @@
|
|||||||
loadLogs()
|
loadLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOperationTypeTag = (type: string) => {
|
const getOperationTypeTag = (type: string): TagType => {
|
||||||
const tagMap: Record<string, string> = {
|
return operationTypeTagMap[type] || 'info'
|
||||||
device_set_wifi: 'primary',
|
|
||||||
device_switch_card: 'success',
|
|
||||||
device_switch_mode: 'success',
|
|
||||||
device_stop: 'danger',
|
|
||||||
device_start: 'success',
|
|
||||||
device_reboot: 'warning',
|
|
||||||
device_reset: 'warning',
|
|
||||||
device_allocate: 'primary',
|
|
||||||
device_recall: 'warning',
|
|
||||||
card_allocate: 'primary',
|
|
||||||
card_recall: 'warning',
|
|
||||||
card_stop: 'danger',
|
|
||||||
card_start: 'success',
|
|
||||||
asset_realname_policy: 'info',
|
|
||||||
asset_realname_status: 'info',
|
|
||||||
asset_polling: 'info'
|
|
||||||
}
|
|
||||||
return tagMap[type] || 'info'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getResultStatusTag = (status: string) => {
|
const getResultStatusTag = (status: string): TagType => {
|
||||||
const tagMap: Record<string, string> = {
|
const tagMap: Record<string, TagType> = {
|
||||||
success: 'success',
|
success: 'success',
|
||||||
failed: 'danger',
|
failed: 'danger',
|
||||||
denied: 'warning'
|
denied: 'warning'
|
||||||
@@ -244,17 +356,18 @@
|
|||||||
return textMap[status] || status
|
return textMap[status] || status
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOperatorTypeText = (type: string) => {
|
const getOperatorTypeText = (row: OperationLogRow) => {
|
||||||
const textMap: Record<string, string> = {
|
if (row.operator_type_code && operatorTypeMap[row.operator_type_code]) {
|
||||||
admin_user: '后台用户',
|
return operatorTypeMap[row.operator_type_code]
|
||||||
enterprise_user: '企业用户',
|
|
||||||
api_user: 'API用户'
|
|
||||||
}
|
}
|
||||||
return textMap[type] || type
|
if (row.operator_type && operatorTypeMap[row.operator_type]) {
|
||||||
|
return operatorTypeMap[row.operator_type]
|
||||||
|
}
|
||||||
|
return row.operator_type || row.operator_type_code || '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatValue = (value: any): string => {
|
const formatValue = (value: any): string => {
|
||||||
if (value === null || value === undefined) return '未知'
|
if (value === null || value === undefined) return '空'
|
||||||
if (typeof value === 'boolean') return value ? '是' : '否'
|
if (typeof value === 'boolean') return value ? '是' : '否'
|
||||||
if (typeof value === 'number') return String(value)
|
if (typeof value === 'number') return String(value)
|
||||||
if (typeof value === 'string') return value || '空'
|
if (typeof value === 'string') return value || '空'
|
||||||
@@ -263,29 +376,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatFieldValue = (key: string, value: any): string => {
|
const formatFieldValue = (key: string, value: any): string => {
|
||||||
|
if (typeof value === 'string' && (key.endsWith('_at') || key.endsWith('_time'))) {
|
||||||
|
return formatDateTime(value)
|
||||||
|
}
|
||||||
if (key === 'enable_polling') return value ? '开启' : '关闭'
|
if (key === 'enable_polling') return value ? '开启' : '关闭'
|
||||||
if (key === 'realname_policy') {
|
if (key === 'realname_policy') return realnamePolicyMap[value] || formatValue(value)
|
||||||
const map: Record<string, string> = {
|
|
||||||
none: '无需实名',
|
|
||||||
before_order: '先实名后充值',
|
|
||||||
after_order: '先充值后实名'
|
|
||||||
}
|
|
||||||
return map[value] || formatValue(value)
|
|
||||||
}
|
|
||||||
if (key === 'real_name_status') return value === 1 ? '已实名' : '未实名'
|
if (key === 'real_name_status') return value === 1 ? '已实名' : '未实名'
|
||||||
if (key === 'switch_mode') return value === 0 ? '自动' : '手动'
|
if (key === 'switch_mode') {
|
||||||
if (key === 'new_status') {
|
return String(value) === '0' ? '自动' : String(value) === '1' ? '手动' : formatValue(value)
|
||||||
const map: Record<string, string> = {
|
|
||||||
allocated: '已分配',
|
|
||||||
recalled: '已回收'
|
|
||||||
}
|
|
||||||
return map[value] || formatValue(value)
|
|
||||||
}
|
}
|
||||||
|
if (key === 'new_status') return newStatusMap[value] || formatValue(value)
|
||||||
|
if (key === 'network_status') {
|
||||||
|
return Number(value) === 1 ? '正常' : Number(value) === 0 ? '停机' : formatValue(value)
|
||||||
|
}
|
||||||
|
if (key === 'stop_reason') return stopReasonMap[String(value ?? '')] ?? formatValue(value)
|
||||||
|
if (key === 'asset_type') return assetTypeMap[String(value)] || formatValue(value)
|
||||||
|
if (key === 'card_category') return cardCategoryMap[String(value)] || formatValue(value)
|
||||||
|
if (key === 'status') return assetStatusMap[String(value)] || formatValue(value)
|
||||||
|
if (key === 'source_service') return value === 'asset_polling' ? '资产轮询' : formatValue(value)
|
||||||
return formatValue(value)
|
return formatValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizeFieldName = (fieldName: string): string => {
|
const sanitizeFieldName = (fieldName: string): string => {
|
||||||
return fieldName.trim().replace(/([^)]*)/g, '').trim()
|
return fieldName
|
||||||
|
.trim()
|
||||||
|
.replace(/([^)]*)/g, '')
|
||||||
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldHideIdField = (key: string, fieldName: string): boolean => {
|
const shouldHideIdField = (key: string, fieldName: string): boolean => {
|
||||||
@@ -304,36 +420,127 @@
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (
|
||||||
normalizedKeyLower === 'id' ||
|
normalizedKeyLower === 'id' ||
|
||||||
|
normalizedFieldName === '绑定记录ID' ||
|
||||||
|
normalizedFieldName === 'ID'
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
normalizedKeyLower.endsWith('_id') ||
|
normalizedKeyLower.endsWith('_id') ||
|
||||||
/^[a-z][a-z0-9]*Id$/.test(normalizedKey) ||
|
/^[a-z][a-z0-9]*Id$/.test(normalizedKey) ||
|
||||||
normalizedFieldName === '绑定记录ID' ||
|
|
||||||
normalizedFieldName === 'ID' ||
|
|
||||||
normalizedFieldName.endsWith('ID')
|
normalizedFieldName.endsWith('ID')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChangeContent = (row: AssetOperationLogItem): ChangeItem[] => {
|
const extractOperationContent = (
|
||||||
const after = row.operation_content_after
|
row: OperationLogRow,
|
||||||
const desc = row.operation_fields_desc || {}
|
type: 'before' | 'after'
|
||||||
|
): Record<string, any> => {
|
||||||
|
const directContent =
|
||||||
|
type === 'before' ? row.operation_content_before : row.operation_content_after
|
||||||
|
if (directContent && Object.keys(directContent).length > 0) {
|
||||||
|
return directContent
|
||||||
|
}
|
||||||
|
|
||||||
if (!after || Object.keys(after).length === 0) return []
|
const fallbackContainer = type === 'before' ? row.before_data : row.after_data
|
||||||
|
const nestedContent = fallbackContainer?.operation_content
|
||||||
|
if (nestedContent && Object.keys(nestedContent).length > 0) {
|
||||||
|
return nestedContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackContainer || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSameValue = (beforeValue: any, afterValue: any) => {
|
||||||
|
if (beforeValue === afterValue) return true
|
||||||
|
return JSON.stringify(beforeValue) === JSON.stringify(afterValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getChangeContent = (row: OperationLogRow): ChangeItem[] => {
|
||||||
|
const before = extractOperationContent(row, 'before')
|
||||||
|
const after = extractOperationContent(row, 'after')
|
||||||
|
const desc = row.operation_fields_desc || {}
|
||||||
|
const keys = new Set([...Object.keys(before), ...Object.keys(after)])
|
||||||
|
|
||||||
const changes: ChangeItem[] = []
|
const changes: ChangeItem[] = []
|
||||||
for (const key of Object.keys(after)) {
|
for (const key of keys) {
|
||||||
const fieldName = sanitizeFieldName(desc[key] || key)
|
const fieldName = sanitizeFieldName(desc[key] || fieldNameMap[key] || key)
|
||||||
if (shouldHideIdField(key, fieldName)) continue
|
if (shouldHideIdField(key, fieldName)) continue
|
||||||
|
|
||||||
|
const beforeValue = before[key]
|
||||||
|
const afterValue = after[key]
|
||||||
|
if (isSameValue(beforeValue, afterValue)) continue
|
||||||
|
|
||||||
changes.push({
|
changes.push({
|
||||||
|
type: 'change',
|
||||||
key,
|
key,
|
||||||
fieldName,
|
fieldName,
|
||||||
afterValue: after[key]
|
beforeValue,
|
||||||
|
afterValue
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMessageItems = (row: OperationLogRow): MessageItem[] => {
|
||||||
|
const items: MessageItem[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
|
||||||
|
const pushMessage = (
|
||||||
|
key: string,
|
||||||
|
fieldName: string,
|
||||||
|
message?: string,
|
||||||
|
level: 'info' | 'error' = 'info'
|
||||||
|
) => {
|
||||||
|
const normalizedMessage = message?.trim()
|
||||||
|
if (!normalizedMessage) return
|
||||||
|
|
||||||
|
const duplicateKey = `${fieldName}:${normalizedMessage}`
|
||||||
|
if (seen.has(duplicateKey)) return
|
||||||
|
seen.add(duplicateKey)
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
type: 'message',
|
||||||
|
key,
|
||||||
|
fieldName,
|
||||||
|
message: normalizedMessage,
|
||||||
|
level
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pushMessage('error_msg', '失败原因', row.error_msg, 'error')
|
||||||
|
pushMessage('reason', '原因说明', row.reason, row.result_status === 'failed' ? 'error' : 'info')
|
||||||
|
|
||||||
|
if (row.fail_count > 0) {
|
||||||
|
pushMessage('fail_count', '批量结果', `失败 ${row.fail_count} 项`, 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
row.failed_items?.slice(0, 3).forEach((item, index) => {
|
||||||
|
const detail = item?.iccid
|
||||||
|
? `${item.iccid}:${item.reason || '未知原因'}`
|
||||||
|
: item?.reason || ''
|
||||||
|
pushMessage(`failed_items_${index}`, '失败明细', detail, 'error')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!items.length && row.result_status === 'failed') {
|
||||||
|
pushMessage('result_status', '执行结果', '执行失败', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.length && row.result_status === 'denied') {
|
||||||
|
pushMessage('result_status', '执行结果', '操作被拒绝', 'info')
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDisplayContent = (row: OperationLogRow): DisplayItem[] => {
|
||||||
|
return [...getChangeContent(row), ...getMessageItems(row)]
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.assetIdentifier,
|
() => props.assetIdentifier,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
@@ -382,27 +589,36 @@
|
|||||||
|
|
||||||
.change-item {
|
.change-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
.field-name {
|
.field-name {
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
min-width: 70px;
|
min-width: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-value {
|
.field-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
.new-value {
|
.new-value {
|
||||||
color: var(--el-color-success);
|
color: var(--el-color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-value {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
|
||||||
|
&.is-error {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fail-reason {
|
|
||||||
color: var(--el-color-danger);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { StorageService } from '@/api/modules/storage'
|
import { StorageService } from '@/api/modules/storage'
|
||||||
import type { DeviceImportTask, DeviceImportTaskStatus } from '@/types/api/device'
|
import type { DeviceImportTask, DeviceImportTaskStatus, RealnamePolicy } from '@/types/api/device'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
import { generatePackageCode } from '@/utils/codeGenerator'
|
import { generatePackageCode } from '@/utils/codeGenerator'
|
||||||
|
|
||||||
@@ -408,7 +408,9 @@
|
|||||||
|
|
||||||
const res = await DeviceService.getImportTasks(params)
|
const res = await DeviceService.getImportTasks(params)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
taskList.value = res.data.items || []
|
const taskItems = (res.data as typeof res.data & { items?: DeviceImportTask[] | null })
|
||||||
|
.items
|
||||||
|
taskList.value = taskItems || []
|
||||||
pagination.total = res.data.total || 0
|
pagination.total = res.data.total || 0
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -503,7 +505,6 @@
|
|||||||
importForm.realname_policy = ''
|
importForm.realname_policy = ''
|
||||||
importDialogVisible.value = false
|
importDialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交上传
|
// 提交上传
|
||||||
const submitUpload = async () => {
|
const submitUpload = async () => {
|
||||||
if (!fileList.value.length) {
|
if (!fileList.value.length) {
|
||||||
@@ -540,7 +541,7 @@
|
|||||||
const importRes = await DeviceService.importDevices({
|
const importRes = await DeviceService.importDevices({
|
||||||
file_key,
|
file_key,
|
||||||
batch_no: importForm.batch_no || undefined,
|
batch_no: importForm.batch_no || undefined,
|
||||||
realname_policy: importForm.realname_policy || undefined
|
realname_policy: (importForm.realname_policy || undefined) as RealnamePolicy | undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
if (importRes.code !== 0) {
|
if (importRes.code !== 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user