tableList.vue 19 KB

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