LiveDashboard.vue 12 KB

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