feat: 新增财务账单管理模块,优化多页面代码与UI
- 新增财务账单路由、Mock API 服务及对应页面文件
- 清理系统配置页面中多余的报错提示逻辑
- 为多个数据表格添加自定义空状态模板与图片加载容错处理
- 新增车辆素材上传的图片尺寸校验(450*210)并添加尺寸提示文案
| | |
| | | fullScreen: true, |
| | | }, |
| | | { |
| | | name: 'financeBill', |
| | | path: '/system/financeBill', |
| | | title: '财务账单', |
| | | requiresAuth: true, |
| | | icon: 'icon-park-outline:bill', |
| | | menuType: 'page', |
| | | componentPath: '/system/financeBill/index.vue', |
| | | id: 204, |
| | | keepAlive: true, |
| | | pid: 2, |
| | | fullScreen: true, |
| | | }, |
| | | { |
| | | name: 'userContent', |
| | | path: '/userContent', |
| | | title: '用户&内容', |
| New file |
| | |
| | | import dayjs from 'dayjs' |
| | | |
| | | export interface FinanceBillRecord { |
| | | id: number |
| | | operationTime: string |
| | | operatorName: string |
| | | codeResult: 'success' | 'param_error' | 'repeat_push' |
| | | 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), |
| | | }) |
| | | } |
| | | |
| | | export function fetchFinanceBillDetail(id: number | string) { |
| | | const target = financeBillMockList.find(item => String(item.id) === String(id)) || null |
| | | return Promise.resolve({ |
| | | code: 200, |
| | | data: target, |
| | | }) |
| | | } |
| | | |
| | | 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), |
| | | }) |
| | | } |
| | |
| | | } |
| | | |
| | | const listData = ref<MaterialItem[]>([]) |
| | | |
| | | function getImageSize(file: File) { |
| | | return new Promise<{ width: number, height: number }>((resolve, reject) => { |
| | | const reader = new FileReader() |
| | | |
| | | reader.onload = () => { |
| | | const image = new Image() |
| | | image.onload = () => { |
| | | resolve({ |
| | | width: image.width, |
| | | height: image.height, |
| | | }) |
| | | } |
| | | image.onerror = () => reject(new Error('图片尺寸读取失败')) |
| | | image.src = String(reader.result || '') |
| | | } |
| | | |
| | | reader.onerror = () => reject(new Error('图片文件读取失败')) |
| | | reader.readAsDataURL(file) |
| | | }) |
| | | } |
| | | |
| | | // 获取列表 |
| | | async function getList() { |
| | | startLoading() |
| | |
| | | const maxSize = 2 * 1024 * 1024 // 2MB |
| | | if (file.size > maxSize) { |
| | | message.error('图片大小不能超过 2MB') |
| | | return false |
| | | } |
| | | |
| | | try { |
| | | // 上传前校验图片实际宽高,只允许 450 * 210 的素材图。 |
| | | const { width, height } = await getImageSize(file) |
| | | if (width !== 450 || height !== 210) { |
| | | message.error('请上传尺寸为 450*210,比例为 15:7 的图片') |
| | | return false |
| | | } |
| | | } |
| | | catch (error) { |
| | | console.error('图片尺寸校验失败:', error) |
| | | message.error('图片尺寸校验失败,请重新选择图片') |
| | | return false |
| | | } |
| | | |
| | |
| | | <div class="mt-12px text-12px" style="color: rgba(23, 31, 38, 0.6);"> |
| | | 点击上传图片 |
| | | </div> |
| | | <div class="mt-4px text-12px" style="color: rgba(23, 31, 38, 0.4);"> |
| | | 尺寸需为 450*210 |
| | | </div> |
| | | </div> |
| | | </NUploadDragger> |
| | | </NUpload> |
| | |
| | | :data="detailInfo.pointInfoList" |
| | | :bordered="false" |
| | | :max-height="300" |
| | | /> |
| | | > |
| | | <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> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | flex-height |
| | | :bordered="false" |
| | | :scroll-x="1060" |
| | | /> |
| | | > |
| | | <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="count" |
| | | :page="pageParams.pageNum" |
| | |
| | | window.$message?.success('保存成功') |
| | | loadSystemSetting() |
| | | } |
| | | else { |
| | | window.$message?.warning(res.msg || '保存失败') |
| | | } |
| | | } |
| | | catch (error) { |
| | | console.warn('验证失败', error) |
| | |
| | | formModel.value.xczHmSpeedPerHour = d.xczHmSpeedPerHour ? d.xczHmSpeedPerHour.toString() : '' |
| | | formModel.value.xczHmDuration = d.xczHmDuration ? d.xczHmDuration.toString() : '' |
| | | } |
| | | else { |
| | | window.$message?.warning('获取系统配置失败') |
| | | } |
| | | } |
| | | catch (e) { |
| | | console.error(e) |
| | | window.$message?.error('请求系统配置失败') |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | <script setup lang="tsx"> |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | 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 |
| | | operationTime: string |
| | | operatorName: string |
| | | codeResult: CodeResultType |
| | | returnResult: string |
| | | } |
| | | |
| | | const dialog = useDialog() |
| | | const message = useMessage() |
| | | |
| | | const loading = ref(false) |
| | | const listData = ref<FinanceBillRow[]>([]) |
| | | const total = ref(0) |
| | | const pageParams = ref({ |
| | | pageNum: 1, |
| | | 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 columns = computed<DataTableColumns<FinanceBillRow>>(() => [ |
| | | { |
| | | title: '操作时间', |
| | | key: 'operationTime', |
| | | width: 180, |
| | | render: row => row.operationTime ? dayjs(row.operationTime).format('YYYY/MM/DD HH:mm:ss') : '-', |
| | | }, |
| | | { |
| | | title: '操作人', |
| | | key: 'operatorName', |
| | | width: 140, |
| | | ellipsis: true, |
| | | render: row => row.operatorName || '-', |
| | | }, |
| | | { |
| | | title: 'code结果', |
| | | key: 'codeResult', |
| | | width: 140, |
| | | render: (row) => { |
| | | const config = codeResultConfigMap[row.codeResult] |
| | | return ( |
| | | <NTag bordered={false} type={config?.type || 'default'}> |
| | | {config?.label || '-'} |
| | | </NTag> |
| | | ) |
| | | }, |
| | | }, |
| | | { |
| | | title: '返回结果', |
| | | key: 'returnResult', |
| | | minWidth: 420, |
| | | ellipsis: { |
| | | tooltip: true, |
| | | }, |
| | | render: row => row.returnResult || '-', |
| | | }, |
| | | { |
| | | title: '操作', |
| | | key: 'actions', |
| | | width: 100, |
| | | fixed: 'right', |
| | | render: row => ( |
| | | <NButton text type="primary" onClick={() => handleOpenDetail(row.id)}> |
| | | 详情 |
| | | </NButton> |
| | | ), |
| | | }, |
| | | ]) |
| | | |
| | | async function getList() { |
| | | loading.value = true |
| | | try { |
| | | const res = await fetchFinanceBillPage(pageParams.value) |
| | | if (res.code === 200 && res.data) { |
| | | listData.value = res.data.records || [] |
| | | total.value = res.data.total || 0 |
| | | } |
| | | } |
| | | finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | async function handleOpenDetail(id: number) { |
| | | detailLoading.value = true |
| | | showDetailModal.value = true |
| | | try { |
| | | const res = await fetchFinanceBillDetail(id) |
| | | if (res.code === 200) { |
| | | detailData.value = res.data |
| | | } |
| | | } |
| | | finally { |
| | | detailLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleOpenOperationLog() { |
| | | showOperationLogModal.value = true |
| | | } |
| | | |
| | | function handleManualPush() { |
| | | dialog.warning({ |
| | | title: '确认手动推送', |
| | | content: '确认立即执行财务账单手动推送吗?', |
| | | positiveText: '确认', |
| | | negativeText: '取消', |
| | | onPositiveClick: async () => { |
| | | const res = await manualPushFinanceBill() |
| | | if (res.code === 200) { |
| | | message.success(res.msg || '手动推送成功') |
| | | getList() |
| | | } |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | function handlePageChange(page: number, pageSize: number) { |
| | | pageParams.value.pageNum = page |
| | | pageParams.value.pageSize = pageSize |
| | | getList() |
| | | } |
| | | |
| | | function handleCloseDetailModal() { |
| | | showDetailModal.value = false |
| | | detailData.value = null |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <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> |
| | | <NButton type="primary" secondary @click="handleManualPush"> |
| | | 手动推送 |
| | | </NButton> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="flex-1 flex flex-col h-full"> |
| | | <n-data-table |
| | | class="flex-1" |
| | | :columns="columns" |
| | | :data="listData" |
| | | :loading="loading" |
| | | :bordered="false" |
| | | :row-key="(row: FinanceBillRow) => row.id" |
| | | flex-height |
| | | :scroll-x="980" |
| | | > |
| | | <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="total" |
| | | :page="pageParams.pageNum" |
| | | :page-size="pageParams.pageSize" |
| | | class="robotaxi-pagination mt-12px" |
| | | @change="handlePageChange" |
| | | /> |
| | | </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="{ |
| | | content: true, |
| | | action: true, |
| | | }" |
| | | @update:show="(show) => !show && handleCloseDetailModal()" |
| | | > |
| | | <n-spin :show="detailLoading"> |
| | | <div class="detail-content"> |
| | | <div class="detail-section"> |
| | | <div class="detail-label"> |
| | | 操作人 |
| | | </div> |
| | | <div class="detail-value detail-value--strong"> |
| | | {{ detailData?.operatorName || '-' }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <div class="detail-label"> |
| | | 操作时间 |
| | | </div> |
| | | <div class="detail-value"> |
| | | {{ detailData?.operationTime ? dayjs(detailData.operationTime).format('YYYY/MM/DD HH:mm:ss') : '-' }} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section"> |
| | | <div class="detail-label"> |
| | | code结果 |
| | | </div> |
| | | <div class="detail-value"> |
| | | <NTag v-if="detailData" :bordered="false" :type="codeResultConfigMap[detailData.codeResult]?.type || 'default'"> |
| | | {{ codeResultConfigMap[detailData.codeResult]?.label || '-' }} |
| | | </NTag> |
| | | <span v-else>-</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="detail-section detail-section--result"> |
| | | <div class="detail-label"> |
| | | 返回结果 |
| | | </div> |
| | | <div class="detail-value detail-value--multiline"> |
| | | {{ detailData?.returnResult || '-' }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </n-spin> |
| | | </n-modal> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="scss"> |
| | | .finance-bill-page { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | padding: 12px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .detail-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .detail-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .detail-section--result { |
| | | gap: 10px; |
| | | } |
| | | |
| | | .detail-label { |
| | | font-size: 14px; |
| | | line-height: 20px; |
| | | color: rgba(23, 31, 38, 0.6); |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 14px; |
| | | line-height: 22px; |
| | | color: #171f26; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .detail-value--strong { |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .detail-value--multiline { |
| | | white-space: pre-wrap; |
| | | } |
| | | </style> |
| | |
| | | :scroll-x="1100" |
| | | :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> |
| | | </n-data-table> |
| | | <Pagination |
| | | :count="recordTotal" |
| | | :page="recordCurrentPage" |
| | |
| | | :scroll-x="tableScrollX" |
| | | :row-key="(row) => row.id" |
| | | @update:checked-row-keys="handleCheck" |
| | | /> |
| | | > |
| | | <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="pagination.itemCount" |
| | | :page="pagination.page" |
| | |
| | | :data="selectedVehicles" |
| | | :bordered="false" |
| | | :single-line="false" |
| | | /> |
| | | > |
| | | <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> |
| | | </div> |
| | | |
| | | <!-- 服务用户卡片 --> |
| | |
| | | :data="selectedUsers" |
| | | :bordered="false" |
| | | :single-line="false" |
| | | /> |
| | | > |
| | | <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> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | :columns="weatherColumns" |
| | | :data="model.dynamicPricingJson.weatherRules" |
| | | :bordered="false" |
| | | /> |
| | | > |
| | | <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> |
| | | </div> |
| | | </div> |
| | | </NSpace> |
| | |
| | | :columns="weatherViewColumns" |
| | | :data="model.dynamicPricingJson.weatherRules" |
| | | :bordered="false" |
| | | /> |
| | | > |
| | | <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> |
| | | <div v-else> |
| | | 暂无配置 |
| | | </div> |
| | |
| | | :row-key="(row) => row.id" |
| | | flex-height |
| | | @update:checked-row-keys="handleCheck" |
| | | /> |
| | | > |
| | | <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" |
| | |
| | | :row-key="row => row.id" |
| | | :row-props="getRowProps" |
| | | class="custom-table" |
| | | /> |
| | | > |
| | | <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> |
| | | |
| | | <n-modal |
| | | v-model:show="showModal" |