Commit 741b36b7 by lijiabin

【需求 17679】 feat: 完成帖子(除视频外)二次编辑功能

parent a25a5702
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
8 8
</div> --> </div> -->
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h3 class="font-semibold text-gray-800">{{ articleDetail?.createUserName }}</h3> <h3 class="font-semibold text-gray-800">{{ articleDetail?.createUserName }}</h3>
...@@ -38,7 +39,16 @@ ...@@ -38,7 +39,16 @@
· {{ articleDetail?.viewCount || 0 }} 阅读 · {{ articleDetail?.viewCount || 0 }} 阅读
</p> </p>
</div> </div>
<!-- 再次编辑按钮 -->
<el-link
v-if="isAuthor"
type="primary"
:underline="false"
@click="router.push(`/publishLongArticle/${articleDetail.type}?id=${articleDetail.id}`)"
class="text-sm"
>
编辑
</el-link>
<!-- 优化后的右侧内容 --> <!-- 优化后的右侧内容 -->
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span <span
...@@ -112,15 +122,30 @@ import type { ArticleItemDto } from '@/api' ...@@ -112,15 +122,30 @@ import type { ArticleItemDto } from '@/api'
import { articleTypeListOptions, ArticleTypeEnum } from '@/constants' import { articleTypeListOptions, ArticleTypeEnum } from '@/constants'
import ActionMore from '@/components/common/ActionMore/index.vue' import ActionMore from '@/components/common/ActionMore/index.vue'
import { jumpToUserHomePage } from '@/utils' import { jumpToUserHomePage } from '@/utils'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const router = useRouter()
const { articleDetail } = defineProps<{ const { articleDetail } = defineProps<{
articleDetail: ArticleItemDto articleDetail: ArticleItemDto
isAudit: boolean // 是否是审核页面
}>() }>()
const articleType = computed(() => { const articleType = computed(() => {
return articleTypeListOptions.find((item) => item.value === articleDetail.type)?.label return articleTypeListOptions.find((item) => item.value === articleDetail.type)?.label
}) })
// 是否是作者
const isAuthor = computed(() => {
return articleDetail.createUserId === userInfo.value.userId
})
// 如果类型是帖子 专栏 和 专访 就是html
const isHtml = computed(() => { const isHtml = computed(() => {
return articleDetail.content?.includes('<') || articleDetail.content?.includes('</') return (
articleDetail.type === ArticleTypeEnum.POST ||
articleDetail.type === ArticleTypeEnum.COLUMN ||
articleDetail.type === ArticleTypeEnum.INTERVIEW
)
}) })
</script> </script>
...@@ -17,8 +17,6 @@ ...@@ -17,8 +17,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { uploadFile } from '@/api' import { uploadFile } from '@/api'
const mode = 'default' const mode = 'default'
...@@ -29,15 +27,14 @@ const editorRef = shallowRef() ...@@ -29,15 +27,14 @@ const editorRef = shallowRef()
// 内容 HTML // 内容 HTML
const valueHtml = defineModel<string>() const valueHtml = defineModel<string>()
// 模拟 ajax 异步获取内容 // 去掉上传视频的功能
onMounted(() => {
setTimeout(() => {
valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>'
}, 1500)
})
const toolbarConfig = {} const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...', MENU_CONF: {} } toolbarConfig.excludeKeys = ['group-video', 'group-more-video', 'group-more-video']
// 去掉上传视频
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {},
}
// 修改 uploadImage 菜单配置 // 修改 uploadImage 菜单配置
......
...@@ -88,7 +88,6 @@ ...@@ -88,7 +88,6 @@
>专访</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>
...@@ -143,23 +142,27 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => { ...@@ -143,23 +142,27 @@ const getSecondLevelKey = (route: RouteLocationNormalizedLoadedGeneric) => {
const key = Object.keys(route.params).length const key = Object.keys(route.params).length
? pathSegments.slice(0, 2).join('/') ? pathSegments.slice(0, 2).join('/')
: pathSegments.slice(0, 1).join('/') : pathSegments.slice(0, 1).join('/')
console.log(key, '*********************') // console.log(key, '*********************')
return key return key
} }
const notShowPath = ['/videoDetail', '/articleDetail', '/questionDetail']
const showOnlineTime = computed(() => { const showOnlineTime = computed(() => {
return !route.path.includes('/videoDetail') && !route.path.includes('/articleDetail') return notShowPath.every((path) => !route.path.includes(path))
}) })
const handlePost = async (type: ArticleTypeEnum) => { const handlePost = async (type: ArticleTypeEnum) => {
if (type === ArticleTypeEnum.VIDEO) { if (type === ArticleTypeEnum.VIDEO) {
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/${type}`)
router.push('/publishLongArticle') } else if (type === ArticleTypeEnum.PRACTICE) {
router.push(`/publishLongArticle/${type}`)
// PublishDialogRef.value?.open(type)
} else { } else {
PublishDialogRef.value?.open(type) router.push(`/publishLongArticle/${type}`)
} }
} }
const isDropdownHover = ref(false) const isDropdownHover = ref(false)
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
></ActionButtons> ></ActionButtons>
<div class="lg:col-span-3"> <div class="lg:col-span-3">
<!-- 帖子主体 --> <!-- 帖子主体 -->
<ArticleContent :articleDetail="articleDetail" /> <ArticleContent :articleDetail="articleDetail" :isAudit="false" />
<!-- 评论区 --> <!-- 评论区 -->
<Comment <Comment
...@@ -29,7 +29,7 @@ import { ArticleTypeEnum } from '@/constants' ...@@ -29,7 +29,7 @@ import { ArticleTypeEnum } from '@/constants'
const commentRef = useTemplateRef<typeof Comment | null>('commentRef') const commentRef = useTemplateRef<typeof Comment | null>('commentRef')
const route = useRoute() const route = useRoute()
const id = route.params.id as string const id = Number(route.params.id)
const isReal = computed(() => { const isReal = computed(() => {
return +( return +(
......
<template> <template>
<div v-loading="loading" class="px-20"> <div v-loading="loading">
<div class="lg:col-span-3 mb-20"> <div class="lg:col-span-3 mb-20">
<ArticleContent :articleDetail="articleDetail" /> <ArticleContent :articleDetail="articleDetail" :isAudit="true" />
</div> </div>
<!-- 底部fixed --> <!-- 底部fixed -->
<div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg"> <div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg">
......
<template> <template>
<div class="min-h-screen bg-[#fff] font-sans"> <div class="min-h-screen bg-gradient-to-br pb-10">
<div class="max-w-7xl mx-auto"> <!-- 主编辑区域 -->
<!-- 顶部面包屑或标题(可选) --> <div class="bg-white rounded-lg shadow-lg border border-gray-100/50 overflow-hidden">
<div class="px-10 py-5">
<el-form <!-- 标题输入 -->
ref="formRef" <el-form-item prop="title" class="mb-8">
:model="form"
:rules="rules"
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="!border-b !border-gray-100">
<el-input <el-input
v-model="form.title" v-model="form.title"
placeholder="请输入文章标题..." :placeholder="`在这里输入你的${text}标题...`"
class="title-input" class="title-input text-3xl font-bold"
show-word-limit show-word-limit
type="textarea" type="textarea"
:autosize="{ minRows: 1, maxRows: 3 }"
resize="none" resize="none"
/> />
</el-form-item> </el-form-item>
<!-- 富文本编辑器 --> <!-- 富文本编辑器 -->
<div class="editor-container"> <div>
<el-form-item prop="content" class="!border-b !border-gray-100"> <el-form-item prop="content">
<WangEditor v-model="form.content" style="height: 800px" /> <!-- 问吧和实践不是富文本 -->
<template
v-if="
form.type === ArticleTypeEnum.PRACTICE || form.type === ArticleTypeEnum.QUESTION
"
>
<el-input
:placeholder="`请输入${text}内容`"
v-model="form.content"
type="textarea"
:rows="30"
:maxlength="2000"
show-word-limit
/>
</template>
<template v-else>
<!-- 回显回来可能会有bug 所以需要key 重新渲染一下-->
<WangEditor :key v-model="form.content" class="min-h[90vh]" />
</template>
</el-form-item> </el-form-item>
</div> </div>
</div> </div>
</div> </div>
<!-- 右侧:配置侧边栏 (占 3 列,吸顶) --> <!-- 底部固定按钮栏 -->
<div class="col-span-12 lg:col-span-3 space-y-4"> <div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg">
<!-- 卡片1:基础设置 --> <div class="max-w-[1200px] mx-auto px-4 py-4">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-5"> <div class="flex justify-center gap-4">
<div class="font-bold text-gray-800 mb-4 flex items-center gap-2"> <el-button @click="handleClosed" class="rounded-lg min-w[120px]"> 取消 </el-button>
<div class="w-1 h-4 bg-blue-500 rounded-full"></div> <el-button
基础设置 type="primary"
:disabled="!canPublish"
@click="openDrawer"
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200 min-w[120px]"
>
{{ isEdit ? '编辑' : '发布' }}
</el-button>
</div>
</div>
</div> </div>
<!-- 右侧抽屉:发布配置 -->
<el-drawer
v-model="drawerVisible"
title="文章信息"
direction="rtl"
size="500px"
:before-close="handleDrawerClose"
>
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="px-4">
<!-- 基础设置 -->
<div class="mb-6">
<!-- 文章类型 --> <!-- 文章类型 -->
<el-form-item label="文章类型" prop="type"> <el-form-item label="文章类型" prop="type">
<el-radio-group <el-button
v-model="form.type" type="primary"
class="w-full grid grid-cols-3 gap-2" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200 min-w[120px]"
fill="#3b82f6"
> >
<el-radio-button :value="ArticleTypeEnum.POST">{{ {{ text }}
articleTypeListOptions.find((item) => item.value === type)?.label </el-button>
}}</el-radio-button>
</el-radio-group>
</el-form-item> </el-form-item>
<!-- 封面图 --> <!-- 内容图片 实践和问吧需要上传内容图片 然后默认第一张是封面图 -->
<template
v-if="form.type === ArticleTypeEnum.PRACTICE || form.type === ArticleTypeEnum.QUESTION"
>
<el-form-item label="图片">
<UploadFile v-model="form.imgUrl" :limit="1" class="w-full" />
</el-form-item>
</template>
<!-- 帖子 专栏专访 是富文本 需要上传封面图 封面图 -->
<template v-else>
<el-form-item label="封面图" prop="faceUrl"> <el-form-item label="封面图" prop="faceUrl">
<div class="w-full">
<UploadFile v-model="form.faceUrl" :limit="1" 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> </el-form-item>
</template>
<!-- 标签和主标签 除了帖子 都需要上传 -->
<template v-if="form.type !== ArticleTypeEnum.POST">
<el-form-item label="主标签" prop="mainTagId">
<SelectTags v-model="form.mainTagId" class="w-full" />
</el-form-item>
<el-form-item label="副标签 (最多3个)">
<SelectTags
v-model="form.tagList"
:filter-tags-fn="filterTagsFn"
:max-selected-tags="3"
class="w-full"
/>
</el-form-item>
</template>
</div> </div>
<!-- 卡片2:高级配置 (专栏/专访特有) --> <!-- 专栏配置 (条件显示) -->
<template <template
v-if="form.type === ArticleTypeEnum.COLUMN || form.type === ArticleTypeEnum.INTERVIEW" 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="mb-6">
<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-form-item label="所属栏目" prop="relateColumnId">
<el-select <el-select
v-model="form.relateColumnId" v-model="form.relateColumnId"
placeholder="请选择专栏栏目" :placeholder="`请选择${text}栏目`"
class="w-full" class="w-full"
> >
<el-option <el-option
v-for="item in columnList" v-for="item in relateColumnList"
:key="item.id" :key="item.id"
:value="item.id" :value="item.id"
:label="item.title" :label="item.title"
...@@ -90,112 +134,78 @@ ...@@ -90,112 +134,78 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="主标签" prop="mainTagId"> <!-- <el-form-item label="主标签" prop="mainTagId">
<SelectTags v-model="form.mainTagId" class="w-full" /> <SelectTags v-model="form.mainTagId" class="w-full" />
</el-form-item> </el-form-item>
<el-form-item label="副标签"> <el-form-item label="副标签 (最多3个)">
<SelectTags <SelectTags
v-model="form.tagList" v-model="form.tagList"
:filter-tags-fn="filterTagsFn" :filter-tags-fn="filterTagsFn"
:max-selected-tags="3" :max-selected-tags="3"
class="w-full" class="w-full"
/> />
</el-form-item> </el-form-item> -->
<el-form-item label="推荐设置"> <el-form-item label="是否推荐" prop="isRecommend">
<div class="flex items-center justify-between w-full"> <!-- 改为radio单选框 -->
<span class="text-gray-600 text-sm">是否推荐</span> <el-radio-group v-model="form.isRecommend">
<el-switch <el-radio :value="BooleanFlag.YES"></el-radio>
v-model="form.isRecommend" <el-radio :value="BooleanFlag.NO"></el-radio>
:active-value="BooleanFlag.YES" </el-radio-group>
:inactive-value="BooleanFlag.NO"
/>
</div>
</el-form-item> </el-form-item>
<el-form-item v-if="form.type === ArticleTypeEnum.COLUMN"> <el-form-item
<div class="flex items-center justify-between w-full"> label="同步同事吧"
<span class="text-gray-600 text-sm">同步同事吧</span> prop="isRelateColleague"
<el-switch v-if="form.type === ArticleTypeEnum.COLUMN"
v-model="form.isRelateColleague" >
:active-value="BooleanFlag.YES" <el-radio-group v-model="form.isRelateColleague">
:inactive-value="BooleanFlag.NO" <el-radio :value="BooleanFlag.YES"></el-radio>
/> <el-radio :value="BooleanFlag.NO"></el-radio>
</div> </el-radio-group>
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
<!-- 卡片3:发布设置 --> <!-- 发布设置 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-5"> <div class="mb-6">
<div class="font-bold text-gray-800 mb-4 flex items-center gap-2"> <el-form-item label="发布时间" prop="sendType">
<div class="w-1 h-4 bg-orange-500 rounded-full"></div> <el-radio-group v-model="form.sendType">
发布设置 <el-radio :value="SendTypeEnum.IMMEDIATE">立即发布</el-radio>
</div> <el-radio :value="SendTypeEnum.SCHEDULED">定时发布</el-radio>
</el-radio-group>
<el-form-item prop="sendType" class="!mb-2"> </el-form-item>
<el-radio-group v-model="form.sendType" class="flex flex-col gap-3 w-full"> <el-form-item v-if="form.sendType === SendTypeEnum.SCHEDULED" prop="sendTime">
<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 <el-date-picker
v-if="form.sendType === SendTypeEnum.SCHEDULED"
v-model="form.sendTime" v-model="form.sendTime"
type="datetime" type="datetime"
placeholder="选择时间" placeholder="选择发布时间"
class="!w-full" :disabled-date="(time: Date) => time.getTime() < Date.now() - 1000 * 60 * 60 * 24"
:disabled-date="
(time: Date) => time.getTime() < Date.now() - 1000 * 60 * 60 * 24
"
value-format="X" value-format="X"
size="small"
/> />
</div>
</el-radio-group>
</el-form-item> </el-form-item>
</div> </div>
<div class="mb-6 flex items-center justify-end w-full"> </el-form>
<div class="flex gap-3">
<el-button class="rounded-lg" @click="handleClosed">取消</el-button> <!-- 抽屉底部按钮 -->
<el-button class="rounded-lg" @click="handleSubmit(ReleaseStatusTypeEnum.DRAFT)"> <template #footer>
<div class="flex gap-3 justify-end">
<el-button @click="handleDrawerClose" class="rounded-lg">取消</el-button>
<el-button @click="handleSubmit(ReleaseStatusTypeEnum.DRAFT)" class="rounded-lg">
存草稿 存草稿
</el-button> </el-button>
<el-button <el-button
:loading="loading"
type="primary" type="primary"
@click="handleSubmit(ReleaseStatusTypeEnum.PUBLISH)" @click="handleSubmit(ReleaseStatusTypeEnum.PUBLISH)"
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white text-sm font-medium shadow-sm hover:shadow-md transition-all duration-200 min-w[120px]"
> >
发布 确认{{ isEdit ? '编辑' : '发布' }}
</el-button> </el-button>
</div> </div>
</div> </template>
</div> </el-drawer>
</el-form>
</div>
</div> </div>
</template> </template>
...@@ -212,22 +222,41 @@ import { ...@@ -212,22 +222,41 @@ import {
import UploadFile from '@/components/common/UploadFile/index.vue' import UploadFile from '@/components/common/UploadFile/index.vue'
import { useResetData } from '@/hooks' import { useResetData } from '@/hooks'
import { useColumnStore } from '@/stores/column' import { useColumnStore } from '@/stores/column'
import { useInterviewStore } from '@/stores/interview'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { addOrUpdateArticle } from '@/api' import { addOrUpdateArticle, addOrUpdatePractice, getArticleDetail } from '@/api'
// ... (逻辑部分保持不变,直接复用您的即可) import { useRouter, useRoute } from 'vue-router'
const columnStore = useColumnStore() const columnStore = useColumnStore()
const { columnList } = storeToRefs(columnStore) const { columnList } = storeToRefs(columnStore)
const interviewStore = useInterviewStore()
const { interviewList } = storeToRefs(interviewStore)
const loading = ref(false)
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const type = route.params.type as ArticleTypeEnum const type = route.params.type as ArticleTypeEnum
const text = articleTypeListOptions.find((item) => item.value === type)?.label
const canPublish = computed(() => {
return form.value.title && form.value.content
})
const relateColumnList = computed(() =>
type === ArticleTypeEnum.COLUMN ? columnList.value : interviewList.value,
)
// 抽屉控制
const drawerVisible = ref(false)
const formRef = useTemplateRef<FormInstance>('formRef') const formRef = useTemplateRef<FormInstance>('formRef')
const [form, resetForm] = useResetData({ const [form, resetForm] = useResetData({
type: type, type,
title: '', title: '',
content: '', content: '',
faceUrl: '', faceUrl: '',
relateColumnId: null, // 建议初始值设为null或undefined,配合placeholder imgUrl: '',
relateColumnId: null,
mainTagId: '', mainTagId: '',
tagList: [], tagList: [],
isRelateColleague: BooleanFlag.NO, isRelateColleague: BooleanFlag.NO,
...@@ -242,64 +271,179 @@ const rules = { ...@@ -242,64 +271,179 @@ const rules = {
content: [{ required: true, message: '请输入文章内容', trigger: 'blur' }], content: [{ required: true, message: '请输入文章内容', trigger: 'blur' }],
type: [{ required: true, message: '请选择文章类型', trigger: 'blur' }], type: [{ required: true, message: '请选择文章类型', trigger: 'blur' }],
faceUrl: [{ required: true, message: '请上传封面图', trigger: 'blur' }], faceUrl: [{ required: true, message: '请上传封面图', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传内容图片', trigger: 'blur' }],
sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }], sendType: [{ required: true, message: '请选择发布类型', trigger: 'blur' }],
sendTime: [{ required: true, message: '请选择发布时间', trigger: 'blur' }], sendTime: [{ required: true, message: '请选择发布时间', trigger: 'trigger' }],
releaseStatus: [{ required: true, message: '请选择发布状态', trigger: 'blur' }], releaseStatus: [{ required: true, message: '请选择发布状态', trigger: 'blur' }],
mainTagId: [{ required: true, message: '请选择主标签', trigger: 'blur' }],
isRecommend: [{ required: true, message: '是否推荐', trigger: 'trigger' }],
isRelateColleague: [{ required: true, message: '是否同步同事吧', trigger: 'trigger' }],
relateColumnId: [{ required: true, message: '请选择对应的栏目', trigger: 'trigger' }],
} }
const filterTagsFn = (allTags: any[]) => { const filterTagsFn = (allTags: any[]) => {
return allTags.filter((tag) => tag.id !== Number(form.value.mainTagId)) return allTags.filter((tag) => tag.id !== Number(form.value.mainTagId))
} }
const handleSubmit = async () => { const transFormData = (releaseStatus: ReleaseStatusTypeEnum) => {
const { tagList, ...data } = form.value
data.tagList = [data.mainTagId, ...tagList].map((item, index) => ({
tagId: Number(item),
sort: index,
}))
data.releaseStatus = releaseStatus
if (data.type === ArticleTypeEnum.PRACTICE || data.type === ArticleTypeEnum.QUESTION) {
// 手动设置一下封面图
if (data.imgUrl) {
data.faceUrl = data.imgUrl.split(',')[0]
}
}
return data
}
// 打开抽屉
const openDrawer = () => {
drawerVisible.value = true
}
// 关闭抽屉
const handleDrawerClose = () => {
drawerVisible.value = false
}
// 取消按钮
const handleClosed = () => {
resetForm()
router.back()
}
// 提交表单
const handleSubmit = async (releaseStatus: ReleaseStatusTypeEnum) => {
try { try {
await formRef.value.validate() await formRef.value?.validate()
const res = await addOrUpdateArticle(form.value) loading.value = true
const res =
form.value.type === ArticleTypeEnum.PRACTICE
? await addOrUpdatePractice(transFormData(releaseStatus))
: await addOrUpdateArticle(transFormData(releaseStatus))
console.log(res) console.log(res)
drawerVisible.value = false
resetForm()
router.back()
// 发布成功后的逻辑...
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} finally {
loading.value = false
} }
} }
const key = ref(0)
const isEdit = computed(() => !!route.query.id)
onActivated(async () => {
key.value++
resetForm()
await nextTick()
if (isEdit.value) {
console.log(route.query.id, '编辑')
// 要编辑回显
const { data } = await getArticleDetail(route.query.id)
// 首先回显基础的信息
// 标题 内容 主标签 副标签 所属栏目 是否推荐 是否同步同事吧 发布时间 发布状态
const {
id,
title,
content,
relateColumnId,
isRecommend,
isRelateColleague,
sendType,
sendTime,
tagIdList,
} = data
form.value = {
...form.value,
title,
content,
relateColumnId,
isRecommend,
isRelateColleague,
sendType,
sendTime,
}
// 回显主副标签
form.value.mainTagId = String(tagIdList[0]) || ''
form.value.tagList = tagIdList.slice(1) || []
const { imgUrl, faceUrl } = data
if (type === ArticleTypeEnum.QUESTION || type === ArticleTypeEnum.PRACTICE) {
form.value.imgUrl = imgUrl
} else {
form.value.faceUrl = faceUrl
}
}
})
</script> </script>
<style scoped lang="scss"> <style scoped>
/* 覆盖 Element Plus 默认样式,使其更符合大标题风格 */ /* 标题输入框样式 */
:deep(.title-input .el-textarea__inner) { :deep(.title-input .el-textarea__inner) {
font-size: 24px;
font-weight: bold;
color: #333;
padding: 0;
border: none; border: none;
box-shadow: none; padding: 0;
font-size: 2rem;
font-weight: 700;
line-height: 1.3;
color: #1f2937;
background: transparent; background: transparent;
box-shadow: none !important;
&::placeholder {
color: #a8abb2;
}
} }
/* 隐藏 Radio Button 的圆点,改用卡片选择样式时需要 */ :deep(.title-input .el-textarea__inner:focus) {
:deep(.el-radio-button__inner) { outline: none;
border-radius: 8px !important;
border: 1px solid #dcdfe6;
border-left: 1px solid #dcdfe6 !important;
box-shadow: none !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; :deep(.title-input .el-textarea__inner::placeholder) {
border-color: #409eff; color: #d1d5db;
color: #409eff; font-weight: 600;
box-shadow: none;
} }
/* 让侧边栏标签文字稍微小一点 */ /* 表单项标签样式 */
:deep(.el-form-item__label) { :deep(.el-form-item__label) {
font-weight: 500; font-weight: 500;
color: #4b5563; color: #374151;
font-size: 0.875rem;
}
/* 抽屉内容滚动 */
:deep(.el-drawer__body) {
padding: 20px 0;
}
/* 滚动条美化 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
} }
</style> </style>
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
class="bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100" class="bg-white rounded-lg p-6 shadow-[0_1px_3px_rgba(0,0,0,0.02)] border border-slate-100"
> >
<!-- 顶部标签行 --> <!-- 顶部标签行 -->
<div class="flex flex-wrap gap-2 mb-4"> <div class="flex flex-wrap gap-2 mb-4 justify-between">
<span <span
v-for="tag in questionDetail.tagNameList" v-for="tag in questionDetail.tagNameList"
:key="tag" :key="tag"
...@@ -14,6 +14,18 @@ ...@@ -14,6 +14,18 @@
> >
#{{ tag }} #{{ tag }}
</span> </span>
<!-- 后面加一个编辑 -->
<el-link
v-if="isAuthor"
type="primary"
:underline="false"
@click="
router.push(`/publishLongArticle/${questionDetail.type}?id=${questionDetail.id}`)
"
class="text-sm"
>
编辑
</el-link>
</div> </div>
<!-- 标题:主要信息,黑重粗 --> <!-- 标题:主要信息,黑重粗 -->
...@@ -78,6 +90,18 @@ ...@@ -78,6 +90,18 @@
</span> </span>
</div> </div>
</div> </div>
<!-- 展示图片相关 -->
<div v-if="questionDetail.imgUrl" class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<el-image
v-for="item in questionDetail.imgUrl.split(',')"
:key="item"
:src="item"
fit="cover"
class="rounded-lg w-full h-64 hover:scale-105 transition-transform cursor-pointer"
:preview-src-list="questionDetail.imgUrl.split(',')"
:preview-teleported="true"
/>
</div>
</div> </div>
<!-- 2. 列表控制栏 --> <!-- 2. 列表控制栏 -->
...@@ -241,14 +265,23 @@ import { usePageSearch } from '@/hooks' ...@@ -241,14 +265,23 @@ import { usePageSearch } from '@/hooks'
import Comment from '@/components/common/Comment/index.vue' import Comment from '@/components/common/Comment/index.vue'
import CommentDialog from '@/components/common/CommentDialog/index.vue' import CommentDialog from '@/components/common/CommentDialog/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const route = useRoute() const route = useRoute()
const questionId = Number(route.params.id) const router = useRouter()
const questionId = Number(route.params.id)
const commentRefList = ref<InstanceType<typeof Comment>[]>([]) const commentRefList = ref<InstanceType<typeof Comment>[]>([])
const questionDetail = ref<ArticleItemDto>({} as ArticleItemDto) const questionDetail = ref<ArticleItemDto>({} as ArticleItemDto)
const commentDialogRef = useTemplateRef<typeof CommentDialog>('commentDialogRef') const commentDialogRef = useTemplateRef<typeof CommentDialog>('commentDialogRef')
const isAuthor = computed(() => {
return questionDetail.value.createUserId === userInfo.value.userId
})
const questionContentRef = useTemplateRef<HTMLElement>('questionContentRef') const questionContentRef = useTemplateRef<HTMLElement>('questionContentRef')
const isExpand = ref(false) const isExpand = ref(false)
......
...@@ -53,6 +53,13 @@ ...@@ -53,6 +53,13 @@
@click="jumpToArticleDetailPage({ type: item.type, id: item.id })" @click="jumpToArticleDetailPage({ type: item.type, id: item.id })"
>查看</el-button >查看</el-button
> >
<el-button
v-if="item.type !== ArticleTypeEnum.VIDEO"
type="primary"
link
@click="jumpToEditPage({ type: item.type, id: item.id })"
>编辑</el-button
>
<el-button type="danger" link @click="handleDelete(item.id)">删除</el-button> <el-button type="danger" link @click="handleDelete(item.id)">删除</el-button>
</div> </div>
</div> </div>
...@@ -93,6 +100,8 @@ import type { TabPaneName } from 'element-plus' ...@@ -93,6 +100,8 @@ import type { TabPaneName } from 'element-plus'
import { IS_REAL_KEY } from '@/constants/symbolKey' import { IS_REAL_KEY } from '@/constants/symbolKey'
import { jumpToArticleDetailPage } from '@/utils' import { jumpToArticleDetailPage } from '@/utils'
const router = useRouter()
const isReal = inject(IS_REAL_KEY) const isReal = inject(IS_REAL_KEY)
const filterArticleType = computed(() => { const filterArticleType = computed(() => {
if (isReal?.value === 1) { if (isReal?.value === 1) {
...@@ -131,6 +140,14 @@ const handleDelete = async (articleId: number) => { ...@@ -131,6 +140,14 @@ const handleDelete = async (articleId: number) => {
ElMessage.success('删除成功') ElMessage.success('删除成功')
} }
const jumpToEditPage = (item: { type: ArticleTypeEnum; id: number }) => {
if (item.type === ArticleTypeEnum.VIDEO) {
router.push(`/publishVideo?id=${item.id}`)
} else {
router.push(`/publishLongArticle/${item.type}?id=${item.id}`)
}
}
defineExpose({ defineExpose({
refresh: () => { refresh: () => {
searchParams.value.type = filterArticleType.value[0]!.value searchParams.value.type = filterArticleType.value[0]!.value
......
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