From fdd158306bf6dc3584f5110a385323f4a8dfb463 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 02 九月 2026 14:25:10 +0800
Subject: [PATCH] feat(wms): 重构仓库管理系统并优化图表显示

---
 src/views/mes/pro/scan-in/index.vue |  144 +++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 117 insertions(+), 27 deletions(-)

diff --git a/src/views/mes/pro/scan-in/index.vue b/src/views/mes/pro/scan-in/index.vue
index 7042afc..3a5d2b9 100644
--- a/src/views/mes/pro/scan-in/index.vue
+++ b/src/views/mes/pro/scan-in/index.vue
@@ -6,32 +6,76 @@
 import { gsap } from 'gsap';
 
 import {
-  getLatestScanEvent,
   getScanDashboard,
   getScanEventPage,
   inboundScanEvent,
   type MesProScanEventApi,
 } from '#/api/mes/pro/scanEvent';
+import { getLineSimpleList, type MesProLineApi } from '#/api/mes/pro/line';
+import {
+  getWarehouseLocationSimpleList,
+  type MesWmWarehouseLocationApi,
+} from '#/api/mes/wm/warehouse/location';
+import {
+  getWarehouseAreaSimpleList,
+  type MesWmWarehouseAreaApi,
+} from '#/api/mes/wm/warehouse/area';
 
 interface ScanRecord extends MesProScanEventApi.ScanEvent {
   key: number;
 }
 
 const code = ref('');
-const deviceCode = ref('');
+const scanType = ref<MesProScanEventApi.ScanType>('BAG');
 const lineCode = ref('');
 const locationId = ref<number>();
 const areaId = ref<number>();
+const lines = ref<MesProLineApi.Line[]>([]);
+const locations = ref<MesWmWarehouseLocationApi.WarehouseLocation[]>([]);
+const areas = ref<MesWmWarehouseAreaApi.WarehouseArea[]>([]);
 const loading = ref(false);
 const refreshing = ref(false);
 const records = ref<ScanRecord[]>([]);
 const dashboard = ref<MesProScanEventApi.Dashboard>({});
 let timer: ReturnType<typeof setInterval> | undefined;
 
+/** 鎵爜绫诲瀷涓枃鍚� */
+const SCAN_TYPE_LABEL: Record<MesProScanEventApi.ScanType, string> = {
+  BAG: '琚嬬爜',
+  PALLET: '鎵樼洏鐮�',
+  BATCH: '鎵规鐮�',
+};
+/** 鎵爜绫诲瀷涓嬫媺閫夐」 */
+const SCAN_TYPE_OPTIONS = [
+  { label: '琚嬬爜', value: 'BAG' },
+  { label: '鎵樼洏鐮�', value: 'PALLET' },
+  { label: '鎵规鐮�', value: 'BATCH' },
+] as const;
+
 const latest = computed(() => dashboard.value.latest);
 const successCount = computed(() => dashboard.value.success ?? records.value.filter((item) => item.status === 'SUCCESS').length);
 const failedCount = computed(() => dashboard.value.failed ?? records.value.filter((item) => item.status === 'FAILED').length);
-const isReady = computed(() => Boolean(deviceCode.value.trim() && locationId.value && areaId.value));
+const isReady = computed(() => Boolean(locationId.value && areaId.value));
+const lineOptions = computed(() =>
+  lines.value.map((item) => ({
+    label: item.code ? `${item.code} ${item.name ?? ''}`.trim() : (item.name ?? ''),
+    value: item.code ?? '',
+  })),
+);
+const locationOptions = computed(() =>
+  locations.value.map((item) => ({
+    label: item.code ? `${item.code} ${item.name ?? ''}`.trim() : (item.name ?? ''),
+    value: item.id ?? 0,
+  })),
+);
+const areaOptions = computed(() =>
+  areas.value.map((item) => ({
+    label: item.code ? `${item.code} ${item.name ?? ''}`.trim() : (item.name ?? ''),
+    value: item.id ?? 0,
+  })),
+);
+const locationName = computed(() => locations.value.find((item) => item.id === locationId.value)?.name ?? '');
+const areaName = computed(() => areas.value.find((item) => item.id === areaId.value)?.name ?? '');
 const pageRoot = ref<HTMLElement>();
 let animationContext: gsap.Context | undefined;
 let feedbackTimeline: gsap.core.Timeline | undefined;
