yeg
2026-09-14 04d52ff58b9d5ee4d5ffa64e213d3b015414418f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<template>
    <view class="page">
        <view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view><text class="page-title">商户进件</text></view>
        <view class="step-bar"><view class="step-item" v-for="(title, index) in stepTitles" :key="index"><text :class="currentStep - 1 >= index ? 'step-index step-index-active' : 'step-index'">{{index + 1}}</text><text :class="currentStep - 1 >= index ? 'step-label step-label-active' : 'step-label'">{{title}}</text></view></view>
        <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>
            </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>
 
            <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 { areaProvinceNames, areaProvinceCodes, areaCityNames, areaCityCodes } from '../../config/area-codes.uts'
 
    const stepTitles = ['主体信息', '法人信息', '联系人', '结算账户']
    const currentStep = ref(1)
    const scrollTop = ref(0)
    const draftNotice = ref(false)
    const submitError = ref('')
 
    const submitting = ref(false); const uploadingField = ref(''); const merchantType = ref('enterprise'); const merchantName = ref(''); const merchantShortName = ref(''); const companyType = ref(''); const companyTypeIndex = ref(-1); const companyTypeOptions = ['政府机构', '国营企业', '私营企业', '外资企业', '个体工商户', '其它组织', '事业单位', '业主委员会']; const industryType = ref(''); const industryTypeIndex = ref(0); const industryTypeOptions = ['餐饮服务', '酒店服务', '景区服务', '加油服务', '停车服务', '其它']; const businessLicenseUrl = ref(''); const creditCode = ref(''); const businessValidity = ref('固定期限'); const businessStartDate = ref(''); const businessEndDate = ref(''); const establishedDate = ref(''); const registeredRegion = ref(''); const registeredProvinceCode = ref(''); const registeredCityCode = ref(''); const registeredDistrictCode = ref(''); const registeredAddress = ref(''); const registeredCapital = ref(''); const subjectName = ref(''); const legalIdFrontUrl = ref(''); const legalIdBackUrl = ref(''); const subjectIdNumber = ref(''); const subjectValidity = ref('10年'); const subjectStartDate = ref(''); const subjectEndDate = ref(''); const legalCertificateAddress = ref(''); const contactName = ref(''); const contactMobile = ref(''); const administratorEmail = ref(''); const bankCardNumber = ref(''); const bankProvince = ref(''); const bankProvinceCode = ref(''); const bankProvinceIndex = ref(0); const bankCity = ref(''); const bankCityCode = ref(''); const bankCityNames = ref([] as string[]); const bankCityCodes = ref([] as string[]); const bankCityIndex = ref(0); const bankBranch = ref(''); const bankBranchCode = ref(''); const bankName = ref(''); const settlementProofUrl = ref(''); const invalidField = ref(''); const errorMessage = ref('')
    const merchantTypeOptions = ['企业', '个人']
    const merchantTypeIndex = ref(0)
    const validityOptions = ['固定期限', '长期']
    const businessValidityIndex = ref(0)
    const subjectValidityOptions = ['10年', '20年', '长期有效']
    const subjectValidityIndex = ref(0)
    const draftStorageKey = 'merchant-onboarding-draft'
    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|请选择经营地区'
            }
        }
        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 个字,请写完整'
        }
        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 => ({ merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, currentStep: currentStep.value })
    const saveDraft = () => { uni.setStorageSync(draftStorageKey, JSON.stringify(draftPayload())) }
    const clearDraft = () => { uni.removeStorageSync(draftStorageKey) }
    const restoreDraft = () => {
        const stored = uni.getStorageSync(draftStorageKey)
        if (stored == null || stored == '') return
        try {
            const draft = JSON.parse(stored as string) as any
            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.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.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 (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 { 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(), administratorEmail: administratorEmail.value.trim(), contactName: merchantType.value == 'enterprise' ? contactName.value.trim() : subjectName.value.trim(), contactMobile: contactMobile.value, bankCardNumber: bankCardNumber.value, bankProvinceCode: bankProvinceCode.value, bankCityCode: bankCityCode.value, bankBranchCode: bankBranchCode.value, settlementCardFrontUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? '' : settlementProofUrl.value, registrationAccountUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? settlementProofUrl.value : '' }, 'onboarding-' + Date.now(), (result: SettlementOnboarding) => { submitting.value = false; if (result.status == 'approved') clearDraft(); uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' }) }, (error: any) => { submitting.value = false; submitError.value = error.message != null ? error.message : '提交失败,请重试'; uni.showToast({ title: submitError.value, icon: 'none' }) })
    }
 
    onShow(() => {
        if (!draftRestored) { draftRestored = true; restoreDraft() }
        loadOnboardingStatus((item: SettlementOnboarding) => {
            if (item.status == 'pending' || item.status == 'approved') {
                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-label { margin-top: 8rpx; color: #9AA4B3; font-size: 22rpx; line-height: 30rpx; }
    .step-label-active { color: #FF7625; font-weight: 700; }
    .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; border: 2rpx solid #FFFFFF; border-radius: 30rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; background-color: #FFFFFF; box-shadow: 0 14rpx 28rpx rgba(43, 44, 52, 0.10); box-sizing: border-box; }
    .text-input { flex: 1; height: 100rpx; 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; box-shadow: none; }
    .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: 2rpx solid #FFFFFF; border-radius: 30rpx; color: #9AA4B3; font-size: 26rpx; background-color: #FFFFFF; box-shadow: 0 14rpx 28rpx rgba(43, 44, 52, 0.10); box-sizing: border-box; }
    .document-image { width: 100%; height: 100%; border-radius: 30rpx; }
    .field-invalid { border-color: #F05243; }
    .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>