// import Vue from 'vue' import router from '@/router' import pinia from '@/store' import CryptoJS from "crypto-js"; import utils from '@/utils/utils'; import wx from 'weixin-js-sdk'; import { useAppStore } from '@/store/app.js' /** * 获取uuid */ export function getUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16) }) } /** * 是否有权限 * @param {*} key */ export function isAuth(key) { return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false } /** * 树形数据转换 * @param {*} data * @param {*} id * @param {*} pid */ export function treeDataTranslate(data, id = 'id', pid = 'parentId') { var res = [] var temp = {} for (var i = 0; i < data.length; i++) { temp[data[i][id]] = data[i] } for (var k = 0; k < data.length; k++) { if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) { if (!temp[data[k][pid]]['children']) { temp[data[k][pid]]['children'] = [] } if (!temp[data[k][pid]]['_level']) { temp[data[k][pid]]['_level'] = 1 } data[k]['_level'] = temp[data[k][pid]]._level + 1; temp[data[k][pid]]['children'].push(data[k]) } else { res.push(data[k]) } } return res } export function toTreeArry(data) { const result = [] data.forEach(item => { const loop = data => { let child = data.children; if (child) { // 是否有子节点,有则继续遍历下一级,无则是叶子节点 for (let i = 0; i < child.length; i++) { loop(child[i]) } } else { result.push(data) } } loop(item) }) // console.log(JSON.stringify(result)) return result; } /** * list转换成tree * @param {*} oldArr * @param {*} menuId * @param {*} parentId */ /** * list转换成tree * @param {*} oldArr * @param {*} menuId * @param {*} parentId */ export function listToTree(oldArr) { // 删除 所有 children,以防止多次调用 oldArr.forEach(function (item) { item.checked = false; delete item.children; }); oldArr.forEach(element => { let parentId = element.parentId; if (parentId !== 0) { oldArr.forEach(ele => { if (ele.menuId == parentId) { //当内层循环的ID== 外层循环的parendId时,(说明有children),需要往该内层id里建个children并push对应的数组; if (!ele.children) { ele.children = []; } // ele.children.push(element); (ele.children || (ele.children = [])).push(element); } }); } }); // console.log(oldArr) //此时的数组是在原基础上补充了children; oldArr = oldArr.filter(ele => ele.parentId === 0); //这一步是过滤,按树展开,将多余的数组剔除; // console.log(JSON.stringify(oldArr)) return oldArr; } /** * list转换成tree * @param {*} data * @param {*} menuId * @param {*} parentId */ export function toTree(data) { // 删除 所有 children,以防止多次调用 data.forEach(function (item) { item.checked = false; delete item.children; }); // 将数据存储为 以 menuId 为 KEY 的 map 索引数据列 var map = {}; data.forEach(function (item) { map[item.menuId] = item; }); var val = []; data.forEach(function (item) { // 以当前遍历项,的parentId,去map对象中找到索引的id var parent = map[item.parentId]; // 好绕啊,如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中 if (parent) { (parent.children || (parent.children = [])).push(item); } else { item.children = []; //如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级 val.push(item); } }); // console.log(JSON.stringify(val)) return val; } /** * 判断tree是不是最后一级 */ export function isTail(data) { const child = data.children; return !child || child.length == 0; } /** * 把tree树的最后一级的父类children改为menuOptionsList */ export function changeChildenName(data) { const child = data.children; if (!!child && child.length > 0) { if (isTail(child[0])) { data.menuOptionsList = child; data.children = []; return; } child.forEach(changeChildenName); } } //根据最后一个找到整个家族 export function familyTree(arr, parentId) { var temp = []; var forFn = function (arr, parentId) { for (var i = 0; i < arr.length; i++) { var item = arr[i]; if (item.menuId == parentId) { temp.push(item); forFn(arr, item.parentId); } } }; forFn(arr, parentId); return temp; } /** * 树转list */ export function treeToList(tree) { for (var i in tree) { var node = tree[i]; list = []; //结果lsit if (node.children.length !== 0) { //遍历树的第一层,只有一个根结点 //第一层加入到list中,因为根结点模块设置为虚拟结点,所以不用加入 /*list.push({ id: node.id, name: node.title, parentId:node.parentId });*/ toListDF(node.children, list, node.menuId); //遍历子树,并加入到list中. } } return list; } /** * 深度优先遍历树 * 一个递归方法 * @params tree:要转换的树结构数据 * @params list:保存结果的列表结构数据,初始传list = [] * @params parentId:当前遍历节点的父级节点id,初始为null(因为根节点无parentId) **/ export function toListDF(tree, list, parentId) { for (var i in tree) { //遍历最上层 //将当前树放入list中 var node = tree[i]; list.push({ menuId: node.menuId, name: node.title, parentId: parentId }); //如果有子结点,再遍历子结点 if (node.children.length !== 0) { toListDF(node.children, list, node.menuId) //递归 } } } /** * 清除登录信息 */ export function clearLoginInfo() { // Vue.cookie.delete('token') useAppStore(pinia).commit('resetStore') router.options.isAddDynamicMenuRoutes = false } /** * 函数防抖 (只执行最后一次点击) * @param fn * @param delay * @returns {Function} * @constructor */ export function _debounce(fn, delay) { var delay = delay || 500; var timer; return function () { var th = this; var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { timer = null; fn.apply(th, args); }, delay); }; } /** * 函数节流 * @param fn * @param interval * @returns {Function} * @constructor */ export function _throttle(fn, interval) { var last; var timer; var interval = interval || 500; return function () { var th = this; var args = arguments; var now = +new Date(); if (last && now - last < interval) { clearTimeout(timer); timer = setTimeout(function () { last = now; fn.apply(th, args); }, interval); } else { last = now; fn.apply(th, args); } } } /** * 判断一个字符串是不是json格式 * @param str * @returns Boolean * @constructor */ export function isJSON(str) { if (typeof (str) === 'string') { try { var obj = JSON.parse(str); if (typeof (obj) === 'object' && obj) { return true; } else { return false; } } catch (e) { return false; } } } //监听页面显示隐藏方法 //显示隐藏页面 export function getHiddenProp() { var prefixes = ['webkit', 'moz', 'ms', 'o']; // if 'hidden' is natively supported just return it if ('hidden' in document) return 'hidden'; // otherwise loop over all the known prefixes until we find one for (var i = 0; i < prefixes.length; i++) { if ((prefixes[i] + 'Hidden') in document) return prefixes[i] + 'Hidden'; } // otherwise it's not supported return null; } export function getVisibilityState() { var prefixes = ['webkit', 'moz', 'ms', 'o']; if ('visibilityState' in document) return 'visibilityState'; for (var i = 0; i < prefixes.length; i++) { if ((prefixes[i] + 'VisibilityState') in document) return prefixes[i] + 'VisibilityState'; } // otherwise it's not supported return null; } export function isHidden() { var prop = getHiddenProp(); if (!prop) return false; return document[prop]; } //本地显示图片 export function getHost() { var protocol = window.location.protocol; var host = window.location.host; var hostName = ''; if (host.indexOf("localhost") >= 0) { hostName = `https://ssp-dev.trace.com.cn` } else { hostName = `..` } return hostName; } function safeUtf8(words) { try { return CryptoJS.enc.Utf8.stringify(words) || '' } catch (e) { return '' } } function normalizeCipherText(value) { let normalized = String(value ?? '').replace(/\s+/g, '').replace(/\r|\n/g, '') try { if (/%[0-9A-Fa-f]{2}/.test(normalized)) { normalized = decodeURIComponent(normalized) } } catch (e) {} // URL-safe Base64 normalized = normalized.replace(/-/g, '+').replace(/_/g, '/') return normalized } export function Encrypt(Okey, Oiv, payload) { const key = CryptoJS.enc.Utf8.parse(Okey); const iv = CryptoJS.enc.Utf8.parse(Oiv); let srcs = CryptoJS.enc.Utf8.parse(payload) const encrypted = CryptoJS.AES.encrypt(srcs, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) const base64str = CryptoJS.enc.Base64.stringify(encrypted.ciphertext) // return encrypted.ciphertext.toString().toUpperCase(); return base64str } export function Decryption(Okey, Oiv, value) { if (value === undefined || value === null || value === '') return '' if (typeof value !== 'string' && typeof value !== 'number') return '' const key = CryptoJS.enc.Utf8.parse(Okey); const iv = CryptoJS.enc.Utf8.parse(Oiv); const normalized = normalizeCipherText(value) const options = { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } // 1) 历史 Hex 密文 if (/^[0-9A-Fa-f]+$/.test(normalized) && normalized.length % 32 === 0) { const hexDecrypt = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Hex.parse(normalized) }, key, options, ) const hexText = safeUtf8(hexDecrypt) if (hexText) return hexText } // 2) 兼容原 OpenSSL/Base64 字符串解密(ASR 等存量逻辑) const opensslDecrypt = CryptoJS.AES.decrypt(normalized, key, options) const opensslText = safeUtf8(opensslDecrypt) if (opensslText) return opensslText // 3) 显式按裸 Base64 ciphertext 再试(与 Encrypt 输出对齐) try { const rawDecrypt = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Base64.parse(normalized) }, key, options, ) return safeUtf8(rawDecrypt) } catch (e) { return '' } } export function decryptByVersionNo1(versionNo1Cipher, cipherText) { if (!versionNo1Cipher || !cipherText) return '' const normalizedCipher = normalizeCipherText(cipherText) const year = new Date().getFullYear() // 兼容跨年:versionNo1 可能按加密当年的 year 生成 const yearCandidates = [year, year - 1, year - 2, year + 1] for (const y of yearCandidates) { const versionNo1 = DecryptionECB(`${y}${y}${y}${y}`, versionNo1Cipher) if (!versionNo1 || versionNo1.length < 32) continue const key = versionNo1.substring(0, 16) const iv = versionNo1.substring(16, 32) if (key.length !== 16 || iv.length !== 16) continue const plain = Decryption(key, iv, normalizedCipher) if (plain) return plain.replace(/^["']+|["']+$/g, '').trim() } return '' } /** * Base64 AES 密文 -> 明文 * 例:SjU9XqI98fNbuE5mk/... + versionNo1 => hDjN0ynT8463W0jH98-eqBask4bW_UtDRjpnMFkqD-Y */ export function decryptBase64AesByVersionNo1(versionNo1Cipher, base64CipherText) { return decryptByVersionNo1(versionNo1Cipher, base64CipherText) } /** * 解析 entryOpenid 得到明文 openId * 1) versionNo1 AES 解密 * 2) 若已是明文(非 JWT)则原样返回 */ export function normalizeEntryOpenIdPlain(text) { if (!text) return "" let trimmed = String(text).replace(/^["']+|["']+$/g, "").trim() if (!trimmed) return "" // 1) JSON:{"iss":"hDjN0ynT..."} try { const parsed = JSON.parse(trimmed) const iss = parsed?.iss ?? parsed?.openId ?? parsed?.openid ?? "" if (iss) return String(iss).trim() } catch (e) {} // 2) JWT:header.payload.sig,payload 里取 iss if (/^eyJ/i.test(trimmed) && trimmed.includes(".")) { try { const payloadPart = trimmed.split(".")[1] || "" const normalized = payloadPart.replace(/-/g, "+").replace(/_/g, "/") const padLen = (4 - (normalized.length % 4)) % 4 const padded = normalized + "=".repeat(padLen) const json = decodeURIComponent( Array.from(atob(padded), (c) => `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join(""), ) const parsed = JSON.parse(json) const iss = parsed?.iss ?? parsed?.openId ?? parsed?.openid ?? "" if (iss) return String(iss).trim() } catch (e) {} } // 3) 明文带 iss 前缀:iss + openId if (trimmed.startsWith("iss") && trimmed.length > 3) { const maybe = trimmed.slice(3).trim() if (maybe) return maybe } return trimmed } export function decodeEntryOpenid(entryOpenid, versionNo1Cipher = "") { if (entryOpenid === undefined || entryOpenid === null || entryOpenid === "") return "" const raw = String(entryOpenid).trim() if (!raw) return "" if (versionNo1Cipher) { // Base64 AES 密文解密后,再从明文中取 iss/openId const plain = decryptByVersionNo1(versionNo1Cipher, raw) const openId = normalizeEntryOpenIdPlain(plain) if (openId) return openId } // 已是明文 openId(例如 hDjN0ynT...),不要把 JWT 当明文 if (/^eyJ/i.test(raw)) return normalizeEntryOpenIdPlain(raw) if (raw.length >= 20) return raw return "" } /** * 使用 versionNo1 将明文 openId 加密为 entryOpenid */ export function encodeEntryOpenid(openId, versionNo1Cipher = "") { if (!openId || !versionNo1Cipher) return "" const year = new Date().getFullYear() const yearCandidates = [year, year - 1, year - 2, year + 1] for (const y of yearCandidates) { const versionNo1 = DecryptionECB(`${y}${y}${y}${y}`, versionNo1Cipher) if (!versionNo1 || versionNo1.length < 32) continue const key = versionNo1.substring(0, 16) const iv = versionNo1.substring(16, 32) if (key.length !== 16 || iv.length !== 16) continue return Encrypt(key, iv, String(openId)) } return "" } export function DecryptionECB(keyECB, valueECB) { if (valueECB === undefined || valueECB === null || valueECB === '') return '' const key = CryptoJS.enc.Utf8.parse(keyECB) const normalized = normalizeCipherText(valueECB) const options = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } // 1) Hex if (/^[0-9A-Fa-f]+$/.test(normalized) && normalized.length % 32 === 0) { const hexDecrypt = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Hex.parse(normalized) }, key, options, ) const hexText = safeUtf8(hexDecrypt) if (hexText) return hexText } // 2) OpenSSL/Base64 string const opensslDecrypt = CryptoJS.AES.decrypt(normalized, key, options) const opensslText = safeUtf8(opensslDecrypt) if (opensslText) return opensslText // 3) raw Base64 ciphertext try { const rawDecrypt = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Base64.parse(normalized) }, key, options, ) return safeUtf8(rawDecrypt) } catch (e) { return '' } } export function isSit() { return window.location.host.includes('sit') } export function isDev() { return window.location.host.includes('localhost') || window.location.host.includes('172.16') } export function isProd() { return window.location.host.includes('sxznjh.trace.com.cn') || window.location.host.includes('ssp.trace.com.cn') || window.location.host.includes('dev.trace.com.cn') } export function jumpOpenLink(url) { utils.AppToHttpHtml(url); } export function jumpOpenWeb(type, url, urlType) { console.log('chat-content', type, url); var params = {}; if (type === "YY_0000") { //保单管家 params = { idnum: "1", ownerId: useAppStore().userInfo.encryptPid, openid: useAppStore().userInfo.sourceUserId, }; //置换密匙 const completeURL = handleURL(url, params) Post("semantic/getYdbqTicket", { url: completeURL, userId: useAppStore().userId }).then(res => { console.log(res) if (res.code === 0) { //data空表示开关关闭 if (res.data === "" || res.data === null || res.data === undefined) { utils.openWxPage(url, params); } else { const ticketParam = { accessTicket: res.data } utils.openWxPage(url, ticketParam); } } else { Toast("网络异常,请重试") } }) } else if (type === "YY_ydbq") { //移动保全页面 params = { mobile: useAppStore().userInfo.phone, pid: useAppStore().userInfo.encryptPid, openid: useAppStore().userInfo.sourceUserId, }; //置换密匙 const completeURL = handleURL(url, params) Post("semantic/getYdbqTicket", { url: completeURL, userId: useAppStore().userId }).then(res => { console.log(res) if (res.code === 0) { //data空表示开关关闭 if (res.data === "" || res.data === null || res.data === undefined) { utils.openWxPage(url, params); } else { const ticketParam = { accessTicket: res.data } utils.openWxPage(url, ticketParam); } } else { Toast("网络异常,请重试") } }) } else if (type === "YY_APP_H5") { //在app内部页面打开h5(带参形式) params = { mobile: useAppStore().userInfo.phone, pid: useAppStore().userInfo.encryptPid, }; utils.AppOpenH5(url, params); } else if (type === "ydbq_2") { // 给付系统 params = { url: url, generateType: type, userId: useAppStore().userId, }; return utils.openSpecPage(params); } else if (type === "YY_CMD") { // 查门店 const newUrl = url + `&idnum=1&openId=${useAppStore().userInfo.sourceUserId}&entryOpenid=${useAppStore().userInfo.entryOpenid}` //置换密匙 Post("semantic/getYdbqTicket", { url: newUrl, userId: useAppStore().userId, isCmd: "1" }).then(res => { console.log(res) if (res.code === 0) { //data空表示开关关闭 if (res.data === "" || res.data === null || res.data === undefined) { return jumpOpenLink(newUrl); } else { const ticketUrl = url + `&accessTicket=${res.data}` return jumpOpenLink(ticketUrl); } } else { Toast("网络异常,请重试") } }) } else if (type === "YY_lsjl") { // 跳转历史记录 const openid = utils.getUrlParams().openid router.push({ name: "history", query: { ht: useAppStore().source, openid: openid, tokenExist: Boolean(useAppStore().token), isWX: utils.isWeiXin(), lostChatInfoNum: useAppStore().isLostMessage || 0, param: utils.getUrlParams().param }, }); return } else if (type === "YY_kjmyd") { utils.useTechSdk() return } else if (type === "YY_hfhz") { // 回访回执 const param = { certNum: "1", systemSource: "yykf", userId: useAppStore().userId } Post("semantic/xdhfCreateToken", param).then(res => { const entryptedId = useAppStore().userInfo.versionHFHZ params = { sessionToken: res.data, holderId: encodeURI(entryptedId), holderType: 'a', hzhfSystemSource: '0803', ishwFlag: '1', isqshzFlag: '1', } utils.openWxPage(url, params); }) } else if (type === "YY_1v1") { // 1v1专属服务 post("thirdPartyAuth/getExclusiveServicesToken").then(res => { if (res.code === 0) { if (res.data) { params = { sour: 'yykf', omoSource: useAppStore().userInfo.source, omoSourceName: encodeURIComponent(useAppStore().userInfo.sourceName), token: res.data } utils.openWxPage(url, params); } } else { Toast("网络异常,请重试") } }) } else if (type === "YY_yzsfx") { // 一站式复效 Post("semantic/getYzsUrl", { userId: useAppStore().userId, bqType: url, urlType }).then(res => { if (res.code === 0) { if (res.data) { let yUrl = res.data utils.openWxPage(yUrl, ''); } } else { Toast("网络异常,请重试") } }) } else if (type === "YY_GWXCX") { //官微小程序 const path = url; wx.miniProgram.navigateTo({ url: path }); } else if (type === "YY_GWXCX_HOME") { //官微小程序首页 const path = url; console.log('path', path, wx.miniProgram.reLaunch) wx.miniProgram.reLaunch({ url: path }); } } export function handleURL(url, params) { let flag = url.indexOf("?") != -1 ? true : false let urlPar = ''; if (params) { Object.getOwnPropertyNames(params).forEach((item, index) => { if (index === 0 && !flag) { urlPar += `?${item}=${params[item]}`; } else { urlPar += `&${item}=${params[item]}` } }); } return url + urlPar }