form.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <template>
  2. <!-- 表单 -->
  3. <div class="about main-background main-background-type0">
  4. <div class="nav-placeholder"></div>
  5. <!-- 表单 -->
  6. <section class="main-section small-h">
  7. <div class="content">
  8. <div class="title left-right small">
  9. <a-button :icon="h(ArrowLeftOutlined)" @click="handleBack">返回主页</a-button>
  10. <h2>{{ title }}</h2>
  11. <div class="button-placeholder"></div>
  12. </div>
  13. <a-spin v-if="loadingData" class="w-full h-full" />
  14. <template v-else>
  15. <a-tabs centered>
  16. <a-tab-pane key="1" :tab="basicTabText">
  17. <a-alert
  18. v-if="(formModel as any).progress === -1 && !isReviewer && !isAdmin"
  19. message="提示:您的信息已被审核退回,请根据审核建议修改后重新提交。"
  20. :description="(formModel as any).comment"
  21. showIcon
  22. ></a-alert>
  23. <DynamicForm
  24. ref="formBase"
  25. :model="(formModel as any)"
  26. :options="finalFormOptions"
  27. />
  28. <div class="flex flex-col mt-4">
  29. <div class="flex flex-row w-full items-center justify-between">
  30. <span>
  31. <ExclamationCircleOutlined class="me-2" />
  32. 提示:上传文件时请勿离开页面防止上传失败,离开之前请保存您的修改以防丢失。
  33. </span>
  34. <a-button size="small" type="primary" @click="showHistory = true">历史版本</a-button>
  35. </div>
  36. <div class="flex flex-row w-full items-center justify-end mt-4">
  37. <a-popover
  38. v-if="!isReviewer && !isAdmin"
  39. title="保存提示"
  40. content="如果未完成编辑,可以先点击保存按钮保存修改,完成后再点击提交审核。您可以在历史版本中查看之前的修改。"
  41. >
  42. <a-button
  43. block
  44. :loading="loading"
  45. class="me-4"
  46. @click="handleSubmitBase(false)"
  47. >
  48. 保存
  49. </a-button>
  50. </a-popover>
  51. <a-button
  52. type="primary"
  53. block
  54. :loading="loading"
  55. @click="handleSubmitBase(true)"
  56. >
  57. 提交
  58. </a-button>
  59. </div>
  60. </div>
  61. </a-tab-pane>
  62. <a-tab-pane v-if="extendFormOptions" key="2" tab="扩展信息">
  63. <DynamicForm
  64. ref="formExtend"
  65. :model="(extendFormModel as any)"
  66. :options="extendFormOptions"
  67. />
  68. <a-button
  69. type="primary"
  70. block
  71. :loading="loading" class="mt-4"
  72. @click="handleSubmitExtend"
  73. >
  74. 提交
  75. </a-button>
  76. </a-tab-pane>
  77. </a-tabs>
  78. </template>
  79. </div>
  80. </section>
  81. <a-drawer
  82. v-model:open="showHistory"
  83. title="历史版本"
  84. placement="right"
  85. :width="showHistoryModel ? (isMobile ? '100%' : '60%') : (isMobile ? '80%' : '50%')"
  86. >
  87. <div v-if="showHistoryModel">
  88. <div class="flex flex-row justify-between">
  89. <a-button :icon="h(ArrowLeftOutlined)" @click="showHistoryModel = null">返回</a-button>
  90. <span>您正在查看 {{ showHistoryModel.desc }} 保存的版本</span>
  91. </div>
  92. <div class="main-section small-h">
  93. <div class="content">
  94. <a-spin v-if="showHistoryLoading" class="w-full h-full" />
  95. <DynamicForm
  96. v-else
  97. :model="(showHistoryModel as any)"
  98. :options="{
  99. ...formOptions,
  100. disabled: true,
  101. }"
  102. />
  103. </div>
  104. </div>
  105. </div>
  106. <CommonListBlock
  107. v-else
  108. :showTotal="true"
  109. :showSearch="false"
  110. :rowCount="1"
  111. :rowType="6"
  112. titleKey="_title"
  113. :load="(page: number, pageSize: number, _, searchText: string, dropDownValues: number[]) => loadHistoryData(page, pageSize, dropDownValues, searchText)"
  114. detailsPage="none"
  115. >
  116. <template #itemRight="{ item }">
  117. <a-button type="link" @click.stop="handleShowHistory(item)">查看</a-button>
  118. </template>
  119. </CommonListBlock>
  120. </a-drawer>
  121. </div>
  122. </template>
  123. <script setup lang="ts" generic="T extends DataModel, U extends DataModel">
  124. import { onMounted, ref, toRefs, type PropType, h, computed } from 'vue';
  125. import { useRoute, useRouter } from 'vue-router';
  126. import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@imengyu/vue-dynamic-form';
  127. import { message, Modal, type FormInstance } from 'ant-design-vue';
  128. import { ArrowLeftOutlined, ExclamationCircleOutlined } from '@ant-design/icons-vue';
  129. import { useAuthStore } from '@/stores/auth';
  130. import type { DataModel } from '@imengyu/js-request-transform';
  131. import InheritorContent, { InheritorWorkInfo } from '@/api/inheritor/InheritorContent';
  132. import CommonListBlock from '@/components/content/CommonListBlock.vue';
  133. import { waitTimeOut } from '@imengyu/imengyu-utils';
  134. import { useImageSimpleUploadCo } from '@/common/upload/ImageUploadCo';
  135. import { useWindowOnUnLoadConfirm } from '@/composeables/useWindowOnUnLoadConfirm';
  136. import { getFormErrorFieldsMessage } from '@/common/Form';
  137. const isMobile = computed(() => {
  138. return window.innerWidth < 768;
  139. });
  140. const props = defineProps({
  141. title: {
  142. type: String,
  143. default: '非遗数字化资源信息校对'
  144. },
  145. basicTabText: {
  146. type: String,
  147. default: '基础信息'
  148. },
  149. formModel: {
  150. type: Object as PropType<T>,
  151. required: true
  152. },
  153. formOptions: {
  154. type: Object as PropType<IDynamicFormOptions>,
  155. required: true
  156. },
  157. extendFormModel: {
  158. type: Object as PropType<U>,
  159. default: null
  160. },
  161. extendFormOptions: {
  162. type: Object as PropType<IDynamicFormOptions>,
  163. default: null
  164. },
  165. model: {
  166. type: Function as unknown as PropType<new () => DataModel>,
  167. required: true
  168. },
  169. load: {
  170. type: Function as PropType<(id: number|undefined) => Promise<void>>,
  171. default: () => Promise.resolve()
  172. },
  173. save: {
  174. type: Function as PropType<(model: T) => Promise<DataModel>>,
  175. default: (a: any) => Promise.resolve(a)
  176. },
  177. saveExtend: {
  178. type: Function as PropType<(model: U) => Promise<DataModel>>,
  179. default: (a: any) => Promise.resolve(a)
  180. },
  181. pushExamine: {
  182. type: Boolean,
  183. default: false
  184. },
  185. })
  186. const { formModel, formOptions, extendFormOptions, load } = toRefs(props);
  187. const formBase = ref<IDynamicFormRef>();
  188. const formExtend = ref<IDynamicFormRef>();
  189. const authStore = useAuthStore();
  190. const router = useRouter();
  191. const route = useRoute();
  192. const loading = ref(false);
  193. const loadingData = ref(false);
  194. const readonly = ref(false);
  195. const showHistory = ref(false);
  196. const showHistoryLoading = ref(false);
  197. const showHistoryModel = ref<any>(null);
  198. const isAdmin = computed(() => authStore.isAdmin);
  199. const isReviewer = computed(() => authStore.isReviewer);
  200. const finalFormOptions = computed(() => {
  201. return {
  202. ...formOptions.value,
  203. formItems: [
  204. ...formOptions.value.formItems,
  205. ...(props.pushExamine ? [
  206. {
  207. type: 'flat-group', label: '审核', name: 'ichInfo',
  208. childrenColProps: { span: 24 },
  209. children: [
  210. {
  211. label: '已排版完成', name: 'typesetting', type: 'check-box-int',
  212. hidden: { callback: (_: any, model: any) => !(isAdmin.value) },
  213. additionalProps: {
  214. text: '确认',
  215. },
  216. formItemProps: {
  217. extra: h('div', {}, [
  218. h(ExclamationCircleOutlined),
  219. h('span', { class: 'ms-2' }, '编辑员专用,如果已经排版,请打勾'),
  220. ]),
  221. }
  222. },
  223. {
  224. label: '审核人员', name: 'text1', type: 'static-text',
  225. hidden: { callback: (_: any, model: any) => isAdmin.value || isReviewer.value },
  226. additionalProps: {
  227. text: '黄念旭,李向群,卢志明',
  228. style: { color: '#999', }
  229. }
  230. },
  231. {
  232. label: '填报人', name: 'text3', type: 'static-text',
  233. hidden: { callback: (_: any, model: any) => !(!isAdmin.value && !isReviewer.value) },
  234. additionalProps: {
  235. text: authStore.userInfo?.nickname,
  236. }
  237. },
  238. /* {
  239. label: '填报人', name: 'text3', type: 'static-text',
  240. hidden: { callback: (_: any, model: any) => !(isAdmin.value || isReviewer.value) },
  241. additionalProps: {
  242. text: authStore.userInfo?.nickname,
  243. }
  244. }, */
  245. {
  246. label: '初审状态', name: 'progress', type: 'select',
  247. hidden: { callback: (_: any, model: any) => !(isAdmin.value && !isReviewer.value) },
  248. additionalProps: {
  249. options: [
  250. { text: '保存未审核', value: -2, disabled: true },
  251. { text: '审核退回', value: -1 },
  252. { text: '暂未审核', value: 0 },
  253. { text: '初审通过', value: 1 },
  254. { text: '专家审核通过', value: 2, disabled: true },
  255. ]
  256. }
  257. },
  258. {
  259. label: '审核状态', name: 'progress', type: 'select',
  260. hidden: { callback: (_: any, model: any) => !(isReviewer.value) },
  261. additionalProps: {
  262. options: [
  263. { text: '未审核', value: -2, disabled: true },
  264. { text: '审核退回', value: -1 },
  265. { text: '未审核', value: 0, disabled: true },
  266. { text: '未审核', value: 1, disabled: true },
  267. { text: '通过审核', value: 2 },
  268. ],
  269. }
  270. },
  271. {
  272. label: '审核状态', name: 'progress', type: 'select',
  273. hidden: { callback: (_: any, model: any) => isAdmin.value || isReviewer.value },
  274. disabled: true,
  275. additionalProps: {
  276. options: [
  277. { text: '保存未审核', value: -2 },
  278. { text: '审核退回', value: -1 },
  279. { text: '暂未审核', value: 0 },
  280. { text: '初审通过', value: 1 },
  281. { text: '专家审核通过', value: 2 },
  282. ]
  283. }
  284. },
  285. {
  286. label: '审核意见', name: 'comment', type: 'textarea',
  287. disabled: { callback: (_: any, model: any) => !isAdmin.value },
  288. additionalProps: {
  289. placeholder: { callback: (_: any, model: any) => (isAdmin.value || isReviewer.value) ? '若审核不通过,请输入审核意见' : '暂无审核意见' },
  290. }
  291. },
  292. {
  293. label: '审核签名', name: 'sign', type: 'sign',
  294. hidden: { callback: (_: any, model: any) => !isReviewer.value },
  295. additionalProps: {
  296. upload: useImageSimpleUploadCo({}),
  297. }
  298. },
  299. ]
  300. }
  301. ] : [])
  302. ],
  303. formRules: {
  304. ...formOptions.value.formRules,
  305. sign: [{ required: true, message: '请审核签名', trigger: ['blur'] }],
  306. },
  307. disabled: readonly.value,
  308. } as IDynamicFormOptions;
  309. });
  310. useWindowOnUnLoadConfirm();
  311. async function handleSubmitBase(valid: boolean) {
  312. loading.value = true;
  313. if (valid) {
  314. if (!isAdmin.value && !await new Promise((resolve, reject) => {
  315. Modal.confirm({
  316. title: '提交提示',
  317. content: '是否提交信息审核?填写完整信息后才可提交审核。如果需要离开,可先保存修改下次接着编辑。',
  318. okText: '提交',
  319. cancelText: '取消',
  320. onOk: () => resolve(true),
  321. onCancel: () => resolve(false),
  322. })
  323. })) {
  324. loading.value = false;
  325. return;
  326. }
  327. }
  328. const ref = (formBase.value?.getFormRef() as FormInstance);
  329. if (valid) {
  330. try {
  331. await ref.validate();
  332. } catch (e) {
  333. message.warning('请填写完整信息: ' + getFormErrorFieldsMessage(e));
  334. loading.value = false;
  335. if ((e as any).errorFields)
  336. ref.scrollToField((e as any).errorFields[0].name, { block: 'center' })
  337. return;
  338. }
  339. }
  340. try {
  341. let result = null;
  342. const data = await props.save(formModel.value);
  343. data.progress = valid ? 0 : -2;
  344. if (formModel.value instanceof InheritorWorkInfo)
  345. result = await InheritorContent.saveWorkInfo(data as InheritorWorkInfo);
  346. else
  347. result = await InheritorContent.saveBaseInfo(data);
  348. Modal.success({
  349. title: '提交成功',
  350. content: result.message,
  351. onOk() {
  352. router.back();
  353. },
  354. onCancel() {}
  355. });
  356. } catch (error) {
  357. Modal.error({
  358. title: '提交失败',
  359. content: '' + error,
  360. });
  361. } finally {
  362. loading.value = false;
  363. }
  364. }
  365. async function handleSubmitExtend() {
  366. loading.value = true;
  367. const ref = (formExtend.value?.getFormRef() as FormInstance);
  368. try {
  369. await ref.validate();
  370. } catch (e) {
  371. message.warning('请填写完整信息: ' + getFormErrorFieldsMessage(e));
  372. loading.value = false;
  373. if ((e as any).errorFields)
  374. ref.scrollToField((e as any).errorFields[0].name, { block: 'center' })
  375. return;
  376. }
  377. try {
  378. const result = await InheritorContent.saveExpandInfo(await props.saveExtend(formModel.value));
  379. await props.save(formModel.value);
  380. Modal.success({
  381. title: '提交成功',
  382. content: result.message,
  383. onOk() {
  384. router.back();
  385. },
  386. });
  387. } catch (error) {
  388. Modal.error({
  389. title: '提交失败',
  390. content: '' + error,
  391. });
  392. } finally {
  393. loading.value = false;
  394. }
  395. }
  396. async function loadData() {
  397. loadingData.value = true;
  398. readonly.value = Boolean(route.query.readonly);
  399. try {
  400. await load.value(route.query.id ? Number(route.query.id) : undefined);
  401. } catch (error) {
  402. console.log(error);
  403. message.error('加载失败 ' + error);
  404. } finally {
  405. loadingData.value = false;
  406. }
  407. }
  408. async function loadHistoryData(page: number, pageSize: number, dropDownValues: number[], searchText: string) {
  409. const contentId = Number(route.query.id || formModel.value.contentId)
  410. if (isNaN(contentId))
  411. return {
  412. page,
  413. total: 0,
  414. data: []
  415. };
  416. const res = (await InheritorContent.getCollectList(props.model, {
  417. contentId,
  418. collectType: 'content',
  419. userId: authStore.userInfo?.id,
  420. page,
  421. pageSize
  422. }))
  423. return {
  424. page,
  425. total: res.total,
  426. data: res.data.map((p) => {
  427. p._title = p.nickname ? `提交人:${p.nickname}` : p.title;
  428. p.desc = `提交时间:${p.updatedAt}`;
  429. return p;
  430. }),
  431. };
  432. }
  433. async function handleShowHistory(item: any) {
  434. showHistoryLoading.value = true;
  435. showHistory.value = true;
  436. await waitTimeOut(100);
  437. showHistoryModel.value = item;
  438. showHistoryLoading.value = false;
  439. }
  440. function handleBack() {
  441. Modal.confirm({
  442. title: '确定返回吗?',
  443. content: '返回后将丢失当未提交的信息,若有修改请先提交哦!',
  444. okText: '确定',
  445. okType: 'danger',
  446. onOk() {
  447. router.back();
  448. },
  449. });
  450. }
  451. onMounted(async () => {
  452. await loadData();
  453. })
  454. defineExpose({
  455. getFormRef() {
  456. return formBase.value;
  457. },
  458. getExtraFormRef() {
  459. return formExtend.value;
  460. },
  461. })
  462. </script>