index.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <template>
  2. <view class="agreement-container">
  3. <view class="agreement-list">
  4. <view
  5. v-for="item in staticAgreementList"
  6. :key="item.path"
  7. class="agreement-card"
  8. hover-class="agreement-card--active"
  9. @click="navigateToAgreementPage(item.path)"
  10. >
  11. <text class="agreement-card__title">《{{ item.name }}》</text>
  12. <text class="agreement-card__arrow">›</text>
  13. </view>
  14. <view
  15. v-for="item in certSuccessList"
  16. :key="item.templateUrl"
  17. class="agreement-card"
  18. hover-class="agreement-card--active"
  19. @click="handlePreviewAgreement(item)"
  20. >
  21. <text class="agreement-card__title">《{{ item.name }}》</text>
  22. <text class="agreement-card__arrow">›</text>
  23. </view>
  24. </view>
  25. </view>
  26. </template>
  27. <script setup lang="ts">
  28. import { computed, ref } from 'vue'
  29. import { onLoad } from '@dcloudio/uni-app'
  30. import { getSubjectLocationAgreementApi } from '@/services/modules/mine/common'
  31. import type { agreementInfosItem } from '@/services/modules/mine/common/type'
  32. import { useUserStore } from '@/stores/modules/user'
  33. import { previewAgreementPdf } from '@/utils/agreementPdfPreview'
  34. interface StaticAgreementItem {
  35. name: string
  36. path: string
  37. }
  38. interface AgreementItem {
  39. name: string
  40. templateUrl: string
  41. subjectLocation: string
  42. }
  43. const AGREEMENT_PAGE_BASE = '/pages-mine/agreement'
  44. const CERT_SUCCESS_STATUS = 'CERT'
  45. const WEB_VIEW_AGREEMENT_SUBJECT_LOCATION = 'CLOUD_ACCOUNT_LXLW'
  46. const WEB_VIEW_AGREEMENT_PAGE = `${AGREEMENT_PAGE_BASE}/web-view`
  47. const userStore = useUserStore()
  48. const currentUserInfo = computed(() => userStore.currentUserInfo)
  49. const certSuccessList = ref<AgreementItem[]>([])
  50. const hasSignedAgreement = (agreementCode: string): boolean => {
  51. const signedAgreement = currentUserInfo.value?.signedAgreement ?? []
  52. return Array.isArray(signedAgreement) && signedAgreement.includes(agreementCode)
  53. }
  54. const showHonestAgreement = computed(() => {
  55. return hasSignedAgreement('HONEST_AGREEMENT')
  56. })
  57. const showHonestAgreementV2 = computed(() => {
  58. return hasSignedAgreement('HONEST_AGREEMENT_V2')
  59. })
  60. const staticAgreementList = computed<StaticAgreementItem[]>(() => {
  61. const list: StaticAgreementItem[] = [
  62. {
  63. name: '要易云平台用户协议',
  64. path: 'platform-user-agreement',
  65. },
  66. {
  67. name: '隐私权政策',
  68. path: 'privacy-policy',
  69. },
  70. {
  71. name: '个人信息使用授权书',
  72. path: 'personal-info-authorization',
  73. },
  74. ]
  75. if (showHonestAgreement.value) {
  76. list.push({
  77. name: '廉洁承诺书',
  78. path: 'integrity-commitment',
  79. })
  80. }
  81. if (showHonestAgreementV2.value) {
  82. list.push({
  83. name: '承诺书2.0',
  84. path: 'integrity-commitment-v2',
  85. })
  86. }
  87. return list
  88. })
  89. const getCertifiedSubjectLocations = (): string[] => {
  90. const certList = currentUserInfo.value?.certList ?? []
  91. const subjectLocations = certList
  92. .filter((item) => item.commonCertStatus === CERT_SUCCESS_STATUS)
  93. .map((item) => item.subjectLocation?.trim())
  94. .filter((subjectLocation): subjectLocation is string => Boolean(subjectLocation))
  95. return Array.from(new Set(subjectLocations))
  96. }
  97. const normalizeAgreementInfos = (
  98. list: agreementInfosItem[] = [],
  99. subjectLocation: string
  100. ): AgreementItem[] => {
  101. return list
  102. .filter((item) => Boolean(item.templateUrl?.trim()))
  103. .map((item) => {
  104. return {
  105. name: item.name?.trim() || '协议',
  106. templateUrl: item.templateUrl.trim(),
  107. subjectLocation,
  108. }
  109. })
  110. }
  111. const dedupeAgreementInfos = (list: AgreementItem[]): AgreementItem[] => {
  112. const map = new Map<string, AgreementItem>()
  113. list.forEach((item) => {
  114. if (!map.has(item.templateUrl)) {
  115. map.set(item.templateUrl, item)
  116. }
  117. })
  118. return Array.from(map.values())
  119. }
  120. const getCertSuccessList = async (): Promise<void> => {
  121. const subjectLocations = getCertifiedSubjectLocations()
  122. if (!subjectLocations.length) {
  123. certSuccessList.value = []
  124. return
  125. }
  126. uni.showLoading({
  127. title: '加载中...',
  128. mask: true,
  129. })
  130. try {
  131. const agreementInfoGroups = await Promise.all(
  132. subjectLocations.map(async (subjectLocation) => {
  133. try {
  134. const res = await getSubjectLocationAgreementApi({
  135. subjectLocation,
  136. })
  137. return normalizeAgreementInfos(res.data?.agreementInfos, subjectLocation)
  138. } catch (error) {
  139. console.log('getSubjectLocationAgreementApi error:', error)
  140. return []
  141. }
  142. })
  143. )
  144. certSuccessList.value = dedupeAgreementInfos(agreementInfoGroups.flat())
  145. } finally {
  146. uni.hideLoading()
  147. }
  148. }
  149. const navigateToAgreementPage = (path: string): void => {
  150. uni.navigateTo({
  151. url: `${AGREEMENT_PAGE_BASE}/${path}`,
  152. })
  153. }
  154. const handlePreviewAgreement = (item: AgreementItem): void => {
  155. if (item.subjectLocation === WEB_VIEW_AGREEMENT_SUBJECT_LOCATION) {
  156. uni.navigateTo({
  157. url: `${WEB_VIEW_AGREEMENT_PAGE}?url=${encodeURIComponent(item.templateUrl)}`,
  158. })
  159. return
  160. }
  161. void previewAgreementPdf({
  162. url: item.templateUrl,
  163. })
  164. }
  165. onLoad(() => {
  166. void getCertSuccessList()
  167. })
  168. </script>
  169. <style lang="scss" scoped>
  170. .agreement-container {
  171. min-height: 100vh;
  172. padding: 28rpx 24rpx 40rpx;
  173. box-sizing: border-box;
  174. background:
  175. radial-gradient(
  176. circle at 50% -120rpx,
  177. rgba(47, 140, 255, 0.14) 0%,
  178. rgba(47, 140, 255, 0) 420rpx
  179. ),
  180. linear-gradient(180deg, #f6f8fc 0%, #f7f8fa 100%);
  181. }
  182. .agreement-list {
  183. display: flex;
  184. flex-direction: column;
  185. gap: 18rpx;
  186. }
  187. .agreement-card {
  188. position: relative;
  189. display: flex;
  190. align-items: center;
  191. min-height: 112rpx;
  192. padding: 0 28rpx 0 32rpx;
  193. box-sizing: border-box;
  194. border: 1rpx solid rgba(31, 41, 55, 0.06);
  195. border-radius: 24rpx;
  196. background:
  197. linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(255, 255, 255, 0.9) 100%), #ffffff;
  198. box-shadow:
  199. 0 16rpx 40rpx rgba(15, 23, 42, 0.05),
  200. 0 2rpx 8rpx rgba(15, 23, 42, 0.03);
  201. overflow: hidden;
  202. transition:
  203. transform 0.18s ease,
  204. opacity 0.18s ease;
  205. }
  206. .agreement-card--active {
  207. opacity: 0.86;
  208. transform: scale(0.985);
  209. }
  210. .agreement-card__title {
  211. flex: 1;
  212. min-width: 0;
  213. padding-right: 24rpx;
  214. font-size: 31rpx;
  215. font-weight: 600;
  216. line-height: 44rpx;
  217. color: #1f2937;
  218. letter-spacing: 0.2rpx;
  219. overflow: hidden;
  220. text-overflow: ellipsis;
  221. white-space: nowrap;
  222. }
  223. .agreement-card__arrow {
  224. flex-shrink: 0;
  225. font-size: 48rpx;
  226. font-weight: 300;
  227. line-height: 1;
  228. color: #aeb8c6;
  229. }
  230. </style>