修复账号切换后「我的」页当前账号展示错误,并统一举报摄影师首页 URI。
切换账号时以用户选中账号为准写入展示标题与业务作用域,避免沿用旧门店名称;同时将首页举报摄影师入口 URI 对齐为 report_photographer。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 **不会**出现在「全部功能」页,即使接口返回了顶层权限:
|
||||
|
||||
|
||||
@ -62,4 +62,4 @@ Home 模块负责登录后的首页工作台,包括当前景区展示、工作
|
||||
|
||||
后续迁移具体首页子模块时,应先在 `HomeRoute` 增加目标页面,再更新 `HomeMenuRouter.resolve` 对应 URI 的映射,并同步补充单元测试。
|
||||
|
||||
`photographer_report` 已由 `Features/WildPhotographerReport` 接管。
|
||||
`report_photographer` 已由 `Features/WildPhotographerReport` 接管。
|
||||
|
||||
@ -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 "项目管理"
|
||||
|
||||
@ -30,7 +30,7 @@ struct HomeCommonMenuStore {
|
||||
"travel_album",
|
||||
"pm",
|
||||
"location_report",
|
||||
"photographer_report",
|
||||
"report_photographer",
|
||||
"registration_invitation",
|
||||
"store",
|
||||
"fly",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -125,3 +125,7 @@ DEBUG 构建下,“我的”列表会额外展示“首页调试”入口,
|
||||
- 无实名认证信息时:点击去实名认证。
|
||||
- 账号切换失败只展示 Toast,不清空登录态。
|
||||
- 实名认证失败只影响实名认证页面,不影响登录态和账号上下文。
|
||||
- 「当前账号」展示规则:
|
||||
- 门店账号(`store_user`)展示门店名称,不回退到景区名称。
|
||||
- 景区账号(`scenic_user`)展示景区名称。
|
||||
- 展示标题在登录/切换成功时写入 `AccountContext.currentAccountDisplayTitle`,切换账号时必须同步更新,不能沿用上一次账号名称。
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
/// 判断账号是否为当前已选账号。
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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` 路由映射。
|
||||
|
||||
Reference in New Issue
Block a user