Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 

307 Zeilen
7.8 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="openSignature">
  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="openSignature">重新签名</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. <!-- #ifdef H5 -->
  51. <h5-signature :show="showH5Sign" @close="showH5Sign = false" @confirm="onH5SignConfirm" />
  52. <!-- #endif -->
  53. </view>
  54. </template>
  55. <script setup>
  56. import { ref, onBeforeUnmount } from 'vue'
  57. import { onLoad } from '@dcloudio/uni-app'
  58. import { get, post } from '@/utils/request.js'
  59. const signType = ref('')
  60. const docTitle = ref('')
  61. const docContent = ref('')
  62. const incomeAmount = ref('')
  63. const guardianName = ref('')
  64. const guardianIdCard = ref('')
  65. const guardianRelation = ref('')
  66. const signImageUrl = ref('')
  67. const submitting = ref(false)
  68. // #ifdef H5
  69. const showH5Sign = ref(false)
  70. // #endif
  71. const onSignatureResult = (data) => {
  72. if (data.url) signImageUrl.value = data.url
  73. }
  74. onLoad((options) => {
  75. signType.value = options.type || 'privacy'
  76. if (options.amount) incomeAmount.value = decodeURIComponent(options.amount)
  77. if (options.guardianName) guardianName.value = decodeURIComponent(options.guardianName)
  78. if (options.guardianIdCard) guardianIdCard.value = decodeURIComponent(options.guardianIdCard)
  79. if (options.guardianRelation) guardianRelation.value = decodeURIComponent(options.guardianRelation)
  80. // #ifdef MP-WEIXIN
  81. uni.$on('signatureResult', onSignatureResult)
  82. // #endif
  83. loadContent()
  84. })
  85. onBeforeUnmount(() => {
  86. // #ifdef MP-WEIXIN
  87. uni.$off('signatureResult', onSignatureResult)
  88. // #endif
  89. })
  90. const loadContent = async () => {
  91. try {
  92. const key = 'sign_' + signType.value
  93. const res = await get('/api/content', { key })
  94. docTitle.value = res.data.title || ''
  95. docContent.value = res.data.content || ''
  96. } catch (e) {}
  97. }
  98. const openSignature = () => {
  99. // #ifdef MP-WEIXIN
  100. uni.navigateTo({ url: '/pages/sign/signature' })
  101. // #endif
  102. // #ifdef H5
  103. showH5Sign.value = true
  104. // #endif
  105. }
  106. // #ifdef H5
  107. const onH5SignConfirm = (data) => {
  108. showH5Sign.value = false
  109. if (data.url) signImageUrl.value = data.url
  110. }
  111. // #endif
  112. const confirmSign = async () => {
  113. if (!signImageUrl.value) {
  114. return uni.showToast({ title: '请先签名', icon: 'none' })
  115. }
  116. if (signType.value === 'income' && (!incomeAmount.value || Number(incomeAmount.value) <= 0)) {
  117. return uni.showToast({ title: '请填写有效的收入金额', icon: 'none' })
  118. }
  119. if (signType.value === 'privacy_jhr') {
  120. if (!guardianName.value.trim()) return uni.showToast({ title: '请输入监护人姓名', icon: 'none' })
  121. if (!guardianIdCard.value.trim()) return uni.showToast({ title: '请输入监护人身份证号', icon: 'none' })
  122. if (!/^\d{17}[\dXx]$/.test(guardianIdCard.value)) return uni.showToast({ title: '监护人身份证格式不正确', icon: 'none' })
  123. if (!guardianRelation.value.trim()) return uni.showToast({ title: '请输入与患者关系', icon: 'none' })
  124. }
  125. submitting.value = true
  126. try {
  127. const params = {
  128. type: signType.value,
  129. signImage: signImageUrl.value,
  130. amount: signType.value === 'income' ? incomeAmount.value : undefined,
  131. guardianName: signType.value === 'privacy_jhr' ? guardianName.value.trim() : undefined,
  132. guardianIdCard: signType.value === 'privacy_jhr' ? guardianIdCard.value.trim() : undefined,
  133. guardianRelation: signType.value === 'privacy_jhr' ? guardianRelation.value.trim() : undefined
  134. }
  135. const res = await post('/api/mp/sign', params)
  136. uni.$emit('signResult', {
  137. type: signType.value,
  138. url: res.data.url,
  139. amount: signType.value === 'income' ? incomeAmount.value : undefined,
  140. guardianName: signType.value === 'privacy_jhr' ? guardianName.value.trim() : undefined,
  141. guardianIdCard: signType.value === 'privacy_jhr' ? guardianIdCard.value.trim() : undefined,
  142. guardianRelation: signType.value === 'privacy_jhr' ? guardianRelation.value.trim() : undefined
  143. })
  144. uni.showToast({ title: '签署成功', icon: 'success' })
  145. setTimeout(() => uni.navigateBack(), 1000)
  146. } catch (e) {
  147. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  148. } finally {
  149. submitting.value = false
  150. }
  151. }
  152. </script>
  153. <style lang="scss" scoped>
  154. .page {
  155. min-height: 100vh;
  156. background: #f4f4f5;
  157. padding: 24rpx;
  158. padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  159. }
  160. .section {
  161. background: #fff;
  162. margin-bottom: 24rpx;
  163. border-radius: 10rpx;
  164. padding: 32rpx;
  165. border: 1rpx solid #ebeef5;
  166. }
  167. .doc-title {
  168. font-size: 32rpx;
  169. font-weight: 600;
  170. text-align: center;
  171. margin-bottom: 32rpx;
  172. color: #222;
  173. }
  174. .doc-body {
  175. font-size: 28rpx;
  176. color: #555;
  177. line-height: 1.8;
  178. }
  179. .form-label {
  180. font-size: 28rpx;
  181. color: #333;
  182. font-weight: 600;
  183. margin-bottom: 16rpx;
  184. }
  185. .amount-wrap {
  186. display: flex;
  187. align-items: center;
  188. border: 1rpx solid #ddd;
  189. border-radius: 12rpx;
  190. overflow: hidden;
  191. gap: 12rpx;
  192. }
  193. .amount-prefix {
  194. padding: 20rpx 24rpx;
  195. font-size: 28rpx;
  196. color: #999;
  197. background: #f8f8f8;
  198. }
  199. .form-row {
  200. margin-bottom: 20rpx;
  201. &:last-child {
  202. margin-bottom: 0;
  203. }
  204. }
  205. .row-label {
  206. font-size: 26rpx;
  207. color: #555;
  208. margin-bottom: 12rpx;
  209. display: block;
  210. }
  211. .sign-label {
  212. font-size: 28rpx;
  213. color: #333;
  214. font-weight: 600;
  215. margin-bottom: 16rpx;
  216. }
  217. .canvas-wrap {
  218. position: relative;
  219. border: 2rpx dashed #ddd;
  220. border-radius: 12rpx;
  221. overflow: hidden;
  222. min-height: 300rpx;
  223. display: flex;
  224. align-items: center;
  225. justify-content: center;
  226. background: #fafafa;
  227. }
  228. .sign-preview {
  229. width: 100%;
  230. height: 300rpx;
  231. }
  232. .canvas-placeholder {
  233. display: flex;
  234. flex-direction: column;
  235. align-items: center;
  236. gap: 12rpx;
  237. padding: 40rpx 0;
  238. text {
  239. color: #ccc;
  240. font-size: 28rpx;
  241. }
  242. }
  243. .sign-actions {
  244. display: flex;
  245. justify-content: flex-end;
  246. margin-top: 16rpx;
  247. }
  248. .clear-btn {
  249. padding: 12rpx 32rpx;
  250. border: 1rpx solid #ccc;
  251. border-radius: 12rpx;
  252. font-size: 26rpx;
  253. color: #666;
  254. &:active {
  255. border-color: #0e63e3;
  256. color: #0e63e3;
  257. }
  258. }
  259. .btn-wrap {
  260. position: fixed;
  261. bottom: 0;
  262. left: 0;
  263. right: 0;
  264. background: #fff;
  265. padding: 20rpx 32rpx;
  266. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  267. box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
  268. z-index: 100;
  269. }
  270. </style>