From 5fc340a47a82419b8f1f57323c5de94bb84413d2 Mon Sep 17 00:00:00 2001 From: Daniel Kou Date: Fri, 22 May 2026 16:47:41 +0800 Subject: [PATCH] fix oss public/private logic, fixed merchant-info submit logic --- components/PrivateImage.vue | 107 +++ components/nav/nav.vue | 27 +- .../customerService/chattingRecords/index.vue | 4 +- packages/customerService/index/index.vue | 1 + packages/myOrders/submitOrder/index.vue | 16 + packages/shopEnter/auditStatus/index.vue | 13 +- packages/shopEnter/choose/index.css | 95 ++ packages/shopEnter/choose/index.vue | 63 ++ packages/shopEnter/example/index.css | 66 ++ packages/shopEnter/example/index.vue | 69 ++ packages/shopEnter/index/index.css | 468 ++++++++-- packages/shopEnter/index/index.vue | 822 ++++++++++++++---- pages.json | 14 + pages/index/index.vue | 6 +- utils/index.js | 90 +- utils/uploadOSS.js | 150 ++++ 16 files changed, 1665 insertions(+), 346 deletions(-) create mode 100644 components/PrivateImage.vue create mode 100644 packages/shopEnter/choose/index.css create mode 100644 packages/shopEnter/choose/index.vue create mode 100644 packages/shopEnter/example/index.css create mode 100644 packages/shopEnter/example/index.vue create mode 100644 utils/uploadOSS.js diff --git a/components/PrivateImage.vue b/components/PrivateImage.vue new file mode 100644 index 00000000..74ddf4c5 --- /dev/null +++ b/components/PrivateImage.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/components/nav/nav.vue b/components/nav/nav.vue index 3cdc2845..1648dbc1 100644 --- a/components/nav/nav.vue +++ b/components/nav/nav.vue @@ -167,18 +167,21 @@ export default { // 解析path字段,提取scene参数的值 const pathParts = res.path.split('?'); if (pathParts.length > 1) { - const queryParams = pathParts[1].split('&'); - for (const param of queryParams) { - const [key, value] = param.split('='); - if (key === 'scene') { - const params = { - id: value - } - uni.setStorageSync('merchantInfo', params); - NavgateTo('/packages/localLife/detail/index') - break; - } - } + if (pathParts[1].startsWith('scene=')){ + const pathPart = decodeURIComponent(pathParts[1].substring(6)) + const queryParams = pathPart.split('&'); + for (const param of queryParams) { + const [key, value] = param.split('='); + if (key === 'id') { + const params = { + id: value + } + uni.setStorageSync('merchantInfo', params); + NavgateTo('/packages/localLife/detail/index') + break; + } + } + } } } }, diff --git a/packages/customerService/chattingRecords/index.vue b/packages/customerService/chattingRecords/index.vue index 55fe074c..27485d7a 100644 --- a/packages/customerService/chattingRecords/index.vue +++ b/packages/customerService/chattingRecords/index.vue @@ -68,9 +68,9 @@ export default { }, getAvatarUrl(record){ if(record.client_id_one == uni.getStorageSync('openId')){ - return record.two.avatar ? picUrl + record.two.avatar : 'https://static.hshuishang.com/defaultTx.png' + return record.two && record.two.avatar ? picUrl + record.two.avatar : 'https://static.hshuishang.com/defaultTx.png' }else{ - return record.one.avatar ? picUrl + record.one.avatar : 'https://static.hshuishang.com/defaultTx.png' + return record.one && record.one.avatar ? picUrl + record.one.avatar : 'https://static.hshuishang.com/defaultTx.png' } }, // 格式化时间 diff --git a/packages/customerService/index/index.vue b/packages/customerService/index/index.vue index 08088ae5..ff67e77b 100644 --- a/packages/customerService/index/index.vue +++ b/packages/customerService/index/index.vue @@ -160,6 +160,7 @@ export default { const options = { clientId: this.selfClientId } + console.log('clientId:', options.clientId) // 添加连接状态回调 const callbacks = { diff --git a/packages/myOrders/submitOrder/index.vue b/packages/myOrders/submitOrder/index.vue index e69de29b..487e47d0 100644 --- a/packages/myOrders/submitOrder/index.vue +++ b/packages/myOrders/submitOrder/index.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/packages/shopEnter/auditStatus/index.vue b/packages/shopEnter/auditStatus/index.vue index f720eb4d..bab1eeaa 100644 --- a/packages/shopEnter/auditStatus/index.vue +++ b/packages/shopEnter/auditStatus/index.vue @@ -100,7 +100,18 @@ export default { } }, onLoad(options) { - this.itemObj = JSON.parse(options.itemObj); + if (options && options.itemObj) { + try { + const raw = decodeURIComponent(options.itemObj) + this.itemObj = JSON.parse(raw) + } catch (e) { + console.error('解析审核状态参数失败:', e) + this.itemObj = {} + } + } else if (options && options.status) { + // 兼容老版本:仅带 status,没有完整对象 + this.itemObj = { status: Number(options.status) } + } }, methods: { goShopManage() { diff --git a/packages/shopEnter/choose/index.css b/packages/shopEnter/choose/index.css new file mode 100644 index 00000000..8feba11e --- /dev/null +++ b/packages/shopEnter/choose/index.css @@ -0,0 +1,95 @@ +page { + background-color: #f6f7fb; + min-height: 100vh; +} + +.container { + padding: 30rpx; +} + +.header { + padding: 40rpx 0 60rpx; + text-align: center; +} + +.header-title { + font-size: 40rpx; + font-weight: bold; + color: #222222; + margin-bottom: 16rpx; +} + +.header-desc { + font-size: 28rpx; + color: #999999; +} + +.type-list { + margin-bottom: 40rpx; +} + +.type-card { + display: flex; + align-items: center; + background: #FFFFFF; + border-radius: 20rpx; + padding: 40rpx 30rpx; + margin-bottom: 24rpx; + box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); +} + +.type-card:active { + opacity: 0.8; +} + +.type-icon { + width: 100rpx; + height: 100rpx; + margin-right: 24rpx; + flex-shrink: 0; +} + +.type-icon image { + width: 100%; + height: 100%; +} + +.type-info { + flex: 1; +} + +.type-name { + font-size: 32rpx; + font-weight: bold; + color: #222222; + margin-bottom: 10rpx; +} + +.type-desc { + font-size: 24rpx; + color: #999999; +} + +.type-arrow { + flex-shrink: 0; +} + +.tips { + background: #FFFFFF; + border-radius: 20rpx; + padding: 30rpx; +} + +.tips-title { + font-size: 28rpx; + font-weight: bold; + color: #222222; + margin-bottom: 20rpx; +} + +.tips-item { + font-size: 24rpx; + color: #666666; + line-height: 1.8; + margin-bottom: 10rpx; +} diff --git a/packages/shopEnter/choose/index.vue b/packages/shopEnter/choose/index.vue new file mode 100644 index 00000000..972a08e9 --- /dev/null +++ b/packages/shopEnter/choose/index.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/shopEnter/example/index.css b/packages/shopEnter/example/index.css new file mode 100644 index 00000000..a1a3d125 --- /dev/null +++ b/packages/shopEnter/example/index.css @@ -0,0 +1,66 @@ +page { + background-color: #f6f7fb; + min-height: 100vh; +} + +.container { + padding: 30rpx; +} + +.example-section { + background: #FFFFFF; + border-radius: 20rpx; + padding: 30rpx; +} + +.title { + font-size: 34rpx; + font-weight: bold; + color: #222222; + margin-bottom: 30rpx; + text-align: center; +} + +.sub-section { + margin-bottom: 40rpx; +} + +.sub-title { + font-size: 28rpx; + color: #333333; + margin-bottom: 20rpx; + font-weight: bold; +} + +.example-img { + width: 100%; + margin-bottom: 20rpx; + display: flex; + justify-content: center; +} + +.example-img image { + width: 500rpx; + border-radius: 10rpx; + border: 2rpx dashed #ddd; +} + +.tips { + background: #FFF8F6; + border-radius: 10rpx; + padding: 24rpx; + margin-top: 20rpx; +} + +.tips-title { + font-size: 26rpx; + font-weight: bold; + color: #FF370B; + margin-bottom: 16rpx; +} + +.tips-item { + font-size: 24rpx; + color: #666666; + line-height: 1.8; +} diff --git a/packages/shopEnter/example/index.vue b/packages/shopEnter/example/index.vue new file mode 100644 index 00000000..228ff667 --- /dev/null +++ b/packages/shopEnter/example/index.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/packages/shopEnter/index/index.css b/packages/shopEnter/index/index.css index 812c372c..b62a3f5f 100644 --- a/packages/shopEnter/index/index.css +++ b/packages/shopEnter/index/index.css @@ -1,116 +1,420 @@ +/* ============================================================ + * 商家入驻 - 基本信息 tab 全新样式(按设计稿 PDF 1) + * 主色 #FF370B 辅色 #FFF1ED 灰边 #EEE 灰字 #999 深字 #222 + * ============================================================ */ + page { - background-color: #f6f7fb; - min-height: 100vh; - padding-bottom: 60rpx; + background-color: #F6F7F9; + min-height: 100vh; + padding-bottom: 200rpx; } .container { - background: url(https://static.hshuishang.com/property-img-file/shopEn_apply.png) no-repeat; - background-size: 750rpx 497rpx; - box-sizing: border-box; - padding-top: 290rpx; + padding-bottom: 40rpx; + box-sizing: border-box; } -.Msg { - width: 710rpx; - background: #FFFFFF; - border-radius: 20rpx 20rpx 20rpx 20rpx; - margin: 0 auto; - padding: 0 20rpx; - box-sizing: border-box; +/* ================= 顶部进度条(红底胶囊) ================= */ +.step-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx 30rpx 28rpx; + background: linear-gradient(91deg, #FF7658 0%, #FF370B 100%); } -.row { - padding-top: 30rpx; - display: flex; - justify-content: space-between; +.step-pill { + flex: 1; + height: 60rpx; + margin: 0 8rpx; + border-radius: 30rpx; + background: rgba(255, 255, 255, 0.18); + display: flex; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.65); + font-size: 26rpx; } -.row_label { - font-size: 28rpx; - color: #999999; - width: 180rpx; +.step-pill.active { + background: #FFFFFF; + color: #FF370B; + font-weight: 600; } -.red { - color: #FF370B; - margin-left: 5rpx; +/* ================= Tab 切换 ================= */ +.tab-bar { + display: flex; + background: #FFFFFF; + padding: 0 60rpx; } -.row_con { - flex: 1; - padding-bottom: 30rpx; - border-bottom: 1rpx solid #EBEBEB; - display: flex; +.tab-item { + flex: 1; + text-align: center; + padding: 28rpx 0 22rpx; + font-size: 30rpx; + color: #222; + position: relative; } -.row_con input { - flex: 1; +.tab-item.active { + color: #FF370B; + font-weight: 600; } -.nonebor { - border-bottom: none; +.tab-item.active::after { + content: ''; + position: absolute; + bottom: 8rpx; + left: 50%; + transform: translateX(-50%); + width: 56rpx; + height: 6rpx; + background: #FF370B; + border-radius: 3rpx; } -.row2 { - display: flex; - align-items: center; +/* ================= 表单卡片 ================= */ +.form-card { + background: #FFFFFF; + margin: 20rpx 24rpx 0; + padding: 24rpx; + border-radius: 16rpx; } -.mt { - margin-top: 26rpx; - padding-top: 36rpx; - padding-bottom: 36rpx; +.field { + margin-top: 20rpx; } - - -.imgCon { - font-size: 18rpx; - color: #222222; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 120rpx; - height: 120rpx; - background: #F6F7FB; - border: 1rpx solid #D1D1D1; - border-radius: 10rpx 10rpx 10rpx 10rpx; +.field:first-child { + margin-top: 0; } -.imgCon image { - width: 34rpx; - height: 34rpx; - margin-bottom: 8rpx; +.field-label { + font-size: 26rpx; + color: #222; + margin-bottom: 14rpx; + display: flex; + align-items: center; } -.mt2 { - margin-top: 30rpx; +.field-label .star { + color: #FF370B; + margin-right: 4rpx; + font-size: 26rpx; } -.u-upload__wrap__preview { - width: 120rpx; - height: 120rpx; - border-radius: 10rpx 0rpx 10rpx 10rpx !important; +.field-input { + height: 80rpx; + border: 1rpx solid #EBEBEB; + border-radius: 8rpx; + padding: 0 20rpx; + display: flex; + align-items: center; + background: #FFFFFF; } -.u-upload__wrap__preview__image { - width: 100% !important; - height: 100% !important; - object-fit: cover; +.field-input input { + flex: 1; + font-size: 28rpx; + color: #222; + height: 80rpx; + line-height: 80rpx; } -.addBtn { - font-size: 36rpx; - color: #FFFFFF; - width: 600rpx; - height: 90rpx; - background: linear-gradient(91deg, #FF7658 0%, #FF370B 100%); - border-radius: 100rpx 100rpx 100rpx 100rpx; - margin: 0 auto; - margin-top: 58rpx; - display: flex; - align-items: center; - justify-content: center; -} \ No newline at end of file +.field-input input::placeholder, +.field-input .placeholder { + color: #B7B7B7; +} + +.field-input.disabled { + background: #FAFAFA; +} + +.field-value { + flex: 1; + font-size: 28rpx; + color: #222; +} + +.field-value.placeholder { + color: #B7B7B7; +} + +.field-arrow { + color: #C8C8C8; + font-size: 24rpx; + margin-left: 12rpx; +} + +/* ================= 上传图片块 ================= */ +.upload-section { + margin-top: 20rpx; +} + +.upload-title { + font-size: 26rpx; + color: #222; + margin-bottom: 16rpx; + display: flex; + align-items: center; +} + +.upload-title .star { + color: #FF370B; + margin-right: 4rpx; +} + +.upload-title .count { + color: #999; + font-size: 24rpx; + margin-left: 8rpx; + font-weight: normal; +} + +.upload-grid { + display: flex; + flex-wrap: wrap; + gap: 16rpx; +} + +/* uview u-upload 内部样式微调(让占位框跟设计稿一致) */ +.upload-section /deep/ .u-upload__wrap__preview { + width: 168rpx !important; + height: 168rpx !important; + border-radius: 12rpx !important; + overflow: hidden; +} + +.upload-section /deep/ .u-upload__button { + width: 168rpx !important; + height: 168rpx !important; + border-radius: 12rpx !important; + background: #FAFAFA !important; + border: 1rpx dashed #D1D1D1 !important; +} + +.upload-add { + width: 168rpx; + height: 168rpx; + background: #FAFAFA; + border: 1rpx dashed #D1D1D1; + border-radius: 12rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #999; + font-size: 22rpx; +} + +.upload-add-icon { + font-size: 48rpx; + color: #C8C8C8; + line-height: 1; + margin-bottom: 6rpx; +} + +/* ================= 底部红色提示框(门店信息要求) ================= */ +.tip-box { + margin: 20rpx 24rpx 0; + background: #FFF7F4; + border: 1rpx solid #FFD9CD; + border-radius: 12rpx; + padding: 20rpx 24rpx 24rpx; + display: flex; + gap: 12rpx; +} + +.tip-box .tip-icon { + color: #FF370B; + font-size: 28rpx; + font-weight: bold; + flex-shrink: 0; + line-height: 40rpx; +} + +.tip-box .tip-content { + flex: 1; +} + +.tip-box .tip-title { + font-size: 26rpx; + color: #FF370B; + font-weight: 600; + margin-bottom: 8rpx; +} + +.tip-box .tip-item { + font-size: 24rpx; + color: #FF370B; + line-height: 40rpx; +} + +/* ================= 底部按钮(吸底) ================= */ +.bottom-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + background: #FFFFFF; + padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); + display: flex; + gap: 16rpx; + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.04); + z-index: 99; +} + +.btn-primary { + flex: 1; + height: 88rpx; + background: linear-gradient(91deg, #FF7658 0%, #FF370B 100%); + border-radius: 44rpx; + color: #FFFFFF; + font-size: 30rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.btn-secondary { + flex: 1; + height: 88rpx; + background: #FFFFFF; + border: 1rpx solid #FF370B; + border-radius: 44rpx; + color: #FF370B; + font-size: 30rpx; + display: flex; + align-items: center; + justify-content: center; +} + +/* ================= 协议勾选 ================= */ +.agreement { + display: flex; + align-items: center; + padding: 24rpx 24rpx 0; +} + +.agreement-text { + font-size: 24rpx; + color: #666; + margin-left: 8rpx; +} + +.agreement-link { + color: #FF370B; +} + +/* ================= 资质示例链接 ================= */ +.example-link { + font-size: 24rpx; + color: #FF370B; + margin-top: 10rpx; +} + +/* ================= 弹层选择器(所在地区/类目/营业状态) ================= */ +.popup-mask { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; + display: flex; + align-items: flex-end; +} + +.popup-sheet { + width: 100%; + background: #FFFFFF; + border-radius: 24rpx 24rpx 0 0; + max-height: 80vh; + display: flex; + flex-direction: column; +} + +.popup-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 28rpx 32rpx; + border-bottom: 1rpx solid #F0F0F0; +} + +.popup-header .pop-cancel { + color: #999; + font-size: 28rpx; +} + +.popup-header .pop-title { + color: #222; + font-size: 30rpx; + font-weight: 600; +} + +.popup-header .pop-confirm { + color: #FF370B; + font-size: 28rpx; +} + +.popup-body { + flex: 1; + display: flex; + overflow: hidden; +} + +.popup-col { + flex: 1; + overflow-y: auto; + padding: 12rpx 0; +} + +.popup-col-item { + padding: 24rpx 32rpx; + font-size: 28rpx; + color: #222; + display: flex; + align-items: center; + justify-content: space-between; +} + +.popup-col-item.selected { + color: #FF370B; +} + +.popup-col-item .check { + color: #FF370B; + font-size: 24rpx; +} + +/* 单列选择列表(营业状态) */ +.popup-list { + flex: 1; + overflow-y: auto; +} + +.popup-list-item { + padding: 30rpx 32rpx; + font-size: 30rpx; + color: #222; + text-align: center; + border-bottom: 1rpx solid #F5F5F5; +} + +.popup-list-item.selected { + color: #FF370B; +} + +/* ================= 类目选择搜索框 ================= */ +.popup-search { + margin: 16rpx 32rpx; + background: #F5F5F5; + border-radius: 8rpx; + padding: 14rpx 20rpx; + display: flex; + align-items: center; + gap: 12rpx; + font-size: 26rpx; + color: #B7B7B7; +} diff --git a/packages/shopEnter/index/index.vue b/packages/shopEnter/index/index.vue index 1ea3d242..d55a7905 100644 --- a/packages/shopEnter/index/index.vue +++ b/packages/shopEnter/index/index.vue @@ -1,119 +1,342 @@ @@ -130,10 +353,13 @@ import { upload, NavgateTo } from '../../../utils'; +import { signPrivateView } from '../../../utils/uploadOSS.js'; export default { data() { return { picUrl, + currentTab: 1, + enterType: 'personal', area: "", confirmCity: "", confirmArea: "", @@ -146,12 +372,26 @@ export default { imgList5: [], imgList6: [], + // 身份证照片 + idCardFrontList: [], + idCardFrontPath: [], + idCardBackList: [], + idCardBackPath: [], + contact_name: "", contact_phone: "", bank_card: "", + bank_name: "", + bank_branch: "", + account_name: "", store_name: "", address: "", + // 企业入驻额外字段 + credit_code: "", + legal_person: "", + legal_id_card: "", + confirmProv: "", confirmCity: "", confirmBusiness: "", @@ -166,9 +406,208 @@ export default { show4: false, itemObj: {}, + agreeProtocol: false, + + // 新版弹层相关 + showRegion: false, + showClassifyPop: false, + showBizStatus: false, + tempProv: {}, + tempCity: {}, + tempBuss: {}, + tempClassify: {}, + bizStatus: 0, // 1 营业中 2 即将开业 3 休息中 + bizStatusList: [ + { value: 1, label: '营业中' }, + { value: 2, label: '即将开业' }, + { value: 3, label: '休息中' }, + ], } }, + computed: { + regionLabel() { + const a = this.confirmProv && this.confirmProv.ad_name + const b = this.confirmCity && this.confirmCity.short_name + const c = this.confirmBusiness && this.confirmBusiness.short_name + if (!a) return '' + return [a, b, c].filter(Boolean).join('/') + }, + bizStatusLabel() { + const opt = this.bizStatusList.find(o => o.value === this.bizStatus) + return opt ? opt.label : '' + }, + }, methods: { + // ===== 新版弹层交互 ===== + async openRegion() { + if (!this.pro || !this.pro.length) await this.getPro() + this.tempProv = this.confirmProv && this.confirmProv.ad_code ? this.confirmProv : {} + this.tempCity = this.confirmCity && this.confirmCity.ad_code ? this.confirmCity : {} + this.tempBuss = this.confirmBusiness && this.confirmBusiness.ad_code ? this.confirmBusiness : {} + if (this.tempProv.ad_code) await this.getCity(this.tempProv.ad_code) + if (this.tempCity.ad_code) await this.getBuss(this.tempCity.ad_code) + this.showRegion = true + }, + async tapProv(item) { + this.tempProv = item + this.tempCity = {} + this.tempBuss = {} + this.city = [] + this.buss = [] + await this.getCity(item.ad_code) + }, + async tapCity(item) { + this.tempCity = item + this.tempBuss = {} + this.buss = [] + await this.getBuss(item.ad_code) + }, + tapBuss(item) { + this.tempBuss = item + }, + confirmRegion() { + if (!this.tempProv.ad_code) { + uni.showToast({ title: '请选择省', icon: 'none' }); return + } + if (!this.tempCity.ad_code) { + uni.showToast({ title: '请选择市', icon: 'none' }); return + } + if (!this.tempBuss.ad_code) { + uni.showToast({ title: '请选择区', icon: 'none' }); return + } + this.confirmProv = this.tempProv + this.confirmCity = this.tempCity + this.confirmBusiness = this.tempBuss + this.showRegion = false + }, + async openClassify() { + if (!this.classify || !this.classify.length) await this.getClassify() + this.tempClassify = this.confirmClassify && this.confirmClassify.id ? this.confirmClassify : {} + this.showClassifyPop = true + }, + confirmClassifyPop() { + if (!this.tempClassify.id) { + uni.showToast({ title: '请选择类目', icon: 'none' }); return + } + this.confirmClassify = this.tempClassify + this.showClassifyPop = false + }, + selectBizStatus(opt) { + this.bizStatus = opt.value + this.showBizStatus = false + }, + + switchTab(tab) { + // 点击 Tab 切换时需要校验前面的 Tab + if (tab > this.currentTab) { + if (this.currentTab === 1 && !this.validateTab1()) return; + if (tab === 3 && !this.validateTab2()) return; + } + this.currentTab = tab; + uni.pageScrollTo({ scrollTop: 0, duration: 200 }); + }, + nextTab(tab) { + if (tab === 2) { + if (!this.validateTab1()) return; + } + if (tab === 3) { + if (!this.validateTab2()) return; + } + this.currentTab = tab; + uni.pageScrollTo({ scrollTop: 0, duration: 200 }); + }, + validateTab1() { + // 基本信息校验 + if (!this.store_name) { uni.showToast({ title: '请输入门店名称', icon: 'none' }); return false; } + if (!this.confirmProv.ad_code) { uni.showToast({ title: '请选择所在省', icon: 'none' }); return false; } + if (!this.confirmCity.ad_code) { uni.showToast({ title: '请选择所在市', icon: 'none' }); return false; } + if (!this.confirmBusiness.ad_code) { uni.showToast({ title: '请选择所在区', icon: 'none' }); return false; } + if (!this.address) { uni.showToast({ title: '请输入详细地址', icon: 'none' }); return false; } + if (!this.contact_name) { uni.showToast({ title: '请输入联系人姓名', icon: 'none' }); return false; } + if (!this.contact_phone) { uni.showToast({ title: '请输入联系人手机号', icon: 'none' }); return false; } + if (!isPhone(this.contact_phone)) { uni.showToast({ title: '联系人手机号格式不正确', icon: 'none' }); return false; } + if (!this.confirmClassify.id) { uni.showToast({ title: '请选择商家分类', icon: 'none' }); return false; } + return true; + }, + validateTab2() { + // 资质信息校验 + if (this.enterType === 'enterprise') { + if (!this.credit_code) { uni.showToast({ title: '请输入统一社会信用代码', icon: 'none' }); return false; } + if (!/^[0-9A-HJ-NPQRTUWXY]{18}$/.test(this.credit_code.toUpperCase())) { + uni.showToast({ title: '统一社会信用代码格式不正确(18位)', icon: 'none' }); + return false; + } + if (!this.legal_person) { uni.showToast({ title: '请输入法人姓名', icon: 'none' }); return false; } + if (!this.legal_id_card) { uni.showToast({ title: '请输入法人身份证号', icon: 'none' }); return false; } + if (!/(^\d{15}$)|(^\d{17}([0-9X])$)/.test(this.legal_id_card.toUpperCase())) { + uni.showToast({ title: '法人身份证号格式不正确', icon: 'none' }); + return false; + } + } + if (!this.imgList6.length) { uni.showToast({ title: '请上传营业执照', icon: 'none' }); return false; } + if (!this.idCardFrontPath.length) { uni.showToast({ title: '请上传身份证正面照片', icon: 'none' }); return false; } + if (!this.idCardBackPath.length) { uni.showToast({ title: '请上传身份证反面照片', icon: 'none' }); return false; } + return true; + }, + validateTab3() { + // 结算信息校验(个人 / 企业都需要银行四要素) + if (!this.account_name) { + uni.showToast({ + title: this.enterType === 'enterprise' ? '请输入企业开户名称' : '请输入开户人姓名', + icon: 'none' + }); + return false; + } + if (!this.bank_card) { + uni.showToast({ + title: this.enterType === 'enterprise' ? '请输入对公银行账号' : '请输入银行卡号', + icon: 'none' + }); + return false; + } + if (!/^\d{15,20}$/.test(this.bank_card)) { + uni.showToast({ title: '银行账号格式不正确(15-20位数字)', icon: 'none' }); + return false; + } + if (!this.bank_name) { uni.showToast({ title: '请输入开户银行', icon: 'none' }); return false; } + if (!this.bank_branch) { uni.showToast({ title: '请输入开户支行', icon: 'none' }); return false; } + return true; + }, + showExample(type) { + // 跳转到资质示例页 + NavgateTo('/packages/shopEnter/example/index?type=' + type); + }, + + // 身份证正面上传(私密 bucket,不能直链回显) + afterReadIdFront(e) { + const files = Array.isArray(e.file) ? e.file : [e.file] + files.forEach(item => { + upload(item.url, res => { + // 私密文件:上传 UI 用本地预览,path 单独保存供提交 + this.idCardFrontList.push({ url: item.url }) + this.idCardFrontPath.push(res.data.path) + }, 'merchant_private') + }) + }, + deleteIdFront(e) { + this.idCardFrontList.splice(e.index, 1); + this.idCardFrontPath.splice(e.index, 1); + }, + // 身份证反面上传(私密 bucket) + afterReadIdBack(e) { + const files = Array.isArray(e.file) ? e.file : [e.file] + files.forEach(item => { + upload(item.url, res => { + this.idCardBackList.push({ url: item.url }) + this.idCardBackPath.push(res.data.path) + }, 'merchant_private') + }) + }, + deleteIdBack(e) { + this.idCardBackList.splice(e.index, 1); + this.idCardBackPath.splice(e.index, 1); + }, + // 根据ad_code解析省市区信息 parseAdCode(adCode) { if (!adCode) return; @@ -207,7 +646,6 @@ export default { this.show3 = false; }, clickBuss(e) { - console.log(e); this.show3 = false; this.confirmBusiness = e.value[0] }, @@ -270,11 +708,13 @@ export default { }) }, afterReadImg3(e) { - e.file.forEach(item => { + // 营业执照(私密 bucket) + const files = Array.isArray(e.file) ? e.file : [e.file] + files.forEach(item => { upload(item.url, res => { - this.imgList5.push({ url: this.picUrl + res.data.path }) + this.imgList5.push({ url: item.url }) this.imgList6.push(res.data.path) - }) + }, 'merchant_private') }) }, deletePic2(e) { @@ -285,101 +725,102 @@ export default { this.imgList5.splice(e.index, 1); this.imgList6.splice(e.index, 1); }, + viewProtocol() { + // 查看商家入驻协议 + uni.showModal({ + title: '商家入驻协议', + content: '本协议是您与平台之间关于商家入驻服务的法律协议。入驻后您需遵守平台规则,按时提供商品或服务,保证信息真实有效。平台有权对违规商家进行处罚。', + showCancel: false, + confirmText: '我知道了' + }); + }, + // 给私密 bucket 中的多张图批量签名 URL,写到目标 fileList + async signPrivatePaths(paths, listKey) { + if (!paths || !paths.length) { + this[listKey] = [] + return + } + const result = [] + for (const p of paths) { + try { + const r = await signPrivateView(p) + result.push({ url: r.url }) + } catch (e) { + console.error('签发私密 URL 失败:', p, e) + result.push({ url: '' }) + } + } + this[listKey] = result + }, submit() { let that = this - if (!that.store_name) { - return uni.showToast({ - title: '请输入门店名称', - duration: 2000 - }); + if (!that.validateTab3()) return; + if (!that.agreeProtocol) { + uni.showToast({ title: '请先阅读并同意商家入驻协议', icon: 'none' }); + return; } - if (!that.confirmProv.ad_code) { - return uni.showToast({ - title: '请选择所在省', - duration: 2000 - }); - } - if (!that.confirmCity.ad_code) { - return uni.showToast({ - title: '请选择所在市', - duration: 2000 - }); - } - if (!that.confirmBusiness.ad_code) { - return uni.showToast({ - title: '请选择所在区', - duration: 2000 - }); - } - if (!that.address) { - return uni.showToast({ - title: '请输入详细地址', - duration: 2000 - }); - } - if (!that.contact_name) { - return uni.showToast({ - title: '请输入联系人姓名', - duration: 2000 - }); - } - if (!that.contact_phone) { - return uni.showToast({ - title: '请输入联系人手机号', - duration: 2000 - }); - } - if (!that.confirmClassify.id) { - return uni.showToast({ - title: '请选择商家分类', - duration: 2000 - }); - } - if (!that.imgList6.length) { - return uni.showToast({ - title: '请上传营业执照', - duration: 2000 - }); - } - let interior_photo = that.imgList4.join(",") let facade_photo = that.imgList2.join(",") let license_photo = that.imgList6.join(",") - request(apiArr.createStore, "POST", { + let id_card_front = that.idCardFrontPath.join(",") + let id_card_back = that.idCardBackPath.join(",") + + let params = { + enter_type: that.enterType === 'enterprise' ? 2 : 1, contact_name: that.contact_name, phone: that.contact_phone, bank_card: that.bank_card, + bank_name: that.bank_name, + bank_branch: that.bank_branch, + account_name: that.account_name, merchant_name: that.store_name, address: that.address, ad_code: that.confirmBusiness.ad_code, facade_photo, interior_photo, license_photo, + id_card_front, + id_card_back, merchant_cate_id: that.confirmClassify.id, - }).then(res => { - that.contact_name = '' - that.contact_phone = '' - that.bank_card = '' - that.store_name = '' - that.address = '' - that.confirmProv = '' - that.confirmCity = '' - that.confirmBusiness = '' - that.imgList = [] - that.imgList2 = [] - that.imgList3 = [] - that.imgList4 = [] - that.imgList5 = [] - that.imgList6 = [] - that.confirmClassify = '' - NavgateTo("../sucess/index") - }).catch(res => { - if (res.message.includes("agent_nil")) { - uni.showToast({ - title: '未找到对应的代理商信息', - icon: 'none', - }); + } + + if (that.enterType === 'enterprise') { + params.credit_code = that.credit_code; + params.legal_person = that.legal_person; + params.legal_id_card = that.legal_id_card; + params.account_type = 2; + } else { + params.account_type = 1; + } + + request(apiArr.createStore, "POST", params).then(res => { + // 提交成功后用最新表单 + 服务端返回拼一个 itemObj 给 auditStatus 页用 + const newItem = { + ...params, + id: res && res.id, + status: 1, + create_time: new Date().toLocaleString('sv-SE').replace('T', ' '), + handle_time: '', + merchant_code: '', + remark: '' } + NavgateTo("../auditStatus/index?itemObj=" + encodeURIComponent(JSON.stringify(newItem))) + }).catch(res => { + const msg = res && res.message ? res.message : '提交失败,请稍后重试' + if (msg.includes("agent_nil")) { + uni.showModal({ + title: '提交失败', + content: '未找到对应的代理商信息,请联系平台', + showCancel: false + }) + return + } + // 其余后端校验错误统一弹窗,避免被吞 + uni.showModal({ + title: '提交失败', + content: msg, + showCancel: false + }) }) }, @@ -414,9 +855,14 @@ export default { }, onLoad(options) { + // 获取入驻类型 + if (options.enterType) { + this.enterType = options.enterType; + } + // 先执行数据获取方法 Promise.all([this.getPro(), this.getClassify()]).then(() => { - // 数据获取完成后再进行赋值操作 + // 数据获取完成后再进行赋值操作(编辑回显) if (options.itemObj) { this.itemObj = JSON.parse(options.itemObj) this.store_name = this.itemObj.merchant_name @@ -425,6 +871,18 @@ export default { this.contact_phone = this.itemObj.phone this.confirmClassify = this.classify.find(item => item.id == this.itemObj.merchant_cate_id) this.bank_card = this.itemObj.bank_card + this.bank_name = this.itemObj.bank_name || '' + this.bank_branch = this.itemObj.bank_branch || '' + this.account_name = this.itemObj.account_name || '' + this.credit_code = this.itemObj.credit_code || '' + this.legal_person = this.itemObj.legal_person || '' + this.legal_id_card = this.itemObj.legal_id_card || '' + + // 入驻类型回显 + if (this.itemObj.enter_type === 2) { + this.enterType = 'enterprise'; + } + // 解析ad_code回显省市区 if (this.itemObj.ad_code) { this.parseAdCode(this.itemObj.ad_code); @@ -433,8 +891,14 @@ export default { this.imgList2 = this.itemObj.facade_photo ? this.itemObj.facade_photo.split(",") : [] this.imgList3 = this.itemObj.interior_photo ? this.itemObj.interior_photo.split(",").map(item => ({ url: this.picUrl + item })) : [] this.imgList4 = this.itemObj.interior_photo ? this.itemObj.interior_photo.split(",") : [] - this.imgList5 = this.itemObj.license_photo?.split(",").map(item => ({ url: this.picUrl + item })) - this.imgList6 = this.itemObj.license_photo?.split(",") + + // 营业执照、身份证 —— 私密 bucket,需要签名 URL 才能回显 + this.imgList6 = this.itemObj.license_photo?.split(",").filter(Boolean) || [] + this.idCardFrontPath = this.itemObj.id_card_front ? this.itemObj.id_card_front.split(",").filter(Boolean) : [] + this.idCardBackPath = this.itemObj.id_card_back ? this.itemObj.id_card_back.split(",").filter(Boolean) : [] + this.signPrivatePaths(this.imgList6, 'imgList5') + this.signPrivatePaths(this.idCardFrontPath, 'idCardFrontList') + this.signPrivatePaths(this.idCardBackPath, 'idCardBackList') } }) } @@ -443,4 +907,4 @@ export default { \ No newline at end of file + diff --git a/pages.json b/pages.json index b7bac54c..3b2648a8 100644 --- a/pages.json +++ b/pages.json @@ -572,6 +572,13 @@ { "root": "packages/shopEnter", "pages": [ + { + "path": "choose/index", + "style": { + "navigationBarTitleText": "商家入驻", + "navigationBarBackgroundColor": "#fff" + } + }, { "path": "index/index", "style": { @@ -593,6 +600,13 @@ "navigationBarTitleText": "审核状态", "navigationBarBackgroundColor": "#fff" } + }, + { + "path": "example/index", + "style": { + "navigationBarTitleText": "资质示例", + "navigationBarBackgroundColor": "#fff" + } } ] }, diff --git a/pages/index/index.vue b/pages/index/index.vue index 20a0e4a5..068c0e89 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -203,7 +203,7 @@ 买单返积分 - + 点评 @@ -665,14 +665,14 @@ export default { async headershopEnterClick() { if (!uni.getStorageSync('userId')) { - NavgateTo('/packages/shopEnter/index/index', { isLogin: false }); + NavgateTo('/packages/shopEnter/choose/index', { isLogin: false }); return } const res = await request(apiArr2.statusQuery, "POST", {}, { silent: false }); if (res.status) { NavgateTo('/packages/shopEnter/auditStatus/index?itemObj=' + JSON.stringify(res)); } else { - NavgateTo('/packages/shopEnter/index/index'); + NavgateTo('/packages/shopEnter/choose/index'); } }, diff --git a/utils/index.js b/utils/index.js index 7abc27e7..56f48cb1 100644 --- a/utils/index.js +++ b/utils/index.js @@ -3,8 +3,8 @@ const environments = { development: { apiUrl: "https://test.hshuishang.com", picUrl: "https://test.hshuishang.com", - aliyunOssUrl: "https://wechat-img-file.oss-cn-beijing.aliyuncs.com", - staticUrl: "https://static.hshuishang.com", + aliyunOssUrl: "https://wechat-img-file-dev.oss-cn-beijing.aliyuncs.com", + staticUrl: "https://static-dev.hshuishang.com", }, production: { apiUrl: "https://api.hshuishang.com", @@ -25,7 +25,7 @@ const getCurrentEnvironment = () => { if (envVersion === "release") { return "production"; // 正式版 } else if (envVersion === "trial") { - return "production"; // 体验版 + return "development"; // 体验版 } else if (envVersion === "develop") { return "development"; // 开发版 } @@ -54,14 +54,14 @@ const getCurrentEnvironment = () => { }; // 获取当前环境配置 -const currentEnv = "production";//getCurrentEnvironment(); +const currentEnv = getCurrentEnvironment(); const envConfig = environments[currentEnv] || environments.production; export const RequsetUrl = envConfig.apiUrl; // 请求地址前缀 -// export const picUrl = envConfig.picUrl; // 图片地址前缀 -export const picUrl = currentEnv==='production'?envConfig.aliyunOssUrl:envConfig.picUrl; // 图片地址前缀 -export const aliyunOssUrl = envConfig.aliyunOssUrl; // 阿里云OSS地址 -export const staticUrl = envConfig.staticUrl; // 静态资源地址 +// 公开图片域名:统一走 CDN(dev: static-dev.hshuishang.com / prod: static.hshuishang.com) +export const picUrl = envConfig.staticUrl; +export const aliyunOssUrl = envConfig.aliyunOssUrl; // 阿里云OSS直链地址(私密文件签名 URL 已自带 host,一般不需要直接拼) +export const staticUrl = envConfig.staticUrl; // CDN 加速域名 /** * 处理图片URL,根据环境自动替换 @@ -326,70 +326,26 @@ export const floatCalculate = (num1, num2, operator) => { }; /** - * 图片上传 - * @param {string} filename - 图片上传地址 - * @param {Function} fn - 接口回调函数 + * 图片上传 —— 走 OSS 直传,回调形状对齐旧后端 { code, data: { path } },path 以 / 开头 + * @param {string} filename - 本地文件路径 + * @param {Function} fn - 回调函数 + * @param {string} [scene] - 场景,默认 common */ -export const upload = (filename, fn) => { - uni.showLoading({ - title: "上传中", - mask: true, - }); - - uni.uploadFile({ - url: RequsetUrl + "/api/v1/public/upload-image", - filePath: filename, - name: "image", - formData: { - uid: uni.getStorageSync("uid"), - }, - success: (f) => { - uni.hideLoading(); - fn(JSON.parse(f.data)); - }, - fail: (res) => { - uni.hideLoading(); - console.log(res); - uni.showToast({ - title: "上传文件失败", - icon: "none", - }); - }, - complete: () => {}, - }); +export const upload = (filename, fn, scene = "common") => { + // 引入此处避免顶层循环依赖 + const { uploadOSSCompat } = require("./uploadOSS.js"); + uploadOSSCompat(filename, fn, scene); }; /** - * 视频上传 - * @param {string} filename - 图片上传地址 - * @param {Function} fn - 接口回调函数 + * 视频上传 —— 走 OSS 直传 + * @param {string} filename - 本地文件路径 + * @param {Function} fn - 回调函数 + * @param {string} [scene] - 场景,默认 video */ -export const uploadVideo = (filename, fn) => { - uni.showLoading({ - title: "上传中", - mask: true, - }); - uni.uploadFile({ - url: RequsetUrl + "/api/v1/public/upload-video", - filePath: filename, - name: "file", - formData: { - uid: uni.getStorageSync("uid"), - }, - success: (f) => { - uni.hideLoading(); - fn(JSON.parse(f.data)); - }, - fail: (res) => { - uni.hideLoading(); - console.log(res); - uni.showToast({ - title: "上传文件失败", - icon: "none", - }); - }, - complete: () => {}, - }); +export const uploadVideo = (filename, fn, scene = "video") => { + const { uploadOSSCompat } = require("./uploadOSS.js"); + uploadOSSCompat(filename, fn, scene); }; /** diff --git a/utils/uploadOSS.js b/utils/uploadOSS.js new file mode 100644 index 00000000..d6abf9d3 --- /dev/null +++ b/utils/uploadOSS.js @@ -0,0 +1,150 @@ +// uniapp-ZHSQ —— OSS 直传客户端 +// +// 用法: +// import { uploadOSS } from '@/utils/uploadOSS' +// const { objectKey, url } = await uploadOSS({ filePath: tempFilePath, scene: 'avatar' }) +// // objectKey: OSS 上的相对 key,存数据库 +// // url: 公开访问 URL(如果走签名 URL 显示,前端再单独处理) +// +// 支持的 scene 由服务端 wechatOssSceneCfg 控制,目前: +// - avatar 用户头像 (jpg/jpeg/png/webp,<=5MB) +// - merchant 商家入驻资料 (jpg/jpeg/png/webp/pdf,<=10MB) + +import { RequsetUrl, aliyunOssUrl } from "@/utils/index.js"; + +const inferExt = (filePath) => { + if (!filePath) return ""; + const idx = filePath.lastIndexOf("."); + if (idx < 0) return ""; + return filePath.substring(idx + 1).toLowerCase(); +}; + +const fetchCredential = (scene, ext) => { + return new Promise((resolve, reject) => { + uni.request({ + url: RequsetUrl + "/api/v1/wechat/oss/credential", + method: "POST", + header: { + Authorization: uni.getStorageSync("ctoken") || "", + "Content-Type": "application/json", + }, + data: { scene, ext }, + success: (res) => { + if (res.statusCode !== 200) return reject(new Error("获取上传凭证失败:" + res.statusCode)); + const body = res.data || {}; + // 兼容服务端通用响应包装 + const data = body.data || body; + if (!data || !data.host) return reject(new Error("上传凭证字段缺失")); + resolve(data); + }, + fail: (err) => reject(err), + }); + }); +}; + +const postToOSS = (filePath, cred) => { + return new Promise((resolve, reject) => { + uni.uploadFile({ + url: cred.host, + filePath, + name: "file", // 必须是 file,OSS PostObject 协议约定 + formData: { + key: cred.object_key, + OSSAccessKeyId: cred.access_key_id, + "x-oss-security-token": cred.security_token, + policy: cred.policy, + signature: cred.signature, + success_action_status: "200", + }, + success: (res) => { + // OSS 200 -> 空 body 视为成功 + if (res.statusCode === 200 || res.statusCode === 204) { + resolve(); + } else { + reject(new Error("OSS 上传失败:" + res.statusCode + " " + (res.data || ""))); + } + }, + fail: (err) => reject(err), + }); + }); +}; + +/** + * 直传 OSS。 + * @param {Object} opts + * @param {string} opts.filePath 本地文件临时路径(uni.chooseImage 拿到的 path) + * @param {string} opts.scene 上传场景:avatar / merchant + * @param {string} [opts.ext] 扩展名(不含点);不传则从 filePath 推断 + * @param {boolean} [opts.showLoading=true] + * @returns {Promise<{ objectKey: string, url: string }>} + */ +export const uploadOSS = async ({ filePath, scene, ext, showLoading = true }) => { + if (!filePath) throw new Error("filePath 不能为空"); + if (!scene) throw new Error("scene 不能为空"); + + const finalExt = (ext || inferExt(filePath)).toLowerCase(); + if (!finalExt) throw new Error("无法识别文件扩展名"); + + if (showLoading) uni.showLoading({ title: "上传中", mask: true }); + try { + const cred = await fetchCredential(scene, finalExt); + await postToOSS(filePath, cred); + return { + objectKey: cred.object_key, + url: aliyunOssUrl + "/" + cred.object_key, + }; + } finally { + if (showLoading) uni.hideLoading(); + } +}; + +/** + * 兼容旧版 utils/index.js 中 upload(filename, fn) 的回调式 API。 + * 内部走 OSS 直传,回调 res 形状对齐旧版(res.data.path 为以 / 开头的 object key, + * 调用方 picUrl + res.data.path 可以直接拼成完整 URL)。 + * + * @param {string} filePath + * @param {Function} fn 接收 { code, msg, data: { path } } + * @param {string} [scene='merchant'] + */ +export const uploadOSSCompat = (filePath, fn, scene = "merchant") => { + uploadOSS({ filePath, scene }) + .then(({ objectKey }) => { + fn && fn({ code: 1, msg: "ok", data: { path: "/" + objectKey } }); + }) + .catch((err) => { + console.error("uploadOSSCompat 失败:", err); + uni.showToast({ title: err.message || "上传文件失败", icon: "none" }); + }); +}; + +/** + * 取私密文件查看 URL(小程序商家本人查看自己上传的证件 / 合同)。 + * 服务端会校验 path 必须以 wechat/merchant_private/{当前 user_id}/ 开头。 + * + * @param {string} objectKey 存数据库的 path(带或不带前导 /) + * @returns {Promise<{ url: string, expireSeconds: number }>} + */ +export const signPrivateView = (objectKey) => { + return new Promise((resolve, reject) => { + if (!objectKey) return reject(new Error("object_key 不能为空")); + const cleanKey = objectKey.replace(/^\/+/, ""); + uni.request({ + url: RequsetUrl + "/api/v1/wechat/oss/sign-private-view", + method: "POST", + header: { + Authorization: uni.getStorageSync("ctoken") || "", + "Content-Type": "application/json", + }, + data: { object_key: cleanKey }, + success: (res) => { + if (res.statusCode !== 200) return reject(new Error("签发失败:" + res.statusCode)); + const body = res.data || {}; + const data = body.data || body; + if (!data || !data.url) return reject(new Error("签发结果字段缺失")); + resolve({ url: data.url, expireSeconds: data.expire_seconds }); + }, + fail: (err) => reject(err), + }); + }); +};