ai-coding
2026-06-27 0158ad87b38ec476ec7dc802ee4e97efc2617c9c
feat(commandCenter):kpi新增呼单乘客、今日完单,样式调整

Co-Authored-By: ai-coding <ai-coding@devgenie.local>
4个文件已修改
88 ■■■■■ 已修改文件
src/views/businessOperations/commandCenter/components/LayerControl.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/commandCenter/components/StatusHeader/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/commandCenter/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/commandCenter/map/PassengerCallLayer.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/commandCenter/components/LayerControl.vue
@@ -9,28 +9,28 @@
    label: '车辆位置',
    active: store.layerVisibility.vehicle,
    toggle: () => store.toggleLayer('vehicle', !store.layerVisibility.vehicle),
    iconHtml: '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><circle cx="12" cy="8" r="4"/><path d="M12 14c-4 0-8 2-8 4v2h16v-2c0-2-4-4-8-4z"/></svg>',
    iconHtml: '',
  },
  {
    key: 'passengerCall',
    label: '呼单乘客',
    active: store.layerVisibility.passengerCall,
    toggle: () => store.toggleLayer('passengerCall', !store.layerVisibility.passengerCall),
    iconHtml: '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><circle cx="9" cy="7" r="3"/><path d="M9 12c-3.5 0-7 1.5-7 3.5V17h14v-1.5c0-2-3.5-3.5-7-3.5z"/><circle cx="17" cy="9" r="2.5"/><path d="M17 13c-1.5 0-3 .5-3.5 1.1.7.6 1.2 1.3 1.3 2.1H22v-1c0-1.2-2.5-2.2-5-2.2z"/></svg>',
    iconHtml: '<span style="font-size:14px;">👤</span>',
  },
  {
    key: 'point',
    label: '运营点位',
    active: store.layerVisibility.point,
    toggle: () => store.toggleLayer('point', !store.layerVisibility.point),
    iconHtml: '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>',
    iconHtml: '<span style="font-size:14px;">📍</span>',
  },
  {
    key: 'fence',
    label: '运营区域',
    active: store.layerVisibility.fence,
    toggle: () => store.toggleLayer('fence', !store.layerVisibility.fence),
    iconHtml: '<svg viewBox="0 0 24 24" width="14" height="14"><rect x="3" y="5" width="18" height="14" rx="2" fill="currentColor" fill-opacity="0.15" stroke="currentColor" stroke-width="1.5"/></svg>',
    iconHtml: '<span style="font-size:13px;font-weight:600;">区</span>',
  },
])
@@ -133,23 +133,23 @@
    flex-shrink: 0;
    &--vehicle {
      background: #7c4dff;
      background: #2563eb;
      color: #fff;
    }
    &--passengerCall {
      background: #ff9800;
      color: #fff;
      background: #f59e0b;
      border-radius: 50%;
    }
    &--point {
      background: #e8e8e8;
      color: #555;
      background: transparent;
    }
    &--fence {
      background: #e3f2fd;
      color: #1890ff;
      background: #fff;
      border: 2px solid #2563eb;
      color: #2563eb;
    }
  }
src/views/businessOperations/commandCenter/components/StatusHeader/index.vue
@@ -8,14 +8,24 @@
defineProps<{ isOnline: boolean }>()
const emit = defineEmits<{ filterVehicle: [group: MonitorGroup | null] }>()
const emit = defineEmits<{
  filterVehicle: [group: MonitorGroup | null]
  togglePassengerCall: [active: boolean]
}>()
const store = useCommandCenterStore()
const kpi = computed(() => store.kpi)
const activeGroup = ref<MonitorGroup | null>(null)
const passengerCallActive = ref(false)
function handleKpiClick(group: MonitorGroup) {
  // 点击车辆分组时,取消呼单乘客激活态
  if (passengerCallActive.value) {
    passengerCallActive.value = false
    emit('togglePassengerCall', false)
  }
  if (activeGroup.value === group) {
    activeGroup.value = null
    emit('filterVehicle', null)
@@ -24,6 +34,17 @@
    activeGroup.value = group
    emit('filterVehicle', group)
  }
}
function handlePassengerCallClick() {
  // 点击呼单乘客时,取消车辆分组筛选
  if (activeGroup.value) {
    activeGroup.value = null
    emit('filterVehicle', null)
  }
  passengerCallActive.value = !passengerCallActive.value
  emit('togglePassengerCall', passengerCallActive.value)
}
</script>
@@ -66,6 +87,20 @@
        :active="activeGroup === 'ABNORMAL'"
        @click="handleKpiClick('ABNORMAL')"
      />
      <KpiCard
        title="呼单乘客"
        :value="store.passengerCalls.length"
        color="#f59e0b"
        subtitle="等待派车"
        :active="passengerCallActive"
        @click="handlePassengerCallClick"
      />
      <KpiCard
        title="今日完单"
        :value="kpi?.todayCompletedOrders ?? 0"
        color="#2563eb"
        subtitle="基础统计"
      />
    </div>
    <div class="status-header__right">
      <Clock />
src/views/businessOperations/commandCenter/index.vue
@@ -178,6 +178,29 @@
/** KPI 车辆过滤 */
function handleFilterVehicle(group: MonitorGroup | null) {
  vehicleLayer?.filterByGroup(group)
  if (group) {
    // 点击车辆分组时,隐藏呼单乘客图层,确保车辆图层可见
    store.toggleLayer('passengerCall', false)
    store.toggleLayer('vehicle', true)
  }
  else {
    // 取消筛选时,恢复呼单乘客图层
    store.toggleLayer('passengerCall', true)
  }
}
/** 呼单乘客模式切换:隐藏车辆,显示呼单乘客信息 */
function handleTogglePassengerCall(active: boolean) {
  if (active) {
    // 隐藏车辆图层,确保呼单乘客图层可见
    store.toggleLayer('vehicle', false)
    store.toggleLayer('passengerCall', true)
  }
  else {
    // 恢复车辆图层和呼单乘客图层
    store.toggleLayer('vehicle', true)
    store.toggleLayer('passengerCall', true)
  }
}
onMounted(async () => {
@@ -200,7 +223,7 @@
    <div v-if="!isOnline" class="command-center__offline-banner">
      连接已断开,正在尝试重连...
    </div>
    <StatusHeader :is-online="isOnline" @filter-vehicle="handleFilterVehicle" />
    <StatusHeader :is-online="isOnline" @filter-vehicle="handleFilterVehicle" @toggle-passenger-call="handleTogglePassengerCall" />
    <div class="command-center__body">
      <div class="command-center__city-selector">
        <NSelect
src/views/businessOperations/commandCenter/map/PassengerCallLayer.ts
@@ -81,8 +81,8 @@
      const marker = new AMap.Marker({
        position: [call.startLon, call.startLat],
        content: '<div style="width:20px;height:20px;font-size:16px;line-height:20px;text-align:center;">🙋</div>',
        offset: new AMap.Pixel(-10, -10),
        content: '<div style="width:26px;height:26px;border-radius:50%;background:#f59e0b;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;">👤</div>',
        offset: new AMap.Pixel(-13, -13),
        zIndex: LAYER_Z_INDEX.PASSENGER_CALL,
      })
      marker._callData = call