index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <template>
  2. <div class="p-base full">
  3. <el-card shadow="never">
  4. <base-form
  5. ref="formRef"
  6. :label-width="'150px'"
  7. size="small"
  8. :groups="groups"
  9. :model="model"
  10. :items="items"
  11. :rules="rules"
  12. :disabled="adding || editing"
  13. >
  14. <template #form-item-setLevelRange>
  15. <div class="flex items-center">
  16. <el-input :model-value="levelRangeView" :disabled="true"></el-input>
  17. <el-button class="m-l-base" type="primary" @click="setLevelRangeVisible = true">设置</el-button>
  18. </div>
  19. </template>
  20. <template #form-item-expand>
  21. <el-button type="primary" link @click="expand = !expand">高级设置</el-button>
  22. </template>
  23. <template #form-item-operation>
  24. <confirm-button
  25. :loading="adding || editing"
  26. ok-text="保存"
  27. @confirm="onSubmit"
  28. @cancel="onCancel"
  29. ></confirm-button>
  30. </template>
  31. <template #form-item-compare>
  32. <el-select v-model="compare" :disabled="isEdit" @change="compareChange">
  33. <el-option value="1" label="启用"></el-option>
  34. <el-option value="0" label="不启用"></el-option>
  35. </el-select>
  36. </template>
  37. </base-form>
  38. </el-card>
  39. </div>
  40. <base-dialog v-model="setLevelRangeVisible" title="档次设置" destroy-on-close :width="340">
  41. <div class="level-list">
  42. <div v-for="([start, end], index) in levelRanges" :key="index" class="flex items-center level-row">
  43. <div class="m-r-large level-label flex items-center justify-between">
  44. <span class="s1">档次{{ (start + end) / 2 }}</span>
  45. <span class="s2">-</span>
  46. <span class="s3">({{ start }}~{{ end }})</span>
  47. </div>
  48. <div class="flex items-center level-input">
  49. <el-input-number
  50. v-model="levelRangValues[index]"
  51. class="m-r-mini"
  52. size="small"
  53. :min="0"
  54. :max="100"
  55. :controls="false"
  56. style="width: 100px"
  57. @keyup="levelItemChange(index, $event)"
  58. ></el-input-number>
  59. %
  60. </div>
  61. </div>
  62. </div>
  63. <template #footer>
  64. <div class="flex justify-center">
  65. <confirm-button @confirm="onSetLevelRangeSubmit" @cancel="setLevelRangeVisible = false"></confirm-button>
  66. </div>
  67. </template>
  68. </base-dialog>
  69. </template>
  70. <script setup lang="ts" name="SubjectAddQuestion">
  71. /** 添加大题 */
  72. import { computed, reactive, ref } from 'vue'
  73. import { useRouter } from 'vue-router'
  74. import { ElCard, ElButton, ElMessage, ElInput, ElInputNumber, ElSelect, ElOption } from 'element-plus'
  75. import { omit } from 'lodash-es'
  76. import ConfirmButton from '@/components/common/ConfirmButton.vue'
  77. import BaseDialog from '@/components/element/BaseDialog.vue'
  78. import BaseForm from '@/components/element/BaseForm.vue'
  79. import useFetch from '@/hooks/useFetch'
  80. import useForm from '@/hooks/useForm'
  81. import useVW from '@/hooks/useVW'
  82. import { TrueOrFalse } from '@/constants/dicts'
  83. import type { ExtractApiParams } from '@/api/api'
  84. import type { EpFormItem, EpFormRules, FormGroup } from 'global-type'
  85. const { fetch: getMainQuestionList, result: mainQuestionListResult } = useFetch('getMainQuestionList')
  86. const { back } = useRouter()
  87. const props = defineProps<{ subjectCode: string; mainNumber?: number | string }>()
  88. getMainQuestionList({ subjectCode: props.subjectCode, excludeNumber: props.mainNumber || undefined })
  89. const mainQuestionOptions = computed(() => {
  90. return (
  91. mainQuestionListResult.value?.map((v: any) => {
  92. return {
  93. ...v,
  94. label: `${v.mainNumber}-${v.title}`,
  95. value: v.mainNumber,
  96. }
  97. }) || []
  98. )
  99. })
  100. const isEdit = !!props.mainNumber
  101. // const levelRanges = [
  102. // [0, 2],
  103. // [3, 5],
  104. // [6, 8],
  105. // [9, 11],
  106. // [12, 14],
  107. // ]
  108. const levelRanges = [
  109. [0, 0],
  110. [1, 3],
  111. [4, 6],
  112. [7, 9],
  113. [10, 12],
  114. [13, 15],
  115. ]
  116. const levelItemChange = (index: number, e: any) => {
  117. if (Number(e.target.value) > 100) {
  118. levelRangValues.value[index] = 100
  119. e.target.value = 100
  120. }
  121. }
  122. const setLevelRangeVisible = ref<boolean>(false)
  123. const levelRangValues = ref<number[]>(Array.from<number>({ length: 6 }).fill(0))
  124. const { fetch: getMainQuestionInfo } = useFetch('getMainQuestionInfo')
  125. const { fetch: addMainQuestion, loading: adding } = useFetch('addMainQuestion')
  126. const { fetch: editMainQuestion, loading: editing } = useFetch('editMainQuestion')
  127. const model = reactive<ExtractApiParams<'addMainQuestion'>>({
  128. subjectCode: props.subjectCode,
  129. groupNumber: void 0,
  130. category: void 0,
  131. intervalScore: 1,
  132. mainNumber: void 0,
  133. mainTitle: '',
  134. minMarkTime: void 0,
  135. questionCount: void 0,
  136. questionScore: void 0,
  137. remarkNumber: void 0,
  138. remarkType: 'QUANTITY',
  139. levelRange: Array.from<number>({ length: 6 }).fill(0),
  140. standardRate: void 0,
  141. selfRate: void 0,
  142. systemRate: void 0,
  143. startNumber: 1,
  144. relationMainNumber: void 0,
  145. markSpeedLimit: false,
  146. })
  147. const compareChange = (val: string) => {
  148. if (val == '0') {
  149. model.relationMainNumber = void 0
  150. }
  151. }
  152. const levelRangeView = computed(() => {
  153. return model.levelRange.map((n) => `${n * 100 || 0}%`).join(',')
  154. })
  155. const { formRef, elFormRef, defineColumn, _ } = useForm()
  156. const validateSmallQuestion = (rule: any, value: any, callback: any) => {
  157. if (
  158. Number(model.questionCount) &&
  159. Number(model.questionScore) &&
  160. (model.questionCount as number) * (model.questionScore as number) > 15
  161. ) {
  162. callback(new Error('小题总分(小题数量乘以小题满分)不能大于15'))
  163. } else {
  164. callback()
  165. }
  166. }
  167. const rules = computed<EpFormRules>(() => {
  168. return {
  169. groupNumber: [{ required: true, message: '请输入小组数量' }],
  170. category: [{ required: false, message: '请选择成绩表对应字段' }],
  171. intervalScore: [{ required: true, message: '请输入小题间隔分' }],
  172. mainNumber: [{ required: true, message: '请输入大题号' }],
  173. mainTitle: [
  174. { required: true, message: '请输入大题名称' },
  175. { type: 'string', message: '大题名称限制50字以内' },
  176. ],
  177. minMarkTime: [{ required: true, message: '请输入最小阅卷时长' }],
  178. questionCount: [
  179. { required: true, message: '请输入小题数量' },
  180. { validator: validateSmallQuestion, trigger: 'change' },
  181. ],
  182. questionScore: [
  183. { required: true, message: '请输入小题满分' },
  184. { validator: validateSmallQuestion, trigger: 'change' },
  185. ],
  186. remarkType: [{ required: true, message: '请选择回评设置' }],
  187. remarkNumber: [{ required: true, message: '请输入回评设置' }],
  188. startNumber: [{ required: true, message: '请输入小题起始号' }],
  189. systemRate: [{ required: !!model.levelRange?.filter(Boolean)?.length, message: '请设置系统抽查比例' }],
  190. relationMainNumber: [{ required: true, message: '请选择关联大题' }],
  191. }
  192. })
  193. const expand = ref<boolean>(false)
  194. const compare = ref('0')
  195. const groups = computed<FormGroup[]>(() => {
  196. return [
  197. {
  198. rowKeys: Array.from({ length: 11 }).map((_, i) => `row-${i + 1}`),
  199. },
  200. {
  201. rowKeys: ['row-12', 'row-13', 'row-14'],
  202. groupTitle: '高级设置',
  203. hidden: !expand.value,
  204. },
  205. {
  206. rowKeys: ['expand'],
  207. hidden: expand.value,
  208. },
  209. ]
  210. })
  211. const Span8 = defineColumn(_, _, { span: 8 })
  212. const items = computed<any>(() =>
  213. [
  214. Span8(
  215. {
  216. label: '大题号',
  217. slotType: 'inputNumber',
  218. prop: 'mainNumber',
  219. slot: {
  220. placeholder: '大题号',
  221. disabled: isEdit,
  222. },
  223. },
  224. 'row-1'
  225. ),
  226. Span8({ label: '大题名称', slotType: 'input', prop: 'mainTitle', slot: { placeholder: '大题名称' } }, 'row-2'),
  227. Span8(
  228. {
  229. label: '成绩表对应字段',
  230. slotType: 'select',
  231. prop: 'category',
  232. slot: {
  233. placeholder: '成绩表对应字段',
  234. clearable: true,
  235. options: [
  236. { label: '作文分', value: 'WRITING' },
  237. { label: '翻译分', value: 'TRANSLATION' },
  238. ],
  239. },
  240. },
  241. 'row-2'
  242. ),
  243. Span8(
  244. {
  245. label: '小题起始号',
  246. slotType: 'inputNumber',
  247. prop: 'startNumber',
  248. slot: { placeholder: '小题起始号', disabled: isEdit, stepStrictly: true, step: 1, min: 0, max: 999999 },
  249. },
  250. 'row-3'
  251. ),
  252. Span8(
  253. {
  254. label: '小题数量',
  255. slotType: 'inputNumber',
  256. prop: 'questionCount',
  257. slot: { placeholder: '小题数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
  258. },
  259. 'row-4'
  260. ),
  261. Span8(
  262. {
  263. label: '小题满分',
  264. slotType: 'inputNumber',
  265. prop: 'questionScore',
  266. slot: { placeholder: '小题满分', stepStrictly: true, step: 1, min: 0, max: 999999 },
  267. },
  268. 'row-5'
  269. ),
  270. Span8(
  271. {
  272. label: '间隔分',
  273. slotType: 'inputNumber',
  274. prop: 'intervalScore',
  275. slot: { placeholder: '间隔分', stepStrictly: true, step: 1, min: 0, max: 999999 },
  276. },
  277. 'row-6'
  278. ),
  279. Span8(
  280. {
  281. label: '最小阅卷时长(秒)',
  282. slotType: 'inputNumber',
  283. prop: 'minMarkTime',
  284. slot: { placeholder: '最小阅卷时长(秒)', stepStrictly: true, step: 1, min: 0, max: 9999999 },
  285. },
  286. 'row-7'
  287. ),
  288. Span8(
  289. {
  290. label: '评卷小组数量',
  291. slotType: 'inputNumber',
  292. prop: 'groupNumber',
  293. slot: { placeholder: '评卷小组数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
  294. },
  295. 'row-8'
  296. ),
  297. Span8(
  298. {
  299. label: '回评设置',
  300. slotType: 'select',
  301. prop: 'remarkType',
  302. slot: {
  303. placeholder: '回评设置',
  304. disabled: isEdit,
  305. options: [
  306. { value: 'QUANTITY', label: '按数量' },
  307. { value: 'TIME', label: '按时间' },
  308. ],
  309. },
  310. },
  311. 'row-9'
  312. ),
  313. Span8(
  314. {
  315. label: model.remarkType === 'QUANTITY' ? '数量' : '时间:(近N分钟)',
  316. prop: 'remarkNumber',
  317. slotType: 'inputNumber',
  318. slot: {
  319. stepStrictly: true,
  320. step: 1,
  321. min: 0,
  322. max: 120,
  323. },
  324. },
  325. 'row-10'
  326. ),
  327. Span8(
  328. {
  329. label: '是否进行限速提醒',
  330. slotType: 'select',
  331. prop: 'markSpeedLimit',
  332. slot: {
  333. placeholder: '是否进行限速提醒',
  334. options: TrueOrFalse,
  335. },
  336. },
  337. 'row-11'
  338. ),
  339. Span8(
  340. {
  341. label: '标准卷分发频度',
  342. slotType: 'inputNumber',
  343. prop: 'standardRate',
  344. slot: { placeholder: '标准卷分发频度', stepStrictly: true, step: 1 },
  345. },
  346. 'row-12'
  347. ),
  348. Span8(
  349. {
  350. label: '自查卷分发频度',
  351. slotType: 'inputNumber',
  352. prop: 'selfRate',
  353. slot: { placeholder: '自查卷分发频度', stepStrictly: true, step: 1 },
  354. },
  355. 'row-12'
  356. ),
  357. Span8(
  358. {
  359. label: '系统抽查卷比例',
  360. slotType: 'inputNumber',
  361. prop: 'systemRate',
  362. slot: { placeholder: '系统抽查卷比例', stepStrictly: true, step: 1 },
  363. },
  364. 'row-13'
  365. ),
  366. Span8(
  367. {
  368. label: '档次抽查比例',
  369. slotName: 'setLevelRange',
  370. },
  371. 'row-13'
  372. ),
  373. Span8(
  374. {
  375. label: '人机抽查比对',
  376. slotName: 'compare',
  377. },
  378. 'row-14'
  379. ),
  380. compare.value == '1'
  381. ? Span8(
  382. {
  383. label: '关联大题',
  384. slotType: 'select',
  385. prop: 'relationMainNumber',
  386. slot: {
  387. clearable: true,
  388. options: mainQuestionOptions.value,
  389. disabled: isEdit,
  390. },
  391. },
  392. 'row-14'
  393. )
  394. : null,
  395. Span8({ slotName: 'expand' }, 'expand'),
  396. Span8({ slotName: 'operation' }, 'operation'),
  397. ].filter((v) => !!v)
  398. )
  399. if (isEdit) {
  400. getMainQuestionInfo({ subjectCode: props.subjectCode, mainNumber: +props.mainNumber }).then((result) => {
  401. if (!!result.relationMainNumber) {
  402. compare.value = '1'
  403. }
  404. if (result.remarkType === 'TIME') {
  405. result.remarkNumber = (result.remarkNumber || 0) / 60
  406. }
  407. Object.assign(model, omit(result, 'examId'))
  408. // levelRangValues.value = result.levelRange.slice(0)
  409. levelRangValues.value = result.levelRange.slice(0).map((item) => {
  410. return item * 100
  411. })
  412. })
  413. }
  414. const onSetLevelRangeSubmit = () => {
  415. // model.levelRange = levelRangValues.value
  416. model.levelRange = levelRangValues.value.map((item) => {
  417. return !!item ? item / 100 : 0
  418. })
  419. setLevelRangeVisible.value = false
  420. }
  421. const onSubmit = async () => {
  422. try {
  423. const valid = await elFormRef?.value?.validate().catch((error: object) => {
  424. if (
  425. !expand.value &&
  426. Object.keys(error).some((k) => ['standardRate', 'selfRate', 'systemRate', 'levelRange'].includes(k))
  427. ) {
  428. expand.value = true
  429. }
  430. })
  431. if (valid) {
  432. const data = Object.assign(
  433. { ...model, levelRange: model.levelRange || [], category: model.category || void 0 },
  434. { remarkNumber: model.remarkType === 'TIME' ? (model.remarkNumber || 0) * 60 : model.remarkNumber }
  435. )
  436. await (isEdit ? editMainQuestion(data) : addMainQuestion(data))
  437. ElMessage.success('保存成功')
  438. back()
  439. }
  440. } catch (error) {
  441. console.error(error)
  442. }
  443. }
  444. const onCancel = () => {
  445. back()
  446. }
  447. </script>
  448. <style scoped lang="scss">
  449. .level-list {
  450. .level-row {
  451. margin-bottom: 8px;
  452. .level-label {
  453. width: 125px;
  454. text-align: right;
  455. .s1 {
  456. width: 41px;
  457. text-align: left;
  458. }
  459. .s2 {
  460. width: 7px;
  461. }
  462. .s3 {
  463. width: 49px;
  464. text-align: right;
  465. }
  466. }
  467. }
  468. }
  469. </style>