Commit 9621cb79 by lijiabin

【需求 25171】refactor: 内容管理列表页的"查看内容"统一成共用弹窗组件

新增 ArticleContentDialog:自动检测内容里有没有真正的 html 标签,有就用
v-html 按富文本渲染,没有就原样当纯文本显示,避免纯文本里的 "<"、"&"
之类字符被误当成标签解析。

专栏/专访原来是 ElMessageBox.alert 写死当富文本渲染,问吧/帖子原来没有
查看入口(代码是注释掉的/纯 tooltip 省略号),实践的内容本来就是纯文本
也没有查看入口——这次 5 个页面统一接入这个共用弹窗,行为一致。
parent afa3c4f8
<template>
<el-dialog v-model="visible" title="内容" width="800px" append-to-body>
<el-scrollbar max-height="60vh">
<div v-if="isHtml" class="html-content" v-html="content"></div>
<div v-else class="plain-content">{{ content }}</div>
</el-scrollbar>
</el-dialog>
</template>
<script setup lang="ts">
/**
* 后台内容管理各个列表页"点击查看内容"共用的弹窗。
*
* 专栏/专访/问吧/帖子是富文本(WangEditor),内容本身就带 <p>、<span> 这些标签,要用 v-html
* 才能正常显示;实践的内容是纯文本 textarea 存的,直接当 html 用 v-html 渲染的话,用户内容里
* 随手打的 "<"、"&" 这些字符会被当成标签解析,显示错乱。所以这里不按"是哪个类型"区分,而是
* 直接检测内容里有没有真正的 html 标签,有就当富文本用 v-html,没有就当纯文本原样显示。
*/
const visible = ref(false)
const content = ref('')
// 粗略判断:内容里有没有形如 <xxx ...> 的标签。纯文本里正常打字基本不会凑出这个模式。
const HTML_TAG_REGEX = /<[a-z][^>]*>/i
const isHtml = computed(() => HTML_TAG_REGEX.test(content.value))
const open = (value: string) => {
content.value = value || ''
visible.value = true
}
defineExpose({ open })
</script>
<style scoped>
.plain-content {
white-space: pre-wrap;
word-break: break-all;
line-height: 1.6;
}
</style>
...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants' ...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { push } from 'notivue' import { push } from 'notivue'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import ArticleContentDialog from '@/components/common/ArticleContentDialog/index.vue'
const { prompt, confirm } = useMessageBox() const { prompt, confirm } = useMessageBox()
const contentDialogRef = useTemplateRef<InstanceType<typeof ArticleContentDialog>>('contentDialogRef')
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getArticleList, {
defaultParams: { defaultParams: {
...@@ -99,13 +101,9 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -99,13 +101,9 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 函数弹窗 v-html // 查看内容:是不是富文本由弹窗组件自己检测,这里只管传内容进去
const handleShowContent = (row: ArticleItemDto) => { const handleShowContent = (row: ArticleItemDto) => {
ElMessageBox.alert(row.content, '内容', { contentDialogRef.value?.open(row.content)
showCancelButton: false,
showConfirmButton: false,
dangerouslyUseHTMLString: true,
})
} }
// 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回 // 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回
...@@ -289,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -289,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => {
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
<ArticleContentDialog ref="contentDialogRef" />
</div> </div>
</template> </template>
......
...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants' ...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { push } from 'notivue' import { push } from 'notivue'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import ArticleContentDialog from '@/components/common/ArticleContentDialog/index.vue'
const { prompt, confirm } = useMessageBox() const { prompt, confirm } = useMessageBox()
const contentDialogRef = useTemplateRef<InstanceType<typeof ArticleContentDialog>>('contentDialogRef')
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getArticleList, {
defaultParams: { defaultParams: {
...@@ -100,13 +102,9 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -100,13 +102,9 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 函数弹窗 v-html // 查看内容:是不是富文本由弹窗组件自己检测,这里只管传内容进去
const handleShowContent = (row: ArticleItemDto) => { const handleShowContent = (row: ArticleItemDto) => {
ElMessageBox.alert(row.content, '内容', { contentDialogRef.value?.open(row.content)
showCancelButton: false,
showConfirmButton: false,
dangerouslyUseHTMLString: true,
})
} }
// 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回 // 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回
...@@ -290,6 +288,8 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -290,6 +288,8 @@ const handleRecallPush = async (row: ArticleItemDto) => {
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
<ArticleContentDialog ref="contentDialogRef" />
</div> </div>
</template> </template>
......
...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants' ...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { push } from 'notivue' import { push } from 'notivue'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import ArticleContentDialog from '@/components/common/ArticleContentDialog/index.vue'
const { prompt, confirm } = useMessageBox() const { prompt, confirm } = useMessageBox()
const contentDialogRef = useTemplateRef<InstanceType<typeof ArticleContentDialog>>('contentDialogRef')
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getArticleList, {
defaultParams: { defaultParams: {
...@@ -99,6 +101,11 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -99,6 +101,11 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 查看内容:是不是富文本由弹窗组件自己检测,这里只管传内容进去
const handleShowContent = (row: ArticleItemDto) => {
contentDialogRef.value?.open(row.content)
}
// 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回 // 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回
const pushStatusTextMap: Record<number, string> = { const pushStatusTextMap: Record<number, string> = {
0: '文章未设置推送', 0: '文章未设置推送',
...@@ -152,15 +159,12 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -152,15 +159,12 @@ const handleRecallPush = async (row: ArticleItemDto) => {
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column prop="title" label="标题" min-width="200" /> <el-table-column prop="title" label="标题" min-width="200" />
<el-table-column <el-table-column prop="content" label="内容" min-width="200">
prop="content" <template #default="{ row }">
label="内容" <el-link type="primary" :underline="false" @click="handleShowContent(row)">
min-width="200" 内容为富文本,点击查看
:show-overflow-tooltip="{ </el-link>
placement: 'top', </template>
popperClass: 'max-w-300',
}"
>
</el-table-column> </el-table-column>
<el-table-column prop="releaseStatus" label="标签" min-width="200"> <el-table-column prop="releaseStatus" label="标签" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
...@@ -283,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -283,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => {
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
<ArticleContentDialog ref="contentDialogRef" />
</div> </div>
</template> </template>
......
...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants' ...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { push } from 'notivue' import { push } from 'notivue'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import ArticleContentDialog from '@/components/common/ArticleContentDialog/index.vue'
const { prompt } = useMessageBox() const { prompt } = useMessageBox()
const contentDialogRef = useTemplateRef<InstanceType<typeof ArticleContentDialog>>('contentDialogRef')
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getArticleList, {
defaultParams: { defaultParams: {
...@@ -98,6 +100,11 @@ const selectedRows = ref<BackendColumnListItemDto[]>([]) ...@@ -98,6 +100,11 @@ const selectedRows = ref<BackendColumnListItemDto[]>([])
const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 查看内容:实践的内容是纯文本,不是富文本,是不是富文本由弹窗组件自己检测,这里只管传内容进去
const handleShowContent = (row: ArticleItemDto) => {
contentDialogRef.value?.open(row.content)
}
</script> </script>
<template> <template>
...@@ -124,15 +131,12 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -124,15 +131,12 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column prop="title" label="标题" min-width="200" /> <el-table-column prop="title" label="标题" min-width="200" />
<el-table-column <el-table-column prop="content" label="内容" min-width="200">
prop="content" <template #default="{ row }">
label="内容" <el-link type="primary" :underline="false" @click="handleShowContent(row)">
min-width="200" 点击查看内容
:show-overflow-tooltip="{ </el-link>
placement: 'top', </template>
popperClass: 'max-w-300',
}"
>
</el-table-column> </el-table-column>
<el-table-column prop="releaseStatus" label="标签" min-width="200"> <el-table-column prop="releaseStatus" label="标签" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
...@@ -229,6 +233,8 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -229,6 +233,8 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
<ArticleContentDialog ref="contentDialogRef" />
</div> </div>
</template> </template>
......
...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants' ...@@ -10,8 +10,10 @@ import { ArticleTypeEnum, BooleanFlag } from '@/constants'
import type { ArticleItemDto } from '@/api/article/types' import type { ArticleItemDto } from '@/api/article/types'
import { push } from 'notivue' import { push } from 'notivue'
import { useMessageBox } from '@/hooks' import { useMessageBox } from '@/hooks'
import ArticleContentDialog from '@/components/common/ArticleContentDialog/index.vue'
const { prompt, confirm } = useMessageBox() const { prompt, confirm } = useMessageBox()
const contentDialogRef = useTemplateRef<InstanceType<typeof ArticleContentDialog>>('contentDialogRef')
const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } = const { loading, list, total, reset, goToPage, changePageSize, refresh, searchParams, search } =
usePageSearch(getArticleList, { usePageSearch(getArticleList, {
defaultParams: { defaultParams: {
...@@ -99,6 +101,11 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => { ...@@ -99,6 +101,11 @@ const handleSelectionChange = (selection: BackendColumnListItemDto[]) => {
selectedRows.value = selection selectedRows.value = selection
} }
// 查看内容:是不是富文本由弹窗组件自己检测,这里只管传内容进去
const handleShowContent = (row: ArticleItemDto) => {
contentDialogRef.value?.open(row.content)
}
// 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回 // 推送状态文案:0 文章未设置推送 / 1 已推送 / 2 定时待推送 / 3 已全部撤回
const pushStatusTextMap: Record<number, string> = { const pushStatusTextMap: Record<number, string> = {
0: '文章未设置推送', 0: '文章未设置推送',
...@@ -152,20 +159,12 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -152,20 +159,12 @@ const handleRecallPush = async (row: ArticleItemDto) => {
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column prop="title" label="标题" min-width="200" /> <el-table-column prop="title" label="标题" min-width="200" />
<el-table-column <el-table-column prop="content" label="内容" min-width="200">
prop="content" <template #default="{ row }">
label="内容"
min-width="200"
:show-overflow-tooltip="{
placement: 'top',
popperClass: 'max-w-300',
}"
>
<!-- <template #default="{ row }">
<el-link type="primary" :underline="false" @click="handleShowContent(row)"> <el-link type="primary" :underline="false" @click="handleShowContent(row)">
内容为富文本,点击查看 内容为富文本,点击查看
</el-link> </el-link>
</template> --> </template>
</el-table-column> </el-table-column>
<el-table-column prop="releaseStatus" label="标签" min-width="200"> <el-table-column prop="releaseStatus" label="标签" min-width="200">
<template #default="{ row }"> <template #default="{ row }">
...@@ -288,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => { ...@@ -288,6 +287,8 @@ const handleRecallPush = async (row: ArticleItemDto) => {
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
<ArticleContentDialog ref="contentDialogRef" />
</div> </div>
</template> </template>
......
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