| | |
| | | <template> |
| | | <view class="page"> |
| | | <view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view><text class="page-title">商户进件</text></view> |
| | | <view class="step-bar"><view class="step-item" v-for="(title, index) in stepTitles" :key="index"><text :class="currentStep - 1 >= index ? 'step-index step-index-active' : 'step-index'">{{index + 1}}</text><text :class="currentStep - 1 >= index ? 'step-label step-label-active' : 'step-label'">{{title}}</text></view></view> |
| | | <view class="step-bar"><view class="step-item" v-for="(title, index) in stepTitles" :key="index"><text :class="index + 1 == currentStep ? 'step-index step-index-active' : (index + 1 < currentStep ? 'step-index step-index-done' : 'step-index')">{{index + 1}}</text><text :class="index + 1 == currentStep ? 'step-label step-label-active' : (index + 1 < currentStep ? 'step-label step-label-done' : 'step-label')">{{stepTitle(index)}}</text></view></view> |
| | | <scroll-view class="form-scroll" scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false"><view class="form-body"> |
| | | <view v-if="draftNotice" class="draft-hint">已恢复上次未提交的草稿,可继续填写</view> |
| | | |
| | |
| | | <view v-if="merchantType == 'individual'"> |
| | | <view class="field-block"><text class="field-label required">行业类型</text><picker class="date-picker" mode="selector" :range="industryTypeOptions" :value="industryTypeIndex" @change="onIndustryTypeChange"><view class="select-shell" :class="invalidField == 'industryType' ? 'field-invalid' : ''"><text :class="industryType.length == 0 ? 'select-placeholder' : ''">{{industryType.length > 0 ? industryType : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'industryType'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">经营地区(省、市、区)</text><picker class="region-picker" mode="region" @change="onRegisteredRegionChange"><view class="select-shell" :class="invalidField == 'registeredRegion' ? 'field-invalid' : ''"><text class="ellipsis-text" :class="registeredRegion.length == 0 ? 'select-placeholder' : ''">{{registeredRegion.length > 0 ? registeredRegion : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'registeredRegion'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">经营详细地址</text><view class="input-shell" :class="invalidField == 'businessAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="businessAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('businessAddress')" /></view><text class="field-hint">与门头照上的地址一致(线下报备需要)</text><text v-if="invalidField == 'businessAddress'" class="field-error">{{errorMessage}}</text></view> |
| | | </view> |
| | | <!-- 门店材料是线下报备的一组三张:门头照(F22)、内景照(F24)、收银台(F105)。通道在 |
| | | 进件这一步就按 F-code 逐张核对,缺一张整笔驳回,所以企业与个人主体都要交。 --> |
| | | <view class="field-block"><text class="field-label required">店铺门头照</text><view class="upload-box" :class="invalidField == 'storeHeaderUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeHeader')"><image v-if="storeHeaderUrl.length > 0" class="document-image" :src="storeHeaderUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeHeader' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">实体门头照(招牌清晰可见);流动经营、上门服务传经营现场照</text><text v-if="invalidField == 'storeHeaderUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">店内环境照</text><view class="upload-box" :class="invalidField == 'storeIndoorUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeIndoor')"><image v-if="storeIndoorUrl.length > 0" class="document-image" :src="storeIndoorUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeIndoor' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">店内环境或工作区域照片,不能与门头照相同</text><text v-if="invalidField == 'storeIndoorUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">店铺收银台照片</text><view class="upload-box" :class="invalidField == 'storeCashierUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeCashier')"><image v-if="storeCashierUrl.length > 0" class="document-image" :src="storeCashierUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeCashier' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">收银台或吧台实拍,能看到收款设备/收款码</text><text v-if="invalidField == 'storeCashierUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | </view> |
| | | |
| | | <view v-if="currentStep == 2"> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人姓名' : '个人姓名'}}</text><view class="input-shell" :class="invalidField == 'subjectName' ? 'field-invalid' : ''"><input class="text-input" v-model="subjectName" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('subjectName')" /></view><text class="field-hint">与身份证上的姓名一致</text><text v-if="invalidField == 'subjectName'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件类型' : '负责人证件类型'}}</text><view class="select-shell readonly-shell"><text>身份证</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件人像面' : '负责人身份证件人像面'}}</text><view class="upload-box" :class="invalidField == 'legalIdFrontUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdFront')"><image v-if="legalIdFrontUrl.length > 0" class="document-image" :src="legalIdFrontUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdFront' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdFrontUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件国徽面' : '负责人身份证件国徽面'}}</text><view class="upload-box" :class="invalidField == 'legalIdBackUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdBack')"><image v-if="legalIdBackUrl.length > 0" class="document-image" :src="legalIdBackUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdBack' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdBackUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件号码' : '负责人证件号码'}}</text><view class="input-shell" :class="invalidField == 'subjectIdNumber' ? 'field-invalid' : ''"><input class="text-input" :value="subjectIdNumber" maxlength="18" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizeIdNumber" /></view><text v-if="invalidField == 'subjectIdNumber'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期类型' : '负责人证件有效期类型'}}</text><picker class="date-picker" mode="selector" :range="subjectValidityOptions" :value="subjectValidityIndex" @change="onSubjectValidityChange"><view class="select-shell"><text>{{subjectValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期开始日期' : '负责人证件有效期开始日期'}}</text><picker class="date-picker" mode="date" :value="subjectStartDate" start="1900-01-01" end="2099-12-31" @change="onSubjectStartChange"><view class="select-shell" :class="invalidField == 'subjectStartDate' ? 'field-invalid' : ''"><text :class="subjectStartDate.length == 0 ? 'select-placeholder' : ''">{{subjectStartDate.length > 0 ? subjectStartDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectStartDate'" class="field-error">{{errorMessage}}</text></view> |
| | | <view v-if="subjectValidity != '长期有效'" class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期截止日期' : '负责人证件有效期截止日期'}}</text><picker class="date-picker" mode="date" :value="subjectEndDate" start="1900-01-01" end="2099-12-31" @change="onSubjectEndChange"><view class="select-shell" :class="invalidField == 'subjectEndDate' ? 'field-invalid' : ''"><text :class="subjectEndDate.length == 0 ? 'select-placeholder' : ''">{{subjectEndDate.length > 0 ? subjectEndDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectEndDate'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件地址' : '负责人证件地址'}}</text><view class="input-shell" :class="invalidField == 'legalCertificateAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="legalCertificateAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('legalCertificateAddress')" /></view><text class="field-hint">与身份证上的住址一致,至少 4 个字</text><text v-if="invalidField == 'legalCertificateAddress'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件类型' : '本人证件类型'}}</text><view class="select-shell readonly-shell"><text>身份证</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件人像面' : '本人身份证件人像面'}}</text><view class="upload-box" :class="invalidField == 'legalIdFrontUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdFront')"><image v-if="legalIdFrontUrl.length > 0" class="document-image" :src="legalIdFrontUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdFront' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdFrontUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件国徽面' : '本人身份证件国徽面'}}</text><view class="upload-box" :class="invalidField == 'legalIdBackUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdBack')"><image v-if="legalIdBackUrl.length > 0" class="document-image" :src="legalIdBackUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdBack' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdBackUrl'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件号码' : '本人证件号码'}}</text><view class="input-shell" :class="invalidField == 'subjectIdNumber' ? 'field-invalid' : ''"><input class="text-input" :value="subjectIdNumber" maxlength="18" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizeIdNumber" /></view><text v-if="invalidField == 'subjectIdNumber'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期类型' : '本人证件有效期类型'}}</text><picker class="date-picker" mode="selector" :range="subjectValidityOptions" :value="subjectValidityIndex" @change="onSubjectValidityChange"><view class="select-shell"><text>{{subjectValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期开始日期' : '本人证件有效期开始日期'}}</text><picker class="date-picker" mode="date" :value="subjectStartDate" start="1900-01-01" end="2099-12-31" @change="onSubjectStartChange"><view class="select-shell" :class="invalidField == 'subjectStartDate' ? 'field-invalid' : ''"><text :class="subjectStartDate.length == 0 ? 'select-placeholder' : ''">{{subjectStartDate.length > 0 ? subjectStartDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectStartDate'" class="field-error">{{errorMessage}}</text></view> |
| | | <view v-if="subjectValidity != '长期有效'" class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期截止日期' : '本人证件有效期截止日期'}}</text><picker class="date-picker" mode="date" :value="subjectEndDate" start="1900-01-01" end="2099-12-31" @change="onSubjectEndChange"><view class="select-shell" :class="invalidField == 'subjectEndDate' ? 'field-invalid' : ''"><text :class="subjectEndDate.length == 0 ? 'select-placeholder' : ''">{{subjectEndDate.length > 0 ? subjectEndDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectEndDate'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件地址' : '本人证件地址'}}</text><view class="input-shell" :class="invalidField == 'legalCertificateAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="legalCertificateAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('legalCertificateAddress')" /></view><text class="field-hint">与身份证上的住址一致,至少 4 个字</text><text v-if="invalidField == 'legalCertificateAddress'" class="field-error">{{errorMessage}}</text></view> |
| | | <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人手机号' : '本人手机号'}}</text><view class="input-shell" :class="invalidField == 'legalMobile' ? 'field-invalid' : ''"><input class="text-input" type="number" v-model="legalMobile" maxlength="11" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('legalMobile')" /></view><text class="field-hint">微信实名认证要用经营者/法人本人的手机号,不是联系人手机号</text><text v-if="invalidField == 'legalMobile'" class="field-error">{{errorMessage}}</text></view> |
| | | </view> |
| | | |
| | | <view v-if="currentStep == 3"> |
| | |
| | | <script setup lang="uts"> |
| | | import { loadOnboardingStatus, SettlementOnboarding, submitOnboarding as submitOnboardingRemote } from '../../services/settlement.uts' |
| | | import { uploadMerchantProductImage } from '../../services/merchant-management.uts' |
| | | import { getMerchantOnboardingDraftStorageKey } from '../../services/onboarding-draft.uts' |
| | | import { getSession } from '../../services/session.uts' |
| | | import { areaProvinceNames, areaProvinceCodes, areaCityNames, areaCityCodes } from '../../config/area-codes.uts' |
| | | |
| | | const stepTitles = ['主体信息', '法人信息', '联系人', '结算账户'] |
| | | /** 个人主体没有法人,第二步展示为「本人信息」。 */ |
| | | const stepTitle = (index: number): string => (index == 1 && merchantType.value == 'individual') ? '本人信息' : stepTitles[index] |
| | | const currentStep = ref(1) |
| | | const scrollTop = ref(0) |
| | | const draftNotice = ref(false) |
| | | const submitError = ref('') |
| | | |
| | | const submitting = ref(false); const uploadingField = ref(''); const merchantType = ref('enterprise'); const merchantName = ref(''); const merchantShortName = ref(''); const companyType = ref(''); const companyTypeIndex = ref(-1); const companyTypeOptions = ['政府机构', '国营企业', '私营企业', '外资企业', '个体工商户', '其它组织', '事业单位', '业主委员会']; const industryType = ref(''); const industryTypeIndex = ref(0); const industryTypeOptions = ['餐饮服务', '酒店服务', '景区服务', '加油服务', '停车服务', '其它']; const businessLicenseUrl = ref(''); const creditCode = ref(''); const businessValidity = ref('固定期限'); const businessStartDate = ref(''); const businessEndDate = ref(''); const establishedDate = ref(''); const registeredRegion = ref(''); const registeredProvinceCode = ref(''); const registeredCityCode = ref(''); const registeredDistrictCode = ref(''); const registeredAddress = ref(''); const registeredCapital = ref(''); const subjectName = ref(''); const legalIdFrontUrl = ref(''); const legalIdBackUrl = ref(''); const subjectIdNumber = ref(''); const subjectValidity = ref('10年'); const subjectStartDate = ref(''); const subjectEndDate = ref(''); const legalCertificateAddress = ref(''); const contactName = ref(''); const contactMobile = ref(''); const administratorEmail = ref(''); const bankCardNumber = ref(''); const bankProvince = ref(''); const bankProvinceCode = ref(''); const bankProvinceIndex = ref(0); const bankCity = ref(''); const bankCityCode = ref(''); const bankCityNames = ref([] as string[]); const bankCityCodes = ref([] as string[]); const bankCityIndex = ref(0); const bankBranch = ref(''); const bankBranchCode = ref(''); const bankName = ref(''); const settlementProofUrl = ref(''); const invalidField = ref(''); const errorMessage = ref('') |
| | | const submitting = ref(false); const uploadingField = ref(''); const merchantType = ref('enterprise'); const merchantName = ref(''); const merchantShortName = ref(''); const companyType = ref(''); const companyTypeIndex = ref(-1); const companyTypeOptions = ['政府机构', '国营企业', '私营企业', '外资企业', '个体工商户', '其它组织', '事业单位', '业主委员会']; const industryType = ref(''); const industryTypeIndex = ref(0); const industryTypeOptions = ['餐饮服务', '酒店服务', '景区服务', '加油服务', '停车服务', '其它']; const businessLicenseUrl = ref(''); const creditCode = ref(''); const businessValidity = ref('固定期限'); const businessStartDate = ref(''); const businessEndDate = ref(''); const establishedDate = ref(''); const registeredRegion = ref(''); const registeredProvinceCode = ref(''); const registeredCityCode = ref(''); const registeredDistrictCode = ref(''); const registeredAddress = ref(''); const businessAddress = ref(''); const registeredCapital = ref(''); const subjectName = ref(''); const legalIdFrontUrl = ref(''); const legalIdBackUrl = ref(''); const subjectIdNumber = ref(''); const subjectValidity = ref('10年'); const subjectStartDate = ref(''); const subjectEndDate = ref(''); const legalCertificateAddress = ref(''); const legalMobile = ref(''); const contactName = ref(''); const contactMobile = ref(''); const administratorEmail = ref(''); const bankCardNumber = ref(''); const bankProvince = ref(''); const bankProvinceCode = ref(''); const bankProvinceIndex = ref(0); const bankCity = ref(''); const bankCityCode = ref(''); const bankCityNames = ref([] as string[]); const bankCityCodes = ref([] as string[]); const bankCityIndex = ref(0); const bankBranch = ref(''); const bankBranchCode = ref(''); const bankName = ref(''); const settlementProofUrl = ref(''); const storeHeaderUrl = ref(''); const storeIndoorUrl = ref(''); const storeCashierUrl = ref(''); const invalidField = ref(''); const errorMessage = ref('') |
| | | const merchantTypeOptions = ['企业', '个人'] |
| | | const merchantTypeIndex = ref(0) |
| | | const validityOptions = ['固定期限', '长期'] |
| | | const businessValidityIndex = ref(0) |
| | | const subjectValidityOptions = ['10年', '20年', '长期有效'] |
| | | const subjectValidityIndex = ref(0) |
| | | const draftStorageKey = 'merchant-onboarding-draft' |
| | | // 草稿按当前登录的操作员分命名空间,换账号看不到上一个账号的草稿(见 services/onboarding-draft.uts)。 |
| | | // 必须在取用点现算:进入页面时机不确定,模块加载时算会把当时的会话固化住。 |
| | | const draftStorageKey = (): string => getMerchantOnboardingDraftStorageKey() |
| | | const currentOperatorId = (): string => { |
| | | const session = getSession() |
| | | if (session == null || session.operatorId == null) return '' |
| | | return (session.operatorId as string).trim() |
| | | } |
| | | let draftRestored = false |
| | | |
| | | /** 私营企业等类型汇付要求必须填注册资本,这里与服务端 normalizeRegisteredCapital 保持一致。 */ |
| | |
| | | } else { |
| | | if (industryType.value.length == 0) return 'industryType|请选择行业类型' |
| | | if (registeredRegion.value.length == 0) return 'registeredRegion|请选择经营地区' |
| | | // 经营详细地址:线下报备(entity 有实体经营场所)下它是通道必填项,是进件的 detail_addr。 |
| | | if (businessAddress.value.trim().length == 0) return 'businessAddress|请输入经营详细地址' |
| | | } |
| | | // 门店材料是线下报备的一组三张(门头照 F22、内景照 F24、收银台 F105),**不分主体类型**: |
| | | // 通道在进件这一步就按 F-code 逐张核对,缺一张整笔驳回。在这里拦住,好过提交上去以后被驳回 |
| | | // ——那时商户得整份进件重走一遍。 |
| | | if (storeHeaderUrl.value.length == 0) return 'storeHeaderUrl|请上传店铺门头照' |
| | | if (storeIndoorUrl.value.length == 0) return 'storeIndoorUrl|请上传店内环境照' |
| | | if (storeCashierUrl.value.length == 0) return 'storeCashierUrl|请上传店铺收银台照片' |
| | | } |
| | | if (step == 2) { |
| | | if (subjectName.value.trim().length == 0) return 'subjectName|' + (merchantType.value == 'enterprise' ? '请输入法人姓名' : '请输入个人姓名') |
| | |
| | | const address = legalCertificateAddress.value.trim() |
| | | if (address.length == 0) return 'legalCertificateAddress|请输入证件地址(与身份证一致)' |
| | | if (address.length < 4) return 'legalCertificateAddress|证件地址至少 4 个字,请写完整' |
| | | const legalMobileValue = legalMobile.value.trim() |
| | | if (legalMobileValue.length == 0) return 'legalMobile|请输入手机号(微信实名认证要用)' |
| | | if (!(/^1\d{10}$/).test(legalMobileValue)) return 'legalMobile|请输入正确的 11 位手机号' |
| | | } |
| | | if (step == 3) { |
| | | if (merchantType.value == 'enterprise' && contactName.value.trim().length == 0) return 'contactName|请输入管理员姓名' |
| | |
| | | } |
| | | const goPrevStep = () => { saveDraft(); if (currentStep.value > 1) currentStep.value = currentStep.value - 1; resetScroll() } |
| | | |
| | | const draftPayload = (): any => ({ merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, currentStep: currentStep.value }) |
| | | const saveDraft = () => { uni.setStorageSync(draftStorageKey, JSON.stringify(draftPayload())) } |
| | | const clearDraft = () => { uni.removeStorageSync(draftStorageKey) } |
| | | const draftPayload = (): any => ({ ownerOperatorId: currentOperatorId(), merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, businessAddress: businessAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, legalMobile: legalMobile.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, storeHeaderUrl: storeHeaderUrl.value, storeIndoorUrl: storeIndoorUrl.value, storeCashierUrl: storeCashierUrl.value, currentStep: currentStep.value }) |
| | | const saveDraft = () => { uni.setStorageSync(draftStorageKey(), JSON.stringify(draftPayload())) } |
| | | const clearDraft = () => { uni.removeStorageSync(draftStorageKey()) } |
| | | const restoreDraft = () => { |
| | | const stored = uni.getStorageSync(draftStorageKey) |
| | | const stored = uni.getStorageSync(draftStorageKey()) |
| | | if (stored == null || stored == '') return |
| | | try { |
| | | const draft = JSON.parse(stored as string) as any |
| | | // 草稿必须属于当前登录的操作员。key 已经按操作员分命名空间,这里再校验一次归属: |
| | | // 更新前存在旧的全局 key 下的草稿,里面可能有别的账号的姓名、身份证号、银行卡号; |
| | | // 归属不符就直接丢弃,宁可让用户重填,也不能把别人的资料预填进来。 |
| | | const owner = draft.ownerOperatorId != null ? (draft.ownerOperatorId as string) : '' |
| | | if (owner != currentOperatorId()) { clearDraft(); return } |
| | | if (draft.merchantType != null) merchantType.value = draft.merchantType as string |
| | | if (draft.merchantName != null) merchantName.value = draft.merchantName as string |
| | | if (draft.merchantShortName != null) merchantShortName.value = draft.merchantShortName as string |
| | |
| | | if (draft.registeredCityCode != null) registeredCityCode.value = draft.registeredCityCode as string |
| | | if (draft.registeredDistrictCode != null) registeredDistrictCode.value = draft.registeredDistrictCode as string |
| | | if (draft.registeredAddress != null) registeredAddress.value = draft.registeredAddress as string |
| | | if (draft.businessAddress != null) businessAddress.value = draft.businessAddress as string |
| | | if (draft.registeredCapital != null) registeredCapital.value = draft.registeredCapital as string |
| | | if (draft.subjectName != null) subjectName.value = draft.subjectName as string |
| | | if (draft.legalIdFrontUrl != null) legalIdFrontUrl.value = draft.legalIdFrontUrl as string |
| | |
| | | if (draft.subjectStartDate != null) subjectStartDate.value = draft.subjectStartDate as string |
| | | if (draft.subjectEndDate != null) subjectEndDate.value = draft.subjectEndDate as string |
| | | if (draft.legalCertificateAddress != null) legalCertificateAddress.value = draft.legalCertificateAddress as string |
| | | if (draft.legalMobile != null) legalMobile.value = draft.legalMobile as string |
| | | if (draft.contactName != null) contactName.value = draft.contactName as string |
| | | if (draft.contactMobile != null) contactMobile.value = draft.contactMobile as string |
| | | if (draft.administratorEmail != null) administratorEmail.value = draft.administratorEmail as string |
| | |
| | | if (draft.bankBranchCode != null) bankBranchCode.value = draft.bankBranchCode as string |
| | | if (draft.bankName != null) bankName.value = draft.bankName as string |
| | | if (draft.settlementProofUrl != null) settlementProofUrl.value = draft.settlementProofUrl as string |
| | | if (draft.storeHeaderUrl != null) storeHeaderUrl.value = draft.storeHeaderUrl as string |
| | | if (draft.storeIndoorUrl != null) storeIndoorUrl.value = draft.storeIndoorUrl as string |
| | | if (draft.storeCashierUrl != null) storeCashierUrl.value = draft.storeCashierUrl as string |
| | | if (bankProvince.value.length > 0 && bankProvinceIndex.value >= 0 && bankProvinceIndex.value < areaCityNames.length) { bankCityNames.value = areaCityNames[bankProvinceIndex.value]; bankCityCodes.value = areaCityCodes[bankProvinceIndex.value] } |
| | | merchantTypeIndex.value = merchantType.value == 'individual' ? 1 : 0 |
| | | const restoredBusinessValidity = validityOptions.indexOf(businessValidity.value); if (restoredBusinessValidity >= 0) businessValidityIndex.value = restoredBusinessValidity |
| | | const restoredSubjectValidity = subjectValidityOptions.indexOf(subjectValidity.value); if (restoredSubjectValidity >= 0) subjectValidityIndex.value = restoredSubjectValidity |
| | | if (draft.currentStep != null) { const restoredStep = Number(draft.currentStep); currentStep.value = (restoredStep >= 1 && restoredStep <= 4) ? restoredStep : 1 } |
| | | // 走到这里才说明草稿确实属于当前账号,这时才提示"已恢复草稿"。 |
| | | draftNotice.value = true |
| | | } catch (_error) { clearDraft() } |
| | | } |
| | |
| | | const onIndustryTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= industryTypeOptions.length) return; industryTypeIndex.value = index; industryType.value = industryTypeOptions[index]; clearFieldError('industryType') } |
| | | const onBusinessStartChange = (e: any) => { businessStartDate.value = e.detail.value as string; clearFieldError('businessStartDate') }; const onBusinessEndChange = (e: any) => { businessEndDate.value = e.detail.value as string; clearFieldError('businessEndDate') }; const onEstablishedDateChange = (e: any) => { establishedDate.value = e.detail.value as string; clearFieldError('establishedDate') }; const onSubjectStartChange = (e: any) => { subjectStartDate.value = e.detail.value as string; clearFieldError('subjectStartDate') }; const onSubjectEndChange = (e: any) => { subjectEndDate.value = e.detail.value as string; clearFieldError('subjectEndDate') } |
| | | const onRegisteredRegionChange = (e: any) => { const values = e.detail.value as Array<string>; const codes = e.detail.code as Array<string>; if (values.length != 3 || codes.length != 3) return; registeredRegion.value = values[0] + ' ' + values[1] + ' ' + values[2]; registeredProvinceCode.value = codes[0]; registeredCityCode.value = codes[1]; registeredDistrictCode.value = codes[2]; clearFieldError('registeredRegion') } |
| | | const chooseDocument = (kind: string) => { if (uploadingField.value.length > 0) return; uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (r: any) => { const files = r.tempFiles as Array<any>; if (files.length == 0) return; if ((files[0].size as number) > 2097152) { uni.showToast({ title: '图片不能超过2MB', icon: 'none' }); return }; uploadingField.value = kind; uploadMerchantProductImage(files[0].path as string, (url: string) => { uploadingField.value = ''; if (kind == 'businessLicense') { businessLicenseUrl.value = url; clearFieldError('businessLicenseUrl') } else if (kind == 'legalIdFront') { legalIdFrontUrl.value = url; clearFieldError('legalIdFrontUrl') } else if (kind == 'legalIdBack') { legalIdBackUrl.value = url; clearFieldError('legalIdBackUrl') } else { settlementProofUrl.value = url; clearFieldError('settlementProofUrl') } }, (error: any) => { uploadingField.value = ''; uni.showToast({ title: error.message != null ? error.message : '图片上传失败,请重试', icon: 'none' }) }) } }) } |
| | | const chooseDocument = (kind: string) => { if (uploadingField.value.length > 0) return; uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (r: any) => { const files = r.tempFiles as Array<any>; if (files.length == 0) return; if ((files[0].size as number) > 2097152) { uni.showToast({ title: '图片不能超过2MB', icon: 'none' }); return }; uploadingField.value = kind; uploadMerchantProductImage(files[0].path as string, (url: string) => { uploadingField.value = ''; if (kind == 'businessLicense') { businessLicenseUrl.value = url; clearFieldError('businessLicenseUrl') } else if (kind == 'legalIdFront') { legalIdFrontUrl.value = url; clearFieldError('legalIdFrontUrl') } else if (kind == 'legalIdBack') { legalIdBackUrl.value = url; clearFieldError('legalIdBackUrl') } else if (kind == 'storeHeader') { storeHeaderUrl.value = url; clearFieldError('storeHeaderUrl') } else if (kind == 'storeIndoor') { storeIndoorUrl.value = url; clearFieldError('storeIndoorUrl') } else if (kind == 'storeCashier') { storeCashierUrl.value = url; clearFieldError('storeCashierUrl') } else { settlementProofUrl.value = url; clearFieldError('settlementProofUrl') } }, (error: any) => { uploadingField.value = ''; uni.showToast({ title: error.message != null ? error.message : '图片上传失败,请重试', icon: 'none' }) }) } }) } |
| | | const onBankProvinceChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= areaProvinceNames.length) return; bankProvinceIndex.value = index; bankProvince.value = areaProvinceNames[index]; bankProvinceCode.value = areaProvinceCodes[index]; bankCityNames.value = areaCityNames[index]; bankCityCodes.value = areaCityCodes[index]; bankCityIndex.value = 0; bankCity.value = ''; bankCityCode.value = ''; bankBranch.value = ''; bankBranchCode.value = ''; bankName.value = '' } |
| | | const onBankCityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= bankCityNames.value.length) return; bankCityIndex.value = index; bankCity.value = bankCityNames.value[index]; bankCityCode.value = bankCityCodes.value[index]; bankBranch.value = ''; bankBranchCode.value = ''; bankName.value = '' } |
| | | const chooseBankBranch = () => { if (bankCityCode.value.length == 0) { uni.showToast({ title: '请先选择开户城市', icon: 'none' }); return }; uni.navigateTo({ url: '/pages/bank-branch-select/bank-branch-select?city=' + encodeURIComponent(bankProvince.value + ' ' + bankCity.value) + '&cityCode=' + encodeURIComponent(bankCityCode.value) }) } |
| | |
| | | } |
| | | } |
| | | submitting.value = true |
| | | submitOnboardingRemote({ subjectType: merchantType.value, subjectName: subjectName.value.trim(), creditCode: creditCode.value, subjectCertificateValidityType: subjectValidity.value == '长期有效' ? 'long_term' : 'fixed', subjectCertificateBeginDate: subjectStartDate.value, subjectCertificateEndDate: subjectEndDate.value, subjectIdNumber: subjectIdNumber.value, licenseValidityType: businessValidity.value == '长期' ? 'long_term' : 'fixed', licenseBeginDate: businessStartDate.value, licenseEndDate: businessEndDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: merchantType.value == 'enterprise' ? registeredAddress.value.trim() : legalCertificateAddress.value.trim(), merchantName: merchantName.value.trim(), merchantShortName: merchantShortName.value.trim(), companyType: merchantType.value == 'enterprise' ? companyType.value : industryType.value, businessLicenseUrl: businessLicenseUrl.value, establishedDate: establishedDate.value, registeredCapital: registeredCapital.value.trim(), legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, legalCertificateAddress: legalCertificateAddress.value.trim(), administratorEmail: administratorEmail.value.trim(), contactName: merchantType.value == 'enterprise' ? contactName.value.trim() : subjectName.value.trim(), contactMobile: contactMobile.value, bankCardNumber: bankCardNumber.value, bankProvinceCode: bankProvinceCode.value, bankCityCode: bankCityCode.value, bankBranchCode: bankBranchCode.value, settlementCardFrontUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? '' : settlementProofUrl.value, registrationAccountUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? settlementProofUrl.value : '' }, 'onboarding-' + Date.now(), (result: SettlementOnboarding) => { submitting.value = false; if (result.status == 'approved') clearDraft(); uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' }) }, (error: any) => { submitting.value = false; submitError.value = error.message != null ? error.message : '提交失败,请重试'; uni.showToast({ title: submitError.value, icon: 'none' }) }) |
| | | submitOnboardingRemote({ subjectType: merchantType.value, subjectName: subjectName.value.trim(), creditCode: creditCode.value, subjectCertificateValidityType: subjectValidity.value == '长期有效' ? 'long_term' : 'fixed', subjectCertificateBeginDate: subjectStartDate.value, subjectCertificateEndDate: subjectEndDate.value, subjectIdNumber: subjectIdNumber.value, licenseValidityType: businessValidity.value == '长期' ? 'long_term' : 'fixed', licenseBeginDate: businessStartDate.value, licenseEndDate: businessEndDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: merchantType.value == 'enterprise' ? registeredAddress.value.trim() : legalCertificateAddress.value.trim(), merchantName: merchantName.value.trim(), merchantShortName: merchantShortName.value.trim(), companyType: merchantType.value == 'enterprise' ? companyType.value : industryType.value, businessLicenseUrl: businessLicenseUrl.value, establishedDate: establishedDate.value, registeredCapital: registeredCapital.value.trim(), legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, legalCertificateAddress: legalCertificateAddress.value.trim(), legalMobile: legalMobile.value.trim(), administratorEmail: administratorEmail.value.trim(), contactName: merchantType.value == 'enterprise' ? contactName.value.trim() : subjectName.value.trim(), contactMobile: contactMobile.value, bankCardNumber: bankCardNumber.value, bankProvinceCode: bankProvinceCode.value, bankCityCode: bankCityCode.value, bankBranchCode: bankBranchCode.value, settlementCardFrontUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? '' : settlementProofUrl.value, registrationAccountUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? settlementProofUrl.value : '', storeHeaderUrl: storeHeaderUrl.value, storeIndoorUrl: storeIndoorUrl.value, storeCashierUrl: storeCashierUrl.value, businessAddress: merchantType.value == 'individual' ? businessAddress.value.trim() : '' }, 'onboarding-' + Date.now(), (result: SettlementOnboarding) => { submitting.value = false; if (result.status == 'approved') clearDraft(); uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' }) }, (error: any) => { submitting.value = false; submitError.value = error.message != null ? error.message : '提交失败,请重试'; uni.showToast({ title: submitError.value, icon: 'none' }) }) |
| | | } |
| | | |
| | | onShow(() => { |
| | | if (!draftRestored) { draftRestored = true; restoreDraft() } |
| | | loadOnboardingStatus((item: SettlementOnboarding) => { |
| | | if (item.status == 'pending' || item.status == 'approved') { |
| | | // retryable:通道侧配置已明确失败(例如微信支付配置被拒),这条进件不会自己再往前走, |
| | | // 停在页面上让商户重新提交,而不是跳去"审核中"把人挡在门外。 |
| | | if ((item.status == 'pending' || item.status == 'approved') && !item.retryable) { |
| | | uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' }) |
| | | } |
| | | }, (_error: any) => {}) |
| | |
| | | .step-item { display: flex; flex: 1; flex-direction: column; align-items: center; } |
| | | .step-index { width: 40rpx; height: 40rpx; border-radius: 20rpx; color: #9AA4B3; font-size: 22rpx; line-height: 40rpx; text-align: center; background-color: #E4E8EF; } |
| | | .step-index-active { color: #FFFFFF; background-color: #FF7625; } |
| | | .step-index-done { color: #FF7625; background-color: #FFF0E4; } |
| | | .step-label { margin-top: 8rpx; color: #9AA4B3; font-size: 22rpx; line-height: 30rpx; } |
| | | .step-label-active { color: #FF7625; font-weight: 700; } |
| | | .step-label-done { color: #C9A08A; } |
| | | .form-scroll { flex: 1; height: 0; background-color: #F4F6F9; } |
| | | .form-body { width: 100%; padding: 8rpx 38rpx 0; background-color: #F4F6F9; box-sizing: border-box; } |
| | | .draft-hint { margin-bottom: 24rpx; padding: 18rpx 24rpx; color: #B4520F; font-size: 24rpx; line-height: 34rpx; background-color: #FFF5EA; border-radius: 20rpx; } |