@@ -41,7 +85,7 @@
   try {
     const [summary, page] = await Promise.all([
       getScanDashboard(),
-      getScanEventPage({ pageNo: 1, pageSize: 20, deviceCode: deviceCode.value.trim() || undefined }),
+      getScanEventPage({ pageNo: 1, pageSize: 20 }),
     ]);
     dashboard.value = summary;
     records.value = (page.list ?? []).map((item, index) => ({ ...item, key: item.id ?? index }));
@@ -50,28 +94,57 @@
   }
 }
 
+async function loadOptions() {
+  try {
+    const [lineList, locationList] = await Promise.all([
+      getLineSimpleList(),
+      getWarehouseLocationSimpleList(),
+    ]);
+    lines.value = lineList ?? [];
+    locations.value = locationList ?? [];
+  } catch {
+    lines.value = [];
+    locations.value = [];
+  }
+}
+
+async function loadAreas() {
+  areas.value = [];
+  areaId.value = undefined;
+  if (!locationId.value) return;
+  try {
+    areas.value = (await getWarehouseAreaSimpleList(locationId.value)) ?? [];
+  } catch {
+    areas.value = [];
+  }
+}
+
+function onScanTypeChange() {
+  code.value = '';
+}
+
 async function submitScan() {
   const bagCode = code.value.trim();
-  if (!bagCode || !deviceCode.value.trim()) {
-    message.warning('璇峰厛濉啓璁惧缂栫爜锛屽啀鎵弿琚嬬爜');
+  if (!bagCode) {
+    message.warning('璇峰厛鎵弿杩芥函鐮�');
     return;
   }
   if (!locationId.value || !areaId.value) {
-    message.warning('璇峰厛閰嶇疆 WIP 鏆傚瓨搴撳尯鍜屽簱浣�');
+    message.warning('璇峰厛閫夋嫨 WIP 鏆傚瓨搴撳尯鍜屽簱浣�');
     return;
   }
   loading.value = true;
   try {
     await inboundScanEvent({
-      eventId: `${deviceCode.value.trim()}-${Date.now()}`,
+      eventId: `${scanType.value}-${Date.now()}`,
       bagCode,
+      scanType: scanType.value,
       quantity: 1,
-      deviceCode: deviceCode.value.trim(),
-      lineCode: lineCode.value.trim() || undefined,
+      lineCode: lineCode.value || undefined,
       locationId: locationId.value,
       areaId: areaId.value,
     });
-    message.success(`琚嬬爜 ${bagCode} 宸插畬鎴愬叆搴揱);
+    message.success(`${SCAN_TYPE_LABEL[scanType.value]} ${bagCode} 宸插畬鎴愬叆搴揱);
     if (animationContext && pageRoot.value && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
       feedbackTimeline?.kill();
       feedbackTimeline = gsap.timeline();
@@ -84,8 +157,23 @@
     code.value = '';
     await refresh();
   } catch (error) {
-    const response = error as { message?: string; msg?: string };
-    message.error(response.message ?? response.msg ?? '鎵爜鍏ュ簱澶辫触锛岃鏌ョ湅浜嬩欢璁板綍');
+    const response = error as {
+      message?: string;
+      msg?: string;
+      response?: { data?: { message?: string; msg?: string } };
+    };
+    const errorMessage =
+      response.response?.data?.message ??
+      response.response?.data?.msg ??
+      response.message ??
+      response.msg;
+    if (errorMessage && /鍏ュ簱浜嬪姟鏁伴噺蹇呴』涓烘鏁皘鏁伴噺.*涓� 0|quantity.*positive/i.test(errorMessage)) {
+      message.error('鍏ュ簱鏁伴噺涓嶈兘涓� 0锛岃妫�鏌ヨ鎵规鐨勮鏁伴厤缃�');
+    } else if (!errorMessage || /UnexpectedRollbackException|绯荤粺寮傚父|Internal Server Error/i.test(errorMessage)) {
+      message.error('鎵爜鍏ュ簱澶辫触锛岃妫�鏌ユ壒娆℃暟閲忓拰搴撲綅閰嶇疆');
+    } else {
+      message.error(errorMessage);
+    }
     await refresh();
   } finally {
     loading.value = false;
@@ -93,7 +181,6 @@
 }
 
 async function refreshLatest() {
-  if (deviceCode.value.trim()) await getLatestScanEvent(deviceCode.value.trim());
   await refresh();
 }
 
@@ -128,6 +215,7 @@
       }
     }, pageRoot.value);
   }
