order.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. <template>
  2. <div class="page">
  3. <div class="top adfac">
  4. <div class="top-pre">
  5. <div class="top-pre-num adfacjb">
  6. <div class="top-pre-num-l">
  7. <p>支付订单数</p>
  8. <p class="n">{{ headData?.payOrderCount??0 }}</p>
  9. </div>
  10. <div class="top-pre-num-r">
  11. <img src="@/assets/images/agent/order_img1.png">
  12. </div>
  13. </div>
  14. <div class="top-pre-text adfac">
  15. <div class="tn">较上周&nbsp;{{ headData?.increasePayOrderCount??0 }}%&nbsp;<i class="el-icon-caret-top" style="color: #33A7A7;"></i></div>
  16. </div>
  17. </div>
  18. <div class="top-pre">
  19. <div class="top-pre-num adfacjb">
  20. <div class="top-pre-num-l">
  21. <p>支付人数</p>
  22. <p class="n">{{ headData?.payUserCount??0 }}</p>
  23. </div>
  24. <div class="top-pre-num-r">
  25. <img src="@/assets/images/agent/order_img2.png">
  26. </div>
  27. </div>
  28. <div class="top-pre-text adfac">
  29. <div class="tn">较上周&nbsp;{{ headData?.increasePayUserCount??0 }}%&nbsp;<i class="el-icon-caret-top" style="color: #33A7A7;"></i></div>
  30. </div>
  31. </div>
  32. <div class="top-pre">
  33. <div class="top-pre-num adfacjb">
  34. <div class="top-pre-num-l">
  35. <p>支付金额(元)</p>
  36. <p class="n">{{ headData?.totalOrderAmount??0 }}</p>
  37. </div>
  38. <div class="top-pre-num-r">
  39. <img src="@/assets/images/agent/order_img3.png">
  40. </div>
  41. </div>
  42. <div class="top-pre-text adfac">
  43. <div class="tn half">基础版&nbsp;{{ headData?.baseOrderAmount??0 }}</div>
  44. <div class="tn half">专业版&nbsp;{{ headData?.proOrderAmount??0 }}</div>
  45. </div>
  46. </div>
  47. <div class="top-pre">
  48. <div class="top-pre-num adfacjb">
  49. <div class="top-pre-num-l">
  50. <p>专业版总销量(次数)</p>
  51. <p class="n">{{ headData?.proSaleCount??0 }}</p>
  52. </div>
  53. <div class="top-pre-num-r">
  54. <img src="@/assets/images/agent/order_img4.png">
  55. </div>
  56. </div>
  57. <div class="top-pre-text adfac">
  58. <div class="tn half">待使用&nbsp;{{ headData?.proWaitCount??0 }}</div>
  59. <div class="tn half">已使用&nbsp;{{ headData?.proUsedCount??0 }}</div>
  60. </div>
  61. </div>
  62. <div class="top-pre">
  63. <div class="top-pre-num adfacjb">
  64. <div class="top-pre-num-l">
  65. <p>基础版总销量(次数)</p>
  66. <p class="n">{{ headData?.baseSaleCount??0 }}</p>
  67. </div>
  68. <div class="top-pre-num-r">
  69. <img src="@/assets/images/agent/order_img5.png">
  70. </div>
  71. </div>
  72. <div class="top-pre-text adfac">
  73. <div class="tn half">待使用&nbsp;{{ headData?.baseWaitCount??0 }}</div>
  74. <div class="tn half">已使用&nbsp;{{ headData?.baseUsedCount??0 }}</div>
  75. </div>
  76. </div>
  77. </div>
  78. <div class="middle adfacjb">
  79. <div class="box adffc">
  80. <div class="box-title">订单统计</div>
  81. <div class="box-echart">
  82. <div ref="ddtjRef" style="width: 100%; height: 100%;"></div>
  83. </div>
  84. </div>
  85. <div class="box adffc">
  86. <div class="box-title">支付人数</div>
  87. <div class="box-echart">
  88. <div ref="zfrsRef" style="width: 100%; height: 100%;"></div>
  89. </div>
  90. </div>
  91. </div>
  92. <div class="bottom">
  93. <div class="date adfac">
  94. <div class="date-pre" :class="{'active':didx==1}" @click="changeDate(1)">近7天</div>
  95. <div class="date-pre" :class="{'active':didx==2}" @click="changeDate(2)">本月</div>
  96. <el-date-picker @change="changeCusDate"
  97. v-model="queryParams.startEndTime"
  98. type="daterange"
  99. range-separator="至"
  100. start-placeholder="开始日期"
  101. end-placeholder="结束日期">
  102. </el-date-picker>
  103. </div>
  104. <div class="box adffc">
  105. <div class="box-title">订单金额</div>
  106. <div class="box-echart">
  107. <div ref="ddjeRef" style="width: 100%; height: 390px;"></div>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. </template>
  113. <script setup name="">
  114. import * as echarts from "echarts";
  115. import {
  116. getOrderHeadData,
  117. getOrderCount,
  118. getPayUserCount,
  119. getOrderMoney
  120. } from '@/api/agent/indexTwo.js'
  121. import { ref, getCurrentInstance, onMounted } from 'vue'
  122. const { proxy } = getCurrentInstance();
  123. const didx = ref(1)
  124. const queryParams = ref({
  125. beginTime:'',
  126. endTime:'',
  127. startEndTime:'',
  128. type:1
  129. })
  130. const headData = ref({})
  131. const ddtjRef = ref(null)
  132. const zfrsRef = ref(null)
  133. const ddjeRef = ref(null)
  134. const changeDate = (idx) => {
  135. if(idx===didx.value) return
  136. didx.value = idx;
  137. queryParams.value.type = idx;
  138. queryParams.value.beginTime = '';
  139. queryParams.value.endTime = '';
  140. queryParams.value.startEndTime = '';
  141. getOrderMoneyFn()
  142. }
  143. const changeCusDate = (e) => {
  144. didx.value = '';
  145. queryParams.value.type = 3;
  146. queryParams.value.beginTime = proxy.parseTime(new Date(e[0]), '{yy}-{mm}-{dd}');
  147. queryParams.value.endTime = proxy.parseTime(new Date(e[1]), '{yy}-{mm}-{dd}');
  148. getOrderMoneyFn()
  149. }
  150. const getOrderCountFn = () => {
  151. getOrderCount({type:1}).then(res => {
  152. const { startDate, endDate } = getDateRange()
  153. let data = fillMissingDates(res.data,startDate,endDate)
  154. let xdata = data.map(item => item.dateDay)
  155. let ydata = data.map(item => item.stats)
  156. initDdtjEcharts(xdata,ydata)
  157. })
  158. }
  159. const getPayUserCountFn = () => {
  160. getPayUserCount({type:1}).then(res => {
  161. const { startDate, endDate } = getDateRange()
  162. let data = fillMissingDates(res.data,startDate,endDate)
  163. let xdata = data.map(item => item.dateDay)
  164. let ydata = data.map(item => item.stats)
  165. initZfrsEcharts(xdata,ydata)
  166. })
  167. }
  168. const getOrderMoneyFn = () => {
  169. let params = JSON.parse(JSON.stringify(queryParams.value))
  170. delete params.startEndTime;
  171. getOrderMoney(params).then(res => {
  172. let startDate = '', endDate = '';
  173. if(queryParams.value.type<3){
  174. startDate = getDateRange(queryParams.value.type).startDate;
  175. endDate = getDateRange(queryParams.value.type).endDate;
  176. }else{
  177. startDate = queryParams.value.beginTime;
  178. endDate = queryParams.value.endTime;
  179. }
  180. let data = fillMissingDates(res.data,startDate,endDate)
  181. let xdata = data.map(item => item.dateDay)
  182. let ydata = data.map(item => item.stats)
  183. initDdjeEcharts(xdata,ydata)
  184. })
  185. }
  186. const initDdtjEcharts = (xdata,ydata) => {
  187. const ddtjChart = echarts.init(ddtjRef.value)
  188. let option = {
  189. color: ['#33A7A7'],
  190. xAxis: {
  191. type: 'category',
  192. data: xdata
  193. },
  194. yAxis: {
  195. type: 'value'
  196. },
  197. grid: {
  198. left: '1%',
  199. right: '1%',
  200. top: '10%',
  201. bottom: '1%',
  202. containLabel: true
  203. },
  204. tooltip: {
  205. trigger: 'axis',
  206. // backgroundColor: '#FFFFFF',
  207. // textStyle: {
  208. // fontSize: 16
  209. // },
  210. // formatter: function (params) {
  211. // let result = params[0].name + '<br/>'
  212. // params.forEach(item => {
  213. // result += '订单笔数:' + item.value + '<br/>'
  214. // })
  215. // return result;
  216. // }
  217. },
  218. series: [
  219. {
  220. data: ydata,
  221. type: 'line',
  222. smooth: true,
  223. lineStyle: {
  224. width: 3
  225. },
  226. showSymbol: false,
  227. areaStyle: {
  228. opacity: 0.8,
  229. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  230. {
  231. offset: 0,
  232. color: 'rgb(51,167,167,.1)'
  233. },
  234. {
  235. offset: 1,
  236. color: 'rgb(51,167,167,0)'
  237. }
  238. ])
  239. },
  240. }
  241. ]
  242. };
  243. ddtjChart.setOption(option)
  244. }
  245. const initZfrsEcharts = (xdata,ydata) => {
  246. const zfrsChart = echarts.init(zfrsRef.value)
  247. let option = {
  248. color: ['#33A7A7'],
  249. xAxis: {
  250. type: 'category',
  251. data: xdata
  252. },
  253. yAxis: {
  254. type: 'value'
  255. },
  256. tooltip: {
  257. trigger: 'axis'
  258. },
  259. grid: {
  260. left: '1%',
  261. right: '1%',
  262. top: '10%',
  263. bottom: '1%',
  264. containLabel: true
  265. },
  266. series: [
  267. {
  268. data: ydata,
  269. type: 'line',
  270. smooth: true,
  271. lineStyle: {
  272. width: 3
  273. },
  274. showSymbol: false,
  275. areaStyle: {
  276. opacity: 0.8,
  277. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  278. {
  279. offset: 0,
  280. color: 'rgb(51,167,167,.1)'
  281. },
  282. {
  283. offset: 1,
  284. color: 'rgb(51,167,167,0)'
  285. }
  286. ])
  287. },
  288. }
  289. ]
  290. };
  291. zfrsChart.setOption(option)
  292. }
  293. const initDdjeEcharts = (xdata,ydata) => {
  294. const ddjeChart = echarts.init(ddjeRef.value)
  295. let option = {
  296. color: ['#33A7A7'],
  297. xAxis: {
  298. type: 'category',
  299. data: xdata
  300. },
  301. yAxis: {
  302. type: 'value'
  303. },
  304. tooltip: {
  305. trigger: 'axis'
  306. },
  307. grid: {
  308. left: '1%',
  309. right: '1%',
  310. top: '10%',
  311. bottom: '1%',
  312. containLabel: true
  313. },
  314. series: [
  315. {
  316. data: ydata,
  317. type: 'line',
  318. smooth: true,
  319. lineStyle: {
  320. width: 3
  321. },
  322. showSymbol: false,
  323. areaStyle: {
  324. opacity: 0.8,
  325. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
  326. {
  327. offset: 0,
  328. color: 'rgb(51,167,167,.1)'
  329. },
  330. {
  331. offset: 1,
  332. color: 'rgb(51,167,167,0)'
  333. }
  334. ])
  335. },
  336. }
  337. ]
  338. };
  339. ddjeChart.setOption(option)
  340. }
  341. /**
  342. * 填充指定日期范围内缺失的数据。
  343. * 该函数会确保从开始日期到结束日期的每一天都有一条数据记录。
  344. * 如果某天的数据不存在,则会使用指定的默认值进行填充。
  345. *
  346. * @param {Array<Object>} dataList - 原始数据数组,例如 [{stats: 2, dateDay: "2025-11-06"}]
  347. * @param {string} startDateStr - 开始日期字符串, 格式为 "YYYY-MM-DD"
  348. * @param {string} endDateStr - 结束日期字符串, 格式为 "YYYY-MM-DD"
  349. * @param {Object} options - (可选) 配置项
  350. * @param {string} options.dateKey - (可选) 数据对象中表示日期的属性名,默认为 'dateDay'
  351. * @param {Object} options.defaultValue - (可选) 缺失日期的默认数据对象,默认为 { stats: 0 }
  352. * @returns {Array<Object>} 填充完毕的完整数据数组
  353. */
  354. function fillMissingDates(dataList, startDateStr, endDateStr, options = {}) {
  355. const {
  356. dateKey = 'dateDay',
  357. defaultValue = { stats: 0 }
  358. } = options;
  359. const dataMap = new Map(dataList.map(item => [item[dateKey], item]));
  360. const result = [];
  361. // 注意:这里的 new Date() 也会受时区影响,但配合下面的循环和 formatLocalDate 可以正确工作
  362. const currentDate = new Date(startDateStr);
  363. const endDate = new Date(endDateStr);
  364. // 将 currentDate 的时间部分设置为中午12点,可以更稳妥地避免夏令时等边界问题
  365. currentDate.setHours(12, 0, 0, 0);
  366. endDate.setHours(12, 0, 0, 0);
  367. while (currentDate <= endDate) {
  368. const dateString = formatDate(currentDate);
  369. if (dataMap.has(dateString)) {
  370. result.push(dataMap.get(dateString));
  371. } else {
  372. result.push({
  373. ...defaultValue,
  374. [dateKey]: dateString,
  375. });
  376. }
  377. // 将日期增加一天
  378. currentDate.setDate(currentDate.getDate() + 1);
  379. }
  380. return result;
  381. }
  382. /**
  383. * 格式化日期对象为 'YYYY.MM.DD' 格式的字符串
  384. * @param {Date} date - 需要格式化的日期对象
  385. * @returns {string} - 格式化后的日期字符串
  386. */
  387. function formatDate(date) {
  388. const year = date.getFullYear();
  389. const month = String(date.getMonth() + 1).padStart(2, '0');
  390. const day = String(date.getDate()).padStart(2, '0');
  391. return `${year}-${month}-${day}`;
  392. }
  393. /**
  394. * 根据类型获取指定的日期范围
  395. * @param {number} type - 类型 (1: 近7天, 2: 本月, 3: 本周)
  396. * @returns {{startDate: string, endDate: string} | {startDate: null, endDate: null}}
  397. */
  398. function getDateRange(type=1) {
  399. const today = new Date();
  400. // 为了避免跨天等时区问题,将时间统一设置为当天的零点
  401. today.setHours(0, 0, 0, 0);
  402. let startDate = new Date(today);
  403. const endDate = new Date(today); // 结束日期在所有情况下都是今天
  404. switch (type) {
  405. case 1:
  406. // --- 近7天 ---
  407. // 开始日期 = 今天 - 6天 (总共7天)
  408. startDate.setDate(today.getDate() - 6);
  409. break;
  410. case 2:
  411. // --- 本月 (从1号到今天) ---
  412. // 直接将开始日期的日设置为1号
  413. startDate.setDate(1);
  414. break;
  415. case 3:
  416. // --- 本周 (从周一到今天) ---
  417. // getDay() 返回 0(周日)-6(周六)。我们希望周一是1,周日是7
  418. const dayOfWeek = today.getDay() === 0 ? 7 : today.getDay();
  419. // 开始日期 = 今天 - (今天是本周第几天 - 1)
  420. startDate.setDate(today.getDate() - (dayOfWeek - 1));
  421. break;
  422. default:
  423. console.error("无效的类型参数,请输入 1, 2, 或 3。");
  424. return { startDate: null, endDate: null };
  425. }
  426. return {
  427. startDate: formatDate(startDate),
  428. endDate: formatDate(endDate),
  429. };
  430. }
  431. onMounted(()=>{
  432. getOrderHeadData().then(res=>{
  433. headData.value = res.data;
  434. })
  435. getOrderCountFn()
  436. getPayUserCountFn()
  437. getOrderMoneyFn()
  438. proxy.$nextTick(()=>{
  439. })
  440. })
  441. </script>
  442. <style scoped lang="scss">
  443. .page{
  444. padding: 16px;
  445. .top{
  446. justify-content: space-between;
  447. &-pre{
  448. width: calc(20% - 9.6px);
  449. background: #FFFFFF;
  450. box-shadow: 0px 2px 12px 0px rgba(0,0,0,0.06);
  451. border-radius: 6px;
  452. padding: 24px 20px;
  453. box-sizing: border-box;
  454. &-num{
  455. &-l{
  456. p{
  457. font-family: PingFangSC, PingFang SC;
  458. font-weight: 400;
  459. font-size: 14px;
  460. color: #335368;
  461. line-height: 14px;
  462. &.n{
  463. font-family: DINAlternate, DINAlternate;
  464. font-weight: bold;
  465. font-size: 28px;
  466. color: #002846;
  467. line-height: 28px;
  468. margin-top: 16px;
  469. }
  470. }
  471. }
  472. &-r{
  473. img{
  474. width: 60px;
  475. height: 60px;
  476. }
  477. }
  478. }
  479. &-text{
  480. margin-top: 16px;
  481. .tn{
  482. width: 100%;
  483. font-family: PingFangSC, PingFang SC;
  484. font-weight: 400;
  485. font-size: 14px;
  486. color: #667E90;
  487. line-height: 14px;
  488. &.half{
  489. width: 50%;
  490. }
  491. }
  492. }
  493. }
  494. }
  495. .box{
  496. background: #FFFFFF;
  497. box-shadow: 0px 2px 12px 0px rgba(0,0,0,0.06);
  498. border-radius: 6px;
  499. padding: 20px;
  500. box-sizing: border-box;
  501. }
  502. .box-title{
  503. font-family: PingFang-SC, PingFang-SC;
  504. font-weight: bold;
  505. font-size: 16px;
  506. color: #002846;
  507. line-height: 16px;
  508. }
  509. .box-echart{
  510. flex: 1;
  511. margin-top: 10px;
  512. }
  513. .middle{
  514. margin-top: 16px;
  515. .box{
  516. width: calc(50% - 6px);
  517. height: 390px;
  518. }
  519. }
  520. .bottom{
  521. margin-top: 16px;
  522. position: relative;
  523. }
  524. .date{
  525. position: absolute;
  526. top: 20px;
  527. right: 20px;
  528. &-pre{
  529. width: 54px;
  530. height: 40px;
  531. border-radius: 6px;
  532. border: 1px solid #DDE0E6;
  533. font-family: PingFangSC, PingFang SC;
  534. font-weight: 400;
  535. font-size: 14px;
  536. color: #335368;
  537. line-height: 40px;
  538. text-align: center;
  539. margin-right: 16px;
  540. cursor: pointer;
  541. &.active{
  542. background: rgba(51,167,167,0.08);
  543. border-radius: 6px;
  544. border: 1px solid #33A7A7;
  545. color: #009191;
  546. }
  547. }
  548. }
  549. }
  550. </style>