feat: rewrite topic

This commit is contained in:
KUN1007 2023-09-28 17:16:21 +08:00
parent 7a8e11a5f2
commit 3ba1e92c44
16 changed files with 409 additions and 139 deletions

View file

@ -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<Edit.EditCreateTopicResponseData> {
// 调用 fetchPost 函数
const response = await fetchPost<Edit.EditCreateTopicResponseData>(
editURLs.create,
editURLs.createTopic,
newTopicData
)
@ -22,6 +23,18 @@ export async function postEditNewTopicApi(
return response
}
// 更新话题
export async function updateEditNewTopicApi(
requestData: Edit.EditUpdateTopicRequestData
): Promise<Edit.EditUpdateTopicResponseData> {
// 调用 fetchPost 函数
const response = await fetchPut<Edit.EditUpdateTopicResponseData>(
editURLs.updateTopic,
requestData
)
return response
}
// 获取 10 个热门 tag
export async function getTopTagsApi(
requestData: Edit.EditGetHotTagsRequestData

View file

@ -4,7 +4,6 @@ export interface EditCreateTopicRequestData {
time: number
tags: Array<string>
category: Array<string>
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<EditKUNGalgameTopic>
// 更新话题响应数据的格式
export type EditUpdateTopicResponseData = KUNGalgameResponseData<{}>
// 热门 tag 的返回数据格式
export type EditGetHotTagsResponseData = KUNGalgameResponseData<string[]>

View file

@ -28,6 +28,7 @@ export interface TopicDetail {
rid: number[]
status: number
share: number[]
category: string[]
}
// 更新话题的请求数据

View file

@ -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)
//

View file

@ -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(() => {
/**
* 编辑器处于回复界面
*/
// edit store
if (!topicRewrite.value.isTopicRewriting) {
// xss
title.value = topicTitle.value
}
/**
* 编辑器处于重新编辑的编辑界面
*/
// store
if (topicRewrite.value.isTopicRewriting) {
topicRewrite.value.title = topicTitle.value
}
}, 300)
//

View file

@ -166,6 +166,7 @@ export default {
Technique: 'Technique',
Others: 'Others',
publish: 'Confirm Publish',
rewrite: 'Confirm Rewrite',
draft: 'Save Draft',
},
login: {

View file

@ -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: {

View file

@ -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<string>} tags -
* @param {Array<string>} category -
* @param {boolean} isSave - 稿
*/
// 话题标题
title: string
// 话题内容
content: string
// 话题标签
tags: Array<string>
// 话题分区
category: Array<string>
// 是否显示热门关键词
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<EditCreateTopicResponseData> {
createNewTopic(): Promise<EditCreateTopicResponseData> | 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<EditUpdateTopicResponseData> {
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<EditGetHotTagsResponseData> {
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
},
},
})

53
src/store/types/edit.d.ts vendored Normal file
View file

@ -0,0 +1,53 @@
// 啊哈哈哈,我就是要起 rewrite 这个名字,来打我呀
interface TopicRewrite {
// 话题 id
tid: number
// 话题标题
title: string
// 话题内容
content: string
// 话题标签
tags: Array<string>
// 话题分区
category: Array<string>
// 是否正在重新编辑话题
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<string>} tags -
* @param {Array<string>} category -
* @param {boolean} isSave - 稿
*/
// 话题标题
title: string
// 话题内容
content: string
// 话题标签
tags: Array<string>
// 话题分区
category: Array<string>
// 是否显示热门关键词
isShowHotKeywords: boolean
// 是否保存话题
isSaveTopic: boolean
/* 重新编辑话题 */
topicRewrite: TopicRewrite
}

View file

@ -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
}
}

View file

