// ==UserScript==
// @name 卡世界数据导出器
// @namespace http://tampermonkey.net/
// @license Apache-2.0
// @version 0.4
// @author byhgz
// @description 卡世界功能拓展
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @noframes
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @match https://ksjhaoka.com/*
// @require https://cdn.jsdelivr.net/npm/vue@2
// @require https://unpkg.com/element-ui/lib/index.js
// ==/UserScript==
"use strict";
(function (Vue) {
'use strict';
var defCss = `
body {
overflow: hidden;
}
.el-vertical-center {
display: flex;
justify-content: center;
}
.el-horizontal-center {
display: flex;
align-items: center;
}
.el-horizontal-right {
display: flex;
justify-content: flex-end;
}
.el-horizontal-left {
display: flex;
justify-content: flex-start;
}
`;
class EventEmitter {
#regularEvents = {
events: {},
futures: {}
}
#callbackEvents = {
events: {},
callbackInterval: 1500
}
on(eventName, callback) {
const events = this.#regularEvents.events;
if (events[eventName]) {
events[eventName].push(callback);
return
}
events[eventName] = [];
events[eventName].push(callback);
const futureEvents = this.#regularEvents.futures;
if (futureEvents[eventName]) {
for (const futureEvent of futureEvents[eventName]) {
callback(...futureEvent);
}
delete futureEvents[eventName];
}
}
once(eventName, callback) {
const onceCallback = (...args) => {
callback(...args);
this.#removeCallback(eventName, onceCallback);
};
this.on(eventName, onceCallback);
}
handler(eventName, callback) {
const handlerEvents = this.#callbackEvents.events;
if (handlerEvents[eventName]) {
throw new Error('该事件名已经存在,请更换事件名')
}
handlerEvents[eventName] = callback;
}
invoke(eventName, ...data) {
return new Promise(resolve => {
const handlerEvents = this.#callbackEvents.events;
if (handlerEvents[eventName]) {
resolve(handlerEvents[eventName](...data));
return
}
const i1 = setInterval(() => {
if (handlerEvents[eventName]) {
clearInterval(i1);
resolve(handlerEvents[eventName](...data));
}
}, this.#callbackEvents.callbackInterval);
})
}
send(eventName, ...data) {
const ordinaryEvents = this.#regularEvents;
const events = ordinaryEvents.events;
const event = events[eventName];
if (event) {
for (const callback of event) {
callback(...data);
}
return;
}
const futures = ordinaryEvents.futures;
if (futures[eventName]) {
futures[eventName].push(data);
return;
}
futures[eventName] = [];
futures[eventName].push(data);
}
#removeCallback(eventName, callback) {
const events = this.#regularEvents.events;
if (events[eventName]) {
events[eventName] = events[eventName].filter(cb => cb !== callback);
}
const handlerEvents = this.#callbackEvents.events;
if (handlerEvents[eventName]) {
handlerEvents[eventName] = handlerEvents[eventName].filter(cb => cb !== callback);
}
}
off(eventName) {
const events = this.#regularEvents.events;
if (events[eventName]) {
delete events[eventName];
return true
}
const handlerEvents = this.#callbackEvents.events;
if (handlerEvents[eventName]) {
delete handlerEvents[eventName];
return true
}
return false
}
setInvokeInterval(interval) {
this.#callbackEvents.callbackInterval = interval;
}
getEvents() {
return {
regularEvents: this.#regularEvents,
callbackEvents: this.#callbackEvents
}
}
}
const eventEmitter = new EventEmitter();
const look_content_dialog_vue = {
template: `
`,
data() {
return {
dialogVisible: false,
content: ''
}
},
methods: {
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {
});
}
},
created() {
eventEmitter.on('展示内容对话框', (newContent) => {
this.content = newContent;
this.$message('已更新内容');
this.dialogVisible = true;
});
}
};
function findElementUntilFound(selector, config = {}) {
const defConfig = {
doc: document,
interval: 1000,
timeout: -1,
};
config = {...defConfig, ...config};
return new Promise((resolve, reject) => {
const i1 = setInterval(() => {
const element = config.doc.querySelector(selector);
if (element) {
resolve(element);
clearInterval(i1);
}
}, config.interval);
if (config.timeout > 0) {
setTimeout(() => {
clearInterval(i1);
reject(null); // 超时则返回 null
}, config.timeout);
}
});
}
const findElement = async (selector, config = {}) => {
try {
const defConfig = {
doc: document,
interval: 1000,
timeout: -1,
};
config = {...defConfig, ...config};
const el = await findElementUntilFound(selector, config);
if (config.timeout === -1) {
return el
}
return {state: true, data: el}
} catch (e) {
return {state: false, data: e}
}
};
const findElements = async (selector, config = {}) => {
const defConfig = {doc: document, interval: 1000, timeout: -1};
config = {...defConfig, ...config};
try {
const elList = await findElementsUntilFound(selector, config);
if (config.timeout === -1) {
return elList
}
return {state: true, data: elList}
} catch (e) {
return {state: false, data: e}
}
};
function findElementsUntilFound(selector, config = {}) {
const defConfig = {doc: document, interval: 1000, timeout: -1};
config = {...defConfig, ...config};
return new Promise((resolve, reject) => {
const i1 = setInterval(() => {
const elements = config.doc.querySelectorAll(selector);
if (elements.length > 0) {
resolve(Array.from(elements));
clearInterval(i1);
}
}, config.interval);
if (config.timeout > 0) {
setTimeout(() => {
clearInterval(i1);
reject(null); // 超时则返回 null
}, config.timeout);
}
});
}
var elUtil = {
findElement,
findElements
};
const isGoodsSalePage = (url = window.location.href) => {
return url.endsWith('ksjhaoka.com/#/goods/sale');
};
const getPromotionCenterData = async () => {
const elList = await elUtil.findElements('.app-main .el-row>div');
const list = [];
for (const el of elList) {
const data = {};
data["运营商"] = el.querySelector(".goods-header-title").textContent.match(/运营商:(.+)/)[1];
data["上架时间"] = el.querySelector(".goods-header-time").textContent.match(/上架时间:(.+)/)[1];
const originalTitle = el.querySelector(".overflow-ellipsis").textContent;
data["卡名"] = originalTitle.substring(2, originalTitle.indexOf("卡") + 1);
data["原始标题"] = originalTitle;
const tempTitleInfo = originalTitle.substring(originalTitle.indexOf("卡") + 1);
const monthlyRentIndexOf = tempTitleInfo.indexOf("元");
let temp_monthlyRent;
let mainParameter;//主要参数
if (monthlyRentIndexOf !== -1) {
temp_monthlyRent = tempTitleInfo.substring(0, monthlyRentIndexOf);
mainParameter = tempTitleInfo.substring(monthlyRentIndexOf + 1, tempTitleInfo.length);
} else {
temp_monthlyRent = "未描述";
mainParameter = "获取失败";
}
data["主要参数"] = mainParameter;
data["月租"] = temp_monthlyRent;
const tagsEl = el.querySelectorAll(".goods-tab>span");
const placeOfBelonging = tagsEl[0].textContent;
data["归属地"] = placeOfBelonging.includes("归属地") ? placeOfBelonging.match(/(.+)归属地/)[1] : placeOfBelonging;
data["是否支持选号"] = mainParameter.includes("支持选号") ? "是" : "未知";
data["是否永久套餐"] = mainParameter.includes("永久套餐") ? "是" : "未知";
data["是否支持结转"] = mainParameter.includes("支持结转") ? "是" : "未知";
data["是否长期套餐"] = mainParameter.includes("长期套餐") ? "是" : "未知";
data["快递"] = tagsEl[1].textContent;
data["合约"] = tagsEl[2].textContent;
data["年龄要求"] = tagsEl[3].textContent;
let tempActivationPlan;
if (tagsEl.length === 5) {
tempActivationPlan = tagsEl[4].textContent;
} else {
tempActivationPlan = "未描述";
}
data["激活方案"] = tempActivationPlan;
data["佣金"] = el.querySelector(".goods-brokerage div").textContent;
for (const li of el.querySelectorAll(".goods-info-ul>.goods-info-li")) {
const strings = li.querySelector(".goods-info-value").textContent.split(":");
data[strings[0]] = strings[1];
}
data["img"] = el.querySelector(".goods-img").src;
for (const key in data) {
data[key] = data[key].trim();
}
list.push(data);
}
return list;
};
const appendPromotionCenterData = async (list) => {
const pageList = await getPromotionCenterData();
const newDataList = [];
const oldSize = list.length;
for (let newData of pageList) {
if (list.some(item => item["产品ID"] === newData["产品ID"])) {
continue
}
newDataList.push(newData);
}
for (let data of newDataList) {
list.push(data);
}
return oldSize < list.length
};
var goodsSale = {
isGoodsSalePage,
appendPromotionCenterData,
getPromotionCenterData
};
const fileDownload = (content, fileName) => {
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
const parseUrl = (urlString) => {
const url = new URL(urlString);
const pathSegments = url.pathname.split('/').filter(segment => segment !== '');
const searchParams = new URLSearchParams(url.search.slice(1));
const queryParams = {};
for (const [key, value] of searchParams.entries()) {
queryParams[key] = value;
}
return {
protocol: url.protocol,
hostname: url.hostname,
port: url.port,
pathname: url.pathname,
pathSegments,
search: url.search,
queryParams,
hash: url.hash
};
};
var defUtil = {
fileDownload,
parseUrl
};
const promotion_center_vue = {
template: `
数据信息
数据
获取推广中心列表数据(当前页)
查看数据
清空数据
导出
导出数据(json文件)
导出数据(控制台)
`,
data() {
return {
dataList: []
}
},
methods: {
clearDataBut() {
this.dataList = [];
this.$message('已清空数据!');
},
lookDataBut() {
this.$message('获取成功!');
eventEmitter.send('展示内容对话框', JSON.stringify(this.dataList, null, 4));
},
outDataToConsoleBut() {
if (this.dataList.length === 0) {
this.$alert('数据为空,请先获取数据');
return
}
console.log("===========当前页面产品列表===========");
console.log(this.dataList);
console.log("===========当前页面产品列表===========");
this.$alert('已导出到控制台,可通过f12查看');
},
outDataBut() {
const content = JSON.stringify(this.dataList, null, 4);
const size = this.dataList.length;
defUtil.fileDownload(content, `卡世界产品管理-推广中心-${size}.json`);
this.$message('已导出数据!');
},
async getPromotionCenterDataBut() {
if (!goodsSale.isGoodsSalePage()) {
this.$message('当前页面不是推广中心页面');
return
}
const isAppendMode = await eventEmitter.invoke('isAppendMode');
if (isAppendMode) {
const bool = await goodsSale.appendPromotionCenterData(this.dataList);
if (bool) {
this.$message('已追加成功!');
} else {
this.$message('追加失败,数据未变化');
}
} else {
this.dataList = await goodsSale.getPromotionCenterData();
}
const isViewMode = await eventEmitter.invoke('isViewMode');
if (isViewMode) {
this.lookDataBut();
}
}
}
};
const product_management_vue = {
template: `
`,
components: {
promotion_center_vue
},
data() {
return {
isAppendMode: true,
isViewMode: false
}
},
methods: {},
created() {
eventEmitter.handler('isAppendMode', () => {
return this.isAppendMode
});
eventEmitter.handler('isViewMode', () => {
return this.isViewMode
});
}
};
const mainLayoutEl = document.createElement('div');
mainLayoutEl.style.position = 'fixed';
mainLayoutEl.style.left = '0';
mainLayoutEl.style.top = '0';
mainLayoutEl.style.width = '100%';
mainLayoutEl.style.height = '100%';
document.body.appendChild(mainLayoutEl);
if (document.head.querySelector('#element-ui-css') === null) {
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
linkElement.id = 'element-ui-css';
document.head.appendChild(linkElement);
console.log('挂载element-ui样式成功');
}
new Vue({
el: mainLayoutEl,
template: `
`,
components: {
look_content_dialog_vue,
product_management_vue,
},
data() {
return {
drawer: true
}
},
methods: {
},
created() {
eventEmitter.on('主面板开关', () => {
this.drawer = !this.drawer;
});
}
});
const styleEl = document.createElement('style');
styleEl.innerHTML = defCss;
document.head.appendChild(styleEl);
const addGMMenu = (text, func, shortcutKey = null) => {
return GM_registerMenuCommand(text, func, shortcutKey);
};
var gmUtil = {
addGMMenu
};
const staticRoute = (url = window.location.href, title = document.title) => {
const parseUrl = defUtil.parseUrl(url);
console.log('静态路由:', url, parseUrl);
};
const dynamicRouting = () => {
let oldUrl = window.location.href;
setInterval(() => {
const newUrl = window.location.href;
if (oldUrl === newUrl) return;
oldUrl = newUrl;
const title = document.title;
callback(newUrl, title);
}, 1000);
const callback = (url, title) => {
const parseUrl = defUtil.parseUrl(url);
console.log('动态路由:', parseUrl, url, title);
};
};
var router = {
staticRoute,
dynamicRouting
};
window.addEventListener('load', () => {
console.log('卡世界页面加载完成');
router.staticRoute();
router.dynamicRouting();
});
document.addEventListener('keydown', function (event) {
if (event.key === "`") {
eventEmitter.send('主面板开关');
}
});
gmUtil.addGMMenu('主面板开关展示', () => {
eventEmitter.send('主面板开关');
});
gmUtil.addGMMenu("获取当前产品页面列表", async () => {
if (!goodsSale.isGoodsSalePage()) {
alert("当前页面不是产品管理");
return;
}
const data = goodsSale.getPromotionCenterData();
console.log("===========当前页面产品列表===========");
console.log(data);
console.log("===========当前页面产品列表===========");
alert("已打印在控制台上");
});
})(Vue);