TotalProgress.vue 12 KB

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