董国庆
2026-05-14 45653e5c366ee600b1bebdae5287d70a8498006d
refactor: 优化项目代码并修复多处问题

- 统一替换多处页面硬编码的拖拽图标路径为导入的静态资源变量
- 优化路由存储初始化逻辑,添加并发缓存避免重复调用接口
- 修复标签页管理逻辑,解决首页因query/hash不同导致的重复标签问题
- 添加全局错误捕获与日志功能,记录各类运行时错误
- 调整开发环境配置文件的注释URL
- 优化布局菜单的类型转换逻辑,提取为计算属性减少重复代码
10个文件已修改
205 ■■■■■ 已修改文件
service.config.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/router/index.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/tab.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/parade-plan/add.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/cruise/parade-plan/detail.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/cruiseCheckInRule/add.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/testDemo/testPlanList/detail.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/userContent/contentCenter/components/AgreementListForm.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service.config.ts
@@ -4,7 +4,7 @@
    // url: 'http://localhost:3000',
    url: 'http://221.182.45.100:8000',
    // url: 'http://rt-admin.test.xiaopeng.local',
    // url: 'https://rt-api.deploy-test.xiaopeng.com',
    // url: 'http://rt-api.deploy-test.xiaopeng.com',
  },
  production: {
    url: 'https://rt-admin.deploy-test.xiaopeng.com',
src/layouts/index.vue
@@ -23,6 +23,7 @@
const routeStore = useRouteStore()
const { layoutMode } = storeToRefs(useAppStore())
const layoutMenus = computed(() => routeStore.menus as MenuOption[])
const {
  layout,
@@ -30,7 +31,7 @@
} = useLayoutMenu({
  mode: layoutMode,
  accordion: true,
  menus: routeStore.menus as MenuOption[],
  menus: layoutMenus,
})
watch(() => route.path, () => {
src/main.ts
@@ -1,5 +1,50 @@
import { router } from './router'
import App from './App.vue'
function getCurrentRouteInfo() {
  const currentRoute = router.currentRoute.value
  return {
    name: currentRoute?.name ?? '',
    path: currentRoute?.path ?? '',
    fullPath: currentRoute?.fullPath ?? '',
    query: currentRoute?.query ?? {},
    params: currentRoute?.params ?? {},
  }
}
function logGlobalError(type: string, payload: Record<string, unknown>) {
  console.error(`[global-error] ${type}`, {
    ...payload,
    route: getCurrentRouteInfo(),
    timestamp: new Date().toISOString(),
  })
}
window.addEventListener('error', (event) => {
  logGlobalError('window.error', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    error: event.error,
  })
})
window.addEventListener('unhandledrejection', (event) => {
  logGlobalError('window.unhandledrejection', {
    reason: event.reason,
  })
})
// 创建应用实例并挂载
const app = createApp(App)
app.config.errorHandler = (error, instance, info) => {
  logGlobalError('vue.errorHandler', {
    info,
    component: instance?.type,
    error,
  })
}
app.mount('#app')
src/store/router/index.ts
@@ -6,6 +6,8 @@
import { $t, local } from '@/utils'
import { createMenus, createRoutes, generateCacheRoutes } from './helper'
let initAuthRoutePromise: Promise<void> | null = null
interface RoutesStatus {
  isInitAuthRoute: boolean
  menus: MenuOption[]
@@ -69,37 +71,52 @@
      }
    },
    async initAuthRoute() {
      if (this.isInitAuthRoute)
        return
      if (initAuthRoutePromise)
        return initAuthRoutePromise
      this.isInitAuthRoute = false
      try {
        // Initialize route information
        const rowRoutes = await this.initRouteInfo()
        console.log('rowRoutes:', rowRoutes)
        if (!rowRoutes) {
          const error = new Error('Failed to get route information')
          window.$message.error($t(`app.getRouteError`))
      initAuthRoutePromise = (async () => {
        try {
          // Initialize route information
          const rowRoutes = await this.initRouteInfo()
          console.log('rowRoutes:', rowRoutes)
          if (!rowRoutes) {
            const error = new Error('Failed to get route information')
            window.$message.error($t(`app.getRouteError`))
            throw error
          }
          this.rowRoutes = rowRoutes
          // Generate actual route and insert
          const routes = createRoutes(rowRoutes)
          console.log('routes:', routes)
          if (router.hasRoute('appRoot'))
            router.removeRoute('appRoot')
          router.addRoute(routes)
          // Generate side menu
          this.menus = createMenus(rowRoutes)
          // Generate the route cache
          this.cacheRoutes = generateCacheRoutes(rowRoutes)
          this.isInitAuthRoute = true
        }
        catch (error) {
          // 重置状态并重新抛出错误
          this.isInitAuthRoute = false
          throw error
        }
        this.rowRoutes = rowRoutes
        finally {
          initAuthRoutePromise = null
        }
      })()
        // Generate actual route and insert
        const routes = createRoutes(rowRoutes)
        console.log('routes:', routes)
        router.addRoute(routes)
        // Generate side menu
        this.menus = createMenus(rowRoutes)
        // Generate the route cache
        this.cacheRoutes = generateCacheRoutes(rowRoutes)
        this.isInitAuthRoute = true
      }
      catch (error) {
        // 重置状态并重新抛出错误
        this.isInitAuthRoute = false
        throw error
      }
      return initAuthRoutePromise
    },
  },
})
src/store/tab.ts
@@ -1,6 +1,8 @@
import type { RouteLocationNormalized } from 'vue-router'
import { router } from '@/router'
const HOME_TAB_PATH = import.meta.env.VITE_HOME_PATH || '/dashboard'
interface TabState {
  pinTabs: RouteLocationNormalized[]
  tabs: RouteLocationNormalized[]
@@ -18,22 +20,62 @@
    allTabs: state => [...state.pinTabs, ...state.tabs],
  },
  actions: {
    normalizeTabPath(fullPath: string) {
      if (!fullPath) {
        return fullPath
      }
      if (fullPath === HOME_TAB_PATH || fullPath.startsWith(`${HOME_TAB_PATH}?`) || fullPath.startsWith(`${HOME_TAB_PATH}#`)) {
        return HOME_TAB_PATH
      }
      return fullPath
    },
    isHomeTabRoute(route: Pick<RouteLocationNormalized, 'path' | 'name' | 'fullPath'>) {
      return route.path === HOME_TAB_PATH || route.name === 'dashboard' || this.normalizeTabPath(route.fullPath) === HOME_TAB_PATH
    },
    normalizeTabRoute(route: RouteLocationNormalized) {
      if (!this.isHomeTabRoute(route)) {
        return route
      }
      return {
        ...route,
        path: HOME_TAB_PATH,
        fullPath: HOME_TAB_PATH,
        query: {},
        hash: '',
      } as RouteLocationNormalized
    },
    addTab(route: RouteLocationNormalized) {
      // 根据meta确定是否不添加,可用于错误页,登录页等
      if (route.meta.withoutTab)
        return
      const normalizedRoute = this.normalizeTabRoute(route)
      // 首页无论 query/hash 如何变化,都只保留一个固定页签
      if (this.isHomeTabRoute(normalizedRoute)) {
        this.tabs = this.tabs.filter(item => !this.isHomeTabRoute(item))
        const index = this.pinTabs.findIndex(item => this.isHomeTabRoute(item))
        if (index > -1) {
          this.pinTabs[index] = normalizedRoute
        }
        else {
          this.pinTabs.push(normalizedRoute)
        }
        return
      }
      // 如果标签名称已存在则不添加
      if (this.hasExistTab(route.fullPath as string))
      if (this.hasExistTab(normalizedRoute.fullPath as string))
        return
      // 根据meta.pinTab传递到不同的分组中
      if (route.meta.pinTab)
        this.pinTabs.push(route)
      if (normalizedRoute.meta.pinTab)
        this.pinTabs.push(normalizedRoute)
      else
        this.tabs.push(route)
        this.tabs.push(normalizedRoute)
    },
    async closeTab(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      const tabsLength = this.tabs.length
      // 如果动态标签大于一个,才会标签跳转
      if (this.tabs.length > 1) {
@@ -60,14 +102,17 @@
    },
    closeOtherTabs(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      const index = this.getTabIndex(fullPath)
      this.tabs = this.tabs.filter((item, i) => i === index)
    },
    closeLeftTabs(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      const index = this.getTabIndex(fullPath)
      this.tabs = this.tabs.filter((item, i) => i >= index)
    },
    closeRightTabs(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      const index = this.getTabIndex(fullPath)
      this.tabs = this.tabs.filter((item, i) => i <= index)
    },
@@ -81,18 +126,20 @@
    },
    hasExistTab(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      const _tabs = [...this.tabs, ...this.pinTabs]
      return _tabs.some((item) => {
        return item.fullPath === fullPath
        return this.normalizeTabPath(item.fullPath) === fullPath
      })
    },
    /* 设置当前激活的标签 */
    setCurrentTab(fullPath: string) {
      this.currentTabPath = fullPath
      this.currentTabPath = this.normalizeTabPath(fullPath)
    },
    getTabIndex(fullPath: string) {
      fullPath = this.normalizeTabPath(fullPath)
      return this.tabs.findIndex((item) => {
        return item.fullPath === fullPath
        return this.normalizeTabPath(item.fullPath) === fullPath
      })
    },
    modifyTab(fullPath: string, modifyFn: (route: RouteLocationNormalized) => void) {
src/views/businessOperations/cruise/parade-plan/add.vue
@@ -3,6 +3,7 @@
import { useRoute, useRouter } from "vue-router";
import SelectPointModal from "../patrol-points/components/SelectPointModal.vue";
import VehicleSelectModal from "@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue";
import dragIcon from "@/assets/point/union.png";
import {
  addCruisePlan,
  editCruisePlan,
@@ -301,7 +302,7 @@
        >
          <div class="w-16 flex">
            <img
              src="/src/assets/point/union.png"
              :src="dragIcon"
              alt="拖动"
              style="width: 15px; height: 15px"
              @error="(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }"
src/views/businessOperations/cruise/parade-plan/detail.vue
@@ -8,6 +8,7 @@
import SelectPointModal from '../patrol-points/components/SelectPointModal.vue'
import VehicleSelectModal from '@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue'
import Pagination from '@/components/common/Pagination.vue'
import dragIcon from '@/assets/point/union.png'
import { deleteCruiseOrder, editCruisePlan, exportCruiseOrderExcel, fetchCruiseOrderPageList, fetchCruisePlanDetail } from '@/service/api/cruise'
import { downloadFile } from '@/utils/common'
import { deriveDefaultValue } from 'naive-ui/es/color-picker/src/utils'
@@ -640,7 +641,7 @@
                          'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png';
                      }
                    "
                    src="/src/assets/point/union.png"
                   :src="dragIcon"
                    alt="拖动"
                    style="width: 15px; height: 15px"
                  />
src/views/operations/cruiseCheckInRule/add.vue
@@ -7,6 +7,7 @@
import dayjs from 'dayjs'
import { addCruiseCheckInRule } from '@/service/api/maintenance'
import { getOperationsUserListNoPage } from '@/service/api/attendance'
import dragIcon from '@/assets/point/union.png'
import { useTabStore } from '@/store'
const tabStore = useTabStore()
@@ -73,7 +74,7 @@
    title: '排序',
    key: 'sort',
    width: 60,
    render: () => <img src="/src/assets/point/union.png" alt="拖动" style="width: 15px; height: 15px" onError={(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }} />,
    render: () => <img src={dragIcon} alt="拖动" style="width: 15px; height: 15px" />,
  },
  {
    title: '打卡地',
src/views/testDemo/testPlanList/detail.vue
@@ -8,6 +8,7 @@
import SelectPointModal from '@/views/testDemo/specialPointManagement/components/SelectPointModal.vue'
import VehicleSelectModal from '@/views/assetManagement/fleetManagement/components/VehicleSelectModal.vue'
import Pagination from '@/components/common/Pagination.vue'
import dragIcon from '@/assets/point/union.png'
import { deleteCruiseOrder, editCruisePlan, exportCruiseOrderExcel, fetchCruiseOrderPageList, fetchCruisePlanDetail } from '@/service/api/copyText'
import { downloadFile } from '@/utils/common'
@@ -562,7 +563,7 @@
                @drop="handleDrop(point.id)"
              >
                <div class="w-16 flex">
                  <img src="/src/assets/point/union.png" alt="拖动" style="width: 15px; height: 15px">
                  <img :src="dragIcon" alt="拖动" style="width: 15px; height: 15px">
                </div>
                <div class="flex-1 text-sm pr-4">
                  {{ point.name }}
src/views/userContent/contentCenter/components/AgreementListForm.vue
@@ -3,6 +3,7 @@
import { NButton, NDataTable, NForm, NFormItem, NInput, NModal, NPopconfirm, NSpace, useMessage } from 'naive-ui'
import type { DataTableColumns, FormInst, FormRules } from 'naive-ui'
import AiRichTextEditor from '@/components/custom/Editor/AiRichTextEditor/index.vue'
import dragIcon from '@/assets/point/union.png'
import { deleteLoginAgreement, getLoginAgreementPageList, saveLoginAgreement } from '@/service/api/operations'
const props = defineProps<{
@@ -54,9 +55,9 @@
    render: () => {
      return h('div', { class: 'sort-cell' }, [
        h('img', {
          src: '/src/assets/point/union.png',
          src: dragIcon,
          alt: '拖动',
          style: 'width: 15px; height: 15px;',
          class: 'sort-icon',
        }),
      ])
    },
@@ -428,7 +429,13 @@
.sort-cell {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
}
.sort-icon {
  width: 15px;
  height: 15px;
  display: block;
}
.rich-text-wrapper {