<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="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="currentStep == 1">
|
<view class="field-block"><text class="field-label required">商户类型</text><picker class="date-picker" mode="selector" :range="merchantTypeOptions" :value="merchantTypeIndex" @change="onMerchantTypeChange"><view class="select-shell"><text>{{merchantType == 'enterprise' ? '企业' : '个人'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view>
|
<view v-if="merchantType == 'enterprise'">
|
<view class="field-block"><text class="field-label required">商户名称</text><view class="input-shell" :class="invalidField == 'merchantName' ? 'field-invalid' : ''"><input class="text-input" v-model="merchantName" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('merchantName')" /></view><text class="field-hint">必须与营业执照上的名称完全一致</text><text v-if="invalidField == 'merchantName'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label">商户简称</text><view class="input-shell"><input class="text-input" v-model="merchantShortName" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" /></view><text class="field-hint">会显示在用户的支付页面上,留空则用商户名称</text></view>
|
<view class="field-block"><text class="field-label required">公司类型</text><picker class="date-picker" mode="selector" :range="companyTypeOptions" :value="companyTypeIndex" @change="onCompanyTypeChange"><view class="select-shell" :class="invalidField == 'companyType' ? 'field-invalid' : ''"><text :class="companyType.length == 0 ? 'select-placeholder' : ''">{{companyType.length > 0 ? companyType : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'companyType'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">营业执照</text><view class="upload-box" :class="invalidField == 'businessLicenseUrl' ? 'field-invalid' : ''" @tap="chooseDocument('businessLicense')"><image v-if="businessLicenseUrl.length > 0" class="document-image" :src="businessLicenseUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'businessLicense' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,证照不能过期</text><text v-if="invalidField == 'businessLicenseUrl'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">统一社会信用代码</text><view class="input-shell" :class="invalidField == 'creditCode' ? 'field-invalid' : ''"><input class="text-input" :value="creditCode" maxlength="18" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizeCreditCode" /></view><text class="field-hint">18 位,字母会自动转大写</text><text v-if="invalidField == 'creditCode'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">营业执照有效期</text><picker class="date-picker" mode="selector" :range="validityOptions" :value="businessValidityIndex" @change="onBusinessValidityChange"><view class="select-shell"><text>{{businessValidity}}</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">营业执照有效期起始日期</text><picker class="date-picker" mode="date" :value="businessStartDate" start="1900-01-01" end="2099-12-31" @change="onBusinessStartChange"><view class="select-shell" :class="invalidField == 'businessStartDate' ? 'field-invalid' : ''"><text :class="businessStartDate.length == 0 ? 'select-placeholder' : ''">{{businessStartDate.length > 0 ? businessStartDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'businessStartDate'" class="field-error">{{errorMessage}}</text></view>
|
<view v-if="businessValidity == '固定期限'" class="field-block"><text class="field-label required">营业执照有效期结束日期</text><picker class="date-picker" mode="date" :value="businessEndDate" start="1900-01-01" end="2099-12-31" @change="onBusinessEndChange"><view class="select-shell" :class="invalidField == 'businessEndDate' ? 'field-invalid' : ''"><text :class="businessEndDate.length == 0 ? 'select-placeholder' : ''">{{businessEndDate.length > 0 ? businessEndDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'businessEndDate'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">成立时间</text><picker class="date-picker" mode="date" :value="establishedDate" start="1900-01-01" end="2099-12-31" @change="onEstablishedDateChange"><view class="select-shell" :class="invalidField == 'establishedDate' ? 'field-invalid' : ''"><text :class="establishedDate.length == 0 ? 'select-placeholder' : ''">{{establishedDate.length > 0 ? establishedDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'establishedDate'" 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 == 'registeredAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="registeredAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('registeredAddress')" /></view><text class="field-hint">与营业执照上的住所一致</text><text v-if="invalidField == 'registeredAddress'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text :class="capitalRequired() ? 'field-label required' : 'field-label'">注册资本</text><view class="input-shell" :class="invalidField == 'registeredCapital' ? 'field-invalid' : ''"><input class="text-input" type="digit" v-model="registeredCapital" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('registeredCapital')" /></view><text class="field-hint">单位:万元,可填小数</text><text v-if="invalidField == 'registeredCapital'" class="field-error">{{errorMessage}}</text></view>
|
</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="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">
|
<view v-if="merchantType == 'enterprise'" class="field-block"><text class="field-label required">管理员姓名</text><view class="input-shell" :class="invalidField == 'contactName' ? 'field-invalid' : ''"><input class="text-input" v-model="contactName" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('contactName')" /></view><text v-if="invalidField == 'contactName'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '管理员手机号' : '手机号'}}</text><view class="input-shell" :class="invalidField == 'contactMobile' ? 'field-invalid' : ''"><input class="text-input" type="number" :value="contactMobile" maxlength="11" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizePhone" /></view><text class="field-hint">汇付审核如需联系会打这个号码</text><text v-if="invalidField == 'contactMobile'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '管理员邮箱' : '邮箱'}}</text><view class="input-shell" :class="invalidField == 'administratorEmail' ? 'field-invalid' : ''"><input class="text-input" v-model="administratorEmail" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('administratorEmail')" /></view><text class="field-hint">用于接收进件结果通知,必须能收信</text><text v-if="invalidField == 'administratorEmail'" class="field-error">{{errorMessage}}</text></view>
|
</view>
|
|
<view v-if="currentStep == 4">
|
<view class="settlement-title">结算账户信息</view>
|
<view class="field-block"><text class="field-label required">{{bankAccountLabel()}}</text><view class="input-shell" :class="invalidField == 'bankCardNumber' ? 'field-invalid' : ''"><input class="text-input" type="number" maxlength="32" :value="bankCardNumber" placeholder="请输入结算账号" placeholder-class="input-placeholder" @input="normalizeBankNumber" /></view><text v-if="bankAccountLabel() == '对公账号'" class="field-hint">账号名必须与营业执照上的名称一致</text><text v-if="invalidField == 'bankCardNumber'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">开户省份</text><picker class="region-picker" mode="selector" :range="areaProvinceNames" :value="bankProvinceIndex" @change="onBankProvinceChange"><view class="select-shell" :class="invalidField == 'bankProvince' ? 'field-invalid' : ''"><text :class="bankProvince.length == 0 ? 'select-placeholder' : ''">{{bankProvince.length > 0 ? bankProvince : '请选择'}}</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">开户城市</text><picker class="region-picker" mode="selector" :range="bankCityNames" :value="bankCityIndex" :disabled="bankProvince.length == 0" @change="onBankCityChange"><view class="select-shell" :class="invalidField == 'bankCity' ? 'field-invalid' : ''"><text :class="bankCity.length == 0 ? 'select-placeholder' : ''">{{bankCity.length > 0 ? bankCity : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text class="field-hint">先选省份和城市,再选开户行</text></view>
|
<view class="field-block"><text class="field-label required">开户行(支行)</text><view :class="invalidField == 'bankBranch' ? 'select-shell field-invalid' : (bankCityCode.length == 0 ? 'select-shell readonly-shell' : 'select-shell')" @tap="chooseBankBranch"><text class="ellipsis-text" :class="bankBranch.length == 0 ? 'select-placeholder' : ''">{{bankBranch.length > 0 ? bankBranch : (bankCityCode.length == 0 ? '请先选择开户城市' : '请选择')}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view><text v-if="invalidField == 'bankBranch'" class="field-error">{{errorMessage}}</text></view>
|
<view class="field-block"><text class="field-label required">{{settlementProofLabel()}}</text><view class="upload-box" :class="invalidField == 'settlementProofUrl' ? 'field-invalid' : ''" @tap="chooseDocument('settlementProof')"><image v-if="settlementProofUrl.length > 0" class="document-image" :src="settlementProofUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'settlementProof' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">{{settlementProofHint()}}</text><text v-if="invalidField == 'settlementProofUrl'" class="field-error">{{errorMessage}}</text></view>
|
</view>
|
|
<view v-if="submitError.length > 0" class="banner-error">{{submitError}}</view>
|
<view class="step-actions">
|
<view v-if="currentStep > 1" class="secondary-button" @tap="goPrevStep"><text>上一步</text></view>
|
<view v-if="currentStep < 4" class="action-primary" @tap="goNextStep"><text>下一步</text></view>
|
<view v-else class="action-primary" :class="submitting || uploadingField.length > 0 ? 'button-disabled' : ''" @tap="submitForm"><text>{{submitting ? '提交中...' : '提交进件'}}</text></view>
|
</view>
|
<view class="safe-space"></view>
|
</view></scroll-view>
|
</view>
|
</template>
|
|
<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 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)
|
// 草稿按当前登录的操作员分命名空间,换账号看不到上一个账号的草稿(见 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 保持一致。 */
|
const capitalRequired = (): boolean => merchantType.value == 'enterprise' && ['国营企业', '私营企业', '外资企业', '其它组织', '事业单位'].indexOf(companyType.value) >= 0
|
const bankAccountLabel = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '对公账号' : '银行卡号'
|
const settlementProofLabel = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '开户许可证' : '银行卡卡号面'
|
const settlementProofHint = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '银行出具的开户许可证或基本存款账户信息,JPG/PNG/BMP,不超过 2MB' : '银行卡正面照片,JPG/PNG/BMP,不超过 2MB,卡号清晰'
|
|
/** 返回本步骤第一处问题,格式「字段|提示」;没有问题时返回空串。纯函数,不改动错误状态。 */
|
const stepProblem = (step: number): string => {
|
if (step == 1) {
|
if (merchantType.value == 'enterprise') {
|
if (merchantName.value.trim().length == 0) return 'merchantName|必须与营业执照一致,请输入商户名称'
|
if (companyType.value.length == 0) return 'companyType|请选择公司类型'
|
if (businessLicenseUrl.value.length == 0) return 'businessLicenseUrl|请上传营业执照'
|
if (!(/^[0-9A-Z]{18}$/).test(creditCode.value)) return 'creditCode|请输入正确的18位统一社会信用代码'
|
if (businessStartDate.value.length == 0) return 'businessStartDate|请选择营业执照有效期开始日期'
|
if (businessValidity.value == '固定期限' && businessEndDate.value.length == 0) return 'businessEndDate|请选择营业执照有效期结束日期'
|
if (businessStartDate.value.length > 0 && businessEndDate.value.length > 0 && businessStartDate.value > businessEndDate.value) return 'businessEndDate|结束日期不能早于开始日期'
|
if (establishedDate.value.length == 0) return 'establishedDate|请选择成立时间'
|
if (registeredRegion.value.length == 0) return 'registeredRegion|请选择注册地址'
|
if (registeredAddress.value.trim().length == 0) return 'registeredAddress|请输入详细注册地址'
|
if (capitalRequired() && registeredCapital.value.trim().length == 0) return 'registeredCapital|该类型必须填写注册资本(单位:万元)'
|
if (registeredCapital.value.trim().length > 0 && !(/^\d+(\.\d{1,2})?$/).test(registeredCapital.value.trim())) return 'registeredCapital|注册资本请输入数字,单位为万元'
|
} 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' ? '请输入法人姓名' : '请输入个人姓名')
|
if (legalIdFrontUrl.value.length == 0) return 'legalIdFrontUrl|请上传身份证人像面'
|
if (legalIdBackUrl.value.length == 0) return 'legalIdBackUrl|请上传身份证国徽面'
|
if (!(/^\d{17}[0-9X]$/).test(subjectIdNumber.value)) return 'subjectIdNumber|请输入正确的18位身份证号码'
|
if (subjectStartDate.value.length == 0) return 'subjectStartDate|请选择证件有效期开始日期'
|
if (subjectValidity.value != '长期有效' && subjectEndDate.value.length == 0) return 'subjectEndDate|请选择证件有效期截止日期'
|
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|请输入管理员姓名'
|
if (!(/^1\d{10}$/).test(contactMobile.value)) return 'contactMobile|请输入正确的11位手机号'
|
if (!(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(administratorEmail.value.trim())) return 'administratorEmail|请输入正确的邮箱,用于接收进件结果'
|
}
|
if (step == 4) {
|
if (!(/^\d{16,32}$/).test(bankCardNumber.value)) return 'bankCardNumber|请输入正确的' + bankAccountLabel()
|
if (bankProvinceCode.value.length == 0) return 'bankProvince|请选择开户省份'
|
if (bankCityCode.value.length == 0) return 'bankCity|请选择开户城市'
|
if (bankBranchCode.value.length == 0) return 'bankBranch|请选择开户行(支行)'
|
if (settlementProofUrl.value.length == 0) return 'settlementProofUrl|请上传' + settlementProofLabel()
|
}
|
return ''
|
}
|
|
/** 校验某一步:通过返回 true;否则把第一处问题写进 invalidField / errorMessage 并返回 false。 */
|
const validateStep = (step: number): boolean => {
|
invalidField.value = ''
|
errorMessage.value = ''
|
const problem = stepProblem(step)
|
if (problem.length == 0) return true
|
const separator = problem.indexOf('|')
|
invalidField.value = problem.substring(0, separator)
|
errorMessage.value = problem.substring(separator + 1)
|
return false
|
}
|
|
const resetScroll = () => { scrollTop.value = scrollTop.value == 0 ? 1 : 0 }
|
const goNextStep = () => {
|
if (!validateStep(currentStep.value)) { uni.showToast({ title: errorMessage.value, icon: 'none' }); return }
|
saveDraft()
|
if (currentStep.value < 4) currentStep.value = currentStep.value + 1
|
resetScroll()
|
}
|
const goPrevStep = () => { saveDraft(); if (currentStep.value > 1) currentStep.value = currentStep.value - 1; resetScroll() }
|
|
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())
|
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.companyType != null) companyType.value = draft.companyType as string
|
if (draft.companyTypeIndex != null) companyTypeIndex.value = Number(draft.companyTypeIndex)
|
if (draft.industryType != null) industryType.value = draft.industryType as string
|
if (draft.industryTypeIndex != null) industryTypeIndex.value = Number(draft.industryTypeIndex)
|
if (draft.businessLicenseUrl != null) businessLicenseUrl.value = draft.businessLicenseUrl as string
|
if (draft.creditCode != null) creditCode.value = draft.creditCode as string
|
if (draft.businessValidity != null) businessValidity.value = draft.businessValidity as string
|
if (draft.businessStartDate != null) businessStartDate.value = draft.businessStartDate as string
|
if (draft.businessEndDate != null) businessEndDate.value = draft.businessEndDate as string
|
if (draft.establishedDate != null) establishedDate.value = draft.establishedDate as string
|
if (draft.registeredRegion != null) registeredRegion.value = draft.registeredRegion as string
|
if (draft.registeredProvinceCode != null) registeredProvinceCode.value = draft.registeredProvinceCode 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.legalIdBackUrl != null) legalIdBackUrl.value = draft.legalIdBackUrl as string
|
if (draft.subjectIdNumber != null) subjectIdNumber.value = draft.subjectIdNumber as string
|
if (draft.subjectValidity != null) subjectValidity.value = draft.subjectValidity 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.bankCardNumber != null) bankCardNumber.value = draft.bankCardNumber as string
|
if (draft.bankProvince != null) bankProvince.value = draft.bankProvince as string
|
if (draft.bankProvinceCode != null) bankProvinceCode.value = draft.bankProvinceCode as string
|
if (draft.bankProvinceIndex != null) bankProvinceIndex.value = Number(draft.bankProvinceIndex)
|
if (draft.bankCity != null) bankCity.value = draft.bankCity as string
|
if (draft.bankCityCode != null) bankCityCode.value = draft.bankCityCode as string
|
if (draft.bankCityIndex != null) bankCityIndex.value = Number(draft.bankCityIndex)
|
if (draft.bankBranch != null) bankBranch.value = draft.bankBranch 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 goBack = () => uni.navigateBack()
|
const setError = (field: string, message: string) => { invalidField.value = field; errorMessage.value = message }
|
const clearFieldError = (field: string) => { if (invalidField.value == field) { invalidField.value = ''; errorMessage.value = '' }; if (submitError.value.length > 0) submitError.value = '' }
|
const normalizeCreditCode = (e: any) => { creditCode.value = (e.detail.value as string).toUpperCase().replace(/[^0-9A-Z]/g, ''); clearFieldError('creditCode') }
|
const normalizeIdNumber = (e: any) => { subjectIdNumber.value = (e.detail.value as string).toUpperCase().replace(/[^0-9X]/g, ''); clearFieldError('subjectIdNumber') }
|
const normalizePhone = (e: any) => { contactMobile.value = (e.detail.value as string).replace(/[^0-9]/g, ''); clearFieldError('contactMobile') }
|
const normalizeBankNumber = (e: any) => { bankCardNumber.value = (e.detail.value as string).replace(/[^0-9]/g, ''); clearFieldError('bankCardNumber') }
|
const onMerchantTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= merchantTypeOptions.length) return; merchantTypeIndex.value = index; merchantType.value = index == 0 ? 'enterprise' : 'individual'; invalidField.value = ''; errorMessage.value = '' }
|
const onBusinessValidityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= validityOptions.length) return; businessValidityIndex.value = index; businessValidity.value = validityOptions[index]; if (businessValidity.value == '长期') businessEndDate.value = '' }
|
const onSubjectValidityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= subjectValidityOptions.length) return; subjectValidityIndex.value = index; subjectValidity.value = subjectValidityOptions[index]; if (subjectValidity.value == '长期有效') subjectEndDate.value = '' }
|
const onCompanyTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= companyTypeOptions.length) return; companyTypeIndex.value = index; companyType.value = companyTypeOptions[index]; clearFieldError('companyType') }
|
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 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) }) }
|
const submitForm = () => {
|
if (submitting.value || uploadingField.value.length > 0) return
|
saveDraft(); submitError.value = ''
|
for (let step = 1; step <= 4; step = step + 1) {
|
if (!validateStep(step)) {
|
currentStep.value = step
|
resetScroll()
|
uni.showToast({ title: errorMessage.value, icon: 'none' })
|
return
|
}
|
}
|
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(), 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) => {
|
// retryable:通道侧配置已明确失败(例如微信支付配置被拒),这条进件不会自己再往前走,
|
// 停在页面上让商户重新提交,而不是跳去"审核中"把人挡在门外。
|
if ((item.status == 'pending' || item.status == 'approved') && !item.retryable) {
|
uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' })
|
}
|
}, (_error: any) => {})
|
const stored = uni.getStorageSync('merchant-settlement-selected-branch')
|
if (stored == null || stored == '') return
|
try {
|
const branch = JSON.parse(stored as string)
|
bankBranch.value = branch.branchName as string
|
bankName.value = branch.bankName as string
|
bankBranchCode.value = branch.branchCode as string
|
} catch (_error) {}
|
uni.removeStorageSync('merchant-settlement-selected-branch')
|
})
|
</script>
|
|
<style>
|
page { background-color: #F4F6F9; }
|
.page { display: flex; flex: 1; width: 100%; height: 100vh; flex-direction: column; overflow: hidden; background-color: #F4F6F9; }
|
.navigation-bar { display: flex; height: 174rpx; padding: 68rpx 38rpx 0; flex: none; flex-direction: row; align-items: center; position: relative; background-color: #F4F6F9; box-sizing: border-box; }
|
.back-button { display: flex; width: 80rpx; height: 80rpx; justify-content: center; align-items: center; position: absolute; left: 18rpx; top: 79rpx; z-index: 2; }
|
.back-icon { width: 44rpx; height: 44rpx; }
|
.page-title { width: 100%; color: #202B3E; font-size: 34rpx; line-height: 48rpx; text-align: center; font-weight: 700; }
|
.step-bar { display: flex; height: 92rpx; padding: 0 38rpx; flex: none; flex-direction: row; align-items: flex-start; background-color: #F4F6F9; box-sizing: border-box; }
|
.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; }
|
.banner-error { margin-top: 8rpx; padding: 18rpx 24rpx; color: #F06A66; font-size: 24rpx; line-height: 34rpx; background-color: #FEF3F2; border-radius: 20rpx; }
|
|
.field-block { display: flex; margin-bottom: 30rpx; flex-direction: column; }
|
.settlement-title { margin: 6rpx 0 28rpx; color: #202B3E; font-size: 30rpx; line-height: 42rpx; font-weight: 700; }
|
.field-label { display: block; margin-bottom: 14rpx; color: #313744; font-size: 28rpx; line-height: 40rpx; font-weight: 700; }
|
.field-hint { margin-top: 10rpx; color: #9AA4B3; font-size: 22rpx; line-height: 32rpx; }
|
.input-shell, .select-shell { display: flex; width: 100%; height: 104rpx; padding: 0 30rpx; align-items: center; flex-direction: row; border: 1rpx solid #E0E5EC; border-radius: 30rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; background-color: #FFFEFD; box-sizing: border-box; }
|
.text-input { flex: 1; height: 100rpx; border-radius: 30rpx; color: #313744; font-size: 28rpx; line-height: 100rpx; }
|
.input-placeholder, .select-placeholder { color: #B8C0CC; font-size: 28rpx; }
|
.select-shell { justify-content: space-between; }
|
.readonly-shell { background-color: #EDF0F4; }
|
.date-picker, .region-picker { width: 100%; }
|
.down-icon { width: 28rpx; height: 28rpx; flex: none; opacity: 0.45; }
|
.ellipsis-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.upload-box { display: flex; width: 100%; height: 220rpx; justify-content: center; align-items: center; overflow: hidden; border: 1rpx solid #E0E5EC; border-radius: 30rpx; color: #9AA4B3; font-size: 26rpx; background-color: #FFFEFD; box-sizing: border-box; }
|
.document-image { width: 100%; height: 100%; border-radius: 30rpx; }
|
.field-invalid { border-color: #F0746D; background-color: #FFF9F8; }
|
.field-error { margin-top: 10rpx; color: #F06A66; font-size: 24rpx; line-height: 34rpx; }
|
.step-actions { display: flex; margin-top: 24rpx; flex-direction: row; align-items: center; }
|
.secondary-button { display: flex; width: 200rpx; height: 100rpx; margin-right: 24rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; font-weight: 700; background-color: #EDF0F4; }
|
.action-primary { display: flex; flex: 1; height: 100rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; font-weight: 700; background-image: linear-gradient(90deg, #FF8A24 0%, #FF4B36 100%); box-shadow: 0 14rpx 26rpx rgba(255, 101, 48, 0.22); }
|
.button-disabled { opacity: 0.58; }
|
.safe-space { height: 120rpx; }
|
</style>
|