+  await loadOptions();
   await refresh();
   timer = setInterval(refreshLatest, 15000);
 });
@@ -157,30 +245,32 @@
       <div class="metric-card"><div class="metric-icon blue"><IconifyIcon icon="ant-design:scan-outlined" /></div><div><span>绱鎵爜浜嬩欢</span><strong>{{ dashboard.total ?? 0 }}</strong><small>鎸佺画鎺ユ敹</small></div></div>
       <div class="metric-card"><div class="metric-icon green"><IconifyIcon icon="ant-design:check-circle-outlined" /></div><div><span>鎴愬姛鍏ュ簱</span><strong>{{ successCount }}</strong><small>宸插啓鍏ュ簱瀛樹簨鍔�</small></div></div>
       <div class="metric-card"><div class="metric-icon red"><IconifyIcon icon="ant-design:warning-outlined" /></div><div><span>澶辫触浜嬩欢</span><strong>{{ failedCount }}</strong><small>闇�鍏虫敞寮傚父璁板綍</small></div></div>
-      <div class="metric-card"><div class="metric-icon orange"><IconifyIcon icon="ant-design:inbox-outlined" /></div><div><span>鏈�杩戝叆搴撹鐮�</span><strong class="text-value">{{ latest?.bagCode ?? '-' }}</strong><small>{{ latest?.eventTime ?? '鏆傛棤璁板綍' }}</small></div></div>
+      <div class="metric-card"><div class="metric-icon orange"><IconifyIcon icon="ant-design:inbox-outlined" /></div><div><span>鏈�杩戝叆搴撹拷婧爜</span><strong class="text-value">{{ latest?.bagCode ?? '-' }}</strong><small>{{ latest?.eventTime ?? '鏆傛棤璁板綍' }}</small></div></div>
     </section>
 
     <section class="main-grid">
       <a-card class="scan-card" :bordered="false">
         <template #title><div class="card-title"><IconifyIcon icon="ant-design:scan-outlined" />鎵爜鍏ュ簱 <a-tag color="blue">鑷姩妯″紡</a-tag></div></template>
         <div class="scan-panel">
-          <div class="scan-intro"><div class="scan-ring"><IconifyIcon icon="ant-design:scan-outlined" /></div><div><h2>绛夊緟鎵爜</h2><p>灏嗚鐮佸鍑嗗伐涓氱浉鏈猴紝鎴栦娇鐢ㄦ壂鐮佹灙鎵弿鍚庡洖杞︾‘璁�</p></div></div>
-          <a-input-search v-model:value="code" class="scan-input" size="large" enter-button="纭鍏ュ簱" :loading="loading" :disabled="loading" placeholder="璇锋壂鎻忚鐮侊紝鎵爜鏋洖杞﹀悗鑷姩鍏ュ簱" @search="submitScan" />
-          <div class="scan-hint"><IconifyIcon icon="ant-design:clock-circle-outlined" /> 鏈嶅姟绔褰曞噯纭叆搴撴椂闂� <span>路</span> <IconifyIcon icon="ant-design:inbox-outlined" /> 姣忚鏁伴噺鎸� 1 璁�</div>
+          <div class="scan-intro"><div class="scan-ring"><IconifyIcon icon="ant-design:scan-outlined" /></div><div><h2>绛夊緟鎵爜</h2><p>閫夋嫨杩芥函鐮佺被鍨嬪悗鎵爜锛屾垨浣跨敤鎵爜鏋壂鎻忓悗鍥炶溅纭</p></div></div>
+          <div class="scan-row">
+            <a-select v-model:value="scanType" class="scan-type" :options="SCAN_TYPE_OPTIONS" size="large" @change="onScanTypeChange" />
+            <a-input-search v-model:value="code" class="scan-input" size="large" enter-button="纭鍏ュ簱" :loading="loading" :disabled="loading" placeholder="璇锋壂鎻忚拷婧爜锛屾壂鐮佹灙鍥炶溅鍚庤嚜鍔ㄥ叆搴�" @search="submitScan" />
+          </div>
+          <div class="scan-hint"><IconifyIcon icon="ant-design:clock-circle-outlined" /> 鏈嶅姟绔褰曞噯纭叆搴撴椂闂� <span>路</span> <IconifyIcon icon="ant-design:inbox-outlined" /> 琚嬬爜鎸� 1 琚嬨�佹墭鐩樻寜宸茶琚嬫暟銆佹壒娆℃寜鎵规琚嬫暟鍏ュ簱</div>
         </div>
         <a-divider />
         <a-form layout="vertical" class="config-form">
