skyline пре 1 недеља
родитељ
комит
e60fb58367
37 измењених фајлова са 709 додато и 1014 уклоњено
  1. 25 91
      admin-mp/src/pages/device/detail.vue
  2. 22 86
      admin-mp/src/pages/device/list.vue
  3. 12 69
      admin-mp/src/pages/finance/index.vue
  4. 18 109
      admin-mp/src/pages/finance/withdraw.vue
  5. 14 1
      admin-mp/src/pages/index/index.vue
  6. 20 141
      admin-mp/src/pages/order/detail.vue
  7. 11 109
      admin-mp/src/pages/order/list.vue
  8. 54 0
      admin-mp/src/utils/index.js
  9. 6 0
      admin-web-new/src/utils/dict.ts
  10. 5 26
      admin-web-new/src/views/admin/finance/settlement.vue
  11. 7 45
      admin-web-new/src/views/admin/finance/withdraw.vue
  12. 11 41
      admin-web-new/src/views/admin/message/index.vue
  13. 4 9
      admin-web-new/src/views/admin/platform/device-config.vue
  14. 3 12
      admin-web-new/src/views/admin/station/device-dialog.vue
  15. 2 8
      admin-web-new/src/views/admin/station/dialog.vue
  16. 21 40
      admin-web-new/src/views/admin/template/index.vue
  17. 7 2
      admin-web-new/src/views/system/dept/index.vue
  18. 7 4
      admin-web-new/src/views/system/dept/utils/hook.tsx
  19. 4 12
      admin-web-new/src/views/system/menu/utils/hook.tsx
  20. 7 2
      admin-web-new/src/views/system/role/index.vue
  21. 5 4
      admin-web-new/src/views/system/role/utils/hook.tsx
  22. 5 14
      admin-web-new/src/views/system/user/form/index.vue
  23. 7 2
      admin-web-new/src/views/system/user/index.vue
  24. 6 5
      admin-web-new/src/views/system/user/utils/hook.tsx
  25. 8 4
      admin-web/src/components/form/ExtBoolean.vue
  26. 1 6
      admin-web/src/components/form/ExtDLabel.vue
  27. 1 6
      admin-web/src/components/form/ExtDSelect.vue
  28. 6 11
      admin-web/src/layout/navBars/breadcrumb/notificationCenter.vue
  29. 16 17
      admin-web/src/stores/message.ts
  30. 15 0
      admin-web/src/utils/u.ts
  31. 2 17
      admin-web/src/views/admin/finance/settlement.vue
  32. 5 21
      admin-web/src/views/admin/log/opt/index.vue
  33. 12 40
      admin-web/src/views/admin/message/index.vue
  34. 4 15
      admin-web/src/views/admin/notice/index.vue
  35. 13 44
      admin-web/src/views/admin/template/index.vue
  36. 74 1
      car-wash-entity/src/main/resources/sql/init.sql
  37. 269 0
      docs/数据字典设计文档.md

+ 25 - 91
admin-mp/src/pages/device/detail.vue

@@ -16,7 +16,7 @@
           <text class="device-name">{{ deviceDetail.deviceName || '未知设备' }}</text>
           <text class="device-id">设备编号: {{ deviceDetail.shortId || '无' }}</text>
         </view>
-        <view class="device-status" :class="getDeviceStatusClass(deviceDetail.state)">
+        <view class="device-status" :style="getDeviceStatusStyle(deviceDetail.state)">
           <text class="status-dot"></text>
           <text>{{ getDeviceStatusText(deviceDetail.state) }}</text>
         </view>
@@ -60,11 +60,11 @@
         </view>
         <view class="status-item">
           <text class="status-label">是否有水</text>
-          <text class="status-value">{{ deviceDetail.hasWater === true ? '有水' : (deviceDetail.hasWater === false ? '无水' : '不支持') }}</text>
+          <text class="status-value">{{ fmtDictName('yes_no', deviceDetail.hasWater) }}</text>
         </view>
         <view class="status-item">
           <text class="status-label">是否有泡沫</text>
-          <text class="status-value">{{ deviceDetail.hasFoam === true ? '有' : (deviceDetail.hasFoam === false ? '无' : '不支持') }}</text>
+          <text class="status-value">{{ fmtDictName('yes_no', deviceDetail.hasFoam) }}</text>
         </view>
         <view class="status-item" v-if="deviceDetail.state === 'fault'">
           <text class="status-label">故障原因</text>
@@ -116,17 +116,24 @@
 <script setup>
 import { ref, onMounted, computed } from 'vue'
 import { getDeviceDetail, stopDevice } from '../../api/device.js'
-import { formatTime, showToast, storage } from '../../utils/index.js'
+import { formatTime, showToast, storage, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const deviceId = ref('')
 const deviceDetail = ref({})
 const loading = ref(true)
 
-// 计算设备是否在线
+// 计算设备是否在线(通过字典查找在线状态值)
 const isOnline = computed(() => {
   const state = deviceDetail.value.state
-  return state === 'init' || state === 'idle' || state === 'busy' || 
-         state === 1 || state === '1' || state === 'ONLINE'
+  if (state === null || state === undefined) return false
+  const dicts = storage.get('dicts')
+  if (dicts && dicts['WashDevice.status']) {
+    const item = dicts['WashDevice.status'].find(k => k.value == state)
+    if (item && item.color) {
+      return item.color === '#52C41A'
+    }
+  }
+  return false
 })
 
 // 获取设备类型文本
@@ -155,74 +162,21 @@ const getDeviceTypeText = (type) => {
   }
 }
 
-// 获取设备状态文本
+// 从字典获取设备状态文本
 const getDeviceStatusText = (state) => {
-  // 添加调试信息
-  console.log('设备状态值:', state, '类型:', typeof state)
-  
-  // 如果状态为空,直接返回离线
-  if (state === null || state === undefined || state === '') {
-    return '离线'
-  }
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['WashDevice.status']) {
-      console.warn('字典数据未加载或不存在 WashDevice.status')
-      // 降级处理:使用默认映射
-      const statusMap = {
-        'init': '初始化',
-        'idle': '设备空闲',
-        'busy': '设备忙碌',
-        'sleep': '不在营业时间',
-        'maintenance': '维护模式',
-        'fault': '设备故障',
-        0: '离线', 1: '在线', 2: '故障', 3: '维护中',
-        '0': '离线', '1': '在线', '2': '故障', '3': '维护中',
-        'ONLINE': '在线', 'OFFLINE': '离线', 'FAULT': '故障', 'MAINTENANCE': '维护中'
-      }
-      return statusMap[state] || statusMap[String(state)] || String(state)
-    }
-    
-    const deviceStatusDict = dicts['WashDevice.status']
-    const dictItem = deviceStatusDict.find(item => item.value == state)
-    
-    return dictItem ? dictItem.name : String(state)
-  } catch (error) {
-    console.error('获取设备状态文本失败:', error)
-    return String(state)
-  }
+  return fmtDictName('WashDevice.status', state)
 }
 
-// 获取设备状态样式类
-const getDeviceStatusClass = (state) => {
-  if (!state) return ''
-  
-  const statusClassMap = {
-    // 新状态值映射
-    'init': 'status-online',      // 初始化 - 在线样式
-    'idle': 'status-online',      // 设备空闲 - 在线样式
-    'busy': 'status-online',      // 设备忙碌 - 在线样式
-    'sleep': 'status-offline',    // 不在营业时间 - 离线样式
-    'maintenance': 'status-maintenance',  // 维护模式 - 维护样式
-    'fault': 'status-fault',      // 设备故障 - 故障样式
-    // 旧状态值映射(兼容)
-    0: 'status-offline',
-    1: 'status-online',
-    2: 'status-fault',
-    3: 'status-maintenance',
-    '0': 'status-offline',
-    '1': 'status-online',
-    '2': 'status-fault',
-    '3': 'status-maintenance',
-    'ONLINE': 'status-online',
-    'OFFLINE': 'status-offline',
-    'FAULT': 'status-fault',
-    'MAINTENANCE': 'status-maintenance'
+// 从字典获取设备状态样式
+const getDeviceStatusStyle = (state) => {
+  const color = getDictColor('WashDevice.status', state)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
+    }
   }
-  
-  // 尝试直接匹配或类型转换后匹配
-  return statusClassMap[state] || statusClassMap[String(state)] || ''
+  return {}
 }
 
 // 格式化运行时长
