heshaofeng
2025-12-26 32898366256ac7a99c4ecdd094e05e54f8a7e621
Merge branch 'htq20251215' of http://115.159.85.185:8098/r/ZhongRui/ALDbanyunxiangmu into htq20251215
已添加18个文件
已修改13个文件
5789 ■■■■■ 文件已修改
项目代码/Dashboard/.gitignore 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/API使用指南.md 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/README.md 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/index.html 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/package-lock.json 945 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/package.json 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/App.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/api/index.js 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/examples/ApiExample.vue 618 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/main.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/router/index.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/utils/http.js 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/utils/index.js 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/views/Dashboard.vue 729 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/views/Inventory.vue 831 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/views/Production.vue 837 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/src/views/Warehouse.vue 582 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/Dashboard/vite.config.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/OrderStockTake.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/printView.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/stock/stockView.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/printForm.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/CodeChunks.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/SemanticSymbols.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/StockStatusEmun.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Jobs/InventoryLockJob.cs 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Jobs/QuartzJobMildd.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/Dashboard/.gitignore
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
ÏîÄ¿´úÂë/Dashboard/APIʹÓÃÖ¸ÄÏ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,400 @@
# API è°ƒç”¨æŽ¥å£ä½¿ç”¨æŒ‡å—
## å¿«é€Ÿå¼€å§‹
### 1. åŸºç¡€ HTTP è°ƒç”¨
使用 `http` å·¥å…·ç›´æŽ¥è°ƒç”¨æŽ¥å£ï¼š
```javascript
import { http } from '@/utils/http'
// GET è¯·æ±‚
const getUsers = async () => {
  const res = await http.get('/api/user/list', { page: 1, pageSize: 10 })
  return res.data
}
// POST è¯·æ±‚
const createUser = async () => {
  const res = await http.post('/api/user/create', {
    name: '张三',
    age: 25
  })
  return res.data
}
// PUT è¯·æ±‚
const updateUser = async (id) => {
  const res = await http.put(`/api/user/${id}`, {
    name: '李四'
  })
  return res.data
}
// DELETE è¯·æ±‚
const deleteUser = async (id) => {
  const res = await http.delete(`/api/user/${id}`)
  return res.data
}
```
### 2. ä½¿ç”¨å°è£…çš„ API æ¨¡å—
所有接口已统一封装在 `/src/api/index.js` ä¸­ï¼š
```javascript
import { warehouseApi, inventoryApi, taskApi } from '@/api'
// èŽ·å–ä»“åº“çŠ¶æ€
const warehouse = await warehouseApi.getWarehouseStatus()
// èŽ·å–åº“å­˜åˆ—è¡¨
const inventory = await inventoryApi.getInventoryList({ page: 1 })
// èŽ·å–ä»»åŠ¡åˆ—è¡¨
const tasks = await taskApi.getTaskList({ status: '进行中' })
// åˆ›å»ºä»»åŠ¡
const newTask = await taskApi.createTask({
  taskNo: 'RK001',
  type: '入库',
  material: '钢板',
  quantity: 100
})
```
## åœ¨ Vue ç»„件中使用
### ç¤ºä¾‹1: ç®€å•数据获取
```vue
<template>
  <div>
    <el-button @click="fetchData" :loading="loading">获取数据</el-button>
    <div v-if="data">{{ data }}</div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import { http } from '@/utils/http'
const loading = ref(false)
const data = ref(null)
const fetchData = async () => {
  loading.value = true
  try {
    const res = await http.get('/api/data')
    data.value = res.data
  } catch (error) {
    console.error('请求失败:', error)
  } finally {
    loading.value = false
  }
}
</script>
```
### ç¤ºä¾‹2: ä½¿ç”¨ API æ¨¡å—
```vue
<template>
  <div>
    <el-table :data="warehouseList" v-loading="loading">
      <el-table-column prop="name" label="仓库名称" />
      <el-table-column prop="capacity" label="容量" />
    </el-table>
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { warehouseApi } from '@/api'
const loading = ref(false)
const warehouseList = ref([])
const fetchWarehouse = async () => {
  loading.value = true
  try {
    const res = await warehouseApi.getAreas()
    warehouseList.value = res.data
  } catch (error) {
    ElMessage.error('获取仓库列表失败')
  } finally {
    loading.value = false
  }
}
onMounted(() => {
  fetchWarehouse()
})
</script>
```
### ç¤ºä¾‹3: è¡¨å•提交
```vue
<template>
  <el-form @submit.prevent="handleSubmit">
    <el-form-item label="任务编号">
      <el-input v-model="form.taskNo" />
    </el-form-item>
    <el-form-item label="任务类型">
      <el-select v-model="form.type">
        <el-option label="入库" value="入库" />
        <el-option label="出库" value="出库" />
      </el-select>
    </el-form-item>
    <el-button type="primary" native-type="submit" :loading="submitting">
      æäº¤
    </el-button>
  </el-form>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { taskApi } from '@/api'
const submitting = ref(false)
const form = ref({
  taskNo: '',
  type: '入库'
})
const handleSubmit = async () => {
  submitting.value = true
  try {
    const res = await taskApi.createTask(form.value)
    ElMessage.success('创建成功')
    // å¤„理返回数据...
  } catch (error) {
    ElMessage.error('创建失败')
  } finally {
    submitting.value = false
  }
}
</script>
```
### ç¤ºä¾‹4: å¹¶å‘请求
```vue
<script setup>
import { ref, onMounted } from 'vue'
import { warehouseApi, inventoryApi, taskApi } from '@/api'
const dashboardData = ref({
  warehouse: null,
  inventory: null,
  tasks: null
})
const fetchDashboardData = async () => {
  try {
    // åŒæ—¶å‘起多个请求,等待全部完成
    const [warehouse, inventory, tasks] = await Promise.all([
      warehouseApi.getWarehouseStatus(),
      inventoryApi.getInventoryOverview(),
      taskApi.getTaskList({ status: '进行中' })
    ])
    dashboardData.value = {
      warehouse: warehouse.data,
      inventory: inventory.data,
      tasks: tasks.data
    }
  } catch (error) {
    console.error('获取看板数据失败:', error)
  }
}
onMounted(() => {
  fetchDashboardData()
})
</script>
```
## API æ¨¡å—完整列表
### warehouseApi - ä»“库相关
```javascript
// èŽ·å–ä»“åº“çŠ¶æ€æ¦‚è§ˆ
warehouseApi.getWarehouseStatus()
// èŽ·å–ä»“åº“åŒºåŸŸä¿¡æ¯
warehouseApi.getAreas()
// èŽ·å–åº“ä½ä¿¡æ¯
warehouseApi.getLocations(params)
// èŽ·å–æ¸©æ¹¿åº¦æ•°æ®
warehouseApi.getEnvData()
```
### inventoryApi - åº“存相关
```javascript
// èŽ·å–åº“å­˜æ¦‚è§ˆ
inventoryApi.getInventoryOverview()
// èŽ·å–åº“å­˜åˆ—è¡¨
inventoryApi.getInventoryList(params)
// åº“存预警列表
inventoryApi.getLowStockAlerts(params)
// åº“存积压列表
inventoryApi.getOverStockAlerts(params)
// å³å°†è¿‡æœŸç‰©æ–™
inventoryApi.getExpiringItems(params)
// åº“存周转率
inventoryApi.getTurnoverRate(params)
// ABC分类统计
inventoryApi.getABCStats()
```
### taskApi - ä»»åŠ¡ç›¸å…³
```javascript
// èŽ·å–ä»»åŠ¡åˆ—è¡¨
taskApi.getTaskList(params)
// åˆ›å»ºä»»åŠ¡
taskApi.createTask(data)
// æ›´æ–°ä»»åŠ¡çŠ¶æ€
taskApi.updateTaskStatus(taskId, data)
// èŽ·å–ä»»åŠ¡è¯¦æƒ…
taskApi.getTaskDetail(taskId)
```
### ioApi - å‡ºå…¥åº“相关
```javascript
// å…¥åº“单列表
ioApi.getInboundList(params)
// å‡ºåº“单列表
ioApi.getOutboundList(params)
// åˆ›å»ºå…¥åº“单
ioApi.createInbound(data)
// åˆ›å»ºå‡ºåº“单
ioApi.createOutbound(data)
// å‡ºå…¥åº“统计
ioApi.getIOStats(params)
```
### productionApi - ç”Ÿäº§ç›¸å…³
```javascript
// èŽ·å–ç”Ÿäº§çº¿çŠ¶æ€
productionApi.getProductionLines()
// èŽ·å–è®¾å¤‡çŠ¶æ€
productionApi.getDeviceStatus(lineId)
// èŽ·å–ç”Ÿäº§ä»»åŠ¡åˆ—è¡¨
productionApi.getProductionTasks(params)
// äº§é‡ç»Ÿè®¡
productionApi.getProductionStats(params)
// è‰¯å“çŽ‡ç»Ÿè®¡
productionApi.getQualityStats(params)
// è®¾å¤‡OEE
productionApi.getDeviceOEE(params)
```
### reportApi - æŠ¥è¡¨ç›¸å…³
```javascript
// ç»¼åˆçœ‹æ¿æ•°æ®
reportApi.getDashboardData()
// å‡ºå…¥åº“趋势
reportApi.getTrendData(params)
// åˆ†ç±»å æ¯”
reportApi.getCategoryStats()
// ä½œä¸šæ•ˆçŽ‡ç»Ÿè®¡
reportApi.getEfficiencyStats(params)
```
## é”™è¯¯å¤„理
http å·¥å…·å·²ç»å†…置了错误处理:
```javascript
import { http } from '@/utils/http'
try {
  const res = await http.get('/api/data')
  // res.data æ˜¯åŽç«¯è¿”回的数据
} catch (error) {
  // é”™è¯¯å·²ç»è¢«æ‹¦æˆªå™¨å¤„理并显示提示
  // è¿™é‡Œå¯ä»¥åšé¢å¤–的错误处理
  console.error('自定义错误处理:', error)
}
```
## è¯·æ±‚/响应拦截器
请求拦截器会自动添加 baseURL å’Œ headers:
```javascript
// åœ¨ src/utils/http.js ä¸­é…ç½®
const request = axios.create({
  baseURL: '/api',  // ä¼šè‡ªåŠ¨æ·»åŠ åˆ°æ‰€æœ‰è¯·æ±‚å‰
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})
```
响应拦截器会自动处理错误:
```javascript
// ç»Ÿä¸€é”™è¯¯æç¤º
ElMessage.error(res.message || '请求失败')
// ç»Ÿä¸€è¿”回格式
// å‡è®¾åŽç«¯è¿”回: { code: 200, data: {}, message: '' }
// æ‹¦æˆªå™¨ä¼šè¿”回 res.data
```
## ä»£ç†é…ç½®
在 `vite.config.js` ä¸­é…ç½®ä»£ç†ï¼š
```javascript
server: {
  port: 3000,
  proxy: {
    '/api': {
      target: 'http://localhost:8080',  // ä¿®æ”¹ä¸ºå®žé™…后端地址
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, '')
    }
  }
}
```
## è®¿é—®ç¤ºä¾‹é¡µé¢
启动项目后,访问 http://localhost:3000/api-example æŸ¥çœ‹å®Œæ•´çš„API调用示例和交互演示。
ÏîÄ¿´úÂë/Dashboard/README.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
# WMS看板系统
制造业WMS仓库管理系统监控看板,使用Vue3+Vite构建,无需登录即可访问。
## æŠ€æœ¯æ ˆ
- Vue 3
- Vite
- Vue Router
- Element Plus
- ECharts
- Axios
- Day.js
## é¡¹ç›®ç»“æž„
```
vue-vite-js/
├── src/
│   â”œâ”€â”€ api/              # API接口定义
│   â”œâ”€â”€ router/           # è·¯ç”±é…ç½®
│   â”œâ”€â”€ utils/            # å·¥å…·ç±»
│   â”‚   â”œâ”€â”€ http.js       # Axios封装
│   â”‚   â””── index.js      # é€šç”¨å·¥å…·å‡½æ•°
│   â”œâ”€â”€ views/            # é¡µé¢ç»„ä»¶
│   â”‚   â”œâ”€â”€ Dashboard.vue # ç»¼åˆçœ‹æ¿
│   â”‚   â”œâ”€â”€ Warehouse.vue # ä»“库监控
│   â”‚   â”œâ”€â”€ Production.vue# ç”Ÿäº§ç›‘控
│   â”‚   â””── Inventory.vue # åº“存预警
│   â”œâ”€â”€ App.vue           # æ ¹ç»„ä»¶
│   â””── main.js           # å…¥å£æ–‡ä»¶
├── index.html
├── vite.config.js
└── package.json
```
## å¿«é€Ÿå¼€å§‹
### 1. å®‰è£…依赖
```bash
npm install
```
### 2. å¯åŠ¨å¼€å‘æœåŠ¡å™¨
```bash
npm run dev
```
访问 http://localhost:3000
### 3. æž„建生产版本
```bash
npm run build
```
## æŽ¥å£è°ƒç”¨ç¤ºä¾‹
项目中已封装好 `http` å·¥å…·ï¼Œä½¿ç”¨éžå¸¸ç®€å•:
```javascript
import { http } from '@/utils/http'
// GET è¯·æ±‚
const getData = async () => {
  const res = await http.get('/api/list', { page: 1, size: 10 })
  console.log(res.data)
}
// POST è¯·æ±‚
const postData = async () => {
  const res = await http.post('/api/create', { name: '测试' })
  console.log(res.data)
}
// PUT è¯·æ±‚
const updateData = async () => {
  const res = await http.put('/api/update/1', { name: '更新' })
}
// DELETE è¯·æ±‚
const deleteData = async () => {
  const res = await http.delete('/api/delete/1')
}
```
## API ç»Ÿä¸€ç®¡ç†
在 `src/api/index.js` ä¸­å·²ç»å®šä¹‰å¥½æ‰€æœ‰ä¸šåŠ¡æŽ¥å£ï¼š
```javascript
import { warehouseApi, inventoryApi, taskApi } from '@/api'
// ä½¿ç”¨ç¤ºä¾‹
const fetchData = async () => {
  // èŽ·å–ä»“åº“çŠ¶æ€
  const status = await warehouseApi.getWarehouseStatus()
  // èŽ·å–åº“å­˜åˆ—è¡¨
  const inventory = await inventoryApi.getInventoryList({ page: 1 })
  // èŽ·å–ä»»åŠ¡åˆ—è¡¨
  const tasks = await taskApi.getTaskList({ status: '进行中' })
}
```
## ä»£ç†é…ç½®
在 `vite.config.js` ä¸­å·²é…ç½®API代理:
```javascript
server: {
  port: 3000,
  proxy: {
    '/api': {
      target: 'http://localhost:8080',  // ä¿®æ”¹ä¸ºå®žé™…后端地址
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, '')
    }
  }
}
```
## å·¥å…·å‡½æ•°
```javascript
import { formatDateTime, formatDate, formatTime, formatNumber } from '@/utils'
// æ ¼å¼åŒ–日期时间
formatDateTime(new Date())  // '2024-12-24 14:30:00'
// æ ¼å¼åŒ–日期
formatDate(new Date())      // '2024-12-24'
// æ ¼å¼åŒ–æ•°å­—
formatNumber(12345.67, 2)   // '12,345.67'
```
## é¡µé¢è¯´æ˜Ž
- **综合看板** - WMS整体运营数据概览
- **仓库监控** - ä»“库区域、库位、环境温湿度监控
- **生产监控** - ç”Ÿäº§çº¿ã€è®¾å¤‡çŠ¶æ€ã€äº§é‡ç»Ÿè®¡
- **库存预警** - åº“存不足、积压、过期预警
## å“åº”式支持
项目已实现响应式设计,支持以下分辨率:
| åˆ†è¾¨çŽ‡èŒƒå›´ | é€‚配说明 |
|-----------|---------|
| 1920px+ | æœ€ä½³æ˜¾ç¤ºæ•ˆæžœï¼Œ4列布局 |
| 1600px | 2-3列布局,字体和间距适中 |
| 1366px | 2列布局为主,图表高度调整 |
| 1024px | ç´§å‡‘布局,部分内容改为单列 |
| 768px | ç§»åŠ¨ç«¯é€‚é…ï¼Œå•åˆ—å¸ƒå±€ï¼Œä¼˜åŒ–è§¦æ‘¸æ“ä½œ |
**主要适配内容:**
- ç½‘格布局自适应调整
- å­—体大小动态缩放
- é—´è·å’Œå†…边距优化
- å¯¼èˆªèœå•在小屏幕下自动换行
- è¡¨æ ¼åœ¨ç§»åŠ¨ç«¯æ˜¾ç¤ºä¼˜åŒ–
- å›¾è¡¨é«˜åº¦è‡ªåŠ¨è°ƒæ•´
## æ³¨æ„äº‹é¡¹
1. ä¿®æ”¹ `vite.config.js` ä¸­çš„ `target` ä¸ºå®žé™…的后端API地址
2. æ ¹æ®åŽç«¯è¿”回格式调整 `src/utils/http.js` ä¸­çš„响应拦截器
3. æ‰€æœ‰é¡µé¢éƒ½å¯ç›´æŽ¥è®¿é—®ï¼Œæ— éœ€ç™»å½•认证
ÏîÄ¿´úÂë/Dashboard/index.html
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WMS看板系统</title>
</head>
<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>
</html>
ÏîÄ¿´úÂë/Dashboard/package-lock.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,945 @@
{
  "name": "wms-dashboard",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "@babel/helper-string-parser": {
      "version": "7.27.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
      "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
    },
    "@babel/helper-validator-identifier": {
      "version": "7.28.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
    },
    "@babel/parser": {
      "version": "7.28.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@babel/parser/-/parser-7.28.5.tgz",
      "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
      "requires": {
        "@babel/types": "^7.28.5"
      }
    },
    "@babel/types": {
      "version": "7.28.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@babel/types/-/types-7.28.5.tgz",
      "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
      "requires": {
        "@babel/helper-string-parser": "^7.27.1",
        "@babel/helper-validator-identifier": "^7.28.5"
      }
    },
    "@ctrl/tinycolor": {
      "version": "3.6.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
      "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA=="
    },
    "@element-plus/icons-vue": {
      "version": "2.3.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
      "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A=="
    },
    "@esbuild/aix-ppc64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
      "dev": true,
      "optional": true
    },
    "@esbuild/android-arm": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/android-arm64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
      "dev": true,
      "optional": true
    },
    "@esbuild/android-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
      "dev": true,
      "optional": true
    },
    "@esbuild/darwin-arm64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
      "dev": true,
      "optional": true
    },
    "@esbuild/darwin-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
      "dev": true,
      "optional": true
    },
    "@esbuild/freebsd-arm64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
      "dev": true,
      "optional": true
    },
    "@esbuild/freebsd-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-arm": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-arm64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-ia32": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-loong64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-mips64el": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-ppc64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-riscv64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-s390x": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
      "dev": true,
      "optional": true
    },
    "@esbuild/linux-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
      "dev": true,
      "optional": true
    },
    "@esbuild/netbsd-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/openbsd-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
      "dev": true,
      "optional": true
    },
    "@esbuild/sunos-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
      "dev": true,
      "optional": true
    },
    "@esbuild/win32-arm64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
      "dev": true,
      "optional": true
    },
    "@esbuild/win32-ia32": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
      "dev": true,
      "optional": true
    },
    "@esbuild/win32-x64": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
      "dev": true,
      "optional": true
    },
    "@floating-ui/core": {
      "version": "1.7.3",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/core/-/core-1.7.3.tgz",
      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
      "requires": {
        "@floating-ui/utils": "^0.2.10"
      }
    },
    "@floating-ui/dom": {
      "version": "1.7.4",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/dom/-/dom-1.7.4.tgz",
      "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
      "requires": {
        "@floating-ui/core": "^1.7.3",
        "@floating-ui/utils": "^0.2.10"
      }
    },
    "@floating-ui/utils": {
      "version": "0.2.10",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@floating-ui/utils/-/utils-0.2.10.tgz",
      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="
    },
    "@jridgewell/sourcemap-codec": {
      "version": "1.5.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
      "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
    },
    "@popperjs/core": {
      "version": "npm:@sxzz/popperjs-es@2.11.7",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
      "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
    },
    "@rollup/rollup-android-arm-eabi": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
      "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-android-arm64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
      "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-darwin-arm64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
      "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-darwin-x64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
      "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-freebsd-arm64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
      "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-freebsd-x64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
      "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-arm-gnueabihf": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
      "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-arm-musleabihf": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
      "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-arm64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
      "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-arm64-musl": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
      "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-loong64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
      "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-ppc64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
      "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-riscv64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
      "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-riscv64-musl": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
      "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-s390x-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
      "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-x64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
      "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-linux-x64-musl": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
      "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-openharmony-arm64": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
      "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-win32-arm64-msvc": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
      "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-win32-ia32-msvc": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
      "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-win32-x64-gnu": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
      "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
      "dev": true,
      "optional": true
    },
    "@rollup/rollup-win32-x64-msvc": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
      "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
      "dev": true,
      "optional": true
    },
    "@types/estree": {
      "version": "1.0.8",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/estree/-/estree-1.0.8.tgz",
      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
      "dev": true
    },
    "@types/lodash": {
      "version": "4.17.21",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ=="
    },
    "@types/lodash-es": {
      "version": "4.17.12",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/lodash-es/-/lodash-es-4.17.12.tgz",
      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
      "requires": {
        "@types/lodash": "*"
      }
    },
    "@types/web-bluetooth": {
      "version": "0.0.20",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
      "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
    },
    "@vitejs/plugin-vue": {
      "version": "5.2.4",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
      "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
      "dev": true
    },
    "@vue/compiler-core": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
      "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
      "requires": {
        "@babel/parser": "^7.28.5",
        "@vue/shared": "3.5.26",
        "entities": "^7.0.0",
        "estree-walker": "^2.0.2",
        "source-map-js": "^1.2.1"
      }
    },
    "@vue/compiler-dom": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
      "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
      "requires": {
        "@vue/compiler-core": "3.5.26",
        "@vue/shared": "3.5.26"
      }
    },
    "@vue/compiler-sfc": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
      "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
      "requires": {
        "@babel/parser": "^7.28.5",
        "@vue/compiler-core": "3.5.26",
        "@vue/compiler-dom": "3.5.26",
        "@vue/compiler-ssr": "3.5.26",
        "@vue/shared": "3.5.26",
        "estree-walker": "^2.0.2",
        "magic-string": "^0.30.21",
        "postcss": "^8.5.6",
        "source-map-js": "^1.2.1"
      }
    },
    "@vue/compiler-ssr": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
      "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
      "requires": {
        "@vue/compiler-dom": "3.5.26",
        "@vue/shared": "3.5.26"
      }
    },
    "@vue/devtools-api": {
      "version": "6.6.4",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
    },
    "@vue/reactivity": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/reactivity/-/reactivity-3.5.26.tgz",
      "integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==",
      "requires": {
        "@vue/shared": "3.5.26"
      }
    },
    "@vue/runtime-core": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/runtime-core/-/runtime-core-3.5.26.tgz",
      "integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==",
      "requires": {
        "@vue/reactivity": "3.5.26",
        "@vue/shared": "3.5.26"
      }
    },
    "@vue/runtime-dom": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz",
      "integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==",
      "requires": {
        "@vue/reactivity": "3.5.26",
        "@vue/runtime-core": "3.5.26",
        "@vue/shared": "3.5.26",
        "csstype": "^3.2.3"
      }
    },
    "@vue/server-renderer": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/server-renderer/-/server-renderer-3.5.26.tgz",
      "integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==",
      "requires": {
        "@vue/compiler-ssr": "3.5.26",
        "@vue/shared": "3.5.26"
      }
    },
    "@vue/shared": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vue/shared/-/shared-3.5.26.tgz",
      "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="
    },
    "@vueuse/core": {
      "version": "10.11.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vueuse/core/-/core-10.11.1.tgz",
      "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
      "requires": {
        "@types/web-bluetooth": "^0.0.20",
        "@vueuse/metadata": "10.11.1",
        "@vueuse/shared": "10.11.1",
        "vue-demi": ">=0.14.8"
      }
    },
    "@vueuse/metadata": {
      "version": "10.11.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vueuse/metadata/-/metadata-10.11.1.tgz",
      "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="
    },
    "@vueuse/shared": {
      "version": "10.11.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/@vueuse/shared/-/shared-10.11.1.tgz",
      "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
      "requires": {
        "vue-demi": ">=0.14.8"
      }
    },
    "async-validator": {
      "version": "4.2.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/async-validator/-/async-validator-4.2.5.tgz",
      "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
    },
    "asynckit": {
      "version": "0.4.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
    },
    "axios": {
      "version": "1.13.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/axios/-/axios-1.13.2.tgz",
      "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
      "requires": {
        "follow-redirects": "^1.15.6",
        "form-data": "^4.0.4",
        "proxy-from-env": "^1.1.0"
      }
    },
    "call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
      "requires": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      }
    },
    "combined-stream": {
      "version": "1.0.8",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
      "requires": {
        "delayed-stream": "~1.0.0"
      }
    },
    "csstype": {
      "version": "3.2.3",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/csstype/-/csstype-3.2.3.tgz",
      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
    },
    "dayjs": {
      "version": "1.11.19",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/dayjs/-/dayjs-1.11.19.tgz",
      "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
    },
    "delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
    },
    "dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
      "requires": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      }
    },
    "echarts": {
      "version": "5.6.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/echarts/-/echarts-5.6.0.tgz",
      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
      "requires": {
        "tslib": "2.3.0",
        "zrender": "5.6.1"
      }
    },
    "element-plus": {
      "version": "2.13.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/element-plus/-/element-plus-2.13.0.tgz",
      "integrity": "sha512-qjxS+SBChvqCl6lU6ShiliLMN6WqFHiXQENYbAY3GKNflG+FS3jqn8JmQq0CBZq4koFqsi95NT1M6SL4whZfrA==",
      "requires": {
        "@ctrl/tinycolor": "^3.4.1",
        "@element-plus/icons-vue": "^2.3.2",
        "@floating-ui/dom": "^1.0.1",
        "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
        "@types/lodash": "^4.17.20",
        "@types/lodash-es": "^4.17.12",
        "@vueuse/core": "^10.11.0",
        "async-validator": "^4.2.5",
        "dayjs": "^1.11.19",
        "lodash": "^4.17.21",
        "lodash-es": "^4.17.21",
        "lodash-unified": "^1.0.3",
        "memoize-one": "^6.0.0",
        "normalize-wheel-es": "^1.2.0"
      }
    },
    "entities": {
      "version": "7.0.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/entities/-/entities-7.0.0.tgz",
      "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="
    },
    "es-define-property": {
      "version": "1.0.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
    },
    "es-errors": {
      "version": "1.3.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
    },
    "es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
      "requires": {
        "es-errors": "^1.3.0"
      }
    },
    "es-set-tostringtag": {
      "version": "2.1.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
      "requires": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      }
    },
    "esbuild": {
      "version": "0.21.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/esbuild/-/esbuild-0.21.5.tgz",
      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
      "dev": true,
      "requires": {
        "@esbuild/aix-ppc64": "0.21.5",
        "@esbuild/android-arm": "0.21.5",
        "@esbuild/android-arm64": "0.21.5",
        "@esbuild/android-x64": "0.21.5",
        "@esbuild/darwin-arm64": "0.21.5",
        "@esbuild/darwin-x64": "0.21.5",
        "@esbuild/freebsd-arm64": "0.21.5",
        "@esbuild/freebsd-x64": "0.21.5",
        "@esbuild/linux-arm": "0.21.5",
        "@esbuild/linux-arm64": "0.21.5",
        "@esbuild/linux-ia32": "0.21.5",
        "@esbuild/linux-loong64": "0.21.5",
        "@esbuild/linux-mips64el": "0.21.5",
        "@esbuild/linux-ppc64": "0.21.5",
        "@esbuild/linux-riscv64": "0.21.5",
        "@esbuild/linux-s390x": "0.21.5",
        "@esbuild/linux-x64": "0.21.5",
        "@esbuild/netbsd-x64": "0.21.5",
        "@esbuild/openbsd-x64": "0.21.5",
        "@esbuild/sunos-x64": "0.21.5",
        "@esbuild/win32-arm64": "0.21.5",
        "@esbuild/win32-ia32": "0.21.5",
        "@esbuild/win32-x64": "0.21.5"
      }
    },
    "estree-walker": {
      "version": "2.0.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-2.0.2.tgz",
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
    },
    "follow-redirects": {
      "version": "1.15.11",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/follow-redirects/-/follow-redirects-1.15.11.tgz",
      "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
    },
    "form-data": {
      "version": "4.0.5",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/form-data/-/form-data-4.0.5.tgz",
      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
      "requires": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "es-set-tostringtag": "^2.1.0",
        "hasown": "^2.0.2",
        "mime-types": "^2.1.12"
      }
    },
    "fsevents": {
      "version": "2.3.3",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
      "dev": true,
      "optional": true
    },
    "function-bind": {
      "version": "1.1.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
    },
    "get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
      "requires": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      }
    },
    "get-proto": {
      "version": "1.0.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
      "requires": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      }
    },
    "gopd": {
      "version": "1.2.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/gopd/-/gopd-1.2.0.tgz",
      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
    },
    "has-symbols": {
      "version": "1.1.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
    },
    "has-tostringtag": {
      "version": "1.0.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
      "requires": {
        "has-symbols": "^1.0.3"
      }
    },
    "hasown": {
      "version": "2.0.2",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/hasown/-/hasown-2.0.2.tgz",
      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
      "requires": {
        "function-bind": "^1.1.2"
      }
    },
    "lodash": {
      "version": "4.17.21",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
    },
    "lodash-es": {
      "version": "4.17.22",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash-es/-/lodash-es-4.17.22.tgz",
      "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q=="
    },
    "lodash-unified": {
      "version": "1.0.3",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash-unified/-/lodash-unified-1.0.3.tgz",
      "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ=="
    },
    "magic-string": {
      "version": "0.30.21",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.30.21.tgz",
      "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
      "requires": {
        "@jridgewell/sourcemap-codec": "^1.5.5"
      }
    },
    "math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
    },
    "memoize-one": {
      "version": "6.0.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/memoize-one/-/memoize-one-6.0.0.tgz",
      "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
    },
    "mime-db": {
      "version": "1.52.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
    },
    "mime-types": {
      "version": "2.1.35",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
      "requires": {
        "mime-db": "1.52.0"
      }
    },
    "nanoid": {
      "version": "3.3.11",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/nanoid/-/nanoid-3.3.11.tgz",
      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
    },
    "normalize-wheel-es": {
      "version": "1.2.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
      "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
    },
    "picocolors": {
      "version": "1.1.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
    },
    "postcss": {
      "version": "8.5.6",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/postcss/-/postcss-8.5.6.tgz",
      "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
      "requires": {
        "nanoid": "^3.3.11",
        "picocolors": "^1.1.1",
        "source-map-js": "^1.2.1"
      }
    },
    "proxy-from-env": {
      "version": "1.1.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
    },
    "rollup": {
      "version": "4.54.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/rollup/-/rollup-4.54.0.tgz",
      "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
      "dev": true,
      "requires": {
        "@rollup/rollup-android-arm-eabi": "4.54.0",
        "@rollup/rollup-android-arm64": "4.54.0",
        "@rollup/rollup-darwin-arm64": "4.54.0",
        "@rollup/rollup-darwin-x64": "4.54.0",
        "@rollup/rollup-freebsd-arm64": "4.54.0",
        "@rollup/rollup-freebsd-x64": "4.54.0",
        "@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
        "@rollup/rollup-linux-arm-musleabihf": "4.54.0",
        "@rollup/rollup-linux-arm64-gnu": "4.54.0",
        "@rollup/rollup-linux-arm64-musl": "4.54.0",
        "@rollup/rollup-linux-loong64-gnu": "4.54.0",
        "@rollup/rollup-linux-ppc64-gnu": "4.54.0",
        "@rollup/rollup-linux-riscv64-gnu": "4.54.0",
        "@rollup/rollup-linux-riscv64-musl": "4.54.0",
        "@rollup/rollup-linux-s390x-gnu": "4.54.0",
        "@rollup/rollup-linux-x64-gnu": "4.54.0",
        "@rollup/rollup-linux-x64-musl": "4.54.0",
        "@rollup/rollup-openharmony-arm64": "4.54.0",
        "@rollup/rollup-win32-arm64-msvc": "4.54.0",
        "@rollup/rollup-win32-ia32-msvc": "4.54.0",
        "@rollup/rollup-win32-x64-gnu": "4.54.0",
        "@rollup/rollup-win32-x64-msvc": "4.54.0",
        "@types/estree": "1.0.8",
        "fsevents": "~2.3.2"
      }
    },
    "source-map-js": {
      "version": "1.2.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/source-map-js/-/source-map-js-1.2.1.tgz",
      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
    },
    "tslib": {
      "version": "2.3.0",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/tslib/-/tslib-2.3.0.tgz",
      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
    },
    "vite": {
      "version": "5.4.21",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/vite/-/vite-5.4.21.tgz",
      "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
      "dev": true,
      "requires": {
        "esbuild": "^0.21.3",
        "fsevents": "~2.3.3",
        "postcss": "^8.4.43",
        "rollup": "^4.20.0"
      }
    },
    "vue": {
      "version": "3.5.26",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/vue/-/vue-3.5.26.tgz",
      "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
      "requires": {
        "@vue/compiler-dom": "3.5.26",
        "@vue/compiler-sfc": "3.5.26",
        "@vue/runtime-dom": "3.5.26",
        "@vue/server-renderer": "3.5.26",
        "@vue/shared": "3.5.26"
      }
    },
    "vue-demi": {
      "version": "0.14.10",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/vue-demi/-/vue-demi-0.14.10.tgz",
      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="
    },
    "vue-router": {
      "version": "4.6.4",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/vue-router/-/vue-router-4.6.4.tgz",
      "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
      "requires": {
        "@vue/devtools-api": "^6.6.4"
      }
    },
    "zrender": {
      "version": "5.6.1",
      "resolved": "https://mirrors.huaweicloud.com/repository/npm/zrender/-/zrender-5.6.1.tgz",
      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
      "requires": {
        "tslib": "2.3.0"
      }
    }
  }
}
ÏîÄ¿´úÂë/Dashboard/package.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
{
  "name": "wms-dashboard",
  "version": "1.0.0",
  "type": "module",
  "description": "制造业WMS看板及大屏展示系统",
  "scripts": {
    "dev": "vite --host",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "vue": "^3.4.21",
    "vue-router": "^4.3.0",
    "axios": "^1.6.8",
    "element-plus": "^2.7.0",
    "@element-plus/icons-vue": "^2.3.1",
    "echarts": "^5.5.0",
    "dayjs": "^1.11.10"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "vite": "^5.2.0"
  }
}
ÏîÄ¿´úÂë/Dashboard/src/App.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
<template>
  <div id="app">
    <router-view />
  </div>
