student.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template
  2. v-loading="loading"
  3. element-loading-spinner="el-icon-loading"
  4. element-loading-background="rgba(0, 0, 0, 0.8)">
  5. <el-card>
  6. <div slot="header" class="clearfix">
  7. <searchHeader
  8. :dates="mdate"
  9. :title="'学员变动'"
  10. @changeValue="changeValue"
  11. :isShowQuert="true"
  12. ref="searchHeader"
  13. />
  14. </div>
  15. <statistic :col="6" class="statistic" :cols="0">
  16. <statistic-item
  17. v-for="(item, key) in items"
  18. :key="key"
  19. :class="{ active: active === key }"
  20. @click="active = key"
  21. >
  22. <span>
  23. {{ item.title + "(人)" }}
  24. <el-tooltip
  25. v-if="item.desc"
  26. :content="item.desc"
  27. :open-delay="0.3"
  28. placement="top"
  29. >
  30. <i
  31. style="margin-left: 5px; cursor: pointer"
  32. class="el-icon-warning-outline"
  33. />
  34. </el-tooltip>
  35. </span>
  36. <span> <count-to :endVal="item.percent" /> </span>
  37. </statistic-item>
  38. </statistic>
  39. <div class="wrap">
  40. <div class="chioseBox">
  41. <el-radio-group v-model="timer" >
  42. <el-radio-button label="day">按天</el-radio-button>
  43. <el-radio-button label="month">按月</el-radio-button>
  44. </el-radio-group>
  45. </div>
  46. <!-- :data-zoom="dataZoom" -->
  47. <ve-line
  48. style="width: 100%"
  49. height="350px"
  50. :data="timer == 'day' ? chartData : chartDataForMoth"
  51. :data-empty="dataEmpty"
  52. :extend="chartExtend"
  53. :legend="legend"
  54. ></ve-line>
  55. </div>
  56. <!-- <ve-funnel v-else style="width: 100%;" height="350px" :data="funnelData" :data-empty="dataEmpty"></ve-funnel> -->
  57. </el-card>
  58. </template>
  59. <script>
  60. import countTo from "vue-count-to";
  61. import veLine from "v-charts/lib/line.common";
  62. import veFunnel from "v-charts/lib/funnel.common";
  63. import searchHeader from "./modals/searchHeader";
  64. import { getIndex } from "../api";
  65. import { getTimes } from "@/utils";
  66. import { descs, chioseNum } from "../constant";
  67. export default {
  68. props: ["data", "search"],
  69. components: {
  70. "ve-funnel": veFunnel,
  71. "ve-line": veLine,
  72. "count-to": countTo,
  73. searchHeader,
  74. },
  75. computed: {
  76. legend() {
  77. return {
  78. left: "10px",
  79. };
  80. },
  81. items() {
  82. let obj = {};
  83. let arr = [
  84. "ADD_STUDENT_REGISTRATION_NUM",
  85. "MUSIC_GROUP_STUDENT",
  86. "NEWLY_STUDENT_NUM",
  87. "QUIT_MUSIC_GROUP_STUDENT_NUM",
  88. "VIP_PRACTICE_STUDENT_NUM",
  89. "VIP_PRACTICE_ADD_STUDENT_NUM",
  90. ];
  91. arr.forEach((str) => {
  92. if (this.data[str]) {
  93. obj[str] = this.data[str];
  94. }
  95. });
  96. /**
  97. * {
  98. ADD_STUDENT_REGISTRATION_NUM:
  99. this.data["ADD_STUDENT_REGISTRATION_NUM"] || {},
  100. MUSIC_GROUP_STUDENT: this.data["MUSIC_GROUP_STUDENT"] || {},
  101. NEWLY_STUDENT_NUM: this.data["NEWLY_STUDENT_NUM"] || {},
  102. QUIT_MUSIC_GROUP_STUDENT_NUM:
  103. this.data["QUIT_MUSIC_GROUP_STUDENT_NUM"] || {},
  104. VIP_PRACTICE_STUDENT_NUM: this.data["VIP_PRACTICE_STUDENT_NUM"] || {},
  105. VIP_PRACTICE_ADD_STUDENT_NUM:
  106. this.data["VIP_PRACTICE_ADD_STUDENT_NUM"] || {},
  107. };
  108. */
  109. return obj;
  110. },
  111. noQuitStudentItem(){
  112. let obj = {};
  113. let arr = [
  114. "ADD_STUDENT_REGISTRATION_NUM",
  115. "MUSIC_GROUP_STUDENT",
  116. "NEWLY_STUDENT_NUM",
  117. "VIP_PRACTICE_STUDENT_NUM",
  118. "VIP_PRACTICE_ADD_STUDENT_NUM",
  119. ];
  120. arr.forEach((str) => {
  121. if (this.data[str]) {
  122. obj[str] = this.data[str];
  123. }
  124. });
  125. return obj;
  126. },
  127. chartExtend() {
  128. return {
  129. yAxis: {
  130. //纵轴标尺固定
  131. minInterval: 1,
  132. type: "value",
  133. scale: true,
  134. min: 0,
  135. axisLabel: {
  136. formatter: "{value}人",
  137. },
  138. },
  139. series: {
  140. type: "line",
  141. smooth: false,
  142. },
  143. tooltip: {
  144. axisPointer: {
  145. type: "shadow",
  146. shadowStyle: {
  147. color: "rgba(150,150,150,0.2)",
  148. },
  149. },
  150. formatter: (item) => {
  151. return [
  152. item[0].axisValueLabel,
  153. ...item.map(
  154. (d) => {
  155. return `<br/>${d.marker}${d.seriesName}: ${d.value[1]}人`}
  156. ),
  157. ].join("");
  158. },
  159. },
  160. };
  161. },
  162. dataZoom() {
  163. return [
  164. {
  165. type: "slider",
  166. start: 60,
  167. end: 100,
  168. },
  169. ];
  170. },
  171. chartData() {
  172. const values = Object.values(this.noQuitStudentItem);
  173. const months = {};
  174. for (const item of values) {
  175. for (const row of item.indexMonthData || []) {
  176. const key = this.$helpers.dayjs(row.month).format("YYYY-MM-DD");
  177. if (!months[key]) {
  178. months[key] = {
  179. 日期: key,
  180. };
  181. }
  182. months[key][item.title] = row.percent;
  183. }
  184. }
  185. return {
  186. columns: ["日期", ...values.map((item) => item.title)],
  187. rows: Object.values(months),
  188. };
  189. },
  190. chartDataForMoth() {
  191. const values = Object.values(this.noQuitStudentItem);
  192. const months = {};
  193. for (const item of values) {
  194. for (const row of item.indexMonthData || []) {
  195. const key = this.$helpers.dayjs(row.month).format("YYYY-MM");
  196. if (!months[key]) {
  197. months[key] = {
  198. 月份: key,
  199. };
  200. months[key][item.title] = row.percent;
  201. } else {
  202. if (
  203. months[key][item.title] &&
  204. item.dataType != "MUSIC_GROUP_STUDENT" &&
  205. item.dataType != "VIP_PRACTICE_STUDENT_NUM"
  206. ) {
  207. months[key][item.title] += parseFloat(row.percent);
  208. } else {
  209. months[key][item.title] = row.percent;
  210. }
  211. }
  212. }
  213. }
  214. return {
  215. columns: ["月份", ...values.map((item) => item.title)],
  216. rows: Object.values(months),
  217. };
  218. },
  219. funnelData() {
  220. const { indexMonthData = [] } = this.data["STUDENT_CONVERSION"] || {};
  221. return {
  222. columns: ["类型", "数值"],
  223. rows: indexMonthData.map((item) => ({
  224. 类型: item.title,
  225. 数值: item.percent,
  226. })),
  227. };
  228. },
  229. dataEmpty() {
  230. return !this.chartData.rows.length;
  231. },
  232. },
  233. data() {
  234. return {
  235. active: "NEWLY_STUDENT_NUM",
  236. isHistogram: true,
  237. timer: "day",
  238. mdate: this.search?.dates,
  239. loading: false,
  240. };
  241. },
  242. mounted(){
  243. this.$refs.searchHeader.initStatue('month');
  244. },
  245. methods: {
  246. init(){
  247. this.FetchDetail()
  248. },
  249. changeValue(date) {
  250. // 请求更改数据
  251. this.mdate = date;
  252. this.isDayOrMoth(date);
  253. this.FetchDetail();
  254. },
  255. async FetchDetail() {
  256. this.loading = true;
  257. let data = [];
  258. try {
  259. const { dates, ...rest } = this.search;
  260. const res = await getIndex({
  261. ...rest,
  262. ...getTimes(this.mdate, ["startDate", "endDate"]),
  263. dataTypes:
  264. "ADD_STUDENT_REGISTRATION_NUM,MUSIC_GROUP_STUDENT,NEWLY_STUDENT_NUM,QUIT_MUSIC_GROUP_STUDENT_NUM,VIP_PRACTICE_STUDENT_NUM,VIP_PRACTICE_ADD_STUDENT_NUM",
  265. });
  266. for (const item of res.data) {
  267. // 再循环一遍
  268. // for (const key in this.items) {
  269. // // console.log(key);
  270. // if (item.dataType == key) {
  271. data[item.dataType] = {
  272. ...item,
  273. desc: descs[item.dataType],
  274. };
  275. // }
  276. // }
  277. }
  278. } catch (error) {
  279. console.log(error);
  280. }
  281. this.loading = false;
  282. this.dataInfo = data;
  283. this.$emit("resetDate", data);
  284. },
  285. isDayOrMoth(arr) {
  286. if (!arr || arr.length < 1) {
  287. this.timer = "day";
  288. } else {
  289. const count = this.$helpers
  290. .dayjs(arr[0])
  291. .diff(this.$helpers.dayjs(arr[1]), "day");
  292. Math.abs(count) > chioseNum
  293. ? (this.timer = "month")
  294. : (this.timer = "day");
  295. }
  296. },
  297. },
  298. watch: {
  299. search: {
  300. deep: true,
  301. handler(val) {
  302. this.mdate = val.dates;
  303. this.timer = "day";
  304. this.$refs.searchHeader.initStatue();
  305. },
  306. },
  307. },
  308. };
  309. </script>
  310. <style lang="less" scoped>
  311. // .statistic{
  312. // ::v-deep .statistic-content{
  313. // cursor: pointer;
  314. // &.active > span{
  315. // color: var(--color-primary) !important;
  316. // }
  317. // }
  318. // }
  319. .chioseBox {
  320. position: absolute;
  321. right: 20px;
  322. z-index: 1000;
  323. }
  324. .wrap {
  325. position: relative;
  326. }
  327. </style>