tableList.vue 16 KB

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