卡世界数据导出器

卡世界功能拓展

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        卡世界数据导出器
// @namespace   http://tampermonkey.net/
// @license     Apache-2.0
// @version     0.5.1
// @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(t){"use strict";const e=new class{#t={events:{},futures:{}};#e={events:{},callbackInterval:1500};on(t,e){const n=this.#t.events;if(n[t])return void n[t].push(e);n[t]=[],n[t].push(e);const a=this.#t.futures;if(a[t]){for(const n of a[t])e(...n);delete a[t]}}once(t,e){const n=(...a)=>{e(...a),this.#n(t,n)};this.on(t,n)}handler(t,e){const n=this.#e.events;if(n[t])throw new Error("该事件名已经存在,请更换事件名");n[t]=e}invoke(t,...e){return new Promise((n=>{const a=this.#e.events;if(a[t])return void n(a[t](...e));const l=setInterval((()=>{a[t]&&(clearInterval(l),n(a[t](...e)))}),this.#e.callbackInterval)}))}send(t,...e){const n=this.#t,a=n.events[t];if(a){for(const t of a)t(...e);return}const l=n.futures;l[t]||(l[t]=[]),l[t].push(e)}#n(t,e){const n=this.#t.events;n[t]&&(n[t]=n[t].filter((t=>t!==e)));const a=this.#e.events;a[t]&&(a[t]=a[t].filter((t=>t!==e)))}off(t){const e=this.#t.events;if(e[t])return delete e[t],!0;const n=this.#e.events;return!!n[t]&&(delete n[t],!0)}setInvokeInterval(t){this.#e.callbackInterval=t}getEvents(){return{regularEvents:this.#t,callbackEvents:this.#e}}},n={template:'\n      <div>\n      <el-dialog\n          :fullscreen="true"\n          title="提示"\n          :visible.sync="dialogVisible"\n          width="30%"\n          :before-close="handleClose">\n        <el-input autosize\n                  type="textarea"\n                  v-model="content"></el-input>\n        <span slot="footer" class="dialog-footer">\n    <el-button @click="dialogVisible = false">取 消</el-button>\n    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>\n  </span>\n      </el-dialog>\n\n      </div>',data:()=>({dialogVisible:!1,content:""}),methods:{handleClose(t){this.$confirm("确认关闭?").then((e=>{t()})).catch((t=>{}))}},created(){e.on("展示内容对话框",(t=>{this.content=t,this.$message("已更新内容"),this.dialogVisible=!0}))}};var a=async(t,e={})=>{try{e={...{doc:document,interval:1e3,timeout:-1},...e};const n=await function(t,e={}){const n={doc:document,interval:1e3,timeout:-1};return e={...n,...e},new Promise(((n,a)=>{const l=setInterval((()=>{const a=e.doc.querySelector(t);a&&(n(a),clearInterval(l))}),e.interval);e.timeout>0&&setTimeout((()=>{clearInterval(l),a(null)}),e.timeout)}))}(t,e);return-1===e.timeout?n:{state:!0,data:n}}catch(t){return{state:!1,data:t}}},l=async(t,e={})=>{e={...{doc:document,interval:1e3,timeout:-1},...e};try{const n=await function(t,e={}){const n={doc:document,interval:1e3,timeout:-1};return e={...n,...e},new Promise(((n,a)=>{const l=setInterval((()=>{const a=e.doc.querySelectorAll(t);a.length>0&&(n(Array.from(a)),clearInterval(l))}),e.interval);e.timeout>0&&setTimeout((()=>{clearInterval(l),a(null)}),e.timeout)}))}(t,e);return-1===e.timeout?n:{state:!0,data:n}}catch(t){return{state:!1,data:t}}};const o=new class{#a=new Map;addEvent(t,e,n,a=!1){const l=this.#a;l.has(t)||l.set(t,{events:[],attrs:[]});const{events:o,attrs:s}=l.get(t);!a&&s.includes(e)||(s.push(e),o.push({eventName:e,callback:n}),t.setAttribute("gz-event",JSON.stringify(s)),t.addEventListener(e,n))}hasEventName(t,e){const n=this.#a;if(n.has(t))return!0;const{attrs:a}=n.get(t);return a.includes(e)}},s=async()=>{const t=await l(".app-main .el-row>div"),e=[];for(const n of t){const t={};t["运营商"]=n.querySelector(".goods-header-title").textContent.match(/运营商:(.+)/)[1],t["上架时间"]=n.querySelector(".goods-header-time").textContent.match(/上架时间:(.+)/)[1];const a=n.querySelector(".overflow-ellipsis").textContent;t["卡名"]=a.substring(2,a.indexOf("卡")+1),t["原始标题"]=a;const l=a.substring(a.indexOf("卡")+1),o=l.indexOf("元");let s,i;-1!==o?(s=l.substring(0,o),i=l.substring(o+1,l.length)):(s="未描述",i="获取失败"),t["主要参数"]=i,t["月租"]=s;const r=n.querySelectorAll(".goods-tab>span"),c=r[0].textContent;let d;t["归属地"]=c.includes("归属地")?c.match(/(.+)归属地/)[1]:c,t["是否支持选号"]=i.includes("支持选号")?"是":"未知",t["是否永久套餐"]=i.includes("永久套餐")?"是":"未知",t["是否支持结转"]=i.includes("支持结转")?"是":"未知",t["是否长期套餐"]=i.includes("长期套餐")?"是":"未知",t["快递"]=r[1].textContent,t["合约"]=r[2].textContent,t["年龄要求"]=r[3].textContent,d=5===r.length?r[4].textContent:"未描述",t["激活方案"]=d,t["佣金"]=n.querySelector(".goods-brokerage div").textContent;for(const e of n.querySelectorAll(".goods-info-ul>.goods-info-li")){const n=e.querySelector(".goods-info-value").textContent.split(":");t[n[0]]=n[1]}t.img=n.querySelector(".goods-img").src;for(const e in t)t[e]=t[e].trim();t["下单地址"]="https://ym.ksjhaoka.com/show?s=mNgPsRk2118639&id="+t["产品ID"],e.push(t)}return e};var i=(t=window.location.href)=>t.endsWith("ksjhaoka.com/#/goods/sale"),r=async t=>{const e=await s(),n=[],a=t.length;for(let a of e)t.some((t=>t["产品ID"]===a["产品ID"]))||n.push(a);for(let e of n)t.push(e);return a<t.length},c=s,d=async()=>{const t=await l(".el-row .goods-list"),e=[];for(const n of t){const t={};let a=n.querySelector(".goods-header-title").textContent.trim();a=a.split(":"),t[a[0]]=a[1];let l=n.querySelector(".goods-header-time").textContent.trim();l=l.split(":"),t[l[0]]=l[1],t.img=n.querySelector("img.goods-img").src;const o=[];t.title=n.querySelector("h3.overflow-ellipsis").textContent;for(let t of n.querySelectorAll(".goods-box>.goods-tab>span"))o.push(t.textContent);t.tag=o;try{t["佣金"]=n.querySelector(".goods-brokerage>div").textContent.trim()}catch(e){t["佣金"]="未描述"}const s=n.querySelectorAll(".goods-info-li>div.goods-info-value");for(let e of s){const n=e.textContent.split(":");t[n[0]]=n[1]}e.push(t)}return e},u=()=>{let t;a(".app-container .type-box").then((n=>{o.addEvent(n,"click",(n=>{const a=n.target,l=a.textContent.trim();"type-activate"===a.className&&t!==l&&(t=l,e.send("产品管理子标签切换",l))}))}))};var m=(t,e)=>{const n=document.createElement("a");n.setAttribute("href","data:text/plain;charset=utf-8,"+encodeURIComponent(t)),n.setAttribute("download",e),n.style.display="none",document.body.appendChild(n),n.click(),document.body.removeChild(n)},h=t=>{const e=new URL(t),n=e.pathname.split("/").filter((t=>""!==t)),a=new URLSearchParams(e.search.slice(1)),l={};for(const[t,e]of a.entries())l[t]=e;return{protocol:e.protocol,hostname:e.hostname,port:e.port,pathname:e.pathname,pathSegments:n,search:e.search,queryParams:l,hash:e.hash}},p=(t,e,n=!1)=>{const a=new Set,l=t.filter((t=>{const n=t[e];return!a.has(n)&&(a.add(n),!0)}));return n&&t.splice(0,t.length,...l),l};const v={props:{dataList:{type:Array,default:()=>[]},fileName:{type:String,default:"列表数据"}},template:'\n      <div>\n      <el-card>\n        <template #header>\n          <span>数据导出</span>\n        </template>\n        <el-button @click="outToJsonFIleBut">导出(JSON文件)</el-button>\n        <el-button @click="outToConsoleBut">导出(控制台)</el-button>\n      </el-card>\n      </div>',data:()=>({}),methods:{outToJsonFIleBut(){const t=this.dataList.length;0!==t?(m(JSON.stringify(this.dataList,null,4),`${this.fileName}-${t}个.json`),this.$alert("已执行导出json文件类型,请留意浏览器下载操作")):this.$message("未有数据可供导出")},outToConsoleBut(){if(0===this.dataList.length)return void this.$message("未有数据可供导出");console.info(this.fileName+"数据=======start");const t=JSON.stringify(this.dataList);console.info(JSON.parse(t)),console.info(t),console.info(this.fileName+"数据=======end"),this.$alert("已输出到控制台,可通过f12导出")}}},f={template:'\n      <div>\n      <el-card>\n        <span>数据信息</span>\n        <el-badge :value="dataList.length" type="primary">\n          <el-button size="small">数据</el-button>\n        </el-badge>\n      </el-card>\n      <el-card>\n        <el-button @click="$emit(\'get-data-list\')">获取{{ butTxt }}列表数据(当前页)</el-button>\n        <el-button @click="lookDataBut">查看数据</el-button>\n        <el-button type="warning" @click="$emit(\'clear-data\')">清空数据</el-button>\n      </el-card>\n      <out_data_vue :file-name="fileName" :data-list="dataList"/>\n      </div>',props:{dataList:{type:Array,default:()=>[]},fileName:{type:String,default:"列表数据"},butTxt:{type:String,default:"列表"}},components:{out_data_vue:v},data:()=>({}),methods:{lookDataBut(){e.send("lookData",this.dataList)}},created(){}},g={template:'\n      <div>\n      <el-card>\n        <el-switch active-text="追加模式" v-model="isAppendMode"/>\n        <el-switch active-text="获取完之后查看" v-model="isViewMode"/>\n      </el-card>\n      <el-tabs type="border-card" v-model="tabsActiveName">\n        <el-tab-pane label="推广中心" name="推广中心">\n          <promotion_center_vue/>\n        </el-tab-pane>\n        <el-tab-pane label="未上架" name="未上架">\n          <data_mode_vue file-name="卡世界产品管理-未上架" :data-list="not_available_data_list" but-txt="未上架"\n                         @get-data-list="getListDataBut(\'未上架\')"\n                         @clear-data="clearDataBut(\'未上架\')"/>\n        </el-tab-pane>\n        <el-tab-pane label="已下架" name="已下架">\n          <data_mode_vue file-name="卡世界产品管理-已下架" :data-list="available_data_list" but-txt="已下架"\n                         @get-data-list="getListDataBut(\'已下架\')"\n                         @clear-data="clearDataBut(\'已下架\')"/>\n        </el-tab-pane>\n      </el-tabs>\n      </div>',components:{promotion_center_vue:{template:'\n      <div>\n      <data_mode_vue file-name="卡世界产品管理-推广中心" but-txt="推广中心" :data-list="dataList"\n                     @get-data-list="getPromotionCenterDataBut" @clear-data="clearDataBut"/>\n      </div>',components:{data_mode_vue:f},data:()=>({dataList:[]}),methods:{async getPromotionCenterDataBut(){if(!i())return void this.$message("当前页面不是推广中心页面");if(await e.invoke("isAppendMode")){await r(this.dataList)?this.$message("已追加成功!"):this.$message("追加失败,数据未变化")}else this.dataList=await c();await e.invoke("isViewMode")&&e.send("lookData",this.dataList)},clearDataBut(){this.dataList=[],this.$message("已清空数据!")}}},data_mode_vue:f},data:()=>({isAppendMode:!0,isViewMode:!1,not_available_data_list:[],available_data_list:[],tabsActiveName:"推广中心"}),methods:{async getListDataBut(t){const n=this.$loading({lock:!0,text:`${t}数据获取中...`}),a=await d(),l=await e.invoke("isAppendMode");let o;n.close();let s="未上架"===t?this.not_available_data_list:this.available_data_list;l?(o=p([...s,...a],"产品ID"),this.$message("已追加成功!")):o=a,"未上架"===t?this.not_available_data_list=o:this.available_data_list=o},clearDataBut(t){"未上架"===t?this.not_available_data_list=[]:this.available_data_list=[],this.$message("已清空数据!")}},created(){e.handler("isAppendMode",(()=>this.isAppendMode)),e.handler("isViewMode",(()=>this.isViewMode)),e.on("lookData",(t=>{this.$message("获取成功!"),e.send("展示内容对话框",JSON.stringify(t,null,4))})),e.on("产品管理子标签切换",(t=>{this.tabsActiveName=t}))}};var b=(t,e,n=null)=>GM_registerMenuCommand(t,e,n),y=(t,e)=>{GM_setValue(t,e)},w=(t,e)=>GM_getValue(t,e);const _={template:'\n      <div>\n      <el-card>\n        <el-button @click="readMKKeyInfoToInputBut">读取存储账号密码</el-button>\n        <el-button @click="saveMKKeyInfoBut">存储账号密码</el-button>\n        <el-button @click="readRightInputKeyInfoBut">读取右侧编辑框号密</el-button>\n      </el-card>\n      <el-form>\n        <el-form-item label="账户名(号码)">\n          <el-input v-model="inputName"></el-input>\n        </el-form-item>\n        <el-form-item label="密码">\n          <el-input v-model="inputPwd" show-password></el-input>\n        </el-form-item>\n      </el-form>\n      <div class="el-horizontal-right">\n        <el-button @click="writeRightInputKeyInfoBut">填写右侧号密信息</el-button>\n      </div>\n      </div>',data:()=>({inputName:"",inputPwd:""}),methods:{readRightInputKeyInfoBut(){const t=document.querySelector('input[name="username"].el-input__inner')?.value||null;if(null===t)return void this.$message("读取用户名或手机号失败");this.inputName=t;const e=document.querySelector('input[name="password"].el-input__inner')?.value||null;null!==e?(this.inputPwd=e,this.$alert("读取成功!")):this.$message("读取密码失败")},readMKKeyInfoToInputBut(){const t=w("info",null);null!==t?(this.inputName=t.name,this.inputPwd=t.pwd,this.$alert("读取成功!")):this.$message("读取失败")},saveMKKeyInfoBut(){""!==this.inputName.trim()&&""!==this.inputPwd.trim()?(y("info",{name:this.inputName,pwd:this.inputPwd}),this.$alert("保存成功!")):this.$message("请输入账号密码")},writeRightInputKeyInfoBut(){if(""===this.inputName.trim()||""===this.inputPwd.trim())return void this.$message("请输入账号密码");const t=document.querySelector('input[name="username"].el-input__inner'),e=document.querySelector('input[name="password"].el-input__inner');null!==t&&null!==e?(t.value=this.inputName,e.value=this.inputPwd,this.$alert("写入成功!")):this.$message("读取输入框失败")}},created(){const t=w("info",null);null!==t&&(this.inputName=t.name,this.inputPwd=t.pwd)}},k=t=>{const e=t.querySelectorAll(".disply-flex>span"),n={};for(let t of e){const e=t.textContent.trim().split(":");n[e[0]]=e[1].trim()}return n},x=t=>{const e={};for(let n=0;n<t.length;n++){const a=t[n];0===n&&(e["商品名称"]=a.querySelector("span").textContent.trim());const l=a.textContent.trim().split(":");e[l[0]]=l[1].trim()}return e},S={template:'\n      <div>\n      <el-card>\n        <span>数据信息</span>\n        <el-badge :value="dataList.length" type="primary">\n          <el-button size="small">数据</el-button>\n        </el-badge>\n      </el-card>\n      <out_data_vue file-name="订单列表" :data-list="dataList"/>\n      <el-card>\n        <el-button @click="getListDataBut">获取列表数据(当前页)</el-button>\n      </el-card>\n      </div>',components:{out_data_vue:v},data:()=>({dataList:[]}),methods:{getListDataBut(){(async()=>{const t=(await a(".el-table__body-wrapper.is-scrolling-left>table")).querySelectorAll("tbody>tr"),e=[];let n={};for(let a of t){if("el-table__row expanded"===a.className){const t=k(a);for(let e in t)n[e]=t[e];continue}const t=Array.from(a.querySelectorAll(".expand-column"));for(let e=0;e<t.length;e++){const a=t[e],l=a.querySelectorAll(".table-list>div");if(4!==e)if(0!==e){if(3===e){const t=a.querySelector(".el-button"),e=t.getAttribute("aria-describedby"),l=t.querySelector("span");l&&(n["短信时间"]=l.textContent.trim());const o=document.querySelector(`#${e}`);o&&(n["短信"]=o.textContent.trim())}if(1!==e&&2!==e&&3!==e)4!==e?5===e&&(n["商家备注"]=a.textContent.trim()):n["失败原因"]=a.textContent.trim();else for(let t of l){const e=t.textContent.trim();let a=e.includes(":")?e.split(":"):e.split(":");n[a[0]]=a[1].trim()}}else{const t=x(l);for(let e in t)n[e]=t[e]}}e.push(n),n={}}return e})().then((t=>{this.dataList=t,this.$alert("获取当前页订单成功")}))}}},C=document.createElement("div");if(C.style.position="fixed",C.style.left="0",C.style.top="0",C.style.width="100%",C.style.height="100%",document.body.appendChild(C),null===document.head.querySelector("#element-ui-css")){const t=document.createElement("link");t.rel="stylesheet",t.href="https://unpkg.com/element-ui/lib/theme-chalk/index.css",t.id="element-ui-css",document.head.appendChild(t),console.log("挂载element-ui样式成功")}new t({el:C,template:'\n      <div>\n      <el-drawer\n          title="卡世界数据获取"\n          :visible.sync="drawer"\n          direction="ltr"\n          size="50%">\n        <el-tabs type="border-card" v-model="tabsActiveName">\n          <el-tab-pane label="产品管理" name="产品管理">\n            <product_management_vue/>\n          </el-tab-pane>\n          <el-tab-pane label="订单管理">\n            <order_management_vue/>\n          </el-tab-pane>\n          <el-tab-pane label="登录" v-if="login_show" lazy name="登录">\n            <login_vue/>\n          </el-tab-pane>\n        </el-tabs>\n      </el-drawer>\n      <look_content_dialog_vue/>\n      </div>',components:{look_content_dialog_vue:n,product_management_vue:g,login_vue:_,order_management_vue:S},data:()=>({drawer:!0,login_show:!1,tabsActiveName:"产品管理"}),methods:{},created(){e.on("主面板开关",(()=>{this.drawer=!this.drawer})),e.on("登录选项卡显隐",(t=>{this.login_show=t})),e.on("切换标签页",(t=>{this.tabsActiveName=t})),e.on("el-msg",(t=>{this.$message(t)}))}});const I=document.createElement("style");I.innerHTML="body {\n    overflow: hidden;\n}\n\n\n.el-vertical-center {\n    display: flex;\n    justify-content: center;\n}\n\n\n.el-horizontal-center {\n    display: flex;\n    align-items: center;\n}\n\n\n.el-horizontal-right {\n    display: flex;\n    justify-content: flex-end;\n}\n\n\n.el-horizontal-left {\n    display: flex;\n    justify-content: flex-start;\n}\n",document.head.appendChild(I);var E=(t=window.location.href,n=document.title)=>{const a=h(t);if(console.log("静态路由:",t,a),t.includes("ksjhaoka.com/#/login"))return e.send("登录选项卡显隐",!0),void e.send("切换标签页","登录");i(t)&&u()},N=()=>{let t=window.location.href;setInterval((()=>{const e=window.location.href;if(t===e)return;t=e;const a=document.title;n(e,a)}),1e3);const n=(t,n)=>{const a=h(t);console.log("动态路由:",a,t,n),t.includes("ksjhaoka.com/#/login")||e.send("登录选项卡显隐",!1),i()&&(e.send("切换标签页","产品管理"),u())}};window.addEventListener("load",(()=>{console.log("卡世界页面加载完成"),E(),N(),a(".floating").then((t=>{t.remove(),e.send("el-msg","已移除页面右下角悬浮广告")}))})),document.addEventListener("keydown",(function(t){"`"===t.key&&e.send("主面板开关")})),b("主面板开关展示",(()=>{e.send("主面板开关")})),b("获取当前产品页面列表",(async()=>{if(!i())return void alert("当前页面不是产品管理");const t=c();console.log("===========当前页面产品列表==========="),console.log(t),console.log("===========当前页面产品列表==========="),alert("已打印在控制台上")}))}(Vue);