index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <template>
  2. <div class="dashboard-editor-container">
  3. <!-- 轮播图区域(保持不变) -->
  4. <el-row class="banner-section">
  5. <el-col :span="24">
  6. <div class="carousel-container">
  7. <div class="carousel-wrapper">
  8. <div
  9. class="carousel-slide"
  10. :style="{ transform: `translateX(-${currentIndex * 100}%)` }"
  11. >
  12. <div
  13. v-for="(item, index) in bannerList"
  14. :key="index"
  15. class="carousel-item"
  16. @click="handleBannerClick(item)"
  17. >
  18. <img
  19. :src="item.imageUrl"
  20. :alt="item.bannerName"
  21. class="carousel-bg-img"
  22. />
  23. <div class="carousel-overlay"></div>
  24. </div>
  25. </div>
  26. <div class="carousel-nav">
  27. <el-button circle class="nav-btn prev-btn" @click="prevSlide">
  28. <el-icon><ArrowLeft /></el-icon>
  29. </el-button>
  30. <el-button circle class="nav-btn next-btn" @click="nextSlide">
  31. <el-icon><ArrowRight /></el-icon>
  32. </el-button>
  33. </div>
  34. <div class="carousel-indicators">
  35. <div
  36. v-for="(item, index) in bannerList"
  37. :key="index"
  38. class="indicator-item"
  39. :class="{ active: currentIndex === index }"
  40. @click="goToSlide(index)"
  41. >
  42. <div class="indicator-dot"></div>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </el-col>
  48. </el-row>
  49. <!-- 顶部卡片区域,传入真实统计数据 -->
  50. <panel-group
  51. :total-order="totalOrderStat.totalOrderCount"
  52. :jd-order="totalOrderStat.jdOrderCount"
  53. :sf-order="totalOrderStat.sfOrderCount"
  54. :total-amount="totalOrderStat.totalAmount"
  55. @handle-set-line-chart-data="handleSetLineChartData"
  56. />
  57. <el-row :gutter="32" class="chart-continer">
  58. <el-col :xs="24" :sm="24" :lg="8" class="chart-section">
  59. <div class="chart-wrapper">
  60. <div class="chart-title">订单渠道占比</div>
  61. <pie-chart-one :chart-data="pieChartData" />
  62. </div>
  63. </el-col>
  64. <el-col :xs="24" :sm="24" :lg="8" class="chart-section">
  65. <div class="chart-wrapper">
  66. <div class="chart-title">近7天订单趋势</div>
  67. <!-- 传入三个系列数据、X轴标签和当前可见系列 -->
  68. <line-chart
  69. :jd-data="jdCounts"
  70. :sf-data="sfCounts"
  71. :total-data="totalCounts"
  72. :x-axis-data="xAxisLabels"
  73. :visible-series="visibleSeries"
  74. />
  75. </div>
  76. </el-col>
  77. </el-row>
  78. </div>
  79. </template>
  80. <script setup>
  81. import { ref, onMounted, onUnmounted, computed } from 'vue'
  82. import { ArrowRight, ArrowLeft } from '@element-plus/icons-vue'
  83. import PanelGroup from './dashboard/PanelGroup.vue'
  84. import LineChart from './dashboard/LineChart.vue'
  85. import PieChartOne from './dashboard/PieChart-one.vue'
  86. import { getTotalOrderStatist, getLast7DaysOrderStatistics } from '@/api/logistics/index.js'
  87. import { listIndexBanner } from '@/api/logistics/banner.js'
  88. // 轮播图数据
  89. const bannerList = ref([])
  90. const currentIndex = ref(0)
  91. let autoPlayTimer = null
  92. const interval = 5000
  93. // 统计数据
  94. const totalOrderStat = ref({
  95. totalOrderCount: 0,
  96. jdOrderCount: 0,
  97. sfOrderCount: 0,
  98. totalAmount: 0
  99. })
  100. const last7DaysOrders = ref([])
  101. // 计算属性:X轴标签(MM-DD)
  102. const xAxisLabels = computed(() => {
  103. return last7DaysOrders.value.map(item => {
  104. const date = new Date(item.statisticsDate)
  105. const month = (date.getMonth() + 1).toString().padStart(2, '0')
  106. const day = date.getDate().toString().padStart(2, '0')
  107. return `${month}-${day}`
  108. })
  109. })
  110. // 各系列数据
  111. const jdCounts = computed(() => last7DaysOrders.value.map(item => item.jdOrderCount))
  112. const sfCounts = computed(() => last7DaysOrders.value.map(item => item.sfOrderCount))
  113. const totalCounts = computed(() => last7DaysOrders.value.map(item => item.totalOrderCount))
  114. // 饼图数据
  115. const pieChartData = computed(() => [
  116. { value: totalOrderStat.value.jdOrderCount, name: '京东订单' },
  117. { value: totalOrderStat.value.sfOrderCount, name: '顺丰订单' }
  118. ])
  119. // 控制折线图显示哪些系列(默认全部显示)
  120. const visibleSeries = ref(['jd', 'sf', 'total'])
  121. // 卡片点击切换系列显示
  122. const handleSetLineChartData = (type) => {
  123. switch (type) {
  124. case 'newVisitis': // 总订单数卡片 -> 只显示总订单
  125. visibleSeries.value = ['total']
  126. break
  127. case 'messages': // 京东订单卡片 -> 只显示京东
  128. visibleSeries.value = ['jd']
  129. break
  130. case 'purchases': // 顺丰订单卡片 -> 只显示顺丰
  131. visibleSeries.value = ['sf']
  132. break
  133. case 'shoppings': // 总费用卡片(暂无每日费用,显示总订单趋势)
  134. visibleSeries.value = ['total']
  135. break
  136. default:
  137. visibleSeries.value = ['jd', 'sf', 'total']
  138. }
  139. }
  140. // 轮播图方法
  141. const nextSlide = () => {
  142. currentIndex.value = (currentIndex.value + 1) % bannerList.value.length
  143. resetAutoPlay()
  144. }
  145. const prevSlide = () => {
  146. currentIndex.value = (currentIndex.value - 1 + bannerList.value.length) % bannerList.value.length
  147. resetAutoPlay()
  148. }
  149. const goToSlide = (index) => {
  150. currentIndex.value = index
  151. resetAutoPlay()
  152. }
  153. const startAutoPlay = () => {
  154. stopAutoPlay()
  155. autoPlayTimer = setInterval(nextSlide, interval)
  156. }
  157. const stopAutoPlay = () => {
  158. if (autoPlayTimer) {
  159. clearInterval(autoPlayTimer)
  160. autoPlayTimer = null
  161. }
  162. }
  163. const resetAutoPlay = () => {
  164. stopAutoPlay()
  165. startAutoPlay()
  166. }
  167. const handleBannerClick = (item) => {
  168. if (item.linkUrl && item.linkUrl.startsWith('http')) {
  169. window.open(item.linkUrl, '_blank')
  170. }
  171. }
  172. // API 调用
  173. const getBannerList = () => {
  174. listIndexBanner().then(response => {
  175. bannerList.value = response.data
  176. startAutoPlay()
  177. })
  178. }
  179. const queryStatist = () => {
  180. getTotalOrderStatist().then(response => {
  181. totalOrderStat.value = response
  182. })
  183. }
  184. const queryLast7DayStatist = () => {
  185. getLast7DaysOrderStatistics().then(response => {
  186. last7DaysOrders.value = response
  187. })
  188. }
  189. onMounted(() => {
  190. getBannerList()
  191. queryStatist()
  192. queryLast7DayStatist()
  193. })
  194. onUnmounted(() => {
  195. stopAutoPlay()
  196. })
  197. </script>
  198. <style lang="scss" scoped>
  199. .dashboard-editor-container {
  200. padding: 16px;
  201. background-color: rgb(240, 242, 245);
  202. position: relative;
  203. .banner-section {
  204. .carousel-container {
  205. position: relative;
  206. border-radius: 8px;
  207. overflow: hidden;
  208. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  209. .carousel-wrapper {
  210. position: relative;
  211. overflow: hidden;
  212. min-height: 230px;
  213. .carousel-slide {
  214. display: flex;
  215. transition: transform 0.5s ease-in-out;
  216. height: 100%;
  217. .carousel-item {
  218. flex: 0 0 100%;
  219. position: relative;
  220. cursor: pointer;
  221. min-height: 230px;
  222. .carousel-bg-img {
  223. width: 100%;
  224. height: 100%;
  225. object-fit: cover;
  226. position: absolute;
  227. top: 0;
  228. left: 0;
  229. }
  230. .carousel-overlay {
  231. position: absolute;
  232. top: 0;
  233. left: 0;
  234. width: 100%;
  235. height: 100%;
  236. background: linear-gradient(
  237. 135deg,
  238. rgba(0, 0, 0, 0.7) 0%,
  239. rgba(0, 0, 0, 0.4) 50%,
  240. rgba(0, 0, 0, 0.2) 100%
  241. );
  242. }
  243. .carousel-content {
  244. position: relative;
  245. z-index: 2;
  246. padding: 20px;
  247. height: 100%;
  248. display: flex;
  249. flex-direction: column;
  250. justify-content: center;
  251. color: white;
  252. .carousel-title {
  253. font-size: 42px;
  254. font-weight: 700;
  255. margin-bottom: 20px;
  256. line-height: 1.2;
  257. text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  258. background: linear-gradient(135deg, #fff 0%, #e6f7ff 100%);
  259. -webkit-background-clip: text;
  260. -webkit-text-fill-color: transparent;
  261. background-clip: text;
  262. }
  263. .carousel-desc {
  264. font-size: 20px;
  265. margin-bottom: 36px;
  266. line-height: 1.6;
  267. color: rgba(255, 255, 255, 0.95);
  268. max-width: 600px;
  269. text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  270. }
  271. .carousel-button {
  272. background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
  273. color: white;
  274. border: none;
  275. padding: 16px 42px;
  276. font-size: 18px;
  277. transition: all 0.3s ease;
  278. box-shadow: 0 6px 20px rgba(64, 158, 255, 0.4);
  279. border-radius: 10px;
  280. border: 2px solid rgba(255, 255, 255, 0.3);
  281. align-self: flex-start;
  282. &:hover {
  283. background: linear-gradient(135deg, #66b1ff 0%, #409EFF 100%);
  284. transform: translateY(-3px);
  285. box-shadow: 0 10px 25px rgba(64, 158, 255, 0.5);
  286. }
  287. &:active {
  288. transform: translateY(-1px);
  289. }
  290. }
  291. }
  292. &:hover {
  293. .carousel-bg-img {
  294. transform: scale(1.05);
  295. transition: transform 0.8s ease;
  296. }
  297. }
  298. }
  299. }
  300. .carousel-nav {
  301. position: absolute;
  302. top: 50%;
  303. left: 0;
  304. right: 0;
  305. transform: translateY(-50%);
  306. display: flex;
  307. justify-content: space-between;
  308. padding: 0 20px;
  309. z-index: 3;
  310. .nav-btn {
  311. background: rgba(255, 255, 255, 0.2);
  312. border: 2px solid rgba(255, 255, 255, 0.3);
  313. color: white;
  314. width: 48px;
  315. height: 48px;
  316. display: flex;
  317. align-items: center;
  318. justify-content: center;
  319. &:hover {
  320. background: rgba(255, 255, 255, 0.3);
  321. border-color: rgba(255, 255, 255, 0.5);
  322. }
  323. .el-icon {
  324. font-size: 20px;
  325. font-weight: bold;
  326. }
  327. }
  328. }
  329. .carousel-indicators {
  330. position: absolute;
  331. bottom: 24px;
  332. left: 0;
  333. right: 0;
  334. display: flex;
  335. justify-content: center;
  336. gap: 12px;
  337. z-index: 3;
  338. .indicator-item {
  339. cursor: pointer;
  340. padding: 8px;
  341. .indicator-dot {
  342. width: 12px;
  343. height: 12px;
  344. border-radius: 50%;
  345. background: rgba(255, 255, 255, 0.4);
  346. transition: all 0.3s ease;
  347. }
  348. &.active {
  349. .indicator-dot {
  350. background: white;
  351. width: 32px;
  352. border-radius: 6px;
  353. box-shadow: 0 0 8px rgba(64, 158, 255, 0.6);
  354. }
  355. }
  356. &:hover:not(.active) {
  357. .indicator-dot {
  358. background: rgba(255, 255, 255, 0.7);
  359. transform: scale(1.2);
  360. }
  361. }
  362. }
  363. }
  364. }
  365. }
  366. }
  367. .chart-continer{
  368. padding: 0px 20px 0px 0px;
  369. .chart-title{
  370. height: 26px;
  371. font-weight: 400;
  372. font-size: 18px;
  373. color: #333333;
  374. line-height: 26px;
  375. &::before{
  376. content: ''; /* 必须设置content,否则伪元素不会显示 */
  377. display: inline-block; /* 伪元素默认是行内,设置为块级以控制宽高 */
  378. width: 4px;
  379. height: 16px;
  380. line-height: 26px;
  381. background: #2D71FF;
  382. border-radius: 2px 2px 2px 2px;
  383. margin-right: 6px;
  384. }
  385. }
  386. .chart-section{
  387. marging:0px !important;
  388. padding:0px !important;
  389. padding-left: 20px !important;
  390. }
  391. }
  392. .chart-wrapper {
  393. background: #fff;
  394. padding: 16px;
  395. margin-bottom: 16px;
  396. border-radius: 8px;
  397. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  398. }
  399. }
  400. @media (max-width: 1200px) {
  401. .dashboard-editor-container {
  402. .banner-section {
  403. .carousel-container {
  404. .carousel-wrapper {
  405. min-height: 190px;
  406. .carousel-slide {
  407. .carousel-item {
  408. min-height: 190px;
  409. .carousel-content {
  410. padding: 10px;
  411. .carousel-title {
  412. font-size: 36px;
  413. }
  414. .carousel-desc {
  415. font-size: 18px;
  416. }
  417. }
  418. }
  419. }
  420. }
  421. }
  422. }
  423. }
  424. }
  425. @media (max-width: 1024px) {
  426. .dashboard-editor-container {
  427. padding: 20px;
  428. .banner-section {
  429. .carousel-container {
  430. .carousel-wrapper {
  431. min-height: 240px;
  432. .carousel-slide {
  433. .carousel-item {
  434. min-height: 240px;
  435. .carousel-content {
  436. padding: 32px;
  437. .carousel-title {
  438. font-size: 32px;
  439. }
  440. .carousel-desc {
  441. font-size: 16px;
  442. margin-bottom: 30px;
  443. }
  444. .carousel-button {
  445. padding: 14px 36px;
  446. font-size: 16px;
  447. }
  448. }
  449. }
  450. }
  451. .carousel-nav {
  452. .nav-btn {
  453. width: 40px;
  454. height: 40px;
  455. }
  456. }
  457. }
  458. }
  459. }
  460. .chart-wrapper {
  461. padding: 12px;
  462. }
  463. }
  464. }
  465. @media (max-width: 768px) {
  466. .dashboard-editor-container {
  467. .banner-section {
  468. .carousel-container {
  469. .carousel-wrapper {
  470. min-height: 200px;
  471. .carousel-slide {
  472. .carousel-item {
  473. min-height: 200px;
  474. .carousel-content {
  475. padding: 24px;
  476. .carousel-title {
  477. font-size: 28px;
  478. margin-bottom: 16px;
  479. }
  480. .carousel-desc {
  481. font-size: 15px;
  482. margin-bottom: 24px;
  483. }
  484. .carousel-button {
  485. padding: 12px 32px;
  486. font-size: 15px;
  487. }
  488. }
  489. }
  490. }
  491. .carousel-nav {
  492. padding: 0 12px;
  493. .nav-btn {
  494. width: 36px;
  495. height: 36px;
  496. }
  497. }
  498. }
  499. }
  500. }
  501. }
  502. }
  503. @media (max-width: 480px) {
  504. .dashboard-editor-container {
  505. padding: 16px;
  506. .banner-section {
  507. .carousel-container {
  508. .carousel-wrapper {
  509. min-height: 180px;
  510. .carousel-slide {
  511. .carousel-item {
  512. min-height: 180px;
  513. .carousel-content {
  514. padding: 20px;
  515. .carousel-title {
  516. font-size: 24px;
  517. }
  518. .carousel-desc {
  519. font-size: 14px;
  520. }
  521. .carousel-button {
  522. padding: 10px 28px;
  523. font-size: 14px;
  524. }
  525. }
  526. }
  527. }
  528. .carousel-nav {
  529. display: none; /* 小屏幕隐藏导航箭头 */
  530. }
  531. .carousel-indicators {
  532. bottom: 16px;
  533. .indicator-item {
  534. .indicator-dot {
  535. width: 8px;
  536. height: 8px;
  537. }
  538. &.active {
  539. .indicator-dot {
  540. width: 24px;
  541. }
  542. }
  543. }
  544. }
  545. }
  546. }
  547. }
  548. }
  549. }
  550. </style>