|
|
@@ -0,0 +1,560 @@
|
|
|
+<template>
|
|
|
+ <view class="page" v-if="loaded">
|
|
|
+ <view v-if="mode !== 'settlement-change'" class="steps">
|
|
|
+ <view v-for="(name, index) in stepNames" :key="name" class="step" :class="{ active: currentStep >= index + 1 }" @click="goStep(index + 1)">
|
|
|
+ <view class="circle">{{ index + 1 }}</view>
|
|
|
+ <text>{{ name }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="currentStep === 1 && mode !== 'settlement-change'">
|
|
|
+ <SectionTitle title="商家基本情况" />
|
|
|
+ <view class="box choice-row">
|
|
|
+ <text class="label required" style="width: 280upx;">商户类别</text>
|
|
|
+ <radio-group @change="setValue('license.merchantType', $event.detail.value)">
|
|
|
+ <label><radio value="TP_MERCHANT" :checked="form.license.merchantType === 'TP_MERCHANT'" />企业</label>
|
|
|
+ <label><radio value="TP_PERSONAL" :checked="form.license.merchantType === 'TP_PERSONAL'" />小微</label>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+ <AttachmentCard v-if="form.license.merchantType === 'TP_MERCHANT'" title="营业执照" imgType="BUSINESS_LICENCE" :items="form.license.attachments" @upload="uploadAttachment('license', 'BUSINESS_LICENCE', true)" />
|
|
|
+ <view class="box">
|
|
|
+ <FormInput v-if="form.license.merchantType === 'TP_MERCHANT'" label="营业执照名称" v-model="form.license.licenseName" required />
|
|
|
+ <FormInput v-if="form.license.merchantType === 'TP_MERCHANT'" label="证件代码" v-model="form.license.licenseNo" required />
|
|
|
+ <DateInput v-if="form.license.merchantType === 'TP_MERCHANT'" label="执照起始日期" v-model="form.license.licenseStart" />
|
|
|
+ <DateInput v-if="form.license.merchantType === 'TP_MERCHANT'" label="执照有效期" v-model="form.license.licenseEnd" />
|
|
|
+ <FormInput v-if="form.license.merchantType === 'TP_MERCHANT'" label="注册地址" v-model="form.license.registeredAddress" />
|
|
|
+ </view>
|
|
|
+ <AttachmentCard title="身份证人像面" imgType="ID_CARD_FRONT" :items="form.license.attachments" @upload="uploadAttachment('license', 'ID_CARD_FRONT', true)" />
|
|
|
+ <AttachmentCard title="身份证国徽面" imgType="ID_CARD_BEHIND" :items="form.license.attachments" @upload="uploadAttachment('license', 'ID_CARD_BEHIND', true)" />
|
|
|
+ <view class="box">
|
|
|
+ <!-- <FormInput label="经营范围" v-model="form.license.businessContent" required /> -->
|
|
|
+ <view v-if="form.license.merchantType === 'TP_PERSONAL'">
|
|
|
+ <FormInput label="姓名" v-model="form.license.legalName" required />
|
|
|
+ </view>
|
|
|
+ <view v-if="form.license.merchantType === 'TP_MERCHANT'">
|
|
|
+ <FormInput label="法人姓名" v-model="form.license.legalName" required />
|
|
|
+ </view>
|
|
|
+ <FormInput label="身份证号" v-model="form.license.legalIdNo" required />
|
|
|
+ <DateInput label="身份证起始日期" v-model="form.license.legalIdStart" />
|
|
|
+ <DateInput label="身份证有效期" v-model="form.license.legalIdEnd" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="currentStep === 2 && mode !== 'settlement-change'">
|
|
|
+ <SectionTitle title="商户信息" />
|
|
|
+ <view class="box">
|
|
|
+ <FormInput label="商户经营名称" v-model="form.merchant.businessName" required />
|
|
|
+ <view class="form-row">
|
|
|
+ <text class="form-label required">商户所在地区</text>
|
|
|
+ <button class="mini-btn" @click="chooseRegion('merchant', 'region')">{{ merchantRegionText }}</button>
|
|
|
+ </view>
|
|
|
+ <FormInput label="详细地址" v-model="form.merchant.address" required />
|
|
|
+ </view>
|
|
|
+ <view class="tip">商户类型固定为花店(5992);联系人、手机号和邮箱按系统默认值提交。</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="currentStep === 3">
|
|
|
+ <SectionTitle title="结算信息" />
|
|
|
+ <view class="box choice-row">
|
|
|
+ <text class="label required" style="width: 290upx;">账户类型</text>
|
|
|
+ <radio-group @change="setValue('settlement.accountType', $event.detail.value)">
|
|
|
+ <label><radio value="58" :checked="form.settlement.accountType === '58'" />对私</label>
|
|
|
+ <label><radio value="57" :checked="form.settlement.accountType === '57'" />对公</label>
|
|
|
+ </radio-group>
|
|
|
+ </view>
|
|
|
+ <view v-if="form.settlement.accountType === '58'" class="box switch-row">
|
|
|
+ <text>是否法人进件</text>
|
|
|
+ <switch :checked="form.settlement.isLegalPerson" @change="setValue('settlement.isLegalPerson', $event.detail.value)" color="#09c567" />
|
|
|
+ </view>
|
|
|
+ <AttachmentCard v-if="form.settlement.accountType === '58'" title="银行卡照片" imgType="BANK_CARD" :items="form.settlement.attachments" @upload="uploadAttachment('settlement', 'BANK_CARD', true)" />
|
|
|
+ <template v-if="form.settlement.accountType === '58' && !form.settlement.isLegalPerson">
|
|
|
+ <AttachmentCard title="结算人身份证人像面" imgType="ID_CARD_FRONT" :items="form.settlement.attachments" @upload="uploadAttachment('settlement', 'ID_CARD_FRONT', true)" />
|
|
|
+ <AttachmentCard title="结算人身份证国徽面" imgType="ID_CARD_BEHIND" :items="form.settlement.attachments" @upload="uploadAttachment('settlement', 'ID_CARD_BEHIND', true)" />
|
|
|
+ <AttachmentCard title="法人授权函" imgType="LETTER_OF_AUTHORIZATION" :items="form.settlement.attachments" @upload="uploadAttachment('settlement', 'LETTER_OF_AUTHORIZATION', false)" />
|
|
|
+ </template>
|
|
|
+ <AttachmentCard v-if="form.settlement.accountType === '57'" title="开户许可证" imgType="OPENING_PERMIT" :items="form.settlement.attachments" @upload="uploadAttachment('settlement', 'OPENING_PERMIT', false)" />
|
|
|
+ <view class="box">
|
|
|
+ <FormInput label="开户名" v-model="form.settlement.accountName" required />
|
|
|
+ <FormInput label="银行卡号" v-model="form.settlement.accountNo" required />
|
|
|
+ <FormInput label="开户银行" v-model="form.settlement.bankName" required />
|
|
|
+ <view class="form-row">
|
|
|
+ <text class="form-label required">开户地区</text>
|
|
|
+ <button class="mini-btn" @click="chooseRegion('settlement', 'bank-region')">{{ settlementRegionText }}</button>
|
|
|
+ </view>
|
|
|
+ <view class="form-row">
|
|
|
+ <text class="form-label required">开户支行</text>
|
|
|
+ <button class="mini-btn" @click="chooseBank">{{ settlementBranchText }}</button>
|
|
|
+ </view>
|
|
|
+ <FormInput v-if="!form.settlement.isLegalPerson" label="结算人身份证号" v-model="form.settlement.accountIdNo" />
|
|
|
+ <DateInput v-if="!form.settlement.isLegalPerson" label="结算人证件起始日" v-model="form.settlement.accountIdStart" />
|
|
|
+ <DateInput v-if="!form.settlement.isLegalPerson" label="结算人证件有效期" v-model="form.settlement.accountIdEnd" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="currentStep === 4 && mode !== 'settlement-change'">
|
|
|
+ <SectionTitle title="门店信息" />
|
|
|
+ <AttachmentCard title="门头照" imgType="SHOP_OUTSIDE_IMG" :items="form.shop.attachments" @upload="uploadAttachment('shop', 'SHOP_OUTSIDE_IMG', false)" />
|
|
|
+ <AttachmentCard title="内设照" imgType="SHOP_INSIDE_IMG" :items="form.shop.attachments" @upload="uploadAttachment('shop', 'SHOP_INSIDE_IMG', false)" />
|
|
|
+ <AttachmentCard title="收银台照" imgType="CHECKSTAND_IMG" :items="form.shop.attachments" @upload="uploadAttachment('shop', 'CHECKSTAND_IMG', false)" />
|
|
|
+ <view class="box">
|
|
|
+ <FormInput label="门店名称" v-model="form.shop.name" required />
|
|
|
+ <FormInput label="详细地址" v-model="form.shop.address" required />
|
|
|
+ <FormInput label="门店联系人" v-model="form.shop.contactName" required />
|
|
|
+ <FormInput label="联系手机号" v-model="form.shop.contactMobile" required />
|
|
|
+ <!-- <FormInput label="经度" v-model="form.shop.longitude" required />
|
|
|
+ <FormInput label="纬度" v-model="form.shop.latitude" required /> -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="currentStep === 5 && mode !== 'settlement-change'">
|
|
|
+ <SectionTitle title="业务信息" />
|
|
|
+ <view class="box summary">
|
|
|
+ <view><text>结算类型</text><text>自动结算</text></view>
|
|
|
+ <view><text>结算周期</text><text>D1</text></view>
|
|
|
+ <view><text>商户类型</text><text>花店(5992)</text></view>
|
|
|
+ <view><text>批发费率</text><text>{{ form.business.rate }}%</text></view>
|
|
|
+ </view>
|
|
|
+ <view class="box">
|
|
|
+ <FormInput label="电子合同签约手机号" v-model="form.business.signMobile" required />
|
|
|
+ </view>
|
|
|
+ <view class="contract">
|
|
|
+ <button class="outline" @click="createContract">生成并签署电子合同</button>
|
|
|
+ <button class="outline" @click="checkContract">刷新签约状态</button>
|
|
|
+ <text :class="{ signed: contractStatus === 'COMPLETED' }">签约状态:{{ contractStatusText }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="bottom-space"></view>
|
|
|
+ <view class="bottom">
|
|
|
+ <button v-if="currentStep > 1 && mode !== 'settlement-change'" class="back" @click="previous">上一步</button>
|
|
|
+ <button v-if="currentStep < 5 && mode !== 'settlement-change'" class="next" @click="next">下一步</button>
|
|
|
+ <button v-else class="next" @click="submit">{{ mode === 'settlement-change' ? '提交申请' : '提交审核' }}</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 省市区/支行等超长列表选择,小程序 showActionSheet 最多 6 项不够用 -->
|
|
|
+ <OptionPicker
|
|
|
+ :visible="optionPicker.visible"
|
|
|
+ :title="optionPicker.title"
|
|
|
+ :options="optionPicker.options"
|
|
|
+ @select="onOptionPickerSelect"
|
|
|
+ @cancel="onOptionPickerCancel"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ applicationDetail, saveDraft, uploadFile, ocrResult, aliyunOcr, applyContract,
|
|
|
+ contractStatus, submitApplication, getOption
|
|
|
+} from "@/api/lakala-account";
|
|
|
+import SectionTitle from "./lakala-components/SectionTitle.vue";
|
|
|
+import FormInput from "./lakala-components/FormInput.vue";
|
|
|
+import DateInput from "./lakala-components/DateInput.vue";
|
|
|
+import AttachmentCard from "./lakala-components/AttachmentCard.vue";
|
|
|
+import OptionPicker from "./lakala-components/OptionPicker.vue";
|
|
|
+
|
|
|
+export default {
|
|
|
+ components: { SectionTitle, FormInput, DateInput, AttachmentCard, OptionPicker },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ id: 0, mode: 'create', loaded: false, currentStep: 1, form: null,
|
|
|
+ stepNames: ['执照信息', '商户信息', '结算信息', '门店信息', '业务信息'],
|
|
|
+ contractStatus: '', saveTimer: null, saving: false,
|
|
|
+ // 通用选项弹层状态,resolve 用于 chooseOption / chooseBank 的 Promise
|
|
|
+ optionPicker: { visible: false, title: '', options: [], resolve: null }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ contractStatusText() {
|
|
|
+ return { COMPLETED: '已完成', UNDONE: '待签署' }[this.contractStatus] || '未生成';
|
|
|
+ },
|
|
|
+ merchantRegionText() {
|
|
|
+ const m = this.form && this.form.merchant;
|
|
|
+ return m && m.countyCode ? `${m.provinceName || ''}${m.cityName || ''}${m.countyName || ''}` : '请选择地区';
|
|
|
+ },
|
|
|
+ settlementRegionText() {
|
|
|
+ const s = this.form && this.form.settlement;
|
|
|
+ return s && s.cityCode ? `${s.provinceName || ''}${s.cityName || ''}` : '请选择开户地区';
|
|
|
+ },
|
|
|
+ /** 已选支行展示名称,未选时提示查询 */
|
|
|
+ settlementBranchText() {
|
|
|
+ const s = this.form && this.form.settlement;
|
|
|
+ if (!s || !s.bankNo) return '查询并选择支行';
|
|
|
+ return s.bankName || s.bankNo;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ form: {
|
|
|
+ deep: true,
|
|
|
+ handler() {
|
|
|
+ if (!this.loaded || this.saving) return;
|
|
|
+ clearTimeout(this.saveTimer);
|
|
|
+ this.saveTimer = setTimeout(() => this.persist(), 800);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad(option) {
|
|
|
+ this.id = Number(option.id || 0);
|
|
|
+ this.mode = option.mode || 'create';
|
|
|
+ this.load();
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ if (this.loaded && this.contractStatus === 'UNDONE') this.checkContract(false);
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ clearTimeout(this.saveTimer);
|
|
|
+ this.persist();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ init() {
|
|
|
+ },
|
|
|
+ async load() {
|
|
|
+ const res = await applicationDetail({ id: this.id });
|
|
|
+ this.form = res.data.formData;
|
|
|
+ this.applyDefaultMerchantType();
|
|
|
+ this.normalizeFormAttachments();
|
|
|
+ this.applyDefaultExpiryDates();
|
|
|
+ this.currentStep = this.mode === 'settlement-change' ? 3 : Number(res.data.currentStep || 1);
|
|
|
+ this.contractStatus = res.data.contractStatus || '';
|
|
|
+ this.loaded = true;
|
|
|
+ },
|
|
|
+ /** 新建进件默认按企业商户处理;已有草稿或详情值不覆盖 */
|
|
|
+ applyDefaultMerchantType() {
|
|
|
+ if (!this.form.license.merchantType) {
|
|
|
+ this.$set(this.form.license, 'merchantType', 'TP_MERCHANT');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /** 返回当天日期 yyyy-MM-dd,供 date picker 默认值使用 */
|
|
|
+ getTodayDateStr() {
|
|
|
+ const d = new Date();
|
|
|
+ const pad = n => (n < 10 ? '0' : '') + n;
|
|
|
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
|
+ },
|
|
|
+ /** 执照/身份证有效期未填时默认当天,减少用户重复选择 */
|
|
|
+ applyDefaultExpiryDates() {
|
|
|
+ const today = this.getTodayDateStr();
|
|
|
+ if (!this.form.license.legalIdEnd || this.form.license.legalIdEnd == '9999-12-31') {
|
|
|
+ this.$set(this.form.license, 'legalIdEnd', today);
|
|
|
+ }
|
|
|
+ if (!this.form.license.licenseEnd || this.form.license.licenseEnd == '9999-12-31') {
|
|
|
+ //this.$set(this.form.license, 'licenseEnd', today);
|
|
|
+ this.$set(this.form.license, 'licenseEnd', '2100-01-01');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async persist() {
|
|
|
+ if (!this.form || this.saving) return;
|
|
|
+ this.saving = true;
|
|
|
+ try {
|
|
|
+ await saveDraft({ id: this.id, currentStep: this.currentStep, formData: JSON.stringify(this.form) });
|
|
|
+ } catch (e) {
|
|
|
+ // Submitted applications are intentionally no longer editable.
|
|
|
+ } finally {
|
|
|
+ this.saving = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ setValue(path, value) {
|
|
|
+ const parts = path.split('.');
|
|
|
+ this.$set(this.form[parts[0]], parts[1], value);
|
|
|
+ },
|
|
|
+ async chooseRegion(section, type) {
|
|
|
+ const province = await this.chooseOption(type, '1', '选择省份');
|
|
|
+ if (!province) return;
|
|
|
+ const city = await this.chooseOption(type, province.code, '选择城市');
|
|
|
+ if (!city) return;
|
|
|
+ this.$set(this.form[section], 'provinceCode', province.code);
|
|
|
+ this.$set(this.form[section], 'provinceName', province.name);
|
|
|
+ this.$set(this.form[section], 'cityCode', city.code);
|
|
|
+ this.$set(this.form[section], 'cityName', city.name);
|
|
|
+ if (section === 'merchant') {
|
|
|
+ const county = await this.chooseOption(type, city.code, '选择区县');
|
|
|
+ if (county) {
|
|
|
+ this.$set(this.form.merchant, 'countyCode', county.code);
|
|
|
+ this.$set(this.form.merchant, 'countyName', county.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async chooseOption(type, parentCode, title) {
|
|
|
+ const res = await getOption({ type, parentCode });
|
|
|
+ const list = this.normalizeOptions(res.data);
|
|
|
+ if (!list.length) {
|
|
|
+ uni.showToast({ title: '没有可选数据', icon: 'none' });
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return this.openOptionPicker(title, list);
|
|
|
+ },
|
|
|
+ /** 打开可滚动选项弹层,返回选中项或 null */
|
|
|
+ openOptionPicker(title, options) {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ this.optionPicker = { visible: true, title, options, resolve };
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onOptionPickerSelect(item) {
|
|
|
+ const { resolve } = this.optionPicker;
|
|
|
+ this.optionPicker.visible = false;
|
|
|
+ this.optionPicker.resolve = null;
|
|
|
+ if (resolve) resolve(item);
|
|
|
+ },
|
|
|
+ onOptionPickerCancel() {
|
|
|
+ const { resolve } = this.optionPicker;
|
|
|
+ this.optionPicker.visible = false;
|
|
|
+ this.optionPicker.resolve = null;
|
|
|
+ if (resolve) resolve(null);
|
|
|
+ },
|
|
|
+ /** OCR/接口日期统一为 yyyy-MM-dd,供 date picker 使用 */
|
|
|
+ normalizeDateStr(value) {
|
|
|
+ if (!value) return value;
|
|
|
+ const str = String(value).trim();
|
|
|
+ if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return str;
|
|
|
+ const cn = str.match(/^(\d{4})年(\d{1,2})月(\d{1,2})日$/);
|
|
|
+ if (cn) {
|
|
|
+ const pad = n => (n < 10 ? '0' : '') + Number(n);
|
|
|
+ return `${cn[1]}-${pad(cn[2])}-${pad(cn[3])}`;
|
|
|
+ }
|
|
|
+ const dot = str.match(/^(\d{4})\.(\d{1,2})\.(\d{1,2})$/);
|
|
|
+ if (dot) {
|
|
|
+ const pad = n => (n < 10 ? '0' : '') + Number(n);
|
|
|
+ return `${dot[1]}-${pad(dot[2])}-${pad(dot[3])}`;
|
|
|
+ }
|
|
|
+ return str;
|
|
|
+ },
|
|
|
+ /** 统一省市区、支行等选项结构;兼容 camelCase 与 snake_case 字段 */
|
|
|
+ normalizeOptions(data) {
|
|
|
+ const source = Array.isArray(data) ? data : ((data && data.data) || []);
|
|
|
+ return source.map(v => ({
|
|
|
+ code: String(
|
|
|
+ v.code
|
|
|
+ || v.branch_bank_no
|
|
|
+ || v.area_code || v.bank_no || v.id || ''
|
|
|
+ ),
|
|
|
+ name: v.name || v.area_name
|
|
|
+ || v.branch_bank_name
|
|
|
+ || v.bank_name || '',
|
|
|
+ raw: v
|
|
|
+ })).filter(v => v.code && v.name);
|
|
|
+ },
|
|
|
+ async chooseBank() {
|
|
|
+ if (!this.form.settlement.cityCode) { //|| !this.form.settlement.bankName
|
|
|
+ uni.showToast({ title: '请先选择开户地区', icon: 'none' }); //并填写银行名称
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = await getOption({ type: 'bank', parentCode: this.form.settlement.cityCode, keyword: this.form.settlement.bankName });
|
|
|
+ const list = this.normalizeOptions(res.data);
|
|
|
+ if (!list.length) {
|
|
|
+ uni.showToast({ title: '没有查询到支行', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const selected = await this.openOptionPicker('选择开户支行', list);
|
|
|
+ if (!selected) return;
|
|
|
+ const row = selected.raw || selected;
|
|
|
+ this.$set(this.form.settlement, 'bankName', row.branch_bank_name);
|
|
|
+ // bankNo 取支行联行号,不能用 bank_no(仅为银行类别码,如 102)
|
|
|
+ this.$set(this.form.settlement, 'bankNo', String(row.branch_bank_no || ''));
|
|
|
+ this.$set(this.form.settlement, 'clearingBankNo', String(row.clear_no || ''));
|
|
|
+ },
|
|
|
+ goStep(step) {
|
|
|
+ if (step < this.currentStep) {
|
|
|
+ this.currentStep = step;
|
|
|
+ this.persist();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ previous() { this.currentStep--; this.persist(); },
|
|
|
+ next() {
|
|
|
+ if (!this.validateStep(this.currentStep)) return;
|
|
|
+ if (this.currentStep === 3) {
|
|
|
+ this.form.shop.name = this.form.merchant.businessName;
|
|
|
+ this.form.shop.address = this.form.merchant.address;
|
|
|
+ this.form.shop.provinceCode = this.form.merchant.provinceCode;
|
|
|
+ this.form.shop.cityCode = this.form.merchant.cityCode;
|
|
|
+ this.form.shop.countyCode = this.form.merchant.countyCode;
|
|
|
+ this.form.shop.provinceName = this.form.merchant.provinceName;
|
|
|
+ this.form.shop.cityName = this.form.merchant.cityName;
|
|
|
+ this.form.shop.countyName = this.form.merchant.countyName;
|
|
|
+ }
|
|
|
+ this.currentStep++;
|
|
|
+ this.persist();
|
|
|
+ },
|
|
|
+ validateStep(step) {
|
|
|
+ const required = {
|
|
|
+ 1: ['license.legalName', 'license.legalIdNo', 'license.legalIdStart', 'license.legalIdEnd'],
|
|
|
+ 2: ['merchant.businessName', 'merchant.provinceCode', 'merchant.cityCode', 'merchant.countyCode', 'merchant.address'],
|
|
|
+ 3: ['settlement.accountName', 'settlement.accountNo', 'settlement.bankName', 'settlement.bankNo', 'settlement.clearingBankNo', 'settlement.provinceCode', 'settlement.cityCode'],
|
|
|
+ 4: ['shop.name', 'shop.address', 'shop.contactName', 'shop.contactMobile', 'shop.longitude', 'shop.latitude'],
|
|
|
+ 5: ['business.signMobile']
|
|
|
+ }[step] || [];
|
|
|
+ // 经营范围仅企业商户必填(小微无营业执照,无此字段)
|
|
|
+ if (step === 1 && this.form.license.merchantType === 'TP_MERCHANT') {
|
|
|
+ required.push('license.businessContent');
|
|
|
+ }
|
|
|
+ const missing = required.some(path => {
|
|
|
+ const [section, field] = path.split('.');
|
|
|
+ return !this.form[section][field];
|
|
|
+ });
|
|
|
+ if (missing) uni.showToast({ title: '请填写完整必填信息', icon: 'none' });
|
|
|
+ return !missing;
|
|
|
+ },
|
|
|
+ async uploadAttachment(section, imgType, isOcr) {
|
|
|
+ const path = await this.chooseImage();
|
|
|
+ if (!path) return;
|
|
|
+ uni.showLoading({ title: '上传识别中', mask: true });
|
|
|
+ try {
|
|
|
+ const content = await this.toBase64(path);
|
|
|
+ const useLakalaOcr = isOcr && imgType === 'BUSINESS_LICENCE';
|
|
|
+ const res = await uploadFile({ id: this.id, content, imgType, isOcr: useLakalaOcr ? 1 : 0 });
|
|
|
+ const list = this.form[section].attachments || [];
|
|
|
+ const attachment = this.normalizeAttachment(res.data);
|
|
|
+ const oldIndex = list.findIndex(v => v.imgType === imgType);
|
|
|
+ if (oldIndex >= 0) list.splice(oldIndex, 1, attachment); else list.push(attachment);
|
|
|
+ this.$set(this.form[section], 'attachments', list);
|
|
|
+ if (isOcr && (attachment.batchNo || attachment.ossPath || attachment.lakalaUrl || attachment.url)) {
|
|
|
+ await this.pollOcr(section, attachment, 0);
|
|
|
+ }
|
|
|
+ await this.persist();
|
|
|
+ } finally {
|
|
|
+ uni.hideLoading();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ normalizeAttachment(attachment) {
|
|
|
+ return Object.assign({}, attachment, {
|
|
|
+ lakalaUrl: attachment.lakalaUrl || attachment.url || '',
|
|
|
+ batchNo: attachment.batchNo || '',
|
|
|
+ imgType: attachment.imgType || '',
|
|
|
+ ocrStatus: attachment.ocrStatus || attachment.status || '',
|
|
|
+ ocrResult: attachment.ocrResult || attachment.ocr || null
|
|
|
+ });
|
|
|
+ },
|
|
|
+ normalizeFormAttachments() {
|
|
|
+ ['license', 'settlement', 'shop'].forEach(section => {
|
|
|
+ const list = (this.form[section] && this.form[section].attachments) || [];
|
|
|
+ this.$set(this.form[section], 'attachments', list.map(item => this.normalizeAttachment(item)));
|
|
|
+ });
|
|
|
+ },
|
|
|
+ chooseImage() {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: res => resolve(res.tempFilePaths[0]), fail: () => resolve('') });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ toBase64(path) {
|
|
|
+ // #ifdef MP-WEIXIN
|
|
|
+ return new Promise((resolve, reject) => uni.getFileSystemManager().readFile({ filePath: path, encoding: 'base64', success: res => resolve('data:image/png;base64,' + res.data), fail: reject }));
|
|
|
+ // #endif
|
|
|
+ // #ifdef H5
|
|
|
+ return fetch(path).then(r => r.blob()).then(blob => new Promise(resolve => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }));
|
|
|
+ // #endif
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ return new Promise((resolve, reject) => { plus.io.resolveLocalFileSystemURL(path, entry => { entry.file(file => { const reader = new plus.io.FileReader(); reader.onloadend = e => resolve(e.target.result); reader.onerror = reject; reader.readAsDataURL(file); }); }, reject); });
|
|
|
+ // #endif
|
|
|
+ },
|
|
|
+ async pollOcr(section, attachment, count) {
|
|
|
+ if (count > 8) return;
|
|
|
+ const type = attachment.imgType;
|
|
|
+ if (type === 'BUSINESS_LICENCE') {
|
|
|
+ const res = await ocrResult({ id: this.id, batchNo: attachment.batchNo, imgType: attachment.imgType });
|
|
|
+ this.updateAttachment(section, attachment.imgType, {
|
|
|
+ ocrStatus: res.data.status || '',
|
|
|
+ ocrResult: res.data.result || null
|
|
|
+ });
|
|
|
+ if (res.data.status === '01') {
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 1200));
|
|
|
+ return this.pollOcr(section, attachment, count + 1);
|
|
|
+ }
|
|
|
+ if (res.data.status === '00' && res.data.result) this.applyOcr(section, attachment.imgType, res.data.result);
|
|
|
+ } else {
|
|
|
+ const imageUrl = attachment.ossPath || attachment.lakalaUrl || attachment.url || '';
|
|
|
+ if (!imageUrl) return;
|
|
|
+ const res = await aliyunOcr({ id: this.id, imgType: attachment.imgType, imageUrl });
|
|
|
+ this.updateAttachment(section, attachment.imgType, {
|
|
|
+ ocrStatus: res.data.status || '',
|
|
|
+ ocrResult: res.data.result || null
|
|
|
+ });
|
|
|
+ if (res.data.status === '00' && res.data.result) this.applyOcr(section, attachment.imgType, res.data.result);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ updateAttachment(section, imgType, patch) {
|
|
|
+ const list = this.form[section].attachments || [];
|
|
|
+ const index = list.findIndex(v => v.imgType === imgType);
|
|
|
+ if (index < 0) return;
|
|
|
+ this.$set(list, index, Object.assign({}, list[index], patch));
|
|
|
+ this.$set(this.form[section], 'attachments', list);
|
|
|
+ },
|
|
|
+ applyOcr(section, type, result) {
|
|
|
+ if (type === 'BUSINESS_LICENCE') {
|
|
|
+ Object.assign(this.form.license, {
|
|
|
+ licenseName: result.biz_license_address[1] || '',
|
|
|
+ licenseNo: result.biz_license_credit_code || result.biz_license_registration_code || this.form.license.licenseNo,
|
|
|
+ registeredAddress: result.biz_license_address[0] || '',
|
|
|
+ businessContent: result.biz_license_scope || this.form.license.businessContent,
|
|
|
+ legalName: result.biz_license_owner_name || this.form.license.legalName,
|
|
|
+ licenseStart: this.normalizeDateStr(result.biz_license_start_time) || this.form.license.licenseStart
|
|
|
+ });
|
|
|
+ if (!this.form.merchant.address) this.$set(this.form.merchant, 'address', result.biz_license_address || '');
|
|
|
+ } else if (type === 'ID_CARD_FRONT') {
|
|
|
+ const target = section === 'license' ? this.form.license : this.form.settlement;
|
|
|
+ this.$set(target, section === 'license' ? 'legalName' : 'accountName', result.name || target.accountName || '');
|
|
|
+ this.$set(target, section === 'license' ? 'legalIdNo' : 'accountIdNo', result.id_number || '');
|
|
|
+ } else if (type === 'ID_CARD_BEHIND') {
|
|
|
+ const validity = String(result.validity || '').split('-');
|
|
|
+ const target = section === 'license' ? this.form.license : this.form.settlement;
|
|
|
+ if (validity.length >= 2) {
|
|
|
+ this.$set(target, section === 'license' ? 'legalIdStart' : 'accountIdStart', this.normalizeDateStr(validity[0]));
|
|
|
+ this.$set(target, section === 'license' ? 'legalIdEnd' : 'accountIdEnd', this.normalizeDateStr(validity.slice(1).join('-')));
|
|
|
+ }
|
|
|
+ } else if (type === 'BANK_CARD') {
|
|
|
+ this.$set(this.form.settlement, 'accountNo', result.card_number || result.bank_card_number || this.form.settlement.accountNo);
|
|
|
+ this.$set(this.form.settlement, 'bankName', result.bank_name || this.form.settlement.bankName);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async createContract() {
|
|
|
+ if (!this.validateStep(5)) return;
|
|
|
+ await this.persist();
|
|
|
+ const res = await applyContract({ id: this.id });
|
|
|
+ this.contractStatus = res.data.status;
|
|
|
+ uni.navigateTo({ url: `/common/webview?url=${encodeURIComponent(res.data.resultUrl)}` });
|
|
|
+ },
|
|
|
+ async checkContract(show = true) {
|
|
|
+ if (!this.contractStatus) {
|
|
|
+ if (show) uni.showToast({ title: '请先生成电子合同', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const res = await contractStatus({ id: this.id });
|
|
|
+ this.contractStatus = res.data.status;
|
|
|
+ if (show) uni.showToast({ title: this.contractStatusText, icon: 'none' });
|
|
|
+ },
|
|
|
+ async submit() {
|
|
|
+ if (!this.validateStep(this.currentStep)) return;
|
|
|
+ await this.persist();
|
|
|
+ const res = await submitApplication({ id: this.id, formData: JSON.stringify(this.form) });
|
|
|
+ uni.showToast({ title: this.mode === 'settlement-change' ? '申请已提交' : '已提交后台审核', icon: 'none' });
|
|
|
+ setTimeout(() => uni.navigateBack(), 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page { min-height: 100vh; background: #f2f7f4; padding-bottom: 30upx; font-size: 28upx; }
|
|
|
+.steps { display: flex; background: #fff; padding: 30upx 12upx; border-radius: 0 0 28upx 28upx; }
|
|
|
+.step { flex: 1; text-align: center; color: #999; font-size: 27.5upx; position: relative; }
|
|
|
+.step:not(:last-child):before { content: ''; position: absolute; height: 4upx; background: #d8ebe2; top: 25upx; left: 60%; right: -40%; }
|
|
|
+.step.active:not(:last-child):before { background: #09c567; }
|
|
|
+.circle { width: 52upx; height: 52upx; line-height: 52upx; border-radius: 50%; background: #d8ebe2; color: #fff; margin: 0 auto 12upx; position: relative; z-index: 1; }
|
|
|
+.active { color: #222; }.active .circle { background: #09c567; }
|
|
|
+.box { margin: 0 24upx 24upx; padding: 24upx; background: #fff; border-radius: 18upx; }
|
|
|
+.choice-row, .switch-row { display: flex; justify-content: space-between; align-items: center; }
|
|
|
+.choice-row label { margin-left: 26upx; font-size: 28upx; }
|
|
|
+.label, .form-label { color: #34445c; }
|
|
|
+.required:before { content: '*'; color: #f33; margin-right: 6upx; }
|
|
|
+.form-row { min-height: 88upx; display: flex; align-items: center; border-bottom: 1upx solid #eee; }
|
|
|
+.form-label { width: 270upx; flex-shrink: 0; }
|
|
|
+.mini-btn { flex: 1; margin: 0; padding: 0 12upx; background: #fff; color: #09c567; font-size: 32.5upx; line-height: 64upx; text-align: right; }
|
|
|
+.tip { margin: 20upx 30upx; color: #999; font-size: 35upx; line-height: 52.5upx; }
|
|
|
+.summary view { display: flex; justify-content: space-between; font-size: 35upx; line-height: 87.5upx; border-bottom: 1upx solid #eee; }
|
|
|
+.contract { margin: 24upx; display: flex; flex-direction: column; text-align: center; color: #999; font-size: 35upx; }
|
|
|
+.contract > view, .contract > button { margin-top: 18upx; }
|
|
|
+.contract > view:first-child, .contract > button:first-child { margin-top: 0; }
|
|
|
+.signed { color: #09c567; }
|
|
|
+.bottom-space { height: 150upx; }
|
|
|
+.bottom { position: fixed; left: 0; right: 0; bottom: 0; display: flex; padding: 20upx 28upx calc(20upx + env(safe-area-inset-bottom)); background: #fff; }
|
|
|
+.bottom button { flex: 1; border-radius: 50upx; font-size: 35upx; }
|
|
|
+.bottom button + button { margin-left: 18upx; }
|
|
|
+.next { color: #fff; background: #09c567; }.back, .outline { color: #09c567; background: #fff; border: 1upx solid #09c567; }
|
|
|
+</style>
|