1.计量器具台账上传附件报错
2.质量拉的数据不对(未明确)
3.计量器具台账逾期的做标红提醒
4.设备保养定时任务和记录要加上具体的保养内容
5.质量要区分质检规则抽检还是全检,抽检的话是抽多少百分比
6.供应商管理东西太少了,没有资质文件啊这些东西(是不是可以参考pro)
7.采购审批把人从李莹莹改成龙红星
已添加4个文件
已修改42个文件
3933 ■■■■ 文件已修改
doc/20260618_home_quality_query_fix.md 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260618_quick_inspect_sample.md 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/quality_inspect_rule.md 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/measuringInstrumentFile.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/HomeTab.vue 758 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/index.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/measurementEquipment/index.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/quickCheckDia.vue 154 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricMaintenance/index0.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/quickCheckDia.vue 150 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/quickCheckDia.vue 150 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/PSIDataAnalysis/index.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/dataDashboard/index.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/PanelHeader.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/center-center.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/center-top.vue 134 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/left-top.vue 130 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/components/right-top.vue 186 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/qualityAnalysis/index.vue 351 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260618_home_quality_query_fix.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
# é¦–页质量类查询修复与优化前端联调文档
> ä¿®å¤ `home` æ¨¡å—下质量类接口的若干问题,并优化查询性能。
> æœ¬æ¬¡æ”¹åЍ**只涉及响应数值**,**接口路径、请求参数、响应字段名均未变化**。
## æ¶‰åŠé¡µé¢
- é¦–页 / æ•°æ®çœ‹æ¿ - è´¨é‡åˆ†æžæ¨¡å—
- é¦–页 / æ•°æ®çœ‹æ¿ - è´¨é‡æ£€éªŒæ•°é‡å¡ç‰‡
- é¦–页 / æ•°æ®çœ‹æ¿ - ä¸åˆæ ¼é¢„è­¦
- é¦–页 / æ•°æ®çœ‹æ¿ - å®Œæˆæ£€éªŒæ•°è¶‹åŠ¿
- é¦–页 / æ•°æ®çœ‹æ¿ - ä¸åˆæ ¼äº§å“æŽ’名
- é¦–页 / æ•°æ®çœ‹æ¿ - åŽŸææ–™/过程/出厂检测合格率
- é¦–页 / æ•°æ®çœ‹æ¿ - è´¨é‡ç»Ÿè®¡ (周/月/å­£)
## å—影响接口一览
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /home/qualityInspectionCount | è´¨é‡æ£€éªŒæ•°é‡ (语义有变更) |
| GET | /home/nonComplianceWarning | è¿‘七天不合格预警 |
| GET | /home/completedInspectionCount | è¿‘七天完成检验数 |
| GET | /home/unqualifiedProductRanking | ä¸åˆæ ¼äº§å“æŽ’名 (口径变更) |
| GET | /home/rawMaterialDetection | åŽŸææ–™æ£€æµ‹åˆæ ¼çŽ‡ |
| GET | /home/processDetection | è¿‡ç¨‹æ£€æµ‹åˆæ ¼çއ |
| GET | /home/factoryDetection | å‡ºåŽ‚æ£€æµ‹åˆæ ¼çŽ‡ |
| GET | /home/qualityInspectionStatistics | è´¨é‡ç»Ÿè®¡ (周/月/å­£) |
## ä¸€ã€`/home/qualityInspectionCount` (语义变更,前端需关注)
### å˜æ›´åŽŸå› 
- åŽŸå®žçŽ°æŠŠå…¨è¡¨åŠ è½½åˆ°å†…å­˜è¿›è¡Œæ±‚å’Œï¼Œä¸” `totalCountGrowthRate` ç”¨ *截止今日累计 vs æˆªæ­¢æ˜¨æ—¥ç´¯è®¡* è®¡ç®—,得到的实质是 `today_only / yesterday_cumulative`,无业务意义。
- åŽŸå®žçŽ°å¯¹ `checkTime` ç”¨ `eq(checkTime, "yyyy-MM-dd")`,若数据库 `check_time` å«éž 0 æ—¶åˆ†ç§’会全部漏匹配,导致 `todayPendingCount`/`todayCompletedCount` å¶å‘为 0。
### æ–°å£å¾„
| å­—段 | æ—§å£å¾„ | æ–°å£å¾„ |
|------|--------|--------|
| `totalCount` | æˆªæ­¢ä»Šæ—¥æ‰€æœ‰ `quantity` ç´¯åŠ  | ä¸å˜ï¼Œä»æ˜¯åŽ†å²ç´¯è®¡ (改用 SQL `SUM` èšåˆ) |
| `totalCountGrowthRate` | (今日累计 - æ˜¨æ—¥ç´¯è®¡) / æ˜¨æ—¥ç´¯è®¡ Ã— 100 | **(今日单日量 - æ˜¨æ—¥å•日量) / æ˜¨æ—¥å•日量 Ã— 100** |
| `todayPendingCount` | `inspect_state=0` ä¸” `check_time = ä»Šå¤© 00:00:00` | `inspect_state=0` ä¸” `check_time` âˆˆ \[今天 00:00, æ˜Žå¤© 00:00) |
| `todayPendingCountGrowthRate` | (今日待完成 - æ˜¨æ—¥å¾…完成) / æ˜¨æ—¥å¾…完成 Ã— 100 | ä¸å˜ï¼Œä½†åˆ†å­åˆ†æ¯éƒ½æ˜¯çœŸæ­£"当日量" |
| `todayCompletedCount` | åŒä¸Š | åŒä¸Š |
| `todayCompletedCountGrowthRate` | åŒä¸Š | åŒä¸Š |
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "totalCount": 12345.00,
    "totalCountGrowthRate": 8.50,
    "todayPendingCount": 12.00,
    "todayPendingCountGrowthRate": -20.00,
    "todayCompletedCount": 80.00,
    "todayCompletedCountGrowthRate": 15.50
  }
}
```
### å‰ç«¯ä¿®æ”¹ç‚¹
`totalCountGrowthRate` çš„视觉提示文案建议从 "总检验数同比增长" æ”¹ä¸º "今日新增 vs æ˜¨æ—¥æ–°å¢ž",避免误解为累计同比。
```html
<el-tooltip content="今日单日检验数相对昨日单日检验数的增长率">
  <span>总检验数同比 {{ formatRate(data.totalCountGrowthRate) }}</span>
</el-tooltip>
```
```js
methods: {
  formatRate(rate) {
    if (rate === null || rate === undefined) return '0%';
    const sign = rate > 0 ? '+' : '';
    return `${sign}${rate}%`;
  }
}
```
## äºŒã€`/home/unqualifiedProductRanking` (口径变更,前端需关注)
### å˜æ›´åŽŸå› 
原实现拉取所有 `inspect_state=1` çš„记录到内存做聚合排序,数据量大时存在 OOM é£Žé™©ã€‚
### æ–°å£å¾„
| ç»´åº¦ | æ—§ | æ–° |
|------|----|----|
| æ—¶é—´èŒƒå›´ | å…¨åŽ†å² | **近 30 å¤© (含今天)** |
| æŽ’序 | åˆæ ¼çŽ‡å‡åº Top5 | ä¸å˜ |
| å­—段 | `productName / totalCount / completedCount / passRate` | ä¸å˜ |
### å‰ç«¯ä¿®æ”¹ç‚¹
如果当前页面有 "全历史" ä¹‹ç±»çš„æ–‡æ¡ˆï¼Œè¯·æ”¹æˆ "近 30 å¤©"。
```html
<div class="card-title">不合格产品排名 <span class="sub">(近 30 å¤©)</span></div>
```
## ä¸‰ã€æ—¥æœŸä¸Šç•Œç»Ÿä¸€æ”¹ä¸ºå³å¼€åŒºé—´ (语义未变,但补全数据)
`commonDetection` (rawMaterial / process / factoryDetection)、`nonComplianceWarning`、`completedInspectionCount`、`qualityInspectionStatistics` ä¹‹å‰éƒ½ç”¨ `le(checkTime, endDate)`。
当 `check_time` æ˜¯ `datetime` åˆ—时,`<= "yyyy-MM-dd"` ç­‰ä»·äºŽ `<= "yyyy-MM-dd 00:00:00"`,会漏掉当天 00:00:00 ä¹‹åŽçš„æ‰€æœ‰è®°å½•。
后端已统一改为 `lt(checkTime, endDate + 1 å¤©)`,**响应字段、字段语义不变**,只是当天数据不再丢失。
### å‰ç«¯ä¿®æ”¹ç‚¹
无需改动代码。但建议联调时核对:周/月/季视图最后一天是否出现以前缺失的检验记录。
## å››ã€å…¶ä»–内部优化 (前端无感)
- `qualityInspectionCount` æ”¹ç”¨ `SELECT COALESCE(SUM(quantity), 0)` èšåˆï¼Œå•次 SQL æ›¿ä»£ä¹‹å‰çš„ 6 æ¬¡å…¨è¡¨ `selectList`。
- `qualityInspectionStatistics` ä¸­ `i.getInspectType() == 0` ä¿®å¤ä¸º `Objects.equals(...)`,避免 Integer è‡ªåŠ¨è£…ç®±ç¼“å­˜èŒƒå›´å¤–çš„ç­‰å€¼è¯¯åˆ¤ã€‚
## æ³¨æ„äº‹é¡¹
- `qualityInspectionCount` çš„ `totalCountGrowthRate` çŽ°åœ¨åæ˜ çš„æ˜¯"日环比增长率",业务侧文案与图表说明需同步调整。
- `unqualifiedProductRanking` æ”¹ä¸ºè¿‘ 30 å¤©åŽï¼Œ**Top5 åˆ—表可能变化**,请通知业务确认。
- ä¸Šçº¿åŽå»ºè®®è”调以下场景:
  - `check_time` ä¸ºéž 00:00:00 çš„æ£€éªŒè®°å½•是否被正确统计 (周/月视图最后一天)。
  - ä»Šæ—¥æ‰€æœ‰æŠ¥æ£€çš„"待完成 / å·²å®Œæˆ"数据是否能实时反映到首页卡片。
- è‹¥æŸæŽ¥å£éœ€è¦ä¿ç•™æ—§çš„全历史口径 (例如导出、对账),请单独提需求增加新接口,避免改回首页接口。
doc/20260618_quick_inspect_sample.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,393 @@
# è´¨æ£€å¿«é€Ÿæ£€éªŒæŠ½æ£€åŠŸèƒ½å‰ç«¯è”è°ƒæ–‡æ¡£
> æ‰©å±•批量快速检验接口,支持全检和抽检两种检验模式,自动填充抽检数量。
## æ¶‰åŠé¡µé¢
- è´¨é‡ç®¡ç† / æ£€éªŒå•列表 - æ‰¹é‡å¿«é€Ÿæ£€éªŒå¼¹çª—
- è´¨é‡ç®¡ç† / åŽŸææ–™æ£€éªŒ / è¿‡ç¨‹æ£€éªŒ / å‡ºåŽ‚æ£€éªŒ - å¿«é€Ÿæ£€éªŒæ“ä½œ
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /qualityInspect/batchQuickInspect | æ‰¹é‡å¿«é€Ÿæ£€éªŒ (扩展参数) |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| ids | List\<Long\> | æ˜¯ | æ£€éªŒå•ID列表 |
| checkResult | String | æ˜¯ | æ£€æµ‹ç»“果:合格/不合格/部分合格 |
| testStandardId | Long | æ˜¯ | æŒ‡æ ‡æ ‡å‡†ID |
| checkCompany | String | å¦ | æ£€æµ‹å•位 |
| checkName | String | å¦ | æ£€éªŒå‘˜å§“名 |
| checkTime | String | å¦ | æ£€æµ‹æ—¥æœŸï¼Œæ ¼å¼ï¼šYYYY-MM-DD |
| paramList | List | å¦ | æ£€éªŒå‚数列表 |
| **sampleQuantity** | BigDecimal | å¦ | **抽检数量(新增)** |
| **qualifiedQuantity** | BigDecimal | å¦ | **合格数量(新增)** |
| **unqualifiedQuantity** | BigDecimal | å¦ | **不合格数量(新增)** |
**响应:**
```json
{
  "code": 200,
  "msg": "快速检验完成:成功 3 æ¡"
}
```
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
### æ£€éªŒæ¨¡å¼åˆ¤æ–­
根据检验单的 `inspectRule` å­—段判断检验模式:
| inspectRule | æ£€éªŒæ¨¡å¼ | å¤„理逻辑 |
|-------------|----------|----------|
| 0 æˆ– null | å…¨æ£€ | æ£€éªŒæ•°é‡ = æ€»æ•°é‡ï¼Œé»˜è®¤å…¨éƒ¨åˆæ ¼ |
| 1 | æŠ½æ£€ | æ£€éªŒæ•°é‡ = æŠ½æ£€æ ·æœ¬æ•°é‡ |
### æŠ½æ£€æ•°é‡æ¥æºä¼˜å…ˆçº§
当检验单为抽检模式时,抽检数量按以下优先级确定:
1. **前端传入** `sampleQuantity`(最高优先级)
2. **检验单已有** `sampleQuantity`
3. **按抽检比例计算**:`总数量 Ã— sampleRatio / 100`(向上取整)
4. æ ¡éªŒï¼šæŠ½æ£€æ•°é‡ä¸èƒ½è¶…过总数量,超过则自动修正为总数量
### åˆæ ¼/不合格数量
| åœºæ™¯ | å¤„理逻辑 |
|------|----------|
| å‰ç«¯ä¼ å…¥äº† qualifiedQuantity/unqualifiedQuantity | ä½¿ç”¨å‰ç«¯ä¼ å…¥å€¼ |
| å‰ç«¯æœªä¼ å…¥ | é»˜è®¤å…¨éƒ¨åˆæ ¼ï¼ˆåˆæ ¼æ•°=检验数量,不合格数=0) |
### å…¥åº“处理
- å…¥åº“数量 = åˆæ ¼æ•°é‡ Ã— å…¥åº“比例 / 100
- å…¥åº“比例默认 100%,可通过 `stockInRatio` å­—段调整
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. å¿«é€Ÿæ£€éªŒå¼¹çª—增加抽检字段
```html
<el-dialog title="批量快速检验" v-model="quickInspectVisible">
  <el-form :model="quickInspectForm" label-width="100px">
    <!-- åŸºç¡€å­—段 -->
    <el-form-item label="检测结果" required>
      <el-select v-model="quickInspectForm.checkResult">
        <el-option label="合格" value="合格" />
        <el-option label="不合格" value="不合格" />
        <el-option label="部分合格" value="部分合格" />
      </el-select>
    </el-form-item>
    <el-form-item label="指标标准" required>
      <el-select v-model="quickInspectForm.testStandardId">
        <!-- æŒ‡æ ‡æ ‡å‡†åˆ—表 -->
      </el-select>
    </el-form-item>
    <el-form-item label="检验员">
      <el-input v-model="quickInspectForm.checkName" />
    </el-form-item>
    <el-form-item label="检测日期">
      <el-date-picker v-model="quickInspectForm.checkTime" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
    </el-form-item>
    <!-- æŠ½æ£€ç›¸å…³å­—段(根据检验单模式动态显示) -->
    <el-form-item label="检验模式">
      <el-tag :type="inspectModeTag">{{ inspectModeLabel }}</el-tag>
    </el-form-item>
    <!-- æŠ½æ£€æ¨¡å¼ä¸‹æ˜¾ç¤º -->
    <el-form-item label="抽检数量" v-if="showSampleFields">
      <el-input-number
        v-model="quickInspectForm.sampleQuantity"
        :min="1"
        :max="maxSampleQuantity"
        :precision="0"
      />
      <span class="tip">最大可抽检数量:{{ maxSampleQuantity }}</span>
    </el-form-item>
    <!-- éƒ¨åˆ†åˆæ ¼æˆ–不合格时显示 -->
    <el-form-item label="合格数量" v-if="showQuantityFields">
      <el-input-number
        v-model="quickInspectForm.qualifiedQuantity"
        :min="0"
        :max="quickInspectForm.sampleQuantity || totalQuantity"
        :precision="0"
      />
    </el-form-item>
    <el-form-item label="不合格数量" v-if="showQuantityFields">
      <el-input-number
        v-model="quickInspectForm.unqualifiedQuantity"
        :min="0"
        :max="quickInspectForm.sampleQuantity || totalQuantity"
        :precision="0"
      />
    </el-form-item>
  </el-form>
  <template #footer>
    <el-button @click="quickInspectVisible = false">取消</el-button>
    <el-button type="primary" @click="submitQuickInspect">确认检验</el-button>
  </template>
