You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

618 lines
15 KiB

  1. <template>
  2. <view id="home" class="page">
  3. <!-- 顶部搜索栏 -->
  4. <view class="header text-white">
  5. <!-- #ifndef H5 -->
  6. <l-icon @click="scanClick" type="scan" color="white" class="header-left text-xxl margin-left-sm" />
  7. <!-- #endif -->
  8. <view @click="moreClick(1)" class="search header-mid margin-lr-sm">
  9. <l-icon type="search" color="white" class="margin-lr-sm" />
  10. 搜索更多应用
  11. </view>
  12. <l-icon @click="msgClick" type="mail" color="white" class="header-right text-xxl margin-right-sm" />
  13. </view>
  14. <!-- 轮播图片 -->
  15. <swiper v-if="imgCount > 0" style="height:120px;">
  16. <swiper-item v-for="(item, index) of imgData" :key="index" style="height:120px;">
  17. <image :src="item" mode="aspectFill" style="height:100%; width:100%;"></image>
  18. </swiper-item>
  19. </swiper>
  20. <!-- 功能宫格列表 -->
  21. <view class="col-4 function-list cu-list grid no-border">
  22. <view v-for="(item, index) in funcListDisplay" @click="funcListClick(item)" :key="index" class="cu-item text-center flex flex-wrap justify-center align-center">
  23. <view class="app-item align-center flex flex-wrap justify-center align-center">
  24. <l-icon :type="item.icon" color="white" class="text-sl" />
  25. </view>
  26. <text>{{ item.F_Name }}</text>
  27. </view>
  28. </view>
  29. <view class="text-center bg-white margin-bottom">
  30. <text @click="moreClick(0)" class="function-more-btn margin-tb-sm text-gray">更多应用</text>
  31. </view>
  32. <!-- 统计数据宫格 -->
  33. <l-title>统计数据</l-title>
  34. <view class="count-list cu-list grid col-3 margin-bottom">
  35. <view v-for="(item, index) in countData" :key="index" class="cu-item text-center">
  36. <text class="margin-bottom-xs">{{ item.title || '(未命名)' }}</text>
  37. <text class="count-item-value">{{ item.value || '-' }}</text>
  38. </view>
  39. </view>
  40. <!-- 通知列表区块 -->
  41. <view v-for="(block, blockIndex) of noticeData" :key="blockIndex" class="margin-bottom">
  42. <!-- <view class="margin-top"></view> -->
  43. <l-title>{{ block.title }}</l-title>
  44. <l-list>
  45. <l-list-item v-for="(item, i) of block.content" @click="noticeClick(item)" :key="i" arrow>
  46. {{ item.f_title || '(无标题)' }}
  47. <l-tag slot="action" line="gray">{{ postDateTime(item.f_time) }}</l-tag>
  48. </l-list-item>
  49. </l-list>
  50. </view>
  51. <!-- 图表区块 -->
  52. <view v-for="(item) of chartData" :key="item.id" class="margin-bottom">
  53. <l-title>{{ item.title }}</l-title>
  54. <view :style="{ width: cWidth + 'px', height: cHeight + 'px' }" class="chart-list">
  55. <!--#ifndef MP-ALIPAY -->
  56. <canvas v-if="item.type === 1" @tap="chartTap(item, $event)" @touchstart="touchStart(item.id, $event)" @touchmove="touchMove(item.id, $event)"
  57. @touchend="touchEnd(item.id, $event)" :style="{ width: cWidth + 'px', height: cHeight + 'px' }" :canvas-id="item.id"
  58. :id="item.id" class="charts"></canvas>
  59. <canvas v-else @tap="chartTap(item, $event)" :style="{ width: cWidth + 'px', height: cHeight + 'px' }" :canvas-id="item.id"
  60. :id="item.id" class="charts"></canvas>
  61. <!--#endif -->
  62. <!-- 阿里小程序,需要以2倍尺寸显示,然后缩放为50% -->
  63. <!--#ifdef MP-ALIPAY -->
  64. <canvas v-if="item.type === 1" @tap="chartTap(item, $event)" @touchstart="touchStart(item.id, $event)" @touchmove="touchMove(item.id, $event)"
  65. @touchend="touchEnd(item.id, $event)" :style="{
  66. width: cWidth * pixelRatio + 'px',
  67. height: cHeight * pixelRatio + 'px',
  68. transform: 'scale(' + 1 / pixelRatio + ')',
  69. marginLeft: (-cWidth * (pixelRatio - 1)) / 2 + 'px',
  70. marginTop: (-cHeight * (pixelRatio - 1)) / 2 + 'px'
  71. }"
  72. :canvas-id="item.id" :id="item.id" class="charts"></canvas>
  73. <canvas v-else @tap="chartTap(item, $event)" :style="{
  74. width: cWidth * pixelRatio + 'px',
  75. height: cHeight * pixelRatio + 'px',
  76. transform: 'scale(' + 1 / pixelRatio + ')',
  77. marginLeft: (-cWidth * (pixelRatio - 1)) / 2 + 'px',
  78. marginTop: (-cHeight * (pixelRatio - 1)) / 2 + 'px'
  79. }"
  80. :canvas-id="item.id" :id="item.id" class="charts"></canvas>
  81. <!--#endif -->
  82. </view>
  83. </view>
  84. </view>
  85. </template>
  86. <script>
  87. import moment from 'moment'
  88. import mapValues from 'lodash/mapValues'
  89. import uCharts from '@/common/u-charts.js'
  90. // 用于保存图表操作对象
  91. let chartsObject = {}
  92. let chartsConfig = {}
  93. export default {
  94. data() {
  95. return {
  96. imgData: [],
  97. listData: [],
  98. myList: [],
  99. countData: [],
  100. noticeData: [],
  101. chartData: [],
  102. pixelRatio: 1,
  103. cWidth: '',
  104. cHeight: '',
  105. chartsFontSize: 10
  106. }
  107. },
  108. async onLoad(param) {
  109. await this.init(param)
  110. },
  111. // 本页面开启下拉刷新,用于刷新首页数据
  112. onPullDownRefresh() {
  113. this.refresh().then(() => {
  114. this.TOAST('已更新首页数据')
  115. uni.stopPullDownRefresh()
  116. })
  117. },
  118. methods: {
  119. // 页面初始化
  120. async init(param) {
  121. this.HIDE_LOADING()
  122. // 有参数表示可能是打开分享消息;将数据存入全局变量以备后续跳转
  123. if (param && param.learun && param.pagePath) {
  124. this.SET_GLOBAL('jumpParam', param)
  125. }
  126. // 登录状态无效,则跳转到登录页
  127. const stateValid = await this.checkLoginState()
  128. if (!stateValid) {
  129. this.RELAUNCH_TO('/pages/login')
  130. return
  131. }
  132. // 图表相关参数初始化
  133. this.initCharts()
  134. // 加载页面数据和全局数据
  135. await this.FETCH_CLIENTDATA()
  136. await this.refresh()
  137. // 监听「我的应用」列表修改
  138. this.ON('home-list-change', () => {
  139. this.HTTP_GET('/function/mylist').then(newList => {
  140. this.myList = newList
  141. })
  142. })
  143. this.SET_STORAGE('nextTime', null)
  144. // 处理小程序分享消息跳转
  145. // #ifdef MP
  146. const jumpParam = this.GET_GLOBAL('jumpParam')
  147. if (jumpParam) {
  148. this.SET_GLOBAL('jumpParam', null)
  149. this.MP_SHARE_DECODE(jumpParam)
  150. }
  151. // #endif
  152. },
  153. // 验证登录状态
  154. async checkLoginState() {
  155. const token = this.GET_GLOBAL('token') || uni.getStorageSync('token')
  156. if (!token || token === 'null' || token === 'undefined') {
  157. this.RELAUNCH_TO('/pages/login')
  158. this.HIDE_LOADING()
  159. return false
  160. }
  161. this.SET_GLOBAL('token', token)
  162. // 判断是否有 loginUser 对象
  163. if (this.GET_GLOBAL('loginUser')) {
  164. return true
  165. }
  166. // 拉取用户信息验证登录态;如果失败则跳转至登录页
  167. const userInfo = await this.HTTP_GET('/user/info')
  168. if (!userInfo) {
  169. this.SET_GLOBAL('token', null)
  170. this.SET_STORAGE('token', null)
  171. return false
  172. }
  173. // 有登录态,则设置用户信息和全局数据
  174. const {
  175. baseinfo,
  176. mpinfo,
  177. post,
  178. role
  179. } = userInfo
  180. const user = { ...baseinfo,
  181. post,
  182. role
  183. }
  184. if (mpinfo && Array.isArray(mpinfo) && mpinfo.includes(this.PLATFORM)) {
  185. user.miniProgram = true
  186. }
  187. this.SET_GLOBAL('loginUser', user)
  188. return true
  189. },
  190. // 刷新首页数据
  191. async refresh() {
  192. // 清空页面数据
  193. chartsObject = {}
  194. this.imgData = []
  195. this.listData = []
  196. this.myList = []
  197. this.countData = []
  198. this.noticeData = []
  199. this.chartData = []
  200. // 同时发出请求,获取轮播图、所有功能列表、我的功能列表、商机通知提醒图表数据
  201. // 商机、通知提醒图表数据,获取的是数据ID,所以还需要进一步请求
  202. const [imgData, listData, myList, settingData] = await Promise.all([
  203. this.HTTP_GET('/desktop/imgid' + (this.CONFIG('isDistributed')==true ? '?isDistributed=true' : '')),
  204. this.HTTP_GET('/function/list').then(result => result.data),
  205. this.HTTP_GET('/function/mylist'),
  206. this.HTTP_GET('/desktop/setting').then(result => result.data)
  207. ])
  208. this.imgData=imgData;
  209. if (!this.CONFIG('isDistributed'))
  210. // 轮播图需要加上 url 前缀
  211. this.imgData = imgData.map(t => this.API + `/desktop/img?data=${t}`)
  212. // 功能区按钮需要处理 icon
  213. this.listData = listData.map(item => {
  214. const icon = item.F_Icon ? item.F_Icon.replace(`iconfont icon-`, ``) : ''
  215. const existsIcon = this.getUiIcons().some(t => t === icon)
  216. return {
  217. ...item,
  218. icon: existsIcon ? icon : 'roundright'
  219. }
  220. })
  221. // 我的应用列表需要过滤掉不存在的按钮
  222. this.myList = myList.filter(t => listData.find(item => item.F_Id === t))
  223. // 发出请求,获取商机信息、消息通知信息、图表信息;三类数据全部同时请求
  224. await Promise.all([
  225. ...settingData.target.map(item =>
  226. this.HTTP_GET('/desktop/data', {
  227. type: 'Target',
  228. id: item.F_Id
  229. }).then(data => {
  230. if (data && data.value) {
  231. const {
  232. value
  233. } = data
  234. this.countData.push({
  235. title: item.F_Name,
  236. value
  237. })
  238. }
  239. })
  240. ),
  241. ...settingData.list.map(item =>
  242. this.HTTP_GET('/desktop/data', {
  243. type: 'list',
  244. id: item.F_Id
  245. }).then(data => {
  246. if (data && data.value) {
  247. const {
  248. value
  249. } = data
  250. this.noticeData.push({
  251. title: item.F_Name,
  252. content: value
  253. })
  254. }
  255. })
  256. ),
  257. ...settingData.chart.map(item =>
  258. this.HTTP_GET('/desktop/data', {
  259. type: 'chart',
  260. id: item.F_Id
  261. }).then(data => {
  262. if (data && data.value) {
  263. const {
  264. value
  265. } = data
  266. this.chartData.push({
  267. title: item.F_Name,
  268. value,
  269. id: item.F_Id,
  270. type: item.F_Type
  271. })
  272. }
  273. })
  274. )
  275. ])
  276. // 渲染图表
  277. this.chartData.forEach(item => {
  278. // 根据 item.type 的值选用对应的图表初始化配置项
  279. // 0=环形图;1=折线图;2=柱状图
  280. const charts = new uCharts(
  281. [{
  282. ...chartsConfig,
  283. canvasId: item.id,
  284. type: 'ring',
  285. series: item.value.map(t => ({
  286. name: t.name,
  287. data: t.value
  288. })),
  289. extra: {
  290. pie: {
  291. offsetAngle: -45,
  292. ringWidth: 20,
  293. labelWidth: 15
  294. }
  295. },
  296. legend: {
  297. lineHeight: 20
  298. }
  299. },
  300. {
  301. ...chartsConfig,
  302. canvasId: item.id,
  303. type: 'line',
  304. series: [{
  305. name: item.title,
  306. data: item.value.map(t => t.value)
  307. }],
  308. categories: item.value.map(t => t.name),
  309. extra: {
  310. line: {
  311. type: 'straight'
  312. }
  313. },
  314. xAxis: {
  315. rotateLabel: true,
  316. fontSize: this.chartsFontSize,
  317. itemCount: 8
  318. },
  319. enableScroll: true
  320. },
  321. {
  322. ...chartsConfig,
  323. canvasId: item.id,
  324. type: 'column',
  325. series: [{
  326. name: item.title,
  327. data: item.value.map(t => t.value)
  328. }],
  329. categories: item.value.map(t => t.name),
  330. xAxis: {
  331. rotateLabel: true,
  332. fontSize: this.chartsFontSize
  333. }
  334. }
  335. ][item.type]
  336. )
  337. chartsObject[item.id] = charts
  338. })
  339. },
  340. // 初始化图表
  341. initCharts() {
  342. // (阿里小程序)设置图表尺寸为2倍
  343. // #ifdef MP-ALIPAY
  344. const pixelRatio = uni.getSystemInfoSync().pixelRatio
  345. if (pixelRatio > 1) {
  346. this.pixelRatio = 2
  347. this.chartsFontSize = 15
  348. }
  349. // #endif
  350. this.cWidth = uni.upx2px(750)
  351. this.cHeight = uni.upx2px(500)
  352. chartsConfig = {
  353. $this: this,
  354. pixelRatio: this.pixelRatio,
  355. width: this.cWidth * this.pixelRatio,
  356. height: this.cHeight * this.pixelRatio,
  357. background: '#FFFFFF',
  358. dataLabel: true,
  359. padding: [20, 15, 5, 15]
  360. }
  361. },
  362. // 图表点击事件
  363. chartTap(chartItem, e) {
  364. const format = ({
  365. name,
  366. data
  367. }, category) => `${category || ''} ${name}: ${data}`
  368. // #ifdef MP-DINGTALK
  369. const detail = e.detail
  370. const item = detail
  371. e.mp.changedTouches = [item]
  372. // #endif
  373. chartsObject[chartItem.id].showToolTip(e, {
  374. format
  375. })
  376. },
  377. // 图表拖动事件(开始)
  378. touchStart(chartId, e) {
  379. chartsObject[chartId].scrollStart(e)
  380. },
  381. // 图表拖动事件(拖动)
  382. touchMove(chartId, e) {
  383. chartsObject[chartId].scroll(e)
  384. },
  385. // 图表拖动事件(结束)
  386. touchEnd(chartId, e) {
  387. chartsObject[chartId].scrollEnd(e)
  388. },
  389. // 格式化日期的显示(新闻列表、通知公告等)
  390. postDateTime(timeStr) {
  391. return moment(timeStr).fromNow()
  392. },
  393. // 点击功能按钮
  394. funcListClick(item) {
  395. if (item.F_IsSystem === 2) {
  396. this.NAV_TO(`/pages/customapp/list?formId=${item.F_FormId}`, item, true)
  397. return
  398. }
  399. this.NAV_TO(`/pages/${item.F_Url}/list`)
  400. },
  401. // 点击通知公告的标题
  402. noticeClick(item) {
  403. this.NAV_TO('/pages/home/notice', item, true)
  404. },
  405. // #ifndef H5
  406. // 点击左上角扫码图标,H5 无此功能
  407. scanClick() {
  408. uni.scanCode({
  409. scanType: ['qrCode', 'barCode'],
  410. success: ({
  411. result,
  412. charSet
  413. }) => {
  414. // 您可以在这里自行定制扫码后的功能
  415. }
  416. })
  417. },
  418. // #endif
  419. // 点击更多功能按钮
  420. moreClick(openSearch) {
  421. this.NAV_TO(`/pages/home/more${openSearch ? '?search=1' : ''}`)
  422. },
  423. // 点击右上角的消息按钮
  424. msgClick() {
  425. this.TAB_TO('/pages/msg')
  426. }
  427. },
  428. computed: {
  429. // 「我的功能」区域按钮列表
  430. funcListDisplay() {
  431. const {
  432. myList,
  433. listData
  434. } = this
  435. const myFuncList = myList.reduce((list, id) => {
  436. if (listData.find(t => t.F_Id === id)) {
  437. return [...list, listData.find(t => t.F_Id === id)]
  438. }
  439. return list
  440. }, [])
  441. return myFuncList
  442. },
  443. // imgData 数组的长度
  444. imgCount() {
  445. return this.imgData.length
  446. }
  447. }
  448. }
  449. </script>
  450. <style lang="less" scoped>
  451. .page {
  452. background-color: #f3f3f3;
  453. .header {
  454. height: 100rpx;
  455. background-color: #0c86d8;
  456. display: flex;
  457. align-items: center;
  458. .header-left {
  459. flex-grow: 0;
  460. }
  461. .header-mid {
  462. flex-grow: 1;
  463. background-color: #3d9ee0;
  464. line-height: 60rpx;
  465. height: 60rpx;
  466. font-size: 24rpx;
  467. color: #fff;
  468. border-radius: 6rpx;
  469. }
  470. .header-right {
  471. flex-grow: 0;
  472. }
  473. }
  474. .content {
  475. background-color: #fff;
  476. }
  477. .function-list {
  478. padding-bottom: 0;
  479. .cu-item {
  480. .app-item {
  481. border-radius: 50%;
  482. height: 45px;
  483. width: 45px;
  484. }
  485. &:nth-child(7n + 1)>.app-item {
  486. background-color: #62bbff;
  487. }
  488. &:nth-child(7n + 2)>.app-item {
  489. background-color: #7bd2ff;
  490. }
  491. &:nth-child(7n + 3)>.app-item {
  492. background-color: #ffd761;
  493. }
  494. &:nth-child(7n + 4)>.app-item {
  495. background-color: #fe955c;
  496. }
  497. &:nth-child(7n + 5)>.app-item {
  498. background-color: #ff6283;
  499. }
  500. &:nth-child(7n + 6)>.app-item {
  501. background-color: #60e3f3;
  502. }
  503. &:nth-child(7n)>.app-item {
  504. background-color: #acc8fe;
  505. }
  506. }
  507. }
  508. .function-more-btn {
  509. display: inline-block;
  510. border: currentColor 1px solid;
  511. border-radius: 2px;
  512. padding: 10rpx 50rpx;
  513. }
  514. .count-list {
  515. &:after {
  516. content: '';
  517. clear: both;
  518. display: table;
  519. }
  520. .count-item-value {
  521. color: #0188d2;
  522. font-size: 24px;
  523. }
  524. }
  525. .chart-list {
  526. background-color: #fff;
  527. overflow: hidden;
  528. }
  529. }
  530. </style>
  531. <style lang="less">
  532. #home {
  533. width: 750rpx;
  534. overflow-x: hidden;
  535. .function-list .cu-item text[class*='cuIcon'] {
  536. margin-top: 0 !important;
  537. }
  538. }
  539. </style>