董国庆
2026-06-24 ef58bb64ff46fca73d806452bc7b5e742c9eb261
修改ui审查
104个文件已修改
1057 ■■■■■ 已修改文件
src/components/common/IconSelect.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/custom/OperationLogModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/common/SettingDrawer.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/header/Search.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/header/UserCenter.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/components/FleetFormModal.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/components/StaffSelectModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/detail.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/components/VehicleMaterialSelectModal.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/inventoryVehicle/add.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/inventoryVehicle/components/CompleteVehicleInfoModal.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/inventoryVehicle/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/InspectionDetailModal.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/MaintenanceDetailModal.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/components/TaskDetailModal.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/operatingVehicle/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleManagement/vehicleMaterial/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleModel/components/AddOrEditModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/vehicleModel/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/parade-plan/add.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/parade-plan/detail.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/parade-plan/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/patrol-points/add.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/patrol-points/components/SelectPointModal.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/patrol-points/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/geofenceManagement/detail.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/geofenceManagement/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/openCity/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/siteInfo/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/index.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/address/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/plan/copy.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/plan/edit.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/plan/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/attendance/shift/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckIn/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckInRule/add.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckInRule/components/DetailModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckInRule/components/SelectPointModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/message/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/personnel/personnelManagement/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/personnel/safetyOfficerInfo/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/policyApplication/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/ticket/feedbackType/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/ticket/workOrder/detail.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/ticket/workOrder/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/permissionGroup/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/roleManagement/components/RoleModal.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/roleManagement/detail.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/roleManagement/form.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/accountPermission/roleManagement/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appVersion/hotfix/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/evaluationTag/index.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/financeBill/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/geofenceManagement/detail.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/geofenceManagement/index.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/operationWorkbench/map.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/specialPointManagement/components/SelectPointModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/add.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/components/AssignVehicleModal.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/components/ManageTestVehicleModal.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/components/OrderGenModeDescModal.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/detail.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanManagement/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPointManagement/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPointManagement/list.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/directServiceManagement/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/components/Modal.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/passwordFreeStrategy/add.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/passwordFreeStrategy/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/pricingManagement/components/TableModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/transactionStrategy/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/activityManagement/add.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/activityManagement/components/CouponSelectModal.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/activityManagement/components/ReceiveRecord.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/activityManagement/components/UserSelectModal.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/activityManagement/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/coupon/BatchSendModal.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/coupon/add.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/active/coupon/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/banner/components/BannerActionCell.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/banner/components/BannerFormModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/banner/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/agreementVersion.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementListForm.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/FaqForm.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/SimpleRichTextForm.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/whitelistManagement/invitationCode/components/GenerateModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/whitelistManagement/whitelist/components/WhitelistEditModal.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/whitelistManagement/whitelist/detail.vue 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/whitelistManagement/whitelist/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/whitelistManagement/whitelistLog/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/common/IconSelect.vue
@@ -134,7 +134,8 @@
    </n-button>
  </n-input-group>
  <n-modal
    v-model:show="showModal" preset="card" :title="$t('components.iconSelector.selectorTitle')" size="small" class="w-800px" :bordered="false"
    v-model:show="showModal"
    :mask-closable="false" preset="card" :title="$t('components.iconSelector.selectorTitle')" size="small" class="w-800px" :bordered="false"
  >
    <template #header-extra>
      <n-button type="warning" size="small" ghost @click="clearIcon">
src/components/custom/OperationLogModal.vue
@@ -315,7 +315,7 @@
</script>
<template>
  <n-modal :show="show" :auto-focus="false" @update:show="handleUpdateShow">
  <n-modal :mask-closable="false" :show="show" :auto-focus="false" @update:show="handleUpdateShow">
    <n-card
      class="operation-log-modal-card"
      :bordered="false"
