TotalProgress.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <template>
  2. <div class="p-base p-b-unset m-b-base radius-base fill-blank total-progress-box">
  3. <base-form size="small" :model="model" :items="items"></base-form>
  4. <div class="flex total-progress-info">
  5. <div class="flex direction-column full-h p-r-base table-info">
  6. <div class="p-t-base p-b-extra-small table-title">整体进度</div>
  7. <div class="flex-1 overflow-hidden">
  8. <base-table
  9. size="small"
  10. height="100%"
  11. highlight-current-row
  12. :columns="totalColumns"
  13. :data="totalProgressData"
  14. @current-change="onCurrentChange"
  15. >
  16. <template #empty>暂无数据</template>
  17. </base-table>
  18. </div>
  19. </div>
  20. <div class="flex-1 p-t-small p-b-extra-small overflow-hidden full-h chart-info">
  21. <vue-e-charts class="full" :option="totalChartsOption" autoresize></vue-e-charts>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="p-base radius-base fill-blank flex group-progress-box">
  26. <div class="flex direction-column full-h p-r-base table-info">
  27. <div class="flex justify-between">
  28. <span class="table-title">小组进度</span>
  29. <span v-show="currentMainName" class="flex items-center current-main-name">
  30. <svg-icon class="m-r-medium-mini" name="question"></svg-icon>
  31. <span>{{ currentMainName }}</span>
  32. </span>
  33. </div>
  34. <div class="flex-1 overflow-hidden">
  35. <base-table size="small" height="100%" :columns="groupColumns" :data="groupProgressData">
  36. <template #empty>暂无数据</template>
  37. </base-table>
  38. </div>
  39. </div>
  40. <div class="flex-1 full-h chart-info">
  41. <vue-e-charts v-if="currentMainName" class="full" :option="groupChartsOption" autoresize></vue-e-charts>
  42. </div>
  43. </div>
  44. </template>
  45. <script setup lang="ts" name="TotalProgress">
  46. /** 评卷进度 */
  47. import { reactive, watch, computed, ref } from 'vue'
  48. import VueECharts from 'vue-echarts'
  49. import { minus } from '@/utils/common'
  50. import BaseForm from '@/components/element/BaseForm.vue'
  51. import BaseTable from '@/components/element/BaseTable.vue'
  52. import SvgIcon from '@/components/common/SvgIcon.vue'
  53. import useForm from '@/hooks/useForm'
  54. import useFetch from '@/hooks/useFetch'
  55. import useOptions from '@/hooks/useOptions'
  56. import useVueECharts from '@/hooks/useVueECharts'
  57. import { usePX } from '@/hooks/useVW'
  58. import type { EChartsOption } from 'echarts'
  59. import type { ExtractApiResponse } from '@/api/api'
  60. import type { EpFormItem, EpTableColumn } from 'global-type'
  61. const { provideInitOption } = useVueECharts()
  62. provideInitOption({ renderer: 'svg' })
  63. const model = reactive({
  64. subjectCode: '',
  65. })
  66. const { subjectList, dataModel, changeModelValue } = useOptions(['subject'])
  67. watch(
  68. dataModel,
  69. () => {
  70. model.subjectCode = dataModel.subject || ''
  71. },
  72. { deep: true }
  73. )
  74. const { defineColumn } = useForm()
  75. const items = computed<EpFormItem[]>(() => [
  76. defineColumn(
  77. {
  78. label: '科目',
  79. slotType: 'select',
  80. prop: 'subjectCode',
  81. slot: { options: subjectList.value, onChange: changeModelValue('subject') },
  82. },
  83. '',
  84. { span: 5 }
  85. ),
  86. ])
  87. /** 整体进度 */
  88. type TotalProgress = ExtractArrayValue<ExtractApiResponse<'getMarkProgress'>>
  89. const getMainName = (row?: TotalProgress) => {
  90. const { questionMainName: name, questionMainNumber: number } = row || {}
  91. return [number, name].filter(Boolean).join('-')
  92. }
  93. const totalColumns: EpTableColumn<TotalProgress>[] = [
  94. {
  95. label: '大题',
  96. formatter: getMainName,
  97. },
  98. { label: '试卷总量', prop: 'totalPaper', align: 'center', width: usePX(100) },
  99. { label: '已完成', prop: 'finishCount', align: 'center', width: usePX(100) },
  100. {
  101. label: '完成比',
  102. align: 'center',
  103. width: usePX(72),
  104. formatter(row) {
  105. return `${row.finishRate}%`
  106. },
  107. },
  108. {
  109. label: '待完成',
  110. align: 'center',
  111. width: usePX(100),
  112. formatter(row) {
  113. return `${minus(row.totalPaper, row.finishCount)}`
  114. },
  115. },
  116. {
  117. label: '待完成比',
  118. align: 'center',
  119. width: usePX(72),
  120. formatter(row) {
  121. return `${minus(100, row.finishRate)}%`
  122. },
  123. },
  124. {
  125. label: '预计耗时(分)',
  126. align: 'center',
  127. width: usePX(100),
  128. formatter(row) {
  129. return `${parseFloat((row.takeTime / 60).toFixed(2))}`
  130. },
  131. },
  132. ]
  133. const { fetch: getMarkProgress, result: markProgressResult } = useFetch('getMarkProgress')
  134. /** 将汇总行放到数组最后 */
  135. const totalProgressData = computed(() => {
  136. if (!markProgressResult?.value) return []
  137. const arr = markProgressResult.value.slice(0)
  138. const totalIndex = arr.findIndex((v) => v.questionMainNumber === 0)
  139. const [total] = arr.splice(totalIndex, 1)
  140. arr.push(total)
  141. return arr
  142. })
  143. watch(
  144. () => model.subjectCode,
  145. (v) => {
  146. v && getMarkProgress(model)
  147. },
  148. { immediate: true }
  149. )
  150. const currentMainQuestion = ref<TotalProgress>()
  151. const currentMainName = computed(() => {
  152. return getMainName(currentMainQuestion.value)
  153. })
  154. const onCurrentChange = (row: TotalProgress) => {
  155. if (row.questionMainNumber !== 0) {
  156. currentMainQuestion.value = row
  157. }
  158. }
  159. const getYAxisData = (field: keyof TotalProgress | 'name', data?: TotalProgress[]) => {
  160. if (!data) {
  161. return []
  162. }
  163. return data.map((v) => {
  164. if (field === 'name') {
  165. return getMainName(v)
  166. }
  167. return v[field]
  168. })
  169. }
  170. const totalChartsOption = computed<EChartsOption>(() => {
  171. return {
  172. grid: {
  173. top: 20,
  174. bottom: -20,
  175. left: 20,
  176. right: 30,
  177. containLabel: true,
  178. },
  179. legend: {
  180. right: 0,
  181. itemWidth: 14,
  182. data: ['试卷总量', '已完成', '完成比'],
  183. },
  184. yAxis: {
  185. axisLine: { show: false },
  186. axisTick: { show: false },
  187. splitLine: { show: false },
  188. inverse: true,
  189. axisLabel: {
  190. align: 'right',
  191. verticalAlign: 'bottom',
  192. },
  193. data: getYAxisData('name', markProgressResult?.value),
  194. },
  195. xAxis: [
  196. {
  197. position: 'top',
  198. type: 'value',
  199. splitLine: { show: true },
  200. },
  201. {
  202. position: 'bottom',
  203. type: 'value',
  204. show: false,
  205. axisLabel: {
  206. formatter: `{value}%`,
  207. },
  208. splitLine: { show: false },
  209. },
  210. ],
  211. series: [
  212. {
  213. name: '试卷总量',
  214. type: 'bar',
  215. barWidth: 11,
  216. barGap: '-200%',
  217. itemStyle: {
  218. color: '#3AD500',
  219. },
  220. data: getYAxisData('totalPaper', markProgressResult?.value),
  221. },
  222. {
  223. name: '已完成',
  224. type: 'bar',
  225. barWidth: 11,
  226. barGap: '-200%',
  227. itemStyle: {
  228. color: '#0064FF',
  229. },
  230. data: getYAxisData('finishCount', markProgressResult?.value),
  231. },
  232. {
  233. name: '完成比',
  234. type: 'bar',
  235. barWidth: 44,
  236. barGap: '-200%',
  237. showBackground: true,
  238. xAxisIndex: 1,
  239. itemStyle: {
  240. color: 'rgba(0, 186, 151,0.3)',
  241. },
  242. label: {
  243. show: true,
  244. color: '#444',
  245. fontSize: 10,
  246. formatter({ value }) {
  247. return value > 0 ? `${value}%` : ''
  248. },
  249. position: 'insideTopRight',
  250. },
  251. data: getYAxisData('finishRate', markProgressResult?.value),
  252. },
  253. ],
  254. }
  255. })
  256. /** 小组进度 */
  257. type GroupProgress = ExtractArrayValue<ExtractApiResponse<'getMarkProgressByGroup'>>
  258. const groupColumns: EpTableColumn<GroupProgress>[] = [
  259. { label: '小组', formatter: (row) => (row.markingGroupNumber ? `第${row.markingGroupNumber}组` : '全体') },
  260. { label: '完成总量', prop: 'finishCount', align: 'center' },
  261. {
  262. label: '完成比',
  263. prop: 'finishRate',
  264. align: 'center',
  265. formatter(row) {
  266. return `${row.finishRate}%`
  267. },
  268. },
  269. { label: '当日已完成', prop: 'dayFinishCount', align: 'center' },
  270. {
  271. label: '当日完成比',
  272. prop: 'dayFinishRate',
  273. align: 'center',
  274. formatter(row) {
  275. return `${row.dayFinishRate}%`
  276. },
  277. },
  278. ]
  279. const { fetch: getMarkProgressByGroup, result: groupProgressResult } = useFetch('getMarkProgressByGroup')
  280. /** 将汇总行放到数组最后 */
  281. const groupProgressData = computed(() => {
  282. if (!groupProgressResult?.value) return []
  283. const arr = groupProgressResult.value.slice(0)
  284. const totalIndex = arr.findIndex((v) => v.markingGroupNumber === 0)
  285. const [total] = arr.splice(totalIndex, 1)
  286. arr.push(total)
  287. return arr
  288. })
  289. watch(
  290. currentMainQuestion,
  291. (v) => {
  292. v?.questionMainNumber &&
  293. getMarkProgressByGroup({ subjectCode: model.subjectCode, questionMainNumber: v.questionMainNumber })
  294. },
  295. { immediate: true }
  296. )
  297. const getXAxisData = (field: keyof GroupProgress, data?: GroupProgress[]) => {
  298. if (!data) {
  299. return []
  300. }
  301. const getValue = (data: GroupProgress, v: keyof GroupProgress) => {
  302. if (v === 'markingGroupNumber') {
  303. return `第${data[v]}组`
  304. }
  305. return data[v]
  306. }
  307. return data.filter((v) => v.markingGroupNumber !== 0).map((v) => getValue(v, field))
  308. }
  309. const groupChartsOption = computed<EChartsOption>(() => {
  310. return {
  311. legend: {
  312. right: 0,
  313. itemWidth: 14,
  314. data: ['完成总量', '当日已完成', '完成比'],
  315. },
  316. xAxis: {
  317. axisLine: { show: false },
  318. axisTick: { show: false },
  319. splitLine: { show: false },
  320. axisLabel: {
  321. align: 'right',
  322. },
  323. data: getXAxisData('markingGroupNumber', groupProgressResult?.value),
  324. },
  325. yAxis: [
  326. {
  327. type: 'value',
  328. },
  329. {
  330. type: 'value',
  331. axisLabel: {
  332. formatter: `{value}%`,
  333. },
  334. splitLine: { show: false },
  335. },
  336. ],
  337. series: [
  338. {
  339. name: '完成总量',
  340. type: 'bar',
  341. barWidth: 20,
  342. itemStyle: {
  343. color: '#3AD500',
  344. },
  345. data: getXAxisData('finishCount', groupProgressResult?.value),
  346. },
  347. {
  348. name: '当日已完成',
  349. type: 'bar',
  350. barWidth: 20,
  351. itemStyle: {
  352. color: '#0064FF',
  353. },
  354. data: getXAxisData('dayFinishCount', groupProgressResult?.value),
  355. },
  356. {
  357. name: '完成比',
  358. type: 'bar',
  359. barWidth: 80,
  360. showBackground: true,
  361. barGap: '-200%',
  362. yAxisIndex: 1,
  363. itemStyle: {
  364. color: 'rgba(0, 186, 151,0.3)',
  365. },
  366. data: getXAxisData('finishRate', groupProgressResult?.value),
  367. },
  368. ],
  369. }
  370. })
  371. </script>
  372. <style scoped lang="scss">
  373. .total-progress-box {
  374. height: 323px;
  375. .total-progress-info {
  376. border-top: $OnePixelLine;
  377. height: 253px;
  378. }
  379. }
  380. .table-info {
  381. width: 720px;
  382. border-right: $OnePixelLine;
  383. .table-title {
  384. font-size: $MediumFont;
  385. }
  386. }
  387. .group-progress-box {
  388. height: 395px;
  389. .current-main-name {
  390. background-color: $BaseBgColor;
  391. font-size: $SmallFont;
  392. color: $PrimaryPlusFontColor;
  393. border-radius: 4px;
  394. &:not(:empty) {
  395. padding: 2px 4px;
  396. }
  397. }
  398. }
  399. </style>