groupAnalysis.vue 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782
  1. <template>
  2. <ReportModule
  3. :showTitle="true"
  4. :titleList="[state.groupTitle]"
  5. :showDescribe="true"
  6. tableOrChart="chart"
  7. :showPrintBtn="false"
  8. :showExportBtn="false"
  9. >
  10. <template #title_right>
  11. <EchartType
  12. :chartTypeList="state.problemAnalysisData.chartTypeList"
  13. :current="state.problemAnalysisData.chartType"
  14. @ChangeEchartType="
  15. (val: string) => ChangeEchartType(val, 'problemAnalysisData')
  16. "
  17. />
  18. </template>
  19. <template #module_table_chart>
  20. <template v-if="state.problemAnalysisData.data.length > 0">
  21. <BarLineCharts
  22. v-if="state.problemAnalysisData.chartType == 'line_bar_chart'"
  23. :legendList="state.problemAnalysisData.legendList"
  24. :showBarLegendIndex="state.problemAnalysisData.showBarLegendIndex"
  25. title="得分率"
  26. :data="state.problemAnalysisData.data"
  27. @HandleChartClick="
  28. (index: any, name: string) => HandleChartClick(index, name)
  29. "
  30. @ChangeChartOrder="
  31. (sortType: string, legendData: any, barIndex: any) =>
  32. ChangeChartOrder(sortType, legendData, barIndex, 1)
  33. "
  34. >
  35. </BarLineCharts>
  36. <BarsCharts
  37. v-if="state.problemAnalysisData.chartType == 'vertical_bar'"
  38. :key="state.chartKey"
  39. :data="state.problemAnalysisData.data"
  40. :legendList="state.problemAnalysisData.legendList"
  41. :showSortSelectbox="true"
  42. unit="%"
  43. title="得分率"
  44. :isClick="true"
  45. @HandleChartClick="
  46. (index: any, name: string) => HandleChartClick(index, name)
  47. "
  48. @ChangeChartOrder="
  49. (sortType: string, legendData: any, barIndex: any) =>
  50. ChangeChartOrder(sortType, legendData, barIndex, 1)
  51. "
  52. >
  53. </BarsCharts>
  54. <RadarCharts
  55. v-if="state.problemAnalysisData.chartType == 'radar_chart'"
  56. :key="state.chartKey"
  57. :data="state.problemAnalysisData.data"
  58. :legendList="state.problemAnalysisData.legendList"
  59. :showCheckBox="true"
  60. :openShowAllLegend="true"
  61. :isClick="true"
  62. @HandleChartClick="
  63. (index: any, name: string) => HandleChartClick(index, name)
  64. "
  65. >
  66. </RadarCharts>
  67. </template>
  68. <div
  69. v-else
  70. class="no_content_data"
  71. v-loading="state.dataLoading"
  72. :element-loading-text="state.loadingText"
  73. :element-loading-spinner="loadingSvgIcon"
  74. element-loading-background="#ffffff"
  75. >
  76. <span>暂无数据</span>
  77. </div>
  78. </template>
  79. <template #module_describe>
  80. 展示每道试题的得分率图。得分率指实际得分/考核分的比值,换算成的百分数。可以用于分析每道试题的难易程度和质量,试题得分率高意味着试题难度低或者学生整体水平高;得分率低意味着试题难度较高或者考生整体水平较低。点击每道试题的柱或雷达图的题号可在下方查看该题每个班的得分率情况。
  81. </template>
  82. </ReportModule>
  83. <ReportModule
  84. :showTitle="true"
  85. :titleList="[state.groupTitle, state.groupName]"
  86. :showDescribe="true"
  87. tableOrChart="chart"
  88. :showPrintBtn="false"
  89. :showExportBtn="false"
  90. >
  91. <template #title_right>
  92. <EchartType
  93. :chartTypeList="state.majorQuestionData.chartTypeList"
  94. :current="state.majorQuestionData.chartType"
  95. @ChangeEchartType="
  96. (val: string) => ChangeEchartType(val, 'majorQuestionData')
  97. "
  98. />
  99. </template>
  100. <template #module_table_chart>
  101. <template v-if="state.majorQuestionData.data.length > 0">
  102. <BarLineCharts
  103. ref="majorQuestionCharts"
  104. v-if="state.majorQuestionData.chartType == 'line_bar_chart'"
  105. :legendList="state.majorQuestionData.legendList"
  106. :showBarLegendIndex="state.majorQuestionData.showBarLegendIndex"
  107. title="得分率"
  108. :data="state.majorQuestionData.data"
  109. @HandleChartClick="
  110. (index: any, name: string) => HandleQuestionChartClick(index, name)
  111. "
  112. @ChangeChartOrder="
  113. (sortType: string, legendData: any, barIndex: any) =>
  114. ChangeChartOrder(sortType, legendData, barIndex, 2)
  115. "
  116. >
  117. </BarLineCharts>
  118. <BarsCharts
  119. ref="majorQuestionCharts"
  120. :key="state.chartKey"
  121. v-if="state.majorQuestionData.chartType == 'vertical_bar'"
  122. :data="state.majorQuestionData.data"
  123. :showSortSelectbox="true"
  124. :legendList="state.majorQuestionData.legendList"
  125. unit="%"
  126. title="得分率"
  127. :isClick="true"
  128. @HandleChartClick="
  129. (index: any, name: string) => HandleQuestionChartClick(index, name)
  130. "
  131. @ChangeChartOrder="
  132. (sortType: string, legendData: any, barIndex: any) =>
  133. ChangeChartOrder(sortType, legendData, barIndex, 2)
  134. "
  135. ></BarsCharts>
  136. <RadarCharts
  137. v-if="state.majorQuestionData.chartType == 'radar_chart'"
  138. :key="state.chartKey"
  139. :data="state.majorQuestionData.data"
  140. :legendList="state.majorQuestionData.legendList"
  141. :showCheckBox="true"
  142. :openShowAllLegend="true"
  143. :isClick="true"
  144. @HandleChartClick="
  145. (index: any, name: string) => HandleQuestionChartClick(index, name)
  146. "
  147. >
  148. </RadarCharts>
  149. </template>
  150. <div
  151. v-else
  152. class="no_content_data"
  153. v-loading="state.dataLoading"
  154. :element-loading-text="state.loadingText"
  155. :element-loading-spinner="loadingSvgIcon"
  156. element-loading-background="#ffffff"
  157. >
  158. <span>暂无数据</span>
  159. </div>
  160. </template>
  161. <template #module_describe>
  162. 展示每道试题的得分率图。得分率指实际得分/考核分的比值,换算成的百分数。可以用于分析每道试题的难易程度和质量,试题得分率高意味着试题难度低或者学生整体水平高;得分率低意味着试题难度较高或者考生整体水平较低。点击每道试题的柱或雷达图的题号可在下方查看该题每个班的得分率情况。
  163. </template>
  164. </ReportModule>
  165. <ReportModule
  166. :showTitle="true"
  167. :titleList="[state.groupTitle, state.questionTitle]"
  168. :showDescribe="true"
  169. tableOrChart="chart"
  170. :showPrintBtn="false"
  171. :showExportBtn="false"
  172. >
  173. <template #title_right>
  174. <EchartType
  175. :chartTypeList="state.questionScoreStatsData.chartTypeList"
  176. :current="state.questionScoreStatsData.chartType"
  177. @ChangeEchartType="
  178. (val: string) => ChangeEchartType(val, 'questionScoreStatsData')
  179. "
  180. />
  181. </template>
  182. <template #module_table_chart>
  183. <template v-if="state.questionScoreStatsData.datax.length > 0">
  184. <BarScoringRateVertical
  185. v-if="state.questionScoreStatsData.chartType == 'vertical_bar'"
  186. :datax="state.questionScoreStatsData.datax"
  187. :datay="state.questionScoreStatsData.dataStackY"
  188. :tooltipData="state.questionScoreStatsData.tooltipData"
  189. :isShowMarkLine="true"
  190. :average="state.questionScoreStatsData.rate"
  191. :markLineData="state.questionScoreStatsData.markLineData"
  192. :color="['#3BA272', '#EE6666']"
  193. typeName="得分率"
  194. :isClick="true"
  195. @HandleChartClick="
  196. (index: number) => HandleQuestionScoreChartClick(index)
  197. "
  198. ></BarScoringRateVertical>
  199. <DifferenceChart
  200. v-if="state.questionScoreStatsData.chartType == 'difference_chart'"
  201. :datax="state.questionScoreStatsData.datax"
  202. :datay="state.questionScoreStatsData.datay"
  203. unit="%"
  204. type="1"
  205. title="得分率"
  206. :rate="state.questionScoreStatsData.rate"
  207. @HandleChartClick="
  208. (index: number) => HandleQuestionScoreChartClick(index)
  209. "
  210. >
  211. </DifferenceChart>
  212. </template>
  213. <div
  214. v-else
  215. class="no_content_data"
  216. v-loading="state.dataLoading"
  217. :element-loading-text="state.loadingText"
  218. :element-loading-spinner="loadingSvgIcon"
  219. element-loading-background="#ffffff"
  220. >
  221. <span>暂无数据</span>
  222. </div>
  223. </template>
  224. <template #module_describe>
  225. 说明:通过上面图形,可查看该题每个班的得分率情况,由图中可以看出,得分率最高为<span
  226. style="color: #3ba272"
  227. >{{ state.questionScoreStatsData.maxClass }}</span
  228. >,最低为<span style="color: #ee6666">{{
  229. state.questionScoreStatsData.minClass
  230. }}</span
  231. >。点击每个班级的柱可在下方查看该班各个选项/得分的人数分布。
  232. </template>
  233. </ReportModule>
  234. <ReportModule
  235. :showTitle="true"
  236. :titleList="[state.groupTitle, state.questionTitle, state.classTitle]"
  237. :showDescribe="true"
  238. tableOrChart="qita"
  239. :showPrintBtn="false"
  240. :showExportBtn="false"
  241. >
  242. <template #module_qita>
  243. <div class="content_left answer">
  244. <BarChart
  245. v-if="state.questionAnswerData.datax.length"
  246. :datax="state.questionAnswerData.datax"
  247. :datay="state.questionAnswerData.datay"
  248. typeName="人数"
  249. :showNuitY="false"
  250. unit="人"
  251. :unitX="
  252. state.questionAnswerData.questionType == '单选题' ||
  253. state.questionAnswerData.questionType == '多选题' ||
  254. state.questionAnswerData.questionType == '判断题'
  255. ? ''
  256. : '分'
  257. "
  258. :showMarkPoint="false"
  259. :isShowMarkLine="false"
  260. :color="state.questionAnswerData.color"
  261. :answerValue="state.questionAnswerData.answerValue"
  262. :answerScore="state.questionAnswerData.answerScore"
  263. :fullMark="state.questionAnswerData.fullMark"
  264. :isClick="true"
  265. @HandleChartClick="
  266. (index: number, name: string) =>
  267. HandleQuestionAnswerChartClick(index, name)
  268. "
  269. style="height: 300px"
  270. ></BarChart>
  271. <div
  272. v-else
  273. class="module_chart no_content_data"
  274. v-loading="state.dataLoading"
  275. :element-loading-text="state.loadingText"
  276. :element-loading-spinner="loadingSvgIcon"
  277. element-loading-background="#ffffff"
  278. >
  279. <span>暂无数据</span>
  280. </div>
  281. </div>
  282. <div class="content_right answer table_42">
  283. <el-table
  284. border
  285. :data="state.questionAnswerData.tableData"
  286. stripe
  287. align="left"
  288. :header-row-style="HeaderRowStyle"
  289. >
  290. <el-table-column :label="state.classTitle" align="center">
  291. <el-table-column
  292. prop="title"
  293. align="center"
  294. label="名称"
  295. ></el-table-column>
  296. <el-table-column
  297. prop="value"
  298. align="center"
  299. label="数值"
  300. ></el-table-column>
  301. </el-table-column>
  302. </el-table>
  303. </div>
  304. </template>
  305. <template #module_describe>
  306. 说明:通过柱图,可查看该题下该班所有学生的选项或试题得分情况。点击每个柱可查看该选项或分数的学生明细和答题卡答题情况。
  307. </template>
  308. </ReportModule>
  309. <ReportModule
  310. :showTitle="true"
  311. :titleList="[
  312. state.groupTitle,
  313. state.questionTitle,
  314. state.classTitle,
  315. state.optionTitle,
  316. ]"
  317. :showDescribe="true"
  318. tableOrChart="qita"
  319. :showPrintBtn="false"
  320. :showExportBtn="false"
  321. >
  322. <template #title_right>
  323. <el-button
  324. class="default_button"
  325. v-if="
  326. state.questionAnswerData.questionType != '单选题' &&
  327. state.questionAnswerData.questionType != '多选题' &&
  328. state.questionAnswerData.questionType != '判断题'
  329. "
  330. @click="VisibleQuestionCard"
  331. >
  332. <img src="@/assets/icon/card_view.webp" />批量查看
  333. </el-button>
  334. </template>
  335. <template #module_qita>
  336. <div class="content_left paper_card">
  337. <StudentQuestionImg :paperInfo="state.paperInfos"></StudentQuestionImg>
  338. </div>
  339. <div class="content_right paper_card table_42">
  340. <el-table
  341. border
  342. :data="state.majorAnswerData.tableData"
  343. stripe
  344. highlight-current-row
  345. align="left"
  346. height="401px"
  347. row-key="studentRegistrationCode"
  348. :row-style="{ cursor: 'pointer' }"
  349. :row-class-name="TableRowClassName"
  350. @row-click="HandleRowClick"
  351. >
  352. <el-table-column
  353. prop="studentUserName"
  354. align="center"
  355. label="学生"
  356. show-overflow-tooltip
  357. ></el-table-column>
  358. <el-table-column
  359. label="班级"
  360. prop="className"
  361. align="center"
  362. min-width="70"
  363. show-overflow-tooltip
  364. ></el-table-column>
  365. <el-table-column
  366. prop="questionScore"
  367. align="center"
  368. label="得分"
  369. width="70"
  370. show-overflow-tooltip
  371. ></el-table-column>
  372. <el-table-column
  373. prop="paperScore"
  374. align="center"
  375. label="成绩"
  376. width="70"
  377. show-overflow-tooltip
  378. ></el-table-column>
  379. <el-table-column align="center" label="操作">
  380. <template #default="scope">
  381. <el-button text @click.stop="handleClick(scope.row as any)"
  382. >查看答题卡</el-button
  383. >
  384. </template>
  385. </el-table-column>
  386. </el-table>
  387. </div>
  388. </template>
  389. <template #module_describe>
  390. 说明:点击左侧表格列可切换学生查看具体学生答题图片,点击学生姓名可在下方查看学生均衡分析,点击学生成绩可查看学生答题卡原卷。
  391. </template>
  392. </ReportModule>
  393. <ReportModule
  394. ref="reportModuleRef"
  395. :showTitle="true"
  396. :titleList="[state.groupTitle]"
  397. :showDescribe="true"
  398. tableOrChart="table"
  399. :showPrintBtn="false"
  400. :showExportBtn="true"
  401. :currentPage="state.majorTableData.currentPage"
  402. :pageSize="state.majorTableData.pageSize"
  403. :total="state.majorTableData.total"
  404. @ChangePageSize="ChangePageSize"
  405. @ChangeCurrentPage="ChangeCurrentPage"
  406. @ExportExcel="ExportExcel"
  407. >
  408. <template #title_right>
  409. <SetTableHeader
  410. :data="{
  411. static: state.majorTableData.headerData,
  412. dynamisc: state.majorTableData.changeHeaderData,
  413. child: state.majorTableData.childHeaderData,
  414. }"
  415. :isShowDynamisc="false"
  416. :notEditable="['questionName']"
  417. @SetEnterHeaderData="SetEnterHeaderData"
  418. ></SetTableHeader>
  419. </template>
  420. <template #module_table_chart>
  421. <el-table
  422. :data="state.majorTableData.tableData"
  423. ref="majorTable"
  424. border
  425. stripe
  426. align="left"
  427. :key="state.majorTableData.tableKey"
  428. >
  429. <template v-for="item in state.majorTableData.headerData">
  430. <el-table-column
  431. v-if="item.display"
  432. :key="item.prop"
  433. align="center"
  434. :prop="item.prop"
  435. :min-width="item.label.length > 4 ? 110 : 90"
  436. :label="item.label"
  437. fixed="left"
  438. show-overflow-tooltip
  439. >
  440. <template #default="scope">
  441. {{ scope.row[item.prop] || "-"
  442. }}{{
  443. item.prop == "estimatedScoreRate" && scope.row[item.prop]
  444. ? "%"
  445. : ""
  446. }}
  447. </template>
  448. </el-table-column>
  449. </template>
  450. <template v-for="parent in state.majorTableData.changeHeaderData">
  451. <el-table-column
  452. v-if="parent.display"
  453. align="center"
  454. :prop="parent.prop"
  455. :label="parent.label"
  456. :key="parent.prop"
  457. >
  458. <template v-for="item in state.majorTableData.childHeaderData">
  459. <el-table-column
  460. v-if="item.display"
  461. :prop="`${parent.prop}_${item.prop}`"
  462. :key="`${parent.prop}_${item.prop}`"
  463. align="center"
  464. :label="item.label"
  465. :min-width="item.label.length > 4 ? 100 : 80"
  466. show-overflow-tooltip
  467. >
  468. <template #default="scope">
  469. <span v-if="item.prop.indexOf('Rate') > -1">{{
  470. scope.row[`${parent.prop}_${item.prop}`]
  471. ? `${scope.row[`${parent.prop}_${item.prop}`]}%`
  472. : "-"
  473. }}</span>
  474. <span
  475. v-else
  476. @click="OpenStudentDialog(item, parent, scope.row)"
  477. :class="{
  478. table_row_blue:
  479. (item.prop.indexOf('Count') > -1 ||
  480. item.prop.indexOf('Number') > -1) &&
  481. scope.row?.[`${parent.prop}_${item.prop}`] > 0,
  482. }"
  483. >{{
  484. scope.row?.[`${parent.prop}_${item.prop}`] ?? "-"
  485. }}</span
  486. >
  487. </template>
  488. </el-table-column>
  489. </template>
  490. </el-table-column>
  491. </template>
  492. </el-table>
  493. </template>
  494. </ReportModule>
  495. <!-- 批量查看小题答题卡 -->
  496. <QuestionCard
  497. :subjectId="analysisStore.filterObject.subjectId"
  498. :questionId="state.cardQuestionId"
  499. :platformNumbers="state.cardRegistrationCodeList"
  500. :groupTitle="state.groupTitle"
  501. :groupName="state.groupName"
  502. :questionTitle="state.questionTitle"
  503. :classTitle="state.classTitle"
  504. :optionTitle="state.optionTitle"
  505. :showDialog="state.showQuestionCardDialog"
  506. @CloseDialog="CloseQuestionCardDialog"
  507. ></QuestionCard>
  508. <!-- 学生答题卡组件 -->
  509. <StudentPaper
  510. :modelValue="state.showStudentPaperDialog"
  511. :paperInfo="state.paperInfo"
  512. :pageTitle="state.paperTitle"
  513. @updateModelValue="UpdateModelValue"
  514. ></StudentPaper>
  515. <StudentList
  516. :showDialog.sync="state.dialogData.showDialog"
  517. :title="state.dialogData.title"
  518. :tableTitle="state.dialogData.tableTitle"
  519. :apiName="state.dialogData.apiName"
  520. :studentParam="{
  521. fiveRateName: state.dialogData.fiveRateName,
  522. selectSubjectName: state.dialogData.selectSubjectName,
  523. selectSchoolLevel: state.dialogData.selectSchoolLevel,
  524. selectSchoolName: state.dialogData.selectSchoolName,
  525. selectClassLevel: state.dialogData.selectClassLevel,
  526. selectClassName: state.dialogData.selectClassName,
  527. questionId: state.dialogData.questionId,
  528. isPaper: state.dialogData.isPaper,
  529. quesScoreType: state.dialogData.quesScoreType,
  530. knowledgeId: state.dialogData.knowledgeId,
  531. questionGroupName: state.dialogData.questionGroupName,
  532. groupTitle: state.dialogData.groupTitle,
  533. }"
  534. @CloseDialog="CloseDialog"
  535. >
  536. </StudentList>
  537. </template>
  538. <script lang="ts" setup>
  539. import ReportModule from "@/components/ReportModule.vue";
  540. import EchartType from "@/components/EchartType.vue";
  541. import QuestionCard from "@/components/QuestionCard.vue";
  542. import StudentPaper from "@/components/StudentPaper.vue";
  543. import { useAnalysisStore } from "@/store/analysis";
  544. import {
  545. questionGroupAnalysis,
  546. queryAnswerListByAnswerAndScore,
  547. publicExport,
  548. } from "@/api/analysis";
  549. import BarLineCharts from "@/components/echarts/barLineCharts.vue";
  550. import RadarCharts from "@/components/echarts/radarCharts.vue";
  551. import BarsCharts from "@/components/echarts/barsCharts.vue";
  552. import BarScoringRateVertical from "@/components/echarts/barScoringRate_vertical.vue";
  553. import DifferenceChart from "@/components/echarts/differenceChart.vue";
  554. import BarChart from "@/components/echarts/barChart_answer.vue";
  555. import StudentQuestionImg from "@/components/StudentQuestionImg.vue";
  556. import SetTableHeader from "@/components/SetTableHeader.vue";
  557. import StudentList from "@/components/StudentList.vue";
  558. import { downloadExcel, GetExcelFileName } from "@/utils/exportExcel";
  559. import { loadingSvgIcon } from "@/utils/common";
  560. import { onMounted, reactive, watch, ref, computed, nextTick } from "vue";
  561. import { cloneDeep } from "lodash-es";
  562. // ================= TS 类型定义 =================
  563. interface ChartTypeItem {
  564. label: string;
  565. value: string;
  566. }
  567. interface HeaderItem {
  568. prop: string;
  569. label: string;
  570. display?: boolean;
  571. type?: number;
  572. }
  573. interface AnswerItem {
  574. name: string;
  575. studentNum: number;
  576. score: number;
  577. registrationCodeList?: string[];
  578. }
  579. interface QuestionStats {
  580. scoreRate: number;
  581. lossRate: number;
  582. averageScore: number;
  583. maxScore: number;
  584. minScore: number;
  585. answerList: AnswerItem[];
  586. headDataBOList?: Array<{ name: string; rate: number; studentNumber: number }>;
  587. [key: string]: any;
  588. }
  589. interface ClassItem {
  590. groupName: string;
  591. groupId: string | number;
  592. questionStats: QuestionStats;
  593. [key: string]: any;
  594. }
  595. interface QuestionItem {
  596. questionName: string;
  597. questionId: string | number;
  598. showCode?: number;
  599. answerValue: string;
  600. questionType: string;
  601. fullMark: string | number;
  602. classList: ClassItem[];
  603. knowledgeId?: string | number;
  604. groupName?: string;
  605. }
  606. interface GroupItem {
  607. groupName: string;
  608. groupCode: number;
  609. groupId: string | number;
  610. questionList: QuestionItem[];
  611. classList: ClassItem[];
  612. questionId?: string | number;
  613. knowledgeId?: string | number;
  614. [key: string]: any;
  615. }
  616. interface MarkLineDataItem {
  617. legendName: string;
  618. value: number;
  619. isShow: boolean;
  620. }
  621. interface ApiResponse {
  622. code: number;
  623. data?: {
  624. groupList: GroupItem[];
  625. headerList: HeaderItem[];
  626. changeHeaderList: HeaderItem[];
  627. childHeaderList: HeaderItem[];
  628. };
  629. }
  630. interface ExportParams {
  631. fileName: string;
  632. examName: string;
  633. sheetName: string;
  634. staticHeaderData: HeaderItem[];
  635. childHeaderData: HeaderItem[];
  636. dynamicsHeaderData: HeaderItem[];
  637. dataList: any[][];
  638. }
  639. interface State {
  640. groupTitle: string;
  641. groupName: string;
  642. questionTitle: string;
  643. classTitle: string;
  644. optionTitle: string;
  645. questionTypesData: {
  646. chartKey: number;
  647. refresh: boolean;
  648. data: any[];
  649. tableData: any[];
  650. };
  651. problemAnalysisData: {
  652. chartType: string;
  653. chartTypeList: ChartTypeItem[];
  654. legendList: string[];
  655. defaultLegendList: string[];
  656. showBarLegendIndex: number;
  657. questionList: QuestionItem[];
  658. questionTableList: any[];
  659. groupList: GroupItem[];
  660. groupTableList: GroupItem[];
  661. headerList: HeaderItem[];
  662. changeHeaderList: HeaderItem[];
  663. childHeaderList: HeaderItem[];
  664. questionListIndex: number;
  665. data: any[];
  666. };
  667. majorQuestionData: {
  668. chartType: string;
  669. chartTypeList: ChartTypeItem[];
  670. legendList: string[];
  671. defaultLegendList: string[];
  672. showBarLegendIndex: number;
  673. questionListIndex: number;
  674. data: any[];
  675. };
  676. questionScoreStatsData: {
  677. checked: boolean;
  678. isIndeterminate: boolean;
  679. markLineData: MarkLineDataItem[];
  680. chartTypeList: ChartTypeItem[];
  681. chartType: string;
  682. datax: string[];
  683. datay: number[];
  684. dataStackY: any[];
  685. tooltipData: any[];
  686. rate: number;
  687. maxClass: string;
  688. minClass: string;
  689. };
  690. questionAnswerData: {
  691. datax: string[];
  692. datay: number[];
  693. color: string;
  694. answerValue: string;
  695. answerScore: number[];
  696. questionType: string;
  697. fullMark: string | number;
  698. tableData: any[];
  699. classListIndex: number;
  700. };
  701. majorAnswerData: { paperUrl: string; tableData: any[]; rowIndex: number };
  702. majorStudentData: {
  703. tableData: any[];
  704. legendList: any[];
  705. radarChartData: any[];
  706. excellentCourseList: string;
  707. inferiorCourseList: string;
  708. };
  709. majorTableData: {
  710. tableKey: number;
  711. tableData: any[];
  712. allTableData: any[];
  713. headerData: HeaderItem[];
  714. changeHeaderData: HeaderItem[];
  715. childHeaderData: HeaderItem[];
  716. pageSize: number;
  717. total: number;
  718. currentPage: number;
  719. };
  720. chartKey: number;
  721. dataLoading: boolean;
  722. loadingText: string;
  723. paperInfo: {
  724. examPaperId: string | number;
  725. platformNumber: string;
  726. questionId: string | number;
  727. };
  728. paperTitle: string;
  729. showStudentPaperDialog: boolean;
  730. paperInfos: {
  731. examPaperId: string | number;
  732. platformNumber: string;
  733. questionId: string | number;
  734. };
  735. dialogData: {
  736. apiName: string;
  737. showDialog: boolean;
  738. title: string;
  739. tableTitle: string;
  740. fiveRateName: string;
  741. selectSubjectName: string;
  742. selectSchoolLevel: string | number;
  743. selectSchoolName: string;
  744. selectClassLevel: string | number;
  745. selectClassName: string;
  746. questionId: string | number;
  747. isPaper: string | number;
  748. quesScoreType: string | number;
  749. knowledgeId: string | number;
  750. questionGroupName: string;
  751. groupTitle: string;
  752. };
  753. showQuestionCardDialog: boolean;
  754. cardQuestionId: string | number;
  755. cardRegistrationCodeList: string[];
  756. }
  757. // =============================================
  758. const analysisStore = useAnalysisStore();
  759. const reportModuleRef = ref<InstanceType<typeof ReportModule> | null>(null);
  760. const getExamName = computed(
  761. () => analysisStore.analysisExamInfo?.examName || "",
  762. );
  763. const state = reactive<State>({
  764. groupTitle: "分组分析",
  765. groupName: "",
  766. questionTitle: "",
  767. classTitle: "",
  768. optionTitle: "",
  769. questionTypesData: { chartKey: 0, refresh: false, data: [], tableData: [] },
  770. problemAnalysisData: {
  771. chartType: "line_bar_chart",
  772. chartTypeList: [
  773. { label: "组合图", value: "line_bar_chart" },
  774. { label: "柱状图", value: "vertical_bar" },
  775. { label: "雷达图", value: "radar_chart" },
  776. ],
  777. legendList: [],
  778. defaultLegendList: [],
  779. showBarLegendIndex: 1,
  780. questionList: [],
  781. questionTableList: [],
  782. groupList: [],
  783. groupTableList: [],
  784. headerList: [],
  785. changeHeaderList: [],
  786. childHeaderList: [],
  787. questionListIndex: 0,
  788. data: [],
  789. },
  790. majorQuestionData: {
  791. chartType: "line_bar_chart",
  792. chartTypeList: [
  793. { label: "组合图", value: "line_bar_chart" },
  794. { label: "柱状图", value: "vertical_bar" },
  795. { label: "雷达图", value: "radar_chart" },
  796. ],
  797. legendList: [],
  798. defaultLegendList: [],
  799. showBarLegendIndex: 1,
  800. questionListIndex: 0,
  801. data: [],
  802. },
  803. questionScoreStatsData: {
  804. checked: false,
  805. isIndeterminate: true,
  806. markLineData: [],
  807. chartTypeList: [
  808. { label: "柱状图", value: "vertical_bar" },
  809. { label: "率差图", value: "difference_chart" },
  810. ],
  811. chartType: "vertical_bar",
  812. datax: [],
  813. datay: [],
  814. dataStackY: [],
  815. tooltipData: [],
  816. rate: 60,
  817. maxClass: "",
  818. minClass: "",
  819. },
  820. questionAnswerData: {
  821. datax: [],
  822. datay: [],
  823. color: "#5470C6",
  824. answerValue: "",
  825. answerScore: [],
  826. questionType: "",
  827. fullMark: "",
  828. tableData: [],
  829. classListIndex: 0,
  830. },
  831. majorAnswerData: { paperUrl: "", tableData: [], rowIndex: 0 },
  832. majorStudentData: {
  833. tableData: [],
  834. legendList: [],
  835. radarChartData: [],
  836. excellentCourseList: "",
  837. inferiorCourseList: "",
  838. },
  839. majorTableData: {
  840. tableKey: 0,
  841. tableData: [],
  842. allTableData: [],
  843. headerData: [],
  844. changeHeaderData: [],
  845. childHeaderData: [],
  846. pageSize: 10,
  847. total: 0,
  848. currentPage: 1,
  849. },
  850. chartKey: 0,
  851. dataLoading: false,
  852. loadingText: "加载中,请稍后...",
  853. paperInfo: { examPaperId: "", platformNumber: "", questionId: "" },
  854. paperTitle: "",
  855. showStudentPaperDialog: false,
  856. paperInfos: { examPaperId: "", platformNumber: "", questionId: "" },
  857. dialogData: {
  858. apiName: "questionGroupStudentInfo",
  859. showDialog: false,
  860. title: "",
  861. tableTitle: "",
  862. fiveRateName: "",
  863. selectSubjectName: "",
  864. selectSchoolLevel: "",
  865. selectSchoolName: "",
  866. selectClassLevel: "",
  867. selectClassName: "",
  868. questionId: "",
  869. isPaper: "",
  870. quesScoreType: "",
  871. knowledgeId: "",
  872. questionGroupName: "",
  873. groupTitle: "",
  874. },
  875. showQuestionCardDialog: false,
  876. cardQuestionId: "",
  877. cardRegistrationCodeList: [],
  878. });
  879. const majorQuestionCharts = ref<any>(null);
  880. const majorTable = ref<InstanceType<
  881. (typeof import("element-plus"))["ElTable"]
  882. > | null>(null);
  883. // 排序
  884. const ChangeChartOrder = (
  885. sortType: string,
  886. legendData: string[],
  887. barIndex: number | string,
  888. number: number,
  889. ) => {
  890. if (number == 1) {
  891. const fullVolume = state.problemAnalysisData.groupList.filter(
  892. (item) => item.groupCode == 999,
  893. );
  894. const groupList = state.problemAnalysisData.groupList.filter(
  895. (item) => item.groupCode != 999,
  896. );
  897. if (sortType == "2") {
  898. groupList.sort(
  899. (a, b) =>
  900. (a?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0) -
  901. (b?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0),
  902. );
  903. } else if (sortType == "3") {
  904. groupList.sort(
  905. (a, b) =>
  906. (b?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0) -
  907. (a?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0),
  908. );
  909. } else {
  910. groupList.sort((a, b) => (a?.groupCode || 0) - (b.groupCode || 0));
  911. }
  912. state.problemAnalysisData.groupList = [...groupList, ...fullVolume];
  913. state.problemAnalysisData.data = [];
  914. state.problemAnalysisData.groupList.forEach((group) => {
  915. const scoreRateArr =
  916. group.classList?.map((item) => item?.questionStats?.scoreRate ?? 0) ||
  917. [];
  918. state.problemAnalysisData.data.push([group.groupName, ...scoreRateArr]);
  919. });
  920. let chartTitle = ["group"];
  921. state.problemAnalysisData.changeHeaderList.forEach((item) => {
  922. chartTitle.push(item.label);
  923. });
  924. state.problemAnalysisData.data.unshift(chartTitle);
  925. state.problemAnalysisData.data.pop();
  926. state.problemAnalysisData.legendList = legendData;
  927. if (state.problemAnalysisData.groupList.length > 0) {
  928. GetClassQuestionData(0, state.problemAnalysisData.groupList[0].groupName);
  929. }
  930. } else {
  931. const fullVolume = state.problemAnalysisData.questionList.filter(
  932. (item) => item.showCode == 999,
  933. );
  934. const questionList = state.problemAnalysisData.questionList.filter(
  935. (item) => item.showCode != 999,
  936. );
  937. if (sortType == "2") {
  938. questionList.sort(
  939. (a, b) =>
  940. (a?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0) -
  941. (b?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0),
  942. );
  943. } else if (sortType == "3") {
  944. questionList.sort(
  945. (a, b) =>
  946. (b?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0) -
  947. (a?.classList?.[Number(barIndex)]?.questionStats?.scoreRate || 0),
  948. );
  949. } else {
  950. questionList.sort((a, b) => (a?.showCode || 0) - (b.showCode || 0));
  951. }
  952. state.problemAnalysisData.questionList = [...questionList, ...fullVolume];
  953. state.majorQuestionData.data = [];
  954. state.problemAnalysisData.questionList.forEach((question) => {
  955. const scoreRateArr =
  956. question.classList?.map(
  957. (item) => item?.questionStats?.scoreRate || 0,
  958. ) || [];
  959. state.majorQuestionData.data.push([
  960. question.questionName,
  961. ...scoreRateArr,
  962. ]);
  963. });
  964. let chartTitle: string[] = [];
  965. state.problemAnalysisData.changeHeaderList.forEach((item) => {
  966. chartTitle.push(item.label);
  967. });
  968. chartTitle.unshift("group");
  969. state.majorQuestionData.legendList = legendData;
  970. state.majorQuestionData.data.unshift(chartTitle);
  971. GetQuestionStatsData(0);
  972. }
  973. };
  974. // 试题图表切换公共方法
  975. type ChartDataProp =
  976. | "problemAnalysisData"
  977. | "majorQuestionData"
  978. | "questionScoreStatsData";
  979. const ChangeEchartType = (value: string, prop: ChartDataProp) => {
  980. if (prop == "problemAnalysisData") {
  981. ChangeChartOrder("1", state.problemAnalysisData.defaultLegendList, "", 1);
  982. } else if (prop == "majorQuestionData") {
  983. ChangeChartOrder("1", state.majorQuestionData.defaultLegendList, "", 2);
  984. }
  985. state[prop].chartType = value;
  986. };
  987. // 题目分组分析
  988. const GetQuestionGroupAnalysisData = () => {
  989. state.problemAnalysisData.data = [];
  990. state.dataLoading = true;
  991. questionGroupAnalysis({
  992. ...analysisStore.filterObject,
  993. analysisType: 4,
  994. })
  995. .then((res: ApiResponse) => {
  996. state.problemAnalysisData.data = [];
  997. if (res.code == 200 && res.data && res.data.groupList.length) {
  998. const { data } = res;
  999. const groupList = data.groupList || [];
  1000. const changeHeaderList = data.changeHeaderList || [];
  1001. state.problemAnalysisData.groupList = groupList;
  1002. state.problemAnalysisData.groupTableList = cloneDeep(groupList);
  1003. state.problemAnalysisData.headerList = data.headerList || [];
  1004. state.problemAnalysisData.changeHeaderList = changeHeaderList;
  1005. state.problemAnalysisData.childHeaderList = data.childHeaderList || [];
  1006. state.chartKey++;
  1007. state.problemAnalysisData.data = groupList.map((item) => [
  1008. item.groupName,
  1009. ]);
  1010. groupList.forEach((group, key) => {
  1011. const scoreRateArr =
  1012. group.classList?.map(
  1013. (item) => item?.questionStats?.scoreRate ?? 0,
  1014. ) || [];
  1015. state.problemAnalysisData.data[key].push(...scoreRateArr);
  1016. });
  1017. let chartTitle: string[] = [];
  1018. let titleType: (string | number)[] = [];
  1019. let classSelectLegend: string[] = [];
  1020. changeHeaderList.forEach((item) => {
  1021. chartTitle.push(item.label);
  1022. titleType.push(item.type ? item.type : "");
  1023. if (item.prop != "0" && item.prop.indexOf("school_group") == -1) {
  1024. classSelectLegend.push(item.label);
  1025. }
  1026. });
  1027. chartTitle.unshift("group");
  1028. if (analysisStore.filterObject.classLevel != 2) {
  1029. let startIndex = 1,
  1030. endIndex = 3;
  1031. if (analysisStore.filterObject.schoolLevel == 2) {
  1032. startIndex =
  1033. titleType.lastIndexOf(1) > -1 ? titleType.lastIndexOf(1) + 1 : 1;
  1034. endIndex = titleType.indexOf(2) > -1 ? titleType.indexOf(2) + 2 : 3;
  1035. } else {
  1036. const barFirtIndex =
  1037. titleType.lastIndexOf(1) > -1 ? titleType.lastIndexOf(1) + 1 : 1;
  1038. startIndex = barFirtIndex;
  1039. endIndex = barFirtIndex + 2;
  1040. }
  1041. state.problemAnalysisData.legendList =
  1042. chartTitle.length > 1 ? chartTitle.slice(startIndex, endIndex) : [];
  1043. state.problemAnalysisData.showBarLegendIndex = endIndex - 2;
  1044. } else {
  1045. state.problemAnalysisData.legendList = classSelectLegend;
  1046. state.problemAnalysisData.showBarLegendIndex =
  1047. changeHeaderList.length - 1;
  1048. }
  1049. state.problemAnalysisData.defaultLegendList = cloneDeep(
  1050. state.problemAnalysisData.legendList,
  1051. );
  1052. state.problemAnalysisData.data.unshift(chartTitle);
  1053. state.problemAnalysisData.data.pop();
  1054. if (groupList.length > 0) {
  1055. GetClassQuestionData(0, groupList[0].groupName);
  1056. }
  1057. GetMajorTableData();
  1058. } else {
  1059. // 重置所有数据
  1060. state.questionTypesData.data = [];
  1061. state.questionTypesData.tableData = [];
  1062. state.problemAnalysisData.showBarLegendIndex = 1;
  1063. state.problemAnalysisData.groupList = [];
  1064. state.problemAnalysisData.questionList = [];
  1065. state.problemAnalysisData.headerList = [];
  1066. state.problemAnalysisData.changeHeaderList = [];
  1067. state.problemAnalysisData.childHeaderList = [];
  1068. state.majorQuestionData.data = [];
  1069. state.majorQuestionData.legendList = [];
  1070. state.questionScoreStatsData.datax = [];
  1071. state.questionScoreStatsData.datay = [];
  1072. state.questionScoreStatsData.dataStackY = [];
  1073. state.questionScoreStatsData.tooltipData = [];
  1074. state.questionScoreStatsData.rate = 0;
  1075. state.questionScoreStatsData.maxClass = "";
  1076. state.questionScoreStatsData.minClass = "";
  1077. state.questionAnswerData.datax = [];
  1078. state.questionAnswerData.datay = [];
  1079. state.questionAnswerData.answerValue = "";
  1080. state.questionAnswerData.answerScore = [];
  1081. state.questionAnswerData.questionType = "";
  1082. state.questionAnswerData.fullMark = "";
  1083. state.questionAnswerData.tableData = [];
  1084. state.questionAnswerData.classListIndex = 0;
  1085. state.majorAnswerData.tableData = [];
  1086. state.majorAnswerData.paperUrl = "";
  1087. state.majorAnswerData.rowIndex = 0;
  1088. state.majorStudentData.tableData = [];
  1089. state.majorStudentData.legendList = [];
  1090. state.majorStudentData.radarChartData = [];
  1091. state.majorStudentData.excellentCourseList = "";
  1092. state.majorStudentData.inferiorCourseList = "";
  1093. state.majorTableData.tableData = [];
  1094. state.majorTableData.headerData = [];
  1095. state.majorTableData.changeHeaderData = [];
  1096. state.majorTableData.childHeaderData = [];
  1097. state.majorTableData.pageSize = 10;
  1098. state.majorTableData.total = 0;
  1099. state.majorTableData.currentPage = 1;
  1100. }
  1101. })
  1102. .finally(() => {
  1103. state.dataLoading = false;
  1104. });
  1105. };
  1106. // 点击柱状图折线图
  1107. const HandleChartClick = (index: number | string, name: string) => {
  1108. majorQuestionCharts.value?.ChangeSelectValue?.();
  1109. GetClassQuestionData(Number(index), name);
  1110. };
  1111. // 大题分析 /阅读表达 点击柱状图
  1112. const HandleQuestionChartClick = (index: number | string, name: string) => {
  1113. GetQuestionStatsData(Number(index));
  1114. };
  1115. // 获取小题分析 /第N题
  1116. const GetQuestionStatsData = (index: number) => {
  1117. const questionData = cloneDeep(state.problemAnalysisData.questionList[index]);
  1118. state.problemAnalysisData.questionListIndex = index;
  1119. state.questionTitle = questionData?.questionName || "";
  1120. if (questionData && questionData.classList) {
  1121. state.questionScoreStatsData.datax = [];
  1122. state.questionScoreStatsData.datay = [];
  1123. state.questionScoreStatsData.dataStackY = [];
  1124. state.questionScoreStatsData.tooltipData = [];
  1125. let dataStackY: number[] = [];
  1126. const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
  1127. (item) => item.prop == "estimatedScore",
  1128. );
  1129. if (questionData.classList.length > 1) {
  1130. const key = state.problemAnalysisData.showBarLegendIndex;
  1131. state.questionScoreStatsData.markLineData = [];
  1132. const classList = questionData.classList || [];
  1133. if (
  1134. analysisStore.filterObject.classLevel == 0 ||
  1135. analysisStore.filterObject.classLevel == 1
  1136. ) {
  1137. state.questionScoreStatsData.rate = Number(
  1138. classList?.[key - 1]?.questionStats?.scoreRate || 0,
  1139. );
  1140. } else {
  1141. state.questionScoreStatsData.rate = Number(
  1142. classList?.[key]?.questionStats?.scoreRate || 0,
  1143. );
  1144. }
  1145. classList.forEach((item: ClassItem, idx: number) => {
  1146. const keyIndex =
  1147. analysisStore.filterObject.classLevel == 0 ||
  1148. analysisStore.filterObject.classLevel == 1
  1149. ? key - (isHasEstimatedScore ? 2 : 1)
  1150. : key;
  1151. if (idx <= keyIndex) {
  1152. const rate = Number(item?.questionStats?.scoreRate || 0);
  1153. state.questionScoreStatsData.markLineData.push({
  1154. legendName: item.groupName,
  1155. value: rate,
  1156. isShow: idx == keyIndex,
  1157. });
  1158. }
  1159. });
  1160. questionData.classList = questionData.classList.slice(
  1161. isHasEstimatedScore ? key - 1 : key,
  1162. );
  1163. } else {
  1164. state.questionScoreStatsData.rate = 60;
  1165. }
  1166. questionData.classList.forEach((item) => {
  1167. state.questionScoreStatsData.datax.push(item.groupName);
  1168. state.questionScoreStatsData.datay.push(
  1169. item.questionStats?.scoreRate || 0,
  1170. );
  1171. dataStackY.push(item.questionStats?.lossRate || 0);
  1172. state.questionScoreStatsData.tooltipData.push({
  1173. list: [
  1174. { name: "失分率", value: `${item.questionStats?.lossRate || 0}%` },
  1175. ],
  1176. });
  1177. });
  1178. const datay = state.questionScoreStatsData.datay;
  1179. const max = datay.length > 0 ? Math.max(...datay) : 0;
  1180. const min = datay.length > 0 ? Math.min(...datay) : 0;
  1181. const maxValue = max.toFixed(2);
  1182. const minValue = min.toFixed(2);
  1183. let maxClass: string[] = [];
  1184. let minClass: string[] = [];
  1185. state.questionScoreStatsData.datay.forEach((item, idx) => {
  1186. if (Number(item).toFixed(2) == maxValue)
  1187. maxClass.push(state.questionScoreStatsData.datax[idx]);
  1188. if (Number(item).toFixed(2) == minValue)
  1189. minClass.push(state.questionScoreStatsData.datax[idx]);
  1190. });
  1191. state.questionScoreStatsData.minClass = minClass.join("、");
  1192. state.questionScoreStatsData.maxClass = maxClass.join("、");
  1193. state.questionScoreStatsData.dataStackY = [
  1194. state.questionScoreStatsData.datay,
  1195. dataStackY,
  1196. ];
  1197. }
  1198. GetQuestionAnswerData(state.problemAnalysisData.showBarLegendIndex);
  1199. };
  1200. // 大题分析 /阅读表达
  1201. const GetClassQuestionData = (index: number, name: string) => {
  1202. state.majorQuestionData.data = [];
  1203. state.groupName = name;
  1204. const groupItem = state.problemAnalysisData.groupList[index];
  1205. if (!groupItem) return;
  1206. const questionList = groupItem.questionList || [];
  1207. state.problemAnalysisData.questionList = questionList;
  1208. state.majorQuestionData.data = questionList.map((item) => [
  1209. item.questionName,
  1210. ]);
  1211. questionList.forEach((question, key) => {
  1212. const scoreRateArr =
  1213. question.classList?.map((item) => item?.questionStats?.scoreRate || 0) ||
  1214. [];
  1215. state.majorQuestionData.data[key].push(...scoreRateArr);
  1216. });
  1217. let chartTitle: string[] = [];
  1218. let titleType: (string | number)[] = [];
  1219. let classSelectLegend: string[] = [];
  1220. state.problemAnalysisData.changeHeaderList.forEach((item) => {
  1221. chartTitle.push(item.label);
  1222. titleType.push(item.type ? item.type : "");
  1223. if (item.prop != "0" && item.prop.indexOf("school_group") == -1) {
  1224. classSelectLegend.push(item.label);
  1225. }
  1226. });
  1227. chartTitle.unshift("group");
  1228. if (analysisStore.filterObject.classLevel != 2) {
  1229. let startIndex = 1,
  1230. endIndex = 3;
  1231. if (analysisStore.filterObject.schoolLevel == 2) {
  1232. startIndex =
  1233. titleType.lastIndexOf(1) > -1 ? titleType.lastIndexOf(1) + 1 : 1;
  1234. endIndex = titleType.indexOf(2) > -1 ? titleType.indexOf(2) + 2 : 3;
  1235. } else {
  1236. const barFirtIndex =
  1237. titleType.lastIndexOf(1) > -1 ? titleType.lastIndexOf(1) + 1 : 1;
  1238. startIndex = barFirtIndex;
  1239. endIndex = barFirtIndex + 2;
  1240. }
  1241. state.majorQuestionData.legendList =
  1242. chartTitle.length > 1 ? chartTitle.slice(startIndex, endIndex) : [];
  1243. state.majorQuestionData.showBarLegendIndex = endIndex - 2;
  1244. } else {
  1245. state.majorQuestionData.legendList = classSelectLegend;
  1246. }
  1247. state.majorQuestionData.defaultLegendList = cloneDeep(
  1248. state.majorQuestionData.legendList,
  1249. );
  1250. state.majorQuestionData.data.unshift(chartTitle);
  1251. GetQuestionStatsData(0);
  1252. };
  1253. // 切换 获取小题分析 /第N题 获取第N班答题列表
  1254. const HandleQuestionScoreChartClick = (index: number) => {
  1255. GetQuestionAnswerData(index + state.problemAnalysisData.showBarLegendIndex);
  1256. };
  1257. // 获取小题分析 /第N题 / 第N班 选项
  1258. const GetQuestionAnswerData = (index: number) => {
  1259. const isHasEstimatedScore = state.problemAnalysisData.headerList.find(
  1260. (item) => item.prop == "estimatedScore",
  1261. );
  1262. const newINdex = isHasEstimatedScore ? index - 1 : index;
  1263. const question =
  1264. state.problemAnalysisData.questionList[
  1265. state.problemAnalysisData.questionListIndex
  1266. ];
  1267. if (!question) return;
  1268. const classList = question.classList?.[newINdex];
  1269. state.questionAnswerData.answerValue = question.answerValue;
  1270. state.questionAnswerData.questionType = question.questionType;
  1271. state.questionAnswerData.fullMark = question.fullMark;
  1272. state.questionAnswerData.classListIndex = newINdex;
  1273. if (classList && classList.questionStats) {
  1274. const answerList = classList.questionStats.answerList || [];
  1275. state.classTitle = classList.groupName;
  1276. state.questionAnswerData.datax = [];
  1277. state.questionAnswerData.datay = [];
  1278. let sum = 0;
  1279. answerList.forEach((item) => {
  1280. sum += item.studentNum;
  1281. state.questionAnswerData.datax.push(item.name);
  1282. state.questionAnswerData.datay.push(item.studentNum);
  1283. state.questionAnswerData.answerScore.push(item.score);
  1284. });
  1285. state.questionAnswerData.tableData = [
  1286. { title: "得分率", value: `${classList.questionStats.scoreRate}%` },
  1287. { title: "平均分", value: `${classList.questionStats.averageScore}` },
  1288. { title: "最高分", value: `${classList.questionStats.maxScore}` },
  1289. { title: "最低分", value: `${classList.questionStats.minScore}` },
  1290. { title: "人数", value: `${sum}人` },
  1291. ];
  1292. if (analysisStore.filterObject.schoolLevel == 2) {
  1293. GetAnswerListByAnswerAndScore(
  1294. 0,
  1295. answerList.length > 0 ? answerList[0].name : "",
  1296. );
  1297. }
  1298. }
  1299. };
  1300. // 点击柱状图小题分析 /第N题 / 第N班 选项 获取答题情况
  1301. const HandleQuestionAnswerChartClick = (index: number, name: string) => {
  1302. if (analysisStore.filterObject.schoolLevel == 2) {
  1303. GetAnswerListByAnswerAndScore(index, name);
  1304. }
  1305. };
  1306. // 通过答案或者分数查询某题作答情况
  1307. const GetAnswerListByAnswerAndScore = (index: number, name: string) => {
  1308. state.optionTitle = name;
  1309. const question =
  1310. state.problemAnalysisData.questionList[
  1311. state.problemAnalysisData.questionListIndex
  1312. ];
  1313. if (!question) return;
  1314. const classItem =
  1315. question.classList?.[state.questionAnswerData.classListIndex];
  1316. if (!classItem) return;
  1317. const classItemKeys = Object.keys(classItem);
  1318. const reportParam: any = { ...analysisStore.filterObject };
  1319. Object.keys(analysisStore.filterObject).forEach((item) => {
  1320. if (classItemKeys.indexOf(item) > -1) {
  1321. reportParam[item] = classItem[item];
  1322. }
  1323. });
  1324. const answer = classItem.questionStats?.answerList?.[index];
  1325. const params = {
  1326. ...reportParam,
  1327. questionId: question.questionId,
  1328. registrationCodeList: answer?.registrationCodeList || [],
  1329. };
  1330. state.cardQuestionId = params.questionId;
  1331. state.cardRegistrationCodeList = params.registrationCodeList;
  1332. queryAnswerListByAnswerAndScore(params).then((res: any) => {
  1333. if (res.code == 200) {
  1334. state.majorAnswerData.tableData = res.data || [];
  1335. HandleRowClick(
  1336. state.majorAnswerData.tableData.length > 0
  1337. ? state.majorAnswerData.tableData[0]
  1338. : {},
  1339. );
  1340. } else {
  1341. state.majorAnswerData.tableData = [];
  1342. }
  1343. });
  1344. };
  1345. // 点击某行学生某题作答情况
  1346. const HandleRowClick = (row: any) => {
  1347. if (row?.studentRegistrationCode) {
  1348. const question =
  1349. state.problemAnalysisData.questionList[
  1350. state.problemAnalysisData.questionListIndex
  1351. ];
  1352. if (question) {
  1353. state.paperInfos = {
  1354. examPaperId: analysisStore.filterObject.subjectId,
  1355. platformNumber: row.studentRegistrationCode,
  1356. questionId: question.questionId,
  1357. };
  1358. }
  1359. state.majorAnswerData.rowIndex = state.majorAnswerData.tableData.findIndex(
  1360. (item) => item.studentRegistrationCode == row?.studentRegistrationCode,
  1361. );
  1362. } else {
  1363. state.paperInfos = { examPaperId: "", platformNumber: "", questionId: "" };
  1364. }
  1365. };
  1366. // 获取项目分析表
  1367. const GetMajorTableData = () => {
  1368. state.majorTableData.tableKey += 1;
  1369. state.majorTableData.headerData = state.problemAnalysisData.headerList;
  1370. state.majorTableData.changeHeaderData =
  1371. state.problemAnalysisData.changeHeaderList;
  1372. state.majorTableData.childHeaderData =
  1373. state.problemAnalysisData.childHeaderList;
  1374. const headerPropData = state.problemAnalysisData.headerList.map(
  1375. (item) => item.prop,
  1376. );
  1377. const childHeaderPropData = state.problemAnalysisData.childHeaderList.map(
  1378. (item) => item.prop,
  1379. );
  1380. let allTableData: any[] = [];
  1381. const allList = state.problemAnalysisData.groupTableList || [];
  1382. allList.forEach((item) => {
  1383. let itemObj: any = {
  1384. questionId: item?.questionId || "",
  1385. knowledgeId: item?.knowledgeId || "",
  1386. };
  1387. const classList = item.classList || [];
  1388. headerPropData.forEach((title) => {
  1389. itemObj[title] = Array.isArray(item[title])
  1390. ? item[title].join("、")
  1391. : item[title];
  1392. });
  1393. classList.forEach((el) => {
  1394. if (
  1395. el.questionStats?.headDataBOList &&
  1396. el.questionStats.headDataBOList.length > 0
  1397. ) {
  1398. el.questionStats.headDataBOList.forEach((bo) => {
  1399. el.questionStats[`${bo.name}Rate`] = bo.rate;
  1400. el.questionStats[`${bo.name}StudentNumber`] = bo.studentNumber;
  1401. });
  1402. } else {
  1403. el.questionStats = el.questionStats || {};
  1404. }
  1405. childHeaderPropData.forEach((field) => {
  1406. itemObj[`${el.groupId}_${field}`] = el.questionStats[field];
  1407. });
  1408. });
  1409. allTableData.push(itemObj);
  1410. });
  1411. state.majorTableData.total = allTableData.length;
  1412. state.majorTableData.allTableData = allTableData;
  1413. GetPageMajorTableData();
  1414. ResetTableScroll();
  1415. };
  1416. const GetPageMajorTableData = () => {
  1417. const start =
  1418. (state.majorTableData.currentPage - 1) * state.majorTableData.pageSize;
  1419. const end = start + state.majorTableData.pageSize;
  1420. state.majorTableData.tableData = state.majorTableData.allTableData.slice(
  1421. start,
  1422. end,
  1423. );
  1424. };
  1425. // 重置表格滚动条位置
  1426. const ResetTableScroll = () => {
  1427. nextTick(() => {
  1428. if (majorTable.value) {
  1429. const tableBody = majorTable.value.$el?.querySelector(
  1430. ".el-table__body-wrapper",
  1431. );
  1432. if (tableBody) {
  1433. tableBody.scrollTop = 0;
  1434. tableBody.scrollLeft = 0;
  1435. }
  1436. }
  1437. });
  1438. };
  1439. // 设置表头字段显示隐藏
  1440. const SetEnterHeaderData = (data: any) => {
  1441. if (data.static && data.static.length > 0)
  1442. state.problemAnalysisData.headerList = [...data.static];
  1443. if (data.dynamisc && data.dynamisc.length > 0)
  1444. state.problemAnalysisData.changeHeaderList = [...data.dynamisc];
  1445. if (data.child && data.child.length > 0)
  1446. state.problemAnalysisData.childHeaderList = [...data.child];
  1447. GetMajorTableData();
  1448. };
  1449. // 人员弹框
  1450. const OpenStudentDialog = (
  1451. childHeader: HeaderItem,
  1452. parent: HeaderItem,
  1453. row: any,
  1454. ) => {
  1455. if (
  1456. (childHeader.prop.indexOf("Count") > -1 ||
  1457. childHeader.prop.indexOf("Number") > -1) &&
  1458. row?.[`${parent.prop}_${childHeader.prop}`] > 0
  1459. ) {
  1460. state.dialogData.showDialog = true;
  1461. state.dialogData.title = childHeader.label;
  1462. state.dialogData.tableTitle = parent.label;
  1463. state.dialogData.fiveRateName = "";
  1464. const fiveRateProps = [
  1465. "topStudentNumber",
  1466. "excellentStudentNumber",
  1467. "goodStudentNumber",
  1468. "passStudentNumber",
  1469. "lowStudentNumber",
  1470. ];
  1471. if (fiveRateProps.includes(childHeader.prop)) {
  1472. state.dialogData.fiveRateName = childHeader.label.replace(/人数/g, "");
  1473. }
  1474. state.dialogData.selectSubjectName = analysisStore.filterObject.subjectName;
  1475. if (parent.prop == "0") {
  1476. state.dialogData.selectSchoolLevel = 0;
  1477. state.dialogData.selectSchoolName = parent.label;
  1478. state.dialogData.selectClassLevel = 0;
  1479. state.dialogData.selectClassName = "年级";
  1480. } else if (parent.prop.indexOf("school_group") > -1) {
  1481. state.dialogData.selectSchoolLevel = 1;
  1482. state.dialogData.selectSchoolName = parent.label;
  1483. state.dialogData.selectClassLevel = 0;
  1484. state.dialogData.selectClassName = "年级";
  1485. } else if (
  1486. parent.prop.indexOf("school") > -1 &&
  1487. parent.prop.indexOf("school_group") == -1
  1488. ) {
  1489. state.dialogData.selectSchoolLevel = 2;
  1490. state.dialogData.selectSchoolName = parent.label;
  1491. state.dialogData.selectClassLevel = 0;
  1492. state.dialogData.selectClassName = "年级";
  1493. } else if (parent.prop.indexOf("class_group") > -1) {
  1494. state.dialogData.selectSchoolLevel = 2;
  1495. state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName;
  1496. state.dialogData.selectClassLevel = 1;
  1497. state.dialogData.selectClassName = parent.label;
  1498. } else {
  1499. state.dialogData.selectSchoolLevel = 2;
  1500. state.dialogData.selectSchoolName = analysisStore.filterObject.schoolName;
  1501. state.dialogData.selectClassLevel = 2;
  1502. state.dialogData.selectClassName = parent.label;
  1503. }
  1504. state.dialogData.questionId = row.questionId;
  1505. state.dialogData.isPaper = row.questionName == "全卷" ? 1 : 0;
  1506. if (childHeader.prop == "fullMarkCount") state.dialogData.quesScoreType = 0;
  1507. else if (childHeader.prop == "zeroMarkCount")
  1508. state.dialogData.quesScoreType = 1;
  1509. else state.dialogData.quesScoreType = 3;
  1510. state.dialogData.knowledgeId = row.knowledgeId;
  1511. state.dialogData.questionGroupName = row?.groupName || "";
  1512. state.dialogData.groupTitle = state.groupTitle.replace(/分析/g, "");
  1513. }
  1514. };
  1515. // 关闭人员弹框
  1516. const CloseDialog = (val: boolean) => {
  1517. state.dialogData.showDialog = val;
  1518. };
  1519. // 分页获取分析表
  1520. const ChangeCurrentPage = (val: number) => {
  1521. state.majorTableData.currentPage = val;
  1522. GetMajorTableData();
  1523. };
  1524. const ChangePageSize = (val: number) => {
  1525. state.majorTableData.pageSize = val;
  1526. state.majorTableData.currentPage = 1;
  1527. GetMajorTableData();
  1528. };
  1529. // 导出Excel
  1530. const ExportExcel = () => {
  1531. reportModuleRef.value?.SetExportLoading?.(true);
  1532. const examName = getExamName.value;
  1533. let params: ExportParams = {
  1534. fileName: `${GetExcelFileName(examName, analysisStore.filterObject, state.groupTitle)}`,
  1535. examName: examName,
  1536. sheetName: state.groupTitle,
  1537. staticHeaderData: [],
  1538. childHeaderData: [],
  1539. dynamicsHeaderData: [],
  1540. dataList: [],
  1541. };
  1542. params.staticHeaderData = state.problemAnalysisData.headerList.filter(
  1543. (item) => item.display,
  1544. );
  1545. params.childHeaderData = state.problemAnalysisData.childHeaderList.filter(
  1546. (item) => item.display,
  1547. );
  1548. params.dynamicsHeaderData = state.problemAnalysisData.changeHeaderList.filter(
  1549. (item) => item.display,
  1550. );
  1551. let dataList: any[][] = [];
  1552. state.majorTableData.allTableData.forEach((item) => {
  1553. const rowData: any[] = [];
  1554. params.staticHeaderData.forEach((header) => {
  1555. rowData.push(item?.[header.prop] || "-");
  1556. });
  1557. params.dynamicsHeaderData.forEach((parent) => {
  1558. params.childHeaderData.forEach((child) => {
  1559. const itemValue = item[`${parent.prop}_${child.prop}`];
  1560. if (child.prop.indexOf("Rate") > -1) {
  1561. rowData.push(itemValue ? `${itemValue}%` : "-");
  1562. } else {
  1563. rowData.push(itemValue ?? "-");
  1564. }
  1565. });
  1566. });
  1567. dataList.push(rowData);
  1568. });
  1569. params.dataList = dataList;
  1570. downloadExcel(publicExport, params).finally(() => {
  1571. reportModuleRef.value?.SetExportLoading?.(false);
  1572. });
  1573. };
  1574. // 设置表头样式
  1575. const HeaderRowStyle = ({ rowIndex }: { row: any; rowIndex: number }) => {
  1576. if (rowIndex === 1) return { display: "none" };
  1577. return {};
  1578. };
  1579. const TableRowClassName = ({ rowIndex }: { row: any; rowIndex: number }) => {
  1580. if (rowIndex === state.majorAnswerData.rowIndex) return "current-row";
  1581. return "";
  1582. };
  1583. // 查看答题卡
  1584. const handleClick = (row: any) => {
  1585. state.paperInfo = {
  1586. examPaperId: analysisStore.filterObject.subjectId,
  1587. platformNumber: row.studentRegistrationCode,
  1588. questionId: "",
  1589. };
  1590. state.paperTitle = `${getExamName.value}-${analysisStore.filterObject.subjectName}-${row.className}-${row.studentUserName}`;
  1591. state.showStudentPaperDialog = true;
  1592. };
  1593. // 更新弹窗状态
  1594. const UpdateModelValue = (val: boolean) => {
  1595. state.showStudentPaperDialog = val;
  1596. };
  1597. // 批量查看小题答题卡
  1598. const VisibleQuestionCard = () => {
  1599. state.showQuestionCardDialog = true;
  1600. };
  1601. // 关闭弹框
  1602. const CloseQuestionCardDialog = () => {
  1603. state.showQuestionCardDialog = false;
  1604. };
  1605. const pageInit = () => {
  1606. const chartTypeLists = [
  1607. { label: "组合图", value: "line_bar_chart" },
  1608. { label: "柱状图", value: "vertical_bar" },
  1609. { label: "雷达图", value: "radar_chart" },
  1610. ];
  1611. const chartTypeList = [
  1612. { label: "柱状图", value: "vertical_bar" },
  1613. { label: "雷达图", value: "radar_chart" },
  1614. ];
  1615. const isNotSingleClass = analysisStore.filterObject.classLevel != 2;
  1616. state.problemAnalysisData.chartTypeList = isNotSingleClass
  1617. ? chartTypeLists
  1618. : chartTypeList;
  1619. state.problemAnalysisData.chartType = isNotSingleClass
  1620. ? "line_bar_chart"
  1621. : "vertical_bar";
  1622. state.majorQuestionData.chartTypeList = isNotSingleClass
  1623. ? chartTypeLists
  1624. : chartTypeList;
  1625. state.majorQuestionData.chartType = isNotSingleClass
  1626. ? "line_bar_chart"
  1627. : "vertical_bar";
  1628. state.questionScoreStatsData.chartType = "vertical_bar";
  1629. state.majorTableData.currentPage = 1;
  1630. state.majorAnswerData.rowIndex = 0;
  1631. GetQuestionGroupAnalysisData();
  1632. };
  1633. // 监听筛选条件
  1634. watch(
  1635. () => analysisStore.filterObject,
  1636. () => {
  1637. pageInit();
  1638. },
  1639. { deep: true },
  1640. );
  1641. onMounted(() => {
  1642. pageInit();
  1643. });
  1644. </script>
  1645. <style lang="scss" scoped>
  1646. .content_left {
  1647. &.answer {
  1648. width: calc(100% - 220px);
  1649. }
  1650. &.paper_card {
  1651. width: calc(100% - 480px);
  1652. height: 400px;
  1653. background-color: #f5f5f5;
  1654. border-radius: 10px;
  1655. }
  1656. }
  1657. .content_right {
  1658. height: 100%;
  1659. display: flex;
  1660. align-items: center;
  1661. margin: auto 0 auto auto;
  1662. padding-bottom: 0;
  1663. &.answer {
  1664. width: 200px;
  1665. }
  1666. &.paper_card {
  1667. width: 460px;
  1668. }
  1669. :deep(.el-table) {
  1670. border-left: 0px;
  1671. border-right: 0px;
  1672. .el-table__cell {
  1673. height: 44px;
  1674. }
  1675. }
  1676. }
  1677. </style>