默认
已采购
草稿
已撤销
状态 日期 供应商名称 打印次数
采购单 -- TBDF
// ========== API 数据加载 ========== var _apiPurchases = []; async function _loadApiPurchases() { try { var res = await ApiService.getPurchases({ limit: 1000 }); var list = (res.data && res.data.list) ? res.data.list : []; _apiPurchases = list.map(function(p) { return { id: p.id, orderNo: p.order_no || '', date: p.date || (p.create_time||'').slice(0,10), supplierId: p.supplier_id, supplierName: p.supplier_name || '', warehouseId: p.warehouse_id, warehouseName: p.warehouse_name || '', totalAmount: parseFloat(p.total_amount) || 0, status: p.status || '草稿', remark: p.remark || '', printCount: p.print_count || 0, freight: parseFloat(p.freight) || 0, discount: parseFloat(p.discount) || 0, paidAmount: parseFloat(p.paid_amount) || 0 }; }); // 覆盖 MockDB.purchases if (window.MockDB) window.MockDB.purchases = _apiPurchases; console.log('API loaded ' + _apiPurchases.length + ' purchases'); renderList(); } catch(e) { console.error('API load error:', e); } } // 从 API 重新加载数据并刷新列表(供所有操作回调使用) async function _reloadFromApi() { try { var res = await ApiService.getPurchases({ limit: 1000 }); var list = (res.data && res.data.list) ? res.data.list : []; _apiPurchases = list.map(function(p) { return { id: p.id, orderNo: p.order_no || '', date: p.date || (p.create_time||'').slice(0,10), supplierId: p.supplier_id, supplierName: p.supplier_name || '', warehouseId: p.warehouse_id, warehouseName: p.warehouse_name || '', totalAmount: parseFloat(p.total_amount) || 0, status: p.status || '草稿', remark: p.remark || '', printCount: p.print_count || 0, freight: parseFloat(p.freight) || 0, discount: parseFloat(p.discount) || 0, paidAmount: parseFloat(p.paid_amount) || 0 }; }); if (window.MockDB) window.MockDB.purchases = _apiPurchases; renderList(); // 如果当前选中的订单仍存在,选中它 if (currentOrderId) { var stillExists = _apiPurchases.some(function(p) { return p.id === currentOrderId; }); if (stillExists) { selectOrder(currentOrderId); } else { showEmptyRight(); currentOrderId = null; } } } catch(e) { console.error('Reload error:', e); throw e; } } // 页面加载后自动调用 setTimeout(function() { if (typeof renderList === 'function') _loadApiPurchases(); }, 500); // ==================== 全局变量 ==================== var currentOrderId = null; var currentTab = 'default'; var formGoods = [ {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''} ]; // 当前编辑的商品行 // ==================== 初始化 ==================== function initPage() { // 填充供应商下拉 const sel = document.getElementById('fSupplier'); (MockDB.suppliers||[]).forEach(s => { sel.innerHTML += ``; }); // 填充仓库下拉 const wh = document.getElementById('fWarehouse'); (MockDB.warehouses||[]).forEach(w => { wh.innerHTML += ``; }); // 渲染列表 renderList(); const data = getFilteredData(); if (!data.length) { newPurchase(); } else { selectOrder(data[0].id); } } // ==================== Tab切换 ==================== function switchTab(tab, el) { currentTab = tab; document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active')); if(el) el.classList.add('active'); renderList(); } function doQuickSearch() { renderList(); } // ==================== 数据过滤 ==================== function getFilteredData() { let d = (MockDB.purchases || []).filter(o => o && o.id); // Tab筛选 if (currentTab === 'purchased') d = d.filter(o => o.status === '已采购' || o.status === '已入库'); else if (currentTab === 'draft') d = d.filter(o => o.status === '草稿'); else if (currentTab === 'cancelled') d = d.filter(o => o.status === '已取消'); // 快速搜索 var qs = (document.getElementById('quickSearch').value||'').trim(); if (qs) d = d.filter(o => App.smartMatch(o.orderNo, qs) || App.smartMatch(o.supplierName, qs)); // 高级筛选 var sp = document.getElementById('fSupplier').value; if (sp) d = d.filter(o => o.supplierName === sp); var on = document.getElementById('fOrderNo').value.trim(); if (on) d = d.filter(o => (o.orderNo||'').includes(on)); var df = document.getElementById('fDateFrom').value; var dt = document.getElementById('fDateTo').value; // 如果未设置日期范围,默认显示最近30天(包含今天) if (!df && !dt) { var today = new Date(); var thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); df = thirtyDaysAgo.toISOString().slice(0,10); dt = today.toISOString().slice(0,10); // 更新筛选框显示 document.getElementById('fDateFrom').value = df; document.getElementById('fDateTo').value = dt; } if (df) d = d.filter(o => o && o.date != null && o.date >= df); if (dt) d = d.filter(o => o && o.date != null && o.date <= dt); d.sort((a,b) => b.id - a.id); return d; } function doSearch() { renderList(); } function resetFilter() { document.getElementById('fDateFrom').value = ''; document.getElementById('fDateTo').value = ''; document.getElementById('fSupplier').selectedIndex = 0; document.getElementById('fOrderNo').value = ''; document.getElementById('fRemark').value = ''; document.getElementById('fCreator').selectedIndex = 0; document.getElementById('fWarehouse').selectedIndex = 0; document.getElementById('fPrintStatus').selectedIndex = 0; document.getElementById('fShop').selectedIndex = 0; renderList(); } // ==================== 左侧列表渲染 ==================== function renderList() { const data = getFilteredData(); const tbody = document.getElementById('listBody'); if (!data.length) { tbody.innerHTML = '暂无数据'; return; } tbody.innerHTML = data.map((o,i) => ` ${statusDot(o.status)} ${o.date} ${o.supplierName||''} ${o.printCount||0} `).join(''); } function statusDot(s) { const map = {'草稿':'draft','已采购':'purchased','已取消':'cancelled'}; const cls = map[s] || 'draft'; const labels = {'草稿':'草稿','已采购':'已采购','已取消':'已取消'}; return `${labels[s]||s}`; } function toggleCheckAll() { const checked = document.getElementById('checkAll').checked; document.querySelectorAll('#listBody input[type=checkbox]').forEach(c => c.checked = checked); } // 从 API 加载订单商品明细 async function loadOrderItems(orderId) { try { var res = await ApiService.request('GET', '/purchases/' + orderId + '/items'); if (res && res.code === 200 && res.data) { return res.data.map(function(item) { return { goodsId: item.goods_id, name: item.goods_name, code: item.goods_code || '', spec: item.spec || '', barcode: item.barcode || '', unit: item.unit || '支', qty: item.qty || 1, price: item.price || 0, discount: item.discount || 100, afterPrice: item.after_price || item.price || 0, amount: item.amount || 0, remark: item.remark || '' }; }); } } catch(e) { console.warn('[Purchase] Failed to load items:', e); } return []; } function selectOrder(id) { currentOrderId = id; formGoods = [ {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''} ]; const o = MockDB.getById('purchases', id); if (o) { var whName = o.warehouseName || '主仓库'; // 如果没有 items,尝试从 API 加载 var items = o.items || []; if (!items.length && o.id) { // 异步加载商品明细 loadOrderItems(o.id).then(function(apiItems) { if (apiItems.length) { o.items = apiItems; // 重新选中以刷新显示 if (currentOrderId === o.id) { selectOrder(o.id); } } }); } formGoods = items.map((item,idx) => { // 从库存表读取当前库存 var stockVal = 0; if(MockDB.stock && MockDB.stock.length){ for(var si=0; si 单据状态: ${statusLabel} `; // 累计应付款 html += `
累计应付款: ${formatMoney(calcAccumulatedPayable(order.id, order.supplierId))} ⓘ 原名"累计应付欠款"已更新为"累计应付款"
`; // 基本信息 - 一行多列布局 html += `
基本信息
${isEditable?``:''}
`; // 商品明细 - 表头对照参考网站 html += `
商品明细
${isEditable ? `` : ''}
${renderGoodsRows(isEditable)}
⚙️商品 📋货号 型号规格 条码 数量 单位 当前库存 单价 折后价(%) 折后价 金额 折后金额 序列号查看 备注
合计
¥0.00
折后合计
¥0.00
`; // ========== 附图上传区域 ========== html += `
附图:
`; // ========== 底部汇总 ========== html += `
费用金额:
抹零:
总合计:${formatMoney(order.totalAmount)}
`; // 底部操作按钮(简化:只保留草稿和已采购两个状态) if (order.status === '草稿') { html += `
`; } else if (order.status === '已采购' || order.status === '已入库') { html += `
`; } document.getElementById('rpBody').innerHTML = html; // 初始化附图 setTimeout(renderAttachImgs, 50); calcGoodsTotal(); } // ==================== 商品行渲染 ==================== function renderGoodsRows(editable) { if (!formGoods.length) { return '点击"选择商品"添加商品明细'; } return formGoods.map((g, idx) => ` ${idx+1} ${editable?``:g.name} ${g.code||''} ${g.spec||''} ${g.barcode||''} ${g.unit||'支'} ${g.stock||0} ${formatMoney(g.afterPrice)} ${formatMoney(g.amount)} ${formatMoney(g.amount)} 点击输... `).join(''); } function addEmptyRow() { formGoods.push({_new:true, goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}); refreshGoodsTable(); } function insertRowAfter(idx) { formGoods.splice(idx+1, 0, {_new:true, goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}); refreshGoodsTable(); } function removeRow(idx) { formGoods.splice(idx, 1); refreshGoodsTable(); } // ========== 批量设置折扣 ========== function batchSetDiscount(){ if(!formGoods || !formGoods.length){ toast('当前单据没有商品行','error'); return; } var h = '
'; h += '
设置所有商品的折扣率(%):
'; h += ''; h += '
范围:1-100,例如输入 90 表示打九折
'; h += '
'; showPageModal('批量设置折扣', h, function(){ var inputEl = document.getElementById('batchDiscountInput'); if(!inputEl) return; var val = parseFloat(inputEl.value); if(isNaN(val) || val < 1 || val > 100){ toast('请输入1-100之间的折扣率','error'); return; } formGoods.forEach(function(g, idx){ updateGoodsRow(idx, 'discount', val); }); refreshGoodsTable(); toast('已将 '+formGoods.length+' 个商品折扣率设为 '+val+'%'); }); } function updateGoodsRow(idx, field, val) { if (!formGoods[idx]) return; if (field === 'qty' || field === 'price' || field === 'discount') { formGoods[idx][field] = parseFloat(val)||0; } else { formGoods[idx][field] = val; } // 重算:折后价 = 单价 * 折扣/100;折后金额 = 数量 * 折后价 const g = formGoods[idx]; g.afterPrice = +(g.price * (g.discount||100)/100).toFixed(2); g.amount = +(g.qty * g.afterPrice).toFixed(2); calcGoodsTotal(); refreshGoodsTable(false); // 不重置滚动位置 } function refreshGoodsTable(fullRefresh=true) { const order = MockDB.getById('purchases', currentOrderId); const editable = order && (order.status==='草稿'||order.status==='待审核'); const tb = document.getElementById('goodsBody'); if (tb) tb.innerHTML = renderGoodsRows(editable); calcGoodsTotal(); } function calcGoodsTotal() { let total = 0, afterTotal = 0; formGoods.forEach(g => { total += (g.qty||0)*(g.price||0); afterTotal += (g.qty||0)*(g.afterPrice||0); }); const gtEl = document.getElementById('gsTotal'); const gatEl = document.getElementById('gsAfterTotal'); if(gtEl) gtEl.textContent = formatMoney(total); if(gatEl) gatEl.textContent = formatMoney(afterTotal); // 联动实付金额输入框(始终同步折后合计) var paidInput = document.getElementById('rf_paid'); if(paidInput){ paidInput.value = formatMoney(afterTotal); } } // ==================== 选择商品弹窗(调用全局 GoodsPicker)==================== function openGoodsSelect(targetIdx) { if (typeof GoodsPicker === 'undefined') { toast('商品选择器未加载', 'error'); return; } var warehouseEl = document.getElementById('pFormWarehouse'); var warehouse = warehouseEl ? warehouseEl.value : ''; GoodsPicker.open({ title: '选择采购商品', priceField: 'purchasePrice', selectedIds: (formGoods || []).filter(function(x){ return x && x.goodsId; }).map(function(x){ return x.goodsId; }), warehouse: warehouse, rowIndex: (typeof targetIdx === 'number') ? targetIdx : -1, onSelect: function(g, rowIndex) { pickGoods(g.id, rowIndex); } }); } function pickGoods(goodsId, targetIdx) { const g = MockDB.getById('goods', goodsId); if (!g) return; // 从库存表查询当前库存 var currentStock = 0; var whName = '主仓库'; // 尝试从当前单据获取仓库,如果还没有则用默认值 try { var orderForWh = MockDB.getById('purchases', currentOrderId); if(orderForWh && orderForWh.warehouseName) whName = orderForWh.warehouseName; } catch(e) {} // 查库存表:按goodsId + warehouse匹配 if(MockDB.stock && MockDB.stock.length){ for(var si=0; si= 0) { formGoods[targetIdx] = row; } else { formGoods.push(row); } // 自动追加空白行 if(!formGoods.some(function(x){ return !x.goodsId; })){ formGoods.push({_new:true, goodsId:null, name:'', code:'', spec:'', barcode:'', model:'', brand:'', category:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}); } closePageModal(); refreshGoodsTable(); toast('已选择商品: ' + g.name); } // 旧 filterGoodsModal 已被 GoodsPicker 内部接管,保留为兼容旧调用的空函数 function filterGoodsModal(kw) { /* 已迁移到 GoodsPicker */ } // ==================== 新增采购单 ==================== function newPurchase() { const today = new Date().toISOString().slice(0,10); const lastId = (MockDB.purchases||[]).filter(o=>o).reduce((m,o)=>Math.max(m,o.id||0),0); const newOrder = { id: lastId + 1, orderNo: 'CG' + today.replace(/-/g,'') + String(lastId+1).padStart(3,'0'), date: today, supplierId: null, supplierName: '', warehouseId: 1, warehouseName: '主仓库', status: '草稿', printCount: 0, items: [], totalAmount: 0, freight: 0, discount: 0, paidAmount: 0, paymentMethod: '', remark: '' }; // 使用MockDB.add确保数据持久化 MockDB.add('purchases', newOrder); currentOrderId = newOrder.id; formGoods = [ {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''}, {goodsId:null, name:'', code:'', spec:'', barcode:'', unit:'支', qty:1, stock:0, price:0, discount:100, afterPrice:0, amount:0, remark:''} ]; renderList(); renderRightPanel(newOrder); toast('已创建新采购单'); } // ==================== 确保订单已存在于 API ==================== // 修正:新建订单时只在 MockDB 留了临时记录,需要先 POST 到 API 拿到真实 ID async function ensureOrderInApi() { var o = MockDB.getById('purchases', currentOrderId); if (!o) return null; if (!o._isNew) return o; // 已有 API ID,直接返回 // 收集基础数据 var supId = parseInt(document.getElementById('rf_supplier')?.value) || null; var whId = parseInt(document.getElementById('rf_warehouse')?.value) || null; var date = document.getElementById('rf_date')?.value || new Date().toISOString().slice(0,10); var remark = document.getElementById('rf_remark')?.value || ''; var items = formGoods.filter(g=>g.goodsId).map(function(g){ return { goods_id: g.goodsId, goods_name: g.name, goods_code: g.code, unit: g.unit, qty: g.qty, price: g.price, amount: g.amount }; }); var totalAmount = items.reduce(function(s,i){ return s + (Number(i.amount)||0); }, 0); try { var resp = await ApiService.request('POST', '/purchases', { order_no: o.orderNo, date: date, supplier_id: supId, warehouse_id: whId, items: items, total_amount: totalAmount, remark: remark }); var newId = resp.data && resp.data.id; if (newId) { // 把 MockDB 里的临时记录 id 替换为 API 真实 id var idx = MockDB.purchases.findIndex(function(x){ return x && x.id === currentOrderId; }); if (idx >= 0) { MockDB.purchases[idx].id = newId; MockDB.purchases[idx]._isNew = false; delete MockDB.purchases[idx]._isNew; currentOrderId = newId; } toast('采购单已同步到服务器(ID: ' + newId + ')', 'success'); return MockDB.getById('purchases', newId); } } catch (e) { console.error('[Purchase] ensureOrderInApi error:', e); throw e; } return o; } // ==================== 保存/提交 ==================== function saveAsDraft() { collectAndSave('草稿'); } function submitOrder() { collectAndSave('待审核'); } function collectAndSave(status) { if (!currentOrderId) { toast('请先选择或新建采购单','error'); return; } // 收集基本信息 const supId = parseInt(document.getElementById('rf_supplier').value); const sup = MockDB.getById('suppliers', supId); const whId = parseInt(document.getElementById('rf_warehouse').value); const wh = MockDB.getById('warehouses', whId); const date = document.getElementById('rf_date').value; const remark = document.getElementById('rf_remark').value; const items = formGoods.filter(g=>g.name).map(g => ({ goods_id: g.goodsId, goods_name: g.name, goods_code: g.code, unit: g.unit, qty: g.qty, price: g.price, amount: g.amount })); const totalAmount = items.reduce((s,i)=>s+i.amount,0); (async function(){ try { // 0. 先确保订单已存在于 API(新建订单时需要先 POST 拿到真实 ID) await ensureOrderInApi(); // 1. 调用 API 更新采购单内容 await ApiService.request('PUT', '/purchases/' + currentOrderId, { date: date, supplier_id: supId, warehouse_id: whId, items: items, total_amount: totalAmount, remark: remark }); // 2. 如果 status 不是草稿,再更新状态 if (status !== '草稿') { await ApiService.request('PUT', '/purchases/' + currentOrderId + '/status', { status: status }); } // 3. 重新从 API 加载最新数据 await _reloadFromApi(); toast(status==='草稿'?'已保存为草稿':'已提交审核'); } catch (e) { toast('保存失败:' + e.message, 'error'); console.error('[Purchase] Save error:', e); } })(); } // ==================== 入库 ==================== function receiveStock(id) { confirm('确认入库?入库后库存将增加,并生成应付账款。', function(){ (async function(){ try { // 先确保订单已存在于 API(新建订单时需要先 POST 拿到真实 ID) await ensureOrderInApi(); id = currentOrderId; // 用 API 真实 ID 替换临时 ID // 先保存当前内容到 API const supId = parseInt(document.getElementById('rf_supplier').value); const whId = parseInt(document.getElementById('rf_warehouse').value); const date = document.getElementById('rf_date').value; const remark = document.getElementById('rf_remark').value; const itemsApi = formGoods.filter(g=>g.name).map(g => ({ goods_id: g.goodsId, goods_name: g.name, goods_code: g.code, unit: g.unit, qty: g.qty, price: g.price, amount: g.amount })); const totalAmount = itemsApi.reduce((s,i)=>s+i.amount,0); await ApiService.request('PUT', '/purchases/' + id, { date: date, supplier_id: supId, warehouse_id: whId, items: itemsApi, total_amount: totalAmount, remark: remark }); // 调用 API 变更状态为「已入库」,后端自动增加库存 await ApiService.request('PUT', '/purchases/' + id + '/status', { status: '已入库' }); // 前端 MockDB 也更新库存(兼容后端不可用时的本地演示) const order = MockDB.getById('purchases', id); const warehouseName = order?.warehouseName || '主仓库'; formGoods.filter(g => g.goodsId).forEach(function(item) { var stockItem = MockDB.stock.find(function(s) { return String(s.goodsId) === String(item.goodsId) && s.warehouse === warehouseName; }); if (stockItem) { var newQty = (stockItem.currentQty || 0) + item.qty; MockDB.update('stock', stockItem.id, { currentQty: newQty, availableQty: newQty }); } else { MockDB.add('stock', { goodsId: item.goodsId, goodsName: item.name, warehouse: warehouseName, currentQty: item.qty, availableQty: item.qty, purchasePrice: item.price, status: '正常' }); } }); // 创建资金流水记录(采购付款) createFlowFromPurchase({ orderNo: order?.orderNo || '', supplierName: order?.supplierName || '', totalAmount: totalAmount, accountId: document.getElementById('rf_account')?.value }); // 重新从 API 加载数据 await _reloadFromApi(); toast('已入库,库存已更新'); } catch (e) { toast('入库失败:' + e.message, 'error'); console.error('[Purchase] receiveStock error:', e); } })(); }); } // 创建资金流水记录(采购付款) function createFlowFromPurchase(order) { var accountSel = document.getElementById('rf_account'); var accountName = '-请选择-'; if (accountSel && accountSel.value) { var acc = (MockDB.accounts || []).find(function(a) { return String(a.id) === String(accountSel.value); }); if (acc) accountName = acc.name; } var flowData = { order_no: order.orderNo, type: '付款', account_name: accountName, account_type: '现金', amount: order.totalAmount || 0, supplier_name: order.supplierName, channel: '银行转账', remark: '采购单付款 - ' + order.orderNo, date: new Date().toISOString().slice(0, 10) }; // 调用资金流水 API if (window.API_BASE) { fetch(API_BASE + '/flow/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(flowData) }).then(function (r) { return r.json(); }) .then(function (res) { console.log('[Flow] Created from purchase:', res); }) .catch(function (e) { console.error('[Flow] Failed:', e); }); } } // ==================== 打印模板对接 ==================== // 从设置读取打印模板配置 function getPrintTemplates(orderType) { var list = JSON.parse(localStorage.getItem('printTemplates_v2') || '[]'); var mainMap = JSON.parse(localStorage.getItem('printMainTemplates_v2') || '{}'); var typeList = list.filter(function(t) { return t.orderType === orderType; }); if (typeList.length === 0) { typeList = getBuiltinTemplates(orderType); } var mainId = mainMap[orderType]; typeList.forEach(function(t) { t.isMainTemplate = (t.id === mainId); }); return typeList; } function getBuiltinTemplates(orderType) { var defaults = { 21: [ // 采购单 { id: 'builtin_pur_a4', orderType: 21, templateName: 'A4标准采购单', templateType: 'A4标准', curSelColumns: ['seq','name','code','modelSpec','unit','qty','price','afterAmount'], curSelSystemVars: ['orderNo','orderDate','supplierName','contact','payable','remark'], fontSize: 12, rowHeight: 28, isMainTemplate: true } ], 22: [ // 采购退货 { id: 'builtin_pur_rtn', orderType: 22, templateName: 'A4采购退货单', templateType: 'A4标准', curSelColumns: ['seq','name','code','modelSpec','unit','qty','price','afterAmount'], curSelSystemVars: ['orderNo','orderDate','supplierName','contact','remark'], fontSize: 12, rowHeight: 28, isMainTemplate: true } ] }; return defaults[orderType] || defaults[21]; } function buildPrintHTMLWithTemplate(order, tpl, docType) { var cols = tpl.curSelColumns || ['name','qty','price','amount']; var sysVars = tpl.curSelSystemVars || ['orderNo','supplierName','payable']; var fontSize = tpl.fontSize || 12; var rowHeight = tpl.rowHeight || 28; var COL_HEADERS = { seq:'序号', name:'名称', code:'货号', barcode:'条码', modelSpec:'型号规格', unit:'单位', qty:'数量', price:'单价', discount:'折扣', afterPrice:'折后单价', amount:'金额', afterAmount:'折后金额', remark:'备注' }; var getVar = function(k) { var map = { orderNo: order.orderNo || '', orderDate: order.date || '', remark: order.remark || '', maker: '管理员', supplierName: order.supplierName || '', companyName: '进销存商城', warehouse: order.warehouseName || '', payable: formatMoney(order.totalAmount || 0), printTime: new Date().toLocaleString('zh-CN') }; return map[k] || ''; }; var showCol = function(k) { return cols.indexOf(k) >= 0; }; var showVar = function(k) { return sysVars.indexOf(k) >= 0; }; var ths = cols.map(function(c) { return COL_HEADERS[c] || ''; }).filter(function(h) { return h; }); if (ths.length === 0) ths = ['名称', '数量', '单价', '金额']; var title = docType === 'purchase' ? '采购单' : '采购退货单'; var html = ''+order.orderNo+''; html += ''; html += ''; return html; } // 打印采购单 function printOrder(id) { var o = MockDB.getById('purchases', id); if(!o) { toast('未找到采购单','error'); return; } var templates = getPrintTemplates(21); var tpl = templates[0]; if(!tpl) { toast('未找到打印模板','error'); return; } var html = buildPrintHTMLWithTemplate(o, tpl, 'purchase'); if(!html) { toast('生成打印内容失败','error'); return; } var pw = null; try { pw = window.open('about:blank', '_printwin', 'width=900,height=700'); } catch(e) {} if(!pw || pw.closed){ var div = document.createElement('div'); div.id = '__print_area__'; div.style.cssText = 'position:fixed;left:-9999px;top:0;width:720px;'; div.innerHTML = html; document.body.appendChild(div); var printWin = window.open('', '_blank'); if(printWin) { printWin.document.write(''+(o.orderNo||'采购单')+''+html+''); printWin.document.close(); printWin.focus(); printWin.print(); } else { toast('弹出窗口被拦截,请允许弹窗后重试,或按Ctrl+P手动打印','error'); } document.body.removeChild(div); return; } pw.document.open(); pw.document.write(html); pw.document.close(); setTimeout(function(){ try { pw.print(); } catch(e){} }, 500); o.printCount = (o.printCount||0) + 1; MockDB.update('purchases', o.id, {printCount: o.printCount}); renderLeftList(); } // ==================== 工具函数 ==================== function formatMoney(n){if(n==null||n=='')return'¥0.00';return'¥'+Number(n).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,',');} function formatMoneyValue(n){if(n==null||n=='')return'0';return Number(n).toFixed(2);} function toast(msg,type){const c=document.getElementById('toastContainer');const t=document.createElement('div');t.className='toast'+(type?' error':'');t.textContent=msg;c.appendChild(t);setTimeout(()=>{t.style.opacity='0';t.style.transition='opacity .3s';setTimeout(()=>{if(t.parentNode)t.parentNode.removeChild(t);},300)},2500);} function confirm(msg,onOk){const ov=document.createElement('div');ov.style.cssText='position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:99999;display:flex;align-items:center;justify-content:center;';ov.innerHTML=`

