diff --git a/frontend/index.html b/frontend/index.html index dc65174..b34130d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -103,6 +103,10 @@ 🛒 物料清单-采购 + + 📋 + 客户订单 + @@ -221,6 +225,7 @@ + diff --git a/frontend/js/app.js b/frontend/js/app.js index dbd56e1..3ac680c 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -30,6 +30,29 @@ } } + // 根据用户角色控制菜单显示 + function updateMenuVisibility(user) { + // 查找计划管理菜单组(包含标题"计划管理"的nav-group) + const navGroups = document.querySelectorAll('.nav-group'); + let planMgmtGroup = null; + + navGroups.forEach(group => { + const title = group.querySelector('.nav-group-title'); + if (title && title.textContent.trim() === '计划管理') { + planMgmtGroup = group; + } + }); + + if (planMgmtGroup) { + // 只有超级管理员可以看到计划管理菜单 + if (user && user.role === 'superadmin') { + planMgmtGroup.style.display = ''; + } else { + planMgmtGroup.style.display = 'none'; + } + } + } + // 创建水印 function createWatermark(username) { // 检查水印是否启用 @@ -91,6 +114,9 @@ document.getElementById('overlay').classList.add('hidden'); updateUserDisplay(currentUser); + // 根据用户角色控制菜单显示 + updateMenuVisibility(currentUser); + // 初始化通知系统(超级管理员和管理员) if (currentUser && (currentUser.role === 'superadmin' || currentUser.role === 'admin') && window.NotificationSystem) { window.NotificationSystem.init(); @@ -102,6 +128,7 @@ API.me().then(user => { currentUser = user; updateUserDisplay(user); + updateMenuVisibility(user); // 初始化通知系统(超级管理员和管理员) if (user && (user.role === 'superadmin' || user.role === 'admin') && window.NotificationSystem) { diff --git a/frontend/js/components/settings.js b/frontend/js/components/settings.js index 182ad64..6c1951a 100644 --- a/frontend/js/components/settings.js +++ b/frontend/js/components/settings.js @@ -1,7 +1,7 @@ Router.register('/settings', async () => { const me = await API.me().catch(()=>({})); const users = (me && me.role === 'superadmin') ? await API.adminUsers().catch(()=>({list:[]})) : {list:[]}; - const userList = (users.list||[]).map(u=>`
  • ${u.username}${u.role}
  • `).join('') || '
  • 暂无用户
  • '; + const userList = (users.list||[]).map(u=>`
  • ${u.username}${u.role}${u.factory ? `${u.factory}` : ''}
  • `).join('') || '
  • 暂无用户
  • '; const html = `
    +
    + +
    +
    +
    📋 用户列表
    @@ -277,16 +302,22 @@ Router.register('/settings', async () => { addUserBtn?.addEventListener('click', async () => { const usernameEl = document.getElementById('new-username'); const passwordEl = document.getElementById('new-password'); + const factoryEl = document.getElementById('new-factory'); const roleEl = document.getElementById('new-role'); const username = usernameEl?.value?.trim(); const password = passwordEl?.value; + const factory = factoryEl?.value?.trim(); const role = roleEl?.value || 'admin'; if (!username || !password) { return API.toast('请输入用户名和密码'); } + if (!factory) { + return API.toast('请输入所属工厂'); + } + if (password.length < 6) { return API.toast('密码长度至少6位'); } @@ -297,7 +328,7 @@ Router.register('/settings', async () => { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ username, password, role }) + body: JSON.stringify({ username, password, factory, role }) }); const data = await res.json(); @@ -307,6 +338,7 @@ Router.register('/settings', async () => { // 清空输入框 if (usernameEl) usernameEl.value = ''; if (passwordEl) passwordEl.value = ''; + if (factoryEl) factoryEl.value = ''; // 刷新页面以更新用户列表 setTimeout(() => Router.navigate('/settings'), 1000); } else { @@ -337,6 +369,57 @@ Router.register('/settings', async () => { change.disabled = false; } }); + + // 更新用户工厂 + const factoryUserSelect = document.getElementById('factory-user'); + const factoryNameInput = document.getElementById('factory-name'); + + // 当选择用户时,自动填充当前工厂 + factoryUserSelect?.addEventListener('change', () => { + const selectedOption = factoryUserSelect.options[factoryUserSelect.selectedIndex]; + const currentFactory = selectedOption.getAttribute('data-factory') || ''; + if (factoryNameInput) { + factoryNameInput.value = currentFactory; + } + }); + + const updateFactoryBtn = document.getElementById('update-factory-btn'); + updateFactoryBtn?.addEventListener('click', async () => { + const username = factoryUserSelect?.value; + const factory = factoryNameInput?.value?.trim(); + + if (!username) { + return API.toast('请选择用户'); + } + + if (!factory) { + return API.toast('请输入工厂名称'); + } + + updateFactoryBtn.disabled = true; + try { + const res = await fetch('/api/admin/update-user-factory', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ username, factory }) + }); + + const data = await res.json(); + + if (res.ok && data.ok) { + API.toast(data.message || '工厂更新成功'); + // 刷新页面以更新用户列表 + setTimeout(() => Router.navigate('/settings'), 1000); + } else { + API.toast(data.error || '更新失败'); + } + } catch(e) { + API.toast('更新失败:' + e.message); + } finally { + updateFactoryBtn.disabled = false; + } + }); // 水印开关 const watermarkToggle = document.getElementById('watermark-toggle'); diff --git a/frontend/js/components/work-order.js b/frontend/js/components/work-order.js index 8e79b82..6c24a16 100644 --- a/frontend/js/components/work-order.js +++ b/frontend/js/components/work-order.js @@ -116,6 +116,8 @@ Router.register('/production-mgmt/work-order', async () => { const html = await render(); setTimeout(async () => { + // 等待DOM完全渲染 + await new Promise(resolve => setTimeout(resolve, 50)); await initPage(); }, 0); return html; @@ -249,6 +251,12 @@ const prevBtn = document.getElementById('prev-page'); const nextBtn = document.getElementById('next-page'); + // 检查DOM元素是否存在 + if (!tbody || !pageInfo || !prevBtn || !nextBtn) { + console.warn('工单页面DOM元素未就绪,跳过加载'); + return; + } + // 获取筛选条件 const factory = document.getElementById('factory-filter')?.value || ''; const order = document.getElementById('order-filter')?.value || ''; @@ -346,6 +354,7 @@ const modal = document.getElementById('work-order-modal'); const modalTitle = document.getElementById('modal-title'); const form = document.getElementById('work-order-form'); + const factoryInput = document.getElementById('form-factory'); form.reset(); @@ -353,7 +362,7 @@ // 编辑模式 modalTitle.textContent = '编辑工单'; document.getElementById('form-order-id').value = order.id; - document.getElementById('form-factory').value = order.factory; + factoryInput.value = order.factory; document.getElementById('form-order-no').value = order.orderNo; document.getElementById('form-product-model').value = order.productModel || ''; document.getElementById('form-order-qty').value = order.orderQty; @@ -364,6 +373,18 @@ // 添加模式 modalTitle.textContent = '添加工单'; document.getElementById('form-order-id').value = ''; + + // 如果是管理员,自动填充工厂并设为只读 + if (currentUser && currentUser.role === 'admin' && currentUser.factory) { + factoryInput.value = currentUser.factory; + factoryInput.readOnly = true; + factoryInput.style.backgroundColor = 'var(--surface)'; + factoryInput.style.cursor = 'not-allowed'; + } else { + factoryInput.readOnly = false; + factoryInput.style.backgroundColor = ''; + factoryInput.style.cursor = ''; + } } modal.style.display = 'flex'; diff --git a/frontend/js/router.js b/frontend/js/router.js index 344d35a..ef63576 100644 --- a/frontend/js/router.js +++ b/frontend/js/router.js @@ -85,6 +85,7 @@ const Router = (() => { 'work-order': '生产工单下发中心', 'plan-mgmt': '计划管理', 'material-purchase': '物料清单-采购', + 'customer-order': '客户订单', export: '导出', settings: '设置' }; diff --git a/server/app.py b/server/app.py index 8a20ade..5256b6a 100644 --- a/server/app.py +++ b/server/app.py @@ -81,6 +81,10 @@ def init_db(): c.execute('ALTER TABLE users ADD COLUMN avatar TEXT') except Exception: pass # 列已存在 + try: + c.execute('ALTER TABLE users ADD COLUMN factory TEXT') + except Exception: + pass # 列已存在 try: c.execute('ALTER TABLE mac_batches ADD COLUMN platform TEXT DEFAULT "pdd"') except Exception: @@ -184,6 +188,23 @@ def init_db(): deleted INTEGER DEFAULT 0, deleted_at TEXT )''') + c.execute('''CREATE TABLE IF NOT EXISTS customer_orders( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_date TEXT NOT NULL, + order_no TEXT NOT NULL, + customer_name TEXT NOT NULL, + material TEXT NOT NULL, + quantity INTEGER NOT NULL, + unit_price REAL NOT NULL, + created_by TEXT, + created_at TEXT, + updated_at TEXT + )''') + # 为已存在的表添加列(如果不存在) + try: + c.execute('ALTER TABLE customer_orders ADD COLUMN customer_name TEXT') + except Exception: + pass # 列已存在 # 为已存在的表添加列(如果不存在) try: c.execute('ALTER TABLE work_orders ADD COLUMN product_model TEXT') @@ -308,6 +329,52 @@ def notify_admins(action, detail=''): pass +def notify_admins_by_factory(action, detail='', factory=None): + """为指定工厂的管理员创建通知(超级管理员操作时使用)""" + 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 + + # 如果指定了工厂,只通知该工厂的管理员;否则通知所有管理员 + if factory: + c.execute('SELECT id FROM users WHERE role=? AND factory=?', ('admin', factory)) + else: + 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: @@ -547,13 +614,18 @@ def login(): def me(): uid = session.get('user_id') if not uid: - return jsonify({'username': None, 'role': None, 'avatar': None}) + return jsonify({'username': None, 'role': None, 'avatar': None, 'factory': None}) conn = get_db() c = conn.cursor() - c.execute('SELECT username, role, avatar FROM users WHERE id=?', (uid,)) + c.execute('SELECT username, role, avatar, factory FROM users WHERE id=?', (uid,)) row = c.fetchone() conn.close() - return jsonify({'username': row['username'], 'role': row['role'], 'avatar': row['avatar'] if row['avatar'] else None}) + return jsonify({ + 'username': row['username'], + 'role': row['role'], + 'avatar': row['avatar'] if row['avatar'] else None, + 'factory': row['factory'] if row['factory'] else None + }) @app.post('/api/auth/logout') @@ -1623,7 +1695,7 @@ def list_shipments(): def list_users(): conn = get_db() c = conn.cursor() - c.execute('SELECT username, role FROM users ORDER BY id ASC') + c.execute('SELECT username, role, factory FROM users ORDER BY id ASC') rows = [dict(r) for r in c.fetchall()] conn.close() return jsonify({'list': rows}) @@ -1675,6 +1747,37 @@ def change_password(): return jsonify({'ok': True}) +@app.post('/api/admin/update-user-factory') +@require_login +@require_any_role('superadmin') +def update_user_factory(): + """更新用户所属工厂""" + data = request.get_json() or {} + username = data.get('username') + factory = (data.get('factory') or '').strip() + + if not username: + return jsonify({'error': '用户名不能为空'}), 400 + + if not factory: + return jsonify({'error': '工厂名称不能为空'}), 400 + + conn = get_db() + c = conn.cursor() + c.execute('SELECT id FROM users WHERE username=?', (username,)) + row = c.fetchone() + if not row: + conn.close() + return jsonify({'error': '用户不存在'}), 404 + + c.execute('UPDATE users SET factory=? WHERE id=?', (factory, row['id'])) + conn.commit() + conn.close() + + log('update_user_factory', f'username={username}, factory={factory}') + return jsonify({'ok': True, 'message': f'已更新用户 {username} 的所属工厂为 {factory}'}) + + @app.post('/api/admin/add-user') @require_login @require_any_role('superadmin') @@ -1684,10 +1787,14 @@ def add_user(): username = (data.get('username') or '').strip() password = data.get('password') role = (data.get('role') or 'admin').strip() + factory = (data.get('factory') or '').strip() if not username or not password: return jsonify({'error': '用户名和密码不能为空'}), 400 + if not factory: + return jsonify({'error': '所属工厂不能为空'}), 400 + if role not in ['admin', 'superadmin']: return jsonify({'error': '角色必须是 admin 或 superadmin'}), 400 @@ -1702,12 +1809,12 @@ def add_user(): # 创建新用户 try: - c.execute('INSERT INTO users(username, password_hash, role) VALUES(?,?,?)', - (username, generate_password_hash(password), role)) + c.execute('INSERT INTO users(username, password_hash, role, factory) VALUES(?,?,?,?)', + (username, generate_password_hash(password), role, factory)) conn.commit() conn.close() - log('add_user', f'username={username}, role={role}') + log('add_user', f'username={username}, role={role}, factory={factory}') return jsonify({'ok': True, 'message': f'用户 {username} 创建成功'}) except Exception as e: conn.close() @@ -2780,12 +2887,26 @@ def get_work_orders(): order_no = request.args.get('order', '') date = request.args.get('date', '') + # 获取当前用户信息 + user_id = session.get('user_id') + user_role = session.get('role') + conn = get_db() c = conn.cursor() + # 获取用户所属工厂 + c.execute('SELECT factory FROM users WHERE id=?', (user_id,)) + user_row = c.fetchone() + user_factory = user_row['factory'] if user_row and user_row['factory'] else None + query = 'SELECT * FROM work_orders WHERE 1=1' params = [] + # 如果是管理员(非超级管理员),只能看到自己工厂的订单 + if user_role == 'admin' and user_factory: + query += ' AND factory = ?' + params.append(user_factory) + if factory: query += ' AND factory LIKE ?' params.append(f'%{factory}%') @@ -2840,13 +2961,28 @@ def create_work_order(): 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 + # 获取当前用户信息 + user_id = session.get('user_id') + user_role = session.get('role') + username = session.get('username', '') conn = get_db() c = conn.cursor() - username = session.get('username', '') + # 如果是管理员(非超级管理员),强制使用用户自己的工厂 + if user_role == 'admin': + c.execute('SELECT factory FROM users WHERE id=?', (user_id,)) + user_row = c.fetchone() + if user_row and user_row['factory']: + factory = user_row['factory'] + else: + conn.close() + return jsonify({'error': '您的账户未设置所属工厂,请联系超级管理员'}), 400 + + if not factory or not order_no or not order_qty: + conn.close() + return jsonify({'error': '请填写所有必填项'}), 400 + now = get_beijing_time() c.execute('''INSERT INTO work_orders( @@ -2863,17 +2999,17 @@ def create_work_order(): log('create_work_order', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}') - # 如果是超级管理员添加工单,通知所有管理员 - notify_admins('添加工单', f'工单号: {order_no}, 工厂: {factory}, 数量: {order_qty}') + # 如果是超级管理员添加工单,通知该工厂的管理员 + notify_admins_by_factory('添加工单', f'工单号: {order_no}, 工厂: {factory}, 数量: {order_qty}', factory=factory) return jsonify({'ok': True, 'id': order_id, 'message': '工单创建成功'}) @app.put('/api/work-orders/') @require_login -@require_any_role('admin','superadmin') +@require_any_role('superadmin') def update_work_order(order_id): - """更新工单""" + """更新工单(仅超级管理员)""" data = request.get_json() factory = data.get('factory', '').strip() @@ -2910,16 +3046,16 @@ def update_work_order(order_id): conn.close() log('update_work_order', f'工单ID: {order_id}, 工单号: {order_no}, 型号: {product_model}') - notify_superadmin('更新工单', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}') + notify_admins_by_factory('更新工单', f'工单号: {order_no}, 工厂: {factory}, 型号: {product_model}', factory=factory) return jsonify({'ok': True, 'message': '工单更新成功'}) @app.delete('/api/work-orders/') @require_login -@require_any_role('admin','superadmin') +@require_any_role('superadmin') def delete_work_order(order_id): - """删除工单""" + """删除工单(仅超级管理员)""" conn = get_db() c = conn.cursor() @@ -2939,7 +3075,7 @@ def delete_work_order(order_id): conn.close() log('delete_work_order', f'工单ID: {order_id}, 工单号: {order_no}') - notify_superadmin('删除工单', f'工单号: {order_no}, 工厂: {factory}') + notify_admins_by_factory('删除工单', f'工单号: {order_no}, 工厂: {factory}', factory=factory) return jsonify({'ok': True, 'message': '工单删除成功'}) @@ -2949,6 +3085,10 @@ def delete_work_order(order_id): @require_any_role('admin','superadmin') def confirm_work_order(order_id): """确认工单""" + # 获取当前用户信息 + user_id = session.get('user_id') + user_role = session.get('role') + conn = get_db() c = conn.cursor() @@ -2964,6 +3104,16 @@ def confirm_work_order(order_id): factory = row['factory'] current_status = row['status'] + # 如果是管理员(非超级管理员),检查工单是否属于自己的工厂 + if user_role == 'admin': + c.execute('SELECT factory FROM users WHERE id=?', (user_id,)) + user_row = c.fetchone() + user_factory = user_row['factory'] if user_row and user_row['factory'] else None + + if not user_factory or factory != user_factory: + conn.close() + return jsonify({'error': '您只能确认自己工厂的工单'}), 403 + # 如果已经确认,返回提示 if current_status == 'confirmed': conn.close() @@ -3020,6 +3170,7 @@ def convert_material_purchase_to_camel(row): @app.get('/api/material-purchase/list') @require_login +@require_any_role('superadmin') def list_material_purchase(): """获取物料清单列表(不包括已删除的)""" conn = get_db() @@ -3034,6 +3185,7 @@ def list_material_purchase(): @app.get('/api/material-purchase/recycle-bin') @require_login +@require_any_role('superadmin') def list_material_purchase_recycle_bin(): """获取回收站列表""" conn = get_db() @@ -3048,7 +3200,7 @@ def list_material_purchase_recycle_bin(): @app.post('/api/material-purchase/add') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def add_material_purchase(): """新增物料需求""" data = request.get_json() or {} @@ -3089,7 +3241,7 @@ def add_material_purchase(): @app.post('/api/material-purchase/update') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def update_material_purchase(): """更新物料需求""" data = request.get_json() or {} @@ -3126,7 +3278,7 @@ def update_material_purchase(): @app.post('/api/material-purchase/delete') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def delete_material_purchase(): """删除物料需求(移到回收站)""" data = request.get_json() or {} @@ -3153,7 +3305,7 @@ def delete_material_purchase(): @app.post('/api/material-purchase/restore') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def restore_material_purchase(): """从回收站恢复""" data = request.get_json() or {} @@ -3178,7 +3330,7 @@ def restore_material_purchase(): @app.post('/api/material-purchase/permanent-delete') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def permanent_delete_material_purchase(): """永久删除""" data = request.get_json() or {} @@ -3201,7 +3353,7 @@ def permanent_delete_material_purchase(): @app.post('/api/material-purchase/empty-recycle-bin') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def empty_recycle_bin(): """清空回收站""" conn = get_db() @@ -3219,7 +3371,7 @@ def empty_recycle_bin(): @app.route('/api/validate/material-purchase-file', methods=['POST']) @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def validate_material_purchase_file(): """验证物料清单Excel文件格式""" f = request.files.get('file') @@ -3274,7 +3426,7 @@ def validate_material_purchase_file(): @app.post('/api/upload/material-purchase-file') @require_login -@require_any_role('admin', 'superadmin') +@require_any_role('superadmin') def upload_material_purchase_file(): """上传物料清单Excel文件""" f = request.files.get('file') @@ -3398,6 +3550,115 @@ def upload_material_purchase_file(): return jsonify({'error': f'导入失败:{str(e)}'}), 500 +# ==================== 客户订单 API ==================== + +@app.get('/api/customer-orders') +@require_login +@require_any_role('superadmin') +def get_customer_orders(): + """获取客户订单列表""" + conn = get_db() + c = conn.cursor() + c.execute('''SELECT id, order_date, order_no, customer_name, material, quantity, unit_price, + created_by, created_at, updated_at + FROM customer_orders + ORDER BY order_date DESC, id DESC''') + rows = c.fetchall() + conn.close() + + orders = [] + for row in rows: + orders.append({ + 'id': row['id'], + 'order_date': row['order_date'], + 'order_no': row['order_no'], + 'customer_name': row['customer_name'] if 'customer_name' in row.keys() else '', + 'material': row['material'], + 'quantity': row['quantity'], + 'unit_price': row['unit_price'], + 'created_by': row['created_by'], + 'created_at': row['created_at'], + 'updated_at': row['updated_at'] + }) + + return jsonify({'list': orders}) + + +@app.post('/api/customer-orders') +@require_login +@require_any_role('superadmin') +def create_customer_order(): + """创建客户订单""" + data = request.get_json() or {} + + order_date = data.get('order_date', '').strip() + order_no = data.get('order_no', '').strip() + customer_name = data.get('customer_name', '').strip() + material = data.get('material', '').strip() + quantity = data.get('quantity', 0) + unit_price = data.get('unit_price', 0) + + if not order_date or not order_no or not customer_name or not material: + return jsonify({'error': '请填写所有必填项'}), 400 + + if quantity <= 0: + return jsonify({'error': '订单数量必须大于0'}), 400 + + if unit_price < 0: + return jsonify({'error': '单价不能为负数'}), 400 + + username = session.get('username', '') + now = get_beijing_time() + + conn = get_db() + c = conn.cursor() + + c.execute('''INSERT INTO customer_orders( + order_date, order_no, customer_name, material, quantity, unit_price, + created_by, created_at, updated_at + ) VALUES(?,?,?,?,?,?,?,?,?)''', ( + order_date, order_no, customer_name, material, quantity, unit_price, + username, now, now + )) + + order_id = c.lastrowid + conn.commit() + conn.close() + + log('create_customer_order', f'客户: {customer_name}, 订单号: {order_no}, 物料: {material}, 数量: {quantity}') + + return jsonify({'ok': True, 'id': order_id, 'message': '订单创建成功'}) + + +@app.delete('/api/customer-orders/') +@require_login +@require_any_role('superadmin') +def delete_customer_order(order_id): + """删除客户订单""" + conn = get_db() + c = conn.cursor() + + # 获取订单信息用于日志 + c.execute('SELECT order_no, customer_name, material FROM customer_orders WHERE id=?', (order_id,)) + row = c.fetchone() + + if not row: + conn.close() + return jsonify({'error': '订单不存在'}), 404 + + order_no = row['order_no'] + customer_name = row['customer_name'] if 'customer_name' in row.keys() else '' + material = row['material'] + + c.execute('DELETE FROM customer_orders WHERE id=?', (order_id,)) + conn.commit() + conn.close() + + log('delete_customer_order', f'订单ID: {order_id}, 客户: {customer_name}, 订单号: {order_no}') + + return jsonify({'ok': True, 'message': '订单删除成功'}) + + @app.errorhandler(404) def not_found(e): # 如果请求的是 HTML 页面(通过 Accept header 判断),返回 index.html