You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

233 lines
4.9 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" />
  14. </view>
  15. </view>
  16. <!-- 签名区域 -->
  17. <view class="section">
  18. <view class="sign-label">请在下方签名确认</view>
  19. <view class="canvas-wrap" @tap="goSignature">
  20. <image v-if="signImageUrl" class="sign-preview" :src="signImageUrl" mode="aspectFit" />
  21. <view v-else class="canvas-placeholder">
  22. <u-icon name="edit-pen" size="28" color="#ccc" />
  23. <text>点击此处去签名</text>
  24. </view>
  25. </view>
  26. <view v-if="signImageUrl" class="sign-actions">
  27. <view class="clear-btn" @tap="goSignature">重新签名</view>
  28. </view>
  29. </view>
  30. <!-- 底部按钮 -->
  31. <view class="btn-wrap">
  32. <u-button text="确认签署" :loading="submitting" @click="confirmSign" color="#0E63E3" size="large" />
  33. </view>
  34. </view>
  35. </template>
  36. <script setup>
  37. import { ref, onBeforeUnmount } from 'vue'
  38. import { onLoad } from '@dcloudio/uni-app'
  39. import { get, post } from '@/utils/request.js'
  40. const signType = ref('')
  41. const docTitle = ref('')
  42. const docContent = ref('')
  43. const incomeAmount = ref('')
  44. const signImageUrl = ref('')
  45. const submitting = ref(false)
  46. const onSignatureResult = (data) => {
  47. if (data.url) signImageUrl.value = data.url
  48. }
  49. onLoad((options) => {
  50. signType.value = options.type || 'privacy'
  51. uni.$on('signatureResult', onSignatureResult)
  52. loadContent()
  53. })
  54. onBeforeUnmount(() => {
  55. uni.$off('signatureResult', onSignatureResult)
  56. })
  57. const loadContent = async () => {
  58. try {
  59. const key = 'sign_' + signType.value
  60. const res = await get('/api/content', { key })
  61. docTitle.value = res.data.title || ''
  62. docContent.value = res.data.content || ''
  63. } catch (e) {}
  64. }
  65. const goSignature = () => {
  66. uni.navigateTo({ url: '/pages/sign/signature' })
  67. }
  68. const confirmSign = async () => {
  69. if (!signImageUrl.value) {
  70. return uni.showToast({ title: '请先签名', icon: 'none' })
  71. }
  72. if (signType.value === 'income' && (!incomeAmount.value || Number(incomeAmount.value) <= 0)) {
  73. return uni.showToast({ title: '请填写有效的收入金额', icon: 'none' })
  74. }
  75. submitting.value = true
  76. try {
  77. const params = {
  78. type: signType.value,
  79. signImage: signImageUrl.value,
  80. amount: signType.value === 'income' ? incomeAmount.value : undefined
  81. }
  82. const res = await post('/api/mp/sign', params)
  83. uni.$emit('signResult', {
  84. type: signType.value,
  85. url: res.data.url,
  86. amount: signType.value === 'income' ? incomeAmount.value : undefined
  87. })
  88. uni.showToast({ title: '签署成功', icon: 'success' })
  89. setTimeout(() => uni.navigateBack(), 1000)
  90. } catch (e) {
  91. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  92. } finally {
  93. submitting.value = false
  94. }
  95. }
  96. </script>
  97. <style lang="scss" scoped>
  98. .page {
  99. min-height: 100vh;
  100. background: #f4f4f5;
  101. padding: 24rpx;
  102. padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
  103. }
  104. .section {
  105. background: #fff;
  106. margin-bottom: 24rpx;
  107. border-radius: 10rpx;
  108. padding: 32rpx;
  109. border: 1rpx solid #ebeef5;
  110. }
  111. .doc-title {
  112. font-size: 32rpx;
  113. font-weight: 600;
  114. text-align: center;
  115. margin-bottom: 32rpx;
  116. color: #222;
  117. }
  118. .doc-body {
  119. font-size: 28rpx;
  120. color: #555;
  121. line-height: 1.8;
  122. }
  123. .form-label {
  124. font-size: 28rpx;
  125. color: #333;
  126. font-weight: 600;
  127. margin-bottom: 16rpx;
  128. }
  129. .amount-wrap {
  130. display: flex;
  131. align-items: center;
  132. border: 1rpx solid #ddd;
  133. border-radius: 12rpx;
  134. overflow: hidden;
  135. gap: 12rpx;
  136. }
  137. .amount-prefix {
  138. padding: 20rpx 24rpx;
  139. font-size: 28rpx;
  140. color: #999;
  141. background: #f8f8f8;
  142. }
  143. .sign-label {
  144. font-size: 28rpx;
  145. color: #333;
  146. font-weight: 600;
  147. margin-bottom: 16rpx;
  148. }
  149. .canvas-wrap {
  150. position: relative;
  151. border: 2rpx dashed #ddd;
  152. border-radius: 12rpx;
  153. overflow: hidden;
  154. min-height: 300rpx;
  155. display: flex;
  156. align-items: center;
  157. justify-content: center;
  158. background: #fafafa;
  159. }
  160. .sign-preview {
  161. width: 100%;
  162. height: 300rpx;
  163. }
  164. .canvas-placeholder {
  165. display: flex;
  166. flex-direction: column;
  167. align-items: center;
  168. gap: 12rpx;
  169. padding: 40rpx 0;
  170. text {
  171. color: #ccc;
  172. font-size: 28rpx;
  173. }
  174. }
  175. .sign-actions {
  176. display: flex;
  177. justify-content: flex-end;
  178. margin-top: 16rpx;
  179. }
  180. .clear-btn {
  181. padding: 12rpx 32rpx;
  182. border: 1rpx solid #ccc;
  183. border-radius: 12rpx;
  184. font-size: 26rpx;
  185. color: #666;
  186. &:active {
  187. border-color: #0e63e3;
  188. color: #0e63e3;
  189. }
  190. }
  191. .btn-wrap {
  192. position: fixed;
  193. bottom: 0;
  194. left: 0;
  195. right: 0;
  196. background: #fff;
  197. padding: 20rpx 32rpx;
  198. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  199. box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
  200. z-index: 100;
  201. }
  202. </style>