</el-dialog>
```
### 2. data æ•°æ®
```js
data() {
  return {
    quickInspectVisible: false,
    quickInspectForm: {
      ids: [],
      checkResult: '合格',
      testStandardId: null,
      checkCompany: '',
      checkName: '',
      checkTime: '',
      paramList: [],
      sampleQuantity: null,
      qualifiedQuantity: null,
      unqualifiedQuantity: null,
    },
    // é€‰ä¸­çš„æ£€éªŒå•列表
    selectedInspects: [],
    // å½“前检验单信息(用于单条快速检验)
    currentInspect: null,
  }
}
```
### 3. computed è®¡ç®—属性
```js
computed: {
  // æ£€éªŒæ¨¡å¼æ ‡ç­¾
  inspectModeLabel() {
    if (!this.currentInspect) return '未知';
    return this.currentInspect.inspectRule === 1 ? '抽检' : '全检';
  },
  inspectModeTag() {
    if (!this.currentInspect) return 'info';
    return this.currentInspect.inspectRule === 1 ? 'warning' : 'success';
  },
  // æ˜¯å¦æ˜¾ç¤ºæŠ½æ£€å­—段
  showSampleFields() {
    return this.currentInspect && this.currentInspect.inspectRule === 1;
  },
  // æ˜¯å¦æ˜¾ç¤ºåˆæ ¼/不合格数量输入(部分合格或不合格时)
  showQuantityFields() {
    return this.quickInspectForm.checkResult === '部分合格' ||
           this.quickInspectForm.checkResult === '不合格';
  },
  // æ€»æ•°é‡
  totalQuantity() {
    return this.currentInspect?.quantity || 0;
  },
  // æœ€å¤§æŠ½æ£€æ•°é‡
  maxSampleQuantity() {
    return this.totalQuantity;
  },
  // é»˜è®¤æŠ½æ£€æ•°é‡ï¼ˆä»Žæ£€éªŒå•获取或按比例计算)
  defaultSampleQuantity() {
    if (!this.currentInspect) return 0;
    if (this.currentInspect.sampleQuantity) {
      return this.currentInspect.sampleQuantity;
    }
    if (this.currentInspect.sampleRatio) {
      return Math.ceil(this.totalQuantity * this.currentInspect.sampleRatio / 100);
    }
    return this.totalQuantity;
  },
}
```
### 4. methods æ–¹æ³•
```js
methods: {
  // æ‰“开快速检验弹窗
  openQuickInspectDialog(row) {
    // å•条快速检验
    this.currentInspect = row;
    this.quickInspectForm.ids = [row.id];
    // æŠ½æ£€æ¨¡å¼ä¸‹è‡ªåŠ¨å¡«å……æŠ½æ£€æ•°é‡
    if (row.inspectRule === 1) {
      this.quickInspectForm.sampleQuantity = this.defaultSampleQuantity;
    }
    // å…¶ä»–字段初始化
    this.quickInspectForm.checkResult = '合格';
    this.quickInspectForm.testStandardId = row.testStandardId;
    this.quickInspectForm.checkTime = new Date().toISOString().slice(0, 10);
    this.quickInspectForm.qualifiedQuantity = null;
    this.quickInspectForm.unqualifiedQuantity = null;
    this.quickInspectVisible = true;
  },
  // æ‰¹é‡å¿«é€Ÿæ£€éªŒ
  openBatchQuickInspectDialog(selection) {
    this.selectedInspects = selection;
    this.quickInspectForm.ids = selection.map(s => s.id);
    // æ‰¹é‡æ£€éªŒæ—¶ï¼Œå–第一条检验单的信息作为参考
    this.currentInspect = selection[0];
    // æŠ½æ£€æ¨¡å¼ä¸‹è‡ªåЍ填充
    if (this.currentInspect?.inspectRule === 1) {
      this.quickInspectForm.sampleQuantity = this.defaultSampleQuantity;
    }
    this.quickInspectVisible = true;
  },
  // æ£€æµ‹ç»“果变化时自动调整数量
  onCheckResultChange(val) {
    if (val === '合格') {
      // åˆæ ¼æ—¶ï¼Œæ¸…空手动输入的数量
      this.quickInspectForm.qualifiedQuantity = null;
      this.quickInspectForm.unqualifiedQuantity = null;
    } else if (val === '不合格') {
      // ä¸åˆæ ¼æ—¶ï¼Œé»˜è®¤å…¨éƒ¨ä¸åˆæ ¼
      const qty = this.showSampleFields ? this.quickInspectForm.sampleQuantity : this.totalQuantity;
      this.quickInspectForm.qualifiedQuantity = 0;
      this.quickInspectForm.unqualifiedQuantity = qty;
    } else if (val === '部分合格') {
      // éƒ¨åˆ†åˆæ ¼æ—¶ï¼Œéœ€è¦ç”¨æˆ·æ‰‹åŠ¨è¾“å…¥
      this.quickInspectForm.qualifiedQuantity = null;
      this.quickInspectForm.unqualifiedQuantity = null;
    }
  },
  // æäº¤å¿«é€Ÿæ£€éªŒ
  async submitQuickInspect() {
    // æ ¡éªŒå¿…填字段
    if (!this.quickInspectForm.checkResult) {
      this.$message.warning('请选择检测结果');
      return;
    }
    if (!this.quickInspectForm.testStandardId) {
      this.$message.warning('请选择指标标准');
      return;
    }
    // æŠ½æ£€æ¨¡å¼ä¸‹æ ¡éªŒæŠ½æ£€æ•°é‡
    if (this.showSampleFields && !this.quickInspectForm.sampleQuantity) {
      this.$message.warning('请输入抽检数量');
      return;
    }
    // éƒ¨åˆ†åˆæ ¼æ—¶æ ¡éªŒæ•°é‡
    if (this.quickInspectForm.checkResult === '部分合格') {
      if (!this.quickInspectForm.qualifiedQuantity && !this.quickInspectForm.unqualifiedQuantity) {
        this.$message.warning('请输入合格数量和不合格数量');
        return;
      }
      const total = this.showSampleFields
        ? this.quickInspectForm.sampleQuantity
        : this.totalQuantity;
      const sum = (this.quickInspectForm.qualifiedQuantity || 0) +
                  (this.quickInspectForm.unqualifiedQuantity || 0);
      if (sum > total) {
        this.$message.warning('合格数量+不合格数量不能超过检验数量');
        return;
      }
    }
    try {
      const res = await axios.post('/qualityInspect/batchQuickInspect', this.quickInspectForm);
      if (res.code === 200) {
        this.$message.success(res.msg);
        this.quickInspectVisible = false;
        this.refreshList();
      } else {
        this.$message.error(res.msg);
      }
    } catch (e) {
      this.$message.error('快速检验失败');
    }
  },
}
```
### 5. æ£€éªŒå•列表显示抽检信息
建议在检验单列表中增加以下列:
```html
<el-table-column label="检验规则" width="100">
  <template #default="{ row }">
    <el-tag :type="row.inspectRule === 1 ? 'warning' : 'success'" size="small">
      {{ row.inspectRule === 1 ? '抽检' : '全检' }}
    </el-tag>
  </template>
</el-table-column>
<el-table-column label="抽检比例" width="100">
  <template #default="{ row }">
    {{ row.inspectRule === 1 ? (row.sampleRatio || 0) + '%' : '-' }}
  </template>
</el-table-column>
<el-table-column label="抽检数量" width="100">
  <template #default="{ row }">
    {{ row.inspectRule === 1 ? (row.sampleQuantity || '-') : row.quantity }}
  </template>
</el-table-column>
```
## è¯·æ±‚示例
### å…¨æ£€æ¨¡å¼
```json
{
  "ids": [1, 2, 3],
  "checkResult": "合格",
  "testStandardId": 100,
  "checkName": "张三",
  "checkTime": "2026-06-18"
}
```
### æŠ½æ£€æ¨¡å¼ - å…¨éƒ¨åˆæ ¼
```json
{
  "ids": [4],
  "checkResult": "合格",
  "testStandardId": 100,
  "checkName": "张三",
  "checkTime": "2026-06-18",
  "sampleQuantity": 50
}
```
### æŠ½æ£€æ¨¡å¼ - éƒ¨åˆ†åˆæ ¼
```json
{
  "ids": [5],
  "checkResult": "部分合格",
  "testStandardId": 100,
  "checkName": "张三",
  "checkTime": "2026-06-18",
  "sampleQuantity": 50,
  "qualifiedQuantity": 45,
  "unqualifiedQuantity": 5
}
```
## æ³¨æ„äº‹é¡¹
- æ‰¹é‡å¿«é€Ÿæ£€éªŒæ—¶ï¼Œå¦‚果选中的检验单有不同的检验规则(全检/抽检),建议提示用户分开处理,或前端按第一条检验单的模式统一处理。
- æŠ½æ£€æ•°é‡ç”±å‰ç«¯ä¼ å…¥æ—¶ï¼ŒåŽç«¯ä¼šæ ¡éªŒä¸è¶…过总数量,超出自动修正。
- æŠ½æ£€æ¨¡å¼ä¸‹ï¼Œå…¥åº“数量按抽检样本的合格数量计算,而非推算总量。
- å¦‚需在抽检模式下根据样本合格率推算总量入库比例,请另行提出需求。
- å‰ç«¯åœ¨æ‰“开弹窗时,应自动填充检验单已有的 `sampleQuantity` æˆ–按比例计算的默认值,减少用户输入负担。
doc/quality_inspect_rule.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
# è´¨é‡æ£€éªŒè§„则(全检/抽检)
## æ¶‰åŠé¡µé¢
- æ£€æµ‹æ ‡å‡†ç®¡ç†é¡µé¢ï¼ˆæ–°å¢ž/编辑弹窗、列表)
- åŽŸææ–™æ£€éªŒé¡µé¢ï¼ˆåˆ—è¡¨ã€æ–°å¢ž/编辑弹窗)
- è¿‡ç¨‹æ£€éªŒé¡µé¢ï¼ˆåˆ—表、新增/编辑弹窗)
- å‡ºåŽ‚æ£€éªŒé¡µé¢ï¼ˆåˆ—è¡¨ã€æ–°å¢ž/编辑弹窗)
## API
### æ£€æµ‹æ ‡å‡†
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /qualityTestStandard/add | æ–°å¢žæ£€æµ‹æ ‡å‡†ï¼ˆæ–°å¢ž inspectRule、sampleRatio å­—段) |
| POST | /qualityTestStandard/update | ä¿®æ”¹æ£€æµ‹æ ‡å‡†ï¼ˆæ–°å¢ž inspectRule、sampleRatio å­—段) |
| GET | /qualityTestStandard/listPage | åˆ—表查询(返回新增字段) |
**新增请求/响应参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| inspectRule | Integer | å¦ | æ£€éªŒè§„则: 0=全检, 1=抽检,默认0 |
| sampleRatio | BigDecimal | å¦ | æŠ½æ£€æ¯”例(百分比),仅inspectRule=1时有效,如10表示10% |
### è´¨æ£€è®°å½•(原材料/过程/出厂检验)
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /qualityInspect/add | æ–°å¢žè´¨æ£€è®°å½•(新增 inspectRule、sampleRatio、sampleQuantity å­—段) |
| POST | /qualityInspect/update | ä¿®æ”¹è´¨æ£€è®°å½•(新增字段) |
| GET | /qualityInspect/listPage | åˆ—表查询(返回新增字段) |
| POST | /qualityInspect/export | å¯¼å‡ºExcel(新增"检验规则"、"抽检比例"、"抽检数量"列) |
**新增响应参数:**
| å‚æ•° | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| inspectRule | Integer | æ£€éªŒè§„则: 0=全检, 1=抽检 |
| sampleRatio | BigDecimal | æŠ½æ£€æ¯”例(%) |
| sampleQuantity | BigDecimal | æŠ½æ£€æ•°é‡ |
**说明:** ç”Ÿäº§æŠ¥å·¥è‡ªåŠ¨åˆ›å»ºè´¨æ£€è®°å½•æ—¶ï¼Œä¼šä»Žå…³è”çš„æ£€æµ‹æ ‡å‡†å¤åˆ¶ `inspectRule` å’Œ `sampleRatio`,并自动计算 `sampleQuantity = æ€»æ•°é‡ Ã— æŠ½æ£€æ¯”例 / 100`(向上取整)。
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. æ£€æµ‹æ ‡å‡† - æ–°å¢ž/编辑弹窗
```html
<el-form-item label="检验规则" prop="inspectRule">
  <el-radio-group v-model="form.inspectRule">
    <el-radio :label="0">全检</el-radio>
    <el-radio :label="1">抽检</el-radio>
  </el-radio-group>
</el-form-item>
<el-form-item label="抽检比例(%)" prop="sampleRatio" v-if="form.inspectRule === 1">
  <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" />
</el-form-item>
```
### 2. æ£€æµ‹æ ‡å‡† - åˆ—表
```html
<el-table-column label="检验规则" prop="inspectRule" min-width="100">
  <template #default="{ row }">
    {{ row.inspectRule === 0 ? '全检' : row.inspectRule === 1 ? '抽检' : '' }}
  </template>
</el-table-column>
<el-table-column label="抽检比例(%)" prop="sampleRatio" min-width="120" />
```
### 3. è´¨æ£€è®°å½• - åˆ—表(原材料/过程/出厂检验页面)
```html
<el-table-column label="检验规则" prop="inspectRule" min-width="100">
  <template #default="{ row }">
    {{ row.inspectRule === 0 ? '全检' : row.inspectRule === 1 ? '抽检' : '' }}
  </template>
</el-table-column>
<el-table-column label="抽检比例(%)" prop="sampleRatio" min-width="120" />
<el-table-column label="抽检数量" prop="sampleQuantity" min-width="100" />
```
### 4. è´¨æ£€è®°å½• - æ–°å¢ž/编辑弹窗(可选手动填写)
```html
<el-form-item label="检验规则" prop="inspectRule">
  <el-radio-group v-model="form.inspectRule">
    <el-radio :label="0">全检</el-radio>
    <el-radio :label="1">抽检</el-radio>
  </el-radio-group>
</el-form-item>
<el-form-item label="抽检比例(%)" prop="sampleRatio" v-if="form.inspectRule === 1">
  <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" />
