修复账号切换后「我的」页当前账号展示错误,并统一举报摄影师首页 URI。

切换账号时以用户选中账号为准写入展示标题与业务作用域,避免沿用旧门店名称;同时将首页举报摄影师入口 URI 对齐为 report_photographer。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-01 17:21:04 +08:00
parent fcb86d43f5
commit 5ab9b1b324
23 changed files with 419 additions and 92 deletions

View File

@ -63,11 +63,22 @@ struct BusinessScope: Codable, Equatable, Identifiable {
final class AccountContext: ObservableObject {
@Published private(set) var profile: AccountProfile?
@Published private(set) var accountType: String?
@Published private(set) var businessUserId: Int?
/// /
@Published private(set) var currentAccountDisplayTitle: String?
@Published private(set) var scenicScopes: [BusinessScope] = []
@Published private(set) var storeScopes: [BusinessScope] = []
@Published var currentScenic: BusinessScope?
@Published var currentStore: BusinessScope?
/// AccountSwitchAccount.id
var currentAccountIdentifier: String? {
guard let businessUserId, businessUserId > 0 else { return nil }
let type = accountType?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !type.isEmpty else { return nil }
return "\(type)_\(businessUserId)"
}
/// Android
var accountCachePrefix: String? {
let userId = profile?.userId.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
@ -77,9 +88,20 @@ final class AccountContext: ObservableObject {
}
///
func applyLogin(profile: AccountProfile? = nil, accountType: String? = nil) {
func applyLogin(
profile: AccountProfile? = nil,
accountType: String? = nil,
businessUserId: Int? = nil,
displayTitle: String? = nil
) {
self.profile = profile
self.accountType = accountType?.trimmingCharacters(in: .whitespacesAndNewlines)
if let businessUserId, businessUserId > 0 {
self.businessUserId = businessUserId
}
if let displayTitle = normalizedText(displayTitle) {
currentAccountDisplayTitle = displayTitle
}
}
///
@ -92,10 +114,29 @@ final class AccountContext: ObservableObject {
scenic: [BusinessScope],
stores: [BusinessScope],
currentScenicId: Int? = nil,
currentStoreId: Int? = nil
currentStoreId: Int? = nil,
explicitSelection: Bool = false
) {
scenicScopes = scenic
storeScopes = stores
if explicitSelection {
currentScenic = currentScenicId.flatMap { id in
scenic.first { $0.id == id }
} ?? scenic.first
if let storeId = currentStoreId, storeId > 0 {
currentStore = resolvedStore(
in: stores,
currentScenic: currentScenic,
preferredStoreId: storeId,
allowFallbackToFirst: false
)
} else {
currentStore = nil
}
return
}
currentScenic = currentScenicId.flatMap { id in
scenic.first { $0.id == id }
} ?? currentScenic.flatMap { current in
@ -104,7 +145,8 @@ final class AccountContext: ObservableObject {
currentStore = resolvedStore(
in: stores,
currentScenic: currentScenic,
preferredStoreId: currentStoreId ?? currentStore?.id
preferredStoreId: currentStoreId ?? currentStore?.id,
allowFallbackToFirst: true
)
}
@ -115,7 +157,8 @@ final class AccountContext: ObservableObject {
currentStore = resolvedStore(
in: storeScopes,
currentScenic: scenic,
preferredStoreId: currentStore?.id
preferredStoreId: currentStore?.id,
allowFallbackToFirst: true
)
}
@ -134,6 +177,8 @@ final class AccountContext: ObservableObject {
func reset() {
profile = nil
accountType = nil
businessUserId = nil
currentAccountDisplayTitle = nil
scenicScopes = []
storeScopes = []
currentScenic = nil
@ -144,7 +189,8 @@ final class AccountContext: ObservableObject {
private func resolvedStore(
in stores: [BusinessScope],
currentScenic: BusinessScope?,
preferredStoreId: Int?
preferredStoreId: Int?,
allowFallbackToFirst: Bool
) -> BusinessScope? {
let scenicId = currentScenic?.id
let storesForScenic = stores.filter { store in
@ -155,6 +201,13 @@ final class AccountContext: ObservableObject {
let store = storesForScenic.first(where: { $0.id == preferredStoreId }) {
return store
}
guard allowFallbackToFirst else { return nil }
return storesForScenic.first
}
///
private func normalizedText(_ value: String?) -> String? {
let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return text.isEmpty ? nil : text
}
}

View File

@ -26,7 +26,8 @@ struct AccountContextLoader {
cachedCurrentRoleCode: String? = nil,
cachedLegacyRoleId: Int? = nil,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
cachedCurrentStoreId: Int? = nil,
explicitScopeSelection: Bool = false
) async throws {
try await restorePermissions(
permissionContext: permissionContext,
@ -35,7 +36,8 @@ struct AccountContextLoader {
cachedCurrentRoleCode: cachedCurrentRoleCode,
cachedLegacyRoleId: cachedLegacyRoleId,
cachedCurrentScenicId: cachedCurrentScenicId,
cachedCurrentStoreId: cachedCurrentStoreId
cachedCurrentStoreId: cachedCurrentStoreId,
explicitScopeSelection: explicitScopeSelection
)
try await refreshSupplementalContext(
accountContext: accountContext,
@ -43,7 +45,8 @@ struct AccountContextLoader {
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
cachedCurrentScenicId: cachedCurrentScenicId,
cachedCurrentStoreId: cachedCurrentStoreId
cachedCurrentStoreId: cachedCurrentStoreId,
explicitScopeSelection: explicitScopeSelection
)
}
@ -55,7 +58,8 @@ struct AccountContextLoader {
cachedCurrentRoleCode: String? = nil,
cachedLegacyRoleId: Int? = nil,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
cachedCurrentStoreId: Int? = nil,
explicitScopeSelection: Bool = false
) async throws {
let rolePermissions = try await accountContextAPI.rolePermissions()
permissionContext.replaceRolePermissions(
@ -70,7 +74,8 @@ struct AccountContextLoader {
scenic: roleScenicScopes,
stores: accountContext.storeScopes,
currentScenicId: cachedCurrentScenicId,
currentStoreId: cachedCurrentStoreId
currentStoreId: cachedCurrentStoreId,
explicitSelection: explicitScopeSelection
)
}
@ -81,7 +86,8 @@ struct AccountContextLoader {
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing,
cachedCurrentScenicId: Int? = nil,
cachedCurrentStoreId: Int? = nil
cachedCurrentStoreId: Int? = nil,
explicitScopeSelection: Bool = false
) async throws {
let userInfo = try await profileAPI.userInfo()
let scenicResponse = await loadScenicList(
@ -94,19 +100,57 @@ struct AccountContextLoader {
permissionContext: permissionContext,
scenicResponse: scenicResponse
)
let storeScopes = storesResponse?.list.map { store in
let fetchedStoreScopes = storesResponse?.list.map { store in
BusinessScope(id: store.id, name: store.name, kind: .store, parentScenicId: store.scenicId)
} ?? []
// /退
let storeScopes = fetchedStoreScopes.isEmpty ? accountContext.storeScopes : fetchedStoreScopes
accountContext.replaceProfile(profile)
accountContext.replaceScopes(
scenic: scenicScopes,
stores: storeScopes,
currentScenicId: cachedCurrentScenicId ?? accountContext.currentScenic?.id,
currentStoreId: cachedCurrentStoreId ?? accountContext.currentStore?.id
currentScenicId: resolvedScenicId(
cachedCurrentScenicId: cachedCurrentScenicId,
accountContext: accountContext,
explicitScopeSelection: explicitScopeSelection
),
currentStoreId: resolvedStoreId(
cachedCurrentStoreId: cachedCurrentStoreId,
accountContext: accountContext,
explicitScopeSelection: explicitScopeSelection
),
explicitSelection: explicitScopeSelection
)
}
/// 使 ID
private func resolvedScenicId(
cachedCurrentScenicId: Int?,
accountContext: AccountContext,
explicitScopeSelection: Bool
) -> Int? {
if explicitScopeSelection {
return cachedCurrentScenicId ?? accountContext.currentScenic?.id
}
return cachedCurrentScenicId ?? accountContext.currentScenic?.id
}
/// 使 ID
private func resolvedStoreId(
cachedCurrentStoreId: Int?,
accountContext: AccountContext,
explicitScopeSelection: Bool
) -> Int? {
if explicitScopeSelection {
if accountContext.accountType == V9ScenicUser.accountTypeValue {
return nil
}
return cachedCurrentStoreId
}
return cachedCurrentStoreId ?? accountContext.currentStore?.id
}
///
private func loadScenicList(
accountContextAPI: AccountContextServing,

View File

@ -52,7 +52,8 @@ final class AuthSessionCoordinator {
accountContext: AccountContext,
permissionContext: PermissionContext,
profileAPI: UserProfileServing,
accountContextAPI: AccountContextServing
accountContextAPI: AccountContextServing,
selectedAccount: AccountSwitchAccount? = nil
) async throws {
let token = response.token.trimmingCharacters(in: .whitespacesAndNewlines)
try tokenStore.save(token)
@ -62,9 +63,22 @@ final class AuthSessionCoordinator {
let profile = response.primaryProfile
let scenicScopes = response.scenicScopes
let storeScopes = response.storeScopes
let identity = response.currentAccountIdentity
accountContext.applyLogin(profile: profile, accountType: identity?.accountType)
accountContext.replaceScopes(scenic: scenicScopes, stores: storeScopes)
let identity = resolvedIdentity(from: response, selectedAccount: selectedAccount)
let scopeSelection = selectedAccount?.preferredScopeSelection ?? response.preferredScopeSelection
let displayTitle = nonEmpty(selectedAccount?.title) ?? response.currentAccountDisplayTitle
accountContext.applyLogin(
profile: profile,
accountType: identity?.accountType,
businessUserId: identity?.businessUserId,
displayTitle: displayTitle
)
accountContext.replaceScopes(
scenic: scenicScopes,
stores: storeScopes,
currentScenicId: scopeSelection.scenicId,
currentStoreId: scopeSelection.storeId,
explicitSelection: true
)
appSession.beginRestoring(token: token)
do {
@ -74,8 +88,9 @@ final class AuthSessionCoordinator {
profileAPI: profileAPI,
accountContextAPI: accountContextAPI,
cachedCurrentRoleCode: response.currentAccountRoleCode,
cachedCurrentScenicId: accountContext.currentScenic?.id,
cachedCurrentStoreId: accountContext.currentStore?.id
cachedCurrentScenicId: scopeSelection.scenicId,
cachedCurrentStoreId: scopeSelection.storeId,
explicitScopeSelection: true
)
} catch {
if APIError.isAuthenticationExpired(error) {
@ -153,6 +168,7 @@ final class AuthSessionCoordinator {
profile: profile,
accountType: accountType,
businessUserId: businessUserId,
currentAccountDisplayTitle: accountContext.currentAccountDisplayTitle,
currentRoleCode: permissionContext?.currentAppRole?.rawValue ?? currentRoleCode,
scenicScopes: accountContext.scenicScopes,
storeScopes: accountContext.storeScopes,
@ -162,6 +178,17 @@ final class AuthSessionCoordinator {
)
}
/// 使 set-user is_current
private func resolvedIdentity(
from response: V9AuthResponse,
selectedAccount: AccountSwitchAccount?
) -> (accountType: String, businessUserId: Int)? {
if let selectedAccount, selectedAccount.businessUserId > 0 {
return (selectedAccount.accountType, selectedAccount.businessUserId)
}
return response.currentAccountIdentity.map { ($0.accountType, $0.businessUserId) }
}
///
private func makeProfile(from userInfo: UserInfoResponse, fallback: AccountProfile?) -> AccountProfile {
AccountProfile(
@ -185,27 +212,3 @@ struct LoginPreferences: Equatable {
let privacyAgreementAccepted: Bool
}
private extension V9AuthResponse {
///
struct CurrentAccountIdentity {
let accountType: String
let businessUserId: Int
}
/// 使 isCurrent
var currentAccountIdentity: CurrentAccountIdentity? {
if let storeUser = storeUsers.first(where: \.isCurrent) ?? storeUsers.first {
return CurrentAccountIdentity(
accountType: storeUser.accountType,
businessUserId: storeUser.businessUserId
)
}
if let scenicUser = scenicUsers.first(where: \.isCurrent) ?? scenicUsers.first {
return CurrentAccountIdentity(
accountType: scenicUser.accountType,
businessUserId: scenicUser.businessUserId
)
}
return nil
}
}

View File

@ -89,12 +89,18 @@ final class SessionBootstrapper {
///
private func restoreCachedSnapshot(to accountContext: AccountContext) -> AccountSnapshot? {
guard let snapshot = snapshotStore.load() else { return nil }
accountContext.applyLogin(profile: snapshot.profile, accountType: snapshot.accountType)
accountContext.applyLogin(
profile: snapshot.profile,
accountType: snapshot.accountType,
businessUserId: snapshot.businessUserId,
displayTitle: snapshot.currentAccountDisplayTitle
)
accountContext.replaceScopes(
scenic: snapshot.scenicScopes,
stores: snapshot.storeScopes,
currentScenicId: snapshot.currentScenicId,
currentStoreId: snapshot.currentStoreId
currentStoreId: snapshot.currentStoreId,
explicitSelection: true
)
return snapshot
}
@ -198,6 +204,7 @@ final class SessionBootstrapper {
profile: accountContext.profile,
accountType: accountContext.accountType ?? existing?.accountType,
businessUserId: existing?.businessUserId,
currentAccountDisplayTitle: accountContext.currentAccountDisplayTitle ?? existing?.currentAccountDisplayTitle,
currentRoleCode: permissionContext.currentAppRole?.rawValue ?? existing?.currentRoleCode,
scenicScopes: accountContext.scenicScopes,
storeScopes: accountContext.storeScopes,

View File

@ -13,6 +13,7 @@ struct AccountSnapshot: Codable, Equatable {
var profile: AccountProfile?
var accountType: String?
var businessUserId: Int?
var currentAccountDisplayTitle: String?
var currentRoleCode: String?
///
var currentRoleId: Int?
@ -26,6 +27,7 @@ struct AccountSnapshot: Codable, Equatable {
profile: AccountProfile? = nil,
accountType: String? = nil,
businessUserId: Int? = nil,
currentAccountDisplayTitle: String? = nil,
currentRoleCode: String? = nil,
currentRoleId: Int? = nil,
scenicScopes: [BusinessScope] = [],
@ -36,6 +38,7 @@ struct AccountSnapshot: Codable, Equatable {
self.profile = profile
self.accountType = accountType
self.businessUserId = businessUserId
self.currentAccountDisplayTitle = currentAccountDisplayTitle
self.currentRoleCode = currentRoleCode
self.currentRoleId = currentRoleId
self.scenicScopes = scenicScopes
@ -117,6 +120,7 @@ final class AccountSnapshotStore {
profile: accountContext.profile ?? existing?.profile,
accountType: accountContext.accountType ?? existing?.accountType,
businessUserId: existing?.businessUserId,
currentAccountDisplayTitle: accountContext.currentAccountDisplayTitle ?? existing?.currentAccountDisplayTitle,
currentRoleCode: currentRoleCode ?? existing?.currentRoleCode,
scenicScopes: accountContext.scenicScopes,
storeScopes: accountContext.storeScopes,

View File

@ -76,6 +76,14 @@ struct AccountSwitchAccount: Identifiable, Hashable {
}
return SetUserRequest(ssUserId: businessUserId)
}
/// ID
var preferredScopeSelection: (scenicId: Int?, storeId: Int?) {
if isStoreUser {
return (scenicId, storeId)
}
return (scenicId, nil)
}
}
/// token
@ -100,9 +108,9 @@ struct V9AuthResponse: Decodable, Equatable {
scenicUsers.map { $0.toAccountSwitchAccount() } + storeUsers.map { $0.toAccountSwitchAccount() }
}
///
/// 使 isCurrent
var primaryProfile: AccountProfile? {
if let storeUser = storeUsers.first(where: \.isCurrent) ?? storeUsers.first {
if let storeUser = resolvedCurrentStoreUser {
return AccountProfile(
userId: String(storeUser.userId),
displayName: storeUser.displayName,
@ -111,7 +119,7 @@ struct V9AuthResponse: Decodable, Equatable {
)
}
if let scenicUser = scenicUsers.first(where: \.isCurrent) ?? scenicUsers.first {
if let scenicUser = resolvedCurrentScenicUser {
return AccountProfile(
userId: String(scenicUser.userId),
displayName: scenicUser.displayName,
@ -123,6 +131,60 @@ struct V9AuthResponse: Decodable, Equatable {
return nil
}
/// ID/
var currentAccountIdentity: (accountType: String, businessUserId: Int)? {
if let storeUser = resolvedCurrentStoreUser {
return (storeUser.accountType, storeUser.businessUserId)
}
if let scenicUser = resolvedCurrentScenicUser {
return (scenicUser.accountType, scenicUser.businessUserId)
}
return nil
}
/// ID/
var preferredScopeSelection: (scenicId: Int?, storeId: Int?) {
if let storeUser = resolvedCurrentStoreUser {
return (
storeUser.scenicId > 0 ? storeUser.scenicId : nil,
storeUser.storeId > 0 ? storeUser.storeId : nil
)
}
if let scenicUser = resolvedCurrentScenicUser {
return (scenicUser.scenicId > 0 ? scenicUser.scenicId : nil, nil)
}
return (nil, nil)
}
///
var currentAccountDisplayTitle: String? {
if let storeUser = resolvedCurrentStoreUser {
return storeUser.displayName.nonEmpty
}
if let scenicUser = resolvedCurrentScenicUser {
return scenicUser.displayName.nonEmpty
}
return nil
}
/// isCurrent isCurrent 退
private var resolvedCurrentStoreUser: V9StoreUser? {
if let current = storeUsers.first(where: \.isCurrent) {
return current
}
guard !scenicUsers.contains(where: \.isCurrent) else { return nil }
return storeUsers.first
}
/// isCurrent isCurrent 退
private var resolvedCurrentScenicUser: V9ScenicUser? {
if let current = scenicUsers.first(where: \.isCurrent) {
return current
}
guard !storeUsers.contains(where: \.isCurrent) else { return nil }
return scenicUsers.first
}
///
var scenicScopes: [BusinessScope] {
var seen = Set<Int>()
@ -159,10 +221,10 @@ struct V9AuthResponse: Decodable, Equatable {
/// app_role_code
var currentAccountRoleCode: String? {
if let storeUser = storeUsers.first(where: \.isCurrent) ?? storeUsers.first {
if let storeUser = resolvedCurrentStoreUser {
return nonEmpty(storeUser.resolvedAppRoleCode)
}
if let scenicUser = scenicUsers.first(where: \.isCurrent) ?? scenicUsers.first {
if let scenicUser = resolvedCurrentScenicUser {
return nonEmpty(scenicUser.resolvedAppRoleCode)
}
return nil

View File

@ -179,7 +179,7 @@ struct LoginView: View {
do {
try await globalLoading.withLoading(message: "账号切换中...") {
let response = try await viewModel.selectAccount(account, authAPI: authAPI)
await completeLogin(with: response)
await completeLogin(with: response, selectedAccount: account)
}
} catch is CancellationError {
// Keep cancellation silent; the current task was abandoned by SwiftUI.
@ -190,7 +190,7 @@ struct LoginView: View {
}
///
private func completeLogin(with response: V9AuthResponse) async {
private func completeLogin(with response: V9AuthResponse, selectedAccount: AccountSwitchAccount? = nil) async {
do {
try await authSessionCoordinator.completeLogin(
with: response,
@ -200,7 +200,8 @@ struct LoginView: View {
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI
accountContextAPI: accountContextAPI,
selectedAccount: selectedAccount
)
} catch {
toastCenter.show(error.localizedDescription)

View File

@ -80,7 +80,7 @@ allFunctions = 顶层权限
.映射为 HomeMenuItem
```
`androidHomeMenuURIs` 与 Android `Constants.menuList` 登记 URI 一致。已登记且会出现在「全部功能」页的 URI 包括 `location_report``photographer_report`(举报摄影师)、`wallet``message_center` 等。
`androidHomeMenuURIs` 与 Android `Constants.menuList` 登记 URI 一致。已登记且会出现在「全部功能」页的 URI 包括 `location_report``report_photographer`(举报摄影师)、`wallet``message_center` 等。
以下典型 URI **不会**出现在「全部功能」页,即使接口返回了顶层权限:

View File

@ -62,4 +62,4 @@ Home 模块负责登录后的首页工作台,包括当前景区展示、工作
后续迁移具体首页子模块时,应先在 `HomeRoute` 增加目标页面,再更新 `HomeMenuRouter.resolve` 对应 URI 的映射,并同步补充单元测试。
`photographer_report` 已由 `Features/WildPhotographerReport` 接管。
`report_photographer` 已由 `Features/WildPhotographerReport` 接管。

View File

@ -46,7 +46,7 @@ enum HomeMenuRouter {
"pm_manager": "店铺项目管理",
"project_edit": "项目管理",
"location_report": "位置上报",
"photographer_report": "举报摄影师",
"report_photographer": "举报摄影师",
"registration_invitation": "注册邀请",
"store": "店铺管理",
"fly": "飞行管理",
@ -132,7 +132,7 @@ enum HomeMenuRouter {
return .destination(.locationReport)
case "location_report_history":
return .destination(.locationReportHistory)
case "photographer_report":
case "report_photographer":
return .destination(.photographerReport)
case "deposit_order_detail", "deposit_order", "deposit_order_shooting_info":
return .destination(.depositOrders)
@ -219,7 +219,7 @@ enum HomeMenuRouter {
"pm_manager",
"project_edit",
"location_report",
"photographer_report",
"report_photographer",
"registration_invitation",
"photographer_invite",
"invite_record",
@ -298,7 +298,7 @@ enum HomeMenuRouter {
return "注册邀请"
case "location_report":
return "位置上报"
case "photographer_report":
case "report_photographer":
return "举报摄影师"
case "pm", "project_edit":
return "项目管理"

View File

@ -30,7 +30,7 @@ struct HomeCommonMenuStore {
"travel_album",
"pm",
"location_report",
"photographer_report",
"report_photographer",
"registration_invitation",
"store",
"fly",

View File

@ -54,7 +54,7 @@ enum HomeIconCatalog {
"circle.grid.2x2.fill"
case "location_report", "location_report_history":
"mappin.circle.fill"
case "photographer_report":
case "report_photographer":
"shield.fill"
case "registration_invitation", "photographer_invite":
"envelope.open.fill"

View File

@ -125,3 +125,7 @@ DEBUG 构建下,“我的”列表会额外展示“首页调试”入口,
- 无实名认证信息时:点击去实名认证。
- 账号切换失败只展示 Toast不清空登录态。
- 实名认证失败只影响实名认证页面,不影响登录态和账号上下文。
- 「当前账号」展示规则:
- 门店账号(`store_user`)展示门店名称,不回退到景区名称。
- 景区账号(`scenic_user`)展示景区名称。
- 展示标题在登录/切换成功时写入 `AccountContext.currentAccountDisplayTitle`,切换账号时必须同步更新,不能沿用上一次账号名称。

View File

@ -212,7 +212,8 @@ struct AccountSwitchView: View {
accountContext: accountContext,
permissionContext: permissionContext,
profileAPI: profileAPI,
accountContextAPI: accountContextAPI
accountContextAPI: accountContextAPI,
selectedAccount: account
)
}
appRouter.reset()
@ -226,14 +227,7 @@ struct AccountSwitchView: View {
}
private var currentAccountId: String? {
guard let current = accountContext.profile else { return nil }
if let store = accountContext.currentStore {
return "\(V9StoreUser.accountTypeValue)_\(store.id)"
}
if let scenic = accountContext.currentScenic {
return "\(V9ScenicUser.accountTypeValue)_\(scenic.id)"
}
return current.userId.isEmpty ? nil : current.userId
accountContext.currentAccountIdentifier
}
///

View File

@ -494,20 +494,51 @@ struct ProfileView: View {
}
private var currentAccountDisplayName: String {
nonEmpty(accountContext.currentAccountDisplayTitle)
?? (isStoreAccountContext ? resolvedStoreAccountName : resolvedScenicAccountName)
}
/// 退
private var resolvedStoreAccountName: String {
nonEmpty(accountContext.currentStore?.name)
?? nonEmpty(accountContext.currentScenic?.name)
?? accountContext.profile?.displayName
?? nonEmpty(matchedStoreScope?.name)
?? nonEmpty(accountContext.profile?.displayName)
?? viewModel.displayNickname
}
///
private var resolvedScenicAccountName: String {
nonEmpty(accountContext.currentScenic?.name)
?? nonEmpty(accountContext.profile?.displayName)
?? viewModel.displayNickname
}
///
private var isStoreAccountContext: Bool {
accountContext.accountType == V9StoreUser.accountTypeValue
}
/// currentStore
private var matchedStoreScope: BusinessScope? {
if let currentStore = accountContext.currentStore {
return currentStore
}
guard accountContext.accountType == V9StoreUser.accountTypeValue else { return nil }
if accountContext.storeScopes.count == 1 {
return accountContext.storeScopes.first
}
return nil
}
private var currentAccountTypeText: String {
if accountContext.currentStore != nil {
switch accountContext.accountType {
case V9StoreUser.accountTypeValue:
return "门店账号"
}
if accountContext.currentScenic != nil {
case V9ScenicUser.accountTypeValue:
return "景区账号"
default:
return nonEmpty(viewModel.userInfo?.roleName) ?? "账号"
}
return nonEmpty(viewModel.userInfo?.roleName) ?? "账号"
}
private var nicknameBinding: Binding<String> {

View File

@ -576,6 +576,7 @@ struct ProfileSpaceView: View {
profile: profile,
accountType: accountContext.accountType ?? existing?.accountType,
businessUserId: existing?.businessUserId,
currentAccountDisplayTitle: accountContext.currentAccountDisplayTitle ?? existing?.currentAccountDisplayTitle,
currentRoleCode: existing?.currentRoleCode,
scenicScopes: accountContext.scenicScopes,
storeScopes: accountContext.storeScopes,

View File

@ -2,7 +2,7 @@
## 模块职责
WildPhotographerReport 模块负责首页 `photographer_report` 入口,为摄影师和景区运营提供「举报摄影师」能力。
WildPhotographerReport 模块负责首页 `report_photographer` 入口,为摄影师和景区运营提供「举报摄影师」能力。
本模块与 `LocationReport`(位置上报)无关:前者是用户举报疑似打野/未戴工牌摄影师,后者是摄影师 GPS 打卡。
@ -49,7 +49,7 @@ flowchart TD
## 路由
- 权限 URI`photographer_report`
- 权限 URI`report_photographer`
- 首页路由:`HomeRoute.photographerReport`
- 入口不固定置顶,依赖权限下发或用户手动添加到常用应用(`HomeCommonMenuStore.androidHomeMenuURIs` 白名单已包含该 URI
@ -57,10 +57,10 @@ flowchart TD
| 模块 | URI | 用途 | 后端 |
| --- | --- | --- | --- |
| WildPhotographerReport | `photographer_report` | 举报摄影师 | Mock |
| WildPhotographerReport | `report_photographer` | 举报摄影师 | Mock |
| LocationReport | `location_report` | 位置上报 | 真实 API |
## 测试要求
- `WildPhotographerReportTests`Home/Submit/List ViewModel 校验与提交成功路径。
- `HomeMenuRouterTests``photographer_report` 路由映射。
- `HomeMenuRouterTests``report_photographer` 路由映射。