@@ -424,26 +378,6 @@ onMounted(() => {
   background-color: currentColor;
 }
 
-/* 状态样式 */
-.status-online {
-  color: #52C41A;
-  background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
-}
-
-.status-offline {
-  color: #999999;
-  background: linear-gradient(135deg, #F5F5F5 0%, #E0E0E0 100%);
-}
-
-.status-fault {
-  color: #F5222D;
-  background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
-}
-
-.status-maintenance {
-  color: #FAAD14;
-  background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
-}
 
 /* 详情信息卡片 */
 .detail-info-card,

+ 22 - 86
admin-mp/src/pages/device/list.vue

@@ -36,7 +36,7 @@
             <text class="device-name">{{ device.name }}</text>
             <view class="device-id-and-status">
                   <text class="device-id">ID: {{ device.shortId }}</text>
-                  <view class="device-status" :class="getDeviceStatusClass(getDeviceStatusValue(device))">
+                  <view class="device-status" :style="getDeviceStatusStyle(getDeviceStatusValue(device))">
                     <text class="status-dot"></text>
                     <text>{{ getDeviceStatusText(getDeviceStatusValue(device)) }}</text>
                   </view>
@@ -89,8 +89,8 @@
           <view class="device-busy-status" :class="getDeviceBusyStatusClass(device)">
             <text class="busy-status-text">{{ getDeviceBusyStatus(device) }}</text>
           </view>
-          <button 
-            v-if="getDeviceStatusValue(device) === 1 || getDeviceStatusValue(device) === '1' || getDeviceStatusValue(device) === 'ONLINE'" 
+          <button
+            v-if="isDeviceOnline(getDeviceStatusValue(device))"
             class="stop-btn"
             @click.stop="handleStopDevice(device.shortId)"
           >
@@ -120,7 +120,7 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { getDeviceList, stopDevice, getDeviceConfigList, batchModifyDeviceConfig } from '../../api/device.js'
-import { formatTime, showToast, storage } from '../../utils/index.js'
+import { formatTime, showToast, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const deviceList = ref([])
 const loading = ref(true)
@@ -360,49 +360,16 @@ const getDeviceStatusValue = (device) => {
 
 // 从字典获取设备状态文本
 const getDeviceStatusText = (status) => {
-  // 添加调试信息
-  console.log('设备状态值:', status, '类型:', typeof status)
-  
-  // 如果状态为空,直接返回离线
-  if (status === null || status === undefined || status === '') {
-    return '离线'
-  }
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['WashDevice.status']) {
-      console.warn('字典数据未加载或不存在 WashDevice.status')
-      // 降级处理:使用默认映射
-      const statusMap = {
-        'init': '初始化',
-        'idle': '设备空闲',
-        'busy': '设备忙碌',
-        'sleep': '不在营业时间',
-        'maintenance': '维护模式',
-        'fault': '设备故障',
-        0: '离线', 1: '在线', 2: '故障', 3: '维护中',
-        '0': '离线', '1': '在线', '2': '故障', '3': '维护中',
-        'ONLINE': '在线', 'OFFLINE': '离线', 'FAULT': '故障', 'MAINTENANCE': '维护中'
-      }
-      return statusMap[status] || statusMap[String(status)] || String(status)
-    }
-    
-    const deviceStatusDict = dicts['WashDevice.status']
-    const dictItem = deviceStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : String(status)
-  } catch (error) {
-    console.error('获取设备状态文本失败:', error)
-    return String(status)
-  }
+  return fmtDictName('WashDevice.status', status)
 }
 
 // 获取设备闲忙状态
 const getDeviceBusyStatus = (device) => {
   const status = getDeviceStatusValue(device)
-  
-  // 如果设备不在线,不显示闲忙状态
-  if (status !== 1 && status !== '1' && status !== 'ONLINE') {
+
+  // 通过字典颜色判断设备是否在线(绿色表示在线状态)
+  const color = getDictColor('WashDevice.status', status)
+  if (color !== '#52C41A') {
     return null
   }
   
@@ -426,32 +393,20 @@ const getDeviceBusyStatusClass = (device) => {
   return busyStatus === '忙碌' ? 'busy-status' : 'idle-status'
 }
 
-const getDeviceStatusClass = (status) => {
-  const statusClassMap = {
-    // 新状态值映射
-    'init': 'status-online',      // 初始化 - 在线样式
-    'idle': 'status-online',      // 设备空闲 - 在线样式
-    'busy': 'status-online',      // 设备忙碌 - 在线样式
-    'sleep': 'status-offline',    // 不在营业时间 - 离线样式
-    'maintenance': 'status-maintenance',  // 维护模式 - 维护样式
-    'fault': 'status-fault',      // 设备故障 - 故障样式
-    // 旧状态值映射(兼容)
-    0: 'status-offline',
-    1: 'status-online',
-    2: 'status-fault',
-    3: 'status-maintenance',
-    '0': 'status-offline',
-    '1': 'status-online',
-    '2': 'status-fault',
-    '3': 'status-maintenance',
-    'ONLINE': 'status-online',
-    'OFFLINE': 'status-offline',
-    'FAULT': 'status-fault',
-    'MAINTENANCE': 'status-maintenance'
+const getDeviceStatusStyle = (status) => {
+  const color = getDictColor('WashDevice.status', status)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
+    }
   }
-  
-  // 尝试直接匹配或类型转换后匹配
-  return statusClassMap[status] || statusClassMap[String(status)] || ''
+  return {}
+}
+
+// 通过字典颜色判断设备是否在线
+const isDeviceOnline = (status) => {
+  return getDictColor('WashDevice.status', status) === '#52C41A'
 }
 
 const getStatusValue = (filterIndex) => {
@@ -664,25 +619,6 @@ const getStatusValue = (filterIndex) => {
   background-color: currentColor;
 }
 
-.status-online {
-  color: #52C41A;
-  background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
-}
-
-.status-offline {
-  color: #999999;
-  background: linear-gradient(135deg, #F5F5F5 0%, #E0E0E0 100%);
-}
-
-.status-fault {
-  color: #F5222D;
-  background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
-}
-
-.status-maintenance {
-  color: #FAAD14;
-  background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
-}
 
 .device-content {
   margin-bottom: 8rpx;

+ 12 - 69
admin-mp/src/pages/finance/index.vue

@@ -111,7 +111,7 @@
             </view>
             <view class="split-detail">
               <text class="detail-label">分账状态:</text>
-              <text class="detail-value" :class="getSplitStatusClass(record.status)">
+              <text class="detail-value" :style="getSplitStatusStyle(record.status)">
                 {{ getSplitStatusText(record.status) }}
               </text>
             </view>
@@ -136,7 +136,7 @@
 import { ref, onMounted } from 'vue'
 import { getStationAccounts, getSplitRecords } from '../../api/finance.js'
 import { getDashboardData } from '../../api/stat.js'
-import { formatTime, showToast, formatAmount, storage } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, storage, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const activeTab = ref(0)
 const tabOptions = ['站点账户', '分账记录']
@@ -233,71 +233,24 @@ const getChangeClass = (change) => {
 }
 
 const getSplitTypeText = (type) => {
-  const typeMap = {
-    'PLATFORM': '平台分账',
-    'STATION': '站点分账',
-    'INVESTOR': '投资人分账'
-  }
-  return typeMap[type] || '未知类型'
+  return fmtDictName('SplitRecord.type', type)
 }
 
 // 从字典获取分账状态文本
 const getSplitStatusText = (status) => {
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Split.status']) {
-      console.warn('字典数据未加载或不存在 Split.status')
-      // 降级处理:使用默认映射
-      const statusMap = {
-        'SUCCESS': '成功',
-        'FAILED': '失败',
-        'PROCESSING': '处理中'
-      }
-      return statusMap[status] || '未知状态'
-    }
-    
-    const splitStatusDict = dicts['Split.status']
-    const dictItem = splitStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : '未知状态'
-  } catch (error) {
-    console.error('获取分账状态文本失败:', error)
-    return '未知状态'
-  }
+  return fmtDictName('SplitRecord.status', status)
 }
 
-const getSplitStatusClass = (status) => {
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Split.status']) {
-      // 降级处理:使用默认映射
-      const statusClassMap = {
-        'SUCCESS': 'status-success',
-        'FAILED': 'status-failed',
-        'PROCESSING': 'status-processing'
-      }
-      return statusClassMap[status] || ''
-    }
-    
-    const splitStatusDict = dicts['Split.status']
-    const dictItem = splitStatusDict.find(item => item.value == status)
-    
-    // 如果字典项有颜色,返回自定义样式类名
-    if (dictItem && dictItem.color) {
-      return 'status-custom'
-    }
-    
-    // 降级处理:使用默认映射
-    const statusClassMap = {
-      'SUCCESS': 'status-success',
-      'FAILED': 'status-failed',
-      'PROCESSING': 'status-processing'
+// 从字典获取分账状态内联样式
+const getSplitStatusStyle = (status) => {
+  const color = getDictColor('SplitRecord.status', status)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
     }
-    return statusClassMap[status] || ''
-  } catch (error) {
-    console.error('获取分账状态样式失败:', error)
-    return ''
   }
+  return {}
 }
 </script>
 
@@ -664,15 +617,5 @@ const getSplitStatusClass = (status) => {
   font-weight: 500;
 }
 
-.status-success {
-  color: #52C41A;
-}
-
-.status-failed {
-  color: #F5222D;
-}
 
-.status-processing {
-  color: #FAAD14;
-}
 </style>

+ 18 - 109
admin-mp/src/pages/finance/withdraw.vue

@@ -97,7 +97,7 @@
               <text class="record-no">提现编号: {{ record.withdrawNo }}</text>
               <text class="record-time">{{ formatTime(record.createTime) }}</text>
             </view>
-            <text class="record-status" :class="getStatusClass(record.status)">
+            <text class="record-status" :style="getStatusStyle(record.status)">
               {{ getStatusText(record.status) }}
             </text>
           </view>
@@ -140,7 +140,7 @@
 import { ref, computed, onMounted } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import { getWithdrawnRecords, reviewWithdrawn, applyWithdrawn, getStationAccounts } from '../../api/finance.js'
-import { formatTime, showToast, formatAmount } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, storage, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const stationId = ref('')
 const stationName = ref('')
@@ -330,99 +330,32 @@ const handleReject = async (recordId) => {
 
 // 从字典获取提现状态文本
 const getStatusText = (status) => {
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Withdraw.status']) {
-      console.warn('字典数据未加载或不存在 Withdraw.status')
-      // 降级处理:使用默认映射
-      const statusMap = {
-        0: '待审核',
-        1: '审核通过',
-        2: '审核失败'
-      }
-      return statusMap[status] || '未知状态'
-    }
-    
-    const withdrawStatusDict = dicts['Withdraw.status']
-    const dictItem = withdrawStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : '未知状态'
-  } catch (error) {
-    console.error('获取提现状态文本失败:', error)
-    return '未知状态'
-  }
+  return fmtDictName('WithdrawnRecord.status', status)
 }
 
-const getStatusClass = (status) => {
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Withdraw.status']) {
-      // 降级处理:使用默认映射
-      const statusClassMap = {
-        0: 'status-pending',
-        1: 'status-approved',
-        2: 'status-rejected'
-      }
-      return statusClassMap[status] || ''
+// 从字典获取提现状态内联样式
+const getStatusStyle = (status) => {
+  const color = getDictColor('WithdrawnRecord.status', status)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
     }
-    
-    const withdrawStatusDict = dicts['Withdraw.status']
-    const dictItem = withdrawStatusDict.find(item => item.value == status)
-    
-    // 如果字典项有颜色,返回自定义样式类名
-    if (dictItem && dictItem.color) {
-      return 'status-custom'
-    }
-    
-    // 降级处理:使用默认映射
-    const statusClassMap = {
-      0: 'status-pending',
-      1: 'status-approved',
-      2: 'status-rejected'
-    }
-    return statusClassMap[status] || ''
-  } catch (error) {
-    console.error('获取提现状态样式失败:', error)
-    return ''
   }
+  return {}
 }
 
 // 从字典获取提现状态值(用于过滤)
 const getStatusValue = (filterIndex) => {
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Withdraw.status']) {
-      console.warn('字典数据未加载或不存在 Withdraw.status')
-      // 降级处理:使用默认映射
-      const statusMap = {
-        1: 0, // 待审核
-        2: 1, // 审核通过
-        3: 2  // 审核失败
-      }
-      return statusMap[filterIndex] || ''
+  if (filterIndex === 0) return ''
+  const dicts = storage.get('dicts')
+  if (dicts && dicts['WithdrawnRecord.status']) {
+    const list = dicts['WithdrawnRecord.status']
+    if (list[filterIndex - 1]) {
+      return list[filterIndex - 1].value
     }
-    
-    // 跳过"全部"选项(index 0)
-    if (filterIndex === 0) {
-      return ''
-    }
-    
-    const withdrawStatusDict = dicts['Withdraw.status']
-    // 从字典中获取对应索引的状态值
-    // 注意:需要确保filterOptions与字典数据顺序一致
-    const dictItem = withdrawStatusDict[filterIndex - 1]
-    
-    return dictItem ? dictItem.value : ''
-  } catch (error) {
-    console.error('获取提现状态值失败:', error)
-    // 降级处理:使用默认映射
-    const statusMap = {
-      1: 0, // 待审核
-      2: 1, // 审核通过
-      3: 2  // 审核失败
-    }
-    return statusMap[filterIndex] || ''
   }
+  return ''
 }
 </script>
 
@@ -681,30 +614,6 @@ const getStatusValue = (filterIndex) => {
   border-radius: 20rpx;
 }
 
-.status-pending {
-  color: #FA8C16;
-  background-color: #FFF7E6;
-}
-
-.status-approved {
-  color: #52C41A;
-  background-color: #F6FFED;
-}
-
-.status-rejected {
-  color: #FF4D4F;
-  background-color: #FFF1F0;
-}
-
-.status-paid {
-  color: #1890FF;
-  background-color: #E6F7FF;
-}
-
-.status-failed {
-  color: #BFBFBF;
-  background-color: #F5F5F5;
-}
 
 .record-content {
   margin-bottom: 20rpx;

+ 14 - 1
admin-mp/src/pages/index/index.vue

@@ -280,7 +280,20 @@ const loadData = async (stationId) => {
     if (deviceRes && deviceRes.code === 200) {
       const deviceData = deviceRes.data
       totalDevices.value = Object.values(deviceData).reduce((sum, count) => sum + count, 0)
-      onlineDevices.value = deviceData['1'] || 0
+      // 从字典获取在线状态值,而不是硬编码 '1'
+      const dicts = storage.get('dicts')
+      let onlineCount = 0
+      if (dicts && dicts['WashDevice.status']) {
+        const onlineStatuses = dicts['WashDevice.status']
+          .filter(item => item.color === '#52C41A')
+          .map(item => String(item.value))
+        onlineCount = Object.entries(deviceData)
+          .filter(([key]) => onlineStatuses.includes(key))
+          .reduce((sum, [, count]) => sum + count, 0)
+      } else {
+        onlineCount = deviceData['1'] || 0
+      }
+      onlineDevices.value = onlineCount
     }
   } catch (error) {
     console.error('加载首页数据失败:', error)

+ 20 - 141
admin-mp/src/pages/order/detail.vue

@@ -13,9 +13,8 @@
         <text class="number-value">{{ orderDetail.orderId }}</text>
       </view>
       <view class="status-row">
-        <text 
-          class="status-tag" 
-          :class="getOrderStatusClass(orderDetail.orderStatus)"
+        <text
+          class="status-tag"
           :style="getOrderStatusStyle(orderDetail.orderStatus)"
         >
           {{ getOrderStatusText(orderDetail.orderStatus) }}
@@ -134,137 +133,42 @@
 <script setup>
 import { ref, onMounted, computed } from 'vue'
 import { getOrderDetail, handleRefund as refundApi } from '../../api/order.js'
-import { formatTime, showToast, formatAmount, storage } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, storage, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const orderId = ref('')
 const orderDetail = ref({})
 const loading = ref(true)
 
-// 获取订单状态文本
+// 从字典获取订单状态文本
 const getOrderStatusText = (status) => {
-  if (!status) return '未知状态'
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.status']) {
-      console.warn('字典数据未加载或不存在 Order.status')
-      return status
-    }
-    
-    const orderStatusDict = dicts['Order.status']
-    const dictItem = orderStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : status
-  } catch (error) {
-    console.error('获取订单状态文本失败:', error)
-    return status
-  }
+  return fmtDictName('WashOrder.orderStatus', status)
 }
 
-// 获取支付状态文本
+// 从字典获取支付状态文本
 const getPayStatusText = (status) => {
-  if (!status) return '未知状态'
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.pay']) {
-      console.warn('字典数据未加载或不存在 Order.pay')
-      return status
-    }
-    
-    const payStatusDict = dicts['Order.pay']
-    const dictItem = payStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : status
-  } catch (error) {
-    console.error('获取支付状态文本失败:', error)
-    return status
-  }
+  return fmtDictName('WashOrder.payStatus', status)
 }
 
-// 获取关机方式文本
+// 从字典获取关机方式文本
 const getCloseTypeText = (type) => {
-  if (!type) return '未知方式'
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.closeType']) {
-      console.warn('字典数据未加载或不存在 Order.closeType')
-      return type
-    }
-    
-    const closeTypeDict = dicts['Order.closeType']
-    const dictItem = closeTypeDict.find(item => item.value == type)
-    
-    return dictItem ? dictItem.name : type
-  } catch (error) {
-    console.error('获取关机方式文本失败:', error)
-    return type
-  }
+  return fmtDictName('WashOrder.closeType', type)
 }
 