-          <a-form-item label="璁惧缂栫爜" required><a-input v-model:value="deviceCode" prefix="璁惧" placeholder="渚嬪锛欳AM-LINE-01" /></a-form-item>
-          <a-form-item label="浜х嚎缂栫爜"><a-input v-model:value="lineCode" placeholder="渚嬪锛歀INE-A" /></a-form-item>
-          <a-form-item label="WIP 鏆傚瓨搴撳尯" required><a-input-number v-model:value="locationId" class="full-width" :min="1" placeholder="搴撳尯缂栧彿" /></a-form-item>
-          <a-form-item label="WIP 鏆傚瓨搴撲綅" required><a-input-number v-model:value="areaId" class="full-width" :min="1" placeholder="搴撲綅缂栧彿" /></a-form-item>
+          <a-form-item label="鐢熶骇浜х嚎"><a-select v-model:value="lineCode" :options="lineOptions" allow-clear show-search option-filter-prop="label" placeholder="璇烽�夋嫨浜х嚎" /></a-form-item>
+          <a-form-item label="WIP 鏆傚瓨搴撳尯" required><a-select v-model:value="locationId" :options="locationOptions" show-search option-filter-prop="label" placeholder="璇烽�夋嫨搴撳尯" @change="loadAreas" /></a-form-item>
+          <a-form-item label="WIP 鏆傚瓨搴撲綅" required><a-select v-model:value="areaId" :options="areaOptions" show-search option-filter-prop="label" :disabled="!locationId" placeholder="璇烽�夋嫨搴撲綅" /></a-form-item>
         </a-form>
       </a-card>
 
       <a-card class="status-card" :bordered="false">
         <template #title><div class="card-title"><ApiOutlined />閲囬泦璁惧鐘舵��</div></template>
