NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name nebula-vot-tools 2.0 // @namespace http://tampermonkey.net/ // @version 1.0 // @description try to take over the world! // @author nebula // @icon http://cdn.newday.me/addon/one/favicon.ico // @match *.changan.com.cn/vot-admin-center-web/ // @license MIT // @require file:///E:/Workspace/tampermonkeyjs/ReactHashListenerCore-user.js // @require file:///E:/Workspace/tampermonkeyjs/jquery.min.3.7.1.js // @require file:///E:/Workspace/tampermonkeyjs/ajaxHooker.js // @require file:///E:/Workspace/tampermonkeyjs/waitForKeyElements-user.js // @require https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/antd/4.23.0/antd.min.js // @resource antdCSS https://cdnjs.cloudflare.com/ajax/libs/antd/4.23.0/antd.min.css // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_getResourceText // @changelog 2025-5-9 VOT车型车系显示当前版本号 // @changelog 2025-5-12 监听Hash变化 // @changelog 2025-5-12 迁移查询内测车辆数量【更新失败】 // ==/UserScript== const css = GM_getResourceText("antdCSS"); // GM_addStyle(css); (function() { // @run-at document-start // @require https://code.jquery.com/jquery-3.6.0.min.js // @require https://update.greasyfork.org/scripts/455943/1270016/ajaxHooker.js // @require https://cdn.jsdelivr.net/gh/CoeJoder/waitForKeyElements.js@v1.2/waitForKeyElements.js // @require https://cdnjs.cloudflare.com/ajax/libs/layui/2.8.18/layui.min.js // @resource layuicss https://cdnjs.cloudflare.com/ajax/libs/layui/2.8.18/css/layui.min.css 'use strict'; const BASE_URL = location.origin; // onHashChange() // function onHashChange() { // if (window.location.hash === '#/vehicleModel') { // confSelectCopyInit(); // waitForTreeNode(); // waitForCopy(); // } else if(window.location.hash === '#/internalTestingVehicleConf') { // innerTestInit(); // } // } // const currentHash = ReactHashListener.getCurrentHash(); // onHashInit(currentHash); // 订阅哈希变化 const unsubscribe = ReactHashListener.subscribe((newHash) => { console.log('🎉 检测到哈希变化:', newHash); // 在这里编写你的业务逻辑 onHashInit(newHash); }); function onHashInit(hash){ if (hash === 'vehicleModel') { confSelectCopyInit(); waitForTreeNode(); waitForCopy(); } else if(hash === 'internalTestingVehicleConf') { innerTestInit(); } } //=========================== React Ant Design =========================== // let cssInited = false; // const initCss = function(){ // // const antdStyleLink = document.createElement('link'); // // antdStyleLink.rel = 'stylesheet'; // // antdStyleLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/antd/4.23.0/antd.min.css'; // // document.head.appendChild(antdStyleLink); // GM_addStyle(css); // cssInited = true; // } function initializeApp(dom, className) { // if(!cssInited) initCss(); function getForPaste(){ let waitForPaste = "无!"; let copyId = localStorage.waitForCopyLevelId; if(copyId && copyId != "undefined"){ waitForPaste = "【" + findLevelConfig(copyId).itemName + "】"; } return "待粘贴项:" + waitForPaste; } const { createElement: h, render } = window.React; const { Button, Modal, message } = window.antd; class App extends window.React.Component { constructor(props) { super(props); this.state = { isModalVisible: false, ajaxResult: getForPaste(), copySource:'' }; }; handleCopy = () => { // 模拟复制操作 // this.copySource = findLevelName(localStorage.currentLevelId); localStorage.setItem("waitForCopyLevelId", localStorage.currentLevelId); this.setState({ ajaxResult: getForPaste() }); message.success('复制成功!!!'); }; handlePaste = () => { // 显示确认框 const levelName = findLevelConfig(localStorage.waitForCopyLevelId).itemName; this.setState({ copySource: levelName, isModalVisible: true}); // this.setState({ isModalVisible: true }); }; handleOk = () => { // 模拟发送 AJAX 请求 this.setState({ isModalVisible: false }); const copyId = localStorage.waitForCopyLevelId; const targetId = localStorage.currentLevelId; if(!copyId || !targetId){ message.error('无法粘贴!!!'); this.setState({ ajaxResult: '还未复制,无法粘贴!!!' }); return; } if(copyId == targetId){ message.error('粘贴失败!!!'); this.setState({ ajaxResult: '不能复制自己!!!' }); return; } let reqResult = {}; let callback = function(result){ reqResult = result; if(result.success){ message.success(result.msg); result.msg = "复制成功!!!"; } else { message.error(result.msg); } } const reqObj = {sourceLevel:copyId, targetLevels:[targetId]}; ajax(LEVEL_COPY, reqObj, callback); // ajaxCopyLevel(copyId, targetId, callback); this.setState({ ajaxResult: reqResult.msg }); return; }; handleCancel = () => { this.setState({ isModalVisible: false }); }; // 修改 Modal 的挂载节点 getContainer = () => { return shadowRoot.getElementById('modal-container'); }; render() { const { isModalVisible, ajaxResult, copySource} = this.state; return h( 'div', { style: { paddingLeft: '20px' }, id : "copyDiv" }, h( Button, { type: 'primary', onClick: this.handleCopy, style: { marginRight: '10px' } , className:className}, '复制' ), h( Button, { type: 'dashed', onClick: this.handlePaste, className:className }, '粘贴' ), h( Modal, // antd.Modal, { title: '确认粘贴', visible: isModalVisible, onOk: this.handleOk, onCancel: this.handleCancel, okText: '确认', cancelText: '取消', getContainer: this.getContainer, // 关键:指定挂载位置 style:{borderRadius:"8px"} }, `是否从【${copySource}】的配置复制粘贴至当前Level?` ), h( 'span', { style: { marginLeft: '10px', color: '#888' } }, `${ajaxResult || '无'}` ), // h(antd.message, { // getContainer: () => shadowRoot.getElementById('modal-container'), // }), // 创建一个容器用于挂载 Modal h('div', { id: 'modal-container' }) ); } } antd.message.config({ duration: 10, getContainer: () => shadowRoot.getElementById('modal-container'), // { // let shadow = shadowRoot.getElementById('modal-container'); // let shadom = document.getElementById('modal-container'); // console.log("调用message getContainer(), shadow:", shadow, shadom); // return shadow||shadom; // }, }); // 创建 Shadow DOM 容器 const host = document.createElement('div'); host.id = "host" document.body.appendChild(host); const shadowRoot = host.attachShadow({ mode: 'open' }); shadowRoot.id = "shadowRoot"; // 加载 antd.css(仅影响 Shadow DOM) // const antdStyle = document.createElement('link'); // antdStyle.rel = 'stylesheet'; // antdStyle.href = 'https://cdnjs.cloudflare.com/ajax/libs/antd/4.23.0/antd.min.css'; // shadowRoot.appendChild(antdStyle); const antdStyle = document.createElement('style'); antdStyle.innerHTML = css + ` .ant-btn { border-radius: 8px !important; transition: all 0.3s !important; } .ant-modal-header{ border-radius: 8px !important; }.ant-modal-content{ border-radius: 8px !important; } `; shadowRoot.appendChild(antdStyle); // 创建 React 容器 //const container = document.createElement('div'); //shadowRoot.appendChild(container); // 挂载 React 应用到页面 const container = document.createElement('div'); container.style.width = "400px"; shadowRoot.appendChild(container); dom.appendChild(host); // document.getElementsByClassName("ant-pro-table-list-toolbar-title")[0].appendChild(container); ReactDOM.render(React.createElement(App), container); // ReactDOM.render(React.createElement(App), dom); //render(h(App), container); } //=========================== React Ant Design END =========================== const LEVEL_COPY = BASE_URL + "/nebula-apigw/vot-admin/admin/api/carDeviceConf/copyLevelConfFunc"; function ajaxCopyLevel(copyId, targetId, okCallback){ // const reqObj = {sourceLevel:copyId, targetLevels:[targetId]}; //const resultObj = {}; // const okCallback = function(res){ // resultObj = res; // resultObj.result = res.success; // resultObj.msg = "复制成功" // }; //const failCallback = function(res){resultObj.result = false;debugger; resultObj.msg = res.msg} // ajax(LEVEL_COPY, reqObj, okCallback); } var ALL_CONFIG;// = JSON.parse(localStorage.allConfig||{}); var childrenConfig = []; function findLevelConfig(levelId){ if(localStorage.VOTChildrenConfig){ childrenConfig = JSON.parse(localStorage.VOTChildrenConfig); } if(childrenConfig.length == 0){ if(!ALL_CONFIG){ ALL_CONFIG = JSON.parse(localStorage.AllConfigVOT); } // childrenConfig = ALL_CONFIG.filter(c => (c.children && c.children.length > 0)); for (let i = 0; i < ALL_CONFIG.length; i++){ var children = ALL_CONFIG[i].children; if(children && children.length > 0){ children.forEach(d => childrenConfig.push(d)); } } localStorage.setItem("VOTChildrenConfig", JSON.stringify(childrenConfig)); } const lid = levelId||localStorage.currentLevelId; var config = childrenConfig.find(cfg => cfg.itemId == lid); localStorage.setItem("currentConfig", JSON.stringify(config)); return config; } function domInserted(selector, callback){ // 创建一个观察器实例并传入一个回调函数 var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // console.log('一个节点被插入:', mutation); // for (var i = 0; i < mutation.addedNodes.length; i++) { // console.log('Added node: ', mutation.addedNodes[i]); // } // for (var i1 = 0; i1 < mutation.removedNodes.length; i1++) { // console.log('Removed node: ', mutation.removedNodes[i1]); // } mutation.addedNodes.forEach((e)=>{ callback&&callback(e); }); }); }); // 配置观察选项: var config = { attributes: true, childList: true, subtree: true }; // 选择需要观察变动的节点 var targetNode = document.querySelector(selector);//document.getElementsByClassName("ant-tree-list")[0]; // 传入目标节点和观察选项开始观察 targetNode && observer.observe(targetNode, config); // 之后,你可以停止观察 // observer.disconnect(); return observer; } function waitForTreeNode(){ waitForKeyElements(".ant-tree-treenode", ()=>{ // console.log("找到ant-tree-treenode元素:", $(".ant-tree-treenode").length, "个"); $(".ant-tree-treenode").click((e)=>{ clickTreeNode(e); }); }, 3); } function clickTreeNode(e){ var callback = function(e){ if(e.className.includes("ant-tree-treenode-switcher-close")){ e.onclick = function(){ console.log(e); waitForCopy(); } } } domInserted(".ant-tree-list", callback); } function waitForCopy(){ setTimeout(copyInit, 1000); } function copyInit(){ let toolbar = $(".ant-pro-table-list-toolbar-title"); if(toolbar.length == 0) return; if($("#copyLevel").length > 0) return; let className = toolbar[0].lastChild.className; const append1 = '<button id="copyLevel" type="button" class="' + className + '" style="display: block; margin-left: 10px;"><span>复制工具</span></button></div>'; // const append2 = '<button id="pasteLevel" type="button" class="' + className + '" style="display: block; margin-left: 10px;"><span>加载</span></button></div>'; toolbar.append(append1); // toolbar.append(append2); $("#copyLevel").click(()=>{ // localStorage.setItem("waitForCopyLevelId", localStorage.currentLevelId); if($("#host").length > 0) return; findLevelConfig(); initializeApp(toolbar[0], className); $("#copyLevel").hide(); // console.log(unsafeWindow.React); // 检查 React 是否存在 // console.log(unsafeWindow.antd); // 检查 Ant Design 是否存在 }) // $("#pasteLevel").click(pasteLevelFunc); } function tamperAjax(url, data, successCallback){ GM_xmlhttpRequest({ method: 'POST', url: url, data: 'data=example', onload: (response) => { if (response.status === 200) { message.success('请求成功: ' + response.responseText); } else { message.error('请求失败: ' + response.statusText); } } }); } function ajax(url, data, successCallback, errorCallback){ const ajaxobj = { type:"post", url: url, dataType: "json", async: false, contentType: "application/json; charset=utf-8", beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Authorization", localStorage.Authorization); }, data: JSON.stringify(data||""), success: function (res){ successCallback && successCallback(res); }, error: function (XMLHttpRequest, textStatus, errorThrown) { console.error('操作失败!!!' + XMLHttpRequest.status + "|" + XMLHttpRequest.readyState + "|" + textStatus); errorCallback && errorCallback(); } }; $.ajax(ajaxobj); } //内测管理 function innerTestInit(){ function queryGroupCount(request){ request.response = res => { const responseText = res.responseText; // 注意保存原数据 const resdata = JSON.parse(responseText); if(resdata && resdata.success && resdata.data.list){ var resp = {total:0}; resdata.data.list.forEach((obj)=>{ req(obj.groupId,resp); obj.createAt += "【" + resp.total.toString().padStart(3, '0') + "】"; }); res.responseText = JSON.stringify(resdata); } }; const queryGroupCountUrl = BASE_URL + "/nebula-apigw/vot-admin/admin/api/inner-test-car/query-inner-test-car"; const query_group = { "current": 1, "pageSize": 1, "query": { "groupId": "" } } function req(groupId, resp){ query_group.query.groupId = groupId; ajax(query_group, queryGroupCountUrl, (res)=>{resp.total = res.data.total;}); } // function ajax(data,successCallback){ // const ajaxobj = { // type:"post", // url: url, // dataType: "json", // async: false, // contentType: "application/json; charset=utf-8", // beforeSend: function (XMLHttpRequest) { // XMLHttpRequest.setRequestHeader("Authorization", localStorage.Authorization); // }, // data: JSON.stringify(data||""), // success: function (res){ // successCallback && successCallback(res); // }, // error: function (XMLHttpRequest, textStatus, errorThrown) { // console.error('操作失败!!!' + XMLHttpRequest.status + "|" + XMLHttpRequest.readyState + "|" + textStatus); // } // }; // $.ajax(ajaxobj); // } } const localStorageKey =[ {url:"get-conf-select-func", key:"votConfData", data:"selectFuncList",id:"#contentOne"}, {url:"get-inner-device-conf-func", key:"votDeviceData", data:"bindList", id:"#contentTwo"} ] ajaxHooker.filter([ {url: /get-conf-select-func/}, {url: /get-inner-device-conf-func/}, // {url: /inner-test-car\/query-group/},//内测车辆数量 ]); ajaxHooker.hook(request => { console.log("innerTestInit-request:",request); // if(request.url.includes("inner-test-car/query-group")){ // queryGroupCount(request); // } if(request.url.includes("get-inner-device-conf-func")){ if($("#bindAll").length == 0){ //const btn_bind = $("#contentOne").next(".operateBtn"); const btn_bind = $("#contentTwo").next(".operateBtn"); if(btn_bind){ const btn_cls = btn_bind.children()[0].className; const apd = '<button type="button" id="bindAll" class="' + btn_cls + ' " style="margin-left: 8px"><span>一键绑定</span></button>' btn_bind.last().append(apd); $("#bindAll").click(bindAll); } } } request.response = res => { const responseText = res.responseText; // 注意保存原数据 const resdata = JSON.parse(responseText); if(resdata && resdata.success){ // const selectFuncList = resdata.data.selectFuncList; // const bindList = resdata.data.bindList; localStorageKey.forEach(function(lobj){ if(request.url.includes(lobj.url)){ const bindData = resdata.data[lobj.data]; localStorage.setItem(lobj.key + "_unBindList", JSON.stringify(resdata.data.unBindList)); if(bindData.length > 0){ localStorage.setItem(lobj.key, responseText); }else{ const selectData = localStorage[lobj.key]; if(selectData && lobj.url == "get-conf-select-func"){ res.responseText = selectData; } } } }) } }; }); function bindAll(){ let votDeviceData = localStorage.votDeviceData; let unBindList = localStorage.votDeviceData_unBindList; if(votDeviceData && unBindList && unBindList.length > 0){ votDeviceData = JSON.parse(votDeviceData); unBindList = JSON.parse(unBindList); const bindList = votDeviceData.data.bindList; unBindList.forEach((unbind)=>{ let bind = bindList.filter(function(b) { return b.applicationFuncId == unbind.applicationFuncId; }); //console.log(unbind); //console.log(bind); if(bind.length > 0){ var bindRequest = {}; bindRequest.protocolType = bind[0].protocolType; bindRequest.productId = bind[0].productId; bindRequest.serviceId = bind[0].serviceId; bindRequest.funcType = bind[0].funcType; bindRequest.commandId = bind[0].commandId; bindRequest.strategyId = bind[0].strategyId; bindRequest.innerTestCarConfFuncId = unbind.innerTestCarConfFuncId; ajax(bindRequest); } }) } } const url = BASE_URL + "/nebula-apigw/vot-admin/admin/api/inner-test-car/inner-conf-func-bind"; // var bindRequest = {}; // { // "protocolType": "0", // "productId": "1760923026673184770", // "serviceId": "1773320466344484866", // "funcType": 1, // "commandId": "1773320466394816512", // "innerTestCarConfFuncId": "1864506974305689616" // //applicationFuncId // } function ajax(data){ const ajaxobj = { type:"post", url: url, dataType: "json", async: false, contentType: "application/json; charset=utf-8", beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Authorization", localStorage.Authorization); }, data: JSON.stringify(data||""), success: function (res){ //successCallback && successCallback(res); }, error: function (XMLHttpRequest, textStatus, errorThrown) { console.error('操作失败!!!' + XMLHttpRequest.status + "|" + XMLHttpRequest.readyState + "|" + textStatus); } }; $.ajax(ajaxobj); } } //车型车系 function confSelectCopyInit(){ ajaxHooker.filter([ //{url: /get-conf-select-func/}, {url: /getBindCarDeviceConfFunc/}, {url: /getConfSelectFunc/}, {url: /carDeviceConf\/conf\/page/}, ]); function bindAll2(){ const url = BASE_URL + "/nebula-apigw/vot-admin/admin/api/carDeviceConf/confFuncBind"; let votDeviceData = localStorage.getBindCarDeviceConfFunc; let unBindList = localStorage.device_unBindList; if(votDeviceData && unBindList && unBindList.length > 0){ votDeviceData = JSON.parse(votDeviceData); unBindList = JSON.parse(unBindList); const bindList = votDeviceData.data.bindList; unBindList.forEach((unbind)=>{ let bind = bindList.filter(function(b) { return b.applicationFuncId == unbind.applicationFuncId; }); //console.log(unbind); //console.log(bind); if(bind.length > 0){ var bindRequest = {}; bindRequest.commandId = bind[0].commandId; bindRequest.strategyId = bind[0].strategyId; bindRequest.confScope = unbind.confScope; bindRequest.funcType = bind[0].funcType; bindRequest.levelId = localStorage.currentLevelId; bindRequest.productId = bind[0].productId; bindRequest.protocolType = bind[0].protocolType; bindRequest.serviceId = bind[0].serviceId; bindRequest.votCarConfFuncId = unbind.votCarConfFuncId; ajax(url, bindRequest); } }) } } ajaxHooker.hook(request => { console.log("confSelectCopyInit-request:",request); if(request.url.includes("getBindCarDeviceConfFunc")){ if($("#bindAll").length == 0){ //const btn_bind = $("#contentOne").next(".operateBtn"); const btn_bind = $("#contentTwo").next(".operateBtn"); if(btn_bind.length > 0){ const btn_cls = btn_bind.children()[0].className; const apd = '<button type="button" id="bindAll" class="' + btn_cls + ' " style="margin-left: 8px"><span>一键绑定</span></button>' btn_bind.last().append(apd); $("#bindAll").click(bindAll2); } } request.response = res => { const responseText = res.responseText; // 注意保存原数据 const resdata = JSON.parse(responseText); if(resdata && resdata.success){ // const selectFuncList = resdata.data.selectFuncList; const bindList = resdata.data.bindList; localStorage.setItem("device_unBindList", JSON.stringify(resdata.data.unBindList)); if(bindList.length > 0){ localStorage.setItem("getBindCarDeviceConfFunc", responseText); } } }; } else if(request.url.includes("getConfSelectFunc")){ const ok_copyId = "ok_copy_conf"; const ok_for_pasteId = "ok_for_pasteConf"; if($("#" + ok_copyId).length == 0){ const btn_bind = $("#contentOne").next(".operateBtn"); //const btn_bind = $("#contentTwo").next(".operateBtn"); if(btn_bind.length > 0){ const paste = localStorage[ok_copyId] && localStorage[ok_for_pasteId] ? "已粘贴" : "无法粘贴"; const btn_cls = btn_bind.children()[1].className; const apd = '<button type="button" id="'+ok_copyId+'" class="' + btn_cls + ' " style="margin-left:8px"><span>一键复制</span></button>' const apd1 = '<button type="button" id='+ok_for_pasteId+'" class="' + btn_cls + ' " style="margin-left:8px"><span>' + paste + '</span></button>' btn_bind.last().append(apd).append(apd1); $("#" + ok_copyId).click((e)=> { localStorage.setItem(ok_copyId, 1); localStorage.setItem(ok_for_pasteId, localStorage.selectFuncList); e.target.innerHTML = "已复制"; }); } } request.response = res => { const responseText = res.responseText; // 注意保存原数据 localStorage.setItem("selectFuncList", responseText); if(localStorage[ok_copyId]){ res.responseText = localStorage.ok_for_paste; localStorage.removeItem(ok_copyId); } }; } else if(request.url.includes("carDeviceConf/conf/page")){ const levelId = new URLSearchParams(request.url).get("levelId"); localStorage.setItem("currentLevelId", levelId); //2025-5-9 VOT车型车系显示当前版本号 showVersion(); } }); function showVersion(){ let url = BASE_URL + "/nebula-apigw/vot-admin/admin/api/carDeviceConf/queryConfFuncVersion"; let callback = resp => { if(resp.code === 0){ let data = resp.data; if(data && data.length > 0){ data = data[0]; let toolbar = $(".ant-pro-table-list-toolbar-title"); if(toolbar.length > 0){ var childrenLength = toolbar[0].children.length; toolbar[0].children[childrenLength - 2].childNodes[0].innerHTML="最新版本:" + data.confVersion; } } } }; let data = { "levelId": localStorage.currentLevelId, "confScope": "1" }; ajax(url, data, callback); } } // Your code here... })();