巴青农资商城

entry-apply.vue 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <view class="entry-apply-wrap">
  3. <safe-nav-bar title="商家入驻" />
  4. <view class="entry-apply-page">
  5. <view v-if="!entryOpen" class="entry-closed">
  6. <text>{{ agreement.message || '商家入驻暂未开放' }}</text>
  7. </view>
  8. <view v-else-if="blocked" class="entry-closed">
  9. <text>您有待审核或公示中的申请,请先在「我的入驻申请」查看进度</text>
  10. <button class="mine-btn-outline entry-closed__btn" @click="goList">查看申请</button>
  11. </view>
  12. <template v-else>
  13. <view class="entry-steps">
  14. <text
  15. v-for="(s, i) in stepTitles"
  16. :key="s"
  17. :class="['entry-steps__item', { 'entry-steps__item--on': stepIndex === i }]"
  18. >{{ s }}</text>
  19. </view>
  20. <scroll-view class="entry-scroll" scroll-y>
  21. <!-- 主体类型 -->
  22. <view v-if="currentStep === 'type'" class="form-card">
  23. <view
  24. :class="['type-card', { 'type-card--on': form.merchantType === '1' }]"
  25. @click="selectType('1')"
  26. >
  27. <text class="type-card__title">个人入驻</text>
  28. <text class="type-card__sub">身份证 + 个人账户</text>
  29. </view>
  30. <view
  31. :class="['type-card', { 'type-card--on': form.merchantType === '2' }]"
  32. @click="selectType('2')"
  33. >
  34. <text class="type-card__title">企业入驻</text>
  35. <text class="type-card__sub">法人 + 企业对公账户</text>
  36. </view>
  37. </view>
  38. <!-- 个人:主体信息 -->
  39. <view v-else-if="isPerson && currentStep === 'subject'" class="form-card">
  40. <entry-person-subject :subject="form.subject" />
  41. </view>
  42. <view v-else-if="isPerson && currentStep === 'biz'" class="form-card">
  43. <entry-person-biz :biz="form.biz" :region="regionBiz" @update:region="onBizRegion" />
  44. </view>
  45. <!-- 企业 -->
  46. <view v-else-if="!isPerson && currentStep === 'subject'" class="form-card">
  47. <entry-enterprise-subject
  48. :subject="form.subject"
  49. :region-reg="regionReg"
  50. @update:region-reg="onRegRegion"
  51. />
  52. </view>
  53. <view v-else-if="!isPerson && currentStep === 'biz'" class="form-card">
  54. <entry-enterprise-biz :biz="form.biz" :region="regionBiz" @update:region="onBizRegion" />
  55. </view>
  56. <!-- 店铺 -->
  57. <view v-else-if="currentStep === 'shop'" class="form-card">
  58. <entry-shop-fields :shop="form.shop" />
  59. </view>
  60. <!-- 提交 -->
  61. <view v-else-if="currentStep === 'submit'" class="form-card">
  62. <text class="submit-tip">请确认信息无误后勾选协议并提交</text>
  63. <agreement-block
  64. v-model="form.agreementAccepted"
  65. :enabled="agreement.enabled"
  66. :checkbox-label="agreement.checkboxLabel"
  67. :agreement-title="agreement.agreementTitle"
  68. :version-label="agreement.versionLabel"
  69. :content="agreement.content"
  70. />
  71. </view>
  72. </scroll-view>
  73. <view class="entry-actions">
  74. <button v-if="stepIndex > 0" class="mine-btn-outline entry-actions__prev" @click="prevStep">
  75. 上一步
  76. </button>
  77. <button class="form-footer__btn entry-actions__next" :disabled="submitting" @click="nextStep">
  78. {{ nextBtnText }}
  79. </button>
  80. </view>
  81. </template>
  82. </view>
  83. </view>
  84. </template>
  85. <script setup>
  86. import { ref, reactive, computed } from 'vue'
  87. import SafeNavBar from '@/components/common/SafeNavBar.vue'
  88. import { onLoad } from '@dcloudio/uni-app'
  89. import { getEntryAgreement, getEntryStatus, getMyEntryApplies, submitEntryApply } from '@/api/merchantEntry'
  90. import { ensureApiToken } from '@/utils/apiAuth'
  91. import { hasBlockingApply } from '@/utils/entryConstants'
  92. import { createEntryForm, validateEntryStep, buildEntrySubmitPayload, applyRegion, applyRegRegion } from '@/utils/entryForm'
  93. import { MERCHANT_TYPE_PERSON } from '@/utils/entryConstants'
  94. import AgreementBlock from '@/components/account/AgreementBlock.vue'
  95. import EntryPersonSubject from '@/components/mine/entry/EntryPersonSubject.vue'
  96. import EntryPersonBiz from '@/components/mine/entry/EntryPersonBiz.vue'
  97. import EntryEnterpriseSubject from '@/components/mine/entry/EntryEnterpriseSubject.vue'
  98. import EntryEnterpriseBiz from '@/components/mine/entry/EntryEnterpriseBiz.vue'
  99. import EntryShopFields from '@/components/mine/entry/EntryShopFields.vue'
  100. import { PAGE_ENTRY_LIST, PAGE_PROFILE } from '@/utils/pageRoute'
  101. import { useUserStore } from '@/store/user'
  102. import { useActionGuard } from '@/utils/actionGuard'
  103. const submitGuard = useActionGuard()
  104. const submitting = submitGuard.locked
  105. const form = reactive(createEntryForm(MERCHANT_TYPE_PERSON))
  106. const agreement = reactive({
  107. enabled: false,
  108. message: '',
  109. agreementTitle: '',
  110. versionLabel: '',
  111. content: '',
  112. checkboxLabel: '我已阅读并同意《商城入驻协议》'
  113. })
  114. const stepIndex = ref(0)
  115. const entryOpen = ref(true)
  116. const blocked = ref(false)
  117. const regionBiz = ref({ regionCode: '', regionName: '', pathCodes: [] })
  118. const regionReg = ref({ regionCode: '', regionName: '', pathCodes: [] })
  119. const userStore = useUserStore()
  120. const stepKeys = computed(() => ['type', 'subject', 'biz', 'shop', 'submit'])
  121. const stepTitles = ['类型', '主体', '经营', '店铺', '提交']
  122. const currentStep = computed(() => stepKeys.value[stepIndex.value])
  123. const isPerson = computed(() => form.merchantType === MERCHANT_TYPE_PERSON)
  124. const nextBtnText = computed(() => {
  125. if (submitting.value) return '提交中…'
  126. return currentStep.value === 'submit' ? '提交申请' : '下一步'
  127. })
  128. onLoad(() => {
  129. if (!ensureApiToken()) return
  130. initPage()
  131. })
  132. function initPage() {
  133. Promise.all([getEntryAgreement(), getEntryStatus(), getMyEntryApplies()])
  134. .then(([agRes, stRes, myRes]) => {
  135. const ag = agRes.data || {}
  136. agreement.enabled = !!ag.enabled
  137. agreement.message = ag.message || ''
  138. agreement.agreementTitle = ag.agreementTitle || '商城入驻协议'
  139. agreement.versionLabel = ag.versionLabel || ''
  140. agreement.content = ag.content || ''
  141. agreement.checkboxLabel = ag.checkboxLabel || agreement.checkboxLabel
  142. entryOpen.value = stRes.data?.entryOpen !== false && agreement.enabled
  143. blocked.value = hasBlockingApply(myRes.data || [])
  144. if (entryOpen.value && !blocked.value) {
  145. ensureMemberNickName()
  146. }
  147. })
  148. .catch(() => {})
  149. }
  150. /** 入驻须会员昵称非空(经营账号登录名) */
  151. function ensureMemberNickName() {
  152. const cached = (userStore.state.nickName || '').trim()
  153. if (cached) return Promise.resolve(true)
  154. return userStore.fetchUserInfo().then(() => {
  155. const nick = (userStore.state.nickName || '').trim()
  156. if (nick) return true
  157. uni.showModal({
  158. title: '提示',
  159. content: '请先完善个人资料中的昵称,方可提交入驻申请',
  160. confirmText: '去完善',
  161. success: (r) => {
  162. if (r.confirm) uni.navigateTo({ url: PAGE_PROFILE })
  163. }
  164. })
  165. return false
  166. })
  167. .catch(() => false)
  168. }
  169. function selectType(type) {
  170. form.merchantType = type
  171. const next = createEntryForm(type)
  172. form.subject = next.subject
  173. form.biz = next.biz
  174. form.shop = next.shop
  175. form.agreementAccepted = false
  176. regionBiz.value = { regionCode: '', regionName: '', pathCodes: [] }
  177. regionReg.value = { regionCode: '', regionName: '', pathCodes: [] }
  178. }
  179. function onBizRegion(v) {
  180. regionBiz.value = v
  181. applyRegion(form.biz, v)
  182. }
  183. function onRegRegion(v) {
  184. regionReg.value = v
  185. applyRegRegion(form.subject, v)
  186. }
  187. function prevStep() {
  188. if (stepIndex.value > 0) stepIndex.value -= 1
  189. }
  190. function nextStep() {
  191. const key = currentStep.value
  192. const err = validateEntryStep(form, key)
  193. if (err) {
  194. uni.showToast({ title: err, icon: 'none' })
  195. return
  196. }
  197. if (key === 'submit') {
  198. doSubmit()
  199. return
  200. }
  201. if (stepIndex.value < stepKeys.value.length - 1) {
  202. stepIndex.value += 1
  203. }
  204. }
  205. function doSubmit() {
  206. const nick = (userStore.state.nickName || '').trim()
  207. if (!nick) {
  208. ensureMemberNickName()
  209. return
  210. }
  211. uni.showModal({
  212. title: '确认提交',
  213. content: '提交后不可修改,是否确认?',
  214. success: (res) => {
  215. if (!res.confirm) return
  216. submitGuard.run(async () => {
  217. await submitEntryApply(buildEntrySubmitPayload(form))
  218. uni.showToast({ title: '提交成功,请等待审核', icon: 'none', duration: 2500 })
  219. setTimeout(() => {
  220. uni.redirectTo({ url: PAGE_ENTRY_LIST })
  221. }, 1500)
  222. })
  223. }
  224. })
  225. }
  226. function goList() {
  227. uni.navigateTo({ url: PAGE_ENTRY_LIST })
  228. }
  229. </script>
  230. <style lang="scss" scoped>
  231. @import '@/styles/mine.scss';
  232. .entry-apply-wrap {
  233. min-height: 100vh;
  234. display: flex;
  235. flex-direction: column;
  236. background: #f0ebe5;
  237. }
  238. .entry-apply-page {
  239. flex: 1;
  240. display: flex;
  241. flex-direction: column;
  242. min-height: 0;
  243. }
  244. .entry-closed {
  245. padding: 80rpx 40rpx;
  246. text-align: center;
  247. font-size: 28rpx;
  248. color: #666;
  249. }
  250. .entry-closed__btn {
  251. margin-top: 32rpx;
  252. width: 100%;
  253. }
  254. .entry-steps {
  255. display: flex;
  256. padding: 20rpx 16rpx;
  257. background: #fff;
  258. }
  259. .entry-steps__item {
  260. flex: 1;
  261. text-align: center;
  262. font-size: 24rpx;
  263. color: #999;
  264. }
  265. .entry-steps__item--on {
  266. color: #2e7d32;
  267. font-weight: 600;
  268. }
  269. .entry-scroll {
  270. flex: 1;
  271. // height: 0;
  272. padding: 24rpx;
  273. box-sizing: border-box;
  274. }
  275. .entry-actions {
  276. display: flex;
  277. gap: 20rpx;
  278. padding: 20rpx 24rpx;
  279. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  280. background: #fff;
  281. }
  282. .entry-actions__prev {
  283. flex: 1;
  284. height: 88rpx;
  285. line-height: 88rpx;
  286. }
  287. .entry-actions__next {
  288. flex: 2;
  289. height: 88rpx;
  290. line-height: 88rpx;
  291. }
  292. .type-card {
  293. margin-bottom: 20rpx;
  294. padding: 32rpx;
  295. border: 2rpx solid #e5ded6;
  296. border-radius: 16rpx;
  297. }
  298. .type-card--on {
  299. border-color: #2e7d32;
  300. background: #f1f8f4;
  301. }
  302. .type-card__title {
  303. display: block;
  304. font-size: 32rpx;
  305. font-weight: 600;
  306. color: #333;
  307. }
  308. .type-card__sub {
  309. display: block;
  310. margin-top: 8rpx;
  311. font-size: 24rpx;
  312. color: #999;
  313. }
  314. .submit-tip {
  315. display: block;
  316. padding: 16rpx 0 24rpx;
  317. font-size: 26rpx;
  318. color: #666;
  319. }
  320. </style>