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.
 
 
 
 
 
 

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