新增 CLAUDE.md 文件以提供项目指导,添加 .claudeignore 文件以排除不必要的文件,更新 pom.xml 版本至 3.9.2,修复多个路径遍历和 SQL 注入漏洞,优化字典翻译切面逻辑,增强文件上传和下载的安全性,新增音频文件类型支持,改进动态数据源的安全校验。
This commit is contained in:
10
jeecgboot-vue3/src/utils/dynamicPages.ts
Normal file
10
jeecgboot-vue3/src/utils/dynamicPages.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// 获取所有动态页面(views目录下的所有vue文件和tsx文件)
|
||||
const allFiles = import.meta.glob(
|
||||
[
|
||||
'../views/**/*.{vue,tsx}',// 获取所有vue和tsx文件
|
||||
// 排除特定文件夹
|
||||
'!../views/system/approvalrole/compoments/**',
|
||||
]
|
||||
);
|
||||
// 合并所有动态页面
|
||||
export const dynamicPages = { ...allFiles };
|
||||
@@ -3,8 +3,7 @@ import {
|
||||
// FunctionalComponent, CSSProperties
|
||||
} from 'vue';
|
||||
import { Spin } from 'ant-design-vue';
|
||||
import { noop } from '/@/utils/index';
|
||||
|
||||
const noop = () => {};
|
||||
// const Loading: FunctionalComponent<{ size: 'small' | 'default' | 'large' }> = (props) => {
|
||||
// const style: CSSProperties = {
|
||||
// position: 'absolute',
|
||||
|
||||
4
jeecgboot-vue3/src/utils/iconfont2.ts
Normal file
4
jeecgboot-vue3/src/utils/iconfont2.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createFromIconfontCN } from '@ant-design/icons-vue';
|
||||
import '/@/assets/icons/js/iconfont2.js';
|
||||
|
||||
export const IconFont = createFromIconfontCN({});
|
||||
@@ -4,6 +4,7 @@ import type { FormSchema, FormActionType } from "@/components/Form";
|
||||
|
||||
import { unref } from 'vue';
|
||||
import { isObject, isFunction, isString } from '/@/utils/is';
|
||||
import { dynamicPages } from './dynamicPages';
|
||||
import Big from 'big.js';
|
||||
import dayjs from "dayjs";
|
||||
// 代码逻辑说明: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
|
||||
@@ -348,7 +349,6 @@ export function numToUpper(value) {
|
||||
}
|
||||
|
||||
// 代码逻辑说明: 解决老的vue2动态导入文件语法 vite不支持的问题
|
||||
const allModules = import.meta.glob('../views/**/*.vue');
|
||||
export function importViewsFile(path): Promise<any> {
|
||||
if (path.startsWith('/')) {
|
||||
path = path.substring(1);
|
||||
@@ -361,10 +361,10 @@ export function importViewsFile(path): Promise<any> {
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let flag = true;
|
||||
for (const path in allModules) {
|
||||
for (const path in dynamicPages) {
|
||||
if (path == page) {
|
||||
flag = false;
|
||||
allModules[path]().then((mod) => {
|
||||
dynamicPages[path]().then((mod) => {
|
||||
console.log(path, mod);
|
||||
resolve(mod);
|
||||
});
|
||||
@@ -669,3 +669,171 @@ export const split = (str) => {
|
||||
}
|
||||
return str;
|
||||
};
|
||||
/**
|
||||
* 处理word文档中的o:p标签
|
||||
* @param html
|
||||
*/
|
||||
export const removeSpecialTags = (html: string): string => {
|
||||
if (!html) return '';
|
||||
try {
|
||||
const BORDER = '1px solid #8c8c8c';
|
||||
|
||||
// ===================================================================
|
||||
// 第一步:移除 Office 垃圾标签(纯字符串)
|
||||
// ===================================================================
|
||||
// o:p 标签(转义和普通形式)
|
||||
html = html.replace(/<o:p[^&]*?>.*?<\/o:p>/gis, '');
|
||||
html = html.replace(/<o:p[^&]*?\/?>/gis, '');
|
||||
html = html.replace(/<\/o:p>/gis, '');
|
||||
html = html.replace(/<o:p[^>]*>.*?<\/o:p>/gis, '');
|
||||
html = html.replace(/<o:p[^>]*\/?>/gis, '');
|
||||
html = html.replace(/<\/o:p>/gis, '');
|
||||
|
||||
// style 标签(转义和普通形式)
|
||||
html = html.replace(/<style[^&]*?>.*?<\/style>/gis, '');
|
||||
html = html.replace(/<style[^&]*?\/?>/gis, '');
|
||||
html = html.replace(/<\/style>/gis, '');
|
||||
html = html.replace(/<style[^>]*>.*?<\/style>/gis, '');
|
||||
|
||||
// 条件注释和 Office 命名空间标签
|
||||
html = html.replace(/<!--\[if[^>]*>.*?<!\[endif\]-->/gis, '');
|
||||
html = html.replace(/<\/?w:[^>]*>/gis, '');
|
||||
html = html.replace(/<\/?xml[^>]*>/gis, '');
|
||||
html = html.replace(/<\/?v:[^>]*>/gis, '');
|
||||
|
||||
// ===================================================================
|
||||
// 第二步:DOM 清理(仅清理空段落,完全不动表格行结构)
|
||||
// ★ 关键:放在边框处理之前,防止 DOM 序列化丢失 !important
|
||||
// ===================================================================
|
||||
try {
|
||||
const DOMParserCtor: any =
|
||||
typeof DOMParser !== 'undefined' ? DOMParser : (globalThis as any).DOMParser;
|
||||
if (DOMParserCtor) {
|
||||
const parser = new DOMParserCtor() as DOMParser;
|
||||
const doc = parser.parseFromString(html, 'text/html') as Document;
|
||||
|
||||
// 仅清理单元格内的空 <p> 标签
|
||||
const cells = doc.querySelectorAll('td,th');
|
||||
for (let ci = 0; ci < cells.length; ci++) {
|
||||
const cell = cells[ci] as HTMLElement;
|
||||
if (!cell) continue;
|
||||
const ps = cell.querySelectorAll('p');
|
||||
for (let pi = ps.length - 1; pi >= 0; pi--) {
|
||||
const p = ps[pi] as HTMLParagraphElement;
|
||||
if (!p) continue;
|
||||
const content = (p.innerHTML || '').replace(/ |\u00A0/g, '').trim();
|
||||
if (content === '') {
|
||||
p.remove();
|
||||
} else {
|
||||
const frag = doc.createDocumentFragment();
|
||||
while (p.firstChild) frag.appendChild(p.firstChild);
|
||||
if (p.parentNode) p.parentNode.replaceChild(frag, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html = doc.body.innerHTML;
|
||||
}
|
||||
} catch (_) {
|
||||
// DOM 不可用,跳过段落清理(不影响边框修复)
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 第三步:注入全局表格样式 <style>(兜底保障)
|
||||
// ★ 放在 DOM 序列化之后,确保 <style> 标签不会被 DOM 改写
|
||||
// ===================================================================
|
||||
const globalCSS = `<style>
|
||||
table{border-collapse:collapse!important;border-spacing:0!important;width:100%!important;max-width:100%!important;}
|
||||
table td,table th{border-top:${BORDER}!important;border-right:${BORDER}!important;border-bottom:${BORDER}!important;border-left:${BORDER}!important;padding:6px 8px!important;vertical-align:middle!important;box-sizing:border-box!important;empty-cells:show!important;}
|
||||
table td p,table th p{margin:0!important;padding:0!important;line-height:1.4!important;}
|
||||
img{max-width:100%!important;height:auto!important;display:block!important;}
|
||||
</style>`;
|
||||
|
||||
if (/<head[^>]*>/i.test(html)) {
|
||||
html = html.replace(/(<head[^>]*>)/i, `$1${globalCSS}`);
|
||||
} else if (/<html[^>]*>/i.test(html)) {
|
||||
html = html.replace(/(<html[^>]*>)/i, `$1<head>${globalCSS}</head>`);
|
||||
} else {
|
||||
html = globalCSS + html;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 第四步:纯字符串处理内联样式(核心边框修复)
|
||||
// ★ 最后执行!确保输出 HTML 中的 !important 不会被任何后续处理丢失
|
||||
//
|
||||
// 策略:按分号拆分 style 值,逐条判断属性名,
|
||||
// 过滤掉所有 border* 和 mso-* 声明,
|
||||
// 然后追加统一的边框声明(含 !important)
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* 从 CSS style 字符串中移除所有 border* 和 mso-* 声明,
|
||||
* 保留其余布局相关属性(width, height, padding, text-align 等)
|
||||
*/
|
||||
function stripBorderAndMso(styleStr: string): string {
|
||||
return styleStr
|
||||
.split(';')
|
||||
.filter(function (decl) {
|
||||
const trimmed = decl.trim();
|
||||
if (!trimmed) return false;
|
||||
const colonIdx = trimmed.indexOf(':');
|
||||
if (colonIdx === -1) return true; // 保留无效声明(安全起见)
|
||||
const prop = trimmed.substring(0, colonIdx).trim().toLowerCase();
|
||||
// 移除所有 border 开头和 mso- 开头的属性
|
||||
if (prop.startsWith('border') || prop.startsWith('mso-')) return false;
|
||||
return true;
|
||||
})
|
||||
.join(';');
|
||||
}
|
||||
|
||||
// 4a. 移除 <table> 上的 border="0" HTML 属性
|
||||
html = html.replace(/(<table\b[^>]*?)\sborder\s*=\s*['"]?0['"]?/gi, '$1');
|
||||
|
||||
// 4b. 处理 <table> 标签的内联样式
|
||||
html = html.replace(/<table(\s[^>]*)>/gi, function (fullMatch, attrs) {
|
||||
try {
|
||||
const sm = attrs.match(/\sstyle\s*=\s*(["'])([\s\S]*?)\1/i);
|
||||
if (sm) {
|
||||
const q = sm[1];
|
||||
const cleaned = stripBorderAndMso(sm[2]);
|
||||
const newStyle = cleaned
|
||||
+ ';border-collapse:collapse!important'
|
||||
+ ';border-spacing:0!important'
|
||||
+ ';width:100%!important';
|
||||
return '<table' + attrs.replace(sm[0], ` style=${q}${newStyle}${q}`) + '>';
|
||||
}
|
||||
return '<table' + attrs
|
||||
+ ' style="border-collapse:collapse!important;border-spacing:0!important;width:100%!important">';
|
||||
} catch (_) { return fullMatch; }
|
||||
});
|
||||
|
||||
// 4c. 处理 <td>/<th> 标签的内联样式
|
||||
const cellBorderCSS =
|
||||
`border-top:${BORDER}!important;` +
|
||||
`border-right:${BORDER}!important;` +
|
||||
`border-bottom:${BORDER}!important;` +
|
||||
`border-left:${BORDER}!important;` +
|
||||
'padding:6px 8px!important;' +
|
||||
'vertical-align:middle!important;' +
|
||||
'box-sizing:border-box!important';
|
||||
|
||||
html = html.replace(/<(td|th)(\s[^>]*)>/gi, function (fullMatch, tag, attrs) {
|
||||
try {
|
||||
const sm = attrs.match(/\sstyle\s*=\s*(["'])([\s\S]*?)\1/i);
|
||||
if (sm) {
|
||||
const q = sm[1];
|
||||
const cleaned = stripBorderAndMso(sm[2]);
|
||||
const newStyle = (cleaned ? cleaned + ';' : '') + cellBorderCSS;
|
||||
return '<' + tag + attrs.replace(sm[0], ` style=${q}${newStyle}${q}`) + '>';
|
||||
}
|
||||
return '<' + tag + attrs + ` style="${cellBorderCSS}">`;
|
||||
} catch (_) { return fullMatch; }
|
||||
});
|
||||
|
||||
return html;
|
||||
} catch (_) {
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { App } from 'vue';
|
||||
import { warn } from '/@/utils/log';
|
||||
import { registerDynamicRouter } from '/@/utils/monorepo/dynamicRouter';
|
||||
import { add } from '/@/components/Form/src/componentMap';
|
||||
|
||||
// 懒加载模块配置(按需加载,访问相关路由时才加载对应包)
|
||||
const lazyPackages = [
|
||||
{ name: '@jeecg/online', importer: () => import('@jeecg/online') },
|
||||
{ name: '@jeecg/aiflow', importer: () => import('@jeecg/aiflow') },
|
||||
];
|
||||
|
||||
@@ -19,6 +17,14 @@ const installOptions = {
|
||||
export function registerPackages(app: App) {
|
||||
// 仅保存 app 实例,不立即加载模块
|
||||
appInstance = app;
|
||||
// app.component(
|
||||
// 'SuperQuery',
|
||||
// createAsyncComponent(() => import('@jeecg/online').then(mod => mod.SuperQuery))
|
||||
// );
|
||||
// app.component(
|
||||
// 'JOnlineSearchSelect',
|
||||
// createAsyncComponent(() => import('@jeecg/online').then(mod => mod.JOnlineSearchSelect))
|
||||
// );
|
||||
}
|
||||
|
||||
/** 已加载的包缓存 */
|
||||
|
||||
Reference in New Issue
Block a user