rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
@@ -1,351 +1,355 @@
<template>
   <view class="page">
      <view class="navigation-bar">
         <view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/chevron-right.svg" mode="aspectFit"></image></view>
         <text class="page-title">商户进件</text>
      </view>
      <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>
      <scroll-view class="form-scroll" scroll-y="true" :show-scrollbar="false" :enhanced="false">
         <view class="form-card subject-card">
            <view class="merchant-type-row" @tap="chooseMerchantType">
               <view class="type-copy"><text class="field-label inline-label">商户类型</text></view>
               <view class="type-control"><text>{{merchantType == 'enterprise' ? '企业' : '个人'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></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="merchantType == 'enterprise'" class="form-card">
            <text class="section-title">企业资料</text>
            <view class="field-block">
               <text class="field-label">统一社会信用代码</text>
               <view class="input-shell" :class="invalidField == 'creditCode' ? 'field-invalid' : ''"><input class="text-input" v-model="creditCode" maxlength="18" placeholder="请输入18位统一社会信用代码" placeholder-class="input-placeholder" @input="clearFieldError('creditCode')" /></view>
               <text v-if="invalidField == 'creditCode'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block">
               <text class="field-label">营业执照有效期</text>
               <view class="select-shell" :class="invalidField == 'businessValidity' ? 'field-invalid' : ''" @tap="chooseBusinessValidity"><text>{{businessValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view>
               <text v-if="invalidField == 'businessValidity'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block">
               <text class="field-label">证照有效期起始日期</text>
               <picker class="date-picker" mode="date" :value="businessStartDate" start="1990-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">证照有效期结束日期</text>
               <picker class="date-picker" mode="date" :value="businessEndDate" start="1990-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">注册地址(省、市、区)</text>
               <view class="select-shell" :class="invalidField == 'registeredRegion' ? 'field-invalid' : ''" @tap="chooseRegisteredRegion"><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>
               <text v-if="invalidField == 'registeredRegion'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block last-field">
               <text class="field-label">详细注册地址</text>
               <view class="input-shell" :class="invalidField == 'registeredAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="registeredAddress" maxlength="80" placeholder="请输入详细注册地址" placeholder-class="input-placeholder" @input="clearFieldError('registeredAddress')" /></view>
               <text v-if="invalidField == 'registeredAddress'" class="field-error">{{errorMessage}}</text>
            </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 class="form-card">
            <text class="section-title">{{merchantType == 'enterprise' ? '法人信息' : '个人信息'}}</text>
            <view class="field-block">
               <text class="field-label">{{merchantType == 'enterprise' ? '法人姓名' : '个人姓名'}}</text>
               <view class="input-shell" :class="invalidField == 'subjectName' ? 'field-invalid' : ''"><input class="text-input" v-model="subjectName" maxlength="30" placeholder="请输入姓名" placeholder-class="input-placeholder" @input="clearFieldError('subjectName')" /></view>
               <text v-if="invalidField == 'subjectName'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block">
               <text class="field-label">{{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">{{merchantType == 'enterprise' ? '法人证件号码' : '个人证件号码'}}</text>
               <view class="input-shell" :class="invalidField == 'subjectIdNumber' ? 'field-invalid' : ''"><input class="text-input" type="idcard" v-model="subjectIdNumber" maxlength="18" placeholder="请输入身份证号码" placeholder-class="input-placeholder" @input="clearFieldError('subjectIdNumber')" /></view>
               <text v-if="invalidField == 'subjectIdNumber'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block">
               <text class="field-label">{{merchantType == 'enterprise' ? '法人证件有效期类型' : '个人证件有效期类型'}}</text>
               <view class="select-shell" :class="invalidField == 'subjectValidity' ? 'field-invalid' : ''" @tap="chooseSubjectValidity"><text>{{subjectValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view>
               <text v-if="invalidField == 'subjectValidity'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block">
               <text class="field-label">{{merchantType == 'enterprise' ? '法人证件有效期开始日期' : '个人证件有效期开始日期'}}</text>
               <picker class="date-picker" mode="date" :value="subjectStartDate" start="1990-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">{{merchantType == 'enterprise' ? '法人证件有效期截止日期' : '个人证件有效期截止日期'}}</text>
               <picker class="date-picker" mode="date" :value="subjectEndDate" start="1990-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 v-if="merchantType == 'individual'" class="field-block last-field">
               <text class="field-label">手机号</text>
               <view class="input-shell" :class="invalidField == 'personalPhone' ? 'field-invalid' : ''"><input class="text-input" type="number" v-model="personalPhone" maxlength="11" placeholder="请输入手机号" placeholder-class="input-placeholder" @input="clearFieldError('personalPhone')" /></view>
               <text v-if="invalidField == 'personalPhone'" class="field-error">{{errorMessage}}</text>
            </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="merchantType == 'enterprise'" class="form-card">
            <text class="section-title">管理员信息</text>
            <view class="field-block">
               <text class="field-label">管理员姓名</text>
               <view class="input-shell" :class="invalidField == 'administratorName' ? 'field-invalid' : ''"><input class="text-input" v-model="administratorName" maxlength="30" placeholder="请输入管理员姓名" placeholder-class="input-placeholder" @input="clearFieldError('administratorName')" /></view>
               <text v-if="invalidField == 'administratorName'" class="field-error">{{errorMessage}}</text>
            </view>
            <view class="field-block last-field">
               <text class="field-label">管理员手机号</text>
               <view class="input-shell" :class="invalidField == 'administratorPhone' ? 'field-invalid' : ''"><input class="text-input" type="number" v-model="administratorPhone" maxlength="11" placeholder="请输入管理员手机号" placeholder-class="input-placeholder" @input="clearFieldError('administratorPhone')" /></view>
               <text v-if="invalidField == 'administratorPhone'" class="field-error">{{errorMessage}}</text>
            </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 class="submit-button" @tap="submitOnboarding"><text>提交审核</text></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>
      </scroll-view>
      </view></scroll-view>
   </view>
</template>
<script setup lang="uts">
   const onboardingStatusStorageKey = 'merchant-withdrawal-onboarding-status'
   const merchantType = ref('enterprise')
   const creditCode = ref('')
   const businessValidity = ref('固定期限')
   const businessStartDate = ref('')
   const businessEndDate = ref('')
   const registeredRegion = ref('')
   const registeredAddress = ref('')
   const subjectName = ref('')
   const subjectIdNumber = ref('')
   const subjectValidity = ref('固定期限')
   const subjectStartDate = ref('')
   const subjectEndDate = ref('')
   const administratorName = ref('')
   const administratorPhone = ref('')
   const personalPhone = ref('')
   const invalidField = ref('')
   const errorMessage = ref('')
   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 regionOptions = ['四川省 攀枝花市 东区', '四川省 攀枝花市 西区', '四川省 攀枝花市 仁和区', '四川省 成都市 锦江区']
   const goBack = () => {
      uni.navigateBack()
   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
   const setFieldError = (field: string, message: string) => {
      invalidField.value = field
      errorMessage.value = message
   }
   /** 私营企业等类型汇付要求必须填注册资本,这里与服务端 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 clearFieldError = (field: string) => {
      if (invalidField.value == field) {
         invalidField.value = ''
         errorMessage.value = ''
      }
   }
   const chooseMerchantType = () => {
      uni.showActionSheet({
         itemList: merchantTypeOptions,
         success: (result: any) => {
            merchantType.value = result.tapIndex == 0 ? 'enterprise' : 'individual'
            invalidField.value = ''
            errorMessage.value = ''
   /** 返回本步骤第一处问题,格式「字段|提示」;没有问题时返回空串。纯函数,不改动错误状态。 */
   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 ''
   }
   const chooseBusinessValidity = () => {
      uni.showActionSheet({
         itemList: validityOptions,
         success: (result: any) => {
            businessValidity.value = validityOptions[result.tapIndex]
            if (businessValidity.value == '长期') businessEndDate.value = ''
            clearFieldError('businessValidity')
         }
      })
   }
   const chooseSubjectValidity = () => {
      uni.showActionSheet({
         itemList: validityOptions,
         success: (result: any) => {
            subjectValidity.value = validityOptions[result.tapIndex]
            if (subjectValidity.value == '长期') subjectEndDate.value = ''
            clearFieldError('subjectValidity')
         }
      })
   }
   const chooseRegisteredRegion = () => {
      uni.showActionSheet({
         itemList: regionOptions,
         success: (result: any) => {
            registeredRegion.value = regionOptions[result.tapIndex]
            clearFieldError('registeredRegion')
         }
      })
   }
   const onBusinessStartChange = (event: any) => {
      businessStartDate.value = event.detail.value as string
      clearFieldError('businessStartDate')
   }
   const onBusinessEndChange = (event: any) => {
      businessEndDate.value = event.detail.value as string
      clearFieldError('businessEndDate')
   }
   const onSubjectStartChange = (event: any) => {
      subjectStartDate.value = event.detail.value as string
      clearFieldError('subjectStartDate')
   }
   const onSubjectEndChange = (event: any) => {
      subjectEndDate.value = event.detail.value as string
      clearFieldError('subjectEndDate')
   }
   const isPhoneValid = (phone: string) => /^1[3-9]\d{9}$/.test(phone)
   const validateCommonIdentity = () => {
      if (subjectName.value.trim().length == 0) {
         setFieldError('subjectName', merchantType.value == 'enterprise' ? '请输入法人姓名' : '请输入个人姓名')
         return false
      }
      if (!(/^\d{17}[\dXx]$/).test(subjectIdNumber.value.trim())) {
         setFieldError('subjectIdNumber', '请输入正确的18位身份证号码')
         return false
      }
      if (subjectStartDate.value.length == 0) {
         setFieldError('subjectStartDate', '请选择证件有效期开始日期')
         return false
      }
      if (subjectValidity.value == '固定期限') {
         if (subjectEndDate.value.length == 0) {
            setFieldError('subjectEndDate', '请选择证件有效期截止日期')
            return false
         }
         if (subjectStartDate.value > subjectEndDate.value) {
            setFieldError('subjectEndDate', '证件截止日期不能早于开始日期')
            return false
         }
      }
      return true
   }
   const validateEnterprise = () => {
      if (!(/^[0-9A-Za-z]{18}$/).test(creditCode.value.trim())) {
         setFieldError('creditCode', '请输入正确的18位统一社会信用代码')
         return false
      }
      if (businessStartDate.value.length == 0) {
         setFieldError('businessStartDate', '请选择营业执照有效期开始日期')
         return false
      }
      if (businessValidity.value == '固定期限') {
         if (businessEndDate.value.length == 0) {
            setFieldError('businessEndDate', '请选择营业执照有效期结束日期')
            return false
         }
         if (businessStartDate.value > businessEndDate.value) {
            setFieldError('businessEndDate', '证照结束日期不能早于开始日期')
            return false
         }
      }
      if (registeredRegion.value.length == 0) {
         setFieldError('registeredRegion', '请选择注册地址')
         return false
      }
      if (registeredAddress.value.trim().length < 5) {
         setFieldError('registeredAddress', '请输入完整的详细注册地址')
         return false
      }
      if (!validateCommonIdentity()) return false
      if (administratorName.value.trim().length == 0) {
         setFieldError('administratorName', '请输入管理员姓名')
         return false
      }
      if (!isPhoneValid(administratorPhone.value.trim())) {
         setFieldError('administratorPhone', '请输入正确的管理员手机号')
         return false
      }
      return true
   }
   const validateIndividual = () => {
      if (!validateCommonIdentity()) return false
      if (!isPhoneValid(personalPhone.value.trim())) {
         setFieldError('personalPhone', '请输入正确的手机号')
         return false
      }
      return true
   }
   const submitOnboarding = () => {
   /** 校验某一步:通过返回 true;否则把第一处问题写进 invalidField / errorMessage 并返回 false。 */
   const validateStep = (step: number): boolean => {
      invalidField.value = ''
      errorMessage.value = ''
      const valid = merchantType.value == 'enterprise' ? validateEnterprise() : validateIndividual()
      if (!valid) {
         uni.showToast({ title: errorMessage.value, icon: 'none' })
         return
      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
         }
      }
      uni.showModal({
         title: '确认提交审核',
         content: '提交后资料将进入审核,审核期间暂不能发起提现。',
         confirmText: '确认提交',
         confirmColor: '#FF6B2C',
         success: (result: any) => {
            if (!result.confirm) return
            uni.setStorageSync(onboardingStatusStorageKey, 'pending')
      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: 64rpx; height: 64rpx; justify-content: center; align-items: center; position: absolute; left: 38rpx; top: 89rpx; z-index: 2; }
   .back-icon { width: 38rpx; height: 38rpx; transform: rotate(180deg); }
   .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; }
   .form-scroll { flex: 1; height: 0; padding: 0 34rpx; box-sizing: border-box; }
   .form-card { margin-top: 26rpx; padding: 30rpx; border-radius: 30rpx; background-color: #FFFFFF; box-shadow: 0 14rpx 34rpx rgba(49, 55, 66, 0.05); box-sizing: border-box; }
   .subject-card { margin-top: 0; padding-top: 20rpx; padding-bottom: 20rpx; }
   .merchant-type-row { display: flex; min-height: 92rpx; flex-direction: row; align-items: center; }
   .type-copy { display: flex; flex: 1; flex-direction: column; }
   .inline-label { margin-bottom: 0; }
   .type-control { display: flex; width: 190rpx; height: 72rpx; padding: 0 18rpx 0 22rpx; flex-direction: row; align-items: center; justify-content: space-between; border-radius: 18rpx; color: #303B4C; font-size: 27rpx; line-height: 38rpx; background-color: #F2F3F5; box-sizing: border-box; }
   .section-title { display: block; margin-bottom: 28rpx; color: #263244; font-size: 31rpx; line-height: 44rpx; font-weight: 700; }
   .field-block { display: flex; margin-bottom: 26rpx; flex-direction: column; }
   .last-field { margin-bottom: 0; }
   .field-label { display: block; margin-bottom: 13rpx; color: #374253; font-size: 27rpx; line-height: 38rpx; font-weight: 600; }
   .input-shell, .select-shell { display: flex; width: 100%; height: 82rpx; padding: 0 22rpx; flex-direction: row; align-items: center; border-width: 1rpx; border-style: solid; border-color: #DDE3EA; border-radius: 18rpx; background-color: #FFFEFD; box-sizing: border-box; }
   .text-input { flex: 1; height: 80rpx; color: #303B4C; font-size: 27rpx; line-height: 80rpx; }
   .input-placeholder, .select-placeholder { color: #A8B1BE; font-size: 26rpx; }
   .select-shell { justify-content: space-between; color: #303B4C; font-size: 27rpx; line-height: 38rpx; }
   .readonly-shell { color: #5A6472; background-color: #F0F2F5; border-color: #F0F2F5; }
   .date-picker { width: 100%; }
   .down-icon { width: 30rpx; height: 30rpx; margin-left: 14rpx; flex: none; }
   .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: 9rpx; color: #E95B55; font-size: 23rpx; line-height: 33rpx; }
   .submit-button { display: flex; height: 88rpx; margin-top: 34rpx; justify-content: center; align-items: center; border-radius: 44rpx; color: #FFFFFF; font-size: 29rpx; line-height: 41rpx; font-weight: 700; background-image: linear-gradient(90deg, #FF7625 0%, #FF9630 100%); box-shadow: 0 12rpx 24rpx rgba(255, 107, 36, 0.20); }
   .safe-space { height: 70rpx; }
</style>
   .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>