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