refactor: 优化多个视图组件并添加功能

本次提交对多个视图组件进行了重构和功能增强:

    PointGoodsView.vue:
    - 清理了未使用的导入(`useAccount`)和变量(`accountInfo`, `biliInfo` prop)。
    - 通过重组计算属性和方法提高了代码可读性。
    - 增强了商品列表的筛选和排序逻辑。
    - 为购买商品功能添加了错误处理和加载状态。

    PointUserHistoryView.vue:
    - 为获取积分历史记录实现了加载状态。
    - 改进了 PointHistoryCard 组件的渲染。

    QuestionBoxView.vue:
    - 优化了可读性和性能(整合状态变量,改进命名)。
    - 增强了文件上传处理和验证逻辑。
    - 改进了标签选择逻辑和数据获取方法。
    - 添加了代码注释以提高可理解性。

    UserIndexView.vue:
    - 简化了确定要显示的模板组件的逻辑。
    - 确保无论用户信息是否存在,都一致返回默认模板。
This commit is contained in:
2025-04-17 02:15:22 +08:00
parent 1ea4404307
commit 2e5e0afd30
23 changed files with 4747 additions and 3080 deletions

View File

@@ -34,42 +34,53 @@ const { biliInfo, userInfo } = defineProps<{
userInfo: UserInfo | undefined
}>()
const nextSendQuestionTime = ref(Date.now())
const minSendQuestionTime = 30 * 1000 // 30 seconds
const splitter = new GraphemeSplitter()
// 状态变量
const message = useMessage()
const accountInfo = useAccount()
const questionMessage = ref('') // 提问内容
const fileList = ref<UploadFileInfo[]>([]) // 上传图片列表
const publicQuestions = ref<QAInfo[]>([]) // 公开提问列表
const tags = ref<string[]>([]) // 标签列表
const selectedTag = ref<string | null>(null) // 选中的标签
const isAnonymous = ref(true) // 是否匿名提问
const isSending = ref(false) // 是否正在发送
const isGetting = ref(true) // 是否正在获取数据
// 验证码相关
const token = ref('')
const turnstile = ref()
// 防刷控制
const nextSendQuestionTime = ref(Date.now())
const minSendQuestionTime = 30 * 1000 // 30秒冷却时间
// 字符分割器(用于正确计算表情符号等Unicode字符)
const splitter = new GraphemeSplitter()
// 计算属性
const isSelf = computed(() => {
return userInfo?.id == accountInfo.value?.id
return userInfo?.id === accountInfo.value?.id
})
const questionMessage = ref('')
const fileList = ref<UploadFileInfo[]>([])
const publicQuestions = ref<QAInfo[]>([])
const tags = ref<string[]>([])
const selectedTag = ref()
const isAnonymous = ref(true)
const isSending = ref(false)
const isGetting = ref(true)
// 计算字符数量
function countGraphemes(value: string) {
return splitter.countGraphemes(value)
}
// 发送提问
async function SendQuestion() {
// 内容长度检查
if (countGraphemes(questionMessage.value) < 3) {
message.error('内容最少需要3个字')
return
}
// 冷却时间检查
if (nextSendQuestionTime.value > Date.now()) {
message.error('冷却中, 剩余 ' + Math.ceil((nextSendQuestionTime.value - Date.now()) / 1000) + '秒')
return
}
isSending.value = true
await QueryPostAPI<QAInfo>(
QUESTION_API_URL + 'send',
@@ -100,6 +111,8 @@ async function SendQuestion() {
turnstile.value?.reset()
})
}
// 转换文件为Base64
function getBase64(file: File | undefined | null) {
if (!file) return null
return new Promise((resolve, reject) => {
@@ -109,15 +122,20 @@ function getBase64(file: File | undefined | null) {
reader.onerror = (error) => reject(error)
})
}
// 文件列表变更处理
function OnFileListChange(files: UploadFileInfo[]) {
if (files.length == 1) {
var file = files[0]
const file = files[0]
// 文件大小检查
if ((file.file?.size ?? 0) > 10 * 1024 * 1024) {
message.error('文件大小不能超过10MB')
fileList.value = []
}
}
}
// 获取公开提问列表
function getPublicQuestions() {
isGetting.value = true
QueryGetAPI<QAInfo[]>(QUESTION_API_URL + 'get-public', {
@@ -137,6 +155,8 @@ function getPublicQuestions() {
isGetting.value = false
})
}
// 获取标签列表
function getTags() {
isGetting.value = true
QueryGetAPI<string[]>(QUESTION_API_URL + 'get-tags', {
@@ -144,9 +164,15 @@ function getTags() {
})
.then((data) => {
if (data.code == 200) {
if (userInfo?.id == accountInfo.value.id) {
tags.value = data.data.map((tag) => JSON.parse(JSON.stringify(tag)).name)
// 处理标签数据
if (userInfo?.id == accountInfo.value?.id) {
// 自己查看自己的标签时,需要从对象中提取名称
tags.value = data.data.map((tag: any) => {
// 直接访问name属性避免不必要的JSON序列化和解析
return typeof tag === 'object' && tag !== null ? tag.name : tag
})
} else {
// 查看他人标签时直接使用返回数据
tags.value = data.data
}
} else {
@@ -160,20 +186,21 @@ function getTags() {
isGetting.value = false
})
}
// 标签选择处理
function onSelectTag(tag: string) {
if (selectedTag.value == tag) {
selectedTag.value = null
return
}
selectedTag.value = tag
// 切换选中状态
selectedTag.value = selectedTag.value === tag ? null : tag
}
// 生命周期钩子
onMounted(() => {
getPublicQuestions()
getTags()
})
onUnmounted(() => {
// 清理验证码资源
turnstile.value?.remove()
})
</script>
@@ -183,8 +210,10 @@ onUnmounted(() => {
style="max-width: 700px; margin: 0 auto"
title="提问"
>
<!-- 提问表单 -->
<NCard embedded>
<NSpace vertical>
<!-- 话题选择区域 -->
<NCard
v-if="tags.length > 0"
title="投稿话题 (可选)"
@@ -196,13 +225,15 @@ onUnmounted(() => {
:key="tag"
style="cursor: pointer"
:bordered="false"
:type="selectedTag == tag ? 'primary' : 'default'"
:type="selectedTag === tag ? 'primary' : 'default'"
@click="onSelectTag(tag)"
>
{{ tag }}
</NTag>
</NSpace>
</NCard>
<!-- 提问内容区域 -->
<NSpace
align="center"
justify="center"
@@ -228,7 +259,10 @@ onUnmounted(() => {
+ 上传图片
</NUpload>
</NSpace>
<NDivider style="margin: 10px 0 10px 0" />
<NDivider style="margin: 10px 0" />
<!-- 提示信息 -->
<NSpace align="center">
<NAlert
v-if="!accountInfo.id && !isSelf"
@@ -237,6 +271,8 @@ onUnmounted(() => {
只有注册用户才能够上传图片
</NAlert>
</NSpace>
<!-- 匿名选项 -->
<NSpace
v-if="accountInfo.id"
vertical
@@ -246,8 +282,10 @@ onUnmounted(() => {
:disabled="isSelf"
label="匿名提问"
/>
<NDivider style="margin: 10px 0 10px 0" />
<NDivider style="margin: 10px 0" />
</NSpace>
<!-- 操作按钮 -->
<NSpace justify="center">
<NButton
:disabled="isSelf"
@@ -265,6 +303,8 @@ onUnmounted(() => {
我发送的
</NButton>
</NSpace>
<!-- 验证码 -->
<VueTurnstile
ref="turnstile"
v-model="token"
@@ -272,6 +312,8 @@ onUnmounted(() => {
theme="auto"
style="text-align: center"
/>
<!-- 错误提示 -->
<NAlert
v-if="isSelf"
type="warning"
@@ -280,6 +322,8 @@ onUnmounted(() => {
</NAlert>
</NSpace>
</NCard>
<!-- 公开回复列表 -->
<NDivider> 公开回复 </NDivider>
<NList v-if="publicQuestions.length > 0">
<NListItem
@@ -291,6 +335,7 @@ onUnmounted(() => {
hoverable
size="small"
>
<!-- 问题头部 -->
<template #header>
<NSpace
:size="0"
@@ -308,11 +353,13 @@ onUnmounted(() => {
type="relative"
/>
</template>
<NTime />
<NTime :time="item.sendAt" />
</NTooltip>
</NText>
</NSpace>
</template>
<!-- 问题内容 -->
<NCard style="text-align: center">
{{ item.question.message }}
<br>
@@ -323,6 +370,8 @@ onUnmounted(() => {
lazy
/>
</NCard>
<!-- 回答内容 -->
<template
v-if="item.answer"
#footer