zjk
2026-04-30 a230729757441cc6a03b666e54e10565a92b8f59
feat(定向服务管理): 优化用户优先级设置与表单交互

- 将用户优先级从手动输入改为拖拽排序,移除优先级设置弹窗
- 在交易策略分单倾向配置中增加默认勾选时间差维度
- 优化日期时间选择器,增加开始/结束时间联动验证
- 统一用户状态标签颜色,正常状态使用 primary 色
- 在免密支付策略表单中增加字段验证和防误触提示
6个文件已修改
383 ■■■■■ 已修改文件
src/views/transaction/directServiceManagement/add.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/directServiceManagement/components/PriorityModal.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/directServiceManagement/components/UserSelectModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/directServiceManagement/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/passwordFreeStrategy/add.vue 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/transactionStrategy/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/directServiceManagement/add.vue
@@ -7,13 +7,13 @@
import { addDirectService, editDirectService, getDirectServiceDetail } from '@/service/api/directService'
import { useTabStore } from '@/store'
import { useBoolean } from '@/hooks'
import PriorityModal from './components/PriorityModal.vue'
import VehicleSelectModal from '@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue'
import CruisePlanSelectModal from './components/CruisePlanSelectModal.vue'
import UserSelectModal from './components/UserSelectModal.vue'
import waitIcon from '@/assets/pricingManagement/wait.png'
import finishIcon from '@/assets/pricingManagement/finish.png'
import activeIcon from '@/assets/pricingManagement/active.png'
import dragIcon from '@/assets/point/union.png'
import dayjs from 'dayjs'
const tabStore = useTabStore()
@@ -53,11 +53,43 @@
const pcaOptions = ref<any[]>([])
function isEndDateDisabled(current: number) {
  if (!formModel.value.startTime)
    return false
  return dayjs(current).isBefore(dayjs(formModel.value.startTime), 'day')
}
function isEndTimeDisabled(current: number) {
  const emptyDisabledConfig = {
    isHourDisabled: () => false,
    isMinuteDisabled: () => false,
    isSecondDisabled: () => false,
  }
  if (!formModel.value.startTime)
    return emptyDisabledConfig
  const start = dayjs(formModel.value.startTime)
  const currentDay = dayjs(current)
  if (!currentDay.isSame(start, 'day'))
    return emptyDisabledConfig
  const startHour = start.hour()
  const startMinute = start.minute()
  const startSecond = start.second()
  return {
    isHourDisabled: (hour: number) => hour < startHour,
    isMinuteDisabled: (minute: number, hour: number) => hour === startHour && minute < startMinute,
    isSecondDisabled: (second: number, minute: number, hour: number) => hour === startHour && minute === startMinute && second < startSecond,
  }
}
// 关联车辆数据和表格配置
const selectedVehicles = ref<any[]>([])
// 服务用户数据和表格配置
const selectedUsers = ref<any[]>([])
const draggedUserId = ref<number | string | null>(null)
// 获取城市数据
let parentIdCounter = 0
@@ -145,12 +177,11 @@
        id: item.whitelistId,
        account: item.account,
        nickName: item.nickName,
        priority: item.weight ?? 0,
        desc: item.remarks || '',
        remarks: item.remarks || '',
        // 详情接口未返回用户状态时,默认按正常展示
        status: item.status ?? 1,
      }))
      selectedUsers.value = sortUsersByPriorityDesc(users)
      selectedUsers.value = users
    }
    else {
      message.error(res.msg || '获取定向服务详情失败')
@@ -193,7 +224,13 @@
])
const userColumns = computed<DataTableColumns<any>>(() => [
  { title: '序号', key: 'index', width: 60, render: (_, index) => index + 1, resizable: true },
  {
    title: '排序',
    key: 'sort',
    width: 60,
    render: () => <img src={dragIcon} alt="拖动排序" style="width: 15px; height: 15px" />,
  },
  // { title: '序号', key: 'index', width: 60, render: (_, index) => index + 1, resizable: true },
  { title: '用户名称', key: 'nickName', resizable: true },
  { title: '白名单账号', key: 'account', resizable: true },
  {
@@ -204,24 +241,21 @@
      return (
        <NTag
          bordered={false}
          type={row.status ? 'success' : 'warning'}
          type={row.status ? 'primary' : 'warning'}
        >
          {row.status ? '正常' : '冻结'}
        </NTag>
      )
    },
  },
  { title: '用户优先级', key: 'priority', resizable: true },
  { title: '优先级说明', key: 'desc', resizable: true },
  {
    title: '操作',
    key: 'actions',
    width: 130,
    width: 80,
    render: (row) => {
      return (
        <div class="flex items-center gap-2">
          <NButton text type="primary" onClick={() => handleRemoveUser(row)}>移除</NButton>
          <NButton text type="primary" onClick={() => handleOpenPriorityModal(row)}>设置优先级</NButton>
        </div>
      )
    },
@@ -229,17 +263,11 @@
])
// 弹窗相关逻辑已抽离到单独组件
const { bool: priorityModalVisible, setTrue: showPriorityModal } = useBoolean(false)
const { bool: vehicleSelectModalVisible, setTrue: showVehicleModal } = useBoolean(false)
const { bool: cruisePlanModalVisible, setTrue: showCruisePlanModal } = useBoolean(false)
const { bool: userSelectModalVisible, setTrue: showUserModal } = useBoolean(false)
const currentEditVehicle = ref<any>(null) // null 表示批量设置,否则为单条设置
const currentEditUser = ref<any>(null)
function sortUsersByPriorityDesc(list: any[]) {
  return [...list].sort((a, b) => Number(b?.priority ?? 0) - Number(a?.priority ?? 0))
}
function handleOpenVehicleSelectModal() {
  showVehicleModal()
@@ -257,11 +285,9 @@
  if (newUsers.length > 0) {
    const mergedUsers = [...(selectedUsers.value || []), ...newUsers.map(u => ({
      ...u,
      priority: 50, // 默认赋予优先级 50
      desc: '', // 默认说明
      remarks: u.remarks || '',
    }))]
    // 用户列表默认按优先级降序排序
    selectedUsers.value = sortUsersByPriorityDesc(mergedUsers)
    selectedUsers.value = mergedUsers
  }
}
@@ -296,16 +322,44 @@
  }
}
function handleOpenPriorityModal(row: any) {
  currentEditUser.value = row
  showPriorityModal()
function handleUserDragStart(row: any) {
  draggedUserId.value = row.id
}
function submitPriority(priority: number) {
  if (currentEditUser.value) {
    currentEditUser.value.priority = priority
    // 修改优先级后立即重排,刷新列表顺序
    selectedUsers.value = sortUsersByPriorityDesc(selectedUsers.value)
function handleUserDragOver(e: DragEvent) {
  e.preventDefault()
}
function handleUserDrop(targetRow: any) {
  if (draggedUserId.value === null)
    return
  if (draggedUserId.value === targetRow.id) {
    draggedUserId.value = null
    return
  }
  const sourceIndex = selectedUsers.value.findIndex(item => item.id === draggedUserId.value)
  const targetIndex = selectedUsers.value.findIndex(item => item.id === targetRow.id)
  if (sourceIndex === -1 || targetIndex === -1) {
    draggedUserId.value = null
    return
  }
  const nextList = [...selectedUsers.value]
  const [moved] = nextList.splice(sourceIndex, 1)
  nextList.splice(targetIndex, 0, moved)
  selectedUsers.value = nextList
  draggedUserId.value = null
}
function getUserRowProps(row: any) {
  return {
    draggable: true,
    style: 'cursor: move;',
    onDragstart: () => handleUserDragStart(row),
    onDragover: (e: DragEvent) => handleUserDragOver(e),
    onDrop: () => handleUserDrop(row),
    onDragend: () => {
      draggedUserId.value = null
    },
  }
}
@@ -493,9 +547,10 @@
    whitelistList: selectedUsers.value.map((item: any) => ({
      account: item.account || '',
      nickName: item.nickName || item.userName || '',
      remarks: item.desc || '',
      remarks: item.remarks || '',
      targetedServiceId: 0,
      weight: item.priority || 0,
      // 按当前拖拽后的顺序传权重,越靠前权重越高
      weight: selectedUsers.value.length - selectedUsers.value.findIndex(user => user.id === item.id),
      whitelistId: item.id || 0,
    })),
  }
@@ -607,16 +662,25 @@
                    :actions="null" :time-picker-props="{
                      onConfirm: (val) => {
                        formModel.startTime = val
                        if (formModel.endTime && val && dayjs(formModel.endTime).isBefore(dayjs(val))) {
                          formModel.endTime = null
                        }
                        startTimePicker?.handlePanelClose()
                      },
                    }" :disabled="formModel.isLongValid === 1" format="yyyy/MM/dd HH:mm" placeholder="请选择开始时间" class="max-w-[200px]"
                  />
                  <span class="text-gray-400 shrink-0">至</span>
                  <span style="color: rgba(23, 31, 38, 0.8);">至</span>
                  <NDatePicker
                    ref="endTimePicker" v-model:value="formModel.endTime" type="datetime" clearable
                    :actions="null" :disabled="formModel.isLongValid === 1" format="yyyy/MM/dd HH:mm" placeholder="请选择结束时间" class="max-w-[200px]"
                    :is-date-disabled="isEndDateDisabled"
                    :is-time-disabled="isEndTimeDisabled"
                    :time-picker-props="{
                      onConfirm: (val) => {
                        if (formModel.startTime && val && dayjs(val).isBefore(dayjs(formModel.startTime))) {
                          message.warning('结束时间不能小于开始时间')
                          return
                        }
                        formModel.endTime = val
                        endTimePicker?.handlePanelClose()
                      },
@@ -660,9 +724,20 @@
          </div>
        </div>
        <NDataTable
          :columns="vehicleColumns" :data="selectedVehicles" :bordered="false" :single-line="false"
          :columns="vehicleColumns" :data="selectedVehicles" :bordered="false"
          flex-height class="flex-1"
        />
          :pagination="false"
        >
          <template #empty>
            <div class="flex-center">
              <img
                src="/src/assets/element-img/no-data.png"
                style="width: 123px; height: 125px"
                @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
              >
            </div>
          </template>
        </NDataTable>
      </div>
      <!-- 服务用户卡片 -->
@@ -679,9 +754,19 @@
          </NButton>
        </div>
        <NDataTable
          :columns="userColumns" :data="selectedUsers" :bordered="false" :single-line="false" flex-height
          class="flex-1"
        />
          :columns="userColumns" :data="selectedUsers" :bordered="false" flex-height
          class="flex-1" :row-key="(row: any) => row.id" :row-props="getUserRowProps"
        >
          <template #empty>
            <div class="flex-center">
              <img
                src="/src/assets/element-img/no-data.png"
                style="width: 123px; height: 125px"
                @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
              >
            </div>
          </template>
        </NDataTable>
      </div>
    </div>
@@ -695,15 +780,10 @@
          上一步
        </NButton>
        <NButton type="primary" class="w-84px" :loading="saving" :disabled="isNextDisabled" @click="handleNext">
          {{ currentStep === 3 ? '完成' : '下一步' }}
          {{ currentStep === 3 ? '保存' : '下一步' }}
        </NButton>
      </NSpace>
    </div>
    <PriorityModal
      v-model:visible="priorityModalVisible" :priority="currentEditUser?.priority ?? null"
      @confirm="submitPriority"
    />
    <VehicleSelectModal
      v-model:visible="vehicleSelectModalVisible" :existing-vehicles="selectedVehicles"
@@ -784,12 +864,11 @@
  width: 160px;
  height: 2px;
  margin: 0 8px;
  background-color: #B6D7FF;
  background-color: rgba(23, 31, 38, 0.1);
}
.step-line--active {
  background-color: #1D8CFF;
  background-color: #0068FF;
}
.head {
src/views/transaction/directServiceManagement/components/PriorityModal.vue
@@ -20,12 +20,6 @@
const rules: FormRules = {
  priority: [
    {
      required: true,
      type: 'number',
      message: '请输入优先权重值(1-100)',
      trigger: ['blur', 'change'],
    },
    {
      validator: (_, value: number | null) => {
        if (value === null || value === undefined)
          return new Error('请输入优先权重值(1-100)')
src/views/transaction/directServiceManagement/components/UserSelectModal.vue
@@ -88,7 +88,7 @@
    render: (row) => {
      return (
        <NTag
          type={row.status ? 'success' : 'warning'}
          type={row.status ? 'primary' : 'warning'}
          bordered={false}
        >
          {row.status ? '正常' : '冻结'}
src/views/transaction/directServiceManagement/index.vue
@@ -156,8 +156,15 @@
        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') : ''
      if (start && end)
        return `${start} ~ ${end}`
      if (start && end) {
        return (
          <span>
            {start}
            <span class="text-[#A1A8AE] px-4px">至</span>
            {end}
          </span>
        )
      }
      return '-'
    },
  },
@@ -184,7 +191,7 @@
      return (
        <NTag
          bordered={false}
          type={isEnabled ? 'success' : 'warning'}
          type={isEnabled ? 'success' : 'default'}
        >
          {label}
        </NTag>
@@ -380,3 +387,9 @@
    </n-card>
  </div>
</template>
<style scoped>
:deep(.__switch-asbddp-m) {
  --n-box-shadow-focus: none;
}
</style>
src/views/transaction/passwordFreeStrategy/add.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import { onMounted, ref, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NButton, NCheckbox, NDatePicker, NForm, NInputNumber, NSelect, NSpace, NSwitch, useMessage } from 'naive-ui'
import { NButton, NCheckbox, NDatePicker, NForm, NInputNumber, NSelect, NSpace, NSwitch, useDialog, useMessage } from 'naive-ui'
import type { FormItemRule } from 'naive-ui'
import dayjs from 'dayjs'
import { addPasswordFreePolicy, editPasswordFreePolicy, getCityList, getFenceListByCityId, getPasswordFreePolicyDetail } from '@/service/api/transaction'
@@ -11,6 +11,7 @@
const router = useRouter()
const route = useRoute()
const message = useMessage()
const dialog = useDialog()
const id = ref<string | null>(null)
const pageType = ref<'add' | 'edit' | 'view'>('add')
@@ -18,12 +19,11 @@
const saving = ref(false)
const isCityOpen = ref(false)
const isOperationAreaOpen = ref(false)
const initialFormSnapshot = ref('')
const formModel = ref({
  cityCode: null as string | number | null,
  cityName: null as string | number | null | null,
  areaId: null as string | number | null,
  areaName: null as string | number | null | null,
  status: 1,
  validTimeStart: null as number | null,
  validTimeEnd: null as number | null,
@@ -37,6 +37,30 @@
const rules = {
  cityCode: { required: true, message: '请选择城市', trigger: ['blur', 'change'] },
  areaId: { required: true, type: 'number', message: '请选择运营区域', trigger: ['blur', 'change'] } as FormItemRule,
  maxRetriesNumber: {
    validator: (_: FormItemRule, value: number | null) => {
      if (value === null || value === undefined || !Number.isInteger(value) || value < 1 || value > 10)
        return new Error('请输入有效的最大重试次数(1-10 的正整数)')
      return true
    },
    trigger: ['input', 'blur', 'change'],
  } as FormItemRule,
  retriesInterval: {
    validator: (_: FormItemRule, value: number | null) => {
      if (value === null || value === undefined || !Number.isInteger(value) || value <= 0)
        return new Error('请输入有效的重试间隔(正整数,单位:秒)')
      return true
    },
    trigger: ['input', 'blur', 'change'],
  } as FormItemRule,
  differenceRatio: {
    validator: (_: FormItemRule, value: number | null) => {
      if (value === null || value === undefined || !Number.isInteger(value) || value < 0 || value > 1000)
        return new Error('请输入有效的金额差异阈值(0-1000 的整数)')
      return true
    },
    trigger: ['input', 'blur', 'change'],
  } as FormItemRule,
}
const formRef = ref<any>(null)
@@ -45,10 +69,66 @@
const operationAreaOptions = ref<any[]>([])
const cityList = ref<any[]>([])
const hasFormChanged = computed(() => JSON.stringify(formModel.value) !== initialFormSnapshot.value)
const isSaveDisabled = computed(() => {
  if (pageType.value === 'view')
    return false
  if (!formModel.value.cityCode || !formModel.value.areaId)
    return true
  if (!formModel.value.validTimeLong) {
    if (!formModel.value.validTimeStart || !formModel.value.validTimeEnd)
      return true
    if (dayjs(formModel.value.validTimeEnd).isBefore(dayjs(formModel.value.validTimeStart)))
      return true
  }
  if (formModel.value.maxRetriesNumber === null || !Number.isInteger(formModel.value.maxRetriesNumber) || formModel.value.maxRetriesNumber < 1 || formModel.value.maxRetriesNumber > 10)
    return true
  if (formModel.value.retriesInterval === null || !Number.isInteger(formModel.value.retriesInterval) || formModel.value.retriesInterval <= 0)
    return true
  if (formModel.value.differenceRatio === null || !Number.isInteger(formModel.value.differenceRatio) || formModel.value.differenceRatio < 0 || formModel.value.differenceRatio > 1000)
    return true
  return false
})
function isValidEndDateDisabled(current: number) {
  if (!formModel.value.validTimeStart)
    return false
  return dayjs(current).isBefore(dayjs(formModel.value.validTimeStart), 'day')
}
function snapshotFormState() {
  initialFormSnapshot.value = JSON.stringify(formModel.value)
}
function isValidEndTimeDisabled(current: number) {
  const emptyDisabledConfig = {
    isHourDisabled: () => false,
    isMinuteDisabled: () => false,
    isSecondDisabled: () => false,
  }
  if (!formModel.value.validTimeStart)
    return emptyDisabledConfig
  const start = dayjs(formModel.value.validTimeStart)
  const currentDay = dayjs(current)
  if (!currentDay.isSame(start, 'day'))
    return emptyDisabledConfig
  const startHour = start.hour()
  const startMinute = start.minute()
  const startSecond = start.second()
  return {
    isHourDisabled: (hour: number) => hour < startHour,
    isMinuteDisabled: (minute: number, hour: number) => hour === startHour && minute < startMinute,
    isSecondDisabled: (second: number, minute: number, hour: number) => hour === startHour && minute === startMinute && second < startSecond,
  }
}
async function getOperationAreaOptions(cityCode: string | number) {
  try {
    const res = await getFenceListByCityId({ cityCode })
    const res = await getFenceListByCityId({ cityCode, statusList: [2] })
    if (res.code === 200) {
      const rawList = res.data?.records || res.data?.list || res.data || []
      const list = Array.isArray(rawList) ? rawList : []
@@ -59,12 +139,10 @@
      return
    }
    operationAreaOptions.value = []
    message.warning(res.msg || '获取运营区域失败')
  }
  catch (error) {
    console.error('获取运营区域失败:', error)
    operationAreaOptions.value = []
    message.error('请求运营区域失败')
  }
}
@@ -77,10 +155,8 @@
    if (res.code === 200 && res.data) {
      const data = res.data
      formModel.value = {
        cityCode: data.cityCode ?? data.cityCode ?? data.cityId ?? null,
        cityName: data.cityName ?? null,
        cityCode: data.cityCode ?? data.cityId ?? null,
        areaId: data.areaId ?? data.operationAreaId ?? null,
        areaName: data.areaName ?? null,
        status: Number(data.status ?? 1),
        validTimeStart: data.validTimeStart ? dayjs(data.validTimeStart).valueOf() : null,
        validTimeEnd: data.validTimeEnd ? dayjs(data.validTimeEnd).valueOf() : null,
@@ -92,16 +168,14 @@
      }
      if (formModel.value.cityCode !== null && formModel.value.cityCode !== undefined) {
        await getOperationAreaOptions(formModel.value.cityCode)
        formModel.value.areaName = data.areaName ?? null
        formModel.value.areaId = data.areaId ? Number(data.areaId) : null
        const areaId = data.areaId ?? data.operationAreaId
        formModel.value.areaId = areaId ? Number(areaId) : null
      }
      return
      snapshotFormState()
    }
    message.warning(res.msg || '获取策略详情失败')
  }
  catch (error) {
    console.error('获取策略详情失败:', error)
    message.error('请求策略详情失败')
  }
  finally {
    loading.value = false
@@ -109,12 +183,25 @@
}
function handleCancel() {
  tabStore.closeTab(route.fullPath)
  router.back()
  if (!hasFormChanged.value) {
    tabStore.closeTab(route.fullPath)
    router.back()
    return
  }
  dialog.warning({
    title: '提示',
    content: '是否放弃当前编辑的免密支付策略?',
    positiveText: '放弃',
    negativeText: '继续编辑',
    onPositiveClick: () => {
      tabStore.closeTab(route.fullPath)
      router.back()
    },
  })
}
async function handleSave() {
  if (saving.value)
  if (saving.value || isSaveDisabled.value)
    return
  try {
    await formRef.value?.validate()
@@ -144,7 +231,8 @@
    if (res.code === 200) {
      message.success('保存成功')
      window.sessionStorage.setItem('passwordFreeStrategyNeedRefresh', '1')
      handleCancel()
      tabStore.closeTab(route.fullPath)
      router.back()
    }
  }
  catch (error) {
@@ -182,7 +270,9 @@
    id.value = route.query.id as string
    pageType.value = route.query.type === 'view' ? 'view' : 'edit'
    fetchDetail()
    return
  }
  snapshotFormState()
})
</script>
@@ -275,6 +365,9 @@
                  :time-picker-props="{
                    onConfirm: (val) => {
                      formModel.validTimeStart = val
                      if (formModel.validTimeEnd && val && dayjs(formModel.validTimeEnd).isBefore(dayjs(val))) {
                        formModel.validTimeEnd = null
                      }
                      validTimeStartPicker?.handlePanelClose()
                    },
                  }"
@@ -290,14 +383,20 @@
                  format="yyyy/MM/dd HH:mm"
                  :actions="null"
                  close-on-select
                  :is-date-disabled="isValidEndDateDisabled"
                  :is-time-disabled="isValidEndTimeDisabled"
                  :time-picker-props="{
                    onConfirm: (val) => {
                      if (formModel.validTimeStart && val && dayjs(val).isBefore(dayjs(formModel.validTimeStart))) {
                        message.warning('结束时间不能小于开始时间')
                        return
                      }
                      formModel.validTimeEnd = val
                      validTimeEndPicker?.handlePanelClose()
                    },
                  }"
                />
                <NCheckbox v-model:checked="formModel.validTimeLong" class="ml-4">
                <NCheckbox v-model:checked="formModel.validTimeLong">
                  长期有效
                </NCheckbox>
                <span class="status-label"><i>*</i>状态</span>
@@ -319,21 +418,27 @@
          <div class="head shrink-0">
            免密支付扣款失败重试策略
          </div>
          <div class="row-group mb-8px">
            <n-form-item :show-label="false" class="full-row">
          <div class="row-group gap-x-8px!  mb-8px flex items-center">
            <n-form-item path="maxRetriesNumber" label="最大重试次数" required>
              <div class="inline-line">
                <span>最大重试次数</span>
                <NInputNumber
                  v-model:value="formModel.maxRetriesNumber"
                  :min="0"
                  :min="1"
                  :max="10"
                  :precision="0"
                  :show-button="false"
                  placeholder="请输入"
                  class="w-109px"
                />
                <span>次,重试间隔</span>
              </div>
            </n-form-item>
            <n-form-item :show-label="false" class="inline-nested-item" path="retriesInterval">
              <div class="flex items-center gap-8px">
                <NInputNumber
                  v-model:value="formModel.retriesInterval"
                  :min="0"
                  :min="1"
                  :precision="0"
                  :show-button="false"
                  placeholder="请输入"
                  class="w-109px"
@@ -358,12 +463,13 @@
            免密支付
          </div>
          <div class="row-group mb-8px">
            <n-form-item :show-label="false" class="full-row">
            <n-form-item label="当首泡发单预估金额与行程结束实际金额差异大于" class="full-row" path="differenceRatio" required>
              <div class="inline-line">
                <span>当首泡发单预估金额与行程结束实际金额差异大于</span>
                <NInputNumber
                  v-model:value="formModel.differenceRatio"
                  :min="0"
                  :max="1000"
                  :precision="0"
                  :show-button="false"
                  placeholder="请输入"
                  class="w-109px"
@@ -382,7 +488,7 @@
        <NButton class="w-84px" @click="handleCancel">
          取消
        </NButton>
        <NButton v-if="pageType !== 'view'" type="primary" class="w-84px" :loading="saving" @click="handleSave">
        <NButton v-if="pageType !== 'view'" type="primary" class="w-84px" :loading="saving" :disabled="isSaveDisabled" @click="handleSave">
          保存
        </NButton>
      </NSpace>
@@ -429,6 +535,7 @@
  display: flex;
  align-items: flex-start;
  gap: 16px;
  font-size: 13px;
}
.inline-item {
@@ -450,7 +557,6 @@
  gap: 8px;
  flex-wrap: wrap;
  color: #171f26;
  font-size: 14px;
}
.status-label {
@@ -458,7 +564,6 @@
  align-items: center;
  margin-left: 8px;
  color: #171f26;
  font-size: 14px;
}
.status-label i {
src/views/transaction/transactionStrategy/index.vue
@@ -45,8 +45,8 @@
  prepaidLimit: null, // 预付额度 订单大于金额需预付
  ruleType: 1, // 规则类型
  simultaneouslyOrderCount: null, // 同时抢单人数
  tendencyPreference: 'orderValue', // 分单倾向配置单选,默认订单价值
  tendencyOrderValueDims: ['timeDifference'], // 订单价值维度的多选
  tendencyPreference: 'tendencyOrderValue', // 分单倾向配置单选,默认订单价值
  tendencyOrderValueDims: ['weightTimeDifference'], // 订单价值维度的多选,默认勾选时间差
  // 连环派单策略相关字段
  chainDispatchMileageThreshold: null, // 连环派单里程阈值
  chainDispatchTimeThreshold: null, // 连环派单时间阈值
@@ -112,6 +112,20 @@
function handleCancel() {
  tabStore.closeTab(route.fullPath)
  router.back()
}
function ensureDefaultOrderValueDim() {
  if (
    model.value.tendencyPreference === 'tendencyOrderValue'
    && (!Array.isArray(model.value.tendencyOrderValueDims) || model.value.tendencyOrderValueDims.length === 0)
  ) {
    model.value.tendencyOrderValueDims = ['weightTimeDifference']
  }
}
function handleTendencyPreferenceChange(value: string) {
  model.value.tendencyPreference = value
  ensureDefaultOrderValueDim()
}
async function handleSubmit() {
@@ -232,6 +246,8 @@
        model.value.tendencyOrderValueDims.push('weightTime')
      if (model.value.weightElectricity === 10)
        model.value.tendencyOrderValueDims.push('weightElectricity')
      ensureDefaultOrderValueDim()
    }
  })
}
@@ -545,7 +561,7 @@
            <NSpace :size="[24, 16]">
              <NSpace vertical :size="8">
                <n-form-item label="分单倾向配置" path="tendencyPreference" required>
                  <NRadioGroup v-model:value="model.tendencyPreference">
                  <NRadioGroup :value="model.tendencyPreference" @update:value="handleTendencyPreferenceChange">
                    <NSpace>
                      <NRadio value="tendencyOrderValue">
                        订单价值