選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

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