修复账号切换后「我的」页当前账号展示错误,并统一举报摄影师首页 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

@ -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` 路由映射。