仪表盘
飞书群机器人逆向操作
配置步骤:
1. 在飞书开放平台创建应用,开启机器人能力
2. 订阅事件:im.message.receive_v1(接收消息)
3. 事件回调URL填写:https://qw.900123.top/api/feishu/event
4. 将应用添加到飞书群中

支持的命令格式(在群中@机器人后输入):
入库 SN:xxx 型号:xxx 仓库:xxx 设备类型:xxx 客户:xxx
出库申请 需求人:xxx 型号:xxx 数量:1
查询 型号:xxx
查询 SN:xxx
注:仓库/设备类型/客户为可选项;操作结果自动同步到网页端
`; } async function loadNotifyConfig() { try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/notify/config', { headers }); const config = await resp.json(); if (config) { const el = (id) => document.getElementById(id); if (el('notify-enabled')) el('notify-enabled').value = String(config.enabled || false); if (el('notify-feishu')) el('notify-feishu').value = config.feishuWebhook || ''; if (el('notify-wechat')) el('notify-wechat').value = config.wechatWebhook || ''; } const sfResp = await fetch(API_BASE + '/sf/config', { headers }); const sfConfig = await sfResp.json(); if (sfConfig) { const el = (id) => document.getElementById(id); if (el('sf-enabled')) el('sf-enabled').value = String(sfConfig.enabled || false); if (el('sf-customer-code')) el('sf-customer-code').value = sfConfig.customerCode || ''; if (el('sf-check-word')) el('sf-check-word').value = sfConfig.checkWord || ''; if (el('sf-month-account')) el('sf-month-account').value = sfConfig.monthAccount || ''; if (el('sf-sender-name')) el('sf-sender-name').value = sfConfig.senderName || ''; if (el('sf-sender-phone')) el('sf-sender-phone').value = sfConfig.senderPhone || ''; if (el('sf-sender-address')) el('sf-sender-address').value = sfConfig.senderAddress || ''; } } catch(e) {} } async function saveNotifyConfig() { const el = (id) => document.getElementById(id); const config = { enabled: el('notify-enabled') ? el('notify-enabled').value === 'true' : false, feishuWebhook: el('notify-feishu') ? el('notify-feishu').value : '', wechatWebhook: el('notify-wechat') ? el('notify-wechat').value : '' }; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; await fetch(API_BASE + '/notify/config', { method: 'PUT', headers, body: JSON.stringify(config) }); } catch(e) {} } async function testNotify() { const el = (id) => document.getElementById(id); const feishuUrl = el('notify-feishu') ? el('notify-feishu').value.trim() : ''; const wechatUrl = el('notify-wechat') ? el('notify-wechat').value.trim() : ''; const resultEl = el('notify-result'); if (!feishuUrl && !wechatUrl) { if (resultEl) resultEl.innerHTML = '请至少填写一个完整的Webhook URL'; return; } if (feishuUrl && !feishuUrl.includes('/bot/v2/hook/')) { if (resultEl) resultEl.innerHTML = '飞书Webhook URL格式不正确,应为: https://open.feishu.cn/open-apis/bot/v2/hook/xxx'; return; } if (wechatUrl && !wechatUrl.includes('/cgi-bin/webhook/send')) { if (resultEl) resultEl.innerHTML = '企业微信Webhook URL格式不正确,应为: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx'; return; } await saveNotifyConfig(); const user = currentUser(); if (resultEl) resultEl.innerHTML = '发送中...'; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/notify/send', { method: 'POST', headers, body: JSON.stringify({ title: '📋 CF库存系统 - 测试通知', content: `这是一条测试消息\n发送人: ${user ? user.name : '未知'}\n时间: ${new Date().toLocaleString()}` }) }); const data = await resp.json(); if (data.success) { const results = data.results || []; if (results.length === 0) { if (resultEl) resultEl.innerHTML = 'API返回成功但无发送结果,请检查Webhook URL是否完整'; } else { const details = results.map(r => { if (r.includes(':200') || r.includes(':0')) return `${r} ✅`; return `${r} ❌`; }).join(' '); if (resultEl) resultEl.innerHTML = `发送结果: ${details}`; } } else { if (resultEl) resultEl.innerHTML = `发送失败: ${data.message || data.error || '未知错误'}`; } } catch(e) { if (resultEl) resultEl.innerHTML = `请求失败: ${e.message}`; } toast('测试通知已发送', 'success'); } function onCourierChange(prefix) { const sel = document.getElementById(prefix ? prefix + '-courier' : 'repair-courier'); const area = document.getElementById(prefix ? 'sf-order-area-' + prefix : 'sf-order-area'); if (area) area.style.display = (sel && sel.value === '顺丰') ? 'block' : 'none'; } async function initD1DB() { const el = document.getElementById('d1-result'); if (el) el.innerHTML = '初始化中...'; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/db/init', { method: 'POST', headers }); const data = await resp.json(); const detail = JSON.stringify(data, null, 2).replace(/✓ ${data.message}
表: ${(data.tables||[]).join(', ')}
${detail}
`; toast('D1表初始化成功', 'success'); } else { if (el) el.innerHTML = `${data.message}
${detail}
`; } } catch(e) { if (el) el.innerHTML = `请求失败: ${e.message}`; } } async function migrateToD1() { const el = document.getElementById('d1-result'); if (el) el.innerHTML = '迁移中,请勿操作...'; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/db/migrate', { method: 'POST', headers }); const data = await resp.json(); const detail = JSON.stringify(data, null, 2).replace(/ `${k}: ${v}条`).join(', '); if (el) el.innerHTML = `✓ ${data.message}
${lines}

${detail}
`; toast('KV→D1迁移成功', 'success'); } else { if (el) el.innerHTML = `${data.message}
${detail}
`; } } catch(e) { if (el) el.innerHTML = `迁移失败: ${e.message}`; } } async function testD1Sync() { const el = document.getElementById('d1-result'); if (el) el.innerHTML = '测试D1同步中...'; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/d1/sync', { headers }); const data = await resp.json(); if (data && data.data) { const d = data.data; const info = `产品: ${(d.products||[]).length}, 单品: ${(d.items||[]).length}, 仓库: ${(d.warehouses||[]).length}, 订单: ${(d.orders||[]).length}, 日志: ${(d.logs||[]).length}`; if (el) el.innerHTML = `✓ D1同步正常
${info}
`; toast('D1同步测试成功', 'success'); } else { if (el) el.innerHTML = `D1同步无数据或失败`; } } catch(e) { if (el) el.innerHTML = `D1同步失败: ${e.message}`; } } async function saveSfConfig() { const el = (id) => document.getElementById(id); const config = { enabled: el('sf-enabled') ? el('sf-enabled').value === 'true' : false, customerCode: el('sf-customer-code') ? el('sf-customer-code').value : '', checkWord: el('sf-check-word') ? el('sf-check-word').value : '', monthAccount: el('sf-month-account') ? el('sf-month-account').value : '', senderName: el('sf-sender-name') ? el('sf-sender-name').value : '', senderPhone: el('sf-sender-phone') ? el('sf-sender-phone').value : '', senderAddress: el('sf-sender-address') ? el('sf-sender-address').value : '', }; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; await fetch(API_BASE + '/sf/config', { method: 'PUT', headers, body: JSON.stringify(config) }); toast('顺丰配置已保存', 'success'); } catch(e) { toast('保存失败', 'error'); } } async function sfQuickOrder(prefix) { const resultEl = document.getElementById(prefix ? 'sf-order-result-' + prefix : 'sf-order-result'); if (resultEl) resultEl.innerHTML = '下单中...'; try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/sf/order', { method: 'POST', headers, body: JSON.stringify({ source: prefix || 'repair' }) }); const data = await resp.json(); if (data.success) { if (resultEl) resultEl.innerHTML = `下单成功!运单号: ${data.waybillNo||''}`; toast('顺丰下单成功', 'success'); } else { if (resultEl) resultEl.innerHTML = `${data.message||data.error||'下单失败'}`; } } catch(e) { if (resultEl) resultEl.innerHTML = `请求失败: ${e.message}`; } } async function sfQuickOrderFromLogistics() { const resultEl = document.getElementById('sf-quick-result'); const courier = document.getElementById('sf-quick-courier').value; const name = document.getElementById('sf-quick-name').value.trim(); const phone = document.getElementById('sf-quick-phone').value.trim(); const address = document.getElementById('sf-quick-address').value.trim(); if (!name || !phone || !address) return toast('请填写收件人、电话和地址', 'error'); if (resultEl) resultEl.innerHTML = '下单中...'; if (courier === '顺丰') { try { const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; const resp = await fetch(API_BASE + '/sf/order', { method: 'POST', headers, body: JSON.stringify({ receiverName: name, receiverPhone: phone, receiverAddress: address, source: 'logistics' }) }); const data = await resp.json(); if (data.success) { const waybillNo = data.waybillNo || ''; if (resultEl) resultEl.innerHTML = `下单成功!运单号: ${waybillNo}`; const db = getDB(); if (!db.logisticsRecords) db.logisticsRecords = []; db.logisticsRecords.push({ trackingNo: waybillNo, courier: '顺丰', status: '已下单', sender: currentUser()?currentUser().name:'系统', receiver: name + ' ' + phone, createdAt: now() }); saveDB(db); toast('顺丰下单成功', 'success'); render(); } else { if (resultEl) resultEl.innerHTML = `${data.message||data.error||'下单失败'}`; } } catch(e) { if (resultEl) resultEl.innerHTML = `请求失败: ${e.message}`; } } else { const db = getDB(); if (!db.logisticsRecords) db.logisticsRecords = []; db.logisticsRecords.push({ trackingNo: '待填写', courier, status: '待寄出', sender: currentUser()?currentUser().name:'系统', receiver: name + ' ' + phone, createdAt: now() }); saveDB(db); if (resultEl) resultEl.innerHTML = `${courier}已记录,请自行下单并填写运单号`; toast(`${courier}寄送记录已保存`, 'success'); render(); } } // ============ Logs ============ function renderLogs() { try { const db = getDB(); let logRows = ''; if (db.logs.length > 0) { logRows = db.logs.map(l => ` ${l.createdAt} ${l.type} ${l.operator||'-'} ${l.targetType||'-'} ${l.pn||'-'} ${l.sn||'-'} ${l.detail||'-'} `).join(''); } else { logRows = '暂无操作日志'; } return `
操作日志${db.logs.length} 条记录
${logRows}
时间 类型 操作人 对象类型 PN SN 详情
`; } catch(e) { return '
操作日志加载失败
'; } } // ============ Reset ============ function resetAllData() { if (confirm('确定要重置所有数据吗?此操作不可恢复,仅保留超级管理员账户。')) { try { const adminUser = { id: 'admin', name: 'admin', password: 'renye1900', role: 'super_admin', status: '已激活', permissions: ['dashboard', 'products', 'inbound', 'stock-query', 'ledger', 'orders', 'outbound', 'users', 'warehouse', 'customers', 'repair', 'export', 'notify', 'logs'] }; const freshDb = { products: [], items: [], orders: [], outboundOrders: [], users: [adminUser], repairOrders: [], logs: [], alerts: [], warehouses: [ { id: 'bj', name: '北京仓库', code: 'BJ', shelves: [] }, { id: 'sh', name: '上海仓库', code: 'SH', shelves: [] }, { id: 'gz', name: '广州仓库', code: 'GZ', shelves: [] } ], spareTypes: [], transferRecords: [], customers: [ { id: 'c_bytedance', name: '字节跳动', code: 'BYTEDANCE', contact: '', phone: '', createdAt: '' }, { id: 'c_tencent', name: '腾讯', code: 'TENCENT', contact: '', phone: '', createdAt: '' }, { id: 'c_alibaba', name: '阿里巴巴', code: 'ALIBABA', contact: '', phone: '', createdAt: '' }, { id: 'c_general', name: '通用', code: 'GENERAL', contact: '', phone: '', createdAt: '' } ] }; localStorage.setItem(DB_KEY, JSON.stringify(freshDb)); localStorage.setItem(SESSION_KEY, JSON.stringify(adminUser)); localStorage.setItem('skip_sync_pull', '1'); _syncEnabled = true; toast('正在重置数据...', 'success'); const headers = { 'Content-Type': 'application/json' }; if (_syncToken) headers['Authorization'] = 'Bearer ' + _syncToken; fetch(API_BASE + '/d1/reset', { method: 'POST', headers }) .then(() => syncPush()) .then(() => { setTimeout(() => location.reload(), 1500); }) .catch(() => { setTimeout(() => location.reload(), 1500); }); } catch(e) { toast('重置失败', 'error'); } } } // ============ Error Handler ============ window.onerror = function(message, source, lineno, colno, error) { safeConsole.error('全局错误:', message, 'at line', lineno); toast('系统错误: ' + message, 'error'); return false; }; // ============ Init ============ window.onload = function() { try { initTheme(); safeConsole.log('页面开始初始化'); if (typeof handleOAuthCallback === 'function') { handleOAuthCallback(); } if (currentUser()) { _syncEnabled = true; syncPull().then(() => { render(); updateSidebarNavigation(); }); } updateLoginUI(); updateSidebarNavigation(); navigate('dashboard'); safeConsole.log('页面初始化完成'); } catch(e) { safeConsole.error('页面初始化失败:', e); toast('系统初始化失败,请刷新页面', 'error'); } };