' + existingHtml;
}
}
}
function closeFbForm() {
var overlay = document.getElementById('fbFormOverlay');
if (overlay) overlay.remove();
window._fbFiles = null;
}
var FB_FILE_ID = 0;
function handleFbFileSelect(e) {
var files = e.target.files;
for (var i = 0; i < files.length; i++) {
addFbFile(files[i]);
}
e.target.value = '';
}
function handleFbDrop(e) {
e.preventDefault();
document.getElementById('fbDropzone').classList.remove('dragover');
var files = e.dataTransfer.files;
for (var i = 0; i < files.length; i++) {
addFbFile(files[i]);
}
}
function addFbFile(file) {
if (!window._fbFiles) window._fbFiles = [];
window._fbFiles.push(file);
renderFbFileList();
}
function removeFbFile(idx) {
window._fbFiles.splice(idx, 1);
renderFbFileList();
}
function renderFbFileList() {
var html = '';
if (window._fbFiles) {
for (var i = 0; i < window._fbFiles.length; i++) {
var f = window._fbFiles[i];
var isImage = f.type && f.type.startsWith('image/');
var preview = isImage ? '' : '📄';
html += '
`;
if (!document.getElementById('prodColModal')) {
container.insertAdjacentHTML('afterend', prodColModalHtml);
}
// 初始化拖拽顺序
window._prodColDragOrder = PROD_COLUMNS.map(c => c.field);
window._prodColDefaultOrder = PROD_COLUMNS.map(c => c.field);
if (!window._prodColPendingVisible) {
window._prodColPendingVisible = {};
PROD_COLUMNS.forEach(c => { window._prodColPendingVisible[c.field] = c.defaultShow; });
}
renderProdColCheckboxes();
// 清除筛选框搜索记忆
['prodCode','prodName','prodSupplier','prodTypeFilter'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.value = '';
});
// 加载
prodActiveTab = '成品';
updateProdTypeFilterOptions('成品');
applyProdTabColumns('成品');
loadProducts(1);
}
function applyProdTabColumns(tab) {
// 包材基础信息表:跳过不需要的列
// 成品基础信息表:恢复所有列显示
var hideFields = ['brand','procurement_follower','procurement_planner','platform','operator','developer'];
hideFields.forEach(function(f) {
window.prodColVisible[f] = tab !== '非成品';
});
// 包材tab显示成本价、货期、物流时间、最低起订量、创建时间
['cost_price','lead_time','logistics_time','min_order_qty','created_at'].forEach(function(f) {
window.prodColVisible[f] = true;
});
// 字段设置checkbox状态同步
document.querySelectorAll('#prodColModal input[type="checkbox"]').forEach(function(cb) {
var field = cb.id && cb.id.replace('col-cb-', '');
if (field && window.prodColVisible[field] !== undefined) cb.checked = window.prodColVisible[field];
});
}
function switchProdTab(tab) {
prodActiveTab = tab;
document.querySelectorAll('#prodTabs .prod-tab').forEach(function(el) {
el.classList.toggle('active', el.dataset.type === tab);
});
document.getElementById('prodListTitle').textContent = tab === '成品' ? '成品列表' : '包材列表';
// 切页清空筛选框,互不影响
['prodCode','prodName','prodSupplier','prodTypeFilter'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.value = '';
});
// 更新货品类型下拉选项
updateProdTypeFilterOptions(tab);
applyProdTabColumns(tab);
// 重建表头使列显隐与当前tab一致
buildProdThead();
loadProducts(1);
}
function updateProdTypeFilterOptions(tab) {
var sel = document.getElementById('prodTypeFilter');
if (!sel) return;
var opts = '';
if (tab === '成品') {
// 成品基础表展示全部产品,下拉包含所有类型供筛选
opts = '';
} else {
opts = '';
}
sel.innerHTML = opts;
}
function buildProdThead() {
var thead = document.getElementById('prodThead');
if (!thead) return;
var theadHtml = '
序号
';
PROD_COLUMNS.forEach(function(c) {
var vis = window.prodColVisible[c.field] ? '' : ' style="display:none"';
var sortAttr = c.sortable ? ' data-sort="' + c.field + '" class="sortable"' : '';
theadHtml += '
' + c.label + '
';
});
theadHtml += '
操作
';
theadHtml += '
';
thead.innerHTML = theadHtml;
}
function loadProducts(page) {
prodCurrentPage = page;
var code = (document.getElementById('prodCode')?.value || '').trim();
var name = (document.getElementById('prodName')?.value || '').trim();
var supplier = (document.getElementById('prodSupplier')?.value || '').trim();
var typeFilter = document.getElementById('prodTypeFilter')?.value || '';
var url = '/api/products/list?page=' + page + '&size=' + PROD_PAGE_SIZE;
if (code) url += '&prod_code=' + encodeURIComponent(code);
if (name) url += '&prod_name=' + encodeURIComponent(name);
if (supplier) url += '&supplier=' + encodeURIComponent(supplier);
if (prodActiveTab === '成品') {
if (typeFilter) {
url += '&product_type=' + encodeURIComponent(typeFilter);
} else {
// 成品基础表:排除包材和其他类
url += '&product_type=' + encodeURIComponent('非包材');
}
} else {
if (typeFilter) {
url += '&product_type=' + encodeURIComponent(typeFilter);
} else {
// 非成品基础表:仅展示包材和其他
url += '&product_type=' + encodeURIComponent('非成品专用');
}
}
if (prodSortBy) url += '&sort_by=' + encodeURIComponent(prodSortBy) + '&sort_order=' + (prodSortAsc ? 'asc' : 'desc');
// 缓存当前页数据供快速编辑
if (!window._prodRowCache) window._prodRowCache = {};
fetch(url)
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.code !== 0) return;
var data = res.data;
var tbody = document.getElementById('prodTbody');
if (!data.rows || data.rows.length === 0) {
tbody.innerHTML = '
暂无数据
';
} else {
var startIdx = (page - 1) * PROD_PAGE_SIZE;
// 缓存行数据
data.rows.forEach(function(r) { if (r.product_code) window._prodRowCache[r.product_code] = r; });
tbody.innerHTML = data.rows.map(function(r, i) {
var html = '
' + (startIdx + i + 1) + '
';
PROD_COLUMNS.forEach(function(c) {
if (!window.prodColVisible[c.field]) return;
var raw = r[c.field];
var val = escHtml(raw || '-');
if (c.field === 'product_code') {
val = '' + val + '';
}
if (c.field === 'product_type') {
var typeColors = {'成品':'tag-blue','包材':'tag-orange','赠品':'tag-green','小样':'tag-purple','物料':'tag-orange'};
var cls = typeColors[raw] || 'tag-grey';
val = '' + val + '';
}
if (c.field === 'cost_price') {
var p = parseFloat(raw);
val = isNaN(p) ? '-' : '¥' + p.toFixed(2);
}
if (c.field === 'min_order_qty') {
var q = parseInt(raw);
val = isNaN(q) ? '-' : q.toLocaleString();
}
if (c.field === 'created_at') {
val = escHtml((raw || '').toString().slice(0, 10) || '-');
}
html += '
' + val + '
';
});
var pcode = r.product_code || '';
html += '
';
html += ' ';
html += ' ';
if (prodActiveTab === '成品') html += ' ';
html += '';
return '
`;
fetchWarehouses(1);
}
function fetchWarehouses(page) {
whCurrentPage = page || 1;
var url = '/api/warehouse/list?page=' + whCurrentPage + '&size=' + WH_PAGE_SIZE;
fetch(url)
.then(r => r.json())
.then(res => {
if (res.code !== 0) return;
var data = res.data || {};
var rows = data.rows || [];
var total = data.total || 0;
var offset = (whCurrentPage - 1) * WH_PAGE_SIZE;
var tbody = document.getElementById('whTbody');
tbody.innerHTML = rows.map(function(w, i) {
var code = escHtml(w.warehouse_code || '-');
var wp = w.warehouse_property || '';
var dp = w.disabled || '否';
// 仓库属性:选中项显示为标签
var wpLabels = wp ? wp.split(',').map(function(p) { return '' + escHtml(p.trim()) + ''; }).join('') : '-';
// 停用:选中状态
var dpLabel = dp === '是' ? '是' : '否';
return '
' + (offset + i + 1) + '
' + code + '
' + escHtml(w.warehouse_name || '-') + '
' + escHtml(w.category || '-') + '
' +
'
' +
'
' +
'' +
'' +
'' +
'
' +
'
' +
'
' +
'
' + escHtml(w.remark || '-') + '
' +
'
';
'
';
}).join('') || '
暂无数据
';
var pel = document.getElementById('whPagination');
if (pel) {
var totalPages = Math.ceil(total / WH_PAGE_SIZE);
if (totalPages <= 1) {
pel.innerHTML = '';
} else {
var ph = '
';
ph += '';
ph += '';
var start = Math.max(1, whCurrentPage - 2);
var end = Math.min(totalPages, whCurrentPage + 2);
for (var i = start; i <= end; i++) {
ph += '';
}
ph += '';
ph += '';
ph += '' + whCurrentPage + '/' + totalPages + '页 共' + total + '条';
ph += '
';
INV_EXPORT_FIELDS.forEach(function(f) {
var checked2 = curFields.indexOf(f.key) >= 0;
html += '';
});
html += '
';
html += '
';
html += '';
html += '
';
var old = document.getElementById('invExportModal');
if (old) old.remove();
var m = document.createElement('div');
m.id = 'invExportModal';
m.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:9999;display:flex;align-items:center;justify-content:center';
m.innerHTML = '
' + html + '
';
document.body.appendChild(m);
// 绑定确认导出
document.getElementById('invExportDoBtn').onclick = function() {
var selFields = [];
document.querySelectorAll('.inv-export-fc:checked').forEach(function(cb) { selFields.push(cb.value); });
if (selFields.length === 0) { alert('请至少选择一个字段'); return; }
window._invExportSelectedFields = selFields;
document.getElementById('invExportModal').remove();
// 执行导出
var date = document.getElementById('invDate')?.value || '';
var type = window._invProductType || '';
fetch('/api/inventory/export', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({product_codes: checked, format: fmt, date: date, product_type: type, fields: selFields})
})
.then(function(r) {
if (!r.ok) return r.json().then(function(d) { throw new Error(d.msg || '导出失败'); });
var filename = 'inventory_export.' + fmt;
return r.blob().then(function(blob) {
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
});
})
.catch(function(e) { alert('❌ ' + e.message); });
};
};
// ─── 供应商管理 ───
function loadSuppliersPage(container) {
container.innerHTML = `
供应商管理
供应商信息管理
供应商列表
序号
供应商编码
供应商名称
做货类型
简称
合同简称
供应商分组
审核人
审核日期
数据状态
禁用状态
操作
`;
// 清除筛选框搜索记忆
['supCode','supName'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.value = '';
});
loadSuppliers(1);
}
let supCurrentPage = 1;
let SUP_PAGE_SIZE = 50;
function loadSuppliers(page) {
supCurrentPage = page;
const code = (document.getElementById('supCode')?.value || '').trim();
const name = (document.getElementById('supName')?.value || '').trim();
let url = '/api/suppliers/list?page=' + page + '&size=' + SUP_PAGE_SIZE;
if (code) url += '&code=' + encodeURIComponent(code);
if (name) url += '&name=' + encodeURIComponent(name);
fetch(url)
.then(r => r.json())
.then(res => {
if (res.code !== 0) return;
const data = res.data;
const tbody = document.getElementById('supTbody');
if (!data.rows || data.rows.length === 0) {
tbody.innerHTML = '
暂无数据
';
} else {
const startIdx = (page - 1) * SUP_PAGE_SIZE;
tbody.innerHTML = data.rows.map((r, i) => {
var supCode = r.supplier_code || '';
var supName = r.supplier_name || '';
var html = '
';
document.body.appendChild(overlay);
}
window._supImportFile = null;
function handleSupImportSelect(e) {
var file = e.target.files[0];
setSupImportFile(file);
}
function handleSupImportDrop(e) {
e.preventDefault();
e.stopPropagation();
var dz = document.getElementById('supImportDropzone');
if (dz) { dz.style.borderColor = '#d9d9d9'; dz.style.background = ''; }
var file = e.dataTransfer.files[0];
if (file && file.name.match(/\.xlsx?$/i)) {
setSupImportFile(file);
// 同步到隐藏的file input
var fi = document.getElementById('supImportFile');
if (fi) {
var dt = new DataTransfer();
dt.items.add(file);
fi.files = dt.files;
}
} else {
alert('请上传 .xlsx 格式的文件');
}
}
function handleSupImportDragOver(e) {
e.preventDefault();
e.stopPropagation();
var dz = document.getElementById('supImportDropzone');
if (dz) { dz.style.borderColor = '#1890ff'; dz.style.background = '#f0f8ff'; }
}
function handleSupImportDragLeave(e) {
e.preventDefault();
e.stopPropagation();
var dz = document.getElementById('supImportDropzone');
if (dz) { dz.style.borderColor = '#d9d9d9'; dz.style.background = ''; }
}
function setSupImportFile(file) {
window._supImportFile = file;
var btn = document.getElementById('supImportBtn');
var preview = document.getElementById('supImportPreview');
var dz = document.getElementById('supImportDropzone');
if (file) {
btn.disabled = false;
preview.textContent = '✅ 已选择:' + file.name;
if (dz) {
dz.style.borderColor = '#52c41a';
dz.style.background = '#f6ffed';
}
} else {
btn.disabled = true;
preview.textContent = '';
if (dz) {
dz.style.borderColor = '#d9d9d9';
dz.style.background = '';
}
}
}
function closeImportOverlay() {
var overlays = document.querySelectorAll('[id^="supImportOverlay-"]');
overlays.forEach(function(o) { o.remove(); });
}
function doImportSuppliers() {
if (!window._supImportFile) { alert('请先选择文件'); return; }
var btn = document.getElementById('supImportBtn');
btn.disabled = true;
btn.textContent = '⏳ 导入中...';
var formData = new FormData();
formData.append('file', window._supImportFile);
fetch('/api/suppliers/import', {
method: 'POST',
body: formData
})
.then(function(r) { return r.json(); })
.then(function(res) {
// 先移除导入弹窗,再显示结果
closeImportOverlay();
if (res.code !== 0) {
alert('导入失败:' + (res.msg || '未知错误'));
} else {
var d = res.data;
var msg = '✅ 导入完成\n\n' +
'成功更新:' + d.updated + ' 条\n' +
(d.not_found > 0 ? '未匹配:' + d.not_found + ' 条\n' : '') +
(d.skipped > 0 ? '跳过无数据行:' + d.skipped + ' 条\n' : '');
if (d.not_found_list && d.not_found_list.length > 0) {
msg += '\n未匹配的供应商名称(前20条):\n' + d.not_found_list.join('、');
}
alert(msg);
loadSuppliers(supCurrentPage);
}
})
.catch(function(err) {
closeImportOverlay();
alert('导入异常:' + err.message);
});
}
// ─── 人员信息管理 ───
function loadPersonnelPage(container) {
// ─── 采购跟单分组数据(从全局映射读取) ───
var followGroups = window._personnelGroups.filter(function(g) { return g.name !== '计划小组'; });
// ─── 采购计划分组数据 ───
var planGroups = window._personnelGroups.filter(function(g) { return g.name === '计划小组'; });
var cardsHtml = [
'
',
'
',
'👥',
'
采购跟单
',
'' + (function(){
var total = 0; followGroups.forEach(function(g){ total += g.members.length; }); return '共 ' + total + ' 人';
})() + '',
'
',
'
',
renderPersonnelCards(followGroups),
'
',
'
',
'
',
'
',
'📋',
'
采购计划
',
'' + (function(){
var total = 0; planGroups.forEach(function(g){ total += g.members.length; }); return '共 ' + total + ' 人';
})() + '',
'
',
'
',
renderPersonnelCards(planGroups),
'
',
'
'
].join('');
container.innerHTML = [
'
人员信息管理
',
'
',
'',
cardsHtml,
'
'
].join('');
}
function renderPersonnelCards(groups) {
var html = '';
groups.forEach(function(g) {
html += '
';
html += '
';
html += '' + (g.icon || '👤') + '';
html += '' + escHtml(g.name) + '';
html += '' + g.members.length + '人';
html += '
';
html += '
';
g.members.forEach(function(m) {
var isLeader = m === g.leader && g.leader !== '';
var cls = 'personnel-tag' + (isLeader ? ' leader' : '');
html += '';
if (isLeader) html += '⭐ ';
html += escHtml(m);
if (isLeader) html += '组长';
html += '';
});
html += '
';
});
tbody.innerHTML = '';
// 把卡片放到tbody的容器里(实际放在其父容器中)
var wrap = document.getElementById('roleOpModalTable');
if (wrap) {
var parent = wrap.parentNode;
// 隐藏table,用卡片替代
wrap.style.display = 'none';
var cardWrap = document.getElementById('roleOpModalCardWrap');
if (!cardWrap) {
cardWrap = document.createElement('div');
cardWrap.id = 'roleOpModalCardWrap';
parent.appendChild(cardWrap);
}
cardWrap.innerHTML = bodyHtml;
}
}
function saveRoleOpPerms(modalId) {
var items = [];
document.querySelectorAll('.role-op-cb').forEach(function(cb) {
items.push({
role: cb.dataset.role,
page: cb.dataset.page,
operation: cb.dataset.op,
permission: cb.checked ? 1 : 0
});
});
fetch('/api/permissions/role-operations/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({items: items})
})
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.code === 0) {
// 更新缓存,下次打开弹窗显示最新状态
_roleOpPerms.length = 0;
items.forEach(function(item) {
_roleOpPerms.push({
role: item.role,
page: item.page,
operation: item.operation,
permission: item.permission
});
});
alert('✅ 角色操作权限已保存');
if (modalId) document.getElementById(modalId)?.remove();
} else {
alert(res.msg || '保存失败');
}
})
.catch(function(e) { console.error(e); alert('保存失败: ' + (e.message || '网络错误')); });
}
function renderPermUserGrid() {
var el = document.getElementById('permUserGrid');
if (!el) return;
var kw = (document.getElementById('permFilterName')?.value || '').trim().toLowerCase();
var posFilter = document.getElementById('permFilterPos')?.value || '';
var levelFilter = document.getElementById('permFilterLevel')?.value || '';
var filtered = _permUsers.filter(function(u) {
if (kw) {
if (!(u.real_name || '').toLowerCase().includes(kw) && !(u.username || '').toLowerCase().includes(kw)) return false;
}
if (posFilter && u.position !== posFilter) return false;
if (levelFilter && u.user_level !== levelFilter) return false;
return true;
});
var countEl = document.getElementById('permFilterCount');
if (countEl) countEl.textContent = filtered.length + ' / ' + _permUsers.length + ' 人';
var roleNames = {
'super_admin': '超级管理员', 'admin': '管理员',
'procurement_planner': '采购计划', 'procurement_follower': '采购跟单',
'product_developer': '产品开发', 'operator': '运营专员',
'viewer': '查看者', '查看者': '查看者'
};
var roleColors = {
'super_admin': '#f5222d', 'admin': '#1890ff',
'procurement_planner': '#722ed1', 'procurement_follower': '#13c2c2',
'product_developer': '#eb2f96', 'operator': '#fa8c16',
'viewer': '#8c8c8c', '查看者': '#8c8c8c'
};
var roleBadgeColors = {
'super_admin': '#fff1f0', 'admin': '#e6f7ff',
'procurement_planner': '#f9f0ff', 'procurement_follower': '#e6fffb',
'product_developer': '#fff0f6', 'operator': '#fff7e6',
'viewer': '#f5f5f5', '查看者': '#f5f5f5'
};
var roleTextColors = {
'super_admin': '#cf1322', 'admin': '#096dd9',
'procurement_planner': '#531dab', 'procurement_follower': '#08979c',
'product_developer': '#c41d7f', 'operator': '#d46b08',
'viewer': '#595959', '查看者': '#595959'
};
// 按角色分组:超级管理员 → 管理员 → 查看者 → 其他
var groupSpecs = [
{role: 'super_admin', icon: '⭐', label: '超级管理员'},
{role: 'admin', icon: '🔐', label: '管理员'},
{role: 'viewer', icon: '👁️', label: '查看者'}
];
var roleGroups = {};
var otherUsers = [];
filtered.forEach(function(u) {
var found = false;
groupSpecs.forEach(function(gs) {
if (u.role === gs.role) {
if (!roleGroups[gs.role]) roleGroups[gs.role] = [];
roleGroups[gs.role].push(u);
found = true;
}
});
if (!found) otherUsers.push(u);
});
function renderUserSection(users, sectionInfo) {
var h = '';
if (!users || users.length === 0) return '';
var icon = sectionInfo.icon || '👤';
var label = sectionInfo.label || '成员';
var roleId = sectionInfo.role || '';
h += '
';
h += '' + icon + ' ' + label + '';
h += '
';
h += '
';
users.forEach(function(u) {
var initial = u.real_name ? u.real_name.charAt(0) : '?';
var roleName = roleNames[u.role] || u.role;
var badgeBg = roleBadgeColors[u.role] || '#f5f5f5';
var badgeText = roleTextColors[u.role] || '#595959';
var avatarBg = roleColors[u.role] || '#8c8c8c';
var permCount = Object.keys(u.permissions || {}).length;
var positionNames = {
'bp': 'BP', 'procurement_planner': '采购计划', 'procurement_follower': '采购跟单',
'procurement_trainee': '采购管培生', '采购开发': '采购开发'
};
var levelNames = {'employee': '员工', 'leader': '主管', 'director': '总监'};
var posName = positionNames[u.position] || u.position || '';
var levelName = levelNames[u.user_level] || u.user_level || '';
h += '
';
// 头像
h += '
' + escHtml(initial) + '
';
// 信息
h += '
';
h += '
';
h += escHtml(u.real_name || u.username);
h += '' + escHtml(roleName) + '';
if (levelName) {
h += '' + levelName + '';
}
h += '
';
var infoParts = ['账号: ' + escHtml(u.username)];
if (posName) infoParts.push('岗位: ' + escHtml(posName));
if (levelName) infoParts.push('级别: ' + levelName);
if (permCount > 0) infoParts.push('已配置 ' + permCount + ' 个页面');
h += '
' + infoParts.join(' · ') + '
';
h += '
';
h += '›';
h += '
';
});
h += '
';
return h;
}
var html = '';
var first = true;
groupSpecs.forEach(function(gs) {
var users = roleGroups[gs.role] || [];
if (users.length > 0) {
if (!first) html += '';
html += renderUserSection(users, gs);
first = false;
}
});
if (otherUsers.length > 0) {
if (!first) html += '';
html += renderUserSection(otherUsers, {icon:'👥', label:'其他成员', role:''});
}
html += '';
if (filtered.length === 0) {
html = '
无匹配用户
';
}
el.innerHTML = html;
}
// 打开用户权限面板 - 使用新API展示用户有效权限
window.openUserPermPanel = function(userId) {
// 关闭已有弹窗,防止重复堆积
var existing = document.querySelectorAll('.modal-overlay');
existing.forEach(function(el) { if (el.id !== 'permModal' && el.id !== 'userModal') el.remove(); });
// 从缓存中获取用户信息,避免额外API调用
var user = (_permUsers || []).find(function(u) { return u.id == userId; });
if (!user) { alert('未找到该用户'); return; }
fetch('/api/permissions/user/field-perms?user_id=' + userId + '&_t=' + Date.now())
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.code !== 0) { alert('加载失败: ' + (res.msg || '')); return; }
var data = res.data || {};
var rolePerms = data.role_perms || {};
var html = '
';
values.forEach(function(v) {
var checked = (sel[v] !== false) ? 'checked' : '';
html += '';
});
html += '
';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
document.body.appendChild(menu);
pendingFilterMenu = menu;
}
function _populatePendingFilterList(field, values) {
var listEl = document.getElementById('pendingFilterList');
if (!listEl) return;
var sel = pendingOrderFilter[field] || {};
var html = '
';
values.forEach(function(v) {
var checked = (sel[v] !== false) ? 'checked' : '';
html += '';
});
listEl.innerHTML = html;
}
function _buildPendingNumericFilter(field, label, rect) {
var menu = document.createElement('div');
menu.id = 'pendingFilterMenu';
var current = pendingOrderFilter[field] || {};
var cond = current.cond || '';
var val = current.val || '';
var html = '
' + escHtml(label) + '
';
html += '
';
html += '
';
html += '
';
html += '';
html += '
';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
menu.style.cssText = 'position:fixed;top:' + (rect.bottom + 4) + 'px;left:' + Math.max(rect.left, 10) + 'px;z-index:10001;background:#fff;border:1px solid #d9d9d9;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);padding:10px 12px;min-width:200px;font-size:13px';
document.body.appendChild(menu);
pendingFilterMenu = menu;
}
function pendingFilterSearch(q) {
var items = document.querySelectorAll('#pendingFilterList label');
q = (q || '').toLowerCase();
items.forEach(function(item) {
item.style.display = item.textContent.toLowerCase().indexOf(q) >= 0 ? '' : 'none';
});
}
function pendingFilterToggleAll(checked) {
document.querySelectorAll('#pendingFilterList .pending-filter-cb').forEach(function(cb) { cb.checked = checked; });
}
function pendingFilterCbChange() {
var all = document.querySelectorAll('#pendingFilterList .pending-filter-cb');
var checked = document.querySelectorAll('#pendingFilterList .pending-filter-cb:checked');
var sa = document.getElementById('pendingFilterSelectAll');
if (sa) sa.checked = checked.length === all.length;
}
function applyPendingFilter() {
var field = pendingFilterField;
var searchInput = document.getElementById('pendingFilterSearch');
var searchText = searchInput ? searchInput.value.trim() : '';
if (searchText) {
pendingOrderFilter[field] = {type: 'search', value: searchText};
} else {
var cbs = document.querySelectorAll('#pendingFilterList .pending-filter-cb:checked');
var checked = [];
cbs.forEach(function(cb) { checked.push(cb.value); });
var allCbs = document.querySelectorAll('#pendingFilterList .pending-filter-cb');
if (checked.length === 0 || checked.length === allCbs.length) {
delete pendingOrderFilter[field];
} else {
pendingOrderFilter[field] = {type: 'list', values: checked};
}
}
if (pendingFilterMenu) { pendingFilterMenu.remove(); pendingFilterMenu = null; }
_syncPendingFilterIcons();
_renderFilteredPending();
}
function applyPendingNumFilter() {
var field = pendingFilterField;
var cond = document.getElementById('pendingNumCond')?.value || '';
var val = document.getElementById('pendingNumVal')?.value || '';
if (!cond || !val) { alert('请选择条件并输入数值'); return; }
pendingOrderFilter[field] = {type: 'num', cond: cond, val: val};
if (pendingFilterMenu) { pendingFilterMenu.remove(); pendingFilterMenu = null; }
_syncPendingFilterIcons();
_renderFilteredPending();
}
function clearPendingFilter() {
delete pendingOrderFilter[pendingFilterField];
if (pendingFilterMenu) { pendingFilterMenu.remove(); pendingFilterMenu = null; }
_syncPendingFilterIcons();
_renderFilteredPending();
}
function _applyPendingFilters(items) {
var keys = Object.keys(pendingOrderFilter);
if (keys.length === 0) return items;
return items.filter(function(item) {
return keys.every(function(f) {
var fp = pendingOrderFilter[f];
if (!fp) return true;
var v = String(item[_pendingFieldMap[f] || f] || '');
if (fp.type === 'search') {
return v.toLowerCase().indexOf((fp.value || '').toLowerCase()) >= 0;
}
if (fp.type === 'list') {
return fp.values.indexOf(v) >= 0;
}
if (fp.type === 'num') {
var n = parseFloat(v);
var tv = parseFloat(fp.val);
if (isNaN(n) || isNaN(tv)) return false;
switch (fp.cond) {
case 'eq': return n === tv;
case 'neq': return n !== tv;
case 'gt': return n > tv;
case 'gte': return n >= tv;
case 'lt': return n < tv;
case 'lte': return n <= tv;
}
}
return true;
});
});
}
function _renderFilteredPending() {
var filtered = _applyPendingFilters(_pendingItems);
var countEl = document.getElementById('pendingCount');
if (countEl) countEl.textContent = '共 ' + filtered.length + ' / ' + _pendingItems.length + ' 条';
var tbody = document.getElementById('pendingTbody');
if (!tbody) return;
if (filtered.length === 0) {
tbody.innerHTML = '
暂无匹配数据
';
return;
}
tbody.innerHTML = _pendingRowsHtml(filtered);
pendingUpdateCount();
}
function _syncPendingFilterIcons() {
var icons = document.querySelectorAll('#pendingTable .order-filt-icon');
icons.forEach(function(icon) {
var field = icon.parentNode.textContent.trim().replace(/▾/,'').trim();
var hasFilter = pendingOrderFilter[field];
icon.style.color = hasFilter ? '#1890ff' : 'var(--text-light)';
icon.style.fontWeight = hasFilter ? 'bold' : 'normal';
});
}
function pendingToggleAll(checked) {
document.querySelectorAll('.pending-row-cb').forEach(function(cb) { cb.checked = checked; });
pendingUpdateCount();
}
function pendingUpdateCount() {
var total = document.querySelectorAll('.pending-row-cb').length;
var checked = document.querySelectorAll('.pending-row-cb:checked').length;
var el = document.getElementById('pendingSelectedCount');
if (el) el.textContent = checked > 0 ? '已选 ' + checked + '/' + total : '';
document.getElementById('pendingSelectAllCb').checked = total > 0 && checked === total;
}
function showPendingConfirmModal(item, onConfirm) {
// 先显示 loading 弹窗,同时加载BOM数据
var modal = document.createElement('div');
modal.id = 'pendingConfirmModal';
modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:10000;display:flex;align-items:center;justify-content:center;overflow-y:auto';
modal.innerHTML = '
' +
'
加载中...
';
document.body.appendChild(modal);
// 异步加载BOM详情(获取包材供应商和单价)
fetch('/api/product/detail_with_bom?code=' + encodeURIComponent(item.product_code))
.then(function(r) { return r.json(); })
.then(function(res) {
var bomMaterials = (res.code === 0 && res.data && res.data.bom_materials) ? res.data.bom_materials : [];
renderPendingConfirmModal(modal, item, bomMaterials, onConfirm);
})
.catch(function() {
renderPendingConfirmModal(modal, item, [], onConfirm);
});
}
function renderPendingConfirmModal(modal, item, bomMaterials, onConfirm) {
var productCode = item.product_code || '';
var supplierName = item.supplier_name || '-';
var createdBy = item.created_by || '-';
var suggestedQty = item.suggested_qty || 0;
var actualQty = item.formal_order_qty || 0;
var auditPrice = item.audit_price || 0;
var hasBom = bomMaterials.length > 0;
// 成品信息字段:2列布局
var prodPairs = [
[['货品编号', productCode], ['货品名称', item.product_name || '-']],
[['供应商名称', supplierName], ['下单人员', createdBy]],
[['建议下单数量', suggestedQty], ['实际下单数量', actualQty]]
];
// 包材信息字段:2列布局
// 每行展示一个物料的所有4个字段:包材编号 | 包材名称 | 供应商名称 | 单价
var formalQtyConfirm = item.formal_order_qty || 0;
// 获取已保存的包材实际下单数量
var savedActualMap = {};
if (item.bom_items) {
try {
var savedBom = typeof item.bom_items === 'string' ? JSON.parse(item.bom_items) : item.bom_items;
savedBom.forEach(function(s) {
if (s.material_code) savedActualMap[s.material_code] = s.actual_quantity || 0;
});
} catch(e) {}
}
var bomRows = [];
if (hasBom) {
// 过滤掉内料
bomMaterials = bomMaterials.filter(function(b) { return !(b.material_name && b.material_name.indexOf('内料') !== -1); });
bomMaterials.forEach(function(b) {
var matSupplier = b.material_supplier || '-';
var matPrice = b.material_cost !== undefined ? '¥' + parseFloat(b.material_cost || 0).toFixed(4) : '-';
var bomQty = b.bom_qty || 0;
var suggestedQty = Math.ceil(formalQtyConfirm * bomQty);
var actualQty = savedActualMap[b.material_code] || 0;
bomRows.push({
code: b.material_code || '',
name: b.material_name || '',
suggested_qty: suggestedQty,
actual_qty: actualQty,
supplier: matSupplier,
price: matPrice
});
});
}
var cellBorder = 'border:1px solid #e8e8e8';
var thBaseLight = 'padding:7px 10px;' + cellBorder + ';text-align:center;background:#e8e8e8;color:#555;font-weight:500;white-space:nowrap';
var thBaseDark = 'padding:7px 10px;' + cellBorder + ';text-align:center;background:#555;color:#fff;font-weight:500;white-space:nowrap';
var tdBase = 'padding:7px 10px;' + cellBorder + ';text-align:center';
var html = '
';
html += '
请核对商品信息
';
// ─── 成品信息 ───
html += '
';
html += '
🏭 成品信息
';
html += '
';
html += '
';
prodPairs.forEach(function(pair) {
var left = pair[0], right = pair[1];
html += '
';
html += '
' + left[0] + '
';
html += '
' + escHtml(String(left[1] || '-')) + '
';
html += '
' + right[0] + '
';
html += '
' + escHtml(String(right[1] || '-')) + '
';
html += '
';
});
html += '
';
// ─── 包材信息 ───
if (hasBom && bomRows.length > 0) {
html += '
';
html += '
📦 包材信息
';
html += '
';
html += '
';
html += '
';
html += '
包材编号
';
html += '
包材名称
';
html += '
建议下单数量
';
html += '
实际下单数量
';
html += '
供应商名称
';
html += '
单价
';
html += '
';
bomRows.forEach(function(b, i) {
var bg = i % 2 === 0 ? '#fff' : '#f5f5f5';
html += '
';
html += '
' + escHtml(b.code) + '
';
html += '
' + escHtml(b.name) + '
';
html += '
' + (b.suggested_qty || 0).toLocaleString() + '
';
html += '
' + (b.actual_qty || 0).toLocaleString() + '
';
html += '
' + escHtml(b.supplier) + '
';
html += '
' + escHtml(b.price) + '
';
html += '
';
});
html += '
';
} else {
html += '
';
html += '
📦 包材信息
';
html += '
暂无包材数据
';
}
html += '
';
html += '
';
html += '';
html += '
';
modal.innerHTML = '
' + html + '
';
document.getElementById('pendingConfirmCancelBtn').onclick = function() { modal.remove(); };
document.getElementById('pendingConfirmOkBtn').onclick = function() {
modal.remove();
if (onConfirm) onConfirm();
};
}
function showConfirmRedirect(hasProduct, hasPackaging) {
var links = [];
if (hasProduct) {
links.push({ label: '📦 国内/海外订单管理', page: '/orders/domestic' });
}
if (hasPackaging) {
links.push({ label: '📦 包材订单管理', page: '/orders/packaging' });
}
// 下单备份始终存在(已标记为ordered的记录)
links.push({ label: '📋 下单备份管理', page: '/purchase' });
if (links.length === 0) return;
var overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:30000;display:flex;align-items:center;justify-content:center;';
var html = '
';
values.forEach(function(v) {
var checked = (sel[v] !== false) ? 'checked' : '';
html += '';
});
html += '
';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
document.body.appendChild(menu);
backupFilterMenu = menu;
}
function _buildBackupNumericFilter(field, label, rect) {
var menu = document.createElement('div');
menu.id = 'backupFilterMenu';
var current = backupOrderFilter[field] || {};
var cond = current.cond || '';
var val = current.val || '';
var html = '
' + escHtml(label) + '
';
html += '';
html += '';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
menu.style.cssText = 'position:fixed;top:' + (rect.bottom + 4) + 'px;left:' + Math.max(rect.left, 10) + 'px;z-index:10001;background:#fff;border:1px solid #d9d9d9;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);padding:10px 12px;min-width:200px;font-size:13px';
document.body.appendChild(menu);
backupFilterMenu = menu;
}
function _populateBackupFilterList(field, values) {
var listEl = document.getElementById('backupFilterList');
if (!listEl) return;
var sel = backupOrderFilter[field] || {};
var html = '
`;
// 初始化字段设置checkbox
var pcGrid = document.getElementById('packagingColGrid');
if (pcGrid) {
window._packagingDragOrder = PACKAGING_COLUMNS.map(function(c) { return c.field; });
renderOrderColCheckboxes('packaging');
}
packagingLoadFilters();
packagingLoadOrders(1);
loadDefaultOrderScheme('packaging');
// 后台预拉取所有列的筛选值
prefetchOrderColumnValues('packaging');
}
function packagingLoadFilters() {
fetch('/api/packaging/orders/filters')
.then(function(r){return r.json()})
.then(function(res){
if (res.code !== 0) return;
var ss = document.getElementById('packagingStatusFilter');
var so = document.getElementById('packagingOrdererFilter');
(res.data.statuses || []).forEach(function(s) {
var opt = document.createElement('option');
opt.value = s; opt.textContent = s;
ss.appendChild(opt);
});
(res.data.orderers || []).forEach(function(s) {
var opt = document.createElement('option');
opt.value = s; opt.textContent = s;
so.appendChild(opt);
});
});
}
function packagingDoSearch() {
packagingApplyFilters();
}
function packagingApplyFilters() {
packagingSearch = document.getElementById('packagingSearchInput').value.trim();
packagingFilterSupplier = document.getElementById('packagingSupplierFilter').value.trim();
packagingFilterStatus = document.getElementById('packagingStatusFilter').value;
packagingFilterOrderer = document.getElementById('packagingOrdererFilter').value;
packagingLoadOrders(1);
}
function packagingResetFilters() {
document.getElementById('packagingSearchInput').value = '';
document.getElementById('packagingSupplierFilter').value = '';
document.getElementById('packagingStatusFilter').value = '';
document.getElementById('packagingOrdererFilter').value = '';
packagingSearch = ''; packagingFilterSupplier = ''; packagingFilterStatus = ''; packagingFilterOrderer = '';
packagingOrderFilter = {};
packagingOrderFilterActive = {};
_syncOrderFilterIcons('packaging');
packagingLoadOrders(1);
}
function packagingLoadOrders(page) { packagingOrderPage = page;
var params = '?page=' + page + '&size=' + PACKAGING_PAGE_SIZE;
if (packagingSearch) params += '&search=' + encodeURIComponent(packagingSearch);
if (packagingFilterStatus) params += '&status=' + encodeURIComponent(packagingFilterStatus);
if (packagingFilterSupplier) params += '&supplier=' + encodeURIComponent(packagingFilterSupplier);
if (packagingFilterOrderer) params += '&orderer=' + encodeURIComponent(packagingFilterOrderer);
var packagingFilterStr = '';
if (Object.keys(packagingOrderFilter).length > 0) {
packagingFilterStr = encodeURIComponent(JSON.stringify(packagingOrderFilter));
params += '&order_filter=' + packagingFilterStr;
}
fetch('/api/packaging/orders/list' + params)
.then(function(r){return r.json()})
.then(function(res){
if (res.code !== 0) return;
var data = res.data;
var tbody = document.getElementById('packagingOrdersTbody');
if (document.getElementById('packagingTotalInfo'))
document.getElementById('packagingTotalInfo').textContent = '共 ' + data.total + ' 条';
if (!data.rows || data.rows.length === 0) {
tbody.innerHTML = '
暂无数据
';
} else {
var startIdx = (page - 1) * PACKAGING_PAGE_SIZE;
var html = '';
data.rows.forEach(function(r, i) {
var rowNum = startIdx + i + 1;
html += '
';
html += '
';
html += '
' + rowNum + '
';
// 按列顺序和可见性渲染单元格
var order = window._packagingDragOrder || PACKAGING_COLUMNS.map(function(c) { return c.field; });
order.forEach(function(field) {
if (!window.packagingColVisible[field]) return;
var cellHtml = '';
if (field === '日期') {
cellHtml = '
' + escHtml(r['日期'] || '') + '
';
} else if (field === '编码') {
cellHtml = '
' + escHtml(r['编码'] || '') + '
';
} else if (field === '名称') {
cellHtml = '
' + escHtml(r['名称'] || '') + '
';
} else if (field === '单价') {
cellHtml = '
';
} else if (field === '数量') {
cellHtml = '
' + (parseInt(r['数量']) || 0).toLocaleString() + '
';
} else if (field === '金额') {
var priceVal = parseFloat(r['单价']) || 0;
var qtyVal = parseInt(r['数量']) || 0;
var amtVal = priceVal * qtyVal;
cellHtml = '
';
values.forEach(function(v) {
var checked = (sel[v] !== false) ? 'checked' : '';
html += '';
});
html += '
';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
document.body.appendChild(menu);
finishedFilterMenu = menu;
}
function _buildFinishedNumericFilter(field, label, rect) {
var menu = document.createElement('div');
menu.id = 'finishedFilterMenu';
var current = finishedOrderFilter[field] || {};
var cond = current.cond || '';
var val = current.val || '';
var html = '
' + escHtml(label) + '
';
html += '';
html += '';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
menu.style.cssText = 'position:fixed;top:' + (rect.bottom + 4) + 'px;left:' + Math.max(rect.left, 10) + 'px;z-index:10001;background:#fff;border:1px solid #d9d9d9;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);padding:10px 12px;min-width:200px;font-size:13px';
document.body.appendChild(menu);
finishedFilterMenu = menu;
}
function _populateFinishedFilterList(field, values) {
var listEl = document.getElementById('finishedFilterList');
if (!listEl) return;
var sel = finishedOrderFilter[field] || {};
var html = '
';
values.forEach(function(v) {
var key = escHtml(v).replace(/'/g, "\\'");
var checked = (sel[v] !== false) ? 'checked' : '';
html += '';
});
html += '
';
html += '
';
html += '
';
menu.innerHTML = html;
document.body.appendChild(menu);
_setOrderFilterMenu(type, menu);
}
// 构建订单筛选菜单外壳(用于异步加载的字段)
function _buildOrderFilterShell(type, field, label, rect) {
var existing = _orderFilterMenu(type);
if (existing) { existing.remove(); _setOrderFilterMenu(type, null); }
_setOrderFilterField(type, field);
var menu = document.createElement('div');
menu.id = 'orderFilterMenu_' + type;
menu.style.cssText = 'position:fixed;top:' + (rect.bottom + 4) + 'px;left:' + Math.max(rect.left, 10) + 'px;z-index:10001;background:#fff;border:1px solid #d9d9d9;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.15);padding:10px 12px;min-width:220px;max-width:320px;max-height:400px;font-size:13px;display:flex;flex-direction:column';
var html = '';
html += '
' + escHtml(label) + '
';
html += '';
html += '
加载中...
';
html += '
';
html += '';
html += '
';
menu.innerHTML = html;
document.body.appendChild(menu);
_setOrderFilterMenu(type, menu);
}
// 填充订单筛选列表(从API返回的数据)
function _populateOrderFilterList(type, field, values) {
var listEl = document.getElementById('orderFilterList_' + type);
if (!listEl) return;
var filterState = _orderFilterState(type);
if (!filterState[field]) filterState[field] = {};
var sel = filterState[field];
var html = '';
html += '
';
}
// \u751f\u6210\u9500\u91cf\u4fe1\u606f\u5b57\u6bb5HTML\uff084\u5217\u7b49\u8ddd\uff09
var salesHtml = '';
for (var i = 0; i < salesFields.length; i++) {
var field = salesFields[i];
var label = POPUP_FIELD_LABELS[field] || field;
var val = p[field] !== undefined ? p[field] : '-';
var valHtml = formatPopupFieldValue(field, val, p);
salesHtml += '
';
thead.innerHTML = thHtml;
}
var code = (document.getElementById('rbCode')?.value || '').trim();
var name = (document.getElementById('rbName')?.value || '').trim();
var url = '/api/products/recycle_bin?page=' + page + '&size=' + _recyclePageSize;
if (code) url += '&prod_code=' + encodeURIComponent(code);
if (name) url += '&prod_name=' + encodeURIComponent(name);
fetch(url)
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.code !== 0) return;
var data = res.data;
var tbody = document.getElementById('rbTbody');
if (!data.rows || data.rows.length === 0) {
tbody.innerHTML = '
'
+ '
🗑️
'
+ '
回收站为空
';
} else {
var theadEl = document.getElementById('rbThead');
var colCount = (theadEl ? theadEl.querySelectorAll('th').length : PROD_COLUMNS.length + 2);
var typeColors = {'成品':'tag-blue','包材':'tag-orange','赠品':'tag-green','小样':'tag-purple','物料':'tag-orange'};
var html = '';
data.rows.forEach(function(r, i) {
html += '
';
html += '
';
html += '
' + ((page - 1) * _recyclePageSize + i + 1) + '
';
PROD_COLUMNS.forEach(function(c) {
if (c.field === 'cost_price') return;
var raw = r[c.field];
var val = raw != null ? String(raw) : '-';
if (c.field === 'created_at') val = raw ? String(raw).slice(0, 10) : '-';
if (c.field === 'import_date') val = raw ? String(raw).slice(0, 10) : '-';
if (c.field === 'product_type') {
var cls = typeColors[raw] || 'tag-grey';
val = '' + escHtml(raw || '-') + '';
} else {
val = escHtml(val);
}
html += '
';
for (var i = 0; i < fields.length; i += 2) {
var left = fields[i];
var right = fields[i + 1];
var bg = (i % 4 === 0) ? '#f9f9f9' : '#fff';
var lv = left.val != null && left.val !== '' ? escHtml(String(left.val)) : '-';
html += '
';
html += '
' + left.label + '
';
html += '
' + lv + '
';
if (right) {
var rv = right.val != null && right.val !== '' ? escHtml(String(right.val)) : '-';
html += '
';
return;
}
var p = res.data.product;
// 获取当前可见列字段名(来自计划预测页面)
var visibleFields = [];
if (window.planColVisible) {
PLAN_COLUMNS.forEach(function(c) {
if (window.planColVisible[c.field]) {
visibleFields.push(c.field);
}
});
} else {
PLAN_COLUMNS.forEach(function(c) {
if (c.defaultShow) {
visibleFields.push(c.field);
}
});
}
// 过滤出可编辑的字段(按 visibleFields 顺序排列)
var editFields = [];
visibleFields.forEach(function(colField) {
var cfg = PLAN_EDITABLE_FIELDS[colField];
if (cfg) {
// 从产品数据取值
var val = null;
if (colField === '成本价' || colField === 'cost_price') val = p.cost_price;
else if (colField === 'product_type') val = p.product_type;
else if (colField === '产品种类') val = p.产品种类;
else if (colField === '品牌') val = p.brand;
else if (colField === 'specification') val = p.specification;
else if (colField === '一级类目') val = p.category1;
else if (colField === '二级类目') val = p.category2;
else if (colField === '三级类目') val = p.category3;
else if (colField === '供应商名称') val = p.supplier_name;
else if (colField === '供应商编码') val = p.supplier_code;
else if (colField === '生产货期') val = p.lead_time;
else if (colField === '物流时间') val = p.logistics_time;
else if (colField === '采购跟单') val = p.procurement_follower;
else if (colField === '采购计划') val = p.procurement_planner;
else if (colField === '开发专员') val = p.developer;
else if (colField === '归属平台') val = p.platform;
else if (colField === '运营专员') val = p.operator;
editFields.push({cfg: cfg, colField: colField, val: val});
}
});
var html = '
';
html += '
';
html += '
基础信息
';
html += '
';
html += '
';
html += '';
html += '';
html += '
';
html += '
';
html += '';
html += '
' + escHtml(p.product_name || '-') + '
';
html += '
';
if (editFields.length > 0) {
html += '
';
html += '
可编辑字段
';
html += '
';
editFields.forEach(function(ef) {
var f = ef.cfg;
var inputId = 'edit_' + f.field;
html += '
';
html += '';
var val = (ef.val !== null && ef.val !== undefined) ? ef.val : '';
if (f.type === 'select' && f.options) {
html += '';
} else if (f.type === 'number') {
html += '';
} else {
html += '';
}
html += '
';
});
html += '
';
}
html += '
';
html += '';
if (editFields.length > 0) {
html += '';
}
html += '
';
document.getElementById('pdEditBody').innerHTML = html;
});
};
window.confirmProductEdit = function(productCode) {
var fields = {};
var changed = false;
// 读取货品编号
var newCodeInput = document.getElementById('edit_product_code');
var newCode = newCodeInput ? newCodeInput.value.trim() : productCode;
if (newCode !== productCode && newCode) {
fields['product_code'] = newCode;
changed = true;
}
// 查找所有以 edit_ 开头的 input 元素(排除 product_code,因为已单独处理)
document.querySelectorAll('#productEditModal input[id^="edit_"]').forEach(function(el) {
var fieldName = el.id.replace('edit_', '');
if (fieldName === 'product_code') return;
var val = el.value.trim();
if (el.type === 'number') val = parseFloat(val) || 0;
fields[fieldName] = val;
changed = true;
});
if (!changed) {
var nbEl = document.getElementById('editNoChangeTip');
if (!nbEl) {
nbEl = document.createElement('div');
nbEl.id = 'editNoChangeTip';
nbEl.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:24px 32px;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,0.15);z-index:10001;font-size:15px;color:var(--text-secondary)';
nbEl.textContent = '没有可保存的字段';
document.body.appendChild(nbEl);
setTimeout(function() { nbEl.remove(); }, 1500);
}
return;
}
var fieldLabels = {
'cost_price':'成本价', 'product_type':'产品类型', 'brand':'品牌', 'specification':'规格',
'category1':'一级类目', 'category2':'二级类目', 'category3':'三级类目',
'supplier_name_match':'供应商名称', 'supplier_code':'供应商编码',
'lead_time':'货期(天)', 'logistics_time':'物流时间(天)',
'procurement_follower':'采购跟单', 'procurement_planner':'采购计划',
'developer':'开发专员', 'operator':'运营专员', 'platform':'归属平台'
};
// 自定义确认弹窗(替代原生 confirm)
var oldConfirm = document.getElementById('editConfirmModal');
if (oldConfirm) oldConfirm.remove();
var confirmHtml = '';
confirmHtml += '