// ==UserScript== // @name ERP图片放大显示-退货 // @namespace https://scriptcat.org/zh-CN/users/214207 // @version 5.2 // @description 监控ERP系统中特定POST请求并放大显示图片,支持拖动、按网址位置记忆、尺寸调整、自适应字体、智能滑动显示和价格变动提醒 // @author HuaSao // @match https://*.erp321.com/app/scm/purchaseout/purchaseout_Item.aspx* // @match https://*.erp321.com/app/wms/allocate/allocate_item.aspx* // @match https://*.erp321.com/app/scm/purchasein/purchaseinitem.aspx* // @match https://*.erp321.com/app/wms/Pack/PackItems.aspx* // @match https://*.erp321.com/app/wms/otherOut/otherOut_Item.aspx* // @icon https://files.erp321.com/img/platIcon/ziShen.png // @grant none // @license MIT // @homepageURL https://openuserjs.org/scripts/HuaSao/ERP图片放大显示-退货 // @supportURL https://github.com/HuaSao/erp-image-zoom-display/issues // ==/UserScript== (function () { 'use strict'; // 添加全局标志,防止脚本重复执行 if (window.erpImageZoomScriptLoaded) { return; } window.erpImageZoomScriptLoaded = true; /*********************** * 工具函数 ***********************/ // 注意:聚水潭 ERP 页面实际域名已变为 w.erp321.com(不再统一是 www.erp321.com)。 // 所有后端 API 请求必须使用 window.location.origin 动态拼接,写死 www.erp321.com // 会被浏览器 CORS 策略拦截(fetch 直接失败)。请勿改回硬编码域名。 // DOM查询工具函数 const qs = (s, root = document) => root.querySelector(s); const qsa = (s, root = document) => Array.from(root.querySelectorAll(s)); // 创建元素工具函数 const createEl = (tag, attrs = {}, css = '') => { const el = document.createElement(tag); Object.assign(el, attrs); if (css) el.style.cssText = css; return el; }; // 本地存储工具函数 const storage = { get(key, fallback = null) { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : fallback; } catch (e) { return fallback; } }, set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) { } } }; // 消息通知工具函数 const notify = (msg, type = 'success') => { const el = createEl('div', { textContent: msg }, ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%) scale(0.9); padding: 14px 20px; border-radius: 8px; font-size: 15px; font-weight: 600; color: #fff; background: ${type === 'success' ? '#4CAF50' : type === 'warning' ? '#FF9800' : type === 'info' ? '#2196F3' : '#f44336'}; box-shadow: 0 4px 12px rgba(0,0,0,0.25); z-index: 10001; opacity: 0; transition: all .25s ease; text-align: center; max-width: 80%; `); document.body.appendChild(el); requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'scale(1)'; }); setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'scale(0.9)'; setTimeout(() => el.remove(), 250); }, 2200); }; // Cookie获取工具函数 const getCookie = (name) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return null; }; // 统一的响应信息解析处理方法 const parseResponseData = (responseText, requestInfo = {}) => { try { // 处理以"0|"开头的响应数据 let processedText = responseText; if (processedText.startsWith('0|')) { processedText = processedText.substring(2); } // 解析为JSON const responseObj = JSON.parse(processedText); // 处理ReturnValue格式的数据 if (responseObj.ReturnValue) { try { // 尝试将ReturnValue转换为JSON if (typeof responseObj.ReturnValue === 'string') { responseObj.ReturnValue = JSON.parse(responseObj.ReturnValue); } } catch (e) { // 如果ReturnValue不是有效的JSON,保持原样 const errorMsg = `⚠️ ReturnValue不是有效的JSON: ${e.message}`; if (requestInfo.description) { // console.warn(errorMsg); } } } return responseObj; } catch (error) { const errorMsg = `解析响应数据失败: ${error.message}`; if (requestInfo.description) { // console.warn(errorMsg); } return {}; } }; const fetchJsonSafe = async (url, options = {}, useCache = false, cacheTtl) => { // 生成缓存键 const cacheKey = `${url}_${JSON.stringify(options)}`; // 发送请求 const res = await fetch(url, options); if (!res.ok) { return {}; } let text = await res.text(); // 尝试清理和解析JSON try { // 移除可能的前缀 while (text && !text.trim().startsWith('{')) { text = text.slice(1); if (text.length === 0) break; } if (!text.trim()) { // 返回空对象,而不是抛出错误 return {}; } const data = JSON.parse(text); return data; } catch (jsonError) { // 返回空对象,而不是抛出错误 return {}; } }; // UUID生成工具函数 const generateUUID = () => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; // 获取当前页面的基础URL(用于位置和尺寸存储的键) const getBaseUrl = () => { const url = new URL(window.location.href); return `${url.protocol}//${url.host}${url.pathname}`; }; /*********************** * API请求客户端 ***********************/ const ApiClient = { // 通用请求方法 async request(endpoint, method = 'POST', data = {}, headers = {}) { try { // 从当前页面获取路径信息 const currentPath = window.location.pathname; // 基础headers const baseHeaders = { 'accept': 'application/json', 'content-type': 'application/json; charset=utf-8', 'webbox-request-id': generateUUID(), 'webbox-route-path': currentPath, 'Cookie': document.cookie }; // 添加其他可能需要的cookie值 const gwfp = getCookie('gwfp'); const u_sso_token = getCookie('u_sso_token'); const u_account = getCookie('u_account'); const u_name = getCookie('u_name'); if (gwfp) baseHeaders['gwfp'] = gwfp; if (u_sso_token) baseHeaders['u_sso_token'] = u_sso_token; if (u_account) baseHeaders['u_account'] = u_account; if (u_name) baseHeaders['u_name'] = u_name; // 合并传入的headers const finalHeaders = { ...baseHeaders, ...headers }; // 发送请求 const response = await fetch(endpoint, { method: method, headers: finalHeaders, body: data ? JSON.stringify(data) : undefined, credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); throw new Error(`HTTP错误!状态:${response.status}`); } return await response.json(); } catch (error) { // console.error('❌ API请求失败:', error.message); throw error; } }, // 表单数据请求方法(用于发送FormData格式的请求) async formRequest(endpoint, formData, method = 'POST', headers = {}) { try { // 从当前页面获取路径信息 const currentPath = window.location.pathname; // 基础headers const baseHeaders = { 'webbox-request-id': generateUUID(), 'webbox-route-path': currentPath, 'Cookie': document.cookie }; // 添加其他可能需要的cookie值 const gwfp = getCookie('gwfp'); const u_sso_token = getCookie('u_sso_token'); const u_account = getCookie('u_account'); const u_name = getCookie('u_name'); if (gwfp) baseHeaders['gwfp'] = gwfp; if (u_sso_token) baseHeaders['u_sso_token'] = u_sso_token; if (u_account) baseHeaders['u_account'] = u_account; if (u_name) baseHeaders['u_name'] = u_name; // 合并传入的headers const finalHeaders = { ...baseHeaders, ...headers }; // 发送请求 const response = await fetch(endpoint, { method: method, headers: finalHeaders, body: formData, credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); throw new Error(`HTTP错误!状态:${response.status}`); } let text = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(text, { description: 'API请求响应' }); return responseObj; } catch (error) { // console.error('❌ 表单请求失败:', error.message); throw error; } }, // 清除VIEWSTATE缓存 clearViewStateCache(url) { if (!url) return; const cacheKey = `viewState_${url.replace(/[^a-zA-Z0-9]/g, '_')}`; storage.set(cacheKey, null); // console.log('[缓存] VIEWSTATE缓存已清除:', url); }, // 动态获取VIEWSTATE和VIEWSTATEGENERATOR async getViewState(url, forceRefresh = false) { if (!url) return { viewState: '', viewStateGenerator: '' }; const CACHE_EXPIRY = 30 * 60 * 1000; // 30分钟过期 const cacheKey = `viewState_${url.replace(/[^a-zA-Z0-9]/g, '_')}`; // 尝试从缓存获取 if (!forceRefresh) { const cachedData = storage.get(cacheKey); if (cachedData && cachedData.timestamp) { const now = Date.now(); if (now - cachedData.timestamp < CACHE_EXPIRY) { // console.log('[缓存] 使用缓存的VIEWSTATE:', url); return { viewState: cachedData.viewState, viewStateGenerator: cachedData.viewStateGenerator }; } else { // console.log('[缓存] VIEWSTATE已过期:', url); } } } try { // console.log('[网络] 获取VIEWSTATE:', url); const response = await fetch(url, { credentials: 'include', mode: 'cors', timeout: 10000 }); const html = await response.text(); // 提取VIEWSTATE和VIEWSTATEGENERATOR(兼容多种属性顺序) const viewStateMatch = html.match(/id="__VIEWSTATE"\s+value="([^"]+)"/i) || html.match(/name="__VIEWSTATE"[^>]*value="([^"]+)"/i) || html.match(/value="([^"]+)"[^>]*name="__VIEWSTATE"/i); const viewStateGeneratorMatch = html.match(/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/i) || html.match(/name="__VIEWSTATEGENERATOR"[^>]*value="([^"]+)"/i) || html.match(/value="([^"]+)"[^>]*name="__VIEWSTATEGENERATOR"/i); const result = { viewState: viewStateMatch ? viewStateMatch[1] : '', viewStateGenerator: viewStateGeneratorMatch ? viewStateGeneratorMatch[1] : '' }; // 缓存VIEWSTATE storage.set(cacheKey, { viewState: result.viewState, viewStateGenerator: result.viewStateGenerator, timestamp: Date.now() }); // console.log('[缓存] VIEWSTATE已更新:', url); return result; } catch (error) { // console.error('❌ 获取VIEWSTATE失败:', error.message); return { viewState: '', viewStateGenerator: '' }; } }, // 构建表单数据 buildFormData(type, params = {}) { const u_id = getCookie('u_id'); const u_co_id = getCookie('u_co_id'); if (!u_id || !u_co_id) { // console.error('❌ 无法从cookie中获取u_id或u_co_id'); throw new Error('无法从cookie中获取u_id或u_co_id'); } switch (type) { case 'checkPriceChange': return { "ip": "", "uid": u_id, "coid": u_co_id, "page": { "currentPage": 1, "pageSize": 20, "starSize": 1, "hasPageInfo": true }, "data": { "type": 1, "id": params.sku_id, "creators": [], "beginDate": "", "endDate": "", "onlyDel": false, "name": "修改普通商品资料" }, "isOtherStore": false }; // 可以在这里添加更多表单类型 default: // console.error(`❌ 未知的表单类型: ${type}`); throw new Error(`未知的表单类型: ${type}`); } }, // 构建操作数量查询表单 buildOperationQtyForm(params = {}) { const formData = new FormData(); // 设置固定值 formData.append('match_form', '0'); formData.append('_jt_page_size', '50'); formData.append('__CALLBACKID', 'JTable1'); // 设置动态值 if (params.viewState) formData.append('__VIEWSTATE', params.viewState); if (params.viewStateGenerator) formData.append('__VIEWSTATEGENERATOR', params.viewStateGenerator); if (params.sku_id) formData.append('sku_id', params.sku_id); if (params.remark) formData.append('remark', params.remark); if (params.type_name) formData.append('type_name', params.type_name); if (params.type) formData.append('type', params.type); // 添加两个created字段:开始时间和结束时间(默认当天) if (params.created) { formData.append('created', params.created); // 开始时间:变动日期 // 获取当天日期作为结束时间 const today = new Date(); const endDateFormatted = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; formData.append('created', endDateFormatted); // 结束时间:默认当天 } // 构建CALLBACKPARAM const filters = []; // 添加sku_id筛选 if (params.sku_id) { filters.push({"k":"sku_id","v":params.sku_id,"c":"="}); } // 添加created筛选 if (params.created) { filters.push({"k":"created","v":params.created,"c":">=","t":"date"}); // 第二个日期是当日日期23:59:59.998 const today = new Date(); const endDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')} 23:59:59.998`; filters.push({"k":"created","v":endDate,"c":"<=","t":"date"}); } // 添加type筛选 if (params.type) { filters.push({"k":"type","v":params.type,"c":"="}); } // 添加match_form筛选 filters.push({"k":"match_form","v":"0","c":"="}); // 添加remark筛选 if (params.remark) { filters.push({"k":"remark","v":params.remark,"c":"="}); } // 添加creator筛选(如果有) if (params.creator) { // 使用固定的5个id,而不是传入的值 filters.push({"k":"creator","v":"20879672,21417895,21465083,22012863,22029078","c":"@="}); } // 添加id2筛选(如果有) if (params.id2) { filters.push({"k":"id2","v":params.id2,"c":"="}); } // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify(filters),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); return formData; }, // 构建订单出库请求的表单数据 buildOrderOutForm(params = {}) { const formData = new FormData(); // 设置固定值 formData.append('_jt_page_increament_enabled', 'true'); formData.append('_jt_page_increament_key_name', 'o_id'); formData.append('_jt_page_size', '2000'); formData.append('_jt_page_action', '1'); formData.append('fe_remark_type', 'single'); formData.append('__CALLBACKID', 'JTable1'); // 设置动态值 if (params.viewState) formData.append('__VIEWSTATE', params.viewState); if (params.viewStateGenerator) formData.append('__VIEWSTATEGENERATOR', params.viewStateGenerator); // 构建CALLBACKPARAM的filters const filters = []; // 添加sku_id筛选 if (params.sku_id) { filters.push({"k":"sku_id","v":params.sku_id,"c":"="}); } // 添加status筛选 filters.push({"k":"status","v":"waitpay,waitconfirm,delivering,sent,question","c":"@="}); // 添加order_date筛选 if (params.startDate) { filters.push({"k":"order_date","v":params.startDate,"c":">=","t":"date"}); } if (params.endDate) { filters.push({"k":"order_date","v":params.endDate,"c":"<=","t":"date"}); } // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify(filters),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); return formData; }, // 解析响应数据 parseResponse(type, response) { switch (type) { case 'checkPriceChange': return this.parsePriceChangeResponse(response); case 'operationQty': return this.parseOperationQtyResponse(response); // 可以在这里添加更多响应解析类型 default: // console.error(`❌ 未知的响应类型: ${type}`); throw new Error(`未知的响应类型: ${type}`); } }, // 获取原价格(从入库单中) async getOriginalPriceFromStockIn(sku_id, priceChangeDate) { try { const u_co_id = getCookie('u_co_id'); if (!u_co_id) { return null; } // 计算日期范围:价格变动时间的前1个月至前3天 const changeDate = new Date(priceChangeDate); const startDate = new Date(changeDate); startDate.setMonth(startDate.getMonth() - 1); // 前1个月 const endDate = new Date(changeDate); endDate.setDate(endDate.getDate() - 3); // 前3天 // 格式化日期为yyyy-MM-dd格式 const formatDate = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const formattedStartDate = formatDate(startDate); const formattedEndDate = formatDate(endDate); const endDateWithTime = `${formattedEndDate} 23:59:59.998`; // 构建请求URL const url = `${window.location.origin}/app/scm/purchasein/purchasein.aspx?_c=jst-epaas&epaas=true&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/scm/purchasein/purchasein.aspx`; const viewStateData = await this.getViewState(viewStateUrl); // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('owner_co_id', u_co_id); formData.append('authorize_co_id', u_co_id); formData.append('io_date', formattedStartDate); formData.append('io_date', formattedEndDate); formData.append('sku_id', sku_id); formData.append('_jt_page_size', '25'); formData.append('bin_check_name', 'pick'); formData.append('freight_radio', '-1'); formData.append('created_pay', '-1'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"io_date","v":formattedStartDate,"c":">=","t":"date"}, {"k":"io_date","v":endDateWithTime,"c":"<=","t":"date"}, {"k":"sku_id","v":sku_id,"c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 处理响应 if (response.ReturnValue) { const returnValue = response.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas) && returnValue.datas.length > 0) { // 提取第一个入库单的单号 const firstStockIn = returnValue.datas[0]; const ioId = firstStockIn.io_id; if (ioId) { // 查询该入库单的详细信息 const detailUrl = `${window.location.origin}/app/scm/purchasein/purchaseinitem.aspx?io_id=${ioId}&wms_co_id=${u_co_id}&_m=true&mode=&tpwAllCompany=0&IsArchive=false&owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}&ts___=${Date.now()}&am___=LoadDataToJSON`; // 发送GET请求 const detailResponse = await fetch(detailUrl, { credentials: 'include', mode: 'cors' }); if (!detailResponse.ok) { // console.error(`❌ HTTP错误!状态:${detailResponse.status}`); return null; } const detailHtml = await detailResponse.text(); // 提取_jt_data let jtData; const divMatch = detailHtml.match(/
]*>([\s\S]*?)<\/div>/); if (divMatch) { jtData = JSON.parse(divMatch[1]); } else { const scriptMatch = detailHtml.match(/_jt_data\s*=\s*({[\s\S]*?});/); if (!scriptMatch) { // console.error('❌ 未找到_jt_data'); return null; } jtData = JSON.parse(scriptMatch[1]); } // 提取datas if (jtData.datas && Array.isArray(jtData.datas)) { // 找到sku_id匹配的记录 const matchingRecord = jtData.datas.find(item => item.sku_id === sku_id); if (matchingRecord && matchingRecord.cost_price) { const originalPrice = matchingRecord.cost_price; return originalPrice; } } } } } return null; } catch (error) { // console.error('❌ 获取原价格失败:', error.message); return null; } }, // 解析价格变动响应 parsePriceChangeResponse(response) { // 检查响应是否包含data数组 if (response.data && Array.isArray(response.data)) { // 遍历data数组 for (const item of response.data) { // 检查remark是否含有关键词"成本价" if (item.remark && item.remark.includes('成本价')) { // 提取成本价变动信息 const costPriceMatch = item.remark.match(/成本价:([\d.]+)->([\d.]+)/); if (costPriceMatch && costPriceMatch.length === 3) { const oldPrice = costPriceMatch[1]; const newPrice = costPriceMatch[2]; // 保存完整的创建时间 const fullCreated = item.created; // 格式化修改日期为yyyy-MM-dd格式 let formattedDate = '未知日期'; if (fullCreated) { try { const date = new Date(fullCreated); if (!isNaN(date.getTime())) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); formattedDate = `${year}-${month}-${day}`; } } catch (e) { // console.error('❌ 日期格式解析错误:', e.message); } } return { hasChange: true, oldPrice, newPrice, formattedDate, fullCreated }; } } } } return { hasChange: false }; }, // 解析操作数量响应 parseOperationQtyResponse(response) { // 检查响应是否包含ReturnValue if (response.ReturnValue) { try { const returnValueObj = response.ReturnValue; // 检查是否包含datas数组 if (returnValueObj.datas && Array.isArray(returnValueObj.datas)) { // 计算总数量 const totalQty = returnValueObj.datas.reduce((sum, item) => { return sum + (parseFloat(item.qty) || 0); }, 0); return { totalQty, datas: returnValueObj.datas }; } } catch (e) { // console.error('❌ 解析操作数量响应失败:', e.message); } } return { totalQty: 0, datas: [] }; }, // 检查价格变动 async checkPriceChange(sku_id) { try { const u_co_id = getCookie('u_co_id'); if (!u_co_id) { return { hasChange: false }; } // 构建请求URL const url = `${window.location.origin}/webapi/ItemApi/Log/GetPageListV2?owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}`; // 构建表单数据 const formData = this.buildFormData('checkPriceChange', { sku_id }); // 发送请求 const response = await this.request(url, 'POST', formData); // 解析响应 return this.parseResponse('checkPriceChange', response); } catch (error) { // console.error('❌ 检查价格变动失败:', error.message); return { hasChange: false }; } }, // 操作数量查询 async queryOperationQty(params = {}, retryCount = 0) { const MAX_RETRIES = 3; try { // 构建请求URL const url = `${window.location.origin}/app/item/skusn/Search.aspx?_c=jst-epaas&epaas=true&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx?_c=jst-epaas&epaas=true`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 构建表单数据 const formData = this.buildOperationQtyForm({ ...params, viewState: viewStateData.viewState, viewStateGenerator: viewStateData.viewStateGenerator }); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 解析响应 return this.parseResponse('operationQty', response); } catch (error) { // console.error('❌ 操作数量查询失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试操作数量查询...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx?_c=jst-epaas&epaas=true`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.queryOperationQty(params, retryCount + 1); } return { totalQty: 0, datas: [] }; } }, // 订单出库请求 async queryOrderOutQty(params = {}, retryCount = 0) { const MAX_RETRIES = 3; try { const { sku_id, startDate, endDate } = params; // 构建请求URL const url = `${window.location.origin}/app/order/order/list.aspx?_c=jst-epaas&epaas=true&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/order/order/list.aspx?_c=jst-epaas&epaas=true`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 构建表单数据 const formData = this.buildOrderOutForm({ ...params, viewState: viewStateData.viewState, viewStateGenerator: viewStateData.viewStateGenerator }); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 处理ReturnValue if (response.ReturnValue) { const returnValue = response.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 统计退款数量 let refundCount = 0; returnValue.datas.forEach(item => { if (item.items && Array.isArray(item.items)) { item.items.forEach(subItem => { if (subItem.refund_status === "退款成功") { refundCount++; } }); } }); // 计算订单出库数量:总订单数 - 退款数量 const orderOutQty = returnValue.datas.length - refundCount; return { totalQty: orderOutQty, datas: returnValue.datas }; } } return { totalQty: 0, datas: [] }; } catch (error) { // console.error('❌ 订单出库请求失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试订单出库请求...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/order/order/list.aspx?_c=jst-epaas&epaas=true`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.queryOrderOutQty(params, retryCount + 1); } return { totalQty: 0, datas: [] }; } }, // 获取退货单号列表 async getReturnOrderIds(sku_id, startDate, endDate, excludeIds = [], retryCount = 0) { const MAX_RETRIES = 3; try { // 构建请求URL const url = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx?&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('io_date', startDate); formData.append('io_date', endDate); formData.append('sku_id', sku_id); formData.append('_jt_page_size', '100'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"io_date","v":startDate,"c":">=","t":"date"}, {"k":"io_date","v":endDate,"c":"<=","t":"date"}, {"k":"sku_id","v":sku_id,"c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 处理响应 if (response.ReturnValue) { const returnValue = response.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 提取所有io_id并排除指定的单号 const ioIds = returnValue.datas .map(item => item.io_id) .filter(Boolean) .filter(id => !excludeIds.includes(id)); return ioIds; } } return []; } catch (error) { // console.error('❌ 获取退货单号列表失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试获取退货单号列表...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.getReturnOrderIds(sku_id, startDate, endDate, excludeIds, retryCount + 1); } return []; } }, // 获取单个退货单号的出库数量 async getOrderOutQty(io_id, sku_id, newPrice = null) { try { // 构建请求URL const url = `${window.location.origin}/app/scm/purchaseout/purchaseout_Item.aspx?io_id=${io_id}`; // 发送GET请求 const response = await fetch(url, { credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); throw new Error(`HTTP错误!状态:${response.status}`); } const html = await response.text(); // 提取div中的_jt_data const divMatch = html.match(/
]*>([\s\S]*?)<\/div>/); let jtData; if (divMatch) { // 从div中解析_jt_data jtData = JSON.parse(divMatch[1]); } else { // 尝试从脚本中提取_jt_data const scriptMatch = html.match(/_jt_data\s*=\s*({[\s\S]*?});/); if (!scriptMatch) { // console.error('❌ 未找到_jt_data'); return 0; } // 从脚本中解析_jt_data jtData = JSON.parse(scriptMatch[1]); } // 提取datas if (jtData.datas && Array.isArray(jtData.datas)) { // 找到sku_id匹配的记录 const matchingRecord = jtData.datas.find(item => item.sku_id === sku_id); if (matchingRecord && matchingRecord.qty) { // 检查cost_price是否为新价格 if (newPrice !== null) { if (matchingRecord.cost_price) { const costPrice = parseFloat(matchingRecord.cost_price) || 0; const newPriceFloat = parseFloat(newPrice) || 0; if (costPrice !== newPriceFloat) { return 0; } } } const qty = parseFloat(matchingRecord.qty) || 0; return qty; } } return 0; } catch (error) { // console.error('❌ 获取单个退货单号的出库数量失败:', error.message); return 0; } }, // 获取总操作数量(基于退货单号) async getTotalOperationQty(sku_id, startDate, endDate, excludeIds = [], newPrice = null) { try { // 获取退货单号列表 const ioIds = await this.getReturnOrderIds(sku_id, startDate, endDate, excludeIds); if (ioIds.length === 0) { return 0; } // 如果只有一个单号,也排除,不用查询 if (ioIds.length === 1) { return 0; } // 排除第一个单号,只查询剩余的单号 const filteredIoIds = ioIds.slice(1); // 为每个单号获取出库数量 const qtyPromises = filteredIoIds.map(ioId => this.getOrderOutQty(ioId, sku_id, newPrice)); const qtys = await Promise.all(qtyPromises); // 计算总数量 const totalQty = qtys.reduce((sum, qty) => sum + qty, 0); return totalQty; } catch (error) { // console.error('❌ 获取总操作数量失败:', error.message); return 0; } }, // 获取入库数量 async getStockInQty(sku_id, priceChangeDate, newPrice, retryCount = 0) { const MAX_RETRIES = 3; try { const u_co_id = getCookie('u_co_id'); if (!u_co_id) { return 0; } // 计算日期范围:价格修改日期的前三天到修改日期的前一天 const changeDate = new Date(priceChangeDate); const startDate = new Date(changeDate); startDate.setDate(startDate.getDate() - 3); const endDate = new Date(changeDate); endDate.setDate(endDate.getDate() - 1); // 格式化日期为yyyy-MM-dd格式 const formatDate = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const formattedStartDate = formatDate(startDate); const formattedEndDate = formatDate(endDate); const endDateWithTime = `${formattedEndDate} 23:59:59.998`; // 构建请求URL const url = `${window.location.origin}/app/scm/purchasein/purchasein.aspx?_c=jst-epaas&epaas=true&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/scm/purchasein/purchasein.aspx`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('owner_co_id', u_co_id); formData.append('authorize_co_id', u_co_id); formData.append('io_date', formattedStartDate); formData.append('io_date', formattedEndDate); formData.append('sku_id', sku_id); formData.append('_jt_page_size', '25'); formData.append('bin_check_name', 'pick'); formData.append('freight_radio', '-1'); formData.append('created_pay', '-1'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"io_date","v":formattedStartDate,"c":">=","t":"date"}, {"k":"io_date","v":endDateWithTime,"c":"<=","t":"date"}, {"k":"sku_id","v":sku_id,"c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 处理响应 if (response.ReturnValue) { const returnValue = response.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 提取所有io_id const ioIds = returnValue.datas .map(item => item.io_id) .filter(Boolean); if (ioIds.length === 0) { return 0; } // 为每个io_id获取详细数据 let totalStockInQty = 0; for (const ioId of ioIds) { try { // 构建请求URL const detailUrl = `${window.location.origin}/app/scm/purchasein/purchaseinitem.aspx?io_id=${ioId}&wms_co_id=${u_co_id}&_m=true&mode=&tpwAllCompany=0&IsArchive=false&owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}&ts___=${Date.now()}&am___=LoadDataToJSON`; // 发送GET请求 const detailResponse = await fetch(detailUrl, { credentials: 'include', mode: 'cors' }); if (!detailResponse.ok) { // console.error(`❌ HTTP错误!状态:${detailResponse.status}`); continue; } const detailHtml = await detailResponse.text(); // 提取_jt_data let jtData; const divMatch = detailHtml.match(/
]*>([\s\S]*?)<\/div>/); if (divMatch) { jtData = JSON.parse(divMatch[1]); } else { const scriptMatch = detailHtml.match(/_jt_data\s*=\s*({[\s\S]*?});/); if (!scriptMatch) { // console.error('❌ 未找到_jt_data'); continue; } jtData = JSON.parse(scriptMatch[1]); } // 提取datas if (jtData.datas && Array.isArray(jtData.datas)) { // 找到sku_id匹配的记录 const matchingRecord = jtData.datas.find(item => item.sku_id === sku_id); if (matchingRecord && matchingRecord.qty && matchingRecord.cost_price) { // 检查cost_price是否为新价格 const costPrice = parseFloat(matchingRecord.cost_price) || 0; const newPriceFloat = parseFloat(newPrice) || 0; if (costPrice === newPriceFloat) { const qty = parseFloat(matchingRecord.qty) || 0; totalStockInQty += qty; } } } } catch (error) { // console.error(`❌ 获取入库单号 ${ioId} 的详细数据失败:`, error.message); continue; } } // 查询修改价格时间后的入库数量 const postPriceChangeQty = await this.getPostPriceChangeStockInQty(sku_id, priceChangeDate, u_co_id); // 计算总入库数量 const finalStockInQty = totalStockInQty + postPriceChangeQty; return finalStockInQty; } } return 0; } catch (error) { // console.error('❌ 获取入库数量失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试获取入库数量...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/scm/purchasein/purchasein.aspx`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.getStockInQty(sku_id, priceChangeDate, newPrice, retryCount + 1); } return 0; } }, // 获取修改价格时间后的入库数量 async getPostPriceChangeStockInQty(sku_id, priceChangeDate, u_co_id, retryCount = 0) { const MAX_RETRIES = 3; try { // 构建请求URL const url = `${window.location.origin}/app/item/skusn/Search.aspx?_c=jst-epaas&epaas=true&ts___=${Date.now()}&am___=LoadDataToJSON`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 计算日期范围:价格修改日期到今天 const endDate = new Date(); const endDateWithTime = `${endDate.getFullYear()}-${String(endDate.getMonth() + 1).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')} 23:59:59.998`; // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('match_form', '0'); formData.append('sku_id', sku_id); formData.append('created', priceChangeDate); formData.append('created', endDateWithTime); formData.append('type_name', '直接采购入库'); formData.append('type', '直接采购入库'); formData.append('_jt_page_size', '500'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"sku_id","v":sku_id,"c":"="}, {"k":"created","v":priceChangeDate,"c":">=","t":"date"}, {"k":"created","v":endDateWithTime,"c":"<=","t":"date"}, {"k":"type","v":"直接采购入库","c":"="}, {"k":"match_form","v":"0","c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await this.formRequest(url, formData, 'POST'); // 处理响应 if (response.ReturnValue) { const returnValue = response.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 计算总入库数量 let totalQty = 0; returnValue.datas.forEach(item => { if (item.qty) { totalQty += parseFloat(item.qty) || 0; } }); return totalQty; } } return 0; } catch (error) { // console.error('❌ 获取修改价格时间后的入库数量失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试获取修改价格时间后的入库数量...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.getPostPriceChangeStockInQty(sku_id, priceChangeDate, u_co_id, retryCount + 1); } return 0; } }, // 获取排除调拨入数量 async getExclusionQuantities(returnData, startDate, retryCount = 0) { const MAX_RETRIES = 3; try { // 提取第一个SKU ID并取前5个字符 const firstSku = returnData.find(item => item.sku_id); if (!firstSku) { // console.error('❌ 未找到有效的SKU ID'); return {}; } const skuPrefix = firstSku.sku_id.substring(0, 5); // 构建请求URL const url = `${window.location.origin}/app/item/skusn/Search.aspx`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx`; const viewStateData = await this.getViewState(viewStateUrl, retryCount > 0); // 计算结束日期(当天) const today = new Date(); const endDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')} 23:59:59.998`; // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('match_form', '0'); formData.append('sku_id', skuPrefix); formData.append('pack_id', '退供应商仓暂存位'); formData.append('created', startDate); formData.append('created', endDate); formData.append('type_name', '调拨入'); formData.append('type', '调拨入'); formData.append('_jt_page_size', '500'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"pack_id","v":"退供应商仓暂存位","c":"="}, {"k":"sku_id","v":skuPrefix,"c":"="}, {"k":"created","v":startDate,"c":">=","t":"date"}, {"k":"created","v":endDate,"c":"<=","t":"date"}, {"k":"type","v":"调拨入","c":"="}, {"k":"match_form","v":"0","c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await fetch(url, { method: 'POST', headers: { 'gwfp': getCookie('gwfp') || '', 'u_sso_token': getCookie('u_sso_token') || '', 'webbox-request-id': generateUUID(), 'Cookie': document.cookie }, body: formData, credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); throw new Error(`HTTP错误!状态:${response.status}`); } // 处理响应 let text = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(text, { description: 'API请求响应' }); if (responseObj.ReturnValue) { const returnValue = responseObj.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 处理datas数组,按sku_id分组并求和 const exclusionQuantities = {}; returnValue.datas.forEach(item => { if (item.sku_id && item.qty) { const skuId = item.sku_id; const qty = parseFloat(item.qty) || 0; if (exclusionQuantities[skuId]) { exclusionQuantities[skuId] += qty; } else { exclusionQuantities[skuId] = qty; } } }); return exclusionQuantities; } } return {}; } catch (error) { // console.error('❌ 获取排除调拨入数量失败:', error.message); if (retryCount < MAX_RETRIES) { // console.log('[网络] 重试获取排除调拨入数量...'); // 清除VIEWSTATE缓存 const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx`; this.clearViewStateCache(viewStateUrl); // 延迟后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); return this.getExclusionQuantities(returnData, startDate, retryCount + 1); } return {}; } } }; /*********************** * 状态管理 ***********************/ const state = { ui: null, isMinimized: false, originalWidth: '400px', originalHeight: '400px', sizeBeforeMinimize: { width: '400px', height: '400px' }, imageUrl: '', currentSkuId: null }; // 全局变量存储modified1信息 let globalModified1Map = new Map(); /*********************** * UI 组件 ***********************/ const UI = { // 创建图片显示容器 createImageDisplay() { // 从localStorage加载保存的位置和尺寸 const savedPosition = DragAndResize.loadDisplayPosition(); const topPosition = savedPosition?.top || '35px'; const rightPosition = savedPosition?.right || '10px'; const width = savedPosition?.width || '400px'; const height = savedPosition?.height || '400px'; state.isMinimized = savedPosition?.isMinimized || false; state.sizeBeforeMinimize = savedPosition?.sizeBeforeMinimize || { width, height }; state.originalWidth = width; state.originalHeight = height; // 创建显示容器 const display = createEl('div', { id: 'erp-image-display' }, ` position: fixed; top: ${topPosition}; right: ${rightPosition}; width: ${state.isMinimized ? '100px' : width}; height: ${state.isMinimized ? '100px' : height}; background: white; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; overflow: hidden; padding: 0; cursor: move; user-select: none; transition: width 0.3s ease, height 0.3s ease, opacity 0.3s ease; `); // 添加提示信息容器 const alertContainer = createEl('div', { id: 'erp-image-alerts' }, ` position: absolute; top: 10px; left: 10px; right: 10px; text-align: center; z-index: 1; pointer-events: none; opacity: ${state.isMinimized ? '0' : '1'}; transition: opacity 0.3s ease; `); // 创建第一个提示信息标签 const alert1 = createEl('div', { id: 'erp-alert-1' }, ` background-color: rgba(255, 255, 255, 0.3); color: red; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; padding: 5px 10px; border-radius: 4px; margin-bottom: 5px; display: none; font-size: 14px; transition: all 0.3s ease; `); // 创建第二个提示信息标签 const alert2 = createEl('div', { id: 'erp-alert-2' }, ` background-color: rgba(255, 255, 255, 0.3); color: gray; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; padding: 5px 10px; border-radius: 4px; display: none; font-size: 14px; transition: all 0.3s ease; `); alertContainer.appendChild(alert1); alertContainer.appendChild(alert2); display.appendChild(alertContainer); // 创建图片滚动容器 const imgContainer = createEl('div', { id: 'erp-image-scroll-container' }, ` width: 100%; height: 100%; overflow: auto; display: flex; justify-content: center; align-items: flex-start; pointer-events: auto; scrollbar-width: none; -ms-overflow-style: none; `); // 添加自定义滚动条CSS this.addCustomScrollbarStyles(); // 创建图片元素 const img = createEl('img', { id: 'erp-displayed-image', alt: 'ERP产品图片' }, ` max-width: 100%; height: auto; display: block; margin: 0 auto; object-fit: contain; `); imgContainer.appendChild(img); display.appendChild(imgContainer); // 添加功能按钮容器 const buttonContainer = createEl('div', { id: 'erp-image-buttons' }, ` position: absolute; bottom: 10px; right: 10px; display: flex; gap: 5px; opacity: 0.8; transition: opacity 0.3s ease; z-index: 2; pointer-events: auto; `); // SVG图标定义 const zoomInSvg = ``; const zoomOutSvg = ``; const plusSvg = ``; const minusSvg = ``; // 添加放大缩小切换按钮 const zoomToggleButton = createEl('button', { id: 'erp-zoom-toggle', innerHTML: state.isMinimized ? zoomInSvg : zoomOutSvg }, ` width: 30px; height: 30px; border: none; border-radius: 50%; background-color: rgba(0, 0, 0, 0.5); color: white; font-size: 14px; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; `); // 添加尺寸加按钮 const sizePlusButton = createEl('button', { id: 'erp-size-plus', innerHTML: plusSvg }, ` width: 30px; height: 30px; border: none; border-radius: 50%; background-color: rgba(0, 0, 0, 0.5); color: white; font-size: 16px; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; `); // 添加尺寸减按钮 const sizeMinusButton = createEl('button', { id: 'erp-size-minus', innerHTML: minusSvg }, ` width: 30px; height: 30px; border: none; border-radius: 50%; background-color: rgba(0, 0, 0, 0.5); color: white; font-size: 16px; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; `); // 根据是否最小化调整按钮显示 if (state.isMinimized) { // 最小化时只显示放大缩小按钮 buttonContainer.appendChild(zoomToggleButton); buttonContainer.style.bottom = '25%'; buttonContainer.style.right = '25%'; buttonContainer.style.opacity = '0.5'; zoomToggleButton.style.width = '50px'; zoomToggleButton.style.height = '50px'; alertContainer.style.display = 'none'; sizePlusButton.style.display = 'none'; sizeMinusButton.style.display = 'none'; } else { buttonContainer.appendChild(zoomToggleButton); buttonContainer.appendChild(sizePlusButton); buttonContainer.appendChild(sizeMinusButton); } // 添加按钮到显示框 display.appendChild(buttonContainer); document.body.appendChild(display); // 保存UI元素到状态 state.ui = { display, alertContainer, imgContainer, img, buttonContainer, zoomToggleButton, sizePlusButton, sizeMinusButton, alert1, alert2 }; // 绑定事件 DragAndResize.bindEvents(); return display; }, // 添加自定义滚动条样式 addCustomScrollbarStyles() { const style = createEl('style', { textContent: ` /* 隐藏WebKit浏览器滚动条 */ #erp-image-scroll-container::-webkit-scrollbar { display: none; } /* 隐藏Firefox滚动条 */ #erp-image-scroll-container { scrollbar-width: none; } /* 隐藏IE滚动条 */ #erp-image-scroll-container { -ms-overflow-style: none; } /* 确保主容器也没有滚动条 */ #erp-image-display { overflow: hidden; } ` }); document.head.appendChild(style); }, // 更新提示字体尺寸 updateAlertFontSize() { const displayElement = state.ui.display; if (!displayElement) return; const currentWidth = parseInt(displayElement.style.width); const alert1Element = state.ui.alert1; const alert2Element = state.ui.alert2; // 基于400px基准尺寸计算字体大小,使用更平缓的缩放比例 const baseSize = 14; const minSize = 12; // 最小字体12px const maxSize = 24; // 最大字体24px // 使用平方根让缩小更平缓 const scaleFactor = Math.sqrt(currentWidth / 400); const newFontSize = baseSize * scaleFactor; // 限制字体大小范围 const clampedFontSize = Math.max(minSize, Math.min(maxSize, newFontSize)); if (alert1Element) { alert1Element.style.fontSize = clampedFontSize + 'px'; } if (alert2Element) { alert2Element.style.fontSize = clampedFontSize + 'px'; } }, // 显示价格变动提示 showPriceChangeAlert(oldPrice, newPrice, formattedDate, stockInQty = 0, stockOutQty = 0) { // 移除已存在的所有价格变动提示 const existingAlert3 = document.getElementById('erp-alert-3'); if (existingAlert3) { existingAlert3.remove(); } // 计算剩余数量 const remainingQty = stockInQty - stockOutQty; // 根据剩余数量确定样式 const isPositive = remainingQty > 0; // 使用更融入的底色,半透明黑色 const backgroundColor = 'rgba(0, 0, 0, 0.7)'; const borderColor = isPositive ? '#4CAF50' : '#f44336'; const textColor = '#fff'; // 白色文字,更清晰 // 创建新的提示元素 const alert3 = createEl('div', { id: 'erp-alert-3' }, ` background-color: ${backgroundColor}; color: ${textColor}; font-weight: bold; font-family: 'Microsoft YaHei', sans-serif; padding: 8px 12px; border-radius: 6px; margin-bottom: 5px; display: block; font-size: 14px; transition: all 0.3s ease; border: 1px solid ${borderColor}; `); // 构建提示内容 let content = ''; if (isPositive) { // 正数剩余:半透明黑底白字 content = `
价格变动, ${oldPrice}->${newPrice} ${formattedDate}, 新价格可退 ${remainingQty}
新价格出入库: 入库(${stockInQty}), 出库:(${stockOutQty})
`; } else { // 负数无余:半透明黑底白字 content = `
价格变动, ${oldPrice}->${newPrice} ${formattedDate}
新价格出入库: 入库(${stockInQty}), 出库:(${stockOutQty})
新价格已退完,请修改为旧价格
`; } alert3.innerHTML = content; // 添加到提示容器 const alertContainer = state.ui.alertContainer; if (alertContainer) { alertContainer.appendChild(alert3); } } }; /*********************** * 拖动与折叠 ***********************/ const DragAndResize = { // 绑定拖动和调整大小事件 bindEvents() { if (!state.ui) return; const { display, imgContainer, zoomToggleButton, sizePlusButton, sizeMinusButton } = state.ui; let isDragging = false; let startX, startY, startTop, startRight; let originalTransition; // 放大缩小切换功能 zoomToggleButton.addEventListener('click', function(e) { e.stopPropagation(); // 防止触发拖动 DragAndResize.toggleMinimize(); }); // 尺寸加功能 sizePlusButton.addEventListener('click', function(e) { e.stopPropagation(); // 防止触发拖动 DragAndResize.resize(50, 50); }); // 尺寸减功能 sizeMinusButton.addEventListener('click', function(e) { e.stopPropagation(); // 防止触发拖动 DragAndResize.resize(-50, -50); }); // 拖动功能 display.addEventListener('mousedown', function(e) { // 如果点击的是按钮,不触发拖动 if (e.target.closest('#erp-image-buttons')) { return; } isDragging = true; startX = e.clientX; startY = e.clientY; startTop = parseInt(display.style.top); startRight = parseInt(display.style.right); display.style.zIndex = '10000'; // 拖动时提高层级 // 保存原始过渡效果并移除,避免拖动时的过渡动画 originalTransition = display.style.transition; display.style.transition = 'none'; }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; // 计算新位置,确保不超出顶部页面范围 const newTop = Math.max(0, startTop + deltaY); // 限制顶部位置 const newRight = Math.max(0, startRight - deltaX); // 限制右侧位置 // 实时更新位置,不使用过渡动画 display.style.top = newTop + 'px'; display.style.right = newRight + 'px'; }); document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; display.style.zIndex = '9999'; // 拖动结束后恢复层级 // 恢复原始过渡效果 display.style.transition = originalTransition; // 再次确保最终位置不超出顶部 const finalTop = Math.max(0, parseInt(display.style.top)); display.style.top = finalTop + 'px'; // 保存当前位置和尺寸到localStorage DragAndResize.saveDisplayPosition(); } }); // 鼠标悬停时显示按钮 display.addEventListener('mouseenter', function() { if (!state.isMinimized) { state.ui.buttonContainer.style.opacity = '1'; } }); display.addEventListener('mouseleave', function() { if (!state.isMinimized) { state.ui.buttonContainer.style.opacity = '0.8'; } }); // 图片加载完成后根据宽高比调整显示方式 state.ui.img.addEventListener('load', function() { // 只有在非缩小状态下才执行调整 if (!state.isMinimized) { // 获取图片原始宽高 const imageWidth = this.naturalWidth; const imageHeight = this.naturalHeight; if (imageHeight > imageWidth) { // 高度大于宽度:上下滑动,宽度填充容器,高度自适应 this.style.cssText = ` display: block; margin: 0 auto; max-width: 100%; max-height: none; width: 100%; height: auto; object-fit: contain; `; // 计算垂直滚动位置 const containerHeight = state.ui.imgContainer.clientHeight; const renderedImageHeight = this.clientHeight; // 如果图片高度大于容器高度,垂直滚动到中间位置 if (renderedImageHeight > containerHeight) { const scrollTop = (renderedImageHeight - containerHeight) / 2; state.ui.imgContainer.scrollTo({ top: scrollTop, behavior: 'smooth' }); } } else { // 宽度大于高度:裁剪成正方形,居中显示,裁剪两边留中间 this.style.cssText = ` display: block; margin: 0 auto; max-width: 100%; max-height: 100%; width: auto; height: 100%; object-fit: cover; object-position: center; `; } } }); }, // 切换最小化/最大化 toggleMinimize() { if (!state.ui) return; const { display, alertContainer, buttonContainer, zoomToggleButton, sizePlusButton, sizeMinusButton, imgContainer, img } = state.ui; state.isMinimized = !state.isMinimized; if (state.isMinimized) { // 缩小前保存当前尺寸 state.sizeBeforeMinimize = { width: display.style.width, height: display.style.height }; // 缩小到100x100 display.style.width = '100px'; display.style.height = '100px'; zoomToggleButton.innerHTML = ``; alertContainer.style.display = 'none'; sizePlusButton.style.display = 'none'; sizeMinusButton.style.display = 'none'; buttonContainer.style.bottom = '25%'; buttonContainer.style.right = '25%'; buttonContainer.style.opacity = '0.5'; zoomToggleButton.style.width = '50px'; zoomToggleButton.style.height = '50px'; // 缩小状态下设置图片为完整显示 img.style.objectFit = 'contain'; img.style.minWidth = 'auto'; img.style.minHeight = 'auto'; img.style.maxWidth = '100%'; img.style.height = 'auto'; // 缩小状态下图片容器不滚动 imgContainer.style.overflow = 'hidden'; } else { // 恢复到缩小前的尺寸 display.style.width = state.sizeBeforeMinimize.width; display.style.height = state.sizeBeforeMinimize.height; zoomToggleButton.innerHTML = ``; alertContainer.style.display = 'block'; alertContainer.style.opacity = '1'; // 将加减按钮添加到DOM并显示 buttonContainer.appendChild(sizePlusButton); buttonContainer.appendChild(sizeMinusButton); sizePlusButton.style.display = 'flex'; sizeMinusButton.style.display = 'flex'; buttonContainer.style.bottom = '10px'; buttonContainer.style.right = '10px'; buttonContainer.style.opacity = '0.8'; zoomToggleButton.style.width = '30px'; zoomToggleButton.style.height = '30px'; // 更新原始尺寸 state.originalWidth = state.sizeBeforeMinimize.width; state.originalHeight = state.sizeBeforeMinimize.height; // 非缩小状态下图片容器可滚动 imgContainer.style.overflow = 'auto'; // 延迟执行,确保DOM更新完成 setTimeout(() => { // 如果图片已加载,根据宽高比调整显示方式 if (img.complete) { // 获取图片原始宽高 const imageWidth = img.naturalWidth; const imageHeight = img.naturalHeight; if (imageHeight > imageWidth) { // 高度大于宽度:上下滑动,宽度填充容器,高度自适应 img.style.cssText = ` display: block; margin: 0 auto; max-width: 100%; max-height: none; width: 100%; height: auto; object-fit: contain; `; // 计算垂直滚动位置 const containerHeight = imgContainer.clientHeight; const renderedImageHeight = img.clientHeight; // 如果图片高度大于容器高度,垂直滚动到中间位置 if (renderedImageHeight > containerHeight) { const scrollTop = (renderedImageHeight - containerHeight) / 2; imgContainer.scrollTo({ top: scrollTop, behavior: 'smooth' }); } } else { // 宽度大于高度:裁剪成正方形,居中显示,裁剪两边留中间 img.style.cssText = ` display: block; margin: 0 auto; max-width: 100%; max-height: 100%; width: auto; height: 100%; object-fit: cover; object-position: center; `; } } }, 200); } // 保存当前状态 DragAndResize.saveDisplayPosition(); }, // 调整尺寸 resize(deltaWidth, deltaHeight) { if (!state.ui) return; const { display } = state.ui; const currentWidth = parseInt(display.style.width); const currentHeight = parseInt(display.style.height); // 限制最小尺寸为150px if (deltaWidth < 0 && deltaHeight < 0 && (currentWidth <= 150 || currentHeight <= 150)) { return; } const newWidth = currentWidth + deltaWidth; const newHeight = currentHeight + deltaHeight; display.style.width = newWidth + 'px'; display.style.height = newHeight + 'px'; // 更新原始尺寸 state.originalWidth = display.style.width; state.originalHeight = display.style.height; // 更新提示字体尺寸 UI.updateAlertFontSize(); // 保存当前状态 DragAndResize.saveDisplayPosition(); }, // 保存显示位置和尺寸到localStorage saveDisplayPosition() { if (!state.ui) return; const { display } = state.ui; try { const baseUrl = getBaseUrl(); const positions = storage.get('erpImageDisplayPositions', {}); positions[baseUrl] = { top: display.style.top, right: display.style.right, width: display.style.width, height: display.style.height, isMinimized: state.isMinimized, sizeBeforeMinimize: state.sizeBeforeMinimize }; storage.set('erpImageDisplayPositions', positions); } catch (error) { // console.error('❌ 保存位置和尺寸失败:', error); } }, // 从localStorage加载显示位置和尺寸 loadDisplayPosition() { try { const baseUrl = getBaseUrl(); const positions = storage.get('erpImageDisplayPositions', {}); if (positions[baseUrl]) { const position = positions[baseUrl]; return position; } } catch (error) { // console.error('❌ 加载位置和尺寸失败:', error); } return null; } }; /*********************** * 数据处理 ***********************/ const DataProcessor = { // 通用的URL查找函数 - 查找第一个URL findFirstImageUrl(obj) { // URL正则表达式 - 查找任何HTTP/HTTPS链接 const urlRegex = /https?:\/\/[^\s"']+(?:\?[^"']*)?/i; // 递归查找函数 function search(obj) { // 如果是字符串,检查是否是URL if (typeof obj === 'string') { const match = obj.match(urlRegex); if (match) { return match[0]; } } // 如果是数组,遍历每个元素 else if (Array.isArray(obj)) { for (let i = 0; i < obj.length; i++) { const result = search(obj[i]); if (result) { return result; } } } // 如果是对象,遍历每个属性 else if (obj && typeof obj === 'object') { for (const key in obj) { if (obj.hasOwnProperty(key)) { const result = search(obj[key]); if (result) { return result; } } } } // 未找到URL return null; } return search(obj); }, // 提取产品信息(name、taxAfterPrice和sku_id) extractProductInfo(obj) { // 存储找到的信息 const info = { name: null, taxAfterPrice: null, sku_id: null }; // 递归查找函数 function search(obj) { // 如果是对象,遍历每个属性 if (obj && typeof obj === 'object') { // 检查是否有name字段 if (obj.hasOwnProperty('name') && typeof obj.name === 'string' && !info.name) { info.name = obj.name; } // 检查是否有taxAfterPrice字段 if (obj.hasOwnProperty('taxAfterPrice') && (typeof obj.taxAfterPrice === 'number' || typeof obj.taxAfterPrice === 'string') && !info.taxAfterPrice) { info.taxAfterPrice = parseFloat(obj.taxAfterPrice); } // 检查是否有sku_id或类似字段(货品编码) if ((obj.hasOwnProperty('sku_id') || obj.hasOwnProperty('id') || obj.hasOwnProperty('SkuId') || obj.hasOwnProperty('SKU_ID')) && !info.sku_id) { info.sku_id = obj.sku_id || obj.id || obj.SkuId || obj.SKU_ID; } // 如果所有字段都找到了,直接返回 if (info.name && info.taxAfterPrice !== null && info.sku_id) { return true; } // 如果是数组,遍历每个元素 if (Array.isArray(obj)) { for (let i = 0; i < obj.length; i++) { if (search(obj[i])) { return true; } } } else { // 遍历对象的每个属性 for (const key in obj) { if (obj.hasOwnProperty(key)) { if (search(obj[key])) { return true; } } } } } return false; } search(obj); return info; }, // 解析请求数据(支持字符串、FormData等) parseRequestData(data) { if (!data) return ''; if (typeof data === 'string') { return data; } else if (data instanceof FormData) { // 解析FormData let result = ''; for (let [key, value] of data.entries()) { if (result) result += '&'; result += `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; } return result; } else { // 尝试将其他类型转换为字符串 try { return JSON.stringify(data); } catch (e) { return String(data); } } }, // 检查请求是否包含__CALLBACKPARAM且Method为CheckQty或LoadDataToJSON isRelevantRequest(url, data) { // 检查URL参数 const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.href : ''; // 检查URL中是否包含PackItems.aspx if (urlStr.includes('PackItems.aspx')) { return false; } const urlContainsTarget = urlStr.includes('__CALLBACKPARAM') && ((urlStr.includes('"Method":"CheckQty"') || urlStr.includes("'Method':'CheckQty'")) || (urlStr.includes('"Method":"LoadDataToJSON"') || urlStr.includes("'Method':'LoadDataToJSON'"))); if (urlContainsTarget) { // 检查URL中是否包含waitpay if (urlStr.includes('sku_id')) { return false; } // 检查URL中是否包含[p].bin if (urlStr.includes('[p].bin')) { return false; } return true; } // 解析请求体数据 const dataStr = this.parseRequestData(data); // 检查请求体 if (!dataStr.includes('__CALLBACKPARAM')) return false; // 提取__CALLBACKPARAM的值 const callbackParamMatch = dataStr.match(/__CALLBACKPARAM=([^&]+)/); if (!callbackParamMatch) return false; // 解码URL参数 const callbackParam = decodeURIComponent(callbackParamMatch[1]); // 检查是否包含[p].bin if (callbackParam.includes('[p].bin')) { return false; } // 检查是否包含Method:CheckQty或Method:LoadDataToJSON const isMatch = callbackParam.includes('"Method":"CheckQty"') || callbackParam.includes("'Method':'CheckQty'") || callbackParam.includes('"Method":"LoadDataToJSON"') || callbackParam.includes("'Method':'LoadDataToJSON'"); // 如果匹配,还要检查是否包含waitpay,避免无限循环 if (isMatch && callbackParam.includes('waitpay')) { return false; } return isMatch; } }; /*********************** * 请求监控 ***********************/ const RequestMonitor = { // 初始化请求监控 init() { this.monitorXMLHttpRequest(); this.monitorFetch(); }, // 监控XMLHttpRequest monitorXMLHttpRequest() { // 保存原始的XMLHttpRequest发送方法 const originalXHRSend = XMLHttpRequest.prototype.send; // 重写XMLHttpRequest的send方法以监控请求 XMLHttpRequest.prototype.send = function(data) { // 保存当前实例 const xhr = this; // 检查请求是否是相关请求(CheckQty或LoadDataToJSON) const isRelevant = DataProcessor.isRelevantRequest(xhr.responseURL, data); // 只处理相关请求 if (isRelevant) { // 监听响应 xhr.addEventListener('readystatechange', function() { if (xhr.readyState === 4) { // 处理响应内容 let responseText = xhr.responseText; try { // 使用统一的响应信息解析处理方法 const response = parseResponseData(responseText, { description: '图片请求响应' }); // 检查响应是否包含ReturnValue if (response.ReturnValue) { // ReturnValue已经被parseResponseData解析为JSON对象 const returnValueObj = response.ReturnValue; // 使用通用函数查找图片链接 const picUrl = DataProcessor.findFirstImageUrl(returnValueObj); if (picUrl) { // 提取产品信息 const productInfo = DataProcessor.extractProductInfo(returnValueObj); RequestMonitor.updateDisplayedImage(picUrl, productInfo); } else { } } } catch (error) { // console.error('❌ XMLHttpRequest响应解析错误:', error); // console.error('📄 XMLHttpRequest原始响应文本:', responseText); } } }); } // 调用原始的send方法 originalXHRSend.call(xhr, data); }; }, // 监控fetch请求 monitorFetch() { // 保存原始的fetch方法 const originalFetch = window.fetch; // 重写fetch方法以监控请求 window.fetch = async function(url, options) { // 解析URL const requestUrl = typeof url === 'string' ? url : url.href; // 检查请求是否是相关请求(CheckQty或LoadDataToJSON) const isRelevant = DataProcessor.isRelevantRequest(requestUrl, options?.body); // 发送原始请求 const response = await originalFetch.call(this, url, options); // 只处理相关请求的响应 if (isRelevant) { // 处理响应 try { // 克隆响应以避免影响原始请求 const clonedResponse = response.clone(); // 获取文本响应 const textResponse = await clonedResponse.text(); // 处理响应内容 let responseText = textResponse; // 尝试解析为JSON try { // 使用统一的响应信息解析处理方法 const responseJson = parseResponseData(responseText, { description: '图片请求响应' }); // 检查响应是否包含ReturnValue if (responseJson.ReturnValue) { // ReturnValue已经被parseResponseData解析为JSON对象 const returnValueObj = responseJson.ReturnValue; // 使用通用函数查找图片链接 const picUrl = DataProcessor.findFirstImageUrl(returnValueObj); if (picUrl) { // 提取产品信息 const productInfo = DataProcessor.extractProductInfo(returnValueObj); RequestMonitor.updateDisplayedImage(picUrl, productInfo); } else { } } } catch (jsonError) { // console.error('❌ fetch JSON解析错误:', jsonError); // console.error('📄 fetch原始响应文本:', responseText); } } catch (error) { // console.error('❌ fetch响应处理错误:', error); } } // 返回原始响应 return response; }; }, // 更新显示的图片和提示信息 updateDisplayedImage(picUrl, productInfo = {}) { let imgElement = document.getElementById('erp-displayed-image'); if (!imgElement) { UI.createImageDisplay(); imgElement = state.ui?.img; } if (imgElement) { // 清除所有之前的显示内容 // 1. 重置图片容器滚动位置 const { imgContainer, alert1, alert2 } = state.ui; if (imgContainer) { imgContainer.scrollTop = 0; imgContainer.scrollLeft = 0; } // 2. 隐藏所有提示信息 alert1.style.display = 'none'; alert2.style.display = 'none'; // 3. 移除所有已存在的价格变动提示 const existingAlert3 = document.getElementById('erp-alert-3'); if (existingAlert3) { existingAlert3.remove(); } // 4. 检查图片URL是否包含特定关键词,如没有则添加处理参数 let processedUrl = picUrl; const ossProcessKey = '?x-oss-process=image/resize,m_lfit,w'; // 如果URL不包含OSS处理关键词且不包含"name"关键词,则添加处理参数 if (!processedUrl.includes(ossProcessKey) && !processedUrl.includes('name')) { // 检查URL是否已有查询参数 const separator = processedUrl.includes('?') ? '&' : '?'; processedUrl += `${separator}x-oss-process=image/resize,m_lfit,w_200,h_200`; } // 5. 更新图片显示 imgElement.src = processedUrl; // 6. 更新当前sku_id state.currentSkuId = productInfo.sku_id || null; // 根据产品信息显示提示 if (productInfo.name) { if (productInfo.name.toLowerCase().includes('18k')) { alert1.textContent = '金镶嵌,注意包装'; alert1.style.display = 'block'; } } if (productInfo.taxAfterPrice !== null) { if (productInfo.taxAfterPrice >= 600) { alert2.textContent = '货品贵重,注意包装'; alert2.style.color = 'gray'; alert2.style.display = 'block'; } } // 检查价格变动 if (productInfo.sku_id) { const currentSkuId = productInfo.sku_id; ApiClient.checkPriceChange(productInfo.sku_id).then(async result => { // 检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } if (result.hasChange) { // 计算一个半月前的日期 const oneAndHalfMonthAgo = new Date(); oneAndHalfMonthAgo.setMonth(oneAndHalfMonthAgo.getMonth() - 1.5); // 检查价格变动时间是否在一个半月内 const changeDate = new Date(result.fullCreated || result.formattedDate); if (changeDate >= oneAndHalfMonthAgo) { // 获取操作数量 try { // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 获取原价格(从入库单中) const originalPrice = await ApiClient.getOriginalPriceFromStockIn(productInfo.sku_id, result.formattedDate); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } const oldPrice = originalPrice || result.oldPrice; // 一、入库数量获取 // 使用新的getStockInQty方法获取入库数量(基于入库单号) const stockInByOrder = await ApiClient.getStockInQty( productInfo.sku_id, result.formattedDate, result.newPrice ); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 二、从操作日志中获取入库数量(基于改价后到今天的时间范围) const now = new Date(); const endDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} 23:59:59.998`; const stockInByOperation = await ApiClient.queryOperationQty({ sku_id: productInfo.sku_id, created: result.formattedDate, type_name: "直接采购入库", type: "直接采购入库" }); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 计算总入库数量:两种方法的结果相加 const totalStockInQty = stockInByOrder + stockInByOperation.totalQty; // 如果入库数量为0,说明没有用新价格销售的订单,不用进行后续查询 if (totalStockInQty === 0) { // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 显示价格变动提示,包含出入库数量 UI.showPriceChangeAlert( oldPrice, result.newPrice, result.formattedDate, totalStockInQty, 0 ); return; } // 使用修改时间作为起始操作时间 const earliestCreated = result.formattedDate; // 二、基于退货单号的出库数量请求 // 使用之前声明的endDate变量 // 需要排除的单号数组 const excludeIds = []; // 这里可以添加需要排除的单号 const totalStockOutQty = await ApiClient.getTotalOperationQty( productInfo.sku_id, result.formattedDate, // 使用修改时间查询 endDate, excludeIds, result.newPrice // 传递新价格作为参数 ); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 三、获取订单出库数量(加到总出库数量中) const orderOutResult = await ApiClient.queryOrderOutQty({ sku_id: productInfo.sku_id, startDate: earliestCreated, endDate: endDate }); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 四、提取taxAfterPrice,判断是不是新价格,如果是新价格把它的qty加到里面 let newPriceOrderQty = 0; if (orderOutResult.datas && Array.isArray(orderOutResult.datas)) { orderOutResult.datas.forEach(order => { if (order.items && Array.isArray(order.items)) { order.items.forEach(item => { // 检查是否为当前商品 if (item.sku_id === productInfo.sku_id) { // 检查taxAfterPrice是否与新价格一致 if (item.taxAfterPrice && parseFloat(item.taxAfterPrice) === parseFloat(result.newPrice)) { // 如果是新价格,添加qty if (item.qty) { newPriceOrderQty += parseFloat(item.qty) || 0; } } } }); } }); } // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 计算总出库数量:基于退货单号的出库数量 + 订单出库数量 + 新价格订单的qty const finalStockOutQty = totalStockOutQty + orderOutResult.totalQty + newPriceOrderQty; // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // 显示价格变动提示,包含出入库数量 UI.showPriceChangeAlert( oldPrice, result.newPrice, result.formattedDate, totalStockInQty, finalStockOutQty ); } catch (error) { // 检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } // console.error('❌ 获取操作数量失败:', error.message); // 发生错误时,只显示价格变动提示,不包含出入库数量 const originalPrice = await ApiClient.getOriginalPriceFromStockIn(productInfo.sku_id, result.formattedDate); // 再次检查当前sku_id是否与请求时的sku_id一致 if (state.currentSkuId !== currentSkuId) { return; } const oldPrice = originalPrice || result.oldPrice; UI.showPriceChangeAlert(oldPrice, result.newPrice, result.formattedDate); } } } }); } // 图片内容更新时自动更新表格数据 setTimeout(async function() { await updateTableData(); }, 1000); } } }; /*********************** * 初始化 ***********************/ function init() { // 只初始化请求监控,不创建窗口 RequestMonitor.init(); // 删除 _jt_msg 元素 const msgElement = document.getElementById('_jt_msg'); if (msgElement) { msgElement.remove(); } // 脚本启动时获取supplier_code (async function() { globalSupplierCode = await getSupplierCode(); })(); // 添加下架类型下拉选择框 addPickTypeSelect(); // 添加一键移仓按钮 addOneClickTransferButton(); // 监控页面刷新 monitorPageRefresh(); // 脚本启动时自动更新表格数据 setTimeout(async function() { await updateTableData(); }, 2000); } // 从URL中提取io_id参数 function getIoIdFromUrl() { const url = new URL(window.location.href); return url.searchParams.get('io_id'); } // 计算成功移仓数量和款数 function calculateSuccessCount(moveRequestArray) { const styleCount = moveRequestArray.length; const itemCount = moveRequestArray.reduce((total, item) => { return total + parseInt(item.qty) || 0; }, 0); return { styleCount, itemCount }; } // 根据退货类型和移仓数量生成备注内容 function generateRemark(pickType, { styleCount, itemCount }) { let returnType = ''; switch(pickType) { case 'move_stock': returnType = '退供应商仓'; break; case '2548': // P区纸质拣货(退样) returnType = 'P区'; break; case '2592': // P-1-1纸质拣货(销退退供) returnType = 'P-1-1区'; break; case '2593': // P-1-4纸质拣货(品控次品汇总) returnType = 'P-1-4区'; break; case '2613': // P-1-3次品下架(当日退) returnType = 'P-1-3区'; break; default: returnType = '其他'; } return `${returnType}退货${styleCount}款${itemCount}件`; } // 获取回调数据的函数 async function fetchCallbackData(io_id) { try { console.log('开始获取回调数据'); const u_co_id = getCookie('u_co_id'); if (!u_co_id) { console.error('无法获取cookie中的u_co_id'); return null; } // 先获取purchaseout.aspx页面的HTML,以获取正确的__VIEWSTATE和__VIEWSTATEGENERATOR const pageUrl = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx`; const pageResponse = await fetch(pageUrl, { method: 'GET', credentials: 'include' }); if (!pageResponse.ok) { console.error(`获取页面失败!状态:${pageResponse.status}`); return null; } const pageHtml = await pageResponse.text(); // 从HTML中提取__VIEWSTATE和__VIEWSTATEGENERATOR let viewState = ''; let viewStateGenerator = ''; const viewStateMatch = pageHtml.match(/id="__VIEWSTATE"\s+value="([^"]+)"/i); const viewStateGeneratorMatch = pageHtml.match(/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/i); if (viewStateMatch) { viewState = viewStateMatch[1]; } else { console.error('未从页面找到__VIEWSTATE'); return null; } if (viewStateGeneratorMatch) { viewStateGenerator = viewStateGeneratorMatch[1]; } else { console.error('未从页面找到__VIEWSTATEGENERATOR'); return null; } const timestamp = Date.now(); // 使用与image_drag_drop_uploader.user.js相同的URL格式 const url = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx?_c=jst-epaas&ts___=${timestamp}&am___=LoadDataToJSON`; // 构建FormData const formData = new FormData(); formData.append('__VIEWSTATE', viewState); formData.append('__VIEWSTATEGENERATOR', viewStateGenerator); formData.append('owner_co_id', u_co_id); formData.append('authorize_co_id', u_co_id); formData.append('io_id', io_id); formData.append('_jt_page_size', '100'); formData.append('__CALLBACKID', 'JTable1'); // 构建__CALLBACKPARAM const callbackParam = { "Method": "LoadDataToJSON", "Args": [ "1", `[{\"k\":\"io_id\",\"v\":\"${io_id}\",\"c\":\"=\"}]`, "{}" ] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await fetch(url, { method: 'POST', credentials: 'include', body: formData }); if (!response.ok) { console.error(`HTTP错误!状态:${response.status}`); return null; } const responseText = await response.text(); // 使用与image_drag_drop_uploader.user.js相同的方式处理响应 const responseJson = parseResponseData(responseText); if (responseJson.ReturnValue !== undefined && responseJson.ReturnValue !== null) { // 再次解析ReturnValue let returnValueJson; if (typeof responseJson.ReturnValue === 'string') { try { returnValueJson = JSON.parse(responseJson.ReturnValue); } catch (e) { console.error('解析ReturnValue失败'); return null; } } else { returnValueJson = responseJson.ReturnValue; } if (returnValueJson.datas && returnValueJson.datas.length > 0) { console.log('回调数据获取成功'); return returnValueJson.datas[0]; } else { console.error('ReturnValue中没有datas数组或数组为空'); } } else { console.error('响应中没有ReturnValue'); } return null; } catch (error) { console.error('获取回调数据失败:', error.message); return null; } } // 修改备注的函数 async function updateRemark(io_id, remark) { try { console.log('开始更新备注'); const u_co_id = getCookie('u_co_id'); if (!u_co_id) { console.error('无法获取cookie中的u_co_id'); return false; } // 先获取purchaseout.aspx页面的HTML,以获取正确的__VIEWSTATE和__VIEWSTATEGENERATOR const pageUrl = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx`; const pageResponse = await fetch(pageUrl, { method: 'GET', credentials: 'include' }); if (!pageResponse.ok) { console.error(`获取页面失败!状态:${pageResponse.status}`); return false; } const pageHtml = await pageResponse.text(); // 从HTML中提取__VIEWSTATE和__VIEWSTATEGENERATOR let viewState = ''; let viewStateGenerator = ''; const viewStateMatch = pageHtml.match(/id="__VIEWSTATE"\s+value="([^"]+)"/i); const viewStateGeneratorMatch = pageHtml.match(/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/i); if (viewStateMatch) { viewState = viewStateMatch[1]; } else { console.error('未从页面找到__VIEWSTATE'); return false; } if (viewStateGeneratorMatch) { viewStateGenerator = viewStateGeneratorMatch[1]; } else { console.error('未从页面找到__VIEWSTATEGENERATOR'); return false; } const timestamp = Date.now(); // 使用与image_drag_drop_uploader.user.js相同的URL格式 const url = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx?_c=jst-epaas&ts___=${timestamp}&am___=Save`; // 构建FormData const formData = new FormData(); formData.append('__VIEWSTATE', viewState); formData.append('__VIEWSTATEGENERATOR', viewStateGenerator); formData.append('owner_co_id', u_co_id); formData.append('authorize_co_id', u_co_id); formData.append('io_id', io_id); formData.append('_jt_page_size', '25'); formData.append('__CALLBACKID', 'JTable1'); // 获取回调数据 const callbackData = await fetchCallbackData(io_id); if (!callbackData) { console.error('获取回调数据失败'); return false; } // 构建__CALLBACKPARAM const callbackObject = { ...callbackData, remark: remark, io_id: io_id }; const callbackParam = { "Method": "Save", "Args": [JSON.stringify(callbackObject)] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await fetch(url, { method: 'POST', credentials: 'include', body: formData }); if (!response.ok) { console.error(`HTTP错误!状态:${response.status}`); return false; } const responseText = await response.text(); const responseJson = parseResponseData(responseText); if (responseJson.IsSuccess) { console.log('备注更新成功'); return true; } else { console.error('备注修改失败:', responseJson.ExceptionMessage || responseJson.Message); return false; } } catch (error) { console.error('修改备注时出错:', error.message); return false; } } // 添加一键移仓按钮 function addOneClickTransferButton() { // 查找目标元素 _jt_toolbar_left(与退货类型选择框在同一容器) const toolbarLeft = document.getElementById('_jt_toolbar_left'); if (toolbarLeft) { // 检查是否已经存在下架/移仓按钮 if (!document.getElementById('one-click-transfer-btn')) { // 创建容器来放置下架/移仓按钮、复选框、标签和日期选择器 const transferContainer = createEl('div', { id: 'one-click-transfer-container', className: '_jt_tool' }, ` display: flex; align-items: center; gap: 5px; padding: 5px; border-radius: 4px; flex-wrap: nowrap; height: 30px; flex-shrink: 0; `); // 创建下架/移仓按钮 const transferButton = createEl('span', { id: 'one-click-transfer-btn', className: '_jt_tool', onclick: async function() { if (!this.disabled) { this.disabled = true; notify('开始执行下架/移仓操作...', 'success'); try { // 1. 获取当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); if (!pickTypeSelect || !pickTypeSelect.value) { notify('请选择退货类型', 'warning'); this.disabled = false; return; } const selectedConfig = JSON.parse(pickTypeSelect.value); // 2. 获取退货数据 const returnData = await getReturnData(); if (returnData.length === 0) { notify('未找到退货数据', 'warning'); this.disabled = false; return; } // 3. 获取所有sku_id const skuIds = returnData.map(item => item.sku_id).filter(Boolean); if (skuIds.length === 0) { notify('未找到有效的sku_id', 'warning'); this.disabled = false; return; } // 4. 查询可移仓/下架数量 let availableStock = []; if (selectedConfig.rule_id === 'move_stock') { // 退供应商仓:查询可移仓数量 availableStock = await getAvailableStock(skuIds); // console.log('📊 可移仓数量查询结果:', availableStock); } else { // 其他退货类型:查询可下架数量 // 优先走SkuSearch(与表格显示同口径),失败时回退旧版查询 availableStock = await searchOffPickQty(skuIds, selectedConfig.filter_name, selectedConfig.rule_id); if (availableStock.length === 0) { console.warn('[ERP图片放大][一键下架]', 'SkuSearch未返回数据,回退到旧版仓位查询'); const skuPrefix = globalSupplierCode || skuIds[0].substring(0, 5); availableStock = await searchBin(skuPrefix, selectedConfig.bin); } } // 更新全局数据存储 globalBinResults = availableStock; // 5. 检查是否需要获取排除数量 let exclusionQuantities = {}; const isExcludeChecked = document.getElementById('exclude-quantity-checkbox').checked; if (isExcludeChecked) { const selectedDate = document.getElementById('exclude-date-picker').value; if (!selectedDate) { notify('请选择排除日期', 'warning'); this.disabled = false; return; } exclusionQuantities = await getExclusionQuantities(returnData, selectedDate); } // 6. 生成移仓/下架请求数组 const { moveRequestArray, errorInfo, hasQuantityShortage } = generateMoveRequestArray(returnData, availableStock, exclusionQuantities); // console.log('📊 移仓请求数组:', moveRequestArray); // console.log('📊 误差信息:', errorInfo); // console.log('📊 是否存在数量不足:', hasQuantityShortage); if (moveRequestArray.length === 0) { notify('没有可移仓/下架的商品', 'warning'); this.disabled = false; return; } // 7. 检查是否有误差 const hasError = errorInfo.length > 0; if (hasError) { // 显示误差信息对话框 const confirmed = await showErrorConfirmationWithDetails(errorInfo, hasQuantityShortage); if (!confirmed) { notify('操作已取消', 'info'); this.disabled = false; return; } // 如果存在数量不足的情况,禁止提交请求 if (hasQuantityShortage) { // 显示详细的错误提示和操作引导 showQuantityShortageError(errorInfo); this.disabled = false; return; } } // 9. 根据选择的退货类型发送不同的请求 let result; if (selectedConfig.rule_id === 'move_stock') { // 退供应商仓:发送移仓请求 result = await sendMoveRequest(moveRequestArray); } else { // 其他退货类型:发送下架请求 result = await confirmPick('returnOrder'); } if (result.success) { notify('操作成功完成', 'success'); // 提取io_id const io_id = getIoIdFromUrl(); if (io_id) { // 计算成功移仓数量和款数 const successInfo = calculateSuccessCount(moveRequestArray); if (successInfo.styleCount > 0) { // 生成备注内容 const pickTypeSelect = document.getElementById('pick-type-select'); const selectedConfig = JSON.parse(pickTypeSelect.value); const remark = generateRemark(selectedConfig.rule_id, successInfo); // 修改备注 updateRemark(io_id, remark).then(remarkResult => { if (remarkResult) { notify('备注更新成功', 'success'); } else { notify('备注更新失败', 'warning'); } }); } } // 点击刷新按钮 const reloadButton = document.getElementById('_jt_reload'); if (reloadButton) { reloadButton.click(); } else { } } else { notify(`操作失败: ${result.message}`, 'error'); } } catch (error) { // console.error('❌ 执行下架/移仓操作失败:', error.message); notify(`执行操作时发生错误: ${error.message}`, 'error'); } finally { this.disabled = false; } } } }); // 添加SVG图标 const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); svgIcon.setAttribute('width', '16'); svgIcon.setAttribute('height', '16'); svgIcon.setAttribute('viewBox', '0 0 24 24'); svgIcon.style.cssText = 'margin-right: 5px; vertical-align: middle;'; const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('fill', '#FF0000'); // 设置为红色 path.setAttribute('d', 'M13.409 2.513a3.75 3.75 0 0 0-2.818 0l-2.19.888l9.592 3.574l3.374-1.303a1.8 1.8 0 0 0-.46-.275zM22 7.191l-9.25 3.574v2.3A6.48 6.48 0 0 1 17.5 11c1.747 0 3.332.689 4.5 1.81zm-10.75 3.574v4.945a6.5 6.5 0 0 0-.25 1.791c0 .621.087 1.222.25 1.79v2.397a4 4 0 0 1-.659-.199l-7.498-3.04A1.75 1.75 0 0 1 2 16.827V7.192zM2.633 5.828L12 9.447l3.917-1.514l-9.543-3.71l-3.281 1.33q-.256.104-.46.275M23 17.5a5.5 5.5 0 1 0-11 0a5.5 5.5 0 0 0 11 0m-5.458-3.498l.086.015l.063.02l.068.035l.047.032l2.548 2.542l.057.07a.5.5 0 0 1-.695.695l-.07-.057L18 15.707v5.294l-.008.09a.5.5 0 0 1-.402.402l-.09.008l-.09-.008a.5.5 0 0 1-.402-.402L17 21l-.001-5.292l-1.645 1.646l-.07.057a.5.5 0 0 1-.568 0l-.07-.057l-.057-.07a.5.5 0 0 1 0-.568l.057-.07l2.513-2.512l.056-.045l.074-.042l.083-.03l.06-.012l.056-.005z'); svgIcon.appendChild(path); transferButton.appendChild(svgIcon); // 添加文本节点 const textNode = document.createTextNode('下架/移仓'); transferButton.appendChild(textNode); // 创建复选框(使用_jt_tool类) const excludeCheckbox = createEl('input', { id: 'exclude-quantity-checkbox', type: 'checkbox', className: '_jt_tool' }); // 创建复选框标签(使用_jt_tool类) const checkboxLabel = createEl('label', { htmlFor: 'exclude-quantity-checkbox', className: '_jt_tool' }); checkboxLabel.textContent = '排除日期'; // 创建时间选择器(使用_jt_tool类) const datePicker = createEl('input', { id: 'exclude-date-picker', type: 'date', className: '_jt_tool' }); // 设置默认值为今天 const today = new Date(); const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; // 从localStorage加载缓存的日期 const cachedDate = storage.get('erpExcludeDate', formattedDate); datePicker.value = cachedDate; // 添加事件监听器,保存日期到localStorage excludeCheckbox.addEventListener('change', function() { // 当勾选状态改变时,重新更新表格数据 updateTableData(); }); datePicker.addEventListener('change', function() { // 只存储日期,不存储勾选状态 storage.set('erpExcludeDate', this.value); // 当日期改变时,重新更新表格数据 if (excludeCheckbox.checked) { updateTableData(); } }); // 组装容器 - 调整顺序:下架/移仓按钮 -> 复选框 -> 标签 -> 日期选择器 transferContainer.appendChild(transferButton); transferContainer.appendChild(excludeCheckbox); transferContainer.appendChild(checkboxLabel); transferContainer.appendChild(datePicker); // 统一设置所有元素的样式 transferButton.style.cssText += '; display: flex; align-items: center; height: 24px;'; excludeCheckbox.style.cssText += '; display: flex; align-items: center; height: 24px; margin: 0; vertical-align: middle;'; checkboxLabel.style.cssText += '; display: flex; align-items: center; height: 24px; margin: 0; vertical-align: middle;'; datePicker.style.cssText += '; display: flex; align-items: center; height: 24px; margin: 0; vertical-align: middle; padding: 0 5px;'; // 确保整个容器的高度一致 transferContainer.style.height = '24px'; // 确保日期选择器在点击时不会改变布局 datePicker.addEventListener('focus', function() { this.style.position = 'relative'; this.style.zIndex = '1000'; }); datePicker.addEventListener('blur', function() { this.style.position = ''; this.style.zIndex = ''; }); // 添加到toolbarLeft的末尾,确保在退货类型模块之后 toolbarLeft.appendChild(transferContainer); // 删除指定的按钮 const buttonsToRemove = ['AddAllSupplierItems', 'btModifyCostPrice', 'SyncQty', 'AddItemsForTask', '$AppendTool']; buttonsToRemove.forEach(buttonId => { const button = document.getElementById(buttonId); if (button) { button.remove(); } }); } else { // 下架/移仓按钮已存在,跳过添加 } } else { // 未找到目标元素 _jt_toolbar_left } } // 添加退货类型下拉选择框 function addPickTypeSelect() { // 查找目标元素 _jt_toolbar_left const toolbarLeft = document.getElementById('_jt_toolbar_left'); if (toolbarLeft) { // 调整容器布局,确保所有元素水平平行排列 toolbarLeft.style.cssText = ` display: flex; align-items: center; justify-content: flex-start; gap: 10px; padding: 10px; flex-wrap: wrap; min-height: 40px; height: auto; align-content: flex-start; `; // 检查是否已经存在退货类型选择框 if (!document.getElementById('pick-type-select')) { // 创建容器来放置退货类型选择框 const pickTypeContainer = createEl('div', { id: 'pick-type-container', className: '_jt_tool' }, ` display: flex; align-items: center; gap: 5px; padding: 5px; border-radius: 4px; height: 24px; `); // 创建标签 const pickTypeLabel = createEl('label', { htmlFor: 'pick-type-select', className: '_jt_tool', style: 'display: flex; align-items: center; height: 24px; margin: 0px; vertical-align: middle; padding: 0px 5px;' }); pickTypeLabel.textContent = '退货类型:'; // 添加点击事件,触发刷新功能 pickTypeLabel.addEventListener('click', async function() { await handlePickTypeChange(); }); // 创建下拉选择框 const pickTypeSelect = createEl('select', { id: 'pick-type-select', className: '_jt_tool', style: 'display: flex; align-items: center; height: 24px; margin: 0px; vertical-align: middle; padding: 0px 5px;' }); // 创建加载状态 const loadingOption = createEl('option', { value: '', textContent: '加载中...' }); pickTypeSelect.appendChild(loadingOption); // 异步加载下拉选项 (async function() { try { const options = await getPickTypeOptions(); // 清空现有选项 pickTypeSelect.innerHTML = ''; // 从localStorage读取保存的选择 const savedPickType = storage.get('erpOneClickPickType'); // 添加新选项 options.forEach(config => { const option = createEl('option', { value: JSON.stringify(config), textContent: config.filter_name, selected: savedPickType && config.rule_id === savedPickType.rule_id }); pickTypeSelect.appendChild(option); }); // 添加退供应商仓选项 const transferOption = createEl('option', { value: JSON.stringify({ type: 0, label: '0:退供应商仓', filter_name: '退供应商仓', rule_id: 'move_stock', bin: '' }), textContent: '退供应商仓', selected: savedPickType && savedPickType.rule_id === 'move_stock' }); pickTypeSelect.appendChild(transferOption); // 绑定事件 pickTypeSelect.addEventListener('change', async function() { // 保存选择到localStorage if (this.value) { const selectedConfig = JSON.parse(this.value); storage.set('erpOneClickPickType', selectedConfig); } await handlePickTypeChange(); }); // 首次加载时执行一次查询 await handlePickTypeChange(); } catch (error) { // console.error('加载下架类型选项失败:', error); // 清空现有选项 pickTypeSelect.innerHTML = ''; // 添加默认选项 const defaultOptions = [ { type: 1, label: '1:P区下架', filter_name: 'P区纸质拣货(退样)', rule_id: '2548', bin: 'P' }, { type: 2, label: '2:1-1销退下架', filter_name: 'P-1-1纸质拣货(销退退供)', rule_id: '2592', bin: 'P-1-1' }, { type: 3, label: '3:1-4次品下架', filter_name: 'P-1-4纸质拣货(品控次品汇总)', rule_id: '2593', bin: 'P-1-4' }, { type: 4, label: '4:1-3次品下架', filter_name: 'P-1-3次品下架(当日退)', rule_id: '2613', bin: 'P-1-3' } ]; // 从localStorage读取保存的选择 const savedPickType = storage.get('erpOneClickPickType'); defaultOptions.forEach(config => { const option = createEl('option', { value: JSON.stringify(config), textContent: config.filter_name, selected: savedPickType && config.rule_id === savedPickType.rule_id }); pickTypeSelect.appendChild(option); }); // 添加退供应商仓选项 const transferOption = createEl('option', { value: JSON.stringify({ type: 0, label: '0:退供应商仓', filter_name: '退供应商仓', rule_id: 'move_stock', bin: '' }), textContent: '退供应商仓', selected: savedPickType && savedPickType.rule_id === 'move_stock' }); pickTypeSelect.appendChild(transferOption); // 绑定事件 pickTypeSelect.addEventListener('change', async function() { // 保存选择到localStorage if (this.value) { const selectedConfig = JSON.parse(this.value); storage.set('erpOneClickPickType', selectedConfig); } await handlePickTypeChange(); }); // 首次加载时执行一次查询 await handlePickTypeChange(); } })(); // 组装容器 pickTypeContainer.appendChild(pickTypeLabel); pickTypeContainer.appendChild(pickTypeSelect); // 添加到 _jt_toolbar_left 元素中 toolbarLeft.appendChild(pickTypeContainer); } else { } } else { } } // 获取退货类型选项 async function getPickTypeOptions() { try { // 获取cookie中的u_co_id const uCoId = getCookie('u_co_id'); if (!uCoId) { // console.error('❌ 未找到u_co_id cookie'); return []; } // 构建请求URL const timestamp = Date.now(); const url = `${window.location.origin}/app/wms/Wave/filterSku.aspx?type=12&title=%E9%99%90%E5%AE%9A%E6%9D%A1%E4%BB%B6%EF%BC%88%E4%B8%8B%E6%9E%B6%E4%BB%BB%E5%8A%A1%EF%BC%89&owner_co_id=${uCoId}&authorize_co_id=${uCoId}&_t=${timestamp}&_h=800px&_float=true`; // 发送GET请求 const response = await fetch(url, { method: 'GET', credentials: 'include' }); if (!response.ok) { // console.error(`❌ 获取下架类型选项失败: HTTP ${response.status}`); return []; } const html = await response.text(); // 解析HTML,提取选项 const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 查找rule_list元素 const ruleList = doc.querySelector('#rule_list'); if (!ruleList) { // console.error('❌ 未找到rule_list元素'); return []; } // 查找list元素 const listElement = ruleList.querySelector('#list'); if (!listElement) { // console.error('❌ 未找到list元素'); return []; } // 查找所有item元素 const items = listElement.querySelectorAll('.item'); // 提取选项 const options = []; items.forEach((item, index) => { const input = item.querySelector('input'); if (input) { const id = input.id; const value = input.value; if (id && value) { // 构建XPath查询表达式 const xpath = `//*[@id="data_${id}"]`; // 查找对应的元素 const dataElement = doc.evaluate(xpath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; let bin = ''; if (dataElement) { try { // 解析元素内容 const data = JSON.parse(dataElement.textContent); // 提取bin属性的值 bin = data.bin || ''; } catch (parseError) { // 解析失败时不做处理 } } const option = { type: index + 1, label: `${index + 1}:${value}`, filter_name: value, rule_id: id, bin: bin }; options.push(option); } } }); return options; } catch (error) { // console.error('获取下架类型选项失败:', error); // 返回默认选项,防止程序崩溃 return [ { type: 1, label: '1:P区下架', filter_name: 'P区纸质拣货(退样)', rule_id: '2548', bin: 'P' }, { type: 2, label: '2:1-1销退下架', filter_name: 'P-1-1纸质拣货(销退退供)', rule_id: '2592', bin: 'P-1-1' }, { type: 3, label: '3:1-4次品下架', filter_name: 'P-1-4纸质拣货(品控次品汇总)', rule_id: '2593', bin: 'P-1-4' }, { type: 4, label: '4:1-3次品下架', filter_name: 'P-1-3次品下架(当日退)', rule_id: '2613', bin: 'P-1-3' } ]; } } // 处理退货类型选择变更 async function handlePickTypeChange() { try { // 获取选中的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); if (!pickTypeSelect || !pickTypeSelect.value) { return; } const selectedConfig = JSON.parse(pickTypeSelect.value); // 更新表格数据(内部会按当前退货类型走SkuSearch重新查询库存与未审订单数) await updateTableData(); } catch (error) { console.error('[ERP图片放大][类型切换] 失败:', error.message); } } // 查询仓位信息 async function searchBin(skuId, bin) { try { // 无任何可用前缀时不发请求,避免空like查询返回错误数据 if (!globalSupplierCode && !skuId) { return []; } // 获取cookie中的u_co_id const uCoId = getCookie('u_co_id'); if (!uCoId) { // console.error('❌ 未找到u_co_id cookie,请确保您已登录ERP系统'); return []; } // 目标页面URL,用于获取VIEWSTATE const targetUrl = `${window.location.origin}/app/wms/Pack/PackItems.aspx`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const { viewState, viewStateGenerator } = await getViewStateFromUrl(targetUrl); if (!viewState || !viewStateGenerator) { // console.error('❌ 无法从目标页面提取VIEWSTATE或VIEWSTATEGENERATOR'); return []; } // 构建请求URL const requestTimestamp = Date.now(); const url = `${window.location.origin}/app/wms/Pack/PackItems.aspx?_c=jst-epaas&ts___=${requestTimestamp}&am___=LoadDataToJSON`; // 构建CALLBACKPARAM let filterConditions = [ {"k":"[pit].sku_id","v":globalSupplierCode || skuId,"c":"like"}, {"k":"[p].bin","v":bin,"c":bin.includes(',') ? "@rightlike" : "rightlike"}, {"k":"[p].wh_id","v":"0","c":">"} ]; const callbackParam = { Method: 'LoadDataToJSON', Args: [ '1', JSON.stringify(filterConditions), '{"sku_id":true}' ] }; // 构建请求参数 const form = new FormData(); form.append('__VIEWSTATE', viewState); form.append('__VIEWSTATEGENERATOR', viewStateGenerator); form.append('owner_co_id', uCoId); form.append('authorize_co_id', uCoId); form.append('_jt_page_size', '500'); form.append('__CALLBACKID', 'JTable1'); form.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送POST请求 const response = await fetch(url, { method: 'POST', credentials: 'include', body: form }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } // 处理响应数据 let responseText = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(responseText, { description: '仓位查询响应' }); if (!responseObj.ReturnValue) { // console.error('❌ 仓位查询 - 未找到ReturnValue字段'); return []; } const returnValue = responseObj.ReturnValue; if (!returnValue.datas || !Array.isArray(returnValue.datas)) { // console.error('❌ 仓位查询 - 未找到有效的datas数组'); return []; } // 解析JSON数据,提取datas数组中所有元素的sku_id、qty和whId字段 const items = returnValue.datas.map(item => ({ sku_id: item.sku_id, qty: item.qty || 0, whId: item.whId || '', modified1: item.modified1 || '' })); // 存储modified1信息到全局变量 globalModified1Map = new Map(); items.forEach(item => { if (item.sku_id && item.modified1) { globalModified1Map.set(item.sku_id, item.modified1); } }); // 返回查询结果 return items; } catch (error) { // 错误处理,不输出 return []; } } // 查询可下架/可退货数量(走ERP原生CreateOffPick SkuSearch接口) // 接口口径来自真实抓包:CreateOffPick.aspx 的 __CALLBACKID=ACall1 / Method=SkuSearch, // 按 RuleId+FilterName 规则计算,与ERP"解决误差"弹窗里搜索按钮的结果完全一致。 // 响应格式:0|{"IsSuccess":true,"ReturnValue":{"Items":[{"sku_id":"xx","qty":N,"wave_qty":N,...}]}} async function searchOffPickQty(skuIds, filterName, ruleId) { const TAG = '[ERP图片放大][SkuSearch]'; try { const uCoId = getCookie('u_co_id'); if (!uCoId) { console.warn(TAG, '缺少u_co_id cookie,跳过查询'); return []; } if (!Array.isArray(skuIds) || skuIds.length === 0) { console.warn(TAG, 'skuIds为空,跳过查询'); return []; } if (!filterName || !ruleId) { console.warn(TAG, 'filterName或ruleId缺失:', filterName, ruleId); return []; } // 从CreateOffPick弹窗页面获取VIEWSTATE const pageUrl = `${window.location.origin}/app/wms/Wave/CreateOffPick.aspx?owner_co_id=${uCoId}&authorize_co_id=${uCoId}&_t=${Date.now()}&_h=837px&_float=true`; const { viewState, viewStateGenerator, html: pageHtml } = await getViewStateFromUrl(pageUrl); if (!viewState || !viewStateGenerator) { console.warn(TAG, '获取VIEWSTATE失败(页面GET可能被重定向到登录页)'); return []; } // 从页面HTML解析当前仓库ID(用户抓包确认P-2-2流程原仓库为8,解析失败时兜底8) let whId = '8'; if (pageHtml) { const whSelMatch = pageHtml.match(/name="warehouse"[\s\S]{0,3000}?<\/select>/i); if (whSelMatch) { const selOpt = whSelMatch[0].match(/value="(\d+)"[^>]*selected/i) || whSelMatch[0].match(/selected[^>]*value="(\d+)"/i); if (selOpt) { whId = selOpt[1]; globalOffPickWhId = whId; } } } console.info(TAG, '使用仓库ID:', whId); // 构建请求URL const url = `${pageUrl}&ts___=${Date.now()}&am___=SkuSearch`; // 构建请求体(字段与真实抓包一一对应) const body = new URLSearchParams(); body.append('__VIEWSTATE', viewState); body.append('__VIEWSTATEGENERATOR', viewStateGenerator); body.append('warehouse', whId); body.append('defaultpack', 'warehouse_10_name'); body.append('filter_name', filterName); body.append('rule_id', String(ruleId)); body.append('i_id', ''); body.append('sku_id', ''); body.append('sku_name', ''); body.append('search_brand', ''); body.append('search_vc', ''); body.append('supplier', ''); body.append('supplier_id', ''); body.append('max_wave_sku_count', ''); body.append('sku_count', ''); body.append('__CALLBACKID', 'ACall1'); body.append('__CALLBACKPARAM', JSON.stringify({ Method: 'SkuSearch', Args: [JSON.stringify({ WhId: whId, SkuIds: skuIds, FilterName: filterName, RuleId: String(ruleId) }), null], CallControl: '{page}' })); const response = await fetch(url, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' }, body: body.toString() }); if (!response.ok) { console.warn(TAG, 'POST请求HTTP错误,状态:', response.status); return []; } // 解析响应(parseResponseData会自动剥掉"0|"前缀) const responseText = await response.text(); const responseObj = parseResponseData(responseText, { description: '可下架数量查询响应' }); const items = responseObj.ReturnValue && responseObj.ReturnValue.Items; if (!Array.isArray(items)) { console.warn(TAG, '响应中无ReturnValue.Items,响应前200字符:', responseText.substring(0, 200)); return []; } const result = items.map(item => ({ sku_id: item.sku_id, qty: item.qty || 0, wave_qty: item.wave_qty || 0 })); console.info(TAG, '查询成功:', JSON.stringify(result)); return result; } catch (error) { console.warn(TAG, '查询异常:', error.message); return []; } } // 查询可移仓数量 async function getAvailableStock(skuIds) { try { // 获取cookie中的u_co_id const uCoId = getCookie('u_co_id'); if (!uCoId) { // console.error('❌ 未找到u_co_id cookie,请确保您已登录ERP系统'); return []; } // 目标页面URL,用于获取VIEWSTATE const targetUrl = `${window.location.origin}/app/wms/Pack/PackItems.aspx`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const { viewState, viewStateGenerator } = await getViewStateFromUrl(targetUrl); if (!viewState || !viewStateGenerator) { // console.error('❌ 无法从目标页面提取VIEWSTATE或VIEWSTATEGENERATOR'); return []; } // 构建请求URL const requestTimestamp = Date.now(); const url = `${window.location.origin}/app/wms/Pack/PackItems.aspx?_c=jst-epaas&ts___=${requestTimestamp}&am___=LoadDataToJSON`; // 构建请求参数 const form = new FormData(); form.append('__VIEWSTATE', viewState); form.append('__VIEWSTATEGENERATOR', viewStateGenerator); form.append('owner_co_id', uCoId); form.append('authorize_co_id', uCoId); form.append('pack_type', 'wh_8'); form.append('_jt_page_size', '500'); form.append('__CALLBACKID', 'JTable1'); // 构建__CALLBACKPARAM const skuPrefix = globalSupplierCode || skuIds[0].substring(0, 5); const callbackParam = { "Method": "LoadDataToJSON", "Args": [ "1", `[{"k":"[pit].sku_id","v":"${skuPrefix}","c":"like"},{"k":"[p].bin","v":null,"c":"isempty"},{"k":"[p].wh_id","v":"8","c":"="},{"k":"[p].wh_id","v":"0","c":">"}]`, "{}" ] }; form.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送POST请求 const response = await fetch(url, { method: 'POST', credentials: 'include', body: form }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } // 处理响应数据 let responseText = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(responseText, { description: '可移仓数量查询响应' }); if (!responseObj.ReturnValue) { // console.error('❌ 可移仓数量查询 - 未找到ReturnValue字段'); return []; } let returnValue = responseObj.ReturnValue; // 处理ReturnValue为字符串的情况 if (typeof returnValue === 'string') { try { returnValue = JSON.parse(returnValue); } catch (e) { // console.error('❌ 解析ReturnValue失败:', e.message); return []; } } if (!returnValue.datas || !Array.isArray(returnValue.datas)) { // console.error('❌ 可移仓数量查询 - 未找到有效的datas数组'); return []; } // 解析JSON数据,提取数组中所有元素的sku_id、qty和modified1字段 const items = returnValue.datas.map(item => ({ sku_id: item.sku_id, qty: item.qty || 0, whId: '', modified1: item.modified1 || '' })); // 存储modified1信息到全局变量 globalModified1Map = new Map(); items.forEach(item => { if (item.sku_id && item.modified1) { globalModified1Map.set(item.sku_id, item.modified1); } }); // 返回查询结果 return items; } catch (error) { // 错误处理,不输出 return []; } } // 监控页面刷新 function monitorPageRefresh() { // 监听页面加载完成事件 window.addEventListener('load', async function() { // 延迟执行,确保页面完全加载 setTimeout(async function() { // 检查退货类型选择框是否存在 const pickTypeSelect = document.getElementById('pick-type-select'); if (pickTypeSelect) { await handlePickTypeChange(); } }, 1000); }); // 监听哈希变化(某些页面刷新可能通过哈希变化实现) window.addEventListener('hashchange', async function() { await handlePickTypeChange(); }); } // 页面加载完成后初始化 // 移除重复的事件监听器,避免重复执行 // window.addEventListener('load', init); /*********************** * 一键移仓功能 ***********************/ // 获取排除调拨入数量 async function getExclusionQuantities(returnData, startDate) { try { // 获取当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); let selectedConfig = null; if (pickTypeSelect && pickTypeSelect.value) { selectedConfig = JSON.parse(pickTypeSelect.value); } // 检查是否选择了退供应商仓选项 if (selectedConfig && selectedConfig.rule_id === 'move_stock') { // 退供应商仓:使用原功能发送的请求 return await ApiClient.getExclusionQuantities(returnData, startDate); } else { // 其他退货类型:查询下架的排除数量 return await getPickExclusionQuantities(returnData, startDate, selectedConfig); } } catch (error) { // console.error('获取排除数量失败:', error); return {}; } } // 缺失的函数定义 async function getSupplierCode() { try { const u_co_id = getCookie('u_co_id'); if (!u_co_id) { // console.error('❌ 未找到u_co_id cookie'); return ''; } // 从当前页面URL获取io_id const url = new URL(window.location.href); const io_id = url.searchParams.get('io_id'); if (!io_id) { // console.error('❌ 未找到io_id参数'); return ''; } // 构建请求URL const requestUrl = `${window.location.origin}/app/scm/purchaseout/purchaseout.aspx?io_id=${io_id}&owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}`; // 发送GET请求 const response = await fetch(requestUrl, { credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return ''; } const html = await response.text(); // 尝试从HTML中提取supplier_code const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 查找包含supplier_code的元素 const supplierCodeElements = doc.querySelectorAll('[id*="supplier_code"], [name*="supplier_code"]'); for (const element of supplierCodeElements) { if (element.value) { return element.value; } } // 尝试从_jt_data中提取 const jtDataMatch = html.match(/
]*>([\s\S]*?)<\/div>/); if (jtDataMatch) { try { const jtData = JSON.parse(jtDataMatch[1]); if (jtData.datas && jtData.datas.length > 0) { const firstItem = jtData.datas[0]; if (firstItem.supplier_code) { return firstItem.supplier_code; } } } catch (parseError) { // console.error('❌ 解析_jt_data失败:', parseError); } } return ''; } catch (error) { // console.error('❌ 获取supplier_code失败:', error); return ''; } } // 获取下架的排除数量 async function getPickExclusionQuantities(returnData, startDate, selectedConfig) { try { // 提取第一个SKU ID并取前五位 const firstSku = returnData.find(item => item.sku_id); if (!firstSku) { // console.error('❌ 未找到有效的SKU ID'); return {}; } const skuPrefix = firstSku.sku_id.substring(0, 5); // 构建请求URL const url = `${window.location.origin}/app/item/skusn/Search.aspx`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateUrl = `${window.location.origin}/app/item/skusn/Search.aspx`; const viewStateData = await getViewStateFromUrl(viewStateUrl); // 计算结束日期(当天) const today = new Date(); const endDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')} 23:59:59.998`; // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('match_form', '0'); formData.append('sku_id', skuPrefix); formData.append('created', startDate); formData.append('created', endDate); formData.append('type_name', '移货,下架,上架,一键上架'); formData.append('type', 'MoveSku,OffShelve,Shelve,一键上架'); formData.append('_jt_page_size', '500'); formData.append('__CALLBACKID', 'JTable1'); // 构建CALLBACKPARAM const callbackParam = { "Method":"LoadDataToJSON", "Args":["1",JSON.stringify([ {"k":"sku_id","v":skuPrefix,"c":"like"}, {"k":"created","v":startDate,"c":">=","t":"date"}, {"k":"created","v":endDate,"c":"<=","t":"date"}, {"k":"type","v":"MoveSku,OffShelve,Shelve,一键上架","c":"in"}, {"k":"match_form","v":"0","c":"="} ]),"{}"] }; formData.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送请求 const response = await fetch(url, { method: 'POST', headers: { 'gwfp': getCookie('gwfp') || '', 'u_sso_token': getCookie('u_sso_token') || '', 'webbox-request-id': generateUUID(), 'Cookie': document.cookie }, body: formData, credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } // 解析响应 let text = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(text, { description: 'API请求响应' }); if (responseObj.ReturnValue) { const returnValue = responseObj.ReturnValue; if (returnValue.datas && Array.isArray(returnValue.datas)) { // 构建排除数量映射 const excludeQtyMap = {}; returnValue.datas.forEach(item => { const sku = item.sku_id; if (sku) { const qty = parseFloat(item.qty) || 0; // 根据type类型处理数量 if (item.type === '下架') { // 下架类型数量记为负数 excludeQtyMap[sku] = (excludeQtyMap[sku] || 0) - qty; } else { // 其他类型数量记为正数 excludeQtyMap[sku] = (excludeQtyMap[sku] || 0) + qty; } } }); return excludeQtyMap; } } return {}; } catch (error) { // console.error('获取下架排除数量失败:', error); return {}; } } // 获取退货数据 async function getReturnData() { try { const currentUrl = window.location.href; const response = await fetch(currentUrl, { credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } const html = await response.text(); // 提取_jt_data let jtData; const divMatch = html.match(/
]*>([\s\S]*?)<\/div>/); if (divMatch) { jtData = JSON.parse(divMatch[1]); } else { const scriptMatch = html.match(/_jt_data\s*=\s*({[\s\S]*?});/); if (!scriptMatch) { // console.error('❌ 未找到_jt_data'); return []; } jtData = JSON.parse(scriptMatch[1]); } // 提取datas中的sku_id和qty if (jtData.datas && Array.isArray(jtData.datas)) { const returnData = jtData.datas .filter(item => item.sku_id) .map(item => ({ sku_id: item.sku_id, qty: parseFloat(item.qty) || 0 })); return returnData; } return []; } catch (error) { // console.error('❌ 获取退货数据失败:', error.message); return []; } } // 确认下架 async function confirmPick(pickMode = 'fullBin') { try { // 检查是否有可下架数据 if (globalBinResults.length === 0) { return { success: false, message: '没有可下架的数据,请先等表格库存列加载完成' }; } // 检查是否有退货数据(仅退货单下架模式需要) if (pickMode === 'returnOrder') { const returnData = await getReturnData(); if (returnData.length === 0) { return { success: false, message: '没有退货数据,无法执行退货单下架' }; } } // 构建请求URL const timestamp = Date.now(); const host = window.location.origin; const u_co_id = getCookie('u_co_id') || '12816726'; const targetUrl = `${host}/app/wms/Wave/CreateOffPick.aspx?owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}&_t=${timestamp}&_h=818px&_float=true`; // 从目标URL获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateData = await getViewStateFromUrl(targetUrl); const viewState = viewStateData.viewState; const viewStateGenerator = viewStateData.viewStateGenerator; if (!viewState || !viewStateGenerator) { return { success: false, message: '无法获取下架页面的VIEWSTATE参数,请刷新页面后重试' }; } // 从globalBinResults中提取原仓库ID // 优先级:查询结果自带的whId > searchOffPickQty解析的仓库ID > 兜底'8' // (修复:SkuSearch结果无whId字段,旧代码兜底'10'是目标仓库ID,导致下架请求仓库错误) let whId = ''; if (globalBinResults && globalBinResults.length > 0) { // 从第一个datas中的whId字段提取原仓库ID const firstItem = globalBinResults[0]; whId = firstItem.whId || ''; if (!whId) { whId = globalOffPickWhId || '8'; } } else { whId = globalOffPickWhId || '8'; } // 构建Items对象 const items = {}; if (pickMode === 'returnOrder') { // 退货单下架:使用退货数据中的数量 const returnData = await getReturnData(); // 将可下架数据转换为Map,方便查找 const pickMap = new Map(); globalBinResults.forEach(item => { pickMap.set(item.sku_id || item.sku, item.qty); }); // 遍历退货数据,使用退货数量,但不超过可下架数量 returnData.forEach(item => { const sku = item.sku_id; const returnQty = parseInt(item.qty); const pickQty = pickMap.get(sku) || 0; // 使用退货数量,但不超过可下架数量 const finalQty = Math.min(returnQty, pickQty); if (finalQty > 0) { items[sku] = finalQty.toString(); } }); } else { // 整仓位下架:使用可下架数据中的全部数量 globalBinResults.forEach(item => { const sku = item.sku_id || item.sku; if (sku) { items[sku] = item.qty.toString(); } }); } // 检查是否有要下架的商品 if (Object.keys(items).length === 0) { return { success: false, message: '没有匹配的商品可下架' }; } // 获取当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); if (!pickTypeSelect || !pickTypeSelect.value) { return { success: false, message: '请选择退货类型' }; } const selectedConfig = JSON.parse(pickTypeSelect.value); // 构建CALLBACKPARAM const callbackParam = { Method: 'Confirm', Args: [ JSON.stringify({ Items: items, WhId: whId, Warehouse: `warehouse_${whId}_name`, MaxWaveSkuCount: '', SplitSkuCount: '', remark: '', filtername: selectedConfig.filter_name, RuleId: selectedConfig.rule_id }) ], CallControl: '{page}' }; // 构建最终请求URL const requestTimestamp = Date.now(); const url = `${targetUrl}&ts___=${requestTimestamp}&am___=Confirm`; // 构建请求参数 const form = new FormData(); form.append('__VIEWSTATE', viewState); form.append('__VIEWSTATEGENERATOR', viewStateGenerator); // 设置defaultpack为下架目标仓库 const targetWhId = '10'; // 下架目标仓库ID let defaultpack = `warehouse_${targetWhId}_name`; let warehouseId = targetWhId; // 修改callbackParam中的Warehouse和WhId // WhId: 原仓库ID // Warehouse: 目标仓库信息 callbackParam.Args[0] = JSON.stringify({ Items: items, WhId: whId, // 原仓库ID Warehouse: defaultpack, // 目标仓库信息 MaxWaveSkuCount: '', SplitSkuCount: '', remark: '', filtername: selectedConfig.filter_name, RuleId: selectedConfig.rule_id }); form.append('warehouse', warehouseId); form.append('defaultpack', defaultpack); form.append('filter_name', selectedConfig.filter_name); form.append('rule_id', selectedConfig.rule_id); form.append('__CALLBACKID', 'ACall1'); form.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送POST请求并解析响应 const responseData = await fetchJsonSafe(url, { method: 'POST', credentials: 'include', body: form }); // 输出响应信息 // 检查响应是否成功 const isSuccess = responseData.ReturnValue === true || responseData.Success === true; if (!isSuccess) { console.error('[ERP图片放大][确认下架] 服务器返回失败:', JSON.stringify(responseData).substring(0, 300)); const serverMsg = (responseData && (responseData.Message || responseData.message || (responseData.ReturnValue && typeof responseData.ReturnValue === 'string' ? responseData.ReturnValue : ''))) || '服务器拒绝了下架请求'; return { success: false, message: serverMsg }; } return { success: true, message: '下架操作成功' }; } catch (error) { // console.error('确认下架失败:', error); return { success: false, message: error.message }; } } // 确认下架(用于上架前的下架操作) async function confirmOffShelve(items, targetWhId) { try { // 构建请求URL const timestamp = Date.now(); const host = window.location.origin; const u_co_id = getCookie('u_co_id') || '12816726'; const targetUrl = `${host}/app/wms/Wave/CreateOffPick.aspx?owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}&_t=${timestamp}&_h=818px&_float=true`; // 从目标URL获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateData = await getViewStateFromUrl(targetUrl); const viewState = viewStateData.viewState; const viewStateGenerator = viewStateData.viewStateGenerator; if (!viewState || !viewStateGenerator) { throw new Error('无法获取目标页面VIEWSTATE参数,请刷新页面后重试'); } // 构建Items对象 const itemsObj = {}; items.forEach(item => { itemsObj[item.sku_id] = item.qty.toString(); }); // 检查是否有要下架的商品 if (Object.keys(itemsObj).length === 0) { throw new Error('没有匹配的商品可下架'); } // 获取当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); if (!pickTypeSelect || !pickTypeSelect.value) { throw new Error('请选择退货类型'); } const selectedConfig = JSON.parse(pickTypeSelect.value); // 构建CALLBACKPARAM const callbackParam = { Method: 'Confirm', Args: [ JSON.stringify({ Items: itemsObj, WhId: '8', // 原仓库ID,固定为8 Warehouse: `warehouse_${targetWhId}_name`, MaxWaveSkuCount: '', SplitSkuCount: '', remark: '', filtername: selectedConfig.filter_name, RuleId: selectedConfig.rule_id }) ], CallControl: '{page}' }; // 构建最终请求URL const requestTimestamp = Date.now(); const url = `${targetUrl}&ts___=${requestTimestamp}&am___=Confirm`; // 构建请求参数 const form = new FormData(); form.append('__VIEWSTATE', viewState); form.append('__VIEWSTATEGENERATOR', viewStateGenerator); // 设置defaultpack为下架目标仓库 let defaultpack = `warehouse_${targetWhId}_name`; let warehouseId = targetWhId; form.append('warehouse', warehouseId); form.append('defaultpack', defaultpack); form.append('filter_name', selectedConfig.filter_name); form.append('rule_id', selectedConfig.rule_id); form.append('__CALLBACKID', 'ACall1'); form.append('__CALLBACKPARAM', JSON.stringify(callbackParam)); // 发送POST请求并解析响应 const responseData = await fetchJsonSafe(url, { method: 'POST', credentials: 'include', body: form }); // 检查响应是否成功 const isSuccess = responseData.ReturnValue === true || responseData.Success === true; if (!isSuccess) { throw new Error(`确认下架失败,服务器返回错误: ${JSON.stringify(responseData)}`); } return { success: true, message: '下架操作成功' }; } catch (error) { // console.error('❌ 下架请求失败:', error.message); return { success: false, message: error.message || '下架操作失败' }; } } // 收集勾选框信息 function collectCheckboxInfo() { const checkboxInfoList = []; // 直接获取页面上所有的INPUT元素 const allInputs = document.querySelectorAll('input[type="checkbox"], input[type="radio"]'); allInputs.forEach(input => { // 检查后面是否有相关元素 let relatedContent = ''; let nextNode = input.nextSibling; // 查找后续节点中的文本内容 while (nextNode) { if (nextNode.nodeType === Node.TEXT_NODE && nextNode.textContent.trim()) { relatedContent = nextNode.textContent.trim(); break; } else if (nextNode.nodeType === Node.ELEMENT_NODE) { const text = nextNode.textContent.trim(); if (text) { relatedContent = text; break; } } nextNode = nextNode.nextSibling; } if (relatedContent) { // 收集勾选框信息 checkboxInfoList.push({ content: relatedContent, id: input.id || '无ID', type: input.type, checked: input.checked }); } }); return checkboxInfoList; } /** * 获取当前页面第一个选择的元素ID * @returns {string} 第一个选中元素的ID */ function getFirstSelectedElementId() { // 收集所有已勾选的元素 const checkboxInfo = collectCheckboxInfo(); const checkedElements = checkboxInfo.filter(info => info.checked); if (checkedElements.length > 0) { return checkedElements[0].id; } return ''; } /** * 从元素ID中提取仓库ID * @param {string} elementId - 元素ID * @returns {string} 提取到的仓库ID */ function extractWarehouseId(elementId) { if (!elementId) return ''; // 尝试1: 从ID中提取_之后的数字 const whIdMatch = elementId.match(/_(\d+)$/); if (whIdMatch) { return whIdMatch[1]; } // 尝试2: 如果ID以'wh'开头,剪切掉'wh'前缀 else if (elementId.toLowerCase().startsWith('wh')) { return elementId.substring(2); } // 尝试3: 直接使用ID作为仓库ID(如果是数字) else if (/^[0-9]+$/.test(elementId)) { return elementId; } // 尝试4: 从元素文本中提取数字 else { // 查找对应的元素并获取文本 const element = document.getElementById(elementId); if (element) { // 检查下一个兄弟节点的文本 let nextNode = element.nextSibling; let relatedText = ''; while (nextNode) { if (nextNode.nodeType === Node.TEXT_NODE && nextNode.textContent.trim()) { relatedText = nextNode.textContent.trim(); break; } else if (nextNode.nodeType === Node.ELEMENT_NODE) { relatedText = nextNode.textContent.trim(); break; } nextNode = nextNode.nextSibling; } // 从文本中提取数字 const textWhIdMatch = relatedText.match(/[0-9]+/); if (textWhIdMatch) { return textWhIdMatch[0]; } } } return ''; } // 生成退供应商仓请求数组和误差信息 function generateMoveRequestArray(returnData, availableStock, exclusionQuantities = {}) { const availableStockMap = new Map(); availableStock.forEach(item => { availableStockMap.set(item.sku_id || item.sku, item.qty); }); const moveRequestArray = []; const errorInfo = []; returnData.forEach(item => { const skuId = item.sku_id; const returnQty = item.qty; const availableQty = availableStockMap.get(skuId) || 0; const exclusionQty = exclusionQuantities[skuId] || 0; // 计算调整后退供应商仓数量 const adjustedAvailableQty = Math.max(0, availableQty - exclusionQty); if (adjustedAvailableQty > 0) { // 退供应商仓数量大于等于退货数量,取退货数量;反之取退供应商仓数量 const moveQty = adjustedAvailableQty >= returnQty ? returnQty : adjustedAvailableQty; moveRequestArray.push({ skuId: skuId, qty: moveQty.toString() }); // 检查是否有误差 if (returnQty !== adjustedAvailableQty) { errorInfo.push({ sku_id: skuId, return_qty: returnQty, available_qty: availableQty, exclusion_qty: exclusionQty, adjusted_available_qty: adjustedAvailableQty, move_qty: moveQty, error_type: adjustedAvailableQty < returnQty ? '数量不足' : '数量超额' }); } } else { // 退供应商仓数量为0,也视为误差 errorInfo.push({ sku_id: skuId, return_qty: returnQty, available_qty: availableQty, exclusion_qty: exclusionQty, adjusted_available_qty: 0, move_qty: 0, error_type: '无可用数量' }); } }); // 检查可退货数组中存在记录但页面退货数量为0的情况 availableStock.forEach(item => { const skuId = item.sku_id || item.sku; const availableQty = item.qty; const exclusionQty = exclusionQuantities[skuId] || 0; const adjustedAvailableQty = Math.max(0, availableQty - exclusionQty); // 检查是否在退货数据中存在 const isInReturnData = returnData.some(returnItem => returnItem.sku_id === skuId); if (!isInReturnData && adjustedAvailableQty > 0) { errorInfo.push({ sku_id: skuId, return_qty: 0, available_qty: availableQty, exclusion_qty: exclusionQty, adjusted_available_qty: adjustedAvailableQty, move_qty: 0, error_type: '退货数量为0' }); } }); // 检查是否存在数量不足的情况 const hasQuantityShortage = errorInfo.some(error => error.error_type === '数量不足' || error.error_type === '无可用数量'); return { moveRequestArray, errorInfo, hasQuantityShortage }; } // 发送移仓请求 async function sendMoveRequest(moveRequestArray) { try { const host = window.location.origin; const url = `${host}/app/wms/Pack/MoveStock.aspx?ts___=${Date.now()}&am___=Move`; // 获取VIEWSTATE和VIEWSTATEGENERATOR const viewStateData = await getViewStateFromUrl(`${host}/app/wms/Pack/MoveStock.aspx`); // 构建表单数据 const formData = new FormData(); formData.append('__VIEWSTATE', viewStateData.viewState); formData.append('__VIEWSTATEGENERATOR', viewStateData.viewStateGenerator); formData.append('move_from', '912816726000008000'); formData.append('move_to', '912816726000010000'); formData.append('txt_sku_id', moveRequestArray.map(item => item.skuId).join(',')); // 添加多个sku_id字段 moveRequestArray.forEach(() => { formData.append('sku_id', 'true'); }); formData.append('__CALLBACKID', 'ACall1'); formData.append('__CALLBACKPARAM', JSON.stringify({ "Method": "Move", "Args": [JSON.stringify(moveRequestArray)], "CallControl": "{page}" })); // 发送请求 const response = await fetch(url, { method: 'POST', credentials: 'include', mode: 'cors', body: formData }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } const responseText = await response.text(); // 使用统一的响应信息解析处理方法 const responseObj = parseResponseData(responseText, { description: '移仓请求响应' }); if (responseObj.success || !responseObj.error) { return { success: true, message: '移仓操作成功' }; } else { return { success: false, message: responseObj.error || '移仓操作失败' }; } } catch (error) { // console.error('❌ 发送移仓请求失败:', error.message); throw error; } } // 从指定URL获取VIEWSTATE和VIEWSTATEGENERATOR async function getViewStateFromUrl(url) { try { const response = await fetch(url, { credentials: 'include', mode: 'cors' }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return { viewState: '', viewStateGenerator: '' }; } const html = await response.text(); // 提取VIEWSTATE(兼容多种格式:name在前/后、id+value等) let viewState = ''; const viewStateMatch = html.match(/id="__VIEWSTATE"\s+value="([^"]+)"/i) || html.match(/name="__VIEWSTATE"[^>]*value="([^"]+)"/i) || html.match(/value="([^"]+)"[^>]*name="__VIEWSTATE"/i); if (viewStateMatch) { viewState = viewStateMatch[1]; } // 提取VIEWSTATEGENERATOR(兼容多种格式) let viewStateGenerator = ''; const viewStateGeneratorMatch = html.match(/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/i) || html.match(/name="__VIEWSTATEGENERATOR"[^>]*value="([^"]+)"/i) || html.match(/value="([^"]+)"[^>]*name="__VIEWSTATEGENERATOR"/i); if (viewStateGeneratorMatch) { viewStateGenerator = viewStateGeneratorMatch[1]; } return { viewState, viewStateGenerator, html }; } catch (error) { // console.error('❌ 获取VIEWSTATE失败:', error.message); return { viewState: '', viewStateGenerator: '', html: '' }; } } // 显示详细的误差信息并让用户确认 function showErrorConfirmationWithDetails(errorInfo, hasQuantityShortage = false, moveRequestArray = []) { return new Promise((resolve) => { // 创建确认窗口 const confirmationWindow = createEl('div', { id: 'move-stock-confirmation' }, ` position: fixed; bottom: 10px; right: 10px; width: 400px; background: white; border: 2px solid #4A90E2; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 99999; font-family: 'Microsoft YaHei', Arial, sans-serif; `); // 创建标题 const title = createEl('div', { textContent: hasQuantityShortage ? '数量不足错误' : '移仓数量确认' }, ` padding: 12px; background-color: ${hasQuantityShortage ? '#ff4d4f' : '#4A90E2'}; color: white; font-weight: bold; font-size: 14px; border-radius: 6px 6px 0 0; `); confirmationWindow.appendChild(title); // 创建内容 const content = createEl('div', {}, ` padding: 10px; max-height: 250px; overflow-y: auto; `); let html = hasQuantityShortage ? '
以下商品的退货数量大于可移仓数量,请解决误差后再操作:
' : '
以下商品的移仓数量与退货数量存在误差,请确认:
'; html += ''; // 在数量不足的情况下,只显示数量不足的信息 const filteredErrorInfo = hasQuantityShortage ? errorInfo.filter(item => item.error_type === '数量不足' || item.error_type === '无可用数量') : errorInfo; filteredErrorInfo.forEach(item => { const skuId = item.sku_id; const returnQty = item.return_qty; const adjustedAvailableQty = item.adjusted_available_qty || 0; const moveQty = item.move_qty; // 当实际移仓数量为0时,显示modified1字段的值 let displayMoveQty = moveQty; if (moveQty === 0) { const modified1 = globalModified1Map.get(skuId) || ''; displayMoveQty = modified1 || ''; } // 计算上架数量:可移仓数量 - 退货数量 const shelfQty = Math.max(0, adjustedAvailableQty - returnQty); html += ``; }); html += '
商品编码退货数可移数移仓数量
${skuId}${returnQty}${adjustedAvailableQty}${displayMoveQty}
'; content.innerHTML = html; confirmationWindow.appendChild(content); // 为List按钮添加点击事件监听器 const listButton = content.querySelector('#list-button'); if (listButton) { listButton.addEventListener('click', async function() { // 获取用户输入的上架数量 const itemsWithUserShelfQty = []; const table = content.querySelector('table'); if (table) { const rows = table.querySelectorAll('tr:not(:first-child)'); rows.forEach((row, index) => { const cells = row.querySelectorAll('td'); if (cells.length >= 5) { const skuId = cells[0].textContent.trim(); const inputElement = cells[4].querySelector('input[type="text"]'); if (inputElement) { const userShelfQty = parseInt(inputElement.value) || 0; // 查找对应的商品信息 const item = filteredErrorInfo.find(item => item.sku_id === skuId); if (item) { itemsWithUserShelfQty.push({ ...item, userShelfQty: userShelfQty }); } } } }); } // 过滤出上架数量大于0的商品 const itemsWithPositiveShelfQty = itemsWithUserShelfQty.filter(item => item.userShelfQty > 0); if (itemsWithPositiveShelfQty.length === 0) { notify('没有需要上架的商品', 'info'); return; } // 显示上架信息模态窗口 showShelveModal(itemsWithPositiveShelfQty, confirmationWindow, moveRequestArray); }); } // 创建按钮容器 const buttonContainer = createEl('div', {}, ` padding: 10px; display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid #eee; `); // 创建取消按钮 const cancelButton = createEl('button', { textContent: '取消', onclick: function() { confirmationWindow.remove(); resolve(false); } }, ` padding: 6px 12px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; font-family: 'Microsoft YaHei', Arial, sans-serif; font-size: 13px; `); buttonContainer.appendChild(cancelButton); // 只有在非数量不足的情况下才显示确认按钮 if (!hasQuantityShortage) { // 创建确认按钮 const confirmButton = createEl('button', { textContent: '确认移仓', onclick: function() { confirmationWindow.remove(); resolve(true); } }, ` padding: 6px 12px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-family: 'Microsoft YaHei', Arial, sans-serif; font-size: 13px; `); buttonContainer.appendChild(confirmButton); } else { // 数量不足的情况下,修改取消按钮为关闭按钮 cancelButton.textContent = '关闭'; // 重新绑定点击事件,确保返回false cancelButton.onclick = function() { confirmationWindow.remove(); resolve(false); }; } confirmationWindow.appendChild(buttonContainer); document.body.appendChild(confirmationWindow); }); } // 显示上架信息模态窗口 function showShelveModal(itemsWithPositiveShelfQty, parentWindow, moveRequestArray) { // 创建模态窗口 const modalWindow = createEl('div', { id: 'shelve-modal' }, ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 99999; display: flex; justify-content: center; align-items: center; `); // 创建模态内容 const modalContent = createEl('div', {}, ` background: white; border-radius: 8px; padding: 20px; width: 400px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); `); // 创建标题 const title = createEl('h3', { textContent: '上架信息确认' }, ` margin-top: 0; margin-bottom: 15px; color: #333; `); modalContent.appendChild(title); // 创建商品列表 const productList = createEl('div', {}, ` max-height: 200px; overflow-y: auto; margin-bottom: 20px; `); itemsWithPositiveShelfQty.forEach(item => { const productItem = createEl('div', {}, ` padding: 8px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; `); const modified1 = globalModified1Map.get(item.sku_id) || ''; productItem.innerHTML = ` ${item.sku_id} ${modified1} 数量: ${item.userShelfQty} `; productList.appendChild(productItem); }); modalContent.appendChild(productList); // 创建按钮容器 const buttonContainer = createEl('div', {}, ` display: flex; justify-content: flex-end; gap: 10px; `); // 创建Cancel按钮 const cancelButton = createEl('button', { textContent: '取消', onclick: function() { modalWindow.remove(); } }, ` padding: 8px 16px; background-color: #6c757d; color: white; border: none; border-radius: 4px; cursor: pointer; `); buttonContainer.appendChild(cancelButton); // 创建上架后移仓按钮 const listTransferButton = createEl('button', { textContent: '上架后移仓', onclick: async function() { try { // 处理上架操作 await processShelveOperation(itemsWithPositiveShelfQty); // 关闭模态窗口 modalWindow.remove(); // 关闭错误确认窗口 if (parentWindow) { parentWindow.remove(); } // 延迟一下,确保上架操作完全完成和页面状态稳定 await new Promise(resolve => setTimeout(resolve, 2000)); // 自动启动仓库转移操作:点击页面中的下架/移仓按钮 const transferButton = document.getElementById('one-click-transfer-btn'); if (transferButton) { // 确保按钮未被禁用 if (!transferButton.disabled) { // 触发点击事件 transferButton.click(); } else { // 如果按钮被禁用,先启用它,然后再点击 transferButton.disabled = false; await new Promise(resolve => setTimeout(resolve, 500)); transferButton.click(); } } else { notify('未找到下架/移仓按钮,请手动执行移仓操作', 'warning'); } notify('上架操作成功完成', 'success'); } catch (error) { notify(`操作失败: ${error.message}`, 'error'); } } }, ` padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; `); buttonContainer.appendChild(listTransferButton); modalContent.appendChild(buttonContainer); modalWindow.appendChild(modalContent); document.body.appendChild(modalWindow); } // 处理上架操作 async function processShelveOperation(itemsWithPositiveShelfQty) { // 获取cookie中的u_co_id和u_id const u_co_id = getCookie('u_co_id'); const u_id = getCookie('u_id'); if (!u_co_id || !u_id) { throw new Error('无法获取用户信息,请重新登录'); } // 获取当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); let selectedPickType = null; if (pickTypeSelect && pickTypeSelect.value) { selectedPickType = JSON.parse(pickTypeSelect.value); } // 检查是否为退供应商仓 const isReturnToSupplier = selectedPickType && selectedPickType.rule_id === 'move_stock'; // 如果不是退供应商仓,先执行下架操作 if (!isReturnToSupplier) { // 构建下架请求数据 const offShelveItems = itemsWithPositiveShelfQty.map(item => ({ sku_id: item.sku_id || item.skuId, qty: item.userShelfQty })); // 执行下架操作 await confirmOffShelve(offShelveItems, '8'); // 目标仓库targetWhId设定为8 // 延迟一下,确保下架操作完全完成 await new Promise(resolve => setTimeout(resolve, 500)); } const skuIds = itemsWithPositiveShelfQty.map(item => item.sku_id || item.skuId).join(','); // 第一个API请求 - 获取商品数据 const firstApiUrl = `https://api.erp321.com/erp/webapi/WmsApi/SkuBinQuery/GetShelveFromPackItems?owner_co_id=${u_co_id}&owner_co_id=${u_co_id}`; // 构建请求headers(cookie由credentials: 'include'自动携带, // 浏览器禁止在fetch中手动设置Cookie头,写了也会被忽略) const headers = { 'Content-Type': 'application/json' }; // 添加其他可能需要的cookie值 const gwfp = getCookie('gwfp'); const u_sso_token = getCookie('u_sso_token'); const u_account = getCookie('u_account'); const u_name = getCookie('u_name'); if (gwfp) headers['gwfp'] = gwfp; if (u_sso_token) headers['u_sso_token'] = u_sso_token; if (u_account) headers['u_account'] = u_account; if (u_name) headers['u_name'] = u_name; const firstResponse = await fetch(firstApiUrl, { method: 'POST', headers: headers, credentials: 'include', mode: 'cors', body: JSON.stringify({ coid: u_co_id, data: { pageIndex: 1, pageSize: 500, shelveFromPackType: "customize_3", skuIds: skuIds, whId: 8 }, ip: "", uid: u_id }) }); if (!firstResponse.ok) { throw new Error(`HTTP错误!状态:${firstResponse.status}`); } const firstResponseData = await firstResponse.json(); // 处理响应数据 if (!firstResponseData.data) { throw new Error('响应数据中没有data字段'); } // 检查响应数据结构,处理嵌套的data数组 let actualDataArray; if (Array.isArray(firstResponseData.data)) { // 直接是数组 actualDataArray = firstResponseData.data; } else if (firstResponseData.data.data && Array.isArray(firstResponseData.data.data)) { // 嵌套的data数组(分页结构) actualDataArray = firstResponseData.data.data; } else { throw new Error('响应数据中的data结构不符合预期'); } // 构建ShelveSkus,使用用户输入的上架数量 const ShelveSkus = actualDataArray.map(item => { // 查找用户输入的上架数量,同时匹配 sku_id 和 skuId const userItem = itemsWithPositiveShelfQty.find(userItem => userItem.sku_id === item.skuId || userItem.skuId === item.skuId ); const shelfQty = userItem ? userItem.userShelfQty : 1; return { ...item, "_qty": shelfQty, "isDisabled": false, "$multiSelect": {"checked": true}, "bin": "P-1-5" }; }); // 第二个API请求 - 确认上架 const secondApiUrl = `https://api.erp321.com/erp/webapi/WmsApi/Shelve/SkuBatchShelveConfirm?owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}`; const secondRequestBody = { coid: u_co_id, data: { ShelveSkus: ShelveSkus, defaultPackType: "customize_3" }, ip: "", uid: u_id }; const secondResponse = await fetch(secondApiUrl, { method: 'POST', headers: headers, credentials: 'include', mode: 'cors', body: JSON.stringify(secondRequestBody) }); if (!secondResponse.ok) { throw new Error(`HTTP错误!状态:${secondResponse.status}`); } const secondResponseData = await secondResponse.json(); // 确定操作结果 if (!secondResponseData.data) { throw new Error('List操作失败'); } } /*********************** * 表格处理功能 ***********************/ const TableProcessor = { // 定位表格元素 findTargetTable() { // 首先尝试定位正确的表格结构:表头在_jt_row_head > _jt_row_head_list,内容在_jt_body > _jt_body_list const jtRowHead = document.getElementById('_jt_row_head'); const jtBody = document.getElementById('_jt_body'); // 检查表头是否包含商品编码列 let hasProductCodeColumn = false; if (jtRowHead) { const jtRowHeadList = jtRowHead.querySelector('#_jt_row_head_list'); if (jtRowHeadList) { const fieldItems = jtRowHeadList.querySelectorAll('._jt_cell_head'); for (const item of fieldItems) { const text = item.textContent.trim(); if (text.includes('商品编码')) { hasProductCodeColumn = true; break; } } } } // 检查表格内容容器 if (hasProductCodeColumn && jtBody) { const jtBodyList = jtBody.querySelector('#_jt_body_list'); if (jtBodyList) { return jtBodyList; } } // 如果没有找到完整结构,尝试定位_jt_body和_jt_body_list容器 const containers = [ document.getElementById('_jt_body'), document.getElementById('_jt_body_list') ].filter(Boolean); for (const container of containers) { const tables = container.querySelectorAll('table'); if (tables.length) { // 查找包含商品编码列的表格 for (const table of tables) { if (this.hasProductCodeColumn(table)) { return table; } } } else { // 处理div表格 if (this.hasProductCodeColumnInDivTable(container)) { return container; } } } // 如果没有找到主要容器,尝试处理所有表格 const allTables = document.querySelectorAll('table'); for (const table of allTables) { if (this.hasProductCodeColumn(table)) { return table; } } return null; }, // 检查表格是否包含商品编码列 hasProductCodeColumn(table) { const headerCells = table.querySelectorAll('tr:first-child th, tr:first-child td'); for (const cell of headerCells) { const text = cell.textContent.trim(); if (text.includes('商品编码')) { return true; } } return false; }, // 检查div表格是否包含商品编码列 hasProductCodeColumnInDivTable(container) { let jtRowHeadListElement = container.querySelector('#_jt_row_head_list'); if (jtRowHeadListElement) { const fieldItems = jtRowHeadListElement.querySelectorAll('._jt_cell_head'); for (const item of fieldItems) { const text = item.textContent.trim(); if (text.includes('商品编码')) { return true; } } } else { const rows = Array.from(container.querySelectorAll('div[class*="row"], div[class*="jt_row"], div._jt_rh')); const headerRow = rows.find(row => { const cells = Array.from(row.querySelectorAll('div[class*="cell"], div[class*="jt_ch"]')); return cells.length > 0 && cells.some(cell => { const text = cell.textContent.trim(); return text.includes('商品编码'); }); }); return !!headerRow; } return false; }, // 获取表格字段位置映射 getTableFieldPositions(table) { const headerCells = table.querySelectorAll('tr:first-child th, tr:first-child td'); const fieldPositions = {}; headerCells.forEach((cell, idx) => { const name = cell.textContent.trim(); if (name && name !== ' ' && name !== ' ') { fieldPositions[name] = idx; } }); return fieldPositions; }, // 获取div表格字段位置映射 getDivTableFieldPositions(container) { let fieldOrder = []; // 首先尝试从_jt_row_head > _jt_row_head_list获取表头字段 const jtRowHead = document.getElementById('_jt_row_head'); if (jtRowHead) { const jtRowHeadListElement = jtRowHead.querySelector('#_jt_row_head_list'); if (jtRowHeadListElement) { const fieldItems = jtRowHeadListElement.querySelectorAll('._jt_cell_head'); fieldItems.forEach(item => { const fieldName = item.textContent.trim(); if (fieldName && fieldName !== ' ' && fieldName !== ' ') { fieldOrder.push(fieldName); } }); } } // 如果从表头容器中没有获取到字段,尝试从当前容器获取 if (fieldOrder.length === 0) { let jtRowHeadListElement = container.querySelector('#_jt_row_head_list'); if (jtRowHeadListElement) { const fieldItems = jtRowHeadListElement.querySelectorAll('._jt_cell_head'); fieldItems.forEach(item => { const fieldName = item.textContent.trim(); if (fieldName && fieldName !== ' ' && fieldName !== ' ') { fieldOrder.push(fieldName); } }); } else { const rows = Array.from(container.querySelectorAll('div[class*="row"], div[class*="jt_row"], div._jt_rh')); const headerRow = rows.find(row => { const cells = Array.from(row.querySelectorAll('div[class*="cell"], div[class*="jt_ch"]')); return cells.length > 0 && cells.some(cell => cell.textContent.trim().length > 0 && cell.textContent.trim().length < 20); }) || rows[0]; if (headerRow) { const headerCells = headerRow.querySelectorAll('div[class*="cell"], div[class*="jt_ch"]'); headerCells.forEach(cell => { const name = cell.textContent.trim(); if (name && name !== ' ' && name !== ' ') { fieldOrder.push(name); } }); } } } const fieldPositions = {}; fieldOrder.forEach((fieldName, index) => { if (fieldName) { fieldPositions[fieldName] = index; } }); return fieldPositions; }, // 按名称解析字段索引:先精确匹配,再包含匹配(兼容表头带"?"帮助图标等前缀的情况) // candidates: 候选名称数组;excludeKey: 包含匹配时需排除的关键字(如商品编码需排除"供应商商品编码") resolveFieldIndex(fieldPositions, candidates, excludeKey) { // 1. 精确匹配 for (const c of candidates) { if (fieldPositions[c] !== undefined) { return fieldPositions[c]; } } // 2. 包含匹配 for (const c of candidates) { for (const name in fieldPositions) { if (name.includes(c) && (!excludeKey || !name.includes(excludeKey))) { return fieldPositions[name]; } } } return undefined; }, // 处理标准表格 processStandardTable(table, binResults) { // 获取字段位置映射 const fieldPositions = this.getTableFieldPositions(table); // 找到商品编码列、可退货库存列和退货数量列(模糊匹配,兼容"?可退货库存"等表头) const productCodeIdx = this.resolveFieldIndex(fieldPositions, ['商品编码'], '供应商'); const returnableStockIdx = this.resolveFieldIndex(fieldPositions, ['可退货库存', '退货库存', '库存']); const returnQtyIdx = this.resolveFieldIndex(fieldPositions, ['退货数量', '数量']); const orderLockIdx = this.resolveFieldIndex(fieldPositions, ['未审订单数', '未审核订单', '订单锁定']); // 调整索引,表头索引需要+2才是实际数据索引 const adjustedProductCodeIdx = productCodeIdx !== undefined ? productCodeIdx + 2 : undefined; const adjustedReturnableStockIdx = returnableStockIdx !== undefined ? returnableStockIdx + 2 : undefined; const adjustedReturnQtyIdx = returnQtyIdx !== undefined ? returnQtyIdx + 2 : undefined; const adjustedOrderLockIdx = orderLockIdx !== undefined ? orderLockIdx + 2 : undefined; if (adjustedProductCodeIdx === undefined || adjustedReturnableStockIdx === undefined) { return; } // 处理表格行 const rows = table.querySelectorAll('tr:not(:first-child)'); let replacedCount = 0; rows.forEach(row => { const cells = row.querySelectorAll('td'); if (cells.length > Math.max(adjustedProductCodeIdx, adjustedReturnableStockIdx, adjustedReturnQtyIdx || 0, adjustedOrderLockIdx || 0)) { const productCode = cells[adjustedProductCodeIdx].textContent.trim(); if (productCode) { // 查找匹配的仓位查询结果 const matchingResult = binResults.find(item => item.sku_id === productCode); let newQty = matchingResult ? matchingResult.qty : '0'; // 与退货数量列对比,设置颜色 let color = ''; if (adjustedReturnQtyIdx !== undefined) { const returnQty = cells[adjustedReturnQtyIdx].textContent.trim(); // 将两个值转换为数字进行比较,避免类型不匹配导致的问题 const newQtyNum = parseFloat(newQty) || 0; const returnQtyNum = parseFloat(returnQty) || 0; if (newQtyNum === returnQtyNum) { color = '#4CAF50'; // 一致为绿色 } else { color = '#f44336'; // 不一致为红色 } } else { // 如果没有退货数量列,根据是否找到匹配结果设置颜色 color = matchingResult ? '#4CAF50' : '#f44336'; } // 替换可退货库存列的数值 cells[adjustedReturnableStockIdx].textContent = newQty; // 可退库存为零的情况下样式设置为黑字不加粗 if (parseFloat(newQty) === 0) { cells[adjustedReturnableStockIdx].style.fontWeight = 'normal'; cells[adjustedReturnableStockIdx].style.color = '#333333'; } else { cells[adjustedReturnableStockIdx].style.fontWeight = 'bold'; cells[adjustedReturnableStockIdx].style.color = color; } // 对退货数量列应用相同的样式 if (adjustedReturnQtyIdx !== undefined) { if (parseFloat(newQty) === 0) { cells[adjustedReturnQtyIdx].style.fontWeight = 'normal'; cells[adjustedReturnQtyIdx].style.color = '#333333'; } else { cells[adjustedReturnQtyIdx].style.fontWeight = 'bold'; cells[adjustedReturnQtyIdx].style.color = color; } // 检查并删除背景色样式 if (cells[adjustedReturnQtyIdx].style.backgroundColor === 'rgb(255, 0, 0)') { cells[adjustedReturnQtyIdx].style.backgroundColor = ''; } } // 处理未审订单数列 if (adjustedOrderLockIdx !== undefined) { // 查找匹配的未审核订单数据 const orderLockItem = globalOrderLockData.find(item => item.sku_id === productCode); const orderLockQty = orderLockItem ? orderLockItem.order_lock : '0'; // 替换未审订单数列的数值 cells[adjustedOrderLockIdx].textContent = orderLockQty; // 只要有替换内容,字体统一显示为红色 if (orderLockQty !== '0') { cells[adjustedOrderLockIdx].style.fontWeight = 'bold'; cells[adjustedOrderLockIdx].style.color = '#f44336'; } else { cells[adjustedOrderLockIdx].style.fontWeight = 'normal'; cells[adjustedOrderLockIdx].style.color = '#333333'; } } replacedCount++; } } }); console.info('[ERP图片放大][表格写入]', '模式:标准表格, 写入行数:', replacedCount, ', 数据条数:', binResults.length); if (replacedCount === 0) { console.warn('[ERP图片放大][表格写入] 一行都没写入!实际表头字段:', Object.keys(fieldPositions).join(' | ')); } }, // 处理div表格 processDivTable(container, binResults) { // 获取字段位置映射 const fieldPositions = this.getDivTableFieldPositions(container); // 找到商品编码列、可退货库存列和退货数量列(模糊匹配,兼容"?可退货库存"等表头) const productCodeIdx = this.resolveFieldIndex(fieldPositions, ['商品编码'], '供应商'); const returnableStockIdx = this.resolveFieldIndex(fieldPositions, ['可退货库存', '退货库存', '库存']); const returnQtyIdx = this.resolveFieldIndex(fieldPositions, ['退货数量', '数量']); const orderLockIdx = this.resolveFieldIndex(fieldPositions, ['未审订单数', '未审核订单', '订单锁定']); // 调整索引,根据用户反馈,表头索引需要+2才是实际数据索引 const adjustedProductCodeIdx = productCodeIdx !== undefined ? productCodeIdx + 2 : undefined; const adjustedReturnableStockIdx = returnableStockIdx !== undefined ? returnableStockIdx + 2 : undefined; const adjustedReturnQtyIdx = returnQtyIdx !== undefined ? returnQtyIdx + 2 : undefined; const adjustedOrderLockIdx = orderLockIdx !== undefined ? orderLockIdx + 2 : undefined; if (adjustedProductCodeIdx === undefined || adjustedReturnableStockIdx === undefined) { return; } // 处理表格行 const rows = container.querySelectorAll('div[class*="row"], div[class*="jt_row"], div._jt_rh'); let replacedCount = 0; rows.forEach(row => { // 跳过表头行 if (row.querySelector('._jt_cell_head')) { return; } const cells = row.querySelectorAll('div[class*="cell"], div[class*="jt_ch"]'); if (cells.length > Math.max(adjustedProductCodeIdx, adjustedReturnableStockIdx, adjustedReturnQtyIdx || 0, adjustedOrderLockIdx || 0)) { const productCode = cells[adjustedProductCodeIdx].textContent.trim(); if (productCode) { // 查找匹配的仓位查询结果 const matchingResult = binResults.find(item => item.sku_id === productCode); let newQty = matchingResult ? matchingResult.qty : '0'; // 与退货数量列对比,设置颜色 let color = ''; if (adjustedReturnQtyIdx !== undefined) { const returnQty = cells[adjustedReturnQtyIdx].textContent.trim(); // 将两个值转换为数字进行比较,避免类型不匹配导致的问题 const newQtyNum = parseFloat(newQty) || 0; const returnQtyNum = parseFloat(returnQty) || 0; if (newQtyNum === returnQtyNum) { color = '#4CAF50'; // 一致为绿色 } else { color = '#f44336'; // 不一致为红色 } } else { // 如果没有退货数量列,根据是否找到匹配结果设置颜色 color = matchingResult ? '#4CAF50' : '#f44336'; } // 替换可退货库存列的数值 cells[adjustedReturnableStockIdx].textContent = newQty; // 可退库存为零的情况下样式设置为黑字不加粗 if (parseFloat(newQty) === 0) { cells[adjustedReturnableStockIdx].style.fontWeight = 'normal'; cells[adjustedReturnableStockIdx].style.color = '#333333'; } else { cells[adjustedReturnableStockIdx].style.fontWeight = 'bold'; cells[adjustedReturnableStockIdx].style.color = color; } // 对退货数量列应用相同的样式 if (adjustedReturnQtyIdx !== undefined) { if (parseFloat(newQty) === 0) { cells[adjustedReturnQtyIdx].style.fontWeight = 'normal'; cells[adjustedReturnQtyIdx].style.color = '#333333'; } else { cells[adjustedReturnQtyIdx].style.fontWeight = 'bold'; cells[adjustedReturnQtyIdx].style.color = color; } // 检查并删除背景色样式 if (cells[adjustedReturnQtyIdx].style.backgroundColor === 'rgb(255, 0, 0)') { cells[adjustedReturnQtyIdx].style.backgroundColor = ''; } } // 处理未审订单数列 if (adjustedOrderLockIdx !== undefined) { // 查找匹配的未审核订单数据 const orderLockItem = globalOrderLockData.find(item => item.sku_id === productCode); const orderLockQty = orderLockItem ? orderLockItem.order_lock : '0'; // 替换未审订单数列的数值 cells[adjustedOrderLockIdx].textContent = orderLockQty; // 只要有替换内容,字体统一显示为红色 if (orderLockQty !== '0') { cells[adjustedOrderLockIdx].style.fontWeight = 'bold'; cells[adjustedOrderLockIdx].style.color = '#f44336'; } else { cells[adjustedOrderLockIdx].style.fontWeight = 'normal'; cells[adjustedOrderLockIdx].style.color = '#333333'; } } replacedCount++; } } }); }, // 处理表格数据 processTableData(binResults) { const table = this.findTargetTable(); if (!table) { console.warn('[ERP图片放大][表格写入] 未找到目标表格'); return; } // 处理表头样式 this.processTableHeaders(); if (table.tagName === 'TABLE') { this.processStandardTable(table, binResults); // 添加行点击事件监听器 this.addRowClickListeners(table); } else { this.processDivTable(table, binResults); // 添加行点击事件监听器 this.addRowClickListeners(table); } }, // 处理表头样式 processTableHeaders() { // 首先尝试从_jt_row_head > _jt_row_head_list获取表头 const jtRowHead = document.getElementById('_jt_row_head'); if (jtRowHead) { const jtRowHeadList = jtRowHead.querySelector('#_jt_row_head_list'); if (jtRowHeadList) { const headerCells = jtRowHeadList.querySelectorAll('._jt_cell_head'); headerCells.forEach(cell => { const text = cell.textContent.trim(); if (text.includes('实物库存')) { // 修改实物库存列为可生效库存 cell.textContent = '可生效库存'; // 应用样式 cell.style.width = '80px'; cell.style.cursor = 'default'; cell.style.position = 'relative'; cell.style.paddingRight = '16px'; // 添加帮助图标 if (!cell.querySelector('.help_bg')) { const helpIcon = document.createElement('span'); helpIcon.className = 'help_bg'; helpIcon.style.position = 'absolute'; helpIcon.style.right = '4px'; helpIcon.style.top = '50%'; helpIcon.style.transform = 'translateY(-50%)'; helpIcon.style.width = '12px'; helpIcon.style.height = '12px'; helpIcon.style.backgroundImage = 'url("/app/Styles/images/help.gif")'; helpIcon.style.backgroundRepeat = 'no-repeat'; helpIcon.style.backgroundPosition = 'center'; cell.appendChild(helpIcon); } // 添加tooltip cell.title = '可生效库存 = 当前选中仓库的该商品库存'; } else if (text.includes('可退货库存')) { // 修改可退货库存列的tooltip cell.title = '可退货库存 = 当前选中退货类型查询到的商品库存'; } }); } } // 处理标准表格的表头 const allTables = document.querySelectorAll('table'); allTables.forEach(table => { const headerCells = table.querySelectorAll('tr:first-child th, tr:first-child td'); headerCells.forEach(cell => { const text = cell.textContent.trim(); if (text.includes('实物库存')) { // 修改实物库存列为可生效库存 cell.textContent = '可生效库存'; // 应用样式 cell.style.width = '80px'; cell.style.cursor = 'default'; cell.style.position = 'relative'; cell.style.paddingRight = '16px'; // 添加帮助图标 if (!cell.querySelector('.help_bg')) { const helpIcon = document.createElement('span'); helpIcon.className = 'help_bg'; helpIcon.style.position = 'absolute'; helpIcon.style.right = '4px'; helpIcon.style.top = '50%'; helpIcon.style.transform = 'translateY(-50%)'; helpIcon.style.width = '12px'; helpIcon.style.height = '12px'; helpIcon.style.backgroundImage = 'url("/app/Styles/images/help.gif")'; helpIcon.style.backgroundRepeat = 'no-repeat'; helpIcon.style.backgroundPosition = 'center'; cell.appendChild(helpIcon); } // 添加tooltip cell.title = '可生效库存 = 当前选中仓库的该商品库存'; } else if (text.includes('可退货库存')) { // 修改可退货库存列的tooltip cell.title = '可退货库存 = 当前选中退货类型查询到的商品库存'; } }); }); }, // 添加行点击事件监听器 addRowClickListeners(table) { // 避免重复添加监听器 if (table._rowClickListenersAdded) { return; } // 标记已添加监听器 table._rowClickListenersAdded = true; // 监听行点击事件 table.addEventListener('click', (e) => { const row = e.target.closest('tr, div[class*="row"], div[class*="jt_row"], div._jt_rh'); if (row) { // 延迟执行,等待系统重新渲染完成 setTimeout(() => { // 使用全局数据重新处理表格 this.processTableData(globalBinResults); }, 100); } }); } }; // 存储全局数据,供表格更新使用 let globalBinResults = []; let globalSupplierCode = ''; let globalOrderLockData = []; // CreateOffPick弹窗页面的当前仓库ID(searchOffPickQty解析页面HTML后缓存,供confirmPick下架请求使用) let globalOffPickWhId = ''; // 更新表格数据的函数 async function updateTableData() { try { // 获取仓位查询结果 let binResults = []; // 检查当前选择的退货类型 const pickTypeSelect = document.getElementById('pick-type-select'); if (pickTypeSelect && pickTypeSelect.value) { const selectedConfig = JSON.parse(pickTypeSelect.value); if (selectedConfig.rule_id === 'move_stock') { // 退供应商仓:查询可移仓数量 // 获取退货数据中的sku_id const returnData = await getReturnData(); const skuIds = returnData.map(item => item.sku_id).filter(Boolean); if (skuIds.length > 0) { binResults = await getAvailableStock(skuIds); } } else { // 其他退货类型:查询可下架数量 // 优先走ERP原生 CreateOffPick SkuSearch 接口(与ERP"解决误差"弹窗口径一致); // 失败时自动回退到旧版 PackItems like 查询,保证功能可用 const returnData = await getReturnData(); const skuIds = returnData.map(item => item.sku_id).filter(Boolean); console.info('[ERP图片放大][表格更新]', '当前类型:', selectedConfig.filter_name, 'rule_id:', selectedConfig.rule_id, '退货SKU:', skuIds.join(',')); if (skuIds.length > 0) { binResults = await searchOffPickQty(skuIds, selectedConfig.filter_name, selectedConfig.rule_id); } else { console.warn('[ERP图片放大][表格更新]', 'getReturnData未取到任何SKU'); } if (binResults.length === 0) { // 回退:旧版查询(不带规则口径,但至少能显示库存、维持功能可用) console.warn('[ERP图片放大][表格更新]', 'SkuSearch未返回数据,回退到旧版仓位查询'); const skuPrefix = globalSupplierCode || (skuIds[0] && skuIds[0].substring(0, 5)); if (skuPrefix) { binResults = await searchBin(skuPrefix, selectedConfig.bin); } } if (binResults.length > 0) { // SkuSearch成功时补一次PackItems查询填充globalModified1Map(商品资料修改标记) const modifiedPrefix = globalSupplierCode || (skuIds[0] && skuIds[0].substring(0, 5)); if (modifiedPrefix) { searchBin(modifiedPrefix, selectedConfig.bin).catch(() => {}); } } } } // 检查是否需要获取排除数量 let processedBinResults = binResults; const isExcludeChecked = document.getElementById('exclude-quantity-checkbox')?.checked; if (isExcludeChecked) { const selectedDate = document.getElementById('exclude-date-picker')?.value; if (selectedDate) { // 获取退货数据 const returnData = await getReturnData(); // 获取排除数量 const exclusionQuantities = await getExclusionQuantities(returnData, selectedDate); // 应用排除数量到binResults processedBinResults = binResults.map(item => { const skuId = item.sku_id || item.sku; const exclusionQty = exclusionQuantities[skuId] || 0; const adjustedQty = Math.max(0, item.qty - exclusionQty); return { ...item, qty: adjustedQty }; }); } } // 获取未审核订单数据 // 修复:getOrderLockData此前从未被调用,globalOrderLockData恒为空数组, // 导致表格中"未审订单数"列永远显示0 try { const orderLockPrefix = globalSupplierCode || (processedBinResults[0] && (processedBinResults[0].sku_id || processedBinResults[0].sku)) || ''; globalOrderLockData = orderLockPrefix ? await getOrderLockData(orderLockPrefix) : []; } catch (e) { globalOrderLockData = []; } // 存储数据供后续使用 globalBinResults = processedBinResults; // 处理表格数据 TableProcessor.processTableData(processedBinResults); // 防御ERP异步渲染覆盖:jtable可能在脚本写入后才完成渲染并重置单元格, // 延迟用缓存数据重写两次,保证数值最终能显示 [3000, 8000].forEach(delay => { setTimeout(() => { if (globalBinResults.length > 0) { TableProcessor.processTableData(globalBinResults); } }, delay); }); } catch (error) { console.error('[ERP图片放大][表格更新] 失败:', error.message, '\n', error.stack); } } // 显示数量不足错误提示和操作引导 function showQuantityShortageError(errorInfo) { // 找到工具栏容器 const toolbarLeft = document.getElementById('_jt_toolbar_left'); if (!toolbarLeft) return; // 检查是否已经存在错误提示元素 let errorContainer = document.getElementById('quantity-shortage-error'); if (errorContainer) { errorContainer.remove(); } // 创建错误提示容器 errorContainer = createEl('div', { id: 'quantity-shortage-error', className: '_jt_tool' }, ` display: flex; flex-direction: column; align-items: flex-start; gap: 8px; padding: 10px; margin-top: 10px; border: 1px solid #ff4d4f; border-radius: 4px; background-color: #fff2f0; width: 100%; max-width: 600px; `); // 创建错误标题 const errorTitle = createEl('div', {}, ` font-size: 14px; font-weight: 600; color: #ff4d4f; margin-bottom: 5px; `); errorTitle.textContent = '⚠️ 数量不足错误'; errorContainer.appendChild(errorTitle); // 创建错误详情列表 const errorList = createEl('ul', {}, ` margin: 0; padding-left: 20px; font-size: 13px; color: #333; `); errorInfo.forEach(error => { if (error.error_type === '数量不足' || error.error_type === '无可用数量') { const listItem = createEl('li', {}, ` margin-bottom: 3px; `); listItem.textContent = `商品 ${error.sku_id}:退货数量(${error.return_qty}) > 可移仓数量(${error.adjusted_available_qty})`; errorList.appendChild(listItem); } }); errorContainer.appendChild(errorList); // 创建操作引导 const actionGuide = createEl('div', {}, ` font-size: 13px; color: #666; margin-top: 5px; `); actionGuide.innerHTML = `
操作引导:
1. 手动调整 - 通过系统提供的手动调整功能修改相关数量
2. 解决误差 - 先解决数量误差问题后再尝试移仓操作
`; errorContainer.appendChild(actionGuide); // 添加点击事件 const manualAdjustLink = actionGuide.querySelector('a:first-child'); if (manualAdjustLink) { manualAdjustLink.addEventListener('click', function(e) { e.preventDefault(); notify('请使用系统提供的手动调整功能修改相关数量', 'info'); }); } const resolveErrorLink = actionGuide.querySelector('a:last-child'); if (resolveErrorLink) { resolveErrorLink.addEventListener('click', function(e) { e.preventDefault(); notify('请先解决数量误差问题后再尝试移仓操作', 'info'); // 可以添加刷新页面或其他操作 const reloadButton = document.getElementById('_jt_reload'); if (reloadButton) { reloadButton.click(); } }); } // 添加到工具栏 toolbarLeft.appendChild(errorContainer); // 禁用"确认移仓"按钮 const transferButton = document.getElementById('one-click-transfer-btn'); if (transferButton) { transferButton.disabled = true; transferButton.style.opacity = '0.6'; transferButton.style.cursor = 'not-allowed'; } } // 获取页面中的_InoutSellerId值 function getInoutSellerId() { // 尝试从全局变量获取 if (window._InoutSellerId) { return window._InoutSellerId; } // 尝试从脚本标签中提取 const scripts = document.querySelectorAll('script'); for (const script of scripts) { const content = script.textContent; if (content.includes('_InoutSellerId')) { const match = content.match(/var _InoutSellerId = (\d+);/); if (match && match[1]) { return parseInt(match[1]); } } } return null; } // 获取未审核订单数据 async function getOrderLockData(skuPrefix) { try { const u_id = getCookie('u_id'); const u_co_id = getCookie('u_co_id'); if (!u_id || !u_co_id) { // console.error('❌ 未找到u_id或u_co_id cookie'); return []; } // 构建请求URL const url = `https://apiweb.erp321.com/webapi/ItemApi/ItemSku/GetPageListV2?__from=web_component&owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}`; // 构建请求体 const requestBody = { "ip": "", "uid": u_id, "coid": u_co_id, "page": { "currentPage": 1, "pageSize": 2000, "pageAction": 1 }, "data": { "sku_type": 1, "queryFlds": ["pic","i_id","sku_id","name","properties_value","labels","qty","order_lock","orderable","salesTrends30","daysInventory","purchaseQty","purchase_qty","purchase_plan_arrive","lock_qty","lwh_result_lock_qty","min_qty","max_qty","min_day","max_day","pick_lock","return_qty","in_qty","customize_qty_1","customize_qty_2","customize_qty_3","customize_qty_4","customize_qty_5","customize_qty_6","customize_qty_7","customize_qty_8","customize_qty_9","customize_qty_10","allocate_qty","virtual_qty","unlock_qty","stock_opensync","bin","sales_qty_yesterday","sales_qty_7","sales_qty_15","afs_qty_yesterday","afs_qty_7","afs_qty_15","qty_modified","multiTimeStocks_3","multiTimeStocks_5","multiTimeStocks_7","multiTimeStocks_10","multiTimeStocks_15","multiTimeStocks_20","multiTimeStocks_25","multiTimeStocks_30","multiTimeStocks_45","is_series_number","sale_price","c_id","supplier_id","supplier_name","pic_big"], "orderBy": "customize_qty_5 ASC", "sku_id": `${skuPrefix}*`, "order_lock_min": 1, "c_id": "" } }; // 发送POST请求 const response = await fetch(url, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); if (!response.ok) { // console.error(`❌ HTTP错误!状态:${response.status}`); return []; } // 处理响应数据 const responseData = await response.json(); if (!responseData.data || !Array.isArray(responseData.data)) { // console.error('❌ 未找到有效的data数组'); return []; } // 提取sku_id和order_lock字段 const orderLockData = responseData.data.map(item => ({ sku_id: item.sku_id, order_lock: item.order_lock || 0 })); return orderLockData; } catch (error) { // console.error('❌ 获取未审核订单数据失败:', error.message); return []; } } // 调用初始化函数 init(); })();