feat(editor): 添加预加载动画和编辑器功能优化
- 新增预加载动画组件及样式 - 优化编辑器图片和视频上传处理逻辑 - 修复编辑器内容转换和格式处理问题 - 添加上传进度显示功能 - 改进编辑器工具栏图标和布局
This commit is contained in:
33
css/edit.css
33
css/edit.css
@@ -9,6 +9,7 @@
|
||||
height: 60px;
|
||||
background: linear-gradient(180deg, #ffffff -41%, #eef8f9 96%);
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
#edit .edit-head .edit-head-container {
|
||||
width: 1200px;
|
||||
@@ -43,6 +44,15 @@
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#edit .edit-head .progress-box {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
height: 5px;
|
||||
width: 2px;
|
||||
background: linear-gradient(315deg, #6772ff 0px, #00f9e5 100%) center center / 104% 104% #4a54ff;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
#edit .edit-container {
|
||||
width: 890px;
|
||||
background-color: #ffffff;
|
||||
@@ -134,6 +144,10 @@
|
||||
#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item > button.active {
|
||||
background-color: #f6f6bd;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item > button.disabled {
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper .editor-toolbar .toolbar-item .file {
|
||||
opacity: 0;
|
||||
/* 隐藏输入框 */
|
||||
@@ -277,19 +291,20 @@
|
||||
line-height: 26px;
|
||||
color: #333333;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper #editor-container a {
|
||||
#edit .edit-container #editor—wrapper #editor-container h1,
|
||||
#edit .edit-container #editor—wrapper #editor-container h1 span {
|
||||
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif !important;
|
||||
font-weight: 650 !important;
|
||||
color: #000000;
|
||||
font-size: 18px !important;
|
||||
line-height: 30px;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper #editor-container a,
|
||||
#edit .edit-container #editor—wrapper #editor-container a span {
|
||||
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
||||
text-decoration: underline;
|
||||
color: #04b0d5;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper #editor-container h1,
|
||||
#edit .edit-container #editor—wrapper #editor-container h1 span {
|
||||
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
|
||||
font-weight: 650;
|
||||
color: #000000;
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
}
|
||||
#edit .edit-container #editor—wrapper #editor-container video {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
height: 60px;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 1) -41%, rgba(238, 248, 249, 1) 96%);
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
|
||||
.edit-head-container {
|
||||
width: 1200px;
|
||||
@@ -48,6 +49,16 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-box {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
height: 5px;
|
||||
width: 2px;
|
||||
background: linear-gradient(315deg, rgb(103, 114, 255) 0px, rgb(0, 249, 229) 100%) center center / 104% 104% rgb(74, 84, 255);
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-container {
|
||||
@@ -152,6 +163,11 @@
|
||||
&.active {
|
||||
background-color: rgba(246, 246, 189, 1);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.file {
|
||||
@@ -327,22 +343,22 @@
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
color: #333333;
|
||||
h1,
|
||||
h1 span {
|
||||
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif !important;
|
||||
font-weight: 650 !important;
|
||||
color: #000000;
|
||||
font-size: 18px !important;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
a {
|
||||
a,
|
||||
a span {
|
||||
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
||||
text-decoration: underline;
|
||||
color: #04b0d5;
|
||||
}
|
||||
|
||||
h1,
|
||||
h1 span {
|
||||
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
|
||||
font-weight: 650;
|
||||
color: #000000;
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1891,3 +1891,38 @@ td {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#pre-loader {
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#pre-loader .three-bounce > div {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
top: 50%;
|
||||
margin-top: -9px;
|
||||
background: #aeadba;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
#pre-loader .three-bounce .one {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
#pre-loader .three-bounce .two {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
@keyframes bouncedelay {
|
||||
0%,
|
||||
100%,
|
||||
80% {
|
||||
transform: scale(0);
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,3 +2273,46 @@ td {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
#pre-loader {
|
||||
height: 70vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.three-bounce {
|
||||
> div {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 100%;
|
||||
top: 50%;
|
||||
margin-top: -9px;
|
||||
background: #aeadba;
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.one {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.two {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
0%,
|
||||
100%,
|
||||
80% {
|
||||
transform: scale(0);
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: scale(1);
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="pre-loader">
|
||||
<div class="three-bounce" p-id="11">
|
||||
<div class="one" p-id="12"></div>
|
||||
<div class="two" p-id="13"></div>
|
||||
<div class="three" p-id="14"></div>
|
||||
</div>
|
||||
</div>
|
||||
<sign-in-box></sign-in-box>
|
||||
<div class="container" id="details" v-cloak>
|
||||
<div class="templateValue" ref="uniqidRef">qXi0yrL189WW</div>
|
||||
<div class="templateValue" ref="uniqidRef">4uPq5uKzTPTP</div>
|
||||
|
||||
<div class="head-top flexacenter">
|
||||
<img class="logo" src="https://oss.gter.net/logo" alt="" />
|
||||
|
||||
11
edit.html
11
edit.html
@@ -21,21 +21,25 @@
|
||||
|
||||
<body>
|
||||
<div class="container" id="edit" v-cloak>
|
||||
<div class="valueA" ref="valueA" style="display: none;">{@}</div>
|
||||
<div class="edit-head flexacenter">
|
||||
<div class="edit-head-container flexacenter">
|
||||
<a class="" href="/" target="_blank"><img class="icon" src="{@/img/edit-logo-icon.png}" /></a>
|
||||
<a class="" href="/" target="_blank">
|
||||
<img class="icon" src="{@/img/edit-logo-icon.png}" />
|
||||
</a>
|
||||
<div class="dot"></div>
|
||||
<div class="title">发帖</div>
|
||||
<div class="hint">发帖奖励 3 个寄托币/篇,每天最高奖励3篇</div>
|
||||
<div class="flex1"></div>
|
||||
<img v-if="userInfoWin.avatar" class="avatar" :src="userInfoWin.avatar" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="progress-box" v-if="progress != 0 || progress != 100" :style="{ width: progress + '%' }"></div> -->
|
||||
</div>
|
||||
<div class="edit-container">
|
||||
<!-- 标题输入 -->
|
||||
<div class="title-box">
|
||||
<input class="title-input" type="title" placeholder="输入标题(非必填)" v-model="info.title"
|
||||
:maxlength="titleLength" />
|
||||
<input class="title-input" type="title" placeholder="输入标题(非必填)" v-model="info.title" :maxlength="titleLength" />
|
||||
<div class="sum">{{ info?.title?.length ? titleLength - info?.title?.length : titleLength }}</div>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +100,7 @@
|
||||
<!-- 内容编辑区 -->
|
||||
<!-- <div class="content-input" id="editor" contenteditable="true" :class="{ 'empty': isEmpty }" placeholder="输入正文" ref="editorRef" @input="onEditorInput" @focus="onEditorFocus" @blur="onEditorBlur" v-html="info.content" @click="handleClick"></div> -->
|
||||
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons flexacenter">
|
||||
<div class="left-section flexacenter" @click="cutAnonymity">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide } = Vue;
|
||||
const ASSET_VERSION = window.__ASSET_VERSION__ || "20251126";
|
||||
const withVer = (p) => `${p}?v=${ASSET_VERSION}`;
|
||||
|
||||
const { itemForum } = await import(withVer("../component/item-forum/item-forum.js"));
|
||||
const { itemOffer } = await import(withVer("../component/item-offer/item-offer.js"));
|
||||
const { itemSummary } = await import(withVer("../component/item-summary/item-summary.js"));
|
||||
@@ -80,6 +79,9 @@ const appSectionIndex = createApp({
|
||||
let uniqidRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
const preLoader = document.getElementById("pre-loader");
|
||||
if (preLoader) preLoader.style.display = "none";
|
||||
|
||||
uniqid = uniqidRef.value.innerText;
|
||||
|
||||
init();
|
||||
@@ -121,7 +123,7 @@ const appSectionIndex = createApp({
|
||||
ajaxGet(`/v2/api/forum/getTopicDetails?uniqid=${uniqid}`).then((res) => {
|
||||
if (res.code != 200) {
|
||||
creationAlertBox("error", res.message || "主题不存在");
|
||||
setTimeout(() => redirectToExternalWebsite(`/`), 3000);
|
||||
// setTimeout(() => redirectToExternalWebsite(`/`), 3000);
|
||||
return;
|
||||
}
|
||||
const data = res.data;
|
||||
@@ -130,27 +132,6 @@ const appSectionIndex = createApp({
|
||||
|
||||
if (!targetInfo.hidden) targetInfo.hidden = 0;
|
||||
|
||||
targetInfo.attachments = {
|
||||
images: [
|
||||
{
|
||||
aid: 708161,
|
||||
url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-S_pItc37qqsgFptxhXa6RWi26P-BuTQYWFOfCsdkb8LQ0NDI5",
|
||||
},
|
||||
],
|
||||
files: [],
|
||||
videos: [
|
||||
{
|
||||
aid: 1009770,
|
||||
posterid: 1009849,
|
||||
posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polfXuP1NFX9ddrB_WbUGy8P79gQxdHR-HKts0V7NkzNDQyOQ~~",
|
||||
url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniG1NFX9ddrB_WbUGy8P79gQxcSFbqQ78MV7NkzNDQyOQ~~",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// targetInfo.content = '<p style="text-align: center;">红红火火<strong>恍恍惚惚</strong></p>[b]红红火火恍恍惚惚有[/b]<p>\n</p><p>\n</p><p>[attach]1009770[/attach]</p><p>\n</p><p>\n</p><p style="text-align: center;">[img=96]708161[/img]</p><p style="text-align: center;">65456456456456465 <a href="11111" target="_blank" contenteditable="false">111</a> </p><p style="text-align: center;">\n</p>';
|
||||
targetInfo.content = '如果你热爱古典文献,又希望在现代职场大展身手——这个项目可能就是你的“本命”!作为香港最正统的中国语言文学项目,它既传承经典,又为你打跨境传播等全新赛道!\n\n<b>🌟 项目核心亮点</b>权威认证:中国语言文学专业认证,考公考编无障碍\n古今结合:深耕古典文献与理论,同时对接AI内容创作等新兴领域\n语言友好:全程中文授课(普通话+粤语),无语言适应压力\n规模可观:每年录取150+,机会相对较多\n\n点击前往 [港校项目库] 查看 \n<a href="https://program.gter.net/details/tf1yFYIBSda7Y5k7s9iHeLVSxDiuYTljNA~~" target="_blank" contenteditable="false">中国语言文学</a>\n手机扫码查看\n[attachimg]1008942[/attachimg]\n\n<b>🎯 谁最适合申请?</b>中文系、汉语言、古代文学等对口专业背景\n希望在教育、传媒、AI内容或国际中文教育领域发展\n看重学校声誉与专业正统性的同学\n<b>💼 毕业出路超多元</b>除了教师、公务员等传统路径,毕业生还活跃于:\n✔ 跨境文化传播\n✔ AI内容策划与生成\n✔ 国际中文教育\n✔ 出版与编辑行业\n<b>📌 申请指南</b>专业背景:严格限定中文相关专业,暂不接受跨专业申请\n成绩要求:985/211同学建议86+\n语言成绩:雅思7.0(小分5.5)即可\n面试体验:氛围轻松,专业问题较少\n<b>💡 内部消息参考</b>前几轮拿到面试邀请的同学基本都能录取\n985背景优势明显,建议尽早提交申请\n双非同学如背景特别匹配也可尝试\n<b>🤝 欢迎交流</b>你对中国文学在AI时代的发展有什么想法?或者对哪个就业方向,申请问题欢迎在评论区分享交流!\n欢迎加入寄托香港群交流\n\n[attachimg]969489[/attachimg]';
|
||||
|
||||
// 替换换行
|
||||
targetInfo.content = targetInfo.content?.replace(/\n/g, "<br>") || "";
|
||||
|
||||
@@ -330,6 +311,11 @@ const appSectionIndex = createApp({
|
||||
let isLikeGif = ref(false);
|
||||
|
||||
const likeClick = () => {
|
||||
if (realname.value == 0 && userInfoWin.value?.uin > 0) {
|
||||
openAttest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLogin.value) {
|
||||
goLogin();
|
||||
return;
|
||||
@@ -558,6 +544,15 @@ const appSectionIndex = createApp({
|
||||
|
||||
const handleAnswerText = (e) => {
|
||||
if (e.target.tagName === "IMG") {
|
||||
// 检查点击的图片是否被a标签包裹
|
||||
const anchorTag = e.target.closest("a");
|
||||
|
||||
// 如果被a标签包裹,则不执行图片预览,让链接正常跳转
|
||||
if (anchorTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 否则,执行图片预览
|
||||
var src = e.target.getAttribute("src");
|
||||
previewImage.initComponent(src);
|
||||
}
|
||||
@@ -603,7 +598,6 @@ const appSectionIndex = createApp({
|
||||
|
||||
let emojiState = ref(false);
|
||||
let emojiMaskState = ref(false);
|
||||
let emojiBottomDistance = ref(0);
|
||||
let inputTextarea = ref("");
|
||||
|
||||
// 打开 Emoji
|
||||
@@ -622,6 +616,28 @@ const appSectionIndex = createApp({
|
||||
}
|
||||
|
||||
emojiMaskState.value = true;
|
||||
|
||||
let emojiBottomDistance = 0;
|
||||
const doc = document.documentElement;
|
||||
try {
|
||||
const targetEl = (event && (event.currentTarget || event.target)) || null;
|
||||
const rect = targetEl && targetEl.getBoundingClientRect ? targetEl.getBoundingClientRect() : null;
|
||||
if (rect) {
|
||||
const elementBottomDocY = rect.bottom + window.scrollY;
|
||||
emojiBottomDistance = Math.max(doc.scrollHeight - elementBottomDocY, 0);
|
||||
} else {
|
||||
const pageY = event && (event.pageY != null ? event.pageY : event.clientY != null ? event.clientY + window.scrollY : 0);
|
||||
emojiBottomDistance = Math.max(doc.scrollHeight - pageY, 0);
|
||||
}
|
||||
|
||||
const itemEl = targetEl && targetEl.closest ? targetEl.closest(".item") : null;
|
||||
|
||||
const boxEl = itemEl ? itemEl.querySelector(".emoji-box") : null;
|
||||
if (boxEl) {
|
||||
if (emojiBottomDistance < 500) boxEl.classList.add("top");
|
||||
else boxEl.classList.remove("top");
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
// 关闭 Emoji
|
||||
@@ -1063,6 +1079,7 @@ const appSectionIndex = createApp({
|
||||
element.timestamp = strtimeago(element.created_at, 4);
|
||||
element["isReplyBoxShow"] = 0;
|
||||
element["picture"] = [];
|
||||
if (element["content"]) element["content"] = restoreHtml(element["content"], element.attachments, "comment");
|
||||
});
|
||||
|
||||
let merged = [...commentList.value[index]["child"], ...data.data.filter((item2) => !commentList.value[index]["child"].find((item1) => item1.id == item2.id))];
|
||||
@@ -1218,7 +1235,7 @@ const appSectionIndex = createApp({
|
||||
ajax(`/v2/api/forum/postTopicShare`, { token });
|
||||
};
|
||||
|
||||
return { emojiBottomDistance, uniqidRef, share, reportToken, isReplyBoxShow, matterHeight, sidebarHeight, deleteItem, maxPicture, sidebarFixed, matterRef, sidebarRef, pitchInputState, ismyself, edit, searchInput, defaultSearchText, goSearch, goPersonalHomepage, QRcode, alsoCommentsData, copyLinkClick, reportState, tokentoken, essence, recommend, hide, report, cutShow, ismanager, show, openDiscuss, commentDelete, handleInputPaste, autoResize, editCommentState, selectEditEmoji, closeEditEmoji, openEditEmoji, closeEdit, openEdit, closeEditFileUpload, postEditComment, submitAnswerComments, closePictureUpload, closeFileUpload, picture, editToken, editPicture, editInput, editEmojiState, handleFileUpload, inputTextarea, judgeLogin, handleEditFile, selectEmoji, emojiData, emojiMaskState, emojiState, closeEmoji, openEmoji, closeAnswerCommentsChild, openAnswerCommentsChild, handleAnswerText, sendMessage, TAHomePage, operateAnswerCommentsLike, closeUserInfo, openUserInfo, permissions, commentList, commentPage, commentTotalCount, picture, userInfoWin, relatedList, relatedTime, coinNubmer, coinList, coinAmount, coinSubmit, strategy, mybalance, coinsState, openCoinBox, closeCoinBox, isLikeGif, likeClick, collectClick, islike, iscollect, recentlyList, medal, count, sectionn, tags, authorInfo, info, timestamp, updatedTime };
|
||||
return { uniqidRef, share, reportToken, isReplyBoxShow, matterHeight, sidebarHeight, deleteItem, maxPicture, sidebarFixed, matterRef, sidebarRef, pitchInputState, ismyself, edit, searchInput, defaultSearchText, goSearch, goPersonalHomepage, QRcode, alsoCommentsData, copyLinkClick, reportState, tokentoken, essence, recommend, hide, report, cutShow, ismanager, show, openDiscuss, commentDelete, handleInputPaste, autoResize, editCommentState, selectEditEmoji, closeEditEmoji, openEditEmoji, closeEdit, openEdit, closeEditFileUpload, postEditComment, submitAnswerComments, closePictureUpload, closeFileUpload, picture, editToken, editPicture, editInput, editEmojiState, handleFileUpload, inputTextarea, judgeLogin, handleEditFile, selectEmoji, emojiData, emojiMaskState, emojiState, closeEmoji, openEmoji, closeAnswerCommentsChild, openAnswerCommentsChild, handleAnswerText, sendMessage, TAHomePage, operateAnswerCommentsLike, closeUserInfo, openUserInfo, permissions, commentList, commentPage, commentTotalCount, picture, userInfoWin, relatedList, relatedTime, coinNubmer, coinList, coinAmount, coinSubmit, strategy, mybalance, coinsState, openCoinBox, closeCoinBox, isLikeGif, likeClick, collectClick, islike, iscollect, recentlyList, medal, count, sectionn, tags, authorInfo, info, timestamp, updatedTime };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
320
js/edit.js
320
js/edit.js
@@ -1,6 +1,6 @@
|
||||
// 简单版本的论坛编辑器,确保图片插入功能正常
|
||||
const { createApp, ref, computed, onMounted, nextTick, onUnmounted } = Vue;
|
||||
import { headTop } from "../component/head-top/head-top.js";
|
||||
const { headTop } = await import(withVer("../component/head-top/head-top.js"));
|
||||
const { createEditor, createToolbar, SlateTransforms, Boot, SlateEditor } = window.wangEditor;
|
||||
|
||||
class MyButtonMenu {
|
||||
@@ -40,6 +40,10 @@ const editApp = createApp({
|
||||
let titleLength = ref(200);
|
||||
|
||||
let uniqid = ref("");
|
||||
|
||||
const valueA = ref(null);
|
||||
let valueUrl = "";
|
||||
|
||||
onMounted(() => {
|
||||
const params = getUrlParams();
|
||||
uniqid.value = params.uniqid || "";
|
||||
@@ -48,6 +52,21 @@ const editApp = createApp({
|
||||
checkWConfig();
|
||||
|
||||
cUpload();
|
||||
|
||||
// console.log(valueA.value);
|
||||
|
||||
valueUrl = valueA.value.innerText;
|
||||
|
||||
if (location.hostname == "127.0.0.1") {
|
||||
realname.value = 1;
|
||||
userInfoWin.value = {
|
||||
uin: 1234567890,
|
||||
uid: 1234567890,
|
||||
realname: "测试用户",
|
||||
};
|
||||
|
||||
isLogin.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
let imageLength = 10;
|
||||
@@ -139,54 +158,20 @@ const editApp = createApp({
|
||||
uniqid: uniqid.value,
|
||||
})
|
||||
.then((res) => {
|
||||
// res = {
|
||||
// code: 200,
|
||||
// message: "成功",
|
||||
// data: {
|
||||
// info: {
|
||||
// uniqid: "8a0yn9CWGjur",
|
||||
// tags: [],
|
||||
// title: "香港🇭🇰梦中情校offer,叉烧包我来了!",
|
||||
// attachments: {
|
||||
// images: [
|
||||
// {
|
||||
// aid: 1008535,
|
||||
// url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokcHyD1NFX9ddrB_WbUGy8P79gQxdHE7aVs5oV7NkzNDQyOQ~~",
|
||||
// },
|
||||
// {
|
||||
// aid: 1008032,
|
||||
// url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokdXyE1NFX9ddrB_WbUGy8P79gQxdAFefK5JoV7NkzNDQyOQ~~",
|
||||
// },
|
||||
// ],
|
||||
// files: [],
|
||||
// videos: [
|
||||
// {
|
||||
// aid: 1008621,
|
||||
// posterid: 1008676,
|
||||
// posterurl: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokc3iA1NFX9ddrB_WbUGy8P79gQxdHFOXC5J0V7NkzNDQyOQ~~",
|
||||
// url: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_pokc32H1NFX9ddrB_WbUGy8P79gQxcSFeCW5s8V7NkzNDQyOQ~~",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// anonymous: 1,
|
||||
// created_at: "2025-11-10 15:49:15",
|
||||
// type: "thread",
|
||||
// content: "[attach]1008621[/attach]\n[b]哈哈哈fdsafdaf🤫afafafas[/b]\n\n[b]😘[/b]\n🍻\n[b]婆婆[/b]\n一\n[b]样[/b]\n😉\n[b]噜噜噜fdsafdsafdafafsafafdsfdafsafsafsas[/b]\n[attachimg]1008535[/attachimg]\n\nfds\n[b]afsa[/b]\nfsdafafafafdas\n[b]魂牵梦萦 fsdaf[/b]\n[attachimg]1008032[/attachimg]",
|
||||
// role: {
|
||||
// type: "public",
|
||||
// },
|
||||
// },
|
||||
// token: "zkzAyP2l9uzYy4S63Ew3zJ1N1QkpC0ZJ7BTUBmhaeQsjc_ACxctWNq5ZtxRkFzPoNTM4W2BkojS6qZ14BLHTPRi3ohhoRKpC22Bui4qps4MDDbdu22VQtra72BDqIykNcfkCj2MDyxbHXAlC6VWGmUbA3VQ0NmUz",
|
||||
// tagList: [],
|
||||
// },
|
||||
// };
|
||||
const data = res.data;
|
||||
|
||||
if (res.code != 200) {
|
||||
creationAlertBox("error", res.message || "操作失败");
|
||||
return;
|
||||
}
|
||||
|
||||
const infoTarget = data.info || {};
|
||||
|
||||
// if (location.hostname == "127.0.0.1")
|
||||
// infoTarget.content = `<p><img src="https://oss.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polfHeD1NFX9ddrB_WbUGy8P79gQxccQOeR45kV7NkzNDQyOQ~~?aid=1009985" alt="图片描述" data-href="https://i-operation.csdnimg.cn/ad/ad_pic/a0beaaca1e2047e0ae5c0783e02b3c0a.png" style=""/></p><div data-w-e-type="video" data-w-e-is-void>
|
||||
// <video poster="https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniH1NFX9ddrB_WbUGy8P79gQxcSQbvLtMsV7NkzNDQyOQ~~?aid=1009771" controls="true" width="auto" height="auto"><source src="https://o.x-php.com/Zvt57TuJSUvkyhw-xG_Y2l-U_polcniG1NFX9ddrB_WbUGy8P79gQxcSFbqQ78MV7NkzNDQyOQ~~?aid=1009770" type="video/mp4"/></video>
|
||||
// </div><p><br></p>`;
|
||||
|
||||
console.log("content", infoTarget.content);
|
||||
if (infoTarget.content) infoTarget.content = restoreHtml(infoTarget.content, infoTarget.attachments);
|
||||
console.log("content", infoTarget.content);
|
||||
@@ -194,6 +179,8 @@ const editApp = createApp({
|
||||
info.value = infoTarget;
|
||||
token.value = data.token;
|
||||
|
||||
console.log("data", data);
|
||||
|
||||
initEditor();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -204,6 +191,18 @@ const editApp = createApp({
|
||||
let editor = null;
|
||||
let toolbarRef = ref(null);
|
||||
|
||||
// 自定义转换视频
|
||||
function customParseVideoSrc(src) {
|
||||
// console.log("customParseVideoSrc", "src:", src);
|
||||
// if (src.includes(".bilibili.com")) {
|
||||
// // 转换 bilibili url 为 iframe (仅作为示例,不保证代码正确和完整)
|
||||
// const arr = location.pathname.split("/");
|
||||
// const vid = arr[arr.length - 1];
|
||||
// return `<iframe src="//player.bilibili.com/player.html?bvid=${vid}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>`;
|
||||
// }
|
||||
return src;
|
||||
}
|
||||
|
||||
const initEditor = () => {
|
||||
let infoTarget = info.value || {};
|
||||
|
||||
@@ -218,58 +217,54 @@ const editApp = createApp({
|
||||
["insertImage"]: {
|
||||
onInsertedImage(imageNode) {
|
||||
console.log("insertImage");
|
||||
if (imageNode == null) return;
|
||||
// if (imageNode == null) return;
|
||||
|
||||
const { src, alt, url, href } = imageNode;
|
||||
|
||||
console.log("src", src);
|
||||
console.log("alt", alt);
|
||||
console.log("url", url);
|
||||
console.log("href", href);
|
||||
},
|
||||
async parseImageSrc(src) {
|
||||
// 如果图片链接中已经包含了 ?aid= ,则说明是本站图片,直接返回,无需处理
|
||||
if (src.includes("?aid=")) return src;
|
||||
|
||||
// 对于不含 ?aid= 的外部图片,执行上传转换
|
||||
if (!uConfigData || !uConfigData.url) return src;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("uploadType", "url");
|
||||
formData.append("url", src);
|
||||
if (uConfigData.params && uConfigData.params.data) {
|
||||
formData.append("data", uConfigData.params.data);
|
||||
}
|
||||
|
||||
const res = await ajax(uConfigData.url, formData);
|
||||
if (res.code == 200 && res.data) return `${res.data.url}?aid=${res.data.aid}`;
|
||||
else creationAlertBox("error", res.message || "操作失败");
|
||||
} catch (e) {
|
||||
console.error("Transform network image failed", e);
|
||||
}
|
||||
return src;
|
||||
},
|
||||
},
|
||||
|
||||
["uploadImage"]: {
|
||||
server: uConfigData.url,
|
||||
|
||||
// form-data fieldName ,默认值 'wangeditor-uploaded-image'
|
||||
fieldName: uConfigData.requestName,
|
||||
|
||||
// 单个文件的最大体积限制,默认为 2M
|
||||
maxFileSize: maxSize, // 1M
|
||||
|
||||
// 最多可上传几个文件,默认为 100
|
||||
maxNumberOfFiles: imageLength,
|
||||
|
||||
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
|
||||
allowedFileTypes: ["image/png", "image/jpeg", "image/jpg"], // .png, .jpg, .jpeg
|
||||
|
||||
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
|
||||
meta: { ...uConfigData.params },
|
||||
|
||||
// 将 meta 拼接到 url 参数中,默认 false
|
||||
metaWithUrl: false,
|
||||
|
||||
// 自定义增加 http header
|
||||
headers: { accept: "application/json, text/plain, */*", ...uConfigData.headers },
|
||||
|
||||
// 跨域是否传递 cookie ,默认为 false
|
||||
withCredentials: true,
|
||||
|
||||
// 超时时间,默认为 10 秒
|
||||
|
||||
timeout: 60 * 1000, // 15 秒
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
let config = uConfigData;
|
||||
// 1. 构造 FormData(包含你的接口所需字段)
|
||||
const formData = new FormData();
|
||||
formData.append(config.requestName, file); // 文件数据
|
||||
formData.append("name", file.name); // 文件名
|
||||
formData.append("type", "image"); // 文件名
|
||||
formData.append("data", config.params.data); // 文件名
|
||||
// uploading(file, file.name, "image").then((data) => {
|
||||
// insertFn(data.url); // 传入图片的可访问 URL
|
||||
// });
|
||||
ajax(config.url, formData).then((res) => {
|
||||
const data = res.data;
|
||||
console.log("上传成功:", data);
|
||||
insertFn(`${data.url}?aid=${data.aid}`); // 传入图片的可访问 URL
|
||||
});
|
||||
const img = await uploading(file, file.name, "image");
|
||||
insertFn(`${img.url}?aid=${img.aid}`);
|
||||
} catch (err) {
|
||||
console.error("上传出错:", err);
|
||||
}
|
||||
@@ -284,6 +279,7 @@ const editApp = createApp({
|
||||
const { src } = videoNode;
|
||||
// console.log("inserted video", src);
|
||||
},
|
||||
parseVideoSrc: customParseVideoSrc, // 也支持 async 函数
|
||||
},
|
||||
|
||||
["uploadVideo"]: {
|
||||
@@ -291,26 +287,24 @@ const editApp = createApp({
|
||||
fieldName: uConfigData.requestName,
|
||||
maxFileSize: maxSize, // 1M
|
||||
maxNumberOfFiles: videoLength,
|
||||
allowedFileTypes: ["video/*"],
|
||||
allowedFileTypes: ["video/flv", "video/mkv", "video/avi", "video/rm", "video/rmvb", "video/mpeg", "video/mpg", "video/ogg", "video/ogv", "video/mov", "video/wmv", "video/mp4", "video/webm", "video/m4v"],
|
||||
meta: { ...uConfigData.params },
|
||||
metaWithUrl: false,
|
||||
headers: { accept: "application/json, text/plain, */*", ...uConfigData.headers },
|
||||
withCredentials: true,
|
||||
timeout: 15 * 1000, // 15 秒
|
||||
timeout: 60 * 1000, // 15 秒
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
const videoUploadRes = await uploading(file, file.name, "video");
|
||||
|
||||
progress.value = 0;
|
||||
const coverFile = await getVideoFirstFrame(file);
|
||||
console.log("第一帧提取成功", coverFile);
|
||||
|
||||
// 步骤3:再上传第一帧封面(type 传 'cover',按后端要求调整)
|
||||
// console.log("第一帧提取成功", coverFile);
|
||||
const coverUploadRes = await uploading(coverFile, coverFile.name, "image");
|
||||
console.log("封面上传成功", coverUploadRes);
|
||||
|
||||
// console.log("封面上传成功", coverUploadRes);
|
||||
insertFn(`${videoUploadRes.url}?aid=${videoUploadRes.aid}`, `${coverUploadRes.url}?aid=${coverUploadRes.aid}`);
|
||||
} catch (err) {
|
||||
console.error("上传出错:", err);
|
||||
progress.value = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -323,9 +317,6 @@ const editApp = createApp({
|
||||
|
||||
hoverbarKeys: { text: { menuKeys: [] }, video: { menuKeys: [] } },
|
||||
};
|
||||
|
||||
// html: infoTarget.content,
|
||||
|
||||
editor = createEditor({
|
||||
selector: "#editor-container",
|
||||
html: infoTarget.content,
|
||||
@@ -334,7 +325,18 @@ const editApp = createApp({
|
||||
});
|
||||
|
||||
const toolbarConfig = {
|
||||
toolbarKeys: ["header1", "uploadImage", "uploadVideo", "emotion", "insertLink", "bold"],
|
||||
toolbarKeys: [
|
||||
"header1",
|
||||
{
|
||||
key: "group-image",
|
||||
title: "图片",
|
||||
menuKeys: ["insertImage", "uploadImage"],
|
||||
},
|
||||
"uploadVideo",
|
||||
"emotion",
|
||||
"insertLink",
|
||||
"bold",
|
||||
],
|
||||
};
|
||||
|
||||
const menu1Conf = {
|
||||
@@ -362,37 +364,37 @@ const editApp = createApp({
|
||||
const h1 = toolbarRef.value.querySelector('[data-menu-key="header1"]');
|
||||
const h1Item = h1.parentElement;
|
||||
h1Item.classList.add("toolbar-item", "flexacenter");
|
||||
h1.innerHTML = '<img class="icon" src="{@/img/t-icon.png}" alt="段落标题" /> <span>段落标题</span>';
|
||||
h1.innerHTML = `<img class="icon" src="${valueUrl}/img/t-icon.png" alt="段落标题" /> <span>段落标题</span>`;
|
||||
|
||||
const image = toolbarRef.value.querySelector('[data-menu-key="uploadImage"]');
|
||||
const image = toolbarRef.value.querySelector('[data-menu-key="group-image"]');
|
||||
const imageItem = image.parentElement;
|
||||
imageItem.classList.add("toolbar-item", "flexacenter");
|
||||
image.innerHTML = '<img class="icon" src="{@/img/img-icon.png}" alt="图片" /> <span>图片</span>';
|
||||
image.innerHTML = `<img class="icon" src="${valueUrl}/img/img-icon.png" alt="图片" /> <span>图片</span>`;
|
||||
|
||||
const video = toolbarRef.value.querySelector('[data-menu-key="uploadVideo"]');
|
||||
const videoItem = video.parentElement;
|
||||
videoItem.classList.add("toolbar-item", "flexacenter");
|
||||
video.innerHTML = '<img class="icon" src="{@/img/video-icon.png}" alt="视频" /> <span>视频</span>';
|
||||
video.innerHTML = `<img class="icon" src="${valueUrl}/img/video-icon.png" alt="视频" /> <span>视频</span>`;
|
||||
|
||||
const emotion = toolbarRef.value.querySelector('[data-menu-key="emotion"]');
|
||||
const emotionItem = emotion.parentElement;
|
||||
emotionItem.classList.add("toolbar-item", "flexacenter");
|
||||
emotion.innerHTML = '<img class="icon" src="{@/img/emotion-icon.png}" alt="表情" /> <span>表情</span>';
|
||||
emotion.innerHTML = `<img class="icon" src="${valueUrl}/img/smiling-face-round-black.png" alt="表情" /> <span>表情</span>`;
|
||||
|
||||
const link = toolbarRef.value.querySelector('[data-menu-key="insertLink"]');
|
||||
const linkItem = link.parentElement;
|
||||
linkItem.classList.add("toolbar-item", "flexacenter");
|
||||
link.innerHTML = '<img class="icon" src="{@/img/link-icon.png}" alt="链接" /> <span>链接</span>';
|
||||
link.innerHTML = `<img class="icon" src="${valueUrl}/img/link-icon.png" alt="链接" /> <span>链接</span>`;
|
||||
|
||||
const bold = toolbarRef.value.querySelector('[data-menu-key="bold"]');
|
||||
const boldItem = bold.parentElement;
|
||||
boldItem.classList.add("toolbar-item", "flexacenter");
|
||||
bold.innerHTML = '<img class="icon" src="{@/img/bold-icon.png}" alt="加粗" /> <span>加粗</span>';
|
||||
bold.innerHTML = `<img style="width: 14px;height: 14px;" class="icon" src="${valueUrl}/img/overstriking-icon.png" alt="加粗" /> <span>加粗</span>`;
|
||||
|
||||
const customCenter = toolbarRef.value.querySelector('[data-menu-key="customCenter"]');
|
||||
const customCenterItem = customCenter.parentElement;
|
||||
customCenterItem.classList.add("toolbar-item", "flexacenter");
|
||||
customCenter.innerHTML = '<img class="icon" src="{@/img/justify-center-icon.png}" alt="居中" /> <span>居中</span>';
|
||||
customCenter.innerHTML = `<img class="icon" src="${valueUrl}/img/between -icon.png" alt="居中" /> <span>居中</span>`;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -405,7 +407,7 @@ const editApp = createApp({
|
||||
let html = formattedText;
|
||||
|
||||
// 0. 将所有 <div> 转为 <p>, </div>转为</p>
|
||||
html = html.replace(/<div>/g, "<p>");
|
||||
html = html.replace(/<div(\s|>)/gi, "<p$1");
|
||||
html = html.replace(/<\/div>/g, "</p>");
|
||||
|
||||
// 1. 还原换行符为<br>标签
|
||||
@@ -424,6 +426,23 @@ const editApp = createApp({
|
||||
// html = html.replace(/\[center\]([\s\S]*?)\[\/center\]/gi, '<p style="text-align: center;">$1</p>');
|
||||
// console.log("html1", html);
|
||||
|
||||
// 5. 还原 a > [img] 的情况
|
||||
html = html.replace(/<a href="([^\"]+)"[^>]*>\[img(?:=([0-9]+(?:\.[0-9]+)?)(?:,([0-9]+(?:\.[0-9]+)?))?)?\](\d+)\[\/img\]<\/a>/gi, (match, href, width, height, aid) => {
|
||||
const image = imageList.find((img) => String(img.aid) === String(aid));
|
||||
if (!image) return match;
|
||||
|
||||
const index = imageList.findIndex((img) => String(img.aid) === String(aid));
|
||||
if (index > -1) imageList.splice(index, 1);
|
||||
|
||||
let style = "";
|
||||
const w = width ? Number(width) : 0;
|
||||
const h = height ? Number(height) : 0;
|
||||
if (w > 0 && h > 0) style = `style="width: ${w}px; height: ${h}px;"`;
|
||||
else if (w > 0) style = `style="width: ${w}px;"`;
|
||||
|
||||
return `<img src="${image.url}?aid=${aid}" data-aid="${aid}" data-href="${href}" ${style}>`;
|
||||
});
|
||||
|
||||
// 5. 还原【新增图片格式】[img=width,height]aid[/img] 或 [img]aid[/img]
|
||||
html = html.replace(/\[img(?:=([0-9]+(?:\.[0-9]+)?)(?:,([0-9]+(?:\.[0-9]+)?))?)?\](\d+)\[\/img\]/gi, (match, width, height, aid) => {
|
||||
const image = imageList.find((img) => String(img.aid) === String(aid)); // 统一字符串比较,避免类型问题
|
||||
@@ -451,7 +470,7 @@ const editApp = createApp({
|
||||
const image = imageList.find((img) => img.aid == aid);
|
||||
if (image) {
|
||||
imageList.splice(imageList.indexOf(image), 1);
|
||||
return `<img src="${image.url}" data-aid="${aid}">`;
|
||||
return `<img src="${image.url}?aid=${aid}" data-aid="${aid}">`;
|
||||
}
|
||||
return match; // 未找到对应图片时保留原始标记
|
||||
});
|
||||
@@ -498,7 +517,6 @@ const editApp = createApp({
|
||||
Array.from(__c.querySelectorAll("video")).forEach((vd) => {
|
||||
const p = vd.parentElement;
|
||||
if (!p || p.tagName !== "P") {
|
||||
console.log(999999999999999999999999999999);
|
||||
const wrap = document.createElement("p");
|
||||
p ? p.insertBefore(wrap, vd) : __c.appendChild(wrap);
|
||||
wrap.appendChild(vd);
|
||||
@@ -507,7 +525,7 @@ const editApp = createApp({
|
||||
|
||||
html = __c.innerHTML;
|
||||
|
||||
console.log("html3", html);
|
||||
console.log("初始化显示的html", html);
|
||||
return html;
|
||||
};
|
||||
|
||||
@@ -532,10 +550,10 @@ const editApp = createApp({
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!isLogin.value) {
|
||||
// goLogin();
|
||||
// return;
|
||||
// }
|
||||
if (!isLogin.value) {
|
||||
goLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
const infoTarget = { ...info.value } || {};
|
||||
let content = editor.getHtml();
|
||||
@@ -551,16 +569,16 @@ const editApp = createApp({
|
||||
info.value["attachments"]["images"] = images;
|
||||
info.value["attachments"]["videos"] = videos;
|
||||
|
||||
console.log(content);
|
||||
console.log("原始html", content);
|
||||
content = formatContent(content);
|
||||
console.log(content);
|
||||
console.log("最终html", content);
|
||||
const data = {
|
||||
...infoTarget,
|
||||
content,
|
||||
};
|
||||
|
||||
console.log("data", data);
|
||||
// return
|
||||
if (location.hostname == "127.0.0.1") return;
|
||||
|
||||
ajax("/v2/api/forum/postPublishTopic", {
|
||||
info: data,
|
||||
@@ -584,9 +602,6 @@ const editApp = createApp({
|
||||
};
|
||||
|
||||
const formatContent = (html) => {
|
||||
// 1. 替换图片标签
|
||||
// html = html.replace(/<img[^>]*data-aid="(\d+)"[^>]*>/gi, "[attachimg]$1[/attachimg]");
|
||||
|
||||
// 1. 替换图片标签(优先解析src中的aid+宽高生成自定义格式,再兼容原有data-aid逻辑)
|
||||
html = html.replace(/<img[^>]*>/gi, (imgTag) => {
|
||||
const srcMatch = imgTag.match(/src="([^"]+)"/i);
|
||||
@@ -625,12 +640,18 @@ const editApp = createApp({
|
||||
console.log("width", width, "height", height);
|
||||
|
||||
// 第四步:按规则生成格式
|
||||
if (width == 0 && height == 0) return `[img]${aid}[/img]`;
|
||||
else return `[img=${width}${height ? "," + height : ""}]${aid}[/img]`;
|
||||
let result;
|
||||
if (width == 0 && height == 0) result = `[img]${aid}[/img]`;
|
||||
else result = `[img=${width}${height ? "," + height : ""}]${aid}[/img]`;
|
||||
|
||||
// 提取 data-href 并添加 a 标签
|
||||
const dataHrefMatch = imgTag.match(/data-href="([^"]+)"/i);
|
||||
if (dataHrefMatch && dataHrefMatch[1]) result = `<a href="${dataHrefMatch[1]}" target="_blank">${result}</a>`;
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// 1.1 替换视频标签
|
||||
// html = html.replace(/<video[^>]*aid="(\d+)"[^>]*>[\s\S]*?<\/video>/gi, "[attach]$1[/attach]");
|
||||
html = html.replace(/<video[^>]*>[\s\S]*?<\/video>/gi, (videoTag) => {
|
||||
// 第一步:提取video内source标签的src属性
|
||||
const sourceSrcMatch = videoTag.match(/<source\s+src="([^"]+)"[^>]*>/i);
|
||||
@@ -652,25 +673,17 @@ const editApp = createApp({
|
||||
});
|
||||
|
||||
// 2. 替换H2标签
|
||||
html = html.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "[b]$1[/b]");
|
||||
html = html.replace(/<h1[^>]*>([\s\S]*?)<\/h1>/gi, "[section]$1[/section]");
|
||||
html = html.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, "[b]$1[/b]");
|
||||
|
||||
// html = html.replace(/<strong[^>]*>([\s\S]*?)<\/strong>/gi, "[strong]$1[/strong]");
|
||||
|
||||
// html = html.replace(/<p[^>]*style=["'][^"']*text-align\s*:\s*center[^"']*["'][^>]*>([\s\S]*?)<\/p>/gi, "[center]$1[/center]");
|
||||
|
||||
// 5. 处理块级标签换行(仅<div>等块级标签前后换行,保持行内内容连续)
|
||||
// 块级标签:div、p、h1-h6等,这里以div为例
|
||||
// html = html.replace(/<\/div>\s*/gi, "</div>\n"); // 闭合div后换行
|
||||
// html = html.replace(/\s*<div[^>]*>/gi, "\n<div>"); // 开启div前换行
|
||||
// 3.<a href="ghj hgj gh jghj " target="_blank">ghj hgj ghj </a> 替换为 [url=ghj hgj gh jghj ]ghj hgj ghj [/url]
|
||||
html = html.replace(/<a\s+href="([^"]+)"\s+target="_blank">([\s\S]*?)<\/a>/gi, (match, href, content) => {
|
||||
return `[url=${href}]${content}[/url]`;
|
||||
});
|
||||
|
||||
// 6. 处理<br>为换行
|
||||
html = html.replace(/<br\s*\/?>/gi, "\n");
|
||||
|
||||
// 7. 移除所有剩余HTML标签 a标签除外
|
||||
// html = html.replace(/<(?!(a\b|\/a\b))[^>]+>/gi, "");
|
||||
|
||||
// 8. 清理连续换行(最多保留两个空行,避免过多空行)
|
||||
// html = html.replace(/\n{3,}/g, "\n\n");
|
||||
// 去除首尾空白
|
||||
html = html.trim();
|
||||
|
||||
@@ -736,26 +749,49 @@ const editApp = createApp({
|
||||
return result;
|
||||
};
|
||||
|
||||
const progress = ref(0);
|
||||
|
||||
const uploading = (target, name, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let config = uConfigData;
|
||||
const formData = new FormData();
|
||||
formData.append(config.requestName, target); // 文件数据
|
||||
formData.append("name", name); // 文件名
|
||||
formData.append("type", type); // 文件名
|
||||
formData.append("data", config.params.data); // 文件名
|
||||
formData.append(config.requestName, target);
|
||||
formData.append("name", name);
|
||||
formData.append("type", type);
|
||||
if (config.params && config.params.data) {
|
||||
formData.append("data", config.params.data);
|
||||
}
|
||||
|
||||
ajax(config.url, formData)
|
||||
.then((res) => {
|
||||
const data = res.data;
|
||||
try {
|
||||
resolve(data);
|
||||
} catch (error) {
|
||||
console.error("插入图片出错:", error);
|
||||
axios
|
||||
.post(config.url, formData, {
|
||||
headers: {
|
||||
...config.headers,
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
onUploadProgress: (e) => {
|
||||
progress.value = Math.round((e.loaded / e.total) * 100);
|
||||
console.log("progress.value", progress.value);
|
||||
},
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response) => {
|
||||
const res = response.data;
|
||||
if (res.code == 200) {
|
||||
resolve(res.data);
|
||||
} else {
|
||||
creationAlertBox("error", res.message || "上传失败");
|
||||
reject(res);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
creationAlertBox("error", `HTTP错误: ${error.response.status}`);
|
||||
} else if (error.request) {
|
||||
creationAlertBox("error", "网络错误");
|
||||
} else {
|
||||
creationAlertBox("error", "请求设置错误");
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -824,7 +860,7 @@ const editApp = createApp({
|
||||
});
|
||||
};
|
||||
|
||||
return { toolbarRef, uniqid, userInfoWin, titleLength, submit, info, tagList, token, cutAnonymity, editorRef };
|
||||
return { progress, valueA, toolbarRef, uniqid, userInfoWin, titleLength, submit, info, tagList, token, cutAnonymity, editorRef };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user