董国庆
2026-06-12 62b9095ea6eedc3d12d96242a9c203ceb017d233
更改路由/统计文案/系统配置加推荐距离
1个文件已添加
11个文件已修改
580 ■■■■■ 已修改文件
.env 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/index.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/useAuthEntryBootstrap.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/auth.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/router/helper.ts 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/router/index.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/typings/route.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/index.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckInStatistics/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/order/orderManagement/index.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env
@@ -11,7 +11,7 @@
VITE_ROUTE_LOAD_MODE = static
# 设置登陆后跳转地址
VITE_HOME_PATH = /dashboard
VITE_HOME_PATH = /transaction/order/orderManagement
# 本地存储前缀
VITE_STORAGE_PREFIX =
src/hooks/index.ts
@@ -1,3 +1,4 @@
export * from './useBoolean'
export * from './useAuthEntryBootstrap'
export * from './useEcharts'
export * from './usePermission'
src/hooks/useAuthEntryBootstrap.ts
New file
@@ -0,0 +1,52 @@
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { fetchSysUserInfo } from '@/service/api/account'
import { fetchSsoCallback } from '@/service/api/login'
import { useAuthStore } from '@/store'
// 统一处理“默认落地页”的登录入口初始化。
// 目前首页已切到订单管理,原先 dashboard 承接的 SSO 回调和用户信息同步
// 需要在新的默认页复用,避免后续再次复制同样逻辑。
async function requestSsoCallback(code: string, state: string, scene: string, attempt: string) {
  return fetchSsoCallback({ code, state })
}
// 返回 true 表示当前页面已经处理了登录入口跳转,不应继续执行页面自己的初始化请求。
export async function bootstrapAuthEntry(route: RouteLocationNormalizedLoaded, scene: string) {
  const authStore = useAuthStore()
  const { code, state } = route.query
  // 线上 SSO 会通过 code/state 回调到默认落地页,这里优先完成单点登录换 token。
  if (typeof code === 'string' && typeof state === 'string') {
    try {
      const firstResult = await requestSsoCallback(code, state, scene, 'first')
      if (!firstResult.data) {
        const secondResult = await requestSsoCallback(code, state, scene, 'retry')
        if (!secondResult.data)
          return true
        await authStore.handleLoginInfo(secondResult.data)
        return true
      }
      await authStore.handleLoginInfo(firstResult.data)
      return true
    }
    catch (error) {
      return true
    }
  }
  // 非 SSO 回调场景下,补拉一次当前登录人的完整信息和权限,
  // 保证默认落地页不依赖旧 dashboard 也能完成用户态同步。
  try {
    const result = await fetchSysUserInfo()
    if (result.isSuccess && result.data)
      await authStore.syncAuthInfo(result.data)
  }
  catch {}
  return false
}
src/router/routes.static.ts
@@ -9,175 +9,9 @@
    menuType: 'page',
    componentPath: '/dashboard/index.vue',
    id: 1,
    hide: true, // 不在菜单中显示
    keepAlive: true,
    pid: null,
  },
  {
    name: 'testDemo',
    path: '/testDemo',
    title: '测试跑单demo',
    requiresAuth: true,
    icon: 'icon-park-outline:analysis',
    menuType: 'dir',
    componentPath: null,
    id: 9,
    pid: null,
  },
  {
    name: 'testPointManagement',
    path: '/testDemo/testPointManagement',
    title: '测试点位管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/index.vue',
    id: 91,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPointManagementList',
    path: '/testDemo/testPointManagement/list',
    title: '测试点位列表',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/list.vue',
    id: 92,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/testPointManagement',
  },
  {
    name: 'testDemoGeofenceManagement',
    path: '/testDemo/geofenceManagement',
    title: '测试围栏管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/index.vue',
    id: 202642974,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testDemoGeofenceDetail',
    path: '/testDemo/geofenceManagement/detail',
    title: '围栏详情',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 94,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'testDemoAddFence',
    path: '/testDemo/geofenceManagement/addFence',
    title: '新增围栏',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 202642975,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'testDemoEditFence',
    path: '/testDemo/geofenceManagement/editFence',
    title: '编辑围栏',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 202642976,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'specialPointManagement',
    path: '/testDemo/specialPointManagement',
    title: '特殊点位管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/specialPointManagement/index.vue',
    id: 10,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPlanList',
    path: '/testDemo/testPlanList',
    title: '测试计划列表',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/index.vue',
    id: 11,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPlanAdd',
    path: '/testDemo/testPlanList/add',
    title: '新增测试计划',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/add.vue',
    id: 111,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testPlanDetail',
    path: '/testDemo/testPlanList/detail/:id',
    title: '测试计划详情',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/detail.vue',
    id: 112,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testDemoOrderDetail',
    path: '/testDemo/testPlanList/orderDetail',
    title: '订单详情',
    requiresAuth: false,
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/order/orderManagement/detail.vue',
    id: 60111,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'systemOperationLog',
    path: '/systemOperationLog',
    title: '系统操作日志',
    requiresAuth: true,
    icon: 'icon-park-outline:log',
    menuType: 'page',
    componentPath: '/system/operationLog/index.vue',
    id: 12,
    keepAlive: true,
    pid: null,
    fullScreen: false,
  },
  {
    name: 'system',
@@ -928,6 +762,8 @@
    path: '/transaction',
    title: '交易&定价',
    requiresAuth: true,
    // 默认首页已切到订单管理,父级目录也需要跳过菜单权限过滤,保证整条链路可见。
    ignorePermission: true,
    icon: 'icon-park-outline:analysis',
    menuType: 'dir',
    componentPath: null,
@@ -940,6 +776,8 @@
    path: '/transaction/order',
    title: '订单',
    requiresAuth: true,
    // 同上,父级目录保留可见,具体数据权限仍由页面内部控制。
    ignorePermission: true,
    icon: 'icon-park-outline:analysis',
    menuType: 'dir',
    componentPath: null,
@@ -952,6 +790,8 @@
    path: '/transaction/order/orderManagement',
    title: '订单管理',
    requiresAuth: true,
    // 订单管理作为默认落地页,不参与菜单/路由权限过滤,但仍需要登录。
    ignorePermission: true,
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/order/orderManagement/index.vue',
@@ -1621,5 +1461,172 @@
    hide: true,
    activeMenu: '/service/ticket/workOrder',
  },
    {
    name: 'testDemo',
    path: '/testDemo',
    title: '测试跑单demo',
    requiresAuth: true,
    icon: 'icon-park-outline:analysis',
    menuType: 'dir',
    componentPath: null,
    id: 9,
    pid: null,
  },
  {
    name: 'testPointManagement',
    path: '/testDemo/testPointManagement',
    title: '测试点位管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/index.vue',
    id: 91,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPointManagementList',
    path: '/testDemo/testPointManagement/list',
    title: '测试点位列表',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPointManagement/list.vue',
    id: 92,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/testPointManagement',
  },
  {
    name: 'testDemoGeofenceManagement',
    path: '/testDemo/geofenceManagement',
    title: '测试围栏管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/index.vue',
    id: 202642974,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testDemoGeofenceDetail',
    path: '/testDemo/geofenceManagement/detail',
    title: '围栏详情',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 94,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'testDemoAddFence',
    path: '/testDemo/geofenceManagement/addFence',
    title: '新增围栏',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 202642975,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'testDemoEditFence',
    path: '/testDemo/geofenceManagement/editFence',
    title: '编辑围栏',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/geofenceManagement/detail.vue',
    id: 202642976,
    pid: 9,
    hide: true,
    fullScreen: true,
    activeMenu: '/testDemo/geofenceManagement',
  },
  {
    name: 'specialPointManagement',
    path: '/testDemo/specialPointManagement',
    title: '特殊点位管理',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/specialPointManagement/index.vue',
    id: 10,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPlanList',
    path: '/testDemo/testPlanList',
    title: '测试计划列表',
    requiresAuth: true,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/index.vue',
    id: 11,
    keepAlive: true,
    pid: 9,
  },
  {
    name: 'testPlanAdd',
    path: '/testDemo/testPlanList/add',
    title: '新增测试计划',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/add.vue',
    id: 111,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testPlanDetail',
    path: '/testDemo/testPlanList/detail/:id',
    title: '测试计划详情',
    requiresAuth: false,
    icon: 'icon-park-outline:list-view',
    menuType: 'page',
    componentPath: '/testDemo/testPlanList/detail.vue',
    id: 112,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'testDemoOrderDetail',
    path: '/testDemo/testPlanList/orderDetail',
    title: '订单详情',
    requiresAuth: false,
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/order/orderManagement/detail.vue',
    id: 60111,
    pid: 9,
    hide: true,
    activeMenu: '/testDemo/testPlanList',
  },
  {
    name: 'systemOperationLog',
    path: '/systemOperationLog',
    title: '系统操作日志',
    requiresAuth: true,
    icon: 'icon-park-outline:log',
    menuType: 'page',
    componentPath: '/system/operationLog/index.vue',
    id: 12,
    keepAlive: true,
    pid: null,
    fullScreen: false,
  },
]
src/store/auth.ts
@@ -245,7 +245,7 @@
      const routeStore = useRouteStore()
      await routeStore.initAuthRoute(true)
      router.replace({ path: '/dashboard' })
      router.replace({ path: import.meta.env.VITE_HOME_PATH })
    },
  },
})
src/store/router/helper.ts
@@ -7,11 +7,17 @@
import { RouterLink } from 'vue-router'
const metaFields: AppRoute.MetaKeys[]
  = ['title', 'icon', 'requiresAuth', 'roles', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType', 'fullScreen']
  = ['title', 'icon', 'requiresAuth', 'roles', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType', 'fullScreen', 'ignorePermission']
function shouldSkipPermission(route: Pick<AppRoute.Route, 'meta'>) {
  // Hidden pages are entered from already-authorized actions, so they only keep login checks.
  return route.meta?.requiresAuth === false || route.meta?.hide === true
}
// ignorePermission 仅跳过“菜单/路由权限过滤”,不改变登录校验语义。
// 这类页面仍然需要登录,但允许作为默认落地页在无菜单权限时显示出来。
function shouldSkipRoutePermission(route: Pick<AppRoute.Route, 'meta'>) {
  return shouldSkipPermission(route) || route.meta?.ignorePermission === true
}
function standardizedRoutes(route: AppRoute.RowRoute[]) {
@@ -26,49 +32,32 @@
export function createRoutes(routes: AppRoute.RowRoute[]) {
  const { hasPermission } = usePermission()
  // console.log('Creating routes with input:', routes)
  // Structure the meta field
  // 正式解开
  let resultRouter = standardizedRoutes(routes)
  // console.log('After standardization:', resultRouter)
  // Route permission filtering
  resultRouter = resultRouter.filter((i) => {
    if (shouldSkipPermission(i))
    // 默认落地页等白名单路由需要保留完整父子链,避免首次进入命中 404。
    if (shouldSkipRoutePermission(i))
      return true
    if (i.id) {
      return hasPermission(i.id)
    }
    return hasPermission(i.meta.roles)
  })
  // 正式解开
  console.log('After permission filtering:', resultRouter)
  // Generate routes, no need to import files for those with redirect
  const modules = import.meta.glob('@/views/**/*.vue')
  // console.log('Available modules:', Object.keys(modules))
  resultRouter = resultRouter.map((item: AppRoute.Route) => {
    if (item.componentPath && !item.redirect) {
      const componentPath = `/src/views${item.componentPath}`
      // console.log(`Looking for component at: ${componentPath}`)
      if (modules[componentPath]) {
      if (modules[componentPath])
        item.component = modules[componentPath]
        // console.log(`Component found for: ${item.path}`)
      }
      else {
        // console.warn(`Component not found for: ${componentPath}`)
      }
    }
    return item
  })
  // Generate route tree
  // 正式解开
  resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
  const appRootRoute: RouteRecordRaw = {
@@ -88,7 +77,6 @@
  // Insert the processed route into the root route
  appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
  // console.log('Final app root route:', appRootRoute)
  return appRootRoute
}
@@ -133,7 +121,8 @@
  // Route permission filtering
  resultRouter = resultRouter.filter((i) => {
    if (shouldSkipPermission(i))
    // 菜单和路由都复用同一套 ignorePermission 规则,避免“页面可进但菜单不显示”。
    if (shouldSkipRoutePermission(i))
      return true
    // 优先检查路由 ID 是否在权限列表中
    if (i.id) {
src/store/router/index.ts
@@ -52,8 +52,10 @@
            throw new Error('Failed to fetch user routes')
          }
          // Hidden pages also skip permission filtering; keep them mounted in dynamic mode.
          const publicRoutes = staticRoutes.filter(route => route.requiresAuth === false || route.hide === true)
          // Hidden pages and permission-exempt landing pages skip route permission filtering in dynamic mode.
          const publicRoutes = staticRoutes.filter(
            route => route.requiresAuth === false || route.hide === true || route.ignorePermission === true,
          )
          const userRoutes = result.data
          const mergedRoutes = [...publicRoutes.filter(p => !userRoutes.some(u => u.name === p.name)), ...userRoutes]
src/typings/route.d.ts
@@ -29,6 +29,8 @@
    menuType?: MenuType
    /** 当前路由是否会全屏显示 */
    fullScreen?: boolean
    /** 当前路由是否跳过菜单/路由权限过滤,仅保留登录校验,常用于默认落地页 */
    ignorePermission?: boolean
  }
  type MetaKeys = keyof RouteMeta
src/views/dashboard/index.vue
@@ -1,66 +1,13 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { fetchSsoCallback, fetchSysUserInfo } from '@/service'
import { useAuthStore } from '@/store'
import { bootstrapAuthEntry } from '@/hooks'
const route = useRoute()
const authStore = useAuthStore()
async function requestSsoCallback(code: string, state: string, attempt: string) {
  console.log(`[dashboard] /account/ssoAuthSdk/sysLogin ${attempt} start`, { code, state })
  const result = await fetchSsoCallback({ code, state })
  console.log(`[dashboard] /account/ssoAuthSdk/sysLogin ${attempt} result`, result)
  return result
}
async function handleSsoCallback() {
  const { code, state } = route.query
  if (typeof code !== 'string' || typeof state !== 'string')
    return false
  try {
    const firstResult = await requestSsoCallback(code, state, 'first')
    if (!firstResult.data) {
      console.warn('[dashboard] /account/ssoAuthSdk/sysLogin first attempt missing data, retry once', firstResult)
      const secondResult = await requestSsoCallback(code, state, 'retry')
      if (!secondResult.data) {
        console.warn('[dashboard] /account/ssoAuthSdk/sysLogin retry still missing data', secondResult)
        return true
      }
      await authStore.handleLoginInfo(secondResult.data)
      return true
    }
    await authStore.handleLoginInfo(firstResult.data)
    return true
  }
  catch (error) {
    console.error('[dashboard] /account/ssoAuthSdk/sysLogin error', error)
    return true
  }
}
onMounted(async () => {
  const handledSsoCallback = await handleSsoCallback()
  if (handledSsoCallback)
    return
  try {
    const result = await fetchSysUserInfo()
    console.log('[dashboard] /account/api/v1/getSysUserInfo', result)
    if (result.isSuccess && result.data)
      await authStore.syncAuthInfo(result.data)
  }
  catch (error) {
    console.error('[dashboard] /account/api/v1/getSysUserInfo error', error)
  }
  // dashboard 先保留同一套入口初始化,兼容 SSO 回调地址切换期间的中间态。
  await bootstrapAuthEntry(route, 'dashboard')
})
</script>
src/views/operations/cruiseCheckInStatistics/index.vue
@@ -484,7 +484,7 @@
        <n-gi :span="1" class="h-368px flex flex-col">
          <div class="box relative">
            <div class="head">
              点位柱状图
              每天点位情况<span class="header-tit">统计7天范围内,每天的点位情况</span>
            </div>
            <div class="legend">
              <div class="legend-item">
@@ -506,7 +506,7 @@
        <n-gi :span="1" class="h-368px flex flex-col">
          <div class="box">
            <div class="head">
              点位状态占比
              点位整体情况<span class="header-tit">统计全部点位的正常点位和异常点位占比</span>
            </div>
            <div ref="pointPieRef" class="box-content" />
          </div>
@@ -514,7 +514,7 @@
        <n-gi :span="1" class="h-368px flex flex-col">
          <div class="box relative">
            <div class="head">
              打卡柱状图
              每天打卡情况<span class="header-tit">统计7天范围内,每天的打卡情况</span>
            </div>
            <div class="legend">
              <div class="legend-item">
@@ -536,7 +536,7 @@
        <n-gi :span="1" class="h-368px flex flex-col">
          <div class="box">
            <div class="head">
              打卡状态占比
              打卡整体情况<span class="header-tit">统计所有运维工作人员打卡情况</span>
            </div>
            <div ref="checkInPieRef" class="box-content" />
          </div>
@@ -577,6 +577,12 @@
    border-radius: 3px;
    background: #0068FF;
  }
  .header-tit {
    font-size: 12px;
    color: #868686;
    margin-left: 12px;
  }
}
.box-content {
src/views/system/config/index.vue
@@ -16,6 +16,7 @@
  xczHmDuration: string
  headShowCount: string
  nearPointDistance: any
  nearPointDistanceSearch: any
  operateFree: boolean
  temperatureUnit: string
}
@@ -37,6 +38,7 @@
  xczHmDuration: '',
  headShowCount: '',
  nearPointDistance: '',
  nearPointDistanceSearch: '',
  operateFree: true,
  temperatureUnit: '℃',
})
@@ -54,6 +56,12 @@
  // 仅保留数字,并在输入阶段直接截断为最多5位,确保不超过99999
  const normalizedValue = value.replace(/\D/g, '').slice(0, 5)
  formModel.value.nearPointDistance = normalizedValue
}
function handleNearPointDistanceSearchChange(value: string) {
  // 仅保留数字,并在输入阶段直接截断为最多5位,确保不超过99999
  const normalizedValue = value.replace(/\D/g, '').slice(0, 5)
  formModel.value.nearPointDistanceSearch = normalizedValue
}
const rules: FormRules = {
@@ -94,6 +102,9 @@
  nearPointDistance: [
    { required: true, message: '请输入正整数', trigger: 'blur' },
  ],
  nearPointDistanceSearch: [
    { required: true, message: '请输入正整数', trigger: 'blur' },
  ],
  temperatureUnit: [
    { required: true, message: '请选择', trigger: 'change' },
  ],
@@ -128,6 +139,7 @@
      arriveTime: Number(formModel.value.arriveTime ?? 0),
      headShowCount: Number(formModel.value.headShowCount ?? 0),
      nearPointDistance: Number(formModel.value.nearPointDistance ?? 0),
      nearPointDistanceSearch: Number(formModel.value.nearPointDistanceSearch ?? 0),
      operateFree: formModel.value.operateFree,
      temperatureUnit: formModel.value.temperatureUnit,
      xczSyDistance: Number(formModel.value.xczSyDistance ?? 0),
@@ -163,6 +175,7 @@
      formModel.value.arriveTime = d.arriveTime ? d.arriveTime.toString() : ''
      formModel.value.headShowCount = d.headShowCount ? d.headShowCount.toString() : ''
      formModel.value.nearPointDistance = d.nearPointDistance ? d.nearPointDistance.toString() : ''
      formModel.value.nearPointDistanceSearch = d.nearPointDistanceSearch ? d.nearPointDistanceSearch.toString() : ''
      formModel.value.operateFree = d.operateFree
      formModel.value.temperatureUnit = d.temperatureUnit || '℃'
      formModel.value.xczSyDistance = d.xczSyDistance ? d.xczSyDistance.toString() : ''
@@ -230,6 +243,19 @@
                <span class="inline-note">内</span>
              </div>
            </n-form-item>
            <n-form-item label="小程序首页地图推荐距离" path="nearPointDistanceSearch" required class="row-item">
              <div class="inline-input">
                <n-input-group style="width: 222px;">
                  <n-input
                    :value="formModel.nearPointDistanceSearch" placeholder="请输入"
                    :allow-input="onlyAllowNumberAndDash"
                    @update:value="handleNearPointDistanceSearchChange"
                  />
                  <n-input-group-label>m</n-input-group-label>
                </n-input-group>
                <span class="inline-note">内</span>
              </div>
            </n-form-item>
            <n-form-item label="小程序天气单位" path="temperatureUnit" required class="row-item">
              <n-select
                v-model:value="formModel.temperatureUnit"
src/views/transaction/order/orderManagement/index.vue
@@ -1,7 +1,8 @@
<script setup lang="tsx">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import type { DataTableColumns, DataTableRowKey } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { bootstrapAuthEntry, useBoolean } from '@/hooks'
import { usePermission } from '@/hooks/usePermission'
import { cancelAndCloseOrder, deleteOrder, exportOrderExcel, getFleetList, getOrderList, refundOrder } from '@/service/api/transaction'
import { operationPageList } from '@/service/api/vehicleConvoy'
import { getRegionCascaderNoArea } from '@/service/api/operations'
@@ -19,7 +20,11 @@
const { t } = useI18n()
const message = useMessage()
const router = useRouter()
const route = useRoute()
const dialog = useDialog()
const { hasPermission } = usePermission()
// 默认落地页允许进入,但无 6011 权限时不请求列表和相关下拉数据。
const canViewOrderList = computed(() => hasPermission(6011))
const initialModel = {
  orderNumber: '',
@@ -42,6 +47,8 @@
})
function handleSearch() {
  if (!canViewOrderList.value)
    return
  pageParams.value.pageNum = 1
  getList()
}
@@ -603,6 +610,11 @@
const count = ref(0)
async function getList() {
  if (!canViewOrderList.value) {
    listData.value = []
    count.value = 0
    return
  }
  startLoading()
  const params = {
    ...pageParams.value,
@@ -627,6 +639,8 @@
}
async function handleExport() {
  if (!canViewOrderList.value)
    return
  let params: any = {}
  if (checkedRowKeysRef.value.length > 0) {
    params.ids = checkedRowKeysRef.value
@@ -670,6 +684,8 @@
}
async function fetchCascaderData() {
  if (!canViewOrderList.value)
    return
  const res = await getRegionCascaderNoArea()
  if (res.code === 200 && res.data) {
    pcaOptions.value = formatCascaderData(res.data)
@@ -677,6 +693,8 @@
}
async function fetchFleetList() {
  if (!canViewOrderList.value)
    return
  const res = await getFleetList()
  if (res.code === 200) {
    options.value = res.data.map((item: any) => ({
@@ -687,6 +705,10 @@
}
async function fetchPlateNumberOptions() {
  if (!canViewOrderList.value) {
    plateNumberOptions.value = []
    return
  }
  const res = await operationPageList({
    pageNum: 1,
    pageSize: 99999,
@@ -713,7 +735,19 @@
let timer: any = null
onMounted(() => {
onMounted(async () => {
  // 承接原 dashboard 的入口初始化逻辑,确保默认首页切换后
  // SSO 回调和用户信息同步仍然会在首屏完成。
  const handledAuthEntry = await bootstrapAuthEntry(route, 'orderManagement')
  if (handledAuthEntry)
    return
  // 页面允许访问,但没有查看权限时保持空态,不发列表/轮询请求。
  if (!canViewOrderList.value) {
    listData.value = []
    count.value = 0
    return
  }
  getList()
  fetchCascaderData()
  fetchFleetList()
@@ -734,6 +768,8 @@
}
function changePage(page: number, size: number) {
  if (!canViewOrderList.value)
    return
  pageParams.value.pageNum = page
  pageParams.value.pageSize = size
  getList()