</el-form-item>
```
### 5. å¯¼å‡º
导出Excel自动新增"检验规则"、"抽检比例(%)"、"抽检数量"列,无需前端额外处理。
## æ³¨æ„äº‹é¡¹
- æ‰§è¡Œæ•°æ®åº“迁移脚本 `doc/20260617_quality_inspect_rule.sql` åŽå†éƒ¨ç½²
- æ£€æµ‹æ ‡å‡†è®¾ç½®æ£€éªŒè§„则后,生产报工自动生成的质检记录会继承该规则
- æŠ½æ£€æ¯”例仅在检验规则为"抽检"时才需填写
- æŠ½æ£€æ•°é‡ç”±ç³»ç»Ÿè‡ªåŠ¨è®¡ç®—ï¼š`总数量 Ã— æŠ½æ£€æ¯”例 / 100`(向上取整),无需手动填写
src/api/equipmentManagement/measuringInstrumentFile.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
import request from "@/utils/request";
// æŸ¥è¯¢è®¡é‡å™¨å…·é™„件列表
export function listMeasuringInstrumentFiles(query) {
  return request({
    url: "/measuringInstrumentFile/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢žè®¡é‡å™¨å…·é™„ä»¶
export function addMeasuringInstrumentFile(data) {
  return request({
    url: "/measuringInstrumentFile/add",
    method: "post",
    data,
  });
}
// åˆ é™¤è®¡é‡å™¨å…·é™„ä»¶
export function delMeasuringInstrumentFile(id) {
  return request({
    url: "/measuringInstrumentFile/del",
    method: "delete",
    data: Array.isArray(id) ? id : [id],
  });
}
src/views/basicData/supplierManage/components/HomeTab.vue
@@ -1,63 +1,135 @@
<template>
  <div class="app-container">
  <div>
    <div class="search_form">
      <div>
      <div style="margin-bottom: 10px;">
        <span class="search_title">供应商档案:</span>
        <el-input
          v-model="searchForm.supplierName"
          style="width: 240px"
          placeholder="输入名称搜索"
          @change="handleQuery"
          clearable
          :prefix-icon="Search"
            v-model="searchForm.supplierName"
            style="width: 240px"
            placeholder="输入供应商名称搜索"
            @change="handleQuery"
            clearable
            :prefix-icon="Search"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >
      </div>
      <div>
        <el-button type="primary" @click="openForm('add')">新增供应商</el-button>
      <div style="margin-bottom: 10px;">
        <el-button type="primary" @click="openForm('add')"
        >新增供应商</el-button
        >
        <el-button @click="handleOut">导出</el-button>
        <el-button type="info" plain icon="Upload" @click="handleImport"
        >导入</el-button
        >
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
    </div>
    <div class="table_list" style="margin-top: 20px">
    <div class="table_list">
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      />
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :page="page"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          :tableLoading="tableLoading"
          @pagination="pagination"
      ></PIMTable>
    </div>
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
      width="600px"
      @close="closeDia"
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增供应商信息' : '编辑供应商信息'"
        width="70%"
        @close="closeDia"
    >
      <el-form
        :model="form"
        label-width="120px"
        label-position="top"
        :rules="rules"
        ref="formRef"
          :model="form"
          label-width="140px"
          label-position="top"
          :rules="rules"
          ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="名称:" prop="supplierName">
              <el-input v-model="form.supplierName" placeholder="请输入" clearable />
            <el-form-item label="供应商名称:" prop="supplierName">
              <el-input
                  v-model="form.supplierName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="纳税人识别号:" prop="taxpayerIdentificationNum">
            <el-form-item
                label="纳税人识别号:"
                prop="taxpayerIdentificationNum"
            >
              <el-input
                v-model="form.taxpayerIdentificationNum"
                placeholder="请输入"
                clearable
                  v-model="form.taxpayerIdentificationNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公司地址:" prop="companyAddress">
              <el-input
                  v-model="form.companyAddress"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公司电话:" prop="companyPhone">
              <el-input
                  v-model="form.companyPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="开户行:" prop="bankAccountName">
              <el-input
                  v-model="form.bankAccountName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="账号:" prop="bankAccountNum">
              <el-input
                  v-model="form.bankAccountNum"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系人:" prop="contactUserName">
              <el-input
                  v-model="form.contactUserName"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="联系电话:" prop="contactUserPhone">
              <el-input
                  v-model="form.contactUserPhone"
                  placeholder="请输入"
                  clearable
              />
            </el-form-item>
          </el-col>
@@ -65,12 +137,17 @@
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="维护人:" prop="maintainUserId">
              <el-select v-model="form.maintainUserId" placeholder="请选择" clearable>
              <el-select
                  v-model="form.maintainUserId"
                  placeholder="请选择"
                  clearable
                  disabled
              >
                <el-option
                  v-for="item in userList"
                  :key="item.userId"
                  :label="item.nickName"
                  :value="item.userId"
                    v-for="item in userList"
                    :key="item.nickName"
                    :label="item.nickName"
                    :value="item.userId"
                />
              </el-select>
            </el-form-item>
@@ -78,14 +155,34 @@
          <el-col :span="12">
            <el-form-item label="维护时间:" prop="maintainTime">
              <el-date-picker
                style="width: 100%"
                v-model="form.maintainTime"
                value-format="YYYY-MM-DD"
                format="YYYY-MM-DD"
                type="date"
                placeholder="请选择"
                clearable
                  style="width: 100%"
                  v-model="form.maintainTime"
                  value-format="YYYY-MM-DD"
                  format="YYYY-MM-DD"
                  type="date"
                  placeholder="请选择"
                  clearable
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商类型:" prop="supplierType">
              <el-select v-model="form.supplierType" placeholder="请选择" clearable>
                <el-option label="甲" value="甲" />
                <el-option label="乙" value="乙" />
                <el-option label="丙" value="丙" />
                <el-option label="丁" value="丁" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否白名单:" prop="isWhite">
              <el-select v-model="form.isWhite" placeholder="请选择" clearable>
                <el-option label="是" :value="0" />
                <el-option label="否" :value="1" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -97,231 +194,402 @@
        </div>
      </template>
    </el-dialog>
    <!-- ä¾›åº”商导入对话框 -->
    <el-dialog
        :title="upload.title"
        v-model="upload.open"
        width="400px"
        append-to-body
    >
      <el-upload
          ref="uploadRef"
          :limit="1"
          accept=".xlsx, .xls"
          :headers="upload.headers"
          :action="upload.url + '?updateSupport=' + upload.updateSupport"
          :disabled="upload.isUploading"
          :on-progress="handleFileUploadProgress"
          :on-success="handleFileSuccess"
          :on-error="handleFileError"
          :auto-upload="false"
          drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入xls、xlsx格式文件。</span>
            <el-link
                type="primary"
                :underline="false"
                style="font-size: 12px; vertical-align: baseline"
                @click="importTemplate"
            >下载模板</el-link
            >
          </div>
        </template>
      </el-upload>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
          <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
        </div>
      </template>
    </el-dialog>
    <FileList v-if="fileListDialogVisible"
              v-model:visible="fileListDialogVisible"
              record-type="supplier_manage"
              :record-id="recordId" />
  </div>
</template>
<script setup>
  import { getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
  import { Search } from "@element-plus/icons-vue";
  import { ElMessageBox } from "element-plus";
  import { addSupplier, delSupplier, getSupplier, listSupplier, updateSupplier } from "@/api/basicData/supplierManageFile.js";
  import useUserStore from "@/store/modules/user";
  import { userListNoPage } from "@/api/system/user.js";
  import { getToken } from "@/utils/auth.js";
import { onMounted, ref } from "vue";
import { Search } from "@element-plus/icons-vue";
import { delSupplier } from "@/api/basicData/supplierManageFile.js";
import { ElMessageBox } from "element-plus";
import { userListNoPage } from "@/api/system/user.js";
import {
  addSupplier,
  getSupplier,
  listSupplier,
  updateSupplier,
} from "@/api/basicData/supplierManageFile.js";
import useUserStore from "@/store/modules/user";
import { getToken } from "@/utils/auth.js";
const FileList = defineAsyncComponent(() =>
    import("@/components/Dialog/FileList.vue")
);
const { proxy } = getCurrentInstance();
const userStore = useUserStore();
  const { proxy } = getCurrentInstance();
  const userStore = useUserStore();
const tableColumn = ref([
  {
    label: "供应商名称",
    prop: "supplierName",
    width: 250,
  },
  {
    label: "供应商类型",
    prop: "supplierType",
    width: 120,
  },
  {
    label: "纳税人识别号",
    prop: "taxpayerIdentificationNum",
    width: 230,
  },
  {
    label: "公司地址",
    prop: "companyAddress",
    width: 220,
  },
  {
    label: "联系方式",
    prop: "companyPhone",
    width:150
  },
  {
    label: "开户行",
    prop: "bankAccountName",
    width: 220,
  },
  {
    label: "账号",
    prop: "bankAccountNum",
    width: 220,
  },
  {
    label: "联系人",
    prop: "contactUserName",
  },
  {
    label: "联系电话",
    prop: "contactUserPhone",
    width: 150,
  },
  {
    label: "维护人",
    prop: "maintainUserName",
  },
  const tableColumn = ref([
    {
      label: "名称",
      prop: "supplierName",
    },
    {
      label: "纳税人识别号",
      prop: "taxpayerIdentificationNum",
    },
    {
      label: "维护人",
      prop: "maintainUserName",
    },
    {
      label: "维护时间",
      prop: "maintainTime",
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 150,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            openForm("edit", row);
          },
  {
    label: "维护时间",
    prop: "maintainTime",
    width:100
  },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: 'right',
    width: 150,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm("edit", row);
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const selectedRows = ref([]);
  const userList = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const operationType = ref("");
  const dialogFormVisible = ref(false);
  const formRef = ref();
  const data = reactive({
    searchForm: {
      supplierName: "",
    },
    form: {
      supplierName: "",
      taxpayerIdentificationNum: "",
      maintainUserId: "",
      maintainTime: "",
    },
    rules: {
      supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
      taxpayerIdentificationNum: [{ required: true, message: "请输入", trigger: "blur" }],
      maintainUserId: [{ required: true, message: "请选择", trigger: "change" }],
      maintainTime: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
  const { searchForm, form, rules } = toRefs(data);
  const upload = reactive({
    open: false,
    title: "",
    isUploading: false,
    updateSupport: 1,
    headers: { Authorization: "Bearer " + getToken() },
    url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
  });
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then(res => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      page.total = res.data.total;
    });
  };
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  const openForm = (type, row) => {
    operationType.value = type;
    form.value = {
      supplierName: "",
      taxpayerIdentificationNum: "",
      maintainUserId: userStore.id,
      maintainTime: getCurrentDate(),
    };
    userListNoPage().then(res => {
      userList.value = res.data;
    });
    if (type === "edit") {
      getSupplier(row.id).then(res => {
        form.value = {
                    id: res.data.id || "",
          supplierName: res.data.supplierName || "",
          taxpayerIdentificationNum: res.data.taxpayerIdentificationNum || "",
          maintainUserId: res.data.maintainUserId || userStore.id,
          maintainTime: res.data.maintainTime || getCurrentDate(),
        };
      });
    }
    dialogFormVisible.value = true;
  };
  const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (!valid) {
        return;
      },
      {
        //资质附件
        name: "资质文件",
        type: "text",
        clickFun: (row) => {
          openFileDialog(row)
        }
      }
    ],
  },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const userList = ref([]);
const tableLoading = ref(false);
const fileListDialogVisible = ref(false);
const recordId = ref();
const page = reactive({
  current: 1,
  size: 100,
  total: 0,
});
// ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
const operationType = ref("");
const dialogFormVisible = ref(false);
const data = reactive({
  searchForm: {
    supplierName: "",
  },
  form: {
    supplierName: "",
    taxpayerIdentificationNum: "",
    companyAddress: "",
    companyPhone: "",
    bankAccountName: "",
    bankAccountNum: "",
    contactUserName: "",
    contactUserPhone: "",
    maintainUserId: "",
    maintainTime: "",
    supplierType: "",
    isWhite: "",
  },
  rules: {
    supplierName: [{ required: true, message: "请输入", trigger: "blur" }],
    taxpayerIdentificationNum: [
      { required: true, message: "请输入", trigger: "blur" },
    ],
    companyAddress: [{ required: true, message: "请输入", trigger: "blur" }],
    companyPhone: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountName: [{ required: true, message: "请输入", trigger: "blur" }],
    bankAccountNum: [{ required: true, message: "请输入", trigger: "blur" }],
    contactUserName: [{ required: false, message: "请输入", trigger: "blur" }],
    contactUserPhone: [{ required: false, message: "请输入", trigger: "blur" }],
    maintainUserId: [{ required: false, message: "请选择", trigger: "change" }],
    maintainTime: [{ required: false, message: "请选择", trigger: "change" }],
    supplierType: [{ required: true, message: "请选择供应商类型", trigger: "change" }],
  },
});
const { searchForm, form, rules } = toRefs(data);
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
  upload.isUploading = true;
  proxy.$refs["uploadRef"].submit();
}
const getList = () => {
  tableLoading.value = true;
  listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then((res) => {
    tableLoading.value = false;
    tableData.value = res.data.records;
    page.total = res.data.total;
  });
};
const upload = reactive({
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(供应商导入)
  open: false,
  // å¼¹å‡ºå±‚标题(供应商导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 1,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import",
});
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
  upload.title = "供应商导入";
  upload.open = true;
}
/** ä¸‹è½½æ¨¡æ¿ */
function importTemplate() {
  proxy.download("/system/supplier/downloadTemplate", {}, "供应商导入模板.xlsx");
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
  upload.isUploading = true;
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response, file, fileList) => {
  upload.isUploading = false;
  if(response.code === 200){
    proxy.$modal.msgSuccess("文件上传成功");
    upload.open = false;
    proxy.$refs["uploadRef"].clearFiles();
    getList();
  }else if(response.code === 500){
    proxy.$modal.msgError(response.msg);
  }else{
    proxy.$modal.msgWarning(response.msg);
  }
};
/** æ–‡ä»¶ä¸Šä¼ å¤±è´¥å¤„理 */
const handleFileError = (error, file, fileList) => {
  upload.isUploading = false;
  proxy.$modal.msgError("文件上传失败");
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
  operationType.value = type;
  form.value = {};
  form.value.maintainUserId = userStore.id;
  form.value.maintainTime = getCurrentDate();
  userListNoPage().then((res) => {
    userList.value = res.data;
  });
  if (type === "edit") {
    getSupplier(row.id).then((res) => {
      form.value = { ...res.data };
    });
  }
  dialogFormVisible.value = true;
};
// æäº¤è¡¨å•
const submitForm = () => {
  proxy.$refs["formRef"].validate((valid) => {
    if (valid) {
      if (operationType.value === "edit") {
        updateSupplier(form.value).then(() => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
          getList();
        });
        submitEdit();
      } else {
        addSupplier(form.value).then(() => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
          getList();
        });
        submitAdd();
      }
    });
  };
  const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
  };
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
    }
  });
};
// æäº¤æ–°å¢ž
const submitAdd = () => {
  addSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// æäº¤ä¿®æ”¹
const submitEdit = () => {
  updateSupplier(form.value).then((res) => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
    getList();
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        proxy.download("/system/supplier/export", { isWhite: 0 }, "供应商档案.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  const handleDelete = () => {
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
};
// åˆ é™¤
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    // æ£€æŸ¥æ˜¯å¦æœ‰ä»–人维护的数据
    const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName);
    if (unauthorizedData.length > 0) {
      proxy.$modal.msgWarning("不可删除他人维护的数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.id);
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        tableLoading.value = true;
        delSupplier(ids)
          .then(() => {
            proxy.$modal.msgSuccess("删除成功");
            getList();
          })
          .finally(() => {
            tableLoading.value = false;
          });
            .then((res) => {
              proxy.$modal.msgSuccess("删除成功");
              getList();
            })
            .finally(() => {
              tableLoading.value = false;
            });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
};
  const getCurrentDate = () => {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0");
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  };
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
  const today = new Date();
  const year = today.getFullYear();
  const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// æ‰“开附件弹框
const openFileDialog = async row => {
  recordId.value = row.id;
  fileListDialogVisible.value = true;
};
  onMounted(() => {
    getList();
  });
onMounted(() => {
  getList();
});
  defineExpose({
    getList,
  });
defineExpose({
  getList,
});
</script>
src/views/basicData/supplierManage/index.vue
@@ -1,9 +1,43 @@
<!-- åœ¨ä½ çš„主页面中 -->
<template>
  <div>
    <HomeTab />
  <div class="app-container">
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <el-tab-pane label="正常供应商" name="home">
        <HomeTab ref="homeTab" />
      </el-tab-pane>
      <el-tab-pane label="黑名单" name="blacklist">
        <BlacklistTab ref="blacklistTab" />
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
<script setup>
  import HomeTab from "./components/HomeTab.vue";
</script>
<script>
import HomeTab from './components/HomeTab.vue'
import BlacklistTab from './components/BlacklistTab.vue'
export default {
  name: 'MainPage',
  components: {
    HomeTab,
    BlacklistTab
  },
  data() {
    return {
      activeTab: 'home'
    }
  },
  methods: {
    handleTabChange(tabName) {
      this.activeTab = tabName
      this.$nextTick(() => {
        if (tabName === 'home') {
          this.$refs.homeTab && this.$refs.homeTab.getList && this.$refs.homeTab.getList()
        } else if (tabName === 'blacklist') {
          this.$refs.blacklistTab && this.$refs.blacklistTab.getList && this.$refs.blacklistTab.getList()
        }
      })
    },
  }
}
</script>
src/views/equipmentManagement/measurementEquipment/index.vue
@@ -42,11 +42,18 @@
                :tableLoading="tableLoading"
                @pagination="pagination"
        :dbRowClick="dbRowClick"
            :rowClassName="overdueRowClass"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <calibration-dia ref="calibrationDia" @close="handleQuery"></calibration-dia>
    <files-dia ref="filesDia"></files-dia>
    <FileList
        v-if="fileDialogVisible"
        v-model:visible="fileDialogVisible"
        record-type="measuring_instrument_ledger"
        :record-id="currentRecordId"
        :editable="true"
      />
    <rowClickDataForm ref="rowClickData"></rowClickDataForm>
    </div>
</template>
@@ -61,7 +68,8 @@
  measuringInstrumentDelete,
  measuringInstrumentListPage,
} from "@/api/equipmentManagement/measurementEquipment.js";
import FilesDia from "./filesDia.vue";
import FileList from "@/components/Dialog/FileList.vue";
import rowClickDataForm from "./components/rowClickData.vue"
const { proxy } = getCurrentInstance();
const userStore = useUserStore()
@@ -178,7 +186,13 @@
const tableData = ref([]);
const tableLoading = ref(false);
const rowClickData = ref([])
const filesDia = ref()
const fileDialogVisible = ref(false)
const currentRecordId = ref(0)
const overdueRowClass = ({ row }) => {
  return row.status === 2 ? "overdue-row" : "";
};
const page = reactive({
    current: 1,
    size: 100,
@@ -188,8 +202,9 @@
// æ‰“开附件弹框
const openFilesFormDia = (row) => {
    filesDia.value?.openDialog(row,'计量器具台账')
};
  currentRecordId.value = row.id
  fileDialogVisible.value = true
}
const dbRowClick = (row)=>{
  rowClickData.value?.openDialog(row)
@@ -295,4 +310,14 @@
<style scoped>
</style>
<style lang="scss">
.el-table .overdue-row {
  --el-table-tr-bg-color: var(--el-color-danger-light-9);
  color: var(--el-color-danger);
}
.el-table .overdue-row td.el-table__cell {
  background: var(--el-color-danger-light-9) !important;
}
</style>
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -119,6 +119,26 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验规则" prop="inspectRule">
              <el-radio-group v-model="form.inspectRule">
                <el-radio :label="0">全检</el-radio>
                <el-radio :label="1">抽检</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检比例(%)" prop="sampleRatio">
              <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" style="width: 100%" @change="calcSampleQuantity" :disabled="isViewMode" />
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检数量" prop="sampleQuantity">
              <el-input-number v-model="form.sampleQuantity" :min="0" :precision="2" style="width: 100%" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测单位:" prop="checkCompany">
              <el-input v-model="form.checkCompany" placeholder="请输入" clearable :disabled="isViewMode"/>
            </el-form-item>
@@ -214,6 +234,9 @@
    checkResult: "",
    salesLedgerId: "",
    salesContractNo: "",
    inspectRule: 0,
    sampleRatio: undefined,
    sampleQuantity: undefined,
  },
  rules: {
    checkTime: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -250,7 +273,7 @@
        prop: "unit",
    },
    {
        label: "标准值",
        label: "厂家标准值",
        prop: "standardValue",
    },
    {
@@ -422,6 +445,7 @@
      if (productData) {
        // åªè‡ªåŠ¨å¸¦å…¥æ•°é‡
        form.value.quantity = productData.quantity || 0;
        calcSampleQuantity();
      }
    } catch (e) {
      console.error('查询销售订单产品信息失败', e);
@@ -455,6 +479,16 @@
  form.value.qualifiedQuantity = Math.max(0, quantity - unqualified);
};
const calcSampleQuantity = () => {
  const q = parseFloat(form.value.quantity) || 0;
  const r = parseFloat(form.value.sampleRatio) || 0;
  if (q > 0 && r > 0) {
    form.value.sampleQuantity = Number((q * r / 100).toFixed(2));
  } else {
    form.value.sampleQuantity = undefined;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
src/views/qualityManagement/finalInspection/components/inspectionFormDia.vue
@@ -58,7 +58,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
src/views/qualityManagement/finalInspection/components/quickCheckDia.vue
@@ -9,6 +9,14 @@
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验模式:">
              <el-tag :type="inspectModeTag" size="large">{{ inspectModeLabel }}</el-tag>
              <span v-if="showSampleFields && currentInspect" class="mode-tip">
                (抽检比例: {{ currentInspect.sampleRatio || 0 }}%)
              </span>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult" placeholder="请选择检测结果" style="width: 100%" @change="handleCheckResultChange">
                <el-option label="合格" value="合格" />
@@ -17,6 +25,8 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select v-model="form.testStandardId" placeholder="请选择指标" style="width: 100%" @change="handleTestStandardChange">
@@ -29,12 +39,27 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="showSampleFields">
            <el-form-item label="抽检数量:" prop="sampleQuantity">
              <el-input-number
                :step="1"
                :min="1"
                :max="maxSampleQuantity"
                style="width: 100%"
                v-model="form.sampleQuantity"
                placeholder="请输入抽检数量"
                :precision="0"
              />
              <span class="tip-text">最大可抽检: {{ maxSampleQuantity }}</span>
            </el-form-item>
          </el-col>
        </el-row>
        <template v-if="form.checkResult">
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange"/>
              <el-form-item label="检验数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange" :disabled="showSampleFields"/>
                <span v-if="showSampleFields" class="tip-text">抽检模式下检验数量等于抽检数量</span>
              </el-form-item>
            </el-col>
            <el-col :span="12">
@@ -46,12 +71,12 @@
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="合格数量:" prop="qualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="不合格数量:" prop="unqualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
          </el-row>
@@ -101,7 +126,7 @@
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch } from "vue";
import { userListNoPage } from "@/api/system/user.js";
import { batchQuickInspect } from "@/api/qualityManagement/rawMaterialInspection.js";
import { qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId } from "@/api/qualityManagement/metricMaintenance.js";
@@ -115,12 +140,14 @@
const selectedRows = ref([]);
const testStandardOptions = ref([]);
const inspectType = ref(2); // å‡ºåŽ‚æ£€éªŒç±»åž‹
const currentInspect = ref(null);
const data = reactive({
  form: {
    checkResult: '',
    testStandardId: '',
    quantity: undefined,
    sampleQuantity: undefined,
    qualifiedQuantity: undefined,
    unqualifiedQuantity: undefined,
    checkCompany: '',
@@ -140,6 +167,59 @@
});
const { form, rules } = toRefs(data);
// æ£€éªŒæ¨¡å¼æ ‡ç­¾
const inspectModeLabel = computed(() => {
  if (!currentInspect.value) return '全检';
  return currentInspect.value.inspectRule === 1 ? '抽检' : '全检';
});
const inspectModeTag = computed(() => {
  if (!currentInspect.value) return 'success';
  return currentInspect.value.inspectRule === 1 ? 'warning' : 'success';
});
// æ˜¯å¦æ˜¾ç¤ºæŠ½æ£€å­—段
const showSampleFields = computed(() => {
  return currentInspect.value && currentInspect.value.inspectRule === 1;
});
// æ€»æ•°é‡
const totalQuantity = computed(() => {
  return currentInspect.value?.quantity || 0;
});
// æœ€å¤§æŠ½æ£€æ•°é‡
const maxSampleQuantity = computed(() => {
  return totalQuantity.value;
});
// é»˜è®¤æŠ½æ£€æ•°é‡ï¼ˆä»Žæ£€éªŒå•获取或按比例计算)
const defaultSampleQuantity = computed(() => {
  if (!currentInspect.value) return 0;
  if (currentInspect.value.sampleQuantity) {
    return currentInspect.value.sampleQuantity;
  }
  if (currentInspect.value.sampleRatio) {
    return Math.ceil(totalQuantity.value * currentInspect.value.sampleRatio / 100);
  }
  return totalQuantity.value;
});
// ç›‘听抽检数量变化
watch(() => form.value.sampleQuantity, (val) => {
  if (showSampleFields.value && val) {
    form.value.quantity = val;
    // æ›´æ–°åˆæ ¼/不合格数量
    if (form.value.checkResult === '合格') {
      form.value.qualifiedQuantity = val;
      form.value.unqualifiedQuantity = 0;
    } else if (form.value.checkResult === '不合格') {
      form.value.qualifiedQuantity = 0;
      form.value.unqualifiedQuantity = val;
    }
  }
});
const tableColumn = ref([
  {
    label: "指标",
@@ -150,7 +230,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
@@ -172,14 +252,22 @@
  selectedIds.value = ids;
  selectedRows.value = rows;
  dialogVisible.value = true;
  // å–第一条检验单的信息作为参考
  if (rows && rows.length > 0) {
    currentInspect.value = rows[0];
  } else {
    currentInspect.value = null;
  }
  // è®¡ç®—选中行的总数量
  const totalQuantity = rows.reduce((sum, row) => sum + (Number(row.quantity) || 0), 0);
  const totalQty = rows.reduce((sum, row) => sum + (Number(row.quantity) || 0), 0);
  const sampleQty = showSampleFields.value ? defaultSampleQuantity.value : undefined;
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  const userListsRes = await userListNoPage();
  userList.value = userListsRes.data;
  // åŠ è½½æŒ‡æ ‡é€‰é¡¹ï¼ˆæ ¹æ®ç¬¬ä¸€ä¸ªé€‰ä¸­è¡Œçš„äº§å“ID)
  if (rows && rows.length > 0 && rows[0].productId) {
    const params = {
@@ -191,20 +279,21 @@
  } else {
    testStandardOptions.value = [];
  }
  // é‡ç½®è¡¨å•
  form.value = {
    checkResult: '',
    testStandardId: '',
    quantity: totalQuantity,
    qualifiedQuantity: totalQuantity,
    quantity: showSampleFields.value ? sampleQty : totalQty,
    sampleQuantity: sampleQty,
    qualifiedQuantity: totalQty,
    unqualifiedQuantity: 0,
    checkCompany: '',
    checkName: '',
    checkTime: '',
  };
  tableData.value = [];
  await nextTick();
  proxy.$refs.formRef?.clearValidate();
};
@@ -233,14 +322,16 @@
// æ£€æµ‹ç»“果变化处理
const handleCheckResultChange = (value) => {
  const qty = form.value.quantity || 0;
  if (value === '合格') {
    // åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ç­‰äºŽæ•°é‡ï¼Œä¸åˆæ ¼æ•°é‡ä¸º0
    form.value.qualifiedQuantity = form.value.quantity || 0;
    form.value.qualifiedQuantity = qty;
    form.value.unqualifiedQuantity = 0;
  } else if (value === '不合格') {
    // ä¸åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ä¸º0,不合格数量等于数量
    form.value.qualifiedQuantity = 0;
    form.value.unqualifiedQuantity = form.value.quantity || 0;
    form.value.unqualifiedQuantity = qty;
  } else if (value === '部分合格') {
    form.value.qualifiedQuantity = undefined;
    form.value.unqualifiedQuantity = undefined;
  }
};
@@ -259,7 +350,7 @@
const handleQualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("合格数量不能大于总数量");
    proxy.$modal.msgWarning("合格数量不能大于检验数量");
    form.value.qualifiedQuantity = quantity;
    form.value.unqualifiedQuantity = 0;
  } else {
@@ -272,7 +363,7 @@
const handleUnqualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("不合格数量不能大于总数量");
    proxy.$modal.msgWarning("不合格数量不能大于检验数量");
    form.value.unqualifiedQuantity = quantity;
    form.value.qualifiedQuantity = 0;
  } else {
@@ -286,7 +377,7 @@
  const qualified = form.value.qualifiedQuantity || 0;
  const unqualified = form.value.unqualifiedQuantity || 0;
  const quantity = form.value.quantity || 0;
  if (qualified === quantity && unqualified === 0) {
    form.value.checkResult = '合格';
  } else if (unqualified === quantity && qualified === 0) {
@@ -300,6 +391,15 @@
const submitForm = () => {
  proxy.$refs.formRef.validate((valid) => {
    if (valid) {
      // éƒ¨åˆ†åˆæ ¼æ—¶æ ¡éªŒæ•°é‡
      if (form.value.checkResult === '部分合格') {
        const sum = (form.value.qualifiedQuantity || 0) + (form.value.unqualifiedQuantity || 0);
        if (sum > (form.value.quantity || 0)) {
          proxy.$modal.msgWarning('合格数量+不合格数量不能超过检验数量');
          return;
        }
      }
      const data = {
        ids: selectedIds.value,
        inspectType: inspectType.value,
@@ -327,4 +427,16 @@
</script>
<style scoped>
.mode-tip {
  margin-left: 10px;
  font-size: 13px;
  color: #909399;
}
.tip-text {
  display: block;
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}
</style>
src/views/qualityManagement/finalInspection/index.vue
@@ -237,6 +237,22 @@
      },
    },
    {
      label: "检验规则",
      prop: "inspectRule",
      width: 100,
      formatData: (val) => val === 0 ? "全检" : val === 1 ? "抽检" : ""
    },
    {
      label: "抽检比例(%)",
      prop: "sampleRatio",
      width: 120,
    },
    {
      label: "抽检数量",
      prop: "sampleQuantity",
      width: 100,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
src/views/qualityManagement/metricMaintenance/ParamFormDialog.vue
@@ -20,8 +20,8 @@
      <el-form-item label="单位" prop="unit">
        <el-input v-model="form.unit" placeholder="请输入单位" />
      </el-form-item>
      <el-form-item label="标准值" prop="standardValue">
        <el-input v-model="form.standardValue" placeholder="请输入标准值" />
      <el-form-item label="厂家标准值" prop="standardValue">
        <el-input v-model="form.standardValue" placeholder="请输入厂家标准值" />
      </el-form-item>
      <el-form-item label="内控值" prop="controlValue">
        <el-input v-model="form.controlValue" placeholder="请输入内控值" />
src/views/qualityManagement/metricMaintenance/StandardFormDialog.vue
@@ -42,6 +42,15 @@
          placeholder="请输入备注"
        />
      </el-form-item>
      <el-form-item label="检验规则" prop="inspectRule">
        <el-radio-group v-model="form.inspectRule">
          <el-radio :label="0">全检</el-radio>
          <el-radio :label="1">抽检</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label="抽检比例(%)" prop="sampleRatio" v-if="form.inspectRule === 1">
        <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" style="width: 100%" />
      </el-form-item>
    </el-form>
  </FormDialog>
</template>
src/views/qualityManagement/metricMaintenance/index.vue
@@ -122,7 +122,7 @@
        <el-table-column type="index" label="序号" width="60" align="center" />
        <el-table-column prop="parameterItem" label="参数项" min-width="120" />
        <el-table-column prop="unit" label="单位" width="80" />
        <el-table-column prop="standardValue" label="标准值" min-width="120" />
        <el-table-column prop="standardValue" label="厂家标准值" min-width="120" />
        <el-table-column prop="controlValue" label="内控值" min-width="120" />
        <el-table-column prop="defaultValue" label="默认值" min-width="120" />
        <el-table-column label="操作" width="140" fixed="right" align="center">
@@ -213,7 +213,9 @@
    remark: '',
    state: '0',
    inspectType: '',
    processId: ''
    processId: '',
    inspectRule: 0,
    sampleRatio: undefined
  },
  standardRules: {
    standardNo: [{ required: true, message: '请输入标准编号', trigger: 'blur' }],
@@ -333,6 +335,19 @@
    label: '备注',
    prop: 'remark',
    minWidth: 160,
    align: 'center'
  },
  {
    label: '检验规则',
    prop: 'inspectRule',
    minWidth: 100,
    align: 'center',
    formatData: (val) => val === 0 ? '全检' : val === 1 ? '抽检' : ''
  },
  {
    label: '抽检比例(%)',
    prop: 'sampleRatio',
    minWidth: 120,
    align: 'center'
  },
  {
@@ -583,7 +598,9 @@
      remark: '',
      state: '0',
      inspectType: '',
      processId: ''
      processId: '',
        inspectRule: 0,
        sampleRatio: undefined
    })
  } else if (type === 'edit' && row) {
    Object.assign(standardForm.value, {
src/views/qualityManagement/metricMaintenance/index0.vue
@@ -110,10 +110,10 @@
        </el-row>
        <el-row>
          <el-col :span="24">
            <el-form-item label="标准值:" prop="standardValue">
            <el-form-item label="厂家标准值:" prop="standardValue">
              <el-input
                  v-model="modelForm.standardValue"
                  placeholder="请输入标准值"
                  placeholder="请输入厂家标准值"
                  clearable
              />
            </el-form-item>
@@ -176,7 +176,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -131,6 +131,26 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验规则" prop="inspectRule">
              <el-radio-group v-model="form.inspectRule">
                <el-radio :label="0">全检</el-radio>
                <el-radio :label="1">抽检</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检比例(%)" prop="sampleRatio">
              <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" style="width: 100%" @change="calcSampleQuantity" :disabled="isViewMode" />
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检数量" prop="sampleQuantity">
              <el-input-number v-model="form.sampleQuantity" :min="0" :precision="2" style="width: 100%" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测单位:"
                          prop="checkCompany">
              <el-input v-model="form.checkCompany"
@@ -252,6 +272,9 @@
      checkCompany: "",
      checkResult: "",
      purchaseContractNo: "",
      inspectRule: 0,
      sampleRatio: undefined,
      sampleQuantity: undefined,
    },
    rules: {
      checkTime: [{ required: false, message: "请输入", trigger: "blur" }],
@@ -291,7 +314,7 @@
      prop: "unit",
    },
    {
      label: "标准值",
      label: "厂家标准值",
      prop: "standardValue",
    },
    {
@@ -341,6 +364,9 @@
      checkCompany: "",
      checkResult: "",
      purchaseContractNo: "",
      inspectRule: 0,
      sampleRatio: undefined,
      sampleQuantity: undefined,
    };
    testStandardOptions.value = [];
    tableData.value = [];
@@ -468,6 +494,16 @@
    form.value.qualifiedQuantity = Math.max(0, quantity - unqualified);
  };
  const calcSampleQuantity = () => {
    const q = parseFloat(form.value.quantity) || 0;
    const r = parseFloat(form.value.sampleRatio) || 0;
    if (q > 0 && r > 0) {
      form.value.sampleQuantity = Number((q * r / 100).toFixed(2));
    } else {
      form.value.sampleQuantity = undefined;
    }
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
src/views/qualityManagement/processInspection/components/inspectionFormDia.vue
@@ -58,7 +58,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
src/views/qualityManagement/processInspection/components/quickCheckDia.vue
@@ -9,6 +9,14 @@
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验模式:">
              <el-tag :type="inspectModeTag" size="large">{{ inspectModeLabel }}</el-tag>
              <span v-if="showSampleFields && currentInspect" class="mode-tip">
                (抽检比例: {{ currentInspect.sampleRatio || 0 }}%)
              </span>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult" placeholder="请选择检测结果" style="width: 100%" @change="handleCheckResultChange">
                <el-option label="合格" value="合格" />
@@ -17,6 +25,8 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select v-model="form.testStandardId" placeholder="请选择指标" style="width: 100%" @change="handleTestStandardChange">
@@ -29,12 +39,27 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="showSampleFields">
            <el-form-item label="抽检数量:" prop="sampleQuantity">
              <el-input-number
                :step="1"
                :min="1"
                :max="maxSampleQuantity"
                style="width: 100%"
                v-model="form.sampleQuantity"
                placeholder="请输入抽检数量"
                :precision="0"
              />
              <span class="tip-text">最大可抽检: {{ maxSampleQuantity }}</span>
            </el-form-item>
          </el-col>
        </el-row>
        <template v-if="form.checkResult">
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange"/>
              <el-form-item label="检验数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange" :disabled="showSampleFields"/>
                <span v-if="showSampleFields" class="tip-text">抽检模式下检验数量等于抽检数量</span>
              </el-form-item>
            </el-col>
            <el-col :span="12">
@@ -46,12 +71,12 @@
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="合格数量:" prop="qualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="不合格数量:" prop="unqualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
          </el-row>
@@ -101,7 +126,7 @@
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch } from "vue";
import { userListNoPage } from "@/api/system/user.js";
import { batchQuickInspect } from "@/api/qualityManagement/rawMaterialInspection.js";
import { qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId } from "@/api/qualityManagement/metricMaintenance.js";
@@ -115,12 +140,14 @@
const selectedRows = ref([]);
const testStandardOptions = ref([]);
const inspectType = ref(1); // è¿‡ç¨‹æ£€éªŒç±»åž‹
const currentInspect = ref(null);
const data = reactive({
  form: {
    checkResult: '',
    testStandardId: '',
    quantity: undefined,
    sampleQuantity: undefined,
    qualifiedQuantity: undefined,
    unqualifiedQuantity: undefined,
    checkCompany: '',
@@ -140,6 +167,59 @@
});
const { form, rules } = toRefs(data);
// æ£€éªŒæ¨¡å¼æ ‡ç­¾
const inspectModeLabel = computed(() => {
  if (!currentInspect.value) return '全检';
  return currentInspect.value.inspectRule === 1 ? '抽检' : '全检';
});
const inspectModeTag = computed(() => {
  if (!currentInspect.value) return 'success';
  return currentInspect.value.inspectRule === 1 ? 'warning' : 'success';
});
// æ˜¯å¦æ˜¾ç¤ºæŠ½æ£€å­—段
const showSampleFields = computed(() => {
  return currentInspect.value && currentInspect.value.inspectRule === 1;
});
// æ€»æ•°é‡
const totalQuantity = computed(() => {
  return currentInspect.value?.quantity || 0;
});
// æœ€å¤§æŠ½æ£€æ•°é‡
const maxSampleQuantity = computed(() => {
  return totalQuantity.value;
});
// é»˜è®¤æŠ½æ£€æ•°é‡ï¼ˆä»Žæ£€éªŒå•获取或按比例计算)
const defaultSampleQuantity = computed(() => {
  if (!currentInspect.value) return 0;
  if (currentInspect.value.sampleQuantity) {
    return currentInspect.value.sampleQuantity;
  }
  if (currentInspect.value.sampleRatio) {
    return Math.ceil(totalQuantity.value * currentInspect.value.sampleRatio / 100);
  }
  return totalQuantity.value;
});
// ç›‘听抽检数量变化
watch(() => form.value.sampleQuantity, (val) => {
  if (showSampleFields.value && val) {
    form.value.quantity = val;
    // æ›´æ–°åˆæ ¼/不合格数量
    if (form.value.checkResult === '合格') {
      form.value.qualifiedQuantity = val;
      form.value.unqualifiedQuantity = 0;
    } else if (form.value.checkResult === '不合格') {
      form.value.qualifiedQuantity = 0;
      form.value.unqualifiedQuantity = val;
    }
  }
});
const tableColumn = ref([
  {
    label: "指标",
@@ -150,7 +230,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
@@ -172,11 +252,18 @@
  selectedIds.value = ids;
  selectedRows.value = rows;
  dialogVisible.value = true;
  // å–第一条检验单的信息作为参考
  if (rows && rows.length > 0) {
    currentInspect.value = rows[0];
  } else {
    currentInspect.value = null;
  }
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  const userListsRes = await userListNoPage();
  userList.value = userListsRes.data;
  // åŠ è½½æŒ‡æ ‡é€‰é¡¹ï¼ˆæ ¹æ®ç¬¬ä¸€ä¸ªé€‰ä¸­è¡Œçš„äº§å“ID)
  if (rows && rows.length > 0 && rows[0].productId) {
    const params = {
@@ -188,12 +275,16 @@
  } else {
    testStandardOptions.value = [];
  }
  // é‡ç½®è¡¨å•
  const totalQty = currentInspect.value?.quantity || undefined;
  const sampleQty = showSampleFields.value ? defaultSampleQuantity.value : undefined;
  form.value = {
    checkResult: '',
    testStandardId: '',
    quantity: undefined,
    quantity: showSampleFields.value ? sampleQty : totalQty,
    sampleQuantity: sampleQty,
    qualifiedQuantity: undefined,
    unqualifiedQuantity: undefined,
    checkCompany: '',
@@ -201,7 +292,7 @@
    checkTime: '',
  };
  tableData.value = [];
  await nextTick();
  proxy.$refs.formRef?.clearValidate();
};
@@ -230,14 +321,16 @@
// æ£€æµ‹ç»“果变化处理
const handleCheckResultChange = (value) => {
  const qty = form.value.quantity || 0;
  if (value === '合格') {
    // åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ç­‰äºŽæ•°é‡ï¼Œä¸åˆæ ¼æ•°é‡ä¸º0
    form.value.qualifiedQuantity = form.value.quantity || 0;
    form.value.qualifiedQuantity = qty;
    form.value.unqualifiedQuantity = 0;
  } else if (value === '不合格') {
    // ä¸åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ä¸º0,不合格数量等于数量
    form.value.qualifiedQuantity = 0;
    form.value.unqualifiedQuantity = form.value.quantity || 0;
    form.value.unqualifiedQuantity = qty;
  } else if (value === '部分合格') {
    form.value.qualifiedQuantity = undefined;
    form.value.unqualifiedQuantity = undefined;
  }
};
@@ -256,7 +349,7 @@
const handleQualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("合格数量不能大于总数量");
    proxy.$modal.msgWarning("合格数量不能大于检验数量");
    form.value.qualifiedQuantity = quantity;
    form.value.unqualifiedQuantity = 0;
  } else {
@@ -269,7 +362,7 @@
const handleUnqualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("不合格数量不能大于总数量");
    proxy.$modal.msgWarning("不合格数量不能大于检验数量");
    form.value.unqualifiedQuantity = quantity;
    form.value.qualifiedQuantity = 0;
  } else {
@@ -283,7 +376,7 @@
  const qualified = form.value.qualifiedQuantity || 0;
  const unqualified = form.value.unqualifiedQuantity || 0;
  const quantity = form.value.quantity || 0;
  if (qualified === quantity && unqualified === 0) {
    form.value.checkResult = '合格';
  } else if (unqualified === quantity && qualified === 0) {
@@ -297,6 +390,15 @@
const submitForm = () => {
  proxy.$refs.formRef.validate((valid) => {
    if (valid) {
      // éƒ¨åˆ†åˆæ ¼æ—¶æ ¡éªŒæ•°é‡
      if (form.value.checkResult === '部分合格') {
        const sum = (form.value.qualifiedQuantity || 0) + (form.value.unqualifiedQuantity || 0);
        if (sum > (form.value.quantity || 0)) {
          proxy.$modal.msgWarning('合格数量+不合格数量不能超过检验数量');
          return;
        }
      }
      const data = {
        ids: selectedIds.value,
        inspectType: inspectType.value,
@@ -324,4 +426,16 @@
</script>
<style scoped>
.mode-tip {
  margin-left: 10px;
  font-size: 13px;
  color: #909399;
}
.tip-text {
  display: block;
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}
</style>
src/views/qualityManagement/processInspection/index.vue
@@ -237,6 +237,22 @@
      },
    },
    {
      label: "检验规则",
      prop: "inspectRule",
      width: 100,
      formatData: (val) => val === 0 ? "全检" : val === 1 ? "抽检" : ""
    },
    {
      label: "抽检比例(%)",
      prop: "sampleRatio",
      width: 120,
    },
    {
      label: "抽检数量",
      prop: "sampleQuantity",
      width: 100,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -109,6 +109,26 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验规则" prop="inspectRule">
              <el-radio-group v-model="form.inspectRule">
                <el-radio :label="0">全检</el-radio>
                <el-radio :label="1">抽检</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检比例(%)" prop="sampleRatio">
              <el-input-number v-model="form.sampleRatio" :min="0.01" :max="100" :precision="2" placeholder="请输入抽检比例" style="width: 100%" @change="calcSampleQuantity" :disabled="isViewMode" />
            </el-form-item>
          </el-col>
          <el-col :span="6" v-if="form.inspectRule === 1">
            <el-form-item label="抽检数量" prop="sampleQuantity">
              <el-input-number v-model="form.sampleQuantity" :min="0" :precision="2" style="width: 100%" disabled />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检测单位:" prop="checkCompany">
              <el-input v-model="form.checkCompany" placeholder="请输入" clearable :disabled="isViewMode"/>
            </el-form-item>
@@ -203,6 +223,9 @@
    stockInRatio: 100.00,
    checkCompany: "",
    checkResult: "",
    inspectRule: 0,
    sampleRatio: undefined,
    sampleQuantity: undefined,
  },
  rules: {
    checkTime: [{required: false, message: "请输入", trigger: "blur"},],
@@ -245,7 +268,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
@@ -308,6 +331,9 @@
    stockInRatio: 100.00,
    checkCompany: "",
    checkResult: "",
    inspectRule: 0,
    sampleRatio: undefined,
    sampleQuantity: undefined,
  }
  testStandardOptions.value = [];
  tableData.value = [];
@@ -403,6 +429,16 @@
  form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || '';
}
const calcSampleQuantity = () => {
  const q = parseFloat(form.value.quantity) || 0;
  const r = parseFloat(form.value.sampleRatio) || 0;
  if (q > 0 && r > 0) {
    form.value.sampleQuantity = Number((q * r / 100).toFixed(2));
  } else {
    form.value.sampleQuantity = undefined;
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === productId) {
src/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue
@@ -58,7 +58,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
src/views/qualityManagement/rawMaterialInspection/components/quickCheckDia.vue
@@ -9,6 +9,14 @@
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="检验模式:">
              <el-tag :type="inspectModeTag" size="large">{{ inspectModeLabel }}</el-tag>
              <span v-if="showSampleFields && currentInspect" class="mode-tip">
                (抽检比例: {{ currentInspect.sampleRatio || 0 }}%)
              </span>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult" placeholder="请选择检测结果" style="width: 100%" @change="handleCheckResultChange">
                <el-option label="合格" value="合格" />
@@ -17,6 +25,8 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="指标选择:" prop="testStandardId">
              <el-select v-model="form.testStandardId" placeholder="请选择指标" style="width: 100%" @change="handleTestStandardChange">
@@ -29,12 +39,27 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="showSampleFields">
            <el-form-item label="抽检数量:" prop="sampleQuantity">
              <el-input-number
                :step="1"
                :min="1"
                :max="maxSampleQuantity"
                style="width: 100%"
                v-model="form.sampleQuantity"
                placeholder="请输入抽检数量"
                :precision="0"
              />
              <span class="tip-text">最大可抽检: {{ maxSampleQuantity }}</span>
            </el-form-item>
          </el-col>
        </el-row>
        <template v-if="form.checkResult">
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange"/>
              <el-form-item label="检验数量:" prop="quantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入数量" clearable :precision="2" @change="handleQuantityChange" :disabled="showSampleFields"/>
                <span v-if="showSampleFields" class="tip-text">抽检模式下检验数量等于抽检数量</span>
              </el-form-item>
            </el-col>
            <el-col :span="12">
@@ -46,12 +71,12 @@
          <el-row :gutter="30">
            <el-col :span="12">
              <el-form-item label="合格数量:" prop="qualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.qualifiedQuantity" placeholder="请输入合格数量" clearable :precision="2" @change="handleQualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="不合格数量:" prop="unqualifiedQuantity">
                <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
                <el-input-number :step="0.01" :min="0" :max="form.quantity" style="width: 100%" v-model="form.unqualifiedQuantity" placeholder="请输入不合格数量" clearable :precision="2" @change="handleUnqualifiedQuantityChange"/>
              </el-form-item>
            </el-col>
          </el-row>
@@ -108,7 +133,7 @@
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed, watch } from "vue";
import { userListNoPage } from "@/api/system/user.js";
import { batchQuickInspect } from "@/api/qualityManagement/rawMaterialInspection.js";
import { qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId } from "@/api/qualityManagement/metricMaintenance.js";
@@ -122,12 +147,14 @@
const selectedRows = ref([]);
const testStandardOptions = ref([]);
const inspectType = ref(0); // åŽŸææ–™æ£€éªŒç±»åž‹
const currentInspect = ref(null);
const data = reactive({
  form: {
    checkResult: '',
    testStandardId: '',
    quantity: undefined,
    sampleQuantity: undefined,
    qualifiedQuantity: undefined,
    unqualifiedQuantity: undefined,
    stockInRatio: 100.00,
@@ -164,6 +191,59 @@
});
const { form, rules } = toRefs(data);
// æ£€éªŒæ¨¡å¼æ ‡ç­¾
const inspectModeLabel = computed(() => {
  if (!currentInspect.value) return '全检';
  return currentInspect.value.inspectRule === 1 ? '抽检' : '全检';
});
const inspectModeTag = computed(() => {
  if (!currentInspect.value) return 'success';
  return currentInspect.value.inspectRule === 1 ? 'warning' : 'success';
});
// æ˜¯å¦æ˜¾ç¤ºæŠ½æ£€å­—段
const showSampleFields = computed(() => {
  return currentInspect.value && currentInspect.value.inspectRule === 1;
});
// æ€»æ•°é‡
const totalQuantity = computed(() => {
  return currentInspect.value?.quantity || 0;
});
// æœ€å¤§æŠ½æ£€æ•°é‡
const maxSampleQuantity = computed(() => {
  return totalQuantity.value;
});
// é»˜è®¤æŠ½æ£€æ•°é‡ï¼ˆä»Žæ£€éªŒå•获取或按比例计算)
const defaultSampleQuantity = computed(() => {
  if (!currentInspect.value) return 0;
  if (currentInspect.value.sampleQuantity) {
    return currentInspect.value.sampleQuantity;
  }
  if (currentInspect.value.sampleRatio) {
    return Math.ceil(totalQuantity.value * currentInspect.value.sampleRatio / 100);
  }
  return totalQuantity.value;
});
// ç›‘听抽检数量变化
watch(() => form.value.sampleQuantity, (val) => {
  if (showSampleFields.value && val) {
    form.value.quantity = val;
    // æ›´æ–°åˆæ ¼/不合格数量
    if (form.value.checkResult === '合格') {
      form.value.qualifiedQuantity = val;
      form.value.unqualifiedQuantity = 0;
    } else if (form.value.checkResult === '不合格') {
      form.value.qualifiedQuantity = 0;
      form.value.unqualifiedQuantity = val;
    }
  }
});
const tableColumn = ref([
  {
    label: "指标",
@@ -174,7 +254,7 @@
    prop: "unit",
  },
  {
    label: "标准值",
    label: "厂家标准值",
    prop: "standardValue",
  },
  {
@@ -196,11 +276,18 @@
  selectedIds.value = ids;
  selectedRows.value = rows;
  dialogVisible.value = true;
  // å–第一条检验单的信息作为参考
  if (rows && rows.length > 0) {
    currentInspect.value = rows[0];
  } else {
    currentInspect.value = null;
  }
  // åŠ è½½ç”¨æˆ·åˆ—è¡¨
  const userListsRes = await userListNoPage();
  userList.value = userListsRes.data;
  // åŠ è½½æŒ‡æ ‡é€‰é¡¹ï¼ˆæ ¹æ®ç¬¬ä¸€ä¸ªé€‰ä¸­è¡Œçš„äº§å“ID)
  if (rows && rows.length > 0 && rows[0].productId) {
    const params = {
@@ -212,12 +299,16 @@
  } else {
    testStandardOptions.value = [];
  }
  // é‡ç½®è¡¨å•
  const totalQty = currentInspect.value?.quantity || undefined;
  const sampleQty = showSampleFields.value ? defaultSampleQuantity.value : undefined;
  form.value = {
    checkResult: '',
    testStandardId: '',
    quantity: undefined,
    quantity: showSampleFields.value ? sampleQty : totalQty,
    sampleQuantity: sampleQty,
    qualifiedQuantity: undefined,
    unqualifiedQuantity: undefined,
    stockInRatio: 100.00,
@@ -226,7 +317,7 @@
    checkTime: '',
  };
  tableData.value = [];
  await nextTick();
  proxy.$refs.formRef?.clearValidate();
};
@@ -255,14 +346,16 @@
// æ£€æµ‹ç»“果变化处理
const handleCheckResultChange = (value) => {
  const qty = form.value.quantity || 0;
  if (value === '合格') {
    // åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ç­‰äºŽæ•°é‡ï¼Œä¸åˆæ ¼æ•°é‡ä¸º0
    form.value.qualifiedQuantity = form.value.quantity || 0;
    form.value.qualifiedQuantity = qty;
    form.value.unqualifiedQuantity = 0;
  } else if (value === '不合格') {
    // ä¸åˆæ ¼æ—¶ï¼Œåˆæ ¼æ•°é‡ä¸º0,不合格数量等于数量
    form.value.qualifiedQuantity = 0;
    form.value.unqualifiedQuantity = form.value.quantity || 0;
    form.value.unqualifiedQuantity = qty;
  } else if (value === '部分合格') {
    form.value.qualifiedQuantity = undefined;
    form.value.unqualifiedQuantity = undefined;
  }
};
@@ -281,7 +374,7 @@
const handleQualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("合格数量不能大于总数量");
    proxy.$modal.msgWarning("合格数量不能大于检验数量");
    form.value.qualifiedQuantity = quantity;
    form.value.unqualifiedQuantity = 0;
  } else {
@@ -294,7 +387,7 @@
const handleUnqualifiedQuantityChange = (value) => {
  const quantity = form.value.quantity || 0;
  if (value > quantity) {
    proxy.$modal.msgWarning("不合格数量不能大于总数量");
    proxy.$modal.msgWarning("不合格数量不能大于检验数量");
    form.value.unqualifiedQuantity = quantity;
    form.value.qualifiedQuantity = 0;
  } else {
@@ -308,7 +401,7 @@
  const qualified = form.value.qualifiedQuantity || 0;
  const unqualified = form.value.unqualifiedQuantity || 0;
  const quantity = form.value.quantity || 0;
  if (qualified === quantity && unqualified === 0) {
    form.value.checkResult = '合格';
  } else if (unqualified === quantity && qualified === 0) {
@@ -322,6 +415,15 @@
const submitForm = () => {
  proxy.$refs.formRef.validate((valid) => {
    if (valid) {
      // éƒ¨åˆ†åˆæ ¼æ—¶æ ¡éªŒæ•°é‡
      if (form.value.checkResult === '部分合格') {
        const sum = (form.value.qualifiedQuantity || 0) + (form.value.unqualifiedQuantity || 0);
        if (sum > (form.value.quantity || 0)) {
          proxy.$modal.msgWarning('合格数量+不合格数量不能超过检验数量');
          return;
        }
      }
      const data = {
        ids: selectedIds.value,
        inspectType: inspectType.value,
@@ -349,4 +451,16 @@
</script>
<style scoped>
.mode-tip {
  margin-left: 10px;
  font-size: 13px;
  color: #909399;
}
.tip-text {
  display: block;
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}
</style>
src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -232,6 +232,22 @@
      },
    },
    {
      label: "检验规则",
      prop: "inspectRule",
      width: 100,
      formatData: (val) => val === 0 ? "全检" : val === 1 ? "抽检" : ""
    },
    {
      label: "抽检比例(%)",
      prop: "sampleRatio",
      width: 120,
    },
    {
      label: "抽检数量",
      prop: "sampleQuantity",
      width: 100,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue
@@ -1,5 +1,6 @@
<template>
  <div class="panel-header">
    <span class="header-dot"></span>
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
@@ -20,6 +21,23 @@
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  gap: 10px;
}
.header-dot {
  width: 12px;
  height: 12px;
  background: linear-gradient(135deg, #4ee4ff 0%, #217aff 100%);
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  animation: dotPulse 2s ease-in-out infinite;
  margin-left: 32px;
}
@keyframes dotPulse {
  0%, 100% { opacity: 0.8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.1); box-shadow: 0 0 10px rgba(78, 228, 255, 0.5); }
}
.panel-title {
@@ -27,7 +45,13 @@
  font-weight: 500;
  font-size: 16px;
  color: #D9ECFF;
  padding-left: 46px;
  padding-left: 10px;
  line-height: 36px;
  animation: titleShimmer 3s ease-in-out infinite;
}
@keyframes titleShimmer {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.85; }
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -183,6 +183,32 @@
  padding: 18px;
  width: 100%;
  height: 428px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -101,6 +101,32 @@
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.equipment-stats::before,
.equipment-stats::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.equipment-stats::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.equipment-stats::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.equipment-header {
@@ -132,5 +158,11 @@
.equipment-icon {
  width: 50px;
  height: 50px;
  animation: iconFloat 4s ease-in-out infinite;
}
@keyframes iconFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -79,12 +79,53 @@
  background-position: center;
  background-repeat: no-repeat;
  height: 142px;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
  animation: cardFadeIn 0.6s ease-out both;
}
.stat-card:nth-child(1) { animation-delay: 0.1s; }
.stat-card:nth-child(2) { animation-delay: 0.2s; }
.stat-card:nth-child(3) { animation-delay: 0.3s; }
@keyframes cardFadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}
.stat-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(0, 212, 255, 0.2);
}
/* å¡ç‰‡åº•部光线 */
.stat-card::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 10%;
  right: 10%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.stat-card:hover::after {
  opacity: 1;
}
.card-icon {
  width: 100px;
  height: 100px;
  width: 70px;
  height: 70px;
  margin: 20px 20px 0 10px;
  animation: iconFloat 4s ease-in-out infinite;
}
@keyframes iconFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}
.card-content {
@@ -95,16 +136,22 @@
.card-value {
  font-weight: 500;
  font-size: 40px;
  font-size: 32px;
  background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  transition: all 0.3s ease;
}
.stat-card:hover .card-value {
  text-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
  transform: scale(1.05);
}
.card-label {
  font-weight: 400;
  font-size: 19px;
  font-size: 16px;
  color: rgba(208, 231, 255, 0.7);
}
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -234,6 +234,32 @@
  padding: 18px;
  width: 100%;
  height: 449px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.pie-chart-wrapper {
@@ -241,6 +267,12 @@
  width: 100%;
  height: 320px;
  background: transparent;
  animation: pieFloat 4s ease-in-out infinite;
}
@keyframes pieFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@@ -258,5 +290,11 @@
  left: 25%;
  top: 50%;
  transform: translate(-51.5%, -50%);
  animation: pieBgRotate 30s linear infinite;
}
@keyframes pieBgRotate {
  from { transform: translate(-51.5%, -50%) rotate(0deg); }
  to { transform: translate(-51.5%, -50%) rotate(360deg); }
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -204,12 +204,46 @@
  padding: 18px;
  width: 100%;
  height: 449px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.pie-chart-wrapper {
  position: relative;
  width: 100%;
  height: 320px;
  animation: pieFloat 4s ease-in-out infinite;
}
@keyframes pieFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
.pie-background {
  position: absolute;
  left: 25%;
@@ -223,5 +257,11 @@
  background-repeat: no-repeat;
  z-index: 1;
  pointer-events: none;
  animation: pieBgRotate 30s linear infinite;
}
@keyframes pieBgRotate {
  from { transform: translate(-51.5%, -50%) rotate(0deg); }
  to { transform: translate(-51.5%, -50%) rotate(360deg); }
}
</style>
src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -183,6 +183,25 @@
justify-content: center;
background-color: #000;
overflow: hidden;
position: relative;
}
/* åŠ¨æ€ç½‘æ ¼èƒŒæ™¯ */
.scale-container::before {
content: '';
position: absolute;
inset: 0;
background-image:
  linear-gradient(rgba(0, 212, 255, 0.03) 1px, transparent 1px),
  linear-gradient(90deg, rgba(0, 212, 255, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
animation: gridMove 20s linear infinite;
z-index: 0;
}
@keyframes gridMove {
0% { transform: translate(0, 0); }
100% { transform: translate(50px, 50px); }
}
/* å†…部内容区域 - å›ºå®šè®¾è®¡å°ºå¯¸ */
@@ -246,6 +265,12 @@
color: #FFFFFF;
top: 16px;
position: absolute;
animation: titleGlow 3s ease-in-out infinite;
}
@keyframes titleGlow {
0%, 100% { text-shadow: 0 0 20px rgba(0, 212, 255, 0.3); }
50% { text-shadow: 0 0 40px rgba(0, 212, 255, 0.6), 0 0 60px rgba(0, 212, 255, 0.3); }
}
.fullscreen-btn {
@@ -286,6 +311,33 @@
overflow: hidden;
}
.left-panel {
animation: slideInLeft 0.8s ease-out;
}
.right-panel {
animation: slideInRight 0.8s ease-out;
}
.center-panel {
animation: slideInUp 0.8s ease-out;
}
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-50px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideInRight {
from { opacity: 0; transform: translateX(50px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.left-panel,
.right-panel {
flex: 1;
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue
@@ -1,5 +1,6 @@
<template>
  <div class="panel-header">
    <span class="header-dot"></span>
    <span class="panel-title">{{ title }}</span>
  </div>
</template>
@@ -20,6 +21,23 @@
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  gap: 10px;
}
.header-dot {
  width: 12px;
  height: 12px;
  background: linear-gradient(135deg, #4ee4ff 0%, #217aff 100%);
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  animation: dotPulse 2s ease-in-out infinite;
  margin-left: 32px;
}
@keyframes dotPulse {
  0%, 100% { opacity: 0.8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.1); box-shadow: 0 0 10px rgba(78, 228, 255, 0.5); }
}
.panel-title {
@@ -27,7 +45,13 @@
  font-weight: 500;
  font-size: 16px;
  color: #D9ECFF;
  padding-left: 46px;
  padding-left: 10px;
  line-height: 36px;
  animation: titleShimmer 3s ease-in-out infinite;
}
@keyframes titleShimmer {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.85; }
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/center-bottom.vue
@@ -172,6 +172,32 @@
  padding: 18px;
  width: 100%;
  height: 370px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.pie-chart-wrapper {
@@ -179,6 +205,12 @@
  width: 100%;
  height: 320px;
  background: transparent;
  animation: pieFloat 4s ease-in-out infinite;
}
@keyframes pieFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
@@ -194,5 +226,11 @@
  background-repeat: no-repeat;
  z-index: 1;
  pointer-events: none;
  animation: pieBgRotate 30s linear infinite;
}
@keyframes pieBgRotate {
  from { transform: translate(-113%, -50%) rotate(0deg); }
  to { transform: translate(-113%, -50%) rotate(360deg); }
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/center-top.vue
@@ -305,12 +305,52 @@
  background-position: center;
  background-repeat: no-repeat;
  height: 142px;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
  animation: cardFadeIn 0.6s ease-out both;
}
.stat-card:nth-child(1) { animation-delay: 0.1s; }
.stat-card:nth-child(2) { animation-delay: 0.2s; }
@keyframes cardFadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}
.stat-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(0, 212, 255, 0.2);
}
/* å¡ç‰‡åº•部光线 */
.stat-card::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 10%;
  right: 10%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.5), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.stat-card:hover::after {
  opacity: 1;
}
.card-icon {
  width: 100px;
  height: 100px;
  width: 70px;
  height: 70px;
  margin: 20px 20px 0 10px;
  animation: iconFloat 4s ease-in-out infinite;
}
@keyframes iconFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-5px); }
}
.card-content {
@@ -321,16 +361,22 @@
.card-value {
  font-weight: 500;
  font-size: 40px;
  font-size: 32px;
  background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  transition: all 0.3s ease;
}
.stat-card:hover .card-value {
  text-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
  transform: scale(1.05);
}
.card-label {
  font-weight: 400;
  font-size: 19px;
  font-size: 16px;
  color: rgba(208, 231, 255, 0.7);
}
@@ -373,6 +419,32 @@
  height: 240px;
  padding-top: 0px;
  margin-bottom: 20px;
  position: relative;
  overflow: hidden;
}
/* è®¾å¤‡ç»Ÿè®¡é¢æ¿è§’落装饰 */
.equipment-stats::before,
.equipment-stats::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.equipment-stats::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.equipment-stats::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.equipment-header {
@@ -419,7 +491,7 @@
.equipment-value {
  display: block;
  font-weight: 500;
  font-size: 40px;
  font-size: 36px;
  color: #ffffff;
  width: 120px;
  height: 110px;
@@ -429,6 +501,13 @@
  background-position: center;
  background-repeat: no-repeat;
  margin-bottom: 8px;
  transition: all 0.3s ease;
  animation: valuePulse 3s ease-in-out infinite;
}
@keyframes valuePulse {
  0%, 100% { text-shadow: 0 0 10px rgba(78, 228, 255, 0.2); }
  50% { text-shadow: 0 0 20px rgba(78, 228, 255, 0.4); }
}
.equipment-label {
@@ -446,6 +525,32 @@
  padding-top: 10px;
  height: 480px;
  flex: 1;
  position: relative;
  overflow: hidden;
}
/* äº‹ä»¶é¢æ¿è§’落装饰 */
.event-info::before,
.event-info::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.event-info::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.event-info::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.event-header {
@@ -482,6 +587,29 @@
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: all 0.3s ease;
  position: relative;
}
.todo-list li:hover {
  background: rgba(0, 212, 255, 0.08);
  transform: translateX(5px);
}
.todo-list li::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.3), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.todo-list li:hover::after {
  opacity: 1;
}
.todo-title {
src/views/reportAnalysis/dataDashboard/components/basic/left-bottom.vue
@@ -47,7 +47,7 @@
import { customerRevenueAnalysis } from '@/api/viewIndex.js'
import { listCustomer } from '@/api/basicData/customer.js'
const dateType = ref(1) // 1=周 2=月 3=季度
const dateType = ref(3) // 1=周 2=月 3=季度
const customerValue = ref(null)
const customerOptions = ref([])
@@ -249,6 +249,32 @@
  padding: 18px;
  width: 100%;
  height: 478px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/left-top.vue
@@ -230,6 +230,32 @@
  padding: 18px;
  width: 100%;
  height: 420px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.pie-chart-wrapper {
@@ -237,6 +263,12 @@
  width: 100%;
  height: 320px;
  background: transparent;
  animation: pieFloat 4s ease-in-out infinite;
}
@keyframes pieFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
.pie-background {
@@ -253,5 +285,11 @@
  left: 50%;
  top: 50%;
  transform: translate(-51.5%, -39%);
  animation: pieBgRotate 30s linear infinite;
}
@keyframes pieBgRotate {
  from { transform: translate(-51.5%, -39%) rotate(0deg); }
  to { transform: translate(-51.5%, -39%) rotate(360deg); }
}
</style>
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -33,7 +33,7 @@
  height: '100%',
}
const dateType = ref(1) // 1=周 2=月 3=季度
const dateType = ref(3) // 1=周 2=月 3=季度
// é£žæœºå›¾æ ‡ SVG path(与 right-top ä¸€è‡´ï¼‰
const aircraft =
@@ -326,6 +326,32 @@
  padding: 18px;
  width: 100%;
  height: 449px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.switch-container {
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -32,7 +32,7 @@
  height: '100%',
}
const radio1 = ref(1)
const radio1 = ref(3)
// é£žæœºå›¾æ ‡ SVG path
const aircraft =
@@ -349,6 +349,32 @@
  padding: 18px;
  width: 100%;
  height: 449px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.switch-container {
src/views/reportAnalysis/dataDashboard/index.vue
@@ -180,6 +180,25 @@
justify-content: center;
background-color: #000;
overflow: hidden;
position: relative;
}
/* åŠ¨æ€ç½‘æ ¼èƒŒæ™¯ */
.scale-container::before {
content: '';
position: absolute;
inset: 0;
background-image:
  linear-gradient(rgba(0, 212, 255, 0.03) 1px, transparent 1px),
  linear-gradient(90deg, rgba(0, 212, 255, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
animation: gridMove 20s linear infinite;
z-index: 0;
}
@keyframes gridMove {
0% { transform: translate(0, 0); }
100% { transform: translate(50px, 50px); }
}
/* å†…部内容区域 - å›ºå®šè®¾è®¡å°ºå¯¸ */
@@ -243,6 +262,12 @@
color: #FFFFFF;
top: 16px;
position: absolute;
animation: titleGlow 3s ease-in-out infinite;
}
@keyframes titleGlow {
0%, 100% { text-shadow: 0 0 20px rgba(0, 212, 255, 0.3); }
50% { text-shadow: 0 0 40px rgba(0, 212, 255, 0.6), 0 0 60px rgba(0, 212, 255, 0.3); }
}
.fullscreen-btn {
@@ -283,6 +308,33 @@
overflow: hidden;
}
.left-panel {
animation: slideInLeft 0.8s ease-out;
}
.right-panel {
animation: slideInRight 0.8s ease-out;
}
.center-panel {
animation: slideInUp 0.8s ease-out;
}
@keyframes slideInLeft {
from { opacity: 0; transform: translateX(-50px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideInRight {
from { opacity: 0; transform: translateX(50px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.left-panel,
.right-panel {
flex: 1;
src/views/reportAnalysis/qualityAnalysis/components/PanelHeader.vue
@@ -1,6 +1,9 @@
<template>
  <div class="panel-header">
    <span class="panel-title">{{ title }}</span>
    <span v-if="$slots.extra" class="panel-extra">
      <slot name="extra"></slot>
    </span>
  </div>
</template>
@@ -20,14 +23,44 @@
  background-size: 100% 100%;
  background-position: center;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
}
/* æ ‡é¢˜è£…饰动画 */
.panel-header::before {
  content: '';
  position: absolute;
  left: 20px;
  top: 50%;
  transform: translateY(-50%);
  width: 8px;
  height: 8px;
  background: #00d4ff;
  border-radius: 50%;
  animation: dotPulse 2s ease-in-out infinite;
  box-shadow: 0 0 10px rgba(0, 212, 255, 0.6);
}
@keyframes dotPulse {
  0%, 100% { opacity: 0.6; transform: translateY(-50%) scale(1); }
  50% { opacity: 1; transform: translateY(-50%) scale(1.2); }
}
.panel-title {
  width: 100%;
  font-weight: 500;
  font-size: 16px;
  color: #D9ECFF;
  padding-left: 46px;
  line-height: 36px;
  position: relative;
  text-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
}
.panel-extra {
  padding-right: 10px;
  line-height: 36px;
}
</style>
src/views/reportAnalysis/qualityAnalysis/components/center-bottom.vue
@@ -190,10 +190,29 @@
  border: 1px solid rgba(78, 228, 255, 0.25);
  cursor: pointer;
  z-index: 10;
  transition: all 0.3s ease;
}
.warn-range:hover {
  background: linear-gradient(180deg, rgba(51, 140, 255, 1) 0%, rgba(0, 184, 237, 1) 100%);
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
  transform: translateY(-1px);
}
/* æŒ‰é’®å¾®å…‰æ•ˆæžœ */
.warn-range::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s;
}
.warn-range:hover::before {
  left: 100%;
}
.main-panel {
@@ -207,5 +226,73 @@
  padding: 18px;
  width: 100%;
  height: 436px;
  position: relative;
  overflow: hidden;
}
/* å›¾è¡¨é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
/* å›¾è¡¨åº•部光线 */
.panel-item-customers::before {
  content: '';
  position: absolute;
  bottom: 0;
  left: 10%;
  right: 10%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.4), transparent);
  animation: bottomGlow 3s ease-in-out infinite;
  z-index: 1;
}
@keyframes bottomGlow {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.7; }
}
/* æ·±åº¦å›¾èƒŒæ™¯å…‰æ•ˆ */
.panel-item-customers > div {
  position: relative;
  z-index: 2;
}
.panel-item-customers::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 80%;
  height: 80%;
  background: radial-gradient(ellipse, rgba(78, 228, 255, 0.05) 0%, transparent 70%);
  pointer-events: none;
  animation: chartGlow 4s ease-in-out infinite;
}
@keyframes chartGlow {
  0%, 100% { opacity: 0.5; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 0.8; transform: translate(-50%, -50%) scale(1.02); }
}
</style>
src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
@@ -175,6 +175,51 @@
  gap: 12px;
  height: 100%;
  box-sizing: border-box;
  position: relative;
  overflow: hidden;
}
/* é¢„警面板角落装饰 */
.warn-panel::before,
.warn-panel::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(255, 77, 79, 0.5);
  border-style: solid;
  pointer-events: none;
}
.warn-panel::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.warn-panel::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
/* é¢„警脉冲背景 */
.warn-panel::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: radial-gradient(circle at 50% 50%, rgba(255, 77, 79, 0.05) 0%, transparent 70%);
  pointer-events: none;
  animation: warnPulse 4s ease-in-out infinite;
  z-index: 0;
}
@keyframes warnPulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.6; }
}
.warn-header {
@@ -188,6 +233,8 @@
      #007eff 78%,
      #007eff 100%) 1;
  padding: 10px 0 6px;
  position: relative;
  z-index: 1;
}
.warn-header-left {
@@ -202,6 +249,12 @@
  background: linear-gradient(180deg, #2aa8ff 0%, #4ee4ff 100%);
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  box-shadow: 0 0 12px rgba(78, 228, 255, 0.25);
  animation: badgePulse 2s ease-in-out infinite;
}
@keyframes badgePulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 12px rgba(78, 228, 255, 0.25); }
  50% { transform: scale(1.1); box-shadow: 0 0 20px rgba(78, 228, 255, 0.5); }
}
.warn-title {
@@ -212,6 +265,12 @@
  -webkit-text-fill-color: transparent;
  background-clip: text;
  line-height: 24px;
  animation: titleGlow 3s ease-in-out infinite;
}
@keyframes titleGlow {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.2); }
}
.warn-range {
@@ -226,6 +285,13 @@
  background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%);
  border: 1px solid rgba(78, 228, 255, 0.25);
  cursor: pointer;
  transition: all 0.3s ease;
}
.warn-range:hover {
  background: linear-gradient(180deg, rgba(51, 140, 255, 1) 0%, rgba(0, 184, 237, 1) 100%);
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
  transform: translateY(-1px);
}
.warn-body {
@@ -233,6 +299,8 @@
  gap: 18px;
  align-items: stretch;
  min-height: 260px;
  position: relative;
  z-index: 1;
}
.warn-list {
@@ -252,12 +320,41 @@
  line-height: 1.2;
  padding: 8px 0;
  border-radius: 4px;
  transition: background-color 0.2s, color 0.2s;
  transition: all 0.3s ease;
  position: relative;
  animation: itemFadeIn 0.5s ease-out both;
}
@keyframes itemFadeIn {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
.warn-item::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 77, 79, 0.2), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.warn-item:hover {
  color: #ff4d4f;
  background-color: rgba(255, 77, 79, 0.06);
  background-color: rgba(255, 77, 79, 0.08);
}
.warn-item:hover::before {
  opacity: 1;
}
.warn-item:hover .warn-text {
@@ -273,6 +370,31 @@
  justify-content: center;
  font-weight: 700;
  color: #ffffff;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}
/* æ ‡ç­¾å‘光效果 */
.warn-tag::after {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  animation: tagShine 3s ease-in-out infinite;
}
@keyframes tagShine {
  0% { left: -100%; }
  50%, 100% { left: 100%; }
}
.warn-item:hover .warn-tag {
  transform: scale(1.02);
  box-shadow: 0 0 15px currentColor;
}
.tag-raw {
@@ -299,6 +421,11 @@
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: all 0.3s ease;
}
.warn-item:hover .warn-action {
  text-shadow: 0 0 10px rgba(255, 77, 79, 0.5);
}
.warn-date {
@@ -335,6 +462,12 @@
  mask: radial-gradient(circle, transparent 62%, #000 63%);
  opacity: 0.5;
  pointer-events: none;
  animation: ringRotate 20s linear infinite;
}
@keyframes ringRotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
/* åå­—辅助线 */
@@ -351,5 +484,11 @@
  background-repeat: no-repeat;
  opacity: 0.35;
  pointer-events: none;
  animation: crossPulse 3s ease-in-out infinite;
}
@keyframes crossPulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 0.5; }
}
</style>
src/views/reportAnalysis/qualityAnalysis/components/center-top.vue
@@ -2,17 +2,26 @@
  <div>
    <!-- é¡¶éƒ¨ç»Ÿè®¡å¡ç‰‡ -->
    <div class="stats-cards">
      <div v-for="item in statItems" :key="item.name" class="stat-card">
        <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
      <div v-for="(item, index) in statItems" :key="item.name" class="stat-card" :style="{ animationDelay: `${index * 0.15}s` }">
        <div class="card-icon-wrapper">
          <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" />
          <div class="icon-ring"></div>
        </div>
        <div class="card-content">
          <span class="card-label">{{ item.name }}</span>
          <span class="card-value">{{ item.value }}</span>
          <span class="card-value">
            <span class="value-number">{{ item.value }}</span>
          </span>
          <div class="card-compare" :class="compareClass(Number(item.rate))">
            <span>同比</span>
            <el-tooltip content="今日单日检验数相对昨日单日检验数的增长率" placement="top">
              <span>{{ item.name === '总检验数' ? '日环比' : '同比' }}</span>
            </el-tooltip>
            <span class="compare-value">{{ formatPercent(item.rate) }}</span>
            <span class="compare-icon">{{ Number(item.rate) >= 0 ? '↑' : '↓' }}</span>
          </div>
        </div>
        <!-- å¡ç‰‡åº•部光线 -->
        <div class="card-glow"></div>
      </div>
    </div>
@@ -70,11 +79,14 @@
<style scoped>
.stats-cards {
  display: flex;
  gap: 30px;
  gap: 20px;
  width: 100%;
  overflow: hidden;
}
.stat-card {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  background-image: url('@/assets/BI/border@2x.png');
@@ -82,47 +94,134 @@
  background-position: center;
  background-repeat: no-repeat;
  height: 142px;
  padding-right: 10px;
  box-sizing: border-box;
  position: relative;
  overflow: hidden;
  animation: cardFadeIn 0.6s ease-out both;
}
@keyframes cardFadeIn {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* å¡ç‰‡åº•部发光效果 */
.card-glow {
  position: absolute;
  bottom: 0;
  left: 10%;
  right: 10%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.6), transparent);
  animation: glowPulse 3s ease-in-out infinite;
}
@keyframes glowPulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.8; }
}
.card-icon-wrapper {
  position: relative;
  width: 70px;
  height: 70px;
  min-width: 70px;
  margin: 0 12px 0 8px;
}
.card-icon {
  width: 100px;
  height: 100px;
  margin: 20px 20px 0 10px;
  width: 70px;
  height: 70px;
  position: relative;
  z-index: 2;
  animation: iconFloat 3s ease-in-out infinite;
}
@keyframes iconFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
/* å›¾æ ‡å¤–圈光环 */
.icon-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 80px;
  height: 80px;
  border: 1px solid rgba(0, 212, 255, 0.3);
  border-radius: 50%;
  animation: ringRotate 8s linear infinite;
}
@keyframes ringRotate {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to { transform: translate(-50%, -50%) rotate(360deg); }
}
.card-content {
  display: flex;
  flex-direction: column;
  gap: 10px;
  gap: 6px;
  min-width: 0;
  flex: 1;
}
.card-value {
  font-weight: 500;
  font-size: 40px;
  font-size: 32px;
  line-height: 1.2;
  background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  overflow: hidden;
  white-space: nowrap;
}
.value-number {
  display: inline-block;
  animation: countUp 1s ease-out;
}
@keyframes countUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.card-label {
  font-weight: 400;
  font-size: 16px;
  font-size: 14px;
  color: rgba(208, 231, 255, 0.7);
  white-space: nowrap;
}
.card-compare {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 15px;
  gap: 4px;
  font-size: 13px;
  color: #d0e7ff;
  white-space: nowrap;
  flex-wrap: nowrap;
}
.card-compare>span:first-child {
  font-size: 13px;
  font-size: 12px;
  opacity: 0.8;
}
@@ -134,7 +233,12 @@
  font-size: 14px;
  position: relative;
  top: -1px;
  /* è½»å¾®ä¸Šç§»ï¼Œè®©ç®­å¤´ä¸Žæ–‡å­—垂直居中对齐 */
  animation: arrowBounce 1s ease-in-out infinite;
}
@keyframes arrowBounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-2px); }
}
.compare-up .compare-value,
src/views/reportAnalysis/qualityAnalysis/components/left-top.vue
@@ -89,7 +89,7 @@
  {
    key: 'raw',
    title: '原材料检测',
    dateType: 1,
    dateType: 3,
    qualifiedCount: 0,
    unqualifiedCount: 0,
    qualifiedRate: 0,
@@ -98,7 +98,7 @@
  {
    key: 'process',
    title: '过程检测',
    dateType: 1,
    dateType: 3,
    qualifiedCount: 0,
    unqualifiedCount: 0,
    qualifiedRate: 0,
@@ -107,7 +107,7 @@
  {
    key: 'final',
    title: '成品出厂检测',
    dateType: 1,
    dateType: 3,
    qualifiedCount: 0,
    unqualifiedCount: 0,
    qualifiedRate: 0,
@@ -238,17 +238,13 @@
  .filters-row-left {
    width: 50%;
    color: white;
    /* ç”¨flex替代float,让子元素对齐更稳定 */
    display: flex;
    align-items: center;
    span {
      /* æ ¸å¿ƒï¼šçˆ¶çº§ç›¸å¯¹å®šä½ï¼Œä½œä¸ºä¼ªå…ƒç´ åŸºå‡† */
      position: relative;
      display: inline-block;
      /* ç»™ä¼ªå…ƒç´ å’Œæ–‡å­—留空间 */
      padding-left: 22px;
      /* æ–‡å­—垂直居中 */
      line-height: 23px;
      margin-right: 8px;
@@ -263,8 +259,8 @@
        top: 50%;
        left: 0;
        transform: translateY(-50%);
        /* ç¡®ä¿è±å½¢åœ¨æ¸å˜å—上方 */
        z-index: 1;
        animation: diamondPulse 2s ease-in-out infinite;
      }
      &::before {
@@ -277,7 +273,6 @@
        position: absolute;
        top: 50%;
        left: -1px;
        /* ç²¾å‡†è´´åœ¨è±å½¢æ­£ä¸‹æ–¹ */
        transform: translateY(calc(0% + 8px));
        z-index: 0;
      }
@@ -286,18 +281,25 @@
    p {
      width: 100px;
      height: 23px;
      /* æ¸å˜èµ·å§‹è‰²å’Œè±å½¢ç»Ÿä¸€ï¼Œæ›´åè°ƒ */
      background: linear-gradient(90deg, #217AFF 0%, rgba(33, 221, 255, 0) 100%);
      /* ç²¾å‡†åž‚直居中 */
      line-height: 26px;
      text-align: center;
      color: white;
      /* ç”¨é«˜åº¦çš„一半做圆角,确保左边是完美半圆 */
      border-radius: 12px 0 0 12px;
      /* å¯é€‰ï¼šåŠ ä¸€ç‚¹å·¦å†…è¾¹è·ï¼Œè®©æ–‡å­—ä¸è´´è¾¹ */
      padding-left: 4px;
      animation: titleShimmer 3s ease-in-out infinite;
    }
  }
}
@keyframes diamondPulse {
  0%, 100% { opacity: 0.8; }
  50% { opacity: 1; box-shadow: 0 0 10px rgba(33, 133, 255, 0.5); }
}
@keyframes titleShimmer {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.85; }
}
.panel-item-customers {
@@ -306,6 +308,31 @@
  width: 100%;
  height: 958px;
  box-sizing: border-box;
  position: relative;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 20px;
  height: 20px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
.inspect-block {
@@ -316,7 +343,24 @@
  padding: 8px 0;
  gap: 6px;
  position: relative;
  animation: blockFadeIn 0.5s ease-out both;
  animation-delay: calc(var(--index, 0) * 0.1s);
}
@keyframes blockFadeIn {
  from {
    opacity: 0;
    transform: translateX(-20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
.inspect-block:nth-child(1) { --index: 0; }
.inspect-block:nth-child(2) { --index: 1; }
.inspect-block:nth-child(3) { --index: 2; }
.inspect-block:not(:last-child)::after {
  content: '';
@@ -346,9 +390,15 @@
  display: flex;
  align-items: center;
  justify-content: center;
  animation: ringFloat 4s ease-in-out infinite;
}
/* å¤–圈刻度(点状环) */
@keyframes ringFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
/* å¤–圈刻度(点状环)- æ—‹è½¬åŠ¨ç”» */
.ring::before {
  content: '';
  position: absolute;
@@ -361,9 +411,15 @@
  mask: radial-gradient(circle, transparent 62%, #000 63%);
  opacity: 0.35;
  pointer-events: none;
  animation: ringRotate 30s linear infinite;
}
/* æŸ”和发光背景 */
@keyframes ringRotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
/* æŸ”和发光背景 - è„‰å†²åŠ¨ç”» */
.ring::after {
  content: '';
  position: absolute;
@@ -372,6 +428,12 @@
  background: radial-gradient(circle, rgba(78, 228, 255, 0.18) 0%, rgba(78, 228, 255, 0.06) 40%, rgba(0, 0, 0, 0) 70%);
  filter: blur(0.2px);
  pointer-events: none;
  animation: ringGlow 3s ease-in-out infinite;
}
@keyframes ringGlow {
  0%, 100% { opacity: 0.8; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.02); }
}
.stats {
@@ -392,6 +454,32 @@
  border: 1px solid rgba(78, 228, 255, 0.22);
  background: linear-gradient(90deg, rgba(33, 122, 255, 0.28) 0%, rgba(10, 28, 58, 0.35) 55%, rgba(10, 28, 58, 0.2) 100%);
  box-shadow: inset 0 0 18px rgba(16, 45, 95, 0.25);
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}
/* ç»Ÿè®¡è¡Œæ‚¬åœæ•ˆæžœ */
.stat-row:hover {
  border-color: rgba(0, 212, 255, 0.4);
  box-shadow: inset 0 0 18px rgba(16, 45, 95, 0.25), 0 0 15px rgba(0, 212, 255, 0.15);
}
/* ç»Ÿè®¡è¡Œåº•部光线 */
.stat-row::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.4), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.stat-row:hover::after {
  opacity: 1;
}
.stat-left {
@@ -408,6 +496,12 @@
  border-radius: 2px;
  display: inline-block;
  box-shadow: 0 0 10px rgba(78, 228, 255, 0.25);
  animation: dotPulse 2s ease-in-out infinite;
}
@keyframes dotPulse {
  0%, 100% { box-shadow: 0 0 10px rgba(78, 228, 255, 0.25); }
  50% { box-shadow: 0 0 15px rgba(78, 228, 255, 0.5); }
}
.dot-qualified {
@@ -431,6 +525,12 @@
  min-width: 40px;
  text-align: right;
  text-shadow: 0 0 10px rgba(78, 228, 255, 0.15);
  transition: all 0.3s ease;
}
.stat-row:hover .stat-value {
  text-shadow: 0 0 15px rgba(78, 228, 255, 0.4);
  transform: scale(1.05);
}
.stat-percent {
src/views/reportAnalysis/qualityAnalysis/components/right-bottom.vue
@@ -164,12 +164,65 @@
  padding: 18px;
  width: 100%;
  height: 449px;
  position: relative;
  overflow: hidden;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(0, 212, 255, 0.5);
  border-style: solid;
  pointer-events: none;
  z-index: 3;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
/* é¢æ¿å‘光背景 */
.panel-item-customers > div:first-child::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  background: radial-gradient(circle, rgba(78, 228, 255, 0.08) 0%, transparent 70%);
  pointer-events: none;
  animation: pieGlow 4s ease-in-out infinite;
  z-index: 0;
}
@keyframes pieGlow {
  0%, 100% { opacity: 0.5; transform: translate(-50%, -50%) scale(1); }
  50% { opacity: 0.8; transform: translate(-50%, -50%) scale(1.1); }
}
.pie-chart-wrapper {
  position: relative;
  width: 100%;
  height: 320px;
  animation: pieFloat 4s ease-in-out infinite;
}
@keyframes pieFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
.pie-background {
@@ -185,5 +238,17 @@
  left: 50%;
  top: 50%;
  transform: translate(-51.5%, -39%);
  animation: pieBgRotate 30s linear infinite;
}
@keyframes pieBgRotate {
  from { transform: translate(-51.5%, -39%) rotate(0deg); }
  to { transform: translate(-51.5%, -39%) rotate(360deg); }
}
/* å›¾è¡¨å‘光效果 */
.land-chart {
  position: relative;
  z-index: 2;
}
</style>
src/views/reportAnalysis/qualityAnalysis/components/right-top.vue
@@ -1,6 +1,10 @@
<template>
  <div>
    <PanelHeader title="不合格产品排名" />
    <PanelHeader title="不合格产品排名">
      <template #extra>
        <span class="range-tip">(近 30 å¤©)</span>
      </template>
    </PanelHeader>
    <div class="main-panel panel-item-customers">
      <div class="main-panel-container">
        <div style="color: white" class="main-panel-box" v-for="(item, index) in panelList" :key="index">
@@ -71,39 +75,115 @@
  flex-direction: row;
  align-items: center;
  height: 40px;
  transition: all 0.3s ease;
  position: relative;
  animation: rankItemFadeIn 0.5s ease-out both;
}
  .main-panel-box-left {
    background: red;
    border-radius: 20px;
    text-align: center;
    line-height: 32px;
    margin: 0 20px;
@keyframes rankItemFadeIn {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
.main-panel-box:hover {
  transform: translateX(5px);
}
/* æŽ’名项底部光线 */
.main-panel-box::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 77, 79, 0.3), transparent);
  opacity: 0;
  transition: opacity 0.3s;
}
.main-panel-box:hover::after {
  opacity: 1;
}
.main-panel-box-left {
  background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%);
  border-radius: 20px;
  text-align: center;
  line-height: 32px;
  margin: 0 20px;
  padding: 0 12px;
  font-weight: 600;
  font-size: 12px;
  color: white;
  min-width: 60px;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}
/* æŽ’名标签发光 */
.main-panel-box-left::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  animation: rankShine 2s ease-in-out infinite;
}
@keyframes rankShine {
  0% { left: -100%; }
  50%, 100% { left: 100%; }
}
.main-panel-box:hover .main-panel-box-left {
  box-shadow: 0 0 15px rgba(255, 77, 79, 0.5);
}
.main-panel-box-right {
  display: flex;
  flex-direction: column;
  flex: 1;
  .main-panel-box-right-title {
    font-size: 14px;
    font-weight: 600;
    color: #ffffff;
    margin-bottom: 6px;
  }
  .main-panel-box-right {
  .main-panel-box-right-text {
    font-size: 12px;
    display: flex;
    flex-direction: column;
    flex: 1;
    justify-content: space-between;
    padding-right: 60px;
    margin-bottom: 4px;
    color: rgba(184, 200, 224, 0.8);
    transition: color 0.3s;
  }
    .main-panel-box-right-title {
      font-size: 14px;
      font-weight: 600;
      color: #ffffff;
      margin-bottom: 6px;
  .main-panel-box:hover .main-panel-box-right-text {
    color: rgba(184, 200, 224, 1);
  }
  .main-panel-box-right-progress {
    :deep(.el-progress__text) {
      color: white !important;
    }
    .main-panel-box-right-text {
      font-size: 12px;
      display: flex;
      justify-content: space-between;
      padding-right: 60px;
      margin-bottom: 4px;
    :deep(.el-progress-bar__outer) {
      background-color: rgba(78, 228, 255, 0.15);
    }
    .main-panel-box-right-progress {
      :deep(.el-progress__text) {
        color: white !important;
      }
    :deep(.el-progress-bar__inner) {
      transition: width 0.8s ease;
    }
  }
}
@@ -128,5 +208,57 @@
  width: 100%;
  height: 449px;
  overflow: hidden;
  position: relative;
}
/* é¢æ¿è§’落装饰 */
.panel-item-customers::before,
.panel-item-customers::after {
  content: '';
  position: absolute;
  width: 15px;
  height: 15px;
  border-color: rgba(255, 77, 79, 0.5);
  border-style: solid;
  pointer-events: none;
}
.panel-item-customers::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}
.panel-item-customers::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}
/* é¢æ¿èƒŒæ™¯è„‰å†² */
.panel-item-customers::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, rgba(255, 77, 79, 0.03) 0%, transparent 70%);
  pointer-events: none;
  animation: panelPulse 5s ease-in-out infinite;
  z-index: 0;
}
@keyframes panelPulse {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.5; }
}
.range-tip {
  font-size: 14px;
  color: rgba(184, 200, 224, 0.75);
  animation: tipFade 2s ease-in-out infinite;
}
@keyframes tipFade {
  0%, 100% { opacity: 0.75; }
  50% { opacity: 1; }
}
</style>
src/views/reportAnalysis/qualityAnalysis/index.vue
@@ -157,132 +157,313 @@
<style scoped>
/* å¤–部缩放容器 - å æ®æ•´ä¸ªè§†å£ */
.scale-container {
position: relative;
width: 100%;
/* é¡µé¢åœ¨å¸¸è§„布局下(有顶栏)默认减去 84px,避免内容被裁切 */
height: calc(100vh - 84px);
display: flex;
align-items: center;
justify-content: center;
background-color: #000;
overflow: hidden;
  position: relative;
  width: 100%;
  /* é¡µé¢åœ¨å¸¸è§„布局下(有顶栏)默认减去 84px,避免内容被裁切 */
  height: calc(100vh - 84px);
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #000;
  overflow: hidden;
}
/* åŠ¨æ€ç½‘æ ¼èƒŒæ™¯ */
.scale-container::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(0, 150, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0, 150, 255, 0.03) 1px, transparent 1px);
  background-size: 50px 50px;
  animation: gridMove 20s linear infinite;
  pointer-events: none;
  z-index: 0;
}
@keyframes gridMove {
  0% { transform: translate(0, 0); }
  100% { transform: translate(50px, 50px); }
}
/* æ‰«æçº¿æ•ˆæžœ */
.scale-container::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.8), transparent);
  animation: scanLine 4s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}
@keyframes scanLine {
  0%, 100% { top: 0; opacity: 0; }
  10% { opacity: 1; }
  90% { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}
/* å†…部内容区域 - å›ºå®šè®¾è®¡å°ºå¯¸ */
.data-dashboard {
position: relative;
width: 1920px;
height: 1080px;
background-image: url("@/assets/BI/backImage@2x.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transform-origin: center center;
  position: relative;
  width: 1920px;
  height: 1080px;
  background-image: url("@/assets/BI/backImage@2x.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  transform-origin: center center;
  z-index: 2;
}
/* å…¨å±çŠ¶æ€çš„æ ·å¼ - ä½œç”¨äºŽscale-container */
.scale-container:fullscreen {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
background-color: #000;
z-index: 9999;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
/* Webkit浏览器前缀 */
.scale-container:-webkit-full-screen {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
background-color: #000;
z-index: 9999;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
/* MS浏览器前缀 */
.scale-container:-ms-fullscreen {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
background-color: #000;
z-index: 9999;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: #000;
  z-index: 9999;
}
.dashboard-header {
position: relative;
z-index: 1;
height: 86px;
background-image: url("@/assets/BI/biaoti.png");
background-size: cover;
background-repeat: no-repeat;
display: flex;
align-items: center;
justify-content: center;
  position: relative;
  z-index: 1;
  height: 86px;
  background-image: url("@/assets/BI/biaoti.png");
  background-size: cover;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* æ ‡é¢˜è£…饰光效 */
.dashboard-header::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 400px;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.6), transparent);
  animation: titleGlow 3s ease-in-out infinite;
}
@keyframes titleGlow {
  0%, 100% { width: 400px; opacity: 0.6; }
  50% { width: 600px; opacity: 1; }
}
.factory-name {
font-weight: 600;
font-size: 52px;
color: #FFFFFF;
top: 16px;
position: absolute;
  font-weight: 600;
  font-size: 52px;
  color: #FFFFFF;
  top: 16px;
  position: absolute;
  text-shadow: 0 0 20px rgba(0, 212, 255, 0.5), 0 0 40px rgba(0, 150, 255, 0.3);
  animation: titleFloat 4s ease-in-out infinite;
  letter-spacing: 8px;
}
@keyframes titleFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}
.fullscreen-btn {
position: absolute;
top: 10px;
left: 20px;
width: 40px;
height: 40px;
background: rgba(0, 20, 60, 0.8);
border: 1px solid rgba(0, 212, 255, 0.3);
border-radius: 6px;
color: #00d4ff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
z-index: 10000;
  position: absolute;
  top: 10px;
  left: 20px;
  width: 40px;
  height: 40px;
  background: rgba(0, 20, 60, 0.8);
  border: 1px solid rgba(0, 212, 255, 0.3);
  border-radius: 6px;
  color: #00d4ff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
  z-index: 10000;
}
.fullscreen-btn:hover {
background: rgba(0, 30, 90, 0.9);
border-color: rgba(0, 212, 255, 0.5);
  background: rgba(0, 30, 90, 0.9);
  border-color: rgba(0, 212, 255, 0.5);
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
}
.dashboard-content {
position: relative;
z-index: 1;
display: flex;
gap: 30px;
padding: 0 30px;
height: calc(100% - 86px);
overflow: hidden;
  position: relative;
  z-index: 1;
  display: flex;
  gap: 30px;
  padding: 0 30px;
  height: calc(100% - 86px);
  overflow: hidden;
}
/* ç¡®ä¿å„面板能够正确显示 */
.left-panel, .center-panel, .right-panel {
overflow: hidden;
  overflow: hidden;
}
.left-panel,
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 24px;
width: 520px;
/* é¢æ¿å…¥åœºåŠ¨ç”» */
.left-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 520px;
  animation: slideInLeft 0.8s ease-out;
}
.center-panel {
flex: 1.5;
display: flex;
flex-direction: column;
gap: 20px;
  flex: 1.5;
  display: flex;
  flex-direction: column;
  gap: 20px;
  animation: slideInUp 0.8s ease-out 0.2s both;
}
.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 520px;
  animation: slideInRight 0.8s ease-out;
}
@keyframes slideInLeft {
  from {
    opacity: 0;
    transform: translateX(-50px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(50px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
@keyframes slideInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
/* é¢æ¿é€šç”¨å‘光边框效果 - é€šè¿‡å­ç»„ä»¶çš„panel-item-customers类生效 */
:deep(.panel-item-customers) {
  position: relative;
  transition: all 0.3s ease;
}
:deep(.panel-item-customers::before) {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: 0;
  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.3), transparent);
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
  z-index: -1;
}
:deep(.panel-item-customers:hover::before) {
  opacity: 1;
  animation: borderGlow 2s linear infinite;
}
@keyframes borderGlow {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
/* ç»Ÿè®¡å¡ç‰‡é—ªçƒæ•ˆæžœ */
:deep(.stat-card) {
  transition: all 0.3s ease;
}
:deep(.stat-card:hover) {
  transform: translateY(-2px);
  box-shadow: 0 5px 20px rgba(0, 150, 255, 0.3);
}
:deep(.stat-card:hover .card-value) {
  animation: valuePulse 0.5s ease;
}
@keyframes valuePulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}
/* æ•°æ®æµåŠ¨æ•ˆæžœèƒŒæ™¯ */
.dashboard-content::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(0, 212, 255, 0.1) 20%,
    rgba(0, 212, 255, 0.3) 50%,
    rgba(0, 212, 255, 0.1) 80%,
    transparent 100%);
  animation: dataFlow 8s linear infinite;
  pointer-events: none;
}
@keyframes dataFlow {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
</style>