fix: 代理系列授权和操作日志去掉
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m58s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m58s
This commit is contained in:
@@ -1,692 +0,0 @@
|
||||
<template>
|
||||
<ElCard shadow="never" class="info-card operation-logs-card" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="header-title">操作审计日志</span>
|
||||
<div class="filter-section">
|
||||
<ElSelect
|
||||
v-model="filterForm.result_status"
|
||||
placeholder="执行结果"
|
||||
clearable
|
||||
class="result-status-select"
|
||||
@change="handleFilterChange"
|
||||
>
|
||||
<ElOption label="成功" value="success" />
|
||||
<ElOption label="失败" value="failed" />
|
||||
<ElOption label="拒绝" value="denied" />
|
||||
</ElSelect>
|
||||
<ElButton type="primary" @click="handleQuery">查询</ElButton>
|
||||
<ElButton @click="handleReset">重置</ElButton>
|
||||
<ElTag type="info" size="small">共 {{ total }} 条</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="logs-table-wrapper">
|
||||
<ElTable :data="logList" class="logs-table" border max-height="400">
|
||||
<ElTableColumn label="操作时间" width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作类型" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="getOperationTypeTag(row.operation_type)" size="small">
|
||||
{{ row.operation_desc }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作人" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.operator_name || '-' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作人类型" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ getOperatorTypeText(row) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="执行结果" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="getResultStatusTag(row.result_status)" size="small">
|
||||
{{ getResultStatusText(row.result_status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="变更内容" min-width="320">
|
||||
<template #default="{ row }">
|
||||
<div v-if="getDisplayContent(row).length" class="change-content">
|
||||
<span
|
||||
v-for="(item, index) in getDisplayContent(row)"
|
||||
:key="`${item.type}-${item.key}-${index}`"
|
||||
class="change-item"
|
||||
>
|
||||
<span class="field-name">{{ item.fieldName }}</span>
|
||||
<span class="field-value">
|
||||
<template v-if="item.type === 'change'">
|
||||
<span class="new-value">{{ formatFieldValue(item.key, item.afterValue) }}</span>
|
||||
<ElButton
|
||||
v-if="canDownloadLogFile && isDownloadableFileKey(item.key, item.afterValue)"
|
||||
link
|
||||
type="primary"
|
||||
@click.stop="handleDownloadLogFile(item.afterValue)"
|
||||
>
|
||||
下载文件
|
||||
</ElButton>
|
||||
</template>
|
||||
<span v-else :class="['message-value', item.level === 'error' ? 'is-error' : '']">
|
||||
{{ item.message }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
<ElPagination
|
||||
class="logs-pagination"
|
||||
v-if="total > 0"
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.page_size"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElPagination,
|
||||
ElSelect,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTag
|
||||
} from 'element-plus'
|
||||
import { AssetService, StorageService } from '@/api/modules'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import type { AssetOperationLogItem } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
interface Props {
|
||||
assetIdentifier?: string
|
||||
assetType?: string
|
||||
downloadPermission?: 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 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',
|
||||
device_bind_card: 'primary',
|
||||
device_unbind_card: '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: '卡类型',
|
||||
device_imei: '设备IMEI',
|
||||
device_sn: '设备SN',
|
||||
device_virtual_no: '设备虚拟号',
|
||||
device_virtual_nos: '设备虚拟号',
|
||||
enable_polling: '轮询开关',
|
||||
file_key: '文件存储键',
|
||||
first_realname_at: '首次实名时间',
|
||||
iccid: 'ICCID',
|
||||
iccids: 'ICCID',
|
||||
network_status: '网络状态',
|
||||
real_name_status: '实名状态',
|
||||
realname_policy: '实名认证策略',
|
||||
shop_name: '店铺',
|
||||
source_service: '来源服务',
|
||||
source_shop_name: '来源店铺',
|
||||
status: '资产状态',
|
||||
stop_reason: '停机原因',
|
||||
switch_mode: '切卡模式',
|
||||
target_shop_name: '目标店铺',
|
||||
to_shop_name: '目标店铺'
|
||||
}
|
||||
|
||||
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 { hasAuth } = useAuth()
|
||||
|
||||
const loading = ref(false)
|
||||
const logList = ref<OperationLogRow[]>([])
|
||||
const total = ref(0)
|
||||
const canDownloadLogFile = computed(() => {
|
||||
return props.downloadPermission ? hasAuth(props.downloadPermission) : false
|
||||
})
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
page_size: 20
|
||||
})
|
||||
|
||||
const filterForm = reactive({
|
||||
result_status: '' as ResultStatus
|
||||
})
|
||||
|
||||
const loadLogs = async () => {
|
||||
if (!props.assetIdentifier) return
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await AssetService.getOperationLogs(props.assetIdentifier, {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
result_status: filterForm.result_status || undefined
|
||||
})
|
||||
if (res.code === 0 && res.data) {
|
||||
logList.value = res.data.items || []
|
||||
total.value = res.data.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载操作日志失败:', error)
|
||||
ElMessage.error('加载操作日志失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleQuery = () => {
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
filterForm.result_status = ''
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const handleFilterChange = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handlePageChange = () => {
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const handleSizeChange = () => {
|
||||
pagination.page = 1
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
const getOperationTypeTag = (type: string): TagType => {
|
||||
return operationTypeTagMap[type] || 'info'
|
||||
}
|
||||
|
||||
const getResultStatusTag = (status: string): TagType => {
|
||||
const tagMap: Record<string, TagType> = {
|
||||
success: 'success',
|
||||
failed: 'danger',
|
||||
denied: 'warning'
|
||||
}
|
||||
return tagMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getResultStatusText = (status: string) => {
|
||||
const textMap: Record<string, string> = {
|
||||
success: '成功',
|
||||
failed: '失败',
|
||||
denied: '拒绝'
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
const isDownloadableFileKey = (key: string, value: unknown) => {
|
||||
return key === 'file_key' && typeof value === 'string' && value.trim() !== ''
|
||||
}
|
||||
|
||||
const handleDownloadLogFile = async (fileKey: string) => {
|
||||
try {
|
||||
await StorageService.downloadFileByKey(fileKey)
|
||||
ElMessage.success('文件下载已开始')
|
||||
} catch (error) {
|
||||
console.error('下载日志文件失败:', error)
|
||||
ElMessage.error('下载文件失败')
|
||||
}
|
||||
}
|
||||
|
||||
const getOperatorTypeText = (row: OperationLogRow) => {
|
||||
if (row.operator_type_code && operatorTypeMap[row.operator_type_code]) {
|
||||
return operatorTypeMap[row.operator_type_code]
|
||||
}
|
||||
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 (typeof value === 'boolean') return value ? '是' : '否'
|
||||
if (typeof value === 'number') return String(value)
|
||||
if (typeof value === 'string') return value || '空'
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return '空'
|
||||
if (value.every((item) => ['string', 'number', 'boolean'].includes(typeof item))) {
|
||||
return value.map((item) => formatValue(item)).join('、')
|
||||
}
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
if (typeof value === 'object') return JSON.stringify(value)
|
||||
return String(value)
|
||||
}
|
||||
|
||||
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') return realnamePolicyMap[value] || formatValue(value)
|
||||
if (key === 'real_name_status') return value === 1 ? '已实名' : '未实名'
|
||||
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()
|
||||
}
|
||||
|
||||
const shouldHideIdField = (key: string, fieldName: string): boolean => {
|
||||
const normalizedKey = key.trim()
|
||||
const normalizedKeyLower = normalizedKey.toLowerCase()
|
||||
const normalizedFieldName = sanitizeFieldName(fieldName)
|
||||
const normalizedFieldNameUpper = normalizedFieldName.toUpperCase()
|
||||
|
||||
const visibleIdentifierKeys = new Set(['iccid', 'msisdn', 'imei', 'imsi', 'sn'])
|
||||
const visibleIdentifierNames = ['ICCID', 'MSISDN', 'IMEI', 'IMSI', 'SN']
|
||||
|
||||
if (
|
||||
visibleIdentifierKeys.has(normalizedKeyLower) ||
|
||||
visibleIdentifierNames.some((name) => normalizedFieldNameUpper.endsWith(name))
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
normalizedKeyLower === 'id' ||
|
||||
normalizedFieldName === '绑定记录ID' ||
|
||||
normalizedFieldName === 'ID'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
normalizedKeyLower.endsWith('_id') ||
|
||||
/^[a-z][a-z0-9]*Id$/.test(normalizedKey) ||
|
||||
normalizedFieldName.endsWith('ID')
|
||||
)
|
||||
}
|
||||
|
||||
const extractOperationContent = (
|
||||
row: OperationLogRow,
|
||||
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
|
||||
}
|
||||
|
||||
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 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,
|
||||
beforeValue,
|
||||
afterValue
|
||||
})
|
||||
}
|
||||
|
||||
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(
|
||||
() => props.assetIdentifier,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
loadLogs()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.operation-logs-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
|
||||
.operation-type-select {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.result-status-select {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs-table-wrapper {
|
||||
overflow-x: auto;
|
||||
|
||||
.logs-table {
|
||||
min-width: 980px;
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
th {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.change-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.change-item {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
|
||||
.field-name {
|
||||
min-width: 72px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.field-value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs-pagination {
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 1200px) {
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.filter-section {
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.filter-section {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
:deep(.el-select),
|
||||
.el-button,
|
||||
.el-tag {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logs-table-wrapper {
|
||||
padding: 0 12px;
|
||||
margin-right: -12px;
|
||||
margin-left: -12px;
|
||||
|
||||
:deep(.el-pagination) {
|
||||
justify-content: flex-start !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -96,15 +96,6 @@
|
||||
@navigate-to-device="handleNavigateToDevice"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 第五行:操作审计日志 -->
|
||||
<div class="row full-width">
|
||||
<OperationLogsCard
|
||||
:asset-identifier="cardInfo.identifier"
|
||||
:asset-type="cardInfo.asset_type"
|
||||
download-permission="asset_info:download_log_file"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -204,7 +195,6 @@
|
||||
import CurrentPackageCard from './components/CurrentPackageCard.vue'
|
||||
import PackageListCard from './components/PackageListCard.vue'
|
||||
import WalletTransactionCard from './components/WalletTransactionCard.vue'
|
||||
import OperationLogsCard from './components/OperationLogsCard.vue'
|
||||
|
||||
// 引入对话框组件
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user