feat:操作日志
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m10s

This commit is contained in:
sexygoat
2026-04-27 16:49:32 +08:00
parent a402f2329a
commit d5c5e07f77
5 changed files with 89 additions and 194 deletions

View File

@@ -0,0 +1,44 @@
<template>
<ElDrawer
v-model="dialogVisible"
title="操作审计日志"
direction="rtl"
size="60%"
:before-close="handleClose"
>
<OperationLogsCard :asset-identifier="identifier" />
</ElDrawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElDrawer } from 'element-plus'
import OperationLogsCard from '@/views/asset-management/asset-information/components/OperationLogsCard.vue'
interface Props {
modelValue: boolean
identifier?: string
}
const props = defineProps<Props>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
const dialogVisible = ref(props.modelValue)
watch(
() => props.modelValue,
(val) => {
dialogVisible.value = val
}
)
watch(dialogVisible, (val) => {
emit('update:modelValue', val)
})
const handleClose = () => {
dialogVisible.value = false
}
</script>

View File

@@ -137,6 +137,7 @@ declare module 'vue' {
LoginLeftView: typeof import('./../components/core/views/login/LoginLeftView.vue')['default']
MenuLayoutSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/MenuLayoutSettings.vue')['default']
MenuStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/MenuStyleSettings.vue')['default']
OperationLogsDialog: typeof import('./../components/business/OperationLogsDialog.vue')['default']
OperatorSelect: typeof import('./../components/business/OperatorSelect.vue')['default']
PackageSelector: typeof import('./../components/business/PackageSelector.vue')['default']
PaymentVoucherDialog: typeof import('./../components/business/PaymentVoucherDialog.vue')['default']

View File

@@ -107,7 +107,7 @@ export function useAssetInfo() {
packageSeries: data.series_name || '-',
packageTotalFlow: formatDataSize(data.package_total_mb || 0),
usedFlow: formatDataSize(data.package_used_mb || 0),
remainFlow: formatDataSize(data.package_remain_mb || 0),
remainFlow: formatDataSize((data.package_total_mb || 0) - (data.package_used_mb || 0)),
usedFlowPercentage:
data.package_total_mb > 0
? `${((data.package_used_mb / data.package_total_mb) * 100).toFixed(2)}%`

View File

@@ -490,6 +490,12 @@
:current-policy="currentRealnamePolicy"
@confirm="handleConfirmRealnamePolicy"
/>
<!-- 操作审计日志弹窗 -->
<OperationLogsDialog
v-model="operationLogsDialogVisible"
:identifier="operationLogsIdentifier"
/>
</ElCard>
</div>
</ArtTableFullScreen>
@@ -525,6 +531,7 @@
import { formatDateTime } from '@/utils/business/format'
import type { PackageSeriesResponse } from '@/types/api'
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
import OperationLogsDialog from '@/components/business/OperationLogsDialog.vue'
defineOptions({ name: 'DeviceList' })
@@ -540,6 +547,8 @@
const allocateDialogVisible = ref(false)
const recallDialogVisible = ref(false)
const selectedDevices = ref<Device[]>([])
const operationLogsDialogVisible = ref(false)
const operationLogsIdentifier = ref('')
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
@@ -1965,6 +1974,17 @@
})
}
if (hasAuth('device:operation_logs')) {
moreActions.push({
label: '操作审计日志',
handler: () => {
operationLogsIdentifier.value = row.virtual_no
operationLogsDialogVisible.value = true
},
type: 'primary'
})
}
// 如果有更多操作,添加到 actions 中
if (moreActions.length > 0) {
actions.push(...moreActions)

View File

@@ -42,19 +42,7 @@
>
批量设置套餐系列
</ElButton>
<ElButton
v-if="
hasAuth('iot_card:batch_recharge') ||
hasAuth('iot_card:network_distribution') ||
hasAuth('iot_card:network_recycle') ||
hasAuth('iot_card:change_package') ||
hasAuth('iot_card:batch_download')
"
type="info"
@click="showMoreMenuOnClick"
>
更多操作
</ElButton>
</template>
</ArtTableHeader>
@@ -508,92 +496,6 @@
</template>
</ElDialog>
<!-- 流量使用查询对话框 -->
<ElDialog v-model="flowUsageDialogVisible" title="流量使用查询" width="500px">
<div v-if="flowUsageLoading" style="padding: 40px; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">查询中...</div>
</div>
<ElDescriptions v-else-if="flowUsageData" :column="1" border>
<ElDescriptionsItem label="套餐总流量"
>{{ flowUsageData.totalFlow || 0 }} MB</ElDescriptionsItem
>
<ElDescriptionsItem label="已用流量"
>{{ flowUsageData.usedFlow || 0 }} MB</ElDescriptionsItem
>
<ElDescriptionsItem label="剩余流量"
>{{ flowUsageData.remainFlow || 0 }} MB</ElDescriptionsItem
>
<ElDescriptionsItem v-if="flowUsageData.extend" label="扩展信息">{{
flowUsageData.extend
}}</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="flowUsageDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
<!-- 实名状态查询对话框 -->
<ElDialog v-model="realnameStatusDialogVisible" title="实名认证状态" width="500px">
<div v-if="realnameStatusLoading" style="padding: 40px; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">查询中...</div>
</div>
<ElDescriptions v-else-if="realnameStatusData" :column="1" border>
<ElDescriptionsItem label="实名状态">{{
realnameStatusData.status || '未知'
}}</ElDescriptionsItem>
<ElDescriptionsItem v-if="realnameStatusData.extend" label="扩展信息">{{
realnameStatusData.extend
}}</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="realnameStatusDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
<!-- 卡实时状态查询对话框 -->
<ElDialog v-model="cardStatusDialogVisible" title="卡实时状态" width="500px">
<div v-if="cardStatusLoading" style="padding: 40px; text-align: center">
<ElIcon class="is-loading" :size="40"><Loading /></ElIcon>
<div style="margin-top: 16px">查询中...</div>
</div>
<ElDescriptions v-else-if="cardStatusData" :column="1" border>
<ElDescriptionsItem label="ICCID">{{
cardStatusData.iccid || '--'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="卡状态">{{
cardStatusData.cardStatus || '未知'
}}</ElDescriptionsItem>
<ElDescriptionsItem v-if="cardStatusData.extend" label="扩展信息">{{
cardStatusData.extend
}}</ElDescriptionsItem>
</ElDescriptions>
<template #footer>
<div class="dialog-footer">
<ElButton type="primary" @click="cardStatusDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
<!-- 更多操作右键菜单 -->
<ArtMenuRight
ref="moreMenuRef"
:menu-items="moreMenuItems"
:menu-width="180"
@select="handleMoreMenuSelect"
/>
<!-- 实名认证策略对话框 -->
<RealnamePolicyDialog
v-model="realnamePolicyDialogVisible"
@@ -609,6 +511,12 @@
:current-realname-status="currentUpdateRealnameStatus"
@success="handleUpdateRealnameStatusSuccess"
/>
<!-- 操作审计日志弹窗 -->
<OperationLogsDialog
v-model="operationLogsDialogVisible"
:identifier="operationLogsIdentifier"
/>
</ElCard>
</div>
</ArtTableFullScreen>
@@ -636,8 +544,6 @@
import { useUserStore } from '@/store/modules/user'
import { storeToRefs } from 'pinia'
import { formatDateTime } from '@/utils/business/format'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
import type {
StandaloneIotCard,
StandaloneCardStatus,
@@ -648,6 +554,7 @@
} from '@/types/api/card'
import { CardSelectionType } from '@/types/api/card'
import type { PackageSeriesResponse } from '@/types/api'
import OperationLogsDialog from '@/components/business/OperationLogsDialog.vue'
defineOptions({ name: 'StandaloneCardList' })
@@ -675,6 +582,10 @@
failed_items: null
})
// 操作审计日志弹窗
const operationLogsDialogVisible = ref(false)
const operationLogsIdentifier = ref('')
// 套餐系列绑定相关
const seriesBindingDialogVisible = ref(false)
const seriesBindingLoading = ref(false)
@@ -821,17 +732,6 @@
const currentCardDetail = ref<any>(null)
// IoT卡操作相关对话框
const flowUsageDialogVisible = ref(false)
const flowUsageLoading = ref(false)
const flowUsageData = ref<any>(null)
const realnameStatusDialogVisible = ref(false)
const realnameStatusLoading = ref(false)
const realnameStatusData = ref<any>(null)
const cardStatusDialogVisible = ref(false)
const cardStatusLoading = ref(false)
const cardStatusData = ref<any>(null)
// 实名认证策略对话框
const realnamePolicyDialogVisible = ref(false)
@@ -898,9 +798,6 @@
await getTableData()
}
// 更多操作右键菜单
const moreMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
// 店铺相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
@@ -1556,6 +1453,17 @@
})
}
if (hasAuth('iot_card:operation_logs')) {
actions.push({
label: '操作审计日志',
handler: () => {
operationLogsIdentifier.value = row.iccid
operationLogsDialogVisible.value = true
},
type: 'primary'
})
}
return actions
}
@@ -1906,59 +1814,6 @@
})
}
// 更多操作菜单项配置
const moreMenuItems = computed((): MenuItemType[] => {
const items: MenuItemType[] = []
if (hasAuth('iot_card:batch_download')) {
items.push({
key: 'download',
label: '批量下载'
})
}
if (hasAuth('iot_card:change_package')) {
items.push({
key: 'changePackage',
label: '其他功能暂定'
})
}
return items
})
// 卡操作菜单项配置
// 显示更多操作菜单 (左键点击)
const showMoreMenuOnClick = (e: MouseEvent) => {
e.stopPropagation()
moreMenuRef.value?.show(e)
}
// 处理更多操作菜单选择
const handleMoreMenuSelect = (item: MenuItemType) => {
switch (item.key) {
case 'distribution':
cardDistribution()
break
case 'recharge':
batchRecharge()
break
case 'recycle':
cardRecycle()
break
case 'download':
batchDownload()
break
case 'changePackage':
changePackage()
break
case 'clearSeries':
handleClearCardSeries()
break
}
}
// 清除卡套餐系列绑定
const handleClearCardSeries = async () => {
if (selectedCards.value.length === 0) {
@@ -1999,31 +1854,6 @@
})
}
// 网卡分销 - 正在开发中
const cardDistribution = () => {
ElMessage.info('功能正在开发中')
}
// 批量充值 - 正在开发中
const batchRecharge = () => {
ElMessage.info('功能正在开发中')
}
// 网卡回收 - 正在开发中
const cardRecycle = () => {
ElMessage.info('功能正在开发中')
}
// 批量下载 - 正在开发中
const batchDownload = () => {
ElMessage.info('功能正在开发中')
}
// 变更套餐 - 正在开发中
const changePackage = () => {
ElMessage.info('功能正在开发中')
}
// IoT卡操作处理函数
const handleCardOperation = (command: string, iccid: string, row?: any) => {
switch (command) {