Преглед на файлове

拉卡拉进件页面实现

shizhongqi преди 1 месец
родител
ревизия
f426ec083e

+ 8 - 6
ghsApp/src/admin/item/lakala-components/AttachmentCard.vue

@@ -14,15 +14,17 @@ export default {
   computed: {
     item() { return this.items.find(v => v.imgType === this.imgType); },
     imageSrc() {
-      return `${this.$constant.imgUrl}/${this.item.ossPath}`;
+      const path = this.item.ossPath || '';
+      if (/^https?:\/\//.test(path)) return path;
+      return `${this.$constant.imgUrl}/${path}`;
     }
   }
 };
 </script>
 <style scoped>
-.attachment { margin: 0 24rpx 24rpx; padding: 24rpx; background: #fff; border-radius: 18rpx; min-height: 180rpx; display: flex; align-items: center; justify-content: space-between; }
-.attach-title { display: block; font-size: 32rpx; color: #111; margin-bottom: 12rpx; }
-.attach-tip { color: #999; font-size: 25rpx; }
-.attachment image, .camera { width: 260rpx; height: 160rpx; border-radius: 12rpx; background: #e9f3ff; }
-.camera { color: #288bf5; font-size: 70rpx; display: flex; align-items: center; justify-content: center; }
+.attachment { margin: 0 24upx 24upx; padding: 24upx; background: #fff; border-radius: 18upx; min-height: 180upx; display: flex; align-items: center; justify-content: space-between; }
+.attach-title { display: block; font-size: 32upx; color: #111; margin-bottom: 12upx; }
+.attach-tip { color: #999; font-size: 28upx; }
+.attachment image, .camera { width: 260upx; height: 160upx; border-radius: 12upx; background: #e9f3ff; }
+.camera { color: #288bf5; font-size: 60upx; display: flex; align-items: center; justify-content: center; }
 </style>

+ 4 - 4
ghsApp/src/admin/item/lakala-components/DateInput.vue

@@ -10,8 +10,8 @@
 export default { props: { value: { default: '' }, label: { type: String, default: '' } } };
 </script>
 <style scoped>
-.form-row { min-height: 88rpx; display: flex; align-items: center; border-bottom: 1rpx solid #eee; }
-.form-label { width: 270rpx; flex-shrink: 0; color: #34445c; }
-.required:before { content: '*'; color: #f33; margin-right: 6rpx; }
-.form-input { flex: 1; text-align: right; color: #333; }
+.form-row { min-height: 88upx; display: flex; align-items: center; border-bottom: 1upx solid #eee; }
+.form-label { width: 270upx; flex-shrink: 0; color: #34445c; font-size: 28upx; }
+.required:before { content: '*'; color: #f33; margin-right: 6upx; }
+.form-input { flex: 1; text-align: right; color: #333; font-size: 28upx; }
 </style>

+ 4 - 4
ghsApp/src/admin/item/lakala-components/FormInput.vue

@@ -8,8 +8,8 @@
 export default { props: { value: { default: '' }, label: { type: String, default: '' }, required: { type: Boolean, default: false } } };
 </script>
 <style scoped>
-.form-row { min-height: 88rpx; display: flex; align-items: center; border-bottom: 1rpx solid #eee; }
-.form-label { width: 270rpx; flex-shrink: 0; color: #34445c; }
-.required:before { content: '*'; color: #f33; margin-right: 6rpx; }
-.form-input { flex: 1; text-align: right; color: #333; }
+.form-row { min-height: 88upx; display: flex; align-items: center; border-bottom: 1upx solid #eee; }
+.form-label { width: 270upx; flex-shrink: 0; color: #34445c; font-size: 28upx; }
+.required:before { content: '*'; color: #f33; margin-right: 6upx; }
+.form-input { flex: 1; text-align: right; color: #333; font-size: 28upx; }
 </style>

+ 8 - 8
ghsApp/src/admin/item/lakala-components/OptionPicker.vue

@@ -60,23 +60,23 @@ export default {
   right: 0;
   bottom: 0;
   background: #fff;
-  border-radius: 24rpx 24rpx 0 0;
+  border-radius: 24upx 24upx 0 0;
   padding-bottom: env(safe-area-inset-bottom);
 }
 .picker-header {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 28rpx 32rpx;
-  border-bottom: 1rpx solid #eee;
+  padding: 28upx 32upx;
+  border-bottom: 1upx solid #eee;
 }
 .picker-title {
-  font-size: 30rpx;
+  font-size: 32upx;
   font-weight: 600;
   color: #222;
 }
 .picker-cancel {
-  font-size: 28rpx;
+  font-size: 35upx;
   color: #999;
 }
 .picker-list {
@@ -84,9 +84,9 @@ export default {
   height: 65vh;
 }
 .picker-item {
-  padding: 28rpx 32rpx;
-  font-size: 28rpx;
+  padding: 28upx 32upx;
+  font-size: 35upx;
   color: #333;
-  border-bottom: 1rpx solid #f5f5f5;
+  border-bottom: 1upx solid #f5f5f5;
 }
 </style>

+ 1 - 1
ghsApp/src/admin/item/lakala-components/SectionTitle.vue

@@ -1,3 +1,3 @@
 <template><view class="section-title">{{ title }}</view></template>
 <script>export default { props: { title: { type: String, default: '' } } };</script>
-<style scoped>.section-title { margin: 28rpx 28rpx 16rpx; font-size: 30rpx; font-weight: 600; border-left: 8rpx solid #288bf5; padding-left: 16rpx; }</style>
+<style scoped>.section-title { margin: 28rpx 28rpx 16rpx; font-size: 37.5rpx; font-weight: 600; border-left: 8rpx solid #288bf5; padding-left: 16rpx; }</style>

+ 23 - 23
ghsApp/src/admin/item/lakala.vue

@@ -18,7 +18,7 @@
         <button v-if="item.canContinueDraft" class="outline" @click="resumeApplication(item.id)">继续填写</button>
         <button v-if="item.canDeleteDraft" class="danger" @click="removeDraft(item.id)">删除</button>
         <button v-if="item.canSign" class="outline" @click="resumeApplication(item.id)">继续签约</button>
-        <button v-if="item.canResubmit" class="primary" @click="resumeApplication(item.id)">修改申请</button>
+        <button v-if="item.canResubmit" class="primary" @click="resumeApplication(item.id)">修改</button>
         <button v-if="item.canRefresh" class="outline" @click="refresh(item.id)">刷新状态</button>
       </view>
     </view>
@@ -56,8 +56,8 @@
 <script>
 import { accountList, createApplication, deleteDraft, refreshApplication, setCurrentAccount } from "@/api/lakala-account";
 
-const STATUS_TEXT = { DRAFT: '草稿', CONTRACT_PENDING: '待签约', WAIT_AUDIT: '待审核', REJECT: '已驳回', APPROVED: '认证通过' };
-const STATUS_CLASS = { REJECT: 'rejected', APPROVED: 'passed', WAIT_AUDIT: 'pending', CONTRACT_PENDING: 'pending' };
+const STATUS_TEXT = { DRAFT: '草稿', CONTRACT_PENDING: '待签约', PLATFORM_AUDIT: '后台审核', WAIT_AUDIT: '待审核', REJECT: '已驳回', APPROVED: '认证通过' };
+const STATUS_CLASS = { REJECT: 'rejected', APPROVED: 'passed', WAIT_AUDIT: 'pending', CONTRACT_PENDING: 'pending', PLATFORM_AUDIT: 'pending' };
 
 export default {
   data() {
@@ -94,7 +94,7 @@ export default {
         canDeleteDraft: status === 'DRAFT',
         canSign: status === 'CONTRACT_PENDING',
         canResubmit: status === 'REJECT',
-        canRefresh: status === 'WAIT_AUDIT' || status === 'APPROVED' || status === 'REJECT'
+        canRefresh: status === 'WAIT_AUDIT' || status === 'APPROVED' //status === 'REJECT'
       };
     },
     /** 收款账户列表项补充展示字段 */
@@ -170,28 +170,28 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.page { min-height: 100vh; background: #f5f7fa; padding: 24rpx; box-sizing: border-box; }
-.empty { padding: 160rpx 0; text-align: center; color: #999; }
-.card { background: #fff; border-radius: 18rpx; padding: 28rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,.05); }
-.account-card { border-left: 6rpx solid #17a85b; }
-.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20rpx; }
-.title { font-size: 32rpx; font-weight: 600; color: #222; }
-.status { padding: 8rpx 18rpx; border-radius: 10rpx; color: #777; background: #f3f3f3; }
+.page { min-height: 100vh; background: #f5f7fa; padding: 24upx; box-sizing: border-box; }
+.empty { padding: 160upx 0; text-align: center; color: #999; }
+.card { background: #fff; border-radius: 18upx; padding: 28upx; margin-bottom: 24upx; box-shadow: 0 4upx 20upx rgba(0,0,0,.05); }
+.account-card { border-left: 6upx solid #17a85b; }
+.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20upx; }
+.title { font-size: 32upx; font-weight: 600; color: #222; }
+.status { padding: 8upx 18upx; border-radius: 10upx; color: #777; background: #f3f3f3; }
 .pending { color: #139a55; background: #ecfaf2; }
 .passed { color: #159957; background: #ecfaf2; }
 .rejected { color: #f04a4a; background: #fff0f0; }
-.line { color: #444; font-size: 27rpx; line-height: 48rpx; }
-.reject { color: #f04a4a; background: #fff3f3; padding: 16rpx; margin-top: 16rpx; border-radius: 10rpx; }
-.actions { display: flex; margin-top: 24rpx; }
-.actions button { flex: 1; font-size: 27rpx; line-height: 72rpx; padding: 0 12rpx; margin-left: 18rpx; }
+.line { color: #444; font-size: 28upx; line-height: 48upx; }
+.reject { color: #f04a4a; background: #fff3f3; padding: 16upx; margin-top: 16upx; border-radius: 10upx; }
+.actions { display: flex; margin-top: 24upx; }
+.actions button { flex: 1; font-size: 28upx; line-height: 72upx; padding: 0 12upx; margin-left: 18upx; }
 .actions button:first-child { margin-left: 0; }
 .primary, .add { color: #fff; background: #079b52; }
-.outline { color: #079b52; background: #fff; border: 1rpx solid #079b52; }
-.danger { color: #f04a4a; background: #fff; border: 1rpx solid #f04a4a; }
-.auth-box { margin-top: 22rpx; padding-top: 22rpx; border-top: 1rpx dashed #e2e8f0; }
-.auth-tip { color: #666; font-size: 26rpx; line-height: 40rpx; margin-bottom: 8rpx; }
-.auth-btn { margin-top: 18rpx; color: #fff; background: #18a85d; font-size: 27rpx; line-height: 72rpx; }
-.bottom-space { height: 130rpx; }
-.bottom { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom)); }
-.add { border-radius: 12rpx; }
+.outline { color: #079b52; background: #fff; border: 1upx solid #079b52; }
+.danger { color: #f04a4a; background: #fff; border: 1upx solid #f04a4a; }
+.auth-box { margin-top: 22upx; padding-top: 22upx; border-top: 1upx dashed #e2e8f0; }
+.auth-tip { color: #666; font-size: 26upx; line-height: 40upx; margin-bottom: 8upx; }
+.auth-btn { margin-top: 18upx; color: #fff; background: #18a85d; font-size: 28upx; line-height: 72upx; }
+.bottom-space { height: 130upx; }
+.bottom { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; padding: 20upx 28upx calc(20upx + env(safe-area-inset-bottom)); }
+.add { border-radius: 12upx; }
 </style>

+ 84 - 32
ghsApp/src/admin/item/lakalaOnboarding.vue

@@ -10,10 +10,10 @@
     <view v-if="currentStep === 1 && mode !== 'settlement-change'">
       <SectionTitle title="商家基本情况" />
       <view class="box choice-row">
-        <text class="label required">商户类别</text>
+        <text class="label required" style="width: 280upx;">商户类别</text>
         <radio-group @change="setValue('license.merchantType', $event.detail.value)">
-          <label><radio value="TP_PERSONAL" :checked="form.license.merchantType === 'TP_PERSONAL'" />小微</label>
           <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)" />
@@ -56,7 +56,7 @@
     <view v-if="currentStep === 3">
       <SectionTitle title="结算信息" />
       <view class="box choice-row">
-        <text class="label required">账户类型</text>
+        <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>
@@ -128,7 +128,7 @@
     <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>
+      <button v-else class="next" @click="submit">{{ mode === 'settlement-change' ? '提交申请' : '提交审核' }}</button>
     </view>
 
     <!-- 省市区/支行等超长列表选择,小程序 showActionSheet 最多 6 项不够用 -->
@@ -144,7 +144,7 @@
 
 <script>
 import {
-  applicationDetail, saveDraft, uploadFile, ocrResult, applyContract,
+  applicationDetail, saveDraft, uploadFile, ocrResult, aliyunOcr, applyContract,
   contractStatus, submitApplication, getOption
 } from "@/api/lakala-account";
 import SectionTitle from "./lakala-components/SectionTitle.vue";
@@ -206,14 +206,24 @@ export default {
     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();
@@ -387,17 +397,36 @@ export default {
       uni.showLoading({ title: '上传识别中', mask: true });
       try {
         const content = await this.toBase64(path);
-        const res = await uploadFile({ id: this.id, content, imgType, isOcr: isOcr ? 1 : 0 });
+        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, res.data); else list.push(res.data);
+        if (oldIndex >= 0) list.splice(oldIndex, 1, attachment); else list.push(attachment);
         this.$set(this.form[section], 'attachments', list);
-        if (isOcr && res.data.batchNo) await this.pollOcr(section, res.data, 0);
+        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('') });
@@ -416,12 +445,35 @@ export default {
     },
     async pollOcr(section, attachment, count) {
       if (count > 8) return;
-      const res = await ocrResult({ id: this.id, batchNo: attachment.batchNo, imgType: attachment.imgType });
-      if (res.data.status === '01') {
-        await new Promise(resolve => setTimeout(resolve, 1200));
-        return this.pollOcr(section, attachment, count + 1);
+      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);
       }
-      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') {
@@ -470,7 +522,7 @@ export default {
       if (!this.validateStep(this.currentStep)) return;
       await this.persist();
       const res = await submitApplication({ id: this.id, formData: JSON.stringify(this.form) });
-      uni.showToast({ title: '申请已提交', icon: 'none' });
+      uni.showToast({ title: this.mode === 'settlement-change' ? '申请已提交' : '已提交后台审核', icon: 'none' });
       setTimeout(() => uni.navigateBack(), 1000);
     }
   }
@@ -478,27 +530,27 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.page { min-height: 100vh; background: #f5f7fa; padding-bottom: 30rpx; }
-.steps { display: flex; background: #fff; padding: 30rpx 12rpx; border-radius: 0 0 28rpx 28rpx; }
-.step { flex: 1; text-align: center; color: #999; font-size: 22rpx; position: relative; }
-.step:not(:last-child):before { content: ''; position: absolute; height: 4rpx; background: #d8e8fa; top: 25rpx; left: 60%; right: -40%; }
+.page { min-height: 100vh; background: #f5f7fa; 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: #d8e8fa; top: 25upx; left: 60%; right: -40%; }
 .step.active:not(:last-child):before { background: #288bf5; }
-.circle { width: 52rpx; height: 52rpx; line-height: 52rpx; border-radius: 50%; background: #d8e8fa; color: #fff; margin: 0 auto 12rpx; position: relative; z-index: 1; }
+.circle { width: 52upx; height: 52upx; line-height: 52upx; border-radius: 50%; background: #d8e8fa; color: #fff; margin: 0 auto 12upx; position: relative; z-index: 1; }
 .active { color: #222; }.active .circle { background: #288bf5; }
-.box { margin: 0 24rpx 24rpx; padding: 24rpx; background: #fff; border-radius: 18rpx; }
+.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: 26rpx; }
+.choice-row label { margin-left: 26upx; font-size: 28upx; }
 .label, .form-label { color: #34445c; }
-.required:before { content: '*'; color: #f33; margin-right: 6rpx; }
-.form-row { min-height: 88rpx; display: flex; align-items: center; border-bottom: 1rpx solid #eee; }
-.form-label { width: 270rpx; flex-shrink: 0; }
-.mini-btn { flex: 1; margin: 0; padding: 0 12rpx; background: #fff; color: #288bf5; font-size: 26rpx; line-height: 64rpx; text-align: right; }
-.tip { margin: 20rpx 30rpx; color: #999; line-height: 42rpx; }
-.summary view { display: flex; justify-content: space-between; line-height: 70rpx; border-bottom: 1rpx solid #eee; }
-.contract { margin: 24rpx; display: flex; flex-direction: column; gap: 18rpx; text-align: center; color: #999; }
+.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: #288bf5; 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; gap: 18upx; text-align: center; color: #999; font-size: 35upx; }
 .signed { color: #079b52; }
-.bottom-space { height: 150rpx; }
-.bottom { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom)); background: #fff; }
-.bottom button { flex: 1; border-radius: 50rpx; }
-.next { color: #fff; background: #288bf5; }.back, .outline { color: #288bf5; background: #fff; border: 1rpx solid #288bf5; }
+.bottom-space { height: 150upx; }
+.bottom { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18upx; padding: 20upx 28upx calc(20upx + env(safe-area-inset-bottom)); background: #fff; }
+.bottom button { flex: 1; border-radius: 50upx; font-size: 35upx; }
+.next { color: #fff; background: #288bf5; }.back, .outline { color: #288bf5; background: #fff; border: 1upx solid #288bf5; }
 </style>

+ 1 - 0
ghsApp/src/api/lakala-account/index.js

@@ -7,6 +7,7 @@ export const saveDraft = data => https.post("/lakala-account/save-draft", data,
 export const deleteDraft = data => https.post("/lakala-account/delete-draft", data);
 export const uploadFile = data => https.post("/lakala-account/upload-file", data);
 export const ocrResult = data => https.get("/lakala-account/ocr-result", data);
+export const aliyunOcr = data => https.post("/lakala-account/aliyun-ocr", data);
 export const getOption = data => https.get("/lakala-account/option", data);
 export const applyContract = data => https.post("/lakala-account/apply-contract", data);
 export const contractStatus = data => https.get("/lakala-account/contract-status", data);

+ 0 - 0
package.json


+ 1 - 0
pt/src/mock/userInfo.js

@@ -169,6 +169,7 @@ let menu = {
 		{id: '15',parentId: '1',name: '小程序',type: 1,icon: 'icongongzuotai',orderNum: 1,router: '/saas/shop/upgrade',viewPath: 'saas/shop/upgrade',keepAlive: 1},
 		{id: '16',parentId: '1',name: '续期订单',type: 1,icon: 'icongongzuotai',orderNum: 1,router: '/saas/shop/buy',viewPath: 'saas/shop/buy',keepAlive: 1},
         {id: '17',parentId: '1',name: '提现申请',type: 1,icon: 'icongongzuotai',orderNum: 1,router: '/saas/shop/deposit',viewPath: 'saas/shop/deposit',keepAlive: 1},
+		{id: '18',parentId: '1',name: '进件审核',type: 1,icon: 'icongongzuotai',orderNum: 1,router: '/saas/shop/jinjian',viewPath: 'saas/shop/jinjian',keepAlive: 1},
 		{id: '10',parentId: null,name: '资产',type: 0,icon: 'icongongzuotai',orderNum: 1,router: '',viewPath: '',keepAlive: 1},
 		{id: '101',parentId: '10',name: '余额明细',type: 1,icon: 'icongongzuotai',orderNum: 1,router: '/assets/ptYeChange',viewPath: 'views/assets/ptYeChange',keepAlive: 1},
 		{id: '2',parentId: null,name: '订单',type: 1,icon: 'icongongzuotai',orderNum: 2,router: '/saas/order/list',viewPath: 'saas/order/list',keepAlive: 1},

+ 750 - 0
pt/src/saas/shop/jinjian.vue

@@ -0,0 +1,750 @@
+<template>
+	<!-- 拉卡拉进件审核页:用于平台人员查看商户入网资料并执行驳回或提交审核。 -->
+	<div class="app-list-content lakala-page">
+		<div class="toolbar">
+			<el-select v-model="query.status" size="small" placeholder="状态" @change="load">
+				<el-option label="全部" value="-1"></el-option>
+				<el-option label="后台审核" value="PLATFORM_AUDIT"></el-option>
+				<el-option label="待拉卡拉审核" value="WAIT_AUDIT"></el-option>
+				<el-option label="已驳回" value="REJECT"></el-option>
+				<el-option label="认证通过" value="APPROVED"></el-option>
+			</el-select>
+			<el-input
+				v-model="query.keyWord"
+				size="small"
+				clearable
+				placeholder="申请ID/商户名称"
+				class="keyword"
+				@keyup.enter.native="load"
+			></el-input>
+			<el-button type="primary" size="small" @click="load">查询</el-button>
+		</div>
+
+		<el-table :data="list" border stripe v-loading="loading">
+			<el-table-column prop="id" label="ID" width="90" align="center"></el-table-column>
+			<el-table-column prop="mainId" label="mainId" width="100" align="center"></el-table-column>
+			<el-table-column prop="shopId" label="shopId" width="100" align="center"></el-table-column>
+			<el-table-column prop="merchantName" label="商户经营名称" min-width="180"></el-table-column>
+			<el-table-column prop="legalName" label="法人/姓名" min-width="120"></el-table-column>
+			<el-table-column prop="accountName" label="收款人" min-width="120"></el-table-column>
+			<el-table-column prop="status" label="状态" width="130" align="center">
+				<template slot-scope="scope">
+					<el-tag :type="statusTag(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column prop="updateTime" label="更新时间" width="170" align="center"></el-table-column>
+			<el-table-column label="操作" width="170" fixed="right" align="center">
+				<template slot-scope="scope">
+					<el-button type="text" size="mini" @click="openDetail(scope.row.id)">详情</el-button>
+					<el-button
+						v-if="scope.row.status === 'PLATFORM_AUDIT'"
+						type="text"
+						size="mini"
+						@click="approve(scope.row)"
+					>提交审核</el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+
+		<div class="pager">
+			<el-pagination
+				background
+				layout="prev, pager, next, total"
+				:current-page="query.page"
+				:page-size="query.pageSize"
+				:total="total"
+				@current-change="pageChange"
+			></el-pagination>
+		</div>
+
+		<el-dialog
+			title="商户入网信息"
+			:visible.sync="detailVisible"
+			:close-on-click-modal="false"
+			width="72%"
+			top="30px"
+			class="detail-dialog"
+		>
+			<div class="detail-body">
+				<section-card title="基本信息">
+					<info-row label="营业执照" sub-label="上传营业执照" required class="info-row--full info-row--license">
+						<file-box
+							:file="attachment('license', 'BUSINESS_LICENCE')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.BUSINESS_LICENCE"
+							@upload="uploadAttachment('license', 'BUSINESS_LICENCE', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="营业执照名称" required editable :value="license.licenseName" @input="setField('license.licenseName', $event)"></info-row>
+					<info-row label="执照起始日期" required editable type="date" :value="license.licenseStart" @input="setField('license.licenseStart', $event)"></info-row>
+					<info-row label="证件代码" required editable :value="license.licenseNo" @input="setField('license.licenseNo', $event)"></info-row>
+					<info-row label="执照有效期" required editable :value="license.licenseEnd" @input="setField('license.licenseEnd', $event)"></info-row>
+					<info-row label="注册地址" editable :value="license.registeredAddress[0]" @input="setField('license.registeredAddress[0]', $event)"></info-row>
+				</section-card>
+
+				<section-card title="法人基本信息">
+					<info-row label="证件类型" required value="身份证" suffix-icon="el-icon-arrow-down"></info-row>
+					<info-row label="证件号码" required editable :value="license.legalIdNo" @input="setField('license.legalIdNo', $event)"></info-row>
+					<info-row label="人像面" sub-label="上传身份证人像面" required>
+						<file-box
+							:file="attachment('license', 'ID_CARD_FRONT')"
+							text="点击上传或拖拽文件"
+							variant="id-front"
+							:loading="uploading.ID_CARD_FRONT"
+							@upload="uploadAttachment('license', 'ID_CARD_FRONT', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="国徽面" sub-label="上传身份证国徽面" required>
+						<file-box
+							:file="attachment('license', 'ID_CARD_BEHIND')"
+							text="点击上传或拖拽文件"
+							variant="id-back"
+							:loading="uploading.ID_CARD_BEHIND"
+							@upload="uploadAttachment('license', 'ID_CARD_BEHIND', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="法人姓名" required editable :value="license.legalName" @input="setField('license.legalName', $event)"></info-row>
+					<!-- 身份证日期沿用接口 yyyyMMdd 格式,日期控件按同一格式解析和回传。 -->
+					<info-row label="身份证起始日期" required editable type="date" date-value-format="yyyyMMdd" :value="license.legalIdStart" @input="setField('license.legalIdStart', $event)"></info-row>
+					<info-row label="身份证号" required editable :value="license.legalIdNo" @input="setField('license.legalIdNo', $event)"></info-row>
+					<info-row label="身份证有效期" required editable type="date" date-value-format="yyyyMMdd" :value="license.legalIdEnd" @input="setField('license.legalIdEnd', $event)"></info-row>
+				</section-card>
+
+				<section-card title="商户信息">
+					<info-row label="商户经营名称" required editable :value="merchant.businessName" @input="setField('merchant.businessName', $event)"></info-row>
+					<info-row label="商户所在地区" required :value="regionText(merchant)" @input="setRegionName('merchant', $event)"></info-row>
+					<info-row label="详细地址" required editable :value="merchant.address" @input="setField('merchant.address', $event)"></info-row>
+					<info-row label="联系人姓名" editable :value="merchant.contactName" @input="setField('merchant.contactName', $event)"></info-row>
+					<info-row label="手机号码" editable :value="merchant.contactMobile" @input="setField('merchant.contactMobile', $event)"></info-row>
+					<info-row label="邮箱" editable :value="merchant.email" @input="setField('merchant.email', $event)"></info-row>
+				</section-card>
+
+				<section-card title="结算信息">
+					<info-row label="账户类型" required editable type="select" :value="settlement.accountType" :options="accountTypeOptions" @input="setField('settlement.accountType', $event)"></info-row>
+					<info-row label="开户名" required editable :value="settlement.accountName" @input="setField('settlement.accountName', $event)"></info-row>
+					<info-row label="银行卡号" required editable :value="settlement.accountNo" @input="setField('settlement.accountNo', $event)"></info-row>
+					<info-row label="开户银行" required :value="settlement.bankName" @input="setField('settlement.bankName', $event)"></info-row>
+					<info-row label="开户地区" required :value="regionText(settlement)" @input="setRegionName('settlement', $event)"></info-row>
+					<!-- <info-row label="开户支行" required editable :value="settlement.bankNo" @input="setField('settlement.bankNo', $event)"></info-row> -->
+					<info-row label="银行卡照片" required>
+						<file-box
+							:file="attachment('settlement', 'BANK_CARD')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.BANK_CARD"
+							@upload="uploadAttachment('settlement', 'BANK_CARD', $event)"
+						></file-box>
+					</info-row>
+					<!-- <info-row label="开户许可证">
+						<file-box
+							:file="attachment('settlement', 'OPENING_PERMIT')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.OPENING_PERMIT"
+							@upload="uploadAttachment('settlement', 'OPENING_PERMIT', $event)"
+						></file-box>
+					</info-row> -->
+				</section-card>
+
+				<section-card title="门店信息">
+					<info-row label="门头照" required>
+						<file-box
+							:file="attachment('shop', 'SHOP_OUTSIDE_IMG')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.SHOP_OUTSIDE_IMG"
+							@upload="uploadAttachment('shop', 'SHOP_OUTSIDE_IMG', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="内设照" required>
+						<file-box
+							:file="attachment('shop', 'SHOP_INSIDE_IMG')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.SHOP_INSIDE_IMG"
+							@upload="uploadAttachment('shop', 'SHOP_INSIDE_IMG', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="收银台照" required>
+						<file-box
+							:file="attachment('shop', 'CHECKSTAND_IMG')"
+							text="点击上传或拖拽文件"
+							:loading="uploading.CHECKSTAND_IMG"
+							@upload="uploadAttachment('shop', 'CHECKSTAND_IMG', $event)"
+						></file-box>
+					</info-row>
+					<info-row label="门店名称" required editable :value="shop.name" @input="setField('shop.name', $event)"></info-row>
+					<info-row label="门店地址" required :value="regionText(shop)" @input="setRegionName('shop', $event)"></info-row>
+					<info-row label="详细地址" required editable :value="shop.address" @input="setField('shop.address', $event)"></info-row>
+					<info-row label="门店联系人" required editable :value="shop.contactName" @input="setField('shop.contactName', $event)"></info-row>
+					<info-row label="联系手机号" required editable :value="shop.contactMobile" @input="setField('shop.contactMobile', $event)"></info-row>
+				</section-card>
+
+				<section-card title="业务信息">
+					<info-row label="结算类型" value="自动结算"></info-row>
+					<info-row label="结算周期" value="D1"></info-row>
+					<info-row label="批发费率" :value="business.rate || '0.33'" @input="setField('business.rate', $event)"></info-row>
+					<info-row label="电子合同签约手机号" required editable :value="business.signMobile" @input="setField('business.signMobile', $event)"></info-row>
+					<!-- <info-row label="合同状态" :value="detail.contractStatus"></info-row> -->
+					<info-row label="合同编号" :value="detail.contractNo"></info-row>
+				</section-card>
+			</div>
+			<div slot="footer" class="dialog-footer">
+				<el-button class="footer-btn" icon="el-icon-arrow-left" @click="detailVisible = false">返回</el-button>
+				<el-button
+					v-if="detail.status === 'PLATFORM_AUDIT'"
+					class="footer-btn"
+					icon="el-icon-document-delete"
+					@click="openRejectDialog(detail)"
+				>驳回</el-button>
+				<el-button
+					class="footer-btn"
+					type="primary"
+					plain
+					icon="el-icon-document-checked"
+					:loading="saving"
+					@click="saveDetail"
+				>保存修改</el-button>
+				<el-button
+					class="footer-btn"
+					type="primary"
+					icon="el-icon-s-promotion"
+					:disabled="detail.status !== 'PLATFORM_AUDIT'"
+					@click="approveDetail"
+				>提交审核</el-button>
+			</div>
+		</el-dialog>
+
+		<el-dialog
+			title="驳回原因"
+			:visible.sync="rejectVisible"
+			:close-on-click-modal="false"
+			width="670px"
+			top="10vh"
+			class="reject-dialog"
+		>
+			<el-input
+				v-model="rejectForm.reason"
+				type="textarea"
+				:rows="9"
+				maxlength="500"
+				show-word-limit
+				placeholder="请输入驳回原因"
+				class="reject-textarea"
+			></el-input>
+			<div slot="footer" class="reject-footer">
+				<el-button class="reject-btn" @click="closeRejectDialog">返回</el-button>
+				<el-button class="reject-btn" type="primary" :loading="rejecting" @click="submitReject">驳回</el-button>
+			</div>
+		</el-dialog>
+	</div>
+</template>
+
+<script>
+// 分组卡片:保持弹窗资料区的标题条、分割线和内容容器一致。
+const SectionCard = {
+	props: ['title'],
+	render(h) {
+		return h('div', { class: 'section-card' }, [
+			h('div', { class: 'section-title' }, [
+				h('span'),
+				this.title
+			]),
+			h('div', { class: 'section-content' }, this.$slots.default)
+		]);
+	}
+};
+
+// 信息行:展示及编辑接口字段,日期控件可按业务字段指定解析和回传格式。
+const InfoRow = {
+	props: {
+		label: String,
+		value: [String, Number],
+		required: Boolean,
+		subLabel: String,
+		suffixIcon: String,
+		editable: Boolean,
+		type: String,
+		dateValueFormat: String,
+		options: {
+			type: Array,
+			default() {
+				return [];
+			}
+		}
+	},
+	render(h) {
+		const labelChildren = [];
+		if (this.required) {
+			labelChildren.push(h('i', '*'));
+		}
+		labelChildren.push(this.label);
+		if (this.subLabel) {
+			labelChildren.push(h('small', [
+				h('em', '·'),
+				this.subLabel
+			]));
+		}
+		let content = this.$slots.default || [
+			h('span', { class: 'info-value-text' }, this.value || '-'),
+			this.suffixIcon ? h('i', { class: ['info-suffix', this.suffixIcon] }) : null
+		];
+		if (!this.$slots.default && this.editable) {
+			if (this.type === 'date') {
+				content = [h('el-date-picker', {
+					class: 'info-control',
+					props: {
+						value: this.value || '',
+						type: 'date',
+						valueFormat: this.dateValueFormat || 'yyyy-MM-dd',
+						placeholder: '请选择'
+					},
+					on: { input: value => this.$emit('input', value) }
+				})];
+			} else if (this.type === 'select') {
+				content = [h('el-select', {
+					class: 'info-control',
+					props: { value: this.value || '', placeholder: '请选择' },
+					on: { input: value => this.$emit('input', value) }
+				}, this.options.map(item => h('el-option', {
+					props: { label: item.label, value: item.value }
+				})))];
+			} else {
+				content = [h('el-input', {
+					class: 'info-control',
+					props: { value: this.value || '', placeholder: '请输入' },
+					on: { input: value => this.$emit('input', value) }
+				})];
+			}
+		}
+		return h('div', { class: 'info-row' }, [
+			h('div', { class: 'info-label' }, labelChildren),
+			h('div', { class: [
+				'info-value',
+				this.$slots.default ? 'info-value-file' : 'info-value-input'
+			] }, content)
+		]);
+	}
+};
+
+// 附件展示:有真实附件时可点击预览,没有附件时按入网资料上传框样式展示占位。
+const FileBox = {
+	props: ['file', 'text', 'variant', 'loading'],
+	data() {
+		return {
+			previewVisible: false
+		};
+	},
+	computed: {
+		// 图片地址:附件展示优先使用后端返回的 ossPath,兼容历史字段作为兜底。
+		url() {
+			return this.file && (this.file.ossPath || this.file.ossUrl || this.file.url);
+		},
+		// 图片判断:只对常见图片类型启用弹窗预览,非图片仍按文件链接打开。
+		isImage() {
+			return /\.(png|jpe?g|webp|gif)(\?.*)?$/i.test(this.url || '');
+		}
+	},
+	methods: {
+		// 选择文件:触发隐藏 input,保持上传入口和原有业务流程一致。
+		chooseFile() {
+			const input = this.$refs.fileInput;
+			if (input) input.click();
+		},
+		// 上传变更:把浏览器选择的文件交给外层业务上传方法处理。
+		changeFile(event) {
+			const file = event.target.files && event.target.files[0];
+			event.target.value = '';
+			if (file) this.$emit('upload', file);
+		},
+		// 图片预览:已上传图片点击后打开大图弹窗,便于审核人员核对原图细节。
+		openPreview() {
+			if (this.isImage) this.previewVisible = true;
+		}
+	},
+	render(h) {
+		const input = h('input', {
+			ref: 'fileInput',
+			class: 'file-input',
+			attrs: { type: 'file', accept: 'image/jpeg,image/png,image/gif,image/webp' },
+			on: { change: this.changeFile }
+		});
+		if (this.url) {
+			return h('div', { class: 'file-box has-file' }, [
+				this.isImage ? h('img', {
+					class: 'file-preview-thumb',
+					attrs: { src: this.url },
+					on: { click: this.openPreview }
+				}) : h('a', {
+					attrs: { href: this.url, target: '_blank' }
+				}, '查看文件'),
+				h('el-button', {
+					class: 'file-change',
+					props: { type: 'text', loading: this.loading },
+					on: { click: this.chooseFile }
+				}, '更换文件'),
+				this.isImage ? h('el-dialog', {
+					class: 'file-preview-dialog',
+					props: {
+						visible: this.previewVisible,
+						appendToBody: true,
+						width: '70%',
+						top: '30px'
+					},
+					on: {
+						'update:visible': value => {
+							this.previewVisible = value;
+						}
+					}
+				}, [
+					h('img', {
+						class: 'file-preview-full',
+						attrs: { src: this.url }
+					})
+				]) : null,
+				input
+			]);
+		}
+		const mockCard = this.variant ? h('div', { class: ['mock-card', `mock-card--${this.variant}`] }, [
+			this.variant === 'id-back' ? h('span', { class: 'mock-emblem' }) : null,
+			h('span', { class: 'mock-lines' }),
+			h('span', { class: 'mock-avatar' })
+		]) : null;
+		return h('div', {
+			class: ['file-box', 'empty-file', this.loading ? 'is-uploading' : ''],
+			on: { click: this.chooseFile }
+		}, [
+			mockCard,
+			h('i', { class: 'el-icon-camera-solid' }),
+			h('div', this.loading ? '上传中...' : (this.text || '点击上传或拖拽文件')),
+			h('small', '支持jpg、png格式,大小不超过10MB'),
+			input
+		]);
+	}
+};
+
+function clone(data) {
+	return JSON.parse(JSON.stringify(data || {}));
+}
+
+function defaultForm() {
+	return {
+		license: { attachments: [] },
+		merchant: {},
+		settlement: { attachments: [] },
+		shop: { attachments: [] },
+		business: {}
+	};
+}
+
+function readFileAsDataUrl(file) {
+	return new Promise((resolve, reject) => {
+		const reader = new FileReader();
+		reader.onload = event => resolve(event.target.result);
+		reader.onerror = reject;
+		reader.readAsDataURL(file);
+	});
+}
+
+function mergeForm(form) {
+	const base = defaultForm();
+	Object.keys(base).forEach(key => {
+		base[key] = Object.assign(base[key], form[key] || {});
+		if (base[key].attachments && !Array.isArray(base[key].attachments)) {
+			base[key].attachments = [];
+		}
+	});
+	return Object.assign({}, form, base);
+}
+
+export default {
+	components: { SectionCard, InfoRow, FileBox },
+	data() {
+		return {
+			loading: false,
+			list: [],
+			total: 0,
+			query: { page: 1, pageSize: 20, status: 'PLATFORM_AUDIT', keyWord: '' },
+			detailVisible: false,
+			detail: {},
+			editableForm: defaultForm(),
+			saving: false,
+			rejectVisible: false,
+			rejecting: false,
+			rejectForm: { id: '', reason: '' },
+			uploading: {},
+			idTypeOptions: [{ label: '身份证', value: '身份证' }],
+			accountTypeOptions: [
+				{ label: '对公', value: '57' },
+				{ label: '对私', value: '58' }
+			]
+		};
+	},
+	computed: {
+		form() {
+			return this.editableForm || {};
+		},
+		license() {
+			return this.form.license || {};
+		},
+		merchant() {
+			return this.form.merchant || {};
+		},
+		settlement() {
+			return this.form.settlement || {};
+		},
+		shop() {
+			return this.form.shop || {};
+		},
+		business() {
+			return this.form.business || {};
+		},
+		identityValidText() {
+			if (!this.license.legalIdEnd) return '-';
+			return this.license.legalIdEnd === '9999-12-31' ? '长期有效' : this.license.legalIdEnd;
+		}
+	},
+	mounted() {
+		this.load();
+	},
+	methods: {
+		load() {
+			this.loading = true;
+			this.$service.saasShop.lakalaApplicationList(this.query).then(res => {
+				this.list = res.list || [];
+				this.total = Number(res.totalNum || 0);
+			}).finally(() => {
+				this.loading = false;
+			});
+		},
+		pageChange(page) {
+			this.query.page = page;
+			this.load();
+		},
+		openDetail(id) {
+			this.$service.saasShop.lakalaApplicationDetail({ id }).then(res => {
+				this.detail = res;
+				this.editableForm = mergeForm(clone(res.formData));
+				this.detailVisible = true;
+			});
+		},
+		setField(path, value) {
+			const keys = path.split('.');
+			let target = this.editableForm;
+			keys.slice(0, -1).forEach(key => {
+				if (!target[key]) this.$set(target, key, {});
+				target = target[key];
+			});
+			this.$set(target, keys[keys.length - 1], value);
+		},
+		setIdentityValid(value) {
+			this.setField('license.legalIdEnd', value === '长期有效' ? '9999-12-31' : value);
+		},
+		setRegionName(section, value) {
+			if (!this.editableForm[section]) this.$set(this.editableForm, section, {});
+			this.$set(this.editableForm[section], 'provinceName', value);
+			this.$set(this.editableForm[section], 'cityName', '');
+			this.$set(this.editableForm[section], 'countyName', '');
+		},
+		saveDetail(showMessage = true) {
+			if (!this.detail.id || this.saving) return Promise.resolve(this.detail);
+			this.saving = true;
+			return this.$service.saasShop.lakalaApplicationSave({
+				id: this.detail.id,
+				currentStep: this.detail.currentStep || 5,
+				formData: this.editableForm
+			}).then(res => {
+				this.detail = res;
+				this.editableForm = mergeForm(clone(res.formData));
+				if (showMessage) this.$message.success('保存成功');
+				this.load();
+				return res;
+			}).finally(() => {
+				this.saving = false;
+			});
+		},
+		approveDetail() {
+			if (!this.detail || this.detail.status !== 'PLATFORM_AUDIT') return;
+			this.saveDetail(false).then(() => {
+				this.approve(this.detail);
+			});
+		},
+		uploadAttachment(section, imgType, file) {
+			if (!this.detail.id || !file) return;
+			if (!/^image\/(jpeg|png|gif|webp)$/.test(file.type)) {
+				this.$message.error('请上传jpg、png、gif或webp图片');
+				return;
+			}
+			if (file.size > 10 * 1024 * 1024) {
+				this.$message.error('图片大小不能超过10MB');
+				return;
+			}
+			this.$set(this.uploading, imgType, true);
+			readFileAsDataUrl(file).then(content => {
+				return this.$service.saasShop.lakalaApplicationUploadFile({
+					id: this.detail.id,
+					content,
+					imgType,
+					isOcr: 0
+				});
+			}).then(fileInfo => {
+				if (!this.editableForm[section]) this.$set(this.editableForm, section, { attachments: [] });
+				if (!Array.isArray(this.editableForm[section].attachments)) {
+					this.$set(this.editableForm[section], 'attachments', []);
+				}
+				const list = this.editableForm[section].attachments;
+				const index = list.findIndex(item => item.imgType === imgType);
+				if (index >= 0) {
+					this.$set(list, index, fileInfo);
+				} else {
+					list.push(fileInfo);
+				}
+				this.$message.success('上传成功,请点击保存修改');
+			}).finally(() => {
+				this.$set(this.uploading, imgType, false);
+			});
+		},
+		approve(row) {
+			if (!row || row.status !== 'PLATFORM_AUDIT') return;
+			this.$confirm('确认审核通过并提交到拉卡拉?', '提交审核', { type: 'warning' }).then(() => {
+				this.$service.saasShop.lakalaApplicationApprove({ id: row.id }).then(() => {
+					this.$message.success('已提交拉卡拉审核');
+					this.detailVisible = false;
+					this.load();
+				});
+			}).catch(() => {});
+		},
+		// 打开驳回表单:仅后台审核状态允许驳回,驳回原因会同步给客户侧列表展示。
+		openRejectDialog(row) {
+			if (!row || row.status !== 'PLATFORM_AUDIT') return;
+			this.rejectForm = { id: row.id, reason: '' };
+			this.rejectVisible = true;
+		},
+		// 关闭驳回表单:清理本次填写内容,避免切换申请时误提交上一条原因。
+		closeRejectDialog() {
+			if (this.rejecting) return;
+			this.rejectVisible = false;
+			this.rejectForm = { id: '', reason: '' };
+		},
+		// 提交驳回:校验原因后调用后台驳回接口,客户侧通过 rejectReason 字段展示该内容。
+		submitReject() {
+			const reason = (this.rejectForm.reason || '').trim();
+			if (!this.rejectForm.id) return;
+			if (!reason) {
+				this.$message.warning('请填写驳回原因');
+				return;
+			}
+			this.rejecting = true;
+			this.$service.saasShop.lakalaApplicationReject({
+				id: this.rejectForm.id,
+				reason
+			}).then(() => {
+				this.$message.success('已驳回');
+				this.rejectVisible = false;
+				this.detailVisible = false;
+				this.rejectForm = { id: '', reason: '' };
+				this.load();
+			}).finally(() => {
+				this.rejecting = false;
+			});
+		},
+		statusText(status) {
+			return {
+				DRAFT: '草稿',
+				CONTRACT_PENDING: '待签约',
+				PLATFORM_AUDIT: '后台审核',
+				WAIT_AUDIT: '拉卡拉审核',
+				REJECT: '已驳回',
+				APPROVED: '认证通过'
+			}[status] || status || '-';
+		},
+		statusTag(status) {
+			return {
+				PLATFORM_AUDIT: 'warning',
+				WAIT_AUDIT: 'primary',
+				REJECT: 'danger',
+				APPROVED: 'success'
+			}[status] || 'info';
+		},
+		regionText(row) {
+			if (!row) return '-';
+			return `${row.provinceName || ''}${row.cityName || ''}${row.countyName || ''}` || '-';
+		},
+		attachment(section, imgType) {
+			const list = (this.form[section] && this.form[section].attachments) || [];
+			return list.find(item => item.imgType === imgType) || null;
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.lakala-page { padding: 16px; }
+.lakala-page .toolbar { display: flex; align-items: center; margin-bottom: 14px; }
+.lakala-page .keyword { width: 260px; margin: 0 10px; }
+.lakala-page .pager { margin-top: 16px; text-align: right; }
+.detail-dialog .el-dialog { display: flex; flex-direction: column; height: calc(100vh - 4px); max-height: 1086px; margin-top: 2px !important; margin-bottom: 0; border-radius: 8px; overflow: hidden; }
+.detail-dialog .el-dialog__header { flex-shrink: 0; padding: 24px 36px 18px; border-bottom: 0; }
+.detail-dialog .el-dialog__title { font-size: 28px; line-height: 36px; font-weight: 700; color: #111827; }
+.detail-dialog .el-dialog__headerbtn { top: 28px; right: 30px; font-size: 28px; }
+.detail-dialog .el-dialog__body { flex: 1; min-height: 0; padding: 0 30px 10px; overflow-y: auto; background: #fff; }
+.detail-dialog .el-dialog__body::-webkit-scrollbar { width: 10px; }
+.detail-dialog .el-dialog__body::-webkit-scrollbar-track { background: #f6f8fb; border-radius: 8px; }
+.detail-dialog .el-dialog__body::-webkit-scrollbar-thumb { background: #9aa4b5; border-radius: 8px; }
+.detail-dialog .el-dialog__footer { flex-shrink: 0; padding: 24px 36px 28px; border-top: 1px solid #e5e7eb; background: #fff; box-shadow: 0 -4px 14px rgba(15, 23, 42, .03); }
+.detail-dialog .detail-body { padding-bottom: 2px; }
+.detail-dialog .section-card { border: 1px solid #e3e8f0; border-radius: 8px; margin-bottom: 8px; padding: 18px 20px 16px; background: #fff; box-shadow: 0 4px 18px rgba(15, 23, 42, .08); }
+.detail-dialog .section-title { display: flex; align-items: center; font-size: 18px; font-weight: 700; color: #111827; padding: 0 0 15px; border-bottom: 1px solid #e5e7eb; }
+.detail-dialog .section-title span { width: 8px; height: 22px; background: #237bff; border-radius: 3px; margin-right: 12px; }
+.detail-dialog .section-content { display: flex; flex-wrap: wrap; padding-top: 12px; }
+.detail-dialog .info-row { width: 50%; display: flex; align-items: flex-start; min-height: 58px; padding: 8px 12px; box-sizing: border-box; }
+.detail-dialog .info-row--full { width: 100%; }
+.detail-dialog .info-row--license { padding-bottom: 12px; margin-bottom: 8px; border-bottom: 1px solid #edf0f5; }
+.detail-dialog .info-label { width: 220px; flex-shrink: 0; min-height: 40px; line-height: 40px; font-size: 16px; font-weight: 700; color: #111827; }
+.detail-dialog .info-label i { color: #ff1616; font-style: normal; margin-right: 8px; }
+.detail-dialog .info-label small { display: block; padding-left: 15px; margin-top: -7px; line-height: 20px; font-size: 13px; font-weight: 400; color: #6b7280; }
+.detail-dialog .info-label small em { color: #ff1616; font-style: normal; margin-right: 6px; }
+.detail-dialog .info-value { flex: 1; min-width: 0; color: #374151; font-size: 15px; }
+.detail-dialog .info-value-input { display: flex; align-items: center; justify-content: space-between; min-height: 42px; line-height: 40px; padding: 0 14px; border: 1px solid #d8dee9; border-radius: 5px; background: #fff; box-sizing: border-box; color: #374151; overflow: hidden; }
+.detail-dialog .info-control { width: 100%; }
+.detail-dialog .info-control .el-input__inner { height: 40px; line-height: 40px; padding: 0; border: 0; color: #374151; background: transparent; }
+.detail-dialog .info-control .el-input__prefix { display: none; }
+.detail-dialog .info-control .el-input__suffix { right: 0; }
+.detail-dialog .info-control.el-date-editor.el-input { width: 100%; }
+.detail-dialog .info-value-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.detail-dialog .info-suffix { flex-shrink: 0; margin-left: 10px; color: #6b7280; font-size: 16px; }
+.detail-dialog .info-value-file { min-height: 120px; line-height: normal; }
+.detail-dialog .file-box { position: relative; width: 100%; max-width: 480px; min-height: 126px; border: 1px dashed #2f7df6; border-radius: 5px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #111827; text-decoration: none; box-sizing: border-box; background: #fff; }
+.detail-dialog .file-box.is-uploading { cursor: wait; opacity: .72; }
+.detail-dialog .file-input { display: none; }
+.detail-dialog .file-preview-thumb { width: auto; height: auto; max-width: 100%; max-height: 220px; display: block; border-radius: 4px; object-fit: contain; cursor: zoom-in; }
+.file-preview-dialog .el-dialog__body { padding: 0 24px 24px; text-align: center; }
+.file-preview-dialog .file-preview-full { width: auto; height: auto; max-width: 100%; max-height: calc(100vh - 150px); display: inline-block; object-fit: contain; }
+.detail-dialog .has-file .file-change { margin-top: 8px; }
+.detail-dialog .empty-file i { width: 64px; height: 64px; line-height: 64px; text-align: center; border-radius: 50%; color: #fff; background: #237bff; font-size: 27px; margin-bottom: 12px; box-shadow: 0 8px 18px rgba(35, 123, 255, .24); z-index: 1; }
+.detail-dialog .empty-file div { font-size: 16px; font-weight: 600; color: #111827; z-index: 1; }
+.detail-dialog .empty-file small { color: #6b7280; margin-top: 10px; font-size: 14px; z-index: 1; }
+.detail-dialog .mock-card { width: 170px; height: 82px; margin-bottom: 10px; border: 1px solid #b7d2ff; border-radius: 4px; background: linear-gradient(135deg, #eef6ff 0%, #dcecff 100%); position: relative; overflow: hidden; }
+.detail-dialog .mock-card--id-front .mock-lines { position: absolute; left: 16px; top: 20px; width: 70px; height: 9px; border-radius: 10px; background: #8bb7fb; box-shadow: 0 22px 0 #a7c7fb; }
+.detail-dialog .mock-card--id-front .mock-avatar { position: absolute; right: 22px; bottom: 13px; width: 48px; height: 50px; border-radius: 22px 22px 6px 6px; background: #8db8fb; }
+.detail-dialog .mock-card--id-back .mock-emblem { position: absolute; left: 18px; top: 12px; width: 42px; height: 42px; border-radius: 50%; background: #ee1d23; border: 4px solid #ffd34d; box-sizing: border-box; }
+.detail-dialog .mock-card--id-back .mock-lines { position: absolute; right: 20px; top: 20px; width: 58px; height: 8px; border-radius: 10px; background: #8bb7fb; box-shadow: 0 20px 0 #8bb7fb, -12px 40px 0 #a7c7fb; }
+.detail-dialog .mock-card--id-back .mock-avatar { display: none; }
+.detail-dialog .mock-card + i { position: absolute; top: 42px; width: 54px; height: 54px; line-height: 54px; font-size: 23px; }
+.detail-dialog .dialog-footer { display: flex; justify-content: center; }
+.detail-dialog .footer-btn { width: 280px; height: 60px; margin: 0 14px; font-size: 22px; border-radius: 5px; }
+.reject-dialog .el-dialog { border: 1px solid #8b8b8b; border-radius: 0; box-shadow: none; }
+.reject-dialog .el-dialog__header { padding: 36px 34px 8px; }
+.reject-dialog .el-dialog__title { font-size: 28px; line-height: 36px; font-weight: 700; color: #333; }
+.reject-dialog .el-dialog__headerbtn { top: 25px; right: 34px; font-size: 28px; }
+.reject-dialog .el-dialog__headerbtn .el-dialog__close { font-weight: 700; color: #333; }
+.reject-dialog .el-dialog__body { padding: 0 34px 20px; }
+.reject-dialog .reject-textarea .el-textarea__inner { height: 272px; min-height: 272px !important; padding: 12px 14px; border: 1px solid #8f8f8f; border-radius: 0; resize: none; font-size: 18px; line-height: 26px; color: #333; }
+.reject-dialog .el-dialog__footer { padding: 0 39px 49px; }
+.reject-dialog .reject-footer { display: flex; justify-content: space-between; }
+.reject-dialog .reject-btn { width: 264px; height: 64px; margin: 0; border: 0; border-radius: 6px; background: #18a8df; color: #fff; font-size: 22px; font-weight: 700; }
+.reject-dialog .reject-btn:hover,
+.reject-dialog .reject-btn:focus { background: #129ed4; color: #fff; }
+.reject-dialog .reject-btn.is-loading { background: #18a8df; }
+@media (max-width: 1200px) {
+	.detail-dialog .info-row { width: 100%; }
+	.detail-dialog .info-label { width: 190px; }
+	.detail-dialog .footer-btn { width: 23%; min-width: 150px; margin: 0 6px; }
+}
+</style>

+ 53 - 1
pt/src/service/saas/shop/index.js

@@ -108,6 +108,58 @@ export class ShopService extends BaseService {
         });
     }
 
+    // 拉卡拉进件审核列表
+    lakalaApplicationList(data) {
+        return this.request({
+            url: '/lakala-application/list',
+            params: data
+        });
+    }
+
+    // 拉卡拉进件详情
+    lakalaApplicationDetail(data) {
+        return this.request({
+            url: '/lakala-application/detail',
+            params: data
+        });
+    }
+
+    // 拉卡拉进件后台保存表单
+    lakalaApplicationSave(data) {
+        return this.request({
+            url: '/lakala-application/save',
+            method: 'POST',
+            data
+        });
+    }
+
+    // 拉卡拉进件后台上传附件
+    lakalaApplicationUploadFile(data) {
+        return this.request({
+            url: '/lakala-application/upload-file',
+            method: 'POST',
+            data
+        });
+    }
+
+    // 拉卡拉进件后台驳回
+    lakalaApplicationReject(data) {
+        return this.request({
+            url: '/lakala-application/reject',
+            method: 'POST',
+            data
+        });
+    }
+
+    // 拉卡拉进件后台审核通过并提交拉卡拉
+    lakalaApplicationApprove(data) {
+        return this.request({
+            url: '/lakala-application/approve',
+            method: 'POST',
+            data
+        });
+    }
+
 }
 
-export default new ShopService();
+export default new ShopService();

+ 1 - 1
pt/vue.config.js

@@ -3,7 +3,7 @@ const path = require('path');
 const isProduction = process.env.NODE_ENV === 'production';
 
 //http和https切换请修改这里,修改后需要重新运行 npm run serve 即可 shish 2021.4.12
-const proxyTarget = 'https://api.pt.huaml.com';
+const proxyTarget = 'http://api.pt.huaml.com';
 
 function resolve(dir) {
 	return path.join(__dirname, dir);

Файловите разлики са ограничени, защото са твърде много
+ 286 - 243
pt/yarn.lock


Някои файлове не бяха показани, защото твърде много файлове са промени