-        <div class="device-state"><span class="online-badge"><span class="status-dot" aria-hidden="true" /><span>{{ isReady ? '璁惧宸插氨缁�' : '绛夊緟閰嶇疆' }}</span></span><span class="muted">15 绉掕嚜鍔ㄥ埛鏂�</span></div>
-        <div class="status-list"><div><span>褰撳墠璁惧</span><strong>{{ deviceCode || '鏈厤缃�' }}</strong></div><div><span>鐢熶骇浜х嚎</span><strong>{{ lineCode || '鏈厤缃�' }}</strong></div><div><span>鏆傚瓨浣嶇疆</span><strong>{{ locationId && areaId ? `${locationId} 鍖� / ${areaId} 浣峘 : '鏈厤缃�' }}</strong></div><div><span>鏈�杩戝叆搴撴椂闂�</span><strong>{{ latest?.eventTime || '-' }}</strong></div><div><span>鏈�杩戠敓浜ф壒娆�</span><strong>{{ latest?.batchCode || '-' }}</strong></div></div>
+        <div class="device-state"><span class="online-badge"><span class="status-dot" aria-hidden="true" /><span>{{ isReady ? '浣嶇疆宸插氨缁�' : '绛夊緟閰嶇疆' }}</span></span><span class="muted">15 绉掕嚜鍔ㄥ埛鏂�</span></div>
+        <div class="status-list"><div><span>鐢熶骇浜х嚎</span><strong>{{ lineCode || '鏈厤缃�' }}</strong></div><div><span>鏆傚瓨浣嶇疆</span><strong>{{ locationId && areaId ? `${locationName || locationId} / ${areaName || areaId}` : '鏈厤缃�' }}</strong></div><div><span>鏈�杩戝叆搴撴椂闂�</span><strong>{{ latest?.eventTime || '-' }}</strong></div><div><span>鏈�杩戠敓浜ф壒娆�</span><strong>{{ latest?.batchCode || '-' }}</strong></div><div><span>鏈�杩戞壂鐮佺被鍨�</span><strong>{{ latest?.scanType ? SCAN_TYPE_LABEL[latest.scanType] : '-' }}</strong></div></div>
         <div class="sync-box"><IconifyIcon icon="ant-design:check-circle-outlined" /><div><strong>浠撳偍绯荤粺鍚屾姝e父</strong><span>鎵爜浜嬩欢涓庡簱瀛樹簨鍔″疄鏃跺啓鍏�</span></div></div>
       </a-card>
     </section>
@@ -188,7 +278,7 @@
     <a-card class="records-card" :bordered="false">
       <template #title><div class="card-title"><IconifyIcon icon="ant-design:clock-circle-outlined" />鏈�杩戞壂鐮佷簨浠� <span class="record-count">鏈�杩� 20 鏉�</span></div></template>
       <a-table :data-source="records" :pagination="false" :scroll="{ x: 1050 }" row-key="key" size="middle">
-        <a-table-column key="bagCode" title="琚嬬爜" data-index="bagCode" /><a-table-column key="batchCode" title="鐢熶骇鎵规" data-index="batchCode" /><a-table-column key="deviceCode" title="璁惧 / 浜х嚎"><template #default="{ record }">{{ record.deviceCode || '-' }}<span class="sub-text">{{ record.lineCode || '-' }}</span></template></a-table-column>
+        <a-table-column key="bagCode" title="杩芥函鐮�" data-index="bagCode" /><a-table-column key="scanType" title="绫诲瀷"><template #default="{ record }">{{ record.scanType ? SCAN_TYPE_LABEL[record.scanType as MesProScanEventApi.ScanType] : '琚嬬爜' }}</template></a-table-column><a-table-column key="batchCode" title="鐢熶骇鎵规" data-index="batchCode" /><a-table-column key="lineCode" title="浜х嚎"><template #default="{ record }">{{ record.lineCode || '-' }}</template></a-table-column>
         <a-table-column key="status" title="鐘舵��"><template #default="{ record }"><a-tag :color="record.status === 'SUCCESS' ? 'success' : 'error'">{{ record.status === 'SUCCESS' ? '鎴愬姛鍏ュ簱' : '澶辫触' }}</a-tag></template></a-table-column><a-table-column key="errorCode" title="閿欒鐮�" data-index="errorCode" /><a-table-column key="errorMessage" title="澶辫触鍘熷洜" data-index="errorMessage" /><a-table-column key="eventTime" title="澶勭悊鏃堕棿" data-index="eventTime" /><a-table-column key="transactionId" title="搴撳瓨浜嬪姟" data-index="transactionId" />
       </a-table>
     </a-card>
@@ -203,8 +293,8 @@
 .header-status, .device-state { display: flex; align-items: center; gap: 10px; color: #16835b; font-size: 13px; font-weight: 600; }.header-status :deep(.ant-btn) { color: #607089; }
 .status-dot { display: inline-block; flex: 0 0 8px; width: 8px; height: 8px; margin: 0 2px 0 1px; border-radius: 50%; background: #20b878; box-shadow: 0 0 0 4px #d9f7e9; transform-origin: center; }
 .metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 18px; }.metric-card { display: flex; align-items: flex-start; gap: 16px; padding: 18px 20px; background: #fff; border: 1px solid #e8edf5; border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }.metric-card > div:last-child { min-width: 0; padding-top: 1px; }.metric-icon { flex: 0 0 42px; display: grid; place-items: center; width: 42px; height: 42px; margin-top: 1px; border-radius: 9px; font-size: 20px; }.metric-icon.blue { color: #1677ff; background: #eaf3ff; }.metric-icon.green { color: #17a673; background: #e6f8f0; }.metric-icon.red { color: #e05252; background: #fff0f0; }.metric-icon.orange { color: #d58a17; background: #fff6df; }.metric-card span, .metric-card small { display: block; color: #7b8799; font-size: 12px; }.metric-card strong { display: block; margin: 4px 0; font-size: 25px; line-height: 1.1; }.metric-card .text-value { overflow: hidden; max-width: 180px; text-overflow: ellipsis; white-space: nowrap; font-size: 18px; }
-.main-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(320px, .8fr); gap: 18px; margin-bottom: 18px; }.scan-card, .status-card, .records-card { border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }.card-title { display: flex; align-items: center; gap: 10px; min-height: 24px; font-weight: 700; }.card-title :deep(.iconify) { flex: 0 0 auto; margin-top: 1px; }.card-title :deep(.anticon) { color: #1677ff; }.card-title .ant-tag { margin-left: 4px; font-weight: 500; }.scan-panel { padding: 8px 0 4px; }.scan-intro { display: flex; align-items: center; gap: 16px; margin: 4px 0 22px; }.scan-ring { display: grid; place-items: center; width: 58px; height: 58px; margin: 4px 2px 4px 4px; border: 1px solid #b9d5ff; border-radius: 50%; color: #1677ff; background: #f1f7ff; font-size: 25px; }.scan-intro h2 { margin: 0 0 7px; font-size: 19px; line-height: 1.35; }.scan-intro p { margin: 0; color: #7b8799; font-size: 13px; }.scan-input :deep(input) { height: 50px; font-size: 16px; }.scan-input :deep(.ant-input-group-addon .ant-btn) { height: 50px; font-weight: 600; }.scan-hint { display: flex; align-items: center; gap: 8px; margin-top: 15px; padding-left: 2px; color: #8793a5; font-size: 12px; line-height: 1.5; }.scan-hint span { margin: 0 2px; color: #b7c0cd; }.config-form { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 18px; }.config-form :deep(.ant-form-item) { margin-bottom: 14px; }.full-width { width: 100%; }
+.main-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(320px, .8fr); gap: 18px; margin-bottom: 18px; }.scan-card, .status-card, .records-card { border-radius: 10px; box-shadow: 0 3px 12px #243b5310; }.card-title { display: flex; align-items: center; gap: 10px; min-height: 24px; font-weight: 700; }.card-title :deep(.iconify) { flex: 0 0 auto; margin-top: 1px; }.card-title :deep(.anticon) { color: #1677ff; }.card-title .ant-tag { margin-left: 4px; font-weight: 500; }.scan-panel { padding: 8px 0 4px; }.scan-intro { display: flex; align-items: center; gap: 16px; margin: 4px 0 22px; }.scan-ring { display: grid; place-items: center; width: 58px; height: 58px; margin: 4px 2px 4px 4px; border: 1px solid #b9d5ff; border-radius: 50%; color: #1677ff; background: #f1f7ff; font-size: 25px; }.scan-intro h2 { margin: 0 0 7px; font-size: 19px; line-height: 1.35; }.scan-intro p { margin: 0; color: #7b8799; font-size: 13px; }.scan-row { display: flex; align-items: stretch; gap: 10px; }.scan-type { flex: 0 0 150px; }.scan-type :deep(.ant-select-selector) { height: 50px !important; }.scan-type :deep(.ant-select-selection-item) { line-height: 48px !important; }.scan-input { flex: 1 1 auto; min-width: 0; }.scan-input :deep(input) { height: 50px; font-size: 16px; }.scan-input :deep(.ant-input-group-addon .ant-btn) { height: 50px; font-weight: 600; }.scan-hint { display: flex; align-items: center; gap: 8px; margin-top: 15px; padding-left: 2px; color: #8793a5; font-size: 12px; line-height: 1.5; }.scan-hint span { margin: 0 2px; color: #b7c0cd; }.config-form { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 18px; }.config-form :deep(.ant-form-item) { margin-bottom: 14px; }.full-width { width: 100%; }
 .device-state { justify-content: space-between; margin-bottom: 20px; }.online-badge { display: inline-flex; align-items: center; gap: 10px; min-height: 24px; color: #16835b; white-space: nowrap; }.muted, .sub-text { color: #8793a5; font-size: 12px; }.status-list { border-top: 1px solid #edf0f5; }.status-list div { display: flex; justify-content: space-between; gap: 12px; padding: 13px 0; border-bottom: 1px solid #edf0f5; font-size: 13px; }.status-list span { color: #7b8799; }.status-list strong { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }.sync-box { display: flex; gap: 10px; margin-top: 20px; padding: 12px; border-radius: 7px; color: #16835b; background: #effaf5; }.sync-box div { display: flex; flex-direction: column; gap: 3px; }.sync-box span { color: #5f8b76; font-size: 12px; }.record-count { margin-left: 4px; color: #9aa5b5; font-size: 12px; font-weight: 400; }.sub-text { display: block; margin-top: 3px; }
 @media (max-width: 900px) { .metric-grid { grid-template-columns: repeat(2, 1fr); }.main-grid { grid-template-columns: 1fr; } }
-@media (max-width: 600px) { .scan-page { padding: 14px; }.page-header { display: block; }.header-status { margin-top: 14px; }.metric-grid { grid-template-columns: 1fr; }.config-form { grid-template-columns: 1fr; } h1 { font-size: 23px; } }
+@media (max-width: 600px) { .scan-page { padding: 14px; }.page-header { display: block; }.header-status { margin-top: 14px; }.metric-grid { grid-template-columns: 1fr; }.config-form { grid-template-columns: 1fr; }.scan-row { flex-wrap: wrap; }.scan-type { flex-basis: 100%; } h1 { font-size: 23px; } }
 </style>

--
Gitblit v1.9.3