Dashboard.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <template>
  2. <div class="dashboard-container">
  3. <!-- Statistics Cards -->
  4. <el-row :gutter="20">
  5. <el-col :span="6">
  6. <el-card shadow="hover" class="stat-card">
  7. <div class="stat-content">
  8. <div class="stat-icon news-bg">
  9. <el-icon><Reading /></el-icon>
  10. </div>
  11. <div class="stat-info">
  12. <div class="stat-value">{{ stats.news }}</div>
  13. <div class="stat-label">新闻动态</div>
  14. </div>
  15. </div>
  16. </el-card>
  17. </el-col>
  18. <el-col :span="6">
  19. <el-card shadow="hover" class="stat-card">
  20. <div class="stat-content">
  21. <div class="stat-icon case-bg">
  22. <el-icon><Suitcase /></el-icon>
  23. </div>
  24. <div class="stat-info">
  25. <div class="stat-value">{{ stats.cases }}</div>
  26. <div class="stat-label">成功案例</div>
  27. </div>
  28. </div>
  29. </el-card>
  30. </el-col>
  31. <el-col :span="6">
  32. <el-card shadow="hover" class="stat-card">
  33. <div class="stat-content">
  34. <div class="stat-icon partner-bg">
  35. <el-icon><User /></el-icon>
  36. </div>
  37. <div class="stat-info">
  38. <div class="stat-value">{{ stats.partners }}</div>
  39. <div class="stat-label">合作伙伴</div>
  40. </div>
  41. </div>
  42. </el-card>
  43. </el-col>
  44. <el-col :span="6">
  45. <el-card shadow="hover" class="stat-card">
  46. <div class="stat-content">
  47. <div class="stat-icon user-bg">
  48. <el-icon><Setting /></el-icon>
  49. </div>
  50. <div class="stat-info">
  51. <div class="stat-value">{{ stats.users }}</div>
  52. <div class="stat-label">系统用户</div>
  53. </div>
  54. </div>
  55. </el-card>
  56. </el-col>
  57. </el-row>
  58. <!-- Charts and Recent Activity -->
  59. <el-row :gutter="20" class="mt-20">
  60. <el-col :span="16">
  61. <el-card shadow="hover" class="chart-card">
  62. <template #header>
  63. <div class="card-header">
  64. <span>数据概览</span>
  65. </div>
  66. </template>
  67. <div ref="chartRef" style="width: 100%; height: 350px;"></div>
  68. </el-card>
  69. </el-col>
  70. <el-col :span="8">
  71. <el-card shadow="hover" class="activity-card">
  72. <template #header>
  73. <div class="card-header">
  74. <span>最新新闻</span>
  75. <el-button text type="primary" @click="$router.push('/news')">查看更多</el-button>
  76. </div>
  77. </template>
  78. <div class="activity-list">
  79. <div v-for="item in recentNews" :key="item.id" class="activity-item">
  80. <span class="activity-title">{{ item.title }}</span>
  81. <span class="activity-time">{{ item.publish_time }}</span>
  82. </div>
  83. <el-empty v-if="recentNews.length === 0" description="暂无数据" />
  84. </div>
  85. </el-card>
  86. </el-col>
  87. </el-row>
  88. <!-- Quick Actions -->
  89. <el-row :gutter="20" class="mt-20">
  90. <el-col :span="24">
  91. <el-card shadow="hover">
  92. <template #header>
  93. <div class="card-header">
  94. <span>快捷操作</span>
  95. </div>
  96. </template>
  97. <div class="quick-actions">
  98. <el-button type="primary" plain icon="Plus" @click="$router.push('/news')">发布新闻</el-button>
  99. <el-button type="success" plain icon="Plus" @click="$router.push('/cases/education')">添加案例</el-button>
  100. <el-button type="warning" plain icon="Plus" @click="$router.push('/partners')">添加伙伴</el-button>
  101. <el-button type="info" plain icon="Picture" @click="$router.push('/carousels/home')">轮播管理</el-button>
  102. </div>
  103. </el-card>
  104. </el-col>
  105. </el-row>
  106. </div>
  107. </template>
  108. <script setup>
  109. import { ref, onMounted, reactive } from 'vue'
  110. import api from '../api'
  111. import * as echarts from 'echarts'
  112. const stats = reactive({
  113. news: 0,
  114. cases: 0,
  115. partners: 0,
  116. users: 0
  117. })
  118. const recentNews = ref([])
  119. const chartRef = ref(null)
  120. const fetchData = async () => {
  121. try {
  122. // Parallel fetching for performance
  123. const [newsRes, partnersRes, usersRes, eduCasesRes, servCasesRes] = await Promise.all([
  124. api.get('/news'),
  125. api.get('/partners'),
  126. api.get('/users'),
  127. api.get('/cases', { params: { section: 'education' } }),
  128. api.get('/cases', { params: { section: 'service' } })
  129. ])
  130. stats.news = newsRes.data.length
  131. stats.partners = partnersRes.data.length
  132. stats.users = usersRes.data.length
  133. stats.cases = eduCasesRes.data.length + servCasesRes.data.length
  134. // Recent news (last 5)
  135. recentNews.value = newsRes.data.slice(-5).reverse()
  136. initChart()
  137. } catch (error) {
  138. console.error('Failed to fetch dashboard data', error)
  139. }
  140. }
  141. const initChart = () => {
  142. if (!chartRef.value) return
  143. const myChart = echarts.init(chartRef.value)
  144. const option = {
  145. tooltip: {
  146. trigger: 'item'
  147. },
  148. legend: {
  149. top: '5%',
  150. left: 'center'
  151. },
  152. series: [
  153. {
  154. name: '数据分布',
  155. type: 'pie',
  156. radius: ['40%', '70%'],
  157. avoidLabelOverlap: false,
  158. itemStyle: {
  159. borderRadius: 10,
  160. borderColor: '#fff',
  161. borderWidth: 2
  162. },
  163. label: {
  164. show: false,
  165. position: 'center'
  166. },
  167. emphasis: {
  168. label: {
  169. show: true,
  170. fontSize: 20,
  171. fontWeight: 'bold'
  172. }
  173. },
  174. labelLine: {
  175. show: false
  176. },
  177. data: [
  178. { value: stats.news, name: '新闻动态' },
  179. { value: stats.cases, name: '成功案例' },
  180. { value: stats.partners, name: '合作伙伴' },
  181. { value: stats.users, name: '系统用户' }
  182. ]
  183. }
  184. ]
  185. }
  186. myChart.setOption(option)
  187. // Responsive resize
  188. window.addEventListener('resize', () => {
  189. myChart.resize()
  190. })
  191. }
  192. onMounted(() => {
  193. fetchData()
  194. })
  195. </script>
  196. <style scoped>
  197. .dashboard-container {
  198. padding: 0;
  199. }
  200. .mt-20 {
  201. margin-top: 20px;
  202. }
  203. .stat-card {
  204. border: none;
  205. border-radius: 8px;
  206. overflow: hidden;
  207. }
  208. .stat-content {
  209. display: flex;
  210. align-items: center;
  211. }
  212. .stat-icon {
  213. width: 60px;
  214. height: 60px;
  215. border-radius: 50%;
  216. display: flex;
  217. align-items: center;
  218. justify-content: center;
  219. margin-right: 15px;
  220. font-size: 30px;
  221. color: #fff;
  222. }
  223. .news-bg { background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%); }
  224. .case-bg { background: linear-gradient(135deg, #ff7a45 0%, #ffc53d 100%); }
  225. .partner-bg { background: linear-gradient(135deg, #722ed1 0%, #b37feb 100%); }
  226. .user-bg { background: linear-gradient(135deg, #52c41a 0%, #95de64 100%); }
  227. .stat-info {
  228. flex: 1;
  229. }
  230. .stat-value {
  231. font-size: 24px;
  232. font-weight: bold;
  233. color: #333;
  234. line-height: 1.2;
  235. }
  236. .stat-label {
  237. font-size: 14px;
  238. color: #888;
  239. margin-top: 5px;
  240. }
  241. .chart-card {
  242. height: 450px;
  243. }
  244. .activity-card {
  245. height: 450px;
  246. display: flex;
  247. flex-direction: column;
  248. }
  249. .card-header {
  250. display: flex;
  251. justify-content: space-between;
  252. align-items: center;
  253. font-weight: bold;
  254. }
  255. .activity-list {
  256. flex: 1;
  257. overflow-y: auto;
  258. }
  259. .activity-item {
  260. display: flex;
  261. justify-content: space-between;
  262. padding: 12px 0;
  263. border-bottom: 1px solid #f0f0f0;
  264. }
  265. .activity-item:last-child {
  266. border-bottom: none;
  267. }
  268. .activity-title {
  269. color: #333;
  270. overflow: hidden;
  271. text-overflow: ellipsis;
  272. white-space: nowrap;
  273. max-width: 70%;
  274. }
  275. .activity-time {
  276. color: #999;
  277. font-size: 12px;
  278. }
  279. .quick-actions {
  280. display: flex;
  281. gap: 15px;
  282. }
  283. </style>