</template>
<script>
export default {
  name: 'App'
}
</script>
<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html, body, #app {
  width: 100%;
  height: 100%;
  font-family: 'Microsoft YaHei', Arial, sans-serif;
  background: #0a0e27;
  color: #fff;
  overflow: hidden;
}
</style>
ÏîÄ¿´úÂë/Dashboard/src/api/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
/**
 * API æŽ¥å£ç»Ÿä¸€ç®¡ç†
 * æ‰€æœ‰æŽ¥å£éƒ½åœ¨è¿™é‡Œå®šä¹‰ï¼Œæ–¹ä¾¿ç»´æŠ¤
 */
import { http } from '@/utils/http'
// ==================== ç”¨æˆ·ç›¸å…³ ====================
export const userApi = {
  // èŽ·å–ç”¨æˆ·ä¿¡æ¯
  getUserInfo: () => http.get('/user/info'),
  // èŽ·å–ç”¨æˆ·åˆ—è¡¨
  getUserList: (params) => http.get('/user/list', params)
}
// ==================== ä»“库相关 ====================
export const warehouseApi = {
  // èŽ·å–ä»“åº“çŠ¶æ€æ¦‚è§ˆ
  getWarehouseStatus: () => http.get('/wms/warehouse/status'),
  // èŽ·å–ä»“åº“åŒºåŸŸä¿¡æ¯
  getAreas: () => http.get('/wms/warehouse/areas'),
  // èŽ·å–åº“ä½ä¿¡æ¯
  getLocations: (params) => http.get('/wms/warehouse/locations', params),
  // èŽ·å–æ¸©æ¹¿åº¦æ•°æ®
  getEnvData: () => http.get('/wms/warehouse/env')
}
// ==================== åº“存相关 ====================
export const inventoryApi = {
  // èŽ·å–åº“å­˜æ¦‚è§ˆ
  getInventoryOverview: () => http.get('/wms/inventory/overview'),
  // èŽ·å–åº“å­˜åˆ—è¡¨
  getInventoryList: (params) => http.get('/wms/inventory/list', params),
  // åº“存预警列表
  getLowStockAlerts: (params) => http.get('/wms/inventory/low-stock', params),
  // åº“存积压列表
  getOverStockAlerts: (params) => http.get('/wms/inventory/over-stock', params),
  // å³å°†è¿‡æœŸç‰©æ–™
  getExpiringItems: (params) => http.get('/wms/inventory/expiring', params),
  // åº“存周转率
  getTurnoverRate: (params) => http.get('/wms/inventory/turnover', params),
  // ABC分类统计
  getABCStats: () => http.get('/wms/inventory/abc-stats')
}
// ==================== ä½œä¸šä»»åŠ¡ç›¸å…³ ====================
export const taskApi = {
  // èŽ·å–ä»»åŠ¡åˆ—è¡¨
  getTaskList: (params) => http.get('/wms/task/list', params),
  // åˆ›å»ºä»»åŠ¡
  createTask: (data) => http.post('/wms/task/create', data),
  // æ›´æ–°ä»»åŠ¡çŠ¶æ€
  updateTaskStatus: (taskId, data) => http.put(`/wms/task/${taskId}/status`, data),
  // èŽ·å–ä»»åŠ¡è¯¦æƒ…
  getTaskDetail: (taskId) => http.get(`/wms/task/${taskId}`)
}
// ==================== å‡ºå…¥åº“相关 ====================
export const ioApi = {
  // å…¥åº“单列表
  getInboundList: (params) => http.get('/wms/inbound/list', params),
  // å‡ºåº“单列表
  getOutboundList: (params) => http.get('/wms/outbound/list', params),
  // åˆ›å»ºå…¥åº“单
  createInbound: (data) => http.post('/wms/inbound/create', data),
  // åˆ›å»ºå‡ºåº“单
  createOutbound: (data) => http.post('/wms/outbound/create', data),
  // å‡ºå…¥åº“统计
  getIOStats: (params) => http.get('/wms/io/stats', params)
}
// ==================== ç”Ÿäº§ç›¸å…³ ====================
export const productionApi = {
  // èŽ·å–ç”Ÿäº§çº¿çŠ¶æ€
  getProductionLines: () => http.get('/wms/production/lines'),
  // èŽ·å–è®¾å¤‡çŠ¶æ€
  getDeviceStatus: (lineId) => http.get(`/wms/production/line/${lineId}/devices`),
  // èŽ·å–ç”Ÿäº§ä»»åŠ¡åˆ—è¡¨
  getProductionTasks: (params) => http.get('/wms/production/tasks', params),
  // äº§é‡ç»Ÿè®¡
  getProductionStats: (params) => http.get('/wms/production/stats', params),
  // è‰¯å“çŽ‡ç»Ÿè®¡
  getQualityStats: (params) => http.get('/wms/production/quality', params),
  // è®¾å¤‡OEE
  getDeviceOEE: (params) => http.get('/wms/production/oee', params)
}
// ==================== æŠ¥è¡¨ç›¸å…³ ====================
export const reportApi = {
  // ç»¼åˆçœ‹æ¿æ•°æ®
  getDashboardData: () => http.get('/wms/report/dashboard'),
  // å‡ºå…¥åº“趋势
  getTrendData: (params) => http.get('/wms/report/trend', params),
  // åˆ†ç±»å æ¯”
  getCategoryStats: () => http.get('/wms/report/category'),
  // ä½œä¸šæ•ˆçŽ‡ç»Ÿè®¡
  getEfficiencyStats: (params) => http.get('/wms/report/efficiency', params)
}
// ==================== ç‰©æ–™ç›¸å…³ ====================
export const materialApi = {
  // èŽ·å–ç‰©æ–™åˆ—è¡¨
  getMaterialList: (params) => http.get('/wms/material/list', params),
  // èŽ·å–ç‰©æ–™è¯¦æƒ…
  getMaterialDetail: (materialId) => http.get(`/wms/material/${materialId}`),
  // èŽ·å–ç‰©æ–™åº“å­˜åŽ†å²
  getMaterialHistory: (materialId, params) => http.get(`/wms/material/${materialId}/history`, params)
}
// ==================== ç³»ç»Ÿé…ç½®ç›¸å…³ ====================
export const configApi = {
  // èŽ·å–ç³»ç»Ÿé…ç½®
  getConfig: () => http.get('/wms/system/config'),
  // æ›´æ–°ç³»ç»Ÿé…ç½®
  updateConfig: (data) => http.put('/wms/system/config', data)
}
ÏîÄ¿´úÂë/Dashboard/src/examples/ApiExample.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,618 @@
<template>
  <div class="api-example-container">
    <div class="header">
      <h1 class="title">API调用示例</h1>
      <p class="subtitle">展示如何使用封装好的http工具和api模块调用后端接口</p>
    </div>
    <div class="content">
      <!-- ç¤ºä¾‹1: åŸºç¡€GET请求 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Document /></el-icon>
          <span>示例1: åŸºç¡€GET请求</span>
        </div>
        <div class="code-block">
          <pre><code>// æ–¹å¼ä¸€ï¼šä½¿ç”¨ http å·¥å…·ç›´æŽ¥è°ƒç”¨
