index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <script setup lang="ts">
  2. import {AreaChartParams, SchoolParams} from "@/api/areaboard/types";
  3. import {GradeList} from "@/api/grade/types";
  4. import {getGradeSelect} from "@/api/grade";
  5. import {getAreaSchool} from "@/api/areaboard";
  6. import {SchoolList} from "@/api/school/types";
  7. import ComboCharts from "@/views/admin/components/ComboCharts.vue";
  8. import {trimInput} from "@/utils";
  9. import {StudentItem, StudentParams} from "@/api/student/types";
  10. import {getStudentLists} from "@/api/student";
  11. import {useRouter} from "vue-router";
  12. import SvgIcon from "@/components/SvgIcon/index.vue";
  13. const router = useRouter();
  14. defineOptions({
  15. name: "DashboardSchool",
  16. inheritAttrs: false,
  17. });
  18. /**
  19. * 筛选条件
  20. */
  21. const dataParams: SchoolParams = reactive({
  22. school_id: 0,
  23. grade_id: 0,
  24. start_time: Math.ceil(Date.parse("2023/1/1 00:00") / 1000),
  25. end_time: Math.ceil(Date.now() / 1000),
  26. });
  27. const datePicker = ref<number[]>([Date.parse("2023/1/1 00:00"), Date.now()]);
  28. const schoolData = ref<SchoolList[]>();
  29. const gradeData = ref<GradeList[]>();
  30. /**
  31. * 获取学校
  32. */
  33. async function getSchoolData() {
  34. getAreaSchool(0, 0)
  35. .then(({data}) => {
  36. schoolData.value = data;
  37. schoolData.value?.unshift({num: "", school_id: 0, name: "全部学校"});
  38. gradeData.value = [{id: 0, name: "全部班级"}];
  39. })
  40. .catch((error) => {
  41. schoolData.value = [{num: "", school_id: 0, name: "全部学校"}];
  42. gradeData.value = [{id: 0, name: "全部班级"}];
  43. console.log(error.message);
  44. });
  45. }
  46. /**
  47. * 班级数据
  48. */
  49. async function getGradeData(schoolId: number) {
  50. getGradeSelect(schoolId)
  51. .then(({data}) => {
  52. dataParams.grade_id = 0;
  53. gradeData.value = data;
  54. gradeData.value?.unshift({id: 0, name: "全部班级"});
  55. })
  56. .catch((error) => {
  57. dataParams.grade_id = 0;
  58. gradeData.value = [{id: 0, name: "全部班级"}];
  59. console.log(error.message);
  60. });
  61. }
  62. // 改变时间
  63. function changeDate() {
  64. dataParams.start_time = Math.ceil(datePicker.value[0] / 1000);
  65. dataParams.end_time = Math.ceil(datePicker.value[1] / 1000);
  66. }
  67. // 获取图表数据条件
  68. const chartStatus = ref(false);
  69. const chartParams: AreaChartParams = reactive({});
  70. /**
  71. * 获取页面数据
  72. */
  73. function getSchoolPageData() {
  74. // 图表参数
  75. chartParams.school_id = dataParams.school_id;
  76. chartParams.grade_id = dataParams.grade_id;
  77. chartParams.start_time = dataParams.start_time;
  78. chartParams.end_time = dataParams.end_time;
  79. chartStatus.value = true;
  80. if (dataParams.school_id > 0) {
  81. // 学生参数
  82. studentParams.school_id = dataParams.school_id;
  83. studentParams.grade_id = dataParams.grade_id;
  84. getStudentData();
  85. } else {
  86. dataMessage.value = "你还未选择具体学校,请选择学校后查看。";
  87. }
  88. }
  89. /**
  90. * 学生数据
  91. */
  92. const studentParams: StudentParams = reactive({
  93. school_id: 0,
  94. grade_id: 0,
  95. search: "",
  96. page: 1,
  97. page_size: 10,
  98. });
  99. const dataMessage = ref("加载中...");
  100. const studentCount = ref(0);
  101. const studentData = ref<StudentItem[]>();
  102. // 重置学生参数
  103. function initStudentParams() {
  104. studentParams.school_id = dataParams.school_id;
  105. studentParams.grade_id = dataParams.grade_id;
  106. studentParams.search = "";
  107. studentParams.page = 1;
  108. studentParams.page_size = 10;
  109. getStudentData();
  110. }
  111. async function getStudentData() {
  112. getStudentLists(studentParams)
  113. .then(({data}) => {
  114. const {count, lists} = data;
  115. studentCount.value = count;
  116. studentData.value = lists;
  117. if (!(count && count > 0 && lists.length > 0)) {
  118. dataMessage.value = "没有符合搜索条件的记录!";
  119. if (studentParams.grade_id == 0 && studentParams.search == "") {
  120. dataMessage.value = "暂时还没有任何学生绑定学校!";
  121. }
  122. }
  123. })
  124. .catch((error) => {
  125. dataMessage.value = error.message;
  126. console.log(error.message);
  127. });
  128. }
  129. function alertTrainingError() {
  130. ElMessage.error("该学生训练数据还不足以进行训练效果分析,请至少完成16次专注力训练后再来查看。");
  131. }
  132. function alertEvaluationError() {
  133. ElMessage.error("该学生还未进行过测评,暂无测评数据。");
  134. }
  135. function getSchoolSearch() {
  136. getStudentData();
  137. }
  138. function pathTo(url: String) {
  139. // 记录搜索条件
  140. sessionStorage.setItem("schoolChartParams", JSON.stringify(dataParams));
  141. sessionStorage.setItem("schoolStudentParams", JSON.stringify(studentParams));
  142. // 路由跳转
  143. router.push(url);
  144. }
  145. onMounted(() => {
  146. // 获取学校
  147. getSchoolData();
  148. gradeData.value = [{id: 0, name: "全部班级"}];
  149. // 获取记录的搜索条件
  150. let params1 = sessionStorage.getItem("schoolChartParams");
  151. if (params1 && params1 != "null") {
  152. const parse1: SchoolParams = JSON.parse(params1);
  153. if (parse1.school_id && parse1.school_id > 0) {
  154. dataParams.school_id = parse1.school_id;
  155. }
  156. if (parse1.grade_id && parse1.grade_id > 0) {
  157. dataParams.grade_id = parse1.grade_id;
  158. }
  159. if (parse1.start_time && parse1.start_time > 0) {
  160. dataParams.start_time = parse1.start_time;
  161. }
  162. if (parse1.end_time && parse1.end_time > 0) {
  163. dataParams.end_time = parse1.end_time;
  164. }
  165. }
  166. let params2 = sessionStorage.getItem("schoolStudentParams");
  167. if (params2 && params2 != "null") {
  168. const parse2: StudentParams = JSON.parse(params2);
  169. if (parse2.page && parse2.page > 0) {
  170. studentParams.page = parse2.page;
  171. }
  172. if (parse2.page_size && parse2.page_size > 0) {
  173. studentParams.page_size = parse2.page_size;
  174. }
  175. if (parse2.search) {
  176. studentParams.search = parse2.search;
  177. }
  178. }
  179. sessionStorage.removeItem("schoolChartParams");
  180. sessionStorage.removeItem("schoolStudentParams");
  181. // 获取页面数据
  182. getSchoolPageData();
  183. });
  184. </script>
  185. <template>
  186. <div class="area-container">
  187. <div class="search-box s2">
  188. <el-select
  189. v-model="dataParams.school_id"
  190. filterable
  191. placeholder="全部学校"
  192. size="large"
  193. @change="getGradeData(dataParams.school_id)">
  194. <el-option v-for="item in schoolData" :key="item.school_id" :label="item.name" :value="item.school_id" />
  195. </el-select>
  196. <el-select v-model="dataParams.grade_id" placeholder="全部班级" size="large">
  197. <el-option v-for="item in gradeData" :key="item.id" :label="item.name" :value="item.id" />
  198. </el-select>
  199. <div>
  200. <el-date-picker
  201. v-model="datePicker"
  202. type="daterange"
  203. size="large"
  204. start-placeholder="开始日期"
  205. end-placeholder="结束日期"
  206. format="YYYY-MM-DD"
  207. value-format="x"
  208. @change="changeDate()" />
  209. </div>
  210. <el-button color="#4284f2" size="large" @click="getSchoolPageData()">查找</el-button>
  211. </div>
  212. <!-- Echarts 图表 -->
  213. <ComboCharts
  214. v-if="chartStatus"
  215. :key="chartParams?.toString()"
  216. :schoolId="chartParams.school_id"
  217. :gradeId="chartParams.grade_id"
  218. :startTime="chartParams.start_time"
  219. :endTime="chartParams.end_time" />
  220. <!-- 学生查找 -->
  221. <div v-if="dataParams.school_id > 0" class="search-box s2">
  222. <el-input
  223. v-model="studentParams.search"
  224. placeholder="请输入学生名称或手机号码"
  225. size="large"
  226. @input="(value:string) => (studentParams.search = trimInput(value))" />
  227. <el-button size="large" type="primary" @click="getSchoolSearch()">查找</el-button>
  228. <el-button size="large" type="primary" @click="initStudentParams()">重置</el-button>
  229. <a
  230. class="download-btn"
  231. href="javascript:void(0);"
  232. @click="pathTo('/download/student/excel?school=' + studentParams.school_id)">
  233. <svg-icon icon-class="download" size="3rem" />
  234. <span>报告原始数据下载</span>
  235. </a>
  236. </div>
  237. <!-- 学生列表-->
  238. <div class="student-container">
  239. <!-- 学生数据 -->
  240. <div class="list-table">
  241. <el-table :data="studentData" style="width: 100%">
  242. <el-table-column align="center" label="序号" max-width="120" type="index" />
  243. <el-table-column prop="name" label="学生名称" align="center" />
  244. <el-table-column prop="grade_name" label="所在班级" align="center" />
  245. <el-table-column prop="phone" label="手机号码" align="center" />
  246. <el-table-column prop="create_time" label="注册时间" align="center" />
  247. <el-table-column prop="count" label="训练次数" align="center" />
  248. <el-table-column label="操作" align="center" min-width="160">
  249. <template #default="scope">
  250. <button
  251. class="table-btn"
  252. @click="pathTo('/schoolBoard/training?id=' + scope.row.id + '&grade=' + scope.row.grade_id)">
  253. 训练报告
  254. </button>
  255. <button
  256. v-if="scope.row.count > 16"
  257. class="table-btn"
  258. @click="pathTo('/schoolBoard/result?id=' + scope.row.id + '&school=' + studentParams.school_id)">
  259. 训练效果分析
  260. </button>
  261. <button v-else class="table-btn disabled" @click="alertTrainingError()">训练效果分析</button>
  262. <button
  263. v-if="scope.row.count_report > 0"
  264. class="table-btn"
  265. @click="pathTo('/schoolBoard/evaluation?id=' + scope.row.id)">
  266. 训练效果分析
  267. </button>
  268. <button v-else class="table-btn disabled" @click="alertEvaluationError()">测评数据对比</button>
  269. </template>
  270. </el-table-column>
  271. <!-- 无数据插槽 -->
  272. <template #empty>
  273. <div class="empty">
  274. <img src="../../../assets/empty.png" alt="数据为空" />
  275. <p>{{ dataMessage }}</p>
  276. </div>
  277. </template>
  278. </el-table>
  279. <pagination
  280. v-if="studentCount > 0"
  281. v-model:total="studentCount"
  282. v-model:page="studentParams.page"
  283. v-model:limit="studentParams.page_size"
  284. @pagination="getSchoolSearch()" />
  285. </div>
  286. </div>
  287. </div>
  288. </template>
  289. <style lang="scss" scoped>
  290. .area-top {
  291. background: #fff;
  292. }
  293. .card-box {
  294. padding: 0 30px 20px;
  295. }
  296. .search-box {
  297. position: relative;
  298. display: flex;
  299. padding: 20px 55px;
  300. line-height: 40px;
  301. background: #fff;
  302. .el-select {
  303. width: 140px;
  304. margin-right: 10px;
  305. }
  306. .el-button {
  307. padding: 0 26px;
  308. margin: 0 0 0 20px;
  309. font-size: 16px;
  310. background: #4284f2;
  311. border-radius: 10px;
  312. }
  313. .el-input {
  314. width: 250px;
  315. margin: 0;
  316. border: 1px solid #ddd;
  317. border-radius: 10px;
  318. }
  319. :deep(.el-input__inner) {
  320. font-size: 16px;
  321. }
  322. .download-btn {
  323. position: absolute;
  324. right: 180px;
  325. color: #4284f2;
  326. }
  327. .download-btn * {
  328. margin-right: 12px;
  329. vertical-align: middle;
  330. }
  331. }
  332. :deep(.el-select) {
  333. --el-select-input-focus-border-color: none !important;
  334. }
  335. :deep(.search-box .el-date-editor) {
  336. width: 300px;
  337. margin: 0;
  338. overflow: hidden;
  339. border: 1px solid #ddd;
  340. border-radius: 10px;
  341. }
  342. :deep(.search-box.s2 .el-select) {
  343. border: 1px solid #ddd;
  344. border-radius: 10px;
  345. }
  346. :deep(.el-date-editor) {
  347. --el-select-input-focus-border-color: none !important;
  348. }
  349. :deep(.el-input__wrapper) {
  350. background: #fff;
  351. border-radius: 12px;
  352. box-shadow: none !important;
  353. }
  354. :deep(.el-input__wrapper.is-focus) {
  355. box-shadow: none !important;
  356. }
  357. :deep(.el-select:hover:not(.el-select--disabled) .el-input__wrapper) {
  358. box-shadow: none !important;
  359. }
  360. :deep(.el-select .el-input__wrapper.is-focus) {
  361. box-shadow: none !important;
  362. }
  363. :deep(.el-table .el-table__header .el-table__cell .cell) {
  364. overflow: visible;
  365. white-space: nowrap;
  366. }
  367. :deep(.el-table th.el-table__cell) {
  368. background: #e9ebee;
  369. }
  370. .mobile .el-col {
  371. margin-bottom: 10px;
  372. }
  373. .student-container {
  374. position: relative;
  375. padding: 20px 30px;
  376. }
  377. .list-table {
  378. overflow: hidden;
  379. background: #fff;
  380. border-radius: 25px;
  381. .table-btn {
  382. display: inline-block;
  383. height: 38px;
  384. padding: 0 15px;
  385. margin-right: 10px;
  386. line-height: 38px;
  387. color: #fff;
  388. background: #4284f2;
  389. border-radius: 6px;
  390. &.disabled {
  391. background: #bfbfbf;
  392. }
  393. }
  394. }
  395. .empty {
  396. padding: 200px 0;
  397. }
  398. </style>