-// 获取发票状态文本
+// 从字典获取发票状态文本
 const getInvoiceStatusText = (status) => {
-  if (!status) return '未开票'
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Invoice.status']) {
-      console.warn('字典数据未加载或不存在 Invoice.status')
-      return status
-    }
-    
-    const invoiceStatusDict = dicts['Invoice.status']
-    const dictItem = invoiceStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : status
-  } catch (error) {
-    console.error('获取发票状态文本失败:', error)
-    return status
-  }
-}
-
-// 获取订单状态样式类
-const getOrderStatusClass = (status) => {
-  if (!status) return ''
-  
-  const statusClassMap = {
-    '0': 'status-wait',        // 待支付
-    '1': 'status-processing',  // 进行中
-    '2': 'status-completed',   // 已完成
-    '3': 'status-refunded',    // 已退款
-    '4': 'status-refunding',   // 退款中
-    '5': 'status-canceled'     // 已取消
-  }
-  return statusClassMap[status] || ''
+  return fmtDictName('WashOrder.invoiceStatus', status)
 }
 
-// 获取订单状态样式
+// 从字典获取订单状态内联样式
 const getOrderStatusStyle = (status) => {
-  if (!status) return {}
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.status']) {
-      return {}
-    }
-    
-    const orderStatusDict = dicts['Order.status']
-    const dictItem = orderStatusDict.find(item => item.value == status)
-    
-    // 如果字典项有颜色,使用字典颜色
-    if (dictItem && dictItem.color) {
-      return {
-        color: dictItem.color,
-        backgroundColor: `${dictItem.color}1A` // 添加10%透明度
-      }
+  const color = getDictColor('WashOrder.orderStatus', status)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
     }
-    
-    return {}
-  } catch (error) {
-    console.error('获取订单状态样式失败:', error)
-    return {}
   }
+  return {}
 }
 
 // 计算是否可以处理退款
