From 7ad8af83cccaff98c3d30ec36fc70de17aa17bdc Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期六, 09 五月 2026 17:52:56 +0800
Subject: [PATCH] feat(system): 新增系统操作日志功能模块
---
src/components/custom/OperationLogModal.vue | 330 +++++++++++++++++++++++
src/views/system/operationLog/index.vue | 422 ++++++++++++++++++++++++++++++
src/service/api/systemOperationLog.ts | 19 +
src/styles/robotaxi.scss | 6
src/router/routes.static.ts | 13
5 files changed, 790 insertions(+), 0 deletions(-)
diff --git a/src/components/custom/OperationLogModal.vue b/src/components/custom/OperationLogModal.vue
new file mode 100644
index 0000000..8e23d2b
--- /dev/null
+++ b/src/components/custom/OperationLogModal.vue
@@ -0,0 +1,330 @@
+<script setup lang="ts">
+import type { DataTableColumns, FormInst, SelectOption } from 'naive-ui'
+import dayjs from 'dayjs'
+import Pagination from '@/components/common/Pagination.vue'
+import SearchPanel from '@/components/custom/SearchPanel.vue'
+
+interface OperationLogRow {
+ operatorName?: string
+ account?: string
+ operationType?: number | string
+ operationContent?: string
+ operationIp?: string
+ operationTime?: string
+ createTime?: string
+}
+
+interface QueryPayload {
+ operatorName?: string
+ account?: string
+ operationType?: number
+ operationContent?: string
+ operationIp?: string
+ startTime?: string
+ endTime?: string
+ pageNum: number
+ pageSize: number
+}
+
+interface FilterModel {
+ operatorName: string
+ account: string
+ operationType: number | null
+ operationContent: string
+ operationIp: string
+ operationTime: [number, number] | null
+}
+
+const props = withDefaults(defineProps<{
+ show: boolean
+ loading?: boolean
+ data: OperationLogRow[]
+ total: number
+ pageNum: number
+ pageSize: number
+ title?: string
+ showExport?: boolean
+ operationTypeOptions?: SelectOption[]
+}>(), {
+ loading: false,
+ title: '操作日志',
+ showExport: true,
+ operationTypeOptions: () => [
+ { label: '新增', value: 1 },
+ { label: '编辑', value: 2 },
+ { label: '删除', value: 3 },
+ { label: '导入', value: 4 },
+ { label: '导出', value: 5 },
+ { label: '批量冻结', value: 6 },
+ ],
+})
+
+const emit = defineEmits<{
+ 'update:show': [show: boolean]
+ 'search': [payload: QueryPayload]
+ 'reset': [payload: QueryPayload]
+ 'page-change': [payload: QueryPayload]
+ 'export': [payload: QueryPayload]
+}>()
+
+const formRef = ref<FormInst | null>(null)
+const isOperationTypeOpen = ref(false)
+
+const filterModel = ref<FilterModel>({
+ operatorName: '',
+ account: '',
+ operationType: null,
+ operationContent: '',
+ operationIp: '',
+ operationTime: null,
+})
+
+const currentPage = ref(props.pageNum)
+const currentPageSize = ref(props.pageSize)
+
+watch(() => props.pageNum, (val) => {
+ currentPage.value = val
+})
+
+watch(() => props.pageSize, (val) => {
+ currentPageSize.value = val
+})
+
+const operationTypeLabelMap = computed(() => {
+ return (props.operationTypeOptions || []).reduce<Record<string, string>>((result, item) => {
+ result[String(item.value)] = String(item.label ?? item.value)
+ return result
+ }, {})
+})
+
+const columns = computed<DataTableColumns<OperationLogRow>>(() => [
+ {
+ title: '序号',
+ key: 'index',
+ width: 80,
+ render: (_, index) => (currentPage.value - 1) * currentPageSize.value + index + 1,
+ },
+ {
+ title: '操作人',
+ key: 'operatorName',
+ width: 120,
+ ellipsis: true,
+ },
+ {
+ title: '账号',
+ key: 'account',
+ width: 160,
+ ellipsis: true,
+ },
+ {
+ title: '操作类型',
+ key: 'operationType',
+ width: 120,
+ render: row => operationTypeLabelMap.value[String(row.operationType)] || row.operationType || '-',
+ },
+ {
+ title: '操作内容',
+ key: 'operationContent',
+ width: 320,
+ ellipsis: true,
+ },
+ {
+ title: '操作IP',
+ key: 'operationIp',
+ width: 140,
+ ellipsis: true,
+ },
+ {
+ title: '操作时间',
+ key: 'operationTime',
+ width: 170,
+ ellipsis: true,
+ render: (row) => {
+ const timeText = row.operationTime || row.createTime
+ return timeText ? dayjs(timeText).format('YYYY/MM/DD HH:mm') : '-'
+ },
+ },
+])
+
+function buildPayload(pageNum = currentPage.value, pageSize = currentPageSize.value): QueryPayload {
+ const payload: QueryPayload = { pageNum, pageSize }
+ // 统一清洗查询条件,避免把空字符串交给父页面
+ if (filterModel.value.operatorName.trim())
+ payload.operatorName = filterModel.value.operatorName.trim()
+ if (filterModel.value.account.trim())
+ payload.account = filterModel.value.account.trim()
+ if (filterModel.value.operationType !== null)
+ payload.operationType = filterModel.value.operationType
+ if (filterModel.value.operationContent.trim())
+ payload.operationContent = filterModel.value.operationContent.trim()
+ if (filterModel.value.operationIp.trim())
+ payload.operationIp = filterModel.value.operationIp.trim()
+ if (filterModel.value.operationTime) {
+ payload.startTime = dayjs(filterModel.value.operationTime[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
+ payload.endTime = dayjs(filterModel.value.operationTime[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+ }
+ return payload
+}
+
+function handleSearch() {
+ currentPage.value = 1
+ emit('search', buildPayload(1, currentPageSize.value))
+}
+
+function handleReset() {
+ filterModel.value = {
+ operatorName: '',
+ account: '',
+ operationType: null,
+ operationContent: '',
+ operationIp: '',
+ operationTime: null,
+ }
+ currentPage.value = 1
+ emit('reset', buildPayload(1, currentPageSize.value))
+}
+
+function handlePageChange(page: number, pageSize: number) {
+ currentPage.value = page
+ currentPageSize.value = pageSize
+ emit('page-change', buildPayload(page, pageSize))
+}
+
+function handleExport() {
+ emit('export', buildPayload())
+}
+
+function handleUpdateShow(show: boolean) {
+ emit('update:show', show)
+}
+
+function handleOperationTypeShow(show: boolean) {
+ isOperationTypeOpen.value = show
+}
+</script>
+
+<template>
+ <n-modal
+ :show="show"
+ preset="card"
+ :title="title"
+ class="operation-log-modal"
+ :style="{ width: '1200px' }"
+ :bordered="false"
+ size="huge"
+ closable
+ @update:show="handleUpdateShow"
+ >
+ <div class="roboTaxi w-full h-full">
+ <div class="roboTaxi-form-card mb-12px">
+ <NForm ref="formRef" :model="filterModel" label-placement="left" inline :show-feedback="false">
+ <SearchPanel>
+ <NGi>
+ <NFormItem label="操作人" path="operatorName">
+ <NInput v-model:value="filterModel.operatorName" placeholder="请输入" />
+ </NFormItem>
+ </NGi>
+ <NGi>
+ <NFormItem label="账号" path="account">
+ <NInput v-model:value="filterModel.account" placeholder="请输入" />
+ </NFormItem>
+ </NGi>
+ <NGi>
+ <NFormItem label="操作类型" path="operationType">
+ <NSelect
+ v-model:value="filterModel.operationType"
+ :options="operationTypeOptions"
+ clearable
+ placeholder="请选择"
+ @update:show="handleOperationTypeShow"
+ >
+ <template #arrow>
+ <img
+ src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
+ :class="`arrow-icon ${isOperationTypeOpen ? 'arrow-icon--open' : ''}`"
+ style="width: 16px; height: 16px;"
+ alt="arrow"
+ >
+ </template>
+ </NSelect>
+ </NFormItem>
+ </NGi>
+ <NGi>
+ <NFormItem label="操作内容" path="operationContent">
+ <NInput v-model:value="filterModel.operationContent" placeholder="请输入" />
+ </NFormItem>
+ </NGi>
+ <NGi>
+ <NFormItem label="操作IP" path="operationIp">
+ <NInput v-model:value="filterModel.operationIp" placeholder="请输入" />
+ </NFormItem>
+ </NGi>
+ <NGi>
+ <NFormItem label="操作时间" path="operationTime">
+ <NDatePicker
+ v-model:value="filterModel.operationTime"
+ type="daterange"
+ clearable
+ format="yyyy/MM/dd"
+ start-placeholder="开始时间"
+ end-placeholder="结束时间"
+ separator="-"
+ close-on-select
+ :actions="[]"
+ />
+ </NFormItem>
+ </NGi>
+ <template #action>
+ <NButton @click="handleReset">
+ 重置
+ </NButton>
+ <NButton type="primary" @click="handleSearch">
+ 查询
+ </NButton>
+ </template>
+ </SearchPanel>
+ </NForm>
+ </div>
+
+ <NCard class="robotaxi-table-card" content-style="display: flex; flex-direction: column; min-height: 420px;">
+ <template #header>
+ <n-flex justify="flex-end" align="center">
+ <NButton v-if="showExport" @click="handleExport">
+ 导出
+ </NButton>
+ </n-flex>
+ </template>
+ <div class="flex-1 flex flex-col h-full">
+ <NDataTable
+ class="flex-1"
+ :loading="loading"
+ :bordered="false"
+ :data="data"
+ :columns="columns"
+ :scroll-x="1080"
+ :header-height="18"
+ :row-height="18"
+ flex-height
+ >
+ <template #empty>
+ <div class="flex-center">
+ <img
+ src="/src/assets/element-img/no-data.png"
+ style="width:123px;height:125px"
+ @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
+ >
+ </div>
+ </template>
+ </NDataTable>
+ <Pagination
+ :count="total"
+ :page="currentPage"
+ :page-size="currentPageSize"
+ class="robotaxi-pagination mt-12px"
+ @change="handlePageChange"
+ />
+ </div>
+ </NCard>
+ </div>
+ </n-modal>
+</template>
diff --git a/src/router/routes.static.ts b/src/router/routes.static.ts
index c5990a1..73bda99 100644
--- a/src/router/routes.static.ts
+++ b/src/router/routes.static.ts
@@ -124,6 +124,19 @@
pid: 9,
},
{
+ name: 'systemOperationLog',
+ path: '/systemOperationLog',
+ title: '系统操作日志',
+ requiresAuth: true,
+ icon: 'icon-park-outline:log',
+ menuType: 'page',
+ componentPath: '/system/operationLog/index.vue',
+ id: 12,
+ keepAlive: true,
+ pid: null,
+ fullScreen: false,
+ },
+ {
name: 'testPlanAdd',
path: '/testDemo/testPlanList/add',
title: '新增测试计划',
diff --git a/src/service/api/systemOperationLog.ts b/src/service/api/systemOperationLog.ts
new file mode 100644
index 0000000..093cae2
--- /dev/null
+++ b/src/service/api/systemOperationLog.ts
@@ -0,0 +1,19 @@
+import { request } from '@/service/http'
+
+/**
+ * 获取系统操作日志分页列表
+ * @param data 查询参数
+ */
+export function fetchSystemOperationLogPageList(data: any) {
+ return request.Post<Service.ResponseResult<any>>('/operation/api/v1/systemOperationLog/pageList', data)
+}
+
+/**
+ * 导出系统操作日志
+ * @param data 查询参数
+ */
+export function exportSystemOperationLog(data: any) {
+ const method = request.Post('/operation/api/v1/systemOperationLog/exportExcel', data, { responseType: 'blob' })
+ method.meta = { ...method.meta, isBlob: true }
+ return method
+}
diff --git a/src/styles/robotaxi.scss b/src/styles/robotaxi.scss
index 9adc536..4754c02 100644
--- a/src/styles/robotaxi.scss
+++ b/src/styles/robotaxi.scss
@@ -33,6 +33,7 @@
.roboTaxi {
height: calc(100vh - 76px - 24px);
+ // height: calc(100vh - 35px - 40px);
display: flex;
flex-direction: column;
@@ -80,6 +81,11 @@
padding-top: 0;
}
+ .n-card-content {
+ padding: 12px;
+ padding-top: 0;
+ }
+
.n-base-selection-input__content {
font-size: 13px;
}
diff --git a/src/views/system/operationLog/index.vue b/src/views/system/operationLog/index.vue
new file mode 100644
index 0000000..bdca287
--- /dev/null
+++ b/src/views/system/operationLog/index.vue
@@ -0,0 +1,422 @@
+<script setup lang="tsx">
+import type { DataTableColumns, FormInst } from 'naive-ui'
+import { NButton, NCard, NDataTable, NForm, NFormItem, NGi, NInput, NSelect, NTooltip } from 'naive-ui'
+import dayjs from 'dayjs'
+import SearchPanel from '@/components/custom/SearchPanel.vue'
+import Pagination from '@/components/common/Pagination.vue'
+import { useBoolean } from '@/hooks'
+import { downloadFile } from '@/utils'
+
+interface QueryModel {
+ functionModule: string
+ operatorName: string
+ account: string
+ operationType: number | null
+ operationContent: string
+ operationIp: string
+ operationTime: [number, number] | null
+ pageNum: number
+ pageSize: number
+}
+
+interface OperationLogItem {
+ functionModule?: string
+ operatorName?: string
+ account?: string
+ operationType?: number | string
+ operationContent?: string
+ operationIp?: string
+ operationTime?: string
+ createTime?: string
+}
+
+const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
+const message = window.$message
+
+const initialModel: QueryModel = {
+ functionModule: '',
+ operatorName: '',
+ account: '',
+ operationType: null,
+ operationContent: '',
+ operationIp: '',
+ operationTime: null,
+ pageNum: 1,
+ pageSize: 10,
+}
+const model = ref<QueryModel>({ ...initialModel })
+const formRef = ref<FormInst | null>(null)
+
+const isOperationTypeOpen = ref(false)
+const operationTypeOptions = [
+ { label: '新增', value: 1 },
+ { label: '编辑', value: 2 },
+ { label: '删除', value: 3 },
+ { label: '导入', value: 4 },
+ { label: '导出', value: 5 },
+ { label: '批量冻结', value: 6 },
+]
+
+const operationTypeLabelMap: Record<number, string> = {
+ 1: '新增',
+ 2: '编辑',
+ 3: '删除',
+ 4: '导入',
+ 5: '导出',
+ 6: '批量冻结',
+}
+
+function createMockData(total = 120): OperationLogItem[] {
+ const moduleList = ['白名单', '运营', '交易', '资产', '活动', '系统配置']
+ const operatorList = ['杨倩芳', '曹敏倩', '罗凯艺', '丁汉辉', '刘丽丽', '方一强']
+ const accountList = ['h33@china.com', 'five@eastday.com', 'bubj@cmju56.com', 's2724133@net.com.cn', 'mbajbigs@t2n.com', '1396827528']
+ const contentList = [
+ '新增账号“1198726442”',
+ '编辑账号“1198726442”',
+ '删除了 3 条无效数据',
+ '导入 126 条用户数据',
+ '导出了筛选后的报表',
+ '批量冻结 8 个账号',
+ ]
+ const ipList = ['198.235.14.9', '172.16.12.8', '10.10.22.33', '221.12.33.44', '36.110.5.7', '47.102.88.100']
+ const typeList = [1, 2, 3, 4, 5, 6]
+
+ return Array.from({ length: total }, (_, index) => {
+ const seed = index % 6
+ return {
+ functionModule: moduleList[seed],
+ operatorName: operatorList[seed],
+ account: accountList[seed],
+ operationType: typeList[seed],
+ operationContent: contentList[seed],
+ operationIp: ipList[seed],
+ operationTime: dayjs().subtract(index * 13, 'minute').format('YYYY-MM-DD HH:mm:ss'),
+ }
+ })
+}
+
+const mockSourceData = ref<OperationLogItem[]>(createMockData())
+
+const filterItems = computed(() => [
+ {
+ label: '功能模块',
+ path: 'functionModule',
+ render: () => <NInput v-model:value={model.value.functionModule} placeholder="请输入" />,
+ },
+ {
+ label: '操作人',
+ path: 'operatorName',
+ render: () => <NInput v-model:value={model.value.operatorName} placeholder="请输入" />,
+ },
+ {
+ label: '账号',
+ path: 'account',
+ render: () => <NInput v-model:value={model.value.account} placeholder="请输入" />,
+ },
+ {
+ label: '操作类型',
+ path: 'operationType',
+ render: () => (
+ <NSelect
+ v-model:value={model.value.operationType}
+ options={operationTypeOptions}
+ clearable
+ placeholder="请选择"
+ onUpdate:show={(value: boolean) => isOperationTypeOpen.value = value}
+ >
+ {{
+ arrow: () => (
+ <img
+ src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png"
+ class={`arrow-icon ${isOperationTypeOpen.value ? 'arrow-icon--open' : ''}`}
+ style={{ width: '16px', height: '16px' }}
+ alt="arrow"
+ />
+ ),
+ empty: () => (
+ <div class="flex-center">
+ <img src="/src/assets/element-img/no-data.png" style={{ width: '50%' }} />
+ </div>
+ ),
+ }}
+ </NSelect>
+ ),
+ },
+ {
+ label: '操作内容',
+ path: 'operationContent',
+ render: () => <NInput v-model:value={model.value.operationContent} placeholder="请输入" />,
+ },
+ {
+ label: '操作IP',
+ path: 'operationIp',
+ render: () => <NInput v-model:value={model.value.operationIp} placeholder="请输入" />,
+ },
+ {
+ label: '操作时间',
+ path: 'operationTime',
+ render: () => (
+ <n-date-picker
+ v-model:value={model.value.operationTime}
+ type="daterange"
+ clearable
+ format="yyyy/MM/dd"
+ start-placeholder="开始时间"
+ end-placeholder="结束时间"
+ separator="-"
+ close-on-select={true}
+ actions={[]}
+ />
+ ),
+ },
+])
+
+const listData = ref<OperationLogItem[]>([])
+const count = ref(0)
+
+const columns = computed<DataTableColumns<OperationLogItem>>(() => [
+ {
+ title: '序号',
+ key: 'index',
+ width: 60,
+ resizable: true,
+ render: (_, index) => <span>{(model.value.pageNum - 1) * model.value.pageSize + index + 1}</span>,
+ },
+ {
+ title: '功能模块',
+ key: 'functionModule',
+ width: 160,
+ ellipsis: {
+ tooltip: true,
+ },
+ resizable: true,
+ },
+ {
+ title: '操作人',
+ key: 'operatorName',
+ width: 100,
+ ellipsis: true,
+ resizable: true,
+ },
+ {
+ title: '账号',
+ key: 'account',
+ width: 150,
+ ellipsis: true,
+ resizable: true,
+ },
+ {
+ title: '操作类型',
+ key: 'operationType',
+ width: 120,
+ resizable: true,
+ render: (row) => {
+ const value = row.operationType
+ return <span>{typeof value === 'number' ? (operationTypeLabelMap[value] || value) : (value || '-')}</span>
+ },
+ },
+ {
+ title: '操作内容',
+ key: 'operationContent',
+ width: 200,
+ ellipsis: true,
+ resizable: true,
+ render: row => (
+ <NTooltip trigger="hover" placement="top" max-width="560px">
+ {{
+ trigger: () => <span class="cursor-pointer">{row.operationContent || '-'}</span>,
+ default: () => row.operationContent || '-',
+ }}
+ </NTooltip>
+ ),
+ },
+ {
+ title: '操作IP',
+ key: 'operationIp',
+ width: 140,
+ ellipsis: true,
+ resizable: true,
+ },
+ {
+ title: '操作时间',
+ key: 'operationTime',
+ width: 120,
+ ellipsis: true,
+ render: (row) => {
+ const timeText = row.operationTime || row.createTime
+ return <span>{timeText ? dayjs(timeText).format('YYYY/MM/DD HH:mm') : '-'}</span>
+ },
+ },
+])
+
+function buildQueryParams() {
+ const params: Record<string, any> = {
+ pageNum: model.value.pageNum,
+ pageSize: model.value.pageSize,
+ }
+ if (model.value.functionModule.trim())
+ params.functionModule = model.value.functionModule.trim()
+ if (model.value.operatorName.trim())
+ params.operatorName = model.value.operatorName.trim()
+ if (model.value.account.trim())
+ params.account = model.value.account.trim()
+ if (model.value.operationType !== null)
+ params.operationType = model.value.operationType
+ if (model.value.operationContent.trim())
+ params.operationContent = model.value.operationContent.trim()
+ if (model.value.operationIp.trim())
+ params.operationIp = model.value.operationIp.trim()
+ if (model.value.operationTime) {
+ params.startTime = dayjs(model.value.operationTime[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss')
+ params.endTime = dayjs(model.value.operationTime[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+ }
+ return params
+}
+
+function getFilteredData() {
+ const params = buildQueryParams()
+ return mockSourceData.value.filter((item) => {
+ if (params.functionModule && !String(item.functionModule || '').includes(params.functionModule))
+ return false
+ if (params.operatorName && !String(item.operatorName || '').includes(params.operatorName))
+ return false
+ if (params.account && !String(item.account || '').includes(params.account))
+ return false
+ if (params.operationType !== undefined && Number(item.operationType) !== Number(params.operationType))
+ return false
+ if (params.operationContent && !String(item.operationContent || '').includes(params.operationContent))
+ return false
+ if (params.operationIp && !String(item.operationIp || '').includes(params.operationIp))
+ return false
+ if (params.startTime && params.endTime) {
+ const currentTime = dayjs(item.operationTime || item.createTime).valueOf()
+ const startTime = dayjs(params.startTime).valueOf()
+ const endTime = dayjs(params.endTime).valueOf()
+ if (currentTime < startTime || currentTime > endTime)
+ return false
+ }
+ return true
+ })
+}
+
+async function getList() {
+ startLoading()
+ try {
+ const filteredData = getFilteredData()
+ count.value = filteredData.length
+ const start = (model.value.pageNum - 1) * model.value.pageSize
+ const end = start + model.value.pageSize
+ listData.value = filteredData.slice(start, end)
+ await Promise.resolve()
+ }
+ finally {
+ endLoading()
+ }
+}
+
+function handleSearch() {
+ model.value.pageNum = 1
+ getList()
+}
+
+function handleResetSearch() {
+ model.value = { ...initialModel }
+ handleSearch()
+}
+
+function changePage(page: number, size: number) {
+ model.value.pageNum = page
+ model.value.pageSize = size
+ getList()
+}
+
+async function handleExport() {
+ const exportList = getFilteredData()
+ const headers = ['序号', '功能模块', '操作人', '账号', '操作类型', '操作内容', '操作IP', '操作时间']
+ const lines = exportList.map((item, index) => {
+ const values = [
+ String(index + 1),
+ item.functionModule || '-',
+ item.operatorName || '-',
+ item.account || '-',
+ operationTypeLabelMap[Number(item.operationType)] || String(item.operationType || '-'),
+ item.operationContent || '-',
+ item.operationIp || '-',
+ item.operationTime || item.createTime || '-',
+ ]
+ return values.map(value => `"${String(value).replace(/"/g, '""')}"`).join(',')
+ })
+ const csvText = `\uFEFF${headers.join(',')}\n${lines.join('\n')}`
+ const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' })
+ downloadFile(blob, `系统操作日志_${dayjs().format('YYYYMMDDHHmmss')}.csv`)
+ message?.success('导出成功')
+}
+
+onMounted(() => {
+ getList()
+})
+</script>
+
+<template>
+ <div class="roboTaxi w-full h-full">
+ <div class="roboTaxi-form-card mb-12px">
+ <NForm ref="formRef" :model="model" label-placement="left" inline :show-feedback="false">
+ <SearchPanel>
+ <NGi v-for="item in filterItems" :key="item.path" class="n-grid-item">
+ <NFormItem :label="item.label" :path="item.path">
+ <component :is="item.render" />
+ </NFormItem>
+ </NGi>
+ <template #action>
+ <NButton @click="handleResetSearch">
+ {{ $t('common.reset') }}
+ </NButton>
+ <NButton type="primary" @click="handleSearch">
+ {{ $t('common.search') }}
+ </NButton>
+ </template>
+ </SearchPanel>
+ </NForm>
+ </div>
+
+ <NCard class="flex-1 robotaxi-table-card">
+ <template #header>
+ <n-flex justify="flex-end" align="center">
+ <NButton @click="handleExport">
+ 导出
+ </NButton>
+ </n-flex>
+ </template>
+ <div class="flex-1 flex flex-col h-full">
+ <NDataTable
+ class="flex-1"
+ :columns="columns"
+ :data="listData"
+ :loading="loading"
+ flex-height
+ :bordered="false"
+ :scroll-x="1280"
+ :header-height="18"
+ :row-height="18"
+ >
+ <template #empty>
+ <div class="flex-center">
+ <img
+ src="/src/assets/element-img/no-data.png"
+ style="width:123px;height:125px"
+ @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
+ >
+ </div>
+ </template>
+ </NDataTable>
+ <Pagination
+ :count="count"
+ :page="model.pageNum"
+ :page-size="model.pageSize"
+ class="robotaxi-pagination mt-12px"
+ @change="changePage"
+ />
+ </div>
+ </NCard>
+ </div>
+</template>
--
Gitblit v1.7.1