ReviewAccountComponent.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <div class="page_item end_item">
  3. <div class="page_search">
  4. <div class="search_content">
  5. <div class="content_left">
  6. <el-checkbox v-model="store.state.evaluationManageStepData.isGenerateAccountByGender"
  7. @change="(val)=>GenerateAccountByGenderOrNot(val)" >
  8. 按男女生成
  9. </el-checkbox>
  10. </div>
  11. <div class="content_right">
  12. <el-button class="delete_button_border_no_bg" @click="ClearReviewAccount">全部清空</el-button>
  13. <el-button class="button_border" @click="OpenBatchEditForm">批量编辑</el-button>
  14. <el-button class="button_refresh" @click="HandleExportExcel">批量导出</el-button>
  15. <el-button class="button_refresh" @click="">批量导入</el-button>
  16. <el-button class="button_background" @click="GoToGenerateCodePage">生成评价码</el-button>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="page_jg_20"></div>
  21. <Table :tableColumns="tableColumns"
  22. :tableData="tableData"
  23. :hidePagination="true">
  24. <template v-for="item in Object.values(originReviewAccount)"
  25. v-slot:[`class_${item.evaluationGradeId}`]="{row}" :key="item">
  26. {{ row[`class_${item.evaluationGradeId}`]?.className }}
  27. </template>
  28. <template v-for="item in Object.values(originReviewAccount)"
  29. v-slot:[`num_${item.evaluationGradeId}`]="{row}" :key="item">
  30. <!-- 没有班级的行不渲染 -->
  31. <el-input v-if="row[`class_${item.evaluationGradeId}`]?.classId" type="number" v-model="row[`num_${item.evaluationGradeId}`]" placeholder="请输入"/>
  32. </template>
  33. <template v-for="item in Object.values(originReviewAccount)"
  34. v-slot:[`male_${item.evaluationGradeId}`]="{row}" :key="item">
  35. <el-input v-if="row[`class_${item.evaluationGradeId}`]?.classId" type="number" v-model="row[`male_${item.evaluationGradeId}`]" placeholder="请输入"/>
  36. </template>
  37. <template v-for="item in Object.values(originReviewAccount)"
  38. v-slot:[`female_${item.evaluationGradeId}`]="{row}" :key="item">
  39. <el-input v-if="row[`class_${item.evaluationGradeId}`]?.classId" type="number" v-model="row[`female_${item.evaluationGradeId}`]" placeholder="请输入"/>
  40. </template>
  41. </Table>
  42. <FormDialog
  43. width="800px"
  44. :visible="batchEditFormVisible" :defaultFormData="{}"
  45. title="批量编辑" :submitLoading="batchEditFormLoading"
  46. @close="CloseBatchEditForm"
  47. @confirm="SaveBatchEditForm">
  48. <template #form_pre>
  49. <ul class="grade_form_item">
  50. <li v-for="grade in batchEditGradeList" :key="grade.evaluationGradeId">
  51. <div class="grade_name">{{ grade.evaluationGradeName }}</div>
  52. <div class="class_list">
  53. <el-checkbox
  54. v-model="grade.isSelectAll"
  55. :indeterminate="grade.selectedClassList?.length < grade.classList?.length && grade.selectedClassList?.length !==0 ? true : false"
  56. @change="(val)=>HandleGradeSelectAllOrNot(grade,val)"
  57. >
  58. 全选
  59. </el-checkbox>
  60. <el-checkbox
  61. v-model="grade.selectedClassList "
  62. v-for="checkItem in grade.classList"
  63. :key="checkItem.value"
  64. :label="checkItem.label"
  65. :value="checkItem.value"
  66. @change="(val)=>HandleGradeClassSelectChange(grade,val)"
  67. />
  68. </div>
  69. </li>
  70. </ul>
  71. </template>
  72. <template #footerRight="{formRef}">
  73. <div v-if="!store.state.evaluationManageStepData.isGenerateAccountByGender" class="batch_num">
  74. <span>数量:<el-input type="number" v-model="batchAccountNum" placeholder="请输入"/></span>
  75. </div>
  76. <div v-else class="group_batch_num">
  77. <span>男生:<el-input type="number" v-model="batchMaleNum" placeholder="请输入"/></span>
  78. <span>女生:<el-input type="number" v-model="batchFemaleNum" placeholder="请输入"/></span>
  79. </div>
  80. </template>
  81. </FormDialog>
  82. </div>
  83. </template>
  84. <script setup lang="ts">
  85. import { onMounted,ref,watch } from 'vue';
  86. import { useStore } from 'vuex';
  87. import { useRoute,useRouter } from 'vue-router';
  88. import ExcelJS from 'exceljs';
  89. import Table from '@/baseComponents/Table.vue';
  90. import FormDialog from '@/baseComponents/FormDialog.vue';
  91. import {
  92. getReviewAccountListApi,
  93. } from '@/api/reviewAccountComponent'
  94. const route = useRoute()
  95. const router = useRouter()
  96. const store = useStore()
  97. // store.state.evaluationManageStepData.isGenerateAccountByGender //按男女生成
  98. // 1. 评价账号数据 渲染 及 操作
  99. // 评价账号原始数据
  100. const originReviewAccount = ref({})
  101. // 渲染到表格中的评价账号数据
  102. const tableData = ref([]);
  103. const tableColumns = ref([])
  104. // 表头初始赋值 和 表行初始赋值 以及 批量编辑弹窗变量赋值
  105. const GetReviewAccountList = async ()=>{
  106. let params = {evaluationId: route.query.id}
  107. let res = await getReviewAccountListApi(params)
  108. if(res?.code == 200){
  109. let gradeMsg = res.data?.dataList || {}
  110. originReviewAccount.value = gradeMsg || {}
  111. // 批量编辑弹窗变量赋值
  112. batchEditGradeList.value = Object.values(gradeMsg).map(item=>{
  113. return {
  114. ...item,
  115. isSelectAll:false,
  116. selectedClassList:[],
  117. classList:item.classData.map(classItm=>{
  118. return {
  119. label: classItm.evaluationClassName,
  120. value: classItm.id
  121. }
  122. })
  123. }
  124. })
  125. //表头赋值
  126. tableColumns.value = Object.values(gradeMsg)?.map(grade=>{
  127. let gradeId = grade.evaluationGradeId
  128. return [
  129. {
  130. name: `grade_${gradeId}`,
  131. label: '年级',
  132. width: '80',
  133. align: 'center',
  134. custom: false,
  135. hidden: false,
  136. },
  137. {
  138. name: `class_${gradeId}`,
  139. label: '班级',
  140. width: '80',
  141. align: 'center',
  142. custom: true,
  143. hidden: false,
  144. },
  145. {
  146. name: `num_${gradeId}`,
  147. label: '数量',
  148. width: '80',
  149. align: 'center',
  150. custom: true,
  151. hidden: false,
  152. },
  153. {
  154. name: `male_${gradeId}`,
  155. label: '男生',
  156. width: '80',
  157. align: 'center',
  158. custom: true,
  159. hidden: true,
  160. },
  161. {
  162. name: `female_${gradeId}`,
  163. label: '女生',
  164. width: '80',
  165. align: 'center',
  166. custom: true,
  167. hidden: true,
  168. },
  169. ]
  170. }).flat()
  171. //初次赋值,要检查 上一次生成账号是否按男女生成
  172. ChangeHeaderShowItem(store.state.evaluationManageStepData.isGenerateAccountByGender)
  173. // 表行赋值
  174. Object.values(gradeMsg)?.map(grade=>{
  175. let gradeId = grade.evaluationGradeId
  176. let gradeName = grade.evaluationGradeName
  177. let classDataList = grade.classData
  178. classDataList.map((classItm,index)=>{
  179. if(typeof tableData.value[index] !== 'object'){
  180. tableData.value[index] = {}
  181. }
  182. // 当前年级下的行赋值
  183. tableData.value[index][`grade_${gradeId}`] = gradeName
  184. tableData.value[index][`class_${gradeId}`] = {
  185. className:classItm.evaluationClassName,
  186. classId:classItm.id,
  187. }
  188. tableData.value[index][`num_${gradeId}`] = classItm.evaluationCount
  189. tableData.value[index][`male_${gradeId}`] = classItm.boyCount
  190. tableData.value[index][`female_${gradeId}`] = classItm.girlCount
  191. })
  192. })
  193. }
  194. }
  195. // 切换评价账号的表头展示项 _ 根据 是否按男女生成 来判断
  196. const ChangeHeaderShowItem = (isGenerateByGender:Boolean)=>{
  197. if(isGenerateByGender){
  198. // 打开
  199. tableColumns.value = tableColumns.value.map(item=>{
  200. if(item?.name.startsWith('num_')){
  201. item.hidden = true
  202. }else if(item?.name.startsWith('male_') || item?.name.startsWith('female_')){
  203. item.hidden = false
  204. }
  205. return item
  206. })
  207. }else{
  208. // 关闭
  209. tableColumns.value = tableColumns.value.map(item=>{
  210. if(item?.name.startsWith('num_')){
  211. item.hidden = false
  212. }else if(item?.name.startsWith('male_') || item?.name.startsWith('female_')){
  213. item.hidden = true
  214. }
  215. return item
  216. })
  217. }
  218. }
  219. watch(()=>store.state.evaluationManageStepData.isGenerateAccountByGender,(val)=>{
  220. ChangeHeaderShowItem(val)
  221. })
  222. // 2. 批量编辑弹窗操作
  223. const batchEditFormVisible = ref(false)
  224. const batchEditFormLoading = ref(false)
  225. const batchAccountNum = ref() //批量的所有数据
  226. const batchFemaleNum = ref() //批量的女性数据
  227. const batchMaleNum = ref() //批量的男性数据
  228. interface batchEditGradeItm {
  229. evaluationGradeName: string,
  230. evaluationGradeId: string,
  231. classList: {label: string, value: string }[],
  232. selectedClassList:string[],
  233. isSelectAll:boolean,
  234. }
  235. // 批量编辑 弹窗数据
  236. const batchEditGradeList = ref<batchEditGradeItm[]>([]);
  237. // 关闭批量编辑弹窗
  238. const CloseBatchEditForm = ()=>{
  239. batchEditFormVisible.value = false
  240. batchAccountNum.value = null
  241. batchMaleNum.value = null
  242. batchFemaleNum.value = null
  243. // 清除 批量弹窗编辑 的批量选中数据
  244. batchEditGradeList.value = batchEditGradeList.value.map(item=>{
  245. return {
  246. ...item,
  247. selectedClassList:[],
  248. isSelectAll:false
  249. }
  250. })
  251. }
  252. // 打开批量编辑弹窗
  253. const OpenBatchEditForm = ()=>{
  254. batchEditFormVisible.value = true
  255. }
  256. // 保存批量编辑配置 ——
  257. const SaveBatchEditForm = (params:any)=>{
  258. const batchData = batchEditGradeList.value.map(item=>{
  259. return {
  260. gradeName: item.evaluationGradeName,
  261. gradeId: item.evaluationGradeId,
  262. selectedClassList:item.selectedClassList
  263. }
  264. })?.filter(item=>{return item.selectedClassList?.length > 0})
  265. // 选中的值赋值
  266. batchEditGradeList.value.map(grade=>{
  267. let gradeId = grade.evaluationGradeId
  268. let classDataList = grade.classData
  269. let changeAim = batchData.find(itm=>itm.gradeId == gradeId)
  270. // 有需要批量修改的 年级
  271. if(changeAim){
  272. classDataList.map((classItm,index)=>{
  273. // 有需要批量修改的 班级
  274. if(changeAim.selectedClassList.find(classId=>classId == classItm.id )){
  275. if(typeof tableData.value[index] !== 'object'){
  276. tableData.value[index] = {}
  277. }
  278. // 当前年级下 某个班级 的行赋值
  279. if(store.state.evaluationManageStepData.isGenerateAccountByGender){
  280. // 按男女生成
  281. tableData.value[index][`num_${gradeId}`] = Number(batchMaleNum.value) + Number(batchFemaleNum.value)
  282. tableData.value[index][`male_${gradeId}`] = batchMaleNum.value
  283. tableData.value[index][`female_${gradeId}`] = batchFemaleNum.value
  284. }else{
  285. // 按总人数生成
  286. tableData.value[index][`num_${gradeId}`] = batchAccountNum.value
  287. tableData.value[index][`male_${gradeId}`] = Math.floor(batchAccountNum.value / 2)
  288. tableData.value[index][`female_${gradeId}`] = Math.ceil(batchAccountNum.value / 2)
  289. }
  290. }
  291. })
  292. }
  293. })
  294. CloseBatchEditForm()
  295. }
  296. // 年级全选/全不选
  297. const HandleGradeSelectAllOrNot = (grade,val)=>{
  298. if(val){
  299. // 全选
  300. batchEditGradeList.value = batchEditGradeList.value.map(item=>{
  301. if(item.evaluationGradeId == grade.evaluationGradeId){
  302. item.selectedClassList = item.classList.map(classItm=>{
  303. return classItm.value
  304. })
  305. }
  306. return item
  307. })
  308. }else{{
  309. // 全不选
  310. batchEditGradeList.value = batchEditGradeList.value.map(item=>{
  311. if(item.evaluationGradeId == grade.evaluationGradeId){
  312. item.selectedClassList = []
  313. }
  314. return item
  315. })
  316. }}
  317. }
  318. // 年级下 班级选择的变更
  319. const HandleGradeClassSelectChange = (grade,val)=>{
  320. batchEditGradeList.value = batchEditGradeList.value.map(item=>{
  321. if(item.evaluationGradeId == grade.evaluationGradeId){
  322. item.isSelectAll = grade.selectedClassList?.length === grade.classList?.length && grade.selectedClassList?.length !==0 ? true : false
  323. }
  324. return item
  325. })
  326. }
  327. // 3. 清空配置的评价账号数据
  328. const ClearReviewAccount = ()=>{
  329. batchEditGradeList.value?.map(grade=>{
  330. let gradeId = grade.evaluationGradeId
  331. let classDataList = grade.classData
  332. classDataList.map((classItm,index)=>{
  333. if(typeof tableData.value[index] !== 'object'){
  334. tableData.value[index] = {}
  335. }
  336. // 当前年级下的行赋值
  337. tableData.value[index][`num_${gradeId}`] = null
  338. tableData.value[index][`male_${gradeId}`] = null
  339. tableData.value[index][`female_${gradeId}`] = null
  340. })
  341. })
  342. }
  343. // 4. 生成评价码页面
  344. const GoToGenerateCodePage = ()=>{
  345. // 跳转到生成评价码页面前要临时存储 当前页面的所有数据
  346. let reviewAccountMsg = {
  347. originReviewAccount:originReviewAccount.value,
  348. batchEditGradeList:batchEditGradeList.value,
  349. tableColumns:tableColumns.value,
  350. tableData:tableData.value
  351. }
  352. let accounts = batchEditGradeList.value?.map(grade=>{
  353. let gradeId = grade.evaluationGradeId
  354. let classDataList = grade.classData
  355. return classDataList.map((classItm,index)=>{
  356. if(typeof tableData.value[index] !== 'object'){
  357. tableData.value[index] = {}
  358. }
  359. return {
  360. id:tableData.value[index][`class_${gradeId}`].classId,
  361. evaluationGradeName: tableData.value[index][`grade_${gradeId}`],
  362. evaluationDataClassName: tableData.value[index][`class_${gradeId}`]?.className,
  363. evaluationCount: tableData.value[index][`num_${gradeId}`],
  364. boyCount: tableData.value[index][`male_${gradeId}`],
  365. girlCount: tableData.value[index][`female_${gradeId}`]
  366. }
  367. })
  368. })?.flat() || []
  369. let params = {
  370. evaluationId: route.query.id,
  371. genderStatus: store.state.evaluationManageStepData.isGenerateAccountByGender ? 1 : 0,
  372. accounts
  373. }
  374. // 存储当前整个页面的内容——防止生成评价码失败后,不能继续延用 配置状态的旧数据
  375. sessionStorage.setItem('reviewAccountMsg',JSON.stringify(reviewAccountMsg))
  376. // 存储生成评价码需要的参数
  377. sessionStorage.setItem('reviewAccountOptionData',JSON.stringify(params))
  378. router.push(`generateReviewCode?id=${route.query.id}`)
  379. }
  380. // 5. 批量导入/ 导出
  381. const HandleExportExcel = async () => {
  382. // 1. 初始化sheet列表
  383. let finalTableColumn = tableColumns.value
  384. let finalTableData = tableData.value
  385. let sheetName = '评价账号'
  386. // 2. 创建工作簿 和 工作表
  387. const workbook = new ExcelJS.Workbook();
  388. const worksheet = workbook.addWorksheet(sheetName);
  389. // 3. 添加表头行
  390. let headers = finalTableColumn.map(column => column.label || column.name)
  391. let headerRow = worksheet.addRow(headers)
  392. //4. 定义表头样式
  393. headerRow.eachCell((cell) => {
  394. cell.style = {
  395. font: { name: 'Microsoft YaHei', size: 12, bold: true, color: { argb: 'FF333333' } },
  396. fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF4F6F8' } },
  397. border: {
  398. top: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  399. left: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  400. bottom: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  401. right: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  402. },
  403. alignment: { vertical: 'middle', horizontal: 'left', wrapText: true },
  404. }
  405. })
  406. // 5. 添加数据行
  407. finalTableData.forEach(row => {
  408. const rowData = finalTableColumn.map(column => {
  409. const cellValue = row[column.name];
  410. if(column.name.startsWith('class_')){
  411. return cellValue?.className
  412. }
  413. return cellValue || '-'
  414. })
  415. const excelRow = worksheet.addRow(rowData);
  416. // 6. 定义数据行样式
  417. excelRow.eachCell({ includeEmpty: true }, (cell) => {
  418. cell.style = {
  419. font: { name: 'Microsoft YaHei', size: 12, color: { argb: 'FF333333' } },
  420. border: {
  421. top: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  422. left: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  423. bottom: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  424. right: { style: 'thin', color: { argb: 'FFD9D9D9' } },
  425. },
  426. alignment: { vertical: 'top', horizontal: 'left', wrapText: true },
  427. };
  428. });
  429. });
  430. // 5. 设置列宽
  431. worksheet.columns = headers.map(() => ({ width: 25 }));
  432. // 6. 导出文件
  433. const buffer = await workbook.xlsx.writeBuffer();
  434. const blob = new Blob([buffer], {
  435. type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  436. });
  437. const url = URL.createObjectURL(blob);
  438. const link = document.createElement('a');
  439. link.href = url;
  440. link.download = `评价账号表_${new Date().toISOString().slice(0, 10)}.xlsx`;
  441. link.style.visibility = 'hidden';
  442. document.body.appendChild(link);
  443. link.click();
  444. document.body.removeChild(link);
  445. URL.revokeObjectURL(url);
  446. };
  447. onMounted(async ()=>{
  448. let reviewAccountMsg = sessionStorage.getItem('reviewAccountMsg')
  449. // 如果有缓存,说明是从生成评价码失败页面跳过来的
  450. if(reviewAccountMsg){
  451. let objMsg = JSON.parse(reviewAccountMsg || '{}')
  452. originReviewAccount.value = objMsg.originReviewAccount
  453. batchEditGradeList.value = objMsg.batchEditGradeList
  454. tableColumns.value = objMsg.tableColumns
  455. tableData.value = objMsg.tableData
  456. sessionStorage.removeItem('reviewAccountMsg') //获取完后清空数据
  457. }else{
  458. await GetReviewAccountList()
  459. }
  460. })
  461. </script>
  462. <style lang="scss" scoped>
  463. .grade_form_item{
  464. display: flex;
  465. flex-direction: column;
  466. gap:26px;
  467. .grade_name{
  468. font-weight: 600;
  469. font-size: 16px;
  470. color: #333333;
  471. margin-bottom: 10px;
  472. }
  473. .class_list .el-checkbox{
  474. font-weight: 400;
  475. font-size: 14px;
  476. color: #666666;
  477. }
  478. }
  479. .group_batch_num,
  480. .batch_num{
  481. display: flex;
  482. align-items: center;
  483. gap:20px;
  484. word-break: keep-all;
  485. > span{
  486. display: flex;
  487. align-items: center;
  488. word-break: keep-all;
  489. }
  490. }
  491. </style>