xm-user-todolist.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. <template>
  2. <div>
  3. <div>
  4. <div :class="$style.box" bordered>
  5. <div :class="$style.opt">
  6. <p>关键用户待办情况统计</p>
  7. <span>
  8. <a-radio-group
  9. v-model="radioVal"
  10. button-style="solid"
  11. :class="$style.radio"
  12. @change="initData(radioVal)"
  13. >
  14. <a-radio-button value="1">当天</a-radio-button>
  15. <a-radio-button value="3">近三天</a-radio-button>
  16. <a-radio-button value="5">近五天</a-radio-button>
  17. <a-radio-button value="7">近一周</a-radio-button>
  18. </a-radio-group>
  19. <a-icon
  20. v-if="isShow"
  21. type="ellipsis"
  22. :class="[$style.icon, $style.iconhead]"
  23. @click="showmore"
  24. />
  25. <a-icon type="sync" :class="[$style.icon, $style.iconhead]" @click="reload" />
  26. <a-icon type="download" :class="[$style.icon, $style.iconhead]" @click="downloadData" />
  27. </span>
  28. </div>
  29. <div :class="$style.customwrap">
  30. <a-collapse
  31. :active-key="activeKey"
  32. :ghost="true"
  33. :show-arrow="false"
  34. :bordered="false"
  35. :class="$style.customstyle"
  36. >
  37. <a-collapse-panel v-for="item in todolist" :key="item.index">
  38. <template slot="header"
  39. ><a-row>
  40. <a-col :span="3" :class="$style.listcolor">{{ item.name }}</a-col>
  41. <a-col :span="5"
  42. ><span :class="$style.listlabel">账号:</span>{{ item.account }}</a-col
  43. >
  44. <a-col :span="4"
  45. ><span :class="$style.listlabel">新到待办:</span>{{ item.TODOCREATE }}</a-col
  46. >
  47. <a-col :span="4"
  48. ><span :class="$style.listlabel">待办处理:</span>{{ item.TODODEL }}</a-col
  49. >
  50. <a-col :span="3" :class="$style.right"
  51. ><span :class="[$style.listlabel, $style.avgtime]">平均时长</span></a-col
  52. >
  53. <a-col :class="$style.times" :span="5" flex="1"
  54. ><li
  55. ><a-icon type="desktop" :class="[$style.icon, $style.dblist]" />{{
  56. item.avg_Computer
  57. }}</li
  58. ><li><a-icon type="mobile" :class="$style.icon" />{{ item.avg_Mobile }}</li>
  59. </a-col>
  60. </a-row></template
  61. >
  62. <div :class="$style.customitem"
  63. ><a-row>
  64. <a-list-item v-for="(i, index) in item.items" :key="i.intId" :class="$style.child"
  65. ><a-col :span="13" :class="$style.dbtext" :title="i.title" :offset="1"
  66. ><span>{{ index + 1 }}、{{ i.title }}</span></a-col
  67. >
  68. <a-col :class="[$style.dbtext, $style.app]" :title="i.appName">{{
  69. i.appName
  70. }}</a-col>
  71. <a-col :class="$style.times" justify="end" :span="5"
  72. ><li><a-icon type="desktop" :class="$style.icon" />{{ i.Computer }}</li
  73. ><li><a-icon type="mobile" :class="$style.icon" />{{ i.Mobile }}</li>
  74. </a-col>
  75. </a-list-item>
  76. </a-row>
  77. </div>
  78. </a-collapse-panel>
  79. </a-collapse>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. </template>
  85. <script>
  86. import XLSX from 'xlsx'
  87. import moment from 'moment'
  88. import components from './_import-components/xm-user-todolist-import'
  89. import TrackService from './track-service'
  90. export default {
  91. name: 'XmUserTodolist',
  92. metaInfo: {
  93. title: 'UserTodolist',
  94. },
  95. components,
  96. data() {
  97. return {
  98. radioVal: '1',
  99. activeKey: ['0', '1', '2'],
  100. todolist: [],
  101. isShow: true,
  102. exportdata: [],
  103. mergeArr: [],
  104. timedata: '',
  105. }
  106. },
  107. created() {
  108. this.initData(this.radioVal)
  109. if (window.location.href.includes('show')) {
  110. this.isShow = false
  111. }
  112. this.radioVal = this.$route.query.radioVal ? this.$route.query.radioVal.toString() : '1'
  113. },
  114. methods: {
  115. initData(days) {
  116. this.getdata(days)
  117. this.getTime(days)
  118. },
  119. getTime(val) {
  120. val = Number(val)
  121. const DateStr = new Date()
  122. const TimeDays = 1000 * 60 * 60 * 24
  123. const newFormat = 'YYYY-MM-DD'
  124. // 当前时间戳
  125. const nowTime = DateStr.getTime()
  126. let prevTime
  127. if (val === 1) {
  128. prevTime = nowTime
  129. this.timedata = moment(prevTime).format(newFormat)
  130. } else {
  131. prevTime = nowTime - TimeDays * val
  132. this.timedata =
  133. moment(prevTime).format(newFormat) + '至' + moment(nowTime).format(newFormat)
  134. }
  135. },
  136. getdata(days) {
  137. const params = {
  138. days: days,
  139. }
  140. TrackService.getUserTodo(params)
  141. .then((res) => {
  142. if (res.data.length > 0) {
  143. this.todolist = res.data
  144. this.todolist.map((item, index) => {
  145. item.items.map((item) => {
  146. if (item.Computer !== null || isNaN(item.Computer)) {
  147. item.Computer = (item.Computer / 1000).toFixed(1) + 's'
  148. } else {
  149. item.Computer = '--'
  150. }
  151. if (item.Mobile !== null || isNaN(item.Mobile)) {
  152. item.Mobile = (item.Mobile / 1000).toFixed(1) + 's'
  153. } else if (item.Mobile === undefined) {
  154. item.Mobile = '--'
  155. } else {
  156. item.Mobile = '--'
  157. }
  158. })
  159. if (item.avg_Computer !== null || isNaN(item.avg_Computer)) {
  160. item.avg_Computer = (item.avg_Computer / 1000).toFixed(1) + 's'
  161. } else {
  162. item.avg_Computer = '--'
  163. }
  164. if (item.avg_Mobile !== null || isNaN(item.avg_Mobile)) {
  165. item.avg_Mobile = (item.avg_Mobile / 1000).toFixed(1) + 's'
  166. } else {
  167. item.avg_Mobile = '--'
  168. }
  169. })
  170. }
  171. })
  172. .catch((e) => {})
  173. // 因为excel中数字类型默认居右对齐,所以转为字符串让表格数据统一居左
  174. this.exportdata = []
  175. this.todolist.map((item, index) => {
  176. Object.keys(item).forEach((i) => {
  177. if (typeof item[i] === 'number') {
  178. item[i] = item[i].toString()
  179. }
  180. })
  181. const parentobj = { ...item }
  182. if (item.items.length > 0) {
  183. delete parentobj.items
  184. const alldata = item.items.map((item) => {
  185. return { ...parentobj, ...item }
  186. })
  187. this.exportdata.push(alldata)
  188. } else {
  189. this.exportdata.push(parentobj)
  190. }
  191. })
  192. this.exportdata = this.exportdata.flat()
  193. // 合并单元格
  194. const repeatArr = [{ s: { r: 1, c: 8 }, e: { r: 1, c: 9 } }]
  195. for (let i = 0; i < this.exportdata.length - 1; i++) {
  196. const item = this.exportdata[i]
  197. if (this.exportdata[i + 1].account === item.account) {
  198. const end = i + 1
  199. // 需要合并的列
  200. const mergeIndex = [0, 1, 2, 3, 8, 9]
  201. mergeIndex.map((item) => {
  202. const repeatObj = {}
  203. repeatObj.s = { r: i + 3, c: item }
  204. repeatObj.e = { r: end + 3, c: item }
  205. repeatArr.push(repeatObj)
  206. })
  207. }
  208. }
  209. this.mergeArr = [...repeatArr]
  210. },
  211. showmore() {
  212. const routeUrl = this.$router.resolve({
  213. path: '/xm-todolist-detail?show',
  214. query: { radioVal: this.radioVal },
  215. })
  216. window.open(routeUrl.href, '_blank')
  217. },
  218. downloadData() {
  219. this.exportJsonDataToExcel()
  220. },
  221. exportJsonDataToExcel() {
  222. let sheet = {}
  223. const headerA = '关键用户待办情况统计'
  224. const headerB = '统计日期:' + this.timedata
  225. let sheetlist = this.exportdata.map((item, index) => {
  226. return {
  227. 姓名: item.name,
  228. 账号: item.account,
  229. 新到代办数量: item.TODOCREATE,
  230. 待办处理数量: item.TODODEL,
  231. 处理待办名称: item.title,
  232. 待办应用: item.appName,
  233. PC端打开时长: item.Computer,
  234. 移动端打开时长: item.Mobile,
  235. PC端平均打开时长: item.avg_Computer,
  236. 移动端平均打开时长: item.avg_Mobile,
  237. }
  238. })
  239. sheetlist = [...sheetlist]
  240. sheet = XLSX.utils.json_to_sheet(sheetlist, { origin: { r: 2, c: 0 } })
  241. const colWidth = []
  242. const colNames = Object.keys(sheetlist[0])
  243. // 遍历行
  244. sheetlist.forEach((row) => {
  245. // 遍历列
  246. let index = 0
  247. for (const key in row) {
  248. if (colWidth[index] == null) colWidth[index] = []
  249. // 计算每列数据的长度
  250. colWidth[index].push(this.getWidth(row[key]))
  251. index++
  252. }
  253. })
  254. sheet['!cols'] = []
  255. // 每一列取最大值最为列宽
  256. colWidth.forEach((widths, index) => {
  257. // 计算列头的宽度
  258. widths.push(this.getWidth(colNames[index]))
  259. // 设置最大值为列宽
  260. sheet['!cols'].push({ wch: Math.max(...widths) })
  261. })
  262. sheet.E1 = {
  263. v: headerA,
  264. t: 's',
  265. }
  266. sheet.I2 = {
  267. v: headerB,
  268. t: 's',
  269. }
  270. sheet['!merges'] = this.mergeArr
  271. const workBook = {
  272. SheetNames: ['eee'],
  273. Sheets: {
  274. eee: sheet,
  275. },
  276. }
  277. XLSX.writeFile(workBook, `关键用户待办情况统计.xlsx`, {
  278. bookType: 'xlsx',
  279. })
  280. },
  281. getWidth(value) {
  282. // 判断是否为null或undefined
  283. if (value == null) {
  284. return 10
  285. } else if (/.*[\u4e00-\u9fa5]+.*$/.test(value)) {
  286. // 判断是否包含中文
  287. let length = value.toString().length * 2.4
  288. if (length > 60) {
  289. length = length - 40
  290. }
  291. return length
  292. } else {
  293. return value.toString().length * 1.2
  294. }
  295. },
  296. reload() {
  297. this.initData(this.radioVal)
  298. },
  299. },
  300. }
  301. </script>
  302. <style module lang="scss">
  303. @use '@/common/design' as *;
  304. .box {
  305. position: absolute;
  306. width: 100%;
  307. height: 84%;
  308. padding-right: 5px;
  309. padding-left: 30px;
  310. margin-top: 5px;
  311. color: $card-background;
  312. -ms-overflow-style: none;
  313. p {
  314. margin-bottom: 0;
  315. font-size: $padding-lg;
  316. }
  317. .opt {
  318. display: flex;
  319. justify-content: space-between;
  320. padding: 0 10px 5px 15px;
  321. }
  322. .radio {
  323. padding-top: 4px;
  324. margin-right: 5px;
  325. }
  326. :global(.ant-collapse-content-box) {
  327. padding: 0;
  328. }
  329. }
  330. .customwrap {
  331. height: 100%;
  332. overflow: scroll;
  333. }
  334. .customstyle {
  335. margin-top: 10px;
  336. overflow: hidden;
  337. color: $alert-error-bg-color;
  338. background: #0b3787;
  339. border: 0;
  340. border-radius: 0;
  341. :global(.ant-collapse-item) {
  342. color: $alert-error-bg-color;
  343. border-bottom: 2px solid #01267b;
  344. :global(.ant-collapse-header) {
  345. color: $alert-error-bg-color;
  346. }
  347. :global(.ant-collapse-content) {
  348. color: $alert-error-bg-color;
  349. }
  350. }
  351. .listcolor {
  352. color: rgba(255, 255, 255, 0.9);
  353. }
  354. .listlabel {
  355. float: left;
  356. color: rgba(255, 255, 255, 0.7);
  357. }
  358. .avgtime {
  359. float: right;
  360. }
  361. .customitem {
  362. color: rgba(255, 255, 255, 0.9);
  363. background-color: #01267b;
  364. .child {
  365. border-bottom: 1px solid #ffffff17;
  366. }
  367. :global(.ant-list-item) {
  368. justify-content: space-around;
  369. padding: 12px 0;
  370. }
  371. :global(.ant-list-item):last-child {
  372. border: 0;
  373. }
  374. .app {
  375. width: 115px;
  376. }
  377. }
  378. }
  379. .times {
  380. white-space: nowrap;
  381. li {
  382. flex: 1;
  383. float: left;
  384. width: 58px;
  385. margin-right: 10px;
  386. }
  387. li:nth-child(3) {
  388. margin-right: 0;
  389. }
  390. }
  391. .icon {
  392. margin-right: 5px;
  393. }
  394. .iconhead {
  395. margin-right: 10px;
  396. font-size: $switch-height;
  397. }
  398. .dbtext {
  399. overflow: hidden;
  400. text-overflow: ellipsis;
  401. white-space: nowrap;
  402. }
  403. .right {
  404. padding-right: 5px;
  405. text-align: right;
  406. }
  407. </style>