feat: 优化响应式布局并添加签到组件
refactor: 重构CSS和LESS文件以支持响应式设计 fix: 修复图片上传和编辑器解析问题 style: 调整搜索框和日历组件的样式 docs: 更新HTML模板中的广告和操作链接
This commit is contained in:
148
js/edit.js
148
js/edit.js
@@ -65,7 +65,6 @@ const editApp = createApp({
|
||||
|
||||
isLogin.value = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let imageLength = 10;
|
||||
@@ -173,7 +172,6 @@ const editApp = createApp({
|
||||
info.value = infoTarget;
|
||||
token.value = data.token;
|
||||
|
||||
|
||||
initEditor();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -211,6 +209,7 @@ const editApp = createApp({
|
||||
onInsertedImage(imageNode) {
|
||||
const { src, alt, url, href } = imageNode;
|
||||
},
|
||||
|
||||
async parseImageSrc(src) {
|
||||
// 如果图片链接中已经包含了 ?aid= ,则说明是本站图片,直接返回,无需处理
|
||||
if (src.includes("?aid=")) return src;
|
||||
@@ -233,8 +232,15 @@ const editApp = createApp({
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Transform network image failed", e);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
checkImage: (src, alt, url) => {
|
||||
if (src.indexOf("http") !== 0) {
|
||||
return "图片网址必须以 http/https 开头";
|
||||
}
|
||||
return true;
|
||||
}, // 也支持 async 函数
|
||||
},
|
||||
|
||||
["uploadImage"]: {
|
||||
@@ -251,6 +257,8 @@ const editApp = createApp({
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
const img = await uploading(file, file.name, "image");
|
||||
console.log("img", img);
|
||||
|
||||
insertFn(`${img.url}?aid=${img.aid}`);
|
||||
} catch (err) {
|
||||
console.error("上传出错:", err);
|
||||
@@ -261,7 +269,6 @@ const editApp = createApp({
|
||||
["insertVideo"]: {
|
||||
onInsertedVideo(videoNode) {
|
||||
if (videoNode == null) return;
|
||||
// console.log(videoNode);
|
||||
|
||||
const { src } = videoNode;
|
||||
// console.log("inserted video", src);
|
||||
@@ -423,6 +430,7 @@ const editApp = createApp({
|
||||
|
||||
// 定义图片处理函数
|
||||
const processImg = (aid, width, height, href) => {
|
||||
console.log("processImg", aid, width, height, href);
|
||||
const image = imageList.find((img) => String(img.aid) === String(aid));
|
||||
if (!image) return "";
|
||||
|
||||
@@ -433,7 +441,7 @@ const editApp = createApp({
|
||||
let style = "";
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith('%')) return val;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
@@ -445,7 +453,7 @@ const editApp = createApp({
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
|
||||
if (styleParts.length > 0) style = `style="${styleParts.join('; ')};"`;
|
||||
if (styleParts.length > 0) style = `style="${styleParts.join("; ")};"`;
|
||||
|
||||
let dataHref = href ? ` data-href="${href}"` : "";
|
||||
return `<img src="${image.url}?aid=${aid}" data-aid="${aid}"${dataHref} ${style}>`;
|
||||
@@ -464,6 +472,41 @@ const editApp = createApp({
|
||||
return imgTag || match;
|
||||
});
|
||||
|
||||
html = html.replace(/\[url=([^\]]+)\]\[img(?:=([0-9.%]+)(?:,([0-9.%]+))?)?\]([^\]]+)\[\/img\]\[\/url\]/gi, (match, href, w, h, inner) => {
|
||||
if (/^\d+$/.test(inner)) return match;
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
const wStyle = formatStyleVal(w);
|
||||
const hStyle = formatStyleVal(h);
|
||||
let styleParts = [];
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
const styleAttr = styleParts.length > 0 ? ` style="${styleParts.join("; ")};"` : "";
|
||||
const dataHref = href ? ` data-href="${href}"` : "";
|
||||
return `<img src="${inner}"${dataHref}${styleAttr}>`;
|
||||
});
|
||||
|
||||
html = html.replace(/\[img(?:=([0-9.%]+)(?:,([0-9.%]+))?)?\]([^\]]+)\[\/img\]/gi, (match, w, h, inner) => {
|
||||
if (/^\d+$/.test(inner)) return match;
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
const wStyle = formatStyleVal(w);
|
||||
const hStyle = formatStyleVal(h);
|
||||
let styleParts = [];
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
const styleAttr = styleParts.length > 0 ? ` style="${styleParts.join("; ")};"` : "";
|
||||
return `<img src="${inner}"${styleAttr}>`;
|
||||
});
|
||||
|
||||
// 6. [attachimg] (兼容旧格式)
|
||||
html = html.replace(/\[attachimg\](\d+)\[\/attachimg\]/gi, (match, aid) => {
|
||||
const imgTag = processImg(aid, 0, 0, null);
|
||||
@@ -507,16 +550,16 @@ const editApp = createApp({
|
||||
__c.innerHTML = html;
|
||||
|
||||
// 确保所有顶层元素都是块级元素 (Slate/WangEditor 要求)
|
||||
const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'UL', 'OL', 'PRE', 'TABLE', 'FIGURE', 'HR'];
|
||||
const newContainer = document.createElement('div');
|
||||
const blockTags = ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "BLOCKQUOTE", "UL", "OL", "PRE", "TABLE", "FIGURE", "HR"];
|
||||
const newContainer = document.createElement("div");
|
||||
let currentInlineNodes = [];
|
||||
let lastWasBlock = false;
|
||||
|
||||
const flushInlines = () => {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
const p = document.createElement('p');
|
||||
currentInlineNodes.forEach(node => p.appendChild(node));
|
||||
const p = document.createElement("p");
|
||||
currentInlineNodes.forEach((node) => p.appendChild(node));
|
||||
newContainer.appendChild(p);
|
||||
}
|
||||
currentInlineNodes = [];
|
||||
@@ -532,7 +575,7 @@ const editApp = createApp({
|
||||
newContainer.appendChild(node);
|
||||
// 记录最后添加的是块级元素
|
||||
lastWasBlock = true;
|
||||
} else if (node.nodeName === 'BR') {
|
||||
} else if (node.nodeName === "BR") {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
flushInlines();
|
||||
lastWasBlock = false; // 刚刚结束了一个段落,不算紧挨着块级
|
||||
@@ -542,8 +585,8 @@ const editApp = createApp({
|
||||
// 忽略
|
||||
lastWasBlock = false; // 消耗掉块级后的换行状态,避免连续 BR 被吞
|
||||
} else {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = '<br>';
|
||||
const p = document.createElement("p");
|
||||
p.innerHTML = "<br>";
|
||||
newContainer.appendChild(p);
|
||||
lastWasBlock = false;
|
||||
}
|
||||
@@ -585,7 +628,7 @@ const editApp = createApp({
|
||||
|
||||
// 提交
|
||||
const submit = (status) => {
|
||||
if (location.hostname == "127.0.0.1") status = 0
|
||||
if (location.hostname == "127.0.0.1") status = 0;
|
||||
|
||||
if (realname.value == 0 && userInfoWin.value?.uin > 0) {
|
||||
openAttest();
|
||||
@@ -600,7 +643,12 @@ const editApp = createApp({
|
||||
const infoTarget = { ...info.value } || {};
|
||||
let content = editor.getHtml();
|
||||
|
||||
const obj = formatContent(content);
|
||||
|
||||
content = obj.content;
|
||||
|
||||
const images = extractImages(editorRef.value);
|
||||
// const images = obj.images;
|
||||
const videos = extractVideos(editorRef.value);
|
||||
|
||||
infoTarget.attachments = infoTarget.attachments || {};
|
||||
@@ -611,16 +659,11 @@ const editApp = createApp({
|
||||
info.value["attachments"]["images"] = images;
|
||||
info.value["attachments"]["videos"] = videos;
|
||||
|
||||
// console.log("原始html", content);
|
||||
content = formatContent(content);
|
||||
// console.log("最终html", content);
|
||||
const data = {
|
||||
...infoTarget,
|
||||
content,
|
||||
};
|
||||
|
||||
// console.log("data", data);
|
||||
|
||||
ajax("/v2/api/forum/postPublishTopic", {
|
||||
info: data,
|
||||
token: token.value,
|
||||
@@ -643,6 +686,8 @@ const editApp = createApp({
|
||||
};
|
||||
|
||||
const formatContent = (html) => {
|
||||
let images = [];
|
||||
|
||||
if (!html) return "";
|
||||
// <p><br></p> 转换为单个换行符
|
||||
html = html.replace(/<p><br><\/p>/gi, "\n");
|
||||
@@ -671,63 +716,65 @@ const editApp = createApp({
|
||||
// 4. 处理图片 [img]
|
||||
html = html.replace(/<img[^>]*>/gi, (imgTag) => {
|
||||
let aid = "";
|
||||
// 尝试从 src 中获取 aid
|
||||
const srcMatch = imgTag.match(/src="([^"]+)"/i);
|
||||
if (srcMatch) {
|
||||
const aidMatch = srcMatch[1].match(/[?&]aid=(\d+)/);
|
||||
if (aidMatch) aid = aidMatch[1];
|
||||
}
|
||||
// 尝试从 data-aid 中获取 aid
|
||||
if (!aid) {
|
||||
const dataAid = imgTag.match(/data-aid="(\d+)"/i);
|
||||
if (dataAid) aid = dataAid[1];
|
||||
}
|
||||
if (!srcMatch) return "";
|
||||
const srcUrl = srcMatch[1];
|
||||
const cleanUrl = srcUrl.split("?")[0];
|
||||
const aidMatch = srcUrl.match(/[?&]aid=(\d+)/);
|
||||
if (aidMatch) aid = aidMatch[1];
|
||||
|
||||
if (!aid) return ""; // 无法获取 aid,跳过
|
||||
|
||||
// 获取宽高 (支持 px 和 %)
|
||||
let w = 0,
|
||||
h = 0;
|
||||
const styleMatch = imgTag.match(/style="([^"]+)"/i);
|
||||
if (styleMatch) {
|
||||
// 匹配数字+单位 (px或%)
|
||||
const wMatch = styleMatch[1].match(/width:\s*([\d.]+(?:px|%)?)/i);
|
||||
const hMatch = styleMatch[1].match(/height:\s*([\d.]+(?:px|%)?)/i);
|
||||
|
||||
if (wMatch) {
|
||||
// 如果是百分比,直接保留字符串;如果是纯数字默认视为 px;如果是 px 去掉单位
|
||||
let val = wMatch[1];
|
||||
if (val.endsWith('%')) w = val; // 保留百分比字符串
|
||||
else w = parseFloat(val); // 转为数字 (px)
|
||||
if (val.endsWith("%")) w = val;
|
||||
else w = parseFloat(val);
|
||||
}
|
||||
if (hMatch) {
|
||||
let val = hMatch[1];
|
||||
if (val.endsWith('%')) h = val;
|
||||
if (val.endsWith("%")) h = val;
|
||||
else h = parseFloat(val);
|
||||
}
|
||||
}
|
||||
// 兼容 width/height 属性 (通常只有数字)
|
||||
if (!w) {
|
||||
const wAttr = imgTag.match(/\swidth="(\d+)"/i);
|
||||
if (wAttr) w = Number(wAttr[1]);
|
||||
}
|
||||
|
||||
if (!h) {
|
||||
const hAttr = imgTag.match(/\sheight="(\d+)"/i);
|
||||
if (hAttr) h = Number(hAttr[1]);
|
||||
}
|
||||
|
||||
const inner = aid ? aid : cleanUrl;
|
||||
let result = "";
|
||||
if (w || h) { // 只要有一个有值
|
||||
if (w || h) {
|
||||
const formatVal = (val) => {
|
||||
if (typeof val === 'string' && val.endsWith('%')) return val;
|
||||
if (typeof val === "string" && val.endsWith("%")) return val;
|
||||
return val ? parseFloat(Number(val).toFixed(2)) : 0;
|
||||
};
|
||||
result = `[img=${formatVal(w)},${formatVal(h)}]${aid}[/img]`;
|
||||
result = `[img=${formatVal(w)},${formatVal(h)}]${inner}[/img]`;
|
||||
|
||||
if (aid) {
|
||||
images.push({ url: cleanUrl, aid: Number(aid) });
|
||||
} else {
|
||||
images.push({ url: cleanUrl });
|
||||
}
|
||||
} else {
|
||||
result = `[img]${aid}[/img]`;
|
||||
result = `[img]${inner}[/img]`;
|
||||
|
||||
if (aid) {
|
||||
images.push({ url: cleanUrl, aid: Number(aid) });
|
||||
} else {
|
||||
images.push({ url: cleanUrl });
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 data-href,包裹在 [url] 中
|
||||
const hrefMatch = imgTag.match(/data-href="([^"]+)"/i);
|
||||
if (hrefMatch && hrefMatch[1]) result = `[url=${hrefMatch[1]}]${result}[/url]`;
|
||||
|
||||
@@ -785,9 +832,13 @@ const editApp = createApp({
|
||||
""": '"',
|
||||
"'": "'",
|
||||
};
|
||||
|
||||
html = html.replace(/&[a-z]+;/gi, (match) => entities[match] || match);
|
||||
|
||||
return html.trim();
|
||||
return {
|
||||
content: html.trim(),
|
||||
images,
|
||||
};
|
||||
};
|
||||
|
||||
const extractImages = (dom) => {
|
||||
@@ -802,11 +853,12 @@ const editApp = createApp({
|
||||
const aid = urlObj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
if (Number(aid)) {
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return images;
|
||||
|
||||
Reference in New Issue
Block a user