|
|
@@ -1,10 +1,33 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="never" class="cate-sidebar">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>文章分类</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="cate-tree-wrapper">
|
|
|
+ <el-tree
|
|
|
+ ref="cateTreeRef"
|
|
|
+ :data="sidebarTreeData"
|
|
|
+ :props="{ label: 'cateName', children: 'children' }"
|
|
|
+ node-key="cateId"
|
|
|
+ highlight-current
|
|
|
+ default-expand-all
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ @node-click="handleCateNodeClick"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="18">
|
|
|
<el-card shadow="never">
|
|
|
<template #header>
|
|
|
<div class="card-header">
|
|
|
<span>内容管理</span>
|
|
|
- <el-button type="primary" size="small" @click="handleAdd">新增文章</el-button>
|
|
|
+ <el-button v-if="queryParams.status === '0' || queryParams.status === '1'" type="primary" size="small" @click="handleAdd">新增文章</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -59,25 +82,25 @@
|
|
|
<transition name="el-fade-in">
|
|
|
<div v-if="selectedRows.length > 0" class="batch-toolbar">
|
|
|
<span class="batch-info">已选择 <strong>{{ selectedRows.length }}</strong> 项</span>
|
|
|
- <el-button size="small" type="success" @click="handleBatchPublish">
|
|
|
+ <el-button v-if="queryParams.status === '0' || queryParams.status === '2'" size="small" type="success" @click="handleBatchPublish">
|
|
|
<el-icon><Check /></el-icon> 批量发布
|
|
|
</el-button>
|
|
|
- <el-button size="small" type="warning" @click="handleBatchTop">
|
|
|
+ <el-button v-if="queryParams.status === '1'" size="small" type="warning" @click="handleBatchTop">
|
|
|
<el-icon><Top /></el-icon> 批量置顶
|
|
|
</el-button>
|
|
|
- <el-button size="small" type="info" @click="handleBatchRecall">
|
|
|
+ <el-button v-if="queryParams.status === '1'" size="small" type="info" @click="handleBatchRecall">
|
|
|
<el-icon><RefreshLeft /></el-icon> 批量撤回
|
|
|
</el-button>
|
|
|
- <el-button size="small" type="danger" @click="handleBatchDelete">
|
|
|
+ <el-button v-if="queryParams.status === '0' || queryParams.status === '1' || queryParams.status === '2'" size="small" type="danger" @click="handleBatchDelete">
|
|
|
<el-icon><Delete /></el-icon> 批量删除
|
|
|
</el-button>
|
|
|
<el-button v-if="queryParams.status === '3'" size="small" type="danger" @click="handleBatchDrop">
|
|
|
<el-icon><DeleteFilled /></el-icon> 批量清空
|
|
|
</el-button>
|
|
|
- <el-button size="small" type="primary" @click="handleBatchCopy">
|
|
|
+ <el-button v-if="queryParams.status === '1'" size="small" type="primary" @click="handleBatchCopy">
|
|
|
<el-icon><CopyDocument /></el-icon> 批量复制
|
|
|
</el-button>
|
|
|
- <el-button size="small" type="primary" @click="handleBatchMove">
|
|
|
+ <el-button v-if="queryParams.status === '1'" size="small" type="primary" @click="handleBatchMove">
|
|
|
<el-icon><FolderOpened /></el-icon> 批量移动
|
|
|
</el-button>
|
|
|
<el-button size="small" text @click="clearSelection">取消选择</el-button>
|
|
|
@@ -106,11 +129,9 @@
|
|
|
<template #default="s">{{ getCateName(s.row.cateId) }}</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="pubUser" label="作者" width="100" />
|
|
|
- <el-table-column prop="pubPlatform" label="发布平台" width="90">
|
|
|
+ <el-table-column prop="pubPlatform" label="发布平台" width="120">
|
|
|
<template #default="s">
|
|
|
- <el-tag size="small" :type="s.row.pubPlatform === 'weixin' ? 'success' : 'info'">
|
|
|
- {{ s.row.pubPlatform === 'weixin' ? '微信' : 'PC' }}
|
|
|
- </el-tag>
|
|
|
+ {{ pubPlatformLabel(s.row.pubPlatform) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="type" label="类型" width="80">
|
|
|
@@ -144,6 +165,8 @@
|
|
|
@pagination="getList"
|
|
|
/>
|
|
|
</el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
|
|
|
<!-- 新增/编辑文章对话框 -->
|
|
|
<el-dialog :title="title" v-model="open" width="900px" append-to-body top="3vh" destroy-on-close>
|
|
|
@@ -189,10 +212,10 @@
|
|
|
</el-col>
|
|
|
<el-col :span="8">
|
|
|
<el-form-item label="发布平台" prop="pubPlatform">
|
|
|
- <el-select v-model="form.pubPlatform" placeholder="请选择" style="width:100%">
|
|
|
- <el-option label="PC" value="pc" />
|
|
|
- <el-option label="微信" value="weixin" />
|
|
|
- </el-select>
|
|
|
+ <el-checkbox-group v-model="form.pubPlatform">
|
|
|
+ <el-checkbox label="1">PC</el-checkbox>
|
|
|
+ <el-checkbox label="2">微信</el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
@@ -210,6 +233,14 @@
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="责任编辑">
|
|
|
+ <el-input v-model="form.auditUser" placeholder="请输入责任编辑" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="缩略图">
|
|
|
@@ -237,7 +268,11 @@
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
- <el-form-item label="正文内容">
|
|
|
+ <el-form-item v-if="form.type === 'link'" label="链接地址" prop="url">
|
|
|
+ <el-input v-model="form.url" placeholder="请输入转载链接,如 https://example.com" />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item v-if="form.type === 'article'" label="正文内容">
|
|
|
<Editor v-model="form.content" :height="320" />
|
|
|
</el-form-item>
|
|
|
|
|
|
@@ -281,6 +316,7 @@
|
|
|
</p>
|
|
|
<div style="text-align:center;color:#999;font-size:13px;margin-bottom:16px">
|
|
|
<span>作者:{{ detailData.pubUser }}</span> |
|
|
|
+ <span>责任编辑:{{ detailData.auditUser }}</span> |
|
|
|
<span>分类:{{ getCateName(detailData.cateId) }}</span> |
|
|
|
<span>发布日期:{{ detailData.pubDate }}</span> |
|
|
|
<span>点击量:{{ detailData.viewCount || 0 }}</span>
|
|
|
@@ -289,9 +325,10 @@
|
|
|
<el-tag size="small" :type="statusTagType(detailData.status)" style="margin-right:8px">
|
|
|
{{ statusLabel(detailData.status) }}
|
|
|
</el-tag>
|
|
|
- <el-tag size="small" :type="detailData.pubPlatform === 'weixin' ? 'success' : 'info'" style="margin-right:8px">
|
|
|
- {{ detailData.pubPlatform === 'weixin' ? '微信' : 'PC' }}
|
|
|
- </el-tag>
|
|
|
+ <template v-if="detailData.pubPlatform">
|
|
|
+ <el-tag v-if="detailData.pubPlatform === '1' || detailData.pubPlatform === '3'" size="small" style="margin-right:8px">PC</el-tag>
|
|
|
+ <el-tag v-if="detailData.pubPlatform === '2' || detailData.pubPlatform === '3'" size="small" type="success" style="margin-right:8px">微信</el-tag>
|
|
|
+ </template>
|
|
|
<el-tag size="small" :type="detailData.type === 'link' ? 'warning' : ''">
|
|
|
{{ detailData.type === 'link' ? '链接' : '图文' }}
|
|
|
</el-tag>
|
|
|
@@ -303,7 +340,24 @@
|
|
|
<img :src="detailData.thumbnail" style="max-width:100%;max-height:200px;border-radius:4px" />
|
|
|
</div>
|
|
|
<el-divider />
|
|
|
- <div style="font-size:15px;line-height:1.8;white-space:pre-line" v-html="detailData.content"></div>
|
|
|
+ <div class="article-toolbar">
|
|
|
+ <div class="toolbar-group">
|
|
|
+ <span class="toolbar-label">字号:</span>
|
|
|
+ <el-button :type="fontSize === 18 ? 'primary' : ''" size="small" @click="fontSize = 18">大</el-button>
|
|
|
+ <el-button :type="fontSize === 16 ? 'primary' : ''" size="small" @click="fontSize = 16">中</el-button>
|
|
|
+ <el-button :type="fontSize === 14 ? 'primary' : ''" size="small" @click="fontSize = 14">小</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="toolbar-group">
|
|
|
+ <el-button size="small" icon="Printer" @click="handlePrint">打印</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="toolbar-group">
|
|
|
+ <span class="toolbar-label">分享:</span>
|
|
|
+ <el-button size="small" @click="shareToWeibo">微博</el-button>
|
|
|
+ <el-button size="small" @click="shareToWechat">微信</el-button>
|
|
|
+ <el-button size="small" @click="shareToQQ">QQ</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div :style="{ fontSize: fontSize + 'px', lineHeight: '1.8', whiteSpace: 'pre-line' }" v-html="detailData.content"></div>
|
|
|
<div v-if="detailFiles.length > 0" class="mt-16">
|
|
|
<el-divider />
|
|
|
<div class="card-header" style="margin-bottom:8px">附件列表</div>
|
|
|
@@ -354,6 +408,7 @@ const list = ref([])
|
|
|
const fileList = ref([])
|
|
|
const detailData = ref(null)
|
|
|
const detailFiles = ref([])
|
|
|
+const fontSize = ref(15) // 文章正文字号:大18、中16、小14
|
|
|
const selectedRows = ref([])
|
|
|
const cateTreeData = ref([])
|
|
|
const tableRef = ref(null)
|
|
|
@@ -361,6 +416,7 @@ const formRef = ref(null)
|
|
|
const thumbUploadRef = ref(null)
|
|
|
const uploadRef = ref(null)
|
|
|
const moveTreeRef = ref(null)
|
|
|
+const cateTreeRef = ref(null)
|
|
|
const moveDialog = reactive({
|
|
|
visible: false,
|
|
|
title: '选择目标分类',
|
|
|
@@ -380,6 +436,10 @@ const queryParams = reactive({
|
|
|
})
|
|
|
const defaultSort = ref({ prop: 'pubDate', order: 'descending' })
|
|
|
|
|
|
+const sidebarTreeData = computed(() => {
|
|
|
+ return [{ cateId: '', cateName: '全部', children: cateTreeData.value }]
|
|
|
+})
|
|
|
+
|
|
|
const statusCounts = reactive({ 0: 0, 1: 0, 2: 0, 3: 0 })
|
|
|
|
|
|
const form = ref({})
|
|
|
@@ -388,11 +448,49 @@ const formRules = {
|
|
|
title: [{ required: true, message: '请输入文章标题', trigger: 'blur' }],
|
|
|
cateId: [{ required: true, message: '请选择文章分类', trigger: 'change' }],
|
|
|
type: [{ required: true, message: '请选择文章类型', trigger: 'change' }],
|
|
|
- pubPlatform: [{ required: true, message: '请选择发布平台', trigger: 'change' }]
|
|
|
+ pubPlatform: [{
|
|
|
+ required: true,
|
|
|
+ message: '请选择发布平台',
|
|
|
+ trigger: 'change',
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ if (!value || value.length === 0) {
|
|
|
+ callback(new Error('请选择发布平台'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }],
|
|
|
+ auditUser: [{ required: true, message: '请输入责任编辑', trigger: 'blur' }],
|
|
|
+ url: [{
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ if (form.value.type === 'link' && (!value || !value.trim())) {
|
|
|
+ callback(new Error('请输入链接地址'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'blur'
|
|
|
+ }]
|
|
|
}
|
|
|
|
|
|
const titleColorPresets = ['#ff4500', '#ff6600', '#e6a23c', '#303133', '#000000', '#e91e63', '#2196f3', '#4caf50']
|
|
|
|
|
|
+/** 发布平台 编码/解码 (数组 ↔ 后端值 1/2/3) */
|
|
|
+function arrayToPubPlatform(arr) {
|
|
|
+ if (!arr || arr.length === 0) return ''
|
|
|
+ if (arr.includes('1') && arr.includes('2')) return '3'
|
|
|
+ return arr[0]
|
|
|
+}
|
|
|
+function pubPlatformToArray(val) {
|
|
|
+ if (val === '3') return ['1', '2']
|
|
|
+ if (val === '1' || val === '2') return [val]
|
|
|
+ return []
|
|
|
+}
|
|
|
+function pubPlatformLabel(val) {
|
|
|
+ const map = { '1': 'PC', '2': '微信', '3': 'PC + 微信' }
|
|
|
+ return map[val] || val || '-'
|
|
|
+}
|
|
|
+
|
|
|
/** 状态工具函数 */
|
|
|
function statusLabel(status) {
|
|
|
const map = { '0': '草稿', '1': '已发布', '2': '已撤回', '3': '已删除' }
|
|
|
@@ -473,9 +571,22 @@ function resetQuery() {
|
|
|
queryParams.title = undefined
|
|
|
queryParams.cateId = undefined
|
|
|
queryParams.status = undefined
|
|
|
+ cateTreeRef.value?.setCurrentKey('')
|
|
|
handleQuery()
|
|
|
}
|
|
|
|
|
|
+/** 分类树节点点击(侧边栏) */
|
|
|
+function handleCateNodeClick(data) {
|
|
|
+ queryParams.cateId = data.cateId || undefined
|
|
|
+ queryParams.pageNum = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+/** 同步侧边栏树高亮与 queryParams.cateId */
|
|
|
+watch(() => queryParams.cateId, (val) => {
|
|
|
+ cateTreeRef.value?.setCurrentKey(val || '')
|
|
|
+})
|
|
|
+
|
|
|
/** 表格选中变化 */
|
|
|
function handleSelectionChange(rows) {
|
|
|
selectedRows.value = rows
|
|
|
@@ -510,7 +621,7 @@ function handleDetail(row) {
|
|
|
|
|
|
/** 新增 */
|
|
|
function handleAdd() {
|
|
|
- form.value = { type: 'article', pubPlatform: 'pc' }
|
|
|
+ form.value = { type: 'article', pubPlatform: [], auditUser: '', url: '' }
|
|
|
fileList.value = []
|
|
|
title.value = '新增文章'
|
|
|
open.value = true
|
|
|
@@ -520,6 +631,7 @@ function handleAdd() {
|
|
|
function handleEdit(row) {
|
|
|
getArticleDetail(row.aid).then(res => {
|
|
|
form.value = { ...res.data }
|
|
|
+ form.value.pubPlatform = pubPlatformToArray(res.data.pubPlatform)
|
|
|
fileList.value = res.data?.files ? [...res.data.files] : []
|
|
|
title.value = '编辑文章'
|
|
|
open.value = true
|
|
|
@@ -577,8 +689,9 @@ function removeThumbnail() {
|
|
|
function submitForm() {
|
|
|
formRef.value?.validate((valid) => {
|
|
|
if (!valid) return
|
|
|
- const method = form.value.aid && title.value === '编辑文章' ? updateArticle : addArticle
|
|
|
- method(form.value).then(() => {
|
|
|
+ const submitData = { ...form.value, pubPlatform: arrayToPubPlatform(form.value.pubPlatform) }
|
|
|
+ const method = submitData.aid && title.value === '编辑文章' ? updateArticle : addArticle
|
|
|
+ method(submitData).then(() => {
|
|
|
proxy.$modal.msgSuccess('操作成功')
|
|
|
open.value = false
|
|
|
getList()
|
|
|
@@ -592,6 +705,30 @@ function cancel() {
|
|
|
open.value = false
|
|
|
}
|
|
|
|
|
|
+/** 打印文章 */
|
|
|
+function handlePrint() {
|
|
|
+ window.print()
|
|
|
+}
|
|
|
+
|
|
|
+/** 分享到微博 */
|
|
|
+function shareToWeibo() {
|
|
|
+ const url = encodeURIComponent(location.href)
|
|
|
+ const title = encodeURIComponent(detailData.value?.title || '')
|
|
|
+ window.open(`https://service.weibo.com/share/share.php?url=${url}&title=${title}`, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
+/** 分享到微信(提示扫描二维码) */
|
|
|
+function shareToWechat() {
|
|
|
+ proxy.$modal.msgInfo('请使用微信扫描页面二维码分享')
|
|
|
+}
|
|
|
+
|
|
|
+/** 分享到QQ */
|
|
|
+function shareToQQ() {
|
|
|
+ const url = encodeURIComponent(location.href)
|
|
|
+ const title = encodeURIComponent(detailData.value?.title || '')
|
|
|
+ window.open(`https://connect.qq.com/widget/shareqq/index.html?url=${url}&title=${title}`, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
/** ====== 批量操作 ====== */
|
|
|
function getSelectedAids() {
|
|
|
return selectedRows.value.map(r => r.aid).join(',')
|
|
|
@@ -684,7 +821,8 @@ function confirmMoveOrCopy() {
|
|
|
title: r.title, subtitle: r.subtitle, cateId: targetCateId,
|
|
|
status: 0, type: r.type, content: r.content,
|
|
|
pubPlatform: r.pubPlatform, titleColor: r.titleColor,
|
|
|
- summary: r.summary, source: r.source, keyword: r.keyword
|
|
|
+ summary: r.summary, source: r.source, keyword: r.keyword,
|
|
|
+ auditUser: r.auditUser
|
|
|
}))
|
|
|
batchAddArticle(items).then(() => {
|
|
|
proxy.$modal.msgSuccess('批量复制成功')
|
|
|
@@ -812,4 +950,47 @@ onMounted(() => {
|
|
|
.thumb-actions {
|
|
|
margin-top: 4px;
|
|
|
}
|
|
|
+
|
|
|
+.cate-sidebar {
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+.cate-sidebar :deep(.el-card__body) {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+.cate-tree-wrapper {
|
|
|
+ padding: 8px 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ max-height: calc(100vh - 260px);
|
|
|
+}
|
|
|
+.cate-tree-wrapper :deep(.el-tree-node__content) {
|
|
|
+ height: 36px;
|
|
|
+ padding-left: 12px;
|
|
|
+}
|
|
|
+.cate-tree-wrapper :deep(.el-tree-node.is-current > .el-tree-node__content) {
|
|
|
+ background-color: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.article-toolbar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 10px 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 4px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+.toolbar-group {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+.toolbar-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ margin-right: 2px;
|
|
|
+}
|
|
|
</style>
|