// ==UserScript== // @name 稽核脚本1022 // @version 3.2.6 // @description 该脚本辅助三费系统进行稽核异常操作 // @match http://10.217.240.219:8090/NCMS/welcome // @require http://192.168.100.150:31628/sevenfeeProvince/pluginCDN/vue.js // @require http://192.168.100.150:31628/sevenfeeProvince/pluginCDN/eleJs.js // @require http://192.168.100.150:31628/sevenfeeProvince/pluginCDN/qs.js // @require http://192.168.100.150:31628/sevenfeeProvince/pluginCDN/jsencrypt.js // @grant GM_xmlhttpRequest // @connect 192.168.100.150 // ==/UserScript== ;(function () { 'use strict' console.log('稽核脚本执行') const linkElem = document.createElement('link') linkElem.rel = 'stylesheet' linkElem.href = 'http://192.168.100.150:31628/sevenfeeProvince/pluginCDN/eleCss.css' document.head.appendChild(linkElem) // html骨架 const html = `
稽核插件 检测稽核异常 退出插件 最小化
异常提示
关闭异常的原因:
关闭异常 打开登录 登录状态: {{ loginState ? '已登录':'未登录' }}
登录 取消登录
稽核插件
` // 创建容器元素 + 将容器添加到页面的 body 元素中 const containerEl = document.createElement('div') containerEl.className = 'TMContainer' containerEl.setAttribute('id', 'TMContainer') document.body.appendChild(containerEl) containerEl.innerHTML = html // ============== 插件拖拽 =============== containerEl.onmousedown = function (event) { containerEl.style.cursor = 'move' // 获取元素的初始位置 let startX = containerEl.offsetLeft let startY = containerEl.offsetTop // 获取鼠标按下的位置 const mouseX = event.pageX const mouseY = event.pageY // 监听鼠标移动 事件 window.onmousemove = function (event) { // 计算鼠标移动距离 let disX = event.pageX - mouseX let disY = event.pageY - mouseY containerEl.style.left = startX + disX + 'px' containerEl.style.top = startY + disY + 'px' } // 监听鼠标 松开 containerEl.onmouseup = function () { // 移除鼠标移动事件 window.onmousemove = null containerEl.style.cursor = '' } } /* 业务类别(0:电费、1:租费、2:铁塔 ) 缴费类型 1 报账点 2 缴费单 */ let feeType_G = 0 let paymentType_G = 1 let nowPageName_G = '' // 当前激活的 nav 的名字 let collectEncodeType_G = '' // 查询时 需要收集页面上的编码, 一般为 报账点编码/缴费单编码 但是租费不一样需要特殊处理 let currentPageNum_G = 1 // 当前的分页器所在的页数 let activeNavIndex_G = 0 // 当前ifream在第几页, "首页"从1开始 let abnormalTableIndexList_G = [] // 存储: 账单异常的表格行的下标, 从0开始 let checkTableIndexList_G = [] let isLatestVersion_G = false // 检测当前插件是否是最新版本 // 属性 type = 0: 查询界面 , 1: 审核界面 // encodeType : 需要收集table中的参数 let matchNavList_G = [ { type: 0, navName: '电费报账点查询', feeType: 0, paymentType: 1, encodeType: '报账点编码' }, { type: 0, navName: '报账点电费查询', feeType: 0, paymentType: 2, encodeType: '缴费单编码' }, { type: 0, navName: '租费报账点信息查询', feeType: 1, paymentType: 1, encodeType: '报账点编码', }, { type: 0, navName: '报账点租费查询', feeType: 1, paymentType: 2, encodeType: '缴费单编码' }, { type: 0, navName: '费用汇总', feeType: 2, paymentType: 5, encodeType: '汇总单号' }, { type: 0, navName: '批量起租表', feeType: 2, paymentType: 2, encodeType: ['站址编码', '业务确认单号', '需求单号', '起租表账期'], }, { type: 1, navName: '报账点电费审核', feeType: 0, paymentType: 2, encodeType: '缴费单编码' }, { type: 1, navName: '报账点租费审核', feeType: 0, paymentType: 1, encodeType: '报账点编码' }, { type: 1, navName: '费用审核', feeType: 2, paymentType: 5, encodeType: '汇总单号' }, ] // 插件接口汇总 const baseURL_G = 'http://192.168.100.150:31628' const urlList_G = [ { name: '登录插件', type: 0, url: '/uaa/authentication/jwt' }, { name: '退出插件', type: 1, url: '/uaa/logout' }, { name: '手动关闭异常', type: 2, url: '/cpa/pluginUpdate/updateAuditState' }, { name: '查询异常详细', type: 3, url: '/cpa/plugin/queryAuditErrorList' }, { name: '批量查询是否异常', type: 4, url: '/cpa/plugin/queryPaymentState' }, { name: '获取登录加密密钥', type: 5, url: '/uaa/config' }, ] // 用户信息 const userInfoObj_G = { oldFeeSystemName: '', browserName: '', plugVersion: 'V3.2.6', } /** * 检测当前插件版本是否是最新 */ async function checkVersion() { const versionId = userInfoObj_G.plugVersion const QS = Qs GM_xmlhttpRequest({ url: `${baseURL_G}/cpa/plugin/checkCatVersion`, method: 'post', anonymous: true, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, data: QS.stringify({ versionId }), onload: (res) => { console.log('res==>', JSON.parse(res.response)) const { isUpdate, url } = JSON.parse(res.response).data if (isUpdate) { isLatestVersion_G = false window.open(url) // 打开更新的链接 ELEMENT.Message.warning('当前插件版本不是最新版本,请更新或者 刷新浏览器') } else { isLatestVersion_G = true console.log('当前版本是最新') } }, onerror: (e) => reject(e), }) } checkVersion() /** * 获取当前客户端信息 */ function getUserInfo() { // 获取当前账户名 userInfoObj_G.oldFeeSystemName = document.querySelector( '.layout-header .header-bar #userName' ).textContent // 获取当前浏览器信息( 浏览器版本/名称 ), 由后台解析。 userInfoObj_G.browserName = navigator.userAgent } getUserInfo() /** * 获取当前的 业务类别 和 缴费类型 , 以及当前页面 */ function setFeeTypeAndPayMentType() { const aEls = document.querySelectorAll('.layout-main-tab .tab-nav .tab-nav-content a') const activeAElements = Array.from(aEls).find((aEl) => { return aEl.classList.contains('active') }) const nowNavName = activeAElements.textContent.replace(/[^\u4e00-\u9fa5]/gi, '') //console.log('nowNavName==>', nowNavName); // 请求前匹配 参数使用的 业务类别:feeType_G 和 缴费类型:paymentType_G let temp = false matchNavList_G.forEach((item) => { if (item.navName === nowNavName) { temp = true feeType_G = item.feeType paymentType_G = item.paymentType collectEncodeType_G = item.encodeType nowPageName_G = item.navName } }) // 当temp为false时, 则说明未匹配到稽核页面, 执行下面, 获取当前所在页 if (!temp) { nowPageName_G = nowNavName } } /** * 封装请求函数 */ function A_X(url, data) { setFeeTypeAndPayMentType() // 确定操作类型 const operationType = urlList_G.find((item) => item.url === url)?.type const headers = { 'Content-Type': 'application/x-www-form-urlencoded', } if (operationType === 3 || operationType === 4) { headers['Content-Type'] = 'application/json' } else if (operationType === 0 || operationType === 5) { headers['X-Requested-With'] = 'XMLHttpRequest' } // 后台人员要求: 每一次请求前都要请求一下日志接口 async function setLog() { let setLogURL = '/cpa/plugin/setLog' // 收集关闭异常单据的参数: 异常数据编码, 关闭的异常编码, 关闭原因 let documentsCode = '' let documentsCodeClosed = '' let closeReason = '' // 当前操作类型调用的日志接口 需要携带token if (operationType === 0 || operationType === 1 || operationType === 2) { setLogURL = '/cpa/pluginUpdate/setLog' } // 手动关闭异常 if (operationType === 2) { const auditToken = localStorage.getItem('auditToken') headers['Authorization'] = `${auditToken}` const QS = Qs const updateAuditParams = QS.parse(data) console.log('updateAuditParams==>', updateAuditParams) documentsCode = updateAuditParams.documentsCode documentsCodeClosed = updateAuditParams.id closeReason = updateAuditParams.describe } // 查询异常详细 if (operationType === 3) { documentsCode = JSON.parse(data).paymentCode } const setLogParams = { ...userInfoObj_G, logType: operationType, documentsCode, documentsCodeClosed, closeReason, } await GM_xmlhttpRequest({ url: `${baseURL_G}${setLogURL}`, method: 'post', anonymous: true, headers: { 'Content-Type': 'application/json', Authorization: `${localStorage.getItem('auditToken')}`, }, data: JSON.stringify(setLogParams), }) } setLog() console.log('headers==>', headers) console.log('url==>', url) return new Promise((resolve, reject) => { GM_xmlhttpRequest({ url: `${baseURL_G}${url}`, method: 'post', data, headers, anonymous: true, onload: (res) => { console.log('res==>', res) if (res.status === 200) { if (operationType === 0) { console.log('url==>', url) // 使用正则表达式匹配 const tokenRegex = /authorization:\s*(.*)/ const match = res.responseHeaders.match(tokenRegex) const token = match ? match[1] : null localStorage.setItem('auditToken', token) // 登录之后得到token, 再次调用一下日志接口. setLog() return resolve(res) } return resolve(JSON.parse(res.response)) } else if (res.status === 302) { ELEMENT.Message.error('登录信息过期') return resolve(res.status) } else { if (!res.finalUrl.includes('logout')) { ELEMENT.Message.error(JSON.parse(res.responseText).msg) resolve(res.status) } // 退出接口, 删除token localStorage.removeItem('auditToken') } }, onerror: (e) => reject(e), }) }) } var TMApp = new Vue({ el: '#TMapp', data() { return { loginForm: { username: 'admin', password: '1qaz2WSX3edc', }, tableHeads: [ // { label: '异常编码', prop: 'account' }, { label: '稽核规则名称', prop: 'auditRuleCn' }, { label: '异常产生时间', prop: 'lastModifyTime' }, // 后台加 最后更新时间 { label: '异常描述', prop: 'actualValue' }, { label: '是否强制稽核', prop: 'isMandatoryAudit', formatter: (row) => ['否', '是'][row.isMandatoryAudit], }, { label: '责任人', prop: 'responsible' }, { label: '异常状态', prop: 'isDelete' }, ], stateList: [], tableData: [], // 收集审核界面 和 查询界面的 nameList, 用于后续判断 queryPageNameList: matchNavList_G .filter(({ type }) => type === 0) .map(({ navName }) => navName), auditPageNameList: matchNavList_G .filter(({ type }) => type === 1) .map(({ navName }) => navName), abnormalCode: '', // 点击查看具有异常的单号 checkedParams: { state: 0 }, // 消除异常参数 1关闭异常, 0不关闭 abnormalTextarea: '', isLoginShow: false, // 登录界面是否展示 loginState: false, // 是否已经登录 isSearchPageShow: true, // 插件界面功能区切换 isAuditpage: false, // 当前是否是审核界面 isAbnormalNewsShow: false, // 异常提示的表格是否显示 isAuditButtonAndTextareaShow: false, publicKey: '', // 登录加密公钥 jsEncrypt: '', // 加密 viewShow: true, // 界面的总开关 isDisAbledSaveBtn: null, // 是否禁用审核二级界面的保存按钮, monitorLoadingList: [], // 存储监听的loading的 observe 对象 } }, created() { this.monitorTabNav() }, methods: { /** * 判断稽核状态 */ async startAudit(first) { // 判断当前插件是否登录 this.loginState = localStorage.getItem('auditToken') ? true : false // 确实使用的是最新版本 if (!isLatestVersion_G) { this.$message({ message: '请升级到最新版本', type: 'warning' }) return } // 判断当前被激活的导航index activeNavIndex_G = this.findActiveTabIndex() // 在发出查询状态的接口前, 需要判断当前要收集的数据 setFeeTypeAndPayMentType() // 稽核之前要判断当前是否是 可以稽核的界面 const navNameList = matchNavList_G.map((item) => item.navName) if (!navNameList.includes(nowPageName_G)) { this.$message({ message: '当前不是可以稽核的界面', type: 'warning', }) return } // 获取当前所在页数 currentPageNum_G = this.getcurrentPageNum() // 当处于审核界面时, 开启checkbox 的监听 if (this.auditPageNameList.includes(nowPageName_G)) { this.monitorCheckboxClick() } // 当前已经存在'异常状态' 这个表头, 则清除重新生成 if (this.isHasAbnormalStateHeader()) { console.log('当前存在异常状态表头, 清除重新生成') this.clearTableHeader('异常状态') } // 开启监听 "loading", 当处于查询界面 , 换页/查询后 自动进行稽核 if (first && this.queryPageNameList.includes(nowPageName_G)) { if (!this.monitorLoadingList[activeNavIndex_G]) { // 使用list+当前激活的nav 的下标来区分当前界面是否存在 observe this.monitorLoadingList[activeNavIndex_G] = this.monitorLoading() } } // 审核界面是否可以通过 审核 if (this.auditPageNameList.includes(nowPageName_G)) { console.log('当前是可以审核的界面') this.monitorAuditSelect() } else { console.log('当前不是可以审核的界面') } let encodeList = [] // 如果是服务费则要传递的参数 if (feeType_G === 2 && paymentType_G === 2) { const addressEncodeList = this.getEncodeList('站址编码') const businessOddList = this.getEncodeList('业务确认单号') const needOddList = this.getEncodeList('需求单号') const startTimeList = this.getEncodeList('起租表账期') const ListLength = startTimeList.length for (let i = 0; i < ListLength; i++) { const temp = addressEncodeList[i] + '_' + businessOddList[i] + '_' + needOddList[i] + '_' + startTimeList[i] encodeList.push(temp) } //console.log('encodeList==>', encodeList) } else { //console.log('collectEncodeType_G==>', collectEncodeType_G) encodeList = this.getEncodeList(collectEncodeType_G) //console.log('encodeList==>', encodeList) } if (!encodeList?.length) { this.$message.warning('当前界面不存在数据') return } const params = { businessCategory: feeType_G, paymentCode: 'string', paymentCodeList: encodeList, paymentType: paymentType_G, } // console.log('params==>', params) const res = await A_X('/cpa/plugin/queryPaymentState', JSON.stringify(params)) console.log('res==>', res) // res.data.list[0].state = '异常' this.stateList = res.data.list abnormalTableIndexList_G = [] checkTableIndexList_G = [] // 重新检测稽核异常后, 关闭清空表格数据并关闭异常的展示 this.tableData = [] this.isAbnormalNewsShow = false this.isAuditButtonAndTextareaShow = false this.isAuditpage = false // 收集当前异常列表中的的 index res.data.list.forEach((item, index) => { if (item.state !== '正常') { abnormalTableIndexList_G.push(index) } }) //console.log('abnormalTableIndexList_G==>', abnormalTableIndexList_G) // 将稽核状态插入表格中, 此时 active 在第几个 a 标签上, 就选中第几个 ifream const thead_tr_Els = window.frames[activeNavIndex_G].document.querySelectorAll( '.fixed-table-body #tb thead tr' ) const tbody_tr_Els = window.frames[activeNavIndex_G].document.querySelectorAll( '.fixed-table-body #tb tbody tr' ) // 当前已经存在'异常状态' 这个表头, 则清除重新生成 if (this.isHasAbnormalStateHeader()) { console.log('当前存在异常状态表头, 清除重新生成') this.clearTableHeader('异常状态') } thead_tr_Els.forEach((trElement) => { const thEl = document.createElement('th') thEl.textContent = '异常状态' // 设置表头 const towChildElement = trElement.children[1] trElement.insertBefore(thEl, towChildElement) }) // 根据返回的状态, 去渲染异常的按钮 tbody_tr_Els.forEach((trElement, index) => { const tdEl = document.createElement('td') if (this.stateList[index].state === '正常') { const spanElement = document.createElement('span') spanElement.textContent = '稽核正常' tdEl.appendChild(spanElement) } else if (this.stateList[index].state === '暂未同步') { const spanElement = document.createElement('span') spanElement.textContent = '暂未同步' tdEl.appendChild(spanElement) } else { const buttonElement = document.createElement('button') buttonElement.addEventListener('click', this.handleAbnormal) buttonElement.textContent = '稽核异常' tdEl.appendChild(buttonElement) } const towChildElement = trElement.children[1] trElement.insertBefore(tdEl, towChildElement) }) }, /** * 点击: 查看异常, 请求接口传递编码 id , 打开异常提示界面 */ async handleAbnormal(event) { // 判断当前插件是否登录 this.loginState = localStorage.getItem('auditToken') ? true : false // 每次点击按钮之前 都要调用一下这两个函数, 防止页面数据出乱 setFeeTypeAndPayMentType() activeNavIndex_G = this.findActiveTabIndex() // 如果是审核界面, 则显示勾选框 if (['报账点电费审核', '报账点租费审核', '费用审核'].includes(nowPageName_G)) { this.isAuditpage = true } else { this.isAuditpage = false } this.isLoginShow = false const td = event.target.parentElement const tr = td.parentElement const cells = tr.getElementsByTagName('td') // 获取当前行中的所有单元格(td) // 获取当前 collectEncodeType_G 所在的下标, 服务费的情况为数组 const nowRowEncodeIndex = this.getNowRowEncodeIndex() // 点击处理异常, 获取当前的编码 collectEncodeType_G , 发起请求 let encodeParams = [] if (Array.isArray(nowRowEncodeIndex)) { nowRowEncodeIndex.forEach((item) => { encodeParams.push(cells[item].textContent) }) encodeParams = encodeParams.join('_') } else { encodeParams = cells[nowRowEncodeIndex].textContent } console.log('处理异常的编码encodeParams==>', encodeParams) this.abnormalCode = encodeParams // 存储异常单号 const params = { businessCategory: feeType_G, paymentCode: encodeParams, paymentCodeList: [], paymentType: paymentType_G, } const res = await A_X('/cpa/plugin/queryAuditErrorList', JSON.stringify(params)) this.tableData = res.data this.isAbnormalNewsShow = true }, /** * 获取 当前行的 collectEncodeType_G 对应的 所在头部的下标, 用来去获取查询异常情况的参数。 * 一般来说,返回一个数字 ,当是服务费时, 返回数组 */ getNowRowEncodeIndex() { const table = window.frames[activeNavIndex_G].document.querySelector('.fixed-table-body #tb') const headerRow = table.querySelector('thead tr') let codeCellIndex = [] //console.log('collectEncodeType_G==>', collectEncodeType_G) if (feeType_G === 2) { // 此时返回一个数组, collectEncodeType_G.forEach((item) => { Array.from(headerRow.cells).forEach((cell, indexCell) => { if (cell.textContent === item) { codeCellIndex.push(indexCell) } }) }) } else { Array.from(headerRow.cells).forEach((cell, indexCell) => { if (cell.textContent === collectEncodeType_G) { codeCellIndex = indexCell } }) } return codeCellIndex }, /** * @param {String} headName 想要收集的数据列的表头 * @return {Array} 数据组成的数组 */ getEncodeList(headName) { const table = window.frames[activeNavIndex_G].document.querySelector('.fixed-table-body #tb') // 获取表头中"headName"所在的列索引 const headerRow = table.rows[0] let columnIndex = -1 for (let i = 0; i < headerRow.cells.length; i++) { if (headerRow.cells[i].textContent === headName) { columnIndex = i break } } // 获取headName列下的所有数据 const dataList = [] try { if (columnIndex !== -1) { const bodyRows = table.tBodies[0].rows for (let j = 0; j < bodyRows.length; j++) { const cell = bodyRows[j].cells[columnIndex] dataList.push(cell.textContent) } } return dataList } catch (error) { // this.$message.error(error) } }, /** * 获取加密公钥 */ async getPublicKey() { const url = '/uaa/config' const params = '' const res = await A_X(url, params) this.publicKey = res.data.publicKey this.jsEncrypt = new JSEncrypt() this.jsEncrypt.setPublicKey(this.publicKey) console.log('getPublicKey res==>', res) }, /** * 登录, 成功之后关闭登录,打开 */ async login() { // 先去获取公钥, 然后再发送登录请求 await this.getPublicKey() // 如果登录成功 , 1. 关闭登陆界面 2. 设置登陆状态已登录 3. 展示审核按钮和输入框 const params = { ...this.loginForm, password: this.jsEncrypt.encrypt(this.loginForm.password), } try { const QS = Qs const res = await A_X('/uaa/authentication/jwt', QS.stringify(params)) this.isLoginShow = false this.loginState = true this.isAuditButtonAndTextareaShow = true } catch { this.$message({ message: '用户名或密码错误', type: 'warning', }) } }, /** * 开始审核,修改异常. */ async closeAbnormal() { //console.log('this.checkedParams==>', this.checkedParams) if (!this.checkedParams.id) { this.$message({ message: '请至少选择一个异常', type: 'warning', }) return } if (!localStorage.getItem('auditToken')) { // 未登录则打开登陆界面, 关闭审核界面, 节省插件空间 this.$message({ message: '审核前, 请先登录', type: 'warning', }) this.isLoginShow = true this.isAuditButtonAndTextareaShow = false return } this.checkedParams.describe = this.abnormalTextarea this.checkedParams.documentsCode = this.abnormalCode console.log('this.checkedParams==>', this.checkedParams) const QS = Qs const status = await A_X( '/cpa/pluginUpdate/updateAuditState', QS.stringify(this.checkedParams) ) if (status === 302) { this.isLoginShow = true this.isAuditButtonAndTextareaShow = false return } this.startAudit() }, /** * 收集勾选的参数 */ selectionChange(selection) { const ids = selection.map((item) => item.id).join(',') this.checkedParams.id = ids this.isAuditButtonAndTextareaShow = this.checkedParams.id ? true : false }, /** * 监听当前的 checkbox 的点击事件, 来禁用审核按钮 * 这个东西可以放在 '检测稽核异常' 按钮里面 */ monitorCheckboxClick() { console.log('开始监听', activeNavIndex_G) const checkboxes = window.frames[activeNavIndex_G].document.querySelectorAll( '.fixed-table-body #tb input[name="btSelectItem"]' ) const that = this // 监听所有 btSelectItem 复选框的点击事件 checkboxes.forEach((checkbox) => { checkbox.addEventListener('change', function (event) { // 获取勾选的数组, 去和 abnormalTableIndexList_G 比较是否有交集, 有则是存在异常 const nowRowIndex = Number(event.target.getAttribute('data-index')) console.log('abnormalTableIndexList_G==>', abnormalTableIndexList_G) const index = checkTableIndexList_G.indexOf(nowRowIndex) if (index !== -1) { checkTableIndexList_G.splice(index, 1) // 如果存在则删除 } else { checkTableIndexList_G.push(nowRowIndex) // 如果不存在则添加 } console.log('checkTableIndexList_G==>', checkTableIndexList_G) // 是否禁用审核通过按钮 function isBanFn(arr1, arr2) { console.log('that.stateList==>', that.stateList) const isForceLock = that.stateList[checkTableIndexList_G[0]].isForceLock || 1 console.log('isForceLock==>', isForceLock) return Number(isForceLock) ? true : false } const isBan = isBanFn(checkTableIndexList_G, abnormalTableIndexList_G) that.isDisAbledSaveBtn = isBan console.log('that.isDisAbledSaveBtn==>', that.isDisAbledSaveBtn) }) }) }, /** * 判断当前 导航栏 哪一个被激活了, 返回当前激活的导航栏的下标 */ findActiveTabIndex() { const tabNavContent = document.querySelector('.tab-nav-content') const links = tabNavContent.getElementsByTagName('a') for (let i = 0; i < links.length; i++) { if (links[i].classList.contains('active')) { return i + 1 // 返回索引加一,因为索引是从 0 开始的 } } return -1 }, /** * 获取当前分页器所在的页数 currentPageNum_G * 并和之前的进行比较,, 判断是否发生变化, 若发生变化则去除"异常状态" 这一个表头 */ getcurrentPageNum() { // 获取具有 "active" 类名的
  • 标签 // 注意: 即便只有一页, 界面中分页器未展示,但还是有分页器的 console.log('activeNavIndex_G==>', activeNavIndex_G) const activeListItem = window.frames[activeNavIndex_G].document.querySelector( '.pull-right.pagination .page-number.active' ) console.log('activeListItem==>', activeListItem) // 获取 标签的文本内容 const activeContent = activeListItem.querySelector('a')?.textContent || activeListItem.querySelector('span')?.textContent if (activeContent !== currentPageNum_G) { this.clearOnlyTableHeader('异常状态') } return activeContent }, /** * 清除插件添加的 '异常状态' 这一列数据 */ clearTableHeader(colName) { const table = window.frames[activeNavIndex_G].document.querySelector('.fixed-table-body #tb') const headerRow = table.querySelector('thead tr') // 查找表头中对应列的索引 let columnIndex = -1 Array.from(headerRow.children).forEach((th, index) => { if (th.textContent.trim() === colName) { columnIndex = index } }) if (columnIndex !== -1) { // 删除表头中的对应列 headerRow.deleteCell(columnIndex) // 删除每个数据行中的对应列 const dataRows = table.querySelectorAll('tbody tr') dataRows.forEach((row) => { row.deleteCell(columnIndex) }) } else { console.log('列名未找到') } }, /** * 删除'异常状态'表头, 用于点击下一页时, 系统会自动清除数据去重新加载, 所以说此时只要删除表头即可 */ clearOnlyTableHeader(colName) { const table = window.frames[activeNavIndex_G].document.querySelector('.fixed-table-body #tb') const headerRow = table.querySelector('thead tr') // 查找表头中对应列的索引 let columnIndex = -1 Array.from(headerRow.children).forEach((th, index) => { if (th.textContent.trim() === colName) { columnIndex = index } }) if (columnIndex !== -1) { // 删除表头中的对应列 headerRow.deleteCell(columnIndex) } else { console.log('列名未找到') } }, /** * 查询当前界面是否含有 '异常状态' 表头 */ isHasAbnormalStateHeader() { const table = window.frames[activeNavIndex_G].document.querySelector('.fixed-table-body #tb') const headerRow = table.querySelector('thead tr') const hasAbnormalHeader = Array.from(headerRow.children).some( (cell) => cell.innerText.trim() === '异常状态' ) return hasAbnormalHeader }, /** * 退出插件登录 */ async logOut() { if (!localStorage.getItem('auditToken')) { this.$message.warning('当前已经退出成功') return } this.$message.success('退出登录成功') // 界面恢复成初始的样子 this.tableData = [] this.isAbnormalNewsShow = false this.isAuditButtonAndTextareaShow = false this.isAuditpage = false // 接口报500报错, 但是无所谓了, 退出的功能和日志记录已经实现 await A_X(urlList_G[1].url, '') }, /** * 放大 和 缩小 界面 */ expandAndShrinkView() { const card = document.querySelector('.el-card.TMbox-card') if (card.classList.contains('is-zoomed')) { card.classList.remove('is-zoomed') } else { card.classList.add('is-zoomed') } }, /** * 展开和关闭界面 */ toggleView() { this.viewShow = !this.viewShow }, /** * 检测当前表格是否有数据 * @returns {boolean} */ checkTableData() { const noTrEl = window.frames[activeNavIndex_G].document.querySelector( '.fixed-table-body #tb tbody .no-records-found' ) const trEls = window.frames[activeNavIndex_G].document.querySelectorAll( '.fixed-table-body #tb tbody tr' ) console.log('noTrEl==>', noTrEl) console.log('trEls==>', trEls) if ((trEls.length === 1 && noTrEl) || !trEls) { return false } return true }, /** * 监听 "正在加载", 达到换页后自动稽核的效果 */ monitorLoading() { console.log('生成一个 observe 对象') // 1. 先获取当前的ifream 中的 "ui_loading_progressBar" activeNavIndex_G = this.findActiveTabIndex() // 获取要监听的目标元素 const loadingEl = window.frames[activeNavIndex_G].document.querySelector('#ui_loading_progressBar') || window.frames[activeNavIndex_G].document.querySelector('.fixed-table-loading') console.log('loadingEl==>', loadingEl) // 2. 开始监听: 创建一个 MutationObserver 实例 const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'attributes') { const styleValueList = [ 'left: 50%; top: 50%; display: none;', 'top: 42px; display: none;', ] if (styleValueList.includes(loadingEl.getAttribute(mutation.attributeName))) { setTimeout(() => { this.startAudit() console.log(`检测到style变为:${loadingEl.getAttribute(mutation.attributeName)}`) }, 800) } } } }) // 配置观察选项 const config = { attributes: true, attributeOldValue: true } // 开始观察目标节点 observer.observe(loadingEl, config) return observer }, /** * 判断只有当是审核界面的时候, 且点击 审核按钮后, 开始监听 select */ monitorAuditSelect() { const auditBtnEl = window.frames[activeNavIndex_G].document.querySelector( '.btn-toolbar .btn.btn-primary' ) // # id="saveSet" const that = this function handleAuditBtnClick() { console.log('监听点击了审批按钮') // 点击审批后, 给界面一点加载的时间, 防止查询不到 标签 // TODO : 请求加载完成之后加载. 方案: 通过使用定时执行器, 当所需dom未加载出来时, 一直循环 // const timer = setInterval(() => { // if('el元素不存在'){ // return // } // clearInterval(timer) // }, 1000) setTimeout(() => { const auditSelectEl = window.frames[activeNavIndex_G].document.querySelector( '.list-group.addReburseStyle .col-md-4 #checkState' ) || window.frames[activeNavIndex_G].document.querySelector( '.list-group .col-md-4 #auditState' ) const saveBtnEl = window.frames[activeNavIndex_G].document.querySelector('.btn-toolbar #saveSet') || window.frames[activeNavIndex_G].document.querySelector('.btn-toolbar .btn-primary') console.log('saveBtnEl==>', saveBtnEl) console.log('auditSelectEl==>', auditSelectEl) // 一开始直接选择的就是 "通过", 一开始选择的通过也得禁止 if (that.isDisAbledSaveBtn) { let index = auditSelectEl.selectedIndex let value = auditSelectEl.options[index].value saveBtnEl.disabled = value === '0' ? true : false console.log('saveBtnEl.disabled==>', saveBtnEl.disabled) } function handleSelectChange() { console.log('开启监听 审核界面的 select') let index = auditSelectEl.selectedIndex let value = auditSelectEl.options[index].value console.log('value==>', value) saveBtnEl.disabled = value === '0' ? true : false // 当 select 选中 "通过" 时候, 禁用当前界面的 保存按钮 console.log('saveBtnEl===>', saveBtnEl) } // 当勾选到了具有异常的单据,也就是 isDisAbledSaveBtn = ture 时,则开启监听 select console.log('that.isDisAbledSaveBtn===>', that.isDisAbledSaveBtn) if (that.isDisAbledSaveBtn) { auditSelectEl.addEventListener('change', handleSelectChange) } // 当点击 "取消" 后,回来再次触发自动稽核 const cancelBtnEl = window.frames[activeNavIndex_G].document.querySelector( '.btn-toolbar .btn.btn-default' ) console.log('cancelBtnEl==>', cancelBtnEl) function monitorCancelBtn() { console.log('点击了取消按钮') const timer = setInterval(() => { const trEls = window.frames[activeNavIndex_G].document.querySelectorAll( '.fixed-table-body #tb tbody tr' ) console.log('trEls==>', trEls) if (trEls.length >= 1) { that.startAudit(true) console.log('关闭定时器') clearInterval(timer) } }, 1000) } cancelBtnEl.addEventListener('click', monitorCancelBtn) }, 2000) } auditBtnEl.addEventListener('click', handleAuditBtnClick) }, /** * 监听 nav-tab 中的 active 所在的 nav-tab 是否时可以稽核的界面 */ monitorTabNav() { const that = this // 获取要监听的元素 const navTabEl = document.querySelector('.layout-main-tab .tab-nav .tab-nav-content') // 观察器的配置 const config = { attributes: true, childList: true, subtree: true } // 当观察到变动时执行的回调函数 const callback = function (mutationsList, observer) { // 遍历每一个变动 for (var mutation of mutationsList) { // 如果是子节点的 class 变化 if (mutation.type === 'childList') { // 遍历每一个子节点 mutation.addedNodes.forEach(function (node) { // 检查类名是否发生变化 if (node.classList.contains('active')) { // 获取当前界面() setFeeTypeAndPayMentType() if (matchNavList_G.map((item) => item.navName).includes(nowPageName_G)) { let count = 0 const timer = setInterval(async function () { count++ // 当是 报账点电费查询 界面时 查询速度较慢, 则定为 15s let timing = nowPageName_G === '报账点电费查询' ? 15 : 5 if (count >= timing) { // 当5/15秒后数据还未加载出来, 则判断未为无数据, 关闭定时器 clearInterval(timer) console.log('定时器已关闭') that.$message.warning('数据超时,请手动点击稽核按钮.') } const isHaveTableData = that.checkTableData() console.log('isHaveTableData==>', isHaveTableData) // 审核界面无数据时 if (!isHaveTableData && that.auditPageNameList.includes(nowPageName_G)) { that.$message.warning('当前界面无数据') console.log('无数据') clearInterval(timer) } console.log('执行 startAudit') await that.startAudit(true) console.log('关闭定时器') clearInterval(timer) }, 1000) } } }) } } } // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver(callback) // 以上述配置开始观察目标节点 observer.observe(navTabEl, config) }, }, }) /** * 样式: 防止类名可能冲突, 前面加上TM */ // 注入样式 let style = document.createElement('style') style.innerText = ` .TMContainer{ position: fixed; top: 25%; left: 70px; background-color: aliceblue; border-radius: 10px; padding: 15px; z-index: 99999; padding: 7px; border: 2px solid gray; } .TMbox-card { width: 500px; } .el-card__body{ padding: 5px !important; } .el-card__header{ padding:5px !important; } .is-zoomed{ width: 100px; height: 100px; } .el-table .el-table__cell{ padding: 3px 0 !important; } .el-table__header .has-gutter{ font-size:12px; } .el-table__body-wrapper .el-table__body tbody{ font-size:12px; } ` document.head.appendChild(style) // Your code here... })()