import { http } from '@/utils/http'
const fetchData = async () => {
  try {
    const res = await http.get('/api/user/list', { page: 1, pageSize: 10 })
    console.log('返回数据:', res.data)
  } catch (error) {
    console.error('请求失败:', error)
  }
}
// æ–¹å¼äºŒï¼šä½¿ç”¨å°è£…好的api模块
import { warehouseApi } from '@/api'
const fetchWarehouse = async () => {
  try {
    const res = await warehouseApi.getWarehouseStatus()
    console.log('仓库状态:', res.data)
  } catch (error) {
    console.error('请求失败:', error)
  }
}</code></pre>
        </div>
        <el-button type="primary" @click="example1" :loading="loading1">
          è¿è¡Œç¤ºä¾‹1
        </el-button>
        <div v-if="result1" class="result-box">
          <div class="result-title">返回结果:</div>
          <pre>{{ JSON.stringify(result1, null, 2) }}</pre>
        </div>
      </div>
      <!-- ç¤ºä¾‹2: POST请求 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Document /></el-icon>
          <span>示例2: POST请求(创建数据)</span>
        </div>
        <div class="code-block">
          <pre><code>// ä½¿ç”¨ http å·¥å…·å‘送POST请求
import { http } from '@/utils/http'
const createData = async () => {
  try {
    const res = await http.post('/api/task/create', {
      taskNo: 'RK001',
      type: '入库',
      material: '钢板',
      quantity: 100
    })
    console.log('创建成功:', res.data)
    return res.data
  } catch (error) {
    console.error('创建失败:', error)
  }
}
// ä½¿ç”¨å°è£…çš„api
import { taskApi } from '@/api'
const createTask = async () => {
  const res = await taskApi.createTask({
    taskNo: 'RK001',
    type: '入库',
    material: '钢板',
    quantity: 100
  })
  return res.data
}</code></pre>
        </div>
        <el-button type="success" @click="example2" :loading="loading2">
          è¿è¡Œç¤ºä¾‹2
        </el-button>
        <div v-if="result2" class="result-box">
          <div class="result-title">返回结果:</div>
          <pre>{{ JSON.stringify(result2, null, 2) }}</pre>
        </div>
      </div>
      <!-- ç¤ºä¾‹3: PUT/DELETE请求 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Document /></el-icon>
          <span>示例3: PUT/DELETE请求</span>
        </div>
        <div class="code-block">
          <pre><code>// PUT请求 - æ›´æ–°æ•°æ®
const updateTask = async (taskId) => {
  const res = await http.put(`/api/task/${taskId}`, {
    status: '已完成'
  })
  return res.data
}
// DELETE请求 - åˆ é™¤æ•°æ®
const deleteTask = async (taskId) => {
  const res = await http.delete(`/api/task/${taskId}`)
  return res.data
}
// ä½¿ç”¨api模块
import { taskApi } from '@/api'
// æ›´æ–°ä»»åŠ¡çŠ¶æ€
await taskApi.updateTaskStatus('TASK001', { status: '已完成' })
// èŽ·å–ä»»åŠ¡è¯¦æƒ…
const detail = await taskApi.getTaskDetail('TASK001')</code></pre>
        </div>
      </div>
      <!-- ç¤ºä¾‹4: å®žé™…业务场景 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Document /></el-icon>
          <span>示例4: å®žé™…业务场景 - ç»„件中调用API</span>
        </div>
        <div class="code-block">
          <pre><code>// åœ¨Vue组件中使用
import { ref, onMounted } from 'vue'
import { warehouseApi, inventoryApi } from '@/api'
export default {
  setup() {
    const loading = ref(false)
    const warehouseList = ref([])
    const inventoryStats = ref({})
    // èŽ·å–ä»“åº“åˆ—è¡¨
    const fetchWarehouseList = async () => {
      loading.value = true
      try {
        const res = await warehouseApi.getAreas()
        warehouseList.value = res.data
      } catch (error) {
        ElMessage.error('获取仓库列表失败')
      } finally {
        loading.value = false
      }
    }
    // èŽ·å–åº“å­˜ç»Ÿè®¡
    const fetchInventoryStats = async () => {
      try {
        const res = await inventoryApi.getInventoryOverview()
        inventoryStats.value = res.data
      } catch (error) {
        console.error('获取库存统计失败:', error)
      }
    }
    // é¡µé¢åŠ è½½æ—¶èŽ·å–æ•°æ®
    onMounted(() => {
      fetchWarehouseList()
      fetchInventoryStats()
    })
    return {
      loading,
      warehouseList,
      inventoryStats,
      fetchWarehouseList
    }
  }
}</code></pre>
        </div>
      </div>
      <!-- ç¤ºä¾‹5: å¹¶å‘请求 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Document /></el-icon>
          <span>示例5: å¹¶å‘请求(使用Promise.all)</span>
        </div>
        <div class="code-block">
          <pre><code>// åŒæ—¶å‘起多个请求
import { http } from '@/utils/http'
const fetchDashboardData = async () => {
  try {
    const [warehouseRes, inventoryRes, taskRes] = await Promise.all([
      http.get('/api/warehouse/status'),
      http.get('/api/inventory/overview'),
      http.get('/api/task/list', { status: '进行中' })
    ])
    return {
      warehouse: warehouseRes.data,
      inventory: inventoryRes.data,
      tasks: taskRes.data
    }
  } catch (error) {
    console.error('获取看板数据失败:', error)
  }
}
// ä½¿ç”¨api模块
import { warehouseApi, inventoryApi, taskApi } from '@/api'
const fetchAllData = async () => {
  const [warehouse, inventory, tasks] = await Promise.all([
    warehouseApi.getWarehouseStatus(),
    inventoryApi.getInventoryOverview(),
    taskApi.getTaskList({ status: '进行中' })
  ])
  return { warehouse, inventory, tasks }
}</code></pre>
        </div>
        <el-button type="warning" @click="example5" :loading="loading5">
          è¿è¡Œç¤ºä¾‹5(并发请求)
        </el-button>
        <div v-if="result5" class="result-box">
          <div class="result-title">返回结果:</div>
          <pre>{{ JSON.stringify(result5, null, 2) }}</pre>
        </div>
      </div>
      <!-- å®žé™…演示 -->
      <div class="example-section">
        <div class="section-title">
          <el-icon><Monitor /></el-icon>
          <span>实际演示</span>
        </div>
        <div class="demo-buttons">
          <el-button @click="fetchWarehouse" :loading="demoLoading.warehouse">
            <el-icon><Box /></el-icon>
            èŽ·å–ä»“åº“çŠ¶æ€
          </el-button>
          <el-button @click="fetchInventory" :loading="demoLoading.inventory">
            <el-icon><List /></el-icon>
            èŽ·å–åº“å­˜åˆ—è¡¨
          </el-button>
          <el-button @click="fetchTasks" :loading="demoLoading.tasks">
            <el-icon><Document /></el-icon>
            èŽ·å–ä»»åŠ¡åˆ—è¡¨
          </el-button>
          <el-button @click="fetchDashboard" :loading="demoLoading.dashboard">
            <el-icon><DataBoard /></el-icon>
            èŽ·å–çœ‹æ¿æ•°æ®ï¼ˆå¹¶å‘ï¼‰
          </el-button>
        </div>
        <div v-if="demoResult" class="result-box large">
          <div class="result-title">API返回数据:</div>
          <pre>{{ JSON.stringify(demoResult, null, 2) }}</pre>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { http } from '@/utils/http'
