SubjectProgress.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <template>
  2. <div class="radius-base">
  3. <div class="fill-blank radius-base p-t-base">
  4. <base-form :items="items" :model="model" :label-width="'80px'" size="small">
  5. <template #form-item-button>
  6. <el-button type="primary" @click="onSearch">查询</el-button>
  7. </template>
  8. </base-form>
  9. </div>
  10. <div class="fill-blank radius-base m-t-base p-base chart-info">
  11. <vue-e-charts v-loading="loading" class="full" :option="totalChartsOption" autoresize></vue-e-charts>
  12. </div>
  13. <div class="m-t-base fill-blank radius-base p-base">
  14. <base-table
  15. v-loading="loading"
  16. border
  17. stripe
  18. size="small"
  19. :data="subjectProgressEndList"
  20. :columns="columns"
  21. :span-method="tableSpanMethod"
  22. ></base-table>
  23. </div>
  24. </div>
  25. </template>
  26. <script setup lang="ts" name="SubjectProgress">
  27. /** 科目进度 */
  28. import { reactive, ref, computed, watch } from 'vue'
  29. import { minus } from '@/utils/common'
  30. import useFetch from '@/hooks/useFetch'
  31. import useMainStore from '@/store/main'
  32. import VueECharts from 'vue-echarts'
  33. import BaseTable from '@/components/element/BaseTable.vue'
  34. import BaseForm from '@/components/element/BaseForm.vue'
  35. import useForm from '@/hooks/useForm'
  36. import useOptions from '@/hooks/useOptions'
  37. import { ElButton } from 'element-plus'
  38. import type { EChartsOption } from 'echarts'
  39. import type { ExtractApiResponse } from '@/api/api'
  40. import type { EpTableColumn, InstanceTable } from 'global-type'
  41. type SubjectProgress = ExtractArrayValue<ExtractApiResponse<'subjectProgressEnd'>>
  42. const subjectView = ref<any>({
  43. code: '',
  44. name: '',
  45. })
  46. const {
  47. subjectList,
  48. mainQuestionList,
  49. groupListWithAll,
  50. onOptionInit,
  51. dataModel,
  52. changeModelValue,
  53. isExpert,
  54. isLeader,
  55. } = useOptions(['subject', 'question', 'group'])
  56. const model = reactive<any>({
  57. // markingGroupNumber: dataModel.group,
  58. // questionMainNumber: dataModel.question,
  59. subjectCode: dataModel.subject || '',
  60. })
  61. watch(dataModel, () => {
  62. model.subjectCode = dataModel.subject || ''
  63. // model.questionMainNumber = dataModel.question
  64. // model.markingGroupNumber = dataModel.group
  65. })
  66. const { defineColumn, _ } = useForm()
  67. const OneRow = defineColumn(_, 'row-1', { span: 5 })
  68. const btnRow = defineColumn(_, 'row-1', { span: 2 })
  69. const items = computed<any>(() => [
  70. OneRow({
  71. label: '科目',
  72. prop: 'subjectCode',
  73. slotType: 'select',
  74. labelWidth: '46px',
  75. slot: { options: subjectList.value, onChange: changeModelValue('subject'), disabled: !isExpert.value },
  76. }),
  77. // OneRow({
  78. // label: '大题',
  79. // prop: 'questionMainNumber',
  80. // slotType: 'select',
  81. // labelWidth: '60px',
  82. // slot: {
  83. // options: mainQuestionList.value,
  84. // onChange: changeModelValue('question'),
  85. // disabled: !isExpert.value && !isLeader.value,
  86. // },
  87. // }),
  88. btnRow({
  89. slotName: 'button',
  90. labelWidth: '20px',
  91. }),
  92. ])
  93. const mainStore = useMainStore()
  94. const { fetch: subjectProgressEnd, result: subjectProgressEndList, loading } = useFetch('subjectProgressEnd')
  95. watch(subjectList, () => {
  96. writeSubjectName()
  97. })
  98. const writeSubjectName = () => {
  99. let sName = (subjectList.value || []).find((item: any) => item.code == model.subjectCode)?.name
  100. subjectView.value = {
  101. code: model.subjectCode,
  102. name: sName || '',
  103. }
  104. }
  105. // subjectProgressEnd({ subjectCode: mainStore.myUserInfo?.subjectCode || '' })
  106. const onSearch = () => {
  107. subjectProgressEnd({ subjectCode: model.subjectCode }).then(() => {
  108. writeSubjectName()
  109. })
  110. }
  111. onOptionInit(onSearch)
  112. const getMainName = (row?: SubjectProgress) => {
  113. const { questionMainName: name, questionMainNumber: number } = row || {}
  114. return [number, name].filter(Boolean).join('-')
  115. }
  116. const getYAxisData = (field: keyof SubjectProgress | 'name', data?: SubjectProgress[]) => {
  117. if (!data) {
  118. return []
  119. }
  120. return data.map((v) => {
  121. if (field === 'name') {
  122. return getMainName(v)
  123. }
  124. return v[field]
  125. })
  126. }
  127. const totalChartsOption = computed<EChartsOption>(() => {
  128. return {
  129. grid: {
  130. top: 20,
  131. bottom: -20,
  132. left: 20,
  133. right: 30,
  134. containLabel: true,
  135. },
  136. legend: {
  137. right: 0,
  138. itemWidth: 14,
  139. data: ['试卷总量', '已完成', '完成比'],
  140. },
  141. yAxis: {
  142. axisLine: { show: false },
  143. axisTick: { show: false },
  144. splitLine: { show: false },
  145. inverse: true,
  146. axisLabel: {
  147. align: 'right',
  148. verticalAlign: 'bottom',
  149. },
  150. data: getYAxisData('name', subjectProgressEndList?.value),
  151. },
  152. xAxis: [
  153. {
  154. position: 'top',
  155. type: 'value',
  156. splitLine: { show: true },
  157. },
  158. {
  159. position: 'bottom',
  160. type: 'value',
  161. show: false,
  162. axisLabel: {
  163. formatter: `{value}%`,
  164. },
  165. splitLine: { show: false },
  166. },
  167. ],
  168. series: [
  169. {
  170. name: '试卷总量',
  171. type: 'bar',
  172. barWidth: 11,
  173. barGap: '-200%',
  174. itemStyle: {
  175. color: '#3AD500',
  176. },
  177. data: getYAxisData('totalPaper', subjectProgressEndList?.value),
  178. },
  179. {
  180. name: '已完成',
  181. type: 'bar',
  182. barWidth: 11,
  183. barGap: '-200%',
  184. itemStyle: {
  185. color: '#0064FF',
  186. },
  187. data: getYAxisData('finishCount', subjectProgressEndList?.value),
  188. },
  189. {
  190. name: '完成比',
  191. type: 'bar',
  192. barWidth: 44,
  193. barGap: '-200%',
  194. showBackground: true,
  195. xAxisIndex: 1,
  196. itemStyle: {
  197. color: 'rgba(0, 186, 151,0.3)',
  198. },
  199. label: {
  200. show: true,
  201. color: '#444',
  202. fontSize: 10,
  203. formatter({ value }) {
  204. return value > 0 ? `${value}%` : ''
  205. },
  206. position: 'insideTopRight',
  207. },
  208. data: getYAxisData('finishRate', subjectProgressEndList?.value),
  209. },
  210. ],
  211. }
  212. })
  213. const columns: EpTableColumn<SubjectProgress>[] = [
  214. {
  215. label: '科目',
  216. formatter() {
  217. // let sName = (subjectList.value || []).find((item: any) => item.code == model.subjectCode)?.name
  218. // return (
  219. // (mainStore.myUserInfo?.subjectCode || model.subjectCode) + '-' + (mainStore.myUserInfo?.subjectName || sName)
  220. // )
  221. return subjectView.value.code + '-' + subjectView.value.name
  222. },
  223. minWidth: 100,
  224. },
  225. { label: '大题', prop: 'questionMainName', minWidth: 80 },
  226. { label: '试卷总量', prop: 'totalPaper', minWidth: 90 },
  227. { label: '已完成', prop: 'finishCount', minWidth: 70 },
  228. {
  229. label: '完成比',
  230. prop: 'finishRate',
  231. minWidth: 70,
  232. formatter(row) {
  233. return `${row.finishRate}%`
  234. },
  235. },
  236. {
  237. label: '待完成',
  238. minWidth: 90,
  239. formatter(row) {
  240. return `${minus(row.totalPaper, row.finishCount)}`
  241. },
  242. },
  243. {
  244. label: '待完成比',
  245. minWidth: 90,
  246. prop: 'totalPaper',
  247. formatter(row) {
  248. return `${minus(100, row.finishRate)}%`
  249. },
  250. },
  251. { label: '问题卷待处理', prop: 'todoProblemPaperCount', minWidth: 110 },
  252. { label: '雷同卷待处理', prop: 'todoSamePaperCount', minWidth: 110 },
  253. { label: '主观题校验待处理', prop: 'subjectiveUnVerifyPaperCount', minWidth: 140 },
  254. {
  255. label: '抽查比例',
  256. prop: 'checkPaperRate',
  257. minWidth: 90,
  258. formatter(row) {
  259. return `${row.checkPaperRate}%`
  260. },
  261. },
  262. { label: '仲裁卷待处理', prop: 'todoArbitrationPaperCount', minWidth: 110 },
  263. {
  264. label: '仲裁率',
  265. prop: 'arbitrationPaperRate',
  266. minWidth: 70,
  267. formatter(row) {
  268. return `${row.arbitrationPaperRate}%`
  269. },
  270. },
  271. ]
  272. const tableSpanMethod: InstanceTable['spanMethod'] = (scope) => {
  273. if (scope.columnIndex === 0) {
  274. if (scope.rowIndex === 0) {
  275. return {
  276. rowspan: subjectProgressEndList.value?.length || 1,
  277. colspan: 1,
  278. }
  279. } else {
  280. return {
  281. rowspan: 0,
  282. colspan: 0,
  283. }
  284. }
  285. }
  286. return {
  287. rowspan: 1,
  288. colspan: 1,
  289. }
  290. }
  291. </script>
  292. <style scoped lang="scss">
  293. .chart-info {
  294. height: 260px;
  295. }
  296. </style>