NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name ERP图片放大显示-退货
// @namespace https://openuserjs.org/users/yiwaima
// @version 5.1
// @description 监控ERP系统中特定POST请求并放大显示图片,支持拖动、按网址位置记忆、尺寸调整、自适应字体、智能滑动显示和价格变动提醒
// @author yiwaima
// @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*
// @match https://**
// @match https://**
// @icon https://files.erp321.com/img/platIcon/ziShen.png
// @grant none
// @license MIT
// @homepageURL https://openuserjs.org/scripts/yiwaima/ERP图片放大显示-退货
// @supportURL https://github.com/yiwaima/erp-image-zoom-display/issues
// @updateURL https://openuserjs.org/meta/yiwaima/ERP图片放大显示-退货.meta.js
// @downloadURL https://openuserjs.org/install/yiwaima/ERP图片放大显示-退货.user.js
// ==/UserScript==
(function () {
'use strict';
// 添加全局标志,防止脚本重复执行
if (window.erpImageZoomScriptLoaded) {
return;
}
window.erpImageZoomScriptLoaded = true;
/***********************
* 工具函数
***********************/
// 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' : '#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) {
}
}
}
return responseObj;
} catch (error) {
const errorMsg = `解析响应数据失败: ${error.message}`;
if (requestInfo.description) {
}
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);
const viewStateGeneratorMatch = html.match(/id="__VIEWSTATEGENERATOR"\s+value="([^"]+)"/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 error;
}
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 error;
}
},
// 构建操作数量查询表单
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 error;
}
},
// 获取原价格(从入库单中)
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(/<div id="_jt_data"[^>]*>([\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 error;
}
const html = await response.text();
// 提取div中的_jt_data
const divMatch = html.match(/<div id="_jt_data"[^>]*>([\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(/<div id="_jt_data"[^>]*>([\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 error;
}
// 处理响应
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 = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 512 512"><path fill="currentColor" d="M414 354q-18-18-41-11l-32-32q43-53 43-119q0-80-56-136T192 0T56 56T0 192t56 136t136 56q70 0 119-43l32 32q-6 24 11 41l85 85q13 13 30 13q18 0 30-13q13-13 13-30t-13-30zm-222-13q-62 0-105.5-43.5T43 192T86.5 86.5T192 43t105.5 43.5T341 192t-43.5 105.5T192 341m64-170h-43v-43q0-21-21-21t-21 21v43h-43q-21 0-21 21t21 21h43v43q0 21 21 21t21-21v-43h43q21 0 21-21t-21-21"></path></svg>`;
const zoomOutSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 1025 1025"><path fill="currentColor" d="M1005.856 822q19 19 19 46t-19 46l-91 92q-19 19-45.5 19t-45.5-19l-114-114l-133 133q-27 0-45.5-19t-18.5-45V576q0-26 18.5-45t45.5-19h384q26 0 45 19t19 45l-133 133zm-557-310h-383q-27 0-45.5-18.5T1.856 448l132-132l-114-113q-19-19-19-46t19-46l91-92q19-19 46-19t46 19l114 114l132-132q27 0 45.5 18.5t18.5 45.5v383q0 27-19 45.5t-45 18.5"></path></svg>`;
const plusSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 64 64"><path fill="currentColor" d="M38 26V2H26v24H2v12h24v24h12V38h24V26z"></path></svg>`;
const minusSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 64 64"><path fill="currentColor" d="M2 26h60v12H2z"></path></svg>`;
// 添加放大缩小切换按钮
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 = `
<div style="margin-bottom: 3px;">价格变动, ${oldPrice}->${newPrice} ${formattedDate}, 新价格可退 <span style="color: #4CAF50;">${remainingQty}</span></div>
<div style="margin-bottom: 3px;">新价格出入库: 入库(${stockInQty}), 出库:(${stockOutQty})</div>
`;
} else {
// 负数无余:半透明黑底白字
content = `
<div style="margin-bottom: 3px;">价格变动, ${oldPrice}->${newPrice} ${formattedDate}</div>
<div style="margin-bottom: 3px;">新价格出入库: 入库(${stockInQty}), 出库:(${stockOutQty})</div>
<div style="color: #f44336;">新价格已退完,请修改为旧价格</div>
`;
}
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 = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 512 512"><path fill="currentColor" d="M414 354q-18-18-41-11l-32-32q43-53 43-119q0-80-56-136T192 0T56 56T0 192t56 136t136 56q70 0 119-43l32 32q-6 24 11 41l85 85q13 13 30 13q18 0 30-13q13-13 13-30t-13-30zm-222-13q-62 0-105.5-43.5T43 192T86.5 86.5T192 43t105.5 43.5T341 192t-43.5 105.5T192 341m64-170h-43v-43q0-21-21-21t-21 21v43h-43q-21 0-21 21t21 21h43v43q0 21 21 21t21-21v-43h43q21 0 21-21t-21-21"></path></svg>`;
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 = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 1025 1025"><path fill="currentColor" d="M1005.856 822q19 19 19 46t-19 46l-91 92q-19 19-45.5 19t-45.5-19l-114-114l-133 133q-27 0-45.5-19t-18.5-45V576q0-26 18.5-45t45.5-19h384q26 0 45 19t19 45l-133 133zm-557-310h-383q-27 0-45.5-18.5T1.856 448l132-132l-114-113q-19-19-19-46t19-46l91-92q19-19 46-19t46 19l114 114l132-132q27 0 45.5 18.5t18.5 45.5v383q0 27-19 45.5t-45 18.5"></path></svg>`;
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 = `https://www.erp321.com/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 = `https://www.erp321.com/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 = `https://www.erp321.com/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 = `https://www.erp321.com/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 {
// 其他退货类型:查询可下架数量
// 截取第一个SKU的前五位作为查询参数
const skuPrefix = skuIds[0].substring(0, 5);
availableStock = await searchBin(skuPrefix, selectedConfig.bin);
// console.log('📊 可下架数量查询结果:', availableStock);
}
// 更新全局数据存储
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);
// 所有退货类型都使用supplier_code作为[pit].sku_id参数值
const skuPrefix = globalSupplierCode;
const binResults = await searchBin(skuPrefix, selectedConfig.bin);
globalBinResults = binResults;
// 更新表格数据
await updateTableData();
} catch (error) {
// 错误处理,不输出
}
}
// 查询仓位信息
async function searchBin(skuId, bin) {
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`;
// 构建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 [];
}
}
// 查询可移仓数量
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 = `https://www.erp321.com/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 = `https://www.erp321.com/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(/<div id="_jt_data"[^>]*>([\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(/<div id="_jt_data"[^>]*>([\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);
throw error;
}
}
// 确认下架
async function confirmPick(pickMode = 'fullBin') {
try {
// 检查是否有可下架数据
if (globalBinResults.length === 0) {
// console.error('❌ 没有可下架的数据,请先点击搜索按钮获取数据');
return [];
}
// 检查是否有退货数据(仅退货单下架模式需要)
if (pickMode === 'returnOrder') {
const returnData = await getReturnData();
if (returnData.length === 0) {
// console.error('❌ 没有退货数据,无法执行退货单下架,请先确保搜索到了退货数据');
return [];
}
}
// 构建请求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) {
// console.error('❌ 无法获取目标页面VIEWSTATE参数,请刷新页面后重试');
return [];
}
// 从globalBinResults中提取原仓库ID
let whId = '';
if (globalBinResults && globalBinResults.length > 0) {
// 从第一个datas中的whId字段提取原仓库ID
const firstItem = globalBinResults[0];
whId = firstItem.whId || '';
if (!whId) {
// 如果没有whId,使用默认值
whId = '10';
}
} else {
// 当前没有可下架数据,使用默认仓库ID
whId = '10'; // 退供应商仓的仓库ID
}
// 构建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) {
// console.error('❌ 没有匹配的商品可下架');
return [];
}
// 获取当前选择的退货类型
const pickTypeSelect = document.getElementById('pick-type-select');
if (!pickTypeSelect || !pickTypeSelect.value) {
// console.error('❌ 请选择退货类型');
return [];
}
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(`❌ 确认下架失败,服务器返回错误: ${JSON.stringify(responseData)}`);
return [];
}
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) {
throw error;
}
}
// 收集勾选框信息
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 [];
}
const html = await response.text();
// 提取VIEWSTATE
let viewState = '';
const viewStateMatch = html.match(/<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="([^"]*)"\s*\/>/);
if (viewStateMatch) {
viewState = viewStateMatch[1];
}
// 提取VIEWSTATEGENERATOR
let viewStateGenerator = '';
const viewStateGeneratorMatch = html.match(/<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="([^"]*)"\s*\/>/);
if (viewStateGeneratorMatch) {
viewStateGenerator = viewStateGeneratorMatch[1];
}
return {
viewState,
viewStateGenerator
};
} catch (error) {
// console.error('❌ 获取VIEWSTATE失败:', error.message);
throw error;
}
}
// 显示详细的误差信息并让用户确认
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 ? '<div style="margin-bottom: 10px; font-size: 13px; text-align: center; color: #ff4d4f;">以下商品的退货数量大于可移仓数量,请解决误差后再操作:</div>' : '<div style="margin-bottom: 10px; font-size: 13px; text-align: center;">以下商品的移仓数量与退货数量存在误差,请确认:</div>';
html += '<table style="width: 100%; border-collapse: collapse; font-size: 13px; text-align: center;"><tr style="background-color: #f8f9fa; font-weight: bold;"><th style="padding: 6px; border-bottom: 1px solid #ddd;">商品编码</th><th style="padding: 6px; border-bottom: 1px solid #ddd;">退货数</th><th style="padding: 6px; border-bottom: 1px solid #ddd;">可移数</th><th style="padding: 6px; border-bottom: 1px solid #ddd;">移仓数量</th><th style="padding: 6px; border-bottom: 1px solid #ddd;"><button type="button" id="list-button" style="padding: 4px 8px; background-color: #4A90E2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">上架</button></th></tr>';
// 在数量不足的情况下,只显示数量不足的信息
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 += `<tr><td style="padding: 6px; border-bottom: 1px solid #eee;">${skuId}</td><td style="padding: 6px; border-bottom: 1px solid #eee;">${returnQty}</td><td style="padding: 6px; border-bottom: 1px solid #eee;">${adjustedAvailableQty}</td><td style="padding: 6px; border-bottom: 1px solid #eee; color: #ff6b6b;">${displayMoveQty}</td><td style="padding: 6px; border-bottom: 1px solid #eee;"><input type="text" style="width: 60px; padding: 2px; text-align: center;" value="${shelfQty}"></td></tr>`;
});
html += '</table>';
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 = `
<span>${item.sku_id} ${modified1}</span>
<span>数量: ${item.userShelfQty}</span>
`;
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都被传递
const headers = {
'Content-Type': 'application/json',
'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) 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;
},
// 处理标准表格
processStandardTable(table, binResults) {
// 获取字段位置映射
const fieldPositions = this.getTableFieldPositions(table);
// 找到商品编码列、可退货库存列和退货数量列
const productCodeIdx = fieldPositions['商品编码'];
const returnableStockIdx = fieldPositions['可退货库存'] || fieldPositions['退货库存'] || fieldPositions['库存'];
const returnQtyIdx = fieldPositions['数量'] || fieldPositions['退货数量'] || fieldPositions['qty'];
const orderLockIdx = fieldPositions['未审订单数'] || fieldPositions['未审核订单'] || 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++;
}
}
});
},
// 处理div表格
processDivTable(container, binResults) {
// 获取字段位置映射
const fieldPositions = this.getDivTableFieldPositions(container);
// 找到商品编码列、可退货库存列和退货数量列
const productCodeIdx = fieldPositions['商品编码'];
const returnableStockIdx = fieldPositions['可退货库存'] || fieldPositions['退货库存'] || fieldPositions['库存'];
const returnQtyIdx = fieldPositions['数量'] || fieldPositions['退货数量'] || fieldPositions['qty'];
const orderLockIdx = fieldPositions['未审订单数'] || fieldPositions['未审核订单'] || 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) {
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 = [];
// 更新表格数据的函数
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 {
// 其他退货类型:查询可下架数量
// 所有退货类型都使用supplier_code作为[pit].sku_id参数值
const skuPrefix = globalSupplierCode;
binResults = await searchBin(skuPrefix, selectedConfig.bin);
}
}
// 检查是否需要获取排除数量
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
};
});
}
}
// 存储数据供后续使用
globalBinResults = processedBinResults;
// 处理表格数据
TableProcessor.processTableData(processedBinResults);
} catch (error) {
// console.error('❌ 更新表格数据失败:', error.message);
// console.error('错误堆栈:', 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 = `
<div style="margin-bottom: 5px;"><strong>操作引导:</strong></div>
<div style="margin-left: 15px; margin-bottom: 3px;">1. <a href="#" style="color: #1890ff; text-decoration: underline; cursor: pointer;">手动调整</a> - 通过系统提供的手动调整功能修改相关数量</div>
<div style="margin-left: 15px;">2. <a href="#" style="color: #1890ff; text-decoration: underline; cursor: pointer;">解决误差</a> - 先解决数量误差问题后再尝试移仓操作</div>
`;
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();
})();