董国庆
2026-06-17 e0b84663ccef181ed8c88bb6430d4d78271006d4
Merge branch 'deploy-1.0' into deploy-2.0
6个文件已修改
195 ■■■■■ 已修改文件
src/router/guard.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/assetManagement/fleetManagement/components/StaffSelectModal.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/components/FencePanel.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/operations/taskOrder/taskOrderCenter/components/TableModal.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/service/review/personnel/index.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/guard.ts
@@ -72,8 +72,13 @@
      next()
      return
    }
    // 如果路由设置了requiresAuth为true,且用户未登录,重定向到首页
    // 如果路由设置了requiresAuth为true,且用户未登录,重定向到首页(列表页)
    else if (to.meta.requiresAuth !== false && !isLogin) {
      // 避免无限重定向到首页
      if (to.path === homePath) {
        next()
        return
      }
      next({ path: homePath, replace: true })
      return
    }
@@ -98,9 +103,8 @@
        return
      }
      catch {
        // 如果路由初始化失败(比如 401 错误),重定向到登录页
        const redirect = to.fullPath !== '/' ? to.fullPath : undefined
        next({ path: '/login', query: redirect ? { redirect } : undefined })
        // 如果路由初始化失败(比如 401 错误),重定向到首页
        next({ path: homePath, replace: true })
        return
      }
    }
