Commit 61a43a4a by lijiabin

【需求 17679】 feat: 加入富文本框,回复弹窗等内容

parent 22f0e398
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@vueuse/components": "^14.0.0", "@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0", "@vueuse/core": "^14.0.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"@wecom/jssdk": "^2.3.3", "@wecom/jssdk": "^2.3.3",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"axios": "^1.13.0", "axios": "^1.13.0",
......
...@@ -312,3 +312,44 @@ export const addComplaint = (data: { articleId: number; reason: string }) => { ...@@ -312,3 +312,44 @@ export const addComplaint = (data: { articleId: number; reason: string }) => {
data, data,
}) })
} }
/**
* 问吧-获取回答列表的评论(二级评论)
*/
export const getSecondCommentList = (data: { pId: number }) => {
return service.request<boolean>({
url: `/api/cultureComment/getQuestionComment`,
method: 'POST',
data,
})
}
/**
* 问吧 获取二级评论的子评论
*/
export const getSecondCommentChildren = (data: {
pid: number
current: number
size: number
articleId: number
}) => {
return service.request<BackendServicePageResult<CommentItemDto>>({
url: `/api/cultureComment/comment/children`,
method: 'POST',
data,
})
}
/**
* 根据commentid 获取这个评论的详情
*/
export const getCommentDetail = (id: number) => {
return service.request<CommentItemDto>({
url: `/api/cultureComment/questionCommentData`,
method: 'POST',
data: {
id,
},
})
}
...@@ -143,63 +143,77 @@ ...@@ -143,63 +143,77 @@
class="cursor-pointer hover:text-blue-500 transition-colors" class="cursor-pointer hover:text-blue-500 transition-colors"
@click="handleReply(item)" @click="handleReply(item)"
> >
回复 回复111111111
</button> </button>
</div> </div>
</div> </div>
<!-- 回复列表 --> <!-- 回复列表 -->
<div v-if="item.children?.length" class="mt-3 ml-4 space-y-3"> <!-- 问吧 和 其他 做区分 -->
<!-- 回复评论的内容 里面可能是 展示全部的 也有可能是展示5条之内容 --> <template v-if="!isQuestion">
<div <div v-if="item.children?.length" class="mt-3 ml-4 space-y-3">
v-for="child in getCurrentChildrenList(item)" <!-- 回复评论的内容 里面可能是 展示全部的 也有可能是展示5条之内容 -->
v-loading="item.loadingChildren" <div
:key="child.id" v-for="child in getCurrentChildrenList(item)"
class="flex gap-2 p-3 rounded-lg" v-loading="item.loadingChildren"
> :key="child.id"
<img class="flex gap-2 p-3 rounded-lg"
@click="handleUserInfo(child)" >
:src="child.avatar" <img
alt="" @click="handleUserInfo(child)"
class="w-8 h-8 rounded-full object-cover cursor-pointer" :src="child.avatar"
/> alt=""
<div class="flex-1"> class="w-8 h-8 rounded-full object-cover cursor-pointer"
<div class="flex items-center gap-2 mb-1"> />
<span class="font-medium text-sm text-gray-800" <div class="flex-1">
>{{ child.replyUser }} 回复 @{{ child.replyName }}</span <div class="flex items-center gap-2 mb-1">
> <span class="font-medium text-sm text-gray-800"
</div> >{{ child.replyUser }} 回复 @{{ child.replyName }}</span
<p class="text-gray-700 my-2 break-all"> >
{{ child.content }} </div>
</p> <p class="text-gray-700 my-2 break-all">
<div class="flex items-center justify-between"> {{ child.content }}
<div class="flex items-center gap-4 text-sm text-gray-500"> </p>
<span>{{ <div class="flex items-center justify-between">
dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss') <div class="flex items-center gap-4 text-sm text-gray-500">
}}</span> <span>{{
<div class="flex gap-2 items-center hover:text-blue-500"> dayjs(child.createTime * 1000).format('YYYY-MM-DD HH:mm:ss')
<div }}</span>
class="flex items-center gap-1 cursor-pointer" <div class="flex gap-2 items-center hover:text-blue-500">
@click="handleLickComment(child)" <div
> class="flex items-center gap-1 cursor-pointer"
<el-icon :size="16"> @click="handleLickComment(child)"
<svg-icon >
:name="child.hasPraise ? 'praise_fill' : 'praise'" <el-icon :size="16">
></svg-icon> <svg-icon
</el-icon> :name="child.hasPraise ? 'praise_fill' : 'praise'"
<span>{{ child.postPriseCount }}</span> ></svg-icon>
</el-icon>
<span>{{ child.postPriseCount }}</span>
</div>
</div> </div>
<button
@click="handleReply(child)"
class="cursor-pointer hover:text-blue-500 transition-colors"
>
回复
</button>
</div> </div>
<button
@click="handleReply(child)"
class="cursor-pointer hover:text-blue-500 transition-colors"
>
回复
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </template>
<!-- 问吧 如果有 子评论 直接变成 查看更多 -->
<template v-else>
<div v-if="item.childNum">
<button
class="cursor-pointer text-sm text-gray-500 mt-2"
@click="handleOpenCommentDialog(item)"
>
查看全部{{ item.childNum }} 条回复>
</button>
</div>
</template>
<!-- 只有大于5 才会显示这个 --> <!-- 只有大于5 才会显示这个 -->
<div class="ml-4" v-show="item.childrenNum > 5"> <div class="ml-4" v-show="item.childrenNum > 5">
<!-- 展示 展开回复 --> <!-- 展示 展开回复 -->
...@@ -293,27 +307,38 @@ ...@@ -293,27 +307,38 @@
</div> </div>
</div> </div>
</div> </div>
<CommentDialog ref="commentDialogRef" :articleId="id" :pid="currentDialogCommentPid" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { getCommentList, addOrCancelCommentLike, addComment, getCommentChildren } from '@/api' import {
getCommentList,
addOrCancelCommentLike,
addComment,
getCommentChildren,
getSecondCommentList,
} from '@/api'
import { usePageSearch, useScrollTop, useHintAnimation } from '@/hooks' import { usePageSearch, useScrollTop, useHintAnimation } from '@/hooks'
import { BooleanFlag } from '@/constants' import { BooleanFlag } from '@/constants'
import type { CommentItemDto } from '@/api' import type { CommentItemDto } from '@/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useUserStore } from '@/stores' import { useUserStore } from '@/stores'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import CommentDialog from '../CommentDialog/index.vue'
const { const {
id, id,
defaultSize = 10, defaultSize = 10,
isReal, isReal,
immediate = true, immediate = true,
isQuestion = false,
commentId = 0,
} = defineProps<{ } = defineProps<{
id: number | string id: number
defaultSize?: number defaultSize?: number
isReal: BooleanFlag isReal: BooleanFlag
isQuestion?: boolean // 如果是问题的话 展示有点不一样
immediate?: boolean immediate?: boolean
commentId?: number // 如果是问题的话 需要传入评论id
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
...@@ -326,8 +351,9 @@ const total = defineModel<number>('total', { required: true, default: 0 }) ...@@ -326,8 +351,9 @@ const total = defineModel<number>('total', { required: true, default: 0 })
const userStore = useUserStore() const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore) const { userInfo } = storeToRefs(userStore)
const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar)) const userAvatar = computed(() => (isReal ? userInfo.value.avatar : userInfo.value.hiddenAvatar))
console.log(userAvatar)
const commentRef = useTemplateRef<HTMLElement | null>('commentRef') const commentRef = useTemplateRef<HTMLElement | null>('commentRef')
const commentDialogRef = useTemplateRef<HTMLElement | null>('commentDialogRef')
const commentInputRef = useTemplateRef<HTMLElement | null>('commentInputRef') const commentInputRef = useTemplateRef<HTMLElement | null>('commentInputRef')
const commentItemRefList = ref<HTMLElement[]>([]) const commentItemRefList = ref<HTMLElement[]>([])
// 回滚到评论框 // 回滚到评论框
...@@ -340,11 +366,15 @@ const { triggerAnimation } = useHintAnimation(commentInputRef, { ...@@ -340,11 +366,15 @@ const { triggerAnimation } = useHintAnimation(commentInputRef, {
}) })
const { list, searchParams, goToPage, loading, changePageSize, refresh, search } = usePageSearch( const { list, searchParams, goToPage, loading, changePageSize, refresh, search } = usePageSearch(
getCommentList, isQuestion ? getSecondCommentList : getCommentList,
{ {
defaultParams: { defaultParams: {
articleId: id, ...(commentId
sortType: 2, ? { pid: commentId, sortType: 2 }
: {
articleId: id,
sortType: 2,
}),
}, },
defaultSize, defaultSize,
formatList(list: CommentItemDto[]) { formatList(list: CommentItemDto[]) {
...@@ -422,6 +452,7 @@ const handleMyComment = async () => { ...@@ -422,6 +452,7 @@ const handleMyComment = async () => {
await addComment({ await addComment({
articleId: id, articleId: id,
content: myComment.value, content: myComment.value,
...(commentId ? { pid: commentId } : {}),
}) })
ElMessage.success('发表评论成功') ElMessage.success('发表评论成功')
refresh() refresh()
...@@ -497,6 +528,12 @@ const handleUserInfo = (item: CommentItemDto) => { ...@@ -497,6 +528,12 @@ const handleUserInfo = (item: CommentItemDto) => {
router.push(`/otherUserPage/${item.userId}/${isReal}`) router.push(`/otherUserPage/${item.userId}/${isReal}`)
} }
const currentDialogCommentPid = ref(0)
const handleOpenCommentDialog = (item: CommentItemDto) => {
currentDialogCommentPid.value = item.id
commentDialogRef.value?.open()
}
defineExpose({ defineExpose({
scrollToCommentBox: () => handleBackTop(), scrollToCommentBox: () => handleBackTop(),
search: () => search(), search: () => search(),
......
<template>
<div style="border: 1px solid #ccc" class="h-full">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { uploadFile } from '@/api'
const mode = 'default'
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = defineModel<string>()
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>'
}, 1500)
})
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...', MENU_CONF: {} }
// 修改 uploadImage 菜单配置
editorConfig.MENU_CONF['uploadImage'] = {
customUpload: async (file, insertFn) => {
const { data } = await uploadFile(file)
console.log(data)
insertFn(data.data[0].filePath)
},
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
</script>
...@@ -6,6 +6,7 @@ export enum ArticleTypeEnum { ...@@ -6,6 +6,7 @@ export enum ArticleTypeEnum {
COLUMN = 'column', // 专栏 COLUMN = 'column', // 专栏
PRACTICE = 'practice', // 实践 PRACTICE = 'practice', // 实践
INTERVIEW = 'interview', // 专访 INTERVIEW = 'interview', // 专访
LONG_ARTICLE = 'longArticle', // 长文章
} }
// 发布状态枚举 // 发布状态枚举
......
...@@ -87,6 +87,7 @@ ...@@ -87,6 +87,7 @@
>专访</el-dropdown-item >专访</el-dropdown-item
> >
</el-dropdown-menu> </el-dropdown-menu>
<el-dropdown-item :command="ArticleTypeEnum.LONG_ARTICLE">长文章</el-dropdown-item>
</template> </template>
</el-dropdown> </el-dropdown>
</div> </div>
...@@ -154,6 +155,8 @@ const handlePost = async (type: ArticleTypeEnum) => { ...@@ -154,6 +155,8 @@ const handlePost = async (type: ArticleTypeEnum) => {
router.push('/publishVideo') router.push('/publishVideo')
} else if (type === ArticleTypeEnum.QUESTION) { } else if (type === ArticleTypeEnum.QUESTION) {
router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`) router.push(`/homePage/askTab#tabsRef?t=${Date.now()}`)
} else if (type === ArticleTypeEnum.LONG_ARTICLE) {
router.push('/publishLongArticle')
} else { } else {
PublishDialogRef.value?.open(type) PublishDialogRef.value?.open(type)
} }
......
...@@ -166,6 +166,11 @@ export const constantsRoute = [ ...@@ -166,6 +166,11 @@ export const constantsRoute = [
name: 'CultureQuestionDetail', name: 'CultureQuestionDetail',
component: () => import('@/views/questionDetail/index.vue'), component: () => import('@/views/questionDetail/index.vue'),
}, },
{
path: 'publishLongArticle',
name: 'CulturePublishLongArticle',
component: () => import('@/views/publishLongArticle/index.vue'),
},
// 发布文章 // 发布文章
// { // {
// { // {
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<!-- 问题标题 --> <!-- 问题标题 -->
<h2 <h2
class="text-xl line-clamp-1 font-semibold text-gray-900 mb-2 leading-relaxed cursor-pointer hover:text-blue-600 transition-colors" class="text-xl line-clamp-1 font-semibold text-gray-900 mb-2 leading-relaxed cursor-pointer hover:text-blue-600 transition-colors"
@click="router.push(`/questionDetail/${item.id}`)" @click="openNewPage(`/questionDetail/${item.id}`)"
> >
{{ item.title }} {{ item.title }}
</h2> </h2>
...@@ -158,6 +158,8 @@ ...@@ -158,6 +158,8 @@
:defaultSize="5" :defaultSize="5"
:isReal="0" :isReal="0"
:immediate="false" :immediate="false"
:isQuestion="true"
:commentId="item.cultureCommentListVo?.id"
@commentSuccess="() => handleCommentSuccess(item)" @commentSuccess="() => handleCommentSuccess(item)"
/> />
</Transition> </Transition>
...@@ -216,11 +218,15 @@ import { TABS_REF_KEY } from '@/constants' ...@@ -216,11 +218,15 @@ import { TABS_REF_KEY } from '@/constants'
import PublishBox from '@/components/common/PublishBox/index.vue' import PublishBox from '@/components/common/PublishBox/index.vue'
import { ArticleTypeEnum } from '@/constants' import { ArticleTypeEnum } from '@/constants'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { getArticleList, addOrCanceArticlelCollect, addOrCancelToAnswerList } from '@/api' import {
getArticleList,
addOrCanceArticlelCollect,
addOrCancelToAnswerList,
getSecondCommentList,
} from '@/api'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { useQuestionStore } from '@/stores/question' import { useQuestionStore } from '@/stores/question'
import ActionMore from '@/components/common/ActionMore/index.vue' import ActionMore from '@/components/common/ActionMore/index.vue'
import router from '@/router'
const { fetchUserQestionNum } = useQuestionStore() const { fetchUserQestionNum } = useQuestionStore()
...@@ -307,6 +313,10 @@ const handleExpand = (item: ArticleItemDto) => { ...@@ -307,6 +313,10 @@ const handleExpand = (item: ArticleItemDto) => {
item.isExpand = !item.isExpand item.isExpand = !item.isExpand
} }
const openNewPage = (path: string) => {
window.open(path)
}
// 是否打开漫游 // 是否打开漫游
watch( watch(
() => route.fullPath, () => route.fullPath,
...@@ -325,6 +335,10 @@ onActivated(async () => { ...@@ -325,6 +335,10 @@ onActivated(async () => {
} }
refresh() refresh()
}) })
onMounted(() => {
console.log('父组件onmounted')
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.fade-enter-from, .fade-enter-from,
......
<template>
<div class="min-h-screen bg-[#fff] p-6 font-sans">
<div class="max-w-7xl mx-auto">
<!-- 顶部面包屑或标题(可选) -->
<el-form :model="form" label-position="top" class="grid grid-cols-12 gap-6 items-start">
<!-- 左侧:沉浸式创作区 (占 9 列) -->
<div class="col-span-12 lg:col-span-9 space-y-6">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-8 min-h-[80vh]">
<!-- 标题输入:模拟大标题风格,去掉边框 -->
<el-form-item prop="title" class="mb-6 !border-b !border-gray-100 pb-2">
<el-input
v-model="form.title"
placeholder="请输入文章标题..."
class="title-input"
:maxlength="100"
show-word-limit
type="textarea"
:autosize="{ minRows: 1, maxRows: 2 }"
resize="none"
/>
</el-form-item>
<!-- 富文本编辑器 -->
<div class="editor-container">
<WangEditor v-model="form.content" style="height: 600px" />
</div>
</div>
</div>
<!-- 右侧:配置侧边栏 (占 3 列,吸顶) -->
<div class="col-span-12 lg:col-span-3 space-y-4 sticky top-4">
<!-- 卡片1:基础设置 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-5">
<div class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<div class="w-1 h-4 bg-blue-500 rounded-full"></div>
基础设置
</div>
<!-- 文章类型 -->
<el-form-item label="文章类型" prop="type">
<el-radio-group v-model="form.type" class="w-full grid grid-cols-3 gap-2">
<el-radio-button :value="ArticleTypeEnum.POST">帖子</el-radio-button>
<el-radio-button :value="ArticleTypeEnum.COLUMN">专栏</el-radio-button>
<el-radio-button :value="ArticleTypeEnum.INTERVIEW">专访</el-radio-button>
</el-radio-group>
</el-form-item>
<!-- 封面图 -->
<el-form-item label="封面图" prop="faceUrl">
<div class="w-full">
<UploadFile v-model="form.faceUrl" :limit="1" class="w-full" />
<div class="text-xs text-gray-400 mt-2">建议尺寸 16:9,支持 jpg/png</div>
</div>
</el-form-item>
</div>
<!-- 卡片2:高级配置 (专栏/专访特有) -->
<template
v-if="form.type === ArticleTypeEnum.COLUMN || form.type === ArticleTypeEnum.INTERVIEW"
>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-5">
<div class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<div class="w-1 h-4 bg-purple-500 rounded-full"></div>
专栏配置
</div>
<el-form-item label="所属栏目" prop="relateColumnId">
<el-select
v-model="form.relateColumnId"
placeholder="请选择专栏栏目"
class="w-full"
>
<el-option
v-for="item in columnList"
:key="item.id"
:value="item.id"
:label="item.title"
/>
</el-select>
</el-form-item>
<el-form-item label="主标签" prop="mainTagId">
<SelectTags v-model="form.mainTagId" class="w-full" />
</el-form-item>
<el-form-item label="副标签">
<SelectTags
v-model="form.tagList"
:filter-tags-fn="filterTagsFn"
:max-selected-tags="3"
class="w-full"
/>
</el-form-item>
<el-form-item label="推荐设置">
<div class="flex items-center justify-between w-full">
<span class="text-gray-600 text-sm">是否推荐</span>
<el-switch
v-model="form.isRecommend"
:active-value="BooleanFlag.YES"
:inactive-value="BooleanFlag.NO"
/>
</div>
</el-form-item>
<el-form-item v-if="form.type === ArticleTypeEnum.COLUMN">
<div class="flex items-center justify-between w-full">
<span class="text-gray-600 text-sm">同步同事吧</span>
<el-switch
v-model="form.isRelateColleague"
:active-value="BooleanFlag.YES"
:inactive-value="BooleanFlag.NO"
/>
</div>
</el-form-item>
</div>
</template>
<!-- 卡片3:发布设置 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-5">
<div class="font-bold text-gray-800 mb-4 flex items-center gap-2">
<div class="w-1 h-4 bg-orange-500 rounded-full"></div>
发布设置
</div>
<el-form-item prop="sendType" class="!mb-2">
<el-radio-group v-model="form.sendType" class="flex flex-col gap-3 w-full">
<div
class="flex items-center p-3 rounded-lg border cursor-pointer transition-all"
:class="
form.sendType === SendTypeEnum.IMMEDIATE
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300'
"
@click="form.sendType = SendTypeEnum.IMMEDIATE"
>
<el-radio :value="SendTypeEnum.IMMEDIATE" class="!mr-2">立即发布</el-radio>
<span class="text-xs text-gray-400 ml-auto">当前时间</span>
</div>
<div
class="flex flex-col p-3 rounded-lg border cursor-pointer transition-all"
:class="
form.sendType === SendTypeEnum.SCHEDULED
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300'
"
@click="form.sendType = SendTypeEnum.SCHEDULED"
>
<div class="flex items-center mb-2">
<el-radio :value="SendTypeEnum.SCHEDULED" class="!mr-2">定时发布</el-radio>
</div>
<el-date-picker
v-if="form.sendType === SendTypeEnum.SCHEDULED"
v-model="form.sendTime"
type="datetime"
placeholder="选择时间"
class="!w-full"
:disabled-date="
(time: Date) => time.getTime() < Date.now() - 1000 * 60 * 60 * 24
"
value-format="X"
size="small"
/>
</div>
</el-radio-group>
</el-form-item>
</div>
<div class="mb-6 flex items-center justify-end w-full">
<div class="flex gap-3">
<el-button type="primary" round class="!px-8 w-full!" @click="handlePublish">
发布
</el-button>
</div>
</div>
</div>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import WangEditor from '@/components/common/WangEditor/index.vue'
import { ArticleTypeEnum, SendTypeEnum, BooleanFlag, ReleaseStatusTypeEnum } from '@/constants'
import UploadFile from '@/components/common/UploadFile/index.vue'
import { useResetData } from '@/hooks'
import { useColumnStore } from '@/stores/column'
import { storeToRefs } from 'pinia'
import { addOrUpdateArticle } from '@/api'
// ... (逻辑部分保持不变,直接复用您的即可)
const columnStore = useColumnStore()
const { columnList } = storeToRefs(columnStore)
const [form, resetForm] = useResetData({
articleType: ArticleTypeEnum.POST,
title: '',
content: '',
faceUrl: '',
relateColumnId: null, // 建议初始值设为null或undefined,配合placeholder
mainTagId: '',
tagList: [],
isRelateColleague: BooleanFlag.NO,
isRecommend: BooleanFlag.NO,
sendType: SendTypeEnum.IMMEDIATE,
sendTime: '',
releaseStatus: ReleaseStatusTypeEnum.PUBLISH,
})
const filterTagsFn = (allTags: any[]) => {
return allTags.filter((tag) => tag.id !== Number(form.value.mainTagId))
}
const handlePublish = async () => {
const res = await addOrUpdateArticle(form.value)
console.log(res)
}
</script>
<style scoped lang="scss">
/* 覆盖 Element Plus 默认样式,使其更符合大标题风格 */
:deep(.title-input .el-textarea__inner) {
font-size: 24px;
font-weight: bold;
color: #333;
padding: 0;
border: none;
box-shadow: none;
background: transparent;
&::placeholder {
color: #a8abb2;
}
}
/* 隐藏 Radio Button 的圆点,改用卡片选择样式时需要 */
:deep(.el-radio-button__inner) {
border-radius: 8px !important;
border: 1px solid #dcdfe6;
border-left: 1px solid #dcdfe6 !important;
box-shadow: none !important;
padding: 8px 16px;
width: 100%;
}
:deep(.el-radio-button:first-child .el-radio-button__inner) {
border-left: 1px solid #dcdfe6;
}
:deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
background-color: #ecf5ff;
border-color: #409eff;
color: #409eff;
box-shadow: none;
}
/* 让侧边栏标签文字稍微小一点 */
:deep(.el-form-item__label) {
font-weight: 500;
color: #4b5563;
}
</style>
<script setup lang="ts">
import { ref } from 'vue'
import { CaretTop, CaretBottom, ChatDotRound, Share, Star, Plus } from '@element-plus/icons-vue'
// --- 模拟数据 ---
const tags = ref([
{ id: 1, name: 'React' },
{ id: 2, name: 'Vue.js' },
])
const question = ref({
title: 'vue转react是什么感受?',
description:
'最近在面试,有一家公司各方面都很满意,但是因为他们是做阿里巴巴网店模板,只能用react写,所以要考虑学习react,来知乎问问各位大佬意见...',
viewCount: '12,304',
followCount: 52,
})
const answers = ref([
{
id: 1,
user: {
name: '我们',
avatar: 'https://picsum.photos/id/64/100/100',
bio: '各位都脑测了就回复了,你们的一切脑测都是对的',
},
votes: 2,
content: '什么时候这群人才能明白框架没那么重要,重要的是逻辑能力和代码水平',
publishDate: '2025-10-13 11:42',
commentCount: 0,
},
{
id: 2,
user: {
name: '老富甲',
avatar: 'https://picsum.photos/id/1025/100/100',
bio: '程序员',
},
votes: 12,
content: `初入前端的时候,写的是 vue 2,非常简单的就入门了,然后就是自己研究html和css,对调样式很感兴趣,乐在其中。<br><br>换公司学 react,由于连 ts 都不会,同时学 react 官网和 ts 官网,两个官网都撸了4-5遍,差不多3天吧,就差不多能写基础的页面了,后续就是熟练度问题了,然后就是要学习各种组件库,其实还好,不会很难的,并且会了之后感觉很爽,比写vue爽了非常非常多。<br><br>写 react 我感觉就是比写 vue 更有激情,可能因为 vscode 对 ts、react 的插件体验更好点...`,
publishDate: '2025-10-12 18:20',
commentCount: 2,
},
])
const isFollowing = ref(false)
</script>
<template> <template>
<div class="min-h-screen p-6 font-sans text-gray-800 flex justify-center"> <div class="min-h-screen p-6 font-sans text-gray-800 flex justify-center">
<!-- <!--
...@@ -70,12 +22,12 @@ const isFollowing = ref(false) ...@@ -70,12 +22,12 @@ const isFollowing = ref(false)
<!-- 标题 --> <!-- 标题 -->
<h1 class="text-2xl font-bold text-gray-900 mb-3 leading-snug"> <h1 class="text-2xl font-bold text-gray-900 mb-3 leading-snug">
{{ question.title }} {{ questionDetail.title }}
</h1> </h1>
<!-- 描述 --> <!-- 描述 -->
<p class="text-gray-600 leading-relaxed mb-6 text-sm md:text-base"> <p class="text-gray-600 leading-relaxed mb-6 text-sm md:text-base">
{{ question.description }} {{ questionDetail.description }}
<span class="text-blue-600 cursor-pointer hover:underline text-sm font-medium ml-1" <span class="text-blue-600 cursor-pointer hover:underline text-sm font-medium ml-1"
>显示全部</span >显示全部</span
> >
...@@ -213,11 +165,23 @@ const isFollowing = ref(false) ...@@ -213,11 +165,23 @@ const isFollowing = ref(false)
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts">
import { getArticleDetail } from '@/api/article'
import type { ArticleItemDto } from '@/api/article/types'
<style scoped> const route = useRoute()
/* const questionId = route.params.id as string
样式微调:
1. 卡片边框改为 border-gray-100,这是一种极淡的边框,配合阴影,质感更细腻。 const questionDetail = ref<ArticleItemDto>({} as ArticleItemDto)
2. 字体大小微调:正文使用 text-sm md:text-base,让信息密度稍微高一点,更像知乎PC端的阅读体验。
*/ const getQuestionDetail = async () => {
</style> const res = await getArticleDetail(questionId)
console.log(res)
}
getQuestionDetail()
onMounted(() => {
getQuestionDetail()
})
</script>
<style scoped></style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment