From 3ba1e92c44361b2d56a5daa54bc4ddd892d1597b Mon Sep 17 00:00:00 2001 From: KUN1007 Date: Thu, 28 Sep 2023 17:16:21 +0800 Subject: [PATCH] feat: rewrite topic --- src/api/edit/index.ts | 19 ++- src/api/edit/types/edit.ts | 13 ++- src/api/topic/types/topic.ts | 1 + src/components/quill-editor/QuillEditor.vue | 39 ++++++- src/components/quill-editor/Title.vue | 31 ++++- src/language/en.ts | 1 + src/language/zh.ts | 4 + src/store/modules/edit.ts | 100 +++++++++------- src/store/types/edit.d.ts | 53 +++++++++ src/store/utils/checkTopicPublish.ts | 34 ++++++ src/views/edit/components/Button.vue | 122 ++++++++++---------- src/views/edit/components/Footer.vue | 14 ++- src/views/edit/components/Tags.vue | 15 ++- src/views/topic/components/Master.vue | 12 +- src/views/topic/components/MasterFooter.vue | 58 ++++++---- src/views/topic/components/Rewrite.vue | 32 ++++- 16 files changed, 409 insertions(+), 139 deletions(-) create mode 100644 src/store/types/edit.d.ts create mode 100644 src/store/utils/checkTopicPublish.ts diff --git a/src/api/edit/index.ts b/src/api/edit/index.ts index 963de884..1eff773b 100644 --- a/src/api/edit/index.ts +++ b/src/api/edit/index.ts @@ -1,10 +1,11 @@ -import { fetchPost, fetchGet } from '@/utils/request' +import { fetchPost, fetchGet, fetchPut } from '@/utils/request' import type * as Edit from './types/edit' // 将对象转为请求参数的函数 import objectToQueryParams from '@/utils/objectToQueryParams' const editURLs = { - create: `/edit/topic`, + createTopic: `/edit/topic`, + updateTopic: `/edit/topic`, hotTags: `/tag/popular`, } @@ -14,7 +15,7 @@ export async function postEditNewTopicApi( ): Promise { // 调用 fetchPost 函数 const response = await fetchPost( - editURLs.create, + editURLs.createTopic, newTopicData ) @@ -22,6 +23,18 @@ export async function postEditNewTopicApi( return response } +// 更新话题 +export async function updateEditNewTopicApi( + requestData: Edit.EditUpdateTopicRequestData +): Promise { + // 调用 fetchPost 函数 + const response = await fetchPut( + editURLs.updateTopic, + requestData + ) + return response +} + // 获取 10 个热门 tag export async function getTopTagsApi( requestData: Edit.EditGetHotTagsRequestData diff --git a/src/api/edit/types/edit.ts b/src/api/edit/types/edit.ts index 3937e76c..a7c1426d 100644 --- a/src/api/edit/types/edit.ts +++ b/src/api/edit/types/edit.ts @@ -4,7 +4,6 @@ export interface EditCreateTopicRequestData { time: number tags: Array category: Array - uid: number } export interface EditKUNGalgameTopic { @@ -28,6 +27,15 @@ export interface EditKUNGalgameTopic { // edited: number } +// 更新话题的请求数据格式 +export interface EditUpdateTopicRequestData { + tid: number + title: string + content: string + tags: string[] + category: string[] +} + // 获取热门 tag 的请求数据格式 export interface EditGetHotTagsRequestData { limit: number @@ -37,5 +45,8 @@ export interface EditGetHotTagsRequestData { export type EditCreateTopicResponseData = KUNGalgameResponseData +// 更新话题响应数据的格式 +export type EditUpdateTopicResponseData = KUNGalgameResponseData<{}> + // 热门 tag 的返回数据格式 export type EditGetHotTagsResponseData = KUNGalgameResponseData diff --git a/src/api/topic/types/topic.ts b/src/api/topic/types/topic.ts index 6bda85ca..484c3e60 100644 --- a/src/api/topic/types/topic.ts +++ b/src/api/topic/types/topic.ts @@ -28,6 +28,7 @@ export interface TopicDetail { rid: number[] status: number share: number[] + category: string[] } // 更新话题的请求数据 diff --git a/src/components/quill-editor/QuillEditor.vue b/src/components/quill-editor/QuillEditor.vue index f07691d5..933442bd 100644 --- a/src/components/quill-editor/QuillEditor.vue +++ b/src/components/quill-editor/QuillEditor.vue @@ -32,8 +32,15 @@ import DOMPurify from 'dompurify' import { debounce } from '@/utils/debounce' // 话题编辑界面 store -const { editorHeight, mode, theme, textCount, isSaveTopic, content } = - storeToRefs(useKUNGalgameEditStore()) +const { + editorHeight, + mode, + theme, + textCount, + isSaveTopic, + content, + topicRewrite, +} = storeToRefs(useKUNGalgameEditStore()) // 话题界面的 store,用于回复 const { replyDraft } = storeToRefs(useKUNGalgameTopicStore()) @@ -83,14 +90,27 @@ const isShowEditorToolbar = computed(() => ) onBeforeMount(() => { + /** + * 编辑器处于编辑界面 + */ // 挂载之前载入话题数据,如果不保存,则不载入(并且当前必须在 Edit 界面) if (isSaveTopic.value && routeName.value === 'Edit') { valueHtml.value = content.value } + /** + * 编辑器处于回复界面 + */ // 挂载之前载入回复数据,如果不保存,则不载入(并且当前必须在 topic 界面) if (replyDraft.value.isSaveReply && routeName.value === 'Topic') { valueHtml.value = replyDraft.value.content } + /** + * 编辑器处于重新编辑的编辑界面 + */ + // 挂载之前载入重新编辑话题的数据 + if (topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') { + valueHtml.value = topicRewrite.value.content + } }) // 编辑器文本改变时自动保存数据 @@ -99,14 +119,27 @@ const handleTextChange = async () => { const purifiedHtml = DOMPurify.sanitize(editorRef.value?.getHTML()) // 创建一个防抖处理函数 const debouncedUpdateContent = debounce(() => { + /** + * 编辑器处于编辑界面 + */ // 如果是在 topic 界面则保存到回复的 store if (routeName.value === 'Topic') { replyDraft.value.content = purifiedHtml } + /** + * 编辑器处于回复界面 + */ // 否则保存在 edit 界面的 store - if (routeName.value === 'Edit') { + if (!topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') { content.value = purifiedHtml } + /** + * 编辑器处于重新编辑的编辑界面 + */ + // 挂载之前载入重新编辑话题的数据 + if (topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') { + topicRewrite.value.content = purifiedHtml + } }, 1007) // 调用防抖处理函数,会在延迟时间内只执行一次更新操作 diff --git a/src/components/quill-editor/Title.vue b/src/components/quill-editor/Title.vue index cdf37fc4..ea1034ae 100644 --- a/src/components/quill-editor/Title.vue +++ b/src/components/quill-editor/Title.vue @@ -8,7 +8,9 @@ import { storeToRefs } from 'pinia' // 导入防抖函数 import { debounce } from '@/utils/debounce' -const { isSaveTopic, title } = storeToRefs(useKUNGalgameEditStore()) +const { isSaveTopic, title, topicRewrite } = storeToRefs( + useKUNGalgameEditStore() +) // 话题标题的文字 const topicTitle = ref('') @@ -16,9 +18,19 @@ const topicTitle = ref('') const maxInputLength = 40 onBeforeMount(() => { + /** + * 编辑器处于编辑界面 + */ if (isSaveTopic.value) { topicTitle.value = title.value } + /** + * 编辑器处于重新编辑的编辑界面 + */ + // 挂载之前载入重新编辑话题的数据 + if (topicRewrite.value.isTopicRewriting) { + topicTitle.value = topicRewrite.value.title + } }) // 处理用户输入 @@ -35,8 +47,21 @@ const handelInput = () => { // 创建一个防抖处理函数 const debouncedInput = debounce(() => { - // 过滤 xss - title.value = topicTitle.value + /** + * 编辑器处于回复界面 + */ + // 不是重新编辑则保存在 edit 界面的 store + if (!topicRewrite.value.isTopicRewriting) { + // 过滤 xss + title.value = topicTitle.value + } + /** + * 编辑器处于重新编辑的编辑界面 + */ + // 重新编辑则保存在重新编辑界面的 store + if (topicRewrite.value.isTopicRewriting) { + topicRewrite.value.title = topicTitle.value + } }, 300) // 调用防抖处理函数,会在延迟时间内只执行一次更新操作 diff --git a/src/language/en.ts b/src/language/en.ts index 615b9c27..fa03d144 100644 --- a/src/language/en.ts +++ b/src/language/en.ts @@ -166,6 +166,7 @@ export default { Technique: 'Technique', Others: 'Others', publish: 'Confirm Publish', + rewrite: 'Confirm Rewrite', draft: 'Save Draft', }, login: { diff --git a/src/language/zh.ts b/src/language/zh.ts index 5191326f..269fbcc0 100644 --- a/src/language/zh.ts +++ b/src/language/zh.ts @@ -165,6 +165,7 @@ export default { Technique: '技术交流', Others: '其它', publish: '确认发布', + rewrite: '确认 Rewrite', draft: '保存草稿', }, login: { @@ -209,6 +210,9 @@ export default { publish: '确认发布吗?', publishSuccess: '发布成功', publishCancel: '取消发布', + rewrite: '确认 Rewrite 吗?', + rewriteSuccess: 'Rewrite 成功', + rewriteCancel: '取消 Rewrite', draft: '草稿已经保存成功!', }, login: { diff --git a/src/store/modules/edit.ts b/src/store/modules/edit.ts index 0ef42390..37136ad8 100644 --- a/src/store/modules/edit.ts +++ b/src/store/modules/edit.ts @@ -1,46 +1,24 @@ /* 编辑区的 store */ import { defineStore } from 'pinia' -import { postEditNewTopicApi, getTopTagsApi } from '@/api/index' +// api +import { + postEditNewTopicApi, + updateEditNewTopicApi, + getTopTagsApi, +} from '@/api' +// api 请求格式 import { EditCreateTopicRequestData, EditCreateTopicResponseData, + EditUpdateTopicRequestData, + EditUpdateTopicResponseData, EditGetHotTagsRequestData, EditGetHotTagsResponseData, -} from '@/api/index' - -interface Topic { - /** - * 编辑器相关 - * @param {number} editorHeight - 编辑器高度 - * @param {'' | 'essential' | 'minimal' | 'full'} mode - 编辑器 toolbar 模式 - * @param {'snow' | 'bubble'} theme - 编辑器主题 - */ - editorHeight: number - textCount: number - mode: '' | 'essential' | 'minimal' | 'full' - theme: 'snow' | 'bubble' - - /** - * 话题相关 - * @param {string} title - 话题标题 - * @param {string} content - 话题内容(富文本) - * @param {Array} tags - 话题标签 - * @param {Array} category - 话题类别 - * @param {boolean} isSave - 是否保存话题草稿 - */ - // 话题标题 - title: string - // 话题内容 - content: string - // 话题标签 - tags: Array - // 话题分区 - category: Array - // 是否显示热门关键词 - isShowHotKeywords: boolean - // 是否保存话题 - isSaveTopic: boolean -} +} from '@/api' +// store interface +import { Topic } from '../types/edit' +// some utils to check topic publish data is valid +import { checkTopicPublish } from '../utils/checkTopicPublish' export const useKUNGalgameEditStore = defineStore({ id: 'edit', @@ -57,15 +35,36 @@ export const useKUNGalgameEditStore = defineStore({ category: [], isShowHotKeywords: true, isSaveTopic: false, + + topicRewrite: { + tid: 0, + title: '', + content: '', + tags: [], + category: [], + + isTopicRewriting: false, + }, }), getters: {}, actions: { // 创建话题 - createNewTopic( - createTopicRequestData: EditCreateTopicRequestData - ): Promise { + createNewTopic(): Promise | undefined { + // 当前话题的数据 + const requestData: EditCreateTopicRequestData = { + title: this.title, + content: this.content, + time: Date.now(), + tags: this.tags, + category: this.category, + } + // 检查话题数据不合法直接返回 + if (!checkTopicPublish(this.textCount, requestData)) { + return + } + // 合法则请求接口发布话题 return new Promise((resolve, reject) => { - postEditNewTopicApi(createTopicRequestData) + postEditNewTopicApi(requestData) .then((res) => { resolve(res) }) @@ -74,6 +73,17 @@ export const useKUNGalgameEditStore = defineStore({ }) }) }, + // 更新话题 + async rewriteTopic(): Promise { + const requestData: EditUpdateTopicRequestData = { + tid: this.topicRewrite.tid, + title: this.topicRewrite.title, + content: this.topicRewrite.content, + tags: this.topicRewrite.tags, + category: this.topicRewrite.category, + } + return await updateEditNewTopicApi(requestData) + }, // 获取热门 tags getHotTags(limit: number): Promise { return new Promise((resolve, reject) => { @@ -87,6 +97,7 @@ export const useKUNGalgameEditStore = defineStore({ }) }) }, + // 重置话题草稿数据,用于发布时 resetTopicData() { this.textCount = 0 this.title = '' @@ -96,5 +107,14 @@ export const useKUNGalgameEditStore = defineStore({ this.isSaveTopic = false }, + // 重置重新发布话题数据,用于重新编辑 + resetRewriteTopicData() { + this.topicRewrite.title = '' + this.topicRewrite.content = '' + this.topicRewrite.tags = [] + this.topicRewrite.category = [] + + this.topicRewrite.isTopicRewriting = false + }, }, }) diff --git a/src/store/types/edit.d.ts b/src/store/types/edit.d.ts new file mode 100644 index 00000000..6d3afc8d --- /dev/null +++ b/src/store/types/edit.d.ts @@ -0,0 +1,53 @@ +// 啊哈哈哈,我就是要起 rewrite 这个名字,来打我呀 +interface TopicRewrite { + // 话题 id + tid: number + // 话题标题 + title: string + // 话题内容 + content: string + // 话题标签 + tags: Array + // 话题分区 + category: Array + + // 是否正在重新编辑话题 + isTopicRewriting: boolean +} + +export interface Topic { + /** + * 编辑器相关 + * @param {number} editorHeight - 编辑器高度 + * @param {'' | 'essential' | 'minimal' | 'full'} mode - 编辑器 toolbar 模式 + * @param {'snow' | 'bubble'} theme - 编辑器主题 + */ + editorHeight: number + textCount: number + mode: '' | 'essential' | 'minimal' | 'full' + theme: 'snow' | 'bubble' + + /** + * 话题相关 + * @param {string} title - 话题标题 + * @param {string} content - 话题内容(富文本) + * @param {Array} tags - 话题标签 + * @param {Array} category - 话题类别 + * @param {boolean} isSave - 是否保存话题草稿 + */ + // 话题标题 + title: string + // 话题内容 + content: string + // 话题标签 + tags: Array + // 话题分区 + category: Array + // 是否显示热门关键词 + isShowHotKeywords: boolean + // 是否保存话题 + isSaveTopic: boolean + + /* 重新编辑话题 */ + topicRewrite: TopicRewrite +} diff --git a/src/store/utils/checkTopicPublish.ts b/src/store/utils/checkTopicPublish.ts new file mode 100644 index 00000000..8143a3a5 --- /dev/null +++ b/src/store/utils/checkTopicPublish.ts @@ -0,0 +1,34 @@ +// api 请求格式 +import { EditCreateTopicRequestData } from '@/api' + +// 全局消息组件(顶部) +import message from '@/components/alert/Message' + +// 发布时检测用户输入是否合法 +export const checkTopicPublish = ( + textCount: number, + topicData: EditCreateTopicRequestData +) => { + if (!topicData.title.trim()) { + // 标题为空的话,警告 + message('Title cannot be empty!', '标题不可为空!', 'warn') + return false + } else if (!textCount) { + if (textCount > 100007) { + message('Content max length is 100007!', '内容最大长度为100007!', 'warn') + } + // 内容为空的话,警告 + message('Content cannot be empty!', '内容不可为空!', 'warn') + return false + } else if (!topicData.tags.length) { + message('Please use at least one tag!', '请至少使用一个标签!', 'warn') + } else if (!topicData.category.length) { + message( + 'Please select at least one category!', + '请至少选择一个分类!', + 'warn' + ) + } else { + return true + } +} diff --git a/src/views/edit/components/Button.vue b/src/views/edit/components/Button.vue index a054c265..4cd42838 100644 --- a/src/views/edit/components/Button.vue +++ b/src/views/edit/components/Button.vue @@ -3,75 +3,26 @@ import { useKUNGalgameMessageStore } from '@/store/modules/message' // 全局消息组件(顶部) import message from '@/components/alert/Message' -// Vue 函数 -import { toRaw } from 'vue' // 导入编辑话题的 store import { useKUNGalgameEditStore } from '@/store/modules/edit' -// 导入用户 store -import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer' import { storeToRefs } from 'pinia' // 导入路由 import { useRouter } from 'vue-router' -// 导入请求数据格式 -import { - EditCreateTopicRequestData, - EditCreateTopicResponseData, -} from '@/api/index' const router = useRouter() -const { textCount, isSaveTopic } = storeToRefs(useKUNGalgameEditStore()) +const { isSaveTopic, topicRewrite } = storeToRefs(useKUNGalgameEditStore()) const messageStore = useKUNGalgameMessageStore() -// 发布时检测用户输入是否合法 -const checkPublish = (topicData: EditCreateTopicRequestData) => { - if (!topicData.title.trim()) { - // 标题为空的话,警告 - message('Title cannot be empty!', '标题不可为空!', 'warn') - return false - } else if (!textCount) { - if (textCount > 100007) { - message('Content max length is 100007!', '内容最大长度为100007!', 'warn') - } - // 内容为空的话,警告 - message('Content cannot be empty!', '内容不可为空!', 'warn') - return false - } else if (!topicData.tags.length) { - message('Please use at least one tag!', '请至少使用一个标签!', 'warn') - } else if (!topicData.category.length) { - message( - 'Please select at least one category!', - '请至少选择一个分类!', - 'warn' - ) - } else { - return true - } -} - +// 发布话题 const handlePublish = async () => { const res = await messageStore.alert('AlertInfo.edit.publish', true) // 这里实现用户的点击确认取消逻辑 if (res) { - // 坑,storeToRefs 不等于 vue 中的 ref 或者 reactive,不能用 toRaw - const rawData = toRaw(useKUNGalgameEditStore().$state) - - // 发送给后端的数据 - const topicToCreate = { - title: rawData.title, - content: rawData.content, - time: Date.now(), - tags: rawData.tags, - category: rawData.category, - uid: useKUNGalgameUserStore().uid, - } - - // 检查提交数据是否合法 - if (checkPublish(topicToCreate)) { - // 后端返回的创建好的话题数据 - const createdTopic: EditCreateTopicResponseData = - await useKUNGalgameEditStore().createNewTopic(topicToCreate) + // 后端返回的创建好的话题数据 + const createdTopic = await useKUNGalgameEditStore().createNewTopic() + if (createdTopic) { // 获取创建好话题的 tid const tid = createdTopic.data.tid @@ -86,12 +37,43 @@ const handlePublish = async () => { messageStore.info('AlertInfo.edit.publishSuccess') // 清除数据,并不再保存数据,因为此时该话题已被发布 useKUNGalgameEditStore().resetTopicData() + } else { + message('Failed to create new topic', '发布话题失败', 'error') } } else { messageStore.info('AlertInfo.edit.publishCancel') } } +// 重新编辑 +const handleRewrite = async () => { + const res = await messageStore.alert('AlertInfo.edit.rewrite', true) + // 这里实现用户的点击确认取消逻辑 + if (res) { + // 更新话题 + const res = await useKUNGalgameEditStore().rewriteTopic() + + console.log(res.data) + + // 获取创建好话题的 tid + const tid = topicRewrite.value.tid + + // 将用户 push 进对应 tid 话题的详情页面 + router.push({ + name: 'Topic', + params: { + tid: tid, + }, + }) + + messageStore.info('AlertInfo.edit.rewriteSuccess') + // 清除数据,并不再保存数据,因为此时该话题已被更新 + useKUNGalgameEditStore().resetRewriteTopicData() + } else { + messageStore.info('AlertInfo.edit.rewriteCancel') + } +} + // 用户点击保存话题的逻辑 const handleSave = () => { // 这个值为 true 的时候每次页面加载的时候都会预加载上一次的话题数据 @@ -104,11 +86,23 @@ const handleSave = () => {
- - + + +
- + @@ -113,8 +115,6 @@ const loliStatus = computed(() => { flex-direction: column; align-items: center; flex-shrink: 0; - /* TODO: */ - /* 楼主话题背景图 */ } /* 楼主话题内容区的容器 */ diff --git a/src/views/topic/components/MasterFooter.vue b/src/views/topic/components/MasterFooter.vue index 5f04d487..d66c9f29 100644 --- a/src/views/topic/components/MasterFooter.vue +++ b/src/views/topic/components/MasterFooter.vue @@ -1,25 +1,27 @@ @@ -62,22 +76,22 @@ const handleClickEdit = () => {
  • - {{ info?.upvotes?.length }} + {{ info.upvotes.length }}
  • -
  • +
  • {{ info.views }}
  • - {{ info?.likes?.length }} + {{ info.likes.length }}
  • - {{ info?.dislikes?.length }} + {{ info.dislikes.length }}
  • diff --git a/src/views/topic/components/Rewrite.vue b/src/views/topic/components/Rewrite.vue index ca30fd22..dffa6c51 100644 --- a/src/views/topic/components/Rewrite.vue +++ b/src/views/topic/components/Rewrite.vue @@ -1,14 +1,44 @@