evaluation-form.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <template>
  2. <CommonRoot>
  3. <FlexCol padding="space.lg">
  4. <SimplePageContentLoader :loader="loader">
  5. <template v-if="loader.isFinished.value">
  6. <Result
  7. v-if="!currentForm"
  8. status="info"
  9. title="您还未填写评估表"
  10. >
  11. <Height :height="30" />
  12. <Button type="primary" @click="createForm">去填写评估表</Button>
  13. </Result>
  14. <FlexCol v-else gap="gap.lg">
  15. <EvaluationFormBlock
  16. :currentForm="(currentForm as SelfAssessmentDetail)"
  17. :formOptions="formOptions"
  18. :checkItemList="(checkItemList as CheckItemInfo[])"
  19. :currentFormCheckItems="(currentFormCheckItems as SelfAssessmentCheckItemAnswer[])"
  20. />
  21. <Divider />
  22. <FlexRow align="flex-end" justify="space-between">
  23. <H3>自评总分</H3>
  24. <Text fontSize="50rpx" color="#315816" fontFamily="HUNdin1451" :text="`${totalPoints}分`" />
  25. </FlexRow>
  26. <Divider />
  27. <FlexCol gap="gap.lg">
  28. <FlexCol v-for="(title, secIdx) in externalReviewSectionTitles" :key="secIdx" gap="gap.sm">
  29. <Text bold :text="title.title" />
  30. <Field v-model="title.suggestion" :disabled="title.disabled" placeholder="(待终审填写)" />
  31. <FlexRow wrap align="center" gap="gap.md">
  32. <CheckBox
  33. v-for="(label, i) in externalReviewScoreRow1"
  34. :key="`${secIdx}-r1-${i}`"
  35. disabled
  36. :model-value="false"
  37. :text="label"
  38. :check-size="28"
  39. />
  40. </FlexRow>
  41. <FlexRow wrap align="center" gap="gap.md">
  42. <CheckBox
  43. v-for="(label, i) in externalReviewScoreRow2"
  44. :key="`${secIdx}-r2-${i}`"
  45. disabled
  46. :model-value="false"
  47. :text="label"
  48. :check-size="28"
  49. />
  50. </FlexRow>
  51. <FlexCol align="flex-end">
  52. <Text color="text.second" text="填写单位(盖章)" />
  53. <Text color="text.second" text="年 月 日" />
  54. </FlexCol>
  55. </FlexCol>
  56. </FlexCol>
  57. <Divider />
  58. <H3>佐证资料上传</H3>
  59. <Result v-if="!currentForm?.id" status="info" title="请先保存评估表后再上传佐证资料" />
  60. <Uploader
  61. v-else
  62. ref="uploaderRef"
  63. :upload="assessmentAnnexUpload"
  64. :max-upload-count="100"
  65. :max-file-size="20 * 1024 * 1024"
  66. :group-type="true"
  67. chooseType="file"
  68. list-type="list"
  69. />
  70. <Height :height="30" />
  71. <Divider />
  72. <Button type="primary" block :loading="submitLoading" @click="saveForm">保存评估表</Button>
  73. <Button :loading="submitLoading" @click="downloadForm">下载评估表PDF</Button>
  74. </FlexCol>
  75. </template>
  76. </SimplePageContentLoader>
  77. <XBarSpace />
  78. </FlexCol>
  79. </CommonRoot>
  80. </template>
  81. <script setup lang="ts">
  82. import { computed, ref } from 'vue';
  83. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  84. import { useAuthStore } from '@/store/auth';
  85. import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
  86. import { useAliOssUploadCo } from '@/common/components/upload/AliOssUploadCo';
  87. import { assertNotNull, formatError, StringUtils, waitTimeOut } from '@imengyu/imengyu-utils';
  88. import { toast, alert } from '@/components/dialog/CommonRoot';
  89. import AssessmentContentApi, {
  90. SelfAssessmentDetail,
  91. CheckItemInfo,
  92. SelfAssessmentCheckItemAnswer,
  93. getCheckAnnexType,
  94. } from '@/api/collect/AssessmentContent';
  95. import CommonRoot from '@/components/dialog/CommonRoot.vue';
  96. import Button from '@/components/basic/Button.vue';
  97. import Result from '@/components/feedback/Result.vue';
  98. import FlexCol from '@/components/layout/FlexCol.vue';
  99. import Height from '@/components/layout/space/Height.vue';
  100. import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
  101. import H3 from '@/components/typography/H3.vue';
  102. import FlexRow from '@/components/layout/FlexRow.vue';
  103. import Text from '@/components/basic/Text.vue';
  104. import XBarSpace from '@/components/layout/space/XBarSpace.vue';
  105. import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
  106. import type { RadioValueProps } from '@/components/dynamic/wrappers/RadioValue';
  107. import type { FieldProps } from '@/components/form/Field.vue';
  108. import type { SignatureFieldProps } from '@/components/form/SignatureField.vue';
  109. import { useImageSimpleUploadCo } from '@/common/components/upload/ImageUploadCo';
  110. import EvaluationFormBlock from './components/EvaluationFormBlock.vue';
  111. import Uploader, { type UploaderInstance } from '@/components/form/Uploader.vue';
  112. import { getMimeType } from '@/common/components/upload/mimes';
  113. import Divider from '@/components/display/Divider.vue';
  114. import { stringUrlToUploaderItem } from '@/components/form/Uploader';
  115. import CheckBox from '@/components/form/CheckBox.vue';
  116. import Field from '@/components/form/Field.vue';
  117. /** 评估表下方展示用:各级审核意见(不接数据、禁用) */
  118. const externalReviewSectionTitles = ref([
  119. { title: '1. 项目保护单位意见', suggestion: '', disabled: false },
  120. { title: '2. 县(区)文旅部门审核意见', suggestion: '', disabled: true },
  121. { title: '3. 设区市文旅部门、省非遗中心审核意见', suggestion: '', disabled: true },
  122. ]);
  123. const externalReviewScoreRow1 = ['优秀', '合格', '不合格'] as const;
  124. const externalReviewScoreRow2 = ['丧失传承能力', '取消资格'] as const;
  125. let loaded = false;
  126. const { querys } = useLoadQuerys({
  127. id: 0,
  128. userId: 0,
  129. }, () => {
  130. if (loaded)
  131. return;
  132. loaded = true;
  133. loader.load();
  134. });
  135. const currentForm = ref<SelfAssessmentDetail | null>(null);
  136. const currentFormCheckItems = ref<SelfAssessmentCheckItemAnswer[]>([]);
  137. const authStore = useAuthStore();
  138. const assessmentAnnexUpload = useAliOssUploadCo('assessment/annex', async (res, item) => {
  139. assertNotNull(currentForm.value, 'currentForm is null');
  140. const mimetype = getMimeType(item.filePath);
  141. await AssessmentContentApi.saveAnnex({
  142. name: item.name,
  143. formId: currentForm.value.id,
  144. url: res,
  145. type: getCheckAnnexType(mimetype),
  146. mimetype: mimetype,
  147. fileSize: item.size
  148. ? Math.max(1, Math.ceil(item.size / 1024))
  149. : undefined,
  150. });
  151. });
  152. const currentYear = new Date().getFullYear();
  153. const uploaderRef = ref<UploaderInstance | null>(null);
  154. const formRef = ref<IDynamicFormRef | null>(null);
  155. const formOptions : IDynamicFormOptions = {
  156. formAdditionaProps: {
  157. labelFlex: 4,
  158. inputFlex: 8,
  159. },
  160. formItems: [
  161. {
  162. type: 'flat-group',
  163. label: '传承人自查评估',
  164. name: 'selfAssessmentGroup',
  165. childrenColProps: { span: 24 },
  166. children: [
  167. {
  168. label: '传承人名称',
  169. name: 'inheritor',
  170. type: 'text',
  171. additionalProps: { placeholder: '请输入传承人名称' },
  172. },
  173. {
  174. label: '项目保护单位',
  175. name: 'unit',
  176. type: 'text',
  177. additionalProps: { placeholder: '请输入项目保护单位' },
  178. },
  179. {
  180. label: '项目名称',
  181. name: 'ichName',
  182. type: 'text',
  183. additionalProps: { placeholder: '请输入项目名称' },
  184. },
  185. {
  186. label: '联系电话',
  187. name: 'mobile',
  188. type: 'text',
  189. additionalProps: { placeholder: '请输入联系电话' },
  190. },
  191. {
  192. label: '身份证号',
  193. name: 'idCard',
  194. type: 'text',
  195. additionalProps: { placeholder: '请输入身份证号' },
  196. },
  197. {
  198. label: '级别',
  199. name: 'level',
  200. type: 'select-id',
  201. additionalProps: {
  202. placeholder: '请选择级别',
  203. loadData: async () => [
  204. { text: '国家级', value: 23 },
  205. { text: '省级', value: 24 },
  206. { text: '市级', value: 25 },
  207. ],
  208. },
  209. formProps: {
  210. showRightArrow: true,
  211. },
  212. },
  213. {
  214. label: '家庭住址',
  215. name: 'address',
  216. type: 'text',
  217. additionalProps: { placeholder: '请输入家庭住址' },
  218. },
  219. {
  220. label: '获评时间',
  221. name: 'awardTime',
  222. type: 'date',
  223. additionalProps: {
  224. placeholder: '请选择获评时间',
  225. shouldUpdateValueImmediately: true,
  226. },
  227. formProps: {
  228. showRightArrow: true,
  229. },
  230. },
  231. {
  232. label: '自评报告',
  233. name: 'content',
  234. type: 'object',
  235. children: [
  236. {
  237. label: '',
  238. name: 'title',
  239. type: 'static-text',
  240. },
  241. {
  242. label: '(一)开展传承活动,培养后继人才情况;',
  243. name: 'item0',
  244. type: 'textarea',
  245. formProps: {
  246. labelPosition: 'top',
  247. },
  248. additionalProps: {
  249. placeholder: '请填写',
  250. maxLength: 1000,
  251. colon: false,
  252. showWordLimit: true,
  253. },
  254. },
  255. {
  256. label: '(二)妥善保存相关实物、资料情况;',
  257. name: 'item1',
  258. type: 'textarea',
  259. formProps: {
  260. labelPosition: 'top',
  261. },
  262. additionalProps: {
  263. placeholder: '请填写',
  264. maxLength: 1000,
  265. colon: false,
  266. showWordLimit: true,
  267. },
  268. },
  269. {
  270. label: '(三)配合进行非物质文化遗产调查情况;',
  271. name: 'item2',
  272. type: 'textarea',
  273. formProps: {
  274. labelPosition: 'top',
  275. },
  276. additionalProps: {
  277. placeholder: '请填写',
  278. maxLength: 1000,
  279. colon: false,
  280. showWordLimit: true,
  281. },
  282. },
  283. {
  284. label: '(四)参加非物质文化遗产公益性宣传情况;',
  285. name: 'item3',
  286. type: 'textarea',
  287. formProps: {
  288. labelPosition: 'top',
  289. },
  290. additionalProps: {
  291. placeholder: '请填写',
  292. maxLength: 1000,
  293. colon: false,
  294. showWordLimit: true,
  295. },
  296. },
  297. {
  298. label: '(五)补助经费使用情况;',
  299. name: 'item4',
  300. type: 'textarea',
  301. formProps: {
  302. labelPosition: 'top',
  303. },
  304. additionalProps: {
  305. placeholder: '请填写',
  306. maxLength: 1000,
  307. colon: false,
  308. showWordLimit: true,
  309. },
  310. },
  311. {
  312. label: '(六)参加培训情况',
  313. name: 'item5',
  314. type: 'textarea',
  315. formProps: {
  316. labelPosition: 'top',
  317. },
  318. additionalProps: {
  319. placeholder: '请填写',
  320. maxLength: 1000,
  321. colon: false,
  322. showWordLimit: true,
  323. },
  324. },
  325. {
  326. label: '(七)其他相关情况;',
  327. name: 'item6',
  328. type: 'textarea',
  329. formProps: {
  330. labelPosition: 'top',
  331. },
  332. additionalProps: {
  333. placeholder: '请填写',
  334. maxLength: 1000,
  335. colon: false,
  336. showWordLimit: true,
  337. } as FieldProps,
  338. },
  339. {
  340. label: '(八)存在的问题及原因分析。',
  341. name: 'item7',
  342. type: 'textarea',
  343. formProps: {
  344. labelPosition: 'top',
  345. },
  346. additionalProps: {
  347. placeholder: '请填写',
  348. maxLength: 1000,
  349. colon: false,
  350. showWordLimit: true,
  351. },
  352. }
  353. ],
  354. },
  355. ],
  356. },
  357. {
  358. type: 'insertion',
  359. name: 'insertCheckList',
  360. },
  361. {
  362. type: 'flat-group',
  363. label: '传承人自查评估',
  364. name: 'selfAssessmentGroup2',
  365. childrenColProps: { span: 24 },
  366. children: [
  367. {
  368. label: '其他相关情况(扣分内容)',
  369. name: 'deductContent',
  370. type: 'text',
  371. additionalProps: {
  372. showWordLimit: true,
  373. maxlength: 260,
  374. placeholder: '请输入其他相关情况(扣分内容)',
  375. } as FieldProps,
  376. },
  377. {
  378. label: '其他相关情况(扣分分值)',
  379. name: 'deductPoints',
  380. type: 'number',
  381. additionalProps: {
  382. placeholder: '请输入其他相关情况(扣分分值)',
  383. min: 0,
  384. max: 100,
  385. },
  386. },
  387. {
  388. label: '自我评估',
  389. name: 'self',
  390. type: 'radio-value',
  391. additionalProps: {
  392. options: [
  393. { text: '优秀', value: 1 },
  394. { text: '合格', value: 2 },
  395. { text: '不合格', value: 3 },
  396. { text: '丧失传承能力', value: 4 },
  397. { text: '取消资格', value: 5 },
  398. ],
  399. vertical: true,
  400. } as RadioValueProps,
  401. },
  402. {
  403. label: '传承人签名',
  404. name: 'inheritorSign',
  405. type: 'sign',
  406. formProps: {
  407. showRightArrow: true,
  408. },
  409. additionalProps: {
  410. upload: useImageSimpleUploadCo(),
  411. previewImageProps: {
  412. width: '400rpx',
  413. }
  414. } as SignatureFieldProps,
  415. }
  416. ],
  417. },
  418. ],
  419. formRules: {
  420. inheritor: [{ required: true, message: '请输入传承人名称' }],
  421. unit: [{ required: true, message: '请输入项目保护单位' }],
  422. ichName: [{ required: true, message: '请输入项目名称' }],
  423. mobile: [{ required: true, message: '请输入联系电话' }],
  424. idCard: [{ required: true, message: '请输入身份证号' }],
  425. level: [{ required: true, message: '请选择级别' }],
  426. address: [{ required: true, message: '请输入家庭住址' }],
  427. content: [{ required: true, message: '请填写自评报告' }],
  428. self: [{ required: true, message: '请选择自我评估' }],
  429. sign: [{ required: true, message: '请传承人签名' }],
  430. awardTime: [{ required: true, message: '请选择获评时间' }],
  431. },
  432. };
  433. const checkItemList = ref<CheckItemInfo[]>([]);
  434. const editorTitle = ref('');
  435. let checkItemMap = new Map<number, CheckItemInfo>();
  436. const totalPoints = computed(() => {
  437. if (!currentForm.value)
  438. return 0;
  439. return currentFormCheckItems.value
  440. .filter((item) => {
  441. const checkItem = checkItemMap.get(item.id);
  442. return checkItem && !checkItem.isTitle;
  443. })
  444. .reduce((acc, item) => acc + (item.count * item.points), 0)
  445. - (currentForm.value.deductPoints ?? 0);
  446. });
  447. const levelTitle = computed(() => {
  448. if (currentForm.value?.level === 23) return '国家级';
  449. if (currentForm.value?.level === 24) return '省级';
  450. if (currentForm.value?.level === 25) return '市级';
  451. return '国家级';
  452. });
  453. function loadEditorContent() {
  454. if (!currentForm.value)
  455. return;
  456. if (typeof currentForm.value.content !== 'object' || currentForm.value.content === null) {
  457. currentForm.value!.content = {};
  458. }
  459. currentForm.value!.content.title = `传承人填写${currentYear}年1月1日至${currentYear}年12月31日${levelTitle.value}非遗传承人义务履行和传承补助经费使用情况等,不超过1000字,如未履行职责请进行说明。参考提纲如下:`;
  460. for (let i = 0; i < 8; i++) {
  461. if (typeof currentForm.value.content[`item${i}`] !== 'string') {
  462. currentForm.value.content[`item${i}`] = '';
  463. }
  464. }
  465. }
  466. async function loadBasicInfo() {
  467. const basicInfo = await AssessmentContentApi.getInheritorBasic(authStore.userInfo?.id);
  468. assertNotNull(currentForm.value, 'currentForm is null');
  469. currentForm.value.inheritor = basicInfo.name;
  470. currentForm.value.unit = basicInfo.unit;
  471. currentForm.value.ichName = basicInfo.ichName;
  472. currentForm.value.mobile = basicInfo.mobile;
  473. currentForm.value.level = basicInfo.level;
  474. currentForm.value.idCard = basicInfo.idCard;
  475. currentForm.value.address = basicInfo.address;
  476. }
  477. async function loadCheckItems() {
  478. assertNotNull(currentForm.value, 'currentForm is null');
  479. const { top, map } = await AssessmentContentApi.getCheckItems(Number(currentForm.value.level));
  480. checkItemList.value = top;
  481. checkItemMap = map;
  482. currentFormCheckItems.value = currentForm.value.checkItems.concat();
  483. }
  484. async function loadAnnexList() {
  485. assertNotNull(currentForm.value, 'currentForm is null');
  486. console.log('awardTime', currentForm.value.awardTime);
  487. const annexList = await AssessmentContentApi.getAnnexList(currentForm.value.id);
  488. setTimeout(() => {
  489. if (uploaderRef.value) {
  490. uploaderRef.value.setList(annexList.data.map((item) => stringUrlToUploaderItem(item.url, item.name)));
  491. }
  492. }, 1000);
  493. }
  494. const submitLoading = ref(false);
  495. async function createForm() {
  496. const detail = new SelfAssessmentDetail();
  497. detail.userId = authStore.userInfo!.id;
  498. detail.year = new Date().getFullYear();
  499. detail.checkItems = [];
  500. currentForm.value = detail;
  501. loadEditorContent();
  502. await loadBasicInfo();
  503. await loadCheckItems();
  504. }
  505. async function saveForm() {
  506. const detail = currentForm.value;
  507. try {
  508. await formRef.value?.validate();
  509. } catch (error) {
  510. toast('请填写完整信息');
  511. return;
  512. }
  513. submitLoading.value = true;
  514. currentForm.value!.checkItems = currentFormCheckItems.value;
  515. try {
  516. assertNotNull(detail, 'currentForm is null');
  517. await AssessmentContentApi.saveSelfAssessment(detail as SelfAssessmentDetail);
  518. toast('保存评估表成功');
  519. await waitTimeOut(1000);
  520. await loader.reload();
  521. } catch (error) {
  522. alert({
  523. title: '保存评估表失败',
  524. content: formatError(error),
  525. });
  526. }
  527. submitLoading.value = false;
  528. }
  529. async function downloadForm() {
  530. if (!currentForm.value?.id) {
  531. toast('请先保存评估表后再下载PDF');
  532. return;
  533. }
  534. try {
  535. assertNotNull(currentForm.value, 'currentForm is null');
  536. const pdfPath = await AssessmentContentApi.downloadSelfAssessmentPdf(currentForm.value.id);
  537. uni.openDocument({
  538. filePath: pdfPath,
  539. fileType: 'pdf',
  540. showMenu: true,
  541. });
  542. } catch (error) {
  543. alert({
  544. title: '下载评估表失败',
  545. content: formatError(error),
  546. });
  547. }
  548. }
  549. const loader = useSimpleDataLoader(async () => {
  550. await waitTimeOut(1000);
  551. if (querys.value.id > 0) {
  552. const detail = await AssessmentContentApi.getSelfAssessmentDetail(querys.value.id, querys.value.userId);
  553. currentForm.value = detail;
  554. loadEditorContent();
  555. await loadCheckItems();
  556. await loadAnnexList();
  557. return;
  558. }
  559. const basicInfo = await AssessmentContentApi.getInheritorBasic(authStore.userInfo?.id);
  560. if (basicInfo.checkId > 0) {
  561. const detail = await AssessmentContentApi.getSelfAssessmentDetail(
  562. basicInfo.checkId,
  563. authStore.userInfo?.id
  564. );
  565. currentForm.value = detail;
  566. loadEditorContent();
  567. await loadAnnexList();
  568. await loadCheckItems();
  569. } else {
  570. currentForm.value = null;
  571. }
  572. return currentForm.value;
  573. }, false);
  574. </script>