// ==UserScript==
// @name 联通卡券助手 V11.1 (抽屉交互+ID展示版)
// @namespace http://tampermonkey.net/
// @version 11.1
// @description 1.列表内嵌抽屉式展示商品 2.ID改为纯展示 3.保留V9样式布局 4.静默兑换
// @author Analysis
// @match https://qy.chinaunicom.cn/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log('%c== 联通助手 V11.1 (ID展示版) 已启动 ==', 'color:white; background:#2196F3; font-size:16px;');
let currentCouponContext = null;
let currentPage = 1;
// ==========================================
// 1. 拦截器
// ==========================================
const originalFetch = window.fetch;
window.fetch = async function(...args) {
const response = await originalFetch(...args);
const clone = response.clone();
if (response.url && response.url.includes('queryCouponList')) {
clone.json().then(data => {
if(data && data.data) handleCouponList(data.data);
}).catch(e => {});
}
return response;
};
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._url = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
this.addEventListener('readystatechange', function() {
if (this.readyState === 4 && this._url && this._url.includes('queryCouponList')) {
try {
const res = JSON.parse(this.responseText);
if(res && res.data) handleCouponList(res.data);
} catch (e) {}
}
});
return originalSend.apply(this, arguments);
};
// ==========================================
// 2. 辅助功能
// ==========================================
function showToast(msg, type = 'info') {
const div = document.createElement('div');
div.innerText = msg;
div.style.cssText = `
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9); color: white; padding: 10px 20px;
border-radius: 6px; font-size: 13px; z-index: 1000002;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); text-align: center;
pointer-events: none; border: 1px solid #444;
`;
if(type === 'success') div.style.color = '#00e676';
if(type === 'error') div.style.color = '#ff5252';
document.body.appendChild(div);
setTimeout(() => div.remove(), 2000);
}
function enableDrag(element, handle) {
let isDragging = false, startX, startY, initialLeft, initialTop;
const start = (x, y) => {
isDragging = true; startX = x; startY = y;
const rect = element.getBoundingClientRect();
initialLeft = rect.left; initialTop = rect.top;
element.style.right = 'auto'; element.style.left = initialLeft + 'px'; element.style.top = initialTop + 'px';
};
const move = (x, y) => {
if (!isDragging) return;
element.style.left = (initialLeft + (x - startX)) + 'px';
element.style.top = (initialTop + (y - startY)) + 'px';
};
const end = () => { isDragging = false; };
(handle||element).addEventListener('mousedown', e => { e.preventDefault(); start(e.clientX, e.clientY); });
document.addEventListener('mousemove', e => move(e.clientX, e.clientY));
document.addEventListener('mouseup', end);
(handle||element).addEventListener('touchstart', e => { const t=e.touches[0]; start(t.clientX, t.clientY); }, {passive:false});
document.addEventListener('touchmove', e => { if(isDragging){ e.preventDefault(); const t=e.touches[0]; move(t.clientX, t.clientY); } }, {passive:false});
document.addEventListener('touchend', end);
}
// ==========================================
// 3. 业务逻辑 (抽屉式交互)
// ==========================================
function handleCouponList(list) {
if(list && list.length > 0) list = list.filter(i => !i.consumeWay);
const listContainer = document.getElementById('uni-coupon-list');
if(!listContainer) return;
listContainer.innerHTML = '';
document.getElementById('uni-status').innerText = `第${currentPage}页: ${list.length}张`;
if (list.length === 0) {
listContainer.innerHTML = '
当前页无数据
';
return;
}
list.forEach((item, index) => {
// 外层容器
const rowWrapper = document.createElement('div');
rowWrapper.style.cssText = 'border-bottom:1px dashed #444; background:#1e1e1e; margin-bottom:2px; transition: background 0.2s;';
// 卡券信息行
const infoRow = document.createElement('div');
infoRow.style.cssText = 'padding: 8px; font-size:12px; position:relative;';
infoRow.innerHTML = `
${index+1}. ${item.couponName}
${item.source}
ID: ${item.couponId}
`;
// 抽屉容器 (默认隐藏)
const drawerDiv = document.createElement('div');
drawerDiv.className = 'goods-drawer';
drawerDiv.style.cssText = 'display:none; background:#111; border-top:1px solid #333; padding:5px;';
drawerDiv.innerHTML = '加载中...
';
rowWrapper.appendChild(infoRow);
rowWrapper.appendChild(drawerDiv);
listContainer.appendChild(rowWrapper);
// 绑定事件
const toggleBtn = infoRow.querySelector('.btn-toggle-drawer');
toggleBtn.onclick = function(e) {
e.stopPropagation();
// 切换显示状态
if (drawerDiv.style.display === 'none') {
// 展开
drawerDiv.style.display = 'block';
toggleBtn.innerText = '▲ 收起';
toggleBtn.style.background = '#444';
rowWrapper.style.background = '#252526';
// 如果还没有加载过商品,则加载
if(!drawerDiv.hasAttribute('data-loaded')) {
fetchActGoods(item, drawerDiv);
}
} else {
// 收起
drawerDiv.style.display = 'none';
toggleBtn.innerText = '▼ 展开兑换选项';
toggleBtn.style.background = '#333';
rowWrapper.style.background = '#1e1e1e';
}
};
});
}
// 获取商品并渲染到抽屉里
function fetchActGoods(couponItem, targetContainer) {
currentCouponContext = couponItem; // 暂存上下文
const actId = couponItem.actId;
const url = `/mobile/coupon/getActByCatID?id=${actId}`;
fetch(url, { headers: {'X-Requested-With': 'XMLHttpRequest'} })
.then(r => r.json())
.then(res => {
let goodsList = [];
let relationId = "";
if (res.data && res.data.length > 0) {
relationId = res.data[0].actId;
if (res.data[0].actGoods) {
// 过滤逻辑保持不变
goodsList = res.data[0].actGoods.filter(i => i.goodsName.includes('腾讯视频'));
}
}
renderGoodsToDrawer(goodsList, relationId, targetContainer, couponItem);
targetContainer.setAttribute('data-loaded', 'true');
})
.catch(err => {
targetContainer.innerHTML = `查询失败: ${err.message}
`;
});
}
// 渲染商品列表到抽屉
function renderGoodsToDrawer(goods, relationId, container, couponContext) {
container.innerHTML = '';
if (!goods || goods.length === 0) {
container.innerHTML = '无匹配商品 (腾讯视频)
';
return;
}
goods.forEach(g => {
const item = document.createElement('div');
item.style.cssText = 'display:flex; align-items:center; background:#222; margin-bottom:4px; padding:4px; border-radius:4px; border:1px solid #333;';
item.innerHTML = `
${g.goodsName}
`;
const btn = item.querySelector('.btn-do-swap');
btn.onclick = function() {
// 确保上下文是当前点击的这个券 (防止多开抽屉后上下文错乱)
currentCouponContext = couponContext;
doWriteOff(g, relationId);
};
container.appendChild(item);
});
}
function doWriteOff(goodItem, relationId) {
// 修改点:直接从展示 Span 中获取 ID,不再查找 Input
const userIdSpan = document.getElementById('uni-userid-display');
const currentUserId = userIdSpan ? userIdSpan.innerText.trim() : "";
if(!currentUserId || currentUserId.includes("未找到")) {
showToast("未找到UserID", "error");
return;
}
showToast("请求中...", "info");
const params = new URLSearchParams();
params.append('couponId', currentCouponContext.couponId);
params.append('createTimeStr', currentCouponContext.createTimeStr || currentCouponContext.createTime);
params.append('id', currentCouponContext.id);
params.append('actId', currentCouponContext.actId);
params.append('goodsIdFrom', goodItem.goodsId);
params.append('smsCode', '');
params.append('relationId', relationId);
params.append('subPage', window.location.href);
const countParamObj = {
"pageUrl": window.location.href,
"originalPageUrl": window.location.href.split('?')[0],
"fPageUrl": "",
"operateName": "",
"type": "",
"countType": 1,
"ip": "",
"cityName": "",
"userId": currentUserId,
"channelId": "",
"source": "3",
"provinceName": "",
"operateSystem": "4"
};
fetch(`/mobile/coupon/couponWriteOff?countParam=${encodeURIComponent(JSON.stringify(countParamObj))}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' },
body: params.toString()
})
.then(r => r.json())
.then(data => {
if (data.responseResult == "1") {
showToast("兑换成功! 刷新中...", "success");
setTimeout(() => triggerFetchCoupons(currentPage), 800);
} else {
showToast(data.errorMsg, "error");
}
})
.catch(err => showToast("网络错误", "error"));
}
function triggerFetchCoupons(page) {
const statusSpan = document.getElementById('uni-status');
if(statusSpan) statusSpan.innerText = `加载 P${page}...`;
fetch(`/mobile/epom/coupon/queryCouponList?couponCatgId=8&status=2&type=1&pageNo=${page}`, {
headers: {'X-Requested-With': 'XMLHttpRequest'}
}).then(r=>r.json()).then(d=>{
const pageSpan = document.getElementById('uni-page-num');
if(pageSpan) pageSpan.innerText = page;
if(d.data) handleCouponList(d.data);
else showToast('该页无数据', 'info');
});
}
// ==========================================
// 4. UI 构建 (V9 样式 + 悬浮球)
// ==========================================
function createUI() {
// --- 悬浮球 ---
const ball = document.createElement('div');
ball.id = 'uni-float-ball';
ball.style.cssText = `
position: fixed; top: 10px; right: 10px; width: 45px; height: 45px;
background: rgba(30, 30, 30, 0.95); border: 2px solid #555;
border-radius: 50%; box-shadow: 0 4px 10px rgba(0,0,0,0.5);
z-index: 999998; cursor: pointer; display: flex;
justify-content: center; align-items: center; color: white;
font-size: 12px; user-select: none;
`;
ball.innerText = "助手";
document.body.appendChild(ball);
// --- 主面板 ---
const div = document.createElement('div');
div.id = 'uni-helper-panel';
div.style.cssText = `
position: fixed; top: 20px; right: 20px;
width: 320px; max-height: 90vh;
background: rgba(30, 30, 30, 0.95); color: white;
border-radius: 8px; z-index: 999999;
font-family: 'Segoe UI', sans-serif; font-size: 13px;
box-shadow: 0 4px 15px rgba(0,0,0,0.6);
display: none; flex-direction: column;
border: 1px solid #444;
`;
// 标题栏
const header = document.createElement('div');
header.style.cssText = `
padding: 10px; border-bottom: 1px solid #555;
font-weight: bold; background: #263238;
border-top-left-radius: 8px; border-top-right-radius: 8px;
cursor: move; user-select: none; display: flex; justify-content: space-between; align-items: center;
`;
header.innerHTML = `
卡券助手
就绪
➖
`;
div.appendChild(header);
// ID 展示区 (修改点:不再是 Input)
const configBar = document.createElement('div');
configBar.style.cssText = 'padding: 8px; background: #333; border-bottom: 1px solid #444; font-size:12px; color:#ccc; display:flex; align-items:center;';
// 读取 ID
let autoId = localStorage.getItem('qyhm') || localStorage.getItem('mqerDpb') || localStorage.getItem('uni_saved_userid') || '未找到(请登录)';
if(autoId && (autoId.startsWith('{') || autoId.startsWith('"'))) { try { autoId = JSON.parse(autoId); } catch(e){} }
configBar.innerHTML = `
User:
${autoId}
`;
div.appendChild(configBar);
// 翻页栏
const actionBar = document.createElement('div');
actionBar.style.cssText = 'padding: 8px; display: flex; gap: 5px; justify-content: center; background: #333; border-bottom: 1px solid #444;';
actionBar.innerHTML = `
1
`;
div.appendChild(actionBar);
// 列表
const listDiv = document.createElement('div');
listDiv.id = 'uni-coupon-list';
listDiv.style.cssText = 'flex: 1; overflow-y: auto; min-height: 150px; background: #1e1e1e; padding:5px;';
listDiv.innerHTML = '请点击刷新
';
div.appendChild(listDiv);
document.body.appendChild(div);
// --- 交互逻辑 ---
// 拖拽
enableDrag(ball);
enableDrag(div, header);
// 显隐切换
ball.onclick = () => {
ball.style.display = 'none';
div.style.display = 'flex';
};
document.getElementById('btn-minimize').onclick = (e) => {
e.stopPropagation(); // 防止触发header拖拽等奇葩问题
div.style.display = 'none';
ball.style.display = 'flex';
};
// 翻页
document.getElementById('btn-prev').onclick = () => { if(currentPage>1) { currentPage--; triggerFetchCoupons(currentPage); } };
document.getElementById('btn-next').onclick = () => { currentPage++; triggerFetchCoupons(currentPage); };
document.getElementById('btn-load').onclick = () => triggerFetchCoupons(currentPage);
}
window.addEventListener('load', () => setTimeout(createUI, 500));
})();