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 3.4
// @description 监控ERP系统中特定POST请求并放大显示图片,支持拖动、按网址位置记忆、尺寸调整、自适应字体和智能滑动显示
// @author yiwaima
// @match https://*.erp321.com/*
// @exclude https://*.erp321.com/app/item/itemsku/itemskudialog.aspx*
// @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';
// 创建图片显示框 - 支持拖动、位置记忆和尺寸调整
function createImageDisplay() {
const display = document.createElement('div');
display.id = 'erp-image-display';
// 从localStorage加载保存的位置和尺寸
const savedPosition = loadDisplayPosition();
const topPosition = savedPosition?.top || '35px';
const rightPosition = savedPosition?.right || '10px';
const width = savedPosition?.width || '400px';
const height = savedPosition?.height || '400px';
const isMinimized = savedPosition?.isMinimized || false;
// 保存原始尺寸和当前尺寸
let originalWidth = savedPosition?.width || '400px';
let originalHeight = savedPosition?.height || '400px';
let currentIsMinimized = isMinimized;
// 保存缩小前的尺寸,用于恢复
let sizeBeforeMinimize = {
width: savedPosition?.sizeBeforeMinimize?.width || width,
height: savedPosition?.sizeBeforeMinimize?.height || height
};
display.style.cssText = `
position: fixed;
top: ${topPosition};
right: ${rightPosition};
width: ${isMinimized ? '100px' : width};
height: ${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 = document.createElement('div');
alertContainer.id = 'erp-image-alerts';
alertContainer.style.cssText = `
position: absolute;
top: 10px;
left: 10px;
right: 10px;
text-align: center;
z-index: 1;
pointer-events: none;
opacity: ${isMinimized ? '0' : '1'};
transition: opacity 0.3s ease;
`;
// 创建第一个提示信息标签(调整透明度为30%)
const alert1 = document.createElement('div');
alert1.id = 'erp-alert-1';
alert1.style.cssText = `
background-color: rgba(255, 255, 255, 0.3);
color: red;
font-weight: bold;
padding: 5px 10px;
border-radius: 4px;
margin-bottom: 5px;
display: none;
font-size: 14px;
transition: all 0.3s ease;
`;
// 创建第二个提示信息标签(调整透明度为30%)
const alert2 = document.createElement('div');
alert2.id = 'erp-alert-2';
alert2.style.cssText = `
background-color: rgba(255, 255, 255, 0.3);
color: gray;
font-weight: bold;
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 = document.createElement('div');
imgContainer.id = 'erp-image-scroll-container';
imgContainer.style.cssText = `
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,隐藏默认滚动条
const style = document.createElement('style');
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);
// 创建图片元素
const img = document.createElement('img');
img.id = 'erp-displayed-image';
img.style.cssText = `
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
object-fit: contain;
`;
img.alt = 'ERP产品图片';
imgContainer.appendChild(img);
display.appendChild(imgContainer);
// 添加功能按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.id = 'erp-image-buttons';
buttonContainer.style.cssText = `
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 = document.createElement('button');
zoomToggleButton.id = 'erp-zoom-toggle';
zoomToggleButton.innerHTML = isMinimized ? zoomInSvg : zoomOutSvg;
zoomToggleButton.style.cssText = `
width: 30px;
height: 30px;
border: none;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
color: white;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
// 添加尺寸加按钮
const sizePlusButton = document.createElement('button');
sizePlusButton.id = 'erp-size-plus';
sizePlusButton.innerHTML = plusSvg;
sizePlusButton.style.cssText = `
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;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
// 添加尺寸减按钮
const sizeMinusButton = document.createElement('button');
sizeMinusButton.id = 'erp-size-minus';
sizeMinusButton.innerHTML = minusSvg;
sizeMinusButton.style.cssText = `
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;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
// 根据是否最小化调整按钮显示
if (isMinimized) {
// 最小化时只显示放大缩小按钮,且占图片1/4大小
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);
// 放大缩小切换功能
zoomToggleButton.addEventListener('click', function(e) {
e.stopPropagation(); // 防止触发拖动
currentIsMinimized = !currentIsMinimized;
if (currentIsMinimized) {
// 缩小前保存当前尺寸
sizeBeforeMinimize = { width: display.style.width, height: display.style.height };
// 缩小到100x100
display.style.width = '100px';
display.style.height = '100px';
zoomToggleButton.innerHTML = zoomInSvg;
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 = sizeBeforeMinimize.width;
display.style.height = sizeBeforeMinimize.height;
zoomToggleButton.innerHTML = zoomOutSvg;
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';
// 更新原始尺寸(用于切换放大缩小)
originalWidth = sizeBeforeMinimize.width;
originalHeight = 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);
}
// 保存当前状态
saveDisplayPosition(display.style.top, display.style.right, display.style.width, display.style.height, currentIsMinimized, sizeBeforeMinimize);
});
// 添加鼠标滚轮滚动功能
imgContainer.addEventListener('wheel', function(e) {
// 只有在非缩小状态下才允许滚动
if (!currentIsMinimized) {
e.stopPropagation(); // 防止影响页面滚动
}
});
// 图片加载完成后根据宽高比调整显示方式
img.addEventListener('load', function() {
// 只有在非缩小状态下才执行调整
if (!currentIsMinimized) {
// 获取图片原始宽高
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;
`;
}
}
});
// 尺寸加功能
sizePlusButton.addEventListener('click', function(e) {
e.stopPropagation(); // 防止触发拖动
const currentWidth = parseInt(display.style.width);
const currentHeight = parseInt(display.style.height);
const newWidth = currentWidth + 50;
const newHeight = currentHeight + 50;
display.style.width = newWidth + 'px';
display.style.height = newHeight + 'px';
// 更新原始尺寸(用于切换放大缩小)
originalWidth = display.style.width;
originalHeight = display.style.height;
// 更新提示字体尺寸
updateAlertFontSize(display);
// 保存当前状态
saveDisplayPosition(display.style.top, display.style.right, display.style.width, display.style.height, currentIsMinimized, sizeBeforeMinimize);
});
// 尺寸减功能
sizeMinusButton.addEventListener('click', function(e) {
e.stopPropagation(); // 防止触发拖动
const currentWidth = parseInt(display.style.width);
const currentHeight = parseInt(display.style.height);
// 限制最小尺寸为100px
if (currentWidth > 150 && currentHeight > 150) {
const newWidth = currentWidth - 50;
const newHeight = currentHeight - 50;
display.style.width = newWidth + 'px';
display.style.height = newHeight + 'px';
// 更新原始尺寸(用于切换放大缩小)
originalWidth = display.style.width;
originalHeight = display.style.height;
// 更新提示字体尺寸
updateAlertFontSize(display);
// 保存当前状态
saveDisplayPosition(display.style.top, display.style.right, display.style.width, display.style.height, currentIsMinimized, sizeBeforeMinimize);
}
});
// 更新提示字体尺寸函数
function updateAlertFontSize(displayElement) {
const currentWidth = parseInt(displayElement.style.width);
const alert1Element = document.getElementById('erp-alert-1');
const alert2Element = document.getElementById('erp-alert-2');
// 基于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';
}
}
// 初始化提示字体尺寸
setTimeout(() => {
updateAlertFontSize(display);
}, 100);
// 添加拖动功能
let isDragging = false;
let startX, startY, startTop, startRight;
let originalTransition;
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
saveDisplayPosition(display.style.top, display.style.right, display.style.width, display.style.height, currentIsMinimized, sizeBeforeMinimize);
}
});
// 鼠标悬停时显示按钮
display.addEventListener('mouseenter', function() {
if (!currentIsMinimized) {
buttonContainer.style.opacity = '1';
}
});
display.addEventListener('mouseleave', function() {
if (!currentIsMinimized) {
buttonContainer.style.opacity = '0.8';
}
});
return display;
}
// 获取当前页面的基础URL(用于位置和尺寸存储的键)
function getBaseUrl() {
const url = new URL(window.location.href);
return `${url.protocol}//${url.host}${url.pathname}`;
}
// 保存显示框位置和尺寸到localStorage,按网址存储
function saveDisplayPosition(top, right, width, height, isMinimized, sizeBeforeMinimize = null) {
try {
const baseUrl = getBaseUrl();
const positions = JSON.parse(localStorage.getItem('erpImageDisplayPositions') || '{}');
positions[baseUrl] = {
top: top,
right: right,
width: width,
height: height,
isMinimized: isMinimized,
sizeBeforeMinimize: sizeBeforeMinimize
};
localStorage.setItem('erpImageDisplayPositions', JSON.stringify(positions));
console.log('💾 已保存图片显示框位置和尺寸:', { top, right, width, height, isMinimized, sizeBeforeMinimize, url: baseUrl });
} catch (error) {
console.error('❌ 保存位置和尺寸失败:', error);
}
}
// 从localStorage加载显示框位置和尺寸,按网址加载
function loadDisplayPosition() {
try {
const baseUrl = getBaseUrl();
const positions = JSON.parse(localStorage.getItem('erpImageDisplayPositions') || '{}');
if (positions[baseUrl]) {
const position = positions[baseUrl];
console.log('📥 已加载保存的图片显示框位置和尺寸:', position, '来自网址:', baseUrl);
return position;
}
} catch (error) {
console.error('❌ 加载位置和尺寸失败:', error);
}
return null;
}
// 通用的URL查找函数 - 查找第一个URL,不限定图片格式
function 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)的函数
function 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;
}
// 从cookie中提取指定名称的值
function getCookie(name) {
const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
return match ? match[2] : null;
}
// 生成UUID函数,用于生成webbox-request-id
function 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);
});
}
// 检查价格变动并显示提示
async function checkPriceChange(sku_id) {
// 从cookie中提取u_id和u_co_id
const u_id = getCookie('u_id');
const u_co_id = getCookie('u_co_id');
console.log('🔍 开始检查价格变动');
console.log('📦 产品编码:', sku_id);
if (!u_id || !u_co_id) {
console.log('❌ 无法从cookie中获取u_id或u_co_id');
return;
}
try {
// 构建请求URL和表单数据
const url = `https://apiweb.erp321.com/webapi/ItemApi/Log/GetPageListV2?owner_co_id=${u_co_id}&authorize_co_id=${u_co_id}`;
const formData = {
"ip": "",
"uid": u_id,
"coid": u_co_id,
"page": {
"currentPage": 1,
"pageSize": 20,
"starSize": 1,
"hasPageInfo": true
},
"data": {
"type": 1,
"id": sku_id,
"creators": [],
"beginDate": "",
"endDate": "",
"onlyDel": false,
"name": "修改普通商品资料"
},
"isOtherStore": false
};
console.log('📤 发送价格变动检查请求');
// 从当前页面获取路径信息
const currentPath = window.location.pathname;
// 构建headers对象,只保留必需字段
const headers = {
'accept': 'application/json',
'content-type': 'application/json; charset=utf-8',
'gwfp': getCookie('gwfp') || '',
'u_sso_token': getCookie('u_sso_token') || '',
'webbox-request-id': generateUUID(),
'webbox-route-path': currentPath,
'Cookie': document.cookie
};
// 发送POST请求,添加credentials: 'include'解决跨域cookie问题
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(formData),
credentials: 'include',
mode: 'cors'
});
const responseData = await response.json();
// 检查响应是否包含data数组
if (responseData.data && Array.isArray(responseData.data)) {
// 遍历data数组
for (const item of responseData.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];
// 格式化修改日期为yyyy-MM-dd格式
let formattedDate = '未知日期';
if (item.created) {
try {
const date = new Date(item.created);
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);
}
}
console.log('💰 发现价格变动:', `${oldPrice}->${newPrice} (修改日期: ${formattedDate})`);
// 显示红字提示
const alert3 = document.createElement('div');
alert3.id = 'erp-alert-3';
alert3.style.cssText = `
background-color: rgba(255, 0, 0, 0.1);
color: red;
font-weight: bold;
padding: 5px 10px;
border-radius: 4px;
margin-bottom: 5px;
display: block;
font-size: 14px;
transition: all 0.3s ease;
`;
alert3.textContent = `价格有变动: ${oldPrice}->${newPrice} (修改日期: ${formattedDate})`;
// 添加到提示容器
const alertContainer = document.getElementById('erp-image-alerts');
if (alertContainer) {
// 移除已存在的价格变动提示
const existingAlert3 = document.getElementById('erp-alert-3');
if (existingAlert3) {
existingAlert3.remove();
}
alertContainer.appendChild(alert3);
console.log('✅ 价格变动提示已显示');
}
return;
}
}
}
console.log('📭 未发现价格变动');
} else {
console.log('❌ 响应不包含有效的数据');
}
} catch (error) {
console.error('❌ 检查价格变动失败:', error.message);
}
}
// 更新显示的图片和提示信息
function updateDisplayedImage(picUrl, productInfo = {}) {
let imgElement = document.getElementById('erp-displayed-image');
if (!imgElement) {
createImageDisplay();
imgElement = document.getElementById('erp-displayed-image');
}
imgElement.src = picUrl;
console.log('📷 已更新图片显示:', picUrl);
// 获取提示信息元素
const alert1 = document.getElementById('erp-alert-1');
const alert2 = document.getElementById('erp-alert-2');
const existingAlert3 = document.getElementById('erp-alert-3');
// 重置提示信息
if (alert1) alert1.style.display = 'none';
if (alert2) alert2.style.display = 'none';
if (existingAlert3) existingAlert3.remove();
// 根据产品信息显示提示
if (alert1 && productInfo.name) {
if (productInfo.name.toLowerCase().includes('18k')) {
alert1.textContent = '金镶嵌,注意包装';
alert1.style.display = 'block';
}
}
if (alert2 && productInfo.taxAfterPrice !== null) {
if (productInfo.taxAfterPrice >= 600) {
alert2.textContent = '货品贵重,注意包装';
alert2.style.color = 'gray';
alert2.style.display = 'block';
}
}
// 检查价格变动
if (productInfo.sku_id) {
checkPriceChange(productInfo.sku_id);
}
}
// 解析请求数据(支持字符串、FormData等)
function 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
function isRelevantRequest(url, data) {
// 检查URL参数
const urlStr = typeof url === 'string' ? url : url instanceof URL ? url.href : '';
const urlContainsTarget = urlStr.includes('__CALLBACKPARAM') &&
((urlStr.includes('"Method":"CheckQty"') || urlStr.includes("'Method':'CheckQty'")) ||
(urlStr.includes('"Method":"LoadDataToJSON"') || urlStr.includes("'Method':'LoadDataToJSON'")));
if (urlContainsTarget) {
return true;
}
// 解析请求体数据
const dataStr = parseRequestData(data);
// 检查请求体
if (!dataStr.includes('__CALLBACKPARAM')) return false;
// 提取__CALLBACKPARAM的值
const callbackParamMatch = dataStr.match(/__CALLBACKPARAM=([^&]+)/);
if (!callbackParamMatch) return false;
// 解码URL参数
const callbackParam = decodeURIComponent(callbackParamMatch[1]);
// 检查是否包含Method:CheckQty或Method:LoadDataToJSON
const isMatch = callbackParam.includes('"Method":"CheckQty"') ||
callbackParam.includes("'Method':'CheckQty'") ||
callbackParam.includes('"Method":"LoadDataToJSON"') ||
callbackParam.includes("'Method':'LoadDataToJSON'");
return isMatch;
}
// 保存原始的XMLHttpRequest发送方法
const originalXHRSend = XMLHttpRequest.prototype.send;
// 重写XMLHttpRequest的send方法以监控请求
XMLHttpRequest.prototype.send = function(data) {
// 保存当前实例
const xhr = this;
// 检查请求是否是相关请求(CheckQty或LoadDataToJSON)
const isRelevant = isRelevantRequest(xhr.responseURL, data);
// 只处理相关请求
if (isRelevant) {
// 监听响应
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState === 4) {
// 处理响应内容,移除可能的前缀
let responseText = xhr.responseText;
// 移除可能的"0|"前缀
if (responseText.startsWith('0|')) {
responseText = responseText.substring(2);
}
try {
// 尝试解析响应内容
const response = JSON.parse(responseText);
// console.log('✅ XMLHttpRequest成功解析响应:', JSON.stringify(response));
// 检查响应是否包含ReturnValue
if (response.ReturnValue && typeof response.ReturnValue === 'string') {
// 解析ReturnValue字符串(可能是嵌套JSON数组)
const returnValueObj = JSON.parse(response.ReturnValue);
// console.log('✅ XMLHttpRequest成功解析ReturnValue:', JSON.stringify(returnValueObj));
// 使用通用函数查找图片链接
const picUrl = findFirstImageUrl(returnValueObj);
if (picUrl) {
// 提取产品信息
const productInfo = extractProductInfo(returnValueObj);
updateDisplayedImage(picUrl, productInfo);
} else {
console.log('❌ XMLHttpRequest未找到图片URL');
}
}
} catch (error) {
console.error('❌ XMLHttpRequest响应解析错误:', error);
console.error('📄 XMLHttpRequest原始响应文本:', responseText);
}
}
});
}
// 调用原始的send方法
originalXHRSend.call(xhr, data);
};
// 同样监控fetch请求
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
// 解析URL
const requestUrl = typeof url === 'string' ? url : url.href;
// 检查请求是否是相关请求(CheckQty或LoadDataToJSON)
const isRelevant = 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;
// 移除可能的"0|"前缀
if (responseText.startsWith('0|')) {
responseText = responseText.substring(2);
}
// 尝试解析为JSON
try {
const responseJson = JSON.parse(responseText);
// console.log('✅ fetch成功解析响应:', JSON.stringify(responseJson));
// 检查响应是否包含ReturnValue
if (responseJson.ReturnValue && typeof responseJson.ReturnValue === 'string') {
// 解析ReturnValue字符串(可能是嵌套JSON数组)
const returnValueObj = JSON.parse(responseJson.ReturnValue);
// console.log('✅ fetch成功解析ReturnValue:', JSON.stringify(returnValueObj));
// 使用通用函数查找图片链接
const picUrl = findFirstImageUrl(returnValueObj);
if (picUrl) {
// 提取产品信息
const productInfo = extractProductInfo(returnValueObj);
updateDisplayedImage(picUrl, productInfo);
} else {
console.log('❌ fetch未找到图片URL');
}
}
} catch (jsonError) {
console.error('❌ fetch JSON解析错误:', jsonError);
console.error('📄 fetch原始响应文本:', responseText);
}
} catch (error) {
console.error('❌ fetch响应处理错误:', error);
}
}
// 返回原始响应
return response;
};
console.log('ERP 图片显示显示 v3.1 已启动,正在监控包含__CALLBACKPARAM且Method为CheckQty或LoadDataToJSON的请求...');
console.log('将在页面右上角显示产品图片,支持拖动和按网址位置记忆。');
})();