jbxl-workflow
Version:
流程图
259 lines (242 loc) • 8.68 kB
JavaScript
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(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/ /g, ' ')
}
// HTML 实体编码
export const encodeHTMLEntities = (str) => {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/ /g, ' ');
};
/**
* @param htmlString {string} 传入的html字符串, escapeHtml处理过的
* @param replacements {Record<string, string>}替换的对象,key为id,value为替换的值
* */
export function replaceValuesByIds(htmlString, replacements) {
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. 替换所有 为空格
let result = decodedStr.replace(/ /g, ' ');
// 2. 提取 data-value 替换整个标签
result = result.replace(/<span[^>]*data-id="([^"]*)"[^>]*>.*?<\/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, ''); // 解析失败时返回原始字符串,避免程序崩溃
}
};