优化细节
This commit is contained in:
parent
3736a4dc9c
commit
290d9eccf6
116
FPY_FEATURE.md
116
FPY_FEATURE.md
@ -1,116 +0,0 @@
|
||||
# 直通良品率(FPY)功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
新增了直通良品率(First Pass Yield, FPY)统计功能,用于追踪首次通过测试的产品数量。
|
||||
|
||||
## 什么是直通良品率?
|
||||
|
||||
**直通良品率(FPY)** = 首次通过测试的产品数量 / 总生产数量 × 100%
|
||||
|
||||
- **直通良品**:首次测试就通过的产品
|
||||
- **良品**:包括首次通过和返修后通过的所有合格产品
|
||||
- **不良品**:测试未通过的产品
|
||||
|
||||
### 示例
|
||||
假设生产了100个产品:
|
||||
- 80个首次测试通过(直通良品)
|
||||
- 15个首次测试失败,返修后通过
|
||||
- 5个无法修复
|
||||
|
||||
则:
|
||||
- **直通良品率** = 80/100 = 80%
|
||||
- **总良品率** = (80+15)/100 = 95%
|
||||
- **不良率** = 5/100 = 5%
|
||||
|
||||
## 功能实现
|
||||
|
||||
### 1. 数据库变更
|
||||
在 `stats` 表中新增 `fpy_good` 字段:
|
||||
```sql
|
||||
ALTER TABLE stats ADD COLUMN fpy_good INTEGER DEFAULT 0
|
||||
```
|
||||
|
||||
### 2. 仪表盘显示
|
||||
在仪表盘顶部新增"直通良品率"卡片,显示在"良品率"之前:
|
||||
- 直通良品率
|
||||
- 良品率
|
||||
- 发货数量
|
||||
- 不良数量
|
||||
|
||||
### 3. 上传页面
|
||||
在"良/不良统计"上传页面新增"直通良品数量"输入框:
|
||||
- 直通良品数量(新增)
|
||||
- 良品数量
|
||||
- 不良数量
|
||||
- 不良明细(可选)
|
||||
|
||||
### 4. API变更
|
||||
|
||||
#### 后端API
|
||||
- `GET /api/dashboard` - 返回 `fpyRate` 字段
|
||||
- `POST /api/upload/stats` - 接受 `fpy_good` 参数
|
||||
- `GET /api/overview` - 返回 `fpyGoodTotal` 字段
|
||||
- `GET /api/list/stats` - 返回 `fpy_good` 字段
|
||||
|
||||
#### 前端API
|
||||
- `API.uploadStats({fpy_good, good, bad, details})` - 上传时包含直通良品数
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 上传统计数据
|
||||
1. 进入"数据上传" -> "良/不良统计"
|
||||
2. 填写:
|
||||
- 直通良品数量:首次测试通过的数量
|
||||
- 良品数量:所有合格产品数量(包括返修后合格的)
|
||||
- 不良数量:测试失败的数量
|
||||
3. 点击"上传"
|
||||
|
||||
### 2. 查看统计
|
||||
在仪表盘可以看到:
|
||||
- **直通良品率**:反映生产质量的稳定性
|
||||
- **良品率**:反映最终产品的合格率
|
||||
|
||||
## 计算公式
|
||||
|
||||
```javascript
|
||||
// 直通良品率
|
||||
fpyRate = (fpy_good / (good + bad)) × 100%
|
||||
|
||||
// 总良品率
|
||||
goodRate = (good / (good + bad)) × 100%
|
||||
```
|
||||
|
||||
## 数据关系
|
||||
|
||||
```
|
||||
总生产数 = 良品数 + 不良数
|
||||
良品数 = 直通良品数 + 返修后合格数
|
||||
直通良品数 ≤ 良品数 ≤ 总生产数
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **直通良品数不能大于良品数**:系统不会强制校验,请确保数据准确
|
||||
2. **历史数据兼容**:旧数据的 `fpy_good` 默认为 0
|
||||
3. **显示逻辑**:如果没有数据,显示为"—"
|
||||
4. **通知功能**:上传时会通知超级管理员,包含直通良品数
|
||||
|
||||
## 业务价值
|
||||
|
||||
1. **质量监控**:直通良品率越高,说明生产过程越稳定
|
||||
2. **成本控制**:减少返修可以降低成本
|
||||
3. **流程优化**:通过对比直通良品率和总良品率,识别返修环节的效率
|
||||
4. **趋势分析**:长期追踪直通良品率变化,发现质量问题
|
||||
|
||||
## 示例数据
|
||||
|
||||
| 日期 | 直通良品 | 良品 | 不良 | 直通良品率 | 总良品率 |
|
||||
|------|---------|------|------|-----------|---------|
|
||||
| 2025-01-01 | 80 | 95 | 5 | 80% | 95% |
|
||||
| 2025-01-02 | 85 | 92 | 8 | 85% | 92% |
|
||||
| 2025-01-03 | 90 | 96 | 4 | 90% | 96% |
|
||||
|
||||
从上表可以看出:
|
||||
- 直通良品率在提升(80% → 85% → 90%),说明生产质量在改善
|
||||
- 总良品率保持在高位(95%左右),说明返修环节有效
|
||||
@ -1,160 +0,0 @@
|
||||
# 内存优化说明
|
||||
|
||||
## 优化内容
|
||||
|
||||
### 1. Dashboard 组件资源清理
|
||||
|
||||
**清理策略(三重保护):**
|
||||
- **策略1**: 每5次更新清理一次(约50秒)
|
||||
- **策略2**: 数据量超过500条时自动清理
|
||||
- **策略3**: 超过1分钟强制清理
|
||||
|
||||
**清理内容:**
|
||||
- 拼多多和圆通审计数据缓存
|
||||
- Canvas 画布内容
|
||||
- 趋势图数据对象
|
||||
- 触发浏览器垃圾回收(如果支持)
|
||||
|
||||
**数据过滤:**
|
||||
- 只缓存最近30天的数据
|
||||
- 自动过滤超过30天的旧数据
|
||||
- 减少内存占用
|
||||
|
||||
**日志输出:**
|
||||
```javascript
|
||||
console.log('[资源清理] 清理缓存数据,更新次数:', count, '数据量:', pddLength, ytLength);
|
||||
```
|
||||
|
||||
### 2. Upload 组件事件监听器管理
|
||||
|
||||
**问题:**
|
||||
- 每次渲染页面都添加新的事件监听器
|
||||
- 旧的监听器没有被移除
|
||||
- 导致内存泄漏
|
||||
|
||||
**解决方案:**
|
||||
- 创建统一的事件监听器管理系统
|
||||
- 使用 `addListener()` 替代 `addEventListener()`
|
||||
- 页面切换时自动清理所有监听器
|
||||
|
||||
**实现:**
|
||||
```javascript
|
||||
const eventListeners = [];
|
||||
const addListener = (element, event, handler) => {
|
||||
if(element){
|
||||
element.addEventListener(event, handler);
|
||||
eventListeners.push({element, event, handler});
|
||||
}
|
||||
};
|
||||
const cleanupListeners = () => {
|
||||
eventListeners.forEach(({element, event, handler}) => {
|
||||
element.removeEventListener(event, handler);
|
||||
});
|
||||
eventListeners.length = 0;
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 内存监控工具
|
||||
|
||||
**功能:**
|
||||
- 实时监控 JavaScript 堆内存使用情况
|
||||
- 每10秒输出一次内存状态
|
||||
- 内存使用超过70%时发出警告
|
||||
|
||||
**使用方法:**
|
||||
```javascript
|
||||
// 开发环境自动启动
|
||||
// 生产环境手动启动
|
||||
MemoryMonitor.start(10000); // 每10秒监控
|
||||
|
||||
// 查看当前内存状态
|
||||
MemoryMonitor.logCurrent();
|
||||
|
||||
// 停止监控
|
||||
MemoryMonitor.stop();
|
||||
```
|
||||
|
||||
**输出示例:**
|
||||
```
|
||||
[内存监控] 使用: 45.23 MB / 2048.00 MB (2.21%)
|
||||
[内存警告] 内存使用率超过70%,建议清理资源
|
||||
```
|
||||
|
||||
## 监控方法
|
||||
|
||||
### 浏览器开发者工具
|
||||
|
||||
1. **打开控制台**:F12 → Console
|
||||
2. **查看内存日志**:
|
||||
- `[资源清理]` - 缓存清理日志
|
||||
- `[内存监控]` - 内存使用情况
|
||||
- `[内存警告]` - 内存使用过高警告
|
||||
|
||||
3. **性能监控**:F12 → Performance
|
||||
- 点击 Record 开始录制
|
||||
- 使用应用一段时间
|
||||
- 停止录制查看内存曲线
|
||||
|
||||
4. **内存快照**:F12 → Memory
|
||||
- 选择 Heap snapshot
|
||||
- 拍摄快照对比内存变化
|
||||
|
||||
### 手动测试
|
||||
|
||||
```javascript
|
||||
// 在控制台执行
|
||||
MemoryMonitor.logCurrent();
|
||||
|
||||
// 查看缓存状态
|
||||
console.log('Dashboard缓存:', window.__auditCache);
|
||||
|
||||
// 查看定时器
|
||||
console.log('定时器ID:', window.__auditTimer);
|
||||
```
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 优化前
|
||||
- 内存持续增长
|
||||
- 30秒清理无明显效果
|
||||
- 长时间使用后内存占用过高
|
||||
|
||||
### 优化后
|
||||
- 内存使用稳定在合理范围
|
||||
- 每50秒或数据量达到500条时自动清理
|
||||
- 超过1分钟强制清理
|
||||
- 只保留最近30天数据
|
||||
- 事件监听器正确清理
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **浏览器支持**:
|
||||
- `performance.memory` API 仅在 Chrome/Edge 中可用
|
||||
- Firefox/Safari 不支持此 API
|
||||
|
||||
2. **垃圾回收**:
|
||||
- `window.gc()` 需要浏览器启动时添加 `--js-flags="--expose-gc"` 参数
|
||||
- 正常情况下浏览器会自动进行垃圾回收
|
||||
|
||||
3. **开发环境**:
|
||||
- 内存监控工具在 localhost 自动启动
|
||||
- 生产环境需要手动启动
|
||||
|
||||
## 调整参数
|
||||
|
||||
如需调整清理策略,修改 `frontend/js/components/dashboard.js`:
|
||||
|
||||
```javascript
|
||||
// 修改清理条件
|
||||
const shouldClean = cache.updateCount >= 5 || // 改为10次
|
||||
(cache.pdd && cache.pdd.length > 500) || // 改为1000条
|
||||
(now - cache.lastClean > 60000); // 改为120000(2分钟)
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. 打开浏览器控制台
|
||||
2. 进入 Dashboard 页面
|
||||
3. 观察内存监控日志
|
||||
4. 等待约1分钟查看清理日志
|
||||
5. 切换到其他页面,确认定时器已停止
|
||||
@ -1,130 +0,0 @@
|
||||
# 通知系统调试指南
|
||||
|
||||
## 已修复的问题
|
||||
|
||||
### 1. 铃铛无法点击问题
|
||||
**原因:**
|
||||
- 仪表盘页面有复杂的Canvas元素和图表,可能遮挡铃铛按钮
|
||||
- z-index层级设置不够高
|
||||
- 事件可能被其他元素捕获
|
||||
|
||||
**解决方案:**
|
||||
- 提高铃铛按钮的z-index到999
|
||||
- 提高通知面板的z-index到1000
|
||||
- 给content-header添加z-index:10
|
||||
- 给#actions容器添加z-index:999
|
||||
- 徽章设置pointer-events:none,避免阻挡点击
|
||||
- 铃铛按钮设置pointer-events:auto,确保可点击
|
||||
|
||||
### 2. 点击铃铛没有弹出面板
|
||||
**原因:**
|
||||
- 通知系统可能被多次初始化,导致事件监听器混乱
|
||||
- 事件监听器可能被重复绑定
|
||||
|
||||
**解决方案:**
|
||||
- 添加isInitialized标志,防止重复初始化
|
||||
- 使用cloneNode()方法移除旧的事件监听器
|
||||
- 添加详细的console.log调试信息
|
||||
- 在togglePanel中添加元素存在性检查
|
||||
|
||||
### 3. 时间显示问题
|
||||
**原因:**
|
||||
- 后端使用UTC时间,前端显示时差8小时
|
||||
|
||||
**解决方案:**
|
||||
- 后端notify_superadmin()使用北京时间(UTC+8)
|
||||
- 前端正确解析ISO格式的时间字符串
|
||||
|
||||
## 调试方法
|
||||
|
||||
### 1. 检查铃铛是否显示
|
||||
打开浏览器控制台,查看是否有以下日志:
|
||||
```
|
||||
[Notifications] 铃铛已显示
|
||||
[Notifications] 初始化完成
|
||||
```
|
||||
|
||||
### 2. 检查点击事件
|
||||
点击铃铛时,应该看到:
|
||||
```
|
||||
[Notifications] 铃铛被点击
|
||||
[Notifications] 面板状态: 打开
|
||||
```
|
||||
|
||||
### 3. 检查元素层级
|
||||
在浏览器开发者工具中:
|
||||
1. 检查#notification-bell的z-index是否为999
|
||||
2. 检查#notification-panel的z-index是否为1000
|
||||
3. 检查.content-header的z-index是否为10
|
||||
4. 检查#actions的z-index是否为999
|
||||
|
||||
### 4. 检查CSS样式
|
||||
确认以下样式已应用:
|
||||
```css
|
||||
.notification-bell {
|
||||
z-index: 999;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.notification-badge {
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.notification-panel {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#actions {
|
||||
z-index: 999;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 手动测试
|
||||
在浏览器控制台执行:
|
||||
```javascript
|
||||
// 检查铃铛元素
|
||||
const bell = document.getElementById('notification-bell');
|
||||
console.log('铃铛元素:', bell);
|
||||
console.log('铃铛样式:', window.getComputedStyle(bell));
|
||||
|
||||
// 检查面板元素
|
||||
const panel = document.getElementById('notification-panel');
|
||||
console.log('面板元素:', panel);
|
||||
console.log('面板显示:', panel.style.display);
|
||||
|
||||
// 手动触发点击
|
||||
bell.click();
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 铃铛显示了但点击没反应
|
||||
A: 检查浏览器控制台是否有错误信息,确认事件监听器已绑定
|
||||
|
||||
### Q: 面板打开了但看不到
|
||||
A: 检查z-index是否被其他元素覆盖,或者面板位置是否在屏幕外
|
||||
|
||||
### Q: 时间显示还是不对
|
||||
A: 确认服务器时区设置,检查后端是否使用了北京时间
|
||||
|
||||
### Q: 在某些页面可以点击,某些页面不行
|
||||
A: 检查该页面是否有特殊的z-index或overflow设置
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **防止重复初始化**:使用isInitialized标志
|
||||
2. **移除旧事件监听器**:使用cloneNode()方法
|
||||
3. **定时器管理**:在cleanup时清理定时器
|
||||
4. **避免内存泄漏**:在页面切换时调用cleanup
|
||||
|
||||
## 代码改进点
|
||||
|
||||
1. 添加了详细的日志输出,方便调试
|
||||
2. 添加了元素存在性检查,避免空指针错误
|
||||
3. 使用e.preventDefault()和e.stopPropagation()防止事件冒泡
|
||||
4. 提高了z-index层级,确保在所有页面都能正常显示
|
||||
@ -1,94 +0,0 @@
|
||||
# 消息通知系统使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
为超级管理员账号添加了消息通知系统,可以实时查看其他用户的操作记录。
|
||||
|
||||
## 主要特性
|
||||
|
||||
### 1. 消息铃铛
|
||||
- 位置:页面右上角(仅超级管理员可见)
|
||||
- 红色圆点徽章:显示未读消息数量
|
||||
- 点击铃铛:打开/关闭消息面板
|
||||
|
||||
### 2. 消息面板
|
||||
- 显示最近100条操作通知
|
||||
- 未读消息有蓝色圆点标记和高亮背景
|
||||
- 点击单条消息:标记为已读
|
||||
- "全部已读"按钮:一键标记所有消息为已读
|
||||
- "删除已读"按钮:删除所有已读消息(需确认)
|
||||
|
||||
### 3. 自动更新
|
||||
- 每30秒自动刷新未读消息数量
|
||||
- 实时显示最新的用户操作
|
||||
|
||||
### 4. 时间显示
|
||||
- 使用北京时间(UTC+8)
|
||||
- 智能显示:刚刚、X分钟前、X小时前、X天前
|
||||
- 超过7天显示完整日期时间
|
||||
|
||||
## 会触发通知的操作
|
||||
|
||||
以下操作会为超级管理员创建通知:
|
||||
|
||||
1. **批量上传MAC文件** - 显示上传类型(pdd/yt/tx)
|
||||
2. **批量上传发货记录文件** - 显示箱数和总数量
|
||||
3. **添加人员信息** - 显示姓名和角色
|
||||
4. **上传发货记录** - 显示日期和数量
|
||||
5. **上传MAC与批次** - 显示记录数量
|
||||
6. **批量上传不良明细文件** - 显示记录数量
|
||||
7. **上传返修记录** - 显示数量
|
||||
8. **上传良/不良统计** - 显示良品和不良品数量
|
||||
9. **上传不良明细** - 显示记录数量
|
||||
|
||||
## 通知内容
|
||||
|
||||
每条通知包含:
|
||||
- **用户名**:执行操作的用户
|
||||
- **操作类型**:执行的具体操作
|
||||
- **详细信息**:操作的详细数据(如数量、日期等)
|
||||
- **时间**:操作时间(智能显示:刚刚、X分钟前、X小时前等)
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端
|
||||
- 新增 `notifications` 数据表
|
||||
- 新增 `notify_superadmin()` 函数,在关键操作点调用,使用北京时间
|
||||
- 新增 5 个 API 端点:
|
||||
- `GET /api/notifications` - 获取通知列表
|
||||
- `GET /api/notifications/unread-count` - 获取未读数量
|
||||
- `POST /api/notifications/mark-read` - 标记单条已读
|
||||
- `POST /api/notifications/mark-all-read` - 标记全部已读
|
||||
- `POST /api/notifications/delete-read` - 删除所有已读消息
|
||||
|
||||
### 前端
|
||||
- 新增 `notifications.js` 组件
|
||||
- 在 `index.html` 添加铃铛和通知面板
|
||||
- 在 `styles.css` 添加通知相关样式
|
||||
- 在 `app.js` 集成通知系统初始化
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 使用超级管理员账号登录系统
|
||||
2. 在页面右上角可以看到消息铃铛图标 🔔
|
||||
3. 当有新消息时,铃铛上会显示红色数字徽章
|
||||
4. 点击铃铛查看消息列表
|
||||
5. 点击单条消息标记为已读
|
||||
6. 点击"全部已读"按钮标记所有消息为已读
|
||||
7. 点击"删除已读"按钮清理已读消息(需确认)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 只有超级管理员(role='superadmin')才能看到消息铃铛
|
||||
- 超级管理员自己的操作不会创建通知
|
||||
- 通知数据存储在 SQLite 数据库中
|
||||
- 消息列表最多显示最近100条记录
|
||||
- 所有时间使用北京时间(UTC+8)
|
||||
- 删除已读消息操作不可恢复,请谨慎操作
|
||||
|
||||
## 修复的问题
|
||||
|
||||
### v1.1 更新
|
||||
1. **修复时间显示问题**:后端使用北京时间(UTC+8)存储,前端正确解析显示
|
||||
2. **修复铃铛无法点击问题**:调整z-index层级,确保铃铛和面板在最上层
|
||||
3. **新增删除已读功能**:可以清理已读消息,保持消息列表整洁
|
||||
@ -63,8 +63,9 @@ const Upload = (() => {
|
||||
async function renderMac(){
|
||||
return section('MAC与批次(MAC与批次对应关系表)',`
|
||||
<div class="field">
|
||||
<label>上传类型</label>
|
||||
<label>上传机种 <span style="color:#ff4444">*</span></label>
|
||||
<select id="mac-type" class="input">
|
||||
<option value="">请选择上传机种</option>
|
||||
<option value="pdd">拼多多</option>
|
||||
<option value="yt">圆通</option>
|
||||
<option value="tx">兔喜</option>
|
||||
@ -166,7 +167,11 @@ const Upload = (() => {
|
||||
try {
|
||||
const data = await API.listMac();
|
||||
if(listEl && data.list && data.list.length > 0){
|
||||
listEl.innerHTML = data.list.slice(0, 10).map(r=>`<li><span>${r.mac}</span><span class="badge">${r.batch}</span></li>`).join('');
|
||||
const platformNames = {pdd: '拼多多', yt: '圆通', tx: '兔喜'};
|
||||
listEl.innerHTML = data.list.slice(0, 10).map(r=>{
|
||||
const platformName = platformNames[r.platform] || r.platform || '未知';
|
||||
return `<li><span>${r.mac}</span><span class="badge">${r.batch}</span><span class="badge" style="background:var(--primary-light);color:var(--primary)">${platformName}</span></li>`;
|
||||
}).join('');
|
||||
} else if(listEl) {
|
||||
listEl.innerHTML = '<li>暂无数据</li>';
|
||||
}
|
||||
@ -278,7 +283,11 @@ const Upload = (() => {
|
||||
const data = await API.listMac();
|
||||
if(listEl){
|
||||
if(data.list && data.list.length > 0){
|
||||
listEl.innerHTML = data.list.map(r=>`<li><span>${r.mac}</span><span class="badge">${r.batch}</span><span style="font-size:11px;color:var(--text-2);margin-left:8px">${new Date(r.ts).toLocaleString('zh-CN')}</span></li>`).join('');
|
||||
const platformNames = {pdd: '拼多多', yt: '圆通', tx: '兔喜'};
|
||||
listEl.innerHTML = data.list.map(r=>{
|
||||
const platformName = platformNames[r.platform] || r.platform || '未知';
|
||||
return `<li><span>${r.mac}</span><span class="badge">${r.batch}</span><span class="badge" style="background:var(--primary-light);color:var(--primary)">${platformName}</span><span style="font-size:11px;color:var(--text-2);margin-left:8px">${new Date(r.ts).toLocaleString('zh-CN')}</span></li>`;
|
||||
}).join('');
|
||||
API.toast(`显示全部 ${data.list.length} 条历史记录`);
|
||||
} else {
|
||||
listEl.innerHTML = '<li>暂无历史记录</li>';
|
||||
@ -307,7 +316,22 @@ const Upload = (() => {
|
||||
}
|
||||
|
||||
const typeEl = document.getElementById('mac-type');
|
||||
const uploadType = typeEl ? typeEl.value : 'pdd';
|
||||
const uploadType = typeEl ? typeEl.value : '';
|
||||
|
||||
// 验证是否选择了上传类型
|
||||
if(!uploadType){
|
||||
API.toast('请选择上传机种(拼多多/圆通/兔喜)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确认上传类型
|
||||
const typeNames = {pdd: '拼多多', yt: '圆通', tx: '兔喜'};
|
||||
const typeName = typeNames[uploadType] || uploadType;
|
||||
const confirmed = confirm(`请确认上传机种:${typeName}\n\n文件名:${file.name}\n\n确认无误后点击"确定"继续上传`);
|
||||
if(!confirmed){
|
||||
return;
|
||||
}
|
||||
|
||||
const logContainer = document.getElementById('upload-log');
|
||||
const logPre = logContainer ? logContainer.querySelector('pre') : null;
|
||||
|
||||
|
||||
@ -80,6 +80,10 @@ def init_db():
|
||||
c.execute('ALTER TABLE users ADD COLUMN avatar TEXT')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
try:
|
||||
c.execute('ALTER TABLE mac_batches ADD COLUMN platform TEXT DEFAULT "pdd"')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS defects(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mac TEXT,
|
||||
@ -1384,7 +1388,7 @@ def export_pdf():
|
||||
def list_mac():
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
c.execute('SELECT mac, batch, ts FROM mac_batches ORDER BY id DESC LIMIT 200')
|
||||
c.execute('SELECT mac, batch, platform, ts FROM mac_batches ORDER BY id DESC LIMIT 200')
|
||||
rows = [dict(r) for r in c.fetchall()]
|
||||
conn.close()
|
||||
return jsonify({'list': rows})
|
||||
@ -1804,7 +1808,7 @@ def upload_mac_file():
|
||||
mac = record.get('mac')
|
||||
batch = record.get('batch')
|
||||
if mac and batch:
|
||||
c.execute('INSERT INTO mac_batches(mac, batch, ts) VALUES(?,?,?)', (mac, batch, now))
|
||||
c.execute('INSERT INTO mac_batches(mac, batch, platform, ts) VALUES(?,?,?,?)', (mac, batch, upload_type, now))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
log('upload_mac_file_db', f"saved {len(records)} records to database")
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
# 发货记录合并单元格处理说明
|
||||
|
||||
## 功能特点
|
||||
|
||||
系统现在完全支持 Excel 中出货日期列的合并单元格,这是实际业务中常见的格式。
|
||||
|
||||
## Excel 文件结构
|
||||
|
||||
### 典型格式
|
||||
```
|
||||
| 出货日期 | 箱号 | SN1 | SN2 | ... | SN20 |
|
||||
|------------|--------|--------|--------|-----|--------|
|
||||
| 2025-11-20 | BOX001 | SN0001 | SN0002 | ... | SN0020 |
|
||||
| ↓ | BOX002 | SN0021 | SN0022 | ... | SN0040 |
|
||||
| ↓ | BOX003 | SN0041 | SN0042 | ... | SN0060 |
|
||||
| 2025-11-21 | BOX004 | SN0061 | SN0062 | ... | SN0080 |
|
||||
| ↓ | BOX005 | SN0081 | SN0082 | ... | SN0100 |
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 出货日期列可以合并单元格
|
||||
- 同一天的多个箱号共用一个日期单元格
|
||||
- 每行代表一个箱号
|
||||
- 横向包含该箱的 20 个 SN
|
||||
|
||||
## 解析逻辑
|
||||
|
||||
### 1. 合并单元格处理
|
||||
```python
|
||||
# 记录上一个有效的日期
|
||||
last_valid_date = None
|
||||
|
||||
for row in rows:
|
||||
# 如果当前行日期为空(合并单元格的后续行)
|
||||
if current_date:
|
||||
last_valid_date = current_date
|
||||
date = current_date
|
||||
else:
|
||||
# 使用上一个有效日期
|
||||
date = last_valid_date
|
||||
```
|
||||
|
||||
### 2. 数据结构
|
||||
每行解析后的数据:
|
||||
```python
|
||||
{
|
||||
'date': '2025-11-20', # 出货日期
|
||||
'box': 'BOX001', # 箱号
|
||||
'sns': ['SN0001', 'SN0002', ...], # SN 列表
|
||||
'qty': 20 # SN 数量
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Redis 存储
|
||||
每个 SN 单独存储:
|
||||
```
|
||||
HSET shipment_sn_mapping "SN0001" '{"date":"2025-11-20","box":"BOX001","ts":"..."}'
|
||||
HSET shipment_sn_mapping "SN0002" '{"date":"2025-11-20","box":"BOX001","ts":"..."}'
|
||||
...
|
||||
```
|
||||
|
||||
## 创建测试模板
|
||||
|
||||
运行以下命令创建带合并单元格的测试模板:
|
||||
|
||||
```bash
|
||||
python create_shipments_template.py
|
||||
```
|
||||
|
||||
这将生成:
|
||||
- `shipments_template.xlsx` - 带示例数据和合并单元格
|
||||
- `shipments_template_empty.xlsx` - 空白模板
|
||||
|
||||
## 导入流程
|
||||
|
||||
1. **准备 Excel 文件**
|
||||
- 第一列:出货日期(可合并单元格)
|
||||
- 第二列:箱号
|
||||
- 第3-22列:SN1-SN20
|
||||
|
||||
2. **上传文件**
|
||||
- 访问"发货记录"页面
|
||||
- 选择 Excel 文件
|
||||
- 点击"验证文件"
|
||||
|
||||
3. **验证通过后导入**
|
||||
- 点击"导入数据"
|
||||
- 系统自动解析合并单元格
|
||||
- 数据存入 SQLite 和 Redis
|
||||
|
||||
4. **查询验证**
|
||||
- 访问"SN出货查询"页面
|
||||
- 输入任意 SN 号
|
||||
- 查看出货日期和箱号
|
||||
|
||||
## 支持的格式
|
||||
|
||||
### Excel (.xlsx, .xls)
|
||||
- ✅ 支持合并单元格
|
||||
- ✅ 自动识别空单元格
|
||||
- ✅ 按 SN 编号排序(SN1, SN2, ..., SN20)
|
||||
|
||||
### CSV (.csv)
|
||||
- ✅ 支持空值(相当于合并单元格)
|
||||
- ✅ 自动使用上一行的日期
|
||||
- ⚠️ CSV 不支持真正的合并单元格,但空值会被正确处理
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如果日期列第一行就是空的怎么办?
|
||||
A: 系统会跳过该行,直到遇到第一个有效日期。
|
||||
|
||||
### Q: 如果某个箱子的 SN 不足 20 个?
|
||||
A: 没问题,后面的 SN 列可以留空,系统只记录有值的 SN。
|
||||
|
||||
### Q: 可以一天有多少个箱子?
|
||||
A: 没有限制,同一天可以有任意多个箱子,都会使用同一个日期。
|
||||
|
||||
### Q: 箱号可以重复吗?
|
||||
A: 技术上可以,但建议保持唯一,便于追溯。
|
||||
|
||||
## 数据验证
|
||||
|
||||
导入后可以通过以下方式验证:
|
||||
|
||||
### 1. 查看 Redis 记录数
|
||||
```bash
|
||||
redis-cli HLEN shipment_sn_mapping
|
||||
```
|
||||
|
||||
### 2. 查询特定 SN
|
||||
```bash
|
||||
redis-cli HGET shipment_sn_mapping "SN0001"
|
||||
```
|
||||
|
||||
### 3. 通过 Web 界面
|
||||
- 访问"SN出货查询"页面
|
||||
- 页面顶部显示总记录数
|
||||
- 输入 SN 查询详细信息
|
||||
@ -1,100 +0,0 @@
|
||||
# 发货记录 Redis 存储结构说明
|
||||
|
||||
## Redis 数据结构
|
||||
|
||||
### Hash 表:shipment_sn_mapping
|
||||
|
||||
用于存储 SN/MAC 到出货信息的映射关系。
|
||||
|
||||
**数据类型**: Redis Hash
|
||||
|
||||
**Key**: `shipment_sn_mapping`
|
||||
|
||||
**结构**:
|
||||
```
|
||||
Hash Key (Field) -> Hash Value
|
||||
SN/MAC 号 -> JSON 格式的出货信息
|
||||
```
|
||||
|
||||
### 存储示例
|
||||
|
||||
```redis
|
||||
HSET shipment_sn_mapping "SN0001" '{"date":"2025-11-20","box":"BOX001","ts":"2025-11-20T10:30:00"}'
|
||||
HSET shipment_sn_mapping "SN0002" '{"date":"2025-11-20","box":"BOX001","ts":"2025-11-20T10:30:00"}'
|
||||
HSET shipment_sn_mapping "SN0021" '{"date":"2025-11-20","box":"BOX002","ts":"2025-11-20T10:30:00"}'
|
||||
```
|
||||
|
||||
### JSON 值格式
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "2025-11-20", // 出货日期
|
||||
"box": "BOX001", // 箱号
|
||||
"ts": "2025-11-20T10:30:00" // 记录时间戳
|
||||
}
|
||||
```
|
||||
|
||||
## 查询方式
|
||||
|
||||
### 1. 通过 API 查询
|
||||
|
||||
**接口**: `GET /api/shipments/query-by-sn?sn=SN0001`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"found": true,
|
||||
"sn": "SN0001",
|
||||
"date": "2025-11-20",
|
||||
"box": "BOX001",
|
||||
"ts": "2025-11-20T10:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 直接使用 Redis 命令查询
|
||||
|
||||
```bash
|
||||
# 查询单个 SN
|
||||
redis-cli HGET shipment_sn_mapping "SN0001"
|
||||
|
||||
# 查询多个 SN
|
||||
redis-cli HMGET shipment_sn_mapping "SN0001" "SN0002" "SN0003"
|
||||
|
||||
# 查看所有记录数量
|
||||
redis-cli HLEN shipment_sn_mapping
|
||||
|
||||
# 查看所有 SN(谨慎使用,数据量大时会很慢)
|
||||
redis-cli HKEYS shipment_sn_mapping
|
||||
|
||||
# 扫描部分数据
|
||||
redis-cli HSCAN shipment_sn_mapping 0 COUNT 100
|
||||
```
|
||||
|
||||
## 数据导入流程
|
||||
|
||||
1. 用户上传 Excel 文件(包含:出货日期、箱号、SN1-SN20)
|
||||
2. 系统解析 Excel 文件
|
||||
3. 提取每行的出货日期、箱号和所有 SN
|
||||
4. 将数据保存到 SQLite(用于统计和列表展示)
|
||||
5. 同时将每个 SN 映射关系写入 Redis Hash(用于快速查询)
|
||||
|
||||
## 性能特点
|
||||
|
||||
- **查询速度**: O(1) 时间复杂度,毫秒级响应
|
||||
- **存储效率**: Hash 结构比单独的 Key-Value 更节省内存
|
||||
- **批量写入**: 使用 Pipeline 批量写入,提高导入速度
|
||||
- **容错性**: Redis 写入失败不影响 SQLite 数据保存
|
||||
|
||||
## 使用场景
|
||||
|
||||
1. **快速查询**: 扫描 SN 后立即查询出货信息
|
||||
2. **追溯管理**: 根据 MAC 地址追溯出货批次
|
||||
3. **质量追踪**: 结合不良品记录,追溯出货批次
|
||||
4. **客户服务**: 快速响应客户关于产品出货信息的查询
|
||||
|
||||
## 维护建议
|
||||
|
||||
1. **定期备份**: 使用 Redis RDB 或 AOF 持久化
|
||||
2. **监控容量**: 定期检查 Hash 大小 `HLEN shipment_sn_mapping`
|
||||
3. **数据清理**: 根据业务需求定期清理过期数据
|
||||
4. **索引优化**: 如需按日期或箱号查询,可建立额外的索引结构
|
||||
@ -1,62 +0,0 @@
|
||||
# 发货记录 Excel 模板说明
|
||||
|
||||
## 文件格式要求
|
||||
|
||||
### 必需列(按顺序)
|
||||
1. **出货日期** - 格式:YYYY-MM-DD(例如:2025-11-20)**支持合并单元格**
|
||||
2. **箱号** - 箱子编号(例如:BOX001)
|
||||
3. **SN1** - 第1个序列号
|
||||
4. **SN2** - 第2个序列号
|
||||
5. **SN3** - 第3个序列号
|
||||
6. ...
|
||||
7. **SN20** - 第20个序列号
|
||||
|
||||
### 支持的文件格式
|
||||
- Excel 文件:.xlsx, .xls
|
||||
- CSV 文件:.csv
|
||||
|
||||
## Excel 模板示例
|
||||
|
||||
### 格式 1:出货日期合并单元格(推荐)
|
||||
|
||||
| 出货日期 | 箱号 | SN1 | SN2 | SN3 | SN4 | SN5 | ... | SN20 |
|
||||
|---------|------|-----|-----|-----|-----|-----|-----|------|
|
||||
| 2025-11-20 | BOX001 | SN001 | SN002 | SN003 | SN004 | SN005 | ... | SN020 |
|
||||
| ↓(合并) | BOX002 | SN021 | SN022 | SN023 | SN024 | SN025 | ... | SN040 |
|
||||
| ↓(合并) | BOX003 | SN041 | SN042 | SN043 | SN044 | SN045 | ... | SN060 |
|
||||
| 2025-11-21 | BOX004 | SN061 | SN062 | SN063 | SN064 | SN065 | ... | SN080 |
|
||||
| ↓(合并) | BOX005 | SN081 | SN082 | SN083 | SN084 | SN085 | ... | SN100 |
|
||||
|
||||
**说明**:同一天的多个箱号,出货日期列可以合并单元格,系统会自动识别。
|
||||
|
||||
### 格式 2:出货日期不合并
|
||||
|
||||
| 出货日期 | 箱号 | SN1 | SN2 | SN3 | SN4 | SN5 | ... | SN20 |
|
||||
|---------|------|-----|-----|-----|-----|-----|-----|------|
|
||||
| 2025-11-20 | BOX001 | SN001 | SN002 | SN003 | SN004 | SN005 | ... | SN020 |
|
||||
| 2025-11-20 | BOX002 | SN021 | SN022 | SN023 | SN024 | SN025 | ... | SN040 |
|
||||
| 2025-11-21 | BOX003 | SN041 | SN042 | SN043 | SN044 | SN045 | ... | SN060 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **列头必须完全匹配**:出货日期、箱号、SN1、SN2、...、SN20
|
||||
2. **合并单元格支持**:出货日期列支持合并单元格,同一天的多个箱号可以共用一个日期单元格
|
||||
3. **SN列可以为空**:如果某个箱子不足20个SN,后面的SN列可以留空
|
||||
4. **日期格式**:建议使用 YYYY-MM-DD 格式(例如:2025-11-20)
|
||||
5. **箱号唯一性**:建议每个箱号保持唯一,便于追溯
|
||||
6. **数据行**:第一行为表头,从第二行开始为数据
|
||||
7. **横向结构**:一行代表一个箱号,横向包含该箱的所有 SN(最多20个)
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. 准备符合格式要求的 Excel 文件
|
||||
2. 在系统中点击"选择文件"上传文件
|
||||
3. 点击"验证文件"检查格式是否正确
|
||||
4. 验证通过后,点击"导入数据"完成上传
|
||||
5. 系统会自动统计每个箱次的SN数量并保存
|
||||
|
||||
## 导入结果
|
||||
|
||||
- 系统会显示导入的箱次数量
|
||||
- 系统会显示导入的总SN数量
|
||||
- 每条记录会保存:出货日期、数量(实际SN个数)、箱号信息
|
||||
Loading…
Reference in New Issue
Block a user