UNPKG

jbxl-workflow

Version:

流程图

269 lines (251 loc) 9.08 kB
import DOMPurify from 'dompurify'; import {ElMessage} from "element-plus"; import {computed, h, ref} from "vue"; export function deepClone(obj) { // 处理基本类型和 null if (obj === null || typeof obj !== 'object') { return obj; } // 处理数组 if (Array.isArray(obj)) { const newArray = []; for (let i = 0; i < obj.length; i++) { newArray[i] = deepClone(obj[i]); } return newArray; } // 处理对象 const newObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = deepClone(obj[key]); } } return newObj; } export const throttle = (fn, delay) => { let timer = null; let lastTime = 0; return function (...args) { const now = Date.now(); if (now - lastTime >= delay) { fn.apply(this, args); lastTime = now; } else if (!timer) { timer = setTimeout(() => { fn.apply(this, args); lastTime = now; timer = null; }, delay); } }; }; export const debounce = (fn, delay) => { let timer = null; return function (...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; }; export const isPropertyReadonly = (obj, propName) => { // 支持 Symbol 属性名 const prop = typeof propName === 'symbol' ? propName : String(propName); let proto = obj; do { // 获取当前原型的属性描述符(含 Symbol) const descriptors = Object.getOwnPropertyDescriptors(proto); const descriptor = descriptors[prop]; if (descriptor) { return descriptor.writable === false || descriptor.set === undefined; } proto = Object.getPrototypeOf(proto); } while (proto); return false; // 属性不存在 } export const decodeHTMLEntities = (str) => { if (!str) return ''; // 先用 DOMPurify 处理以确保安全 const sanitized = DOMPurify.sanitize(str, { ALLOWED_TAGS: ['span'], ALLOWED_ATTR: ['class', 'data-id', 'data-value', 'contenteditable'], KEEP_CONTENT: true }); // 使用 DOMParser 解析 HTML const parser = new DOMParser(); const doc = parser.parseFromString(sanitized, 'text/html'); return doc.body.innerHTML; } export const escapeHtml = (str) => { if (!str) return ''; return DOMPurify.sanitize(str, { ALLOWED_TAGS: ['span'], // 允许 span 标签 ALLOWED_ATTR: ['class', 'data-id', 'data-value', 'contenteditable'], // 允许需要的属性 KEEP_CONTENT: true }); } // 检查是否已编码的 HTML export const isHTMLEncoded = (str) => { if (!str) return false; // 检查是否存在未编码的特殊字符 const unEncodedPattern = /[<>"&]/g; const matches = str.match(unEncodedPattern); // 如果没有未编码的特殊字符,说明已经完全编码 return !matches; }; export const decodedString = (htmlString) => { if (!htmlString) return return htmlString.replace(/&amp;/g, '&') .replace(/&quot;/g, '"') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&nbsp;/g, ' ') } // HTML 实体编码 export const encodeHTMLEntities = (str) => { if (!str) return ''; return str .replace(/&/g, '&amp;') .replace(/"/g, '&quot;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/ /g, '&nbsp;'); }; /** * @param htmlString {string} 传入的html字符串, escapeHtml处理过的 * @param replacements {Record<string, string>}替换的对象,key为id,value为替换的值 * @param flag {boolean} 是否使用正则匹配 htmlString 字符串来替换标签的内容 * */ export function replaceValuesByIds(htmlString, replacements, flag = false) { if (flag) { let result = htmlString Object.entries(replacements).forEach(([id, newValue]) => { const reg = new RegExp(`<span[^>]*data-id="${id}"[^>]*>.*?<\\/span>`) result = result.replace(reg, newValue) }) return result } let doc = new DOMParser().parseFromString(decodedString(htmlString), 'text/html') Object.entries(replacements).forEach(([id, newValue]) => { // 查找所有匹配的元素(因为可能有重复的 data-id) const elements = doc.querySelectorAll(`[data-id="${id}"]`) elements.forEach(element => { element.setAttribute('data-value', newValue) element.textContent = newValue }) }) return doc.body.innerHTML } export const processString = (str, flag = false) =>{ const decodedStr = !flag ? decodedString(str) : str; // 1. 替换所有 &nbsp; 为空格 let result = decodedStr.replace(/&nbsp;/g, ' '); // 2. 提取 data-value 替换整个标签 result = result.replace(/<span[^>]*data-value="([^"]*)"[^>]*>.*?<\/span>/g, '$1'); // 3. 合并连续空格并去除首尾空格 return result.replace(/\s+/g, ' ').trim(); } // 判断是否为图片链接 export function isImageUrl(url) { return /\.(jpg|jpeg|png|gif|bmp)$/i.test(url); } // 移除缩进 export const removeIndent = (str) => { return str.split('\n').map(line => line.trimStart()).join('\n'); } // runDebug运行结果 export const openVn = (() => { let messageInstance = null const action = ref('open') // const ActionTextEnum = Object.freeze({ // open: '展开', // close: '收起' // }) return ({res, time, tokenNum}) => { const successBg = '#DFF4EE' const runningBg = '#EBE5FF' const errorBg = '#FEECEF' const successBorder = '#08A57B' const runningBorder = 'var(--theme-color)' const errBorder = '#FA4A65' // console.log('rrr', res) // 背景颜色 const StatusEnum = Object.freeze({ success: successBg, running: runningBg, error: errorBg }) // 边框 const StatusBorder = Object.freeze({ success: successBorder, running: runningBorder, error: errBorder }) // 状态文本 const StatusText = Object.freeze({ success: '运行成功', running: '运行中', error: '运行失败' }) // const clickFunc = () => { // const nodePool = globalThis._graph?.nodePool // const {nodes} = nodePool || {} // nodes.forEach((node) => { // if (node?.runDebugStatus) { // node?.vueInstance?.exposed?.setShowParamsResult(action.value === 'open') // } // }) // action.value = action.value === 'open' ? 'close' : 'open' // // // 关闭旧消息并创建新消息 // if (messageInstance) { // messageInstance?.close?.() // } // messageInstance = createMessage() // } const createMessage = () => { return ElMessage({ icon: null, type: '', plain: true, duration: 0, customClass: 'full-width-message', center: true, message: h('div', { style: `font-size: 14px;width: 100%;display: flex;color: #666666;padding: 6px 13px;background:${StatusEnum[res]};align-items: center;border: 1px solid ${StatusBorder[res]};margin-top: 0 !important;`, class: `run-result-wrap ${res}` }, [ h('div', {class: `status-icon`, style: `width: 14px;height: 14px;`}, []), h('div', { style: `font-size: 14px;` }, [ h('span', {style: `margin:0 8px;`}, `${StatusText[res]}`), h('span', {style: `margin-right: 8px;`}, `耗时${time},`), h('span', {style: `margin-right: 8px;`}, `${tokenNum} Tokens`), // h('span', { // style: 'color: var(--theme-color);cursor: pointer;', // onClick: clickFunc // }, `${ActionTextEnum[action.value]}全部结果`) ]), h('span', {}, []) ]) }) } // 初始创建消息 if (messageInstance) { messageInstance?.close?.() } messageInstance = createMessage() return messageInstance } })() export const safeParseJSON = (params) => { if (typeof params !== 'string') return params; // 如果不是字符串,直接返回原始值 try { return JSON.parse(params); } catch (error) { return params && params.replace(/\n/g, ''); // 解析失败时返回原始字符串,避免程序崩溃 } };