src/views/assetManagement/fleetManagement/components/StaffSelectModal.vue
@@ -1,8 +1,9 @@
<script setup lang="tsx">
import type { DataTableColumns } from 'naive-ui'
import { NAvatar, NButton, NSelect, NSpace } from 'naive-ui'
import { NButton, NImage, NSelect, NSpace } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { fetchOperationsUserList, fetchRoleList } from '@/service/api/fleet'
import defaultAvatarImg from '@/assets/public/avater.png'
interface StaffItem {
  id: number
@@ -53,6 +54,19 @@
const currentPage = ref(1)
const pageSize = ref(10)
function getAvatarUrl(avatar?: string) {
  if (!avatar)
    return ''
  try {
    const parsedAvatar = JSON.parse(avatar)
    return parsedAvatar?.url || ''
  }
  catch {
    return avatar
  }
}
// 表格列定义
const columns = computed<DataTableColumns<StaffItem>>(() => [
  {
@@ -65,9 +79,34 @@
    key: 'nickName',
    width: 200,
    render: (row) => {
      const avatarUrl = getAvatarUrl(row.avatar)
      return (
        <NSpace align="center" justify="flex-start">
          <NAvatar round size={32} src={row.avatar} fallback-src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png" style={{ backgroundColor: '#d0d0d0' }}></NAvatar>
          {avatarUrl
            ? (
                <NImage
                  src={avatarUrl}
                  width={32}
                  height={32}
                  objectFit="cover"
                  imgProps={{ style: { borderRadius: '50%' } }}
                  previewDisabled={false}
                  fallbackSrc="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
                />
              )
            : (
                <div
                  class="flex justify-center items-center"
                  style={{ width: '32px', height: '32px', borderRadius: '50%', backgroundColor: '#F6F7FA', flexShrink: 0 }}
                >
                  <NImage
                    src={defaultAvatarImg}
                    width={17}
                    height={21}
                    previewDisabled
                  />
                </div>
              )}
          <span>{row.nickName}</span>
        </NSpace>
      )
src/views/businessOperations/operationWorkbench/components/FencePanel.vue
@@ -511,7 +511,7 @@
                :checked-value="1"
                :unchecked-value="2"
                :disabled="isAreaStatusSwitchDisabled"
                class="text-13px"
                class="text-13px flex-shrink-0"
              >
                <template #checked> 启用 </template>
                <template #unchecked> 停用 </template>
@@ -1601,6 +1601,9 @@
  position: absolute;
  inset: 0;
}
.flex-shrink-0 {
  flex-shrink: 0;
}
:deep(.n-date-picker) {
  height: 100%;
src/views/businessOperations/operationWorkbench/map.vue
@@ -10,8 +10,9 @@
  ref,
  shallowRef,
  watch,
} from 'vue'
import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
  h,
} from "vue";
import { onBeforeRouteLeave, useRoute, useRouter } from "vue-router";
import {
  checkPointNameDuplicate,
  createPoint,
@@ -1355,21 +1356,38 @@
  }
})
/**
 * 根据围栏修改的影响范围,生成提交时的二次确认提示文案
 * @param disabledPointCount - 因移出围栏而将被停用的点位数量
 * @returns 拼接好的确认提示文案(返回一个渲染函数以支持高亮)。如果没有任何移出影响,则返回空字符串
 */
function buildFenceSubmitConfirmMessage(disabledPointCount = 0) {
  const pointCount = fenceImpactDeltaSummary.value.remove.pointCount
  const stationCount = fenceImpactDeltaSummary.value.remove.stationCount
  const segments: string[] = []
  if (pointCount && stationCount)
    segments.push(`将移出${pointCount}个点位、${stationCount}个场站`)
  else if (pointCount)
    segments.push(`将移出${pointCount}个点位`)
  else if (stationCount)
    segments.push(`将移出${stationCount}个场站`)
  if (disabledPointCount > 0)
    segments.push(`其中${disabledPointCount}个点位会被停用`)
  if (!segments.length)
    return ''
  return `确认提交吗?本次修改${segments.join(',')}。`
  // 1. 从状态中获取本次修改将要移出的【点位数量】和【场站数量】
  const pointCount = fenceImpactDeltaSummary.value.remove.pointCount;
  const stationCount = fenceImpactDeltaSummary.value.remove.stationCount;
  // 2. 如果没有任何点位和场站被移出,直接返回空字符串
  if (!pointCount && !stationCount) return "";
  // 3. 返回一个渲染函数,使用 h 函数生成虚拟 DOM 以支持文案的高亮显示
  return () => {
    const parts = [];
    parts.push('因围栏调整,有 ');
    // 拼接点位和场站高亮片段
    if (pointCount > 0) {
      parts.push(h('span', { style: 'color: #0068FF;' }, `${pointCount}个点位`));
    }
    if (pointCount > 0 && stationCount > 0) {
      parts.push('、');
    }
    if (stationCount > 0) {
      parts.push(h('span', { style: 'color: #0068FF;' }, `${stationCount}个场站`));
    }
    parts.push(' 将变更为“已停用”,确认发布本次修改?');
    return h('span', null, parts);
  };
}
const fenceOutOfRangePointList = computed(() => {
@@ -3293,10 +3311,10 @@
  const failureType = source.fenceFailureType ?? source.failureType
  const failureTime = source.fenceFailureTime ?? source.failureTime
  if (
    effectType === undefined
    && effectTime === undefined
    && failureType === undefined
    && failureTime === undefined
    effectType == null &&
    effectTime == null &&
    failureType == null &&
    failureTime == null
  ) {
    return null
  }
@@ -3385,13 +3403,12 @@
}
const stationOperationLifecycle = computed(() => {
  if (!stationOperationArea.value)
    return null
  const detailLifecycle = normalizeStationLifecycleSource(stationDetailData.value)
  if (detailLifecycle)
    return detailLifecycle
  return stationOperationLifecycleFallback.value
})
  const detailLifecycle = normalizeStationLifecycleSource(stationDetailData.value);
  if (detailLifecycle) return detailLifecycle;
  if (!stationOperationArea.value) return null;
  return stationOperationLifecycleFallback.value;
});
// 围栏空间关系判断统一收口在这里,便于保存前复用原围栏页的点位校验逻辑。
function isPointInFencePath(point: LngLatTuple, path: LngLatTuple[]) {
@@ -4834,7 +4851,7 @@
function openConfirmDialog(
  title: string,
  content = '',
  content: string | (() => any) = "",
  options?: {
    positiveText?: string
    negativeText?: string
src/views/operations/taskOrder/taskOrderCenter/components/TableModal.vue
@@ -74,6 +74,22 @@
const userList = ref<any[]>([]);
const userOptions = computed(() =>
  userList.value.map(item => ({
    label: item.nickName || "-",
    value: item.id,
    searchText: `${item.nickName || ""}`.toLowerCase(),
  })),
);
function handleUserFilter(pattern: string, option: any) {
  const keyword = pattern.trim().toLowerCase();
  if (!keyword)
    return true;
  return option.searchText?.includes(keyword) ?? false;
}
const getUserList = async () => {
  const res = await getOperationsUserListNoPage();
  if (res.code === 200) {
@@ -103,9 +119,11 @@
      <n-form-item label="选择处理人" path="handlerUserId">
        <n-select
          v-model:value="formModel.handlerUserId"
          :options="userList"
          value-field="id"
          label-field="nickName"
          :options="userOptions"
          filterable
          clearable
          placeholder="请输入姓名搜索"
          :filter="handleUserFilter"
          @update:show="val => isUserSelectOpen = val"
        >
          <template #arrow>
src/views/service/review/personnel/index.vue
@@ -1,7 +1,7 @@
<script setup lang="tsx">
import { computed, onMounted, ref, watch } from 'vue'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NSpace, NTag, NTooltip } from 'naive-ui'
import { NButton, NImage, NSpace, NTag, NTooltip } from 'naive-ui'
import { useBoolean } from '@/hooks'
import OperationLogModal from '@/components/custom/OperationLogModal.vue'
import {
@@ -53,6 +53,19 @@
  3: 'error',
}
function getAvatarUrl(avatar?: string) {
  if (!avatar)
    return ''
  try {
    const parsedAvatar = JSON.parse(avatar)
    return parsedAvatar?.url || ''
  }
  catch {
    return avatar
  }
}
const columns: DataTableColumns<any> = [
  {
    type: 'selection',
@@ -70,12 +83,29 @@
    width: 150,
    resizable: true,
    render: (row) => {
      const avatarUrl = getAvatarUrl(row.avatar)
      return (
        <div class="flex items-center gap-2">
          {row.avatar && <img src={JSON.parse(row.avatar).url} alt="头像" class="w-24px h-24px rounded-full" onError={(e: any) => { e.target.src = 'https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png' }} />}
          {!row.avatar && (
          {avatarUrl
            ? (
                <NImage
                  src={avatarUrl}
                  width={24}
                  height={24}
                  objectFit="cover"
                  imgProps={{ style: { borderRadius: '50%' } }}
                  previewDisabled={false}
                  fallbackSrc="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/9bed493b-a6aa-4ef6-ac10-7f67f5c6adc5.png"
                />
              )
            : (
            <div class="w-24px h-24px rounded-full flex justify-center" style="background-color: #F6F7FA;">
              <img src={defaultAvatarImg} class="w-17px h-21px" />
              <NImage
                src={defaultAvatarImg}
                width={17}
                height={21}
                previewDisabled
              />
            </div>
          )}
          <NTooltip trigger="hover" placement="top" max-width="500px">