src/layouts/components/common/SettingDrawer.vue
@@ -60,6 +60,7 @@
function resetSetting() {
  window.$dialog.warning({
    maskClosable: false,
    title: t('app.resetSettingTitle'),
    content: t('app.resetSettingContent'),
    positiveText: t('common.confirm'),
src/layouts/components/header/Search.vue
@@ -151,6 +151,7 @@
  </CommonWrapper>
  <n-modal
    v-model:show="showModal"
    :mask-closable="false"
    class="w-560px fixed top-60px inset-x-0 max-w-full"
    size="small"
    preset="card"
src/layouts/components/header/UserCenter.vue
@@ -1,10 +1,6 @@
<script setup lang="ts">
import { useAuthStore } from '@/store'
import { renderIcon } from '@/utils/icon'
import IconBookOpen from '~icons/icon-park-outline/book-open'
import IconGithub from '~icons/icon-park-outline/github'
import IconLogout from '~icons/icon-park-outline/logout'
import IconUser from '~icons/icon-park-outline/user'
const { t } = useI18n()
@@ -23,6 +19,7 @@
function handleSelect(key: string | number) {
  if (key === 'loginOut') {
    window.$dialog?.info({
      maskClosable: false,
      title: t('app.loginOutTitle'),
      content: t('app.loginOutContent'),
      positiveText: t('common.confirm'),
src/main.ts
@@ -1,5 +1,13 @@
import { router } from './router'
import App from './App.vue'
import { NModal } from 'naive-ui'
// 同时也修改 NDialog 的全局默认行为,以防止 dialog.create 创建的弹窗点击蒙版关闭
// 全局配置 NModal 的 maskClosable 默认值为 false,防止点击蒙版关闭弹窗
if (NModal && NModal.props && NModal.props.maskClosable) {
  NModal.props.maskClosable.default = false
}
// 注意: NDialogProvider 是一个组件,并不是直接包含 dialog options 的地方,但 dialog 会受到 NModal 的影响。
function getCurrentRouteInfo() {
  const currentRoute = router.currentRoute.value
src/router/routes.static.ts
@@ -834,6 +834,20 @@
    activeMenu: '/assetManagement/vehicleManagement/operatingVehicle',
  },
  {
    name: 'operatingVehicleGeofenceDetail',
    path: '/assetManagement/vehicleManagement/operatingVehicle/geofence/detail',
    title: '运营区域详情',
    requiresAuth: true,
    icon: 'icon-park-outline:auto-focus',
    menuType: 'page',
    componentPath: '/businessOperations/geofenceManagement/detail.vue',
    id: 2026062401,
    keepAlive: false,
    pid: 501,
    hide: true, // 不在菜单中显示
    activeMenu: '/assetManagement/vehicleManagement/operatingVehicle',
  },
  {
    name: 'vehicleMaterial',
    path: '/assetManagement/vehicleManagement/vehicleMaterial',
    title: '车辆素材管理',
src/views/assetManagement/fleetManagement/components/FleetFormModal.vue
@@ -403,6 +403,7 @@
<template>
  <n-modal
    v-model:show="visible"
    :mask-closable="false"
    preset="card"
    :title="modalTitle"
    :style="{ width: '600px' }"
src/views/assetManagement/fleetManagement/components/StaffSelectModal.vue
@@ -266,7 +266,7 @@
</script>
<template>
  <n-modal v-model:show="visible">
  <n-modal v-model:show="visible" :mask-closable="false">
    <n-card
      style="width: 1000px; height: 80vh; max-height: 800px; border-radius: 8px; display: flex; flex-direction: column;"
      :bordered="false"
src/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue
@@ -5,7 +5,7 @@
import { fetchVehiclePageList } from '@/service/api/fleet'
interface VehicleItem {
  id: number
  id: number | string
  vehicleCode: string
  plateNumber: string
  vehicleModel: string
@@ -48,12 +48,12 @@
})
// 选中的行
const checkedRowKeys = ref<number[]>([])
const checkedRowKeys = ref<string[]>([])
const listData = ref<VehicleItem[]>([])
// 缓存所有加载过的数据,以便在 handleConfirm 时能找到不在当前页但在 checkedRowKeys 中的项
const allLoadedData = ref<Map<number, VehicleItem>>(new Map())
const allLoadedData = ref<Map<string, VehicleItem>>(new Map())
const count = ref(0)
const currentPage = ref(1)
@@ -111,6 +111,46 @@
  },
])
function shouldIgnoreRowClick(target: EventTarget | null) {
  const element = target as HTMLElement | null
  return Boolean(
    element?.closest(
      '.n-checkbox, .n-checkbox-box, .n-checkbox-input, button, a, input, textarea, .n-base-selection',
    ),
  )
}
function handleUpdateChecked(keys: Array<string | number>) {
  checkedRowKeys.value = keys.map(key => String(key))
}
function toggleRowSelection(row: VehicleItem) {
  if (props.selectionDisabled?.(row))
    return
  const rowKey = String(row.id)
  if (!rowKey)
    return
  if (checkedRowKeys.value.includes(rowKey)) {
    checkedRowKeys.value = checkedRowKeys.value.filter(key => key !== rowKey)
    return
  }
  checkedRowKeys.value = [...checkedRowKeys.value, rowKey]
}
function getRowProps(row: VehicleItem) {
  return {
    style: props.selectionDisabled?.(row) ? '' : 'cursor: pointer;',
    onClick: (event: MouseEvent) => {
      if (shouldIgnoreRowClick(event.target))
        return
      toggleRowSelection(row)
    },
  }
}
// 获取车辆列表
async function getVehicleList() {
  startLoading()
@@ -130,7 +170,7 @@
      // 存入缓存
      listData.value.forEach((item) => {
        allLoadedData.value.set(item.id, item)
        allLoadedData.value.set(String(item.id), item)
      })
    }
    else {
@@ -207,11 +247,11 @@
    resetSearchModel()
    currentPage.value = 1
    pageSize.value = 10
    checkedRowKeys.value = props.existingVehicles.map(v => v.id)
    checkedRowKeys.value = props.existingVehicles.map(v => String(v.id))
    // 清空并初始化 allLoadedData
    allLoadedData.value.clear()
    props.existingVehicles.forEach((v) => {
      allLoadedData.value.set(v.id, v)
      allLoadedData.value.set(String(v.id), v)
    })
    getVehicleList()
@@ -225,7 +265,7 @@
</script>
<template>
  <n-modal v-model:show="visible">
  <n-modal v-model:show="visible" :mask-closable="false">
    <n-card
      style="
        width: 1000px;
@@ -307,8 +347,10 @@
            :data="listData"
            :loading="loading"
            :bordered="false"
            :row-key="(row: VehicleItem) => row.id"
            :row-key="(row: VehicleItem) => String(row.id)"
            :row-props="getRowProps"
            :pagination="false"
            @update:checked-row-keys="handleUpdateChecked"
          >
            <template #empty>
              <div class="flex-center">
src/views/assetManagement/fleetManagement/detail.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NIcon, NSpace, NTag } from 'naive-ui'
import { NButton, NTag } from 'naive-ui'
import { useRoute, useRouter } from 'vue-router'
import { addUserToFleet, addVehicleToFleet, fetchFleetDetail, fetchFleetUserList, fetchFleetVehicleList, fetchRoleList, removeUserFromFleet, removeVehicleFromFleet } from '@/service/api/fleet'
import { useBoolean } from '@/hooks'
@@ -333,6 +333,7 @@
// 移除车辆
async function handleRemoveVehicle(vehicleId: number) {
  window.$dialog.warning({
    maskClosable: false,
    title: '是否确认移除车辆?',
    content: '移除后车辆自动任务将不再下发给车队!',
    positiveText: '确定',
@@ -413,6 +414,7 @@
// 移除人员
async function handleRemoveStaff(userId: number) {
  window.$dialog.warning({
    maskClosable: false,
    title: '是否确认移除人员?',
    content: '',
    positiveText: '确定',
src/views/assetManagement/fleetManagement/index.vue
@@ -134,7 +134,9 @@
    align: 'left',
    key: 'locationName',
    width: 180,
    ellipsis: true,
    ellipsis: {
      tooltip: true,
    },
    resizable: true,
  },
  {
@@ -283,6 +285,7 @@
async function handleDeleteFleet(id: number) {
  window.$dialog.warning({
    maskClosable: false,
    title: '是否确认删除',
    content: '删除后数据不可恢复,请谨慎操作',
    positiveText: '确定',
src/views/assetManagement/vehicleManagement/components/VehicleMaterialSelectModal.vue
@@ -4,7 +4,6 @@
import { NButton, NCard, NImage, NModal, NSpin } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { getVehicleMaterialList } from '@/service/api/vehicle'
import { downloadFile } from '@/utils'
import iconPreview from '@/assets/element-img/icon-perview.png'
import iconDownload from '@/assets/element-img/icon-download.png'
import selectedIcon from '@/assets/element-img/selected.png'
src/views/assetManagement/vehicleManagement/inventoryVehicle/add.vue
@@ -1,6 +1,7 @@
<script setup lang="tsx">
import { onMounted, ref, watch } from 'vue'
import type { FormInst, FormRules, UploadFileInfo } from 'naive-ui'
import { NTooltip, tooltipDark } from 'naive-ui'
import { useRoute, useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { addVehicle, editVehicle, getVehicleById } from '@/service/api/vehicle'
@@ -14,7 +15,6 @@
import wordIcon from '@/assets/element-img/word.png'
import pdfIcon from '@/assets/element-img/pdf.png'
import uploadIcon from '@/assets/element-img/upload.png'
import noticeIcon from '@/assets/element-img/notice.png'
import noDataIcon from '@/assets/element-img/no-data.png'
const router = useRouter()
@@ -203,7 +203,7 @@
  try {
    const res = await getVehicleById(vehicleId.value)
    if (res.code !== 200 || !res.data) {
      // window.$message.error(res.msg || '获取车辆详情失败')
      // window.$message.error(res.msg || '获取车辆详情失败')
      return
    }
    const data = res.data
@@ -253,7 +253,7 @@
  }
  catch (error) {
    console.error('获取车辆详情失败:', error)
    // window.$message.error('获取车辆详情失败')
    // window.$message.error('获取车辆详情失败')
  }
}
@@ -287,6 +287,64 @@
  catch (error) {
    console.error('获取运营区域失败:', error)
  }
}
function renderOperationAreaOptionLabel(option: any) {
  const content = option?.label || ''
  if (!content)
    return '-'
  return (
    <NTooltip placement="top-center">
      {{
        trigger: () => (
          <div
            title={content}
            style={{
              display: 'block',
              width: '100%',
              maxWidth: '140px',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
            }}
          >
            {content}
          </div>
        ),
        default: () => content,
      }}
    </NTooltip>
  )
}
function renderOperationAreaTag(tag: any) {
  const content = tag.option?.label || ''
  if (!content)
    return null
  return (
    <NTooltip themeOverrides={tooltipDark}>
      {{
        trigger: () => (
          <div
            title={content}
            style={{
              display: 'block',
              width: '100%',
              maxWidth: '140px',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
            }}
          >
            {content}
          </div>
        ),
        default: () => content,
      }}
    </NTooltip>
  )
}
onMounted(() => {
@@ -665,6 +723,7 @@
// 处理返回
function handleBack() {
  window.$dialog.warning({
    maskClosable: false,
    title: '提示',
    content: '是否放弃当前修改?',
    positiveText: '确定',
@@ -890,6 +949,8 @@
                      placeholder="请选择"
                      filterable
                      clearable
                      :render-label="renderOperationAreaOptionLabel"
                      :render-tag="renderOperationAreaTag"
                      @update:show="isOperationAreaSelectOpen = $event"
                    >
                      <template #arrow>
@@ -1051,12 +1112,12 @@
                  :actions="[]"
                  style="width: 100%; margin-right: 4px;"
                />
                <n-tooltip placement="top-start" trigger="hover">
                <NTooltip placement="top-center" trigger="hover">
                  <template #trigger>
                    <icon-park-outline-help color="#0256ff" />
                  </template>
                  以机动车行驶证为准
                </n-tooltip>
                </NTooltip>
              </n-form-item>
            </n-gi>
src/views/assetManagement/vehicleManagement/inventoryVehicle/components/CompleteVehicleInfoModal.vue
@@ -240,6 +240,7 @@
  if (hasData) {
    window.$dialog.warning({
      maskClosable: false,
      title: '确认取消',
      content: '表单内有已输入的数据,取消后将不会保存,是否确认取消?',
      positiveText: '确定',
@@ -589,14 +590,16 @@
        <n-button style="padding: 7px 29px; border-radius: 4px;" @click="handleCancel">
          取消
        </n-button>
        <n-button style="width: unset;
        padding: 7px 16px;
        <n-button
          style="width: unset;
        padding: 7px 16px;
        border-radius: 4px;
        border: 1px solid #7ABDFF;
        background-color: #E6F4FF;
        color: #0068FF;
        cursor: pointer;
        " @click="handleSave(true)">
        " @click="handleSave(true)"
        >
          保存并运营
        </n-button>
        <n-button type="primary" style="padding: 7px 29px;border-radius: 4px;" @click="handleSave(false)">
src/views/assetManagement/vehicleManagement/inventoryVehicle/index.vue
@@ -333,6 +333,7 @@
    align: 'left',
    width: 120,
    key: 'vehicleVin',
    ellipsis: { tooltip: true },
    resizable: true,
  },
  {
@@ -562,6 +563,7 @@
  }
  const idStr = selectedVehicles.value.map(item => item.id).join(',')
  dialog.warning({
    maskClosable: false,
    title: '确认开始运营',
    content: () => `确认对选中的 ${selectedVehicles.value.length} 辆车辆执行开始运营吗?`,
    negativeText: '取消',
@@ -598,7 +600,7 @@
      window.$message.success('下载成功')
    }
    else {
      // window.$message.error('下载失败:接口未返回正确的文件模板')
      // window.$message.error('下载失败:接口未返回正确的文件模板')
    }
  }
  catch (error) {
@@ -678,6 +680,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '确认开始运营',
    content: () => {
      return (
@@ -738,6 +741,7 @@
function handleDelete(row: any) {
  window.$dialog.warning({
    maskClosable: false,
    title: '是否确认删除',
    content: () => {
      return (
src/views/assetManagement/vehicleManagement/operatingVehicle/components/DetectionDetailModal.vue
@@ -1,8 +1,6 @@
<script setup lang="ts">
import { NButton, NImage, NModal, NSpace, NTag } from 'naive-ui'
import { NImage, NModal } from 'naive-ui'
import { computed } from 'vue'
import successIcon from '@/assets/point/success.png'
import failIcon from '@/assets/point/fail.png'
import dayjs from 'dayjs'
interface Props {
@@ -216,7 +214,7 @@
            <div class="detail-label" style="color:rgba(23, 31, 38, 0.6)">
              操作时间:
            </div>
            <span class="detail-value">{{ detailData.detectionTime ? dayjs(detailData.detectionTime).format("YYYY/MM/DD HH:mm:ss") : '-' }}</span>
            <span class="detail-value">{{ detailData.detectionTime ? dayjs(detailData.detectionTime).format("YYYY/MM/DD HH:mm") : '-' }}</span>
          </div>
        </div>
      </div>
src/views/assetManagement/vehicleManagement/operatingVehicle/components/InspectionDetailModal.vue
@@ -110,7 +110,7 @@
            <div class="value">
              {{
                detailData.inspectionExpireDate
                  ? dayjs(detailData.inspectionExpireDate).format('YYYY/MM/DD HH:mm:ss')
                  ? dayjs(detailData.inspectionExpireDate).format('YYYY/MM/DD HH:mm')
                  : "-"
              }}
            </div>
@@ -150,7 +150,7 @@
                <div class="value">
                  {{
                    detailData.insuranceExpireDate
                      ? dayjs(detailData.insuranceExpireDate).format('YYYY/MM/DD HH:mm:ss')
                      ? dayjs(detailData.insuranceExpireDate).format('YYYY/MM/DD HH:mm')
                      : "-"
                  }}
                </div>
@@ -197,7 +197,7 @@
            <div class="value">
              {{
                detailData.licenseExpireDate
                  ? dayjs(detailData.licenseExpireDate).format('YYYY/MM/DD HH:mm:ss')
                  ? dayjs(detailData.licenseExpireDate).format('YYYY/MM/DD HH:mm')
                  : "-"
              }}
            </div>
@@ -236,7 +236,7 @@
            </div>
            <div style="flex: 1">
              <div class="label">操作时间</div>
              <div class="value">{{ detailData.addTime || "-" }}</div>
              <div class="value">{{ detailData.addTime ? dayjs(detailData.addTime).format("YYYY/MM/DD HH:mm") : "-" }}</div>
            </div>
          </div>
        </div>
src/views/assetManagement/vehicleManagement/operatingVehicle/components/MaintenanceDetailModal.vue
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { NButton, NImage, NModal } from 'naive-ui'
import { NImage, NModal } from 'naive-ui'
import { computed } from 'vue'
import dayjs from 'dayjs'
@@ -58,7 +58,7 @@
            <div class="value">
              {{
                detailData.sendTime
                  ? dayjs(detailData.sendTime).format("YYYY/MM/DD HH:mm:ss")
                  ? dayjs(detailData.sendTime).format("YYYY/MM/DD HH:mm")
                  : "-"
              }}
            </div>
@@ -73,7 +73,7 @@
              {{
                detailData.repairStartTime
                  ? dayjs(detailData.repairStartTime).format(
                    "YYYY/MM/DD HH:mm:ss",
                    "YYYY/MM/DD HH:mm",
                  )
                  : "-"
              }}
@@ -128,7 +128,7 @@
            <div class="value">
              {{
                detailData.receiveTime
                  ? dayjs(detailData.receiveTime).format("YYYY/MM/DD HH:mm:ss")
                  ? dayjs(detailData.receiveTime).format("YYYY/MM/DD HH:mm")
                  : "-"
              }}
            </div>
@@ -184,7 +184,7 @@
  }
  .value {
    color: rgba(23, 31, 38, 1);
    font-size: 13px;
    font-size: 13px;
    line-height: 18px;
  }
  .pic {
src/views/assetManagement/vehicleManagement/operatingVehicle/components/PreparationDetailModal.vue
@@ -82,7 +82,7 @@
          <div class="value">
            {{
              detailData.operationTime
                ? dayjs(detailData.operationTime).format("YYYY/MM/DD HH:mm:ss")
                ? dayjs(detailData.operationTime).format("YYYY/MM/DD HH:mm")
                : "-"
            }}
          </div>
src/views/assetManagement/vehicleManagement/operatingVehicle/components/TaskDetailModal.vue
@@ -3,10 +3,6 @@
import { NAvatar, NButton, NImage, NModal, NSpace, NSpin, NTag } from 'naive-ui'
import { getTaskDetail } from '@/service/api/vehicleConvoy'
import { collectImageUrls } from '@/utils/common'
import IconCar from '~icons/icon-park-outline/car'
import IconCheckOne from '~icons/icon-park-outline/check-one'
import IconCaution from '~icons/icon-park-outline/caution'
import IconParkOutlineImageFiles from '~icons/icon-park-outline/image-files'
const props = defineProps<{
  show: boolean
src/views/assetManagement/vehicleManagement/operatingVehicle/detail.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import { NButton, NCard, NCascader, NDataTable, NDatePicker, NForm, NFormItem, NImage, NInput, NSelect, NSpace, NTabPane, NTabs, NTag, tooltipDark } from 'naive-ui'
import { NButton, NCard, NCascader, NDataTable, NDatePicker, NForm, NFormItem, NImage, NInput, NSelect, NSpace, NTabPane, NTabs, NTag, NTooltip } from 'naive-ui'
import { useRoute, useRouter } from 'vue-router'
import { useBoolean } from '@/hooks'
import InspectionDetailModal from './components/InspectionDetailModal.vue'
@@ -83,6 +83,91 @@
// 事故任务类型选项
const taskErrorIdOptions = ref<any[]>([])
function renderTaskErrorOptionLabel(option: any) {
  const content = option?.errorName || option?.label || ''
  if (!content)
    return '-'
  return (
    <NTooltip placement="top-center">
      {{
        trigger: () => (
          <div
            style={{
              display: 'block',
              width: '100%',
              maxWidth: '140px',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
            }}
          >
            {content}
          </div>
        ),
        default: () => content,
      }}
    </NTooltip>
  )
}
function renderTaskErrorTag(tag: any) {
  const content = taskErrorIdDisplayLabel.value
  if (!content)
    return null
  return (
    <NTooltip placement="top-center">
      {{
        trigger: () => (
          <div
            title={content}
            style={{
              display: 'block',
              width: '100%',
              maxWidth: '140px',
              overflow: 'hidden',
              textOverflow: 'ellipsis',
              whiteSpace: 'nowrap',
            }}
          >
            {content}
          </div>
        ),
        default: () => content,
      }}
    </NTooltip>
  )
}
function findTaskErrorOptionPath(options: any[], targetValue: string | number | null | undefined, parents: any[] = []): any[] {
  if (targetValue === null || targetValue === undefined || targetValue === '')
    return []
  for (const option of options || []) {
    const currentPath = [...parents, option]
    if (String(option?.id ?? '') === String(targetValue))
      return currentPath
    const childPath = findTaskErrorOptionPath(option?.child || [], targetValue, currentPath)
    if (childPath.length)
      return childPath
  }
  return []
}
const taskErrorIdDisplayLabel = computed(() => {
  const matchedPath = findTaskErrorOptionPath(taskErrorIdOptions.value, taskSearchModel.value.taskErrorId)
  if (!matchedPath.length)
    return ''
  return matchedPath
    .map(item => item?.errorName)
    .filter(Boolean)
    .join(' / ')
})
// 获取事故类型列表
function getAccidentTypeList() {
  getTaskAccidentTypeList().then((res) => {
@@ -98,7 +183,7 @@
  // 3: { type: 'info', text: '已收车' },
  // 4: { type: 'error', text: '车辆异常' },
   1: { type: 'success', text: '运营中' },
  1: { type: 'success', text: '运营中' },
  2: { type: 'success', text: '出车中' },
  3: { type: 'success', text: '接单中' },
  4: { type: 'success', text: '停止接单' },
@@ -895,6 +980,7 @@
// 已收车
function _handleRecall() {
  window.$dialog.warning({
    maskClosable: false,
    title: '确认收车',
    content: `确认将车辆 ${vehicleDetail.value.plateNumber} 收车吗?`,
    positiveText: '确定',
@@ -907,8 +993,11 @@
// 查看运营区域
function handleViewArea() {
  if (!vehicleDetail.value.electronicId)
    return
  router.push({
    path: '/businessOperations/geofenceManagement/detail',
    name: 'operatingVehicleGeofenceDetail',
    query: {
      type: 'detail',
      id: vehicleDetail.value.electronicId,
@@ -1034,6 +1123,7 @@
// 删除任务记录
function handleDeleteTask(row: any) {
  window.$dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确认删除该任务记录吗?`,
    positiveText: '确定',
@@ -1041,7 +1131,7 @@
    onPositiveClick: () => {
      const id = String(row.id ?? row.taskId ?? '')
      if (!id) {
        // window.$message.error('缺少ID,无法删除')
        // window.$message.error('缺少ID,无法删除')
        return
      }
      deleteTaskById(id).then(({ error }) => {
@@ -1059,6 +1149,7 @@
function handleDeleteCharging(row: any) {
  window.$dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确认删除该充电记录吗?`,
    positiveText: '确定',
@@ -1075,7 +1166,7 @@
          getChargingList()
        }
        else {
          // window.$message.error('删除失败')
          // window.$message.error('删除失败')
        }
      })
    },
@@ -1084,6 +1175,7 @@
function handleDeleteClean(row: any) {
  window.$dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确认删除该车辆清洁记录吗?`,
    positiveText: '确定',
@@ -1098,9 +1190,8 @@
        if (res.code === 200) {
          window.$message.success('删除成功')
          getCleanList()
          return
        }
        // window.$message.error('删除失败')
        // window.$message.error('删除失败')
      })
    },
  })
@@ -1273,9 +1364,8 @@
        accidentDetailModalRef.value?.openModal(res.data, id)
      else
        routineDetailModalRef.value?.openModal(res.data, id)
      return
    }
    // window.$message.error(res.msg || '获取任务详情失败')
    // window.$message.error(res.msg || '获取任务详情失败')
  }
  else {
    window.$message.info(`查看${type}记录详情: ${row.id}`)
@@ -2098,12 +2188,15 @@
                          v-model:value="taskSearchModel.taskErrorId"
                          placeholder="请选择任务类型"
                          style="width: 180px"
                          class="task-error-cascader"
                          :options="taskErrorIdOptions"
                          value-field="id"
                          label-field="errorName"
                          children-field="child"
                          check-strategy="child"
                          clearable
                          :render-label="renderTaskErrorOptionLabel"
                          :render-tag="renderTaskErrorTag"
                          @update:show="isTaskErrorIdOpen = $event"
                        >
                          <template #arrow>
@@ -2491,4 +2584,14 @@
:deep(.n-tabs-pane-wrapper) {
  flex: 1;
}
:deep(.task-error-cascader .n-base-selection-tags) {
  flex-wrap: nowrap;
  overflow: hidden;
}
:deep(.task-error-cascader .n-base-selection-overlay__wrapper) {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
</style>
src/views/assetManagement/vehicleManagement/operatingVehicle/index.vue
@@ -310,6 +310,7 @@
    align: 'left',
    width: 160,
    key: 'vehicleVin',
    ellipsis: { tooltip: true },
    resizable: true,
  },
  {
@@ -452,6 +453,7 @@
  }
  dialog.warning({
    maskClosable: false,
    title: '批量转为在库车辆',
    content: () => {
      return (
@@ -483,6 +485,7 @@
function handleConvertToInventory(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '转为在库车辆',
    content: () => {
      return (
src/views/assetManagement/vehicleManagement/vehicleMaterial/index.vue
@@ -202,6 +202,7 @@
    }
    dialog.warning({
      maskClosable: false,
      title: '删除确认',
      content,
      positiveText: '确认',
@@ -219,6 +220,7 @@
    console.error('删除图片校验失败', error)
    // fallback directly to delete if check fails
    dialog.warning({
      maskClosable: false,
      title: '删除确认',
      content: '确认删除该图片?此操作不可恢复。',
      positiveText: '确认',
src/views/assetManagement/vehicleModel/components/AddOrEditModal.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import type { FormInst, FormRules } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NSpace, useMessage } from 'naive-ui'
import { NButton, NForm, NFormItem, NInput, NModal, NSpace, useMessage } from 'naive-ui'
import { addVehicleModel, editVehicleModel, getVehicleModelDetail } from '@/service/api/vehicleConvoy'
const emit = defineEmits<{
src/views/assetManagement/vehicleModel/index.vue
@@ -213,6 +213,7 @@
function handleDelete(row: any) {
  dialog.create({
    maskClosable: false,
    title: '是否确认删除',
    content: `确认删除该车型?删除后不可恢复。`,
    positiveText: '确定',
src/views/businessOperations/cruise/parade-plan/add.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import { computed, onMounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NButton, NInput, NRadio, NRadioGroup, NSelect, useDialog, useMessage } from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
@@ -46,6 +46,16 @@
const isEdit = computed(() => route.query.mode === 'edit')
const planId = computed(() => String(route.query.id || ''))
const scrollContainerRef = ref<HTMLElement | null>(null)
const showFooterShadow = ref(false)
let resizeObserver: ResizeObserver | null = null
function checkScroll() {
  if (!scrollContainerRef.value) return
  const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.value
  showFooterShadow.value = scrollHeight > clientHeight && Math.ceil(scrollTop + clientHeight) < scrollHeight - 2
}
let draggedIndex: number | null = null
const initialSnapshot = ref('')
@@ -294,12 +304,45 @@
onMounted(() => {
  if (isEdit.value) {
    getDetail()
    getDetail().finally(() => {
      nextTick(() => {
        checkScroll()
      })
    })
  }
  else {
    setInitialSnapshot()
    nextTick(() => {
      checkScroll()
    })
  }
  if (scrollContainerRef.value) {
    resizeObserver = new ResizeObserver(() => {
      checkScroll()
    })
    if (scrollContainerRef.value.firstElementChild) {
      resizeObserver.observe(scrollContainerRef.value.firstElementChild)
    }
    resizeObserver.observe(scrollContainerRef.value)
  }
})
onUnmounted(() => {
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }
})
watch(
  () => [chargePoints.value.length, vehicles.value.length],
  () => {
    nextTick(() => {
      checkScroll()
    })
  }
)
watch(
  () => planId.value,
@@ -312,8 +355,13 @@
</script>
<template>
  <div class="h-full flex flex-col gap-4">
    <n-card style="border-radius: 6px" content-style="padding: 16px 24px;">
  <div class="h-full flex flex-col page-container">
    <div
      ref="scrollContainerRef"
      class="flex-1 p-12px flex flex-col gap-4 overflow-auto page-scroll-content"
      @scroll="checkScroll"
    >
      <n-card style="border-radius: 6px" content-style="padding: 16px 24px;">
      <div class="text-lg font-bold">
        {{ isEdit ? "编辑巡游计划" : "新增巡游计划" }}
      </div>
@@ -390,7 +438,7 @@
    </n-card>
    <n-card
      style="border-radius: 6px; margin-bottom: 80px"
      style="border-radius: 6px;"
      content-style="padding: 24px;"
    >
      <div class="mb-4 flex items-center">
@@ -437,10 +485,11 @@
        </div>
      </div>
    </n-card>
    </div>
    <div
      class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4"
      style="box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); z-index: 100"
      class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4 transition-shadow duration-300"
      :style="{ boxShadow: showFooterShadow ? '0 -2px 8px rgba(0, 0, 0, 0.1)' : 'none', zIndex: 100 }"
    >
      <NButton style="width: 84px; height: 32px" @click="handleCancel">
        返回
@@ -468,4 +517,19 @@
  </div>
</template>
<style scoped></style>
<style scoped lang="scss">
.page-container {
  position: relative;
  height: calc(100vh - 76px - 28px);
  padding-bottom: 64px;
}
.page-scroll-content {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.page-scroll-content::-webkit-scrollbar {
  display: none;
}
</style>
src/views/businessOperations/cruise/parade-plan/detail.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import { NButton, NDatePicker, NInput, NRadio, NRadioGroup, NSelect, useDialog, useMessage } from 'naive-ui'
import { NButton, NInput, NRadio, NRadioGroup, useDialog, useMessage } from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
import dayjs from 'dayjs'
import { computed, onMounted, ref, watch } from 'vue'
src/views/businessOperations/cruise/parade-plan/index.vue
@@ -100,6 +100,7 @@
function handleDelete(row: CruisePlanRow) {
  dialog.create({
    maskClosable: false,
    title: '确认删除该巡游计划吗?',
    content: () => {
      return (
src/views/businessOperations/cruise/patrol-points/add.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns } from 'naive-ui'
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NButton, NTag, useMessage } from 'naive-ui'
import SearchPanel from '@/components/custom/SearchPanel.vue'
@@ -43,6 +43,17 @@
const showPointModal = ref(false)
const showVehicleModal = ref(false)
const saving = ref(false)
const scrollContainerRef = ref<HTMLElement | null>(null)
const showFooterShadow = ref(true)
let resizeObserver: ResizeObserver | null = null
function checkScroll() {
  if (!scrollContainerRef.value) return
  const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.value
  // 如果滚动高度大于容器高度,并且没有滚动到底部(允许 2px 误差)
  showFooterShadow.value = scrollHeight > clientHeight && Math.ceil(scrollTop + clientHeight) < scrollHeight - 2
}
const selectedPoint = ref<PointItem | null>(null)
const selectedVehicles = ref<VehicleItem[]>([])
@@ -422,13 +433,49 @@
}
onMounted(() => {
  fetchDetail()
  fetchDetail().finally(() => {
    nextTick(() => {
      checkScroll()
    })
  })
  if (scrollContainerRef.value) {
    resizeObserver = new ResizeObserver(() => {
      checkScroll()
    })
    // 监听内容高度变化
    if (scrollContainerRef.value.firstElementChild) {
      resizeObserver.observe(scrollContainerRef.value.firstElementChild)
    }
    // 监听容器本身变化
    resizeObserver.observe(scrollContainerRef.value)
  }
})
onUnmounted(() => {
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }
})
watch(
  () => [selectedPoint.value, selectedVehicles.value.length, pagedVehicleList.value.length],
  () => {
    nextTick(() => {
      checkScroll()
    })
  }
)
</script>
<template>
  <div class="special-point-add-page roboTaxi">
    <div class="p-12px h-full flex flex-col gap-12px">
    <div
      ref="scrollContainerRef"
      class="page-scroll-content p-12px h-full flex flex-col gap-12px overflow-auto"
      @scroll="checkScroll"
    >
      <n-card class="content-card" :bordered="false">
        <div class="flex justify-between items-center mb-12px">
          <div class="section-title-wrap">
@@ -518,7 +565,10 @@
      </n-card>
    </div>
    <div class="page-footer">
    <div
      class="absolute bottom-0 left-0 right-0 flex justify-end gap-3 bg-white px-6 py-4 transition-shadow duration-300"
      :style="{ boxShadow: showFooterShadow ? '0 -2px 8px rgba(0, 0, 0, 0.1)' : 'none', zIndex: 100 }"
    >
      <NButton class="w-84px" @click="handleCancel">
        取消
      </NButton>
@@ -544,7 +594,18 @@
<style scoped lang="scss">
.special-point-add-page {
  height: calc(100vh - 76px);
  position: relative;
  height: calc(100vh - 76px - 28px);
  padding-bottom: 64px;
}
.page-scroll-content {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.page-scroll-content::-webkit-scrollbar {
  display: none;
}
.page-title-card,
src/views/businessOperations/cruise/patrol-points/components/SelectPointModal.vue
@@ -7,7 +7,7 @@
import { useBoolean } from '@/hooks'
interface Point {
  id: number
  id: number | string
  name?: string
  pointAddress?: string
  address?: string
@@ -37,8 +37,8 @@
const model = ref({
  pointName: '',
})
const selectedPoints = ref<number[]>([])
const allSelectedMap = ref<Map<number, Point>>(new Map())
const selectedPoints = ref<string[]>([])
const allSelectedMap = ref<Map<string, Point>>(new Map())
const isSingleSelect = computed(() => props.maxSelectCount === 1)
const listData = ref<any[]>([])
@@ -126,22 +126,24 @@
  emit('update:show', false)
}
function handleUpdateChecked(keys: number[]) {
function handleUpdateChecked(keys: Array<string | number>) {
  const normalizedKeys = keys.map(key => String(key))
  selectedPoints.value = isSingleSelect.value && keys.length > 1
    ? [keys[keys.length - 1]]
    : keys
    ? [normalizedKeys[normalizedKeys.length - 1]]
    : normalizedKeys
  // 遍历当前页数据,如果有被选中的,存入 map
  listData.value.forEach((item) => {
    if (selectedPoints.value.includes(item.id)) {
    const pointId = String(item.id)
    if (selectedPoints.value.includes(pointId)) {
      // 如果 map 中已有该 id,尝试合并属性以保留额外信息(如 isCharge 等)
      // 优先使用新数据覆盖,但保留新数据中没有的旧字段
      const old = allSelectedMap.value.get(item.id)
      const old = allSelectedMap.value.get(pointId)
      if (old) {
        allSelectedMap.value.set(item.id, { ...old, ...item })
        allSelectedMap.value.set(pointId, { ...old, ...item })
      }
      else {
        allSelectedMap.value.set(item.id, item)
        allSelectedMap.value.set(pointId, item)
      }
    }
  })
@@ -156,21 +158,35 @@
}
function handleRowClick(row: Point) {
  const rowKey = String(row.id)
  if (isSingleSelect.value) {
    handleUpdateChecked([row.id])
    handleUpdateChecked([rowKey])
    return
  }
  const nextKeys = selectedPoints.value.includes(row.id)
    ? selectedPoints.value.filter(id => id !== row.id)
    : [...selectedPoints.value, row.id]
  const nextKeys = selectedPoints.value.includes(rowKey)
    ? selectedPoints.value.filter(id => id !== rowKey)
    : [...selectedPoints.value, rowKey]
  handleUpdateChecked(nextKeys)
}
function shouldIgnoreRowClick(target: EventTarget | null) {
  const element = target as HTMLElement | null
  return Boolean(
    element?.closest(
      '.n-checkbox, .n-checkbox-box, .n-checkbox-input, button, a, input, textarea, .n-base-selection',
    ),
  )
}
function rowProps(row: Point) {
  return {
    style: 'cursor: pointer;',
    onClick: () => handleRowClick(row),
    onClick: (event: MouseEvent) => {
      if (shouldIgnoreRowClick(event.target))
        return
      handleRowClick(row)
    },
  }
}
@@ -181,12 +197,12 @@
      model.value.pointName = ''
      // 初始化
      selectedPoints.value = props.existingPoints
        ? props.existingPoints.map(p => p.id)
        ? props.existingPoints.map(p => String(p.id))
        : []
      allSelectedMap.value.clear()
      if (props.existingPoints) {
        props.existingPoints.forEach((p) => {
          allSelectedMap.value.set(p.id, p)
          allSelectedMap.value.set(String(p.id), p)
        })
      }
@@ -237,7 +253,7 @@
</script>
<template>
  <n-modal :show="show" @update:show="emit('update:show', $event)">
  <n-modal :mask-closable="false" :show="show" @update:show="emit('update:show', $event)">
    <n-card
      style="
        width: 1000px;
@@ -293,7 +309,7 @@
            flex-height
            style="height: 100%"
            :loading="loading"
            :row-key="(row: Point) => row.id"
            :row-key="(row: Point) => String(row.id)"
            :row-props="rowProps"
            @update:checked-row-keys="handleUpdateChecked"
          >
src/views/businessOperations/cruise/patrol-points/index.vue
@@ -126,6 +126,7 @@
function handleRemovePoint(type: 'charge' | 'park', pointId: number | string) {
  dialog.warning({
    maskClosable: false,
    title: '删除确认',
    content: '确认移除该特殊点位吗?',
    positiveText: '确认',
src/views/businessOperations/geofenceManagement/detail.vue
@@ -1673,6 +1673,7 @@
    else if (modalType.value === 'edit') {
      if (formModel.value.areaStatus !== fenceDetail.value.electronicFenceAreas.find((item: any) => item.areaType === 1)?.areaStatus) {
        dialog.create({
          maskClosable: false,
          title: `确认${formModel.value.areaStatus === 1 ? '启用' : '停用'}该围栏吗?`,
          content: '',
          positiveText: '确定',
src/views/businessOperations/geofenceManagement/index.vue
@@ -216,6 +216,7 @@
            disabled={row.status !== 3}
            onClick={() => {
              dialog.create({
                maskClosable: false,
                title: '是否确认复制?',
                content: `复制为【${row.electronicName}_副本】`,
                positiveText: '确定',
@@ -270,7 +271,7 @@
    }
  }
  catch {
    // message.error('导出失败')
    // message.error('导出失败')
  }
}
src/views/businessOperations/openCity/index.vue
@@ -122,6 +122,7 @@
            onClick={() => {
              const name = row.provinceCode === row.cityCode ? row.provinceName || '' : `${row.provinceName || ''}${row.cityName || ''}${row.districtName || ''}`
              dialog.create({
                maskClosable: false,
                title: '是否确认删除数据?',
                content: `确认删除【${name || ''}】城市的开通记录吗?`,
                positiveText: '确认',
@@ -256,6 +257,7 @@
function handleChangeStatus(row: any) {
  const actionText = row.status === 1 ? '启用' : '禁用'
  dialog.warning({
    maskClosable: false,
    title: `是否确认${actionText}城市?`,
    content: row.status === 0 ? '禁用后,该城市用户不可下单!' : `确认${actionText}该城市吗?`,
    positiveText: '确定',
src/views/businessOperations/operationWorkbench/map.vue
@@ -4280,6 +4280,7 @@
      return
    }
    window.$dialog.warning({
      maskClosable: false,
      title: '提示',
      content: () => h('span', null, [
        '当前场站包含',
@@ -5709,6 +5710,7 @@
) {
  return new Promise<boolean>((resolve) => {
    window.$dialog?.warning({
      maskClosable: false,
      title,
      content,
      positiveText: options?.positiveText || '确认',
@@ -5853,6 +5855,7 @@
function openPointSaveLeaveDialog() {
  return new Promise<'save' | 'discard' | 'cancel'>((resolve) => {
    window.$dialog?.warning({
      maskClosable: false,
      title: '当前点位还未保存,确定离开吗?',
      content: '删除点位,编辑的内容将丢失,确定离开吗?',
      positiveText: '保存离开',
@@ -6653,7 +6656,7 @@
  isPointAddressDropdownVisible.value = show
}
function handleMapCanvasClick(event: any) {
async function handleMapCanvasClick(event: any) {
  const lnglat = event?.lnglat
  if (!lnglat)
    return
@@ -6681,6 +6684,16 @@
    updatePickedPoint([lnglat.getLng(), lnglat.getLat()], true)
    return
  }
  if (
    activeTab.value === 'point'
    && !isPointFormMode.value
    && !pointPanelClosed.value
    && !!selectedId.value
    && !!pointDetailData.value
  ) {
    await handleClosePointPanel({ clearSelection: true })
    return
  }
  if (activeTab.value !== 'fence' || !isFenceFormMode.value)
    return
  const pendingArea = fenceRestrictedAreas.value.find(
src/views/businessOperations/siteInfo/index.vue
@@ -371,6 +371,7 @@
function handleDelete(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '是否确认删除数据?',
    content: '确认删除该点位现场信息吗?删除后不可恢复。',
    positiveText: '确定',
src/views/dashboard/index.vue
@@ -13,7 +13,9 @@
<template>
  <n-card class="w-full h-full">
    <div class="text-base font-bold">首页开发中...</div>
    <div class="text-base font-bold">
      首页开发中...
    </div>
  </n-card>
</template>
src/views/operations/attendance/address/index.vue
@@ -49,6 +49,7 @@
async function handleSwitchChange(row: any) {
  dialog.warning({
    maskClosable: false,
    title: `是否确认${row.status === 1 ? '禁用' : '启用'}打卡地?`,
    content: '',
    positiveText: '确认',
@@ -143,6 +144,7 @@
            style={{ color: isEnabled ? '#0068FF' : '#0068FF' }}
            onClick={() => {
              dialog.create({
      maskClosable: false,
                title: `是否确认${isEnabled ? '禁用' : '启用'}打卡地?`,
                content: '',
                positiveText: '确定',
src/views/operations/attendance/plan/copy.vue
@@ -280,7 +280,7 @@
    positiveText: '确认',
    negativeText: '取消',
    maskClosable: false,
                autoFocus: false,
    autoFocus: false,
    onPositiveClick: () => {
      handleDeleteSchedule(teamIndex, dateIndex)
    },
src/views/operations/attendance/plan/edit.vue
@@ -266,7 +266,7 @@
    positiveText: '确认',
    negativeText: '取消',
    maskClosable: false,
                autoFocus: false,
    autoFocus: false,
    onPositiveClick: () => {
      handleDeleteSchedule(teamIndex, dateIndex)
    },
src/views/operations/attendance/plan/index.vue
@@ -364,7 +364,7 @@
    </n-card>
    <!-- 排班弹窗 -->
    <n-modal v-model:show="showScheduleModal" preset="card" title="排班选择" class="w-400px">
    <n-modal v-model:show="showScheduleModal" :mask-closable="false" preset="card" title="排班选择" class="w-400px">
      <div class="flex flex-col gap-3">
        <div
          v-for="shift in shiftTags" :key="`${shift.name}-${shift.time}`"
src/views/operations/attendance/shift/index.vue
@@ -6,7 +6,6 @@
import { addShift, deleteShift, editShift, getShiftList } from '@/service/api/attendance'
import { NButton, NSpace } from 'naive-ui'
import ShiftFormModal from './components/ShiftFormModal.vue'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import dayjs from 'dayjs'
src/views/operations/cruiseCheckIn/index.vue
@@ -51,6 +51,7 @@
async function handleSwitchChange(row: any) {
  dialog.warning({
    maskClosable: false,
    title: `是否确认${row.status === 1 ? '禁用' : '启用'}打卡地?`,
    content: '',
    positiveText: '确认',
src/views/operations/cruiseCheckInRule/add.vue
@@ -178,7 +178,7 @@
  }
  catch (error) {
    console.error('获取巡游打卡规则详情失败:', error)
    // message.error('获取巡游打卡规则详情失败')
    // message.error('获取巡游打卡规则详情失败')
  }
}
src/views/operations/cruiseCheckInRule/components/DetailModal.vue
@@ -106,7 +106,7 @@
    :bordered="false"
    size="huge"
    class="custom-modal"
    :mask-closable="true"
    :mask-closable="false"
    @update:show="(val) => emit('update:show', val)"
    @close="handleClose"
  >
src/views/operations/cruiseCheckInRule/components/SelectPointModal.vue
@@ -86,7 +86,7 @@
  }
  catch (error) {
    console.error('新增打卡点失败:', error)
    // window.$message.error('新增打卡点失败')
    // window.$message.error('新增打卡点失败')
  }
}
src/views/operations/message/index.vue
@@ -88,7 +88,8 @@
            { default: () => '短信通知' },
          ),
        )
      } else {
      }
      else {
        row.isMessage = false
      }
@@ -107,7 +108,7 @@
function handleSave() {
  console.log('保存', tableData.value)
  startLoading()
  const notifications = tableData.value.map((item) => ({
  const notifications = tableData.value.map(item => ({
    ...item,
    isMessage: canShowMessageNotification(item.notificationType) ? item.isMessage : false,
  }))
src/views/operations/personnel/personnelManagement/index.vue
@@ -1,10 +1,10 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import { computed, h, onActivated, ref } from 'vue'
import { computed, onActivated, ref } from 'vue'
import { useBoolean, usePermission } from '@/hooks'
import { useRouter } from 'vue-router'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NDropdown, NSwitch, NTag, NTooltip } from 'naive-ui'
import { NButton, NSwitch, NTag, NTooltip } from 'naive-ui'
import { addOperationsUser, deleteOperationsUser, editOperationsUser, exportOperationsUser, exportOperationsUserTemplate, getOperationsUserList, importOperationsUser, updateOperationsUserStatus } from '@/service/api/attendance'
import { fetchRoleList } from '@/service/api/fleet'
import { downloadFile, onlyAllowNumber, onlyAllowNumberAndLetter } from '@/utils'
@@ -171,6 +171,7 @@
  const actionType = newStatus ? '启用' : '禁用'
  dialog.warning({
    maskClosable: false,
    title: newStatus ? '确认启用' : '确认禁用',
    content: `是否确认${actionType}该人员?`,
    positiveText: '确认',
src/views/operations/personnel/safetyOfficerInfo/index.vue
@@ -238,6 +238,7 @@
  const nextStatus = row.status === 1 ? 0 : 1
  const actionText = nextStatus === 1 ? '停用' : '启用'
  dialog.warning({
    maskClosable: false,
    title: `是否确认${actionText}该安全员?`,
    content: ``,
    positiveText: '确定',
src/views/operations/taskOrder/taskOrderCenter/components/detailModal.vue
@@ -1,8 +1,6 @@
<script setup lang="ts">
import { useBoolean } from '@/hooks'
import { NTag } from 'naive-ui'
import successIcon from '@/assets/point/success.png'
import failIcon from '@/assets/point/fail.png'
import dayjs from 'dayjs'
const emit = defineEmits<{
src/views/service/review/personnel/index.vue
@@ -274,6 +274,7 @@
function handleDelete(id: number) {
  window.$dialog?.warning({
    maskClosable: false,
    title: t('serviceReview.common.deleteTitle'),
    content: t('serviceReview.common.deletePersonnelContent'),
    positiveText: t('common.confirm'),
src/views/service/review/policyApplication/index.vue
@@ -252,6 +252,7 @@
function handleDelete(id: number) {
  window.$dialog?.warning({
    maskClosable: false,
    title: t('serviceReview.common.deleteTitle'),
    content: t('serviceReview.common.deletePolicyContent'),
    positiveText: t('common.confirm'),
src/views/service/ticket/feedbackType/index.vue
@@ -147,6 +147,7 @@
function handleDelete(row: FeedbackTypeItem) {
  window.$dialog?.warning({
    maskClosable: false,
    title: t('ticket.feedbackType.confirmDelete'),
    content: t('ticket.feedbackType.confirmDeleteContent'),
    positiveText: t('common.confirm'),
src/views/service/ticket/workOrder/detail.vue
@@ -519,6 +519,7 @@
    </n-card>
    <n-modal
      v-model:show="previewVisible"
      :mask-closable="false"
      preset="card"
      :title="t('workOrder.detailPage.preview')"
      :style="{ width: '720px' }"
src/views/service/ticket/workOrder/index.vue
@@ -371,6 +371,7 @@
  }
  if (payload.handleType === 2) {
    window.$dialog?.warning({
      maskClosable: false,
      title: t('workOrder.handleModal.confirmClose'),
      content: '',
      positiveText: t('common.confirm'),
@@ -384,6 +385,7 @@
function handleDelete(row: WorkOrderItem) {
  window.$dialog?.warning({
    maskClosable: false,
    title: t('workOrder.confirmDelete'),
    content: t('workOrder.confirmDeleteContent'),
    positiveText: t('common.confirm'),
src/views/system/accountPermission/permissionGroup/index.vue
@@ -167,6 +167,7 @@
async function handleDelete(id: number) {
  dialog.warning({
    maskClosable: false,
    title: t('permissionGroup.messages.deleteConfirmTitle'),
    content: t('permissionGroup.messages.deleteConfirmContent'),
    positiveText: t('common.confirm'),
src/views/system/accountPermission/roleManagement/components/RoleModal.vue
@@ -105,7 +105,7 @@
    positiveText: t('roleManagement.modal.closeConfirm.confirm'),
    negativeText: t('roleManagement.modal.closeConfirm.continue'),
    maskClosable: false,
                autoFocus: false,
    autoFocus: false,
    onPositiveClick: () => {
      closeModal()
    },
@@ -213,7 +213,6 @@
          <NFormItem :label="t('roleManagement.modal.form.roleName')" path="roleName" required>
            <NInput
              v-model:value="formModel.roleName" :placeholder="t('roleManagement.modal.placeholder.roleName')" :disabled="mode === 'view'" :maxlength="20"
            />
          </NFormItem>
        </NGridItem>
src/views/system/accountPermission/roleManagement/detail.vue
@@ -364,10 +364,10 @@
            {{ t("roleManagement.detail.roleName") }}:<span class="detail-item-value">{{ roleDetailData.roleName }}</span>
          </div>
          <div class="detail-item detail-item-fixed">
            {{ t("roleManagement.detail.relatedPermissionGroup") }}:<span class="detail-item-value" >{{ roleDetailData.groupName }}</span>
            {{ t("roleManagement.detail.relatedPermissionGroup") }}:<span class="detail-item-value">{{ roleDetailData.groupName }}</span>
          </div>
          <div class="detail-item">
            {{ t("roleManagement.detail.remarks") }}:<span class="detail-item-value"  >{{ roleDetailData.remarks }}</span>
            {{ t("roleManagement.detail.remarks") }}:<span class="detail-item-value">{{ roleDetailData.remarks }}</span>
          </div>
        </NSpace>
        <!-- <div style="margin-top: 16px; display: flex; justify-content: flex-end;">
@@ -404,7 +404,7 @@
                      :src="noDataImg"
                      class="empty-image"
                      @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
                    />
                    >
                  </div>
                </template>
              </NTree>
@@ -473,7 +473,7 @@
                      <img
                        :src="noDataImg"
                        class="empty-image"
                      />
                      >
                    </div>
                  </template>
                </NDataTable>
src/views/system/accountPermission/roleManagement/form.vue
@@ -610,6 +610,7 @@
function handleResetPermissions() {
  dialog.warning({
    maskClosable: false,
    title: '重置确认',
    content: '确定将所有权限恢复为初始状态吗?',
    positiveText: '确定',
src/views/system/accountPermission/roleManagement/index.vue
@@ -309,6 +309,7 @@
function handleDelete(id: number) {
  window.$dialog.warning({
    maskClosable: false,
    title: t('roleManagement.messages.deleteConfirmTitle'),
    content: t('roleManagement.messages.deleteConfirmContent'),
    positiveText: t('common.confirm'),
src/views/system/appVersion/hotfix/index.vue
@@ -153,12 +153,12 @@
                style="width: 240px;"
                clearable
                :time-picker-props="{
                        onConfirm: (val) => {
                          formModel.startTime = val
                          startTimeRef?.handlePanelClose()
                        },
                      }"
                :actions="[]"
                  onConfirm: (val) => {
                    formModel.startTime = val
                    startTimeRef?.handlePanelClose()
                  },
                }"
                :actions="[]"
                :update-value-on-close="true" :close-on-select="true"
              />
            </n-form-item>
@@ -175,11 +175,11 @@
                :actions="[]" :update-value-on-close="true" :close-on-select="true"
                clearable
                :time-picker-props="{
                        onConfirm: (val) => {
                          formModel.endTime = val
                          endTimeRef?.handlePanelClose()
                        },
                      }"
                  onConfirm: (val) => {
                    formModel.endTime = val
                    endTimeRef?.handlePanelClose()
                  },
                }"
              />
            </n-form-item>
          </n-space>
src/views/system/evaluationTag/index.vue
@@ -2,7 +2,7 @@
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns } from 'naive-ui'
import { computed, h, onMounted, reactive, ref, watch } from 'vue'
import { NButton, NTag, NSwitch, NTooltip, useMessage } from 'naive-ui'
import { NButton, NSwitch, NTag, NTooltip, useMessage } from 'naive-ui'
import dayjs from 'dayjs'
import dragIcon from '@/assets/point/union.png'
import tooltipIcon from '@/assets/element-img/tooltip.png'
@@ -416,7 +416,7 @@
      tagContent: payload.labelContent,
    })
    if (!isSuccessCode(res?.code)) {
      // message.error(res?.msg || '保存失败')
      // message.error(res?.msg || '保存失败')
      return
    }
@@ -463,7 +463,7 @@
      status: nextStatus as 0 | 1,
    })
    if (!isSuccessCode(res?.code)) {
      // message.error(res?.msg || '状态更新失败')
      // message.error(res?.msg || '状态更新失败')
      return
    }
    await loadTabList(activeTab.value)
@@ -530,7 +530,7 @@
      sortOrder: item.sortOrder,
    })))
    if (!isSuccessCode(res?.code)) {
      // message.error(res?.msg || '排序更新失败')
      // message.error(res?.msg || '排序更新失败')
      return
    }
    await loadTabList(activeTab.value)
@@ -725,8 +725,12 @@
                    @update:value="markDirty"
                  >
                    <n-space>
                      <n-radio :value="1"> 是 </n-radio>
                      <n-radio :value="0"> 否 </n-radio>
                      <n-radio :value="1">
                        是
                      </n-radio>
                      <n-radio :value="0">
                        否
                      </n-radio>
                    </n-space>
                  </n-radio-group>
                </n-form-item>
@@ -787,11 +791,11 @@
                            }`"
                            style="width: 16px; height: 16px"
                            alt="arrow"
                          />
                          >
                        </template>
                        <template #empty>
                          <div class="flex-center">
                            <img :src="noDataImg" style="width: 50%" />
                            <img :src="noDataImg" style="width: 50%">
                          </div>
                        </template>
                      </n-select>
@@ -813,7 +817,9 @@
                    </n-form-item>
                  </n-gi>
                  <template #action>
                    <NButton @click="handleResetSearch"> 重置 </NButton>
                    <NButton @click="handleResetSearch">
                      重置
                    </NButton>
                    <NButton type="primary" @click="handleSearch">
                      查询
                    </NButton>
@@ -825,7 +831,9 @@
            <n-card class="robotaxi-table-card" :bordered="false">
              <template #header>
                <n-flex justify="space-between">
                  <NButton @click="handleOpenOperationLog"> 操作日志 </NButton>
                  <NButton @click="handleOpenOperationLog">
                    操作日志
                  </NButton>
                  <div class="flex items-center justify-end">
                    <NButton type="primary" secondary @click="handleAddTag">
                      新增
@@ -852,7 +860,7 @@
                        :src="noDataImg"
                        style="width: 123px; height: 125px"
                        @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
                      />
                      >
                    </div>
                  </template>
                </n-data-table>
@@ -873,7 +881,9 @@
    <div class="footer">
      <n-space justify="end">
        <NButton class="w-84px" @click="handleCancelChanges"> 取消 </NButton>
        <NButton class="w-84px" @click="handleCancelChanges">
          取消
        </NButton>
        <NButton
          v-permission="2026061535"
          type="primary"
src/views/system/financeBill/index.vue
@@ -237,7 +237,7 @@
    <n-card class="flex-1 robotaxi-table-card" :bordered="false">
      <template #header>
        <div class="flex items-center justify-end">
          <NButton type="primary" v-permission="2026061530" secondary @click="handleManualPush">
          <NButton v-permission="2026061530" type="primary" secondary @click="handleManualPush">
            手动推送
          </NButton>
        </div>
src/views/testDemo/geofenceManagement/detail.vue
@@ -1649,6 +1649,7 @@
    else if (modalType.value === 'edit') {
      if (formModel.value.areaStatus !== fenceDetail.value.electronicFenceAreas.find((item: any) => item.areaType === 1)?.areaStatus) {
        dialog.create({
          maskClosable: false,
          title: `确认${formModel.value.areaStatus === 1 ? '启用' : '停用'}该围栏吗?`,
          content: '',
          positiveText: '确定',
src/views/testDemo/geofenceManagement/index.vue
@@ -217,6 +217,7 @@
            disabled={row.status !== 3}
            onClick={() => {
              dialog.create({
                maskClosable: false,
                title: '是否确认复制?',
                content: `复制为【${row.electronicName}_副本】`,
                positiveText: '确定',
src/views/testDemo/operationWorkbench/map.vue
@@ -6427,7 +6427,7 @@
  isPointAddressDropdownVisible.value = show
}
function handleMapCanvasClick(event: any) {
async function handleMapCanvasClick(event: any) {
  const lnglat = event?.lnglat
  if (!lnglat)
    return
@@ -6465,6 +6465,16 @@
    updatePickedPoint([lnglat.getLng(), lnglat.getLat()], true)
    return
  }
  if (
    activeTab.value === 'point'
    && !isPointFormMode.value
    && !pointPanelClosed.value
    && !!selectedId.value
    && !!pointDetailData.value
  ) {
    await handleClosePointPanel({ clearSelection: true })
    return
  }
  if (activeTab.value !== 'fence' || !isFenceFormMode.value)
    return
  const pendingArea = fenceRestrictedAreas.value.find(
src/views/testDemo/specialPointManagement/components/SelectPointModal.vue
@@ -160,7 +160,7 @@
</script>
<template>
  <n-modal :show="show" @update:show="emit('update:show', $event)">
  <n-modal :mask-closable="false" :show="show" @update:show="emit('update:show', $event)">
    <n-card
      style="width: 1000px; height: 80vh; border-radius: 8px; display: flex; flex-direction: column;"
      :bordered="false"
src/views/testDemo/testPlanList/add.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTabStore } from '@/store'
import SelectPointModal from '@/views/testDemo/specialPointManagement/components/SelectPointModal.vue'
@@ -155,12 +155,12 @@
async function initCreatePlanId() {
  const res = await addTestPlan()
  if (res?.code !== 200) {
    // message.error(res?.message || '初始化测试计划失败')
    // message.error(res?.message || '初始化测试计划失败')
    return
  }
  const nextPlanId = resolvePlanId(res.data)
  if (!nextPlanId) {
    // message.error('初始化测试计划失败')
    // message.error('初始化测试计划失败')
    return
  }
  planId.value = nextPlanId
@@ -317,7 +317,7 @@
    message.error(res?.message || '订单生成失败')
  }
  catch {
    // message.error('订单生成失败,请稍后重试')
    // message.error('订单生成失败,请稍后重试')
  }
}
@@ -356,17 +356,17 @@
const { bool: orderLoading, setTrue: startOrderLoading, setFalse: endOrderLoading } = useBoolean(false)
const statusOptions = [
  { label: '待接单', value: 1, type: 'primary',color:'#E6F4FF',textColor:'#0068FF' },
  { label: '待出发', value: 2, type: 'primary' ,color:'#E6F4FF',textColor:'#0068FF' },
  { label: '接驾中', value: 3, type: 'warning' ,color:'#E6F4FF',textColor:'#0068FF' },
  { label: '待客上车', value: 4, type: 'primary',color:'#E6F4FF',textColor:'#0068FF'  },
  { label: '乘客已上车', value: 5, type: 'success',color:'#F6F0FF',textColor:'#764DFF' },
  { label: '服务中', value: 6, type: 'success',color:'#F6F0FF',textColor:'#764DFF' },
  { label: '待客下车', value: 7, type: 'warning' ,color:'#F6F0FF',textColor:'#764DFF'},
  { label: '待付款', value: 8, type: 'warning' ,color:'#FFF8E6',textColor:'#FF9000'},
  { label: '待评价', value: 9, type: 'default',color:'#FFF8E6',textColor:'#FF9000' },
  { label: '已完成', value: 10, type: 'success',color:'#DCF5DD',textColor:'#0BB52D' },
  { label: '已取消', value: 0, type: 'default',color:'#171F260D',textColor:'#171F26CC' },
  { label: '待接单', value: 1, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '待出发', value: 2, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '接驾中', value: 3, type: 'warning', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '待客上车', value: 4, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '乘客已上车', value: 5, type: 'success', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '服务中', value: 6, type: 'success', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '待客下车', value: 7, type: 'warning', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '待付款', value: 8, type: 'warning', color: '#FFF8E6', textColor: '#FF9000' },
  { label: '待评价', value: 9, type: 'default', color: '#FFF8E6', textColor: '#FF9000' },
  { label: '已完成', value: 10, type: 'success', color: '#DCF5DD', textColor: '#0BB52D' },
  { label: '已取消', value: 0, type: 'default', color: '#171F260D', textColor: '#171F26CC' },
]
const orderStatusOptions = statusOptions.map(item => ({ label: item.label, value: item.value }))
@@ -1044,6 +1044,7 @@
  }
  dialog.create({
    maskClosable: false,
    title: '确认启动批量测试?',
    content: `将为 ${vehicles.value.length} 辆车按 ${testPoints.value.length} 个点位组合生成链式巡游订单,每车跑 ${lapCount.value} 圈`,
    positiveText: '确定',
@@ -1058,7 +1059,7 @@
        startBatchPolling()
      }
      else {
        // message.error(res?.message || '启动失败')
        // message.error(res?.message || '启动失败')
      }
    },
  })
@@ -1075,7 +1076,7 @@
      startBatchPolling()
  }
  else {
    // message.error(res?.message || '重试失败')
    // message.error(res?.message || '重试失败')
  }
}
@@ -1097,7 +1098,7 @@
        await refreshBatchData()
      }
      else {
        // message.error(res?.message || '取消失败')
        // message.error(res?.message || '取消失败')
      }
    },
  })
@@ -1183,6 +1184,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '故障恢复确认',
    content: `⚠️ 故障恢复将跳过订单 ${row.currentOrderId},并立即派发该车辆的下一段订单。\n\n• 此操作适用于车辆故障已修复、旧订单确认无法继续的场景\n• 旧订单将被标记为「已取消」,不会通知车端\n• 操作前请确认车端已不再执行旧订单,否则可能与新订单冲突\n• ERROR 状态的车辆执行后将重新进入 RUNNING 并派发下一段订单\n\n是否继续?`,
    positiveText: '确定',
@@ -1202,7 +1204,7 @@
          await refreshBatchData()
        }
        else {
          // message.error(res?.message || res?.msg || '故障恢复失败')
          // message.error(res?.message || res?.msg || '故障恢复失败')
        }
      }
      finally {
@@ -1525,9 +1527,9 @@
          <span class="font-bold" style="font-size: 16px">批次进度</span>
        </div>
        <div class="flex items-center gap-6 mb-4">
          <n-tag :type="getBatchStatusType(batchProgress.status)" size="medium">
          <NTag :type="getBatchStatusType(batchProgress.status)" size="medium">
            {{ getBatchStatusLabel(batchProgress.status) }}
          </n-tag>
          </NTag>
          <span v-if="batchProgress.startTime" class="text-sm text-gray-500">
            开始:{{ dayjs(batchProgress.startTime).format('YYYY/MM/DD HH:mm:ss') }}
          </span>
src/views/testDemo/testPlanList/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NSpace, NTooltip } from 'naive-ui'
import { NButton, NSpace } from 'naive-ui'
import { computed, onActivated, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
src/views/testDemo/testPlanManagement/components/AssignVehicleModal.vue
@@ -118,7 +118,7 @@
  catch (error: any) {
    listData.value = []
    count.value = 0
    // message.error(error?.message || '获取车辆列表失败')
    // message.error(error?.message || '获取车辆列表失败')
  }
  finally {
    loading.value = false
@@ -201,7 +201,7 @@
    show.value = false
  }
  catch (error: any) {
    // message.error(error?.message || (isAssignAction.value ? '分派车辆失败' : '改派车辆失败'))
    // message.error(error?.message || (isAssignAction.value ? '分派车辆失败' : '改派车辆失败'))
  }
  finally {
    submitting.value = false
src/views/testDemo/testPlanManagement/components/ManageTestVehicleModal.vue
@@ -123,7 +123,7 @@
  catch (error: any) {
    listData.value = []
    count.value = 0
    // message.error(error?.message || '获取测试车辆失败')
    // message.error(error?.message || '获取测试车辆失败')
  }
  finally {
    loading.value = false
@@ -191,7 +191,7 @@
    emit('success')
  }
  catch (error: any) {
    // message.error(error?.message || successText)
    // message.error(error?.message || successText)
  }
  finally {
    actionLoading.value = false
@@ -218,7 +218,7 @@
    emit('success')
  }
  catch (error: any) {
    // message.error(error?.message || successText)
    // message.error(error?.message || successText)
  }
  finally {
    actionLoading.value = false
@@ -231,6 +231,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '确认批量移除所选车辆吗?',
    content: '移除后将从测试计划中删除',
    positiveText: '确定',
@@ -247,6 +248,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '确认批量暂停所选车辆吗?',
    content: '所选车辆可能正在执行订单,暂停服务后将无法接收新订单',
    positiveText: '确定',
@@ -271,6 +273,7 @@
function handleRemove(row: VehicleRow) {
  dialog.warning({
    maskClosable: false,
    title: '确认移除该车辆吗?',
    content: '移除后将从测试计划中删除',
    positiveText: '确定',
@@ -287,6 +290,7 @@
  if (paused === 1) {
    dialog.warning({
      maskClosable: false,
      title: '确认暂停该车辆吗?',
      content: '该车辆正在执行订单,暂停服务后将无法接收新订单',
      positiveText: '确定',
src/views/testDemo/testPlanManagement/components/OrderGenModeDescModal.vue
@@ -1,6 +1,6 @@
<script setup lang="tsx">
import { computed } from 'vue'
import { NModal, NCard } from 'naive-ui'
import { NModal } from 'naive-ui'
import type1Img from '@/assets/element-img/type1.png'
import type2Img from '@/assets/element-img/type2.png'
@@ -14,7 +14,7 @@
const show = computed({
  get: () => props.visible,
  set: (val) => emit('update:visible', val)
  set: val => emit('update:visible', val),
})
function handleCancel() {
@@ -23,8 +23,9 @@
</script>
<template>
  <n-modal
  <NModal
    v-model:show="show"
    :mask-closable="false"
    preset="card"
    title="订单生成方式说明"
    class="w-[800px] order-gen-modal"
@@ -34,11 +35,13 @@
      <!-- 全排列模式 -->
      <div>
        <div class="flex items-center gap-8px mb-12px">
          <div class="w-4px h-16px bg-[#0068FF] rounded-sm"></div>
          <div class="w-4px h-16px bg-[#0068FF] rounded-sm" />
          <span class="text-16px font-bold text-[#333]">全排列模式</span>
        </div>
        <div class="bg-[#F7F8FA] rounded-4px p-12px">
          <div class="mb-12px text-13px" style="color:rgba(23, 31, 38, 0.6)">生成方式说明</div>
          <div class="mb-12px text-13px" style="color:rgba(23, 31, 38, 0.6)">
            生成方式说明
          </div>
          <div class="flex items-start items justify-between gap-32px">
            <div class="flex-1 text-[#171F26] text-14px leading-24px">
              输入A\B\C\D\E\F等多个点位(所有的遍历一次,正逆向,屏蔽过边界的道路和高德生成道路失败的订单,订单生成失败需要有标记),生成所有点位间的两两订单序列
@@ -55,11 +58,13 @@
      <!-- 串烧模式 -->
      <div>
        <div class="flex items-center gap-8px mb-12px">
          <div class="w-4px h-16px bg-[#0068FF] rounded-sm"></div>
          <div class="w-4px h-16px bg-[#0068FF] rounded-sm" />
          <span class="text-16px font-bold text-[#333]">串烧模式</span>
        </div>
        <div class="bg-[#F7F8FA] rounded-4px p-12px">
          <div class="mb-12px text-13px" style="color:rgba(23, 31, 38, 0.6)">生成方式说明</div>
          <div class="mb-12px text-13px" style="color:rgba(23, 31, 38, 0.6)">
            生成方式说明
          </div>
          <div class="flex items-start items justify-between gap-32px">
            <div class="flex-1 text-[#171F26] text-14px leading-24px">
              生成方式说明:输入A、B、C、D、E等多个点位,生成A→B→C→D→E→...A的闭环订单序列,顺向和逆向都需要生成;
@@ -73,7 +78,7 @@
        </div>
      </div>
    </div>
  </n-modal>
  </NModal>
</template>
<style scoped>
src/views/testDemo/testPlanManagement/detail.vue
@@ -293,7 +293,7 @@
      return ['取消订单', '改派', '删除']
    case 6:
      return ['关闭订单', '详情', '删除']
       case 10:
    case 10:
      return ['详情', '删除']
    default:
      return ['删除']
@@ -699,6 +699,7 @@
function confirmDeleteOrders(orderIds: Array<number | string>) {
  dialog.warning({
    maskClosable: false,
    title: '确认删除订单吗?',
    content: '删除后无法恢复',
    positiveText: '确定',
@@ -735,6 +736,7 @@
  }
  if (action === '取消订单') {
    dialog.warning({
      maskClosable: false,
      title: '确认取消订单吗?',
      positiveText: '确定',
      negativeText: '取消',
@@ -750,6 +752,7 @@
  }
  if (action === '关闭订单') {
    dialog.warning({
      maskClosable: false,
      title: '确认关闭订单吗?',
      positiveText: '确定',
      negativeText: '取消',
@@ -786,6 +789,7 @@
  const isResume = planStatusCode.value === 3
  dialog.warning({
    maskClosable: false,
    title: isResume ? '是否确认继续下发' : '是否确认中断下发',
    content: isResume ? '继续后,系统将继续下发剩余订单' : '中断后,系统将暂停下发剩余订单',
    positiveText: '确定',
src/views/testDemo/testPlanManagement/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NSpace, NTag, NTooltip, useDialog, useMessage } from 'naive-ui'
import { NButton, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useBoolean } from '@/hooks'
@@ -197,6 +197,7 @@
function handleInterrupt(row: TestPlanRow) {
  dialog.warning({
    maskClosable: false,
    title: '是否确认中断下发',
    content: '中断后,系统将暂停下发剩余订单',
    positiveText: '确定',
@@ -220,6 +221,7 @@
function handleClose(row: TestPlanRow) {
  dialog.warning({
    maskClosable: false,
    title: '确认关闭测试吗?',
    content: '关闭后测试计划将终止',
    positiveText: '确定',
@@ -243,6 +245,7 @@
function handleContinue(row: TestPlanRow) {
  dialog.info({
    maskClosable: false,
    title: '确认继续测试吗?',
    content: '测试计划将继续执行',
    positiveText: '确定',
src/views/testDemo/testPointManagement/index.vue
@@ -3,7 +3,7 @@
import { computed, nextTick, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
import { useScriptTag } from '@vueuse/core'
import { useRoute } from 'vue-router'
import { createPoint, deletePoint, enableDisablePoint, fetchElectronicFenceAreaList, fetchPointList, updatePoint } from '@/service/api/copyText'
import { createPoint, deletePoint, fetchElectronicFenceAreaList, fetchPointList, updatePoint } from '@/service/api/copyText'
import PointStatusSteps from '@/components/PointStatusSteps/custom.vue'
import ConfirmDialog from '@/components/ConfirmDialog/index.vue'
import { debounce } from 'radash'
src/views/testDemo/testPointManagement/list.vue
@@ -476,6 +476,7 @@
  const targetStatus = isEnabled ? 3 : 2
  dialog.warning({
    maskClosable: false,
    title: '提示',
    content: `确认${actionText}该点位吗?`,
    positiveText: '确定',
@@ -515,7 +516,7 @@
    }
  }
  catch {
    // message.error('导出失败')
    // message.error('导出失败')
  }
}
src/views/transaction/directServiceManagement/index.vue
@@ -348,6 +348,7 @@
// 删除
function handleDelete(row: any) {
  dialog.create({
    maskClosable: false,
    title: '删除确认',
    content: '确认删除该定向服务?删除后不可恢复。',
    positiveText: '确定',
@@ -368,6 +369,7 @@
  const statusValue = targetStatus ? 1 : 2
  const actionText = targetStatus ? '启用' : '禁用'
  dialog.create({
    maskClosable: false,
    title: `${actionText}确认`,
    content: `确定要${actionText}该定向服务吗?`,
    positiveText: '确定',
src/views/transaction/order/orderManagement/components/Modal.vue
@@ -60,20 +60,21 @@
      type: 'number',
      required: true,
      message: '请输入优惠金额',
      trigger: ['blur', 'input']
      trigger: ['blur', 'input'],
    },
    {
      validator: (rule: any, value: number | null) => {
        if (value === null || value === undefined) return true
        if (value === null || value === undefined)
          return true
        const payAmount = Number(formModel.value.payAmount)
        if (!isNaN(payAmount) && value > maxDiscountAmount.value) {
          return new Error('优惠后金额需大于1元')
        }
        return true
      },
      trigger: ['input', 'blur']
    }
  ]
      trigger: ['input', 'blur'],
    },
  ],
}
async function submitModal() {
src/views/transaction/order/orderManagement/index.vue
@@ -6,7 +6,7 @@
import { cancelAndCloseOrder, deleteOrder, exportOrderExcel, getFleetList, getOrderList, refundOrder } from '@/service/api/transaction'
import { operationPageList } from '@/service/api/vehicleConvoy'
import { getRegionCascaderNoArea } from '@/service/api/operations'
import { NButton, NSpace, useMessage, useDialog,NTag } from 'naive-ui'
import { NButton, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import dayjs from 'dayjs'
@@ -202,17 +202,17 @@
//   },
// ]
const statusOptions = [
  { label: '待接单', value: 1, type: 'primary',color:'#E6F4FF',textColor:'#0068FF' },
  { label: '待出发', value: 2, type: 'primary' ,color:'#E6F4FF',textColor:'#0068FF' },
  { label: '接驾中', value: 3, type: 'warning' ,color:'#E6F4FF',textColor:'#0068FF' },
  { label: '待客上车', value: 4, type: 'primary',color:'#E6F4FF',textColor:'#0068FF'  },
  { label: '乘客已上车', value: 5, type: 'success',color:'#F6F0FF',textColor:'#764DFF' },
  { label: '服务中', value: 6, type: 'success',color:'#F6F0FF',textColor:'#764DFF' },
  { label: '待客下车', value: 7, type: 'warning' ,color:'#F6F0FF',textColor:'#764DFF'},
  { label: '待付款', value: 8, type: 'warning' ,color:'#FFF8E6',textColor:'#FF9000'},
  { label: '待评价', value: 9, type: 'default',color:'#FFF8E6',textColor:'#FF9000' },
  { label: '已完成', value: 10, type: 'success',color:'#DCF5DD',textColor:'#0BB52D' },
  { label: '已取消', value: 0, type: 'default',color:'#171F260D',textColor:'#171F26CC' },
  { label: '待接单', value: 1, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '待出发', value: 2, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '接驾中', value: 3, type: 'warning', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '待客上车', value: 4, type: 'primary', color: '#E6F4FF', textColor: '#0068FF' },
  { label: '乘客已上车', value: 5, type: 'success', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '服务中', value: 6, type: 'success', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '待客下车', value: 7, type: 'warning', color: '#F6F0FF', textColor: '#764DFF' },
  { label: '待付款', value: 8, type: 'warning', color: '#FFF8E6', textColor: '#FF9000' },
  { label: '待评价', value: 9, type: 'default', color: '#FFF8E6', textColor: '#FF9000' },
  { label: '已完成', value: 10, type: 'success', color: '#DCF5DD', textColor: '#0BB52D' },
  { label: '已取消', value: 0, type: 'default', color: '#171F260D', textColor: '#171F26CC' },
]
const columns = computed<DataTableColumns<any>>(() => [
src/views/transaction/passwordFreeStrategy/add.vue
@@ -190,6 +190,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '提示',
    content: '是否放弃当前编辑的免密支付策略?',
    positiveText: '放弃',
src/views/transaction/passwordFreeStrategy/index.vue
@@ -73,7 +73,7 @@
  {
    label: '城市',
    path: 'cityName',
    render: () => <n-input v-model:value={model.value.cityName} allow-input={(value: string) => !value || /^[a-zA-Z\u4e00-\u9fa5]+$/.test(value)} clearable placeholder="请输入城市" />,
    render: () => <n-input v-model:value={model.value.cityName} allow-input={(value: string) => !value || /^[a-z\u4E00-\u9FA5]+$/i.test(value)} clearable placeholder="请输入城市" />,
  },
  {
    label: '生效时间段',
@@ -278,6 +278,7 @@
// 删除
function handleDelete(row: any) {
  dialog.create({
    maskClosable: false,
    title: '删除确认',
    content: '确定要删除该免密支付策略吗?删除后不可恢复。',
    positiveText: '确定',
src/views/transaction/pricingManagement/components/TableModal.vue
@@ -132,6 +132,7 @@
  if (result) {
    if (modalType.value === 'add') {
      dialog.success({
        maskClosable: false,
        title: '创建成功,是否去设置价格?',
        content: '',
        positiveText: '设置价格',
@@ -155,6 +156,7 @@
    else {
      if (data.pricingType !== formModel.value.pricingType) {
        dialog.success({
          maskClosable: false,
          title: '编辑成功,是否去设置价格?',
          content: '',
          positiveText: '设置价格',
src/views/transaction/transactionStrategy/index.vue
@@ -8,7 +8,6 @@
  getCityList,
  getTradingStrategyDetail,
} from '@/service/api/transaction'
import { handleInputNumberKeyDown } from '@/utils/common'
import dayjs from 'dayjs'
const {
@@ -298,6 +297,7 @@
function handleDeleteSpecialTimeRule(rule: SpecialTimeStrategyItem) {
  dialog.warning({
    maskClosable: false,
    title: '删除确认',
    content: isSpecialTimeRuleEffective(rule)
      ? '该时段规则正在生效,删除后订单将按基础策略执行,是否确认删除?'
src/views/userContent/active/activityManagement/add.vue
@@ -379,7 +379,7 @@
function createCouponColumns(strategyIndex: number, type: 'normal' | 'afterUse'): DataTableColumns<any> {
  return [
    { title: '添加时间', key: 'createTime', width: 220 },
    { title: '添加时间', key: 'createTime', width: 220, render: row => dayjs(row.createTime).format('YYYY/MM/DD HH:mm') },
    { title: '优惠券名称', key: 'couponName', ellipsis: true },
    {
      title: '优惠券类型',
@@ -1357,7 +1357,7 @@
</script>
<template>
  <div class=" w-full h-full relative content flex flex-col">
  <div class=" w-full h-full relative content flex flex-col" >
    <div class="p-12px main flex flex-col flex-1 overflow-hidden">
      <NTabs v-model:value="activeTab" type="line" class="custom-tabs" animated>
        <NTabPane :name="1" tab="活动介绍内容" />
@@ -2187,6 +2187,12 @@
  box-shadow: 0px 0px 5px 0px rgba(23, 31, 38, 0.05);
  padding: 16px 24px;
}
.n-button--secondary {
  box-sizing: border-box;
    color: #0068FF;
    border: 1px solid #7ABDFF;
    background-color: #E6F4FF;
}
:deep(.n-tabs-tab-pad) {
  width: 24px !important;
src/views/userContent/active/activityManagement/components/CouponSelectModal.vue
@@ -111,7 +111,7 @@
    title: '优惠券名称',
    key: 'couponName',
    render: (row) => {
      return <span style={{ color: '#1890FF', cursor: 'pointer' }}>{row.couponName}</span>
      return <span>{row.couponName}</span>
    },
  },
  {
@@ -187,6 +187,39 @@
  pageSize.value = size
  currentPage.value = isPageSizeChanged ? 1 : page
  getList()
}
function toggleRowSelection(row: any) {
  const rowKey = String(row?.id ?? '')
  if (!rowKey)
    return
  if (checkedRowKeys.value.includes(rowKey)) {
    checkedRowKeys.value = checkedRowKeys.value.filter(key => key !== rowKey)
    return
  }
  checkedRowKeys.value = [...checkedRowKeys.value, rowKey]
}
function shouldIgnoreRowClick(target: EventTarget | null) {
  const element = target as HTMLElement | null
  return Boolean(
    element?.closest(
      '.n-checkbox, .n-checkbox-box, .n-checkbox-input, button, a, input, textarea, .n-base-selection',
    ),
  )
}
function getRowProps(row: any) {
  return {
    style: 'cursor: pointer;',
    onClick: (event: MouseEvent) => {
      if (shouldIgnoreRowClick(event.target))
        return
      toggleRowSelection(row)
    },
  }
}
function handleSave() {
@@ -268,7 +301,7 @@
          <!-- 表格区域 -->
          <n-data-table
            v-model:checked-row-keys="checkedRowKeys" style="height: 100%;" flex-height :columns="columns" :data="list" :loading="loading"
            :bordered="false" :row-key="(row) => String(row.id)" :pagination="false"
            :bordered="false" :row-key="(row) => String(row.id)" :row-props="getRowProps" :pagination="false"
          >
            <template #empty>
              <div class="flex-center">
src/views/userContent/active/activityManagement/components/ReceiveRecord.vue
@@ -86,6 +86,7 @@
    return
  }
  dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: '确定要删除选中的记录吗?',
    positiveText: '确定',
src/views/userContent/active/activityManagement/components/UserSelectModal.vue
@@ -101,7 +101,7 @@
    render(row) {
      const isNormal = isNormalStatus(row.status)
      return (
        <NTag type={isNormal ? 'info' : 'warning'} size="small" bordered={false} style={{ color: isNormal ? '#1890FF' : '#FAAD14', backgroundColor: isNormal ? '#E6F7FF' : '#FFFBE6' }}>
        <NTag color={{ color: isNormal ? '#E6F4FF' : '#FFF8E6', textColor: isNormal ? '#0068FF' : '#FF9000' }} size="small" bordered={false}>
          {isNormal ? '正常' : '冻结'}
        </NTag>
      )
@@ -163,6 +163,42 @@
    currentPage.value = page
  }
  getList()
}
function toggleRowSelection(row: any) {
  if (!isNormalStatus(row?.status))
    return
  const rowKey = String(row?.id ?? '')
  if (!rowKey)
    return
  if (checkedRowKeys.value.includes(rowKey)) {
    checkedRowKeys.value = checkedRowKeys.value.filter(key => key !== rowKey)
    return
  }
  checkedRowKeys.value = [...checkedRowKeys.value, rowKey]
}
function shouldIgnoreRowClick(target: EventTarget | null) {
  const element = target as HTMLElement | null
  return Boolean(
    element?.closest(
      '.n-checkbox, .n-checkbox-box, .n-checkbox-input, button, a, input, textarea, .n-base-selection',
    ),
  )
}
function getRowProps(row: any) {
  return {
    style: isNormalStatus(row?.status) ? 'cursor: pointer;' : '',
    onClick: (event: MouseEvent) => {
      if (shouldIgnoreRowClick(event.target))
        return
      toggleRowSelection(row)
    },
  }
}
function handleSave() {
@@ -283,7 +319,7 @@
          <!-- 表格区域 -->
          <n-data-table
            v-model:checked-row-keys="checkedRowKeys" style="height: 100%;" flex-height :columns="columns" :data="list" :loading="loading"
            :bordered="false" :row-key="(row) => String(row.id)" :pagination="false"
            :bordered="false" :row-key="(row) => String(row.id)" :row-props="getRowProps" :pagination="false"
          >
            <template #empty>
              <div class="flex-center">
src/views/userContent/active/activityManagement/index.vue
@@ -77,6 +77,7 @@
function handlePublish(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '确认发布',
    content: '确认发布该活动吗?',
    positiveText: '确定',
@@ -108,6 +109,7 @@
function handlePause(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '确认暂停',
    content: '确认暂停该活动吗?',
    positiveText: '确定',
@@ -125,6 +127,7 @@
function handleResume(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '确认恢复',
    content: '确认恢复该活动吗?',
    positiveText: '确定',
@@ -142,6 +145,7 @@
function handleStop(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '确认终止',
    content: '确认终止该活动吗?终止后不可恢复。',
    positiveText: '确定',
@@ -159,6 +163,7 @@
function handleDelete(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '是否确认删除数据?',
    content: '确认删除该活动?删除后不可恢复。',
    positiveText: '确定',
src/views/userContent/active/coupon/BatchSendModal.vue
@@ -56,7 +56,7 @@
const isUserTypeSelectOpen = ref(false)
const isStatusSelectOpen = ref(false)
const checkedRowKeys = ref<number[]>([])
const checkedRowKeys = ref<string[]>([])
const list = ref<any[]>([])
const currentPage = ref(1)
@@ -108,7 +108,7 @@
    render(row) {
      const isNormal = isNormalStatus(row.status)
      return (
        <NTag type={isNormal ? 'info' : 'warning'} size="small" bordered={false} style={{ color: isNormal ? '#1890FF' : '#FAAD14', backgroundColor: isNormal ? '#E6F7FF' : '#FFFBE6' }}>
        <NTag color={{ color: isNormal ? '#E6F4FF' : '#FFF8E6', textColor: isNormal ? '#0068FF' : '#FF9000' }} size="small" bordered={false}>
          {isNormal ? '正常' : '冻结'}
        </NTag>
      )
@@ -162,6 +162,42 @@
  getList()
}
function toggleRowSelection(row: any) {
  if (!isNormalStatus(row?.status))
    return
  const rowKey = String(row?.id ?? '')
  if (!rowKey)
    return
  if (checkedRowKeys.value.includes(rowKey)) {
    checkedRowKeys.value = checkedRowKeys.value.filter(key => key !== rowKey)
    return
  }
  checkedRowKeys.value = [...checkedRowKeys.value, rowKey]
}
function shouldIgnoreRowClick(target: EventTarget | null) {
  const element = target as HTMLElement | null
  return Boolean(
    element?.closest(
      '.n-checkbox, .n-checkbox-box, .n-checkbox-input, button, a, input, textarea, .n-base-selection',
    ),
  )
}
function getRowProps(row: any) {
  return {
    style: isNormalStatus(row?.status) ? 'cursor: pointer;' : '',
    onClick: (event: MouseEvent) => {
      if (shouldIgnoreRowClick(event.target))
        return
      toggleRowSelection(row)
    },
  }
}
function handleSendAll() {
  // if (!isNormalStatus(searchModel.value.status)) {
  //   message.warning('一键发放所有用户仅支持“冻结状态”筛选为“正常”')
@@ -172,7 +208,7 @@
}
function handleSendSelected() {
  const selectedRows = list.value.filter(item => checkedRowKeys.value.includes(item.id))
  const selectedRows = list.value.filter(item => checkedRowKeys.value.includes(String(item?.id ?? '')))
  const hasFrozen = selectedRows.some(row => !isNormalStatus(row.status))
  if (hasFrozen) {
    message.warning('选中用户包含冻结状态,请仅选择“正常”用户')
@@ -273,7 +309,7 @@
          <!-- 表格区域 -->
          <n-data-table
            v-model:checked-row-keys="checkedRowKeys" style="height: 100%;" flex-height :columns="columns" :data="list" :loading="loading"
            :bordered="false" :row-key="(row) => row.id" :pagination="false"
            :bordered="false" :row-key="(row) => String(row.id)" :row-props="getRowProps" :pagination="false"
          >
            <template #empty>
              <div class="flex-center">
src/views/userContent/active/coupon/add.vue
@@ -643,7 +643,7 @@
<style scoped lang="scss">
.content {
  height: calc(100vh - 76px);
  height: calc(100vh - 76px - 28px);
  width: 100%;
  overflow-y: auto;
  scrollbar-width: none;
@@ -663,7 +663,7 @@
}
.box.full-height {
  height: calc(100vh - 76px - 64px);
  height: calc(100vh - 76px - 64px - 28px);
  padding: 24px;
  margin-bottom: 0;
}
src/views/userContent/active/coupon/index.vue
@@ -229,6 +229,7 @@
    return
  dialog.warning({
    maskClosable: false,
    title: '确认发放',
    // content: `确定向当前筛选条件下所有用户发放优惠券么?共${payload.userCount}个用户。`,
    content: `将发给状态为“正常”的用户,状态为“冻结”的用户将不发放,是否确认发放?`,
@@ -261,6 +262,7 @@
    return
  dialog.warning({
    maskClosable: false,
    title: '确认发放',
    content: `确定要给选中的 ${rows.length} 位用户发放该优惠券吗?`,
    positiveText: '确定',
src/views/userContent/banner/components/BannerActionCell.vue
@@ -33,7 +33,7 @@
      删除
    </span>
  </div>
  </template>
</template>
<style scoped>
</style>
src/views/userContent/banner/components/BannerFormModal.vue
@@ -228,7 +228,7 @@
      // }
    }
    catch {
      // message.error('图片读取失败,请重试')
      // message.error('图片读取失败,请重试')
      formModel.value.bannerPictures = ''
      fileList.value = []
      options.onError?.()
src/views/userContent/banner/index.vue
@@ -2,7 +2,7 @@
import noDataImg from '@/assets/element-img/no-data.png'
import { computed, h, onMounted, ref } from 'vue'
import type { DataTableColumns } from 'naive-ui'
import { NImage, NSwitch, NTag, useDialog, useMessage } from 'naive-ui'
import { NImage, NTag, useDialog, useMessage } from 'naive-ui'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import BannerFormModal from './components/BannerFormModal.vue'
@@ -403,6 +403,7 @@
function handleDelete(row: Banner) {
  dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确认删除广告 "${row.bannerTitle}" 吗?`,
    positiveText: '确定',
@@ -424,6 +425,7 @@
    return
  dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确认删除选中的 ${selectedRowKeys.value.length} 条广告吗?`,
    positiveText: '确定',
@@ -444,13 +446,13 @@
          fetchData()
        }
        else {
          // message.error('部分删除失败,请重试')
          // message.error('部分删除失败,请重试')
          fetchData()
        }
      }
      catch (e) {
        console.error(e)
        // message.error('删除失败,请重试')
        // message.error('删除失败,请重试')
      }
    },
  })
src/views/userContent/contentCenter/agreementVersion.vue
@@ -149,7 +149,7 @@
  }
  const res = await getLoginAgreementDetail(row.id)
  if (res?.code !== 200 || !res.data) {
    // message.error(res?.msg || '协议详情获取失败')
    // message.error(res?.msg || '协议详情获取失败')
    return null
  }
  return res.data
@@ -268,7 +268,7 @@
  const res = await getLoginAgreementLatestVersion(nameId)
  if (res?.code !== 200 || !res.data) {
    // message.error(res?.msg || '最新版本号获取失败')
    // message.error(res?.msg || '最新版本号获取失败')
    return ''
  }
  return String(res.data)
@@ -347,7 +347,7 @@
      type: agreementType.value,
    })
    if (res?.code !== 200 || !res.data) {
      // message.error(res?.msg || '协议版本列表获取失败')
      // message.error(res?.msg || '协议版本列表获取失败')
      return
    }
    const records = res.data.records || []
@@ -423,7 +423,7 @@
  try {
    const res = await copyLoginAgreement(row.id)
    if (res?.code !== 200) {
      // message.error(res?.msg || '复制失败')
      // message.error(res?.msg || '复制失败')
      return
    }
    message.success('复制成功')
@@ -447,7 +447,7 @@
      ? await enableLoginAgreement(row.id)
      : await disableLoginAgreement(row.id)
    if (res?.code !== 200) {
      // message.error(res?.msg || `${loadingType === 'enable' ? '启用' : '停用'}失败`)
      // message.error(res?.msg || `${loadingType === 'enable' ? '启用' : '停用'}失败`)
      return
    }
    message.success(loadingType === 'enable' ? '启用成功' : '停用成功')
@@ -468,6 +468,7 @@
function confirmEnableVersion(row: AgreementVersionRow) {
  dialog.warning({
    maskClosable: false,
    title: '提示',
    content: '启用新版本后,旧版本将失效,新用户将看到新版本协议,是否确认?',
    positiveText: '确认',
@@ -478,6 +479,7 @@
function confirmDisableVersion(row: AgreementVersionRow) {
  dialog.warning({
    maskClosable: false,
    title: '提示',
    content: '停用后乘客端将无协议展示,影响用户登录流程,是否确认停用?',
    positiveText: '确认',
src/views/userContent/contentCenter/components/AgreementListForm.vue
@@ -118,7 +118,7 @@
    }
    else {
      tableData.value = []
      // message.error(res?.msg || '协议列表获取失败')
      // message.error(res?.msg || '协议列表获取失败')
    }
  }
  finally {
@@ -173,7 +173,7 @@
async function handleEdit(row: AgreementItem) {
  const res = await getLoginAgreementDetail(row.nameId)
  if (res?.code !== 200 || !res.data) {
    // message.error(res?.msg || '协议详情获取失败')
    // message.error(res?.msg || '协议详情获取失败')
    return
  }
  router.push({
@@ -195,7 +195,7 @@
      await loadList()
    }
    else {
      // message.error(res?.msg || '删除失败')
      // message.error(res?.msg || '删除失败')
    }
  }
  finally {
@@ -238,7 +238,7 @@
  )
  const failed = responses.find(item => item?.code !== 200)
  if (failed) {
    // message.error(failed?.msg || '排序保存失败')
    // message.error(failed?.msg || '排序保存失败')
    await loadList()
    return false
  }
src/views/userContent/contentCenter/components/FaqForm.vue
@@ -107,7 +107,10 @@
                v-model:value="item.question"
                type="textarea"
                placeholder="请输入"
                auto-height
                :autosize="{
                  minRows: 1,
                  maxRows: 5,
                }"
                :bordered="false"
                :resizable="false"
              />
@@ -117,9 +120,12 @@
                v-model:value="item.answer"
                type="textarea"
                placeholder="请输入"
                :autosize="{
                  minRows: 1,
                  maxRows: 5,
                }"
                :bordered="false"
                :resizable="false"
                auto-height
              />
            </td>
            <td style="text-align: center;">
src/views/userContent/contentCenter/components/SimpleRichTextForm.vue
@@ -10,7 +10,7 @@
const formRef = ref<FormInst | null>(null)
const formModel = ref<FormModel>({
  content: ''
  content: '',
})
const rules: FormRules = {
@@ -35,11 +35,11 @@
<template>
  <div>
    <n-form
      ref="formRef"
    <n-form
      ref="formRef"
      :model="formModel"
      :rules="rules"
      label-placement="left"
      :rules="rules"
      label-placement="left"
      label-width="80"
      require-mark-placement="left"
    >
src/views/userContent/whitelistManagement/invitationCode/components/GenerateModal.vue
@@ -151,7 +151,7 @@
      positiveText: '确定',
      negativeText: '取消',
      maskClosable: false,
                autoFocus: false,
      autoFocus: false,
      onPositiveClick: () => {
        doClose()
      },
src/views/userContent/whitelistManagement/whitelist/components/WhitelistEditModal.vue
@@ -260,7 +260,7 @@
            <NInput v-model:value="formModel.nickName" placeholder="请输入" />
          </NFormItem>
        </NGridItem>
        <NGridItem>
          <NFormItem label="手机号" path="account">
            <NInput v-model:value="formModel.account" placeholder="请输入" />
src/views/userContent/whitelistManagement/whitelist/detail.vue
@@ -247,6 +247,7 @@
  }
  dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: '确认删除选中的记录吗?',
    positiveText: '确认',
src/views/userContent/whitelistManagement/whitelist/index.vue
@@ -3,7 +3,7 @@
import type { DataTableColumns } from 'naive-ui'
import { NButton, NTag, useDialog, useMessage } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { downloadFile } from '@/utils'
import { downloadFile, onlyAllowNumber, onlyAllowNumberAndLetter } from '@/utils'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import WhitelistEditModal from './components/WhitelistEditModal.vue'
@@ -20,7 +20,6 @@
import Pagination from '@/components/common/Pagination.vue'
import dayjs from 'dayjs'
import { useRouter } from 'vue-router'
import { onlyAllowNumber, onlyAllowNumberAndLetter } from '@/utils'
// 路由实例,用于从列表跳转到白名单详情页。
const router = useRouter()
@@ -683,6 +682,7 @@
// 确认后调用解冻接口,并在成功后刷新列表状态。
function handleUnfreeze(row: any) {
  dialog.warning({
    maskClosable: false,
    title: '确认解冻',
    content: `确定要解冻用户 ${row.nickName || row.account} 吗?`,
    positiveText: '确定',
@@ -708,6 +708,7 @@
  }
  dialog.warning({
    maskClosable: false,
    title: '确认删除',
    content: `确定要删除用户 ${row.nickName || row.account} 吗?删除后数据不可恢复。`,
    positiveText: '确定',
src/views/userContent/whitelistManagement/whitelistLog/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import noDataImg from '@/assets/element-img/no-data.png'
import type { DataTableColumns, FormInst } from 'naive-ui'
import { NButton, NCard, NDataTable, NDatePicker, NForm, NFormItem, NGi, NInput, NSelect, NTooltip, useMessage } from 'naive-ui'
import { NButton, NCard, NDataTable, NForm, NFormItem, NGi, NTooltip } from 'naive-ui'
import dayjs from 'dayjs'
import { useBoolean } from '@/hooks'
import { downloadFile } from '@/utils'