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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
<template>
    <view class="page">
        <view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view><text class="page-title">商户进件</text></view>
        <view class="step-bar"><view class="step-item" v-for="(title, index) in stepTitles" :key="index"><text :class="index + 1 == currentStep ? 'step-index step-index-active' : (index + 1 < currentStep ? 'step-index step-index-done' : 'step-index')">{{index + 1}}</text><text :class="index + 1 == currentStep ? 'step-label step-label-active' : (index + 1 < currentStep ? 'step-label step-label-done' : 'step-label')">{{stepTitle(index)}}</text></view></view>
        <scroll-view class="form-scroll" scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false"><view class="form-body">
            <view v-if="draftNotice" class="draft-hint">已恢复上次未提交的草稿,可继续填写</view>
 
            <view v-if="currentStep == 1">
                <view class="field-block"><text class="field-label required">商户类型</text><picker class="date-picker" mode="selector" :range="merchantTypeOptions" :value="merchantTypeIndex" @change="onMerchantTypeChange"><view class="select-shell"><text>{{merchantType == 'enterprise' ? '企业' : '个人'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view>
                <view v-if="merchantType == 'enterprise'">
                    <view class="field-block"><text class="field-label required">商户名称</text><view class="input-shell" :class="invalidField == 'merchantName' ? 'field-invalid' : ''"><input class="text-input" v-model="merchantName" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('merchantName')" /></view><text class="field-hint">必须与营业执照上的名称完全一致</text><text v-if="invalidField == 'merchantName'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label">商户简称</text><view class="input-shell"><input class="text-input" v-model="merchantShortName" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" /></view><text class="field-hint">会显示在用户的支付页面上,留空则用商户名称</text></view>
                    <view class="field-block"><text class="field-label required">公司类型</text><picker class="date-picker" mode="selector" :range="companyTypeOptions" :value="companyTypeIndex" @change="onCompanyTypeChange"><view class="select-shell" :class="invalidField == 'companyType' ? 'field-invalid' : ''"><text :class="companyType.length == 0 ? 'select-placeholder' : ''">{{companyType.length > 0 ? companyType : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'companyType'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">营业执照</text><view class="upload-box" :class="invalidField == 'businessLicenseUrl' ? 'field-invalid' : ''" @tap="chooseDocument('businessLicense')"><image v-if="businessLicenseUrl.length > 0" class="document-image" :src="businessLicenseUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'businessLicense' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,证照不能过期</text><text v-if="invalidField == 'businessLicenseUrl'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">统一社会信用代码</text><view class="input-shell" :class="invalidField == 'creditCode' ? 'field-invalid' : ''"><input class="text-input" :value="creditCode" maxlength="18" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizeCreditCode" /></view><text class="field-hint">18 位,字母会自动转大写</text><text v-if="invalidField == 'creditCode'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">营业执照有效期</text><picker class="date-picker" mode="selector" :range="validityOptions" :value="businessValidityIndex" @change="onBusinessValidityChange"><view class="select-shell"><text>{{businessValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view>
                    <view class="field-block"><text class="field-label required">营业执照有效期起始日期</text><picker class="date-picker" mode="date" :value="businessStartDate" start="1900-01-01" end="2099-12-31" @change="onBusinessStartChange"><view class="select-shell" :class="invalidField == 'businessStartDate' ? 'field-invalid' : ''"><text :class="businessStartDate.length == 0 ? 'select-placeholder' : ''">{{businessStartDate.length > 0 ? businessStartDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'businessStartDate'" class="field-error">{{errorMessage}}</text></view>
                    <view v-if="businessValidity == '固定期限'" class="field-block"><text class="field-label required">营业执照有效期结束日期</text><picker class="date-picker" mode="date" :value="businessEndDate" start="1900-01-01" end="2099-12-31" @change="onBusinessEndChange"><view class="select-shell" :class="invalidField == 'businessEndDate' ? 'field-invalid' : ''"><text :class="businessEndDate.length == 0 ? 'select-placeholder' : ''">{{businessEndDate.length > 0 ? businessEndDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'businessEndDate'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">成立时间</text><picker class="date-picker" mode="date" :value="establishedDate" start="1900-01-01" end="2099-12-31" @change="onEstablishedDateChange"><view class="select-shell" :class="invalidField == 'establishedDate' ? 'field-invalid' : ''"><text :class="establishedDate.length == 0 ? 'select-placeholder' : ''">{{establishedDate.length > 0 ? establishedDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'establishedDate'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">注册地址(省、市、区)</text><picker class="region-picker" mode="region" @change="onRegisteredRegionChange"><view class="select-shell" :class="invalidField == 'registeredRegion' ? 'field-invalid' : ''"><text class="ellipsis-text" :class="registeredRegion.length == 0 ? 'select-placeholder' : ''">{{registeredRegion.length > 0 ? registeredRegion : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'registeredRegion'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">详细注册地址</text><view class="input-shell" :class="invalidField == 'registeredAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="registeredAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('registeredAddress')" /></view><text class="field-hint">与营业执照上的住所一致</text><text v-if="invalidField == 'registeredAddress'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text :class="capitalRequired() ? 'field-label required' : 'field-label'">注册资本</text><view class="input-shell" :class="invalidField == 'registeredCapital' ? 'field-invalid' : ''"><input class="text-input" type="digit" v-model="registeredCapital" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('registeredCapital')" /></view><text class="field-hint">单位:万元,可填小数</text><text v-if="invalidField == 'registeredCapital'" class="field-error">{{errorMessage}}</text></view>
                </view>
                <view v-if="merchantType == 'individual'">
                    <view class="field-block"><text class="field-label required">行业类型</text><picker class="date-picker" mode="selector" :range="industryTypeOptions" :value="industryTypeIndex" @change="onIndustryTypeChange"><view class="select-shell" :class="invalidField == 'industryType' ? 'field-invalid' : ''"><text :class="industryType.length == 0 ? 'select-placeholder' : ''">{{industryType.length > 0 ? industryType : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'industryType'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">经营地区(省、市、区)</text><picker class="region-picker" mode="region" @change="onRegisteredRegionChange"><view class="select-shell" :class="invalidField == 'registeredRegion' ? 'field-invalid' : ''"><text class="ellipsis-text" :class="registeredRegion.length == 0 ? 'select-placeholder' : ''">{{registeredRegion.length > 0 ? registeredRegion : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'registeredRegion'" class="field-error">{{errorMessage}}</text></view>
                    <view class="field-block"><text class="field-label required">经营详细地址</text><view class="input-shell" :class="invalidField == 'businessAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="businessAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('businessAddress')" /></view><text class="field-hint">与门头照上的地址一致(线下报备需要)</text><text v-if="invalidField == 'businessAddress'" class="field-error">{{errorMessage}}</text></view>
                </view>
                <!-- 门店材料是线下报备的一组三张:门头照(F22)、内景照(F24)、收银台(F105)。通道在
                     进件这一步就按 F-code 逐张核对,缺一张整笔驳回,所以企业与个人主体都要交。 -->
                <view class="field-block"><text class="field-label required">店铺门头照</text><view class="upload-box" :class="invalidField == 'storeHeaderUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeHeader')"><image v-if="storeHeaderUrl.length > 0" class="document-image" :src="storeHeaderUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeHeader' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">实体门头照(招牌清晰可见);流动经营、上门服务传经营现场照</text><text v-if="invalidField == 'storeHeaderUrl'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">店内环境照</text><view class="upload-box" :class="invalidField == 'storeIndoorUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeIndoor')"><image v-if="storeIndoorUrl.length > 0" class="document-image" :src="storeIndoorUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeIndoor' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">店内环境或工作区域照片,不能与门头照相同</text><text v-if="invalidField == 'storeIndoorUrl'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">店铺收银台照片</text><view class="upload-box" :class="invalidField == 'storeCashierUrl' ? 'field-invalid' : ''" @tap="chooseDocument('storeCashier')"><image v-if="storeCashierUrl.length > 0" class="document-image" :src="storeCashierUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'storeCashier' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">收银台或吧台实拍,能看到收款设备/收款码</text><text v-if="invalidField == 'storeCashierUrl'" class="field-error">{{errorMessage}}</text></view>
            </view>
 
            <view v-if="currentStep == 2">
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人姓名' : '个人姓名'}}</text><view class="input-shell" :class="invalidField == 'subjectName' ? 'field-invalid' : ''"><input class="text-input" v-model="subjectName" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('subjectName')" /></view><text class="field-hint">与身份证上的姓名一致</text><text v-if="invalidField == 'subjectName'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件类型' : '本人证件类型'}}</text><view class="select-shell readonly-shell"><text>身份证</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件人像面' : '本人身份证件人像面'}}</text><view class="upload-box" :class="invalidField == 'legalIdFrontUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdFront')"><image v-if="legalIdFrontUrl.length > 0" class="document-image" :src="legalIdFrontUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdFront' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdFrontUrl'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人身份证件国徽面' : '本人身份证件国徽面'}}</text><view class="upload-box" :class="invalidField == 'legalIdBackUrl' ? 'field-invalid' : ''" @tap="chooseDocument('legalIdBack')"><image v-if="legalIdBackUrl.length > 0" class="document-image" :src="legalIdBackUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'legalIdBack' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">JPG/PNG/BMP,单张不超过 2MB,四角完整、不要反光</text><text v-if="invalidField == 'legalIdBackUrl'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件号码' : '本人证件号码'}}</text><view class="input-shell" :class="invalidField == 'subjectIdNumber' ? 'field-invalid' : ''"><input class="text-input" :value="subjectIdNumber" maxlength="18" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizeIdNumber" /></view><text v-if="invalidField == 'subjectIdNumber'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期类型' : '本人证件有效期类型'}}</text><picker class="date-picker" mode="selector" :range="subjectValidityOptions" :value="subjectValidityIndex" @change="onSubjectValidityChange"><view class="select-shell"><text>{{subjectValidity}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期开始日期' : '本人证件有效期开始日期'}}</text><picker class="date-picker" mode="date" :value="subjectStartDate" start="1900-01-01" end="2099-12-31" @change="onSubjectStartChange"><view class="select-shell" :class="invalidField == 'subjectStartDate' ? 'field-invalid' : ''"><text :class="subjectStartDate.length == 0 ? 'select-placeholder' : ''">{{subjectStartDate.length > 0 ? subjectStartDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectStartDate'" class="field-error">{{errorMessage}}</text></view>
                <view v-if="subjectValidity != '长期有效'" class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件有效期截止日期' : '本人证件有效期截止日期'}}</text><picker class="date-picker" mode="date" :value="subjectEndDate" start="1900-01-01" end="2099-12-31" @change="onSubjectEndChange"><view class="select-shell" :class="invalidField == 'subjectEndDate' ? 'field-invalid' : ''"><text :class="subjectEndDate.length == 0 ? 'select-placeholder' : ''">{{subjectEndDate.length > 0 ? subjectEndDate : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text v-if="invalidField == 'subjectEndDate'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人证件地址' : '本人证件地址'}}</text><view class="input-shell" :class="invalidField == 'legalCertificateAddress' ? 'field-invalid' : ''"><input class="text-input" v-model="legalCertificateAddress" maxlength="255" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('legalCertificateAddress')" /></view><text class="field-hint">与身份证上的住址一致,至少 4 个字</text><text v-if="invalidField == 'legalCertificateAddress'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '法人手机号' : '本人手机号'}}</text><view class="input-shell" :class="invalidField == 'legalMobile' ? 'field-invalid' : ''"><input class="text-input" type="number" v-model="legalMobile" maxlength="11" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('legalMobile')" /></view><text class="field-hint">微信实名认证要用经营者/法人本人的手机号,不是联系人手机号</text><text v-if="invalidField == 'legalMobile'" class="field-error">{{errorMessage}}</text></view>
            </view>
 
            <view v-if="currentStep == 3">
                <view v-if="merchantType == 'enterprise'" class="field-block"><text class="field-label required">管理员姓名</text><view class="input-shell" :class="invalidField == 'contactName' ? 'field-invalid' : ''"><input class="text-input" v-model="contactName" maxlength="64" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('contactName')" /></view><text v-if="invalidField == 'contactName'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '管理员手机号' : '手机号'}}</text><view class="input-shell" :class="invalidField == 'contactMobile' ? 'field-invalid' : ''"><input class="text-input" type="number" :value="contactMobile" maxlength="11" placeholder="请输入" placeholder-class="input-placeholder" @input="normalizePhone" /></view><text class="field-hint">汇付审核如需联系会打这个号码</text><text v-if="invalidField == 'contactMobile'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{merchantType == 'enterprise' ? '管理员邮箱' : '邮箱'}}</text><view class="input-shell" :class="invalidField == 'administratorEmail' ? 'field-invalid' : ''"><input class="text-input" v-model="administratorEmail" maxlength="128" placeholder="请输入" placeholder-class="input-placeholder" @input="clearFieldError('administratorEmail')" /></view><text class="field-hint">用于接收进件结果通知,必须能收信</text><text v-if="invalidField == 'administratorEmail'" class="field-error">{{errorMessage}}</text></view>
            </view>
 
            <view v-if="currentStep == 4">
                <view class="settlement-title">结算账户信息</view>
                <view class="field-block"><text class="field-label required">{{bankAccountLabel()}}</text><view class="input-shell" :class="invalidField == 'bankCardNumber' ? 'field-invalid' : ''"><input class="text-input" type="number" maxlength="32" :value="bankCardNumber" placeholder="请输入结算账号" placeholder-class="input-placeholder" @input="normalizeBankNumber" /></view><text v-if="bankAccountLabel() == '对公账号'" class="field-hint">账号名必须与营业执照上的名称一致</text><text v-if="invalidField == 'bankCardNumber'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">开户省份</text><picker class="region-picker" mode="selector" :range="areaProvinceNames" :value="bankProvinceIndex" @change="onBankProvinceChange"><view class="select-shell" :class="invalidField == 'bankProvince' ? 'field-invalid' : ''"><text :class="bankProvince.length == 0 ? 'select-placeholder' : ''">{{bankProvince.length > 0 ? bankProvince : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker></view>
                <view class="field-block"><text class="field-label required">开户城市</text><picker class="region-picker" mode="selector" :range="bankCityNames" :value="bankCityIndex" :disabled="bankProvince.length == 0" @change="onBankCityChange"><view class="select-shell" :class="invalidField == 'bankCity' ? 'field-invalid' : ''"><text :class="bankCity.length == 0 ? 'select-placeholder' : ''">{{bankCity.length > 0 ? bankCity : '请选择'}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view></picker><text class="field-hint">先选省份和城市,再选开户行</text></view>
                <view class="field-block"><text class="field-label required">开户行(支行)</text><view :class="invalidField == 'bankBranch' ? 'select-shell field-invalid' : (bankCityCode.length == 0 ? 'select-shell readonly-shell' : 'select-shell')" @tap="chooseBankBranch"><text class="ellipsis-text" :class="bankBranch.length == 0 ? 'select-placeholder' : ''">{{bankBranch.length > 0 ? bankBranch : (bankCityCode.length == 0 ? '请先选择开户城市' : '请选择')}}</text><image class="down-icon" src="/static/icons/lucide/chevron-down.svg" mode="aspectFit"></image></view><text v-if="invalidField == 'bankBranch'" class="field-error">{{errorMessage}}</text></view>
                <view class="field-block"><text class="field-label required">{{settlementProofLabel()}}</text><view class="upload-box" :class="invalidField == 'settlementProofUrl' ? 'field-invalid' : ''" @tap="chooseDocument('settlementProof')"><image v-if="settlementProofUrl.length > 0" class="document-image" :src="settlementProofUrl" mode="aspectFill"></image><text v-else>{{uploadingField == 'settlementProof' ? '上传中...' : '添加图片'}}</text></view><text class="field-hint">{{settlementProofHint()}}</text><text v-if="invalidField == 'settlementProofUrl'" class="field-error">{{errorMessage}}</text></view>
            </view>
 
            <view v-if="submitError.length > 0" class="banner-error">{{submitError}}</view>
            <view class="step-actions">
                <view v-if="currentStep > 1" class="secondary-button" @tap="goPrevStep"><text>上一步</text></view>
                <view v-if="currentStep < 4" class="action-primary" @tap="goNextStep"><text>下一步</text></view>
                <view v-else class="action-primary" :class="submitting || uploadingField.length > 0 ? 'button-disabled' : ''" @tap="submitForm"><text>{{submitting ? '提交中...' : '提交进件'}}</text></view>
            </view>
            <view class="safe-space"></view>
        </view></scroll-view>
    </view>
</template>
 
<script setup lang="uts">
    import { loadOnboardingStatus, SettlementOnboarding, submitOnboarding as submitOnboardingRemote } from '../../services/settlement.uts'
    import { uploadMerchantProductImage } from '../../services/merchant-management.uts'
    import { getMerchantOnboardingDraftStorageKey } from '../../services/onboarding-draft.uts'
    import { getSession } from '../../services/session.uts'
    import { areaProvinceNames, areaProvinceCodes, areaCityNames, areaCityCodes } from '../../config/area-codes.uts'
 
    const stepTitles = ['主体信息', '法人信息', '联系人', '结算账户']
    /** 个人主体没有法人,第二步展示为「本人信息」。 */
    const stepTitle = (index: number): string => (index == 1 && merchantType.value == 'individual') ? '本人信息' : stepTitles[index]
    const currentStep = ref(1)
    const scrollTop = ref(0)
    const draftNotice = ref(false)
    const submitError = ref('')
 
    const submitting = ref(false); const uploadingField = ref(''); const merchantType = ref('enterprise'); const merchantName = ref(''); const merchantShortName = ref(''); const companyType = ref(''); const companyTypeIndex = ref(-1); const companyTypeOptions = ['政府机构', '国营企业', '私营企业', '外资企业', '个体工商户', '其它组织', '事业单位', '业主委员会']; const industryType = ref(''); const industryTypeIndex = ref(0); const industryTypeOptions = ['餐饮服务', '酒店服务', '景区服务', '加油服务', '停车服务', '其它']; const businessLicenseUrl = ref(''); const creditCode = ref(''); const businessValidity = ref('固定期限'); const businessStartDate = ref(''); const businessEndDate = ref(''); const establishedDate = ref(''); const registeredRegion = ref(''); const registeredProvinceCode = ref(''); const registeredCityCode = ref(''); const registeredDistrictCode = ref(''); const registeredAddress = ref(''); const businessAddress = ref(''); const registeredCapital = ref(''); const subjectName = ref(''); const legalIdFrontUrl = ref(''); const legalIdBackUrl = ref(''); const subjectIdNumber = ref(''); const subjectValidity = ref('10年'); const subjectStartDate = ref(''); const subjectEndDate = ref(''); const legalCertificateAddress = ref(''); const legalMobile = ref(''); const contactName = ref(''); const contactMobile = ref(''); const administratorEmail = ref(''); const bankCardNumber = ref(''); const bankProvince = ref(''); const bankProvinceCode = ref(''); const bankProvinceIndex = ref(0); const bankCity = ref(''); const bankCityCode = ref(''); const bankCityNames = ref([] as string[]); const bankCityCodes = ref([] as string[]); const bankCityIndex = ref(0); const bankBranch = ref(''); const bankBranchCode = ref(''); const bankName = ref(''); const settlementProofUrl = ref(''); const storeHeaderUrl = ref(''); const storeIndoorUrl = ref(''); const storeCashierUrl = ref(''); const invalidField = ref(''); const errorMessage = ref('')
    const merchantTypeOptions = ['企业', '个人']
    const merchantTypeIndex = ref(0)
    const validityOptions = ['固定期限', '长期']
    const businessValidityIndex = ref(0)
    const subjectValidityOptions = ['10年', '20年', '长期有效']
    const subjectValidityIndex = ref(0)
    // 草稿按当前登录的操作员分命名空间,换账号看不到上一个账号的草稿(见 services/onboarding-draft.uts)。
    // 必须在取用点现算:进入页面时机不确定,模块加载时算会把当时的会话固化住。
    const draftStorageKey = (): string => getMerchantOnboardingDraftStorageKey()
    const currentOperatorId = (): string => {
        const session = getSession()
        if (session == null || session.operatorId == null) return ''
        return (session.operatorId as string).trim()
    }
    let draftRestored = false
 
    /** 私营企业等类型汇付要求必须填注册资本,这里与服务端 normalizeRegisteredCapital 保持一致。 */
    const capitalRequired = (): boolean => merchantType.value == 'enterprise' && ['国营企业', '私营企业', '外资企业', '其它组织', '事业单位'].indexOf(companyType.value) >= 0
    const bankAccountLabel = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '对公账号' : '银行卡号'
    const settlementProofLabel = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '开户许可证' : '银行卡卡号面'
    const settlementProofHint = (): string => (merchantType.value == 'enterprise' && companyType.value != '个体工商户') ? '银行出具的开户许可证或基本存款账户信息,JPG/PNG/BMP,不超过 2MB' : '银行卡正面照片,JPG/PNG/BMP,不超过 2MB,卡号清晰'
 
    /** 返回本步骤第一处问题,格式「字段|提示」;没有问题时返回空串。纯函数,不改动错误状态。 */
    const stepProblem = (step: number): string => {
        if (step == 1) {
            if (merchantType.value == 'enterprise') {
                if (merchantName.value.trim().length == 0) return 'merchantName|必须与营业执照一致,请输入商户名称'
                if (companyType.value.length == 0) return 'companyType|请选择公司类型'
                if (businessLicenseUrl.value.length == 0) return 'businessLicenseUrl|请上传营业执照'
                if (!(/^[0-9A-Z]{18}$/).test(creditCode.value)) return 'creditCode|请输入正确的18位统一社会信用代码'
                if (businessStartDate.value.length == 0) return 'businessStartDate|请选择营业执照有效期开始日期'
                if (businessValidity.value == '固定期限' && businessEndDate.value.length == 0) return 'businessEndDate|请选择营业执照有效期结束日期'
                if (businessStartDate.value.length > 0 && businessEndDate.value.length > 0 && businessStartDate.value > businessEndDate.value) return 'businessEndDate|结束日期不能早于开始日期'
                if (establishedDate.value.length == 0) return 'establishedDate|请选择成立时间'
                if (registeredRegion.value.length == 0) return 'registeredRegion|请选择注册地址'
                if (registeredAddress.value.trim().length == 0) return 'registeredAddress|请输入详细注册地址'
                if (capitalRequired() && registeredCapital.value.trim().length == 0) return 'registeredCapital|该类型必须填写注册资本(单位:万元)'
                if (registeredCapital.value.trim().length > 0 && !(/^\d+(\.\d{1,2})?$/).test(registeredCapital.value.trim())) return 'registeredCapital|注册资本请输入数字,单位为万元'
            } else {
                if (industryType.value.length == 0) return 'industryType|请选择行业类型'
                if (registeredRegion.value.length == 0) return 'registeredRegion|请选择经营地区'
                // 经营详细地址:线下报备(entity 有实体经营场所)下它是通道必填项,是进件的 detail_addr。
                if (businessAddress.value.trim().length == 0) return 'businessAddress|请输入经营详细地址'
            }
            // 门店材料是线下报备的一组三张(门头照 F22、内景照 F24、收银台 F105),**不分主体类型**:
            // 通道在进件这一步就按 F-code 逐张核对,缺一张整笔驳回。在这里拦住,好过提交上去以后被驳回
            // ——那时商户得整份进件重走一遍。
            if (storeHeaderUrl.value.length == 0) return 'storeHeaderUrl|请上传店铺门头照'
            if (storeIndoorUrl.value.length == 0) return 'storeIndoorUrl|请上传店内环境照'
            if (storeCashierUrl.value.length == 0) return 'storeCashierUrl|请上传店铺收银台照片'
        }
        if (step == 2) {
            if (subjectName.value.trim().length == 0) return 'subjectName|' + (merchantType.value == 'enterprise' ? '请输入法人姓名' : '请输入个人姓名')
            if (legalIdFrontUrl.value.length == 0) return 'legalIdFrontUrl|请上传身份证人像面'
            if (legalIdBackUrl.value.length == 0) return 'legalIdBackUrl|请上传身份证国徽面'
            if (!(/^\d{17}[0-9X]$/).test(subjectIdNumber.value)) return 'subjectIdNumber|请输入正确的18位身份证号码'
            if (subjectStartDate.value.length == 0) return 'subjectStartDate|请选择证件有效期开始日期'
            if (subjectValidity.value != '长期有效' && subjectEndDate.value.length == 0) return 'subjectEndDate|请选择证件有效期截止日期'
            const address = legalCertificateAddress.value.trim()
            if (address.length == 0) return 'legalCertificateAddress|请输入证件地址(与身份证一致)'
            if (address.length < 4) return 'legalCertificateAddress|证件地址至少 4 个字,请写完整'
            const legalMobileValue = legalMobile.value.trim()
            if (legalMobileValue.length == 0) return 'legalMobile|请输入手机号(微信实名认证要用)'
            if (!(/^1\d{10}$/).test(legalMobileValue)) return 'legalMobile|请输入正确的 11 位手机号'
        }
        if (step == 3) {
            if (merchantType.value == 'enterprise' && contactName.value.trim().length == 0) return 'contactName|请输入管理员姓名'
            if (!(/^1\d{10}$/).test(contactMobile.value)) return 'contactMobile|请输入正确的11位手机号'
            if (!(/^[^\s@]+@[^\s@]+\.[^\s@]+$/).test(administratorEmail.value.trim())) return 'administratorEmail|请输入正确的邮箱,用于接收进件结果'
        }
        if (step == 4) {
            if (!(/^\d{16,32}$/).test(bankCardNumber.value)) return 'bankCardNumber|请输入正确的' + bankAccountLabel()
            if (bankProvinceCode.value.length == 0) return 'bankProvince|请选择开户省份'
            if (bankCityCode.value.length == 0) return 'bankCity|请选择开户城市'
            if (bankBranchCode.value.length == 0) return 'bankBranch|请选择开户行(支行)'
            if (settlementProofUrl.value.length == 0) return 'settlementProofUrl|请上传' + settlementProofLabel()
        }
        return ''
    }
 
    /** 校验某一步:通过返回 true;否则把第一处问题写进 invalidField / errorMessage 并返回 false。 */
    const validateStep = (step: number): boolean => {
        invalidField.value = ''
        errorMessage.value = ''
        const problem = stepProblem(step)
        if (problem.length == 0) return true
        const separator = problem.indexOf('|')
        invalidField.value = problem.substring(0, separator)
        errorMessage.value = problem.substring(separator + 1)
        return false
    }
 
    const resetScroll = () => { scrollTop.value = scrollTop.value == 0 ? 1 : 0 }
    const goNextStep = () => {
        if (!validateStep(currentStep.value)) { uni.showToast({ title: errorMessage.value, icon: 'none' }); return }
        saveDraft()
        if (currentStep.value < 4) currentStep.value = currentStep.value + 1
        resetScroll()
    }
    const goPrevStep = () => { saveDraft(); if (currentStep.value > 1) currentStep.value = currentStep.value - 1; resetScroll() }
 
    const draftPayload = (): any => ({ ownerOperatorId: currentOperatorId(), merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, businessAddress: businessAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, legalMobile: legalMobile.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, storeHeaderUrl: storeHeaderUrl.value, storeIndoorUrl: storeIndoorUrl.value, storeCashierUrl: storeCashierUrl.value, currentStep: currentStep.value })
    const saveDraft = () => { uni.setStorageSync(draftStorageKey(), JSON.stringify(draftPayload())) }
    const clearDraft = () => { uni.removeStorageSync(draftStorageKey()) }
    const restoreDraft = () => {
        const stored = uni.getStorageSync(draftStorageKey())
        if (stored == null || stored == '') return
        try {
            const draft = JSON.parse(stored as string) as any
            // 草稿必须属于当前登录的操作员。key 已经按操作员分命名空间,这里再校验一次归属:
            // 更新前存在旧的全局 key 下的草稿,里面可能有别的账号的姓名、身份证号、银行卡号;
            // 归属不符就直接丢弃,宁可让用户重填,也不能把别人的资料预填进来。
            const owner = draft.ownerOperatorId != null ? (draft.ownerOperatorId as string) : ''
            if (owner != currentOperatorId()) { clearDraft(); return }
            if (draft.merchantType != null) merchantType.value = draft.merchantType as string
            if (draft.merchantName != null) merchantName.value = draft.merchantName as string
            if (draft.merchantShortName != null) merchantShortName.value = draft.merchantShortName as string
            if (draft.companyType != null) companyType.value = draft.companyType as string
            if (draft.companyTypeIndex != null) companyTypeIndex.value = Number(draft.companyTypeIndex)
            if (draft.industryType != null) industryType.value = draft.industryType as string
            if (draft.industryTypeIndex != null) industryTypeIndex.value = Number(draft.industryTypeIndex)
            if (draft.businessLicenseUrl != null) businessLicenseUrl.value = draft.businessLicenseUrl as string
            if (draft.creditCode != null) creditCode.value = draft.creditCode as string
            if (draft.businessValidity != null) businessValidity.value = draft.businessValidity as string
            if (draft.businessStartDate != null) businessStartDate.value = draft.businessStartDate as string
            if (draft.businessEndDate != null) businessEndDate.value = draft.businessEndDate as string
            if (draft.establishedDate != null) establishedDate.value = draft.establishedDate as string
            if (draft.registeredRegion != null) registeredRegion.value = draft.registeredRegion as string
            if (draft.registeredProvinceCode != null) registeredProvinceCode.value = draft.registeredProvinceCode as string
            if (draft.registeredCityCode != null) registeredCityCode.value = draft.registeredCityCode as string
            if (draft.registeredDistrictCode != null) registeredDistrictCode.value = draft.registeredDistrictCode as string
            if (draft.registeredAddress != null) registeredAddress.value = draft.registeredAddress as string
            if (draft.businessAddress != null) businessAddress.value = draft.businessAddress as string
            if (draft.registeredCapital != null) registeredCapital.value = draft.registeredCapital as string
            if (draft.subjectName != null) subjectName.value = draft.subjectName as string
            if (draft.legalIdFrontUrl != null) legalIdFrontUrl.value = draft.legalIdFrontUrl as string
            if (draft.legalIdBackUrl != null) legalIdBackUrl.value = draft.legalIdBackUrl as string
            if (draft.subjectIdNumber != null) subjectIdNumber.value = draft.subjectIdNumber as string
            if (draft.subjectValidity != null) subjectValidity.value = draft.subjectValidity as string
            if (draft.subjectStartDate != null) subjectStartDate.value = draft.subjectStartDate as string
            if (draft.subjectEndDate != null) subjectEndDate.value = draft.subjectEndDate as string
            if (draft.legalCertificateAddress != null) legalCertificateAddress.value = draft.legalCertificateAddress as string
            if (draft.legalMobile != null) legalMobile.value = draft.legalMobile as string
            if (draft.contactName != null) contactName.value = draft.contactName as string
            if (draft.contactMobile != null) contactMobile.value = draft.contactMobile as string
            if (draft.administratorEmail != null) administratorEmail.value = draft.administratorEmail as string
            if (draft.bankCardNumber != null) bankCardNumber.value = draft.bankCardNumber as string
            if (draft.bankProvince != null) bankProvince.value = draft.bankProvince as string
            if (draft.bankProvinceCode != null) bankProvinceCode.value = draft.bankProvinceCode as string
            if (draft.bankProvinceIndex != null) bankProvinceIndex.value = Number(draft.bankProvinceIndex)
            if (draft.bankCity != null) bankCity.value = draft.bankCity as string
            if (draft.bankCityCode != null) bankCityCode.value = draft.bankCityCode as string
            if (draft.bankCityIndex != null) bankCityIndex.value = Number(draft.bankCityIndex)
            if (draft.bankBranch != null) bankBranch.value = draft.bankBranch as string
            if (draft.bankBranchCode != null) bankBranchCode.value = draft.bankBranchCode as string
            if (draft.bankName != null) bankName.value = draft.bankName as string
            if (draft.settlementProofUrl != null) settlementProofUrl.value = draft.settlementProofUrl as string
            if (draft.storeHeaderUrl != null) storeHeaderUrl.value = draft.storeHeaderUrl as string
            if (draft.storeIndoorUrl != null) storeIndoorUrl.value = draft.storeIndoorUrl as string
            if (draft.storeCashierUrl != null) storeCashierUrl.value = draft.storeCashierUrl as string
            if (bankProvince.value.length > 0 && bankProvinceIndex.value >= 0 && bankProvinceIndex.value < areaCityNames.length) { bankCityNames.value = areaCityNames[bankProvinceIndex.value]; bankCityCodes.value = areaCityCodes[bankProvinceIndex.value] }
            merchantTypeIndex.value = merchantType.value == 'individual' ? 1 : 0
            const restoredBusinessValidity = validityOptions.indexOf(businessValidity.value); if (restoredBusinessValidity >= 0) businessValidityIndex.value = restoredBusinessValidity
            const restoredSubjectValidity = subjectValidityOptions.indexOf(subjectValidity.value); if (restoredSubjectValidity >= 0) subjectValidityIndex.value = restoredSubjectValidity
            if (draft.currentStep != null) { const restoredStep = Number(draft.currentStep); currentStep.value = (restoredStep >= 1 && restoredStep <= 4) ? restoredStep : 1 }
            // 走到这里才说明草稿确实属于当前账号,这时才提示"已恢复草稿"。
            draftNotice.value = true
        } catch (_error) { clearDraft() }
    }
    const goBack = () => uni.navigateBack()
    const setError = (field: string, message: string) => { invalidField.value = field; errorMessage.value = message }
    const clearFieldError = (field: string) => { if (invalidField.value == field) { invalidField.value = ''; errorMessage.value = '' }; if (submitError.value.length > 0) submitError.value = '' }
    const normalizeCreditCode = (e: any) => { creditCode.value = (e.detail.value as string).toUpperCase().replace(/[^0-9A-Z]/g, ''); clearFieldError('creditCode') }
    const normalizeIdNumber = (e: any) => { subjectIdNumber.value = (e.detail.value as string).toUpperCase().replace(/[^0-9X]/g, ''); clearFieldError('subjectIdNumber') }
    const normalizePhone = (e: any) => { contactMobile.value = (e.detail.value as string).replace(/[^0-9]/g, ''); clearFieldError('contactMobile') }
    const normalizeBankNumber = (e: any) => { bankCardNumber.value = (e.detail.value as string).replace(/[^0-9]/g, ''); clearFieldError('bankCardNumber') }
    const onMerchantTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= merchantTypeOptions.length) return; merchantTypeIndex.value = index; merchantType.value = index == 0 ? 'enterprise' : 'individual'; invalidField.value = ''; errorMessage.value = '' }
    const onBusinessValidityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= validityOptions.length) return; businessValidityIndex.value = index; businessValidity.value = validityOptions[index]; if (businessValidity.value == '长期') businessEndDate.value = '' }
    const onSubjectValidityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= subjectValidityOptions.length) return; subjectValidityIndex.value = index; subjectValidity.value = subjectValidityOptions[index]; if (subjectValidity.value == '长期有效') subjectEndDate.value = '' }
    const onCompanyTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= companyTypeOptions.length) return; companyTypeIndex.value = index; companyType.value = companyTypeOptions[index]; clearFieldError('companyType') }
    const onIndustryTypeChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= industryTypeOptions.length) return; industryTypeIndex.value = index; industryType.value = industryTypeOptions[index]; clearFieldError('industryType') }
    const onBusinessStartChange = (e: any) => { businessStartDate.value = e.detail.value as string; clearFieldError('businessStartDate') }; const onBusinessEndChange = (e: any) => { businessEndDate.value = e.detail.value as string; clearFieldError('businessEndDate') }; const onEstablishedDateChange = (e: any) => { establishedDate.value = e.detail.value as string; clearFieldError('establishedDate') }; const onSubjectStartChange = (e: any) => { subjectStartDate.value = e.detail.value as string; clearFieldError('subjectStartDate') }; const onSubjectEndChange = (e: any) => { subjectEndDate.value = e.detail.value as string; clearFieldError('subjectEndDate') }
    const onRegisteredRegionChange = (e: any) => { const values = e.detail.value as Array<string>; const codes = e.detail.code as Array<string>; if (values.length != 3 || codes.length != 3) return; registeredRegion.value = values[0] + ' ' + values[1] + ' ' + values[2]; registeredProvinceCode.value = codes[0]; registeredCityCode.value = codes[1]; registeredDistrictCode.value = codes[2]; clearFieldError('registeredRegion') }
    const chooseDocument = (kind: string) => { if (uploadingField.value.length > 0) return; uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (r: any) => { const files = r.tempFiles as Array<any>; if (files.length == 0) return; if ((files[0].size as number) > 2097152) { uni.showToast({ title: '图片不能超过2MB', icon: 'none' }); return }; uploadingField.value = kind; uploadMerchantProductImage(files[0].path as string, (url: string) => { uploadingField.value = ''; if (kind == 'businessLicense') { businessLicenseUrl.value = url; clearFieldError('businessLicenseUrl') } else if (kind == 'legalIdFront') { legalIdFrontUrl.value = url; clearFieldError('legalIdFrontUrl') } else if (kind == 'legalIdBack') { legalIdBackUrl.value = url; clearFieldError('legalIdBackUrl') } else if (kind == 'storeHeader') { storeHeaderUrl.value = url; clearFieldError('storeHeaderUrl') } else if (kind == 'storeIndoor') { storeIndoorUrl.value = url; clearFieldError('storeIndoorUrl') } else if (kind == 'storeCashier') { storeCashierUrl.value = url; clearFieldError('storeCashierUrl') } else { settlementProofUrl.value = url; clearFieldError('settlementProofUrl') } }, (error: any) => { uploadingField.value = ''; uni.showToast({ title: error.message != null ? error.message : '图片上传失败,请重试', icon: 'none' }) }) } }) }
    const onBankProvinceChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= areaProvinceNames.length) return; bankProvinceIndex.value = index; bankProvince.value = areaProvinceNames[index]; bankProvinceCode.value = areaProvinceCodes[index]; bankCityNames.value = areaCityNames[index]; bankCityCodes.value = areaCityCodes[index]; bankCityIndex.value = 0; bankCity.value = ''; bankCityCode.value = ''; bankBranch.value = ''; bankBranchCode.value = ''; bankName.value = '' }
    const onBankCityChange = (event: any) => { const index = parseInt(event.detail.value as string); if (index < 0 || index >= bankCityNames.value.length) return; bankCityIndex.value = index; bankCity.value = bankCityNames.value[index]; bankCityCode.value = bankCityCodes.value[index]; bankBranch.value = ''; bankBranchCode.value = ''; bankName.value = '' }
    const chooseBankBranch = () => { if (bankCityCode.value.length == 0) { uni.showToast({ title: '请先选择开户城市', icon: 'none' }); return }; uni.navigateTo({ url: '/pages/bank-branch-select/bank-branch-select?city=' + encodeURIComponent(bankProvince.value + ' ' + bankCity.value) + '&cityCode=' + encodeURIComponent(bankCityCode.value) }) }
    const submitForm = () => {
        if (submitting.value || uploadingField.value.length > 0) return
        saveDraft(); submitError.value = ''
        for (let step = 1; step <= 4; step = step + 1) {
            if (!validateStep(step)) {
                currentStep.value = step
                resetScroll()
                uni.showToast({ title: errorMessage.value, icon: 'none' })
                return
            }
        }
        submitting.value = true
        submitOnboardingRemote({ subjectType: merchantType.value, subjectName: subjectName.value.trim(), creditCode: creditCode.value, subjectCertificateValidityType: subjectValidity.value == '长期有效' ? 'long_term' : 'fixed', subjectCertificateBeginDate: subjectStartDate.value, subjectCertificateEndDate: subjectEndDate.value, subjectIdNumber: subjectIdNumber.value, licenseValidityType: businessValidity.value == '长期' ? 'long_term' : 'fixed', licenseBeginDate: businessStartDate.value, licenseEndDate: businessEndDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: merchantType.value == 'enterprise' ? registeredAddress.value.trim() : legalCertificateAddress.value.trim(), merchantName: merchantName.value.trim(), merchantShortName: merchantShortName.value.trim(), companyType: merchantType.value == 'enterprise' ? companyType.value : industryType.value, businessLicenseUrl: businessLicenseUrl.value, establishedDate: establishedDate.value, registeredCapital: registeredCapital.value.trim(), legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, legalCertificateAddress: legalCertificateAddress.value.trim(), legalMobile: legalMobile.value.trim(), administratorEmail: administratorEmail.value.trim(), contactName: merchantType.value == 'enterprise' ? contactName.value.trim() : subjectName.value.trim(), contactMobile: contactMobile.value, bankCardNumber: bankCardNumber.value, bankProvinceCode: bankProvinceCode.value, bankCityCode: bankCityCode.value, bankBranchCode: bankBranchCode.value, settlementCardFrontUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? '' : settlementProofUrl.value, registrationAccountUrl: merchantType.value == 'enterprise' && companyType.value != '个体工商户' ? settlementProofUrl.value : '', storeHeaderUrl: storeHeaderUrl.value, storeIndoorUrl: storeIndoorUrl.value, storeCashierUrl: storeCashierUrl.value, businessAddress: merchantType.value == 'individual' ? businessAddress.value.trim() : '' }, 'onboarding-' + Date.now(), (result: SettlementOnboarding) => { submitting.value = false; if (result.status == 'approved') clearDraft(); uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' }) }, (error: any) => { submitting.value = false; submitError.value = error.message != null ? error.message : '提交失败,请重试'; uni.showToast({ title: submitError.value, icon: 'none' }) })
    }
 
    onShow(() => {
        if (!draftRestored) { draftRestored = true; restoreDraft() }
        loadOnboardingStatus((item: SettlementOnboarding) => {
            // retryable:通道侧配置已明确失败(例如微信支付配置被拒),这条进件不会自己再往前走,
            // 停在页面上让商户重新提交,而不是跳去"审核中"把人挡在门外。
            if ((item.status == 'pending' || item.status == 'approved') && !item.retryable) {
                uni.redirectTo({ url: '/pages/merchant-onboarding-pending/merchant-onboarding-pending' })
            }
        }, (_error: any) => {})
        const stored = uni.getStorageSync('merchant-settlement-selected-branch')
        if (stored == null || stored == '') return
        try {
            const branch = JSON.parse(stored as string)
            bankBranch.value = branch.branchName as string
            bankName.value = branch.bankName as string
            bankBranchCode.value = branch.branchCode as string
        } catch (_error) {}
        uni.removeStorageSync('merchant-settlement-selected-branch')
    })
</script>
 
<style>
    page { background-color: #F4F6F9; }
    .page { display: flex; flex: 1; width: 100%; height: 100vh; flex-direction: column; overflow: hidden; background-color: #F4F6F9; }
    .navigation-bar { display: flex; height: 174rpx; padding: 68rpx 38rpx 0; flex: none; flex-direction: row; align-items: center; position: relative; background-color: #F4F6F9; box-sizing: border-box; }
    .back-button { display: flex; width: 80rpx; height: 80rpx; justify-content: center; align-items: center; position: absolute; left: 18rpx; top: 79rpx; z-index: 2; }
    .back-icon { width: 44rpx; height: 44rpx; }
    .page-title { width: 100%; color: #202B3E; font-size: 34rpx; line-height: 48rpx; text-align: center; font-weight: 700; }
    .step-bar { display: flex; height: 92rpx; padding: 0 38rpx; flex: none; flex-direction: row; align-items: flex-start; background-color: #F4F6F9; box-sizing: border-box; }
    .step-item { display: flex; flex: 1; flex-direction: column; align-items: center; }
    .step-index { width: 40rpx; height: 40rpx; border-radius: 20rpx; color: #9AA4B3; font-size: 22rpx; line-height: 40rpx; text-align: center; background-color: #E4E8EF; }
    .step-index-active { color: #FFFFFF; background-color: #FF7625; }
    .step-index-done { color: #FF7625; background-color: #FFF0E4; }
    .step-label { margin-top: 8rpx; color: #9AA4B3; font-size: 22rpx; line-height: 30rpx; }
    .step-label-active { color: #FF7625; font-weight: 700; }
    .step-label-done { color: #C9A08A; }
    .form-scroll { flex: 1; height: 0; background-color: #F4F6F9; }
    .form-body { width: 100%; padding: 8rpx 38rpx 0; background-color: #F4F6F9; box-sizing: border-box; }
    .draft-hint { margin-bottom: 24rpx; padding: 18rpx 24rpx; color: #B4520F; font-size: 24rpx; line-height: 34rpx; background-color: #FFF5EA; border-radius: 20rpx; }
    .banner-error { margin-top: 8rpx; padding: 18rpx 24rpx; color: #F06A66; font-size: 24rpx; line-height: 34rpx; background-color: #FEF3F2; border-radius: 20rpx; }
 
    .field-block { display: flex; margin-bottom: 30rpx; flex-direction: column; }
    .settlement-title { margin: 6rpx 0 28rpx; color: #202B3E; font-size: 30rpx; line-height: 42rpx; font-weight: 700; }
    .field-label { display: block; margin-bottom: 14rpx; color: #313744; font-size: 28rpx; line-height: 40rpx; font-weight: 700; }
    .field-hint { margin-top: 10rpx; color: #9AA4B3; font-size: 22rpx; line-height: 32rpx; }
    .input-shell, .select-shell { display: flex; width: 100%; height: 104rpx; padding: 0 30rpx; align-items: center; flex-direction: row; border: 1rpx solid #E0E5EC; border-radius: 30rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; background-color: #FFFEFD; box-sizing: border-box; }
    .text-input { flex: 1; height: 100rpx; border-radius: 30rpx; color: #313744; font-size: 28rpx; line-height: 100rpx; }
    .input-placeholder, .select-placeholder { color: #B8C0CC; font-size: 28rpx; }
    .select-shell { justify-content: space-between; }
    .readonly-shell { background-color: #EDF0F4; }
    .date-picker, .region-picker { width: 100%; }
    .down-icon { width: 28rpx; height: 28rpx; flex: none; opacity: 0.45; }
    .ellipsis-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .upload-box { display: flex; width: 100%; height: 220rpx; justify-content: center; align-items: center; overflow: hidden; border: 1rpx solid #E0E5EC; border-radius: 30rpx; color: #9AA4B3; font-size: 26rpx; background-color: #FFFEFD; box-sizing: border-box; }
    .document-image { width: 100%; height: 100%; border-radius: 30rpx; }
    .field-invalid { border-color: #F0746D; background-color: #FFF9F8; }
    .field-error { margin-top: 10rpx; color: #F06A66; font-size: 24rpx; line-height: 34rpx; }
    .step-actions { display: flex; margin-top: 24rpx; flex-direction: row; align-items: center; }
    .secondary-button { display: flex; width: 200rpx; height: 100rpx; margin-right: 24rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; font-weight: 700; background-color: #EDF0F4; }
    .action-primary { display: flex; flex: 1; height: 100rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; font-weight: 700; background-image: linear-gradient(90deg, #FF8A24 0%, #FF4B36 100%); box-shadow: 0 14rpx 26rpx rgba(255, 101, 48, 0.22); }
    .button-disabled { opacity: 0.58; }
    .safe-space { height: 120rpx; }
</style>