@@ -310,8 +214,8 @@ const loadOrderDetail = async () => {
             let serviceName = item.name
             try {
               const dicts = storage.get('dicts')
-              if (dicts && dicts['Order.feeType']) {
-                const feeTypeDict = dicts['Order.feeType']
+              if (dicts && dicts['WashOrder.feeType']) {
+                const feeTypeDict = dicts['WashOrder.feeType']
                 const dictItem = feeTypeDict.find(d => d.value == item.name)
                 if (dictItem) {
                   serviceName = dictItem.name
@@ -734,30 +638,5 @@ onMounted(() => {
   opacity: 0.9;
 }
 
-/* 状态颜色 */
-.status-wait {
-  color: #FAAD14;
-  background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
-}
-
-.status-processing {
-  color: #1890FF;
-  background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
-}
-
-.status-completed {
-  color: #52C41A;
-  background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
-}
-
-.status-refunded,
-.status-canceled {
-  color: #999999;
-  background: linear-gradient(135deg, #F5F5F5 0%, #E0E0E0 100%);
-}
 
-.status-refunding {
-  color: #F5222D;
-  background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
-}
 </style>

+ 11 - 109
admin-mp/src/pages/order/list.vue

@@ -29,9 +29,8 @@
             <text class="order-id-label">订单号</text>
             <text class="order-id">{{ order.orderId }}</text>
           </view>
-          <text 
-            class="order-status" 
-            :class="getOrderStatusClass(order.orderStatus)"
+          <text
+            class="order-status"
             :style="getOrderStatusStyle(order.orderStatus)"
           >
             {{ getOrderStatusText(order.orderStatus) }}
@@ -124,7 +123,7 @@
 <script setup>
 import { ref, onMounted, reactive } from 'vue'
 import { getOrderList, handleRefund as refundApi } from '../../api/order.js'
-import { formatTime, showToast, formatAmount, storage } from '../../utils/index.js'
+import { formatTime, showToast, formatAmount, fmtDictName, getDictColor } from '../../utils/index.js'
 
 const orderList = ref([])
 const loading = ref(true)
@@ -282,86 +281,19 @@ const handleRefund = async (refundLogId) => {
 
 // 从字典获取订单状态文本
 const getOrderStatusText = (status) => {
-  if (!status) return '未知状态'
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.status']) {
-      console.warn('字典数据未加载或不存在 Order.status')
-      return status
-    }
-    
-    const orderStatusDict = dicts['Order.status']
-    const dictItem = orderStatusDict.find(item => item.value == status)
-    
-    return dictItem ? dictItem.name : status
-  } catch (error) {
-    console.error('获取订单状态文本失败:', error)
-    return status
-  }
+  return fmtDictName('WashOrder.orderStatus', status)
 }
 
-// 从字典获取订单状态样式(基于字典颜色)
-const getOrderStatusClass = (status) => {
-  if (!status) return ''
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.status']) {
-      return ''
-    }
-    
-    const orderStatusDict = dicts['Order.status']
-    const dictItem = orderStatusDict.find(item => item.value == status)
-    
-    // 如果字典项有颜色,返回自定义样式类名
-    if (dictItem && dictItem.color) {
-      return 'status-custom'
-    }
-    
-    // 使用默认样式映射
-    const statusClassMap = {
-      '0': 'status-wait',        // 待支付
-      '1': 'status-processing',  // 进行中
-      '2': 'status-completed',   // 已完成
-      '3': 'status-refunded',    // 已退款
-      '4': 'status-refunding',   // 退款中
-      '5': 'status-canceled'     // 已取消
-    }
-    return statusClassMap[status] || ''
-  } catch (error) {
-    console.error('获取订单状态样式失败:', error)
-    return ''
-  }
-}
-
-// 从字典获取订单状态内联样式(基于字典颜色)
+// 从字典获取订单状态内联样式
 const getOrderStatusStyle = (status) => {
-  if (!status) return {}
-  
-  try {
-    const dicts = storage.get('dicts')
-    if (!dicts || !dicts['Order.status']) {
-      return {}
+  const color = getDictColor('WashOrder.orderStatus', status)
+  if (color) {
+    return {
+      color: color,
+      backgroundColor: `${color}1A`
     }
-    
-    const orderStatusDict = dicts['Order.status']
-    const dictItem = orderStatusDict.find(item => item.value == status)
-    
-    // 如果字典项有颜色,使用字典颜色
-    if (dictItem && dictItem.color) {
-      return {
-        color: dictItem.color,
-        backgroundColor: `${dictItem.color}1A`, // 添加10%透明度
-        borderColor: dictItem.color
-      }
-    }
-    
-    return {}
-  } catch (error) {
-    console.error('获取订单状态内联样式失败:', error)
-    return {}
   }
+  return {}
 }
 
 
@@ -496,36 +428,6 @@ const onPullDownRefresh = () => {
   white-space: nowrap;
 }
 
-/* 状态颜色 */
-.status-wait {
-  color: #FAAD14;
-  background: linear-gradient(135deg, #FFF7E6 0%, #FFE7BA 100%);
-}
-
-.status-processing {
-  color: #1890FF;
-  background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
-}
-
-.status-completed {
-  color: #52C41A;
-  background: linear-gradient(135deg, #F6FFED 0%, #D9F7BE 100%);
-}
-
-.status-refunded,
-.status-canceled {
-  color: #999999;
-  background: linear-gradient(135deg, #F5F5F5 0%, #E0E0E0 100%);
-}
-
-.status-refunding {
-  color: #F5222D;
-  background: linear-gradient(135deg, #FFF1F0 0%, #FFCCC7 100%);
-}
-
-.status-custom {
-  font-weight: 600;
-}
 
 /* 核心信息样式 */
 .order-core-info {

+ 54 - 0
admin-mp/src/utils/index.js

@@ -123,6 +123,60 @@ export const formatAmount = (amount, isCent = true) => {
   return yuan.toFixed(2)
 }
 
+/**
+ * 从字典获取名称(标签)
+ * @param {string} code - 字典编码
+ * @param {any} value - 字典值
+ * @returns {string} 字典名称,找不到时返回原值
+ */
+export const fmtDictName = (code, value) => {
+  if (value === null || value === undefined || value === '') {
+    return value
+  }
+
+  const dictStorage = storage.get('dicts')
+
+  if (!dictStorage) {
+    return value
+  }
+
+  const elements = dictStorage[code]
+  if (elements) {
+    const ele = elements.find(k => k.value == value)
+    if (ele) {
+      return ele.name
+    }
+  }
+  return String(value)
+}
+
+/**
+ * 从字典获取颜色值
+ * @param {string} code - 字典编码
+ * @param {any} value - 字典值
+ * @returns {string} 颜色值,找不到时返回空字符串
+ */
+export const getDictColor = (code, value) => {
+  if (value === null || value === undefined) {
+    return ''
+  }
+
+  const dictStorage = storage.get('dicts')
+
+  if (!dictStorage) {
+    return ''
+  }
+
+  const elements = dictStorage[code]
+  if (elements) {
+    const ele = elements.find(k => k.value == value)
+    if (ele) {
+      return ele.color || ''
+    }
+  }
+  return ''
+}
+
 /**
  * 获取请求头
  * @returns {Object} 请求头

+ 6 - 0
admin-web-new/src/utils/dict.ts

@@ -96,4 +96,10 @@ export const getDictOptions = (code: string): { label: string; value: any }[] =>
   }));
 };
 
+export const getDictColor = (code: string, value: any): string => {
+  const list = dictUtil.getDictList(code);
+  const item = list.find(k => k.value == value);
+  return item?.color || "";
+};
+
 export default dictUtil;

+ 5 - 26
admin-web-new/src/views/admin/finance/settlement.vue

@@ -4,6 +4,7 @@ import { getSettlementList, triggerSettlement } from "@/api/finance";
 import { getStationList } from "@/api/station";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { ElMessage } from "element-plus";
+import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
 
 defineOptions({
   name: "AdminFinanceSettlement"
@@ -45,12 +46,7 @@ const state = reactive({
     ]
   },
   stationOptions: [] as Array<{ stationId: string; stationName: string }>,
-  statusOptions: [
-    { label: "全部", value: "" },
-    { label: "待结算", value: "0" },
-    { label: "已结算", value: "1" },
-    { label: "异常结算", value: "2" }
-  ]
+  get statusOptions() { return getDictOptions("Settlement.status"); }
 });
 
 onMounted(() => {
@@ -131,23 +127,6 @@ const formatMoney = (value: number) => {
   return `¥${(value / 100).toFixed(2)}`;
 };
 
-const getStatusLabel = (status: number) => {
-  const map: Record<number, string> = {
-    0: "待结算",
-    1: "已结算",
-    2: "异常结算"
-  };
-  return map[status] || String(status);
-};
-
-const getStatusType = (status: number): "info" | "success" | "danger" | "warning" => {
-  const map: Record<number, "info" | "success" | "danger"> = {
-    0: "info",
-    1: "success",
-    2: "danger"
-  };
-  return map[status] || "info";
-};
 </script>
 
 <template>
@@ -167,7 +146,7 @@ const getStatusType = (status: number): "info" | "success" | "danger" | "warning
         </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="state.formQuery.status" placeholder="请选择状态" clearable @change="handleSearch">
-            <el-option v-for="item in state.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
+            <el-option v-for="item in getDictOptions('Settlement.status')" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -184,8 +163,8 @@ const getStatusType = (status: number): "info" | "success" | "danger" | "warning
         <el-table-column v-for="col in state.tableData.columns" :key="col.prop" :prop="col.prop" :label="col.label" :width="col.width" show-overflow-tooltip>
           <template #default="{ row }">
             <template v-if="col.prop === 'status'">
-              <el-tag :type="getStatusType(row.status)" size="small">
-                {{ getStatusLabel(row.status) }}
+              <el-tag :type="getDictColor('Settlement.status', row.status)" size="small">
+                {{ formatDict('Settlement.status', row.status) }}
               </el-tag>
             </template>
             <template v-else-if="['openingPendingBalance', 'totalRecharge', 'totalRefund', 'totalCrossIncome', 'totalCrossExpend', 'platformFeeBase', 'platformFee', 'settlementAmount', 'closingPendingBalance'].includes(col.prop)">

+ 7 - 45
admin-web-new/src/views/admin/finance/withdraw.vue

@@ -4,6 +4,7 @@ import { getWithdrawList } from "@/api/finance";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { http } from "@/utils/http";
 import { ElMessage, ElMessageBox } from "element-plus";
+import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
 
 defineOptions({
   name: "AdminFinanceWithdraw"
@@ -41,16 +42,8 @@ const state = reactive({
       { label: "更新时间", prop: "updateTime", width: 180 }
     ]
   },
-  statusOptions: [
-    { label: "全部", value: "" },
-    { label: "待审核", value: "0" },
-    { label: "已审核", value: "1" }
-  ],
-  paymentStatusOptions: [
-    { label: "全部", value: "" },
-    { label: "待打款", value: "0" },
-    { label: "已打款", value: "1" }
-  ]
+  get statusOptions() { return getDictOptions("WithdrawnRecord.status"); },
+  get paymentStatusOptions() { return getDictOptions("WithdrawnRecord.paymentStatus"); }
 });
 
 onMounted(() => {
@@ -165,37 +158,6 @@ const formatMoney = (value: number) => {
   return `¥${(value / 100).toFixed(2)}`;
 };
 
-const getStatusLabel = (status: number) => {
-  const map: Record<number, string> = {
-    0: "待审核",
-    1: "已审核"
-  };
-  return map[status] || String(status);
-};
-
-const getStatusType = (status: number): "info" | "success" | "danger" | "warning" => {
-  const map: Record<number, "warning" | "success"> = {
-    0: "warning",
-    1: "success"
-  };
-  return map[status] || "info";
-};
-
-const getPaymentStatusLabel = (status: number) => {
-  const map: Record<number, string> = {
-    0: "待打款",
-    1: "已打款"
-  };
-  return map[status] || String(status);
-};
-
-const getPaymentStatusType = (status: number): "info" | "success" | "danger" | "warning" => {
-  const map: Record<number, "warning" | "success"> = {
-    0: "warning",
-    1: "success"
-  };
-  return map[status] || "info";
-};
 </script>
 
 <template>
@@ -281,13 +243,13 @@ const getPaymentStatusType = (status: number): "info" | "success" | "danger" | "
               {{ formatMoney(row[col.prop]) }}
             </template>
             <template v-else-if="col.prop === 'status'">
-              <el-tag :type="getStatusType(row.status)" size="small">
-                {{ getStatusLabel(row.status) }}
+              <el-tag :type="getDictColor('WithdrawnRecord.status', row.status)" size="small">
+                {{ formatDict('WithdrawnRecord.status', row.status) }}
               </el-tag>
             </template>
             <template v-else-if="col.prop === 'paymentStatus'">
-              <el-tag :type="getPaymentStatusType(row.paymentStatus)" size="small">
-                {{ getPaymentStatusLabel(row.paymentStatus) }}
+              <el-tag :type="getDictColor('WithdrawnRecord.paymentStatus', row.paymentStatus)" size="small">
+                {{ formatDict('WithdrawnRecord.paymentStatus', row.paymentStatus) }}
               </el-tag>
             </template>
             <template v-else>

+ 11 - 41
admin-web-new/src/views/admin/message/index.vue

@@ -3,6 +3,7 @@ import { reactive, onMounted, ref, nextTick } from "vue";
 import { getMessageList, sendMessage, deleteMessage, batchReadMessage, batchDeleteMessage, searchAdminUser } from "@/api/message";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { ElMessage, ElMessageBox } from "element-plus";
+import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
 
 defineOptions({ name: "AdminMessage" });
 
@@ -44,36 +45,8 @@ const userOptions = ref<any[]>([]);
 const detailVisible = ref(false);
 const currentMsg = ref<any>(null);
 
-const typeOptions = [
-  { label: "系统通知", value: 1 },
-  { label: "站内信", value: 2 },
-  { label: "待办事项", value: 3 },
-  { label: "公告通知", value: 4 }
-];
-
-const priorityOptions = [
-  { label: "普通", value: 0 },
-  { label: "重要", value: 1 },
-  { label: "紧急", value: 2 }
-];
-
-const getTypeTag = (type: number) => {
-  const map: Record<number, string> = { 1: "", 2: "success", 3: "warning", 4: "info" };
-  return map[type] || "";
-};
-
-const getPriorityTag = (priority: number) => {
-  const map: Record<number, string> = { 0: "info", 1: "warning", 2: "danger" };
-  return map[priority] || "";
-};
-
-const getTypeName = (type: number) => {
-  return typeOptions.find(k => k.value === type)?.label || "";
-};
-
-const getPriorityName = (priority: number) => {
-  return priorityOptions.find(k => k.value === priority)?.label || "";
-};
+const typeOptions = () => getDictOptions("message_type");
+const priorityOptions = () => getDictOptions("priority");
 
 onMounted(() => {
   loadData();
@@ -251,13 +224,12 @@ const handleSendSubmit = async () => {
         </el-form-item>
         <el-form-item label="类型">
           <el-select v-model="state.formQuery.type" placeholder="请选择" clearable style="width: 140px">
-            <el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in typeOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="state.formQuery.status" placeholder="请选择" clearable style="width: 120px">
-            <el-option label="未读" :value="0" />
-            <el-option label="已读" :value="1" />
+            <el-option v-for="opt in getDictOptions('message_status')" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -290,21 +262,19 @@ const handleSendSubmit = async () => {
         <el-table-column label="标题" prop="title" min-width="200" show-overflow-tooltip />
         <el-table-column label="类型" prop="type" width="120">
           <template #default="{ row }">
-            <el-tag :type="getTypeTag(row.type)" size="small">{{ getTypeName(row.type) }}</el-tag>
+            <el-tag :type="getDictColor('message_type', row.type)" size="small">{{ formatDict('message_type', row.type) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="发送者" prop="senderName" width="100" />
         <el-table-column label="接收者" prop="receiverName" width="100" />
         <el-table-column label="优先级" prop="priority" width="100">
           <template #default="{ row }">
-            <el-tag :type="getPriorityTag(row.priority)" size="small">{{ getPriorityName(row.priority) }}</el-tag>
+            <el-tag :type="getDictColor('priority', row.priority)" size="small">{{ formatDict('priority', row.priority) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="状态" prop="status" width="80">
           <template #default="{ row }">
-            <el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
-              {{ row.status === 1 ? "已读" : "未读" }}
-            </el-tag>
+            <el-tag :type="getDictColor('message_status', row.status)" size="small">{{ formatDict('message_status', row.status) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="发送时间" prop="createTime" width="170" />
@@ -337,12 +307,12 @@ const handleSendSubmit = async () => {
         </el-form-item>
         <el-form-item label="类型">
           <el-select v-model="sendForm.type" style="width: 100%">
-            <el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in typeOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="优先级">
           <el-select v-model="sendForm.priority" style="width: 100%">
-            <el-option v-for="opt in priorityOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in priorityOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="发送方式">
@@ -382,7 +352,7 @@ const handleSendSubmit = async () => {
         <el-descriptions :column="2" border>
           <el-descriptions-item label="标题">{{ currentMsg.title }}</el-descriptions-item>
           <el-descriptions-item label="类型">
-            <el-tag :type="getTypeTag(currentMsg.type)" size="small">{{ getTypeName(currentMsg.type) }}</el-tag>
+            <el-tag :type="getDictColor('message_type', currentMsg.type)" size="small">{{ formatDict('message_type', currentMsg.type) }}</el-tag>
           </el-descriptions-item>
           <el-descriptions-item label="发送者">{{ currentMsg.senderName }}</el-descriptions-item>
           <el-descriptions-item label="发送时间">{{ currentMsg.createTime }}</el-descriptions-item>

+ 4 - 9
admin-web-new/src/views/admin/platform/device-config.vue

@@ -4,6 +4,7 @@ import { getDeviceConfigList, removeDeviceConfig } from "@/api/device-config";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { ElMessage, ElMessageBox } from "element-plus";
 import DeviceConfigDialog from "./device-config-dialog.vue";
+import { formatDict, getDictColor } from "@/utils/dict";
 
 defineOptions({
   name: "AdminPlatformDeviceConfig"
@@ -120,15 +121,9 @@ const handleDelete = (row: any) => {
   });
 };
 
-const getBooleanText = (val: string | boolean) => {
-  if (val === true || val === "true" || val === "1") return "是";
-  return "否";
-};
-
-const getBooleanType = (val: string | boolean) => {
-  if (val === true || val === "true" || val === "1") return "success";
-  return "danger";
-};
+const toBoolVal = (val: string | boolean) => (val === true || val === "true" || val === "1") ? 1 : 0;
+const getBooleanText = (val: string | boolean) => formatDict("yes_no", toBoolVal(val));
+const getBooleanType = (val: string | boolean) => getDictColor("yes_no", toBoolVal(val));
 
 const formatPrice = (val: number) => {
   if (!val) return "0";

+ 3 - 12
admin-web-new/src/views/admin/station/device-dialog.vue

@@ -125,22 +125,13 @@ defineExpose({ open });
         <el-input v-model="state.ruleForm.productKey" placeholder="请输入产品key" />
       </el-form-item>
       <el-form-item label="状态" prop="status">
-        <el-select v-model="state.ruleForm.status" placeholder="请选择状态">
-          <el-option label="离线" value="0" />
-          <el-option label="在线" value="1" />
-        </el-select>
+        <ext-d-select v-model="state.ruleForm.status" type="Device.state" placeholder="请选择状态" />
       </el-form-item>
       <el-form-item label="是否有泡沫" prop="hasFoam">
-        <el-select v-model="state.ruleForm.hasFoam" placeholder="请选择">
-          <el-option label="否" value="0" />
-          <el-option label="是" value="1" />
-        </el-select>
+        <ext-d-select v-model="state.ruleForm.hasFoam" type="YesNo" placeholder="请选择" />
       </el-form-item>
       <el-form-item label="是否有水" prop="hasWater">
-        <el-select v-model="state.ruleForm.hasWater" placeholder="请选择">
-          <el-option label="否" value="0" />
-          <el-option label="是" value="1" />
-        </el-select>
+        <ext-d-select v-model="state.ruleForm.hasWater" type="YesNo" placeholder="请选择" />
       </el-form-item>
       <el-form-item label="板载温度" prop="temperatureChip">
         <el-input v-model="state.ruleForm.temperatureChip" placeholder="板载温度传感器的温度值" />

+ 2 - 8
admin-web-new/src/views/admin/station/dialog.vue

@@ -130,16 +130,10 @@ defineExpose({ open });
         <el-input v-model="state.ruleForm.address" placeholder="请输入站点地址" />
       </el-form-item>
       <el-form-item label="站点状态" prop="stationStatus">
-        <el-select v-model="state.ruleForm.stationStatus" placeholder="请选择站点状态">
-          <el-option label="营业中" value="1" />
-          <el-option label="已关闭" value="0" />
-        </el-select>
+        <ext-d-select v-model="state.ruleForm.stationStatus" type="Station.status" placeholder="请选择站点状态" />
       </el-form-item>
       <el-form-item label="站点类型" prop="stationType">
-        <el-select v-model="state.ruleForm.stationType" placeholder="请选择站点类型">
-          <el-option label="公共" value="1" />
-          <el-option label="私人" value="2" />
-        </el-select>
+        <ext-d-select v-model="state.ruleForm.stationType" type="Station.type" placeholder="请选择站点类型" />
       </el-form-item>
       <el-form-item label="停车减免规则" prop="parkingFee">
         <el-input

+ 21 - 40
admin-web-new/src/views/admin/template/index.vue

@@ -4,6 +4,7 @@ import { getTemplateList, createTemplate, updateTemplate, deleteTemplate, sendBy
 import { searchAdminUser } from "@/api/message";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { ElMessage, ElMessageBox } from "element-plus";
+import { getDictOptions, formatDict, getDictColor } from "@/utils/dict";
 
 defineOptions({ name: "AdminMessageTemplate" });
 
@@ -58,36 +59,8 @@ const sendForm = reactive({
 });
 const userOptions = ref<any[]>([]);
 
-const typeOptions = [
-  { label: "系统通知", value: 1 },
-  { label: "站内信", value: 2 },
-  { label: "待办事项", value: 3 },
-  { label: "公告通知", value: 4 }
-];
-
-const priorityOptions = [
-  { label: "普通", value: 0 },
-  { label: "重要", value: 1 },
-  { label: "紧急", value: 2 }
-];
-
-const getTypeTag = (type: number) => {
-  const map: Record<number, string> = { 1: "", 2: "success", 3: "warning", 4: "info" };
-  return map[type] || "";
-};
-
-const getTypeName = (type: number) => {
-  return typeOptions.find(k => k.value === type)?.label || "";
-};
-
-const getPriorityTag = (priority: number) => {
-  const map: Record<number, string> = { 0: "info", 1: "warning", 2: "danger" };
-  return map[priority] || "";
-};
-
-const getPriorityName = (priority: number) => {
-  return priorityOptions.find(k => k.value === priority)?.label || "";
-};
+const typeOptions = () => getDictOptions("message_type");
+const priorityOptions = () => getDictOptions("priority");
 
 onMounted(() => {
   loadData();
@@ -232,13 +205,12 @@ const handleSendSubmit = async () => {
         </el-form-item>
         <el-form-item label="消息类型">
           <el-select v-model="state.formQuery.type" placeholder="请选择" clearable style="width: 130px">
-            <el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in typeOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="state.formQuery.status" placeholder="请选择" clearable style="width: 100px">
-            <el-option label="启用" :value="1" />
-            <el-option label="禁用" :value="0" />
+            <el-option v-for="opt in getDictOptions('user_status')" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -255,19 +227,19 @@ const handleSendSubmit = async () => {
         <el-table-column label="模板名称" prop="name" width="140" />
         <el-table-column label="消息类型" prop="type" width="110">
           <template #default="{ row }">
-            <el-tag :type="getTypeTag(row.type)" size="small">{{ getTypeName(row.type) }}</el-tag>
+            <el-tag :type="getDictColor('message_type', row.type)" size="small">{{ formatDict('message_type', row.type) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="标题模板" prop="titleTemplate" min-width="180" show-overflow-tooltip />
         <el-table-column label="内容模板" prop="contentTemplate" min-width="250" show-overflow-tooltip />
         <el-table-column label="优先级" prop="priority" width="90">
           <template #default="{ row }">
-            <el-tag :type="getPriorityTag(row.priority)" size="small">{{ getPriorityName(row.priority) }}</el-tag>
+            <el-tag :type="getDictColor('priority', row.priority)" size="small">{{ formatDict('priority', row.priority) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="状态" prop="status" width="80">
           <template #default="{ row }">
-            <el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">{{ row.status === 1 ? "启用" : "禁用" }}</el-tag>
+            <el-tag :type="getDictColor('user_status', row.status)" size="small">{{ formatDict('user_status', row.status) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="创建时间" prop="createTime" width="170" />
@@ -304,12 +276,12 @@ const handleSendSubmit = async () => {
         </el-form-item>
         <el-form-item label="消息类型">
           <el-select v-model="formData.type" style="width: 100%">
-            <el-option v-for="opt in typeOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in typeOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="优先级">
           <el-select v-model="formData.priority" style="width: 100%">
-            <el-option v-for="opt in priorityOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
+            <el-option v-for="opt in priorityOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
           </el-select>
         </el-form-item>
         <el-form-item label="标题模板" required>
@@ -320,8 +292,7 @@ const handleSendSubmit = async () => {
         </el-form-item>
         <el-form-item label="状态">
           <el-radio-group v-model="formData.status">
-            <el-radio :value="1">启用</el-radio>
-            <el-radio :value="0">禁用</el-radio>
+            <el-radio v-for="opt in getDictOptions('user_status')" :key="opt.value" :value="opt.value">{{ opt.label }}</el-radio>
           </el-radio-group>
         </el-form-item>
         <el-form-item label="备注">
@@ -343,6 +314,16 @@ const handleSendSubmit = async () => {
         <el-form-item label="内容" required>
           <el-input v-model="sendForm.content" type="textarea" :rows="5" placeholder="消息内容" />
         </el-form-item>
+        <el-form-item label="类型">
+          <el-select v-model="sendForm.type" style="width: 100%">
+            <el-option v-for="opt in typeOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="优先级">
+          <el-select v-model="sendForm.priority" style="width: 100%">
+            <el-option v-for="opt in priorityOptions()" :key="opt.value" :label="opt.label" :value="opt.value" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="发送方式">
           <el-radio-group v-model="sendForm.sendType">
             <el-radio value="all">全部用户</el-radio>

+ 7 - 2
admin-web-new/src/views/system/dept/index.vue

@@ -3,6 +3,7 @@ import { ref } from "vue";
 import { useDept } from "./utils/hook";
 import { PureTableBar } from "@/components/RePureTableBar";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { getDictOptions } from "@/utils/dict";
 
 import Delete from "~icons/ep/delete";
 import EditPen from "~icons/ep/edit-pen";
@@ -56,8 +57,12 @@ function onFullscreen() {
           clearable
           class="w-[180px]!"
         >
-          <el-option label="启用" :value="1" />
-          <el-option label="停用" :value="0" />
+          <el-option
+            v-for="opt in getDictOptions('user_status')"
+            :key="opt.value"
+            :label="opt.label"
+            :value="opt.value"
+          />
         </el-select>
       </el-form-item>
       <el-form-item>

+ 7 - 4
admin-web-new/src/views/system/dept/utils/hook.tsx

@@ -3,11 +3,11 @@ import editForm from "../form.vue";
 import { handleTree } from "@/utils/tree";
 import { message } from "@/utils/message";
 import { getDeptList } from "@/api/system";
-import { usePublicHooks } from "../../hooks";
 import { addDialog } from "@/components/ReDialog";
 import { reactive, ref, onMounted, h } from "vue";
 import type { FormItemProps } from "../utils/types";
 import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils";
+import { formatDict, getDictColor } from "@/utils/dict";
 
 export function useDept() {
   const form = reactive({
@@ -18,7 +18,6 @@ export function useDept() {
   const formRef = ref();
   const dataList = ref([]);
   const loading = ref(true);
-  const { tagStyle } = usePublicHooks();
 
   const columns: TableColumnList = [
     {
@@ -37,8 +36,12 @@ export function useDept() {
       prop: "status",
       minWidth: 100,
       cellRenderer: ({ row, props }) => (
-        <el-tag size={props.size} style={tagStyle.value(row.status)}>
-          {row.status === 1 ? "启用" : "停用"}
+        <el-tag
+          size={props.size}
+          type={getDictColor("user_status", row.status) || undefined}
+          effect="plain"
+        >
+          {formatDict("user_status", row.status)}
         </el-tag>
       )
     },

+ 4 - 12
admin-web-new/src/views/system/menu/utils/hook.tsx

@@ -8,6 +8,7 @@ import { reactive, ref, onMounted, h } from "vue";
 import type { FormItemProps } from "../utils/types";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils";
+import { formatDict, getDictColor } from "@/utils/dict";
 
 export function useMenu() {
   const form = reactive({
@@ -18,17 +19,8 @@ export function useMenu() {
   const dataList = ref([]);
   const loading = ref(true);
 
-  const getMenuType = (type, text = false) => {
-    switch (type) {
-      case 0:
-        return text ? "菜单" : "primary";
-      case 1:
-        return text ? "iframe" : "warning";
-      case 2:
-        return text ? "外链" : "danger";
-      case 3:
-        return text ? "按钮" : "info";
-    }
+  const getMenuType = (type: number, text = false) => {
+    return text ? formatDict("Menu.type", type) : getDictColor("Menu.type", type);
   };
 
   const columns: TableColumnList = [
@@ -83,7 +75,7 @@ export function useMenu() {
     {
       label: "隐藏",
       prop: "showLink",
-      formatter: ({ showLink }) => (showLink ? "否" : "是"),
+      formatter: ({ showLink }) => formatDict("yes_no", showLink ? 0 : 1),
       width: 100
     },
     {

+ 7 - 2
admin-web-new/src/views/system/role/index.vue

@@ -3,6 +3,7 @@ import { useRole } from "./utils/hook";
 import { ref, computed, nextTick, onMounted } from "vue";
 import { PureTableBar } from "@/components/RePureTableBar";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { getDictOptions } from "@/utils/dict";
 import {
   delay,
   subBefore,
@@ -121,8 +122,12 @@ onMounted(() => {
           clearable
           class="w-[180px]!"
         >
-          <el-option label="已启用" value="1" />
-          <el-option label="已停用" value="0" />
+          <el-option
+            v-for="opt in getDictOptions('user_status')"
+            :key="opt.value"
+            :label="opt.label"
+            :value="opt.value"
+          />
         </el-select>
       </el-form-item>
       <el-form-item>

+ 5 - 4
admin-web-new/src/views/system/role/utils/hook.tsx

@@ -11,6 +11,7 @@ import type { PaginationProps } from "@pureadmin/table";
 import { getKeyList, deviceDetection } from "@pureadmin/utils";
 import { getRoleList, getRoleMenu, getRoleMenuIds } from "@/api/system";
 import { type Ref, reactive, ref, onMounted, h, toRaw, watch } from "vue";
+import { formatDict } from "@/utils/dict";
 
 export function useRole(treeRef: Ref) {
   const form = reactive({
@@ -64,8 +65,8 @@ export function useRole(treeRef: Ref) {
           v-model={scope.row.status}
           active-value={1}
           inactive-value={0}
-          active-text="已启用"
-          inactive-text="已停用"
+          active-text={formatDict("user_status", 1)}
+          inactive-text={formatDict("user_status", 0)}
           inline-prompt
           style={switchStyle.value}
           onChange={() => onChange(scope as any)}
@@ -105,7 +106,7 @@ export function useRole(treeRef: Ref) {
   function onChange({ row, index }) {
     ElMessageBox.confirm(
       `确认要<strong>${
-        row.status === 0 ? "停用" : "启用"
+        formatDict("user_status", row.status)
       }</strong><strong style='color:var(--el-color-primary)'>${
         row.name
       }</strong>吗?`,
@@ -134,7 +135,7 @@ export function useRole(treeRef: Ref) {
               loading: false
             }
           );
-          message(`已${row.status === 0 ? "停用" : "启用"}${row.name}`, {
+          message(`已${formatDict("user_status", row.status)}${row.name}`, {
             type: "success"
           });
         }, 300);

+ 5 - 14
admin-web-new/src/views/system/user/form/index.vue

@@ -4,6 +4,7 @@ import ReCol from "@/components/ReCol";
 import { formRules } from "../utils/rule";
 import { FormProps } from "../utils/types";
 import { usePublicHooks } from "../../hooks";
+import { getDictOptions, formatDict } from "@/utils/dict";
 
 const props = withDefaults(defineProps<FormProps>(), {
   formInline: () => ({
@@ -21,16 +22,6 @@ const props = withDefaults(defineProps<FormProps>(), {
   })
 });
 
-const sexOptions = [
-  {
-    value: 0,
-    label: "男"
-  },
-  {
-    value: 1,
-    label: "女"
-  }
-];
 const ruleFormRef = ref();
 const { switchStyle } = usePublicHooks();
 const newFormInline = ref(props.formInline);
@@ -111,8 +102,8 @@ defineExpose({ getRef });
             clearable
           >
             <el-option
-              v-for="(item, index) in sexOptions"
-              :key="index"
+              v-for="item in getDictOptions('AdminUser.sex')"
+              :key="item.value"
               :label="item.label"
               :value="item.value"
             />
@@ -155,8 +146,8 @@ defineExpose({ getRef });
             inline-prompt
             :active-value="1"
             :inactive-value="0"
-            active-text="启用"
-            inactive-text="停用"
+            :active-text="formatDict('user_status', 1)"
+            :inactive-text="formatDict('user_status', 0)"
             :style="switchStyle"
           />
         </el-form-item>

+ 7 - 2
admin-web-new/src/views/system/user/index.vue

@@ -4,6 +4,7 @@ import tree from "./tree.vue";
 import { useUser } from "./utils/hook";
 import { PureTableBar } from "@/components/RePureTableBar";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
+import { getDictOptions } from "@/utils/dict";
 
 import Upload from "~icons/ri/upload-line";
 import Role from "~icons/ri/admin-line";
@@ -91,8 +92,12 @@ const {
             clearable
             class="w-[180px]!"
           >
-            <el-option label="已开启" value="1" />
-            <el-option label="已关闭" value="0" />
+            <el-option
+              v-for="opt in getDictOptions('user_status')"
+              :key="opt.value"
+              :label="opt.label"
+              :value="opt.value"
+            />
           </el-select>
         </el-form-item>
         <el-form-item>

+ 6 - 5
admin-web-new/src/views/system/user/utils/hook.tsx

@@ -40,6 +40,7 @@ import {
   reactive,
   onMounted
 } from "vue";
+import { formatDict, getDictColor } from "@/utils/dict";
 
 export function useUser(tableRef: Ref, treeRef: Ref) {
   const form = reactive({
@@ -110,10 +111,10 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
       cellRenderer: ({ row, props }) => (
         <el-tag
           size={props.size}
-          type={row.sex === 1 ? "danger" : null}
+          type={getDictColor("AdminUser.sex", row.sex) || null}
           effect="plain"
         >
-          {row.sex === 1 ? "女" : "男"}
+          {formatDict("AdminUser.sex", row.sex)}
         </el-tag>
       )
     },
@@ -139,8 +140,8 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
           v-model={scope.row.status}
           active-value={1}
           inactive-value={0}
-          active-text="已启用"
-          inactive-text="已停用"
+          active-text={formatDict("user_status", 1)}
+          inactive-text={formatDict("user_status", 0)}
           inline-prompt
           style={switchStyle.value}
           onChange={() => onChange(scope as any)}
@@ -188,7 +189,7 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
   function onChange({ row, index }) {
     ElMessageBox.confirm(
       `确认要<strong>${
-        row.status === 0 ? "停用" : "启用"
+        formatDict("user_status", row.status)
       }</strong><strong style='color:var(--el-color-primary)'>${
         row.username
       }</strong>用户吗?`,

+ 8 - 4
admin-web/src/components/form/ExtBoolean.vue

@@ -23,7 +23,7 @@
 </style>
 <template>
   <div>
-    <span v-if="disabled" class="text" :class="{'true-text':props.modelValue==true}">{{ props.modelValue ? '是' : '否' }}</span>
+    <span v-if="disabled" class="text" :class="{'true-text':props.modelValue==true}">{{ props.modelValue ? trueLabel : falseLabel }}</span>
     <el-select v-else
                transfer
                :multiple="false"
@@ -32,13 +32,14 @@
                style="width: 100%"
                :placeholder="placeholder"
                v-model="state.model">
-      <el-option label="是" :value="true"><el-text type="success"></el-text></el-option>
-      <el-option label="否" :value="false"><el-text type="danger"></el-text></el-option>
+      <el-option label="是" :value="true"><el-text type="success">{{ trueLabel }}</el-text></el-option>
+      <el-option label="否" :value="false"><el-text type="danger">{{ falseLabel }}</el-text></el-option>
     </el-select>
   </div>
 </template>
 <script setup lang="ts" name="ExtBoolean">
-import {reactive, onMounted, watch} from 'vue';
+import {reactive, onMounted, watch, computed} from 'vue';
+import u from '/@/utils/u';
 //数据字典的布尔值类型的下拉选择组件
 
 const props = defineProps({
@@ -62,6 +63,9 @@ const state = reactive({
   model: false,
 })
 
+const trueLabel = computed(() => u.fmt.fmtDict('1', 'yes_no'));
+const falseLabel = computed(() => u.fmt.fmtDict('0', 'yes_no'));
+
 onMounted(() => {
   state.model = props.modelValue;
 })

+ 1 - 6
admin-web/src/components/form/ExtDLabel.vue

@@ -119,12 +119,7 @@ const state = reactive({
   style: {color: '#000000'},
   color:'#fff',
   colorList:["#FFB800","#009688","#1E9FFF","#00C7D2","#599CDE","#FF5722","#eb2f96","#4a5055"],
-  dicts:{
-    'User.status':[
-        {label:'有效',value:1},
-        {label:'无效',value:0},
-    ]
-  }
+  dicts:{} as any
 })
 const setupColorStyle = (hex: string = "#000000") => {
   let hexToRgb = u.hexToRgb(hex);

+ 1 - 6
admin-web/src/components/form/ExtDSelect.vue

@@ -68,12 +68,7 @@ const state = reactive({
   dicts: [] as Array<any>,
   dataVal: null as any,
   colorList: ["#FFB800", "#009688", "#1E9FFF", "#00C7D2", "#599CDE", "#FF5722", "#eb2f96", "#4a5055"],
-  dictList: {
-    'User.status': [
-      {label: '有效', value: 1},
-      {label: '无效', value: 0},
-    ]
-  }
+  dictList: {} as any
 })
 
 const emit = defineEmits(['update:modelValue', 'on-change']);

+ 6 - 11
admin-web/src/layout/navBars/breadcrumb/notificationCenter.vue

@@ -159,8 +159,9 @@
 <script setup lang="ts" name="NotificationCenter">
 import { reactive, computed, onMounted, onUnmounted } from 'vue';
 import { storeToRefs } from 'pinia';
-import { useMessageStore, MessageTypeLabel, PriorityLabel, MessageItem } from '/@/stores/message';
+import { useMessageStore, getMessageTypeLabel, getPriorityLabel as getStorePriorityLabel, getPriorityColor as getStorePriorityColor, MessageItem } from '/@/stores/message';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import u from '/@/utils/u';
 
 const messageStore = useMessageStore();
 const { unreadCount, unreadCountByType, messageList, pagination, loading } = storeToRefs(messageStore);
@@ -290,18 +291,12 @@ const formatTime = (time: string) => {
 };
 
 // 获取类型标签
-const getTypeLabel = (type: number) => MessageTypeLabel[type] || '未知';
-const getTypeColor = (type: number) => {
-    const colors: Record<number, string> = { 1: 'primary', 2: 'success', 3: 'warning', 4: 'info' };
-    return colors[type] || '';
-};
+const getTypeLabel = (type: number) => getMessageTypeLabel(type) || '未知';
+const getTypeColor = (type: number) => u.fmt.fmtDictColor(type, 'message_type');
 
 // 获取优先级
-const getPriorityLabel = (priority: number) => PriorityLabel[priority] || '';
-const getPriorityType = (priority: number) => {
-    const types: Record<number, string> = { 1: 'warning', 2: 'danger' };
-    return types[priority] || '';
-};
+const getPriorityLabel = (priority: number) => getStorePriorityLabel(priority) || '';
+const getPriorityType = (priority: number) => getStorePriorityColor(priority) || '';
 
 // 是否已初始化
 let initialized = false;

+ 16 - 17
admin-web/src/stores/message.ts

@@ -27,28 +27,27 @@ export const MessageType = {
     NOTICE: 4       // 公告通知
 };
 
-// 消息类型标签
-export const MessageTypeLabel: Record<number, string> = {
-    1: '系统通知',
-    2: '站内信',
-    3: '待办事项',
-    4: '公告通知'
-};
+// 消息类型标签(从字典读取)
+import Session from '/@/utils/session';
 
-// 优先级标签
-export const PriorityLabel: Record<number, string> = {
-    0: '普通',
-    1: '重要',
-    2: '紧急'
+const getDictLabel = (code: string, value: number): string => {
+    const dicts = Session.get("dicts");
+    if (!dicts) return '';
+    const item = dicts[code]?.find((k: any) => k.value == value);
+    return item?.name || '';
 };
 
-// 优先级颜色
-export const PriorityColor: Record<number, string> = {
-    0: '',
-    1: 'warning',
-    2: 'danger'
+const getDictColor = (code: string, value: number): string => {
+    const dicts = Session.get("dicts");
+    if (!dicts) return '';
+    const item = dicts[code]?.find((k: any) => k.value == value);
+    return item?.color || '';
 };
 
+export const getMessageTypeLabel = (type: number) => getDictLabel('message_type', type);
+export const getPriorityLabel = (priority: number) => getDictLabel('priority', priority);
+export const getPriorityColor = (priority: number) => getDictColor('priority', priority);
+
 // 是否使用Mock数据
 const useMock = true;
 

+ 15 - 0
admin-web/src/utils/u.ts

@@ -705,6 +705,21 @@ const u = {
             }
             return dict.name;
         },
+        fmtDictColor(v: any, type: string) {
+            if (v == null) {
+                return "";
+            }
+
+            const dicts = Session.get("dicts");
+            if (u.isEmptyOrNull(dicts)) {
+                return '';
+            }
+            let dict = dicts[type].find(k =>  k.value == v);
+            if (u.isEmptyOrNull(dict)) {
+                return "";
+            }
+            return dict.color || "";
+        },
     }
 }
 

+ 2 - 17
admin-web/src/views/admin/finance/settlement.vue

@@ -198,21 +198,6 @@ const handleTriggerSettlement = () => {
   })
 };
 
-const getStatusLabel = (status: number) => {
-  const map: Record<number, string> = {
-    0: '待结算',
-    1: '已结算',
-    2: '异常结算'
-  };
-  return map[status] || String(status);
-};
-
-const getStatusType = (status: number): any => {
-  const map: Record<number, string> = {
-    0: 'info',
-    1: 'success',
-    2: 'danger'
-  };
-  return map[status] || 'info';
-};
+const getStatusLabel = (status: number) => u.fmt.fmtDict(status, 'Settlement.status');
+const getStatusType = (status: number): any => u.fmt.fmtDictColor(status, 'Settlement.status');
 </script>

+ 5 - 21
admin-web/src/views/admin/log/opt/index.vue

@@ -10,15 +10,7 @@
                     <el-input v-model="queryParams.module" placeholder="请输入" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item label="操作类型">
-                    <el-select v-model="queryParams.operationType" placeholder="请选择" clearable style="width: 150px">
-                        <el-option label="新增" value="CREATE" />
-                        <el-option label="修改" value="UPDATE" />
-                        <el-option label="删除" value="DELETE" />
-                        <el-option label="查询" value="QUERY" />
-                        <el-option label="登录" value="LOGIN" />
-                        <el-option label="登出" value="LOGOUT" />
-                        <el-option label="其他" value="OTHER" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.operationType" type="OptLog.operationType" placeholder="请选择" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item label="操作时间">
                     <el-date-picker
@@ -139,6 +131,7 @@
 import { ref, reactive, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { $get, $body } from '/@/utils/request';
+import u from '/@/utils/u';
 
 // 查询参数
 const queryParams = reactive({
@@ -171,20 +164,11 @@ const mockData = [
 ];
 
 // 操作类型标签
-const getOperationTypeLabel = (type: string) => {
-    const map: Record<string, string> = { 'CREATE': '新增', 'UPDATE': '修改', 'DELETE': '删除', 'QUERY': '查询', 'LOGIN': '登录', 'LOGOUT': '登出', 'OTHER': '其他' };
-    return map[type] || type;
-};
-const getOperationTypeColor = (type: string) => {
-    const map: Record<string, string> = { 'CREATE': 'success', 'UPDATE': 'warning', 'DELETE': 'danger', 'QUERY': 'info', 'LOGIN': 'primary', 'LOGOUT': '', 'OTHER': '' };
-    return map[type] || '';
-};
+const getOperationTypeLabel = (type: string) => u.fmt.fmtDict(type, 'OptLog.operationType');
+const getOperationTypeColor = (type: string) => u.fmt.fmtDictColor(type, 'OptLog.operationType');
 
 // 请求方式颜色
-const getMethodColor = (method: string) => {
-    const map: Record<string, string> = { 'GET': 'success', 'POST': 'primary', 'PUT': 'warning', 'DELETE': 'danger' };
-    return map[method] || '';
-};
+const getMethodColor = (method: string) => u.fmt.fmtDictColor(method, 'OptLog.httpMethod');
 
 // 格式化 JSON
 const formatJson = (str: string) => {

+ 12 - 40
admin-web/src/views/admin/message/index.vue

@@ -7,18 +7,10 @@
                     <el-input v-model="queryParams.title" placeholder="请输入标题" clearable style="width: 200px" />
                 </el-form-item>
                 <el-form-item label="类型">
-                    <el-select v-model="queryParams.type" placeholder="请选择" clearable style="width: 150px">
-                        <el-option label="系统通知" :value="1" />
-                        <el-option label="站内信" :value="2" />
-                        <el-option label="待办事项" :value="3" />
-                        <el-option label="公告通知" :value="4" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.type" type="message_type" placeholder="请选择" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item label="状态">
-                    <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 150px">
-                        <el-option label="未读" :value="0" />
-                        <el-option label="已读" :value="1" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.status" type="message_status" placeholder="请选择" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item>
                     <el-button type="primary" @click="handleQuery">
@@ -67,13 +59,13 @@
                         <el-tag v-if="row.priority > 0" :type="getPriorityColor(row.priority)">
                             {{ getPriorityLabel(row.priority) }}
                         </el-tag>
-                        <span v-else>普通</span>
+                        <span v-else>{{ u.fmt.fmtDict(row.priority, 'priority') }}</span>
                     </template>
                 </el-table-column>
                 <el-table-column prop="status" label="状态" width="80" align="center">
                     <template #default="{ row }">
-                        <el-tag :type="row.status === 0 ? 'danger' : 'success'">
-                            {{ row.status === 0 ? '未读' : '已读' }}
+                        <el-tag :type="u.fmt.fmtDictColor(row.status, 'message_status')">
+                            {{ u.fmt.fmtDict(row.status, 'message_status') }}
                         </el-tag>
                     </template>
                 </el-table-column>
@@ -109,21 +101,12 @@
                 <el-row :gutter="20">
                     <el-col :span="12">
                         <el-form-item label="消息类型" prop="type">
-                            <el-select v-model="sendForm.type" placeholder="请选择" style="width: 100%">
-                                <el-option label="系统通知" :value="1" />
-                                <el-option label="站内信" :value="2" />
-                                <el-option label="待办事项" :value="3" />
-                                <el-option label="公告通知" :value="4" />
-                            </el-select>
+                            <ext-d-select v-model="sendForm.type" type="message_type" placeholder="请选择" style="width: 100%" />
                         </el-form-item>
                     </el-col>
                     <el-col :span="12">
                         <el-form-item label="优先级" prop="priority">
-                            <el-select v-model="sendForm.priority" placeholder="请选择" style="width: 100%">
-                                <el-option label="普通" :value="0" />
-                                <el-option label="重要" :value="1" />
-                                <el-option label="紧急" :value="2" />
-                            </el-select>
+                            <ext-d-select v-model="sendForm.priority" type="priority" placeholder="请选择" style="width: 100%" />
                         </el-form-item>
                     </el-col>
                 </el-row>
@@ -199,6 +182,7 @@
 import { ref, reactive, onMounted } from 'vue';
 import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { $get, $body } from '/@/utils/request';
+import u from '/@/utils/u';
 
 // 查询参数
 const queryParams = reactive({
@@ -266,24 +250,12 @@ const mockData = [
 ];
 
 // 类型标签
-const getTypeLabel = (type: number) => {
-    const map: Record<number, string> = { 1: '系统通知', 2: '站内信', 3: '待办事项', 4: '公告通知' };
-    return map[type] || '未知';
-};
-const getTypeColor = (type: number) => {
-    const map: Record<number, string> = { 1: 'primary', 2: 'success', 3: 'warning', 4: 'info' };
-    return map[type] || '';
-};
+const getTypeLabel = (type: number) => u.fmt.fmtDict(type, 'message_type');
+const getTypeColor = (type: number) => u.fmt.fmtDictColor(type, 'message_type');
 
 // 优先级
-const getPriorityLabel = (priority: number) => {
-    const map: Record<number, string> = { 1: '重要', 2: '紧急' };
-    return map[priority] || '';
-};
-const getPriorityColor = (priority: number) => {
-    const map: Record<number, string> = { 1: 'warning', 2: 'danger' };
-    return map[priority] || '';
-};
+const getPriorityLabel = (priority: number) => u.fmt.fmtDict(priority, 'priority');
+const getPriorityColor = (priority: number) => u.fmt.fmtDictColor(priority, 'priority');
 
 // 查询数据
 const handleQuery = async () => {

+ 4 - 15
admin-web/src/views/admin/notice/index.vue

@@ -7,12 +7,7 @@
                     <el-input v-model="queryParams.title" placeholder="请输入标题" clearable style="width: 200px" />
                 </el-form-item>
                 <el-form-item label="状态">
-                    <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 150px">
-                        <el-option label="未开始" :value="0" />
-                        <el-option label="生效中" :value="1" />
-                        <el-option label="已结束" :value="2" />
-                        <el-option label="已取消" :value="3" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.status" type="notice_status" placeholder="请选择" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item>
                     <el-button type="primary" @click="handleQuery">
@@ -114,6 +109,7 @@
 import { ref, reactive, onMounted } from 'vue';
 import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { $get, $body } from '/@/utils/request';
+import u from '/@/utils/u';
 
 // 查询参数
 const queryParams = reactive({
@@ -154,15 +150,8 @@ const mockData = [
 ];
 
 // 获取状态标签
-const getStatusLabel = (status: number) => {
-    const map: Record<number, string> = { 0: '未开始', 1: '生效中', 2: '已结束', 3: '已取消' };
-    return map[status] || '未知';
-};
-
-const getStatusType = (status: number) => {
-    const map: Record<number, string> = { 0: 'info', 1: 'success', 2: '', 3: 'danger' };
-    return map[status] || '';
-};
+const getStatusLabel = (status: number) => u.fmt.fmtDict(status, 'notice_status');
+const getStatusType = (status: number) => u.fmt.fmtDictColor(status, 'notice_status');
 
 // 查询数据
 const handleQuery = async () => {

+ 13 - 44
admin-web/src/views/admin/template/index.vue

@@ -10,18 +10,10 @@
                     <el-input v-model="queryParams.code" placeholder="请输入" clearable style="width: 150px" />
                 </el-form-item>
                 <el-form-item label="消息类型">
-                    <el-select v-model="queryParams.type" placeholder="请选择" clearable style="width: 130px">
-                        <el-option label="系统通知" :value="1" />
-                        <el-option label="站内信" :value="2" />
-                        <el-option label="待办事项" :value="3" />
-                        <el-option label="公告通知" :value="4" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.type" type="message_type" placeholder="请选择" clearable style="width: 130px" />
                 </el-form-item>
                 <el-form-item label="状态">
-                    <el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 100px">
-                        <el-option label="启用" :value="1" />
-                        <el-option label="禁用" :value="0" />
-                    </el-select>
+                    <ext-d-select v-model="queryParams.status" type="user_status" placeholder="请选择" clearable style="width: 100px" />
                 </el-form-item>
                 <el-form-item>
                     <el-button type="primary" @click="handleQuery">
@@ -62,13 +54,13 @@
                         <el-tag v-if="row.priority > 0" :type="getPriorityColor(row.priority)" size="small">
                             {{ getPriorityLabel(row.priority) }}
                         </el-tag>
-                        <span v-else>普通</span>
+                        <span v-else>{{ u.fmt.fmtDict(row.priority, 'priority') }}</span>
                     </template>
                 </el-table-column>
                 <el-table-column prop="status" label="状态" width="80" align="center">
                     <template #default="{ row }">
-                        <el-tag :type="row.status === 1 ? 'success' : 'info'">
-                            {{ row.status === 1 ? '启用' : '禁用' }}
+                        <el-tag :type="u.fmt.fmtDictColor(row.status, 'user_status')">
+                            {{ u.fmt.fmtDict(row.status, 'user_status') }}
                         </el-tag>
                     </template>
                 </el-table-column>
@@ -114,21 +106,12 @@
                 <el-row :gutter="20">
                     <el-col :span="12">
                         <el-form-item label="消息类型" prop="type">
-                            <el-select v-model="formData.type" placeholder="请选择" style="width: 100%">
-                                <el-option label="系统通知" :value="1" />
-                                <el-option label="站内信" :value="2" />
-                                <el-option label="待办事项" :value="3" />
-                                <el-option label="公告通知" :value="4" />
-                            </el-select>
+                            <ext-d-select v-model="formData.type" type="message_type" placeholder="请选择" style="width: 100%" />
                         </el-form-item>
                     </el-col>
                     <el-col :span="12">
                         <el-form-item label="优先级" prop="priority">
-                            <el-select v-model="formData.priority" placeholder="请选择" style="width: 100%">
-                                <el-option label="普通" :value="0" />
-                                <el-option label="重要" :value="1" />
-                                <el-option label="紧急" :value="2" />
-                            </el-select>
+                            <ext-d-select v-model="formData.priority" type="priority" placeholder="请选择" style="width: 100%" />
                         </el-form-item>
                     </el-col>
                 </el-row>
@@ -141,10 +124,7 @@
                 <el-row :gutter="20">
                     <el-col :span="12">
                         <el-form-item label="状态" prop="status">
-                            <el-radio-group v-model="formData.status">
-                                <el-radio :value="1">启用</el-radio>
-                                <el-radio :value="0">禁用</el-radio>
-                            </el-radio-group>
+                            <ext-d-radio v-model="formData.status" type="user_status" />
                         </el-form-item>
                     </el-col>
                 </el-row>
@@ -215,6 +195,7 @@
 import { ref, reactive, onMounted } from 'vue';
 import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
 import { $get, $body } from '/@/utils/request';
+import u from '/@/utils/u';
 
 // 查询参数
 const queryParams = reactive({
@@ -303,24 +284,12 @@ const mockUsers = [
 ];
 
 // 类型标签
-const getTypeLabel = (type: number) => {
-    const map: Record<number, string> = { 1: '系统通知', 2: '站内信', 3: '待办事项', 4: '公告通知' };
-    return map[type] || '未知';
-};
-const getTypeColor = (type: number) => {
-    const map: Record<number, string> = { 1: 'primary', 2: 'success', 3: 'warning', 4: 'info' };
-    return map[type] || '';
-};
+const getTypeLabel = (type: number) => u.fmt.fmtDict(type, 'message_type');
+const getTypeColor = (type: number) => u.fmt.fmtDictColor(type, 'message_type');
 
 // 优先级
-const getPriorityLabel = (priority: number) => {
-    const map: Record<number, string> = { 1: '重要', 2: '紧急' };
-    return map[priority] || '';
-};
-const getPriorityColor = (priority: number) => {
-    const map: Record<number, string> = { 1: 'warning', 2: 'danger' };
-    return map[priority] || '';
-};
+const getPriorityLabel = (priority: number) => u.fmt.fmtDict(priority, 'priority');
+const getPriorityColor = (priority: number) => u.fmt.fmtDictColor(priority, 'priority');
 
 // 查询数据
 const handleQuery = async () => {

+ 74 - 1
car-wash-entity/src/main/resources/sql/init.sql

@@ -346,7 +346,80 @@ INSERT INTO `t_data_dict` (`code`, `name`, `value`, `weight`, `remark`, `color`)
 ('notice_status', '已取消', '3', 4, '公告状态', 'danger'),
 -- 是否
 ('yes_no', '否', '0', 1, '是否', 'info'),
-('yes_no', '是', '1', 2, '是否', 'success');
+('yes_no', '是', '1', 2, '是否', 'success'),
+-- 结算状态
+('Settlement.status', '待结算', '0', 1, '结算状态', 'info'),
+('Settlement.status', '已结算', '1', 2, '结算状态', 'success'),
+('Settlement.status', '异常结算', '2', 3, '结算状态', 'danger'),
+-- 操作日志 - 操作类型
+('OptLog.operationType', '新增', 'CREATE', 1, '操作类型', 'success'),
+('OptLog.operationType', '修改', 'UPDATE', 2, '操作类型', 'warning'),
+('OptLog.operationType', '删除', 'DELETE', 3, '操作类型', 'danger'),
+('OptLog.operationType', '查询', 'QUERY', 4, '操作类型', 'info'),
+('OptLog.operationType', '登录', 'LOGIN', 5, '操作类型', 'primary'),
+('OptLog.operationType', '登出', 'LOGOUT', 6, '操作类型', ''),
+('OptLog.operationType', '其他', 'OTHER', 7, '操作类型', ''),
+-- 操作日志 - 请求方式
+('OptLog.httpMethod', 'GET', 'GET', 1, '请求方式', 'success'),
+('OptLog.httpMethod', 'POST', 'POST', 2, '请求方式', 'primary'),
+('OptLog.httpMethod', 'PUT', 'PUT', 3, '请求方式', 'warning'),
+('OptLog.httpMethod', 'DELETE', 'DELETE', 4, '请求方式', 'danger'),
+-- 菜单类型
+('Menu.type', '菜单', '0', 1, '菜单类型', 'primary'),
+('Menu.type', 'iframe', '1', 2, '菜单类型', 'warning'),
+('Menu.type', '外链', '2', 3, '菜单类型', 'danger'),
+('Menu.type', '按钮', '3', 4, '菜单类型', 'info'),
+-- 用户性别
+('AdminUser.sex', '男', '0', 1, '用户性别', ''),
+('AdminUser.sex', '女', '1', 2, '用户性别', ''),
+-- 钱包明细类型
+('WalletDetail.type', '积分', '1', 1, '钱包明细类型', ''),
+('WalletDetail.type', '红包', '2', 2, '钱包明细类型', ''),
+('WalletDetail.type', '消费', '3', 3, '钱包明细类型', ''),
+-- 设备状态 (WashDevice.state)
+('WashDevice.status', '设备正在初始化', 'init', 1, '设备状态', '#52C41A'),
+('WashDevice.status', '设备空闲', 'idle', 2, '设备状态', '#52C41A'),
+('WashDevice.status', '设备忙碌', 'busy', 3, '设备状态', '#52C41A'),
+('WashDevice.status', '不在营业时间', 'sleep', 4, '设备状态', '#999999'),
+('WashDevice.status', '维护模式', 'maintenance', 5, '设备状态', '#FAAD14'),
+('WashDevice.status', '设备故障', 'fault', 6, '设备状态', '#F5222D'),
+-- 提现记录状态 (WithdrawnRecord.status)
+('WithdrawnRecord.status', '待审核', '0', 1, '提现状态', '#FA8C16'),
+('WithdrawnRecord.status', '审核通过', '1', 2, '提现状态', '#52C41A'),
+('WithdrawnRecord.status', '审核失败', '2', 3, '提现状态', '#FF4D4F'),
+-- 分账记录类型 (SplitRecord.type)
+('SplitRecord.type', '平台技术服务费', '0', 1, '分账类型', ''),
+('SplitRecord.type', '充值', '1', 2, '分账类型', ''),
+('SplitRecord.type', '消费', '2', 3, '分账类型', ''),
+('SplitRecord.type', '解冻', '3', 4, '分账类型', ''),
+('SplitRecord.type', '跨店支出', '4', 5, '分账类型', ''),
+('SplitRecord.type', '退款', '5', 6, '分账类型', ''),
+('SplitRecord.type', '跨店收入', '6', 7, '分账类型', ''),
+-- 分账记录状态 (SplitRecord.status)
+('SplitRecord.status', '处理中', '0', 1, '分账状态', '#FAAD14'),
+('SplitRecord.status', '成功', '1', 2, '分账状态', '#52C41A'),
+('SplitRecord.status', '失败', '2', 3, '分账状态', '#F5222D'),
+-- 订单状态 (WashOrder.orderStatus)
+('WashOrder.orderStatus', '未知', '-1', 1, '订单状态', '#999999'),
+('WashOrder.orderStatus', '开机', '0', 2, '订单状态', '#1890FF'),
+('WashOrder.orderStatus', '成功', '1', 3, '订单状态', '#52C41A'),
+('WashOrder.orderStatus', '失败', '2', 4, '订单状态', '#F5222D'),
+('WashOrder.orderStatus', '取消', '3', 5, '订单状态', '#999999'),
+-- 支付状态 (WashOrder.payStatus)
+('WashOrder.payStatus', '未支付', '0', 1, '支付状态', '#FAAD14'),
+('WashOrder.payStatus', '已支付', '1', 2, '支付状态', '#52C41A'),
+-- 关机方式 (WashOrder.closeType)
+('WashOrder.closeType', '维护按钮', 'button', 1, '关机方式', ''),
+('WashOrder.closeType', '网络命令', 'network', 2, '关机方式', ''),
+('WashOrder.closeType', '超过预设金额', 'no_balance', 3, '关机方式', ''),
+('WashOrder.closeType', '设备空闲超时', 'idle_timeout', 4, '关机方式', ''),
+('WashOrder.closeType', '操作超时', 'operation_timeout', 5, '关机方式', ''),
+('WashOrder.closeType', '刷卡', 'card', 6, '关机方式', ''),
+-- 发票状态 (WashOrder.invoiceStatus)
+('WashOrder.invoiceStatus', '待开票', '0', 1, '发票状态', '#FAAD14'),
+('WashOrder.invoiceStatus', '已开票', '1', 2, '发票状态', '#52C41A'),
+('WashOrder.invoiceStatus', '已作废', '2', 3, '发票状态', '#999999'),
+('WashOrder.invoiceStatus', '开票中', '3', 4, '发票状态', '#1890FF');
 
 -- ----------------------------
 -- 初始化消息(示例数据)

+ 269 - 0
docs/数据字典设计文档.md

@@ -0,0 +1,269 @@
+# 数据字典设计文档
+
+## 一、设计背景
+
+前端开发中,大量存在"字段值 → 展示值 → 颜色标签"的映射需求。例如表格中状态列的 `0/1` 需要显示为"禁用/启用",下拉选择器需要选项列表,标签需要根据值显示不同颜色。
+
+在没有字典机制时,这些映射以硬编码形式散落在各个页面的函数、map 对象、模板三元表达式中,带来以下问题:
+
+- **重复定义**:同一个 `user_status`(0=禁用, 1=启用)在多个页面中重复硬编码
+- **不一致风险**:不同页面可能定义不同的展示文本(如"禁用"/"停用"/"无效")
+- **修改成本高**:新增一个状态或修改展示文本需要改动多处代码并重新发布
+- **魔法值泛滥**:代码中到处是 `=== 0 ? '禁用' : '启用'`,可读性和可维护性差
+
+字典功能的设计初衷是:**将值转换映射统一收口到数据库,前端组件自动读取并渲染,消除硬编码**。
+
+## 二、数据模型
+
+### 2.1 数据库表结构
+
+表名:`t_data_dict`
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `id` | BIGINT | 主键 |
+| `company_id` | BIGINT | 租户ID(多租户隔离) |
+| `code` | VARCHAR(50) | 字典编码,作为分组标识 |
+| `name` | VARCHAR(100) | 展示名称 |
+| `value` | VARCHAR(200) | 字典值 |
+| `weight` | INT | 排序权重,控制前端展示顺序 |
+| `color` | VARCHAR(20) | 颜色标记(如 success/danger/warning/info/primary) |
+| `remark` | VARCHAR(255) | 备注说明 |
+| `create_time` | DATETIME | 创建时间 |
+| `update_time` | DATETIME | 更新时间 |
+
+### 2.2 设计特点
+
+**无字典类型表**:不设独立的"字典类型"实体。`code` 字段即类型标识,共享同一 `code` 的多条记录组成一个字典分组。这种扁平化设计降低了查询复杂度。
+
+**code 命名约定**:字典 code 采用 `类名.属性名` 格式(如 `Order.status`、`WashStation.type`),与业务实体类属性直接对应,前端开发者可直观地从字段名推断对应的字典 code。
+
+### 2.3 唯一性约束
+
+同一 `code` 分组内,`name` 和 `value` 均不可重复。后端 `DataDictServiceImpl.saveOrUpdate` 方法在写入前执行校验:
+
+```
+字典[code]取值异常,请核对
+```
+
+## 三、架构设计
+
+### 3.1 整体架构
+
+```
+┌─────────────────────────────────────────────────┐
+│                    前端                          │
+│  ExtDSelect / ExtDLabel / dictUtil              │
+│         ↓ 读取                                   │
+│  sessionStorage / uni.Storage (key: "dicts")    │
+│         ↑ 登录/启动时一次性拉取                   │
+├─────────────────────────────────────────────────┤
+│                    后端                           │
+│  DataDictController                              │
+│         ↓                                        │
+│  DataDictService                                │
+│         ↓                                        │
+│  t_data_dict (MySQL)                            │
+└─────────────────────────────────────────────────┘
+```
+
+### 3.2 缓存策略
+
+| 层级 | 缓存方式 | 生命周期 | 说明 |
+|---|---|---|---|
+| 后端 | 无缓存 | — | 每次请求直接查库,字典变更低频,无缓存失效问题 |
+| 前端 | sessionStorage / uni.Storage | 登录会话 | 登录后调用 `POST /dataDict/list (pageSize=1024)` 全量拉取,按 `code` 分组存储 |
+
+**为什么后端不缓存**:字典数据变更频率极低(设计理念为"定义后不可修改")。前端 sessionStorage 缓存的生命周期恰好是一个登录会话。免去了 Redis 同步/失效的运维复杂度。
+
+**为什么全量拉取**:字典数据量小(通常 50-200 条),一次请求全量获取后,后续所有字典组件均为纯内存操作,零网络延迟。
+
+### 3.3 后端 API
+
+| 方法 | 路径 | 认证 | 说明 |
+|---|---|---|---|
+| POST | `/dataDict/list` | 管理端需登录 | 按 code/name 模糊查询,前端以 pageSize=1024 拉取全量 |
+| POST | `/dataDict/saveOrUpdate` | 需 `dict.add/edit` 权限 | 批量保存/更新,含唯一性校验 |
+| POST | `/dict/list` | 无需认证 | 小程序端公开接口 |
+
+## 四、前端实现
+
+### 4.1 admin-web-new (Vue 3)
+
+**工具模块**:`src/utils/dict.ts`
+
+| 函数 | 说明 |
+|---|---|
+| `dictUtil.loadDicts()` | 从服务端拉取全量字典并缓存到 sessionStorage |
+| `dictUtil.getDicts()` | 获取完整的 `{ [code]: DictItem[] }` 结构 |
+| `dictUtil.getDictList(code)` | 获取指定 code 下的字典条目列表 |
+| `dictUtil.getDictLabel(code, value)` | 根据 code 和 value 获取展示名称(输出:`"--"` / label) |
+| `dictUtil.getDictValue(code, name)` | 根据 code 和 name 反查值 |
+| `getDictOptions(code)` | 返回 `[{label, value}]` 格式,用于 el-select 的 options |
+| `formatDict(code, value)` | `getDictLabel` 的便捷导出 |
+| `getDictColor(code, value)` | 根据 code 和 value 获取颜色标记 |
+
+**字典组件**:
+
+| 组件 | 路径 | 用法 |
+|---|---|---|
+| `ExtDSelect` | `components/ExtForm/ExtDSelect.vue` | `<ext-d-select type="Order.status" v-model="..." />` |
+| `ExtDLabel` | `components/ExtForm/ExtDLabel.vue` | `<ext-d-label type="Order.status" :value="row.status" />` |
+
+### 4.2 admin-web (Vue 2)
+
+**工具模块**:`src/utils/u.ts`
+
+| 函数 | 说明 |
+|---|---|
+| `u.fmt.fmtDict(value, code)` | 根据 code 和 value 获取展示名称(输出:`"--"` / label) |
+| `u.fmt.fmtDictColor(value, code)` | 根据 code 和 value 获取颜色标记 |
+
+原始数据存储在 `Session.get("dicts")`(与 admin-web-new 相同的结构)。
+
+**字典组件**:
+
+| 组件 | 路径 | 用法 |
+|---|---|---|
+| `ExtDSelect` | `components/form/ExtDSelect.vue` | `<ext-d-select type="Order.status" v-model="..." />` |
+| `ExtDLabel` | `components/form/ExtDLabel.vue` | `<ext-d-label type="Order.status" :value="row.status" />` |
+| `ExtDRadio` | `components/form/ExtDRadio.vue` | `<ext-d-radio type="user_status" v-model="..." />` |
+| `ExtBoolean` | `components/form/ExtBoolean.vue` | 布尔值选择器,使用 `yes_no` 字典 |
+
+表格列和查询表单列在配置中指定 `type: 'dict'` + `conf: {dict: 'xxx'}` 即可自动渲染为对应字典组件。
+
+### 4.3 car-wash-mp (UniApp 小程序)
+
+**工具函数**:`src/utils/common.ts`
+
+| 函数 | 说明 |
+|---|---|
+| `fmtDictName(code, value)` | 根据 code 和 value 获取展示名称 |
+| `getServicePhone()` | 从 `Service.phone` 字典获取客服电话 |
+
+字典数据在 `App.vue` 的 `onLaunch` 中通过 `POST /dict/list`(无需认证)全量拉取,使用 `uni.setStorage({key: 'dict'})` 缓存。
+
+## 五、字典条目清单
+
+### 5.1 种子数据(init.sql)
+
+| code | 条目 |
+|---|---|
+| `user_status` | 0=禁用(danger), 1=启用(success) |
+| `message_type` | 1=系统通知(primary), 2=站内信(success), 3=待办事项(warning), 4=公告通知(info) |
+| `message_status` | 0=未读(danger), 1=已读(success), 2=已删除(info) |
+| `priority` | 0=普通, 1=重要(warning), 2=紧急(danger) |
+| `notice_status` | 0=未开始(info), 1=生效中(success), 2=已结束, 3=已取消(danger) |
+| `yes_no` | 0=否(info), 1=是(success) |
+| `Settlement.status` | 0=待结算(info), 1=已结算(success), 2=异常结算(danger) |
+| `OptLog.operationType` | CREATE=新增(success), UPDATE=修改(warning), DELETE=删除(danger), QUERY=查询(info), LOGIN=登录(primary), LOGOUT=登出, OTHER=其他 |
+| `OptLog.httpMethod` | GET(success), POST(primary), PUT(warning), DELETE(danger) |
+| `Menu.type` | 0=菜单(primary), 1=iframe(warning), 2=外链(danger), 3=按钮(info) |
+| `AdminUser.sex` | 0=男, 1=女 |
+| `WalletDetail.type` | 1=积分, 2=红包, 3=消费 |
+
+### 5.2 业务字典(通过管理界面维护,部分)
+
+| code | 业务含义 |
+|---|---|
+| `AdminUser.status` | 管理员用户状态 |
+| `Order.status` | 订单状态 |
+| `Order.pay` | 支付状态 |
+| `Order.openType` | 开单方式 |
+| `Order.closeType` | 关单方式 |
+| `Order.feeType` | 费用类型 |
+| `OrderCard.type` | 订单卡类型 |
+| `WashStation.status` | 站点运营状态 |
+| `WashStation.type` | 站点类型 |
+| `WashDevice.status` | 设备状态 |
+| `WashDevice.foam` | 泡沫能力 |
+| `WashDevice.water` | 水能力 |
+| `Activity.discountType` | 活动优惠类型 |
+| `Banner.status` | 横幅状态 |
+| `Investor.status` | 投资人状态 |
+| `Department.status` | 部门状态 |
+| `Invoice.status` | 发票状态 |
+| `Feedback.type` | 反馈类型 |
+| `SplitRecord.type` | 分账类型 |
+| `SplitRecord.status` | 分账状态 |
+| `WithdrawnRecord.status` | 提现审核状态 |
+| `WithdrawnRecord.paymentStatus` | 提现打款状态 |
+| `RefundLog.status` | 退款状态 |
+| `RefundLog.fundsAccount` | 退款资金账户 |
+| `Faq.status` | 常见问题状态 |
+| `Object.type` | 通用对象类型 |
+| `Service.phone` | 客服电话 |
+
+## 六、设计理念与约束
+
+### 核心原则
+
+> 字典以常量形式定义在业务实体类中,业务逻辑以常量替代,取消魔法值
+
+### 运营约束
+
+- 字典**只提供查询接口**,修改和新增由数据库 DML 执行实现
+- 字典定义后**不可修改 code**,不可删除,可废弃
+- 为保持与前端及关联系统一致,字典条目需全量维护
+
+### 适用场景
+
+字典机制适合以下场景:
+- 状态/类型等有限枚举值的展示
+- 需要下拉选择的枚举值列表
+- 带颜色标记的状态标签
+
+不适合的场景:
+- 动态变化频繁的数据
+- 数据量巨大的列表(字典全量缓存在前端,条目过多影响性能)
+
+## 七、最佳实践
+
+### 前端开发
+
+```typescript
+// admin-web-new: 表格列中使用 dict 函数
+<el-table-column label="状态">
+  <template #default="{ row }">
+    <el-tag :type="getDictColor('Order.status', row.status)" size="small">
+      {{ formatDict('Order.status', row.status) }}
+    </el-tag>
+  </template>
+</el-table-column>
+
+// admin-web-new: 查询表单中使用字典下拉
+<el-form-item label="状态">
+  <el-select v-model="query.status">
+    <el-option v-for="opt in getDictOptions('Order.status')"
+      :key="opt.value" :label="opt.label" :value="opt.value" />
+  </el-select>
+</el-form-item>
+
+// admin-web (Vue 2): 使用字典组件
+<ext-d-select type="Order.status" v-model="query.status" />
+<ext-d-label type="Order.status" :value="row.status" />
+
+// admin-web (Vue 2): 使用工具函数
+const label = u.fmt.fmtDict(value, 'Order.status');
+const color = u.fmt.fmtDictColor(value, 'Order.status');
+```
+
+### 禁止的写法
+
+```javascript
+// 禁止:硬编码 map 对象
+const statusMap = { 0: '禁用', 1: '启用' };
+
+// 禁止:模板内三元表达式
+{{ row.status === 1 ? '启用' : '禁用' }}
+
+// 禁止:硬编码下拉选项
+<el-option label="启用" :value="1" />
+<el-option label="禁用" :value="0" />
+```
+
+### 新增字典步骤
+
+1. 执行 SQL 插入字典数据到 `t_data_dict` 表
+2. 前端更新后会自动加载新字典
+3. 在页面中使用字典工具函数或组件引用新的字典 code