From c7c19c5bb1798e51dcd45e0bb98634a57317f14d Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期一, 14 九月 2026 20:57:42 +0800
Subject: [PATCH] fix(merchant): 进件草稿按操作员隔离,换账号不再看到上一个账号的资料
---
rencaika_merchant/services/onboarding-draft.uts | 25 ++++++++++++
rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue | 4 +
rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue | 25 ++++++++++--
rencaika_merchant/services/session.uts | 7 +++
4 files changed, 55 insertions(+), 6 deletions(-)
diff --git a/rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue b/rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue
index 7ce2c92..054ae52 100644
--- a/rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue
+++ b/rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue
@@ -17,6 +17,7 @@
<script setup lang="uts">
import { loadOnboardingStatus, SettlementOnboarding } from '../../services/settlement.uts'
+ import { getMerchantOnboardingDraftStorageKey } from '../../services/onboarding-draft.uts'
const onboardingStatus = ref('pending')
const statusReason = ref('')
@@ -46,7 +47,8 @@
return
}
onboardingStatus.value = item.status
- if (item.status == 'approved') uni.removeStorageSync('merchant-onboarding-draft')
+ // 只清当前账号自己的草稿:草稿 key 按操作员分命名空间,不能误删别的账号的。
+ if (item.status == 'approved') uni.removeStorageSync(getMerchantOnboardingDraftStorageKey())
statusReason.value = item.status == 'rejected' ? item.reason : ''
}, (error: any) => {
uni.showToast({ title: error.message != null ? error.message : '进件状态加载失败', icon: 'none' })
diff --git a/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue b/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
index 19a180f..ab275eb 100644
--- a/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
+++ b/rencaika_merchant/pages/merchant-onboarding/merchant-onboarding.uvue
@@ -68,6 +68,8 @@
<script setup lang="uts">
import { loadOnboardingStatus, SettlementOnboarding, submitOnboarding as submitOnboardingRemote } from '../../services/settlement.uts'
import { uploadMerchantProductImage } from '../../services/merchant-management.uts'
+ import { getMerchantOnboardingDraftStorageKey } from '../../services/onboarding-draft.uts'
+ import { getSession } from '../../services/session.uts'
import { areaProvinceNames, areaProvinceCodes, areaCityNames, areaCityCodes } from '../../config/area-codes.uts'
const stepTitles = ['主体信息', '法人信息', '联系人', '结算账户']
@@ -85,7 +87,14 @@
const businessValidityIndex = ref(0)
const subjectValidityOptions = ['10年', '20年', '长期有效']
const subjectValidityIndex = ref(0)
- const draftStorageKey = 'merchant-onboarding-draft'
+ // 草稿按当前登录的操作员分命名空间,换账号看不到上一个账号的草稿(见 services/onboarding-draft.uts)。
+ // 必须在取用点现算:进入页面时机不确定,模块加载时算会把当时的会话固化住。
+ const draftStorageKey = (): string => getMerchantOnboardingDraftStorageKey()
+ const currentOperatorId = (): string => {
+ const session = getSession()
+ if (session == null || session.operatorId == null) return ''
+ return (session.operatorId as string).trim()
+ }
let draftRestored = false
/** 私营企业等类型汇付要求必须填注册资本,这里与服务端 normalizeRegisteredCapital 保持一致。 */
@@ -162,14 +171,19 @@
}
const goPrevStep = () => { saveDraft(); if (currentStep.value > 1) currentStep.value = currentStep.value - 1; resetScroll() }
- const draftPayload = (): any => ({ merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, currentStep: currentStep.value })
- const saveDraft = () => { uni.setStorageSync(draftStorageKey, JSON.stringify(draftPayload())) }
- const clearDraft = () => { uni.removeStorageSync(draftStorageKey) }
+ const draftPayload = (): any => ({ ownerOperatorId: currentOperatorId(), merchantType: merchantType.value, merchantName: merchantName.value, merchantShortName: merchantShortName.value, companyType: companyType.value, companyTypeIndex: companyTypeIndex.value, industryType: industryType.value, industryTypeIndex: industryTypeIndex.value, businessLicenseUrl: businessLicenseUrl.value, creditCode: creditCode.value, businessValidity: businessValidity.value, businessStartDate: businessStartDate.value, businessEndDate: businessEndDate.value, establishedDate: establishedDate.value, registeredRegion: registeredRegion.value, registeredProvinceCode: registeredProvinceCode.value, registeredCityCode: registeredCityCode.value, registeredDistrictCode: registeredDistrictCode.value, registeredAddress: registeredAddress.value, registeredCapital: registeredCapital.value, subjectName: subjectName.value, legalIdFrontUrl: legalIdFrontUrl.value, legalIdBackUrl: legalIdBackUrl.value, subjectIdNumber: subjectIdNumber.value, subjectValidity: subjectValidity.value, subjectStartDate: subjectStartDate.value, subjectEndDate: subjectEndDate.value, legalCertificateAddress: legalCertificateAddress.value, contactName: contactName.value, contactMobile: contactMobile.value, administratorEmail: administratorEmail.value, bankCardNumber: bankCardNumber.value, bankProvince: bankProvince.value, bankProvinceCode: bankProvinceCode.value, bankProvinceIndex: bankProvinceIndex.value, bankCity: bankCity.value, bankCityCode: bankCityCode.value, bankCityIndex: bankCityIndex.value, bankBranch: bankBranch.value, bankBranchCode: bankBranchCode.value, bankName: bankName.value, settlementProofUrl: settlementProofUrl.value, currentStep: currentStep.value })
+ const saveDraft = () => { uni.setStorageSync(draftStorageKey(), JSON.stringify(draftPayload())) }
+ const clearDraft = () => { uni.removeStorageSync(draftStorageKey()) }
const restoreDraft = () => {
- const stored = uni.getStorageSync(draftStorageKey)
+ const stored = uni.getStorageSync(draftStorageKey())
if (stored == null || stored == '') return
try {
const draft = JSON.parse(stored as string) as any
+ // 草稿必须属于当前登录的操作员。key 已经按操作员分命名空间,这里再校验一次归属:
+ // 更新前存在旧的全局 key 下的草稿,里面可能有别的账号的姓名、身份证号、银行卡号;
+ // 归属不符就直接丢弃,宁可让用户重填,也不能把别人的资料预填进来。
+ const owner = draft.ownerOperatorId != null ? (draft.ownerOperatorId as string) : ''
+ if (owner != currentOperatorId()) { clearDraft(); return }
if (draft.merchantType != null) merchantType.value = draft.merchantType as string
if (draft.merchantName != null) merchantName.value = draft.merchantName as string
if (draft.merchantShortName != null) merchantShortName.value = draft.merchantShortName as string
@@ -216,6 +230,7 @@
const restoredBusinessValidity = validityOptions.indexOf(businessValidity.value); if (restoredBusinessValidity >= 0) businessValidityIndex.value = restoredBusinessValidity
const restoredSubjectValidity = subjectValidityOptions.indexOf(subjectValidity.value); if (restoredSubjectValidity >= 0) subjectValidityIndex.value = restoredSubjectValidity
if (draft.currentStep != null) { const restoredStep = Number(draft.currentStep); currentStep.value = (restoredStep >= 1 && restoredStep <= 4) ? restoredStep : 1 }
+ // 走到这里才说明草稿确实属于当前账号,这时才提示"已恢复草稿"。
draftNotice.value = true
} catch (_error) { clearDraft() }
}
diff --git a/rencaika_merchant/services/onboarding-draft.uts b/rencaika_merchant/services/onboarding-draft.uts
new file mode 100644
index 0000000..f743e36
--- /dev/null
+++ b/rencaika_merchant/services/onboarding-draft.uts
@@ -0,0 +1,25 @@
+import { getSession } from './session.uts'
+
+/**
+ * 进件草稿的 storage key。
+ *
+ * 草稿原本存在一个固定的全局 key(`merchant-onboarding-draft`)里,而 `uni` 的 storage
+ * 不区分账号:所以在商户端 A 账号填了一半退出、换成 B 账号登录后,B 打开进件页会看到
+ * 「已恢复上次未提交的草稿」,并把 **A 的姓名、身份证号、银行卡号**预填进 B 的表单。
+ * 这不只是体验问题,是把上一个账号的敏感信息泄露给了下一个账号。
+ *
+ * 所以按「当前登录的操作员」给 key 加命名空间:换账号即换 key,彼此看不到对方的草稿。
+ *
+ * 退路:会话还没建立时(例如开发态的登录旁路)取不到 operatorId,此时回退到门店维度;
+ * 再取不到就退回不带后缀的旧 key——宁可退回旧行为,也不能让 key 变成 `...-` 这种会互相覆盖的形态。
+ */
+export const getMerchantOnboardingDraftStorageKey = (): string => {
+ const prefix = 'merchant-onboarding-draft'
+ const session = getSession()
+ if (session == null) return prefix
+ const operator = session.operatorId != null ? (session.operatorId as string).trim() : ''
+ if (operator.length > 0) return prefix + '-' + operator
+ const store = session.storeId != null ? (session.storeId as string).trim() : ''
+ if (store.length > 0) return prefix + '-store-' + store
+ return prefix
+}
diff --git a/rencaika_merchant/services/session.uts b/rencaika_merchant/services/session.uts
index 581c9d5..ff509db 100644
--- a/rencaika_merchant/services/session.uts
+++ b/rencaika_merchant/services/session.uts
@@ -25,6 +25,13 @@
}
export const clearSession = () => {
+ // 退出时把当前操作员的进件草稿一起清掉。草稿里有姓名、身份证号、银行卡号,
+ // 让它在设备上跨会话留存没有收益,反而会在换账号时被误当成"上次未提交的草稿"恢复出来。
+ // 顺序很重要:必须先取到操作员再清会话,否则算不出这个账号的草稿 key。
+ // 只删当前操作员的那一份,不会误删同一台设备上别的账号的草稿。
+ const session = getSession()
+ const operator = session != null && session.operatorId != null ? (session.operatorId as string).trim() : ''
+ if (operator.length > 0) uni.removeStorageSync('merchant-onboarding-draft-' + operator)
uni.removeStorageSync(sessionStorageKey)
}
--
Gitblit v1.7.1