import { warehouseApi, inventoryApi, taskApi, reportApi } from '@/api'
export default {
  name: 'ApiExample',
  setup() {
    const loading1 = ref(false)
    const loading2 = ref(false)
    const loading5 = ref(false)
    const result1 = ref(null)
    const result2 = ref(null)
    const result5 = ref(null)
    const demoLoading = ref({
      warehouse: false,
      inventory: false,
      tasks: false,
      dashboard: false
    })
    const demoResult = ref(null)
    // ç¤ºä¾‹1: GET请求
    const example1 = async () => {
      loading1.value = true
      result1.value = null
      try {
        // æ¨¡æ‹ŸAPI调用
        // å®žé™…使用时换成真实的API
        const res = await new Promise(resolve => {
          setTimeout(() => {
            resolve({
              code: 200,
              data: {
                total: 100,
                list: [
                  { id: 1, name: '仓库A', capacity: 1000, used: 600 },
                  { id: 2, name: '仓库B', capacity: 800, used: 400 }
                ]
              }
            })
          }, 1000)
        })
        result1.value = res.data
        ElMessage.success('GET请求成功')
      } catch (error) {
        ElMessage.error('请求失败: ' + error.message)
      } finally {
        loading1.value = false
      }
    }
    // ç¤ºä¾‹2: POST请求
    const example2 = async () => {
      loading2.value = true
      result2.value = null
      try {
        const res = await new Promise(resolve => {
          setTimeout(() => {
            resolve({
              code: 200,
              data: {
                id: 'TASK' + Date.now(),
                status: 'success',
                message: '创建成功'
              }
            })
          }, 1000)
        })
        result2.value = res.data
        ElMessage.success('POST请求成功')
      } catch (error) {
        ElMessage.error('请求失败: ' + error.message)
      } finally {
        loading2.value = false
      }
    }
    // ç¤ºä¾‹5: å¹¶å‘请求
    const example5 = async () => {
      loading5.value = true
      result5.value = null
      try {
        // æ¨¡æ‹Ÿå¹¶å‘请求
        const [res1, res2, res3] = await Promise.all([
          new Promise(resolve => setTimeout(() => resolve({ code: 200, data: { warehouse: '正常' } }), 800)),
          new Promise(resolve => setTimeout(() => resolve({ code: 200, data: { inventory: 12580 } }), 600)),
          new Promise(resolve => setTimeout(() => resolve({ code: 200, data: { tasks: 48 } }), 1000))
        ])
        result5.value = {
          warehouse: res1.data,
          inventory: res2.data,
          tasks: res3.data
        }
        ElMessage.success('并发请求成功')
      } catch (error) {
        ElMessage.error('请求失败: ' + error.message)
      } finally {
        loading5.value = false
      }
    }
    // å®žé™…演示 - èŽ·å–ä»“åº“çŠ¶æ€
    const fetchWarehouse = async () => {
      demoLoading.value.warehouse = true
      demoResult.value = null
      try {
        // å®žé™…使用时调用真实接口
        // const res = await warehouseApi.getWarehouseStatus()
        // æ¨¡æ‹Ÿæ•°æ®
        const res = {
          code: 200,
          data: {
            totalAreas: 4,
            totalPositions: 11000,
            usedPositions: 7700,
            utilization: 70
          }
        }
        demoResult.value = res
        ElMessage.success('获取仓库状态成功')
      } catch (error) {
        ElMessage.error('获取仓库状态失败')
      } finally {
        demoLoading.value.warehouse = false
      }
    }
    // èŽ·å–åº“å­˜åˆ—è¡¨
    const fetchInventory = async () => {
      demoLoading.value.inventory = true
      demoResult.value = null
      try {
        const res = {
          code: 200,
          data: {
            total: 12580,
            items: [
              { code: 'M001', name: '钢板', quantity: 5000 },
              { code: 'M002', name: '铝板', quantity: 3000 },
              { code: 'M003', name: '螺丝', quantity: 4580 }
            ]
          }
        }
        demoResult.value = res
        ElMessage.success('获取库存列表成功')
      } catch (error) {
        ElMessage.error('获取库存列表失败')
      } finally {
        demoLoading.value.inventory = false
      }
    }
    // èŽ·å–ä»»åŠ¡åˆ—è¡¨
    const fetchTasks = async () => {
      demoLoading.value.tasks = true
      demoResult.value = null
      try {
        const res = {
          code: 200,
          data: {
            total: 48,
            list: [
              { taskNo: 'RK001', type: '入库', status: '进行中' },
              { taskNo: 'CK001', type: '出库', status: '已完成' },
              { taskNo: 'RK002', type: '入库', status: '待处理' }
            ]
          }
        }
        demoResult.value = res
        ElMessage.success('获取任务列表成功')
      } catch (error) {
        ElMessage.error('获取任务列表失败')
      } finally {
        demoLoading.value.tasks = false
      }
    }
    // å¹¶å‘获取看板数据
    const fetchDashboard = async () => {
      demoLoading.value.dashboard = true
      demoResult.value = null
      try {
        // å®žé™…使用时使用并发请求
        // const [warehouse, inventory, tasks] = await Promise.all([
        //   warehouseApi.getWarehouseStatus(),
        //   inventoryApi.getInventoryOverview(),
        //   taskApi.getTaskList({ status: '进行中' })
        // ])
        // æ¨¡æ‹Ÿæ•°æ®
        const res = {
          code: 200,
          data: {
            warehouse: { totalAreas: 4, utilization: 70 },
            inventory: { total: 12580, lowStock: 8 },
            tasks: { total: 48, inProgress: 15 }
          }
        }
        demoResult.value = res
        ElMessage.success('获取看板数据成功')
      } catch (error) {
        ElMessage.error('获取看板数据失败')
      } finally {
        demoLoading.value.dashboard = false
      }
    }
    return {
      loading1,
      loading2,
      loading5,
      result1,
      result2,
      result5,
      demoLoading,
      demoResult,
      example1,
      example2,
      example5,
      fetchWarehouse,
      fetchInventory,
      fetchTasks,
      fetchDashboard
    }
  }
}
</script>
<style scoped>
.api-example-container {
  width: 100%;
  min-height: 100vh;
  max-height: 100vh;
  padding: 20px;
  padding-bottom: 50px;
  background: #0a0e27;
  color: #fff;
  overflow-y: auto;
  box-sizing: border-box;
}
.header {
  text-align: center;
  margin-bottom: 30px;
  padding: 30px;
  background: linear-gradient(135deg, rgba(79, 172, 254, 0.1) 0%, rgba(0, 242, 254, 0.1) 100%);
  border-radius: 10px;
  border: 1px solid rgba(79, 172, 254, 0.2);
}
.title {
  font-size: 32px;
  font-weight: bold;
  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  margin-bottom: 10px;
}
.subtitle {
  font-size: 16px;
  color: rgba(255, 255, 255, 0.6);
}
.content {
  max-width: 1200px;
  margin: 0 auto;
}
.example-section {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 20px;
}
.section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 18px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 15px;
}
.code-block {
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  padding: 15px;
  margin-bottom: 15px;
  overflow-x: auto;
}
.code-block pre {
  margin: 0;
  font-size: 13px;
  line-height: 1.6;
}
.code-block code {
  color: #e6a23c;
  font-family: 'Courier New', monospace;
}
.result-box {
  margin-top: 15px;
  padding: 15px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(103, 194, 58, 0.3);
  border-radius: 8px;
}
.result-box.large {
  max-height: 400px;
  overflow-y: auto;
}
.result-title {
  font-size: 14px;
  font-weight: bold;
  color: #67c23a;
  margin-bottom: 10px;
}
.result-box pre {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: #fff;
  overflow-x: auto;
}
.demo-buttons {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
:deep(.el-button) {
  margin: 0;
}
</style>
ÏîÄ¿´úÂë/Dashboard/src/main.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
// æ³¨å†Œæ‰€æœ‰å›¾æ ‡
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.use(ElementPlus)
app.use(router)
app.mount('#app')
ÏîÄ¿´úÂë/Dashboard/src/router/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
  {
    path: '/',
    redirect: '/dashboard'
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { title: 'WMS综合看板' }
  },
  {
    path: '/warehouse',
    name: 'Warehouse',
    component: () => import('@/views/Warehouse.vue'),
    meta: { title: '仓库监控' }
  },
  {
    path: '/production',
    name: 'Production',
    component: () => import('@/views/Production.vue'),
    meta: { title: '生产监控' }
  },
  {
    path: '/inventory',
    name: 'Inventory',
    component: () => import('@/views/Inventory.vue'),
    meta: { title: '库存预警' }
  },
  {
    path: '/api-example',
    name: 'ApiExample',
    component: () => import('@/examples/ApiExample.vue'),
    meta: { title: 'API调用示例' }
  }
]
const router = createRouter({
  history: createWebHistory(),
  routes
})
export default router
ÏîÄ¿´úÂë/Dashboard/src/utils/http.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
// åˆ›å»ºaxios实例
const request = axios.create({
  baseURL: '/api',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})
// è¯·æ±‚拦截器
request.interceptors.request.use(
  config => {
    // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ token等认证信息
    // const token = localStorage.getItem('token')
    // if (token) {
    //   config.headers['Authorization'] = `Bearer ${token}`
    // }
    return config
  },
  error => {
    console.error('请求错误:', error)
    return Promise.reject(error)
  }
)
// å“åº”拦截器
request.interceptors.response.use(
  response => {
    const res = response.data
    // æ ¹æ®å®žé™…后端返回格式调整
    // å‡è®¾åŽç«¯è¿”回格式为 { code: 200, data: {}, message: '' }
    if (res.code !== undefined && res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      return Promise.reject(new Error(res.message || '请求失败'))
    }
    return res
  },
  error => {
    console.error('响应错误:', error)
    let message = '网络错误'
    if (error.response) {
      switch (error.response.status) {
        case 400:
          message = '请求错误'
          break
        case 401:
          message = '未授权,请重新登录'
          break
        case 403:
          message = '拒绝访问'
          break
        case 404:
          message = '请求地址不存在'
          break
        case 500:
          message = '服务器内部错误'
          break
        default:
          message = error.response.data?.message || '请求失败'
      }
    } else if (error.request) {
      message = '网络连接失败'
    }
    ElMessage.error(message)
    return Promise.reject(error)
  }
)
// å¯¼å‡ºå¸¸ç”¨çš„请求方法
export const http = {
  get(url, params, config = {}) {
    return request.get(url, { params, ...config })
  },
  post(url, data, config = {}) {
    return request.post(url, data, config)
  },
  put(url, data, config = {}) {
    return request.put(url, data, config)
  },
  delete(url, params, config = {}) {
    return request.delete(url, { params, ...config })
  },
  // ç”¨äºŽæ–‡ä»¶ä¸Šä¼ 
  upload(url, file, config = {}) {
    const formData = new FormData()
    formData.append('file', file)
    return request.post(url, formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      ...config
    })
  }
}
export default request
ÏîÄ¿´úÂë/Dashboard/src/utils/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
// é€šç”¨å·¥å…·å‡½æ•°
import dayjs from 'dayjs'
/**
 * æ ¼å¼åŒ–日期时间
 * @param {Date|string|number} date - æ—¥æœŸå¯¹è±¡ã€æ—¥æœŸå­—符串或时间戳
 * @param {string} format - æ ¼å¼åŒ–模板,默认 'YYYY-MM-DD HH:mm:ss'
 * @returns {string} æ ¼å¼åŒ–后的日期字符串
 */
export function formatDateTime(date, format = 'YYYY-MM-DD HH:mm:ss') {
  return dayjs(date).format(format)
}
/**
 * æ ¼å¼åŒ–日期
 * @param {Date|string|number} date - æ—¥æœŸå¯¹è±¡ã€æ—¥æœŸå­—符串或时间戳
 * @returns {string} æ ¼å¼åŒ–后的日期字符串 YYYY-MM-DD
 */
export function formatDate(date) {
  return formatDateTime(date, 'YYYY-MM-DD')
}
/**
 * æ ¼å¼åŒ–æ—¶é—´
 * @param {Date|string|number} date - æ—¥æœŸå¯¹è±¡ã€æ—¥æœŸå­—符串或时间戳
 * @returns {string} æ ¼å¼åŒ–后的时间字符串 HH:mm:ss
 */
export function formatTime(date) {
  return formatDateTime(date, 'HH:mm:ss')
}
/**
 * æ•°å­—格式化,添加千分位
 * @param {number} num - æ•°å­—
 * @param {number} decimals - å°æ•°ä½æ•°ï¼Œé»˜è®¤0
 * @returns {string} æ ¼å¼åŒ–后的数字字符串
 */
export function formatNumber(num, decimals = 0) {
  return Number(num).toLocaleString('zh-CN', {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals
  })
}
/**
 * èŽ·å–éšæœºé¢œè‰²
 * @returns {string} é¢œè‰²å€¼
 */
export function getRandomColor() {
  const colors = [
    '#409EFF', '#67C23A', '#E6A23C', '#F56C6C',
    '#909399', '#5470c6', '#91cc75', '#fac858',
    '#ee6666', '#73c0de', '#3ba272', '#fc8452'
  ]
  return colors[Math.floor(Math.random() * colors.length)]
}
/**
 * é˜²æŠ–函数
 * @param {Function} func - è¦é˜²æŠ–的函数
 * @param {number} wait - ç­‰å¾…时间(毫秒)
 * @returns {Function} é˜²æŠ–后的函数
 */
export function debounce(func, wait = 300) {
  let timeout
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout)
      func(...args)
    }
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)
  }
}
/**
 * èŠ‚æµå‡½æ•°
 * @param {Function} func - è¦èŠ‚æµçš„å‡½æ•°
 * @param {number} wait - ç­‰å¾…时间(毫秒)
 * @returns {Function} èŠ‚æµåŽçš„å‡½æ•°
 */
export function throttle(func, wait = 300) {
  let inThrottle
  return function executedFunction(...args) {
    if (!inThrottle) {
      func(...args)
      inThrottle = true
      setTimeout(() => inThrottle = false, wait)
    }
  }
}
/**
 * æ·±æ‹·è´
 * @param {any} obj - è¦æ‹·è´çš„对象
 * @returns {any} æ‹·è´åŽçš„对象
 */
