refactor(api,pages): 替换Mock接口并优化多页面交互与UI
将财务账单模块的Mock数据与模拟接口替换为真实后端API请求
优化交易策略页面时间选择器,选择后自动关闭弹窗
重构财务账单页面:调整表格列配置、修复手动推送弹窗加载状态
全面重构巡逻点位管理页面,新增标签页、搜索过滤、分页及编辑删除功能
| | |
| | | import dayjs from 'dayjs' |
| | | import { request } from '@/service/http' |
| | | |
| | | export interface FinanceBillRecord { |
| | | id: number |
| | |
| | | returnResult: string |
| | | } |
| | | |
| | | export interface FinanceBillLogRecord { |
| | | id: number |
| | | operatorName: string |
| | | operatorAccount: string |
| | | operationType: number |
| | | operationContent: string |
| | | operationIp: string |
| | | operationTime: string |
| | | } |
| | | |
| | | const operatorNames = ['蔡文婷', '黄芸欢', '王思龙'] |
| | | |
| | | const financeBillResultTemplates = [ |
| | | { |
| | | codeResult: 'success' as const, |
| | | returnResult: '返回结果返回结果返回结果返回结果返回结果返回结果返回结果返回结果返回结果返回结果...', |
| | | }, |
| | | { |
| | | codeResult: 'param_error' as const, |
| | | returnResult: '参数校验失败,请检查账单批次号、账期和推送目标后重试。', |
| | | }, |
| | | { |
| | | codeResult: 'repeat_push' as const, |
| | | returnResult: '重复事件,同一账单在短时间内已发起过一次推送,系统已自动拦截。', |
| | | }, |
| | | ] |
| | | |
| | | // TODO: 联调时将以下 mock 实现替换为真实接口请求。 |
| | | const financeBillMockList: FinanceBillRecord[] = Array.from({ length: 28 }, (_, index) => { |
| | | const result = financeBillResultTemplates[index % financeBillResultTemplates.length] |
| | | return { |
| | | id: index + 1, |
| | | operationTime: dayjs('2026-09-09 11:09:08').subtract(index, 'minute').format('YYYY-MM-DD HH:mm:ss'), |
| | | operatorName: operatorNames[index % operatorNames.length], |
| | | codeResult: result.codeResult, |
| | | returnResult: result.returnResult, |
| | | } |
| | | }) |
| | | |
| | | const financeBillOperationLogMockList: FinanceBillLogRecord[] = Array.from({ length: 18 }, (_, index) => ({ |
| | | id: index + 1, |
| | | operatorName: operatorNames[index % operatorNames.length], |
| | | operatorAccount: `finance_${index + 1}`, |
| | | operationType: index % 2 === 0 ? 2 : 1, |
| | | operationContent: index % 2 === 0 ? '执行财务账单手动推送' : '查看财务账单详情', |
| | | operationIp: `10.10.10.${index + 10}`, |
| | | operationTime: dayjs('2026-09-09 11:09:08').subtract(index * 3, 'minute').format('YYYY-MM-DD HH:mm:ss'), |
| | | })) |
| | | |
| | | function paginateList<T>(list: T[], pageNum = 1, pageSize = 10) { |
| | | const startIndex = (pageNum - 1) * pageSize |
| | | return { |
| | | records: list.slice(startIndex, startIndex + pageSize), |
| | | total: list.length, |
| | | } |
| | | } |
| | | |
| | | export function fetchFinanceBillPage(params: { pageNum?: number, pageSize?: number }) { |
| | | const { pageNum = 1, pageSize = 10 } = params || {} |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: paginateList(financeBillMockList, pageNum, pageSize), |
| | | }) |
| | | return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/manualPush/batchDetail', params) |
| | | } |
| | | |
| | | export function fetchFinanceBillDetail(id: number | string) { |
| | | const target = financeBillMockList.find(item => String(item.id) === String(id)) || null |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: target, |
| | | }) |
| | | return request.Get<Service.ResponseResult<any>>('/order/api/v1/order/manualPush/detail', { params: { id } }) |
| | | } |
| | | |
| | | export function manualPushFinanceBill() { |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | msg: '手动推送成功', |
| | | }) |
| | | } |
| | | |
| | | export function fetchFinanceBillOperationLogPage(params: { pageNum?: number, pageSize?: number }) { |
| | | const { pageNum = 1, pageSize = 10 } = params || {} |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: paginateList(financeBillOperationLogMockList, pageNum, pageSize), |
| | | }) |
| | | return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/manualPush') |
| | | } |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, onMounted, ref } from 'vue' |
| | | <script setup lang="tsx"> |
| | | import { computed, h, onMounted, ref, watch } from 'vue' |
| | | import { useMessage } from 'naive-ui' |
| | | import SelectPointModal from './components/SelectPointModal.vue' |
| | | import { getSpecialPoints, setSpecialPoint } from '@/service/api/point' |
| | | import { useBoolean } from '@/hooks' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import OperationLogModal from '@/components/custom/OperationLogModal.vue' |
| | | |
| | | const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false) |
| | | const message = useMessage() |
| | | |
| | | const showSelectModal = ref(false) |
| | | const currentSelectType = ref<'charge' | 'park'>('charge') |
| | | const showOperationLogModal = ref(false) |
| | | const activeTab = ref<'park' | 'charge'>('park') |
| | | const currentSelectType = ref<'charge' | 'park'>('park') |
| | | const editingPointId = ref<number | string | null>(null) |
| | | |
| | | const chargePoints = ref<any[]>([]) |
| | | const parkPoints = ref<any[]>([]) |
| | | |
| | | const currentExistingIds = computed(() => { |
| | | const list = currentSelectType.value === 'charge' ? chargePoints.value : parkPoints.value |
| | | return list.map(p => p.pointId) |
| | | const model = ref({ |
| | | pointName: '', |
| | | pointAddress: '', |
| | | }) |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const currentExistingPoints = computed(() => { |
| | | if (editingPointId.value !== null) |
| | | return [] |
| | | |
| | | return currentTabList.value.map(item => ({ |
| | | id: item.pointId, |
| | | pointName: item.pointName, |
| | | pointAddress: item.pointAddress, |
| | | })) |
| | | }) |
| | | |
| | | const currentTabList = computed(() => activeTab.value === 'charge' ? chargePoints.value : parkPoints.value) |
| | | |
| | | const filteredList = computed(() => { |
| | | return currentTabList.value.filter((item) => { |
| | | const matchPointName = !model.value.pointName || String(item.pointName || '').includes(model.value.pointName.trim()) |
| | | const matchPointAddress = !model.value.pointAddress || String(item.pointAddress || '').includes(model.value.pointAddress.trim()) |
| | | return matchPointName && matchPointAddress |
| | | }) |
| | | }) |
| | | |
| | | const pagedList = computed(() => { |
| | | const startIndex = (pageParams.value.pageNum - 1) * pageParams.value.pageSize |
| | | return filteredList.value.slice(startIndex, startIndex + pageParams.value.pageSize) |
| | | }) |
| | | |
| | | const columns = computed(() => [ |
| | | { |
| | | title: '点位名称', |
| | | key: 'pointName', |
| | | minWidth: 220, |
| | | ellipsis: { tooltip: true }, |
| | | }, |
| | | { |
| | | title: '详细地址', |
| | | key: 'pointAddress', |
| | | minWidth: 280, |
| | | ellipsis: { tooltip: true }, |
| | | }, |
| | | { |
| | | title: '关联车辆数', |
| | | key: 'relatedVehicleCount', |
| | | width: 140, |
| | | render: (row: any) => getRelatedVehicleCount(row), |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 120, |
| | | render: (row: any) => [ |
| | | h('span', { |
| | | class: 'table-action', |
| | | onClick: () => handleEditPoint(row), |
| | | }, '编辑'), |
| | | h('span', { |
| | | class: 'table-action ml-16px', |
| | | onClick: () => handleRemovePoint(activeTab.value, row.pointId), |
| | | }, '移除'), |
| | | ], |
| | | }, |
| | | ]) |
| | | |
| | | const filterItems = computed(() => [ |
| | | { |
| | | label: '点位名称', |
| | | path: 'pointName', |
| | | render: () => <n-input v-model:value={model.value.pointName} placeholder="请输入" clearable />, |
| | | }, |
| | | { |
| | | label: '详细地址', |
| | | path: 'pointAddress', |
| | | render: () => <n-input v-model:value={model.value.pointAddress} placeholder="请输入" clearable />, |
| | | }, |
| | | ]) |
| | | |
| | | function getRelatedVehicleCount(row: any) { |
| | | return row.relatedVehicleCount ?? row.bindVehicleCount ?? row.vehicleCount ?? 0 |
| | | } |
| | | |
| | | async function getList() { |
| | | startLoading() |
| | |
| | | |
| | | function handleSelectPoint(type: 'charge' | 'park') { |
| | | currentSelectType.value = type |
| | | editingPointId.value = null |
| | | showSelectModal.value = true |
| | | } |
| | | |
| | |
| | | pointId: p.id, |
| | | pointName: p.pointName, |
| | | pointAddress: p.pointAddress, |
| | | relatedVehicleCount: p.relatedVehicleCount ?? p.bindVehicleCount ?? p.vehicleCount ?? 0, |
| | | })) |
| | | |
| | | if (editingPointId.value !== null) { |
| | | const targetPoint = selectedPoints[0] |
| | | if (!targetPoint) |
| | | return |
| | | |
| | | const targetList = currentSelectType.value === 'charge' ? chargePoints.value : parkPoints.value |
| | | const currentIndex = targetList.findIndex(item => String(item.pointId) === String(editingPointId.value)) |
| | | const duplicateIndex = targetList.findIndex(item => String(item.pointId) === String(targetPoint.pointId)) |
| | | if (duplicateIndex !== -1 && duplicateIndex !== currentIndex) { |
| | | message.warning('该点位已存在,请重新选择') |
| | | return |
| | | } |
| | | if (currentIndex !== -1) |
| | | targetList.splice(currentIndex, 1, targetPoint) |
| | | |
| | | editingPointId.value = null |
| | | return |
| | | } |
| | | |
| | | if (currentSelectType.value === 'charge') { |
| | | const existingIds = new Set(chargePoints.value.map(p => p.pointId)) |
| | |
| | | } |
| | | } |
| | | |
| | | function handleRemovePoint(type: 'charge' | 'park', index: number) { |
| | | function handleRemovePoint(type: 'charge' | 'park', pointId: number | string) { |
| | | if (type === 'charge') { |
| | | chargePoints.value.splice(index, 1) |
| | | chargePoints.value = chargePoints.value.filter(item => String(item.pointId) !== String(pointId)) |
| | | } |
| | | else { |
| | | parkPoints.value.splice(index, 1) |
| | | parkPoints.value = parkPoints.value.filter(item => String(item.pointId) !== String(pointId)) |
| | | } |
| | | } |
| | | |
| | | function handleEditPoint(row: any) { |
| | | currentSelectType.value = activeTab.value |
| | | editingPointId.value = row.pointId |
| | | showSelectModal.value = true |
| | | } |
| | | |
| | | function handleSearch() { |
| | | pageParams.value.pageNum = 1 |
| | | } |
| | | |
| | | function handleResetSearch() { |
| | | model.value.pointName = '' |
| | | model.value.pointAddress = '' |
| | | pageParams.value.pageNum = 1 |
| | | } |
| | | |
| | | function handlePageChange(page: number, pageSize: number) { |
| | | pageParams.value.pageNum = page |
| | | pageParams.value.pageSize = pageSize |
| | | } |
| | | |
| | | function handleOpenOperationLog() { |
| | | showOperationLogModal.value = true |
| | | } |
| | | |
| | | async function handleSave() { |
| | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | |
| | | watch(activeTab, () => { |
| | | handleResetSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="h-full roboTaxi"> |
| | | <n-card class="w-full full-content" style="border-radius: 6px" content-style="padding:24px;"> |
| | | <div class="mb-6 flex items-center"> |
| | | <div class="mr-2 bg-blue-500" style="width: 3px; height: 14px" /> |
| | | <span class="font-bold" style="font-size: 16px">特殊点位管理</span> |
| | | </div> |
| | | <div class="h-full roboTaxi flex flex-col"> |
| | | <n-tabs v-model:value="activeTab" type="line" animated class="special-point-tabs"> |
| | | <n-tab-pane name="park" tab="收车点位" /> |
| | | <n-tab-pane name="charge" tab="充电点位" /> |
| | | </n-tabs> |
| | | |
| | | <div class="mb-4"> |
| | | <div class="flex items-center justify-between"> |
| | | <span>充电点位</span> |
| | | <NButton |
| | | secondary |
| | | type="primary" |
| | | @click="handleSelectPoint('charge')" |
| | | > |
| | | 选择点位 |
| | | </NButton> |
| | | </div> |
| | | </div> |
| | | <div class="roboTaxi-form-card mb-12px"> |
| | | <n-form :model="model" label-placement="left" inline :show-feedback="false"> |
| | | <SearchPanel> |
| | | <n-gi v-for="item in filterItems" :key="item.path"> |
| | | <n-form-item :label="item.label" :path="item.path"> |
| | | <component :is="item.render" /> |
| | | </n-form-item> |
| | | </n-gi> |
| | | <template #action> |
| | | <n-button @click="handleResetSearch"> |
| | | 重置 |
| | | </n-button> |
| | | <n-button type="primary" @click="handleSearch"> |
| | | 查询 |
| | | </n-button> |
| | | </template> |
| | | </SearchPanel> |
| | | </n-form> |
| | | </div> |
| | | |
| | | <div class="mb-2 flex bg-gray-100 font-bold px-4 py-3 font-bold"> |
| | | <div class="flex-1 text-sm text-gray-700"> |
| | | 点位名称 |
| | | </div> |
| | | <div class="flex-1 text-sm text-gray-700"> |
| | | 详细地址 |
| | | </div> |
| | | <div class="w-24 text-sm text-gray-700"> |
| | | 操作 |
| | | </div> |
| | | </div> |
| | | <div v-for="(point, index) in chargePoints" :key="index" class="mb-2 flex items-center bg-white px-4 py-3" style="border-bottom: 1px solid rgba(23, 31, 38, 0.1)"> |
| | | <div class="flex-1 text-sm pr-4"> |
| | | {{ point.pointName }} |
| | | </div> |
| | | <div class="flex-1 text-sm text-gray-600 pr-4"> |
| | | {{ point.pointAddress }} |
| | | </div> |
| | | <div class="w-24"> |
| | | <n-popconfirm @positive-click="handleRemovePoint('charge', index)"> |
| | | <template #trigger> |
| | | <n-button text type="primary" size="small"> |
| | | 移除 |
| | | </n-button> |
| | | </template> |
| | | 确定移除该点位吗? |
| | | </n-popconfirm> |
| | | </div> |
| | | </div> |
| | | <n-card |
| | | class="flex-1 robotaxi-table-card" |
| | | content-style="display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0;" |
| | | > |
| | | <template #header> |
| | | <n-flex justify="space-between"> |
| | | <n-button @click="handleOpenOperationLog"> |
| | | 操作日志 |
| | | </n-button> |
| | | <n-button secondary type="primary" @click="handleSelectPoint(activeTab)"> |
| | | 新增 |
| | | </n-button> |
| | | </n-flex> |
| | | </template> |
| | | |
| | | <div class="mt-6 mb-4"> |
| | | <div class="flex items-center justify-between"> |
| | | <span>收车点位</span> |
| | | <NButton |
| | | secondary |
| | | type="primary" |
| | | @click="handleSelectPoint('park')" |
| | | > |
| | | 选择点位 |
| | | </NButton> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="mb-2 flex bg-gray-100 px-4 py-3 font-bold"> |
| | | <div class="flex-1 text-sm text-gray-700"> |
| | | 点位名称 |
| | | </div> |
| | | <div class="flex-1 text-sm text-gray-700"> |
| | | 详细地址 |
| | | </div> |
| | | <div class="w-24 text-sm text-gray-700"> |
| | | 操作 |
| | | </div> |
| | | </div> |
| | | <div v-for="(point, index) in parkPoints" :key="index" class="mb-2 flex items-center bg-white px-4 py-3" style="border-bottom: 1px solid rgba(23, 31, 38, 0.1)"> |
| | | <div class="flex-1 text-sm pr-4"> |
| | | {{ point.pointName }} |
| | | </div> |
| | | <div class="flex-1 text-sm text-gray-600 pr-4"> |
| | | {{ point.pointAddress }} |
| | | </div> |
| | | <div class="w-24"> |
| | | <n-popconfirm @positive-click="handleRemovePoint('park', index)"> |
| | | <template #trigger> |
| | | <n-button text type="primary" size="small"> |
| | | 移除 |
| | | </n-button> |
| | | </template> |
| | | 确定移除该点位吗? |
| | | </n-popconfirm> |
| | | </div> |
| | | <div class="flex-1 flex flex-col h-full" style="min-width: 0; overflow: hidden"> |
| | | <n-data-table |
| | | class="flex-1" |
| | | :columns="columns" |
| | | :data="pagedList" |
| | | :loading="loading" |
| | | flex-height |
| | | :bordered="false" |
| | | :scroll-x="980" |
| | | :header-height="18" |
| | | :row-height="18" |
| | | :row-key="(row) => row.pointId" |
| | | > |
| | | <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> |
| | | </n-data-table> |
| | | <Pagination |
| | | :count="filteredList.length" |
| | | :page="pageParams.pageNum" |
| | | :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" |
| | | @change="handlePageChange" |
| | | /> |
| | | </div> |
| | | </n-card> |
| | | |
| | | <div v-permission="40711" class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white py-16px px-24px"> |
| | | <n-button style="width: 84px; height: 32px" @click="getList">取消</n-button> |
| | | <n-button type="primary" style="width: 84px; height: 32px" :loading="loading" @click="handleSave"> |
| | | 保存 |
| | | </n-button> |
| | | </div> |
| | | |
| | | <SelectPointModal v-model:show="showSelectModal" :existing-ids="currentExistingIds" @confirm="handleConfirmSelect" /> |
| | | <SelectPointModal |
| | | v-model:show="showSelectModal" |
| | | :existing-points="currentExistingPoints" |
| | | @confirm="handleConfirmSelect" |
| | | /> |
| | | <OperationLogModal v-model:show="showOperationLogModal" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .full-content{ |
| | | height: calc(100vh - 164px); |
| | | :deep(.n-tabs-nav-scroll-content) { |
| | | border-color: transparent !important; |
| | | } |
| | | |
| | | .table-action { |
| | | color: #0068ff; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .table-action:hover { |
| | | opacity: 0.8; |
| | | } |
| | | </style> |
| | |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { NButton, NTag, useDialog, useMessage } from 'naive-ui' |
| | | import dayjs from 'dayjs' |
| | | import OperationLogModal from '@/components/custom/OperationLogModal.vue' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import { |
| | | fetchFinanceBillDetail, |
| | | fetchFinanceBillOperationLogPage, |
| | | fetchFinanceBillPage, |
| | | manualPushFinanceBill, |
| | | } from '@/service/api/financeBill' |
| | | |
| | | type CodeResultType = 'success' | 'param_error' | 'repeat_push' |
| | | |
| | | interface FinanceBillRow { |
| | | id: number |
| | | id: number | string |
| | | batchNo: string |
| | | orderId: number | string |
| | | orderNumber: string |
| | | operationTime: string |
| | | operatorName: string |
| | | codeResult: CodeResultType |
| | | returnResult: string |
| | | operatorAccount: string |
| | | responseCode: number | string | null |
| | | codeResult: string |
| | | responseJson: string |
| | | createTime: string |
| | | } |
| | | |
| | | const dialog = useDialog() |
| | |
| | | pageSize: 10, |
| | | }) |
| | | |
| | | const showOperationLogModal = ref(false) |
| | | const showDetailModal = ref(false) |
| | | const detailLoading = ref(false) |
| | | const detailData = ref<FinanceBillRow | null>(null) |
| | | |
| | | const codeResultConfigMap: Record<CodeResultType, { label: string, type: 'success' | 'error' | 'warning' }> = { |
| | | success: { label: '接收成功', type: 'success' }, |
| | | param_error: { label: '参数错误', type: 'error' }, |
| | | repeat_push: { label: '重复事件', type: 'warning' }, |
| | | const codeResultTagMap: Record<string, { label: string, type: 'success' | 'error' | 'warning' }> = { |
| | | 接收成功: { label: '接收成功', type: 'success' }, |
| | | 参数错误: { label: '参数错误', type: 'error' }, |
| | | 重复事件: { label: '重复事件', type: 'warning' }, |
| | | } |
| | | |
| | | function getCodeResultTagConfig(codeResult?: string) { |
| | | if (!codeResult) |
| | | return null |
| | | |
| | | return codeResultTagMap[codeResult] || null |
| | | } |
| | | |
| | | const columns = computed<DataTableColumns<FinanceBillRow>>(() => [ |
| | |
| | | title: '操作时间', |
| | | key: 'operationTime', |
| | | width: 180, |
| | | resizable: true, |
| | | render: row => row.operationTime ? dayjs(row.operationTime).format('YYYY/MM/DD HH:mm:ss') : '-', |
| | | }, |
| | | { |
| | | title: '操作人', |
| | | key: 'operatorName', |
| | | width: 140, |
| | | width: 160, |
| | | ellipsis: true, |
| | | render: row => row.operatorName || '-', |
| | | resizable: true, |
| | | render: row => row.operatorName || row.operatorAccount || '-', |
| | | }, |
| | | { |
| | | title: 'code结果', |
| | | key: 'codeResult', |
| | | width: 140, |
| | | width: 160, |
| | | resizable: true, |
| | | ellipsis: { |
| | | tooltip: true, |
| | | }, |
| | | render: (row) => { |
| | | const config = codeResultConfigMap[row.codeResult] |
| | | const config = getCodeResultTagConfig(row.codeResult) |
| | | if (!config) |
| | | return row.codeResult || '-' |
| | | |
| | | return ( |
| | | <NTag bordered={false} type={config?.type || 'default'}> |
| | | {config?.label || '-'} |
| | | <NTag bordered={false} type={config.type}> |
| | | {config.label} |
| | | </NTag> |
| | | ) |
| | | }, |
| | | }, |
| | | { |
| | | title: '返回结果', |
| | | key: 'returnResult', |
| | | minWidth: 420, |
| | | key: 'responseJson', |
| | | width: 420, |
| | | resizable: true, |
| | | ellipsis: { |
| | | tooltip: true, |
| | | }, |
| | | render: row => row.returnResult || '-', |
| | | render: row => row.responseJson || '-', |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 100, |
| | | width: 60, |
| | | fixed: 'right', |
| | | render: row => ( |
| | | <NButton text type="primary" onClick={() => handleOpenDetail(row.id)}> |
| | |
| | | } |
| | | } |
| | | |
| | | function handleOpenOperationLog() { |
| | | showOperationLogModal.value = true |
| | | } |
| | | |
| | | function handleManualPush() { |
| | | dialog.warning({ |
| | | const dialogReactive = dialog.warning({ |
| | | title: '确认手动推送', |
| | | content: '确认立即执行财务账单手动推送吗?', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | maskClosable: false, |
| | | autoFocus: false, |
| | | onPositiveClick: async () => { |
| | | const res = await manualPushFinanceBill() |
| | | if (res.code === 200) { |
| | | message.success(res.msg || '手动推送成功') |
| | | getList() |
| | | dialogReactive.loading = true |
| | | try { |
| | | const res = await manualPushFinanceBill() |
| | | if (res.code === 200) { |
| | | message.success(res.msg || '手动推送成功') |
| | | getList() |
| | | } |
| | | } |
| | | finally { |
| | | dialogReactive.loading = false |
| | | } |
| | | }, |
| | | }) |
| | |
| | | <div class="finance-bill-page roboTaxi"> |
| | | <n-card class="flex-1 robotaxi-table-card" :bordered="false"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between"> |
| | | <NButton @click="handleOpenOperationLog"> |
| | | 操作日志 |
| | | </NButton> |
| | | <div class="flex items-center justify-end"> |
| | | <NButton type="primary" secondary @click="handleManualPush"> |
| | | 手动推送 |
| | | </NButton> |
| | |
| | | </div> |
| | | </n-card> |
| | | |
| | | <OperationLogModal |
| | | v-model:show="showOperationLogModal" |
| | | title="操作日志" |
| | | :show-export="false" |
| | | :operation-type-options="[ |
| | | { label: '手动推送', value: 1 }, |
| | | { label: '查看详情', value: 2 }, |
| | | ]" |
| | | :request-list="fetchFinanceBillOperationLogPage" |
| | | /> |
| | | |
| | | <n-modal |
| | | v-model:show="showDetailModal" :mask-closable="false" preset="card" title="推送详情" class="w-400px" |
| | | :auto-focus="false" :segmented="{ |
| | |
| | | 操作人 |
| | | </div> |
| | | <div class="detail-value detail-value--strong"> |
| | | {{ detailData?.operatorName || '-' }} |
| | | {{ detailData?.operatorName || detailData?.operatorAccount || '-' }} |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | code结果 |
| | | </div> |
| | | <div class="detail-value"> |
| | | <NTag v-if="detailData" :bordered="false" :type="codeResultConfigMap[detailData.codeResult]?.type || 'default'"> |
| | | {{ codeResultConfigMap[detailData.codeResult]?.label || '-' }} |
| | | <NTag |
| | | v-if="getCodeResultTagConfig(detailData?.codeResult)" |
| | | :bordered="false" |
| | | :type="getCodeResultTagConfig(detailData?.codeResult)?.type || 'default'" |
| | | > |
| | | {{ getCodeResultTagConfig(detailData?.codeResult)?.label || '-' }} |
| | | </NTag> |
| | | <span v-else>-</span> |
| | | <span v-else>{{ detailData?.codeResult || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | 返回结果 |
| | | </div> |
| | | <div class="detail-value detail-value--multiline"> |
| | | {{ detailData?.returnResult || '-' }} |
| | | {{ detailData?.responseJson || '-' }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | } |
| | | } |
| | | |
| | | function closeTimePicker() { |
| | | if (document.activeElement) { |
| | | (document.activeElement as HTMLElement).blur() |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getDetail() |
| | | getCityListData() |
| | |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | clearable |
| | | close-on-select |
| | | :actions="['now']" |
| | | class="w-140px" |
| | | placeholder="开始时间" |
| | | @update:value=" |
| | | () => { |
| | | closeTimePicker(); |
| | | } |
| | | " |
| | | /> |
| | | <span class="mx-8px text-[#171F2660]">至</span> |
| | | <n-time-picker |
| | |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | clearable |
| | | close-on-select |
| | | :actions="['now']" |
| | | class="w-140px" |
| | | placeholder="结束时间" |
| | | @update:value=" |
| | | () => { |
| | | closeTimePicker(); |
| | | } |
| | | " |
| | | /> |
| | | <icon-park-outline-delete class="special-time-delete-btn" @click="handleDeleteSpecialTimeRule(rule)" /> |
| | | </n-form-item> |