Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 

285 rindas
7.4 KiB

  1. <template>
  2. <view class="page">
  3. <!-- 协议内容 -->
  4. <view class="section">
  5. <view class="doc-title">{{ docTitle }}</view>
  6. <rich-text class="doc-body" :nodes="docContent" />
  7. </view>
  8. <!-- 收入金额(仅income类型) -->
  9. <view class="section" v-if="signType === 'income'">
  10. <view class="form-label">请填写您的个人年可支配收入(元)</view>
  11. <view class="amount-wrap">
  12. <text class="amount-prefix">¥</text>
  13. <u-input v-model="incomeAmount" type="number" placeholder="请输入金额" border="none" maxlength="7" />
  14. </view>
  15. </view>
  16. <!-- 监护人信息(仅privacy_jhr类型) -->
  17. <view class="section" v-if="signType === 'privacy_jhr'">
  18. <view class="form-label">监护人信息</view>
  19. <view class="form-row">
  20. <text class="row-label">监护人姓名</text>
  21. <u-input v-model="guardianName" placeholder="请输入监护人姓名" border="surround" maxlength="20" />
  22. </view>
  23. <view class="form-row">
  24. <text class="row-label">监护人身份证</text>
  25. <u-input v-model="guardianIdCard" placeholder="请输入监护人身份证号" border="surround" maxlength="18" />
  26. </view>
  27. <view class="form-row">
  28. <text class="row-label">与患者关系</text>
  29. <u-input v-model="guardianRelation" placeholder="如:父亲、母亲" border="surround" maxlength="10" />
  30. </view>
  31. </view>
  32. <!-- 签名区域 -->
  33. <view class="section">
  34. <view class="sign-label">请在下方签名确认</view>
  35. <view class="canvas-wrap" @tap="goSignature">
  36. <image v-if="signImageUrl" class="sign-preview" :src="signImageUrl" mode="aspectFit" />
  37. <view v-else class="canvas-placeholder">
  38. <u-icon name="edit-pen" size="28" color="#ccc" />
  39. <text>点击此处去签名</text>
  40. </view>
  41. </view>
  42. <view v-if="signImageUrl" class="sign-actions">
  43. <view class="clear-btn" @tap="goSignature">重新签名</view>
  44. </view>
  45. </view>
  46. <!-- 底部按钮 -->
  47. <view class="btn-wrap">
  48. <u-button text="确认签署" :loading="submitting" @click="confirmSign" color="#0E63E3" size="large" />
  49. </view>
  50. </view>
  51. </template>
  52. <script setup>
  53. import { ref, onBeforeUnmount } from 'vue'
  54. import { onLoad } from '@dcloudio/uni-app'
  55. import { get, post } from '@/utils/request.js'
  56. const signType = ref('')
  57. const docTitle = ref('')
  58. const docContent = ref('')
  59. const incomeAmount = ref('')
  60. const guardianName = ref('')
  61. const guardianIdCard = ref('')
  62. const guardianRelation = ref('')
  63. const signImageUrl = ref('')
  64. const submitting = ref(false)
  65. const onSignatureResult = (data) => {
  66. if (data.url) signImageUrl.value = data.url
  67. }
  68. onLoad((options) => {
  69. signType.value = options.type || 'privacy'
  70. // 回显上次填写的数据
  71. if (options.amount) incomeAmount.value = decodeURIComponent(options.amount)
  72. if (options.guardianName) guardianName.value = decodeURIComponent(options.guardianName)
  73. if (options.guardianIdCard) guardianIdCard.value = decodeURIComponent(options.guardianIdCard)
  74. if (options.guardianRelation) guardianRelation.value = decodeURIComponent(options.guardianRelation)
  75. uni.$on('signatureResult', onSignatureResult)
  76. loadContent()
  77. })
  78. onBeforeUnmount(() => {
  79. uni.$off('signatureResult', onSignatureResult)
  80. })
  81. const loadContent = async () => {
  82. try {
  83. const key = 'sign_' + signType.value
  84. const res = await get('/api/content', { key })
  85. docTitle.value = res.data.title || ''
  86. docContent.value = res.data.content || ''
  87. } catch (e) {}
  88. }
  89. const goSignature = () => {
  90. uni.navigateTo({ url: '/pages/sign/signature' })
  91. }
  92. const confirmSign = async () => {
  93. if (!signImageUrl.value) {
  94. return uni.showToast({ title: '请先签名', icon: 'none' })
  95. }
  96. if (signType.value === 'income' && (!incomeAmount.value || Number(incomeAmount.value) <= 0)) {
  97. return uni.showToast({ title: '请填写有效的收入金额', icon: 'none' })
  98. }
  99. if (signType.value === 'privacy_jhr') {
  100. if (!guardianName.value.trim()) return uni.showToast({ title: '请输入监护人姓名', icon: 'none' })
  101. if (!guardianIdCard.value.trim()) return uni.showToast({ title: '请输入监护人身份证号', icon: 'none' })
  102. if (!/^\d{17}[\dXx]$/.test(guardianIdCard.value)) return uni.showToast({ title: '监护人身份证格式不正确', icon: 'none' })
  103. if (!guardianRelation.value.trim()) return uni.showToast({ title: '请输入与患者关系', icon: 'none' })
  104. }
  105. submitting.value = true
  106. try {
  107. const params = {
  108. type: signType.value,
  109. signImage: signImageUrl.value,
  110. amount: signType.value === 'income' ? incomeAmount.value : undefined,
  111. guardianName: signType.value === 'privacy_jhr' ? guardianName.value.trim() : undefined,
  112. guardianIdCard: signType.value === 'privacy_jhr' ? guardianIdCard.value.trim() : undefined,
  113. guardianRelation: signType.value === 'privacy_jhr' ? guardianRelation.value.trim() : undefined
  114. }
  115. const res = await post('/api/mp/sign', params)
  116. uni.$emit('signResult', {
  117. type: signType.value,
  118. url: res.data.url,
  119. amount: signType.value === 'income' ? incomeAmount.value : undefined,
  120. guardianName: signType.value === 'privacy_jhr' ? guardianName.value.trim() : undefined,
  121. guardianIdCard: signType.value === 'privacy_jhr' ? guardianIdCard.value.trim() : undefined,
  122. guardianRelation: signType.value === 'privacy_jhr' ? guardianRelation.value.trim() : undefined
  123. })
  124. uni.showToast({ title: '签署成功', icon: 'success' })
  125. setTimeout(() => uni.navigateBack(), 1000)
  126. } catch (e) {
  127. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  128. } finally {
  129. submitting.value = false
  130. }
  131. }
  132. </script>
  133. <style lang="scss" scoped>
  134. .page {
  135. min-height: 100vh;
  136. background: #f4f4f5;
  137. padding: 24rpx;
  138. padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  139. }
  140. .section {
  141. background: #fff;
  142. margin-bottom: 24rpx;
  143. border-radius: 10rpx;
  144. padding: 32rpx;
  145. border: 1rpx solid #ebeef5;
  146. }
  147. .doc-title {
  148. font-size: 32rpx;
  149. font-weight: 600;
  150. text-align: center;
  151. margin-bottom: 32rpx;
  152. color: #222;
  153. }
  154. .doc-body {
  155. font-size: 28rpx;
  156. color: #555;
  157. line-height: 1.8;
  158. }
  159. .form-label {
  160. font-size: 28rpx;
  161. color: #333;
  162. font-weight: 600;
  163. margin-bottom: 16rpx;
  164. }
  165. .amount-wrap {
  166. display: flex;
  167. align-items: center;
  168. border: 1rpx solid #ddd;
  169. border-radius: 12rpx;
  170. overflow: hidden;
  171. gap: 12rpx;
  172. }
  173. .amount-prefix {
  174. padding: 20rpx 24rpx;
  175. font-size: 28rpx;
  176. color: #999;
  177. background: #f8f8f8;
  178. }
  179. .form-row {
  180. margin-bottom: 20rpx;
  181. &:last-child {
  182. margin-bottom: 0;
  183. }
  184. }
  185. .row-label {
  186. font-size: 26rpx;
  187. color: #555;
  188. margin-bottom: 12rpx;
  189. display: block;
  190. }
  191. .sign-label {
  192. font-size: 28rpx;
  193. color: #333;
  194. font-weight: 600;
  195. margin-bottom: 16rpx;
  196. }
  197. .canvas-wrap {
  198. position: relative;
  199. border: 2rpx dashed #ddd;
  200. border-radius: 12rpx;
  201. overflow: hidden;
  202. min-height: 300rpx;
  203. display: flex;
  204. align-items: center;
  205. justify-content: center;
  206. background: #fafafa;
  207. }
  208. .sign-preview {
  209. width: 100%;
  210. height: 300rpx;
  211. }
  212. .canvas-placeholder {
  213. display: flex;
  214. flex-direction: column;
  215. align-items: center;
  216. gap: 12rpx;
  217. padding: 40rpx 0;
  218. text {
  219. color: #ccc;
  220. font-size: 28rpx;
  221. }
  222. }
  223. .sign-actions {
  224. display: flex;
  225. justify-content: flex-end;
  226. margin-top: 16rpx;
  227. }
  228. .clear-btn {
  229. padding: 12rpx 32rpx;
  230. border: 1rpx solid #ccc;
  231. border-radius: 12rpx;
  232. font-size: 26rpx;
  233. color: #666;
  234. &:active {
  235. border-color: #0e63e3;
  236. color: #0e63e3;
  237. }
  238. }
  239. .btn-wrap {
  240. position: fixed;
  241. bottom: 0;
  242. left: 0;
  243. right: 0;
  244. background: #fff;
  245. padding: 20rpx 32rpx;
  246. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  247. box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
  248. z-index: 100;
  249. }
  250. </style>