zjk
2026-02-26 772fc6cca620479e3db71f41ef4bcfbe5fb1f494
feat: 班次管理接口对接、订单列表样式调整、交易策略样式调整
9个文件已修改
888 ■■■■■ 已修改文件
src/components/custom/SearchPanel.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/attendance.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/transaction.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/address/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/shift/components/ShiftFormModal.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/shift/index.vue 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/index.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/transactionStrategy/index.vue 402 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/custom/SearchPanel.vue
@@ -6,7 +6,6 @@
      <!-- 操作区 -->
      <n-gi suffix>
        <div class="search-panel__actions">
          <slot name="action" />
          <!-- 展开 / 收起 -->
          <n-button v-if="showToggle" text size="small" @click="toggle">
            <template #icon>
@@ -15,6 +14,7 @@
            </template>
            {{ collapsed ? t('common.expand') : t('common.collapse') }}
          </n-button>
          <slot name="action" />
        </div>
      </n-gi>
    </n-grid>
src/router/routes.static.ts
@@ -616,6 +616,7 @@
    componentPath: '/transaction/transactionStrategy/index.vue',
    id: 605,
    pid: 6,
    fullScreen: true,
  },
  {
    name: 'navigationAndRoutePlanning',
src/service/api/attendance.ts
@@ -48,5 +48,5 @@
// 删除班次
export function deleteShift(id: any) {
  return request.Delete<Service.ResponseResult<any>>('/maintenance/xp-shift/api/v1/shift/deleteById', { id })
  return request.Delete<Service.ResponseResult<any>>('/maintenance/xp-shift/api/v1/shift/deleteById?id=' + id)
}
src/service/api/transaction.ts
@@ -14,7 +14,10 @@
// 导出订单Excel
export function exportOrderExcel(params: any) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/exportExcel', params)
  // return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/exportExcel', params, { responseType: 'blob' })
  const method = request.Post<Service.ResponseResult<any>>('/order/api/v1/order/exportExcel', params, { responseType: 'blob' })
  method.meta = { ...method.meta, isBlob: true }
  return method
}
// 改派订单
@@ -28,8 +31,13 @@
}
// 删除订单
export function deleteOrder(params: any) {
  return request.Delete<Service.ResponseResult<any>>('/order/api/v1/order/deleteById', params)
export function deleteOrder(id: any) {
  return request.Delete<Service.ResponseResult<any>>('/order/api/v1/order/deleteById?id=' + id)
}
// 获取车队不分页列表
export function getFleetList() {
  return request.Post<Service.ResponseResult<any>>('/vehicleConvoy/api/v1/fleet/list')
}
/** 订单评价 */
src/views/operations/attendance/address/index.vue
@@ -54,7 +54,8 @@
  {
    title: '打卡地',
    key: 'locationAddress',
    resizable: true
    resizable: true,
    minWidth: 200
  },
  {
    title: '状态',
@@ -80,7 +81,7 @@
    width: 150,
    render: (row) => {
      return (
        <NSpace justify="center">
        <NSpace>
          <NButton
            text
            type='primary'
@@ -182,6 +183,7 @@
    endLoading()
  })
}
onMounted(() => {
  getList()
})
@@ -191,7 +193,6 @@
  pageParams.value.pageSize = size
  getList()
}
</script>
<template>
src/views/operations/attendance/shift/components/ShiftFormModal.vue
@@ -3,11 +3,11 @@
import type { FormInst, FormRules } from 'naive-ui'
interface FormModel {
  name: string
  color: string
  shiftName: string
  shiftColor: string
  startTime: string | null
  endTime: string | null
  hasRest: boolean
  isRest: boolean
  restStartTime: string | null
  restEndTime: string | null
}
@@ -29,11 +29,11 @@
const formRef = ref<FormInst | null>(null)
const formModel = ref<FormModel>({
  name: '',
  color: '#DE868F',
  shiftName: '',
  shiftColor: '#DE868F',
  startTime: null,
  endTime: null,
  hasRest: false,
  isRest: false,
  restStartTime: null,
  restEndTime: null
})
@@ -48,10 +48,10 @@
]
const rules: FormRules = {
  name: [
  shiftName: [
    { required: true, message: '请输入班次名称', trigger: 'blur' }
  ],
  color: [
  shiftColor: [
    { required: true, message: '请选择班次颜色', trigger: 'change' }
  ],
  startTime: [
@@ -61,10 +61,10 @@
    { required: true, message: '请选择结束时间', trigger: 'change' }
  ],
  restStartTime: [
    {
      required: true,
    {
      required: true,
      validator: (_rule: any, value: any) => {
        if (formModel.value.hasRest && !value) {
        if (formModel.value.isRest && !value) {
          return new Error('请选择开始休息时间')
        }
        return true
@@ -73,10 +73,10 @@
    }
  ],
  restEndTime: [
    {
      required: true,
    {
      required: true,
      validator: (_rule: any, value: any) => {
        if (formModel.value.hasRest && !value) {
        if (formModel.value.isRest && !value) {
          return new Error('请选择结束休息时间')
        }
        return true
@@ -104,23 +104,31 @@
const resetForm = () => {
  formModel.value = {
    name: '',
    color: '#DE868F',
    shiftName: '',
    shiftColor: '#DE868F',
    startTime: null,
    endTime: null,
    hasRest: false,
    isRest: false,
    restStartTime: null,
    restEndTime: null
  }
}
const openEdit = (data: any) => {
  formModel.value = { ...data }
}
defineExpose({
  openEdit,
})
</script>
<template>
  <n-modal
    v-model:show="show"
    :mask-closable="false"
    preset="card"
    :title="mode === 'edit' ? '编辑班次' : '新增班次'"
  <n-modal
    v-model:show="show"
    :mask-closable="false"
    preset="card"
    :title="mode === 'edit' ? '编辑班次' : '新增班次'"
    class="w-600px"
    :segmented="{
      content: true,
@@ -134,8 +142,8 @@
      require-mark-placement="left"
      show-require-mark
    >
      <n-form-item label="班次" path="name">
        <n-input v-model:value="formModel.name" placeholder="请输入" clearable />
      <n-form-item label="班次" path="shiftName">
        <n-input v-model:value="formModel.shiftName" placeholder="请输入" clearable />
      </n-form-item>
      <n-form-item label="班次色号" path="color">
@@ -144,11 +152,11 @@
            v-for="color in colorOptions"
            :key="color.value"
            class="w-12 h-12 rounded cursor-pointer relative"
            :style="{ backgroundColor: color.value, border: formModel.color === color.value ? '2px solid #0068FF' : '1px solid #e5e7eb' }"
            @click="formModel.color = color.value"
            :style="{ backgroundColor: color.value, border: formModel.shiftColor === color.value ? '2px solid #0068FF' : '1px solid #e5e7eb' }"
            @click="formModel.shiftColor = color.value"
          >
            <div
              v-if="formModel.color === color.value"
              v-if="formModel.shiftColor === color.value"
              class="absolute top-0 right-0 w-4 h-4 bg-blue-500 rounded-bl flex items-center justify-center"
            >
              <span class="text-white text-xs">✓</span>
@@ -165,11 +173,11 @@
        <n-time-picker v-model:formatted-value="formModel.endTime" format="HH:mm" value-format="HH:mm" placeholder="--:--" clearable style="width: 100%" />
      </n-form-item>
      <n-form-item label="是否中途休息" path="hasRest">
        <n-switch v-model:value="formModel.hasRest" />
      <n-form-item label="是否中途休息" path="isRest">
        <n-switch v-model:value="formModel.isRest" />
      </n-form-item>
      <template v-if="formModel.hasRest">
      <template v-if="formModel.isRest">
        <n-form-item label="开始休息时间" path="restStartTime">
          <n-time-picker v-model:formatted-value="formModel.restStartTime" format="HH:mm" value-format="HH:mm" placeholder="--:--" clearable style="width: 100%" />
        </n-form-item>
src/views/operations/attendance/shift/index.vue
@@ -1,16 +1,12 @@
<script setup lang="tsx">
import { ref, computed, h } from 'vue'
import { useBoolean } from '@/hooks'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NTag } from 'naive-ui'
import { getShiftList, addShift, editShift, deleteShift } from '@/service/api/attendance'
import { NButton, NSpace, NPopconfirm } from 'naive-ui'
import ShiftFormModal from './components/ShiftFormModal.vue'
interface Shift {
  id: number
  name: string
  workTime: string
  restTime: string
  createTime: string
}
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
const shiftName = ref<string | null>(null)
@@ -20,22 +16,13 @@
  { label: '角色1', value: '角色1' }
]
const shiftList = ref<Shift[]>([
  { id: 1, name: '早班', workTime: '9:00-12:00', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 2, name: '中班', workTime: '13:00-17:00', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 3, name: '晚班', workTime: '18:00-22:00', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 4, name: '全天', workTime: '9:00-22:00', restTime: '(12:00-13:00休息)', createTime: '2025-05-07 14:53' },
  { id: 5, name: '全天', workTime: '9:00-12:00', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 6, name: '全天', workTime: '13:00-17:00', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 7, name: '全天', workTime: '9:00-22:00', restTime: '(12:00-13:00休息)', createTime: '2025-05-07 14:53' },
  { id: 8, name: '全天', workTime: '-', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 9, name: '全天', workTime: '-', restTime: '', createTime: '2025-05-07 14:53' },
  { id: 10, name: '全天', workTime: '-', restTime: '', createTime: '2025-05-07 14:53' }
])
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(1000)
const shiftList = ref<any[]>([])
const modalRef = ref()
const total = ref(0)
const pageParams = ref({
  pageNum: 1,
  pageSize: 10,
})
const showFormModal = ref(false)
const formMode = ref<'add' | 'edit'>('add')
@@ -53,120 +40,161 @@
  showFormModal.value = true
}
const handleEdit = (row: Shift) => {
const handleEdit = (row: any) => {
  formMode.value = 'edit'
  showFormModal.value = true
  console.log('编辑', row)
  modalRef.value.openEdit(row)
}
const handleConfirmForm = (data: any) => {
  console.log('保存数据', data)
  if (data.id) {
    editShift(data).then((res) => {
      if (res.code === 200) {
        window.$message.success('编辑成功')
      }
    }).finally(() => {
      getList()
    })
  } else {
    addShift(data).then((res) => {
      if (res.code === 200) {
        window.$message.success('新增成功')
      }
    }).finally(() => {
      getList()
    })
  }
}
const handleDelete = (row: Shift) => {
  console.log('删除', row)
const handleDelete = (id: any) => {
  deleteShift(id).then((res) => {
    if (res.code === 200) {
      window.$message.success('删除成功')
    }
  }).finally(() => {
    getList()
  })
}
const getShiftTagType = (name: string) => {
  if (name === '早班') return { bg: '#DCF5DD', color: '#0BB52D' }
  if (name === '中班') return { bg: '#FFF8E6', color: '#FF9000' }
  if (name === '晚班') return { bg: '#F6F0FF', color: '#764DFF' }
  if (name === '全天') return { bg: '#E6F4FF', color: '#0068FF' }
  return { bg: '#E6F4FF', color: '#0068FF' }
}
const columns = computed<DataTableColumns<Shift>>(() => [
  { title: '序号', key: 'id', width: 80, align: 'center' },
const columns = computed<DataTableColumns<any>>(() => [
  { title: '序号', key: 'serialNumber', width: 158, resizable: true, },
  {
    title: '状态',
    key: 'name',
    width: 120,
    key: 'shiftName',
    width: 256,
    resizable: true,
    render: (row) => {
      const style = getShiftTagType(row.name)
      return h('span', {
        style: {
          display: 'inline-block',
          padding: '2px 12px',
          borderRadius: '4px',
          fontSize: '12px',
          backgroundColor: style.bg,
          color: style.color
          backgroundColor: row.shiftColor + '25',
          color: row.shiftColor
        }
      }, row.name)
      }, row.shiftName)
    }
  },
  {
    title: '工作时间',
    key: 'workTime',
    minWidth: 200,
    resizable: true,
    render: (row) => (
      <span>
        {row.workTime}
        {row.restTime && <span style={{ color: '#0068FF', marginLeft: '4px' }}>{row.restTime}</span>}
        {row.startTime}-{row.endTime}
        {row.isRest && <span style={{ color: '#0068FF', marginLeft: '4px' }}>({row.restStartTime}-{row.restEndTime}休息)</span>}
      </span>
    )
  },
  { title: '添加时间', key: 'createTime', width: 180 },
  { title: '添加时间', key: 'createTime', width: 290, resizable: true, },
  {
    title: '操作',
    key: 'actions',
    width: 150,
    align: 'center',
    width: 134,
    fixed: 'right',
    render: (row) => (
      <div class="flex gap-3 justify-center">
        <NButton text type="primary" onClick={() => handleEdit(row)}>
          编辑
        </NButton>
        <NButton text type="primary" onClick={() => handleDelete(row)}>
          删除
        </NButton>
      </div>
       <NSpace>
          <NButton
            text
            type='primary'
            onClick={() => handleEdit(row)}
          >
            编辑
          </NButton>
          <NPopconfirm onPositiveClick={() => handleDelete(row.id!)}>
            {{
              default: () => '确认删除',
              trigger: () => <NButton text type="error">删除</NButton>,
            }}
          </NPopconfirm>
        </NSpace>
    )
  }
])
async function getList() {
  startLoading()
  const params = {
    ...pageParams.value
  }
  getShiftList(params).then((res) => {
    shiftList.value = res.data.records || []
    shiftList.value.forEach((item: any, index: number) => {
      item.serialNumber = index + 1
    })
    total.value = res.data.total || res.data.records?.length
  }).finally(() => {
    endLoading()
  })
}
onMounted(() => {
  getList()
})
function changePage(page: number, size: number) {
  pageParams.value.pageNum = page
  pageParams.value.pageSize = size
  getList()
}
</script>
<template>
  <div class="h-full flex flex-col">
    <n-card style="border-radius: 6px" content-style="padding: 12px">
      <div class="flex items-center justify-between">
        <div class="flex items-center gap-2">
          <div class="text-sm">角色</div>
          <n-select v-model:value="shiftName" :options="roleOptions" placeholder="请选择" clearable style="width: 269px" />
  <div class="roboTaxi">
    <NSpace vertical class="flex-1">
      <!-- <n-card style="border-radius: 6px" content-style="padding: 12px">
        <div class="flex items-center justify-between">
          <div class="flex items-center gap-2">
            <div class="text-sm">角色</div>
            <n-select v-model:value="shiftName" :options="roleOptions" placeholder="请选择" clearable
              style="width: 269px" />
          </div>
          <div class="flex gap-2">
            <n-button style="width: 84px; height: 32px" @click="handleReset">重置</n-button>
            <n-button type="primary" style="width: 84px; height: 32px" @click="handleSearch">查询</n-button>
          </div>
        </div>
        <div class="flex gap-2">
          <n-button style="width: 84px; height: 32px" @click="handleReset">重置</n-button>
          <n-button type="primary" style="width: 84px; height: 32px" @click="handleSearch">查询</n-button>
        </div>
      </div>
    </n-card>
      </n-card> -->
    <n-card style="border-radius: 6px; margin-top: 12px" content-style="padding: 12px">
      <div class="mb-4 flex justify-end">
        <n-button
          style="height: 32px; background-color: #E6F4FF; color: #0068FF; border: 1px solid #7ABDFF"
          @click="handleAdd">
          新增班次
        </n-button>
      </div>
      <n-card class="flex-1">
        <template #header>
          <n-flex justify="flex-end">
            <NButton type="primary" secondary @click="handleAdd">
              新增班次
            </NButton>
          </n-flex>
        </template>
        <NSpace vertical :size="12">
          <n-data-table :columns="columns" :data="shiftList" :loading="loading" :bordered="false" :header-height="18"
            :row-height="18" />
          <Pagination :count="total" @change="changePage" />
        </NSpace>
      </n-card>
    </NSpace>
      <n-data-table
        :columns="columns"
        :data="shiftList"
        :pagination="{
          page: currentPage,
          pageSize: pageSize,
          itemCount: total,
          showSizePicker: true,
          pageSizes: [10, 20, 50, 100],
          prefix: ({ itemCount }) => `第 ${(currentPage - 1) * pageSize + 1}-${Math.min(currentPage * pageSize, itemCount || 0)} 条 / 总共 ${itemCount || 0} 条`
        }"
        :scroll-x="1000"
      />
    </n-card>
    <ShiftFormModal v-model:show="showFormModal" :mode="formMode" @confirm="handleConfirmForm" />
    <ShiftFormModal v-model:show="showFormModal" ref="modalRef" :mode="formMode" @confirm="handleConfirmForm" />
  </div>
</template>
src/views/transaction/order/orderManagement/index.vue
@@ -1,11 +1,12 @@
<script setup lang="tsx">
import type { DataTableColumns, FormInst } from 'naive-ui'
import type { DataTableColumns, FormInst, DataTableRowKey } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { getOrderList, cancelAndCloseOrder, exportOrderExcel, deleteOrder } from '@/service/api/transaction'
import { getOrderList, cancelAndCloseOrder, exportOrderExcel, deleteOrder, getFleetList } from '@/service/api/transaction'
import { getRegionCascader } from '@/service/api/operations'
import { NButton, NSpace, NTag, useMessage } from 'naive-ui'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import PcaCascader from '@/components/custom/PcaCascader.vue'
import dayjs from 'dayjs'
import { downloadFile } from '@/utils'
import TableModal from './components/TableModal.vue'
import Modal from './components/Modal.vue'
@@ -38,104 +39,110 @@
const modalRef1 = ref()
const formRef = ref<FormInst | null>()
const checkedRowKeysRef = ref<DataTableRowKey[]>([])
const columns = computed<DataTableColumns<any>>(() => [
  {
    type: 'selection',
    width: 40,
    fixed: 'left'
  },
  {
    title: t('transaction.orderNo'),
    align: 'center',
    key: 'orderNumber',
    ellipsis: true,
    resizable: true
    width: 150,
    resizable: true,
    fixed: 'left'
  },
  {
    title: t('transaction.orderTime'),
    align: 'center',
    key: 'createTime',
    ellipsis: true,
    width: 160,
    resizable: true
  },
  {
    title: t('transaction.orderSource'),
    align: 'center',
    key: 'orderSource',
    ellipsis: true,
    width: 100,
    resizable: true,
    render(row) {
      return row.orderSource === 1 ? '小鹏小程序' : '-'
    }
  },
  {
    title: t('transaction.pickupTime'),
    align: 'center',
    key: 'rideTime',
    ellipsis: true,
    width: 160,
    resizable: true
  },
  {
    title: t('transaction.userNickname'),
    align: 'center',
    key: 'nickName',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.userPhone'),
    align: 'center',
    key: 'phoneNumber',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.pickupLocation'),
    align: 'center',
    key: 'startAddress',
    ellipsis: true,
    resizable: true
    width: 160,
    resizable: true,
    ellipsis: {
      tooltip: true
    }
  },
  {
    title: t('transaction.dropoffLocation'),
    align: 'center',
    key: 'endAddress',
    ellipsis: true,
    resizable: true
    width: 160,
    resizable: true,
    ellipsis: {
      tooltip: true
    }
  },
  {
    title: t('transaction.selectedVehicleType'),
    align: 'center',
    key: 'vehicleModel',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.acceptedVehicleType'),
    align: 'center',
    key: 'plateNumber',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.affiliatedFleet'),
    align: 'center',
    key: 'fleetName',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.orderAmount'),
    align: 'center',
    key: 'orderMoney',
    ellipsis: true,
    width: 120,
    resizable: true
  },
  {
    title: t('transaction.status'),
    align: 'center',
    key: 'status',
    ellipsis: true,
    width: 120,
    resizable: true,
    render(row) {
        return h(
          NTag,
          {
            type: row.status === 1 || row.status === 4 ? 'primary' : row.status === 2 || row.status === 5 ? 'success' :  row.status === 3 || row.status === 6 ? 'warning' : 'default',
            type: statusOptions.find(item => item.value === row.status)?.type || 'default',
            style: row.status === 7 ? {
              color: '#764DFF',
              backgroundColor: '#F6F0FF',
            } : {},
            bordered: false
          },
          {
@@ -146,14 +153,12 @@
  },
  {
    title: '操作',
    align: 'center',
    key: 'actions',
    width: 200,
    fixed: 'right',
    render: (row) => {
      console.log(row.status === 1)
      return (
        <NSpace justify="center">
        <NSpace>
          {(row.status === 1 || row.status === 2 || row.status === 3 || row.status === 4) && <NButton
            text
            type="primary"
@@ -239,16 +244,13 @@
                negativeText: '取消',
                draggable: true,
                onPositiveClick: () => {
                  deleteOrder({ id: row.id! }).then(res => {
                  if (res.code === 200) {
                    message.success('删除订单成功')
                    getList()
                  }
                })
                  deleteOrder(row.id!).then(res => {
                    if (res.code === 200) {
                      message.success('删除订单成功')
                      getList()
                    }
                  })
                },
                onNegativeClick: () => {
                  // message.error('取消')
                }
              })
            }}
          >
@@ -260,16 +262,7 @@
  },
])
// 车队
const options = [
  {
    label: '车队1',
    value: 1,
  },
  {
    label: '车队2',
    value: 2,
  },
]
const options = ref<any[]>([])
// 渠道
const chanleOptions = [
  {
@@ -282,40 +275,50 @@
  {
    label: '待接单',
    value: 1,
    type: 'primary',
  },
  {
    label: '待出发',
    value: 2,
    type: 'success',
  },
  {
    label: '待到达预约地点',
    value: 3,
    type: 'warning',
  },
  {
    label: '待乘客上车',
    value: 4,
    type: 'primary',
  },
  {
    label: '服务中',
    value: 5,
    type: 'success',
  },
  {
    label: '待支付',
    value: 6,
    type: 'warning',
  },
  {
    label: '待评价',
    value: 7,
    type: 'default',
  },
  {
    label: '已完成',
    value: 8,
    type: 'default',
  },
  {
    label: '已取消',
    value: 0,
    type: 'default',
  },
]
const pcaOptions = ref<any[]>([])
// 搜索项
const filterItems = computed(() => [
  {
@@ -336,7 +339,7 @@
  {
    label: t('transaction.fleetName'),
    path: 'fleetId',
    render: () => <n-select v-model:value={model.value.fleetId} options={options} />,
    render: () => <n-select v-model:value={model.value.fleetId} options={options.value} />,
  },
  {
    label: t('transaction.channel'),
@@ -345,7 +348,7 @@
  },{
    label: t('transaction.city'),
    path: 'cityCode',
    render: () => <PcaCascader v-model:value={model.value.cityCode}  />,
    render: () => <n-cascader v-model:value={model.value.cityCode} options={pcaOptions.value} value-field="code" label-field="name" check-strategy="all" clearable />,
  },
  {
    label: t('transaction.status'),
@@ -387,7 +390,7 @@
  })
}
const handleExport = () => {
const handleExport =async () => {
  const params = {
    ...pageParams.value,
    status: model.value.status,
@@ -401,16 +404,55 @@
    startTime: model.value.orderTime ? dayjs(model.value.orderTime?.[0]).format('YYYY-MM-DD HH:mm:ss') : null,
    endTime: model.value.orderTime ? dayjs(model.value.orderTime?.[1]).format('YYYY-MM-DD HH:mm:ss') : null,
  }
  exportOrderExcel(params).then(res => {
    if (res.code === 200) {
      message.success('导出订单成功')
  const response = await exportOrderExcel(params)
  if (response instanceof Blob) {
    downloadFile(response, `订单列表_${new Date().getTime()}.xlsx`)
    message.success('导出成功')
  }
}
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 getRegionCascader()
  if (res.code === 200 && res.data) {
    pcaOptions.value = formatCascaderData(res.data)
  }
}
async function fetchFleetList() {
  const res = await getFleetList()
  if (res.code === 200 && res.data) {
    options.value = res.data.map((item: any) => ({
      label: item.fleetName,
      value: item.id,
    }))
  }
}
function rowKey(row: any) {
  return row.id
}
onMounted(() => {
  getList()
  fetchCascaderData()
  fetchFleetList()
})
function handleCheck(rowKeys: DataTableRowKey[]) {
  checkedRowKeysRef.value = rowKeys
}
function changePage(page: number, size: number) {
  pageParams.value.pageNum = page
@@ -420,11 +462,11 @@
</script>
<template>
  <n-flex class="roboTaxi">
    <NSpace vertical class="flex-1">
  <n-flex class="roboTaxi w-full">
    <NSpace vertical class="flex-1 w-full">
      <div class="roboTaxi-form-card">
        <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false">
          <SearchPanel>
          <SearchPanel showToggle>
            <n-gi v-for="item in filterItems" :key="item.path">
              <n-form-item :label="item.label" :path="item.path">
                <component :is="item.render" />
@@ -442,7 +484,7 @@
        </n-form>
      </div>
      <n-card class="flex-1">
      <n-card class="flex-1" >
        <template #header>
          <n-flex justify="flex-start">
            <NButton strong @click="handleExport">
@@ -451,8 +493,8 @@
          </n-flex>
        </template>
        <NSpace vertical>
          <n-data-table :columns="columns" :data="listData" :loading="loading" :bordered="false" :header-height="18"
            :row-height="18" />
          <n-data-table :row-key="rowKey" :scroll-x="1950" :columns="columns" :data="listData" :loading="loading" :bordered="false"
            :header-height="18" :row-height="18" @update:checked-row-keys="handleCheck" />
          <Pagination :count="count" @change="changePage" />
        </NSpace>
      </n-card>
src/views/transaction/transactionStrategy/index.vue
@@ -2,10 +2,15 @@
import { useBoolean } from '@/hooks'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NSpace } from 'naive-ui'
import { useTabStore } from '@/store'
import { editTradingStrategy, getTradingStrategyDetail } from '@/service/api/transaction'
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
const message = useMessage()
const router = useRouter()
const route = useRoute()
const tabStore = useTabStore()
const initialModel:any = {
  cancleCount: null, // 取消订单次数
@@ -69,17 +74,38 @@
const columns = computed<DataTableColumns<any>>(() =>[
  {
    title: '纬度',
    align: 'center',
    title() {
      return h(
          'div',
          {
            style: {
              fontWeight: 'bold',
            }
          },
          { default: () => '纬度' }
        )
    },
    key: 'name',
    resizable: true,
    width: 244,
  },
  {
    title: '权重',
    align: 'center',
    title() {
      return h(
          'div',
          {
            style: {
              fontWeight: 'bold',
            }
          },
          { default: () => '权重' }
        )
    },
    width: 180,
    key: 'value',
    render: (row) => {
      return (
        <n-input-number v-model:value={row.value} min="0" max="10" precision={0} show-button={false} placeholder="请输入1-10" />
        <n-input-number v-model:value={row.value} width={148} min="0" max="10" precision={0} show-button={false} placeholder="请输入1-10" />
      )
    },
  },
@@ -87,17 +113,38 @@
const columns1 = computed<DataTableColumns<any>>(() =>[
  {
    title: '纬度',
    align: 'center',
    title() {
      return h(
          'div',
          {
            style: {
              fontWeight: 'bold',
            }
          },
          { default: () => '纬度' }
        )
    },
    width: 244,
    resizable: true,
    key: 'name'
  },
  {
    title: '权重',
    align: 'center',
    title() {
      return h(
          'div',
          {
            style: {
              fontWeight: 'bold',
            }
          },
          { default: () => '权重' }
        )
    },
    width: 180,
    key: 'value',
    render: (row) => {
      return (
        <n-input-number v-model:value={row.value} min="0" max="10" precision={0} show-button={false} placeholder="请输入1-10" />
        <n-input-number v-model:value={row.value} width={148} min="0" max="10" precision={0} show-button={false} placeholder="请输入1-10" />
      )
    },
  },
@@ -152,6 +199,11 @@
    value: null,
  },
])
function handleCancel() {
  tabStore.closeTab(route.fullPath)
  router.back()
}
async function handleSubmit() {
  startLoading()
@@ -218,150 +270,222 @@
</script>
<template>
  <n-flex class="w-full h-full">
    <n-card>
      <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false"
        require-mark-placement="left">
  <div class="w-full h-full relative content">
    <div class="p-12px">
      <div class="box py-16px! title mb-12px">交易策略配置</div>
      <n-form ref="formRef" :model="model" label-placement="left" :show-feedback="false" require-mark-placement="left">
        <n-spin :show="loading">
          <NSpace vertical :size="16">
            <!-- 发单策略 -->
            <NSpace vertical :size="16">
              <div class="text-base font-bold">发单策略</div>
              <div class="pl-4">
                <n-form-item label="用户取消订单" path="cancleCount" required>
                  <n-tooltip placement="top-start" trigger="hover">
                    <template #trigger>
                      <icon-park-outline-help />
                    </template>
                    自然日(00:00~24:00)内用户取消订单数超过X单后,不播单
                  </n-tooltip>
                </n-form-item>
              </div>
              <div class="pl-20">
                <n-form-item label="用户每日取消订单数" path="cancleCount" required>
                  <n-input-number v-model:value="model.cancleCount" min="0" :precision="0" :show-button="false" />
                </n-form-item>
              </div>
          <!-- 发单策略 -->
          <div class="box mb-12px">
            <div class="head">发单策略</div>
            <n-form-item label="用户每日取消订单数" path="cancleCount" required>
              <n-input-number v-model:value="model.cancleCount" min="0" class="w-272px mr-8px" :precision="0"
                :show-button="false" />
              <n-tooltip placement="top-start" trigger="hover">
                <template #trigger>
                  <icon-park-outline-help color="#0256ff" />
                </template>
                自然日(00:00~24:00)内用户取消订单数超过X单后,不播单
              </n-tooltip>
            </n-form-item>
          </div>
          <!-- 发单拦截策略(1.0做, mvp版本不做) -->
          <div class="box mb-12px" v-if="false">
            <div class="head">发单拦截策略</div>
            <div class="mb-12px text-14px">
              小额豁免配置
            </div>
            <NSpace :size="[24, 16]" style="color: rgba(23, 31, 38, 0.8);">
              <n-form-item label="金额上限" path="smallAmountExemption" label-style="color: rgba(23, 31, 38, 0.8);">
                <n-input-number v-model:value="model.smallAmountExemption" class="w-200px mr-8px" min="0" :precision="0"
                  :show-button="false" />元内可豁免
              </n-form-item>
              <n-form-item label="豁免城市" path="cityList" label-style="color: rgba(23, 31, 38, 0.8);">
                <PcaCascader v-model:value="model.cityList" class="w-200px" />
              </n-form-item>
              <n-form-item label="豁免时效" path="exemptionTime" label-style="color: rgba(23, 31, 38, 0.8);">
                <n-date-picker v-model:value="model.exemptionTime" type="daterange" clearable class="mr-2 w-[320px]" />
                <n-checkbox v-model:checked="model.isLongValid">
                  长期有效
                </n-checkbox>
              </n-form-item>
              <n-form-item label="豁免角色" path="exemptionRole" label-style="color: rgba(23, 31, 38, 0.8);">
                <n-select v-model:value="model.exemptionRole" :options="options" multiple class="w-[200px]" />
              </n-form-item>
            </NSpace>
            <!-- 发单拦截策略(1.0做, mvp版本不做) -->
            <NSpace vertical :size="16" v-if="false">
              <div class="text-base font-bold">发单拦截策略</div>
              <div class="pl-4">
                <n-form-item label="小额豁免配置" path="smallAmountExemption" />
              </div>
              <NSpace vertical class="pl-20" :size="16">
                <n-form-item label="金额上限" path="smallAmountExemption" required>
                  <n-input-number v-model:value="model.smallAmountExemption" min="0" :precision="0" :show-button="false"
                    class="mr-2" />元内可豁免
                </n-form-item>
                <n-form-item label="豁免城市" path="cityList" required>
                  <PcaCascader v-model:value="model.cityList" class="w-[240px]" />
                </n-form-item>
                <n-form-item label="豁免时效" path="exemptionTime" required>
                  <n-date-picker v-model:value="model.exemptionTime" type="daterange" clearable
                    class="mr-2 w-[240px]" />
                  <n-checkbox v-model:checked="model.isLongValid">
                    长期有效
                  </n-checkbox>
                </n-form-item>
                <n-form-item label="豁免角色" path="exemptionRole" required>
                  <n-select v-model:value="model.exemptionRole" :options="options" multiple class="w-[240px]" />
                </n-form-item>
              </NSpace>
              <div class="pl-4">
                <n-form-item label="分级预付配置" path="condition_1" />
              </div>
              <NSpace vertical class="pl-20" :size="16">
                <n-form-item label="小额免密授权" path="condition_1">
                  订单 >
                  <n-input-number v-model:value="model.condition_1" min="0" :show-button="false" class="mx-2" />
                  元,需免密授权
                </n-form-item>
                <n-form-item label="预付" path="condition_1" required>
                  订单>
                  <n-input-number v-model:value="model.condition_1" min="0" :show-button="false" class="mx-2" />
                  元,需预付
                </n-form-item>
              </NSpace>
            <div class="mb-12px text-14px mt-24px">
              分级预付配置
            </div>
            <NSpace :size="[24, 16]" style="color: rgba(23, 31, 38, 0.8);">
              <n-form-item label="小额免密授权" path="condition_1" label-style="color: rgba(23, 31, 38, 0.8);">
                订单 >
                <n-input-number v-model:value="model.condition_1" min="0" :show-button="false" class="mx-2 w-200px" />
                元,需免密授权
              </n-form-item>
              <n-form-item label="预付订单 >" path="condition_1" required label-style="color: rgba(23, 31, 38, 0.8);">
                <n-input-number v-model:value="model.condition_1" min="0" :show-button="false" class="mx-2 w-200px" />
                元,需预付
              </n-form-item>
            </NSpace>
            <!-- 播单策略 -->
            <NSpace vertical :size="16">
              <div class="text-base font-bold">播单策略</div>
              <NSpace vertical class="pl-20" :size="16">
                <n-form-item label="第一轮推单" path="onePushDistance" required>
                  推单距离为
                  <n-input-number v-model:value="model.onePushDistance" min="0" :show-button="false" class="mx-2" />
                  公里,推单时间为
                  <n-input-number v-model:value="model.onePushTime" min="0" :show-button="false" class="mx-2" />
                  秒
                </n-form-item>
                <n-form-item label="第二轮推单" path="twoPushDistance" required>
                  推单距离为
                  <n-input-number v-model:value="model.twoPushDistance" min="0" :show-button="false" class="mx-2" />
                  公里,推单时间为
                  <n-input-number v-model:value="model.twoPushTime" min="0" :show-button="false" class="mx-2" />
                  秒
                </n-form-item>
                <n-form-item label="第三轮推单" path="threePushTime" required>
                  推单距离为
                  <n-input-number v-model:value="model.threePushDistance" min="0" :show-button="false" class="mx-2" />
                  公里,推单时间为
                  <n-input-number v-model:value="model.threePushTime" min="0" :show-button="false" class="mx-2" />
                  秒
                </n-form-item>
                <n-form-item label="第四轮推单" path="fourPushDistance" required>
                  推单距离为
                  <n-input-number v-model:value="model.fourPushDistance" min="0" :show-button="false" class="mx-2" />
                  公里,推单时间为
                  <n-input-number v-model:value="model.fourPushTime" min="0" :show-button="false" class="mx-2" />
                  秒
                </n-form-item>
                <n-form-item label="第五轮推单" path="fivePushDistance" required>
                  推单距离为
                  <n-input-number v-model:value="model.fivePushDistance" min="0" :show-button="false" class="mx-2" />
                  公里,推单时间为
                  <n-input-number v-model:value="model.fivePushTime" min="0" :show-button="false" class="mx-2" />
                  秒
                </n-form-item>
              </NSpace>
          </div>
          <!-- 播单策略 -->
          <div class="box mb-12px">
            <div class="head">播单策略</div>
            <NSpace :size="[24, 16]" style="color: rgba(23, 31, 38, 0.8);">
              <n-form-item label="第一轮推单:" path="onePushDistance" required label-style="color: rgba(23, 31, 38, 0.8);">
                推单距离为
                <n-input-number v-model:value="model.onePushDistance" min="0" :show-button="false"
                  class="mx-2 w-102px" />
                公里,推单时间为
                <n-input-number v-model:value="model.onePushTime" min="0" :show-button="false" class="mx-2 w-102px" />
                秒
              </n-form-item>
              <n-form-item label="第二轮推单:" path="twoPushDistance" required label-style="color: rgba(23, 31, 38, 0.8);">
                推单距离为
                <n-input-number v-model:value="model.twoPushDistance" min="0" :show-button="false"
                  class="mx-2 w-102px" />
                公里,推单时间为
                <n-input-number v-model:value="model.twoPushTime" min="0" :show-button="false" class="mx-2 w-102px" />
                秒
              </n-form-item>
              <n-form-item label="第三轮推单:" path="threePushTime" required label-style="color: rgba(23, 31, 38, 0.8);">
                推单距离为
                <n-input-number v-model:value="model.threePushDistance" min="0" :show-button="false"
                  class="mx-2 w-102px" />
                公里,推单时间为
                <n-input-number v-model:value="model.threePushTime" min="0" :show-button="false" class="mx-2 w-102px" />
                秒
              </n-form-item>
              <n-form-item label="第四轮推单:" path="fourPushDistance" required label-style="color: rgba(23, 31, 38, 0.8);">
                推单距离为
                <n-input-number v-model:value="model.fourPushDistance" min="0" :show-button="false"
                  class="mx-2 w-102px" />
                公里,推单时间为
                <n-input-number v-model:value="model.fourPushTime" min="0" :show-button="false" class="mx-2 w-102px" />
                秒
              </n-form-item>
              <n-form-item label="第五轮推单:" path="fivePushDistance" required label-style="color: rgba(23, 31, 38, 0.8);">
                推单距离为
                <n-input-number v-model:value="model.fivePushDistance" min="0" :show-button="false"
                  class="mx-2 w-102px" />
                公里,推单时间为
                <n-input-number v-model:value="model.fivePushTime" min="0" :show-button="false" class="mx-2 w-102px" />
                秒
              </n-form-item>
            </NSpace>
            <!-- 分单策略 -->
            <NSpace vertical :size="16">
              <div class="text-base font-bold">分单策略</div>
              <NSpace vertical class="pl-20" :size="16">
                <n-form-item label="举手规则配置" path="ruleType" required>
                  <n-radio-group v-model:value="model.ruleType" name="radiobuttongroup1">
                    <n-radio-button :value="1" label="单手举手" />
                    <n-radio-button :value="2" label="多手举手" />
                  </n-radio-group>
                </n-form-item>
                <n-form-item label="最多" path="simultaneouslyOrderCount" required class="pl-20">
                  <n-input-number v-model:value="model.simultaneouslyOrderCount" min="0" :show-button="false"
                    class="mr-2" />
                  可同时抢单
                </n-form-item>
              </NSpace>
              <NSpace vertical class="pl-20" :size="16">
          </div>
          <!-- 分单策略 -->
          <div class="box">
            <div class="head">分单策略</div>
            <NSpace vertical :size="16" style="color: rgba(23, 31, 38, 0.8);" class="mb-24px">
              <n-form-item label="举手规则配置" path="ruleType" required label-style="color: rgba(23, 31, 38, 0.8);">
                <n-radio-group v-model:value="model.ruleType" name="radiobuttongroup1">
                  <n-radio-button :value="1" label="单手举手" />
                  <n-radio-button :value="2" label="多手举手" />
                </n-radio-group>
              </n-form-item>
              <n-form-item label="最多" path="simultaneouslyOrderCount" required
                label-style="color: rgba(23, 31, 38, 0.8);">
                <n-input-number v-model:value="model.simultaneouslyOrderCount" min="0" :show-button="false"
                  class="mr-2 w-218px" />
                可同时抢单
              </n-form-item>
            </NSpace>
            <NSpace :size="[24, 16]">
              <NSpace vertical :size="8">
                <n-form-item label="分单倾向配置" path="splitOrderPreference" required>
                </n-form-item>
                <div class="pl-30 w-[500px]">
                  <n-data-table :columns="columns" :data="data" />
                <div class=" w-[424px]">
                  <n-data-table :columns="columns" :data="data" :bordered="false"/>
                </div>
              </NSpace>
              <NSpace vertical :size="8">
                <n-form-item label="订单价值权重配置" path="orderValueWeight" required>
                </n-form-item>
                <div class="pl-30 w-[500px]">
                  <n-data-table :columns="columns1" :data="data1" />
                <div class=" w-[424px]">
                  <n-data-table :columns="columns1" :data="data1" :bordered="false"/>
                </div>
              </NSpace>
            </NSpace>
          </NSpace>
          </div>
        </n-spin>
      </n-form>
      <NSpace justify="center" class="w-[50%] mt-12">
        <NButton type='primary' @click="handleSubmit">
    </div>
    <div class="footer">
      <NSpace justify="end" :size="8">
        <NButton strong @click="handleCancel" class="w-84px">
          取消
        </NButton>
        <NButton type='primary' @click="handleSubmit" class="w-84px">
          保存
        </NButton>
      </NSpace>
    </n-card>
  </n-flex>
    </div>
  </div>
</template>
<style scoped lang="scss">
.content {
  height: calc(100vh - 76px);
  width: 100%;
  overflow-y: auto;
  scrollbar-width: none;
  &::-webkit-scrollbar {
    display: none;
  }
}
.box {
  padding: 24px;
  background-color: #fff;
  border-radius: 6px;
  width: 100%;
  box-sizing: border-box;
  --n-label-text-color: rgba(23, 31, 38, 0.8);
}
.title {
  font-size: 20px;
  color: #171F26;
  font-weight: 600;
}
.head {
  font-family: PingFang SC;
  font-size: 16px;
  font-weight: 600;
  line-height: 24px;
  letter-spacing: normal;
  color: #171F26;
  position: relative;
  text-indent: 11px;
  margin-bottom: 16px;
  &::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 0;
    transform: translateY(-50%);
    width: 3px;
    height: 14px;
    z-index: 0;
    border-radius: 3px;
    background: #0068FF;
  }
}
.footer {
  position: sticky;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 64px;
  background-color: #fff;
  box-shadow: 0px 0px 5px 0px rgba(23, 31, 38, 0.05);
  padding: 16px 24px;
}
</style>