提示

${msg}

`;document.body.appendChild(ov);ov.querySelector('#_cok').onclick=function(){onOk();ov.remove();};ov.onclick=function(e){if(e.target===ov)ov.remove();};} function scanBarcode(){toast('扫码功能开发中...','info')} function importOrder(){toast('导入功能开发中...','info')} // 批量打印采购单(单独窗口,不含系统界面) function batchPrint(){ var cbs = document.querySelectorAll('#listBody input[type=checkbox]:checked'); if(!cbs.length){ toast('请先勾选要打印的单据','error'); return; } var orders = []; cbs.forEach(function(cb){ var tr = cb.closest('tr'); if(tr && tr.dataset.id) { var o = MockDB.getById('purchases', parseInt(tr.dataset.id)); if(o) orders.push(o); } }); if(!orders.length) { toast('未找到要打印的单据','error'); return; } var templates = getPrintTemplates(21); var tpl = templates.find(function(t){ return t.isMainTemplate; }) || templates[0]; if (!tpl) { toast('未找到打印模板','error'); return; } // 构建批量打印HTML(单独窗口) var html = '批量打印采购单'; html += ''; orders.forEach(function(o, idx){ if(idx > 0) html += '
'; html += buildPrintHTMLWithTemplate(o, tpl, 'purchase'); o.printCount = (o.printCount || 0) + 1; MockDB.update('purchases', o.id, {printCount: o.printCount}); }); html += ' // 支持从资金流水页面通过 query 跳转自动定位单据:purchase/list.html?highlight=XXX (function(){ var qs = new URLSearchParams(location.search); var highlight = qs.get('highlight'); if(!highlight) return; // 等待页面加载完成后自动搜索并定位 var tryAuto = function(n){ if(MockDB.purchases && MockDB.purchases.length && document.getElementById('fOrderNo')){ document.getElementById('fOrderNo').value = highlight; doSearch(); var found = MockDB.purchases.find(function(o){ return o.orderNo === highlight; }); if(found){ selectOrder(found.id); toast('已定位到「' + highlight + '」单据', 'success'); } return; } if(n > 0) setTimeout(function(){ tryAuto(n-1); }, 200); }; tryAuto(25); })();