feat:更新博客评论模块

This commit is contained in:
2025-06-22 21:01:49 +08:00
parent b9c18375f5
commit b4e0d8ebc2
13 changed files with 366 additions and 277 deletions

177
package-lock.json generated
View File

@@ -29,9 +29,11 @@
"lunar-javascript": "^1.7.2",
"md-editor-v3": "^4.21.3",
"pinia": "^2.3.1",
"ua-parser-js": "^2.0.3",
"v-calendar": "^3.1.2",
"vue": "3.5.12",
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
"vue3-emoji-picker": "^1.1.8",
"vue3-eslint": "git+https://gitee.com/Cxx0822/vue3-eslint"
},
"devDependencies": {
@@ -2737,12 +2739,21 @@
"version": "18.19.86",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.86.tgz",
"integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/resize-observer-browser": {
"version": "0.1.11",
"resolved": "https://registry.npmmirror.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz",
@@ -3519,8 +3530,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/atob": {
"version": "2.1.2",
@@ -4019,7 +4029,6 @@
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"peer": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -4488,11 +4497,30 @@
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-europe-js": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
"integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
@@ -5848,7 +5876,6 @@
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -6372,6 +6399,12 @@
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/idb": {
"version": "7.1.1",
"resolved": "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz",
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
"license": "ISC"
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
@@ -6844,6 +6877,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-standalone-pwa": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz",
"integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/is-string": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz",
@@ -7400,7 +7453,6 @@
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6"
}
@@ -7410,7 +7462,6 @@
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -7636,6 +7687,26 @@
"license": "MIT",
"optional": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
@@ -10207,6 +10278,12 @@
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"license": "MIT"
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/traverse": {
"version": "0.6.11",
"resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz",
@@ -10406,6 +10483,59 @@
"node": ">=14.17"
}
},
"node_modules/ua-is-frozen": {
"version": "0.1.2",
"resolved": "https://registry.npmmirror.com/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz",
"integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
},
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"license": "MIT"
},
"node_modules/ua-parser-js": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/ua-parser-js/-/ua-parser-js-2.0.3.tgz",
"integrity": "sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "AGPL-3.0-or-later",
"dependencies": {
"@types/node-fetch": "^2.6.12",
"detect-europe-js": "^0.1.2",
"is-standalone-pwa": "^0.1.1",
"node-fetch": "^2.7.0",
"ua-is-frozen": "^0.1.2"
},
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
@@ -10434,7 +10564,6 @@
"version": "5.26.5",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT"
},
"node_modules/union-value": {
@@ -11406,6 +11535,20 @@
"vue-router": "^4.2.2"
}
},
"node_modules/vue3-emoji-picker": {
"version": "1.1.8",
"resolved": "https://registry.npmmirror.com/vue3-emoji-picker/-/vue3-emoji-picker-1.1.8.tgz",
"integrity": "sha512-k9tVHeQEBVLzVCLYAkFaI1nib3FJFQwdPhWD5khJkhks3ktg3g12z5wPGOSDpIuSLNtelRGvq1qdmZuJu5khfA==",
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.0",
"idb": "^7.1.0",
"vue": "^3.2.23"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/vue3-eslint": {
"version": "1.0.0",
"resolved": "git+https://gitee.com/Cxx0822/vue3-eslint#849a4e0e783d3ecc57035fb2f92950998b6b2e41",
@@ -11444,6 +11587,22 @@
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/whatwg-url/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",

View File

@@ -29,6 +29,7 @@
"lunar-javascript": "^1.7.2",
"md-editor-v3": "^4.21.3",
"pinia": "^2.3.1",
"ua-parser-js": "^2.0.3",
"v-calendar": "^3.1.2",
"vue": "3.5.12",
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",

View File

@@ -1,5 +1,5 @@
import { cloudService } from './index'
import { IBlog, IBlogCategory, IBlogStats, IBlogVisit, INewBlog, IQueryBlog } from '@/types/blog'
import { IBlog, IBlogCategory, IBlogComment, IBlogStats, IBlogVisit, INewBlog, IQueryBlog } from '@/types/blog'
import qs from 'qs'
import { IPageRecord } from '@/types'
@@ -45,6 +45,19 @@ export const queryLatestBlogApi = (): Promise<IBlog[]> =>
method: 'get'
})
export const queryBlogCommentApi = (blogId: number): Promise<IBlogComment[]> =>
cloudService({
url: `/blog-service/blog/comment/${blogId}`,
method: 'get'
})
export const addBlogCommentApi = (blogComment: IBlogComment): Promise<boolean> =>
cloudService({
url: '/blog-service/blog/comment',
method: 'put',
data: blogComment
})
export const queryAdminBlogApi = (currentPage: number,
pageSize: number): Promise<IPageRecord<IBlog>> =>
cloudService({

View File

@@ -13,16 +13,9 @@
<script setup lang="ts">
import BlogCommentItem from '@/components/Blog/Comment/BlogCommentItem.vue'
import { onMounted } from 'vue'
import { useBlogStore } from '@/store/blog'
import { flattenToTwoLevel } from '@/utils/blog/blogUtil'
const blogStore = useBlogStore()
onMounted(() => {
blogStore.queryBlogComment(blogStore.currentBlogId)
blogStore.blogNestedCommentList = flattenToTwoLevel(blogStore.blogCommentList)
})
</script>
<style lang="scss">

View File

@@ -2,13 +2,15 @@
<div class="blog-comment-edit">
<div class="comment-title">
<el-input v-model="blogComment.name"
placeholder="请输入Github用户名"/>
:maxlength="10" show-word-limit
placeholder="请输入昵称"/>
<el-input v-model="blogComment.website"
:maxlength="50" show-word-limit
placeholder="请输入个人博客网站"/>
</div>
<el-input v-model="blogComment.content"
placeholder="有什么想和我说的呢" maxlength="500"
placeholder="有什么想和我说的呢" :maxlength="500"
:rows="7" show-word-limit type="textarea"
/>
@@ -28,6 +30,7 @@
<el-button type="primary"
:disabled="blogComment.content.length === 0"
@click="sendCommentClick"
class="send-button">发送</el-button>
</div>
</div>
@@ -36,24 +39,59 @@
<script setup lang="ts">
import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css'
import { defineProps, ref, onMounted } from 'vue'
import { useBlogStore } from '@/store/blog.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { IBlogComment } from '@/types/blog.ts'
import { reactive, ref } from 'vue'
const blogStore = useBlogStore()
const emojiPopoverRef = ref()
const blogComment = reactive({
const props = defineProps({
parentId: {
required: true,
type: Number,
default: 0
}
})
const blogComment = ref<IBlogComment>({
id: 0,
blogId: 0,
name: '',
website: '',
content: ''
content: '',
ipAddress: '',
userAgent: '',
parentId: 0,
isApproved: true,
createTime: ''
})
onMounted(() => {
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
})
const onSelectEmoji = (emoji) => {
if (blogComment.content.length <= 498) {
blogComment.content += emoji.i
if (blogComment.value.content.length <= 498) {
blogComment.value.content += emoji.i
}
emojiPopoverRef.value.hide()
}
const sendCommentClick = async () => {
blogComment.value.parentId = props.parentId
blogComment.value.blogId = blogStore.currentBlogId
if (!blogComment.value.name) {
blogComment.value.name = 'Anonymous'
}
await blogStore.addBlogComment(blogComment.value)
blogComment.value = deepCopyObject(blogStore.getEmptyBlogComment())
blogStore.isShowBlogCommentEdit = false
}
</script>
<style lang="scss">

View File

@@ -1,12 +1,13 @@
<template>
<div class="blog-comment-card">
<el-avatar :size="50" :src="`https://github.com/${props.comment.name}.png`" alt="未加载"/>
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${props.comment.name}`"
:size="50" alt="未加载"/>
<div class="blog-comment-content">
<div class="basic-info">
<span>{{ props.comment.name }}</span>
<span class="content">Chrome 137.0.0.0</span>
<span class="content">macOS 10.15.7</span>
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
</div>
<div class="sub-info">
@@ -19,17 +20,19 @@
<p>{{ props.comment.content }}</p>
<blog-comment-edit v-if="blogComment.isShowEdit"/>
<blog-comment-edit v-if="blogComment.isShowEdit"
:parent-id="props.comment.id"/>
<div class="reply-list">
<div class="blog-comment-card" v-for="(item, index) in props.replyList" :key="index">
<el-avatar :size="50" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'" />
<el-avatar :src="`https://api.dicebear.com/6.x/adventurer/svg?seed=${item.name}`"
:size="50" alt="未加载"/>
<div class="blog-comment-content">
<div class="basic-info">
<span>{{ item.name }}</span>
<span class="content">Chrome 137.0.0.0</span>
<span class="content">macOS 10.15.7</span>
<span class="content">{{ getBrowser(props.comment.userAgent) }}</span>
<span class="content">{{ getOsInfo(props.comment.userAgent) }}</span>
</div>
<div class="sub-info">
@@ -48,12 +51,18 @@ import BlogCommentEdit from '@/components/Blog/Comment/BlogCommentEdit.vue'
import 'vue3-emoji-picker/css'
import { defineProps, PropType, reactive } from 'vue'
import { IBlogComment } from '@/types/blog'
import { useBlogStore } from '@/store/blog.ts'
import { UAParser } from 'ua-parser-js'
const blogStore = useBlogStore()
const props = defineProps({
comment: {
required: true,
type: Object as PropType<IBlogComment>
},
replyList: {
required: true,
type: Object as PropType<IBlogComment[]>
}
})
@@ -63,12 +72,23 @@ const blogComment = reactive({
})
const replyClick = () => {
blogStore.currentBlogComment = blogStore.getEmptyBlogComment()
blogComment.isShowEdit = true
}
const cancelClick = () => {
blogComment.isShowEdit = false
}
const getOsInfo = (userAgent: string) => {
const uaParse = new UAParser(userAgent)
return `${uaParse.getOS().name} ${uaParse.getOS().version}`
}
const getBrowser = (userAgent: string) => {
const uaParse = new UAParser(userAgent)
return `${uaParse.getBrowser().name} ${uaParse.getBrowser().version}`
}
</script>
<style lang="scss">

View File

@@ -29,7 +29,7 @@
<span>-------------本文结束感谢您的阅读给个五星好评吧~~-------------</span>
</div>
<blog-comment-edit />
<blog-comment-edit :parent-id="0"/>
<blog-comment />
</div>
@@ -44,15 +44,19 @@ import 'md-editor-v3/lib/style.css'
import { useBlogStore } from '@/store/blog.ts'
import { getBasicBlog } from '@/utils/blog/blogUtil.ts'
import { useRoute } from 'vue-router'
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
const blogStore = useBlogStore()
const route = useRoute()
const currentUrl = new URL(window.location.href)
const blogComment = computed(() => blogStore.getEmptyBlogComment())
onMounted(async () => {
await blogStore.queryBlogById(route.params.id as number)
const blogId = route.params.id as number
blogStore.currentBlogId = blogId
await blogStore.queryBlogById(blogId)
})
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div class="title-bar">
<div class="left-container">
<el-button v-if="appStore.isShowMobileBack"
type="info" :icon="Back" size="small"
circle color="#FFFFFF"
@click="goBackClick" />
<div v-else class="logo-container">
<img :src="getAssetsImageFile('logo/logo.png')" alt="暂无图片" class="logo"/>
<span>{{ appStore.appName }}</span>
</div>
</div>
<span class="title-container">{{ title }}</span>
<div class="right-container">
<svg-icon v-if="appStore.isWebSocket"
name="home-websocket" style="color: #FFFFFF;"></svg-icon>
<el-button type="info" :icon="Setting" size="small"
circle color="#FFFFFF" @click="openSettingClick"/>
<el-drawer
v-model="appStore.isShowSetting"
title="系统设置" :show-close="false"
size="50%" class="setting-drawer">
<mobile-setting />
</el-drawer>
</div>
</div>
</template>
<script lang="ts" setup>
import { SvgIcon } from 'vue3-common'
import MobileSetting from '@/components/Mobile/MobileSetting.vue'
import { Back } from '@element-plus/icons-vue'
import { Setting } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app.ts'
import { computed } from 'vue'
import { getAssetsImageFile } from '@/utils'
const appStore = useAppStore()
const router = useRouter()
const title = computed(() => router.currentRoute.value.meta.title)
const goBackClick = () => {
appStore.isShowMobileBack = false
history.back()
}
const openSettingClick = () => {
appStore.isShowSetting = true
}
</script>
<style lang="scss">
.setting-drawer {
.el-drawer__header {
margin-bottom: 0;
}
}
</style>

View File

@@ -1,33 +1,6 @@
<template>
<section class="layout-mobile-home">
<div class="title-bar">
<div class="left-container">
<el-button v-if="appStore.isShowMobileBack"
type="info" :icon="Back" size="small"
circle color="#FFFFFF"
@click="goBackClick" />
<div v-else class="logo-container">
<img :src="getAssetsImageFile('logo/logo.png')" alt="暂无图片" class="logo"/>
<span>Sweet Hut</span>
</div>
</div>
<span class="title-container">{{ title }}</span>
<div class="right-container">
<svg-icon v-if="appStore.isWebSocket"
name="home-websocket" style="color: #FFFFFF;"></svg-icon>
<el-button type="info" :icon="Setting" size="small"
circle color="#FFFFFF" @click="openSettingClick"/>
<el-drawer
v-model="appStore.isShowSetting"
title="系统设置" :show-close="false"
size="50%" class="setting-drawer">
<mobile-setting />
</el-drawer>
</div>
</div>
<title-bar />
<router-view />
@@ -36,37 +9,10 @@
</template>
<script lang="ts" setup>
import { SvgIcon } from 'vue3-common'
import MobileSetting from '@/components/Mobile/MobileSetting.vue'
import { Back } from '@element-plus/icons-vue'
import { Setting } from '@element-plus/icons-vue'
import TitleBar from '@/layout/mobile-home/TitleBar.vue'
import TabBar from '@/layout/mobile-home/TabBar.vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app.ts'
import { computed } from 'vue'
import { getAssetsImageFile } from '@/utils'
const appStore = useAppStore()
const router = useRouter()
const title = computed(() => router.currentRoute.value.meta.title)
const goBackClick = () => {
appStore.isShowMobileBack = false
history.back()
}
const openSettingClick = () => {
appStore.isShowSetting = true
}
</script>
<style lang="scss">
@use "@/styles/mobile.home.layout.module.scss";
.setting-drawer {
.el-drawer__header {
margin-bottom: 0;
}
}
</style>

View File

@@ -4,6 +4,7 @@ import { useDark, useToggle } from '@vueuse/core'
export const useAppStore = defineStore('ipp', {
state: () => ({
appName: 'Sweet Hut',
sidebar: {
// !!:转为布尔值
// 保存当前菜单栏显示和隐藏的状态

View File

@@ -4,19 +4,32 @@ import {
IBlogLatest, IBlogNestedComment, IBlogStats, INewBlog, IQueryBlog
} from '@/types/blog'
import {
addBlogApi, deleteBlogApi, queryAdminBlogApi, queryAdminBlogByIdApi, queryAdminBlogCategoryApi,
addBlogApi, addBlogCommentApi, deleteBlogApi, queryAdminBlogApi, queryAdminBlogByIdApi, queryAdminBlogCategoryApi,
queryBlogApi, queryBlogByConditionApi, queryBlogByIdApi,
queryBlogCategoryApi, queryBlogStatsApi, queryLatestBlogApi, updateBlogApi
queryBlogCategoryApi, queryBlogCommentApi, queryBlogStatsApi, queryLatestBlogApi, updateBlogApi
} from '@/apis/blog.ts'
import { IHandleApi } from 'vue3-common/types'
import { ElMessage } from 'element-plus'
import { ImagePathEnum } from '@/types'
import { flattenToTwoLevel } from '@/utils/blog/blogUtil.ts'
export const useBlogStore = defineStore('blog', {
state: () => ({
blogList: [] as IBlog[],
blogCommentList: [] as IBlogComment[],
blogNestedCommentList: [] as IBlogNestedComment[],
currentBlogComment: {
id: 0,
blogId: 0,
name: '',
website: '',
content: '',
ipAddress: '',
userAgent: '',
parentId: 0,
isApproved: true,
createTime: ''
} as IBlogComment,
currentBlogId: 0,
currentBlog: {
id: 0,
@@ -77,193 +90,18 @@ export const useBlogStore = defineStore('blog', {
},
async queryBlogById(id: number) {
this.currentBlog = await queryBlogByIdApi(id)
await this.queryBlogComment(this.currentBlogId)
this.blogNestedCommentList = flattenToTwoLevel(this.blogCommentList)
},
async queryAdminBlogById(id: number) {
this.currentBlog = await queryAdminBlogByIdApi(id)
},
async queryBlogComment(blogId: number) {
this.blogCommentList = [
{
id: 1,
blogId,
name: 'Cxx0822',
website: 'https://zhangsan.com',
content: '这篇文章写得非常好,对我理解前端框架很有帮助,期待更多类似内容!',
ipAddress: '114.114.114.114',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
parentId: 0,
isApproved: true,
createTime: '2025-06-15 08:30:22'
this.blogCommentList = await queryBlogCommentApi(blogId)
},
{
id: 2,
blogId,
name: '李四',
website: '',
content: '请问文中提到的性能优化方案在实际项目中效果如何?有没有遇到什么坑?',
ipAddress: '127.0.0.1',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.2; rv:109.0) Gecko/20100101 Firefox/114.0',
parentId: 0,
isApproved: true,
createTime: '2025-06-15 14:15:47'
},
{
id: 3,
blogId,
name: '作者',
website: 'https://blog.example.com',
content: '回复 @李四:实际应用中效果显著,但需要注意兼容性问题,特别是旧版本浏览器的支持情况。',
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0',
parentId: 2,
isApproved: true,
createTime: '2025-06-16 09:22:10'
},
{
id: 4,
blogId,
name: '王五',
website: 'https://wangwu.dev',
content: '能否分享一下文章中提到的代码示例?想学习具体实现方式。',
ipAddress: '203.0.113.5',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15',
parentId: 0,
isApproved: true,
createTime: '2025-06-16 16:38:55'
},
{
id: 5,
blogId,
name: '赵六',
website: '',
content: '前端性能优化确实是个重要课题,我在项目中也遇到过类似问题,感谢分享经验!',
ipAddress: '103.235.46.133',
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
parentId: 0,
isApproved: true,
createTime: '2025-06-17 10:12:30'
},
{
id: 6,
blogId,
name: '林七',
website: 'https://linqi.com',
content: '这个框架的状态管理方案看起来很新颖和Redux相比有什么优势吗',
ipAddress: '172.16.31.10',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
parentId: 0,
isApproved: true,
createTime: '2025-06-10 15:45:18'
},
{
id: 7,
blogId,
name: '作者',
website: 'https://blog.example.com',
content: '回复 @林七最大的优势是更简洁的API和更好的TypeScript支持尤其适合小型到中型项目。',
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0',
parentId: 6,
isApproved: true,
createTime: '2025-06-11 09:10:22'
},
{
id: 8,
blogId,
name: '孙八',
website: 'https://sunba.dev',
content: '已经按照文章中的步骤实践了效果不错不过在SSR场景下遇到了一些问题...',
ipAddress: '192.168.3.11',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.2; rv:109.0) Gecko/20100101 Firefox/114.0',
parentId: 0,
isApproved: true,
createTime: '2025-06-12 11:22:35'
},
{
id: 9,
blogId,
name: '周九',
website: '',
content: '请问文章中的示例代码是否支持IE11我们项目还在使用旧浏览器。',
ipAddress: '172.16.58.3',
userAgent: 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko',
parentId: 0,
isApproved: false, // 未审核
createTime: '2025-06-08 08:08:08'
},
{
id: 10,
blogId,
name: '吴十',
website: 'https://wushi.net',
content: '前端工程化真的很重要,之前项目因为没有规范导致维护困难,感谢分享最佳实践!',
ipAddress: '192.168.127.12',
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
parentId: 0,
isApproved: true,
createTime: '2025-06-09 14:30:45'
},
{
id: 11,
blogId,
name: '郑十一',
website: '',
content: '回复 @周九需要添加polyfill具体可以参考这篇文档https://example.com/ie-support',
ipAddress: '172.16.17.32',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
parentId: 9,
isApproved: false, // 未审核
createTime: '2025-06-08 09:15:22'
},
{
id: 12,
blogId,
name: '钱十二',
website: 'https://qianshier.com',
content: '这个UI组件库的设计风格我很喜欢请问有移动端适配方案吗',
ipAddress: '10.10.10.10',
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15',
parentId: 0,
isApproved: true,
createTime: '2025-06-05 16:20:10'
},
{
id: 13,
blogId,
name: '作者',
website: 'https://blog.example.com',
content: '回复 @钱十二:有的,组件库内置了响应式设计,支持移动端自适应布局。',
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0',
parentId: 12,
isApproved: true,
createTime: '2025-06-06 10:05:33'
},
{
id: 14,
blogId,
name: '冯十三',
website: 'https://fengshisan.dev',
content: '文档很详细但是在Tree Shaking时遇到了问题请问如何配置',
ipAddress: '192.168.3.11',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13.2; rv:109.0) Gecko/20100101 Firefox/114.0',
parentId: 0,
isApproved: true,
createTime: '2025-06-07 09:45:15'
},
{
id: 15,
blogId,
name: '陈十四',
website: '',
content: '性能测试部分的数据很有参考价值,能否分享一下测试环境和工具?',
ipAddress: '172.16.31.10',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
parentId: 0,
isApproved: true,
createTime: '2025-06-04 14:12:28'
}
]
async addBlogComment(blogComment: IBlogComment) {
await addBlogCommentApi(blogComment)
await this.queryBlogById(blogComment.blogId)
},
async queryBlogCategory() {
this.blogCategoryList = await queryBlogCategoryApi()
@@ -311,6 +149,20 @@ export const useBlogStore = defineStore('blog', {
title: '',
topValue: 0
}
},
getEmptyBlogComment(): IBlogComment {
return {
blogId: 0,
content: '',
createTime: '',
id: 0,
ipAddress: '',
isApproved: true,
name: '',
parentId: 0,
userAgent: '',
website: ''
}
}
}
})

View File

@@ -4,7 +4,7 @@ export enum ImagePathEnum {
FOOD = 'food',
TRAVEL = 'travel',
PRODUCT = 'product',
JOURNAL = 'JOURNAL',
JOURNAL = 'journal',
LEDGER = 'ledger',
USER = 'user'
}

View File

@@ -192,4 +192,3 @@ export function flattenToTwoLevel(comments: IBlogComment[]): IBlogNestedComment[
return result
}