exerciseDuration.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <template>
  2. <div class="m-container">
  3. <!-- 搜索标题 -->
  4. <saveform
  5. :inline="true"
  6. ref="searchForm"
  7. class="searchForm"
  8. saveKey="exerciseDuration"
  9. :model.sync="searchForm"
  10. >
  11. <el-form-item prop="dates">
  12. <el-date-picker
  13. @change="changeWeek"
  14. :clearable="false"
  15. v-model="searchForm.dates"
  16. value-format="yyyy-MM-dd"
  17. :picker-options="{ firstDayOfWeek: 1 }"
  18. type="daterange"
  19. style="width: 405px"
  20. range-separator="至"
  21. start-placeholder="开始日期"
  22. end-placeholder="结束日期"
  23. >
  24. </el-date-picker>
  25. </el-form-item>
  26. <el-form-item>
  27. <el-button @click="search" type="danger">搜索</el-button>
  28. <el-button @click="onReSet" type="primary">重置</el-button>
  29. </el-form-item>
  30. </saveform>
  31. <!-- 分组添加柱状图 -->
  32. <el-tabs v-model="activeName" class="submitTab">
  33. <el-tab-pane label="人数图表" name="first">
  34. <ve-histogram
  35. v-if="activeName == 'first'"
  36. style="width: 100%"
  37. height="350px"
  38. :data="chartData"
  39. :data-empty="dataEmpty"
  40. :extend="chartExtend"
  41. :legend="legend"
  42. :data-zoom="dataZoom"
  43. ></ve-histogram>
  44. </el-tab-pane>
  45. <el-tab-pane label="比例图标" name="second">
  46. <ve-histogram
  47. v-if="activeName == 'second'"
  48. style="width: 100%"
  49. height="350px"
  50. :data="scaleCharData"
  51. :data-empty="dataEmpty"
  52. :extend="scalChartExtend"
  53. :legend="legend"
  54. :data-zoom="dataZoom"
  55. ></ve-histogram>
  56. </el-tab-pane>
  57. </el-tabs>
  58. <el-button
  59. type="primary"
  60. @click="exportQuestion"
  61. style="margin: 16px 0"
  62. v-permission="'export/managerDownload/num'"
  63. >导出</el-button
  64. >
  65. <div class="tableWrap">
  66. <el-table
  67. :data="tableList"
  68. :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
  69. >
  70. <el-table-column width="120px" align="center" prop="id" label="排名">
  71. <template slot-scope="scope">
  72. <div>
  73. {{ scope.$index + 1 }}
  74. </div>
  75. </template>
  76. </el-table-column>
  77. <el-table-column
  78. align="center"
  79. prop="organName"
  80. label="分部名称"
  81. ></el-table-column>
  82. <el-table-column align="center" prop="normalNum" label="会员人数">
  83. <template slot-scope="scope">
  84. <div>{{ scope.row.normalNum }}人</div>
  85. </template>
  86. </el-table-column>
  87. <el-table-column align="center" prop="trainStudentNum" label="练习人数">
  88. <template slot-scope="scope">
  89. <div>{{ scope.row.trainStudentNum }}人</div>
  90. </template></el-table-column
  91. >
  92. <el-table-column align="center" prop="trainRate" label="练习率">
  93. <template slot-scope="scope">
  94. <div>{{ scope.row.trainRate }}%</div>
  95. </template>
  96. </el-table-column>
  97. <el-table-column align="center" prop="id" label="≤60分钟">
  98. <template slot-scope="scope">
  99. <div>{{ scope.row.train1 }}人</div>
  100. </template></el-table-column
  101. >
  102. <el-table-column align="center" prop="id" label="60~120分钟">
  103. <template slot-scope="scope">
  104. <div>{{ scope.row.train2 }}人</div>
  105. </template>
  106. </el-table-column>
  107. <el-table-column align="center" prop="id" label="120~240分钟">
  108. <template slot-scope="scope">
  109. <div>{{ scope.row.train3 }}人</div>
  110. </template>
  111. </el-table-column>
  112. <el-table-column align="center" prop="id" label=">240分钟">
  113. <template slot-scope="scope">
  114. <div>{{ scope.row.train4 }}人</div>
  115. </template>
  116. </el-table-column>
  117. <el-table-column align="center" prop="id" label="平均练习时长">
  118. <template slot-scope="scope">
  119. <div>{{ scope.row.avgTrainTime }}分钟</div>
  120. </template>
  121. </el-table-column>
  122. <el-table-column align="center" prop="id" label="练习达标占比">
  123. <template slot-scope="scope">
  124. <div>{{ scope.row.trainStandRate }}%</div>
  125. </template>
  126. </el-table-column>
  127. <el-table-column align="center" label="操作" fixed="right">
  128. <template slot-scope="scope">
  129. <el-button
  130. v-permission="'/exerciseDurationDetail'"
  131. @click="onDetail(scope.row)"
  132. type="text"
  133. >详情</el-button
  134. >
  135. </template>
  136. </el-table-column>
  137. </el-table>
  138. </div>
  139. </div>
  140. </template>
  141. <script>
  142. import { Export } from "@/utils/downLoadFile";
  143. import saveform from "@/components/save-form";
  144. import dayjs from "dayjs";
  145. import histogram from "v-charts/lib/histogram.common";
  146. import { getNowDateAndMonday, getNowDateAndSunday } from "@/utils/utils";
  147. import { cloudTeacherNum } from "../api";
  148. let nowTime = new Date();
  149. nowTime =
  150. nowTime.getFullYear() +
  151. "-" +
  152. (nowTime.getMonth() + 1) +
  153. "-" +
  154. nowTime.getDate();
  155. export const getTimes = (times, keys = []) => {
  156. if (times && times.length) {
  157. return {
  158. [keys[0] || "start"]: dayjs(times[0]).format("YYYY-MM-DD"),
  159. [keys[1] || "start"]: dayjs(times[1]).format("YYYY-MM-DD")
  160. };
  161. }
  162. return {};
  163. };
  164. export default {
  165. components: { saveform, "ve-histogram": histogram },
  166. name: "helpCategory",
  167. data() {
  168. return {
  169. searchForm: {
  170. dates: []
  171. },
  172. tableList: [],
  173. activeName: "first"
  174. };
  175. },
  176. async mounted() {
  177. if (this.searchForm.dates?.length <= 0) {
  178. this.searchForm.dates.push(getNowDateAndMonday(nowTime));
  179. this.searchForm.dates.push(getNowDateAndSunday(nowTime));
  180. }
  181. this.getList();
  182. },
  183. methods: {
  184. exportQuestion() {
  185. const { dates } = this.searchForm;
  186. Export(
  187. this,
  188. {
  189. url: "/api-web/export/managerDownload",
  190. fileName: "云教练练习时长.xls",
  191. method: "post",
  192. params: {
  193. exportEnum: "EXPORT_CLOUD_TEACHER_SUM",
  194. queryInfo: {
  195. ...getTimes(dates, ["startDate", "endDate"])
  196. }
  197. }
  198. },
  199. "您确定导出云教练练习时长?"
  200. );
  201. },
  202. onDetail(row) {
  203. this.$router.push({
  204. path: "/exerciseDurationDetail",
  205. query: {
  206. organId: row.organId
  207. }
  208. });
  209. },
  210. search() {
  211. this.$refs.searchForm.validate(valid => {
  212. this.pageInfo = {
  213. ...this.pageInfo,
  214. page: 1
  215. };
  216. this.getList();
  217. });
  218. },
  219. onReSet() {
  220. this.pageInfo = {
  221. ...this.pageInfo,
  222. page: 1
  223. };
  224. this.$refs.searchForm.resetFields();
  225. this.searchForm.dates.push(getNowDateAndMonday(nowTime));
  226. this.searchForm.dates.push(getNowDateAndSunday(nowTime));
  227. this.getList();
  228. },
  229. getDefaultTime() {
  230. const dayjs = this.$helpers.dayjs;
  231. let nowDate = dayjs(new Date()).format("YYYY-MM-DD");
  232. let lastWeek = dayjs(nowDate)
  233. .subtract(1, "week")
  234. .format("YYYY-MM-DD");
  235. console.log(lastWeek, nowDate, "121212");
  236. this.searchForm.dates = [
  237. getNowDateAndMonday(lastWeek),
  238. getNowDateAndSunday(nowDate)
  239. ];
  240. console.log(this.searchForm.dates, "this.searchForm.dates");
  241. },
  242. changeWeek(val) {
  243. console.log(val, "121212");
  244. if (val) {
  245. this.searchForm.dates = [
  246. getNowDateAndMonday(val[0]),
  247. getNowDateAndSunday(val[1])
  248. ];
  249. } else {
  250. this.getDefaultTime();
  251. }
  252. },
  253. async getList() {
  254. // cloudTeacherNum
  255. try {
  256. const { dates } = this.searchForm;
  257. let params = {
  258. ...getTimes(dates, ["startDate", "endDate"])
  259. };
  260. const res = await cloudTeacherNum({
  261. page: 1,
  262. rows: 10,
  263. ...params
  264. });
  265. this.tableList = [];
  266. this.tableList = res.data;
  267. } catch {}
  268. }
  269. },
  270. computed: {
  271. items() {
  272. let obj = {};
  273. // "eVipStudentNum",
  274. let arr = [
  275. "normalNum",
  276. "train1",
  277. "train2",
  278. "train3",
  279. "train4",
  280. "buyRate",
  281. "avgTrainTime"
  282. ];
  283. arr.forEach(str => {
  284. if (this.dataList[str] + "") {
  285. obj[str] = {
  286. title: titles[str],
  287. percent: this.dataList[str],
  288. desc: descs[str]
  289. };
  290. }
  291. });
  292. return obj;
  293. },
  294. scaleItems() {
  295. let obj = {};
  296. // "eVipStudentNum",
  297. let arr = ["trainRate", "trainStandRate"];
  298. arr.forEach(str => {
  299. if (this.dataList[str] + "") {
  300. obj[str] = {
  301. title: titles[str],
  302. percent: this.dataList[str],
  303. desc: descs[str]
  304. };
  305. }
  306. });
  307. return obj;
  308. },
  309. legend() {
  310. return {
  311. left: "10px"
  312. };
  313. },
  314. scaleCharData() {
  315. const temp = {
  316. trainRate: "练习率",
  317. trainStandRate: "练习达标率"
  318. };
  319. const values = this.tableList;
  320. const months = {};
  321. for (const key in temp) {
  322. for (const item of values) {
  323. if (!months[item.organName]) {
  324. months[item.organName] = {
  325. 分部: item.organName
  326. };
  327. }
  328. months[item.organName][temp[key]] = item[key] || 0;
  329. }
  330. }
  331. console.log(months, "months");
  332. return {
  333. columns: ["分部", "练习率", "练习达标率"],
  334. rows: Object.values(months)
  335. };
  336. },
  337. chartData() {
  338. const temp = {
  339. normalNum: "会员人数",
  340. train1: "<=60分钟",
  341. train2: "60-120分钟",
  342. train3: "120-240分钟",
  343. train4: ">240分钟",
  344. avgTrainTime: "平均时长"
  345. };
  346. const values = this.tableList;
  347. const months = {};
  348. for (const key in temp) {
  349. for (const item of values) {
  350. if (!months[item.organName]) {
  351. months[item.organName] = {
  352. 分部: item.organName
  353. };
  354. }
  355. months[item.organName][temp[key]] = item[key] || 0;
  356. }
  357. }
  358. console.log(months, "months");
  359. return {
  360. columns: [
  361. "分部",
  362. "会员人数",
  363. "<=60分钟",
  364. "60-120分钟",
  365. "120-240分钟",
  366. ">240分钟",
  367. "平均时长"
  368. ],
  369. rows: Object.values(months)
  370. };
  371. },
  372. chartExtend() {
  373. return {
  374. yAxis: {
  375. //纵轴标尺固定
  376. minInterval: 1,
  377. type: "value",
  378. scale: true,
  379. min: 0,
  380. axisLabel: {
  381. formatter: "{value}"
  382. }
  383. },
  384. series: {
  385. type: "bar",
  386. smooth: false
  387. },
  388. tooltip: {
  389. axisPointer: {
  390. type: "shadow",
  391. shadowStyle: {
  392. color: "rgba(150,150,150,0.2)"
  393. }
  394. },
  395. formatter: item => {
  396. // let arr = [
  397. // { name: "会员人数", dot: "元" },
  398. // { name: "<=60分钟", dot: "%" },
  399. // { name: "60-120分钟", dot: "元" },
  400. // { name: "120-240分钟", dot: "元" },
  401. // { name: ">240分钟", dot: "元" },
  402. // { name: "平均时长", dot: "元" }
  403. // ];
  404. // console.log(item, "-121212--------------");
  405. return [
  406. item[0].axisValueLabel,
  407. ...item.map(d => {
  408. return `<br/>${d.marker}${d.seriesName}: ${d.value}${
  409. d.seriesName == "平均时长" ? "分钟" : "人"
  410. }`;
  411. })
  412. ].join("");
  413. }
  414. }
  415. };
  416. },
  417. scalChartExtend() {
  418. return {
  419. yAxis: {
  420. //纵轴标尺固定
  421. minInterval: 1,
  422. type: "value",
  423. scale: true,
  424. min: 0,
  425. axisLabel: {
  426. formatter: "{value}%"
  427. }
  428. },
  429. series: {
  430. type: "bar",
  431. smooth: false
  432. },
  433. tooltip: {
  434. axisPointer: {
  435. type: "shadow",
  436. shadowStyle: {
  437. color: "rgba(150,150,150,0.2)"
  438. }
  439. },
  440. formatter: item => {
  441. return [
  442. item[0].axisValueLabel,
  443. ...item.map(d => {
  444. return `<br/>${d.marker}${d.seriesName}: ${d.value}%`;
  445. })
  446. ].join("");
  447. }
  448. }
  449. };
  450. },
  451. dataZoom() {
  452. return [
  453. {
  454. show: true,
  455. type: "slider",
  456. start: 0,
  457. end: 30,
  458. filterMode: "empty",
  459. zoomLock: true,
  460. handleSize: 0
  461. }
  462. ];
  463. },
  464. dataEmpty() {
  465. return !this.chartData.rows.length;
  466. },
  467. exporyun() {
  468. return {
  469. organId: this.organId
  470. };
  471. }
  472. }
  473. };
  474. </script>
  475. <style lang="scss" scoped>
  476. ::v-deep .el-date-editor.el-input {
  477. width: 100% !important;
  478. }
  479. ::v-deep .el-select {
  480. width: 100% !important;
  481. }
  482. ::v-deep .el-table .cell {
  483. display: -webkit-box;
  484. overflow: hidden;
  485. max-height: 45px;
  486. text-overflow: ellipsis;
  487. -webkit-line-clamp: 3;
  488. -webkit-box-orient: vertical;
  489. }
  490. ::v-deep .el-dialog__body {
  491. padding: 10px 20px;
  492. }
  493. .newBand {
  494. display: inline-block;
  495. }
  496. ::v-deep .el-tabs__active-bar {
  497. background-color: transparent !important;
  498. }
  499. ::v-deep.el-tabs__nav-wrap {
  500. &:after {
  501. background-color: transparent !important;
  502. }
  503. }
  504. /*去掉切换时el-tab-pane底部的蓝色下划线*/
  505. ::v-deep .el-tabs__nav-wrap::after {
  506. background-color: transparent !important;
  507. }
  508. /*去掉tabs底部的下划线*/
  509. </style>