user-data.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. <?php
  2. use common\helpers\ComponentHelper;
  3. ComponentHelper::loadComponentView('components/my-table', __DIR__);
  4. ComponentHelper::loadComponentView('components/screen-contain', __DIR__);
  5. ComponentHelper::loadComponentView('components/header-time', __DIR__);
  6. ComponentHelper::loadComponentView('components/mini-echarts', __DIR__);
  7. ?>
  8. <style>
  9. .screen-container {
  10. min-width: 100%;
  11. }
  12. .bgCom {
  13. border: 2px solid #0061CC;
  14. margin-bottom: 16px;
  15. background: linear-gradient(135deg, #0034E4 0%, #14141A 13%, rgba(7, 7, 9, 0.89) 81%, rgba(20, 101, 255, 0.11) 100%);
  16. border-radius: 16px;
  17. padding: 30px;
  18. }
  19. .titleCom {
  20. height: 37px;
  21. font-size: 30px;
  22. font-family: DIN;
  23. font-weight: bold;
  24. line-height: 37px;
  25. color: #F8F8F8;
  26. }
  27. .subTitle {
  28. line-height: 37px;
  29. }
  30. .user-data-box {
  31. padding: 30px 60px;
  32. }
  33. .user-data-left {
  34. width: 1184px;
  35. margin-right: 16px;
  36. }
  37. .user-total {
  38. flex: 1;
  39. height: 348px;
  40. }
  41. .total-num {
  42. font-size: 30px;
  43. padding-right: 10px;
  44. }
  45. .total-up,
  46. .total-down {
  47. font-size: 12px;
  48. }
  49. .total-up {
  50. color: #1BBC26;
  51. }
  52. .total-down {
  53. color: #C12727;
  54. }
  55. .user-data-right {
  56. width: 512px;
  57. margin-bottom: 16px;
  58. }
  59. .user-statistical {
  60. height: 136px;
  61. }
  62. .user-clinch {
  63. height: 300px;
  64. }
  65. .block-items {
  66. height: 100%;
  67. }
  68. .preferences {
  69. height: 300px;
  70. }
  71. .table-goods {
  72. cursor: pointer;
  73. }
  74. .goods-name {
  75. color: #F8F8F8;
  76. padding-left: 6px;
  77. }
  78. .customer-new,
  79. .customer-old {
  80. position: relative;
  81. margin-left: 40px;
  82. }
  83. .customer-new::before,
  84. .customer-old::before {
  85. position: absolute;
  86. content: "";
  87. top: 50%;
  88. left: -20px;
  89. transform: translateY(-50%);
  90. width: 12px;
  91. height: 12px;
  92. border-radius: 50%;
  93. }
  94. .customer-new::before {
  95. background-color: #ED7453;
  96. }
  97. .customer-old::before {
  98. background-color: #0034E4;
  99. }
  100. .customer-canvas-left,
  101. .customer-canvas-right {
  102. /* width: 41%; */
  103. padding-top: 10px;
  104. flex: 1;
  105. }
  106. .canvas-lf-content,
  107. .canvas-rg-content {
  108. height: 200px;
  109. padding: 20px 0;
  110. text-align: right;
  111. }
  112. .canvas-rg-content {
  113. text-align: left;
  114. }
  115. .canvas-cont-amount {
  116. font-size: 28px;
  117. color: #F8F8F8;
  118. font-weight: 700;
  119. }
  120. .canvas-cont-text {
  121. font-size: 16px;
  122. color: #CCCFDA;
  123. white-space: nowrap;
  124. }
  125. .item {
  126. width: 203px;
  127. height: 92px;
  128. background: linear-gradient(90deg, rgba(0, 52, 228, 0.4) 0%, rgba(0, 0, 3, 0) 100%);
  129. border-radius: 8px;
  130. padding-left: 11px;
  131. }
  132. .item-number {
  133. font-size: 26px;
  134. font-family: DIN;
  135. font-weight: bold;
  136. line-height: 32px;
  137. color: #F8F8F8;
  138. }
  139. .item-label {
  140. font-size: 15px;
  141. font-family: Source Han Sans CN;
  142. font-weight: 400;
  143. line-height: 25px;
  144. color: #CCCFDA;
  145. opacity: 1;
  146. }
  147. .item-img {
  148. width: 60px;
  149. height: 45px;
  150. margin-bottom: 6px;
  151. }
  152. #echartsNum,
  153. #echartsFlow {
  154. width: 100%;
  155. height: 240px;
  156. }
  157. #echartsLevel {
  158. width: 100%;
  159. /* height: 150px; */
  160. height: 80%;
  161. }
  162. #echartsMakeMoney,
  163. #echartsMakePeple {
  164. /* width: 300px; */
  165. width: 90%;
  166. height: 200px;
  167. }
  168. </style>
  169. <!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script> -->
  170. <script src="/resources/unpkg/echarts@5.2.2/dist/echarts.min.js"></script>
  171. <div id="user-data" v-cloak>
  172. <screen-contain text-left="会员分析">
  173. <template v-slot:right>
  174. <header-time></header-time>
  175. </template>
  176. <div class="user-data-box">
  177. <div class="user-data-top">
  178. <div flex>
  179. <div class="bgCom user-total" style="margin-right: 16px;">
  180. <div class="title" flex="main:justify">
  181. <div class="titleCom">累计会员数</div>
  182. <div flex="cross:center">
  183. <div class="total-num">{{echartsData.cumulative_members?echartsData.cumulative_members.total_users:''}}</div>
  184. <!-- <div>
  185. <div class="total-up">环比 <i class="el-icon-arrow-up"></i>4.2%</div>
  186. <div class="total-down">同比 <i class="el-icon-arrow-down"></i>0.9%</div>
  187. </div> -->
  188. </div>
  189. </div>
  190. <div id="echartsNum"></div>
  191. </div>
  192. <div class="bgCom user-total">
  193. <div class="titleCom">会员活跃数</div>
  194. <div id="echartsFlow"></div>
  195. </div>
  196. </div>
  197. </div>
  198. <div class="user-data-bottom" flex="main:around">
  199. <div class="user-data-left">
  200. <div class="bgCom user-statistical" style="padding: 0 20px;" flex="wrap:wrap main:justify cross:center">
  201. <div class="item" flex="cross:center main:justify" v-for="(item,index) in flowList" :key="index" :style=" index >= flowList.length - 2 ? 'margin-bottom:0': '' ">
  202. <div>
  203. <div class="item-number">{{numSystemVal(echartsData.order[item.key])}}</div>
  204. <div class="item-label">{{item.text}}</div>
  205. </div>
  206. <div flex="wrap:wrap main:right">
  207. <div class="item-img">
  208. <mini-echarts></mini-echarts>
  209. </div>
  210. <!-- <div>
  211. <span style="color: #1BBC26;"><i class="el-icon-arrow-down"></i>1.2%</span>
  212. <span style="color: #C12727;"><i class="el-icon-arrow-up"></i>1.2%</span>
  213. </div> -->
  214. </div>
  215. </div>
  216. </div>
  217. <div class="bgCom user-clinch">
  218. <div flex="cross:center main:justify">
  219. <div class="titleCom">成交客户</div>
  220. <div flex>
  221. <div class="customer-new">新客户</div>
  222. <div class="customer-old">老客户</div>
  223. </div>
  224. </div>
  225. <div flex="main:justify">
  226. <div flex="cross:center main:justify" class="customer-canvas-left">
  227. <div class="canvas-lf-content" flex="dir:top main:justify">
  228. <div class="">
  229. <div class="canvas-cont-amount">{{numSystemVal(echartsData.deal_members.new_customers_pay_money)}}</div>
  230. <div class="canvas-cont-text">新客户成交金额(元)</div>
  231. </div>
  232. <div class="">
  233. <div class="canvas-cont-amount">{{numSystemVal(echartsData.deal_members.regular_customers_pay_money)}}</div>
  234. <div class="canvas-cont-text">老客户成交金额(元)</div>
  235. </div>
  236. </div>
  237. <div id="echartsMakeMoney"></div>
  238. </div>
  239. <div flex="cross:center main:justify" class="customer-canvas-right">
  240. <div id="echartsMakePeple"></div>
  241. <div class="canvas-rg-content" flex="dir:top main:justify">
  242. <div>
  243. <div class="canvas-cont-amount">{{numSystemVal(echartsData.deal_members.new_customers_pay_peoples)}}</div>
  244. <div class="canvas-cont-text">新客户成交人数(人)</div>
  245. </div>
  246. <div>
  247. <div class="canvas-cont-amount">{{numSystemVal(echartsData.deal_members.regular_customers_pay_peoples)}}</div>
  248. <div class="canvas-cont-text">老客户成交人数(人)</div>
  249. </div>
  250. </div>
  251. </div>
  252. </div>
  253. </div>
  254. </div>
  255. <div class="user-data-right" >
  256. <div class="bgCom block-items">
  257. <div class="titleCom">会员等级分布</div>
  258. <div id="echartsLevel"></div>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. </screen-contain>
  264. </div>
  265. <script>
  266. const app = new Vue({
  267. el: '#user-data',
  268. data() {
  269. return {
  270. flowList: [{
  271. key: 'repurchase_people',
  272. text: '复购会员数'
  273. }, {
  274. key: 'total_pay_people',
  275. text: '支付人数'
  276. }, {
  277. key: 'order_proportion',
  278. text: '访问-下单转化率'
  279. }, {
  280. key: 'pay_order_proportion',
  281. text: '访问-支付转化率'
  282. },],
  283. echartsData: {
  284. deal_members: {},
  285. order: {},
  286. }
  287. }
  288. },
  289. mounted() {
  290. this.getEchartsData();
  291. },
  292. methods: {
  293. getEchartsData(){
  294. let that = this;
  295. request({
  296. params: {
  297. r: 'mall/statistics/car-user-data',
  298. },
  299. method: 'get'
  300. }).then(e => {
  301. if (e.data.code === 0) {
  302. console.info(e.data.data);
  303. this.echartsData = e.data.data;
  304. } else {
  305. this.$message.error(e.data.msg);
  306. }
  307. this.echartsNumInit()
  308. this.echartsFlowInit()
  309. this.echartsLevelInit()
  310. this.echartsMakeMoneyInit()
  311. this.echartsMakePepleInit()
  312. });
  313. },
  314. echartsNumInit() {
  315. var chartDom = document.getElementById('echartsNum');
  316. var myChart = echarts.init(chartDom);
  317. // 指定图表的配置项和数据
  318. var option = {
  319. textStyle: {
  320. color: "#DCDFEB"
  321. },
  322. grid: {
  323. left: '0px',
  324. right: '20px',
  325. top: '30px',
  326. bottom: '0px',
  327. containLabel: true
  328. },
  329. color: ['#80FFA5'],
  330. tooltip: {
  331. trigger: 'axis',
  332. axisPointer: {
  333. type: 'cross',
  334. label: {
  335. backgroundColor: '#6a7985'
  336. }
  337. }
  338. },
  339. xAxis: [{
  340. type: 'category',
  341. boundaryGap: false,
  342. data: this.echartsData.cumulative_members.new_user.map((v) => {return v.day})
  343. }],
  344. yAxis: [{
  345. type: 'value',
  346. splitLine: {
  347. lineStyle: {
  348. color: "rgba(250,250,250,0.4)"
  349. }
  350. }
  351. }],
  352. series: [{
  353. // name: '交易金额',
  354. type: 'line',
  355. stack: 'Total',
  356. smooth: true,
  357. lineStyle: {
  358. width: '2',
  359. color: "#0034E4"
  360. },
  361. showSymbol: false,
  362. areaStyle: {
  363. opacity: 0.8,
  364. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  365. offset: 0,
  366. color: 'rgba(0, 52, 228, 0.42)'
  367. },
  368. {
  369. offset: 0.5,
  370. color: 'rgba(0, 52, 228, 0.28)'
  371. },
  372. {
  373. offset: 0.82,
  374. color: 'rgba(0, 52, 228, 0.08)'
  375. },
  376. {
  377. offset: 1,
  378. color: 'rgba(0, 52, 228, 0)'
  379. }
  380. ])
  381. },
  382. emphasis: {
  383. focus: 'series'
  384. },
  385. data: this.echartsData.cumulative_members.new_user.map((v) => {return v.num})
  386. }]
  387. };
  388. myChart.setOption(option);
  389. },
  390. echartsFlowInit() {
  391. var chartDom = document.getElementById('echartsFlow');
  392. var myChart = echarts.init(chartDom);
  393. // 指定图表的配置项和数据
  394. var option = {
  395. tooltip: {},
  396. grid: {
  397. left: '0',
  398. right: '20px',
  399. top: '30px',
  400. bottom: '0px',
  401. containLabel: true
  402. },
  403. xAxis: {
  404. type: 'category',
  405. data: this.echartsData.active_members.map((v) => {return v.day})
  406. },
  407. yAxis: {
  408. type: 'value'
  409. },
  410. series: [
  411. {
  412. data: this.echartsData.active_members.map((v) => {return v.num}),
  413. type: 'bar',
  414. itemStyle: {
  415. color: "#0034E4"
  416. }
  417. }
  418. ]
  419. };
  420. myChart.setOption(option);
  421. },
  422. echartsLevelInit() {
  423. var chartDom = document.getElementById('echartsLevel');
  424. var myChart = echarts.init(chartDom);
  425. let newData = [];
  426. this.echartsData.level.forEach(v => {
  427. let obj = {
  428. name: v.level_name,
  429. value: v.proportion,
  430. num: v.num,
  431. }
  432. newData.push(obj);
  433. })
  434. let legendData = []
  435. newData.forEach(it => {
  436. legendData.push({
  437. name: it.name,
  438. icon: 'circle',
  439. // 设置文本为红色
  440. textStyle: {
  441. color: '#fff'
  442. }
  443. })
  444. })
  445. console.info(JSON.stringify(legendData))
  446. var option = {
  447. tooltip: {
  448. trigger: 'item',
  449. formatter(params){
  450. let data = params.data;
  451. return `${data.name}:${data.value}%<br />会员人数:${data.num}人`
  452. }
  453. },
  454. legend: {
  455. orient: 'vertical',
  456. // left: 'left',
  457. bottom : '0',
  458. height: 40,
  459. width: '60%',
  460. itemWidth: 12,
  461. // itemHeight: 12,
  462. data: legendData
  463. },
  464. // color: ['#0034E4', '#0187DF', '#A2E157', '#EDA553', '#ED7453', '#F24E4E'],
  465. series: [{
  466. name: '会员等级分布',
  467. type: 'pie',
  468. radius: ['60%', '80%'],
  469. avoidLabelOverlap: false,
  470. top: 30,
  471. bottom: 80,
  472. left: 0,
  473. roundCap: true,
  474. label: {
  475. show: true,
  476. position: 'outside',
  477. color: '#fff',
  478. formatter: (params) => {
  479. return params.value + "%"
  480. }
  481. },
  482. labelLine: {
  483. show: true,
  484. length: 15,
  485. lineStyle: {
  486. color: '#fff'
  487. }
  488. },
  489. data: newData
  490. }]
  491. };
  492. option && myChart.setOption(option);
  493. },
  494. echartsMakeMoneyInit() {
  495. var chartDom = document.getElementById('echartsMakeMoney');
  496. var myChart = echarts.init(chartDom);
  497. let that = this;
  498. // new_customers_pay_money
  499. // regular_customers_pay_money
  500. var option = {
  501. tooltip: {
  502. trigger: 'item',
  503. formatter(params){
  504. let data = params.data;
  505. return `${data.name}:${data.value}%<br />交易金额:${JSON.stringify(that.echartsData.deal_members[data.key])}元`
  506. }
  507. },
  508. color: ['#0034E4', '#ED7453'],
  509. series: [{
  510. name: '成交客户',
  511. type: 'pie',
  512. radius: ['50%', '70%'],
  513. avoidLabelOverlap: false,
  514. label: {
  515. show: true,
  516. position: 'outside',
  517. color: '#fff',
  518. formatter: (params) => {
  519. return params.value + '%\n' + params.name + '成交金额占比';
  520. }
  521. },
  522. labelLine: {
  523. show: true,
  524. length: 15,
  525. length2: 6,
  526. lineStyle: {
  527. color: '#fff'
  528. }
  529. },
  530. data: [{
  531. value: this.echartsData.deal_members.regular_customers_pay_money_proportion,
  532. name: '老客户',
  533. key: "regular_customers_pay_money",
  534. },
  535. {
  536. value: this.echartsData.deal_members.new_customers_pay_money_proportion,
  537. name: '新客户',
  538. key: "new_customers_pay_money",
  539. }
  540. ]
  541. }]
  542. };
  543. option && myChart.setOption(option);
  544. },
  545. echartsMakePepleInit() {
  546. var chartDom = document.getElementById('echartsMakePeple');
  547. var myChart = echarts.init(chartDom);
  548. let that = this;
  549. var option = {
  550. tooltip: {
  551. trigger: 'item',
  552. formatter(params){
  553. let data = params.data;
  554. //
  555. return `${data.name}:${data.value}%<br />成交人数:${JSON.stringify(that.echartsData.deal_members[data.key])}人`
  556. }
  557. },
  558. color: ['#0034E4', '#ED7453'],
  559. series: [{
  560. name: '成交客户',
  561. type: 'pie',
  562. radius: ['50%', '70%'],
  563. avoidLabelOverlap: false,
  564. roundCap: true,
  565. label: {
  566. show: true,
  567. position: 'outside',
  568. color: '#fff',
  569. formatter: (params) => {
  570. return params.value + '%\n' + params.name + '数占比';
  571. }
  572. },
  573. labelLine: {
  574. show: true,
  575. length: 15,
  576. length2: 6,
  577. lineStyle: {
  578. color: '#fff'
  579. }
  580. },
  581. data: [{
  582. value: this.echartsData.deal_members.regular_customers_proportion,
  583. name: '老客户',
  584. key: 'regular_customers_pay_peoples',
  585. },
  586. {
  587. value: this.echartsData.deal_members.new_customers_proportion,
  588. name: '新客户',
  589. key: 'new_customers_pay_peoples',
  590. }
  591. ]
  592. }]
  593. };
  594. option && myChart.setOption(option);
  595. },
  596. numSystemVal(val) {
  597. if(isNaN(val)){
  598. // val = '-'
  599. }else if (val * 1 < 10000) {
  600. val = val * 1
  601. } else {
  602. if ((val * 1) % 10000 == 0) {
  603. val = parseInt((val * 1) / 10000) + 'W'
  604. } else {
  605. val = parseInt((val * 1) / 10000) + 'W+'
  606. }
  607. }
  608. return val
  609. }
  610. }
  611. })
  612. </script>
  613. <style>
  614. #user-data {
  615. height: 100%;
  616. }
  617. </style>