返回 Social Auto Upload
Dashboard.vue
根目录 / sau_frontend / src / views / Dashboard.vue
1 <template>
2 <div class="dashboard">
3 <div class="page-header">
4 <h1>自媒体自动化运营系统</h1>
5 </div>
6
7 <div class="dashboard-content">
8 <el-row :gutter="20">
9 <!-- 账号统计卡片 -->
10 <el-col :span="8">
11 <el-card class="stat-card">
12 <div class="stat-card-content">
13 <div class="stat-icon">
14 <el-icon><User /></el-icon>
15 </div>
16 <div class="stat-info">
17 <div class="stat-value">{{ accountStats.total }}</div>
18 <div class="stat-label">账号总数</div>
19 </div>
20 </div>
21 <div class="stat-footer">
22 <div class="stat-detail">
23 <span>正常: {{ accountStats.normal }}</span>
24 <span>异常: {{ accountStats.abnormal }}</span>
25 </div>
26 </div>
27 </el-card>
28 </el-col>
29
30 <!-- 平台统计卡片 -->
31 <el-col :span="8">
32 <el-card class="stat-card">
33 <div class="stat-card-content">
34 <div class="stat-icon platform-icon">
35 <el-icon><Platform /></el-icon>
36 </div>
37 <div class="stat-info">
38 <div class="stat-value">{{ platformStats.total }}</div>
39 <div class="stat-label">已接入平台</div>
40 </div>
41 </div>
42 <div class="stat-footer">
43 <div class="stat-detail">
44 <el-tooltip content="快手账号" placement="top">
45 <el-tag size="small" type="success">{{ platformStats.kuaishou }}</el-tag>
46 </el-tooltip>
47 <el-tooltip content="抖音账号" placement="top">
48 <el-tag size="small" type="danger">{{ platformStats.douyin }}</el-tag>
49 </el-tooltip>
50 <el-tooltip content="视频号账号" placement="top">
51 <el-tag size="small" type="warning">{{ platformStats.channels }}</el-tag>
52 </el-tooltip>
53 <el-tooltip content="小红书账号" placement="top">
54 <el-tag size="small" type="info">{{ platformStats.xiaohongshu }}</el-tag>
55 </el-tooltip>
56 </div>
57 </div>
58 </el-card>
59 </el-col>
60
61 <!-- 素材统计卡片 -->
62 <el-col :span="8">
63 <el-card class="stat-card">
64 <div class="stat-card-content">
65 <div class="stat-icon content-icon">
66 <el-icon><Document /></el-icon>
67 </div>
68 <div class="stat-info">
69 <div class="stat-value">{{ contentStats.total }}</div>
70 <div class="stat-label">素材总数</div>
71 </div>
72 </div>
73 <div class="stat-footer">
74 <div class="stat-detail">
75 <span>视频: {{ contentStats.videos }}</span>
76 <span>图片: {{ contentStats.images }}</span>
77 <span>其他: {{ contentStats.others }}</span>
78 </div>
79 </div>
80 </el-card>
81 </el-col>
82 </el-row>
83
84 <!-- 快捷操作区域 -->
85 <div class="quick-actions">
86 <h2>快捷操作</h2>
87 <el-row :gutter="20">
88 <el-col :span="6">
89 <el-card class="action-card" @click="navigateTo('/account-management')">
90 <div class="action-icon">
91 <el-icon><UserFilled /></el-icon>
92 </div>
93 <div class="action-title">账号管理</div>
94 <div class="action-desc">管理所有平台账号</div>
95 </el-card>
96 </el-col>
97 <el-col :span="6">
98 <el-card class="action-card" @click="navigateTo('/material-management')">
99 <div class="action-icon">
100 <el-icon><Upload /></el-icon>
101 </div>
102 <div class="action-title">素材管理</div>
103 <div class="action-desc">上传和管理视频素材</div>
104 </el-card>
105 </el-col>
106 <el-col :span="6">
107 <el-card class="action-card" @click="navigateTo('/publish-center')">
108 <div class="action-icon">
109 <el-icon><Timer /></el-icon>
110 </div>
111 <div class="action-title">发布中心</div>
112 <div class="action-desc">发布内容到各平台</div>
113 </el-card>
114 </el-col>
115 <el-col :span="6">
116 <el-card class="action-card" @click="navigateTo('/about')">
117 <div class="action-icon">
118 <el-icon><DataAnalysis /></el-icon>
119 </div>
120 <div class="action-title">关于系统</div>
121 <div class="action-desc">查看系统信息</div>
122 </el-card>
123 </el-col>
124 </el-row>
125 </div>
126
127 <!-- 素材列表 -->
128 <div class="recent-tasks">
129 <div class="section-header">
130 <h2>最近上传素材</h2>
131 <el-button text @click="navigateTo('/material-management')">查看全部</el-button>
132 </div>
133
134 <el-table :data="recentMaterials" style="width: 100%" v-loading="loading">
135 <el-table-column prop="filename" label="文件名" width="300" />
136 <el-table-column prop="filesize" label="文件大小" width="120">
137 <template #default="scope">
138 {{ scope.row.filesize }} MB
139 </template>
140 </el-table-column>
141 <el-table-column prop="upload_time" label="上传时间" width="200" />
142 <el-table-column label="类型" width="100">
143 <template #default="scope">
144 <el-tag
145 :type="getFileTypeTag(scope.row.filename)"
146 effect="plain"
147 size="small"
148 >
149 {{ getFileType(scope.row.filename) }}
150 </el-tag>
151 </template>
152 </el-table-column>
153 </el-table>
154
155 <el-empty v-if="!loading && recentMaterials.length === 0" description="暂无素材数据" />
156 </div>
157 </div>
158 </div>
159 </template>
160
161 <script setup>
162 import { ref, reactive, computed, onMounted } from 'vue'
163 import { useRouter } from 'vue-router'
164 import {
165 User, UserFilled, Platform, Document,
166 Upload, Timer, DataAnalysis
167 } from '@element-plus/icons-vue'
168 import { accountApi } from '@/api/account'
169 import { materialApi } from '@/api/material'
170 import { useAccountStore } from '@/stores/account'
171 import { useAppStore } from '@/stores/app'
172
173 const router = useRouter()
174 const accountStore = useAccountStore()
175 const appStore = useAppStore()
176 const loading = ref(false)
177
178 // 账号统计数据 - 从真实数据计算
179 const accountStats = computed(() => {
180 const accounts = accountStore.accounts
181 const normal = accounts.filter(a => a.status === '正常').length
182 const abnormal = accounts.filter(a => a.status !== '正常' && a.status !== '验证中').length
183 return {
184 total: accounts.length,
185 normal,
186 abnormal
187 }
188 })
189
190 // 平台统计数据 - 从真实数据计算
191 const platformStats = computed(() => {
192 const accounts = accountStore.accounts
193 const kuaishou = accounts.filter(a => a.platform === '快手').length
194 const douyin = accounts.filter(a => a.platform === '抖音').length
195 const channels = accounts.filter(a => a.platform === '视频号').length
196 const xiaohongshu = accounts.filter(a => a.platform === '小红书').length
197 // 统计有账号的平台数量
198 const total = [kuaishou, douyin, channels, xiaohongshu].filter(n => n > 0).length
199 return { total, kuaishou, douyin, channels, xiaohongshu }
200 })
201
202 // 素材统计数据 - 从真实数据计算
203 const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
204 const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
205
206 const contentStats = computed(() => {
207 const materials = appStore.materials
208 const videos = materials.filter(m => videoExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
209 const images = materials.filter(m => imageExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
210 return {
211 total: materials.length,
212 videos,
213 images,
214 others: materials.length - videos - images
215 }
216 })
217
218 // 最近上传的素材(最多显示5条)
219 const recentMaterials = computed(() => {
220 return [...appStore.materials]
221 .sort((a, b) => new Date(b.upload_time) - new Date(a.upload_time))
222 .slice(0, 5)
223 })
224
225 // 获取文件类型
226 const getFileType = (filename) => {
227 if (videoExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '视频'
228 if (imageExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '图片'
229 return '其他'
230 }
231
232 // 获取文件类型标签颜色
233 const getFileTypeTag = (filename) => {
234 const type = getFileType(filename)
235 return { '视频': 'success', '图片': 'warning', '其他': 'info' }[type] || 'info'
236 }
237
238 // 导航到指定路由
239 const navigateTo = (path) => {
240 router.push(path)
241 }
242
243 // 加载数据
244 const fetchDashboardData = async () => {
245 loading.value = true
246 try {
247 // 并行获取账号和素材数据
248 const [accountRes, materialRes] = await Promise.allSettled([
249 accountApi.getAccounts(),
250 materialApi.getAllMaterials()
251 ])
252
253 if (accountRes.status === 'fulfilled' && accountRes.value.code === 200) {
254 accountStore.setAccounts(accountRes.value.data)
255 }
256 if (materialRes.status === 'fulfilled' && materialRes.value.code === 200) {
257 appStore.setMaterials(materialRes.value.data)
258 }
259 } catch (error) {
260 console.error('获取仪表盘数据失败:', error)
261 } finally {
262 loading.value = false
263 }
264 }
265
266 onMounted(() => {
267 fetchDashboardData()
268 })
269 </script>
270
271 <style lang="scss" scoped>
272 @use '@/styles/variables.scss' as *;
273
274 .dashboard {
275 .page-header {
276 margin-bottom: 20px;
277
278 h1 {
279 font-size: 24px;
280 color: $text-primary;
281 margin: 0;
282 }
283 }
284
285 .dashboard-content {
286 .stat-card {
287 height: 140px;
288 margin-bottom: 20px;
289
290 .stat-card-content {
291 display: flex;
292 align-items: center;
293 margin-bottom: 15px;
294
295 .stat-icon {
296 width: 60px;
297 height: 60px;
298 border-radius: 50%;
299 background-color: rgba($primary-color, 0.1);
300 display: flex;
301 justify-content: center;
302 align-items: center;
303 margin-right: 15px;
304
305 .el-icon {
306 font-size: 30px;
307 color: $primary-color;
308 }
309
310 &.platform-icon {
311 background-color: rgba($success-color, 0.1);
312
313 .el-icon {
314 color: $success-color;
315 }
316 }
317
318 &.content-icon {
319 background-color: rgba($info-color, 0.1);
320
321 .el-icon {
322 color: $info-color;
323 }
324 }
325 }
326
327 .stat-info {
328 .stat-value {
329 font-size: 24px;
330 font-weight: bold;
331 color: $text-primary;
332 line-height: 1.2;
333 }
334
335 .stat-label {
336 font-size: 14px;
337 color: $text-secondary;
338 }
339 }
340 }
341
342 .stat-footer {
343 border-top: 1px solid $border-lighter;
344 padding-top: 10px;
345
346 .stat-detail {
347 display: flex;
348 justify-content: space-between;
349 color: $text-secondary;
350 font-size: 13px;
351
352 .el-tag {
353 margin-right: 5px;
354 }
355 }
356 }
357 }
358
359 .quick-actions {
360 margin: 20px 0 30px;
361
362 h2 {
363 font-size: 18px;
364 margin-bottom: 15px;
365 color: $text-primary;
366 }
367
368 .action-card {
369 height: 160px;
370 display: flex;
371 flex-direction: column;
372 align-items: center;
373 justify-content: center;
374 cursor: pointer;
375 transition: all 0.3s;
376
377 &:hover {
378 transform: translateY(-5px);
379 box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
380 }
381
382 .action-icon {
383 width: 50px;
384 height: 50px;
385 border-radius: 50%;
386 background-color: rgba($primary-color, 0.1);
387 display: flex;
388 justify-content: center;
389 align-items: center;
390 margin-bottom: 15px;
391
392 .el-icon {
393 font-size: 24px;
394 color: $primary-color;
395 }
396 }
397
398 .action-title {
399 font-size: 16px;
400 font-weight: bold;
401 color: $text-primary;
402 margin-bottom: 5px;
403 }
404
405 .action-desc {
406 font-size: 13px;
407 color: $text-secondary;
408 text-align: center;
409 }
410 }
411 }
412
413 .recent-tasks {
414 margin-top: 30px;
415
416 .section-header {
417 display: flex;
418 justify-content: space-between;
419 align-items: center;
420 margin-bottom: 15px;
421
422 h2 {
423 font-size: 18px;
424 color: $text-primary;
425 margin: 0;
426 }
427 }
428 }
429 }
430 }
431 </style>
432
432 lines Plain Text