article.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. const { createApp, ref, onMounted, watch, computed, defineComponent, nextTick } = Vue;
  2. const { createDiscreteApi } = naive;
  3. const MessageReplies = defineComponent({
  4. name: 'MessageReplies',
  5. props: {
  6. msg: { type: Array, required: true },
  7. depth: { type: Number, default: 0 },
  8. isAdmin: { type: Boolean, default: false }
  9. },
  10. emits: ['pass', 'del', 'bounce-email', 'reply-msg', 'vote-comment'],
  11. methods: {
  12. pass(id) { this.$emit('pass', id); },
  13. del(id) { this.$emit('del', id); },
  14. bounceEmail(email) { this.$emit('bounce-email', email); },
  15. voteComment(item) { this.$emit('vote-comment', item); },
  16. replyMsg(item) { this.$emit('reply-msg', item); },
  17. GetOperatingSystem(os) { return window.GetOperatingSystem(os); },
  18. GetBrowser(browser) { return window.GetBrowser(browser); },
  19. diffDateFromNow(date) {
  20. return dayjs().diff(dayjs(date), 'day')
  21. }
  22. },
  23. template: `
  24. <hr/>
  25. <ul class="comment-list" :data-depth="depth">
  26. <li v-for="(row, idx) in msg" :key="row.Id" class="comment-item" :data-depth="depth">
  27. <div class="comment-meta-row">
  28. <div>
  29. <span class="comment-floor">{{depth}}-{{idx + 1}}#</span>
  30. <span class="comment-author-admin" v-if="row.IsMaster">{{row.NickName}}(管理员)</span>
  31. <span class="comment-author" v-else>{{row.NickName}}</span>
  32. <span class="comment-time">{{ row.CommentDate }}</span>
  33. </div>
  34. <div>
  35. <span class="comment-btn" @click="del(row.Id)" v-if="isAdmin">删除</span>
  36. <span class="comment-btn" @click="pass(row.Id)" v-if="row.Status==4&&isAdmin">通过</span>
  37. <span class="comment-opinfo" v-html="GetOperatingSystem(row.OperatingSystem)"></span>
  38. <span class="comment-opinfo" v-html="GetBrowser(row.Browser)"></span>
  39. </div>
  40. </div>
  41. <div class="comment-content" v-html="row.Content"></div>
  42. <div class="comment-actions-row">
  43. <button class="comment-like-btn" @click="voteComment(row)">👍{{row.VoteCount}}</button>
  44. <button class="comment-reply-btn" @click="replyMsg(row)" v-if="diffDateFromNow(row.CommentDate) < 180">回复</button>
  45. </div>
  46. <div class="comment-info-row" style="margin-top:4px;display:flex;flex-wrap:wrap;gap:16px;font-size:.97rem;color:#888;" v-if="isAdmin">
  47. <span class="comment-email" style="color:#1566b6;" @click="bounceEmail(row.Email)">邮箱:{{row.Email}}</span>
  48. <span class="comment-ipinfo" style="color:#1e90ff;">
  49. IP/地区:{{row.IP}} / {{row.Location}}
  50. </span>
  51. </div>
  52. <message-replies v-if="row.Children && row.Children.length"
  53. :msg="row.Children"
  54. :depth="depth + 1"
  55. :is-admin="isAdmin"
  56. @bounce-email="bounceEmail"
  57. @reply-msg="replyMsg"
  58. @vote-comment="voteComment"
  59. @pass="$emit('pass', $event)"
  60. @del="$emit('del', $event)">
  61. </message-replies>
  62. </li>
  63. </ul>
  64. `
  65. });
  66. const ParentMessages = defineComponent({
  67. name: 'ParentMessages',
  68. props: { data: Object, isAdmin: { type: Boolean, default: false } },
  69. components: { MessageReplies },
  70. emits: ['reply-msg', 'getmsgs'],
  71. computed: {
  72. startfloor() {
  73. if (!this.data) return 1;
  74. const page = Math.min(Math.max(this.data.page, 1), Math.ceil(this.data.total / this.data.size));
  75. return this.data.parentTotal - (page - 1) * this.data.size;
  76. }
  77. },
  78. methods: {
  79. voteComment(item) {
  80. axios.post("/comment/" + item.Id + "/Vote").then((response) => {
  81. const data = response.data;
  82. if (data) {
  83. if (data.Success) {
  84. item.VoteCount++;
  85. message.success(data.Message);
  86. } else {
  87. message.error(data.Message);
  88. }
  89. }
  90. });
  91. },
  92. pass(id) {
  93. axios.post("/comment/pass/" + id).then((res) => {
  94. window.message.success(res.data.Message);
  95. this.$emit('getmsgs');
  96. });
  97. },
  98. del(id) {
  99. dialog.warning({
  100. title: '删除留言',
  101. content: '确认删除这条留言吗?',
  102. positiveText: '确定',
  103. negativeText: '取消',
  104. draggable: true,
  105. onPositiveClick: () => {
  106. axios.post("/comment/delete/" + id).then((res) => {
  107. message.success(res.data.Message);
  108. this.$emit('getmsgs', null);
  109. });
  110. }
  111. })
  112. },
  113. replyMsg(item) { this.$emit('reply-msg', item); },
  114. bounceEmail(email) {
  115. dialog.warning({
  116. title: '添加邮箱黑名单',
  117. content: '确认将此邮箱添加到黑名单吗?',
  118. positiveText: '确定',
  119. negativeText: '取消',
  120. draggable: true,
  121. onPositiveClick: () => {
  122. axios.post("/system/BounceEmail", { email: email }).then(() => {
  123. message.success('邮箱添加到黑名单成功');
  124. }).catch(() => {
  125. message.error('操作失败,请稍候再试');
  126. });
  127. }
  128. })
  129. },
  130. GetOperatingSystem(os) { return window.GetOperatingSystem(os); },
  131. GetBrowser(browser) { return window.GetBrowser(browser); },
  132. diffDateFromNow(date) {
  133. return dayjs().diff(dayjs(date), 'day')
  134. }
  135. },
  136. template: `
  137. <ul v-if="data && data.rows" class="comment-list">
  138. <li v-for="(row, idx) in data.rows" :key="row.Id" class="comment-item">
  139. <div class="comment-meta-row">
  140. <div>
  141. <span class="comment-floor">{{ startfloor - idx }}# </span>
  142. <span class="comment-author-admin" v-if="row.IsMaster">{{row.NickName}}(管理员)</span>
  143. <span class="comment-author" v-else>{{row.NickName}}</span>
  144. <span class="comment-time">{{ row.CommentDate }}</span>
  145. </div>
  146. <div>
  147. <span class="comment-btn" @click="del(row.Id)" v-if="isAdmin">删除</span>
  148. <span class="comment-btn" @click="pass(row.Id)" v-if="row.Status==4&&isAdmin">通过</span>
  149. <span class="comment-opinfo" v-html="GetOperatingSystem(row.OperatingSystem)"></span>
  150. <span class="comment-opinfo" v-html="GetBrowser(row.Browser)"></span>
  151. </div>
  152. </div>
  153. <div class="comment-content" v-html="row.Content"></div>
  154. <div class="comment-actions-row">
  155. <button class="comment-like-btn" @click="voteComment(row)">👍{{row.VoteCount}}</button>
  156. <button class="comment-reply-btn" @click="replyMsg(row)" v-if="diffDateFromNow(row.CommentDate) < 180">回复</button>
  157. </div>
  158. <div class="comment-info-row" style="margin-top:4px;display:flex;flex-wrap:wrap;gap:16px;font-size:.97rem;color:#888;" v-if="isAdmin">
  159. <span class="comment-email" style="color:#1566b6;" @click="bounceEmail(row.Email)">邮箱:{{row.Email}}</span>
  160. <span class="comment-ipinfo" style="color:#1e90ff;">
  161. IP/地区:{{row.IP}} / {{row.Location}}
  162. </span>
  163. </div>
  164. <message-replies
  165. v-if="row.Children && row.Children.length"
  166. :msg="row.Children"
  167. :depth="1"
  168. :is-admin="isAdmin"
  169. @pass="pass"
  170. @vote-comment="voteComment"
  171. @bounce-email="bounceEmail"
  172. @reply-msg="replyMsg"
  173. @del="del"></message-replies>
  174. </li>
  175. </ul>
  176. `
  177. });
  178. createApp({
  179. components: { ParentMessages },
  180. setup() {
  181. const seg = window.location.pathname.substring(1).split('/');
  182. const id = seg[0];
  183. const cid = seg[2] || new URLSearchParams(window.location.search).get("cid") || 0;
  184. const user = window.defaultUser ? window.defaultUser() : { NickName: '', Email: '', Agree: false };
  185. const voteUpCount = ref(user.voteUpCount);
  186. const voteDownCount = ref(user.voteDownCount);
  187. const disableVoteUp = ref(false);
  188. const disableVoteDown = ref(false);
  189. const msg = ref({
  190. Content: '',
  191. Id: 0,
  192. PostId: id,
  193. NickName: user.NickName,
  194. Email: user.Email,
  195. ParentId: null,
  196. Agree: user.Agree,
  197. OperatingSystem: DeviceInfo.OS.toString(),
  198. Browser: DeviceInfo.browserInfo.Name + " " + DeviceInfo.browserInfo.Version
  199. });
  200. const reply = ref({
  201. for: null,
  202. Content: '',
  203. Id: 0,
  204. PostId: id,
  205. NickName: user.NickName,
  206. Email: user.Email,
  207. ParentId: null,
  208. Agree: user.Agree,
  209. OperatingSystem: DeviceInfo.OS.toString(),
  210. Browser: DeviceInfo.browserInfo.Name + " " + DeviceInfo.browserInfo.Version
  211. });
  212. const disableGetcode = ref(false);
  213. const codeMsg = ref("获取验证码");
  214. const list = ref([]);
  215. const pageConfig = ref({
  216. page: 1,
  217. size: 10,
  218. total: 0
  219. });
  220. const viewToken = ref({ email: user.Email, token: "" });
  221. const online = ref(0);
  222. const eventSource = ref(null);
  223. return {
  224. msg,
  225. reply,
  226. disableGetcode,
  227. codeMsg,
  228. viewToken,
  229. online,
  230. eventSource,
  231. list,
  232. cid,
  233. id,
  234. pageConfig,
  235. voteUpCount,
  236. voteDownCount,
  237. disableVoteUp,
  238. disableVoteDown
  239. };
  240. },
  241. data() {
  242. return {
  243. showPopup: false,
  244. showModel: false
  245. };
  246. },
  247. methods: {
  248. submit(item) {
  249. if (item.NickName.trim().length <= 0 || item.NickName.trim().length > 24) {
  250. message.error('昵称要求2-24个字符!');
  251. return;
  252. }
  253. if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(item.Email.trim())) {
  254. message.error('请输入正确的邮箱格式!');
  255. return;
  256. }
  257. if (item.Content.trim().length <= 2 || item.Content.trim().length > 1000) {
  258. message.error('内容过短或者超长,请输入有效的留言内容!');
  259. return;
  260. }
  261. if (item.Email.indexOf("163") > 1 || item.Email.indexOf("126") > 1) {
  262. dialog.warning({
  263. title: '邮箱确认',
  264. content: '检测到您输入的邮箱是网易邮箱,本站的邮件服务器可能会因为您的反垃圾设置而无法将邮件正常发送到您的邮箱,建议使用您的其他邮箱,或者检查反垃圾设置后,再点击确定按钮继续!',
  265. positiveText: '确定',
  266. negativeText: '取消',
  267. draggable: true,
  268. onPositiveClick: () => {
  269. this.postMessage(item);
  270. }
  271. });
  272. return;
  273. }
  274. this.postMessage(item);
  275. },
  276. postMessage(item) {
  277. axios.create({
  278. headers: {
  279. 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
  280. }
  281. }).post("/comment/submit", item).then((response) => {
  282. const data = response.data;
  283. if (data && data.Success) {
  284. message.success(data.Message);
  285. item.Content = '';
  286. item.ParentId = null;
  287. this.getcomments();
  288. try {
  289. window.ue.hasContents() && window.ue.setContent('');
  290. window.ue2 && window.ue2.hasContents() && window.ue2.setContent('');
  291. } catch (e) {
  292. console.error(e);
  293. }
  294. } else {
  295. message.error(data.Message);
  296. }
  297. }).catch(err => {
  298. console.log(err);
  299. message.error(err.response?.data?.Message || '请求失败,请稍候再试!');
  300. });
  301. },
  302. getcomments() {
  303. axios.get(`/comment/getcomments?id=${this.id}&page=${this.pageConfig.page}&size=${this.pageConfig.size}&cid=${this.cid}`).then((response) => {
  304. if (response.data && response.data.Success) {
  305. this.list = response.data.Data;
  306. this.pageConfig.total = this.list.total;
  307. }
  308. });
  309. },
  310. async getcode(email) {
  311. message.info('正在发送验证码,请稍候...');
  312. const data = await axios.create({
  313. headers: {
  314. 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
  315. }
  316. }).post("/validate/sendcode", {
  317. //__RequestVerificationToken: document.querySelector('input[name="__RequestVerificationToken"]').value,
  318. email: email
  319. }).then(res => res.data);
  320. if (data.Success) {
  321. this.disableGetcode = true;
  322. message.success('验证码发送成功,请注意查收邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!');
  323. localStorage.setItem("user", JSON.stringify({ NickName: this.reply.NickName || this.msg.NickName, Email: this.reply.Email || this.msg.Email }));
  324. var count = 0;
  325. var timer = setInterval(() => {
  326. count++;
  327. this.codeMsg = '重新发送(' + (120 - count) + ')';
  328. if (count > 120) {
  329. clearInterval(timer);
  330. this.disableGetcode = false;
  331. this.codeMsg = '重新发送';
  332. }
  333. }, 1000);
  334. } else {
  335. message.error(data.Message);
  336. this.disableGetcode = false;
  337. }
  338. },
  339. async getToken(email) {
  340. message.info('正在发送验证码,请稍候...');
  341. const data = await axios.create({
  342. headers: {
  343. 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
  344. }
  345. }).post("/post/GetViewToken", {
  346. email: email
  347. }).then(res => res.data);
  348. if (data.Success) {
  349. this.disableGetcode = true;
  350. message.success('验证码发送成功,请注意查收邮件,若未收到,请检查你的邮箱地址或邮件垃圾箱!');
  351. localStorage.setItem("user", JSON.stringify({ NickName: this.reply.NickName || this.msg.NickName, Email: this.reply.Email || this.msg.Email }));
  352. var count = 0;
  353. var timer = setInterval(() => {
  354. count++;
  355. this.codeMsg = '重新发送(' + (120 - count) + ')';
  356. if (count > 120) {
  357. clearInterval(timer);
  358. this.disableGetcode = false;
  359. this.codeMsg = '重新发送';
  360. }
  361. }, 1000);
  362. } else {
  363. message.error(data.Message);
  364. this.disableGetcode = false;
  365. }
  366. },
  367. replyMsg(item) {
  368. this.reply.ParentId = item.Id;
  369. this.reply.for = item;
  370. this.showPopup = true;
  371. },
  372. validateViewToken() {
  373. axios.create({
  374. headers: {
  375. 'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
  376. }
  377. }).post("/post/CheckViewToken", this.viewToken).then((response) => {
  378. const data = response.data;
  379. if (data.Success) {
  380. window.location.reload();
  381. } else {
  382. message.error(data.Message);
  383. }
  384. }, function () {
  385. message.error("请求失败,请稍候再试!");
  386. });
  387. },
  388. voteUp() {
  389. axios.post("/post/voteup/" + this.id).then((response) => {
  390. const data = response.data;
  391. if (data) {
  392. this.disableVoteUp = true;
  393. this.disableVoteDown = true;
  394. if (data.Success) {
  395. this.voteUpCount++;
  396. message.success(data.Message);
  397. } else {
  398. message.error(data.Message);
  399. }
  400. }
  401. }).catch(() => {
  402. message.error("请求失败,请稍候再试!");
  403. });
  404. },
  405. voteDown() {
  406. axios.post("/post/votedown/" + this.id).then((response) => {
  407. const data = response.data;
  408. if (data) {
  409. this.disableVoteUp = true;
  410. this.disableVoteDown = true;
  411. if (data.Success) {
  412. this.voteDownCount++;
  413. message.success(data.Message);
  414. } else {
  415. message.error(data.Message);
  416. }
  417. }
  418. }).catch(() => {
  419. message.error("请求失败,请稍候再试!");
  420. });
  421. },
  422. takedown() {
  423. dialog.warning({
  424. title: '下架文章',
  425. content: '确认下架这篇文章吗?',
  426. positiveText: '确定',
  427. negativeText: '取消',
  428. draggable: true,
  429. onPositiveClick: () => {
  430. axios.post("/post/takedown/" + this.id).then((res) => {
  431. message.success(res.data.Message);
  432. window.location.reload();
  433. });
  434. }
  435. })
  436. },
  437. postPass() {
  438. axios.post("/post/pass/" + this.id).then((res) => {
  439. message.success(res.data.Message);
  440. window.location.reload();
  441. });
  442. },
  443. fixtop() {
  444. axios.post("/post/fixtop/" + this.id).then((res) => {
  445. message.success(res.data.Message);
  446. window.location.reload();
  447. });
  448. },
  449. deleteVersion(id, postId) {
  450. dialog.warning({
  451. title: '删除版本',
  452. content: '确认删除这个版本吗?',
  453. positiveText: '确定',
  454. negativeText: '取消',
  455. draggable: true,
  456. onPositiveClick: () => {
  457. axios.post("/post/DeleteHistory/" + id).then(function (res) {
  458. message.success(res.data.Message);
  459. location.href = "/" + postId + "/history";
  460. });
  461. }
  462. });
  463. },
  464. revertVersion(id, postId) {
  465. dialog.warning({
  466. title: '还原版本',
  467. content: '确认还原到这个版本吗?',
  468. positiveText: '确定',
  469. negativeText: '取消',
  470. draggable: true,
  471. onPositiveClick: () => {
  472. axios.post("/post/revert/" + id).then(function (data) {
  473. message.success(data.data.Message);
  474. location.href = "/" + postId;
  475. });
  476. }
  477. });
  478. },
  479. // 初始化EventSource监听统计数据
  480. initEventSource() {
  481. // 如果页面不可见,不初始化连接
  482. if (document.hidden) {
  483. return
  484. }
  485. this.eventSource = new EventSource(`${this.id}/online`)
  486. this.eventSource.onmessage = (event) => {
  487. try {
  488. this.online = event.data
  489. } catch (error) {
  490. console.error('解析服务器发送的数据时出错:', error)
  491. }
  492. }
  493. this.eventSource.onerror = (error) => {
  494. console.error('EventSource 连接错误!', error)
  495. }
  496. },
  497. // 关闭 EventSource 连接
  498. closeEventSources() {
  499. if (this.eventSource) {
  500. this.eventSource.close()
  501. this.eventSource = null
  502. }
  503. },
  504. handleVisibilityChange() {
  505. if (document.hidden) {
  506. // 页面变为不可见,断开连接
  507. this.closeEventSources()
  508. } else {
  509. // 页面变为可见,恢复连接
  510. this.initEventSource()
  511. }
  512. },
  513. showViewer() {
  514. var html = "";
  515. axios.get("/" + this.id + "/online-viewer").then((res) => {
  516. const data = res.data;
  517. for (let item of data) {
  518. html += `${item.Key}:${item.Value}`;
  519. }
  520. dialog.success({
  521. title: '在看列表',
  522. content: html
  523. })
  524. });
  525. }
  526. },
  527. watch: {
  528. 'pageConfig.page'(newVal, oldVal) {
  529. if (newVal !== oldVal) {
  530. this.getcomments();
  531. document.querySelector('.comment-form-btn').scrollIntoView({ behavior: 'smooth', block: 'start' });
  532. }
  533. },
  534. 'pageConfig.size'(newVal, oldVal) {
  535. if (newVal !== oldVal) {
  536. this.getcomments();
  537. }
  538. },
  539. showPopup(newVal) {
  540. if (newVal) {
  541. nextTick(() => {
  542. window.ue2 = UE.getEditor('editor2', {
  543. //这里可以选择自己需要的工具按钮名称,此处仅选择如下五个
  544. toolbars: [['source', //源代码
  545. 'removeformat', //清除格式
  546. 'bold', //加粗
  547. 'italic', //斜体
  548. 'underline', //下划线
  549. 'strikethrough', //删除线
  550. 'blockquote', //引用
  551. 'pasteplain', //纯文本粘贴模式
  552. 'fontsize', //字号
  553. 'paragraph', //段落格式
  554. 'forecolor', //字体颜色
  555. 'backcolor', //背景色
  556. 'insertcode', //代码语言
  557. 'horizontal', //分隔线
  558. 'justifyleft', //居左对齐
  559. 'justifyright', //居右对齐
  560. 'justifycenter', //居中对齐
  561. 'link', //超链接
  562. 'unlink', //取消链接
  563. 'emotion', //表情
  564. 'simpleupload', //单图上传
  565. 'insertorderedlist', //有序列表
  566. 'insertunorderedlist', //无序列表
  567. ]],
  568. initialFrameWidth: null,
  569. //默认的编辑区域高度
  570. initialFrameHeight: 200,
  571. maximumWords: 500,
  572. paragraph: { 'p': '', 'h4': '', 'h5': '', 'h6': '' },
  573. autoHeightEnabled: true
  574. });
  575. window.ue2.addListener('contentChange', () => {
  576. this.reply.Content = window.ue2.getContent();
  577. const links = window.ue2.body.querySelectorAll('a');
  578. for (let i = 0; i < links.length; i++) {
  579. // 如果链接没有设置target属性,则设置为 _blank
  580. if (!links[i].getAttribute('target')) {
  581. links[i].setAttribute('target', '_blank');
  582. }
  583. }
  584. });
  585. });
  586. } else {
  587. if (window.ue2) {
  588. window.ue2.destroy();
  589. }
  590. }
  591. }
  592. },
  593. mounted() {
  594. // 添加页面可见性变化监听
  595. document.addEventListener('visibilitychange', this.handleVisibilityChange)
  596. this.initEventSource();
  597. const { message, dialog } = createDiscreteApi(["message", "dialog"]);
  598. window.message = message;
  599. window.dialog = dialog;
  600. document.querySelectorAll('.article-content-card p > img').forEach(function (img) {
  601. img.addEventListener('click', function () {
  602. window.open(this.getAttribute('src'));
  603. });
  604. });
  605. SyntaxHighlighter.all();
  606. SyntaxHighlighter.defaults['toolbar'] = false;
  607. this.getcomments();
  608. if (window.UE) {
  609. window.ue = UE.getEditor('editor', {
  610. //这里可以选择自己需要的工具按钮名称,此处仅选择如下五个
  611. toolbars: [['source', //源代码
  612. 'removeformat', //清除格式
  613. 'bold', //加粗
  614. 'italic', //斜体
  615. 'underline', //下划线
  616. 'strikethrough', //删除线
  617. 'blockquote', //引用
  618. 'pasteplain', //纯文本粘贴模式
  619. 'fontsize', //字号
  620. 'paragraph', //段落格式
  621. 'forecolor', //字体颜色
  622. 'backcolor', //背景色
  623. 'insertcode', //代码语言
  624. 'horizontal', //分隔线
  625. 'justifyleft', //居左对齐
  626. 'justifyright', //居右对齐
  627. 'justifycenter', //居中对齐
  628. 'link', //超链接
  629. 'unlink', //取消链接
  630. 'emotion', //表情
  631. 'simpleupload', //单图上传
  632. 'insertorderedlist', //有序列表
  633. 'insertunorderedlist', //无序列表
  634. ]],
  635. initialFrameWidth: null,
  636. //默认的编辑区域高度
  637. initialFrameHeight: 200,
  638. maximumWords: 500,
  639. paragraph: { 'p': '', 'h4': '', 'h5': '', 'h6': '' },
  640. autoHeightEnabled: true
  641. });
  642. window.ue.addListener('contentChange', () => {
  643. this.msg.Content = window.ue.getContent();
  644. const links = window.ue.body.querySelectorAll('a');
  645. for (let i = 0; i < links.length; i++) {
  646. // 如果链接没有设置target属性,则设置为 _blank
  647. if (!links[i].getAttribute('target')) {
  648. links[i].setAttribute('target', '_blank');
  649. }
  650. }
  651. });
  652. }
  653. setTimeout(() => {
  654. let left = document.querySelector('.main-container').offsetLeft;
  655. new AutoToc({
  656. contentSelector: '.main-container',
  657. minLevel: 2,
  658. maxLevel: 6,
  659. scrollOffset: 110,
  660. observe: true,
  661. position: 'left', // 可选: left | right | top | custom
  662. float: true,
  663. offset: { top: 138, left: left - Math.min(left - 5, 600) },
  664. width: Math.max(Math.min(left - 7, 600), 220),
  665. closeButton: true,
  666. draggable: true
  667. }).build();
  668. }, 500);
  669. },
  670. beforeUnmount() {
  671. // 移除页面可见性变化监听
  672. document.removeEventListener('visibilitychange', handleVisibilityChange)
  673. if (this.eventSource) {
  674. this.eventSource.close()
  675. }
  676. },
  677. }).use(naive).mount('#postApp');