LiveDashboard.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <template>
  2. <div class="dashboard-container">
  3. <!-- 实时大屏主体(100%占满父容器) -->
  4. <div class="dashboard">
  5. <!-- 顶部数据卡片区(占满宽度,高度20%) -->
  6. <div class="data-cards">
  7. <div class="card" v-for="item in dataCards" :key="item.title">
  8. <h3>{{ item.title }}</h3>
  9. <p class="value">{{ item.value }}</p>
  10. <div class="sub-values" v-if="item.subValues">
  11. <span class="sub-item">直播: {{ item.subValues.live }}</span>
  12. <span class="sub-item">回放: {{ item.subValues.replay }}</span>
  13. </div>
  14. </div>
  15. </div>
  16. <!-- 中间内容区(上下分两部分,各占40%高度) -->
  17. <div class="middle-content">
  18. <!-- 左侧用户分析区(占50%宽度) -->
  19. <div class="user-analysis">
  20. <div class="new-old">
  21. <h3>直播新老用户占比</h3>
  22. <EChartsComponent chartId="liveNewOldChart" :option="liveNewOldOption" />
  23. </div>
  24. <div class="region">
  25. <h3>回放新老用户占比</h3>
  26. <EChartsComponent chartId="replayNewOldChart" :option="replayNewOldOption" />
  27. </div>
  28. </div>
  29. <!-- 右侧来源与榜单区(占50%宽度) -->
  30. <div class="source-rank">
  31. <div class="source">
  32. <h3>观众来源</h3>
  33. <EChartsComponent chartId="sourceChart" :option="sourceOption" />
  34. </div>
  35. <div class="rank">
  36. <h3>邀请达人榜</h3>
  37. <ul class="rank-list" v-if="rankList.length > 0">
  38. <li v-for="(item, index) in rankList" :key="index">
  39. <span class="rank-num">{{ index + 1 }}</span>
  40. <span class="rank-name">{{ item.userName }}</span>
  41. <span class="rank-count">{{ item.inviteNum }}</span>
  42. </li>
  43. </ul>
  44. <p class="no-data" style="text-align: center;" v-if="rankList.length == 0">暂无数据</p>
  45. </div>
  46. </div>
  47. </div>
  48. <!-- 底部趋势图(占20%高度) -->
  49. <!-- <div class="trend">-->
  50. <!-- <h3>实时在线人数趋势</h3>-->
  51. <!-- <EChartsComponent chartId="trendChart" :option="trendOption" />-->
  52. <!-- </div>-->
  53. </div>
  54. </div>
  55. </template>
  56. <script>
  57. import EChartsComponent from './EchartsComponent.vue';
  58. import {dashboardData} from '@/api/live/liveData'
  59. export default {
  60. components: {EChartsComponent},
  61. props: {
  62. liveId: {
  63. type: String,
  64. default: null
  65. }
  66. },
  67. data() {
  68. return {
  69. // 数据保持不变(与原代码一致)
  70. dataCards: [
  71. {title: '在线人数', value: 0},
  72. {
  73. title: '观看人次',
  74. value: 0,
  75. subValues: {live: 0, replay: 0}
  76. },
  77. {
  78. title: '点赞数',
  79. value: 0,
  80. subValues: {live: 0, replay: 0}
  81. },
  82. {
  83. title: '评论数',
  84. value: 0,
  85. subValues: {live: 0, replay: 0}
  86. }
  87. ],
  88. liveNewOldOption: {
  89. tooltip: {trigger: 'item'},
  90. series: [{
  91. type: 'pie', radius: '70%',
  92. data: [{value: 0, name: '新用户'}, {value: 0, name: '老用户'}],
  93. itemStyle: {colors: ['#36cfc9', '#722ed1']}
  94. }]
  95. },
  96. replayNewOldOption: {
  97. tooltip: {trigger: 'item'},
  98. series: [{
  99. type: 'pie', radius: '70%',
  100. data: [{value: 0, name: '新用户'}, {value: 0, name: '老用户'}],
  101. itemStyle: {colors: ['#36cfc9', '#722ed1']}
  102. }]
  103. },
  104. regionOption: {
  105. tooltip: {trigger: 'item'},
  106. series: [{
  107. type: 'map', map: 'china', roam: true,
  108. itemStyle: {
  109. normal: {areaColor: '#36cfc9', borderColor: '#fff'},
  110. emphasis: {areaColor: '#722ed1'}
  111. },
  112. data: [{name: '北京', value: 120}, {name: '上海', value: 150}, {name: '广州', value: 90}, {
  113. name: '深圳',
  114. value: 80
  115. }, {name: '杭州', value: 70}]
  116. }]
  117. },
  118. sourceOption: {
  119. tooltip: {trigger: 'item'},
  120. series: [{
  121. type: 'pie', radius: '70%',
  122. data: [{value: 45, name: '分享链接'}, {value: 30, name: '直接访问'}, {value: 25, name: '其他'}],
  123. itemStyle: {colors: ['#3b82f6', '#10b981', '#f59e0b']}
  124. }]
  125. },
  126. rankList: [
  127. {userName: '达人1', inviteNum: 258},
  128. {userName: '达人2', inviteNum: 186},
  129. {userName: '达人3', inviteNum: 152},
  130. {userName: '达人4', inviteNum: 121},
  131. {userName: '达人5', inviteNum: 98}
  132. ],
  133. trendOption: {
  134. // 网格:控制图表与容器的边距,避免内容被裁剪
  135. grid: {
  136. left: '3%',
  137. right: '4%',
  138. bottom: '3%',
  139. top: '10%',
  140. containLabel: true // 确保坐标轴标签不被裁剪
  141. },
  142. // X轴配置(补充样式,适配深色背景)
  143. xAxis: {
  144. type: 'category',
  145. data: ['09:00', '09:10', '09:20', '09:30', '09:40', '09:50'],
  146. axisLine: {
  147. lineStyle: {
  148. color: '#475569' // 坐标轴线条颜色(深色背景可见)
  149. }
  150. },
  151. axisLabel: {
  152. color: '#cbd5e1' // 坐标轴文字颜色
  153. }
  154. },
  155. // Y轴配置(同上)
  156. yAxis: {
  157. type: 'value',
  158. axisLine: {
  159. lineStyle: {
  160. color: '#475569'
  161. }
  162. },
  163. axisLabel: {
  164. color: '#cbd5e1'
  165. },
  166. splitLine: {
  167. lineStyle: {
  168. color: 'rgba(71, 85, 105, 0.3)' // 网格线半透明
  169. }
  170. }
  171. },
  172. // 系列配置(补充线条和区域样式)
  173. series: [{
  174. data: [1200, 1500, 1800, 2100, 2300, 2500],
  175. type: 'line',
  176. smooth: true,
  177. // 线条样式
  178. lineStyle: {
  179. color: '#36cfc9',
  180. width: 3
  181. },
  182. // 点样式
  183. itemStyle: {
  184. color: '#36cfc9',
  185. borderWidth: 2,
  186. borderColor: '#fff' // 点边缘白色,突出显示
  187. },
  188. // 填充区域(可选,增强视觉效果)
  189. areaStyle: {
  190. color: {
  191. type: 'linear',
  192. x: 0,
  193. y: 0,
  194. x2: 0,
  195. y2: 1,
  196. colorStops: [{
  197. offset: 0, color: 'rgba(54, 207, 201, 0.3)'
  198. }, {
  199. offset: 1, color: 'rgba(54, 207, 201, 0)'
  200. }]
  201. }
  202. }
  203. }]
  204. },
  205. socket: null,
  206. timer: null
  207. };
  208. },
  209. created() {
  210. this.getInitData()
  211. this.timer = setInterval(() => {
  212. this.getInitData();
  213. }, 30000);
  214. },
  215. beforeDestroy() {
  216. clearInterval(this.timer); // 组件销毁时清除定时器
  217. },
  218. methods: {
  219. async getInitData() {
  220. dashboardData(this.liveId).then(res => {
  221. if (res.code == 200) {
  222. this.dealData(res.data)
  223. }
  224. });
  225. },
  226. dealData(data) {
  227. // 计算总计
  228. const totalViewNum = (data.liveViewNum || 0) + (data.replayViewNum || 0);
  229. const totalLikeNum = (data.liveLikeNum || 0) + (data.replayLikeNum || 0);
  230. const totalCommentNum = (data.liveCommentNum || 0) + (data.replayCommentNum || 0);
  231. this.dataCards = [
  232. {title: '在线人数', value: data.onlineNum || 0},
  233. {
  234. title: '观看人次',
  235. value: totalViewNum,
  236. subValues: {
  237. live: data.liveViewNum || 0,
  238. replay: data.replayViewNum || 0
  239. }
  240. },
  241. {
  242. title: '点赞数',
  243. value: totalLikeNum,
  244. subValues: {
  245. live: data.liveLikeNum || 0,
  246. replay: data.replayLikeNum || 0
  247. }
  248. },
  249. {
  250. title: '评论数',
  251. value: totalCommentNum,
  252. subValues: {
  253. live: data.liveCommentNum || 0,
  254. replay: data.replayCommentNum || 0
  255. }
  256. }
  257. ]
  258. // 直播新老用户占比
  259. this.liveNewOldOption = {
  260. tooltip: {trigger: 'item'},
  261. series: [{
  262. type: 'pie', radius: '70%',
  263. data: [
  264. {value: data.liveNewUserNum || 0, name: '新用户'},
  265. {value: data.liveOldUserNum || 0, name: '老用户'}
  266. ],
  267. itemStyle: {colors: ['#36cfc9', '#722ed1']}
  268. }]
  269. }
  270. // 回放新老用户占比
  271. this.replayNewOldOption = {
  272. tooltip: {trigger: 'item'},
  273. series: [{
  274. type: 'pie', radius: '70%',
  275. data: [
  276. {value: data.replayNewUserNum || 0, name: '新用户'},
  277. {value: data.replayOldUserNum || 0, name: '老用户'}
  278. ],
  279. itemStyle: {colors: ['#36cfc9', '#722ed1']}
  280. }]
  281. }
  282. // 观众来源
  283. this.sourceOption = {
  284. tooltip: {trigger: 'item'},
  285. series: [{
  286. type: 'pie', radius: '70%',
  287. data: [
  288. {value: data.shareUrlNum || 0, name: '分享链接'},
  289. {value: data.directAccessNum || 0, name: '直接访问'}
  290. ],
  291. itemStyle: {colors: ['#3b82f6', '#10b981']}
  292. }]
  293. }
  294. // 邀请用户列表
  295. this.rankList = data.inviteUserList || [];
  296. },
  297. }
  298. };
  299. </script>
  300. <style scoped>
  301. /* 容器占满整个屏幕 */
  302. .dashboard-container {
  303. width: 100vw;
  304. height: 92vh;
  305. overflow: hidden;
  306. background-color: #0f172a;
  307. }
  308. /* 大屏主体:100%宽高,内边距用百分比 */
  309. .dashboard {
  310. width: 100%;
  311. height: 100%;
  312. padding: 1%; /* 用百分比内边距,适配不同屏幕 */
  313. box-sizing: border-box;
  314. color: #fff;
  315. display: flex;
  316. flex-direction: column;
  317. gap: 1%; /* 区域间间距 */
  318. }
  319. /* 顶部数据卡片区:高度20%,横向排列 */
  320. .data-cards {
  321. width: 100%;
  322. height: 20%;
  323. display: flex;
  324. justify-content: space-between;
  325. gap: 1%; /* 卡片间间距 */
  326. }
  327. /* 每个卡片:平均分配宽度(4个卡片各占 ~24%) */
  328. .card {
  329. width: 24%; /* 100% - 3个间距(1%) = 97% → 97%/4 ≈ 24% */
  330. height: 100%;
  331. background: #1a202c;
  332. border-radius: 8px;
  333. padding: 1%;
  334. box-sizing: border-box;
  335. text-align: center;
  336. }
  337. .card h3 {
  338. font-size: 1.2vw; /* 字体用vw单位,随屏幕宽度缩放 */
  339. margin: 0 0 5% 0;
  340. }
  341. .card .value {
  342. font-size: 2vw;
  343. font-weight: bold;
  344. margin: 0;
  345. }
  346. .card .sub-values {
  347. display: flex;
  348. justify-content: space-around;
  349. margin-top: 10px;
  350. font-size: 0.8vw;
  351. color: #94a3b8;
  352. }
  353. .card .sub-item {
  354. padding: 3px 8px;
  355. background: rgba(54, 207, 201, 0.1);
  356. border-radius: 4px;
  357. }
  358. /* 中间内容区:高度40%,左右分栏 */
  359. .middle-content {
  360. width: 100%;
  361. height: 70%;
  362. display: flex;
  363. gap: 1%;
  364. }
  365. /* 左侧用户分析区:占50%宽度,内部上下分栏 */
  366. .user-analysis {
  367. width: 50%;
  368. height: 100%;
  369. display: flex;
  370. flex-direction: column;
  371. gap: 1%;
  372. }
  373. /* 右侧来源与榜单区:占50%宽度,内部上下分栏 */
  374. .source-rank {
  375. width: 50%;
  376. height: 100%;
  377. display: flex;
  378. flex-direction: column;
  379. gap: 1%;
  380. }
  381. /* 中间区域的子模块(新老用户/地域/来源/榜单):各占50%高度 */
  382. .new-old, .region, .source, .rank {
  383. width: 100%;
  384. height: 60.5%; /* 100% - 1个间距(1%) = 99% → 99%/2 ≈ 49.5% */
  385. background: #1a202c;
  386. border-radius: 8px;
  387. padding: 1%;
  388. box-sizing: border-box;
  389. }
  390. /* 标题样式:用vw单位适配 */
  391. .new-old h3, .region h3, .source h3, .rank h3, .trend h3 {
  392. font-size: 1.1vw;
  393. margin: 0 0 2% 0;
  394. }
  395. /* 达人榜单列表 */
  396. .rank-list {
  397. list-style: none;
  398. padding: 0;
  399. height: calc(100% - 15%); /* 减去标题高度 */
  400. overflow-y: auto;
  401. }
  402. .rank-list li {
  403. display: flex;
  404. justify-content: space-between;
  405. align-items: center;
  406. padding: 2%;
  407. margin-bottom: 1%;
  408. border-bottom: 1px solid #2d3748;
  409. font-size: 1vw;
  410. }
  411. .rank-num {
  412. width: 3vw;
  413. height: 3vw;
  414. max-width: 30px;
  415. max-height: 30px;
  416. border-radius: 50%;
  417. background: #36cfc9;
  418. display: flex;
  419. justify-content: center;
  420. align-items: center;
  421. font-size: 0.8vw;
  422. }
  423. /* 底部趋势图:高度20% */
  424. .trend {
  425. width: 100%;
  426. height: 20%;
  427. background: #1a202c;
  428. border-radius: 8px;
  429. padding: 1%;
  430. box-sizing: border-box;
  431. }
  432. /* ECharts图表容器:占满父元素 */
  433. ::v-deep .chart-container {
  434. width: 100% !important;
  435. height: calc(100% - 15%) !important; /* 减去标题高度 */
  436. }
  437. </style>