@ -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 = () => {
<!-- 按钮的容器 -->
<div class="btn-container">
<!-- 确认按钮 -->
<button class="confirm-btn" @click="handlePublish">
<button
v-if="!topicRewrite.isTopicRewriting"
class="confirm-btn"
@click="handlePublish"
>
{{ $tm('edit.publish') }}
</button>
<!-- 重新编辑按钮 -->
<button
v-if="topicRewrite.isTopicRewriting"
class="rewrite-btn"
@click="handleRewrite"
>
{{ $tm('edit.rewrite') }}
</button>
<!-- 保存按钮 -->
<button class="save-btn" @click="handleSave">
{{ $tm('edit.draft') }}
@ -148,8 +142,20 @@ const handleSave = () => {
}
.confirm-btn:hover {
background-color: var(--kungalgame-blue-4);
transition: 0.1s;
transition: 0.2s;
}
/* 重新编辑按钮的样式 */
.rewrite-btn {
color: var(--kungalgame-red-4);
background-color: var(--kungalgame-trans-red-1);
border: 1px solid var(--kungalgame-red-4);
}
.rewrite-btn:hover {
background-color: var(--kungalgame-red-4);
transition: 0.2s;
}
/* 保存按钮的样式 */
.save-btn {
color: var(--kungalgame-pink-4);
@ -158,7 +164,7 @@ const handleSave = () => {
}
.save-btn:hover {
background-color: var(--kungalgame-pink-4);
transition: 0.1s;
transition: 0.2s;
}
.save-btn:active {
background-color: var(--kungalgame-pink-3);

View file

@ -8,17 +8,29 @@ import { useKUNGalgameEditStore } from '@/store/modules/edit'
import { Category, topicCategory } from './category'
import { storeToRefs } from 'pinia'
const { isSaveTopic, category } = storeToRefs(useKUNGalgameEditStore())
const { isSaveTopic, category, topicRewrite } = storeToRefs(
useKUNGalgameEditStore()
)
//
const selectedCategories = ref<string[]>([])
// store
onBeforeMount(() => {
/**
* 编辑器处于编辑界面
*/
// 稿
if (isSaveTopic.value) {
selectedCategories.value = category.value
}
/**
* 编辑器处于重新编辑的编辑界面
*/
//
if (topicRewrite.value.isTopicRewriting) {
selectedCategories.value = topicRewrite.value.category
}
})
//

View file

@ -14,7 +14,7 @@ const route = useRoute()
const routeName = computed(() => route.name as string)
// store
const { isShowHotKeywords, tags, isSaveTopic } = storeToRefs(
const { isShowHotKeywords, tags, isSaveTopic, topicRewrite } = storeToRefs(
useKUNGalgameEditStore()
)
// store
@ -40,14 +40,27 @@ const canDeleteTag = ref(false)
// store
onBeforeMount(() => {
/**
* 编辑器处于编辑界面
*/
// edit
if (isSaveTopic.value && routeName.value === 'Edit') {
selectedTags.value = tags.value
}
/**
* 编辑器处于回复界面
*/
// topic
if (replyDraft.value.isSaveReply && routeName.value === 'Topic') {
selectedTags.value = replyDraft.value.tags
}
/**
* 编辑器处于重新编辑的编辑界面
*/
// tags
if (topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') {
selectedTags.value = topicRewrite.value.tags
}
})
// tag

View file

@ -34,9 +34,10 @@ const {
tags,
edited,
user,
rid,
// rid,
status,
share,
// share,
category,
} = topicData.topicData
//
@ -93,9 +94,10 @@ const loliStatus = computed(() => {
<Rewrite v-if="edited" :time="edited" />
</div>
</div>
<!-- 话题的点赞数等信息 -->
<!-- 话题的点赞数等信息楼主的 floor 就是 0 -->
<MasterFooter
:info="{ views, likes, dislikes, upvotes, to_floor: 0 }"
:info="{ tid, views, likes, dislikes, upvotes, to_floor: 0 }"
:topic="{ tid, title, content, tags, category }"
:r-user="user"
/>
</div>
@ -113,8 +115,6 @@ const loliStatus = computed(() => {
flex-direction: column;
align-items: center;
flex-shrink: 0;
/* TODO: */
/* 楼主话题背景图 */
}
/* 楼主话题内容区的容器 */

View file

@ -1,25 +1,27 @@
<!-- 话题的底部区域推话题回复点赞等 -->
<script setup lang="ts">
import { nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { Icon } from '@iconify/vue'
import { useRouter } from 'vue-router'
import { TopicUserInfo } from '@/api'
// store
import { useKUNGalgameEditStore } from '@/store/modules/edit'
// store
import { useKUNGalgameTopicStore } from '@/store/modules/topic'
import { storeToRefs } from 'pinia'
// tid
const tid = parseInt(useRoute().params.tid as string)
// 使 store
const { topicRewrite } = storeToRefs(useKUNGalgameEditStore())
// 使 store
const topicStore = useKUNGalgameTopicStore()
const { isEdit, replyDraft } = storeToRefs(topicStore)
const { isEdit, replyDraft } = storeToRefs(useKUNGalgameTopicStore())
// 使
const router = useRouter()
//
const props = defineProps<{
info: {
tid: number
views: number
likes: number[]
dislikes: number[]
@ -27,29 +29,41 @@ const props = defineProps<{
// floor
to_floor: number
}
topic: {
tid: number
title: string
content: string
tags: string[]
category: string[]
}
rUser: TopicUserInfo
}>()
//
const saveDraft = () => {
replyDraft.value.tid = tid
replyDraft.value.replyUserName = props.rUser.name
replyDraft.value.to_uid = props.rUser.uid
replyDraft.value.to_floor = props.info.to_floor
}
//
const handelReply = async () => {
// 便
saveDraft()
replyDraft.value.tid = props.info.tid
// uid
replyDraft.value.to_uid = props.rUser.uid
replyDraft.value.to_floor = props.info.to_floor
isEdit.value = true
}
//
const handleClickEdit = () => {
// 便
saveDraft()
//
topicRewrite.value.tid = props.topic.tid
topicRewrite.value.title = props.topic.title
topicRewrite.value.content = props.topic.content
topicRewrite.value.tags = props.topic.tags
topicRewrite.value.category = props.topic.category
//
topicRewrite.value.isTopicRewriting = true
//
router.push({ name: 'Edit' })
}
</script>
@ -62,22 +76,22 @@ const handleClickEdit = () => {
<!-- 推话题 -->
<li>
<span class="icon"><Icon icon="bi:rocket" /></span>
{{ info?.upvotes?.length }}
{{ info.upvotes.length }}
</li>
<!-- 查看数量 -->
<li v-if="info.views">
<li>
<span class="icon"><Icon icon="ic:outline-remove-red-eye" /></span>
{{ info.views }}
</li>
<!-- 点赞 -->
<li>
<span class="icon"><Icon icon="line-md:thumbs-up-twotone" /></span>
{{ info?.likes?.length }}
{{ info.likes.length }}
</li>
<!-- -->
<li>
<span class="icon"><Icon icon="line-md:thumbs-down-twotone" /></span>
{{ info?.dislikes?.length }}
{{ info.dislikes.length }}
</li>
</ul>
</div>

View file

@ -1,14 +1,44 @@
<!-- 重新编辑话题信息显示 -->
<!-- 我就要把这个组件拆出来因为是Rewrite啊哈哈哈我不会告诉你这个实际上应该命名 Reedit -->
<script setup lang="ts">
import { computed } from 'vue'
import dayjs from 'dayjs'
import 'dayjs/locale/en' //
// 使 store
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
import { storeToRefs } from 'pinia'
const props = defineProps<{
time: number
}>()
// 使 store
const settingsStore = useKUNGalgameSettingsStore()
const { showKUNGalgameLanguage } = storeToRefs(settingsStore)
// 使
dayjs.locale('en')
const formattedCNDate = dayjs(props.time).format('YYYY年MM月DD日-HH:mm:ss')
const formattedENDate = dayjs(props.time).format('M / D, YYYY - h:mm:ss A')
const loliTime = computed(() => {
if (showKUNGalgameLanguage.value === 'en') {
return formattedENDate
}
if (showKUNGalgameLanguage.value === 'zh') {
return formattedCNDate
}
return ''
})
</script>
<template>
<!-- 是否重新编辑 -->
<span>在2019年10月7日10:07:00重新编辑</span>
<!-- 为什么这里没有 i18n 别问我为什么问就是 Rewrite啊哈哈哈 -->
<span>{{ `Rewrite at ${loliTime}` }}</span>
</template>
<style lang="scss" scoped>