export function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof Array) return obj.map(item => deepClone(item))
  if (obj instanceof Object) {
    const cloneObj = {}
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloneObj[key] = deepClone(obj[key])
      }
    }
    return cloneObj
  }
}
ÏîÄ¿´úÂë/Dashboard/src/views/Dashboard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,729 @@
<template>
  <div class="dashboard-container">
    <!-- é¡¶éƒ¨æ ‡é¢˜ -->
    <div class="header">
      <h1 class="title">WMS仓库管理系统监控看板</h1>
      <div class="datetime">{{ currentTime }}</div>
    </div>
    <!-- å¯¼èˆªèœå• -->
    <div class="nav-menu">
      <router-link to="/dashboard" class="nav-item" active-class="active">
        <el-icon><DataBoard /></el-icon>
        <span>综合看板</span>
      </router-link>
      <router-link to="/warehouse" class="nav-item" active-class="active">
        <el-icon><Box /></el-icon>
        <span>仓库监控</span>
      </router-link>
      <router-link to="/production" class="nav-item" active-class="active">
        <el-icon><Operation /></el-icon>
        <span>生产监控</span>
      </router-link>
      <router-link to="/inventory" class="nav-item" active-class="active">
        <el-icon><Warning /></el-icon>
        <span>库存预警</span>
      </router-link>
    </div>
    <!-- ä¸»è¦å†…容区域 -->
    <div class="main-content">
      <!-- ç¬¬ä¸€è¡Œï¼šå…³é”®æŒ‡æ ‡å¡ç‰‡ -->
      <div class="metrics-row">
        <div class="metric-card" v-for="(item, index) in metrics" :key="index">
          <div class="metric-icon" :style="{ background: item.color }">
            <el-icon :size="32">
              <Box v-if="index === 0" />
              <Download v-else-if="index === 1" />
              <Upload v-else-if="index === 2" />
              <List v-else />
            </el-icon>
          </div>
          <div class="metric-content">
            <div class="metric-value">{{ item.value }}</div>
            <div class="metric-label">{{ item.label }}</div>
            <div class="metric-trend" :class="item.trend > 0 ? 'up' : 'down'">
              <el-icon>
                <Top v-if="item.trend > 0" />
                <Bottom v-else />
              </el-icon>
              <span>{{ Math.abs(item.trend) }}%</span>
            </div>
          </div>
        </div>
      </div>
      <!-- ç¬¬äºŒè¡Œï¼šå›¾è¡¨åŒºåŸŸ -->
      <div class="charts-row">
        <!-- å…¥åº“出库趋势 -->
        <div class="chart-card">
          <div class="card-title">
            <span>出入库趋势</span>
          </div>
          <div ref="trendChartRef" class="chart-container"></div>
        </div>
        <!-- åº“存分类占比 -->
        <div class="chart-card">
          <div class="card-title">
            <span>库存分类占比</span>
          </div>
          <div ref="categoryChartRef" class="chart-container"></div>
        </div>
        <!-- ä½œä¸šæ•ˆçއ -->
        <div class="chart-card">
          <div class="card-title">
            <span>作业效率统计</span>
          </div>
          <div ref="efficiencyChartRef" class="chart-container"></div>
        </div>
      </div>
      <!-- ç¬¬ä¸‰è¡Œï¼šæ•°æ®è¡¨æ ¼ -->
      <div class="table-row">
        <div class="table-card">
          <div class="card-title">
            <span>实时作业任务</span>
            <el-button type="primary" size="small" @click="refreshData">
              <el-icon><Refresh /></el-icon>
              åˆ·æ–°
            </el-button>
          </div>
          <el-table :data="taskList" style="width: 100%" height="200">
            <el-table-column prop="taskNo" label="任务编号" width="150" />
            <el-table-column prop="type" label="任务类型" width="100">
              <template #default="{ row }">
                <el-tag :type="row.type === '入库' ? 'success' : 'warning'">{{ row.type }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="material" label="物料名称" />
            <el-table-column prop="quantity" label="数量" width="100" />
            <el-table-column prop="location" label="库位" width="120" />
            <el-table-column prop="status" label="状态" width="100">
              <template #default="{ row }">
                <el-tag :type="getStatusType(row.status)">{{ row.status }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="operator" label="操作员" width="100" />
            <el-table-column prop="time" label="时间" width="160" />
          </el-table>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { http } from '@/utils/http'
import { formatDateTime } from '@/utils'
export default {
  name: 'Dashboard',
  setup() {
    const currentTime = ref('')
    const trendChartRef = ref(null)
    const categoryChartRef = ref(null)
    const efficiencyChartRef = ref(null)
    const metrics = ref([
      { label: '总库存量', value: '12,580', icon: 'Box', color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', trend: 8.5 },
      { label: '今日入库', value: '1,280', icon: 'Download', color: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', trend: 12.3 },
      { label: '今日出库', value: '965', icon: 'Upload', color: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', trend: -3.2 },
      { label: '待处理任务', value: '48', icon: 'List', color: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', trend: 5.7 }
    ])
    const taskList = ref([])
    // æ›´æ–°æ—¶é—´
    let timer
    const updateTime = () => {
      currentTime.value = formatDateTime(new Date())
    }
    // èŽ·å–çŠ¶æ€ç±»åž‹
    const getStatusType = (status) => {
      const map = {
        '进行中': 'primary',
        '已完成': 'success',
        '待处理': 'info',
        '异常': 'danger'
      }
      return map[status] || 'info'
    }
    // åˆå§‹åŒ–趋势图
    const initTrendChart = () => {
      const chart = echarts.init(trendChartRef.value)
      const option = {
        tooltip: { trigger: 'axis' },
        legend: {
          data: ['入库量', '出库量'],
          textStyle: { color: '#fff' }
        },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
          axisLine: { lineStyle: { color: '#fff' } }
        },
        yAxis: {
          type: 'value',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
        },
        series: [
          {
            name: '入库量',
            type: 'line',
            smooth: true,
            data: [120, 200, 450, 680, 520, 780, 650],
            itemStyle: { color: '#43e97b' },
            areaStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(67, 233, 123, 0.5)' },
                { offset: 1, color: 'rgba(67, 233, 123, 0)' }
              ])
            }
          },
          {
            name: '出库量',
            type: 'line',
            smooth: true,
            data: [80, 150, 380, 520, 420, 650, 580],
            itemStyle: { color: '#4facfe' },
            areaStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(79, 172, 254, 0.5)' },
                { offset: 1, color: 'rgba(79, 172, 254, 0)' }
              ])
            }
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–分类占比图
    const initCategoryChart = () => {
      const chart = echarts.init(categoryChartRef.value)
      const option = {
        tooltip: { trigger: 'item' },
        legend: {
          orient: 'vertical',
          right: '10%',
          top: 'center',
          textStyle: { color: '#fff' }
        },
        series: [
          {
            name: '库存分类',
            type: 'pie',
            radius: ['40%', '70%'],
            center: ['35%', '50%'],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#0a0e27',
              borderWidth: 2
            },
            label: { show: false },
            emphasis: {
              label: { show: true, fontSize: 16, fontWeight: 'bold' }
            },
            data: [
              { value: 3580, name: '原材料', itemStyle: { color: '#5470c6' } },
              { value: 2840, name: '半成品', itemStyle: { color: '#91cc75' } },
              { value: 4120, name: '成品', itemStyle: { color: '#fac858' } },
              { value: 2040, name: '辅料', itemStyle: { color: '#ee6666' } }
            ]
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–效率统计图
    const initEfficiencyChart = () => {
      const chart = echarts.init(efficiencyChartRef.value)
      const option = {
        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '10%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['入库作业', '出库作业', '盘点作业', '调拨作业', '补货作业'],
          axisLine: { lineStyle: { color: '#fff' } },
          axisLabel: { color: '#fff' }
        },
        yAxis: {
          type: 'value',
          name: '效率(单位/小时)',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } },
          axisLabel: { color: '#fff' }
        },
        series: [
          {
            data: [180, 156, 95, 78, 120],
            type: 'bar',
            barWidth: '40%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#83bff6' },
                { offset: 1, color: '#188df0' }
              ]),
              borderRadius: [5, 5, 0, 0]
            }
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // èŽ·å–ä»»åŠ¡åˆ—è¡¨æ•°æ®
    const fetchTaskList = async () => {
      try {
        // æ¨¡æ‹Ÿæ•°æ®ï¼Œå®žé™…使用时调用接口
        // const res = await http.get('/wms/task/list')
        // æ¨¡æ‹Ÿæ•°æ®
        taskList.value = [
          { taskNo: 'RK20241224001', type: '入库', material: '钢板 A型', quantity: 500, location: 'A-01-01', status: '进行中', operator: '张三', time: '2024-12-24 14:25:30' },
          { taskNo: 'CK20241224002', type: '出库', material: '螺丝 M8', quantity: 2000, location: 'B-03-05', status: '已完成', operator: '李四', time: '2024-12-24 14:20:15' },
          { taskNo: 'RK20241224003', type: '入库', material: '铝板 B型', quantity: 300, location: 'A-02-03', status: '待处理', operator: '王五', time: '2024-12-24 14:15:00' },
          { taskNo: 'CK20241224004', type: '出库', material: '螺母 M8', quantity: 1500, location: 'B-02-01', status: '进行中', operator: '赵六', time: '2024-12-24 14:10:45' },
          { taskNo: 'PD20241224001', type: '盘点', material: '垫片', quantity: 5000, location: 'C-01-01', status: '进行中', operator: '孙七', time: '2024-12-24 14:05:20' }
        ]
      } catch (error) {
        console.error('获取任务列表失败:', error)
      }
    }
    // åˆ·æ–°æ•°æ®
    const refreshData = () => {
      fetchTaskList()
    }
    // çª—口大小改变时重绘图表
    const handleResize = () => {
      try {
        const refs = [trendChartRef.value, categoryChartRef.value, efficiencyChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.resize()
            }
          }
        })
      } catch (error) {
        console.warn('图表重绘时出错:', error)
      }
    }
    onMounted(() => {
      updateTime()
      timer = setInterval(updateTime, 1000)
      initTrendChart()
      initCategoryChart()
      initEfficiencyChart()
      fetchTaskList()
      window.addEventListener('resize', handleResize)
    })
    onUnmounted(() => {
      clearInterval(timer)
      window.removeEventListener('resize', handleResize)
      // é”€æ¯å›¾è¡¨å®žä¾‹
      try {
        const refs = [trendChartRef.value, categoryChartRef.value, efficiencyChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.dispose()
            }
          }
        })
      } catch (error) {
        console.warn('图表销毁时出错:', error)
      }
    })
    return {
      currentTime,
      metrics,
      taskList,
      trendChartRef,
      categoryChartRef,
      efficiencyChartRef,
      getStatusType,
      refreshData
    }
  }
}
</script>
<style scoped>
.dashboard-container {
  width: 100%;
  height: 100%;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  overflow-y: auto;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 60px;
  background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
  font-size: 24px;
  font-weight: bold;
  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.datetime {
  font-size: 16px;
  color: #4facfe;
}
.nav-menu {
  display: flex;
  gap: 15px;
  padding: 0 20px;
}
.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 25px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #fff;
  text-decoration: none;
  transition: all 0.3s;
  cursor: pointer;
}
.nav-item:hover {
  background: rgba(79, 172, 254, 0.2);
  border-color: #4facfe;
}
.nav-item.active {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  border-color: transparent;
}
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.metrics-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
}
.metric-card {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  transition: all 0.3s;
}
.metric-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.metric-icon {
  width: 60px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  color: #fff;
}
.metric-content {
  flex: 1;
}
.metric-value {
  font-size: 28px;
  font-weight: bold;
  margin-bottom: 5px;
}
.metric-label {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: 5px;
}
.metric-trend {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
}
.metric-trend.up {
  color: #67c23a;
}
.metric-trend.down {
  color: #f56c6c;
}
.charts-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 15px;
}
.chart-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.card-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
}
.chart-container {
  width: 100%;
  height: 250px;
}
.table-row {
  flex: 1;
}
.table-card {
  height: 100%;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
:deep(.el-table) {
  background: transparent !important;
}
:deep(.el-table th) {
  background: rgba(255, 255, 255, 0.1) !important;
  color: #fff !important;
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table td) {
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table tr) {
  background: transparent !important;
}
:deep(.el-table__row:hover td) {
  background: rgba(255, 255, 255, 0.05) !important;
}
:deep(.el-table--enable-row-hover .el-table__body tr:hover > td) {
  background: rgba(255, 255, 255, 0.05) !important;
}
/* å“åº”式适配 */
@media screen and (max-width: 1920px) {
  .metric-value {
    font-size: 24px;
  }
  .chart-container {
    height: 220px;
  }
}
@media screen and (max-width: 1600px) {
  .metrics-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .charts-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .title {
    font-size: 20px;
  }
  .metric-value {
    font-size: 20px;
  }
  .chart-container {
    height: 200px;
  }
}
@media screen and (max-width: 1366px) {
  .dashboard-container {
    padding: 15px;
  }
  .header {
    height: 50px;
    padding: 0 15px;
  }
  .title {
    font-size: 18px;
  }
  .nav-item {
    padding: 8px 20px;
    font-size: 14px;
  }
  .metrics-row {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .metric-card {
    padding: 15px;
  }
  .metric-value {
    font-size: 18px;
  }
  .charts-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  .chart-card {
    padding: 15px;
  }
  .chart-container {
    height: 180px;
  }
}
@media screen and (max-width: 1024px) {
  .metrics-row {
    grid-template-columns: 1fr 1fr;
  }
  .nav-menu {
    flex-wrap: wrap;
  }
  .nav-item {
    flex: 1;
    min-width: 100px;
    justify-content: center;
  }
}
@media screen and (max-width: 768px) {
  .dashboard-container {
    padding: 10px;
  }
  .header {
    flex-direction: column;
    height: auto;
    padding: 10px;
    gap: 10px;
  }
  .title {
    font-size: 16px;
  }
  .datetime {
    font-size: 14px;
  }
  .nav-menu {
    gap: 8px;
    padding: 0;
  }
  .nav-item {
    padding: 6px 12px;
    font-size: 12px;
    flex-direction: column;
    gap: 4px;
  }
  .metrics-row {
    grid-template-columns: 1fr;
  }
  .metric-card {
    padding: 12px;
    flex-direction: column;
    text-align: center;
  }
  .metric-icon {
    width: 45px;
    height: 45px;
  }
  .chart-container {
    height: 150px;
  }
}
</style>
ÏîÄ¿´úÂë/Dashboard/src/views/Inventory.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,831 @@
<template>
  <div class="inventory-container">
    <div class="header">
      <h1 class="title">库存预警中心</h1>
      <div class="datetime">{{ currentTime }}</div>
    </div>
    <div class="nav-menu">
      <router-link to="/dashboard" class="nav-item">综合看板</router-link>
      <router-link to="/warehouse" class="nav-item">仓库监控</router-link>
      <router-link to="/production" class="nav-item">生产监控</router-link>
      <router-link to="/inventory" class="nav-item active">库存预警</router-link>
    </div>
    <div class="main-content">
      <!-- é¢„警统计 -->
      <div class="alert-summary">
        <div class="alert-card critical">
          <div class="alert-icon">
            <el-icon :size="36"><WarningFilled /></el-icon>
          </div>
          <div class="alert-content">
            <div class="alert-count">{{ alertStats.critical }}</div>
            <div class="alert-label">紧急预警</div>
          </div>
        </div>
        <div class="alert-card warning">
          <div class="alert-icon">
            <el-icon :size="36"><Warning /></el-icon>
          </div>
          <div class="alert-content">
            <div class="alert-count">{{ alertStats.warning }}</div>
            <div class="alert-label">预警提醒</div>
          </div>
        </div>
        <div class="alert-card info">
          <div class="alert-icon">
            <el-icon :size="36"><InfoFilled /></el-icon>
          </div>
          <div class="alert-content">
            <div class="alert-count">{{ alertStats.info }}</div>
            <div class="alert-label">信息提醒</div>
          </div>
        </div>
        <div class="alert-card normal">
          <div class="alert-icon">
            <el-icon :size="36"><CircleCheckFilled /></el-icon>
          </div>
          <div class="alert-content">
            <div class="alert-count">{{ alertStats.normal }}</div>
            <div class="alert-label">库存正常</div>
          </div>
        </div>
      </div>
      <!-- åº“存预警列表 -->
      <div class="alert-lists">
        <div class="list-card">
          <div class="list-header">
            <span class="list-title">
              <el-icon><WarningFilled /></el-icon>
              åº“存不足预警
            </span>
            <el-badge :value="lowStockAlerts.length" class="badge" />
          </div>
          <el-table :data="lowStockAlerts" style="width: 100%" max-height="250">
            <el-table-column prop="materialCode" label="物料编码" width="130" />
            <el-table-column prop="materialName" label="物料名称" />
            <el-table-column prop="currentStock" label="当前库存" width="100">
              <template #default="{ row }">
                <span :class="getStockClass(row.currentStock, row.minStock)">{{ row.currentStock }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="minStock" label="最小库存" width="100" />
            <el-table-column prop="maxStock" label="最大库存" width="100" />
            <el-table-column prop="unit" label="单位" width="60" />
            <el-table-column prop="suggestion" label="建议补货" width="100" />
            <el-table-column prop="warehouse" label="仓库" width="100" />
            <el-table-column label="操作" width="100" fixed="right">
              <template #default="{ row }">
                <el-button type="primary" size="small" link>补货</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div class="list-card">
          <div class="list-header">
            <span class="list-title">
              <el-icon><Top /></el-icon>
              åº“存积压预警
            </span>
            <el-badge :value="overStockAlerts.length" class="badge" />
          </div>
          <el-table :data="overStockAlerts" style="width: 100%" max-height="250">
            <el-table-column prop="materialCode" label="物料编码" width="130" />
            <el-table-column prop="materialName" label="物料名称" />
            <el-table-column prop="currentStock" label="当前库存" width="100">
              <template #default="{ row }">
                <span class="over-stock">{{ row.currentStock }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="maxStock" label="最大库存" width="100" />
            <el-table-column prop="overRate" label="超储率" width="100">
              <template #default="{ row }">
                <el-progress type="circle" :percentage="row.overRate" :width="50" :color="'#f56c6c'" />
              </template>
            </el-table-column>
            <el-table-column prop="unit" label="单位" width="60" />
            <el-table-column prop="daysInStock" label="库龄(天)" width="90" />
            <el-table-column prop="warehouse" label="仓库" width="100" />
            <el-table-column label="操作" width="100" fixed="right">
              <template #default="{ row }">
                <el-button type="warning" size="small" link>处理</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </div>
      <!-- åº“存趋势图表 -->
      <div class="charts-row">
        <div class="chart-card">
          <div class="card-title">库存周转率趋势</div>
          <div ref="turnoverChartRef" class="chart-container"></div>
        </div>
        <div class="chart-card">
          <div class="card-title">ABC分类库存占比</div>
          <div ref="abcChartRef" class="chart-container"></div>
        </div>
        <div class="chart-card">
          <div class="card-title">近效期物料预警</div>
          <div ref="expiryChartRef" class="chart-container"></div>
        </div>
      </div>
      <!-- å³å°†è¿‡æœŸç‰©æ–™ -->
      <div class="expiry-list">
        <div class="list-title">即将过期物料清单</div>
        <el-table :data="expiryItems" style="width: 100%" height="200">
          <el-table-column prop="batchNo" label="批次号" width="140" />
          <el-table-column prop="materialName" label="物料名称" />
          <el-table-column prop="quantity" label="数量" width="100" />
          <el-table-column prop="expiryDate" label="有效期" width="120" />
          <el-table-column prop="daysLeft" label="剩余天数" width="100">
            <template #default="{ row }">
              <el-tag :type="getExpiryTagType(row.daysLeft)">{{ row.daysLeft }}天</el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="location" label="库位" width="100" />
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag :type="row.status === '已锁定' ? 'danger' : 'warning'">{{ row.status }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="150" fixed="right">
            <template #default="{ row }">
              <el-button size="small" type="primary" link>优先出库</el-button>
              <el-button size="small" type="danger" link>报废</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
  </div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import { http } from '@/utils/http'
import { formatDateTime } from '@/utils'
export default {
  name: 'Inventory',
  setup() {
    const currentTime = ref('')
    const turnoverChartRef = ref(null)
    const abcChartRef = ref(null)
    const expiryChartRef = ref(null)
    const alertStats = ref({
      critical: 8,
      warning: 23,
      info: 15,
      normal: 1456
    })
    const lowStockAlerts = ref([
      { materialCode: 'M001', materialName: '钢板 Q235', currentStock: 50, minStock: 200, maxStock: 1000, unit: '吨', suggestion: '150吨', warehouse: 'A区' },
      { materialCode: 'M002', materialName: '铝板 6061', currentStock: 80, minStock: 150, maxStock: 800, unit: '吨', suggestion: '70吨', warehouse: 'A区' },
      { materialCode: 'M003', materialName: '螺丝 M8', currentStock: 500, minStock: 2000, maxStock: 10000, unit: '个', suggestion: '1500个', warehouse: 'B区' },
      { materialCode: 'M004', materialName: '螺母 M8', currentStock: 300, minStock: 1500, maxStock: 8000, unit: '个', suggestion: '1200个', warehouse: 'B区' },
      { materialCode: 'M005', materialName: '垫片 Î¦10', currentStock: 200, minStock: 1000, maxStock: 5000, unit: '个', suggestion: '800个', warehouse: 'B区' }
    ])
    const overStockAlerts = ref([
      { materialCode: 'M101', materialName: '轴承 6204', currentStock: 8000, maxStock: 5000, overRate: 160, unit: '个', daysInStock: 180, warehouse: 'C区' },
      { materialCode: 'M102', materialName: '密封圈 O型', currentStock: 15000, maxStock: 8000, overRate: 187, unit: '个', daysInStock: 240, warehouse: 'C区' },
      { materialCode: 'M103', materialName: '润滑油', currentStock: 500, maxStock: 200, overRate: 250, unit: '升', daysInStock: 300, warehouse: 'D区' }
    ])
    const expiryItems = ref([
      { batchNo: 'B20240601', materialName: '化工原料A', quantity: 100, expiryDate: '2025-01-15', daysLeft: 22, location: 'D-01-01', status: '预警' },
      { batchNo: 'B20240701', materialName: '胶粘剂', quantity: 50, expiryDate: '2025-01-20', daysLeft: 27, location: 'D-02-03', status: '预警' },
      { batchNo: 'B20240515', materialName: '防锈油', quantity: 80, expiryDate: '2025-01-10', daysLeft: 17, location: 'D-01-05', status: '已锁定' },
      { batchNo: 'B20240801', materialName: '清洗剂', quantity: 120, expiryDate: '2025-02-01', daysLeft: 39, location: 'D-03-02', status: '预警' },
      { batchNo: 'B20240620', materialName: '涂料', quantity: 200, expiryDate: '2025-01-25', daysLeft: 32, location: 'D-02-01', status: '预警' }
    ])
    let timer
    const updateTime = () => {
      currentTime.value = formatDateTime(new Date())
    }
    const getStockClass = (current, min) => {
      if (current < min * 0.3) return 'critical'
      if (current < min) return 'warning'
      return 'normal'
    }
    const getExpiryTagType = (days) => {
      if (days < 15) return 'danger'
      if (days < 30) return 'warning'
      return 'info'
    }
    // åˆå§‹åŒ–周转率图表
    const initTurnoverChart = () => {
      const chart = echarts.init(turnoverChartRef.value)
      const option = {
        tooltip: { trigger: 'axis' },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '10%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['1月', '2月', '3月', '4月', '5月', '6月'],
          axisLine: { lineStyle: { color: '#fff' } }
        },
        yAxis: {
          type: 'value',
          name: '周转率',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
        },
        series: [
          {
            name: '周转率',
            type: 'line',
            smooth: true,
            data: [4.2, 3.8, 4.5, 5.2, 4.8, 5.5],
            itemStyle: { color: '#4facfe' },
            areaStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(79, 172, 254, 0.5)' },
                { offset: 1, color: 'rgba(79, 172, 254, 0)' }
              ])
            }
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–ABC分类图表
    const initAbcChart = () => {
      const chart = echarts.init(abcChartRef.value)
      const option = {
        tooltip: { trigger: 'item' },
        legend: {
          orient: 'vertical',
          right: '5%',
          top: 'center',
          textStyle: { color: '#fff' }
        },
        series: [
          {
            name: 'ABC分类',
            type: 'pie',
            radius: ['40%', '70%'],
            center: ['40%', '50%'],
            itemStyle: {
              borderRadius: 10,
              borderColor: '#0a0e27',
              borderWidth: 2
            },
            label: { show: false },
            data: [
              { value: 4850, name: 'Aç±»(高价值)', itemStyle: { color: '#f56c6c' } },
              { value: 3280, name: 'Bç±»(中价值)', itemStyle: { color: '#e6a23c' } },
              { value: 4450, name: 'Cç±»(低价值)', itemStyle: { color: '#67c23a' } }
            ]
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–近效期图表
    const initExpiryChart = () => {
      const chart = echarts.init(expiryChartRef.value)
      const option = {
        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '10%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['15天内', '30天内', '60天内', '90天内', '90天以上'],
          axisLine: { lineStyle: { color: '#fff' } },
          axisLabel: { color: '#fff', rotate: 30 }
        },
        yAxis: {
          type: 'value',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
        },
        series: [
          {
            data: [5, 12, 28, 45, 18],
            type: 'bar',
            barWidth: '50%',
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#fa709a' },
                { offset: 1, color: '#fee140' }
              ]),
              borderRadius: [5, 5, 0, 0]
            }
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    const handleResize = () => {
      try {
        const refs = [turnoverChartRef.value, abcChartRef.value, expiryChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.resize()
            }
          }
        })
      } catch (error) {
        console.warn('图表重绘时出错:', error)
      }
    }
    onMounted(() => {
      updateTime()
      timer = setInterval(updateTime, 1000)
      initTurnoverChart()
      initAbcChart()
      initExpiryChart()
      window.addEventListener('resize', handleResize)
    })
    onUnmounted(() => {
      clearInterval(timer)
      window.removeEventListener('resize', handleResize)
      // é”€æ¯å›¾è¡¨å®žä¾‹
      try {
        const refs = [turnoverChartRef.value, abcChartRef.value, expiryChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.dispose()
            }
          }
        })
      } catch (error) {
        console.warn('图表销毁时出错:', error)
      }
    })
    return {
      currentTime,
      alertStats,
      lowStockAlerts,
      overStockAlerts,
      expiryItems,
      turnoverChartRef,
      abcChartRef,
      expiryChartRef,
      getStockClass,
      getExpiryTagType
    }
  }
}
</script>
<style scoped>
.inventory-container {
  width: 100%;
  height: 100%;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  overflow-y: auto;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 60px;
  background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
  font-size: 24px;
  font-weight: bold;
  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.datetime {
  font-size: 16px;
  color: #4facfe;
}
.nav-menu {
  display: flex;
  gap: 15px;
  padding: 0 20px;
}
.nav-item {
  padding: 10px 25px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #fff;
  text-decoration: none;
  transition: all 0.3s;
}
.nav-item:hover {
  background: rgba(79, 172, 254, 0.2);
  border-color: #4facfe;
}
.nav-item.active {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  border-color: transparent;
}
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.alert-summary {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
}
.alert-card {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 20px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.alert-card.critical {
  background: linear-gradient(135deg, rgba(245, 108, 108, 0.2) 0%, rgba(245, 108, 108, 0.05) 100%);
  border-color: rgba(245, 108, 108, 0.3);
}
.alert-card.warning {
  background: linear-gradient(135deg, rgba(230, 162, 60, 0.2) 0%, rgba(230, 162, 60, 0.05) 100%);
  border-color: rgba(230, 162, 60, 0.3);
}
.alert-card.info {
  background: linear-gradient(135deg, rgba(64, 158, 255, 0.2) 0%, rgba(64, 158, 255, 0.05) 100%);
  border-color: rgba(64, 158, 255, 0.3);
}
.alert-card.normal {
  background: linear-gradient(135deg, rgba(103, 194, 58, 0.2) 0%, rgba(103, 194, 58, 0.05) 100%);
  border-color: rgba(103, 194, 58, 0.3);
}
.alert-icon {
  width: 60px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.1);
}
.alert-card.critical .alert-icon {
  color: #f56c6c;
}
.alert-card.warning .alert-icon {
  color: #e6a23c;
}
.alert-card.info .alert-icon {
  color: #409eff;
}
.alert-card.normal .alert-icon {
  color: #67c23a;
}
.alert-content {
  flex: 1;
}
.alert-count {
  font-size: 32px;
  font-weight: bold;
  margin-bottom: 5px;
}
.alert-card.critical .alert-count {
  color: #f56c6c;
}
.alert-card.warning .alert-count {
  color: #e6a23c;
}
.alert-card.info .alert-count {
  color: #409eff;
}
.alert-card.normal .alert-count {
  color: #67c23a;
}
.alert-label {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
}
.alert-lists {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
}
.list-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
}
.list-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
}
.critical {
  color: #f56c6c !important;
}
.warning {
  color: rgba(255, 255, 255, 0.7);
}
.normal {
  color: #67c23a;
}
.over-stock {
  color: #e6a23c;
  font-weight: bold;
}
.charts-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 15px;
}
.chart-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.card-title {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 15px;
}
.chart-container {
  width: 100%;
  height: 200px;
}
.expiry-list {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.expiry-list .list-title {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 15px;
}
:deep(.el-table) {
  background: transparent !important;
}
:deep(.el-table th) {
  background: rgba(255, 255, 255, 0.1) !important;
  color: #fff !important;
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table td) {
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table tr) {
  background: transparent !important;
}
:deep(.el-table__row:hover td) {
  background: rgba(255, 255, 255, 0.05) !important;
}
:deep(.el-badge__content) {
  background-color: #f56c6c;
}
/* å“åº”式适配 */
@media screen and (max-width: 1600px) {
  .alert-summary {
    grid-template-columns: repeat(2, 1fr);
  }
  .charts-row {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media screen and (max-width: 1366px) {
  .inventory-container {
    padding: 15px;
  }
  .header {
    height: 50px;
    padding: 0 15px;
  }
  .title {
    font-size: 18px;
  }
  .alert-summary {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .alert-card {
    padding: 15px;
  }
  .alert-count {
    font-size: 24px;
  }
  .alert-lists {
    grid-template-columns: 1fr;
  }
  .list-card {
    padding: 15px;
  }
  .charts-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  .chart-container {
    height: 180px;
  }
}
@media screen and (max-width: 1024px) {
  .alert-summary {
    grid-template-columns: repeat(2, 1fr);
  }
  .charts-row {
    grid-template-columns: 1fr;
  }
  .list-card {
    padding: 12px;
  }
}
@media screen and (max-width: 768px) {
  .inventory-container {
    padding: 10px;
  }
  .header {
    flex-direction: column;
    height: auto;
    padding: 10px;
    gap: 10px;
  }
  .title {
    font-size: 16px;
  }
  .nav-menu {
    gap: 8px;
    padding: 0;
    flex-wrap: wrap;
  }
  .nav-item {
    padding: 6px 12px;
    font-size: 12px;
  }
  .alert-summary {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }
  .alert-card {
    padding: 12px;
    flex-direction: column;
    text-align: center;
  }
  .alert-icon {
    width: 45px;
    height: 45px;
  }
  .alert-count {
    font-size: 20px;
  }
  .alert-lists {
    grid-template-columns: 1fr;
  }
  .list-card {
    padding: 12px;
  }
  .list-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  .charts-row {
    grid-template-columns: 1fr;
  }
  .chart-container {
    height: 150px;
  }
  .expiry-list {
    padding: 12px;
  }
  :deep(.el-table) {
    font-size: 12px;
  }
  :deep(.el-table th) {
    padding: 8px 0;
  }
  :deep(.el-table td) {
    padding: 8px 0;
  }
}
</style>
ÏîÄ¿´úÂë/Dashboard/src/views/Production.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,837 @@
<template>
  <div class="production-container">
    <div class="header">
      <h1 class="title">生产监控中心</h1>
      <div class="datetime">{{ currentTime }}</div>
    </div>
    <div class="nav-menu">
      <router-link to="/dashboard" class="nav-item">综合看板</router-link>
      <router-link to="/warehouse" class="nav-item">仓库监控</router-link>
      <router-link to="/production" class="nav-item active">生产监控</router-link>
      <router-link to="/inventory" class="nav-item">库存预警</router-link>
    </div>
    <div class="main-content">
      <!-- ç”Ÿäº§æŒ‡æ ‡ -->
      <div class="metrics-row">
        <div class="metric-card" v-for="(metric, index) in metrics" :key="index">
          <div class="metric-icon" :style="{ background: metric.color }">
            <el-icon :size="28">
              <Calendar v-if="index === 0" />
              <SuccessFilled v-else-if="index === 1" />
              <CircleCheckFilled v-else-if="index === 2" />
              <Setting v-else />
            </el-icon>
          </div>
          <div class="metric-info">
            <div class="metric-value">{{ metric.value }}</div>
            <div class="metric-label">{{ metric.label }}</div>
            <div class="metric-target">目标: {{ metric.target }}</div>
          </div>
          <div class="metric-progress" :style="{ width: metric.progress + '%', background: metric.color }"></div>
        </div>
      </div>
      <!-- ç”Ÿäº§çº¿å’Œè®¾å¤‡çŠ¶æ€ -->
      <div class="production-lines">
        <div class="line-card" v-for="(line, index) in productionLines" :key="index">
          <div class="line-header">
            <div class="line-title">
              <span class="line-name">{{ line.name }}</span>
              <el-tag :type="line.status === '运行中' ? 'success' : 'warning'">{{ line.status }}</el-tag>
            </div>
            <div class="line-output">今日产量: {{ line.output }}</div>
          </div>
          <div class="line-devices">
            <div class="device-item" v-for="(device, dIndex) in line.devices" :key="dIndex">
              <div class="device-icon" :class="device.status === '运行' ? 'running' : 'stopped'">
                <el-icon>
                  <Cpu v-if="device.name.includes('CNC')" />
                  <Operation v-else-if="device.name.includes('输送带')" />
                  <Connection v-else />
                </el-icon>
              </div>
              <div class="device-info">
                <div class="device-name">{{ device.name }}</div>
                <div class="device-status">{{ device.status }}</div>
              </div>
              <div class="device-data">
                <div class="data-item">
                  <span class="data-label">温度</span>
                  <span class="data-value">{{ device.temperature }}°C</span>
                </div>
                <div class="data-item">
                  <span class="data-label">转速</span>
                  <span class="data-value">{{ device.speed }}</span>
                </div>
                <div class="data-item">
                  <span class="data-label">负载</span>
                  <span class="data-value">{{ device.load }}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <!-- ç”Ÿäº§ç»Ÿè®¡å›¾è¡¨ -->
      <div class="charts-row">
        <div class="chart-card">
          <div class="card-title">
            <span>产量趋势</span>
            <el-radio-group v-model="trendPeriod" size="small">
              <el-radio-button label="日">日</el-radio-button>
              <el-radio-button label="周">周</el-radio-button>
              <el-radio-button label="月">月</el-radio-button>
            </el-radio-group>
          </div>
          <div ref="productionChartRef" class="chart-container"></div>
        </div>
        <div class="chart-card">
          <div class="card-title">良品率统计</div>
          <div ref="qualityChartRef" class="chart-container"></div>
        </div>
        <div class="chart-card">
          <div class="card-title">设备OEE</div>
          <div ref="oeeChartRef" class="chart-container"></div>
        </div>
      </div>
      <!-- å®žæ—¶ç”Ÿäº§ä»»åŠ¡ -->
      <div class="tasks-panel">
        <div class="panel-title">实时生产任务</div>
        <el-table :data="tasks" style="width: 100%" height="200">
          <el-table-column prop="orderNo" label="生产订单" width="140" />
          <el-table-column prop="product" label="产品名称" />
          <el-table-column prop="planQty" label="计划数量" width="100" />
          <el-table-column prop="completedQty" label="完成数量" width="100" />
          <el-table-column prop="progress" label="进度" width="150">
            <template #default="{ row }">
              <el-progress :percentage="row.progress" :color="getProgressColor(row.progress)" />
            </template>
          </el-table-column>
          <el-table-column prop="line" label="产线" width="100" />
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag :type="getTaskStatusType(row.status)">{{ row.status }}</el-tag>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
  </div>
</template>
<script>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as echarts from 'echarts'
import { http } from '@/utils/http'
import { formatDateTime } from '@/utils'
export default {
  name: 'Production',
  setup() {
    const currentTime = ref('')
    const trendPeriod = ref('日')
    const productionChartRef = ref(null)
    const qualityChartRef = ref(null)
    const oeeChartRef = ref(null)
    const metrics = ref([
      { label: '计划产量', value: '5,000', target: '5,000', progress: 100, icon: 'Calendar', color: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
      { label: '实际产量', value: '4,280', target: '5,000', progress: 85.6, icon: 'Check', color: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
      { label: '良品数量', value: '4,156', target: '4,280', progress: 97.1, icon: 'CircleCheck', color: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
      { label: '设备运行', value: '12/15', target: '15', progress: 80, icon: 'Setting', color: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' }
    ])
    const productionLines = ref([
      {
        name: '生产线 A',
        status: '运行中',
        output: '1,850',
        devices: [
          { name: 'CNC机床-01', status: '运行', temperature: 45, speed: '2400rpm', load: '78%', icon: 'Cpu' },
          { name: 'CNC机床-02', status: '运行', temperature: 48, speed: '2200rpm', load: '82%', icon: 'Cpu' },
          { name: '机器人-01', status: '运行', temperature: 38, speed: '1200rpm', load: '65%', icon: 'Robot' },
          { name: '输送带-01', status: '运行', temperature: 35, speed: '800rpm', load: '55%', icon: 'Operation' }
        ]
      },
      {
        name: '生产线 B',
        status: '运行中',
        output: '1,620',
        devices: [
          { name: 'CNC机床-03', status: '运行', temperature: 50, speed: '2500rpm', load: '85%', icon: 'Cpu' },
          { name: 'CNC机床-04', status: '停机', temperature: 25, speed: '0rpm', load: '0%', icon: 'Cpu' },
          { name: '机器人-02', status: '运行', temperature: 40, speed: '1100rpm', load: '70%', icon: 'Robot' },
          { name: '输送带-02', status: '运行', temperature: 36, speed: '750rpm', load: '60%', icon: 'Operation' }
        ]
      },
      {
        name: '生产线 C',
        status: '维护中',
        output: '810',
        devices: [
          { name: 'CNC机床-05', status: '停机', temperature: 28, speed: '0rpm', load: '0%', icon: 'Cpu' },
          { name: 'CNC机床-06', status: '运行', temperature: 42, speed: '2300rpm', load: '75%', icon: 'Cpu' },
          { name: '机器人-03', status: '运行', temperature: 39, speed: '1150rpm', load: '68%', icon: 'Robot' },
          { name: '输送带-03', status: '运行', temperature: 34, speed: '850rpm', load: '58%', icon: 'Operation' }
        ]
      }
    ])
    const tasks = ref([
      { orderNo: 'PO20241224001', product: '精密齿轮-A型', planQty: 2000, completedQty: 1850, progress: 92.5, line: 'A线', status: '生产中' },
      { orderNo: 'PO20241224002', product: '传动轴-B型', planQty: 1500, completedQty: 1240, progress: 82.7, line: 'B线', status: '生产中' },
      { orderNo: 'PO20241224003', product: '轴承座-C型', planQty: 1000, completedQty: 810, progress: 81, line: 'C线', status: '生产中' },
      { orderNo: 'PO20241224004', product: '连接器-D型', planQty: 500, completedQty: 380, progress: 76, line: 'A线', status: '生产中' }
    ])
    let timer
    const updateTime = () => {
      currentTime.value = formatDateTime(new Date())
    }
    const getProgressColor = (progress) => {
      if (progress >= 90) return '#67c23a'
      if (progress >= 70) return '#409eff'
      if (progress >= 50) return '#e6a23c'
      return '#f56c6c'
    }
    const getTaskStatusType = (status) => {
      const map = {
        '生产中': 'primary',
        '已完成': 'success',
        '待排产': 'info',
        '暂停': 'warning'
      }
      return map[status] || 'info'
    }
    // åˆå§‹åŒ–产量趋势图
    const initProductionChart = () => {
      const chart = echarts.init(productionChartRef.value)
      const option = {
        tooltip: { trigger: 'axis' },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '10%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['08:00', '10:00', '12:00', '14:00', '16:00', '18:00'],
          axisLine: { lineStyle: { color: '#fff' } }
        },
        yAxis: {
          type: 'value',
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
        },
        series: [
          {
            name: '产量',
            type: 'bar',
            data: [420, 680, 850, 920, 780, 630],
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#83bff6' },
                { offset: 1, color: '#188df0' }
              ]),
              borderRadius: [5, 5, 0, 0]
            }
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–良品率图
    const initQualityChart = () => {
      const chart = echarts.init(qualityChartRef.value)
      const option = {
        tooltip: { trigger: 'item' },
        series: [
          {
            name: '良品率',
            type: 'gauge',
            progress: { show: true, width: 18 },
            axisLine: { lineStyle: { width: 18, color: [[1, '#4facfe']] } },
            axisTick: { show: false },
            splitLine: { length: 15, lineStyle: { width: 2, color: '#fff' } },
            axisLabel: { distance: 25, color: '#fff', fontSize: 20 },
            anchor: { show: true, showAbove: true, size: 25, itemStyle: { borderWidth: 10 } },
            detail: {
              valueAnimation: true,
              fontSize: 40,
              offsetCenter: [0, '70%'],
              color: '#fff',
              formatter: '{value}%'
            },
            data: [{ value: 97.1 }]
          }
        ]
      }
      chart.setOption(option)
      return chart
    }
    // åˆå§‹åŒ–OEE图
    const initOeeChart = () => {
      const chart = echarts.init(oeeChartRef.value)
      const option = {
        tooltip: { trigger: 'axis' },
        legend: {
          data: ['可用率', '表现指数', '质量指数'],
          textStyle: { color: '#fff' },
          top: 5
        },
        grid: {
          left: '3%', right: '4%', bottom: '3%', top: '20%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['A线', 'B线', 'C线'],
          axisLine: { lineStyle: { color: '#fff' } }
        },
        yAxis: {
          type: 'value',
          max: 100,
          axisLine: { lineStyle: { color: '#fff' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
        },
        series: [
          { name: '可用率', type: 'bar', data: [92, 88, 75], itemStyle: { color: '#5470c6' } },
          { name: '表现指数', type: 'bar', data: [85, 82, 78], itemStyle: { color: '#91cc75' } },
          { name: '质量指数', type: 'bar', data: [97, 96, 95], itemStyle: { color: '#fac858' } }
        ]
      }
      chart.setOption(option)
      return chart
    }
    const handleResize = () => {
      try {
        const refs = [productionChartRef.value, qualityChartRef.value, oeeChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.resize()
            }
          }
        })
      } catch (error) {
        console.warn('图表重绘时出错:', error)
      }
    }
    onMounted(() => {
      updateTime()
      timer = setInterval(updateTime, 1000)
      initProductionChart()
      initQualityChart()
      initOeeChart()
      window.addEventListener('resize', handleResize)
    })
    onUnmounted(() => {
      clearInterval(timer)
      window.removeEventListener('resize', handleResize)
      // é”€æ¯å›¾è¡¨å®žä¾‹
      try {
        const refs = [productionChartRef.value, qualityChartRef.value, oeeChartRef.value]
        refs.forEach(dom => {
          if (dom) {
            const chart = echarts.getInstanceByDom(dom)
            if (chart) {
              chart.dispose()
            }
          }
        })
      } catch (error) {
        console.warn('图表销毁时出错:', error)
      }
    })
    return {
      currentTime,
      trendPeriod,
      metrics,
      productionLines,
      tasks,
      productionChartRef,
      qualityChartRef,
      oeeChartRef,
      getProgressColor,
      getTaskStatusType
    }
  }
}
</script>
<style scoped>
.production-container {
  width: 100%;
  height: 100%;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  overflow-y: auto;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 60px;
  background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
  font-size: 24px;
  font-weight: bold;
  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.datetime {
  font-size: 16px;
  color: #4facfe;
}
.nav-menu {
  display: flex;
  gap: 15px;
  padding: 0 20px;
}
.nav-item {
  padding: 10px 25px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #fff;
  text-decoration: none;
  transition: all 0.3s;
}
.nav-item:hover {
  background: rgba(79, 172, 254, 0.2);
  border-color: #4facfe;
}
.nav-item.active {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  border-color: transparent;
}
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.metrics-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
}
.metric-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  overflow: hidden;
}
.metric-icon {
  width: 50px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  color: #fff;
  flex-shrink: 0;
}
.metric-info {
  flex: 1;
}
.metric-value {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 3px;
}
.metric-label {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: 3px;
}
.metric-target {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}
.metric-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  transition: width 0.5s;
}
.production-lines {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 15px;
}
.line-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.line-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.line-title {
  display: flex;
  align-items: center;
  gap: 10px;
}
.line-name {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
}
.line-output {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
}
.line-devices {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.device-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: 8px;
}
.device-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.5);
}
.device-icon.running {
  background: rgba(103, 194, 58, 0.2);
  color: #67c23a;
}
.device-icon.stopped {
  background: rgba(245, 108, 108, 0.2);
  color: #f56c6c;
}
.device-info {
  flex: 1;
}
.device-name {
  font-size: 14px;
  font-weight: bold;
  color: #fff;
  margin-bottom: 3px;
}
.device-status {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}
.device-data {
  display: flex;
  gap: 15px;
}
.data-item {
  text-align: center;
}
.data-label {
  font-size: 10px;
  color: rgba(255, 255, 255, 0.5);
  display: block;
  margin-bottom: 2px;
}
.data-value {
  font-size: 12px;
  color: #4facfe;
  font-weight: bold;
}
.charts-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 15px;
}
.chart-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.card-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
}
.chart-container {
  width: 100%;
  height: 220px;
}
.tasks-panel {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.panel-title {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 15px;
}
:deep(.el-table) {
  background: transparent !important;
}
:deep(.el-table th) {
  background: rgba(255, 255, 255, 0.1) !important;
  color: #fff !important;
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table td) {
  border-color: rgba(255, 255, 255, 0.1) !important;
}
:deep(.el-table tr) {
  background: transparent !important;
}
:deep(.el-table__row:hover td) {
  background: rgba(255, 255, 255, 0.05) !important;
}
:deep(.el-progress__text) {
  color: #fff !important;
}
:deep(.el-progress-bar__outer) {
  background: rgba(255, 255, 255, 0.1) !important;
}
/* å“åº”式适配 */
@media screen and (max-width: 1600px) {
  .metrics-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .production-lines {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media screen and (max-width: 1366px) {
  .production-container {
    padding: 15px;
  }
  .header {
    height: 50px;
    padding: 0 15px;
  }
  .title {
    font-size: 18px;
  }
  .metrics-row {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .metric-card {
    padding: 15px;
  }
  .metric-value {
    font-size: 20px;
  }
  .production-lines {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .line-card {
    padding: 15px;
  }
  .charts-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  .chart-container {
    height: 180px;
  }
}
@media screen and (max-width: 1024px) {
  .production-lines {
    grid-template-columns: 1fr;
  }
  .device-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .device-data {
    width: 100%;
    justify-content: space-between;
  }
}
@media screen and (max-width: 768px) {
  .production-container {
    padding: 10px;
  }
  .header {
    flex-direction: column;
    height: auto;
    padding: 10px;
  }
  .title {
    font-size: 16px;
  }
  .nav-menu {
    gap: 8px;
    padding: 0;
    flex-wrap: wrap;
  }
  .nav-item {
    padding: 6px 12px;
    font-size: 12px;
  }
  .metrics-row {
    grid-template-columns: 1fr;
  }
  .metric-card {
    padding: 12px;
  }
  .metric-icon {
    width: 45px;
    height: 45px;
  }
  .metric-value {
    font-size: 18px;
  }
  .production-lines {
    grid-template-columns: 1fr;
  }
  .line-card {
    padding: 12px;
  }
  .device-item {
    padding: 10px;
  }
  .device-icon {
    width: 35px;
    height: 35px;
  }
  .device-data {
    flex-wrap: wrap;
    gap: 10px;
  }
  .chart-container {
    height: 150px;
  }
  .tasks-panel {
    padding: 12px;
  }
}
</style>
ÏîÄ¿´úÂë/Dashboard/src/views/Warehouse.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,582 @@
<template>
  <div class="warehouse-container">
    <div class="header">
      <h1 class="title">仓库监控中心</h1>
      <div class="datetime">{{ currentTime }}</div>
    </div>
    <div class="nav-menu">
      <router-link to="/dashboard" class="nav-item">综合看板</router-link>
      <router-link to="/warehouse" class="nav-item active">仓库监控</router-link>
      <router-link to="/production" class="nav-item">生产监控</router-link>
      <router-link to="/inventory" class="nav-item">库存预警</router-link>
    </div>
    <div class="main-content">
      <!-- ä»“库区域状态 -->
      <div class="warehouse-areas">
        <div class="area-card" v-for="area in areas" :key="area.name">
          <div class="area-header">
            <span class="area-name">{{ area.name }}</span>
            <span class="area-capacity">容量: {{ area.used }}/{{ area.total }}</span>
          </div>
          <div class="area-progress">
            <div class="progress-bar" :style="{ width: (area.used / area.total * 100) + '%', background: area.color }"></div>
          </div>
          <div class="area-stats">
            <div class="stat-item">
              <span class="stat-label">货架数</span>
              <span class="stat-value">{{ area.shelves }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">库位数</span>
              <span class="stat-value">{{ area.positions }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">利用率</span>
              <span class="stat-value">{{ (area.used / area.total * 100).toFixed(1) }}%</span>
            </div>
          </div>
        </div>
      </div>
      <!-- ä»“库布局图 -->
      <div class="warehouse-layout">
        <div class="card-title">仓库实时布局</div>
        <div class="layout-grid">
          <div v-for="zone in 8" :key="zone" class="warehouse-zone">
            <div class="zone-label">{{ String.fromCharCode(64 + zone) }}区</div>
            <div class="zone-slots">
              <div v-for="slot in 12" :key="slot"
                   class="slot"
                   :class="getSlotStatus(zone, slot)">
                {{ String.fromCharCode(64 + zone) }}-{{ Math.ceil(slot / 4) }}-{{ slot % 4 || 4 }}
              </div>
            </div>
          </div>
        </div>
        <div class="legend">
          <div class="legend-item">
            <span class="legend-color occupied"></span>
            <span>已占用</span>
          </div>
          <div class="legend-item">
            <span class="legend-color empty"></span>
            <span>空闲</span>
          </div>
          <div class="legend-item">
            <span class="legend-color reserved"></span>
            <span>预留</span>
          </div>
          <div class="legend-item">
            <span class="legend-color locked"></span>
            <span>锁定</span>
          </div>
        </div>
      </div>
      <!-- æ¸©æ¹¿åº¦ç›‘控 -->
      <div class="env-monitor">
        <div class="env-card" v-for="(env, index) in envData" :key="index">
          <div class="env-icon" :style="{ background: env.gradient }">
            <el-icon :size="28">
              <Monitor v-if="index < 3" />
              <Warning v-else />
            </el-icon>
          </div>
          <div class="env-content">
            <div class="env-label">{{ env.location }}</div>
            <div class="env-value">
              <span class="temp">{{ env.temperature }}°C</span>
              <span class="humidity">{{ env.humidity }}%</span>
            </div>
            <div class="env-status" :class="env.status === '正常' ? 'normal' : 'warning'">
              {{ env.status }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { http } from '@/utils/http'
import { formatDateTime } from '@/utils'
export default {
  name: 'Warehouse',
  setup() {
    const currentTime = ref('')
    const areas = ref([
      { name: '原材料区', used: 2150, total: 3000, shelves: 50, positions: 3000, color: '#5470c6' },
      { name: '半成品区', used: 1680, total: 2500, shelves: 42, positions: 2500, color: '#91cc75' },
      { name: '成品区', used: 2890, total: 4000, shelves: 60, positions: 4000, color: '#fac858' },
      { name: '辅料区', used: 980, total: 1500, shelves: 30, positions: 1500, color: '#ee6666' }
    ])
    const envData = ref([
      { location: 'A区-原料仓', temperature: 22.5, humidity: 55, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
      { location: 'B区-半成品仓', temperature: 23.8, humidity: 58, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
      { location: 'C区-成品仓', temperature: 21.2, humidity: 52, status: '正常', icon: 'Thermometer', gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
      { location: 'D区-辅料仓', temperature: 26.5, humidity: 68, status: '预警', icon: 'Warning', gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' }
    ])
    let timer
    const updateTime = () => {
      currentTime.value = formatDateTime(new Date())
    }
    // æ¨¡æ‹Ÿåº“位状态
    const getSlotStatus = (zone, slot) => {
      const random = (zone * slot) % 10
      if (random < 6) return 'occupied'
      if (random < 8) return 'empty'
      if (random < 9) return 'reserved'
      return 'locked'
    }
    // èŽ·å–ä»“åº“æ•°æ®
    const fetchWarehouseData = async () => {
      try {
        // å®žé™…使用时调用接口
        // const res = await http.get('/wms/warehouse/status')
      } catch (error) {
        console.error('获取仓库数据失败:', error)
      }
    }
    onMounted(() => {
      updateTime()
      timer = setInterval(updateTime, 1000)
      fetchWarehouseData()
    })
    onUnmounted(() => {
      clearInterval(timer)
    })
    return {
      currentTime,
      areas,
      envData,
      getSlotStatus
    }
  }
}
</script>
<style scoped>
.warehouse-container {
  width: 100%;
  height: 100%;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 15px;
  overflow-y: auto;
}
.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 60px;
  background: linear-gradient(90deg, rgba(30, 58, 138, 0.5) 0%, rgba(30, 58, 138, 0.1) 100%);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
  font-size: 24px;
  font-weight: bold;
  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.datetime {
  font-size: 16px;
  color: #4facfe;
}
.nav-menu {
  display: flex;
  gap: 15px;
  padding: 0 20px;
}
.nav-item {
  padding: 10px 25px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #fff;
  text-decoration: none;
  transition: all 0.3s;
}
.nav-item:hover {
  background: rgba(79, 172, 254, 0.2);
  border-color: #4facfe;
}
.nav-item.active {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  border-color: transparent;
}
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.warehouse-areas {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
}
.area-card {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.area-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 15px;
}
.area-name {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
}
.area-capacity {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.6);
}
.area-progress {
  height: 8px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 4px;
  margin-bottom: 15px;
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  border-radius: 4px;
  transition: width 0.5s;
}
.area-stats {
  display: flex;
  justify-content: space-between;
}
.stat-item {
  text-align: center;
}
.stat-label {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
  display: block;
  margin-bottom: 5px;
}
.stat-value {
  font-size: 16px;
  font-weight: bold;
  color: #fff;
}
.warehouse-layout {
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.card-title {
  font-size: 16px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 15px;
}
.layout-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
  margin-bottom: 20px;
}
.warehouse-zone {
  background: rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  padding: 15px;
}
.zone-label {
  text-align: center;
  font-size: 14px;
  font-weight: bold;
  color: #4facfe;
  margin-bottom: 10px;
}
.zone-slots {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.slot {
  padding: 8px 4px;
  font-size: 10px;
  text-align: center;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
  transition: all 0.3s;
  cursor: pointer;
}
.slot:hover {
  transform: scale(1.05);
}
.slot.occupied {
  background: rgba(84, 112, 198, 0.5);
  border-color: #5470c6;
}
.slot.empty {
  background: rgba(67, 233, 123, 0.2);
  border-color: #43e97b;
}
.slot.reserved {
  background: rgba(250, 200, 88, 0.3);
  border-color: #fac858;
}
.slot.locked {
  background: rgba(238, 102, 102, 0.3);
  border-color: #ee6666;
}
.legend {
  display: flex;
  justify-content: center;
  gap: 30px;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
}
.legend-color {
  width: 20px;
  height: 20px;
  border-radius: 4px;
}
.legend-color.occupied {
  background: rgba(84, 112, 198, 0.5);
  border: 1px solid #5470c6;
}
.legend-color.empty {
  background: rgba(67, 233, 123, 0.2);
  border: 1px solid #43e97b;
}
.legend-color.reserved {
  background: rgba(250, 200, 88, 0.3);
  border: 1px solid #fac858;
}
.legend-color.locked {
  background: rgba(238, 102, 102, 0.3);
  border: 1px solid #ee6666;
}
.env-monitor {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
}
.env-card {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
}
.env-icon {
  width: 50px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  color: #fff;
}
.env-content {
  flex: 1;
}
.env-label {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: 5px;
}
.env-value {
  display: flex;
  gap: 15px;
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 5px;
}
.env-value .temp {
  color: #f5576c;
}
.env-value .humidity {
  color: #4facfe;
}
.env-status {
  font-size: 12px;
}
.env-status.normal {
  color: #67c23a;
}
.env-status.warning {
  color: #e6a23c;
}
/* å“åº”式适配 */
@media screen and (max-width: 1600px) {
  .warehouse-areas {
    grid-template-columns: repeat(2, 1fr);
  }
  .layout-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media screen and (max-width: 1366px) {
  .warehouse-areas {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .layout-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .env-monitor {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media screen and (max-width: 1024px) {
  .warehouse-areas {
    grid-template-columns: 1fr 1fr;
  }
  .layout-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .warehouse-zone {
    padding: 10px;
  }
  .zone-slots {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media screen and (max-width: 768px) {
  .warehouse-container {
    padding: 10px;
  }
  .header {
    flex-direction: column;
    height: auto;
    padding: 10px;
    gap: 10px;
  }
  .title {
    font-size: 16px;
  }
  .warehouse-areas {
    grid-template-columns: 1fr;
  }
  .area-card {
    padding: 12px;
  }
  .layout-grid {
    grid-template-columns: 1fr;
  }
  .zone-slots {
    grid-template-columns: repeat(2, 1fr);
    gap: 5px;
  }
  .slot {
    font-size: 9px;
    padding: 6px 2px;
  }
  .legend {
    flex-wrap: wrap;
    gap: 15px;
  }
  .env-monitor {
    grid-template-columns: 1fr;
  }
  .env-card {
    padding: 12px;
  }
}
</style>
ÏîÄ¿´úÂë/Dashboard/vite.config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src')
    }
  },
  server: {
    port: 3000,
    open: true,
    proxy: {
      '^/api/': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/OrderStockTake.vue
@@ -185,7 +185,6 @@
<script setup>
import { ref, reactive, onMounted, nextTick, watch, defineEmits, computed } from "vue";
import { ElMessage, ElTag, ElMessageBox } from "element-plus";
import { Search, Check, Return } from "@element-plus/icons-vue";
import VolBox from "@/components/basic/VolBox.vue";
import http from "@/api/http";
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/printView.vue
@@ -19,6 +19,10 @@
                                    <span class="label">供应商编码</span>
                                    <span class="value full-width">{{ item.suplierCode }}</span>
                                </div>
                                <div class="column">
                                    <span class="label">数量/总数</span>
                                    <span class="value full-width">{{ item.quantity }}</span>
                                </div>
                            </div>
                            <div class="row dual-column">
@@ -37,10 +41,7 @@
                                    <span class="label">规格</span>
                                    <span class="value full-width">{{ item.materialSpec }}</span>
                                </div>
                                <div class="column">
                                    <span class="label">数量/总数</span>
                                    <span class="value full-width">{{ item.quantity }}</span>
                                </div>
                            </div>
                            <div class="row dual-column">
@@ -89,6 +90,10 @@
                                        <span class="label-preview">供应商编码</span>
                                        <span class="value-preview full-width">{{ item.suplierCode }}</span>
                                    </div>
                                    <div class="column-preview">
                                        <span class="label-preview">数量/总数</span>
                                        <span class="value-preview full-width">{{ item.quantity }}</span>
                                    </div>
                                </div>
                                <div class="row-preview dual-column">
@@ -107,10 +112,7 @@
                                        <span class="label-preview">规格</span>
                                        <span class="value-preview full-width">{{ item.materialSpec }}</span>
                                    </div>
                                    <div class="column-preview">
                                        <span class="label-preview">数量/总数</span>
                                        <span class="value-preview full-width">{{ item.quantity }}</span>
                                    </div>
                                </div>
                                <div class="row-preview dual-column">
@@ -305,7 +307,7 @@
                  .row {
                    display: flex !important;
                    margin-bottom: 1.2mm !important;
                    min-height: 6.5mm !important;
                    min-height: 5.5mm !important;
                    align-items: stretch !important;
                  }
                  
@@ -318,15 +320,11 @@
                    flex: 1 !important;
                    display: flex !important;
                    align-items: center !important;
                    min-height: 5mm !important;
                    min-height: 4mm !important;
                  }
                  
                  .column:first-child {
                    margin-right: 2.5mm !important;
                  }
                  
                  .label {
                    flex: 0 0 8mm !important;
                    white-space: nowrap !important;
                    font-size: 2.5mm !important;
                    line-height: 1.3 !important;
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/stock/stockView.js
@@ -281,15 +281,11 @@
                      return;
                    }
                    // æž„造请求参数:
                    // 1. è¯·æ±‚体:直接传递stockViews数组(对应后端List<StockViewDTO>)
                    // 2. outStation:通过查询参数传递(因后端未指定[FromBody]接收该参数)
                    const requestBody = stockViews; // ç›´æŽ¥ä¼ é€’选中数组(确保数组内对象结构与StockViewDTO一致)
                    const requestBody = stockViews;
                    const outStation = formData.outStation;
                    // æŽ¥å£è¯·æ±‚:Post方式,请求体传stockViews数组,outStation拼到URL查询参数
                    try {
                      // æ‹¼æŽ¥æŸ¥è¯¢å‚数(如果outStation有特殊字符,可使用encodeURIComponent处理)
                      const url = `api/Task/TakeOutbound?outStation=${encodeURIComponent(outStation)}`;
                      
                      const x = await this.http.post(
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/printForm.vue
@@ -32,25 +32,25 @@
              />
            </el-form-item>
            <el-form-item label="规格" prop="specification">
            <el-form-item label="规格" prop="materialSpec">
              <el-input
                v-model="printForm.specification"
                v-model="printForm.materialSpec"
                placeholder="请输入物料规格"
                clearable
              />
            </el-form-item>
            <el-form-item label="批号" prop="batchNumber">
            <el-form-item label="批号" prop="barcode">
              <el-input
                v-model="printForm.batchNumber"
                v-model="printForm.barcode"
                placeholder="请输入批号(生成二维码用)"
                clearable
              />
            </el-form-item>
            <el-form-item label="厂区" prop="factory">
            <el-form-item label="厂区" prop="factoryArea">
              <el-input
                v-model="printForm.factory"
                v-model="printForm.factoryArea"
                placeholder="请输入厂区名称"
                clearable
              />
@@ -59,33 +59,33 @@
          <!-- ç¬¬äºŒåˆ— -->
          <el-col :span="12">
            <el-form-item label="供应商编码" prop="supplierCode">
            <el-form-item label="供应商编码" prop="suplierCode">
              <el-input
                v-model="printForm.supplierCode"
                v-model="printForm.suplierCode"
                placeholder="请输入供应商编码"
                clearable
              />
            </el-form-item>
            <el-form-item label="采购单号" prop="purchaseOrderNo">
            <el-form-item label="采购单号" prop="pruchaseOrderNo">
              <el-input
                v-model="printForm.purchaseOrderNo"
                v-model="printForm.pruchaseOrderNo"
                placeholder="请输入采购单号"
                clearable
              />
            </el-form-item>
            <el-form-item label="数量/总数" prop="quantityTotal">
            <el-form-item label="数量/总数" prop="quantity">
              <el-input
                v-model="printForm.quantityTotal"
                v-model="printForm.quantity"
                placeholder="例:100/5000"
                clearable
              />
            </el-form-item>
            <el-form-item label="批次" prop="batch">
            <el-form-item label="批次" prop="batchNo">
              <el-input
                v-model="printForm.batch"
                v-model="printForm.batchNo"
                placeholder="请输入批次号"
                clearable
              />
@@ -141,14 +141,14 @@
      // è¡¨å•数据(对应printView所需的参数)
      printForm: {
        materialCode: "", // æ–™å·
        supplierCode: "", // ä¾›åº”商编码
        suplierCode: "", // ä¾›åº”商编码
        materialName: "", // å“å
        purchaseOrderNo: "", // é‡‡è´­å•号
        specification: "", // è§„æ ¼
        quantityTotal: "", // æ•°é‡/总数
        batchNumber: "", // æ‰¹å·ï¼ˆäºŒç»´ç å†…容)
        batch: "", // æ‰¹æ¬¡
        factory: "", // åŽ‚åŒº
        pruchaseOrderNo: "", // é‡‡è´­å•号
        materialSpec: "", // è§„æ ¼
        quantity: "", // æ•°é‡/总数
        barcode: "", // æ‰¹å·ï¼ˆäºŒç»´ç å†…容)
        batchNo: "", // æ‰¹æ¬¡
        factoryArea: "", // åŽ‚åŒº
        date: "", // æ—¥æœŸï¼ˆé»˜è®¤å½“前日期)
      },
      // æ‰“印份数(支持批量生成多条相同数据)
@@ -157,7 +157,7 @@
      formRules: {
        materialCode: [{ required: true, message: "请输入料号", trigger: "blur" }],
        materialName: [{ required: true, message: "请输入品名", trigger: "blur" }],
        batchNumber: [{ required: true, message: "请输入批号", trigger: "blur" }],
        barcode: [{ required: true, message: "请输入批号", trigger: "blur" }],
        date: [{ required: true, message: "请选择日期", trigger: "change" }],
      },
    };
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/CodeChunks.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/SemanticSymbols.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/StockStatusEmun.cs
@@ -107,6 +107,9 @@
        [Description("已清理")]
        å·²æ¸…理 = 33,
        [Description("手动解锁")]
        æ‰‹åŠ¨è§£é” = 66,
        [Description("过期")]
        è¿‡æœŸ =98,
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs
@@ -134,7 +134,6 @@
                        OrderNo = inboundOrder.InboundOrderNo,
                        BusinessType = inboundOrder.BusinessType,
                        ValidDate = inboundOrder.BusinessType == BusinessTypeEnum.外部仓库调智仓.ToString() ? item.ValidDate : datevaliDate == null ? null : Convert.ToDateTime(DateTime.Now).AddDays(Convert.ToDouble(datevaliDate.ValidityDays)),
                        //ValidDate = datevaliDate == null ? null : DateTime.Now.AddDays(datevaliDate.ValidityDays),
                    });
                    item.ReceiptQuantity = item.BarcodeQty;
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -2483,7 +2483,7 @@
                        Operator = App.User.UserName,
                        lineNo = outboundOrderDetail.lineNo,
                        WarehouseCode = outboundOrderDetail.WarehouseCode ?? "无",
                        BarcodeQty = outboundOrderDetail.NoStockOutQty,
                        BarcodeQty = outboundOrderDetail.BarcodeQty,
                        BarcodeUnit = outboundOrderDetail.BarcodeUnit,
                        BatchNo = outboundOrderDetail.BatchNo
                    };
@@ -2618,6 +2618,8 @@
                              }).ToList()
                          }).ToList();
                    outfeedmodel.details.AddRange(groupdata);
                    _outStockLockInfoService.DeleteData(detailLocks);
                }
                //存储回传参数,保证异常手动回传
                Dt_InterfaceLog interfaceLog = new Dt_InterfaceLog
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -549,7 +549,7 @@
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            // åŸºç¡€æŸ¥è¯¢æ¡ä»¶ï¼šç‰©æ–™ç¼–号、批次号(如果提供)、库存数量>0
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0);
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0 && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä¾›åº”å•†ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Jobs/InventoryLockJob.cs
@@ -4,37 +4,25 @@
using WIDESEA_Common.StockEnum;
using WIDESEA_Model.Models;
using WIDESEA_Model.Models.Basic;
using WIDESEA_WMSServer.Jobs;
namespace WIDESEA_WMSServer.Jobs
namespace WIDESEA_WMSServer
{
    [DisallowConcurrentExecution]
    public class InventoryLockJob : IJob
    {
        private readonly ILogger<ErpJob> _logger;
        private readonly ISqlSugarClient _db;
        public InventoryLockJob(ILogger<ErpJob> logger, ISqlSugarClient db )
        public InventoryLockJob(ISqlSugarClient db)
        {
            _logger = logger;
            _db = db;
        }
        public Task Execute(IJobExecutionContext context)
        {
            // ç¬¬ä¸€æ­¥ï¼šæ›´æ–°ValidDate的原生SQL(SQL Server)
    //        string updateSql = @"
    //UPDATE s
    //SET s.ValidDate = DATEADD(DAY, m.ValidityDays, s.CreateDate)
    //FROM Dt_StockInfoDetail s
    //INNER JOIN Dt_MaterialExpirationDate m ON SUBSTRING(s.MaterielCode, 1, 6) = m.MaterialCode
    //WHERE s.ValidDate IS NULL";
    //        int updateValidDateResult = _db.Ado.ExecuteCommand(updateSql);
            // ç¬¬äºŒæ­¥ï¼šæ›´æ–°è¿‡æœŸçŠ¶æ€çš„åŽŸç”ŸSQL
            string updateStatusSql = @"
    UPDATE Dt_StockInfoDetail
    SET Status = 98
    WHERE ValidDate IS NOT NULL AND ValidDate < GETDATE()";
                                        UPDATE Dt_StockInfoDetail
                                        SET Status = 98
                                        WHERE  ValidDate IS NOT NULL AND ValidDate < GETDATE()
                                        AND Status = 6";
            int updateStatusResult = _db.Ado.ExecuteCommand(updateStatusSql);
            return Task.CompletedTask;
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Jobs/QuartzJobMildd.cs
@@ -35,6 +35,19 @@
                         Name = "AgvTaskJob",
                         TriggerType = 0
                    },
                     new TasksQz()
                    {
                         Id = 2,
                         AssemblyName = "WIDESEA_WMSServer",
                         ClassName = "InventoryLockJob",
                         CreateTime = DateTime.Now,
                         IntervalSecond = 3,
                         IsDeleted = false,
                         IsStart = false,
                         JobGroup = "WIDESEA_WMSServer",
                         Name = "InventoryLockJob",
                         TriggerType = 0
                    },
                };
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
@@ -188,6 +188,7 @@
});
builder.Services.AddSingleton<IJobFactory, JobFactory>();
builder.Services.AddTransient<AgvTaskJob>();//Job使用瞬时依赖注入
builder.Services.AddTransient<InventoryLockJob>();//Job使用瞬时依赖注入
builder.Services.AddSingleton<ISchedulerCenter, SchedulerCenterServer>();
builder.Services.AddQuartz(q =>
@@ -202,13 +203,13 @@
        .WithIdentity("ErpJob-trigger")
        .WithCronSchedule("0 0 10,14,20 * * ?"));
    var inventoryLockJobKey = new JobKey("InventoryLockJob");
    q.AddJob<InventoryLockJob>(opts => opts.WithIdentity(inventoryLockJobKey));
    //var inventoryLockJobKey = new JobKey("InventoryLockJob");
    //q.AddJob<InventoryLockJob>(opts => opts.WithIdentity(inventoryLockJobKey));
    q.AddTrigger(opts => opts
        .ForJob(inventoryLockJobKey)
        .WithIdentity("InventoryLockJob-trigger")
        .WithCronSchedule("0 0/10 * * * ?")); // æ¯10分钟执行一次
    //q.AddTrigger(opts => opts
    //    .ForJob(inventoryLockJobKey)
    //    .WithIdentity("InventoryLockJob-trigger")
    //    .WithCronSchedule("0 0/10 * * * ?")); // æ¯10分钟执行一次
});
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);