feat: 优化响应式布局并添加签到组件

refactor: 重构CSS和LESS文件以支持响应式设计
fix: 修复图片上传和编辑器解析问题
style: 调整搜索框和日历组件的样式
docs: 更新HTML模板中的广告和操作链接
This commit is contained in:
DESKTOP-RQ919RC\Pc
2025-12-04 19:15:08 +08:00
parent e4f97dafb8
commit 40d83d5374
10 changed files with 775 additions and 237 deletions

View File

@@ -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({
"&quot;": '"',
"&apos;": "'",
};
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;