代码
// 我的脚本 console.log('========================= 程序脚本执行成功 ==============================') let appConfig = { host: 'http://sz.emtailor.com:8068/', // host: 'http://localhost:8068/', // host: 'http://e.7mo.org:8068/', /* 客户端类型: client,middle,file client: 1.客户端模式,不进行心跳; middle: 1.中间件模式,进行心跳; file: 1.文件端模式,不进行心跳; one: 4.一体端模式,不进行心跳; */ serviceType: 'one', /* 是否debug模式: true,false 影响一些日志的输出 */ isDebug: false, /* 是否全局结束所有任务,默认false */ allStop: false, /* axios全局超时时间ms */ axiosTimeOut: 60 * 1000, heartBeatPunish: 10, heartBeatFixedDelay: 10, /* 用户解封时间ms */ userUnBanIntervalMs: 172800000, /* 默认任务间隔ms */ defaultSleepTaskMs: 86400000, defaultUserCheckUrl: 'https://www.instagram.com/graphql/query/?query_id=17888483320059182&id=4143607182&first=40', /* 忽略图片下载失败 */ ignoreGetImage: true, /* 是否健康检查(自动启动任务) */ isHealthCheck: true, /* 健康检查周期(阀值,超过此时间将尝试启动任务) */ healthCheckIntervalMs: 10 * 60 * 1000, /* 邮件告警间隔(触发第多少次发送邮件,才发送邮件;不要让邮件发送太频繁,添加容错范围) */ emailCountIgnore : 6, }; /* ============================================== */ // common.js String.prototype.reverse = function () { return this.split('').reverse().join('') }; String.prototype.toSize = function () { let size = this; if (!!size && size > 0) { const KB = 1024; const MB = 1024 * KB; const GB = 1024 * MB; const TB = 1024 * GB; let sizeStr = size > TB ? size / TB + "TB" : size > GB ? size / GB + "GB" : size > MB ? size / MB + "MB" : size > KB ? size / KB + "KB" : size; let unit = sizeStr.substr(-2); let number = sizeStr.replace(unit, ''); number = Math.floor(number * 10) / 10; sizeStr = number + unit; return sizeStr; } else { return '0KB'; } }; String.prototype.between = function (start = '', end = '', isInner = true) { let str = this; if (!!str && str.length > 0) { let startIndex = 0; if (start) { let io = str.indexOf(start); if (io >= 0) { if (isInner) { io = io + start.length; } startIndex = io; } else { return ''; } } let endIndex = str.length; if (end) { let io = str.indexOf(end, startIndex); if (io >= 0) { if (!isInner) { io = io + end.length; } endIndex = io; } else { return ''; } } return str.substring(startIndex, endIndex); } else { return ''; } }; let UUID = (len) => { let max = 520 << 520; len = len === null || isNaN(len) || len > max ? 6 : len; let uuid = ''; let rand = () => ('' + Math.random()).substring(2); do { uuid += rand(); } while (uuid.length < len) ; return uuid.substring(0, len); }; let trimQuote = (str) => { let trimStr = String(str).trim(); if (!!trimStr) { let quote = '"'; if (trimStr.startsWith(quote)) { trimStr = trimStr.substr(1); } if (trimStr.endsWith(quote)) { trimStr = trimStr.substring(0, trimStr.lastIndexOf(quote)); } } return trimStr; }; /** * 获取字符串的 哈希值 * @param str 字符串 * @param caseSensitive 区分大小写 * @returns {number} */ let getHashCode = (str, caseSensitive = true) => { str = '' + str; if (!caseSensitive) { str = str.toLowerCase(); } let hash = 13709061665, i, ch; for (i = str.length - 1; i >= 0; i--) { ch = str.charCodeAt(i); hash ^= ((hash << 5) + ch + (hash >> 2)); } return (hash & 2147483647); }; /** * fake flat for webStorm,idea * @type {string} */ let fakeU = 'fake webStorm,idea'; if (!fakeU) { Array.prototype.flat = () => Array.prototype.flat(); /** * * @param url{string} * @param param{object} * @returns {object} */ let axiosParam = (url, param = {}) => Object.assign(url, param); /** * * @type {{request: (function(string, Object): string & Object), post: (function(string, Object): string & Object), get: (function(string, Object): string & Object), delete: (function(string, Object): string & Object), put: (function(string, Object): string & Object)}} */ axios = { get: axiosParam, put: axiosParam, post: axiosParam, delete: axiosParam, request: axiosParam, }; Vue = { getCurrentInstance: () => 0, // @see <a href="https://blog.csdn.net/weixin_34370347/article/details/89617464">Vue获取组件name属性</a> $options: {}, globalProperties: {}, $emit: () => 0, mount: () => 0, unmount: () => 0, createApp: () => 0, reactive: () => 0, $el: {}, }; /** * js-sha1 * @see https://github.com/emn178/js-sha1 * @param str * @returns {string} */ sha1 = (str) => ''; VueRouter = { createRouter: () => 0, createWebHashHistory: () => 0, }; /** * js 代码格式化工具 * @link https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.11.0/beautify.min.js * @param jsCode js脚本文本 * @returns {string} */ js_beautify = (jsCode) => String(jsCode); } /** * 验证脚本合法性 * @returns {boolean} */ String.prototype.validScript = function () { let scriptStrObj = this; try { // 这里要转换为字符串才能正确验证, 否则传入的是String()对象, 无法验证 eval(String(scriptStrObj)); return true; } catch (e) { console.error('无效的脚本: %o, error: {}', scriptStrObj, e); } return false; }; /** * 禁止用F5键 * @returns {boolean} */ document.onkeydown = () => { console.log('event.keyCode', event.keyCode); if (event.keyCode === 116) { //event.keyCode = 0; //event.cancelBubble = true; //// window.location.href = '/'; // return false; } }; /** * 从数组中的对象中查找key键名对应的值包含被查找的值数组 * @param sourceArr 源对象数组 * @param key 数组中对象的key键名 * @param valuesArr key的值数组 * @returns {*} */ let findKey = (sourceArr, key, valuesArr) => sourceArr.filter(e => valuesArr.filter(f => f === e[key]).length === 1); let randNumber = (min, max) => parseInt(Math.random() * (max - min + 1) + min, 10); let deepClone = (obj) => JSON.parse(JSON.stringify(obj)); /** * 日期转指定格式字符串 * @param fmt 日期格式化函数 * @returns {string} 格式化后的日期字符串 * @modified zhongbo * @date 2020/5/22 */ Date.prototype.fmt = function dateFormat(fmt = 'MM月dd日HH:mm:ss,SSS') { //eg: yyyy-MM-dd HH:mm:ss let date = this; let ret; let opt = { "y+": date.getFullYear().toString(), // 年 "Y+": date.getFullYear().toString(), // 年 "M+": (date.getMonth() + 1).toString(), // 月 "d+": date.getDate().toString(), // 日 "D+": date.getDate().toString(), // 日 "H+": date.getHours().toString(), // 时 "m+": date.getMinutes().toString(), // 分 "s+": date.getSeconds().toString(), // 秒 "S+": date.getMilliseconds().toString() // 秒 // 有其他格式化字符需求可以继续添加,必须转化成字符串 }; for (let k in opt) { ret = new RegExp("(" + k + ")").exec(fmt); if (ret) { fmt = fmt.replace(ret[1], (ret[1].length === 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) } } return fmt; }; /** * 秒转时间字符串 * @param second * @returns {string} */ let timeUnit = (second) => { if (!!Math.floor(second)) { const MINUTE = 60; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; let timeStr = second > DAY ? second / DAY + '天' : second > HOUR ? second / HOUR + '时' : second > MINUTE ? second / MINUTE + '分' : second + '秒'; let unit = timeStr.substr(-1); let number = timeStr.replace(unit, ''); number = Math.floor(number * 10) / 10; timeStr = number + unit; return timeStr; } else { return '0秒' } }; /** * 按键事件检测 * * 特殊按键说明 * metaKey : 即Win键 * ctrlKey : 即Ctrl键 * altKey : 即Alt键 * shiftKey : 即Shift键 * 'Tab' : 即Tab键 * 'Escape' : 即Esc键 * @param {KeyboardEvent} event * @param {array} param * @return {boolean} */ let isEventKey = (event, param = []) => { let matchAllKey = true; // 判断 event 按键事件需要使用 event.nativeEvent 来判断事件类型 if (!!event && !!param && event.nativeEvent instanceof KeyboardEvent && param instanceof Array) { param.forEach(key => { if (matchAllKey) { if ('ctrl' === key) { matchAllKey = event.ctrlKey; } else if ('alt' === key) { matchAllKey = event.altKey; } else { matchAllKey = event.key === key; } } }) } else { matchAllKey = false; } return matchAllKey; }; /** * 字符串中间插入文本 * @param sourceStr * @param start * @param insertStr * @return {*} */ let insertStr = (sourceStr, start, insertStr) => { if (!!sourceStr && !!insertStr && !isNaN(start) && sourceStr instanceof String && insertStr instanceof String) { return sourceStr.slice(0, start) + insertStr + sourceStr.slice(start); } return sourceStr; }; /** * 数组中间插入内容 * @param {array} sourceArr * @param {number} start * @param {*} insertObj * @return {*} */ let insertArr = (sourceArr, start, insertObj) => { if (!!sourceArr && !!insertObj) { sourceArr = _.clone(sourceArr); if (sourceArr instanceof Array) { // 判断超越最大下标 if (sourceArr[start] === undefined) { sourceArr.push(insertObj); } else { // 中间添加一行 sourceArr = sourceArr.map((v, i) => i === start ? [v, insertObj] : v).flat(); } } } return sourceArr; }; /** * 数组中间插入内容 * @param {array} sourceArr * @param {number} start * @return {*} */ let removeArr = (sourceArr, start) => { if (!!sourceArr && typeof start === 'number') { sourceArr = _.clone(sourceArr); if (sourceArr instanceof Array) { // 中间删除一行 sourceArr = sourceArr.filter((url, index) => index !== start); } } else { console.log('移除数组错误! 参数错误 sourceArr: %o start: %o', sourceArr, start); } return sourceArr; }; /** * 字节转字符串 * @param byteSize * @returns {string} */ let byteUnit = (byteSize) => { if (!!Math.floor(byteSize)) { const KB = 1024; const MB = 1024 * KB; const GB = 1024 * MB; let timeStr = byteSize > GB ? byteSize / GB + 'GB' : byteSize > MB ? byteSize / MB + 'MB' : byteSize > KB ? byteSize / KB + 'KB' : byteSize + 'B'; let unit = timeStr.substr(-1); let unitHigh = timeStr.substr(-2, 1); unit = unitHigh > '9' ? unitHigh + unit : unit; let number = timeStr.replace(unit, ''); number = Math.floor(number * 10) / 10; timeStr = number + unit; return timeStr; } else { return '0B' } }; /** * 判断是否管理员 * @return {boolean} */ let isAdmin = () => !!localStorage.getItem('token'); /** * 判断字符串是否为json * @param str * @returns {boolean} * @see <a href="https://www.cnblogs.com/lanleiming/p/7096973.html">【最简单的方法】js判断字符串是否为JSON格式(20180115更新)</a> */ let isJson = (str) => { if (typeof str === 'string') { try { let obj = JSON.parse(str); return !!(typeof obj === 'object' && obj); } catch (e) { console.log('isJson error: %o !!!', str, e); } } return false; }; /** * 提取日志打印FormData信息 * @param params 请求参数 * @returns {object} */ let extractFormData = (params) => FormData && params && params instanceof FormData ? (() => { let next, returnObj = {}, pk = params.keys(); while ((next = pk.next()) && !next.done) returnObj[next.value] = params.getAll(next.value); return returnObj; })() : params; //console.__proto__.blue = { // log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ') // , 'background-color:blue;color:white;line-height:20px;', new Date().fmt(), ...e) //}; // //console.__proto__.green = { // log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ') // , 'background-color:blue;color:blue;line-height:20px;', new Date().fmt(), ...e) //}; // //console.__proto__.yellow = { // log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ') // , 'background-color:blue;color:yellow;line-height:20px;', new Date().fmt(), ...e) //}; // //console.__proto__.green = { // log: (...e) => console.log('%c[ %s ] Proxy' + e.map((e) => typeof(e) === 'object' ? '%o' : '%s').join(' ') // , 'background-color:blue;color:green;line-height:20px;', new Date().fmt(), ...e) //}; /** * 禁用调试模式 * @see <a href="https://zmingcx.com/wp-content/cache/autoptimize/js/autoptimize_e7a8a795e88cf81ddfa417af57368243.js">提取自源码forbidDebug()</a> */ let forbidDebug = function () { // 是否允许调试 let enableDebug = true; if (enableDebug) { return; } try { ((function () { let callbacks = [], timeLimit = 50, open = false; setInterval(loop, 1); return { addListener: function (fn) { callbacks.push(fn) }, cancleListenr: function (fn) { callbacks = callbacks.filter(function (v) { return v !== fn }) } }; function loop() { let startTime = new Date(); debugger; if (new Date() - startTime > timeLimit) { if (!open) { callbacks.forEach(function (fn) { fn.call(null) }) } open = true; window.stop(); alert("\u5173\u95ed\u63a7\u5236\u53f0\u540e\u5237\u65b0\uff01"); document.body.innerHTML = "" } else { open = false } } })()).addListener(function () { window.location.reload() }) } catch (e) { } }; forbidDebug(); /** * 自制禁用调试 */ (() => { // 是否启用 let enable = false; let c = String.fromCharCode; let d = [40, 40, 41, 61, 62, 123, 100, 101, 98, 117, 103, 103, 101, 114, 125, 41, 40, 41]; try { enable && eval(d.map(e => c(e)).join('')); } catch (e) { console.error(e); window.stop(); } })(); /* =========================================== */ // api.js // api配置 let apis = { style: { getApp: { // url: 'http://127.0.0.1:3003/src/css/app.css', url: 'src/css/app.css', method: 'get' } }, swagger: { apiDocs: { url: 'v3/api-docs', method: 'get' }, }, task: { getOne: { url: 'Task/${id}', method: 'get' }, getAll: { url: 'Task/findAll', method: 'get' }, add: { url: 'Task', method: 'post' }, update: { url: 'Task', method: 'put' }, delete: { url: 'Task/${id}', method: 'delete' }, start: { url: 'Task/${id}/start', method: 'get' }, stop: { url: 'Task/${id}/stop', method: 'get' }, pause: { url: 'Task/${id}/pause', method: 'get' }, resume: { url: 'Task/${id}/resume', method: 'get' }, test: { url: 'Task/test', method: 'post' } }, proxy: { getOne: { url: 'Proxy/${id}', method: 'get' }, getAll: { url: 'Proxy/findAll', method: 'get' }, add: { url: 'Proxy', method: 'post' }, update: { url: 'Proxy', method: 'put' }, delete: { url: 'Proxy/${id}', method: 'delete' } }, config: { getOne: { url: 'Config/${id}', method: 'get' }, getAll: { url: 'Config/findAll', method: 'get' }, add: { url: 'Config', method: 'post' }, update: { url: 'Config', method: 'put' }, delete: { url: 'Config/${id}', method: 'delete' } }, user: { getOne: { url: 'User/${id}', method: 'get' }, getAll: { url: 'User/findAll', method: 'get' }, add: { url: 'User', method: 'post' }, update: { url: 'User', method: 'put' }, delete: { url: 'User/${id}', method: 'delete' } }, notifyEmail: { getOne: { url: 'NotifyEmail/${id}', method: 'get' }, getAll: { url: 'NotifyEmail/findAll', method: 'get' }, add: { url: 'NotifyEmail', method: 'post' }, update: { url: 'NotifyEmail', method: 'put' }, delete: { url: 'NotifyEmail/${id}', method: 'delete' } }, eMail: { send: { url: 'Email', method: 'post', param_example: {content: "发送一封邮件", title: "swagger测试", to: "ni81@qq.com"}, }, }, ins: { count: { url: 'Ins/user/count', method: 'get' }, current: { url: 'Ins/user/current', method: 'get' }, move: { url: 'Ins/user/move', method: 'get' }, next: { url: 'Ins/user/next', method: 'get' }, reset: { url: 'Ins/user/reset', method: 'get' }, }, post: { getOne: { url: 'Ins/post/${id}', method: 'get' }, getPage: { url: 'Ins/post', method: 'get' }, addOrUpdate: { url: 'Ins/post', method: 'post' }, delete: { url: 'Ins/post/${id}', method: 'delete' }, deleteMany: { url: 'Ins/post/delete', method: 'delete' }, count: { url: 'Ins/post/count', method: 'get' }, }, middle: { heartBeat: { url: 'middle/ping', method: 'get' }, lastHeartBeat: { url: 'middle/lastHeart', method: 'get' }, }, file: { listJson: { url: 'file/json', method: 'get' }, list: { url: 'file', method: 'get' }, delete: { // file?del=<String fileName> url: 'file?del=${file}', method: 'get', param_example: {file: 'aaa.txt0.8008880603584201'} }, getOne: { // file?get=<String fileName> url: 'file?get=${file}', method: 'get', param_example: {file: 'aaa.txt0.8008880603584201'}, config: { responseType: 'blob' }, }, upload: { url: 'file', method: 'post', param_example: '/*FormData*/tmp=await api.file.getOne({file:\'aaa.jpg\'});fd=new FormData();fd.append(\'file\',tmp.data,\'test.jpg\'+Math.random());up=await api.file.upload(fd)', config: { overrideMimeType: 'multipart/form-data' }, info: '参数格式为 FormData' }, }, gram: { // https://www.instagram.com/graphql/query/?query_id=17888483320059182&id=1001283596&first=40 getPost: { url: 'https://www.instagram.com/graphql/query/?query_id=17888483320059182&id=${id}&first=40', method: 'get', param_example: {id: '1001283596'} }, // https://www.instagram.com/graphql/query/?query_hash=8c2a529969ee035a5063f2fc8602a0fd&variables={"id":"51132914782","first":12} getPostNew: { url: 'https://www.instagram.com/graphql/query/?query_hash=${queryHash}&variables={"id":"${id}","first":40}', method: 'get', queryHash: true, // 多图id: '51132914782' // 多图__typename: 'GraphSidecar' param_example: {id: '1001283596'} }, getJpeg: { url: 'http${url}', method: 'get', param_example: {url: '1001283596'}, usage_example: 'await api.gram.getJpeg({url:decodeURIComponent(\'https://scontent-hkt1-1.cdninstagram.com/v/t51.2885-15/sh0.08/e35/c0.180.1440.1440a/s640x640/271796139_918243432220319_6606270248120166088_n.jpg?_nc_ht=scontent-hkt1-1.cdninstagram.com\u0026_nc_cat=111\u0026_nc_ohc=2KPbEtGsfIIAX9MVvN3\u0026edm=APU89FABAAAA\u0026ccb=7-4\u0026oh=00_AT9LlzKJwZxDGjaZyEI4ov4XEGonwuvdU0xr2oAZ4kCgWA\u0026oe=61EC5D1C\u0026_nc_sid=86f79a\').substr(4)})', config: { responseType: 'blob' }, }, } }; let doBuildUrl = (apiCfg, params) => { let url = apiCfg.url.startsWith('http') ? apiCfg.url : appConfig.host + apiCfg.url; let pathParams = url.match(/\${\w+}/g); if (pathParams) { pathParams.forEach(param => { let paramKey = param.match(/\${(.*?)}/)[1]; let paramValue = params[paramKey] || ''; if (apiCfg.queryHash && 'queryHash' === paramKey && !paramValue) { paramValue = store.state.queryHash; } url = url.replace(param, paramValue); delete params[paramKey]; }) } return url; }; /** * build for axios * @param apiCfg * @returns {(function(*=): *)|*} */ let build = (apiCfg) => { if (apiCfg) { let keys = Object.keys(apiCfg); if (keys.includes('url') && apiCfg.url) { let method = apiCfg.method || 'get'; axios[method].toString(); //try { //} catch (e) { // throw new Error('错误的方法名! method: ' + method + ' url: ' + apiCfg.url); //} return (originParams = {}) => { let params = deepClone(originParams); let url = doBuildUrl(apiCfg, params); // 支持配置信息(如文件下载 blob) if (apiCfg.config) { Object.assign(params, apiCfg.config) } // 支持表单提交(如文件上传 FormData) if (typeof (originParams) && originParams instanceof FormData) { if (appConfig.isDebug) { console.log('url', url, extractFormData(originParams), params, apiCfg); } return axios[method](url, originParams, params); } if (appConfig.isDebug) { console.log('url', url, extractFormData(params), apiCfg); } return axios[method](url, params); } } else { keys.forEach(key => apiCfg[key] = build(apiCfg[key])); return apiCfg; } } }; /** * build for GM_xmlhttpRequest * @param apiCfg * @returns {(function(*=): *)|*} */ let buildGm = (apiCfg) => { if (apiCfg) { let keys = Object.keys(apiCfg); if (keys.includes('url') && apiCfg.url) { let method = apiCfg.method || 'get'; return (params = {}) => { params = deepClone(params); let url = doBuildUrl(apiCfg, params); let param = { url, method, data: JSON.stringify(params), }; console.log('url', url, params, apiCfg, param); window.GM_xmlhttpRequest = typeof (GM_xmlhttpRequest) === 'undefined' ? undefined : GM_xmlhttpRequest; return new Promise((resolve, reject) => { param.onerror = (error) => { reject(error) }; param.onload = (resp) => { let headers = {}; let headArr = resp.responseHeaders.split("\n"); headArr.forEach(head => { let hArr = head.split(";"); let key = hArr.shift(); headers[key] = hArr.join().trim(); }); resp.headers = headers; let data = {}; if (isJson(resp.responseText)) { data = JSON.parse(resp.responseText); } resp.data = data; resolve(resp); }; if (!!GM_xmlhttpRequest) { GM_xmlhttpRequest(param) } else { console.error("没有找到GM_xmlhttpRequest"); } }); } } else { keys.forEach(key => apiCfg[key] = buildGm(apiCfg[key])); return apiCfg; } } }; /** * usage in tamperMonkey: * let gmDownFile = buildGmDownFile(); * * @returns {function(String): Promise<Object>} * @see <a href="https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest">tamperMonkey doc</a> * @author http://mmbro.gitee.com * @since 2022110 */ let buildGmDownFile = () => { window.GM_xmlhttpRequest = typeof (GM_xmlhttpRequest) === 'undefined' ? undefined : GM_xmlhttpRequest; return (url) => { url = String(url).startsWith('http') ? url : appConfig.host + url; return new Promise((resolve, reject) => { let param = { url, method: 'get', responseType: 'blob' }; param.onerror = (error) => { reject(error) }; param.onload = (resp) => { resp.data = resp.response; resolve(resp); }; if (!!GM_xmlhttpRequest) { GM_xmlhttpRequest(param) } else { console.error("没有找到GM_xmlhttpRequest"); } }) } }; /** * usage in tamperMonkey: * let gmUpLoad = buildGmUpLoadFile(); * * @returns {function(String, FormData): Promise<Object>} * @see <a href="https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest">tamperMonkey doc</a> * @author http://mmbro.gitee.com * @since 2022110 */ let buildGmUpLoadFile = () => { window.GM_xmlhttpRequest = typeof (GM_xmlhttpRequest) === 'undefined' ? undefined : GM_xmlhttpRequest; return (url, formData) => { url = String(url).startsWith('http') ? url : appConfig.host + url; return new Promise((resolve, reject) => { let param = { url, data: formData, method: 'post', responseType: 'blob', overrideMimeType: 'multipart/form-data', }; if (typeof (formData) === 'undefined') { reject('error! formData is not a FormData Object!') } param.onerror = (error) => { reject(error) }; let isHeadersJson = (headers) => headers && headers['content-type'] && String(headers['content-type']).indexOf('application/json') >= 0; param.onload = (resp) => { let extractRespHeaders = (responseHeaders) => { let headers = {}; let headArr = responseHeaders.split("\n"); headArr.forEach(head => { if (head) { let hArr = head.split(":"); let key = hArr.shift(); headers[key] = hArr.join().trim(); } }); return headers; }; resp.headers = extractRespHeaders(resp.responseHeaders); let data; if (isHeadersJson(headers)) { if (isJson(resp.responseText)) { data = JSON.parse(resp.responseText); } else { if (appConfig.isDebug) { let contentType = headers['content-type']; console.error('响应非json文本,contentType: %s', contentType); } data = resp.responseText; } } else { data = resp.responseText; } resp.data = data; resp.request = param; resolve(resp); }; if (!!GM_xmlhttpRequest) { GM_xmlhttpRequest(param) } else { console.error("没有找到GM_xmlhttpRequest"); } }) } }; /* ================================================ */ // store.js /** * Vue3 简单状态管理 * @see <a href="https://v3.cn.vuejs.org/guide/state-management.html">从零打造简单状态管理</a> */ let store = { debug: true, state: Vue.reactive({ runTask: null, apiDocs: null, lastMessageTime: new Date(), healthState: true, queryHash: '', }), setRunTask(newValue) { if (this.debug) { console.log('setRunTask triggered with', newValue) } this.state.runTask = newValue }, clearRunTask(newValue) { if (this.debug) { console.log('clearRunTask triggered with', newValue) } let {runTask} = this.state; if (newValue && runTask && runTask.id === newValue.id) { this.state.runTask = null } }, }; /* ================================================= */ // list.js var template = ` <div class="list"> <table v-if="list.length > 0 && Object.keys(showModel).length > 0" border="0" cellspacing="0" cellpadding="5"> <tr><th v-for="key in Object.keys(showModel)">{{doGetKey(key)}}</th><th v-if="controlSlot">操作</th></tr> <tr :class="{green: doSuccess(item)}" v-for="item in list"> <td v-for="key in Object.keys(showModel)">{{doFormat(item,key)}}</td> <td v-if="controlSlot"><slot :item="item"></slot></td> </tr> </table> <div class="no-data" v-else>没有数据</div> </div> `; let List = { name: 'List', components: {}, template, props: { list: { type: Array, default: [] }, showModel: { type: Object, default: {} }, schema: { type: String, default: '' }, }, methods: { doFormat(item, key) { if (this.showModel[key] && this.showModel[key].formatter) { if ('date' === this.showModel[key].type) { return new Date(item[key]).fmt(this.showModel[key].formatter); } else { return item[key]; } } else if ('text' === this.showModel[key].type) { if (this.showModel[key].maxLength && this.showModel[key].maxLength > 0) { let maxLength = this.showModel[key].maxLength; if (typeof (item[key]) === 'string' && item[key].length > maxLength) { return item[key].substr(0, maxLength) + '...'; } else { return item[key]; } } else { return item[key]; } } else { return item[key]; } }, doSuccess(item) { let successKeys = Object.keys(this.showModel).filter(key => !!this.showModel[key] && this.showModel[key].hasOwnProperty('success')); let success = successKeys.length > 0; successKeys.forEach(key => { if (item[key] !== this.showModel[key]['success']) { success = false; } }); return success; }, doGetKey(key = '') { let {apiDocs} = this.sharedState; if (apiDocs) { let {schemas = {}} = apiDocs.components; let schemaObj = schemas[this.schema]; if (schemaObj) { return schemaObj.properties && schemaObj.properties[key] && schemaObj.properties[key].description || key; } } return key; }, }, data() { const {useSlots} = Vue; return { sharedState: store.state, controlSlot: !!useSlots().default } } }; /* =============================================== */ // message.js var template = ` <div class="message" v-if="isShow"> <div :class="'content ' + level"> <button @click="close" :class="level">X</button> <div class="text" v-if="message instanceof Array" v-for="msg in message"> {{ msg }} </div> <div class="text" v-else> {{ message }} </div> </div> </div> `; let Message = { name: 'Message', data() { return { list: [] } }, props: { message: { // @see <a href="https://v3.cn.vuejs.org/guide/component-props.html#prop-%E9%AA%8C%E8%AF%81">Vue3 Prop 验证</a> type: [String, Array], required: true, }, level: { type: String, default: 'success' }, duration: { type: Number, default: 3, }, }, setup(props) { let {ref} = Vue; let isShow = ref(false); const show = () => { isShow.value = true; }; const close = () => { isShow.value = false; }; let {level} = props; if ('success' === level) { store.state.lastMessageTime = Date.now(); } return {isShow, show, close}; }, created() { this.show(); }, template, watch: { isShow(nVal, oVal) { } } }; /* =============================================== */ // app.js // <!-- 展示模板 --> var template = ` <div> <div class="nav"> <h1 class="title">{{appName}}!</h1> <p class="menu"> <!--使用 router-link 组件进行导航 --> <!--通过传递 \`to\` 来指定链接 --> <!--\`<router-link>\` 将呈现一个带有正确 \`href\` 属性的 \`<a>\` 标签--> <router-link to="/">任务管理</router-link> <router-link to="/proxy">代理配置</router-link> <router-link to="/user">用户配置</router-link> <router-link to="/config">系统配置</router-link> <router-link to="/notify-email">通知邮箱</router-link> </p> </div> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> <!-- <a href="https://www.jianshu.com/p/b0c16bab3388">vue3使用is和v-is</a> --> <div v-is="'style'" scoped> </div> <!-- <Css></Css> --> <!-- <HeartBeat></HeartBeat> --> </div> `; // <!-- Vue 代码 --> let App = { name: 'App', data: () => { return { sharedState: store.state, appName: 'Instagram Crawler Admin', book: 'book1' } }, components: { // Css, HeartBeat }, template: template, mounted: () => { const {proxy} = Vue.getCurrentInstance(); console.log('%s mounted', proxy.$options.name); proxy.initApiDocs(); }, methods: { initApiDocs() { api.swagger.apiDocs().then(res => { if (res.status === 200 && res.data && res.data.components) { this.sharedState.apiDocs = res.data; console.log('load apiDocs', this.sharedState.apiDocs); } }).catch(e => { console.log('load apiDocs error', e) }); } } }; /* ==================== 路由 ================================= ================================================ */ // 1. 定义路由组件. // 也可以从其他文件导入 // const Task = { template: '<div>Task Page</div>' }; // const Proxy = { template: '<div>Proxy Page</div>' }; // 2. 定义一些路由 // 每个路由都需要映射到一个组件。 // 我们后面再讨论嵌套路由。 const routes = [ // {path: '/', component: Task}, // {path: '/proxy', component: ProxyCom}, // {path: '/config', component: Config}, // {path: '/user', component: User}, // {path: '/notify-email', component: NotifyEmail}, ]; /* ==================== 初始化 ================================= ============================================================= */ // axios 自定义超时时间 axios.defaults.timeout = appConfig.axiosTimeOut; let cloneApis = JSON.parse(JSON.stringify(apis)); let api = build(cloneApis); window.api = api; window.appConfig = appConfig; // 3. 创建路由实例并传递 `routes` 配置 // 你可以在这里输入更多的配置,但我们在这里 // 暂时保持简单 const router = VueRouter.createRouter({ // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。 history: VueRouter.createWebHashHistory(), routes, // `routes: routes` 的缩写 }); // 5. 创建并挂载根实例 const app = Vue.createApp(App); /** * @see <a href="https://blog.csdn.net/qq_27694835/article/details/114243019">Vue3.0 挂载axios全局方法</a> * @type {(function(*=): *)|undefined} */ app.config.globalProperties.$api = api; //确保 _use_ 路由实例使 //整个应用支持路由。 app.use(router); app.mount('#app-main'); // 现在,应用已经启动了! // 消息组件 https://blog.csdn.net/hncu1990/article/details/119273475 const {createApp, h} = Vue; const message = (props) => { const container = document.createElement("div"); // 获取组件的DOM,将其挂载到body上 const vm = createApp({ render() { return h(Message, props); }, }); const appMessage = document.getElementById("app-message"); appMessage.appendChild(vm.mount(container).$el); // @see <a href="https://v3.cn.vuejs.org/api/application-api.html#unmount">Vue3 unmount</a> let {duration = 3} = props; setTimeout(() => { vm.unmount(); }, duration * 1000); return { close: () => (vm.component.proxy.isShow = false), }; }; app.config.globalProperties.$message = message; console.log('================================ 程序脚本初始化完成 ================================')