feat: 统一日期格式并完善多个功能模块
- 为多个视图的日期选择器添加统一的 `format="yyyy/MM/dd"` 格式
- 在车辆管理模块中集成车型的增删改查后端 API
- 在免密支付策略模块中集成后端 API 并调整表单字段
- 为多个下拉选择组件添加空状态图片及错误回退处理
- 更新路由配置中免密支付策略相关页面的标题
- 优化 API 函数参数拼接方式及代码格式
| | |
| | | { |
| | | name: 'passwordFreeStrategy', |
| | | path: '/transaction/passwordFreeStrategy', |
| | | title: '免密支付策略配置', |
| | | title: '免密支付策略', |
| | | requiresAuth: true, |
| | | icon: 'icon-park-outline:list-view', |
| | | menuType: 'page', |
| | |
| | | { |
| | | name: 'passwordFreeStrategyAdd', |
| | | path: '/transaction/passwordFreeStrategy/add', |
| | | title: '新增免密支付策略', |
| | | title: '新增策略', |
| | | requiresAuth: true, |
| | | icon: 'icon-park-outline:list-view', |
| | | menuType: 'page', |
| | |
| | | { |
| | | name: 'passwordFreeStrategyEdit', |
| | | path: '/transaction/passwordFreeStrategy/edit', |
| | | title: '编辑免密支付策略', |
| | | title: '编辑策略', |
| | | requiresAuth: true, |
| | | icon: 'icon-park-outline:list-view', |
| | | menuType: 'page', |
| | |
| | | { |
| | | name: 'passwordFreeStrategyDetail', |
| | | path: '/transaction/passwordFreeStrategy/detail', |
| | | title: '免密支付策略详情', |
| | | title: '策略详情', |
| | | requiresAuth: true, |
| | | icon: 'icon-park-outline:list-view', |
| | | menuType: 'page', |
| | |
| | | |
| | | // 获取可用车辆列表 |
| | | export function getAvailableVehicleList(params: any) { |
| | | const query = new URLSearchParams({ |
| | | orderId: String(params.orderId ?? ''), |
| | | orderType: String(params.orderType ?? ''), |
| | | }) |
| | | if (params.plateNumber !== undefined && params.plateNumber !== null) |
| | | query.set('plateNumber', String(params.plateNumber)) |
| | | if (params.fleetName !== undefined && params.fleetName !== null) |
| | | query.set('fleetName', String(params.fleetName)) |
| | | return request.Get<Service.ResponseResult<any>>(`/vehicleConvoy/api/v1/vehicle/availableList?${query.toString()}`) |
| | | return request.Get<Service.ResponseResult<any>>(`/vehicleConvoy/api/v1/vehicle/availableList?orderId=${params.orderId}&orderType=${params.orderType}&plateNumber=${params.plateNumber}&fleetName=${params.fleetName}`) |
| | | } |
| | | |
| | | /** 订单评价 */ |
| | |
| | | export function setRule(params: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/pricing/setRule', params) |
| | | } |
| | | |
| | | // 新增免密支付策略 |
| | | export function addPasswordFreePolicy(params: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/passwordFreePolicy/add', params) |
| | | } |
| | | |
| | | // 编辑免密支付策略 |
| | | export function editPasswordFreePolicy(params: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/passwordFreePolicy/edit', params) |
| | | } |
| | | |
| | | // 删除免密支付策略 |
| | | export function deletePasswordFreePolicy(id: any) { |
| | | return request.Delete<Service.ResponseResult<any>>(`/operation/api/v1/passwordFreePolicy/deleteById/${id}`) |
| | | } |
| | | |
| | | // 获取免密支付策略列表 |
| | | export function getPasswordFreePolicyList(params: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/passwordFreePolicy/pageList', params) |
| | | } |
| | | |
| | | // 获取免密支付策略详情 |
| | | export function getPasswordFreePolicyDetail(id: any) { |
| | | return request.Get<Service.ResponseResult<any>>(`/operation/api/v1/passwordFreePolicy/queryById?id=${id}`) |
| | | } |
| | |
| | | import { request } from '@/service/http' |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/batchStock |
| | | // 批量在库 |
| | | export function batchStock(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/vehicle/batchStock?idStr=' + data.idStr,) |
| | | } |
| | | // POST |
| | | // /api/v1/vehicle/operationExportExcel |
| | | // 运营车辆管理列表导出 |
| | | export function operationExportExcel(data: any) { |
| | | const method= request.Post('/vehicleConvoy/api/v1/vehicle/operationExportExcel', data, { responseType: 'blob' }) |
| | | method.meta = { ...method.meta, isBlob: true } |
| | | return method |
| | | return request.Post<Service.ResponseResult<any>>(`/vehicleConvoy/api/v1/vehicle/batchStock?idStr=${data.idStr}`) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/operationPageList |
| | | // 运营车辆管理列表导出 |
| | | export function operationExportExcel(data: any) { |
| | | const method = request.Post('/vehicleConvoy/api/v1/vehicle/operationExportExcel', data, { responseType: 'blob' }) |
| | | method.meta = { ...method.meta, isBlob: true } |
| | | return method |
| | | } |
| | | |
| | | // 运营车辆管理分页列表 |
| | | export function operationPageList(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/vehicle/operationPageList', data) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryTaskRoutineByVehicleId |
| | | // 通过车辆id查询年检分页列表 |
| | | export function queryTaskRoutineByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/vehicle/queryTaskRoutineByVehicleId', data) |
| | | } |
| | | |
| | | // DELETE |
| | | // /api/v1/chargingRecord/delete/{id} |
| | | // 删除充电记录 |
| | | export function deleteChargingRecordById(id: string) { |
| | | return request.Delete<Service.ResponseResult<any>>(`/maintenance/api/v1/chargingRecord/delete/${id}`) |
| | | } |
| | | |
| | | // DELETE |
| | | // /api/v1/task/delete/{id} |
| | | // 删除删除任务/事故 |
| | | export function deleteTaskById(id: string) { |
| | | return request.Delete<Service.ResponseResult<any>>(`/maintenance/api/v1/task/delete/${id}`) |
| | | } |
| | | // GET |
| | | // /api/v1/task/errorTypeTreeList |
| | | |
| | | // 事故类型列表 |
| | | export function errorTypeTreeList() { |
| | | return request.Get<Service.ResponseResult<any>>('/maintenance/api/v1/task/errorTypeTreeList') |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryChargingByVehicleId |
| | | // 通过车辆id查询充电分页列表 |
| | | export function queryChargingByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicle/queryChargingByVehicleId', data) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryDetectionByVehicleId |
| | | // 通过车辆id查询检测分页列表 |
| | | export function queryDetectionByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicle/queryDetectionByVehicleId', data) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryPreparationByVehicleId |
| | | // 通过车辆ID查询整备分页列表 |
| | | export function queryPreparationByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicle/queryPreparationByVehicleId', data) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicleClean/list |
| | | // 车辆管理-清洁列表 |
| | | export function queryCleanByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicleClean/list', data) |
| | | } |
| | | |
| | | // DELETE |
| | | // /api/v1/vehicleClean/delete |
| | | // 车辆管理-删除清洁记录 |
| | | export function deleteCleanRecordById(id: string | number) { |
| | | return request.Delete<Service.ResponseResult<any>>(`/maintenance/api/v1/vehicleClean/delete?id=${id}`) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryRepairByVehicleId |
| | | // 通过车辆id查询维修分页列表 |
| | | export function queryRepairByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicle/queryRepairByVehicleId', data) |
| | | } |
| | | |
| | | // POST |
| | | // /api/v1/vehicle/queryTaskByVehicleId |
| | | // 通过车辆id查询任务事故分页列表 |
| | | export function queryTaskByVehicleId(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/api/v1/vehicle/queryTaskByVehicleId', data) |
| | | } |
| | | |
| | | // GET |
| | | // /api/v1/electronicFence/list |
| | | // 获取不分页的电子围栏列表 |
| | | export function electronicFenceList() { |
| | | return request.Get<Service.ResponseResult<any>>('/operation/api/v1/electronicFence/list') |
| | | } |
| | | |
| | | // GET |
| | | // /api/v1/taskManagement/taskDetail |
| | | // 任务/事故详情 |
| | | export function getTaskDetail(id: string) { |
| | | return request.Get<Service.ResponseResult<any>>('/maintenance/api/v1/taskManagement/taskDetail', { params: { id } }) |
| | | } |
| | | |
| | | // 车辆管理-车型列表 |
| | | export function queryVehicleModelList(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/logan/inner/xpVehicleModel/pageList', data) |
| | | } |
| | | |
| | | // 车辆管理-新增车型 |
| | | export function addVehicleModel(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/logan/inner/xpVehicleModel/add', data) |
| | | } |
| | | |
| | | // 车辆管理-编辑车型 |
| | | export function editVehicleModel(data: any) { |
| | | return request.Post<Service.ResponseResult<any>>('/maintenance/logan/inner/xpVehicleModel/edit', data) |
| | | } |
| | | |
| | | // 车辆管理-删除车型 |
| | | export function deleteVehicleModelById(id: string | number) { |
| | | return request.Delete<Service.ResponseResult<any>>(`/maintenance/logan/inner/xpVehicleModel/deleteById?id=${id}`) |
| | | } |
| | | |
| | | // 车辆管理-车型详情 |
| | | export function getVehicleModelDetail(id: string) { |
| | | return request.Get<Service.ResponseResult<any>>(`/maintenance/logan/inner/xpVehicleModel/getById?id=${id}`) |
| | | } |
| | |
| | | import { ref } from 'vue' |
| | | import type { FormInst, FormRules } from 'naive-ui' |
| | | import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NSpace, useMessage } from 'naive-ui' |
| | | import { addVehicleModel, editVehicleModel, getVehicleModelDetail } from '@/service/api/vehicleConvoy' |
| | | |
| | | const emit = defineEmits<{ |
| | | refresh: [] |
| | |
| | | const formRef = ref<FormInst | null>(null) |
| | | |
| | | const defaultModel = { |
| | | id: undefined as string | undefined, |
| | | modelName: '', |
| | | seats: null as number | null, |
| | | id: undefined as string | number | undefined, |
| | | name: '', |
| | | seat: null as number | null, |
| | | } |
| | | |
| | | const formModel = ref({ ...defaultModel }) |
| | | |
| | | const rules: FormRules = { |
| | | modelName: { |
| | | name: { |
| | | required: true, |
| | | message: '请输入车型名称', |
| | | trigger: ['blur', 'input'], |
| | | }, |
| | | seats: { |
| | | seat: { |
| | | required: true, |
| | | type: 'number', |
| | | message: '请输入座位数', |
| | |
| | | }, |
| | | } |
| | | |
| | | function openModal(row?: any) { |
| | | async function openModal(row?: any) { |
| | | isEdit.value = !!row |
| | | if (row) { |
| | | formModel.value = { ...row } |
| | | formModel.value = { |
| | | ...defaultModel, |
| | | ...row, |
| | | } |
| | | if (row.id !== undefined && row.id !== null) { |
| | | try { |
| | | const res = await getVehicleModelDetail(String(row.id)) |
| | | if (res.code === 200 && res.data) { |
| | | formModel.value = { |
| | | ...defaultModel, |
| | | ...res.data, |
| | | seat: Number(res.data.seat), |
| | | } |
| | | } |
| | | } |
| | | catch (error) { |
| | | console.error('获取车型详情失败:', error) |
| | | } |
| | | } |
| | | } |
| | | else { |
| | | formModel.value = { ...defaultModel } |
| | |
| | | visible.value = false |
| | | } |
| | | |
| | | function handleSubmit() { |
| | | formRef.value?.validate((errors) => { |
| | | if (!errors) { |
| | | loading.value = true |
| | | // 模拟提交 |
| | | setTimeout(() => { |
| | | loading.value = false |
| | | message.success(isEdit.value ? '编辑成功' : '新增成功') |
| | | visible.value = false |
| | | emit('refresh') |
| | | }, 500) |
| | | async function handleSubmit() { |
| | | if (loading.value) |
| | | return |
| | | try { |
| | | await formRef.value?.validate() |
| | | loading.value = true |
| | | const params = { |
| | | id: formModel.value.id, |
| | | name: formModel.value.name.trim(), |
| | | seat: formModel.value.seat, |
| | | } |
| | | }) |
| | | const request = isEdit.value ? editVehicleModel(params) : addVehicleModel(params) |
| | | const res = await request |
| | | if (res.code === 200) { |
| | | message.success(isEdit.value ? '编辑成功' : '新增成功') |
| | | visible.value = false |
| | | emit('refresh') |
| | | return |
| | | } |
| | | message.warning(res.msg || '保存失败') |
| | | } |
| | | catch (error) { |
| | | console.error('保存车型失败:', error) |
| | | message.error('请求失败,请稍后重试') |
| | | } |
| | | finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | defineExpose({ |
| | |
| | | |
| | | <template> |
| | | <NModal |
| | | v-model:show="visible" |
| | | preset="card" |
| | | :title="isEdit ? '编辑车型' : '新增车型'" |
| | | style="width: 400px; border-radius: 8px;" |
| | | :segmented="{ content: true }" |
| | | content-style="padding: 24px;" |
| | | footer-style="padding: 16px 24px;" |
| | | v-model:show="visible" :mask-closable="false" preset="card" :title="isEdit ? '编辑车型' : '新增车型'" class="w-400px" |
| | | :segmented="{ |
| | | content: true, |
| | | action: true, |
| | | }" |
| | | > |
| | | <NForm |
| | | ref="formRef" |
| | |
| | | label-placement="top" |
| | | require-mark-placement="left" |
| | | > |
| | | <NFormItem label="车型名称" path="modelName"> |
| | | <NFormItem label="车型名称" path="name"> |
| | | <NInput |
| | | v-model:value="formModel.modelName" |
| | | v-model:value="formModel.name" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </NFormItem> |
| | | <NFormItem label="座位数" path="seats"> |
| | | <NFormItem label="座位数" path="seat"> |
| | | <NInputNumber |
| | | v-model:value="formModel.seats" |
| | | v-model:value="formModel.seat" |
| | | placeholder="请输入" |
| | | clearable |
| | | :min="1" |
| | |
| | | </NFormItem> |
| | | </NForm> |
| | | |
| | | <template #footer> |
| | | <template #action> |
| | | <NSpace justify="end"> |
| | | <NButton @click="handleCancel"> |
| | | 取消 |
| | |
| | | import { useBoolean } from '@/hooks' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | | import Pagination from '@/components/common/Pagination.vue' |
| | | import { deleteVehicleModelById, queryVehicleModelList } from '@/service/api/vehicleConvoy' |
| | | import AddOrEditModal from './components/AddOrEditModal.vue' |
| | | import dayjs from 'dayjs' |
| | | |
| | | const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false) |
| | | const dialog = useDialog() |
| | | const addOrEditModalRef = ref() |
| | | |
| | | const initialModel = { |
| | | modelName: '', |
| | | name: '', |
| | | } |
| | | const model = ref({ ...initialModel }) |
| | | const pagination = reactive({ |
| | |
| | | const filterItems = computed(() => [ |
| | | { |
| | | label: '车型名称', |
| | | path: 'modelName', |
| | | render: () => <n-input v-model:value={model.value.modelName} placeholder="请输入" clearable />, |
| | | path: 'name', |
| | | render: () => <n-input v-model:value={model.value.name} placeholder="请输入" clearable />, |
| | | }, |
| | | ]) |
| | | |
| | |
| | | key: 'createTime', |
| | | width: 160, |
| | | resizable: true, |
| | | render: (row) => { |
| | | return row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm:ss') : '-' |
| | | }, |
| | | }, |
| | | { |
| | | title: '车型名称', |
| | | key: 'modelName', |
| | | key: 'name', |
| | | width: 300, |
| | | resizable: true, |
| | | }, |
| | | { |
| | | title: '座位数', |
| | | key: 'seats', |
| | | key: 'seat', |
| | | width: 100, |
| | | resizable: true, |
| | | }, |
| | |
| | | |
| | | async function getList() { |
| | | startLoading() |
| | | // Mock data simulation |
| | | setTimeout(() => { |
| | | const mockData = Array.from({ length: pagination.pageSize }).map((_, index) => { |
| | | const idx = (pagination.page - 1) * pagination.pageSize + index + 1 |
| | | return { |
| | | id: `mock-id-${idx}`, |
| | | createTime: '2025-05-07 14:53', |
| | | modelName: idx === 1 ? 'Robotaxi R1' : 'Robotaxi X1', |
| | | seats: idx === 1 ? 4 : (idx === 2 ? 6 : 4), |
| | | } |
| | | }) |
| | | count.value = 1000 |
| | | listData.value = mockData |
| | | try { |
| | | const params = { |
| | | pageNum: pagination.page, |
| | | pageSize: pagination.pageSize, |
| | | name: model.value.name?.trim() || undefined, |
| | | } |
| | | const res = await queryVehicleModelList(params) |
| | | if (res.code === 200) { |
| | | // 兼容分页/非分页两种返回结构 |
| | | const records = res.data?.records || res.data?.list || [] |
| | | count.value = Number(res.data?.total ?? records.length ?? 0) |
| | | listData.value = Array.isArray(records) ? records : [] |
| | | return |
| | | } |
| | | listData.value = [] |
| | | count.value = 0 |
| | | window.$message?.warning(res.msg || '获取车型列表失败') |
| | | } |
| | | catch (error) { |
| | | console.error('获取车型列表失败:', error) |
| | | listData.value = [] |
| | | count.value = 0 |
| | | window.$message?.error('请求车型列表失败') |
| | | } |
| | | finally { |
| | | endLoading() |
| | | }, 500) |
| | | } |
| | | } |
| | | |
| | | function handleAdd() { |
| | |
| | | function handleDelete(row: any) { |
| | | dialog.create({ |
| | | title: '是否确认删除', |
| | | content: `确认删除车型 ${row.modelName} 吗?`, |
| | | content: `确认删除该车型?删除后不可恢复。`, |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | | window.$message?.success('删除成功') |
| | | getList() |
| | | onPositiveClick: async () => { |
| | | const res = await deleteVehicleModelById(row.id) |
| | | if (res.code === 200) { |
| | | window.$message?.success('删除成功') |
| | | getList() |
| | | } |
| | | else { |
| | | window.$message?.warning(res.msg || '删除失败') |
| | | } |
| | | }, |
| | | }) |
| | | } |
| | |
| | | fenceDetail.effectType === 1 |
| | | ? "立即生效" |
| | | : dayjs(fenceDetail.effectTime).format( |
| | | "YYYY-MM-DD HH:mm:ss", |
| | | "YYYY/MM/DD HH:mm:ss", |
| | | ) || "-" |
| | | }} |
| | | </div> |
| | |
| | | fenceDetail.failureType === 1 |
| | | ? "永不失效" |
| | | : dayjs(fenceDetail.failureTime).format( |
| | | "YYYY-MM-DD HH:mm:ss", |
| | | "YYYY/MM/DD HH:mm:ss", |
| | | ) || "-" |
| | | }} |
| | | </div> |
| | |
| | | clearable |
| | | actions={[]} |
| | | close-on-select={true} |
| | | format="yyyy/MM/dd" |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | separator="-" |
| | |
| | | clearable |
| | | class="flex-1" |
| | | placeholder="请选择起止日期" |
| | | format="yyyy/MM/dd" |
| | | :actions="[]" |
| | | :close-on-select="true" |
| | | /> |
| | |
| | | v-model:value={model.value.dateRange} |
| | | type="daterange" |
| | | clearable |
| | | format="yyyy/MM/dd" |
| | | actions={[]} |
| | | close-on-select={true} |
| | | start-placeholder="开始时间" |
| | |
| | | data: ['2026-03-23', '2026-03-24', '2026-03-25', '2026-03-26', '2026-03-27', '2026-03-28'], |
| | | axisTick: { show: false }, |
| | | axisLine: { lineStyle: { color: '#E5E6EB' } }, |
| | | axisLabel: { color: '#4E5969' } |
| | | axisLabel: { color: '#4E5969' }, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | |
| | | max: 250, |
| | | interval: 50, |
| | | splitLine: { lineStyle: { type: 'solid', color: '#E5E6EB' } }, |
| | | axisLabel: { color: '#4E5969' } |
| | | axisLabel: { color: '#4E5969' }, |
| | | }, |
| | | series: [ |
| | | { |
| | |
| | | show: true, |
| | | position: 'inner', |
| | | formatter: '{d}%', |
| | | color: '#fff' |
| | | color: '#fff', |
| | | }, |
| | | emphasis: { |
| | | itemStyle: { |
| | |
| | | data: ['2026-03-23', '2026-03-24', '2026-03-25', '2026-03-26', '2026-03-27', '2026-03-28'], |
| | | axisTick: { show: false }, |
| | | axisLine: { lineStyle: { color: '#E5E6EB' } }, |
| | | axisLabel: { color: '#4E5969' } |
| | | axisLabel: { color: '#4E5969' }, |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | |
| | | max: 250, |
| | | interval: 50, |
| | | splitLine: { lineStyle: { type: 'solid', color: '#E5E6EB' } }, |
| | | axisLabel: { color: '#4E5969' } |
| | | axisLabel: { color: '#4E5969' }, |
| | | }, |
| | | series: [ |
| | | { |
| | |
| | | show: true, |
| | | position: 'inner', |
| | | formatter: '{d}%', |
| | | color: '#fff' |
| | | color: '#fff', |
| | | }, |
| | | emphasis: { |
| | | itemStyle: { |
| | |
| | | .w-full { |
| | | width: 100%; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | class="arrow-icon" style="width: 16px; height: 16px" alt="arrow" |
| | | > |
| | | </template> |
| | | <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> |
| | | </NCascader> |
| | | </n-form-item-gi> |
| | | |
| | |
| | | formModel.startTime = val |
| | | startTimePicker?.handlePanelClose() |
| | | }, |
| | | }" :disabled="formModel.isLongValid === 1" placeholder="请选择开始时间" class="max-w-[200px]" |
| | | }" :disabled="formModel.isLongValid === 1" format="yyyy/MM/dd HH:mm" placeholder="请选择开始时间" class="max-w-[200px]" |
| | | /> |
| | | <span class="text-gray-400 shrink-0">至</span> |
| | | <NDatePicker |
| | | ref="endTimePicker" v-model:value="formModel.endTime" type="datetime" clearable |
| | | :actions="null" :disabled="formModel.isLongValid === 1" placeholder="请选择结束时间" class="max-w-[200px]" |
| | | :actions="null" :disabled="formModel.isLongValid === 1" format="yyyy/MM/dd HH:mm" placeholder="请选择结束时间" class="max-w-[200px]" |
| | | :time-picker-props="{ |
| | | onConfirm: (val) => { |
| | | formModel.endTime = val |
| | |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '123px', height: '125px' }} |
| | | onError={(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | }} |
| | | </n-cascader> |
| | | ), |
| | |
| | | function handleDelete(row: any) { |
| | | dialog.create({ |
| | | title: '删除确认', |
| | | content: '确定要删除该定向服务吗?', |
| | | content: '确认删除该定向服务?删除后不可恢复。', |
| | | positiveText: '确定', |
| | | negativeText: '取消', |
| | | onPositiveClick: () => { |
| | |
| | | <script setup lang="tsx"> |
| | | import { computed, onMounted, ref, watch } from 'vue' |
| | | import { onMounted, ref, watch } from 'vue' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { NButton, NCheckbox, NDatePicker, NForm, NFormItemGi, NGrid, NInputNumber, NRadio, NRadioGroup, NSelect, NSpace, NSwitch, useMessage } from 'naive-ui' |
| | | import { NButton, NCheckbox, NDatePicker, NForm, NInputNumber, NSelect, NSpace, NSwitch, useMessage } from 'naive-ui' |
| | | import type { FormItemRule } from 'naive-ui' |
| | | import dayjs from 'dayjs' |
| | | import { addPasswordFreePolicy, editPasswordFreePolicy, getCityList, getPasswordFreePolicyDetail } from '@/service/api/transaction' |
| | | import { useTabStore } from '@/store' |
| | | |
| | | const tabStore = useTabStore() |
| | |
| | | const id = ref<string | null>(null) |
| | | const pageType = ref<'add' | 'edit' | 'view'>('add') |
| | | const loading = ref(false) |
| | | const saving = ref(false) |
| | | const isCityOpen = ref(false) |
| | | const isOperationAreaOpen = ref(false) |
| | | |
| | | const formModel = ref({ |
| | | city: null as string | null, |
| | | operationArea: null as string | null, |
| | | status: 1, |
| | | startTime: null as number | null, |
| | | endTime: null as number | null, |
| | | longTermEffective: false, |
| | | maxRetryCount: null as number | null, |
| | | retryInterval: null as number | null, |
| | | sendNotification: true, |
| | | priceDifferenceThreshold: null as number | null, |
| | | validTimeStart: null as number | null, |
| | | validTimeEnd: null as number | null, |
| | | validTimeLong: false, |
| | | maxRetriesNumber: null as number | null, |
| | | retriesInterval: null as number | null, |
| | | sendNotification: 1 as 0 | 1, |
| | | differenceRatio: null as number | null, |
| | | }) |
| | | |
| | | const rules = { |
| | | city: { required: true, message: '请选择城市', trigger: ['blur', 'change'] }, |
| | | operationArea: { required: true, message: '请选择运营区域', trigger: ['blur', 'change'] }, |
| | | status: { required: true, type: 'number', message: '请选择状态', trigger: ['blur', 'change'] }, |
| | | status: { required: true, type: 'number', message: '请选择状态', trigger: ['blur', 'change'] } as FormItemRule, |
| | | } |
| | | |
| | | watch(() => formModel.value.longTermEffective, (val) => { |
| | | watch(() => formModel.value.validTimeLong, (val) => { |
| | | if (val) { |
| | | formModel.value.startTime = null |
| | | formModel.value.endTime = null |
| | | formModel.value.validTimeStart = null |
| | | formModel.value.validTimeEnd = null |
| | | } |
| | | }) |
| | | |
| | | const formRef = ref<any>(null) |
| | | |
| | | const pageTitle = computed(() => { |
| | | if (pageType.value === 'add') |
| | | return '免密支付策略配置' |
| | | if (pageType.value === 'edit') |
| | | return '编辑免密支付策略' |
| | | return '免密支付策略详情' |
| | | }) |
| | | const validTimeStartPicker = ref<any>(null) |
| | | const validTimeEndPicker = ref<any>(null) |
| | | |
| | | async function fetchDetail() { |
| | | if (!id.value) |
| | | return |
| | | loading.value = true |
| | | try { |
| | | // Mock fetching detail |
| | | setTimeout(() => { |
| | | const res = await getPasswordFreePolicyDetail(id.value) |
| | | if (res.code === 200 && res.data) { |
| | | const data = res.data |
| | | formModel.value = { |
| | | city: 'shenzhen', |
| | | operationArea: 'nanshan', |
| | | status: 1, |
| | | startTime: null, |
| | | endTime: null, |
| | | longTermEffective: true, |
| | | maxRetryCount: 3, |
| | | retryInterval: 60, |
| | | sendNotification: true, |
| | | priceDifferenceThreshold: 20, |
| | | city: data.city ?? data.cityId ?? null, |
| | | operationArea: data.operationArea ?? data.operationAreaId ?? null, |
| | | status: Number(data.status ?? 1), |
| | | validTimeStart: data.validTimeStart ? dayjs(data.validTimeStart).valueOf() : null, |
| | | validTimeEnd: data.validTimeEnd ? dayjs(data.validTimeEnd).valueOf() : null, |
| | | validTimeLong: data.validTimeLong === true || data.validTimeLong === 1, |
| | | maxRetriesNumber: data.maxRetriesNumber ?? null, |
| | | retriesInterval: data.retriesInterval ?? null, |
| | | sendNotification: data.sendNotification === 1 || data.sendNotification === true ? 1 : 0, |
| | | differenceRatio: data.differenceRatio ?? null, |
| | | } |
| | | loading.value = false |
| | | }, 500) |
| | | return |
| | | } |
| | | message.warning(res.msg || '获取策略详情失败') |
| | | } |
| | | catch (error) { |
| | | console.error(error) |
| | | console.error('获取策略详情失败:', error) |
| | | message.error('请求策略详情失败') |
| | | } |
| | | finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | |
| | | router.back() |
| | | } |
| | | |
| | | function handleSave() { |
| | | formRef.value?.validate((errors: any) => { |
| | | if (!errors) { |
| | | // Mock API call |
| | | async function handleSave() { |
| | | if (saving.value) |
| | | return |
| | | try { |
| | | await formRef.value?.validate() |
| | | saving.value = true |
| | | const params = { |
| | | id: id.value ? Number(id.value) : undefined, |
| | | city: formModel.value.city, |
| | | cityId: formModel.value.city, |
| | | operationArea: formModel.value.operationArea, |
| | | operationAreaId: formModel.value.operationArea, |
| | | status: Number(formModel.value.status), |
| | | validTimeLong: formModel.value.validTimeLong ? 1 : 0, |
| | | validTimeStart: formModel.value.validTimeLong || !formModel.value.validTimeStart |
| | | ? undefined |
| | | : dayjs(formModel.value.validTimeStart).format('YYYY-MM-DD HH:mm:ss'), |
| | | validTimeEnd: formModel.value.validTimeLong || !formModel.value.validTimeEnd |
| | | ? undefined |
| | | : dayjs(formModel.value.validTimeEnd).format('YYYY-MM-DD HH:mm:ss'), |
| | | maxRetriesNumber: formModel.value.maxRetriesNumber, |
| | | retriesInterval: formModel.value.retriesInterval, |
| | | sendNotification: Number(formModel.value.sendNotification), |
| | | differenceRatio: formModel.value.differenceRatio, |
| | | } |
| | | |
| | | const request = pageType.value === 'edit' ? editPasswordFreePolicy(params) : addPasswordFreePolicy(params) |
| | | const res = await request |
| | | if (res.code === 200) { |
| | | message.success('保存成功') |
| | | handleCancel() |
| | | return |
| | | } |
| | | message.warning(res.msg || '保存失败') |
| | | } |
| | | catch (error) { |
| | | console.error('保存免密支付策略失败:', error) |
| | | message.error('请求失败,请稍后重试') |
| | | } |
| | | finally { |
| | | saving.value = false |
| | | } |
| | | } |
| | | |
| | | const cityList = ref<any[]>([]) |
| | | |
| | | function getCityListData() { |
| | | getCityList({ type: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | cityList.value = res.data || [] |
| | | cityList.value.forEach((item) => { |
| | | item.label = item.name |
| | | item.value = item.id |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getCityListData() |
| | | if (route.query.id) { |
| | | id.value = route.query.id as string |
| | | pageType.value = route.query.type === 'view' ? 'view' : 'edit' |
| | |
| | | <template> |
| | | <div class="w-full h-full relative content flex flex-col"> |
| | | <div class="p-12px main flex flex-col flex-1 overflow-y-auto"> |
| | | <div class="box mb-12px shrink-0"> |
| | | <div class="text-xl"> |
| | | {{ pageTitle }} |
| | | </div> |
| | | </div> |
| | | |
| | | <NForm |
| | | ref="formRef" |
| | | :model="formModel" |
| | |
| | | <div class="head shrink-0"> |
| | | 基础信息 |
| | | </div> |
| | | |
| | | <NGrid :cols="24" :x-gap="24" :y-gap="16" class="mt-4"> |
| | | <NFormItemGi :span="5" label="城市" path="city"> |
| | | <NSelect v-model:value="formModel.city" placeholder="请选择" :options="[]" class="w-240px" /> |
| | | </NFormItemGi> |
| | | <NFormItemGi :span="8" label="运营区域" path="operationArea"> |
| | | <NSelect v-model:value="formModel.operationArea" placeholder="请选择" :options="[]" class="w-240px" /> |
| | | </NFormItemGi> |
| | | <NFormItemGi :span="8" label="状态" path="status"> |
| | | <NRadioGroup v-model:value="formModel.status"> |
| | | <NSpace> |
| | | <NRadio :value="1">启用</NRadio> |
| | | <NRadio :value="0">禁用</NRadio> |
| | | </NSpace> |
| | | </NRadioGroup> |
| | | </NFormItemGi> |
| | | |
| | | <NFormItemGi :span="24" label="生效时间段" :required="!formModel.longTermEffective"> |
| | | <div class="flex items-center gap-2"> |
| | | <div class="row-group mb-8px"> |
| | | <n-form-item label="城市" path="city" required class="inline-item"> |
| | | <NSelect |
| | | v-model:value="formModel.city" |
| | | placeholder="请选择" |
| | | :options="cityList" |
| | | class="w-300px" |
| | | @update:show="(val: boolean) => isCityOpen = val" |
| | | > |
| | | <template #arrow> |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class="arrow-icon" |
| | | :class="{ 'arrow-icon--open': isCityOpen }" |
| | | alt="arrow" |
| | | > |
| | | </template> |
| | | l <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 50%;" |
| | | @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }" |
| | | > |
| | | </div> |
| | | </template> |
| | | </NSelect> |
| | | </n-form-item> |
| | | <n-form-item label="运营区域" path="operationArea" required class="inline-item"> |
| | | <NSelect |
| | | v-model:value="formModel.operationArea" |
| | | placeholder="请选择" |
| | | :options="[]" |
| | | class="w-300px" |
| | | @update:show="(val: boolean) => isOperationAreaOpen = val" |
| | | > |
| | | <template #arrow> |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class="arrow-icon" |
| | | :class="{ 'arrow-icon--open': isOperationAreaOpen }" |
| | | alt="arrow" |
| | | > |
| | | </template> |
| | | <template #empty> |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style="width: 50%;" |
| | | @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }" |
| | | > |
| | | </div> |
| | | </template> |
| | | </NSelect> |
| | | </n-form-item> |
| | | </div> |
| | | <div class="row-group"> |
| | | <n-form-item label="生效时间段" required class="inline-item time-item"> |
| | | <div class="inline-line"> |
| | | <NDatePicker |
| | | v-model:value="formModel.startTime" |
| | | ref="validTimeStartPicker" |
| | | v-model:value="formModel.validTimeStart" |
| | | type="datetime" |
| | | placeholder="请选择" |
| | | class="w-200px" |
| | | :disabled="formModel.longTermEffective" |
| | | class="w-240px" |
| | | :disabled="formModel.validTimeLong" |
| | | :actions="null" |
| | | close-on-select |
| | | format="yyyy/MM/dd HH:mm" |
| | | :time-picker-props="{ |
| | | onConfirm: (val) => { |
| | | formModel.validTimeStart = val |
| | | validTimeStartPicker?.handlePanelClose() |
| | | }, |
| | | }" |
| | | /> |
| | | <span class="text-gray-600 mx-2">至</span> |
| | | <span class="inline-text">至</span> |
| | | <NDatePicker |
| | | v-model:value="formModel.endTime" |
| | | ref="validTimeEndPicker" |
| | | v-model:value="formModel.validTimeEnd" |
| | | type="datetime" |
| | | placeholder="请选择" |
| | | class="w-200px" |
| | | :disabled="formModel.longTermEffective" |
| | | class="w-240px" |
| | | :disabled="formModel.validTimeLong" |
| | | format="yyyy/MM/dd HH:mm" |
| | | :actions="null" |
| | | close-on-select |
| | | :time-picker-props="{ |
| | | onConfirm: (val) => { |
| | | formModel.validTimeEnd = val |
| | | validTimeEndPicker?.handlePanelClose() |
| | | }, |
| | | }" |
| | | /> |
| | | <NCheckbox v-model:checked="formModel.longTermEffective" class="ml-4"> |
| | | <NCheckbox v-model:checked="formModel.validTimeLong" class="ml-4"> |
| | | 长期有效 |
| | | </NCheckbox> |
| | | <span class="status-label"><i>*</i>状态</span> |
| | | <NSwitch v-model:value="formModel.status" :checked-value="1" :unchecked-value="2"> |
| | | <template #checked> |
| | | 启用 |
| | | </template> |
| | | <template #unchecked> |
| | | 禁用 |
| | | </template> |
| | | </NSwitch> |
| | | </div> |
| | | </NFormItemGi> |
| | | </NGrid> |
| | | </n-form-item> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 卡片1:免密支付扣款失败重试策略 --> |
| | |
| | | <div class="head shrink-0"> |
| | | 免密支付扣款失败重试策略 |
| | | </div> |
| | | |
| | | <NGrid :cols="24" :x-gap="24" :y-gap="16" class="mt-4"> |
| | | <NFormItemGi :span="24" label="" :show-label="false"> |
| | | <div class="flex items-center text-[14px] text-gray-800"> |
| | | 最大重试次数 |
| | | <div class="row-group mb-8px"> |
| | | <n-form-item :show-label="false" class="full-row"> |
| | | <div class="inline-line"> |
| | | <span>最大重试次数</span> |
| | | <NInputNumber |
| | | v-model:value="formModel.maxRetryCount" |
| | | min="0" |
| | | v-model:value="formModel.maxRetriesNumber" |
| | | :min="0" |
| | | :show-button="false" |
| | | placeholder="请输入" |
| | | class="mx-2 w-120px" |
| | | class="w-109px" |
| | | /> |
| | | 次,重试间隔 |
| | | <span>次,重试间隔</span> |
| | | <NInputNumber |
| | | v-model:value="formModel.retryInterval" |
| | | min="0" |
| | | v-model:value="formModel.retriesInterval" |
| | | :min="0" |
| | | :show-button="false" |
| | | placeholder="请输入" |
| | | class="mx-2 w-120px" |
| | | class="w-109px" |
| | | /> |
| | | 秒 |
| | | <span>秒</span> |
| | | </div> |
| | | </NFormItemGi> |
| | | |
| | | <NFormItemGi :span="24" label="" :show-label="false"> |
| | | <div class="flex items-center text-[14px] text-gray-800"> |
| | | <span class="mr-4">重试失败后,是否给乘客发送推送通知</span> |
| | | <NSwitch v-model:value="formModel.sendNotification" /> |
| | | </n-form-item> |
| | | </div> |
| | | <div class="row-group"> |
| | | <n-form-item :show-label="false" class="full-row"> |
| | | <div class="inline-line"> |
| | | <span>重试失败后,是否给乘客发送推送通知</span> |
| | | <NSwitch v-model:value="formModel.sendNotification" :unchecked-value="0" :checked-value="1" /> |
| | | </div> |
| | | </NFormItemGi> |
| | | </NGrid> |
| | | </n-form-item> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 卡片2:免密支付 --> |
| | |
| | | <div class="head shrink-0"> |
| | | 免密支付 |
| | | </div> |
| | | |
| | | <NGrid :cols="24" :x-gap="24" :y-gap="16" class="mt-4"> |
| | | <NFormItemGi :span="24" label="" :show-label="false"> |
| | | <div class="flex items-center text-[14px] text-gray-800"> |
| | | 当首泡发单预估金额 与 行程结束实际金额差异大于 |
| | | <div class="row-group mb-8px"> |
| | | <n-form-item :show-label="false" class="full-row"> |
| | | <div class="inline-line"> |
| | | <span>当首泡发单预估金额与行程结束实际金额差异大于</span> |
| | | <NInputNumber |
| | | v-model:value="formModel.priceDifferenceThreshold" |
| | | min="0" |
| | | v-model:value="formModel.differenceRatio" |
| | | :min="0" |
| | | :show-button="false" |
| | | placeholder="请输入" |
| | | class="mx-2 w-120px" |
| | | class="w-109px" |
| | | /> |
| | | %时,乘客端触发二次确认弹窗 |
| | | <span>%时,乘客端触发二次确认弹窗</span> |
| | | </div> |
| | | </NFormItemGi> |
| | | </NGrid> |
| | | </n-form-item> |
| | | </div> |
| | | </div> |
| | | </NForm> |
| | | </div> |
| | |
| | | <NButton class="w-84px" @click="handleCancel"> |
| | | 取消 |
| | | </NButton> |
| | | <NButton v-if="pageType !== 'view'" type="primary" class="w-84px" @click="handleSave"> |
| | | <NButton v-if="pageType !== 'view'" type="primary" class="w-84px" :loading="saving" @click="handleSave"> |
| | | 保存 |
| | | </NButton> |
| | | </NSpace> |
| | |
| | | <style scoped lang="scss"> |
| | | .box { |
| | | padding: 24px; |
| | | padding-bottom: 16px; |
| | | background-color: #fff; |
| | | border-radius: 6px; |
| | | width: 100%; |
| | |
| | | color: #171f26; |
| | | position: relative; |
| | | text-indent: 11px; |
| | | margin-bottom: 8px; |
| | | margin-bottom: 16px; |
| | | |
| | | &::before { |
| | | content: ""; |
| | |
| | | } |
| | | } |
| | | |
| | | .row-group { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .inline-item { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .time-item { |
| | | width: 100%; |
| | | } |
| | | |
| | | .full-row { |
| | | width: 100%; |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .inline-line { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-wrap: wrap; |
| | | color: #171f26; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .status-label { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | margin-left: 8px; |
| | | color: #171f26; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .status-label i { |
| | | font-style: normal; |
| | | color: #ff3a30; |
| | | margin-right: 2px; |
| | | } |
| | | |
| | | .content { |
| | | background: #f5f7fa; |
| | | } |
| | | |
| | | .main { |
| | | gap: 12px; |
| | | } |
| | | |
| | | .arrow-icon { |
| | | width: 16px; |
| | | height: 16px; |
| | | transition: transform 0.2s ease; |
| | | } |
| | | |
| | | .arrow-icon--open { |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .footer { |
| | | position: sticky; |
| | | bottom: 0; |
| | |
| | | box-shadow: 0px 0px 5px 0px rgba(23, 31, 38, 0.05); |
| | | padding: 16px 24px; |
| | | } |
| | | |
| | | :deep(.n-form-item .n-form-item-label) { |
| | | padding-right: 8px; |
| | | } |
| | | </style> |
| | |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { useBoolean } from '@/hooks' |
| | | import { getRegionCascaderNoArea } from '@/service/api/operations' |
| | | import { getPasswordFreePolicyList } from '@/service/api/transaction' |
| | | import { NButton, NTag, useDialog } from 'naive-ui' |
| | | import { useRouter } from 'vue-router' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | |
| | | |
| | | const count = ref(0) |
| | | const listData = ref<any[]>([]) |
| | | const pcaOptions = ref<any[]>([]) |
| | | |
| | | // 获取城市数据 |
| | | function formatCascaderData(list: any[]) { |
| | | return list.map((item) => { |
| | | const newItem = { ...item } |
| | | if (newItem.children && newItem.children.length > 0) { |
| | | newItem.children = formatCascaderData(newItem.children) |
| | | } |
| | | else { |
| | | delete newItem.children |
| | | } |
| | | return newItem |
| | | }) |
| | | } |
| | | |
| | | async function fetchCascaderData() { |
| | | const res = await getRegionCascaderNoArea() |
| | | if (res.code === 200 && res.data) { |
| | | pcaOptions.value = formatCascaderData(res.data) |
| | | } |
| | | } |
| | | |
| | | // 搜索项 |
| | | const filterItems = computed(() => [ |
| | |
| | | render: () => ( |
| | | <n-date-picker |
| | | v-model:value={model.value.createTimeRange} |
| | | type="datetimerange" |
| | | type="daterange" |
| | | clearable |
| | | format="yyyy/MM/dd" |
| | | placeholder="开始时间 - 结束时间" |
| | | style={{ width: '320px' }} |
| | | actions={null} |
| | | close-on-select={true} |
| | | /> |
| | | ), |
| | | }, |
| | |
| | | // 获取列表数据 |
| | | async function getList() { |
| | | startLoading() |
| | | // Mock data simulation |
| | | setTimeout(() => { |
| | | const mockData = Array.from({ length: 10 }).map((_, index) => { |
| | | const isPermanent = index === 1 |
| | | const isEnabled = index !== 1 |
| | | |
| | | // City generation based on selection or random |
| | | let cityName = '成都市 > 金牛区' |
| | | if (model.value.cityName) { |
| | | cityName = `已选城市(${model.value.cityName})` |
| | | } |
| | | |
| | | return { |
| | | id: `mock-id-${pageParams.value.page}-${index}`, |
| | | createTime: '2021/07/29 10:59', |
| | | cityName, |
| | | isPermanent, |
| | | startTime: isPermanent ? null : '2026-3-20 19:35:43', |
| | | endTime: isPermanent ? null : '2026-3-20 19:36:01', |
| | | status: isEnabled ? 1 : 0, |
| | | } |
| | | }) |
| | | |
| | | count.value = 1000 // Mock total count |
| | | listData.value = mockData |
| | | try { |
| | | const params = { |
| | | pageNum: pageParams.value.page, |
| | | pageSize: pageParams.value.size, |
| | | cityName: model.value.cityName?.trim() || undefined, |
| | | // createTimeStart: model.value.createTimeRange?.[0] |
| | | // ? dayjs(model.value.createTimeRange[0]).format('YYYY-MM-DD HH:mm:ss') |
| | | // : undefined, |
| | | // createTimeEnd: model.value.createTimeRange?.[1] |
| | | // ? dayjs(model.value.createTimeRange[1]).format('YYYY-MM-DD HH:mm:ss') |
| | | // : undefined, |
| | | } |
| | | const res = await getPasswordFreePolicyList(params) |
| | | if (res.code === 200) { |
| | | listData.value = res.data?.records || [] |
| | | count.value = res.data?.total ?? 0 |
| | | } |
| | | } |
| | | catch (error) { |
| | | console.error('获取免密支付策略列表失败:', error) |
| | | listData.value = [] |
| | | count.value = 0 |
| | | } |
| | | finally { |
| | | endLoading() |
| | | }, 500) |
| | | } |
| | | } |
| | | |
| | | // 分页变化 |
| | |
| | | key: 'createTime', |
| | | width: 150, |
| | | resizable: true, |
| | | render: row => (row.createTime ? dayjs(row.createTime).format('YYYY/MM/DD HH:mm') : '-'), |
| | | }, |
| | | { |
| | | title: '适用城市', |
| | | key: 'cityName', |
| | | width: 160, |
| | | resizable: true, |
| | | |
| | | }, |
| | | { |
| | | title: '生效时间段', |
| | |
| | | width: 280, |
| | | resizable: true, |
| | | render: (row) => { |
| | | if (row.isPermanent) |
| | | if (row.validTimeLong) |
| | | return '长期有效' |
| | | const start = row.startTime ? dayjs(row.startTime).format('YYYY/MM/DD HH:mm') : '' |
| | | const end = row.endTime ? dayjs(row.endTime).format('YYYY/MM/DD HH:mm') : '' |
| | | const start = row.validTimeStart ? dayjs(row.validTimeStart).format('YYYY/MM/DD HH:mm') : '' |
| | | const end = row.validTimeEnd ? dayjs(row.validTimeEnd).format('YYYY/MM/DD HH:mm') : '' |
| | | if (start && end) |
| | | return `${start} ~ ${end}` |
| | | return '-' |
| | |
| | | width: 100, |
| | | resizable: true, |
| | | render: (row) => { |
| | | const isEnabled = row.status === 1 || row.status === '1' || row.status === true |
| | | const label = isEnabled ? '启用' : '禁用' |
| | | const label = row.status ? '启用' : '禁用' |
| | | return ( |
| | | <NTag |
| | | round |
| | | bordered={false} |
| | | style={{ |
| | | borderRadius: '4px', |
| | | border: 'none', |
| | | color: isEnabled ? '#0BB52D' : '#666666', |
| | | backgroundColor: isEnabled ? '#DCF5DD' : 'rgba(23, 31, 38, 0.05)', |
| | | }} |
| | | type={row.status ? 'success' : 'default'} |
| | | > |
| | | {label} |
| | | </NTag> |
| | |
| | | }) |
| | | } |
| | | onMounted(() => { |
| | | fetchCascaderData() |
| | | getList() |
| | | }) |
| | | </script> |
| | |
| | | <div class="flex items-center gap-12px"> |
| | | <NDatePicker |
| | | v-model:value="formModel.timeRange" |
| | | format="yyyy/MM/dd" |
| | | type="daterange" |
| | | clearable |
| | | start-placeholder="请选择日期" |
| | |
| | | <NDatePicker |
| | | v-model:value="strategy.effectDate" |
| | | type="date" |
| | | format="yyyy/MM/dd" |
| | | clearable |
| | | :actions="null" |
| | | close-on-select |
| | | placeholder="请选择日期" |
| | | style="width: 200px;" |
| | | /> |
| | |
| | | <NDatePicker |
| | | v-model:value="strategy.expireDate" |
| | | type="date" |
| | | format="yyyy/MM/dd" |
| | | clearable |
| | | :actions="null" |
| | | close-on-select |
| | | placeholder="请选择日期" |
| | | style="width: 200px;" |
| | | /> |
| | |
| | | type="daterange" |
| | | clearable |
| | | actions={null} |
| | | format="yyyy/MM/dd" |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | style={{ width: '260px' }} |
| | |
| | | const isOrderSourceOpen = ref(false) |
| | | const isOrderCityOpen = ref(false) |
| | | const isOrderStatusOpen = ref(false) |
| | | const noDataFallbackImg = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' |
| | | |
| | | function renderDropdownEmpty() { |
| | | return ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | onError={(e: any) => { e.target.src = noDataFallbackImg }} |
| | | /> |
| | | </div> |
| | | ) |
| | | } |
| | | |
| | | const fleetOptions = ref<any[]>([]) |
| | | const channelOptions = [{ label: '小鹏小程序', value: 1 }] |
| | |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | empty: () => renderDropdownEmpty(), |
| | | }} |
| | | </NSelect> |
| | | ), |
| | |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | empty: () => renderDropdownEmpty(), |
| | | }} |
| | | </NSelect> |
| | | ), |
| | |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => renderDropdownEmpty(), |
| | | }} |
| | | </NCascader> |
| | | ), |
| | |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src="/src/assets/element-img/no-data.png" |
| | | style={{ width: '50%' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | empty: () => renderDropdownEmpty(), |
| | | }} |
| | | </NSelect> |
| | | ), |
| | |
| | | start-placeholder="开始时间" |
| | | end-placeholder="结束时间" |
| | | style={{ width: '260px' }} |
| | | actions={[]} |
| | | actions={null} |
| | | format="yyyy/MM/dd" |
| | | close-on-select={true} |
| | | > |
| | | {{ |