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.
 
 
 
 
 
 

723 lines
19 KiB

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