fix bug
This commit is contained in:
parent
daf4530753
commit
c4455def3f
@ -616,3 +616,71 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{
|
||||
[data-theme="light"] .watermark-text {
|
||||
opacity: 0.06;
|
||||
}
|
||||
|
||||
/* 工单页面样式 */
|
||||
.page-container{max-width:100%;margin:0 auto}
|
||||
.page-header{margin-bottom:24px}
|
||||
.page-header h1{font-size:24px;font-weight:700;margin:0;color:var(--text)}
|
||||
|
||||
.filter-section{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:20px}
|
||||
.filter-row{display:flex;gap:16px;align-items:flex-end;flex-wrap:wrap}
|
||||
.filter-item{flex:1;min-width:200px;display:flex;flex-direction:column;gap:6px}
|
||||
.filter-item label{color:var(--text-2);font-size:13px;font-weight:500}
|
||||
.filter-item input,.filter-item select{background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px;font-size:14px}
|
||||
.filter-item input:focus,.filter-item select:focus{outline:2px solid var(--primary);border-color:var(--primary)}
|
||||
.filter-actions{display:flex;gap:8px;align-items:flex-end}
|
||||
.filter-actions .btn{padding:10px 20px;font-size:14px}
|
||||
|
||||
.table-container{background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;margin-bottom:20px}
|
||||
.data-table{width:100%;border-collapse:collapse;font-size:13px}
|
||||
.data-table thead{background:var(--surface-2);position:sticky;top:0;z-index:1}
|
||||
.data-table th{padding:12px 16px;text-align:left;font-weight:600;color:var(--text);border-bottom:2px solid var(--border);white-space:nowrap}
|
||||
.data-table td{padding:12px 16px;border-bottom:1px solid var(--border);color:var(--text-2)}
|
||||
.data-table tbody tr:hover{background:rgba(79,140,255,.08)}
|
||||
.data-table tbody tr:last-child td{border-bottom:none}
|
||||
|
||||
.status-badge{display:inline-block;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:500}
|
||||
.status-badge.status-pending{background:rgba(245,158,11,.2);color:#f59e0b}
|
||||
.status-badge.status-processing{background:rgba(79,140,255,.2);color:#4f8cff}
|
||||
.status-badge.status-completed{background:rgba(34,197,94,.2);color:#22c55e}
|
||||
.status-badge.status-cancelled{background:rgba(239,68,68,.2);color:#ef4444}
|
||||
|
||||
.btn-icon{background:transparent;border:none;cursor:pointer;font-size:16px;padding:4px 8px;border-radius:6px;transition:background 0.2s}
|
||||
.btn-icon:hover{background:rgba(79,140,255,.15)}
|
||||
|
||||
.pagination{display:flex;align-items:center;justify-content:center;gap:16px;padding:16px}
|
||||
.pagination .btn{min-width:80px}
|
||||
.pagination .btn:disabled{opacity:0.5;cursor:not-allowed}
|
||||
#page-info{color:var(--text-2);font-size:14px}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:1000;backdrop-filter:blur(4px)}
|
||||
.modal-content{background:var(--surface);border:1px solid var(--border);border-radius:16px;width:90%;max-width:600px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.3)}
|
||||
.modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--border)}
|
||||
.modal-header h2{margin:0;font-size:20px;font-weight:700;color:var(--text)}
|
||||
.modal-close{background:transparent;border:none;font-size:28px;color:var(--text-2);cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:all 0.2s}
|
||||
.modal-close:hover{background:rgba(239,68,68,0.15);color:var(--danger)}
|
||||
.modal-body{padding:24px;overflow-y:auto;flex:1}
|
||||
.modal-footer{display:flex;gap:12px;justify-content:flex-end;padding:16px 24px;border-top:1px solid var(--border)}
|
||||
|
||||
/* 表单网格 */
|
||||
.form-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
|
||||
.form-grid .field.full-width{grid-column:1 / -1}
|
||||
.field .required{color:var(--danger);margin-left:2px}
|
||||
.field textarea{resize:vertical;font-family:inherit}
|
||||
|
||||
/* 页面头部样式 */
|
||||
.page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}
|
||||
|
||||
/* 状态徽章 - 已下发为绿色 */
|
||||
.status-badge.status-issued{background:rgba(34,197,94,.2);color:#22c55e}
|
||||
|
||||
/* 状态徽章 - 已确认为橘黄色 */
|
||||
.status-badge.status-confirmed{background:rgba(251,146,60,.2);color:#fb923c}
|
||||
|
||||
/* 删除按钮样式 */
|
||||
.btn-icon.btn-danger:hover{background:rgba(239,68,68,.15);color:var(--danger)}
|
||||
|
||||
/* 确认按钮样式 */
|
||||
.btn-icon.btn-confirm{color:#fb923c}
|
||||
.btn-icon.btn-confirm:hover{background:rgba(251,146,60,.15);color:#fb923c}
|
||||
|
||||
@ -74,6 +74,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">生产管理</div>
|
||||
<div class="nav-item has-children" data-expand="production-mgmt">
|
||||
<button class="nav-item-btn">
|
||||
<span class="icon">📋</span>
|
||||
<span class="text">生产管理</span>
|
||||
<span class="caret">▸</span>
|
||||
</button>
|
||||
<div class="nav-children" data-parent="production-mgmt">
|
||||
<a href="#/production-mgmt/work-order" class="nav-child" data-route="production-mgmt-work-order">
|
||||
<span class="child-icon">📝</span>
|
||||
<span>生产工单下发中心</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-group-title">扩展采集</div>
|
||||
<div class="nav-item has-children" data-expand="collect">
|
||||
@ -188,6 +204,7 @@
|
||||
<script src="./js/components/personnel.js"></script>
|
||||
<script src="./js/components/qa.js"></script>
|
||||
<script src="./js/components/production.js"></script>
|
||||
<script src="./js/components/work-order.js"></script>
|
||||
<script src="./js/components/export.js"></script>
|
||||
<script src="./js/components/settings.js"></script>
|
||||
<script src="./js/components/notifications.js"></script>
|
||||
|
||||
@ -90,8 +90,8 @@
|
||||
document.getElementById('overlay').classList.add('hidden');
|
||||
updateUserDisplay(currentUser);
|
||||
|
||||
// 初始化通知系统(仅对超级管理员)
|
||||
if (currentUser && currentUser.role === 'superadmin' && window.NotificationSystem) {
|
||||
// 初始化通知系统(超级管理员和管理员)
|
||||
if (currentUser && (currentUser.role === 'superadmin' || currentUser.role === 'admin') && window.NotificationSystem) {
|
||||
window.NotificationSystem.init();
|
||||
}
|
||||
});
|
||||
@ -102,8 +102,8 @@
|
||||
currentUser = user;
|
||||
updateUserDisplay(user);
|
||||
|
||||
// 初始化通知系统(仅对超级管理员)
|
||||
if (user && user.role === 'superadmin' && window.NotificationSystem) {
|
||||
// 初始化通知系统(超级管理员和管理员)
|
||||
if (user && (user.role === 'superadmin' || user.role === 'admin') && window.NotificationSystem) {
|
||||
window.NotificationSystem.init();
|
||||
}
|
||||
}).catch(()=>{});
|
||||
|
||||
@ -47,7 +47,8 @@
|
||||
'批量上传不良明细文件': '批量上传了不良明细',
|
||||
'上传返修记录': '上传了返修记录',
|
||||
'上传良/不良统计': '上传了良/不良统计',
|
||||
'上传不良明细': '上传了不良明细'
|
||||
'上传不良明细': '上传了不良明细',
|
||||
'添加工单': '添加了新工单'
|
||||
};
|
||||
return actionMap[action] || action;
|
||||
}
|
||||
@ -148,9 +149,9 @@
|
||||
try {
|
||||
// 检查用户角色
|
||||
const user = await API.me();
|
||||
if (user.role !== 'superadmin') {
|
||||
console.log('[Notifications] 非超级管理员,不显示通知');
|
||||
return; // 只有超级管理员才显示通知铃铛
|
||||
if (user.role !== 'superadmin' && user.role !== 'admin') {
|
||||
console.log('[Notifications] 非管理员,不显示通知');
|
||||
return; // 超级管理员和管理员才显示通知铃铛
|
||||
}
|
||||
|
||||
// 显示通知铃铛
|
||||
|
||||
@ -92,6 +92,8 @@ const Router = (() => {
|
||||
personnel: '人员信息',
|
||||
qa: '质检报告',
|
||||
production: '时间记录',
|
||||
'production-mgmt': '生产管理',
|
||||
'work-order': '生产工单下发中心',
|
||||
export: '导出',
|
||||
settings: '设置'
|
||||
};
|
||||
|
||||
289
server/app.py
289
server/app.py
@ -142,6 +142,26 @@ def init_db():
|
||||
uploader TEXT,
|
||||
ts TEXT
|
||||
)''')
|
||||
c.execute('''CREATE TABLE IF NOT EXISTS work_orders(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
factory TEXT NOT NULL,
|
||||
order_no TEXT NOT NULL,
|
||||
product_model TEXT,
|
||||
order_qty INTEGER NOT NULL,
|
||||
production_start_time TEXT,
|
||||
production_end_time TEXT,
|
||||
status TEXT DEFAULT 'issued',
|
||||
status_text TEXT DEFAULT '已下发',
|
||||
remark TEXT,
|
||||
created_by TEXT,
|
||||
created_at TEXT,
|
||||
updated_at TEXT
|
||||
)''')
|
||||
# 为已存在的表添加列(如果不存在)
|
||||
try:
|
||||
c.execute('ALTER TABLE work_orders ADD COLUMN product_model TEXT')
|
||||
except Exception:
|
||||
pass # 列已存在
|
||||
conn.commit()
|
||||
# create default admin
|
||||
c.execute('SELECT id FROM users WHERE username=?', ('admin',))
|
||||
@ -219,6 +239,48 @@ def notify_superadmin(action, detail=''):
|
||||
pass
|
||||
|
||||
|
||||
def notify_admins(action, detail=''):
|
||||
"""为管理员创建通知(超级管理员操作时使用)"""
|
||||
try:
|
||||
user_id = session.get('user_id')
|
||||
if not user_id:
|
||||
return
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 获取当前用户信息
|
||||
c.execute('SELECT username, role FROM users WHERE id=?', (user_id,))
|
||||
user = c.fetchone()
|
||||
if not user:
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# 只有超级管理员的操作才通知管理员
|
||||
if user['role'] != 'superadmin':
|
||||
conn.close()
|
||||
return
|
||||
|
||||
# 为所有管理员创建通知
|
||||
c.execute('SELECT id FROM users WHERE role=?', ('admin',))
|
||||
admins = c.fetchall()
|
||||
|
||||
# 使用北京时间(UTC+8)
|
||||
from datetime import timezone, timedelta
|
||||
beijing_tz = timezone(timedelta(hours=8))
|
||||
now = datetime.now(beijing_tz).isoformat()
|
||||
|
||||
for admin in admins:
|
||||
c.execute('INSERT INTO notifications(user_id, username, action, detail, ts, read) VALUES(?,?,?,?,?,?)', (
|
||||
admin['id'], user['username'], action, detail, now, 0
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_redis():
|
||||
global _redis_client
|
||||
if not redis:
|
||||
@ -365,6 +427,7 @@ def login():
|
||||
return jsonify({'error': 'invalid credentials'}), 400
|
||||
session['user_id'] = row['id']
|
||||
session['role'] = row['role']
|
||||
session['username'] = username
|
||||
session.permanent = True
|
||||
log('login', username)
|
||||
return jsonify({'ok': True})
|
||||
@ -1634,7 +1697,7 @@ def clear_module():
|
||||
# notifications
|
||||
@app.get('/api/notifications')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@require_any_role('superadmin', 'admin')
|
||||
def get_notifications():
|
||||
"""获取当前用户的通知列表"""
|
||||
user_id = session.get('user_id')
|
||||
@ -1648,7 +1711,7 @@ def get_notifications():
|
||||
|
||||
@app.get('/api/notifications/unread-count')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@require_any_role('superadmin', 'admin')
|
||||
def get_unread_count():
|
||||
"""获取未读通知数量"""
|
||||
user_id = session.get('user_id')
|
||||
@ -1662,7 +1725,7 @@ def get_unread_count():
|
||||
|
||||
@app.post('/api/notifications/mark-read')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@require_any_role('superadmin', 'admin')
|
||||
def mark_notification_read():
|
||||
"""标记通知为已读"""
|
||||
data = request.get_json() or {}
|
||||
@ -1682,7 +1745,7 @@ def mark_notification_read():
|
||||
|
||||
@app.post('/api/notifications/mark-all-read')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@require_any_role('superadmin', 'admin')
|
||||
def mark_all_notifications_read():
|
||||
"""标记所有通知为已读"""
|
||||
user_id = session.get('user_id')
|
||||
@ -1696,7 +1759,7 @@ def mark_all_notifications_read():
|
||||
|
||||
@app.post('/api/notifications/delete-read')
|
||||
@require_login
|
||||
@require_any_role('superadmin')
|
||||
@require_any_role('superadmin', 'admin')
|
||||
def delete_read_notifications():
|
||||
"""删除所有已读通知"""
|
||||
user_id = session.get('user_id')
|
||||
@ -2543,6 +2606,222 @@ def delete_sop_file(file_id):
|
||||
return jsonify({'ok': True, 'message': 'SOP 文件已删除'})
|
||||
|
||||
|
||||
# ==================== 工单管理 API ====================
|
||||
|
||||
@app.get('/api/work-orders')
|
||||
@require_login
|
||||
def get_work_orders():
|
||||
"""获取工单列表"""
|
||||
factory = request.args.get('factory', '')
|
||||
order_no = request.args.get('order', '')
|
||||
date = request.args.get('date', '')
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
query = 'SELECT * FROM work_orders WHERE 1=1'
|
||||
params = []
|
||||
|
||||
if factory:
|
||||
query += ' AND factory LIKE ?'
|
||||
params.append(f'%{factory}%')
|
||||
|
||||
if order_no:
|
||||
query += ' AND order_no LIKE ?'
|
||||
params.append(f'%{order_no}%')
|
||||
|
||||
if date:
|
||||
query += ' AND (production_start_time LIKE ? OR production_end_time LIKE ?)'
|
||||
params.append(f'{date}%')
|
||||
params.append(f'{date}%')
|
||||
|
||||
query += ' ORDER BY created_at DESC'
|
||||
|
||||
c.execute(query, params)
|
||||
rows = c.fetchall()
|
||||
conn.close()
|
||||
|
||||
orders = []
|
||||
for row in rows:
|
||||
orders.append({
|
||||
'id': str(row['id']),
|
||||
'factory': row['factory'],
|
||||
'orderNo': row['order_no'],
|
||||
'productModel': row['product_model'] if 'product_model' in row.keys() else '',
|
||||
'orderQty': row['order_qty'],
|
||||
'productionStartTime': row['production_start_time'],
|
||||
'productionEndTime': row['production_end_time'],
|
||||
'status': row['status'],
|
||||
'statusText': row['status_text'],
|
||||
'remark': row['remark'],
|
||||
'createdBy': row['created_by'],
|
||||
'createdAt': row['created_at']
|
||||
})
|
||||
|
||||
return jsonify({'ok': True, 'data': orders})
|
||||
|
||||
|
||||
@app.post('/api/work-orders')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
def create_work_order():
|
||||
"""创建工单"""
|
||||
data = request.get_json()
|
||||
|
||||
factory = data.get('factory', '').strip()
|
||||
order_no = data.get('orderNo', '').strip()
|
||||
product_model = data.get('productModel', '').strip()
|
||||
order_qty = data.get('orderQty', 0)
|
||||
production_start_time = data.get('productionStartTime', '')
|
||||
production_end_time = data.get('productionEndTime', '')
|
||||
remark = data.get('remark', '').strip()
|
||||
|
||||
if not factory or not order_no or not order_qty:
|
||||
return jsonify({'error': '请填写所有必填项'}), 400
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
username = session.get('username', '')
|
||||
now = get_beijing_time()
|
||||
|
||||
c.execute('''INSERT INTO work_orders(
|
||||
factory, order_no, product_model, order_qty, production_start_time, production_end_time,
|
||||
status, status_text, remark, created_by, created_at, updated_at
|
||||
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)''', (
|
||||
factory, order_no, product_model, order_qty, production_start_time, production_end_time,
|
||||
'issued', '已下发', remark, username, now, now
|
||||
))
|
||||
|
||||
order_id = c.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('create_work_order', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}')
|
||||
|
||||
# 如果是超级管理员添加工单,通知所有管理员
|
||||
notify_admins('添加工单', f'工单号: {order_no}, 工厂: {factory}, 数量: {order_qty}')
|
||||
|
||||
return jsonify({'ok': True, 'id': order_id, 'message': '工单创建成功'})
|
||||
|
||||
|
||||
@app.put('/api/work-orders/<int:order_id>')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
def update_work_order(order_id):
|
||||
"""更新工单"""
|
||||
data = request.get_json()
|
||||
|
||||
factory = data.get('factory', '').strip()
|
||||
order_no = data.get('orderNo', '').strip()
|
||||
product_model = data.get('productModel', '').strip()
|
||||
order_qty = data.get('orderQty', 0)
|
||||
production_start_time = data.get('productionStartTime', '')
|
||||
production_end_time = data.get('productionEndTime', '')
|
||||
remark = data.get('remark', '').strip()
|
||||
|
||||
if not factory or not order_no or not order_qty:
|
||||
return jsonify({'error': '请填写所有必填项'}), 400
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 检查工单是否存在
|
||||
c.execute('SELECT id FROM work_orders WHERE id=?', (order_id,))
|
||||
if not c.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '工单不存在'}), 404
|
||||
|
||||
now = get_beijing_time()
|
||||
|
||||
c.execute('''UPDATE work_orders SET
|
||||
factory=?, order_no=?, product_model=?, order_qty=?, production_start_time=?, production_end_time=?,
|
||||
remark=?, updated_at=?
|
||||
WHERE id=?''', (
|
||||
factory, order_no, product_model, order_qty, production_start_time, production_end_time,
|
||||
remark, now, order_id
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('update_work_order', f'工单ID: {order_id}, 工单号: {order_no}, 型号: {product_model}')
|
||||
notify_superadmin('更新工单', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}')
|
||||
|
||||
return jsonify({'ok': True, 'message': '工单更新成功'})
|
||||
|
||||
|
||||
@app.delete('/api/work-orders/<int:order_id>')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
def delete_work_order(order_id):
|
||||
"""删除工单"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 获取工单信息用于日志
|
||||
c.execute('SELECT order_no, factory FROM work_orders WHERE id=?', (order_id,))
|
||||
row = c.fetchone()
|
||||
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({'error': '工单不存在'}), 404
|
||||
|
||||
order_no = row['order_no']
|
||||
factory = row['factory']
|
||||
|
||||
c.execute('DELETE FROM work_orders WHERE id=?', (order_id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('delete_work_order', f'工单ID: {order_id}, 工单号: {order_no}')
|
||||
notify_superadmin('删除工单', f'工单号: {order_no}, 工厂: {factory}')
|
||||
|
||||
return jsonify({'ok': True, 'message': '工单删除成功'})
|
||||
|
||||
|
||||
@app.post('/api/work-orders/<int:order_id>/confirm')
|
||||
@require_login
|
||||
@require_any_role('admin','superadmin')
|
||||
def confirm_work_order(order_id):
|
||||
"""确认工单"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# 检查工单是否存在
|
||||
c.execute('SELECT order_no, factory, status FROM work_orders WHERE id=?', (order_id,))
|
||||
row = c.fetchone()
|
||||
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({'error': '工单不存在'}), 404
|
||||
|
||||
order_no = row['order_no']
|
||||
factory = row['factory']
|
||||
current_status = row['status']
|
||||
|
||||
# 如果已经确认,返回提示
|
||||
if current_status == 'confirmed':
|
||||
conn.close()
|
||||
return jsonify({'ok': True, 'message': '工单已确认'})
|
||||
|
||||
# 更新状态为已确认
|
||||
now = get_beijing_time()
|
||||
c.execute('''UPDATE work_orders SET
|
||||
status=?, status_text=?, updated_at=?
|
||||
WHERE id=?''', (
|
||||
'confirmed', '已确认', now, order_id
|
||||
))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
log('confirm_work_order', f'工单ID: {order_id}, 工单号: {order_no}')
|
||||
notify_superadmin('确认工单', f'工单号: {order_no}, 工厂: {factory}')
|
||||
|
||||
return jsonify({'ok': True, 'message': '工单确认成功'})
|
||||
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user