tableList.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <template>
  2. <div class="tableList">
  3. <save-form
  4. :inline="true"
  5. class="searchForm"
  6. @submit="onSearch"
  7. @reset="onReSet"
  8. ref="searchForm"
  9. :model.sync="searchForm"
  10. >
  11. <el-form-item prop="search">
  12. <el-input
  13. clearable
  14. placeholder="学生姓名/编号"
  15. @keyup.enter.native="
  16. (e) => {
  17. e.target.blur();
  18. $refs.searchForm.save();
  19. onSearch();
  20. }
  21. "
  22. v-model.trim="searchForm.search"
  23. ></el-input>
  24. </el-form-item>
  25. <el-form-item prop="studentStatus">
  26. <el-select
  27. class="multiple"
  28. filterable
  29. v-model.trim="searchForm.studentStatus"
  30. clearable
  31. placeholder="请选择学员状态"
  32. >
  33. <el-option label="在读" value="NORMAL"></el-option>
  34. <el-option label="沉睡" value="SLEEPY"></el-option>
  35. <el-option label="流失" value="LOST"></el-option>
  36. <el-option label="暂停" value="PAUSE"></el-option>
  37. </el-select>
  38. </el-form-item>
  39. <el-form-item prop="feedbackType">
  40. <el-select
  41. class="multiple"
  42. filterable
  43. v-model.trim="searchForm.feedbackType"
  44. clearable
  45. placeholder="请选择回访状态"
  46. >
  47. <el-option
  48. :label="item.label"
  49. :value="item.value"
  50. v-for="(item, index) in feedbackTypeList"
  51. :key="index"
  52. ></el-option>
  53. </el-select>
  54. </el-form-item>
  55. <el-form-item prop="latelyCourseConsumer">
  56. <el-input
  57. clearable
  58. placeholder="近30天课耗"
  59. v-model.trim="searchForm.latelyCourseConsumer"
  60. ></el-input>
  61. </el-form-item>
  62. <el-form-item prop="visitNum">
  63. <el-input
  64. clearable
  65. placeholder="回访次数"
  66. v-model.trim="searchForm.visitNum"
  67. ></el-input>
  68. </el-form-item>
  69. <el-form-item prop="teacherId">
  70. <remote-search :commit="'setTeachers'" v-model="searchForm.teacherId" :isForzenWithQueryCondition="true"/>
  71. </el-form-item>
  72. <el-form-item prop="musicDirectorId">
  73. <remote-search
  74. :commit="'setEducations'"
  75. v-model="searchForm.musicDirectorId"
  76. />
  77. </el-form-item>
  78. <el-form-item prop="courseConsumerError">
  79. <el-select
  80. class="multiple"
  81. filterable
  82. v-model.trim="searchForm.courseConsumerError"
  83. clearable
  84. placeholder="请选择课耗是否异常"
  85. >
  86. <el-option label="是" :value="true"></el-option>
  87. <el-option label="否" :value="false"></el-option>
  88. </el-select>
  89. </el-form-item>
  90. <el-form-item prop="wornFlag">
  91. <el-select
  92. class="multiple"
  93. filterable
  94. v-model.trim="searchForm.wornFlag"
  95. clearable
  96. placeholder="请选择是否预警"
  97. >
  98. <el-option label="是" :value="true"></el-option>
  99. <el-option label="否" :value="false"></el-option>
  100. </el-select>
  101. </el-form-item>
  102. <el-form-item prop="subCourseMinNum">
  103. <el-input-number
  104. class="number-input"
  105. :controls="false"
  106. :precision="0"
  107. :min="0"
  108. v-model="searchForm.subCourseMinNum"
  109. placeholder="请输入剩余课时最小数"
  110. ></el-input-number>
  111. </el-form-item>
  112. <el-form-item prop="subCourseMaxNum">
  113. <el-input-number
  114. class="number-input"
  115. :controls="false"
  116. :precision="0"
  117. v-model="searchForm.subCourseMaxNum"
  118. :min="searchForm.subCourseMinNum"
  119. placeholder="请输入剩余课时最大数"
  120. ></el-input-number>
  121. </el-form-item>
  122. <el-form-item prop="timer">
  123. <el-date-picker
  124. v-model.trim="searchForm.timer"
  125. type="daterange"
  126. value-format="yyyy-MM-dd"
  127. range-separator="-"
  128. start-placeholder="第一次课开始时间"
  129. end-placeholder="第一次课结束时间"
  130. :picker-options="{
  131. firstDayOfWeek: 1,
  132. }"
  133. ></el-date-picker>
  134. </el-form-item>
  135. <el-form-item>
  136. <el-date-picker
  137. key="visiList1"
  138. v-model.trim="searchForm.lastTimer"
  139. style="width: 420px"
  140. type="daterange"
  141. value-format="yyyy-MM-dd"
  142. range-separator="至"
  143. start-placeholder="最后回访开始日期"
  144. end-placeholder="最后回访结束日期"
  145. :picker-options="{
  146. firstDayOfWeek: 1,
  147. }"
  148. >
  149. </el-date-picker>
  150. </el-form-item>
  151. <el-form-item>
  152. <el-button native-type="submit" type="danger">搜索</el-button>
  153. <el-button native-type="reset" type="primary">重置</el-button>
  154. <el-button
  155. type="primary"
  156. v-if="tableList.length > 0"
  157. v-permission="
  158. 'studentStatistics/exportStudentSmallClassStatisticsSum'
  159. "
  160. @click="smallStudentExport"
  161. >导出</el-button
  162. >
  163. </el-form-item>
  164. </save-form>
  165. <div class="tableWrap">
  166. <el-table
  167. :data="tableList"
  168. :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
  169. >
  170. <el-table-column type="expand">
  171. <template slot-scope="props">
  172. <el-form
  173. label-position="left"
  174. class="demo-table-expand"
  175. :inline="true"
  176. >
  177. <el-row>
  178. <el-col :span="6">
  179. <el-form-item label="指导老师信息">
  180. <div
  181. class="schoolWrap"
  182. style="color: var(--color-primary); cursor: pointer"
  183. v-if="props.row.teacherId"
  184. @click="gototeacher(props.row.teacherId)"
  185. >
  186. <span>{{ props.row.teacherName }}</span>
  187. <span>({{ props.row.teacherId }})</span>
  188. </div>
  189. </el-form-item></el-col
  190. >
  191. <el-col :span="6">
  192. <el-form-item label="乐团主管">
  193. <div class="schoolWrap" v-if="props.row.musicDirectorId">
  194. <span>{{ props.row.musicDirectorName }}</span>
  195. <span>({{ props.row.musicDirectorId }})</span>
  196. </div>
  197. </el-form-item></el-col
  198. >
  199. <el-col :span="6">
  200. <el-form-item label="声部课老师">
  201. <div class="schoolWrap">
  202. <overflow-text
  203. width="100%"
  204. :text="props.row.studentBasicInfo.subjectTeacherName"
  205. ></overflow-text>
  206. </div> </el-form-item
  207. ></el-col>
  208. <el-col :span="6">
  209. <el-form-item label="年级">
  210. <div class="schoolWrap">
  211. <overflow-text
  212. width="100%"
  213. :text="props.row.studentBasicInfo.grade"
  214. ></overflow-text>
  215. </div> </el-form-item
  216. ></el-col>
  217. </el-row>
  218. <el-row>
  219. <el-col :span="6">
  220. <el-form-item label="近30天课耗">
  221. <div class="schoolWrap">
  222. <span>{{ props.row.latelyCourseConsumer }}</span>
  223. </div>
  224. </el-form-item></el-col
  225. >
  226. <el-col :span="6">
  227. <el-form-item label="回访次数">
  228. <div class="schoolWrap">
  229. <span>{{ props.row.visitNum }}</span>
  230. </div>
  231. </el-form-item></el-col
  232. >
  233. <el-col :span="6">
  234. <el-form-item label="回访状态">
  235. <div class="schoolWrap">
  236. {{ props.row.lastVisitStatus | feedbackTypeFilter }}
  237. </div>
  238. </el-form-item></el-col
  239. >
  240. <el-col :span="6">
  241. <el-form-item label="回访日期">
  242. <div class="schoolWrap" v-if="props.row.lastVisitTime">
  243. {{ props.row.lastVisitTime | dayjsFormat }}
  244. </div>
  245. </el-form-item></el-col
  246. >
  247. </el-row>
  248. <el-row>
  249. <el-col :span="6">
  250. <el-form-item label="第一次课时间">
  251. <div class="schoolWrap">
  252. <overflow-text
  253. width="100%"
  254. :text="props.row.firstCourseTime"
  255. ></overflow-text>
  256. </div> </el-form-item
  257. ></el-col>
  258. <el-col :span="6">
  259. <el-form-item label="最近上课时间">
  260. <div class="schoolWrap">
  261. <overflow-text
  262. width="100%"
  263. :text="props.row.lastCourseTime"
  264. ></overflow-text>
  265. </div> </el-form-item
  266. ></el-col>
  267. <el-col :span="6">
  268. <el-form-item label="原因">
  269. <div class="schoolWrap">
  270. <overflow-text
  271. width="100%"
  272. :text="props.row.visitReason"
  273. ></overflow-text>
  274. </div> </el-form-item
  275. ></el-col>
  276. </el-row>
  277. </el-form>
  278. </template>
  279. </el-table-column>
  280. <el-table-column align="center" prop="organName" label="所属分部">
  281. <template slot-scope="scope">
  282. <copy-text>{{
  283. scope.row.studentBasicInfo && scope.row.studentBasicInfo.organName
  284. }}</copy-text>
  285. </template>
  286. </el-table-column>
  287. <el-table-column
  288. align="center"
  289. prop="userId"
  290. label="学员信息"
  291. width="140px"
  292. >
  293. <template slot-scope="scope">
  294. <div
  295. style="color: var(--color-primary); cursor: pointer"
  296. v-if="scope.row.studentBasicInfo"
  297. @click="gotoStudent(scope.row.studentBasicInfo.userId)"
  298. >
  299. {{
  300. scope.row.studentBasicInfo &&
  301. scope.row.studentBasicInfo.userName
  302. }}<br />
  303. {{
  304. scope.row.studentBasicInfo && scope.row.studentBasicInfo.userId
  305. }}
  306. </div>
  307. </template>
  308. </el-table-column>
  309. <el-table-column
  310. align="center"
  311. prop="studentBasicInfo.phone"
  312. label="联系电话"
  313. >
  314. </el-table-column>
  315. <el-table-column
  316. align="center"
  317. prop="studentBasicInfo.subjectName"
  318. label="声部"
  319. >
  320. </el-table-column>
  321. <el-table-column
  322. align="center"
  323. prop="studentBasicInfo.cooperationOrganName"
  324. label="学校"
  325. >
  326. </el-table-column>
  327. <el-table-column align="center" prop="organName" label="学员状态">
  328. <template slot-scope="scope">
  329. <div>
  330. {{ scope.row.studentBasicInfo.studentStatus }}
  331. <!-- <p
  332. v-if="
  333. (scope.row.noScheduleNum || scope.row.subCourseNum) &&
  334. (scope.row.latelyYearCourseConsumer ||
  335. !scope.row.overCourseNum)
  336. "
  337. >
  338. 在读
  339. </p>
  340. <p
  341. v-if="
  342. scope.row.overCourseNum &&
  343. (scope.row.subCourseNum || scope.row.noScheduleNum) &&
  344. !scope.row.latelyYearCourseConsumer
  345. "
  346. >
  347. 沉睡
  348. </p>
  349. <p
  350. v-if="
  351. scope.row.overCourseNum &&
  352. !scope.row.noScheduleNum &&
  353. !scope.row.subCourseNum
  354. "
  355. >
  356. 流失
  357. </p>
  358. <p
  359. v-if="
  360. scope.row.studentBasicInfo.courseBalance &&
  361. !scope.row.noScheduleNum &&
  362. !scope.row.subCourseNum
  363. "
  364. >
  365. 暂停
  366. </p> -->
  367. </div>
  368. </template>
  369. </el-table-column>
  370. <el-table-column align="center" prop="noScheduleNum" label="未排课时">
  371. <template slot-scope="scope">
  372. {{ scope.row.noScheduleNum }}节
  373. </template>
  374. </el-table-column>
  375. <el-table-column align="center" prop="totalCourseNum" label="总课时">
  376. <template slot-scope="scope">
  377. {{ parseInt(scope.row.totalCourseNum+scope.row.noScheduleNum) }}节
  378. </template>
  379. </el-table-column>
  380. <el-table-column align="center" prop="overCourseNum" label="已完成课时">
  381. <template slot-scope="scope">
  382. {{ scope.row.overCourseNum }}节
  383. </template>
  384. </el-table-column>
  385. <el-table-column align="center" prop="subCourseNum" label="剩余课时">
  386. <template slot-scope="scope">
  387. {{ scope.row.subTotalCourse }}节
  388. </template>
  389. </el-table-column>
  390. <el-table-column align="center" prop="preCourseFee" label="预收金额">
  391. <template slot-scope="scope">
  392. {{ scope.row.preCourseFee | moneyFormat }}元
  393. </template>
  394. </el-table-column>
  395. <el-table-column align="center" width="180px" label="操作">
  396. <template slot-scope="scope">
  397. <el-button type="text" @click="recordVisit(scope.row)"
  398. >回访记录</el-button
  399. >
  400. </template>
  401. </el-table-column>
  402. </el-table>
  403. <pagination
  404. sync
  405. :total.sync="pageInfo.total"
  406. :page.sync="pageInfo.page"
  407. :limit.sync="pageInfo.limit"
  408. :page-sizes="pageInfo.page_size"
  409. @pagination="getList"
  410. />
  411. </div>
  412. <el-dialog
  413. title="回访记录"
  414. width="1000px"
  415. v-if="visitVisiable"
  416. :close-on-click-modal="false"
  417. :visible.sync="visitVisiable"
  418. >
  419. <visiList
  420. :studentId="activeRow.userId"
  421. :studentName="activeRow.studentBasicInfo.userName"
  422. :groupType="groupType"
  423. @getList="getList"
  424. />
  425. </el-dialog>
  426. <div v-show="isSearchs"></div>
  427. </div>
  428. </template>
  429. <script>
  430. import pagination from "@/components/Pagination/index";
  431. import { queryPage } from "../api";
  432. import { feedbackTypeList } from "@/utils/searchArray";
  433. import { getTimes } from "@/utils";
  434. import { Export } from "@/utils/downLoadFile";
  435. import cleanDeep from "clean-deep";
  436. import visiList from "./visiList.vue";
  437. import qs from "qs";
  438. export default {
  439. name: "tableList",
  440. props: ["groupType"],
  441. inject: ["organId", "isSearch"],
  442. components: {
  443. pagination,
  444. visiList,
  445. },
  446. data() {
  447. return {
  448. searchForm: {
  449. search: "",
  450. studentStatus: "",
  451. feedbackType: "",
  452. latelyCourseConsumer: "",
  453. visitNum: "",
  454. teacherId: "",
  455. musicDirectorId: "",
  456. courseConsumerError: "",
  457. timer: [],
  458. lastTimer: [],
  459. },
  460. tableList: [],
  461. feedbackTypeList,
  462. pageInfo: {
  463. // 分页规则
  464. limit: 10, // 限制显示条数
  465. page: 1, // 当前页
  466. total: 0, // 总条数
  467. page_size: [10, 20, 40, 50], // 选择限制显示条数
  468. },
  469. visitVisiable: false,
  470. activeRow: null,
  471. flag: false,
  472. };
  473. },
  474. async mounted() {
  475. this.$store.dispatch("setBranchs");
  476. this.$store.dispatch("setTeachers");
  477. await this.getList();
  478. },
  479. methods: {
  480. onSearch() {
  481. this.pageInfo.page = 1;
  482. this.getList();
  483. },
  484. onReSet() {
  485. this.searchForm.timer = [];
  486. this.searchForm.lastTimer = [];
  487. this.$refs.searchForm.resetFields();
  488. this.onSearch();
  489. },
  490. async getList() {
  491. const { timer, lastTimer, ...rest } = this.searchForm;
  492. try {
  493. let obj = {
  494. groupType: this.groupType,
  495. ...rest,
  496. ...getTimes(timer, ["firstCourseStartTime", "firstCourseEndTime"]),
  497. ...getTimes(lastTimer, ["lastVisitStartTime", "lastVisitEndTime"]),
  498. rows: this.pageInfo.limit,
  499. page: this.pageInfo.page,
  500. organId: this.organIds,
  501. };
  502. let res = await queryPage({
  503. ...obj,
  504. });
  505. this.tableList = res.data.rows || [];
  506. this.pageInfo.total = res.data.total;
  507. } catch (e) {
  508. console.log(e);
  509. }
  510. },
  511. gotoStudent(search) {
  512. this.$router.push({
  513. name: "studentList",
  514. params: { search: search },
  515. });
  516. },
  517. gototeacher(search) {
  518. this.$router.push({
  519. name: "teacherList",
  520. params: { search: search },
  521. });
  522. },
  523. recordVisit(row) {
  524. this.activeRow = row;
  525. this.visitVisiable = true;
  526. },
  527. smallStudentExport() {
  528. const { timer, ...rest } = this.searchForm;
  529. let str = "";
  530. if (this.groupType === "PRACTICE") {
  531. str = "网管课";
  532. } else if (this.groupType === "VIP") {
  533. str = "VIP课";
  534. } else {
  535. str = "乐理课";
  536. }
  537. Export(
  538. this,
  539. {
  540. url: "/api-web/studentStatistics/exportStudentSmallClassStatisticsSum",
  541. fileName: `${str}学员管理.xls`,
  542. method: "post",
  543. params: qs.stringify(
  544. cleanDeep({
  545. groupType: this.groupType,
  546. ...rest,
  547. ...getTimes(timer, [
  548. "firstCourseStartTime",
  549. "firstCourseEndTime",
  550. ]),
  551. rows: this.pageInfo.limit,
  552. page: this.pageInfo.page,
  553. organId: this.organIds,
  554. })
  555. ),
  556. },
  557. `您确定导出${str}学员管理?`
  558. );
  559. },
  560. },
  561. computed: {
  562. organIds() {
  563. return this.organId();
  564. },
  565. isSearchs: {
  566. get() {
  567. let flag = this.isSearch();
  568. if (this.flag != flag) {
  569. this.onSearch();
  570. }
  571. this.flag = flag;
  572. return flag;
  573. },
  574. },
  575. },
  576. };
  577. </script>
  578. <style lang="scss" scoped>
  579. .rows {
  580. > div {
  581. margin-bottom: 20px;
  582. }
  583. }
  584. ::v-deep .el-card__body .statistic {
  585. margin-bottom: 15px;
  586. padding: 0;
  587. }
  588. .statistic {
  589. .statistic-content > span {
  590. font-size: 22px !important;
  591. &:first-child {
  592. font-size: 14px !important;
  593. }
  594. }
  595. }
  596. .number-input {
  597. width: 180px !important;
  598. ::v-deep .el-input__inner {
  599. text-align: left;
  600. }
  601. }
  602. </style>