+
- {{ change.fieldName }}
+ {{ item.fieldName }}
- {{ formatFieldValue(change.key, change.afterValue) }}
+
+ {{ formatFieldValue(item.key, item.afterValue) }}
+
+
+ {{ item.message }}
+
-
- 失败 {{ row.fail_count }} 项
-
-
@@ -135,16 +127,154 @@
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 {
+ type: 'change'
key: string
fieldName: string
+ beforeValue?: 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
= {
+ 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 = {
+ system: '系统',
+ admin_user: '平台用户',
+ agent_user: '代理账号',
+ enterprise_user: '企业用户',
+ api_user: 'API用户'
+ }
+
+ const fieldNameMap: Record = {
+ 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 = {
+ none: '无需实名',
+ before_order: '先实名后充值/购买',
+ after_order: '先充值/购买后实名'
+ }
+
+ const stopReasonMap: Record = {
+ '': '无',
+ no_package: '无可用套餐',
+ carrier_stopped: '运营商停机',
+ not_realname: '未实名'
+ }
+
+ const assetTypeMap: Record = {
+ iot_card: 'IoT卡',
+ device: '设备'
+ }
+
+ const cardCategoryMap: Record = {
+ normal: '普通卡',
+ industry: '行业卡'
+ }
+
+ const assetStatusMap: Record = {
+ '1': '在库',
+ '2': '已分销',
+ '3': '已激活',
+ '4': '已停用'
+ }
+
+ const newStatusMap: Record = {
+ allocated: '已分配',
+ recalled: '已回收'
+ }
+
const props = defineProps()
const loading = ref(false)
- const logList = ref([])
+ const logList = ref([])
const total = ref(0)
const pagination = reactive({
page: 1,
@@ -153,7 +283,7 @@
const filterForm = reactive({
operation_type: '',
- result_status: '' as '' | 'success' | 'failed' | 'denied'
+ result_status: '' as ResultStatus
})
const loadLogs = async () => {
@@ -204,30 +334,12 @@
loadLogs()
}
- const getOperationTypeTag = (type: string) => {
- const tagMap: Record = {
- 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 getOperationTypeTag = (type: string): TagType => {
+ return operationTypeTagMap[type] || 'info'
}
- const getResultStatusTag = (status: string) => {
- const tagMap: Record = {
+ const getResultStatusTag = (status: string): TagType => {
+ const tagMap: Record = {
success: 'success',
failed: 'danger',
denied: 'warning'
@@ -244,17 +356,18 @@
return textMap[status] || status
}
- const getOperatorTypeText = (type: string) => {
- const textMap: Record = {
- admin_user: '后台用户',
- enterprise_user: '企业用户',
- api_user: 'API用户'
+ const getOperatorTypeText = (row: OperationLogRow) => {
+ if (row.operator_type_code && operatorTypeMap[row.operator_type_code]) {
+ return operatorTypeMap[row.operator_type_code]
}
- 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 => {
- if (value === null || value === undefined) return '未知'
+ if (value === null || value === undefined) return '空'
if (typeof value === 'boolean') return value ? '是' : '否'
if (typeof value === 'number') return String(value)
if (typeof value === 'string') return value || '空'
@@ -263,29 +376,32 @@
}
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 === 'realname_policy') {
- const map: Record = {
- none: '无需实名',
- before_order: '先实名后充值',
- after_order: '先充值后实名'
- }
- return map[value] || formatValue(value)
- }
+ if (key === 'realname_policy') return realnamePolicyMap[value] || formatValue(value)
if (key === 'real_name_status') return value === 1 ? '已实名' : '未实名'
- if (key === 'switch_mode') return value === 0 ? '自动' : '手动'
- if (key === 'new_status') {
- const map: Record = {
- allocated: '已分配',
- recalled: '已回收'
- }
- return map[value] || formatValue(value)
+ if (key === 'switch_mode') {
+ return String(value) === '0' ? '自动' : String(value) === '1' ? '手动' : 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)
}
const sanitizeFieldName = (fieldName: string): string => {
- return fieldName.trim().replace(/([^)]*)/g, '').trim()
+ return fieldName
+ .trim()
+ .replace(/([^)]*)/g, '')
+ .trim()
}
const shouldHideIdField = (key: string, fieldName: string): boolean => {
@@ -304,36 +420,127 @@
return false
}
- return (
+ if (
normalizedKeyLower === 'id' ||
+ normalizedFieldName === '绑定记录ID' ||
+ normalizedFieldName === 'ID'
+ ) {
+ return true
+ }
+
+ return (
normalizedKeyLower.endsWith('_id') ||
/^[a-z][a-z0-9]*Id$/.test(normalizedKey) ||
- normalizedFieldName === '绑定记录ID' ||
- normalizedFieldName === 'ID' ||
normalizedFieldName.endsWith('ID')
)
}
- const getChangeContent = (row: AssetOperationLogItem): ChangeItem[] => {
- const after = row.operation_content_after
- const desc = row.operation_fields_desc || {}
+ const extractOperationContent = (
+ row: OperationLogRow,
+ type: 'before' | 'after'
+ ): Record => {
+ 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[] = []
- for (const key of Object.keys(after)) {
- const fieldName = sanitizeFieldName(desc[key] || key)
+ for (const key of keys) {
+ const fieldName = sanitizeFieldName(desc[key] || fieldNameMap[key] || key)
if (shouldHideIdField(key, fieldName)) continue
+ const beforeValue = before[key]
+ const afterValue = after[key]
+ if (isSameValue(beforeValue, afterValue)) continue
+
changes.push({
+ type: 'change',
key,
fieldName,
- afterValue: after[key]
+ beforeValue,
+ afterValue
})
}
+
return changes
}
+ const getMessageItems = (row: OperationLogRow): MessageItem[] => {
+ const items: MessageItem[] = []
+ const seen = new Set()
+
+ 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(
() => props.assetIdentifier,
(newVal) => {
@@ -382,27 +589,36 @@
.change-item {
display: flex;
- align-items: center;
+ align-items: flex-start;
gap: 4px;
font-size: 12px;
.field-name {
color: var(--el-text-color-secondary);
- min-width: 70px;
+ min-width: 72px;
}
.field-value {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 4px;
+ word-break: break-all;
+
.new-value {
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;
- }
}
}
}
diff --git a/src/views/asset-management/task-management/device-task/index.vue b/src/views/asset-management/task-management/device-task/index.vue
index 86867ef..794c86d 100644
--- a/src/views/asset-management/task-management/device-task/index.vue
+++ b/src/views/asset-management/task-management/device-task/index.vue
@@ -151,7 +151,7 @@
import { useAuth } from '@/composables/useAuth'
import { formatDateTime } from '@/utils/business/format'
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 { generatePackageCode } from '@/utils/codeGenerator'
@@ -408,7 +408,9 @@
const res = await DeviceService.getImportTasks(params)
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
}
} catch (error) {
@@ -503,7 +505,6 @@
importForm.realname_policy = ''
importDialogVisible.value = false
}
-
// 提交上传
const submitUpload = async () => {
if (!fileList.value.length) {
@@ -540,7 +541,7 @@
const importRes = await DeviceService.importDevices({
file_key,
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) {