argeement-sign.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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="!currentAgreement"
  8. status="info"
  9. title="您还未签署传承协议"
  10. description="请先签署传承协议"
  11. >
  12. <Height :height="30" />
  13. <Button type="primary" @click="createAgreement">去签署传承协议</Button>
  14. </Result>
  15. <FlexCol v-else gap="gap.md">
  16. <Alert type="info" message="请仔细阅读传承协议,确保您已理解内容,并签署传承协议。" />
  17. <FlexCol gap="gap.md">
  18. <Form ref="formRef" :model="currentAgreement" :rules="formRules">
  19. <H3>{{ agreementTitle }}</H3>
  20. <AgreementBodyNational
  21. v-if="currentAgreement?.level === 23"
  22. :detail="(currentAgreement as AgreementDetail)"
  23. :agreement-year="agreementYear"
  24. :party-a-stamp-date="partyAStampDate"
  25. :party-b-sign-date="partyBSignDate"
  26. :upload-agreement-sign="uploadAgreementSign"
  27. @update:party-a-stamp-date="partyAStampDate = $event"
  28. @update:party-b-sign-date="partyBSignDate = $event"
  29. />
  30. <AgreementBodyProvincial
  31. v-else-if="currentAgreement?.level === 24"
  32. :detail="(currentAgreement as AgreementDetail)"
  33. :agreement-year="agreementYear"
  34. :party-a-stamp-date="partyAStampDate"
  35. :party-b-sign-date="partyBSignDate"
  36. :upload-agreement-sign="uploadAgreementSign"
  37. @update:party-a-stamp-date="partyAStampDate = $event"
  38. @update:party-b-sign-date="partyBSignDate = $event"
  39. />
  40. <AgreementBodyMunicipal
  41. v-else-if="currentAgreement?.level === 25"
  42. :detail="(currentAgreement as AgreementDetail)"
  43. :agreement-year="agreementYear"
  44. :party-a-stamp-date="partyAStampDate"
  45. :party-b-sign-date="partyBSignDate"
  46. :upload-agreement-sign="uploadAgreementSign"
  47. @update:party-a-stamp-date="partyAStampDate = $event"
  48. @update:party-b-sign-date="partyBSignDate = $event"
  49. />
  50. </Form>
  51. </FlexCol>
  52. <Button type="primary" block :loading="submitLoading" @click="saveAgreement">保存传承协议</Button>
  53. <Button :loading="submitLoading" @click="downloadAgreement">下载协议PDF</Button>
  54. </FlexCol>
  55. </template>
  56. </SimplePageContentLoader>
  57. </FlexCol>
  58. </CommonRoot>
  59. </template>
  60. <script setup lang="ts">
  61. import { computed, ref } from 'vue';
  62. import { useAuthStore } from '@/store/auth';
  63. import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
  64. import { assertNotNull, formatError } from '@imengyu/imengyu-utils';
  65. import { toast, alert } from '@/components/dialog/CommonRoot';
  66. import AssessmentContentApi, { AgreementDetail } from '@/api/collect/AssessmentContent';
  67. import FlexCol from '@/components/layout/FlexCol.vue';
  68. import Result from '@/components/feedback/Result.vue';
  69. import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
  70. import Button from '@/components/basic/Button.vue';
  71. import Height from '@/components/layout/space/Height.vue';
  72. import H3 from '@/components/typography/H3.vue';
  73. import Alert from '@/components/feedback/Alert.vue';
  74. import Form, { type FormInstance } from '@/components/form/Form.vue';
  75. import AgreementBodyNational from './components/AgreementBodyNational.vue';
  76. import AgreementBodyProvincial from './components/AgreementBodyProvincial.vue';
  77. import AgreementBodyMunicipal from './components/AgreementBodyMunicipal.vue';
  78. import type { AgreementYmdParts } from './components/AgreementDateWriteBlock.vue';
  79. import type { Rules } from 'async-validator';
  80. import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
  81. import CommonRoot from '@/components/dialog/CommonRoot.vue';
  82. import { useImageSimpleUploadCo } from '@/common/components/upload/ImageUploadCo';
  83. import { injectAppConfiguration } from '@/api/system/useAppConfiguration';
  84. const { querys } = useLoadQuerys({
  85. id: 0,
  86. userId: 0,
  87. }, () => {
  88. loader.load();
  89. });
  90. const authStore = useAuthStore();
  91. const appConfig = injectAppConfiguration();
  92. const currentAgreement = ref<AgreementDetail | null>(null);
  93. /** 甲方盖章日期(页面填写,保存时可拼为字符串提交) */
  94. const partyAStampDate = ref<AgreementYmdParts>({ year: '', month: '', day: '' });
  95. /** 乙方签署日期 */
  96. const partyBSignDate = ref<AgreementYmdParts>({ year: '', month: '', day: '' });
  97. const formRef = ref<FormInstance | null>(null);
  98. const CN_MOBILE_RE = /^1\d{10}$/;
  99. const CN_ID_RE = /^(?:\d{15}|\d{17}[\dXx])$/;
  100. const formRules = computed<Rules>(() => {
  101. const rules: Rules = {
  102. partyB: [{ required: true, message: '请填写乙方(传承人)姓名' }],
  103. apprentice: [
  104. { required: true, type: 'number', message: '请填写本年度带徒人数' },
  105. { type: 'number', min: 0, max: 100, message: '须为不小于 0 的整数' },
  106. ],
  107. activity: [
  108. { required: true, type: 'number', message: '请填写本年度宣传活动场次' },
  109. { type: 'number', min: 0, max: 100, message: '须为不小于 0 的整数' },
  110. ],
  111. partyBSign: [
  112. {
  113. validator(_rule, value, callback) {
  114. const s = typeof value === 'string' ? value.trim() : '';
  115. if (!s)
  116. callback(new Error('请完成乙方签名'));
  117. else
  118. callback();
  119. },
  120. },
  121. ],
  122. idCard: [
  123. { required: true, message: '请填写身份证号' },
  124. {
  125. validator(_rule, value, callback) {
  126. const s = value != null ? String(value).trim() : '';
  127. if (!CN_ID_RE.test(s))
  128. callback(new Error('请输入正确的身份证号'));
  129. else
  130. callback();
  131. },
  132. },
  133. ],
  134. ich: [{ required: true, message: '请填写非遗项目名称' }],
  135. health: [{ required: true, message: '请填写身体状况' }],
  136. mobile: [
  137. { required: true, message: '请填写乙方联系电话' },
  138. {
  139. validator(_rule, value, callback) {
  140. const s = value != null ? String(value).trim() : '';
  141. if (!CN_MOBILE_RE.test(s))
  142. callback(new Error('请输入正确的手机号'));
  143. else
  144. callback();
  145. },
  146. },
  147. ],
  148. };
  149. if (currentAgreement.value?.level !== 25)
  150. rules.course = [
  151. { required: true, type: 'number', message: '请填写本年度研修班场次' },
  152. { type: 'number', min: 0, max: 100, message: '须为不小于 0 的整数' },
  153. ];
  154. return rules;
  155. });
  156. async function loadBasicInfo() {
  157. const basicInfo = await AssessmentContentApi.getInheritorBasic(authStore.userInfo?.id);
  158. assertNotNull(currentAgreement.value, 'currentAgreement is null');
  159. currentAgreement.value.partyB = basicInfo.name;
  160. currentAgreement.value.mobile = basicInfo.mobile;
  161. currentAgreement.value.idCard = basicInfo.idCard;
  162. currentAgreement.value.ich = basicInfo.ichName;
  163. currentAgreement.value.level = basicInfo.level;
  164. }
  165. const uploadAgreementSign = useImageSimpleUploadCo();
  166. const agreementYear = computed(() => currentAgreement.value?.year ?? 2027);
  167. const agreementTitle = computed(
  168. () => `${agreementYear.value} 年度${levelTitle.value}非物质文化遗产代表性传承人传承协议`,
  169. );
  170. const levelTitle = computed(() => {
  171. if (currentAgreement.value?.level === 23) return '国家级';
  172. if (currentAgreement.value?.level === 24) return '省级';
  173. return '市级';
  174. });
  175. const loader = useSimpleDataLoader(async () => {
  176. if (querys.value.id > 0) {
  177. const detail = await AssessmentContentApi.getAgreementDetail(querys.value.id, querys.value.userId);
  178. currentAgreement.value = detail;
  179. partyAStampDate.value = { year: '', month: '', day: '' };
  180. partyBSignDate.value = { year: '', month: '', day: '' };
  181. return currentAgreement.value;
  182. }
  183. const basicInfo = await AssessmentContentApi.getInheritorBasic(authStore.userInfo?.id);
  184. if (basicInfo.agreementId > 0) {
  185. const detail = await AssessmentContentApi.getAgreementDetail(
  186. basicInfo.agreementId,
  187. authStore.userInfo?.id
  188. );
  189. currentAgreement.value = detail;
  190. partyAStampDate.value = { year: '', month: '', day: '' };
  191. partyBSignDate.value = { year: detail.updatetime.getFullYear().toString(), month: (detail.updatetime.getMonth() + 1).toString(), day: detail.updatetime.getDate().toString() };
  192. } else {
  193. currentAgreement.value = null;
  194. }
  195. return currentAgreement.value;
  196. });
  197. const submitLoading = ref(false);
  198. async function createAgreement() {
  199. const now = new Date();
  200. const u = authStore.userInfo;
  201. const nick = u?.nickname || u?.diyname;
  202. const uid = authStore.userInfo?.id ?? authStore.userId;
  203. const basicInfo = await AssessmentContentApi.getInheritorBasic(uid);
  204. const detail = new AgreementDetail();
  205. detail.userId = authStore.userInfo!.id;
  206. detail.year = appConfig.value?.collectSignYear || now.getFullYear() + 1;;
  207. if (basicInfo.level === 24) {
  208. detail.partyA = '厦门市文化和旅游局';
  209. } else if (basicInfo.level === 25) {
  210. detail.partyA = '厦门市文化和旅游局';
  211. } else {
  212. detail.partyA = '福建省文化和旅游厅';
  213. }
  214. detail.partyB = nick ?? '';
  215. console.log(authStore.userInfo, u, nick);
  216. partyAStampDate.value = { year: now.getFullYear().toString(), month: (now.getMonth() + 1).toString(), day: now.getDate().toString() };
  217. partyBSignDate.value = { year: now.getFullYear().toString(), month: (now.getMonth() + 1).toString(), day: now.getDate().toString() };
  218. currentAgreement.value = detail;
  219. loadBasicInfo();
  220. }
  221. async function saveAgreement() {
  222. const detail = currentAgreement.value;
  223. console.log(detail);
  224. try {
  225. await formRef.value?.validate();
  226. } catch (error) {
  227. toast('请填写完整信息');
  228. return;
  229. }
  230. submitLoading.value = true;
  231. try {
  232. assertNotNull(detail, 'currentAgreement is null');
  233. await AssessmentContentApi.saveAgreement(detail as AgreementDetail);
  234. toast('保存传承协议成功');
  235. } catch (error) {
  236. alert({
  237. title: '保存传承协议失败',
  238. content: formatError(error),
  239. });
  240. }
  241. submitLoading.value = false;
  242. }
  243. async function downloadAgreement() {
  244. if (!currentAgreement.value?.id) {
  245. toast('请先保存评估表后再下载PDF');
  246. return;
  247. }
  248. try {
  249. assertNotNull(currentAgreement.value, 'currentForm is null');
  250. const pdfPath = await AssessmentContentApi.downloadAgreementPdf(currentAgreement.value.id);
  251. uni.openDocument({
  252. filePath: pdfPath,
  253. fileType: 'pdf',
  254. showMenu: true,
  255. });
  256. } catch (error) {
  257. alert({
  258. title: '下载评估表失败',
  259. content: formatError(error),
  260. });
  261. }
  262. }
  263. </script>