feat: 深度优化自媒体发布系统及修复关键BUG
This commit is contained in:
@@ -3,11 +3,11 @@
|
||||
<div class="page-header">
|
||||
<h1>自媒体自动化运营系统</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="dashboard-content">
|
||||
<el-row :gutter="20">
|
||||
<!-- 账号统计卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon">
|
||||
@@ -26,9 +26,9 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
|
||||
<!-- 平台统计卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon platform-icon">
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ platformStats.total }}</div>
|
||||
<div class="stat-label">平台总数</div>
|
||||
<div class="stat-label">已接入平台</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
@@ -57,31 +57,9 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 任务统计卡片 -->
|
||||
<el-col :span="6">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon task-icon">
|
||||
<el-icon><List /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ taskStats.total }}</div>
|
||||
<div class="stat-label">任务总数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<div class="stat-detail">
|
||||
<span>完成: {{ taskStats.completed }}</span>
|
||||
<span>进行中: {{ taskStats.inProgress }}</span>
|
||||
<span>失败: {{ taskStats.failed }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 内容统计卡片 -->
|
||||
<el-col :span="6">
|
||||
|
||||
<!-- 素材统计卡片 -->
|
||||
<el-col :span="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-card-content">
|
||||
<div class="stat-icon content-icon">
|
||||
@@ -89,19 +67,20 @@
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ contentStats.total }}</div>
|
||||
<div class="stat-label">内容总数</div>
|
||||
<div class="stat-label">素材总数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-footer">
|
||||
<div class="stat-detail">
|
||||
<span>已发布: {{ contentStats.published }}</span>
|
||||
<span>草稿: {{ contentStats.draft }}</span>
|
||||
<span>视频: {{ contentStats.videos }}</span>
|
||||
<span>图片: {{ contentStats.images }}</span>
|
||||
<span>其他: {{ contentStats.others }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<!-- 快捷操作区域 -->
|
||||
<div class="quick-actions">
|
||||
<h2>快捷操作</h2>
|
||||
@@ -116,199 +95,144 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="action-card">
|
||||
<el-card class="action-card" @click="navigateTo('/material-management')">
|
||||
<div class="action-icon">
|
||||
<el-icon><Upload /></el-icon>
|
||||
</div>
|
||||
<div class="action-title">内容上传</div>
|
||||
<div class="action-desc">上传视频和图文内容</div>
|
||||
<div class="action-title">素材管理</div>
|
||||
<div class="action-desc">上传和管理视频素材</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="action-card">
|
||||
<el-card class="action-card" @click="navigateTo('/publish-center')">
|
||||
<div class="action-icon">
|
||||
<el-icon><Timer /></el-icon>
|
||||
</div>
|
||||
<div class="action-title">定时发布</div>
|
||||
<div class="action-desc">设置内容发布时间</div>
|
||||
<div class="action-title">发布中心</div>
|
||||
<div class="action-desc">发布内容到各平台</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="action-card">
|
||||
<el-card class="action-card" @click="navigateTo('/about')">
|
||||
<div class="action-icon">
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
</div>
|
||||
<div class="action-title">数据分析</div>
|
||||
<div class="action-desc">查看内容数据分析</div>
|
||||
<div class="action-title">关于系统</div>
|
||||
<div class="action-desc">查看系统信息</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 最近任务列表 -->
|
||||
|
||||
<!-- 素材列表 -->
|
||||
<div class="recent-tasks">
|
||||
<div class="section-header">
|
||||
<h2>最近任务</h2>
|
||||
<el-button text>查看全部</el-button>
|
||||
<h2>最近上传素材</h2>
|
||||
<el-button text @click="navigateTo('/material-management')">查看全部</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="recentTasks" style="width: 100%">
|
||||
<el-table-column prop="title" label="任务名称" width="250" />
|
||||
<el-table-column prop="platform" label="平台" width="120">
|
||||
|
||||
<el-table :data="recentMaterials" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="filename" label="文件名" width="300" />
|
||||
<el-table-column prop="filesize" label="文件大小" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getPlatformTagType(scope.row.platform)"
|
||||
effect="plain"
|
||||
>
|
||||
{{ scope.row.platform }}
|
||||
</el-tag>
|
||||
{{ scope.row.filesize }} MB
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="account" label="账号" width="150" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<el-table-column prop="upload_time" label="上传时间" width="200" />
|
||||
<el-table-column label="类型" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="getStatusTagType(scope.row.status)"
|
||||
:type="getFileTypeTag(scope.row.filename)"
|
||||
effect="plain"
|
||||
size="small"
|
||||
>
|
||||
{{ scope.row.status }}
|
||||
{{ getFileType(scope.row.filename) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="viewTaskDetail(scope.row)">查看</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
v-if="scope.row.status === '待执行'"
|
||||
@click="executeTask(scope.row)"
|
||||
>
|
||||
执行
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
v-if="scope.row.status !== '已完成' && scope.row.status !== '已失败'"
|
||||
@click="cancelTask(scope.row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-empty v-if="!loading && recentMaterials.length === 0" description="暂无素材数据" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
User, UserFilled, Platform, List, Document,
|
||||
Upload, Timer, DataAnalysis
|
||||
import {
|
||||
User, UserFilled, Platform, Document,
|
||||
Upload, Timer, DataAnalysis
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { accountApi } from '@/api/account'
|
||||
import { materialApi } from '@/api/material'
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const router = useRouter()
|
||||
const accountStore = useAccountStore()
|
||||
const appStore = useAppStore()
|
||||
const loading = ref(false)
|
||||
|
||||
// 账号统计数据
|
||||
const accountStats = reactive({
|
||||
total: 12,
|
||||
normal: 10,
|
||||
abnormal: 2
|
||||
})
|
||||
|
||||
// 平台统计数据
|
||||
const platformStats = reactive({
|
||||
total: 4,
|
||||
kuaishou: 3,
|
||||
douyin: 4,
|
||||
channels: 2,
|
||||
xiaohongshu: 3
|
||||
})
|
||||
|
||||
// 任务统计数据
|
||||
const taskStats = reactive({
|
||||
total: 24,
|
||||
completed: 18,
|
||||
inProgress: 5,
|
||||
failed: 1
|
||||
})
|
||||
|
||||
// 内容统计数据
|
||||
const contentStats = reactive({
|
||||
total: 36,
|
||||
published: 30,
|
||||
draft: 6
|
||||
})
|
||||
|
||||
// 最近任务数据
|
||||
const recentTasks = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '快手视频自动发布',
|
||||
platform: '快手',
|
||||
account: '快手账号1',
|
||||
createTime: '2024-05-01 10:30:00',
|
||||
status: '已完成'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '抖音视频定时发布',
|
||||
platform: '抖音',
|
||||
account: '抖音账号1',
|
||||
createTime: '2024-05-01 11:15:00',
|
||||
status: '进行中'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '视频号内容上传',
|
||||
platform: '视频号',
|
||||
account: '视频号账号1',
|
||||
createTime: '2024-05-01 14:20:00',
|
||||
status: '待执行'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '小红书图文发布',
|
||||
platform: '小红书',
|
||||
account: '小红书账号1',
|
||||
createTime: '2024-05-01 16:45:00',
|
||||
status: '已失败'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '快手短视频批量上传',
|
||||
platform: '快手',
|
||||
account: '快手账号2',
|
||||
createTime: '2024-05-02 09:10:00',
|
||||
status: '待执行'
|
||||
// 账号统计数据 - 从真实数据计算
|
||||
const accountStats = computed(() => {
|
||||
const accounts = accountStore.accounts
|
||||
const normal = accounts.filter(a => a.status === '正常').length
|
||||
const abnormal = accounts.filter(a => a.status !== '正常' && a.status !== '验证中').length
|
||||
return {
|
||||
total: accounts.length,
|
||||
normal,
|
||||
abnormal
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// 根据平台获取标签类型
|
||||
const getPlatformTagType = (platform) => {
|
||||
const typeMap = {
|
||||
'快手': 'success',
|
||||
'抖音': 'danger',
|
||||
'视频号': 'warning',
|
||||
'小红书': 'info'
|
||||
// 平台统计数据 - 从真实数据计算
|
||||
const platformStats = computed(() => {
|
||||
const accounts = accountStore.accounts
|
||||
const kuaishou = accounts.filter(a => a.platform === '快手').length
|
||||
const douyin = accounts.filter(a => a.platform === '抖音').length
|
||||
const channels = accounts.filter(a => a.platform === '视频号').length
|
||||
const xiaohongshu = accounts.filter(a => a.platform === '小红书').length
|
||||
// 统计有账号的平台数量
|
||||
const total = [kuaishou, douyin, channels, xiaohongshu].filter(n => n > 0).length
|
||||
return { total, kuaishou, douyin, channels, xiaohongshu }
|
||||
})
|
||||
|
||||
// 素材统计数据 - 从真实数据计算
|
||||
const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
|
||||
|
||||
const contentStats = computed(() => {
|
||||
const materials = appStore.materials
|
||||
const videos = materials.filter(m => videoExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
|
||||
const images = materials.filter(m => imageExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
|
||||
return {
|
||||
total: materials.length,
|
||||
videos,
|
||||
images,
|
||||
others: materials.length - videos - images
|
||||
}
|
||||
return typeMap[platform] || 'info'
|
||||
})
|
||||
|
||||
// 最近上传的素材(最多显示5条)
|
||||
const recentMaterials = computed(() => {
|
||||
return [...appStore.materials]
|
||||
.sort((a, b) => new Date(b.upload_time) - new Date(a.upload_time))
|
||||
.slice(0, 5)
|
||||
})
|
||||
|
||||
// 获取文件类型
|
||||
const getFileType = (filename) => {
|
||||
if (videoExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '视频'
|
||||
if (imageExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '图片'
|
||||
return '其他'
|
||||
}
|
||||
|
||||
// 根据状态获取标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
const typeMap = {
|
||||
'已完成': 'success',
|
||||
'进行中': 'warning',
|
||||
'待执行': 'info',
|
||||
'已失败': 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
// 获取文件类型标签颜色
|
||||
const getFileTypeTag = (filename) => {
|
||||
const type = getFileType(filename)
|
||||
return { '视频': 'success', '图片': 'warning', '其他': 'info' }[type] || 'info'
|
||||
}
|
||||
|
||||
// 导航到指定路由
|
||||
@@ -316,65 +240,32 @@ const navigateTo = (path) => {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
// 查看任务详情
|
||||
const viewTaskDetail = (task) => {
|
||||
ElMessage.info(`查看任务: ${task.title}`)
|
||||
// 实际应用中应该跳转到任务详情页面
|
||||
// 加载数据
|
||||
const fetchDashboardData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 并行获取账号和素材数据
|
||||
const [accountRes, materialRes] = await Promise.allSettled([
|
||||
accountApi.getAccounts(),
|
||||
materialApi.getAllMaterials()
|
||||
])
|
||||
|
||||
if (accountRes.status === 'fulfilled' && accountRes.value.code === 200) {
|
||||
accountStore.setAccounts(accountRes.value.data)
|
||||
}
|
||||
if (materialRes.status === 'fulfilled' && materialRes.value.code === 200) {
|
||||
appStore.setMaterials(materialRes.value.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取仪表盘数据失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
const executeTask = (task) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要执行任务 ${task.title} 吗?`,
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// 更新任务状态
|
||||
const index = recentTasks.value.findIndex(t => t.id === task.id)
|
||||
if (index !== -1) {
|
||||
recentTasks.value[index].status = '进行中'
|
||||
}
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '任务已开始执行',
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消执行
|
||||
})
|
||||
}
|
||||
|
||||
// 取消任务
|
||||
const cancelTask = (task) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要取消任务 ${task.title} 吗?`,
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// 更新任务状态
|
||||
const index = recentTasks.value.findIndex(t => t.id === task.id)
|
||||
if (index !== -1) {
|
||||
recentTasks.value[index].status = '已取消'
|
||||
}
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '任务已取消',
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
fetchDashboardData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -383,24 +274,24 @@ const cancelTask = (task) => {
|
||||
.dashboard {
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: $text-primary;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.dashboard-content {
|
||||
.stat-card {
|
||||
height: 140px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
|
||||
.stat-card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
@@ -410,37 +301,29 @@ const cancelTask = (task) => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
|
||||
|
||||
.el-icon {
|
||||
font-size: 30px;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
|
||||
&.platform-icon {
|
||||
background-color: rgba($success-color, 0.1);
|
||||
|
||||
|
||||
.el-icon {
|
||||
color: $success-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.task-icon {
|
||||
background-color: rgba($warning-color, 0.1);
|
||||
|
||||
.el-icon {
|
||||
color: $warning-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.content-icon {
|
||||
background-color: rgba($info-color, 0.1);
|
||||
|
||||
|
||||
.el-icon {
|
||||
color: $info-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.stat-info {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
@@ -448,40 +331,40 @@ const cancelTask = (task) => {
|
||||
color: $text-primary;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.stat-footer {
|
||||
border-top: 1px solid $border-lighter;
|
||||
padding-top: 10px;
|
||||
|
||||
|
||||
.stat-detail {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: $text-secondary;
|
||||
font-size: 13px;
|
||||
|
||||
|
||||
.el-tag {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.quick-actions {
|
||||
margin: 20px 0 30px;
|
||||
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
|
||||
.action-card {
|
||||
height: 160px;
|
||||
display: flex;
|
||||
@@ -490,12 +373,12 @@ const cancelTask = (task) => {
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.action-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
@@ -505,20 +388,20 @@ const cancelTask = (task) => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: $text-primary;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
.action-desc {
|
||||
font-size: 13px;
|
||||
color: $text-secondary;
|
||||
@@ -526,16 +409,16 @@ const cancelTask = (task) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.recent-tasks {
|
||||
margin-top: 30px;
|
||||
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
color: $text-primary;
|
||||
@@ -545,4 +428,4 @@ const cancelTask = (task) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user