index.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  1. <route lang="yaml">
  2. name: home
  3. meta:
  4. title: 主页
  5. icon: ant-design:home-twotone
  6. </route>
  7. <script setup lang="ts">
  8. import * as echarts from 'echarts'
  9. import api from '@/api'
  10. import { useRouter } from 'vue-router'
  11. import up from '@/assets/images/home/up.png'
  12. import down from '@/assets/images/home/down.png'
  13. import cxcd from '@/assets/images/home/cxcd.png'
  14. import bldd from '@/assets/images/home/bldd.png'
  15. import ddcx from '@/assets/images/home/ddcx.png'
  16. import xygl from '@/assets/images/home/xygl.png'
  17. import fygl from '@/assets/images/home/fygl.png'
  18. import bxgs from '@/assets/images/home/bxgs.png'
  19. import bxgszh from '@/assets/images/home/bxgszh.png'
  20. import ywygl from '@/assets/images/home/ywygl.png'
  21. import chinaMap from '@/assets/json/china.json'
  22. echarts.registerMap('china', chinaMap)
  23. const provinceMapLoaders: Record<string, () => Promise<any>> = {
  24. '安徽': () => import('@/assets/json/anhui.json'),
  25. '澳门': () => import('@/assets/json/aomen.json'),
  26. '北京': () => import('@/assets/json/beijing.json'),
  27. '重庆': () => import('@/assets/json/chongqing.json'),
  28. '福建': () => import('@/assets/json/fujian.json'),
  29. '甘肃': () => import('@/assets/json/gansu.json'),
  30. '广东': () => import('@/assets/json/guangdong.json'),
  31. '广西': () => import('@/assets/json/guangxi.json'),
  32. '贵州': () => import('@/assets/json/guizhou.json'),
  33. '海南': () => import('@/assets/json/hainan.json'),
  34. '河北': () => import('@/assets/json/hebei.json'),
  35. '黑龙江': () => import('@/assets/json/heilongjiang.json'),
  36. '河南': () => import('@/assets/json/henan.json'),
  37. '湖北': () => import('@/assets/json/hubei.json'),
  38. '湖南': () => import('@/assets/json/hunan.json'),
  39. '江苏': () => import('@/assets/json/jiangsu.json'),
  40. '江西': () => import('@/assets/json/jiangxi.json'),
  41. '吉林': () => import('@/assets/json/jilin.json'),
  42. '辽宁': () => import('@/assets/json/liaoning.json'),
  43. '内蒙': () => import('@/assets/json/neimenggu.json'),
  44. '宁夏': () => import('@/assets/json/ningxia.json'),
  45. '青海': () => import('@/assets/json/qinghai.json'),
  46. '山东': () => import('@/assets/json/shandong.json'),
  47. '上海': () => import('@/assets/json/shanghai.json'),
  48. '山西': () => import('@/assets/json/shanxi.json'),
  49. '陕西': () => import('@/assets/json/shanxi1.json'),
  50. '四川': () => import('@/assets/json/sichuan.json'),
  51. '天津': () => import('@/assets/json/tianjin.json'),
  52. '香港': () => import('@/assets/json/xianggang.json'),
  53. '新疆': () => import('@/assets/json/xinjiang.json'),
  54. '西藏': () => import('@/assets/json/xizang.json'),
  55. '云南': () => import('@/assets/json/yunnan.json'),
  56. '浙江': () => import('@/assets/json/zhejiang.json'),
  57. }
  58. const loadedProvinceMaps = new Set<string>()
  59. async function loadProvinceMap(name: string): Promise<boolean> {
  60. if (loadedProvinceMaps.has(name)) return true
  61. const loader = provinceMapLoaders[name]
  62. if (!loader) return false
  63. const module = await loader()
  64. echarts.registerMap(name, module.default)
  65. loadedProvinceMaps.add(name)
  66. return true
  67. }
  68. let myRouter = useRouter()
  69. // 地图
  70. let ChinaMap = ref()
  71. const initChinaMap = () => {
  72. const myChart = echarts.init(ChinaMap.value)
  73. let option = {
  74. tooltip: {
  75. formatter: function (e, t, n) {
  76. // return .5 == e.value ? e.name + ":有疑似病例" : e.seriesName + "<br />" + e.name + ":" + e.value
  77. return e.name + "<br />" +
  78. "业务员数量:" + e.value + "<br />" +
  79. "个代业务员数量:" + e.data.personalAgentCount + "<br />" +
  80. "渠道业务员数量:" + e.data.channelSalesmanCount
  81. }
  82. },
  83. visualMap: {
  84. min: 0,
  85. max: 1000,
  86. left: 26,
  87. bottom: 40,
  88. showLabel: !0,
  89. text: ["高", "低"],
  90. pieces: [{
  91. gt: 150,
  92. label: ">150",
  93. color: "#0485FF"
  94. }, {
  95. gte: 100,
  96. lte: 150,
  97. label: "100-150",
  98. color: "#2E9AFF"
  99. }, {
  100. gte: 50,
  101. lt: 100,
  102. label: "50-100",
  103. color: "#81C2FF"
  104. }, {
  105. gt: 0,
  106. lt: 50,
  107. label: "<50",
  108. color: "#BCDFFF"
  109. }, {
  110. value: 0,
  111. color: "#DCEEFF"
  112. }],
  113. show: !0
  114. },
  115. geo: {
  116. map: "china",
  117. roam: !1,
  118. scaleLimit: {
  119. min: 1,
  120. max: 2
  121. },
  122. zoom: 1.23,
  123. top: 120,
  124. label: {
  125. normal: {
  126. show: false,
  127. fontSize: "14",
  128. color: "rgba(0,0,0,0.7)"
  129. }
  130. },
  131. itemStyle: {
  132. normal: {
  133. //shadowBlur: 50,
  134. //shadowColor: 'rgba(0, 0, 0, 0.2)',
  135. borderColor: "rgba(0, 0, 0, 0.2)"
  136. },
  137. emphasis: {
  138. areaColor: "#f2d5ad",
  139. shadowOffsetX: 0,
  140. shadowOffsetY: 0,
  141. borderWidth: 0
  142. }
  143. }
  144. },
  145. series: [{
  146. name: "",
  147. type: "map",
  148. geoIndex: 0,
  149. data: chinaMapData.value
  150. }]
  151. };
  152. myChart.setOption(option)
  153. myChart.on('click', function (a) {
  154. console.log(a)
  155. if (a.data.code) {
  156. provinceStr.value = a.data.code
  157. provinceName.value = a.data.name
  158. initBusinessDistribution()
  159. }
  160. })
  161. window.addEventListener('resize', () => {
  162. myChart.resize()
  163. })
  164. }
  165. // 市级地图
  166. let CityMap = ref()
  167. const initCityMap = async (name) => {
  168. console.log(name)
  169. let str = ''
  170. if (name.length == 3) {
  171. str = name.substring(0, 2)
  172. await initMap(str)
  173. } else if (name.length == 4) {
  174. str = name.substring(0, 3)
  175. await initMap(str)
  176. } else if (name.length > 4) {
  177. str = name.substring(0, 2)
  178. await initMap(str)
  179. }
  180. }
  181. const initMap = async (str) => {
  182. console.log(str)
  183. const loaded = await loadProvinceMap(str)
  184. if (!loaded) return
  185. const myChart = echarts.init(CityMap.value)
  186. let option = {
  187. tooltip: {
  188. formatter: function (e, t, n) {
  189. // return .5 == e.value ? e.name + ":有疑似病例" : e.seriesName + "<br />" + e.name + ":" + e.value
  190. return e.name + "<br />" +
  191. "业务员数量2:" + e.value + "<br />" +
  192. "个代业务员数量:" + e.data.personalAgentCount + "<br />" +
  193. "渠道业务员数量:" + e.data.channelSalesmanCount
  194. }
  195. },
  196. visualMap: {
  197. min: 0,
  198. max: 1000,
  199. left: 26,
  200. bottom: 40,
  201. showLabel: !0,
  202. text: ["高", "低"],
  203. pieces: [{
  204. gt: 150,
  205. label: ">150",
  206. color: "#0485FF"
  207. }, {
  208. gte: 100,
  209. lte: 150,
  210. label: "100-150",
  211. color: "#2E9AFF"
  212. }, {
  213. gte: 50,
  214. lt: 100,
  215. label: "50-100",
  216. color: "#81C2FF"
  217. }, {
  218. gt: 0,
  219. lt: 50,
  220. label: "<50",
  221. color: "#BCDFFF"
  222. }, {
  223. value: 0,
  224. color: "#DCEEFF"
  225. }],
  226. show: !0
  227. },
  228. geo: {
  229. map: str,
  230. roam: !1,
  231. scaleLimit: {
  232. min: 1,
  233. max: 2
  234. },
  235. zoom: 1,
  236. top: 30,
  237. label: {
  238. normal: {
  239. show: true,
  240. fontSize: "14",
  241. color: "rgba(0,0,0,0.7)"
  242. }
  243. },
  244. itemStyle: {
  245. normal: {
  246. //shadowBlur: 50,
  247. //shadowColor: 'rgba(0, 0, 0, 0.2)',
  248. borderColor: "rgba(0, 0, 0, 0.2)"
  249. },
  250. emphasis: {
  251. areaColor: "#f2d5ad",
  252. shadowOffsetX: 0,
  253. shadowOffsetY: 0,
  254. borderWidth: 0
  255. }
  256. }
  257. },
  258. series: [{
  259. name: "",
  260. type: "map",
  261. geoIndex: 0,
  262. data: subMapData.value
  263. }]
  264. };
  265. myChart.setOption(option)
  266. window.addEventListener('resize', () => {
  267. myChart.resize()
  268. })
  269. }
  270. // 出单趋势
  271. let TrendRef = ref()
  272. const initTrend = () => {
  273. const myChart = echarts.init(TrendRef.value)
  274. let option = {
  275. grid: {
  276. left: '20',
  277. right: '20',
  278. top: '20%',
  279. bottom: '15%',
  280. containLabel: true
  281. },
  282. tooltip: {
  283. trigger: 'axis',
  284. axisPointer: {
  285. type: 'cross',
  286. label: {
  287. backgroundColor: '#6a7985'
  288. }
  289. }
  290. },
  291. legend: {
  292. show: true,
  293. right: '10',
  294. top: '10',
  295. icon: 'stack',
  296. itemWidth: 10,
  297. itemHeight: 10,
  298. textStyle: {
  299. color: '#333333'
  300. },
  301. data: ['订单数', '保费']
  302. },
  303. xAxis: [
  304. {
  305. type: 'category',
  306. boundaryGap: false,
  307. axisLabel: {
  308. color: '#666666'
  309. },
  310. axisLine: {
  311. show: true,
  312. lineStyle: {
  313. color: '#999999'
  314. }
  315. },
  316. axisTick: {
  317. show: false,
  318. },
  319. data: orderTrend.value.xAxis
  320. }
  321. ],
  322. yAxis: [
  323. {
  324. type: 'value',
  325. name: '信息量',
  326. min: 0,
  327. max: 100000,
  328. axisLabel: {
  329. formatter: '{value}',
  330. textStyle: {
  331. color: '#666666'
  332. }
  333. },
  334. axisTick: {
  335. show: false,
  336. }
  337. }
  338. ],
  339. series: [
  340. {
  341. name: '订单数',
  342. type: 'line',
  343. stack: '总量',
  344. symbol: 'circle',
  345. symbolSize: 8,
  346. itemStyle: {
  347. normal: {
  348. color: '#15EB87',
  349. lineStyle: {
  350. color: "#15EB87",
  351. width: 1
  352. },
  353. areaStyle: {
  354. //color: '#94C9EC'
  355. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  356. offset: 0,
  357. color: 'rgba(21,235,135,0.3)'
  358. }, {
  359. offset: 1,
  360. color: 'rgba(21,235,135,0.1)'
  361. }]),
  362. }
  363. }
  364. },
  365. markPoint: {
  366. itemStyle: {
  367. normal: {
  368. color: 'red'
  369. }
  370. }
  371. },
  372. data: orderTrend.value.orderCount
  373. },
  374. {
  375. name: '保费',
  376. type: 'line',
  377. stack: '总量',
  378. symbol: 'circle',
  379. symbolSize: 8,
  380. itemStyle: {
  381. normal: {
  382. color: '#14D5E1',
  383. lineStyle: {
  384. color: "#14D5E1",
  385. width: 1
  386. },
  387. areaStyle: {
  388. //color: '#94C9EC'
  389. color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  390. offset: 0,
  391. color: 'rgba(20,213,225,0.3)'
  392. }, {
  393. offset: 1,
  394. color: 'rgba(20,213,225,0.1)'
  395. }]),
  396. }
  397. }
  398. },
  399. data: orderTrend.value.premium
  400. }
  401. ]
  402. };
  403. myChart.setOption(option)
  404. window.addEventListener('resize', () => {
  405. myChart.resize()
  406. })
  407. }
  408. // 业务漏斗
  409. let FunnelRef = ref()
  410. const initFunnel = () => {
  411. const myChart = echarts.init(FunnelRef.value)
  412. let colors = ['#20c8ff', '#2cb7ff', '#1785ef'];
  413. let option = {
  414. backgroundColor: '#ffffff',
  415. color: colors,
  416. tooltip: {
  417. trigger: "axis",
  418. axisPointer: {
  419. type: "cross",
  420. label: {
  421. backgroundColor: "red"
  422. },
  423. lineStyle: {
  424. color: "#9eb2cb"
  425. }
  426. }
  427. },
  428. legend: {
  429. show: false,
  430. top: "top",
  431. left: "2%",
  432. textStyle: {
  433. color: "#000"
  434. },
  435. itemHeight: 2,
  436. data: ['报价订单', '核保订单', '承保订单']
  437. },
  438. grid: {
  439. top: 24,
  440. left: "2%",
  441. right: 20,
  442. bottom: 30,
  443. containLabel: true,
  444. borderWidth: 0.5
  445. },
  446. series: [
  447. {
  448. top: 40,
  449. type: 'funnel',
  450. left: '10%',
  451. width: '50%',
  452. bottom: 0,
  453. gap: 16,
  454. minSize: 150,
  455. maxSize: 250,
  456. label: {
  457. show: true,
  458. color: '#ffffff',
  459. position: 'inside',
  460. formatter: '{c}%'
  461. },
  462. data: businessFunnelRadio.value
  463. },
  464. {
  465. top: 40,
  466. type: 'funnel',
  467. left: '10%',
  468. width: '50%',
  469. gap: 16,
  470. bottom: 0,
  471. z: 1,
  472. minSize: 150,
  473. maxSize: 150,
  474. label: {
  475. normal: {
  476. color: '#333333',
  477. position: 'right',
  478. formatter: '{b}{c}'
  479. },
  480. },
  481. //右侧的百分比显示的地方
  482. labelLine: {
  483. show: true,
  484. normal: {
  485. length: 200,
  486. position: 'right',
  487. lineStyle: {
  488. width: 1,
  489. color: '#999999',
  490. type: 'solid'
  491. },
  492. },
  493. },
  494. //主体是透明的
  495. itemStyle: {
  496. normal: {
  497. color: 'transparent',
  498. borderWidth: 0,
  499. opacity: 1
  500. }
  501. },
  502. data: businessFunnel.value
  503. }
  504. ]
  505. };
  506. myChart.setOption(option)
  507. window.addEventListener('resize', () => {
  508. myChart.resize()
  509. })
  510. }
  511. // 业务类型分布
  512. let PieRef = ref()
  513. const initPie = () => {
  514. const myChart = echarts.init(PieRef.value)
  515. let option = {
  516. legend: {
  517. top: 'bottom'
  518. },
  519. series: [
  520. {
  521. name: 'Nightingale Chart',
  522. type: 'pie',
  523. radius: [40, 100],
  524. center: ['50%', '50%'],
  525. roseType: 'area',
  526. color: ["#3BD5DC", "#26A8FF", "#FFC61B"],
  527. data: distributionData.value,
  528. label: {
  529. show: true,
  530. formatter: '{b}{c}%'
  531. }
  532. }
  533. ]
  534. };
  535. myChart.setOption(option)
  536. window.addEventListener('resize', () => {
  537. myChart.resize()
  538. })
  539. }
  540. // 今日数据
  541. let dailyData = ref({
  542. insuranceIssuersCount: '',
  543. insuranceIssuersCountChange: '',
  544. orderQuantity: '',
  545. orderQuantityChange: '',
  546. premium: '',
  547. premiumChange: '',
  548. quoteCount: '',
  549. quoteCountChange: ''
  550. })
  551. const initDailyData = () => {
  552. api.homeApi.dailyData().then((res: any) => {
  553. if (res.code == 200) {
  554. dailyData.value = res.data
  555. } else {
  556. dailyData.value = {
  557. insuranceIssuersCount: '',
  558. insuranceIssuersCountChange: '',
  559. orderQuantity: '',
  560. orderQuantityChange: '',
  561. premium: '',
  562. premiumChange: '',
  563. quoteCount: '',
  564. quoteCountChange: ''
  565. }
  566. }
  567. })
  568. }
  569. let orderTrend = ref({
  570. xAxis: [],
  571. orderCount: [],
  572. premium: []
  573. })
  574. const initOrderTrend = () => {
  575. api.homeApi.orderTrend().then((res: any) => {
  576. if (res.code == 200) {
  577. res.data?.forEach(item => {
  578. orderTrend.value.xAxis.push(`${item.month}月`)
  579. orderTrend.value.orderCount.push(item.orderCount)
  580. orderTrend.value.premium.push(item.premium)
  581. })
  582. } else {
  583. orderTrend.value = {
  584. xAxis: [],
  585. orderCount: [],
  586. premium: []
  587. }
  588. }
  589. initTrend()
  590. })
  591. }
  592. let businessFunnel = ref([])
  593. let businessFunnelRadio = ref([])
  594. const initBusinessFunnel = () => {
  595. api.homeApi.businessFunnel().then((res: any) => {
  596. if (res.code == 200) {
  597. businessFunnel.value = [
  598. {
  599. value: res.data.quoteCount,
  600. name: '报价订单'
  601. }, {
  602. value: res.data.underwriteCount,
  603. name: '核保订单'
  604. }, {
  605. value: res.data.orderCount,
  606. name: '承保订单'
  607. },
  608. ]
  609. businessFunnelRadio.value = [
  610. {
  611. value: res.data.quoteToUnderwriteRatio,
  612. name: '报价订单'
  613. }, {
  614. value: res.data.underwriteToOrderRatio,
  615. name: '核保订单'
  616. }, {
  617. value: res.data.quoteToOrderRatio,
  618. name: '承保订单'
  619. },
  620. ]
  621. } else {
  622. businessFunnel.value = []
  623. businessFunnelRadio.value = []
  624. }
  625. initFunnel();
  626. })
  627. }
  628. let distributionData = ref([])
  629. const initDistribution = () => {
  630. api.homeApi.distribution().then((res: any) => {
  631. if (res.code == 200) {
  632. distributionData.value = [
  633. {
  634. name: '交强险',
  635. value: res.data.ciProportion || 0
  636. }, {
  637. name: '商业险',
  638. value: res.data.biProportion || 0
  639. }, {
  640. name: '驾意险',
  641. value: res.data.niProportion || 0
  642. },
  643. ]
  644. } else {
  645. distributionData.value = []
  646. }
  647. initPie()
  648. })
  649. }
  650. let chinaMapData = ref([])
  651. let subMapData = ref([])
  652. let provinceStr = ref('')
  653. let provinceName = ref('')
  654. const initBusinessDistribution = async () => {
  655. console.log(provinceStr.value)
  656. api.homeApi.businessDistribution(provinceStr.value).then(async (res: any) => {
  657. if (!provinceStr.value) {
  658. if (res.code == 200) {
  659. chinaMapData.value = res.data?.map(item => {
  660. return {
  661. name: item.areaCname,
  662. code: item.areaCode,
  663. value: item.totalSalesmanCount,
  664. personalAgentCount: item.personalAgentCount,
  665. channelSalesmanCount: item.channelSalesmanCount
  666. }
  667. })
  668. } else {
  669. chinaMapData.value = []
  670. }
  671. initChinaMap()
  672. } else {
  673. if (res.code == 200) {
  674. subMapData.value = res.data?.map(item => {
  675. return {
  676. name: item.areaCname,
  677. code: item.areaCode,
  678. value: item.totalSalesmanCount,
  679. personalAgentCount: item.personalAgentCount,
  680. channelSalesmanCount: item.channelSalesmanCount
  681. }
  682. })
  683. } else {
  684. subMapData.value = []
  685. }
  686. await initCityMap(provinceName.value)
  687. }
  688. })
  689. }
  690. let functionsList = ref([])
  691. const initCommonFunctions = () => {
  692. api.homeApi.commonFunctions().then((res: any) => {
  693. if (res.code == 200) {
  694. functionsList.value = res.data
  695. } else {
  696. functionsList.value = []
  697. }
  698. })
  699. }
  700. let newsList = ref([])
  701. const getNewList = () => {
  702. api.homeApi.messageList({
  703. type: "2",
  704. pages: 1,
  705. size: 25
  706. }).then((res: any) => {
  707. if (res.code == 200) {
  708. newsList.value = res.data.records
  709. }
  710. })
  711. }
  712. const handleRouterChange = (item, index) => {
  713. myRouter.push({
  714. name: item.name,
  715. })
  716. }
  717. let downloadUrl = ref('')// app二维码
  718. let appPath = ref('')// app最新版本下载地址
  719. // 获取app最新版本下载地址
  720. const initAppLatestVersion = () => {
  721. return new Promise((resolve) => {
  722. api.appApi.appLatestVersion({
  723. appid: "__UNI__05D695B",
  724. packageType: "apk",
  725. }).then((res: any) => {
  726. if (res.code == 200) {
  727. appPath.value = res.data.path // 使用实际的下载路径
  728. } else {
  729. appPath.value = ''
  730. }
  731. resolve(true)
  732. }).catch(() => {
  733. appPath.value = ''
  734. resolve(false)
  735. })
  736. })
  737. }
  738. onMounted(async () => {
  739. let userInfo = JSON.parse(localStorage.getItem('userInfo'))
  740. if (userInfo?.province) {
  741. await initCityMap(userInfo?.province);
  742. }
  743. initDailyData();
  744. getNewList();
  745. initOrderTrend();
  746. initBusinessFunnel();
  747. initDistribution();
  748. initBusinessDistribution();
  749. initCommonFunctions();
  750. await initAppLatestVersion();
  751. // initChinaMap();
  752. // initTrend();
  753. // initFunnel();
  754. // initPie();
  755. const QRCode = (await import('qrcode')).default
  756. await QRCode.toDataURL(appPath.value, {
  757. width: 116,
  758. height: 116,
  759. }).then((url: string) => {
  760. downloadUrl.value = url
  761. }).catch((err: any) => {
  762. console.log(err)
  763. })
  764. })
  765. </script>
  766. <template>
  767. <div class="container111">
  768. <div class="w-full left-section">
  769. <PageMain class="ecology">
  770. <div class="title">今日数据</div>
  771. <div class="dataList">
  772. <div class="item">
  773. <div class="name">
  774. 今日保费(万元)
  775. </div>
  776. <div class="data">
  777. <span class="number">
  778. {{ dailyData.premium }}
  779. </span>
  780. <span class="line">
  781. 较昨日<img :src="dailyData.premiumChange > 0 ? up : down" />{{ dailyData.premiumChange || 0 }}%
  782. </span>
  783. </div>
  784. </div>
  785. <div class="item">
  786. <div class="name">
  787. 今日承保订单数(笔)
  788. </div>
  789. <div class="data">
  790. <span class="number">
  791. {{ dailyData.orderQuantity }}
  792. </span>
  793. <span class="line">
  794. 较昨日<img :src="dailyData.orderQuantityChange > 0 ? up : down" />{{ dailyData.orderQuantityChange || 0 }}%
  795. </span>
  796. </div>
  797. </div>
  798. <div class="item">
  799. <div class="name">
  800. 今日出单业务员(人)
  801. </div>
  802. <div class="data">
  803. <span class="number">
  804. {{ dailyData.insuranceIssuersCount }}
  805. </span>
  806. <span class="line">
  807. 较昨日<img :src="dailyData.insuranceIssuersCountChange > 0 ? up : down" />{{
  808. dailyData.insuranceIssuersCountChange
  809. || 0 }}%
  810. </span>
  811. </div>
  812. </div>
  813. <div class="item">
  814. <div class="name">
  815. 今日报价次数(次)
  816. </div>
  817. <div class="data">
  818. <span class="number">
  819. {{ dailyData.quoteCount }}
  820. </span>
  821. <span class="line">
  822. 较昨日<img :src="dailyData.quoteCountChange > 0 ? up : down" />{{ dailyData.quoteCountChange || 0 }}%
  823. </span>
  824. </div>
  825. </div>
  826. </div>
  827. </PageMain>
  828. <PageMain class="ecology">
  829. <div class="title">常用功能</div>
  830. <div class="functionList">
  831. <div class="item flex a-c j-c f-c " v-for="(item, index) in functionsList" :key="item.id"
  832. @click="handleRouterChange(item, index)">
  833. <img class="icon" :src="item.ican" />
  834. <div class="name">{{ item.title }}</div>
  835. </div>
  836. </div>
  837. </PageMain>
  838. <div class="mapBox">
  839. <div class="map">
  840. <div class="title">业务员分布</div>
  841. <div class="chinaMap" ref="ChinaMap"></div>
  842. </div>
  843. <div class="level">
  844. <div class="title">{{ provinceName || '市级' }}</div>
  845. <div class="cityMap" ref="CityMap"></div>
  846. </div>
  847. </div>
  848. <PageMain class="ecology">
  849. <div class="title">出单趋势</div>
  850. <div class="orderTrend" ref="TrendRef"></div>
  851. </PageMain>
  852. <div class="otherBox">
  853. <div class="funnel">
  854. <div class="title">业务漏斗</div>
  855. <div class="chinaMap" ref="FunnelRef"></div>
  856. </div>
  857. <div class="pie">
  858. <div class="title">业务类型分布</div>
  859. <div class="cityMap" ref="PieRef"></div>
  860. </div>
  861. </div>
  862. </div>
  863. <PageMain class="right-section ">
  864. <div class="title">业务动态</div>
  865. <!-- 订单 -->
  866. <div class="quoteOrder mt-[24px]">
  867. <div class="item flex f-c mb-[16px]" v-for="item in newsList" :key="item.id">
  868. <div class="flex a-c j-s mb-[8px]">
  869. <span class="Order-title">报价订单</span>
  870. <span class="Order-time">{{ item.createTime }}</span>
  871. </div>
  872. <span class="Order-premium">{{ item.title }}</span>
  873. </div>
  874. </div>
  875. <!-- app二维码 -->
  876. <div class="qrcode flex a-c f-c">
  877. <img :src="downloadUrl" alt="">
  878. <span>扫码下载App</span>
  879. </div>
  880. </PageMain>
  881. </div>
  882. </template>
  883. <style lang="scss" scoped>
  884. .container111 {
  885. display: flex;
  886. gap: 0;
  887. }
  888. .right-section {
  889. flex: 0 0 280px;
  890. /* 固定宽度,不缩放 */
  891. margin-left: 0;
  892. height: 1983px;
  893. .title {
  894. color: #333333;
  895. font-size: 20px;
  896. font-weight: 500;
  897. position: relative;
  898. /* 添加相对定位 */
  899. padding-left: 12px;
  900. display: flex;
  901. align-items: center;
  902. /* 给左侧留出空间 */
  903. &::before {
  904. /* 用 ::before 代替 ::after */
  905. content: '';
  906. width: 4px;
  907. height: 18px;
  908. position: absolute;
  909. background-color: #0083FF;
  910. left: 0;
  911. top: 0;
  912. bottom: 0;
  913. margin: auto;
  914. border-radius: 10px;
  915. }
  916. }
  917. .quoteOrder {
  918. height: calc(100% - 201px);
  919. overflow-y: auto;
  920. .item {
  921. .Order-title {
  922. font-size: 16px;
  923. font-weight: 500;
  924. color: #333333;
  925. }
  926. .Order-time {
  927. font-size: 12px;
  928. color: #999;
  929. font-weight: 400;
  930. }
  931. .Order-premium {
  932. font-weight: 400;
  933. font-size: 14px;
  934. color: #333;
  935. }
  936. }
  937. .qrcode {
  938. font-size: 14px;
  939. color: #333;
  940. font-weight: 400;
  941. }
  942. }
  943. }
  944. .w-full {
  945. flex: 1;
  946. .title {
  947. color: #333333;
  948. font-size: 20px;
  949. font-weight: 500;
  950. position: relative;
  951. /* 添加相对定位 */
  952. padding-left: 12px;
  953. display: flex;
  954. align-items: center;
  955. /* 给左侧留出空间 */
  956. &::before {
  957. /* 用 ::before 代替 ::after */
  958. content: '';
  959. width: 4px;
  960. height: 18px;
  961. position: absolute;
  962. background-color: #0083FF;
  963. left: 0;
  964. top: 0;
  965. bottom: 0;
  966. margin: auto;
  967. border-radius: 10px;
  968. }
  969. }
  970. .dataList {
  971. margin-top: 24px;
  972. display: grid;
  973. grid-template-columns: repeat(4, 1fr);
  974. grid-template-rows: auto;
  975. column-gap: 24px;
  976. .item {
  977. background: #F9FCFF;
  978. padding: 16px 24px;
  979. border-radius: 6px;
  980. .name {
  981. color: #333;
  982. font-weight: 400;
  983. font-size: 18px;
  984. margin-bottom: 12px;
  985. }
  986. .data {
  987. display: flex;
  988. justify-content: flex-start;
  989. .number {
  990. color: #333333;
  991. font-weight: 600;
  992. font-size: 24px;
  993. }
  994. .line {
  995. display: flex;
  996. align-items: center;
  997. justify-content: flex-start;
  998. font-size: 14px;
  999. color: #666666;
  1000. margin-left: 10px;
  1001. img {
  1002. width: 14px;
  1003. height: 14px;
  1004. margin: 0 2px;
  1005. }
  1006. }
  1007. }
  1008. }
  1009. }
  1010. .functionList {
  1011. display: flex;
  1012. align-items: center;
  1013. justify-content: space-between;
  1014. margin-top: 24px;
  1015. padding: 0 8px;
  1016. box-sizing: border-box;
  1017. .item {
  1018. .icon {
  1019. display: block;
  1020. width: 60px;
  1021. height: 60px;
  1022. margin-bottom: 12px;
  1023. }
  1024. .name {
  1025. color: #333333;
  1026. font-size: 18px;
  1027. }
  1028. }
  1029. }
  1030. .mapBox,
  1031. .otherBox {
  1032. display: flex;
  1033. align-items: center;
  1034. justify-content: space-between;
  1035. margin: 24px;
  1036. gap: 24px;
  1037. .map,
  1038. .level {
  1039. width: calc(50% - 5px);
  1040. background: #ffffff;
  1041. padding: 24px;
  1042. border-radius: 6px;
  1043. .chinaMap,
  1044. .cityMap {
  1045. width: 100%;
  1046. height: 550px;
  1047. }
  1048. }
  1049. .funnel,
  1050. .pie {
  1051. width: calc(50% - 5px);
  1052. background: #ffffff;
  1053. padding: 24px;
  1054. border-radius: 6px;
  1055. .chinaMap,
  1056. .cityMap {
  1057. width: 100%;
  1058. height: 300px;
  1059. }
  1060. }
  1061. }
  1062. .orderTrend {
  1063. width: 100%;
  1064. height: 400px;
  1065. margin-top: 24px;
  1066. }
  1067. }
  1068. </style>