fix: 资产信息字段显示隐藏根据角色
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m24s

This commit is contained in:
luo
2026-07-13 16:40:35 +08:00
parent 4c0207c6e6
commit 2c8524485b
11 changed files with 382 additions and 34 deletions

View File

@@ -0,0 +1,44 @@
# Change: 更新资产信息账号类型字段可见性与运营商账户展示
## Why
资产信息页当前在卡/设备基础信息区域存在两类与最新业务口径不一致的问题:
- 卡资产“卡信息”中的 `实名认证策略` 仍会对 `user_type=3`(代理账号)和 `user_type=4`(企业账号)显示。
- 设备资产“设备信息”中的 `实名认证策略``制造商` 仍会对 `user_type=3/4` 显示。
- 卡资产“运营商账户”当前展示的是 `carrier_name`,而最新要求需要改为展示 `carrier_type`,并按运营商管理中的映射规则转成中文,例如 `CTCC -> 中国电信`
如果不统一这些展示规则,不同账号在资产信息页会继续看到不应暴露的字段,同时“运营商账户”的显示口径也会与系统内其他模块不一致。
## What Changes
- 更新卡资产“卡信息”区域:
- 当当前登录账号 `user_type``3``4` 时,隐藏 `实名认证策略` 字段。
- 当当前登录账号 `user_type``3``4` 时,`运营商账户` 字段改为展示 `carrier_type` 的中文映射值,而不是 `carrier_name`
- 当当前登录账号 `user_type` 不是 `3` 且不是 `4` 时,`运营商账户` 字段继续保持当前 `carrier_name` 展示口径。
- 已知运营商类型映射沿用运营商管理口径:`CMCC -> 中国移动``CUCC -> 中国联通``CTCC -> 中国电信``CBN -> 中国广电`
-`carrier_type` 为空时显示 `-`;若返回未知编码,则保留原编码展示,避免吞掉上游信息。
- 更新设备资产“设备信息”区域:
- 当当前登录账号 `user_type``3``4` 时,隐藏 `实名认证策略` 字段。
- 当当前登录账号 `user_type``3``4` 时,隐藏 `制造商` 字段。
- 范围约束:
- 本提案仅调整资产信息基础描述区字段展示。
- 本提案不改动实名认证策略相关操作按钮的权限和可见性。
- 本提案不改动设备详情“绑定卡列表”中的列展示规则,除非后续另行提出变更。
## Impact
- Affected specs:
- `asset-information`
- Affected code:
- `src/views/asset-management/asset-information/components/BasicInfoCard.vue`
- `src/views/asset-management/asset-information/composables/useAssetFormatters.ts`
- `src/views/asset-management/asset-information/types.ts`
- `src/views/asset-management/asset-information/composables/useAssetInfo.ts`
- `src/config/constants/carrierTypes.ts`
- Dependencies:
- 依赖当前登录账号 `user_type` 可从用户状态中稳定读取
- 依赖资产详情接口继续返回 `carrier_type`
- 运营商类型中文映射应与运营商管理模块保持一致
- Breaking changes:
- 无外部接口破坏性变更;本次仅调整后台页面展示契约和字段可见性

View File

@@ -0,0 +1,123 @@
## ADDED Requirements
### Requirement: Admin Card Asset Carrier Type Contract
The admin asset information integration SHALL accept and preserve `carrier_type` from `GET /api/admin/assets/resolve/:identifier` for card assets so the card information section can render the carrier account value from the carrier type code.
#### Scenario: Card asset detail preserves carrier_type from resolve response
- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** `GET /api/admin/assets/resolve/:identifier` 返回了 `carrier_type`
- **WHEN** 前端解析卡资产详情响应
- **THEN** 页面状态 MUST 保留该 `carrier_type` 原始值
- **AND** 后续“卡信息”区域的 `运营商账户` 展示 MUST 基于该状态值进行格式化
#### Scenario: Missing carrier_type does not break card asset detail parsing
- **GIVEN** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** `GET /api/admin/assets/resolve/:identifier` 未返回 `carrier_type` 或该字段为空
- **WHEN** 前端解析卡资产详情响应
- **THEN** 页面 MUST 继续保留其他卡资产字段的解析行为
- **AND** MUST NOT 因 `carrier_type` 缺失导致“卡信息”区域渲染失败
### Requirement: Admin Card Asset Carrier Account Display
The admin asset information view SHALL render the card information field `运营商账户` according to the current logged-in account type.
For logged-in accounts whose `user_type` is `3` (代理账号) or `4` (企业账号), the field MUST use `carrier_type` and the same carrier-type-to-Chinese mapping used by carrier management.
Known mappings MUST include `CMCC -> 中国移动`, `CUCC -> 中国联通`, `CTCC -> 中国电信`, and `CBN -> 中国广电`.
For other logged-in account types, the field MUST keep the existing `carrier_name` display behavior.
If `carrier_type` is empty or missing, the page MUST display `-`. If `carrier_type` contains an unknown code, the page MUST preserve and display the original code.
#### Scenario: Show China Telecom label for CTCC in card information for agent or enterprise account
- **GIVEN** 当前登录账号的 `user_type``3``4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** 当前卡资产详情状态中的 `carrier_type``CTCC`
- **WHEN** 页面渲染“卡信息”区域中的 `运营商账户`
- **THEN** 页面 MUST 显示 `中国电信`
#### Scenario: Show placeholder when carrier_type is empty for agent or enterprise account
- **GIVEN** 当前登录账号的 `user_type``3``4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** 当前卡资产详情状态中的 `carrier_type` 为空、缺失或仅包含空白字符
- **WHEN** 页面渲染“卡信息”区域中的 `运营商账户`
- **THEN** 页面 MUST 显示 `-`
#### Scenario: Preserve unknown carrier_type code for agent or enterprise account
- **GIVEN** 当前登录账号的 `user_type``3``4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** 当前卡资产详情状态中的 `carrier_type` 为未收录的编码
- **WHEN** 页面渲染“卡信息”区域中的 `运营商账户`
- **THEN** 页面 MUST 直接显示该原始编码
- **AND** MUST NOT 错误映射为其他运营商中文名称
#### Scenario: Keep carrier_name display for non-agent non-enterprise account
- **GIVEN** 当前登录账号的 `user_type` 不是 `3` 也不是 `4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **AND** 当前卡资产详情状态中的 `carrier_name` 为有效值
- **WHEN** 页面渲染“卡信息”区域中的 `运营商账户`
- **THEN** 页面 MUST 显示该 `carrier_name`
### Requirement: Admin Card Realname Policy Visibility By Account Type
The admin asset information view SHALL hide the card information field `实名认证策略` for logged-in accounts whose `user_type` is `3` (代理账号) or `4` (企业账号).
For other logged-in account types, the page SHALL keep the existing card information field visibility behavior for `实名认证策略`.
#### Scenario: Hide card realname policy for agent account
- **GIVEN** 当前登录账号的 `user_type``3`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **WHEN** 页面渲染“卡信息”区域
- **THEN** 页面 MUST NOT 显示 `实名认证策略` 字段
#### Scenario: Hide card realname policy for enterprise account
- **GIVEN** 当前登录账号的 `user_type``4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **WHEN** 页面渲染“卡信息”区域
- **THEN** 页面 MUST NOT 显示 `实名认证策略` 字段
#### Scenario: Keep card realname policy visible for non-agent non-enterprise account
- **GIVEN** 当前登录账号的 `user_type` 不是 `3` 也不是 `4`
- **AND** 用户正在查看后台“资产信息”中的卡资产详情
- **WHEN** 页面渲染“卡信息”区域
- **THEN** 页面 MUST 继续按现有规则渲染 `实名认证策略` 字段
### Requirement: Admin Device Sensitive Field Visibility By Account Type
The admin asset information view SHALL hide the device information fields `实名认证策略` and `制造商` for logged-in accounts whose `user_type` is `3` (代理账号) or `4` (企业账号).
For other logged-in account types, the page SHALL keep the existing device information field visibility behavior for both fields.
#### Scenario: Hide device realname policy and manufacturer for agent account
- **GIVEN** 当前登录账号的 `user_type``3`
- **AND** 用户正在查看后台“资产信息”中的设备资产详情
- **WHEN** 页面渲染“设备信息”区域
- **THEN** 页面 MUST NOT 显示 `实名认证策略` 字段
- **AND** 页面 MUST NOT 显示 `制造商` 字段
#### Scenario: Hide device realname policy and manufacturer for enterprise account
- **GIVEN** 当前登录账号的 `user_type``4`
- **AND** 用户正在查看后台“资产信息”中的设备资产详情
- **WHEN** 页面渲染“设备信息”区域
- **THEN** 页面 MUST NOT 显示 `实名认证策略` 字段
- **AND** 页面 MUST NOT 显示 `制造商` 字段
#### Scenario: Keep device realname policy and manufacturer visible for non-agent non-enterprise account
- **GIVEN** 当前登录账号的 `user_type` 不是 `3` 也不是 `4`
- **AND** 用户正在查看后台“资产信息”中的设备资产详情
- **WHEN** 页面渲染“设备信息”区域
- **THEN** 页面 MUST 继续按现有规则渲染 `实名认证策略` 字段
- **AND** 页面 MUST 继续按现有规则渲染 `制造商` 字段

View File

@@ -0,0 +1,20 @@
## 1. Contract And Formatting
- [x] 1.1 补齐资产信息页面本地类型/状态中的 `carrier_type` 字段,确保卡资产详情可稳定保留该值。
- [x] 1.2 复用运营商管理中的运营商类型映射规则,为资产信息页提供 `carrier_type` 到中文文案的格式化能力。
## 2. UI Visibility
- [x] 2.1 调整卡资产“卡信息”区域的 `运营商账户` 字段:`user_type=3/4` 展示 `carrier_type` 中文映射,其他账号继续展示原来的 `carrier_name`
- [x] 2.2 当当前登录账号 `user_type``3``4` 时,隐藏卡资产“卡信息”区域的 `实名认证策略` 字段。
- [x] 2.3 当当前登录账号 `user_type``3``4` 时,隐藏设备资产“设备信息”区域的 `实名认证策略``制造商` 字段。
- [x] 2.4 确认本次变更仅影响基础信息描述区,不改动实名认证策略操作按钮和设备绑定卡列表列。
## 3. Verification
- [ ] 3.1 验证 `user_type=3/4` 查看卡资产且返回 `carrier_type=CTCC` 时,“运营商账户”显示为 `中国电信`
- [ ] 3.2 验证 `user_type=3/4` 查看卡资产且返回 `carrier_type=CMCC/CUCC/CBN` 时,“运营商账户”分别显示与运营商管理一致的中文文案;空值显示 `-`
- [ ] 3.3 验证 `user_type=3/4` 查看卡资产且返回未知 `carrier_type` 编码时,“运营商账户”保留原编码展示。
- [ ] 3.4 验证 `user_type=3``user_type=4` 查看卡资产时,不显示 `实名认证策略` 字段。
- [ ] 3.5 验证 `user_type=3``user_type=4` 查看设备资产时,不显示 `实名认证策略``制造商` 字段。
- [ ] 3.6 验证其他账号类型查看卡资产时,“运营商账户”继续显示原来的 `carrier_name`;其余上述字段继续按现有规则显示。

View File

@@ -0,0 +1,39 @@
# Change: 允许企业账号登录后台管理系统
## Why
当前前端登录流程会在登录接口成功返回后,基于 `user_type=4` 对企业账号做前端拦截,并提示“企业账号无法登录后台管理系统,请使用平台/代理账号登录”。这与最新业务要求不一致,导致企业账号即使后端已返回有效 token、菜单和权限也无法进入后台。
如果只保留现状,企业账号无法访问后台中已经按权限和菜单控制开放给它们的页面,也会与近期多项针对企业账号后台可见性和资产能力的变更方向相冲突。
## What Changes
- 移除前端登录流程中对 `user_type=4` 企业账号的后台登录拦截。
- 企业账号登录成功后,前端应与其他账号类型一致地:
- 保存 token
- 保存用户信息
- 保存权限、菜单和按钮数据
- 在存在可用后台菜单和权限时,继续进入后台默认首页/重定向目标页
- 当登录接口成功但 `menus` 为空或 `permissions` 为空时:
- 前端仅提示“当前账号暂无任何权限,请联系管理员分配角色和权限”
- 不继续进入后台首页或动态路由注册流程
- 不作为这条提示的后续动作调用退出登录接口
- 登录成功后的页面访问边界继续由现有菜单、路由权限、按钮权限和后端接口权限共同控制。
- 本提案不要求本次同步放开所有企业账号受限接口或页面;仅调整“企业账号能否进入后台”的前端准入逻辑。
- 本提案不修改登录接口路径、请求参数或返回结构。
## Impact
- Affected specs:
- `admin-authentication`
- Affected code:
- `src/composables/useLogin.ts`
- `src/router/guards/beforeEach.ts`
- `src/router/guards/permission.ts`
- `src/App.vue`
- 其他依赖当前登录态、菜单和权限进行页面准入的后台入口逻辑
- Dependencies:
- 后端登录接口继续允许企业账号返回有效 `access_token``user``permissions``menus``buttons`
- 企业账号需要至少具备一部分后台菜单或权限,才能进入可访问页面
- Breaking changes:
- 无外部接口破坏性变更;本次为前端登录准入行为放宽

View File

@@ -0,0 +1,70 @@
## ADDED Requirements
### Requirement: Enterprise Account Admin Login Admission
The admin login flow SHALL allow enterprise accounts whose `user_type` is `4` to sign in to the admin system when the login API returns a successful response with valid authentication data.
The frontend MUST NOT reject admin login solely because `user_type` equals `4`.
#### Scenario: Enterprise account is allowed to complete admin login
- **GIVEN** 用户使用企业账号凭证登录后台管理系统
- **AND** 登录接口返回成功状态
- **AND** 返回数据包含有效 `access_token`
- **AND** 返回数据中的 `user.user_type``4`
- **WHEN** 前端处理登录成功响应
- **THEN** 前端 MUST 继续执行后台登录成功流程
- **AND** MUST NOT 弹出“企业账号无法登录后台管理系统,请使用平台/代理账号登录”提示
#### Scenario: Enterprise account login still fails when login API itself fails
- **GIVEN** 用户使用企业账号凭证登录后台管理系统
- **AND** 登录接口返回失败状态,或未返回有效 `access_token`
- **WHEN** 前端处理登录响应
- **THEN** 前端 MUST 继续按现有登录失败流程处理
- **AND** MUST NOT 将“允许企业账号登录后台”解释为绕过登录接口失败
### Requirement: Enterprise Account Login State Persistence
The admin login flow SHALL persist enterprise-account login state using the same token, user, permissions, menus, and buttons handling used for other successful admin logins only when the login response provides usable admin-access data.
#### Scenario: Enterprise account saves auth state after successful login with usable admin access data
- **GIVEN** 用户使用企业账号成功登录后台管理系统
- **AND** 登录返回中包含 `access_token``refresh_token``user``permissions``menus``buttons`
- **AND** `permissions` 非空
- **AND** `menus` 非空
- **WHEN** 前端写入登录态
- **THEN** 前端 MUST 保存 token
- **AND** MUST 保存用户信息
- **AND** MUST 保存权限、菜单和按钮数据
#### Scenario: Enterprise account shows no-permission warning without logout follow-up
- **GIVEN** 用户使用企业账号成功登录后台管理系统
- **AND** 登录返回中的 `permissions` 为空,或 `menus` 为空
- **WHEN** 前端处理登录成功响应
- **THEN** 前端 MUST 继续显示现有“当前账号暂无任何权限,请联系管理员分配角色和权限”提示
- **AND** MUST NOT 因为空权限或空菜单回退为企业账号登录拦截
- **AND** MUST NOT 继续进入后台首页或动态路由注册流程
- **AND** MUST NOT 因这条无权限提示而调用退出登录接口
### Requirement: Enterprise Account Admin Entry Uses Existing Authorization Boundaries
Allowing enterprise accounts to sign in to the admin system SHALL NOT by itself grant additional page or API access beyond the existing menu, route-permission, button-permission, and backend authorization boundaries.
#### Scenario: Enterprise account enters admin and follows existing route registration flow
- **GIVEN** 用户使用企业账号成功登录后台管理系统
- **AND** 登录返回中的 `permissions` 非空
- **AND** 登录返回中的 `menus` 非空
- **WHEN** 前端进入登录后的路由注册和首页跳转流程
- **THEN** 前端 MUST 继续按现有菜单和动态路由注册逻辑处理
- **AND** MUST NOT 因 `user_type=4` 在该阶段额外拒绝用户进入后台
#### Scenario: Enterprise account page access remains permission-controlled
- **GIVEN** 用户已使用企业账号登录后台管理系统
- **WHEN** 用户尝试访问某个后台页面或触发某个后台操作
- **THEN** 页面访问和操作可用性 MUST 继续由现有菜单、路由权限、按钮权限和后端接口鉴权决定
- **AND** 允许登录后台本身 MUST NOT 被解释为默认拥有全部后台页面和操作权限

View File

@@ -0,0 +1,17 @@
## 1. Login Flow
- [x] 1.1 移除登录成功后对 `user_type=4` 企业账号的前端拒绝逻辑和错误提示。
- [x] 1.2 确保企业账号在登录成功后与其他账号一样完成 token、用户信息、权限、菜单、按钮的保存流程。
- [x] 1.3 当登录成功但 `menus``permissions` 为空时,仅提示“当前账号暂无任何权限,请联系管理员分配角色和权限”并停止后续登录流程,不调用退出登录接口。
## 2. Navigation And Access
- [x] 2.1 确保企业账号在具备可用后台菜单和权限时,继续走现有后台首页跳转和路由注册流程,不因账号类型被额外拦截。
- [x] 2.2 确认企业账号后台访问范围仍由现有菜单、路由权限和后端接口权限控制,而不是在登录阶段一刀切拒绝。
## 3. Verification
- [ ] 3.1 验证企业账号登录成功后不再出现“企业账号无法登录后台管理系统,请使用平台/代理账号登录”提示。
- [ ] 3.2 验证企业账号在具备可用后台菜单和权限时,能正常保存登录态并进入后台首页或重定向目标页。
- [ ] 3.3 验证企业账号在菜单为空或权限为空时,仅提示“暂无任何权限”,且不会继续调用退出登录接口。
- [ ] 3.4 验证平台账号和代理账号登录流程不受影响。

View File

@@ -3,18 +3,17 @@
*/ */
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ElNotification, ElMessage } from 'element-plus' import { ElNotification, ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { HOME_PAGE, RoutesAlias } from '@/router/routesAlias' import { RoutesAlias } from '@/router/routesAlias'
import AppConfig from '@/config' import AppConfig from '@/config'
import { AuthService } from '@/api/modules' import { AuthService } from '@/api/modules'
import { ApiStatus } from '@/utils/http/status' import { ApiStatus } from '@/utils/http/status'
import { saveCredentials, getRememberedCredentials } from '@/utils/auth/rememberPassword' import { saveCredentials, getRememberedCredentials } from '@/utils/auth/rememberPassword'
import { usernameRules, passwordRules } from '@/utils/auth/loginValidation' import { usernameRules, passwordRules } from '@/utils/auth/loginValidation'
import { getRedirectPath } from '@/router/guards/permission'
/** /**
* 登录表单数据 * 登录表单数据
@@ -28,7 +27,6 @@ interface LoginForm {
export function useLogin() { export function useLogin() {
const { t } = useI18n() const { t } = useI18n()
const router = useRouter() const router = useRouter()
const route = useRoute()
const userStore = useUserStore() const userStore = useUserStore()
// 表单引用 // 表单引用
@@ -108,9 +106,24 @@ export function useLogin() {
return return
} }
// 检查用户类型 - 禁止企业账号登录 if (!response.data.user) {
if (response.data.user && response.data.user.user_type === 4) { ElMessage.error(t('login.error.getUserInfoFailed'))
ElMessage.error('企业账号无法登录后台管理系统,请使用平台/代理账号登录') return
}
// 登录成功但没有后台菜单或权限时,仅提示并停止后续流程。
// 这里不保存登录态,也不触发后续跳转,避免进入路由守卫后再连续调用退出接口。
const hasPermissions =
Array.isArray(response.data.permissions) && response.data.permissions.length > 0
const hasMenus = Array.isArray(response.data.menus) && response.data.menus.length > 0
if (!hasPermissions || !hasMenus) {
ElMessage.warning({
message: '当前账号暂无任何权限,请联系管理员分配角色和权限',
duration: 4000,
showClose: true
})
saveCredentials(formData.username, formData.password, formData.rememberPassword)
return return
} }
@@ -118,13 +131,8 @@ export function useLogin() {
userStore.setToken(response.data.access_token, response.data.refresh_token) userStore.setToken(response.data.access_token, response.data.refresh_token)
// 保存用户信息 // 保存用户信息
if (response.data.user) { userStore.setUserInfo(response.data.user)
userStore.setUserInfo(response.data.user) userStore.setLoginStatus(true)
userStore.setLoginStatus(true)
} else {
ElMessage.error(t('login.error.getUserInfoFailed'))
return
}
// 保存权限、菜单和按钮 // 保存权限、菜单和按钮
if (response.data.permissions) { if (response.data.permissions) {
@@ -137,19 +145,6 @@ export function useLogin() {
userStore.setButtons(response.data.buttons) userStore.setButtons(response.data.buttons)
} }
// 检查权限和菜单是否为空
const hasPermissions = response.data.permissions && response.data.permissions.length > 0
const hasMenus = response.data.menus && response.data.menus.length > 0
if (!hasPermissions || !hasMenus) {
ElMessage.warning({
message: '当前账号暂无任何权限,请联系管理员分配角色和权限',
duration: 4000,
showClose: true
})
// 虽然没有权限,但仍然允许登录,让用户看到空白页面而不是停留在登录页
}
// 保存记住密码 // 保存记住密码
saveCredentials(formData.username, formData.password, formData.rememberPassword) saveCredentials(formData.username, formData.password, formData.rememberPassword)

View File

@@ -9,7 +9,7 @@ const axiosInstance = axios.create({
baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理其他模式使用完整URL baseURL: import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL, // 开发服务器使用代理其他模式使用完整URL
withCredentials: false, // 异步请求携带cookie withCredentials: false, // 异步请求携带cookie
transformRequest: [ transformRequest: [
(data, headers) => { (data) => {
// 如果是 FormData,不进行转换 // 如果是 FormData,不进行转换
if (data instanceof FormData) { if (data instanceof FormData) {
return data return data
@@ -72,6 +72,7 @@ let failedQueue: Array<{
resolve: (value?: any) => void resolve: (value?: any) => void
reject: (reason?: any) => void reject: (reason?: any) => void
}> = [] // 失败队列,存储因 token 过期而失败的请求 }> = [] // 失败队列,存储因 token 过期而失败的请求
let isHandlingAuthExpiry = false
// 处理失败队列 // 处理失败队列
const processQueue = (error: any, token: string | null = null) => { const processQueue = (error: any, token: string | null = null) => {
@@ -222,6 +223,11 @@ function handleErrorMessage(
const backendMessage = error.response?.data?.msg const backendMessage = error.response?.data?.msg
const httpStatus = error.response?.status const httpStatus = error.response?.status
// 401 过期类错误由 clearLocalStateAndRedirect 统一提示,避免重复弹出后端报错。
if (httpStatus === 401 || error.response?.data?.code === ApiStatus.unauthorized) {
return
}
// 如果是404错误且未配置显示404错误则不显示任何提示 // 如果是404错误且未配置显示404错误则不显示任何提示
if (httpStatus === 404 && !show404Error) { if (httpStatus === 404 && !show404Error) {
return return
@@ -306,9 +312,13 @@ const api = {
// 仅清理本地状态(不调用接口)- 用于401等情况 // 仅清理本地状态(不调用接口)- 用于401等情况
const clearLocalStateAndRedirect = () => { const clearLocalStateAndRedirect = () => {
if (isHandlingAuthExpiry) return
isHandlingAuthExpiry = true
ElMessage.error('登录已过期,请重新登录') ElMessage.error('登录已过期,请重新登录')
setTimeout(() => { setTimeout(() => {
useUserStore().clearLocalState() useUserStore().clearLocalState()
isHandlingAuthExpiry = false
}, 1000) }, 1000)
} }

View File

@@ -32,15 +32,13 @@
<ElDescriptionsItem label="IMEI">{{ <ElDescriptionsItem label="IMEI">{{
cardInfo?.gateway_card_imei || '-' cardInfo?.gateway_card_imei || '-'
}}</ElDescriptionsItem> }}</ElDescriptionsItem>
<ElDescriptionsItem label="运营商账户">{{ <ElDescriptionsItem label="运营商账户">{{ carrierAccountDisplay }}</ElDescriptionsItem>
cardInfo?.carrier_name || '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="实名状态"> <ElDescriptionsItem label="实名状态">
<ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small"> <ElTag :type="getRealNameStatusType(cardInfo?.real_name_status)" size="small">
{{ getRealNameStatusName(cardInfo?.real_name_status) }} {{ getRealNameStatusName(cardInfo?.real_name_status) }}
</ElTag> </ElTag>
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="实名认证策略"> <ElDescriptionsItem v-if="canShowSensitiveFields" label="实名认证策略">
{{ getRealnamePolicyName(cardInfo?.realname_policy) }} {{ getRealnamePolicyName(cardInfo?.realname_policy) }}
</ElDescriptionsItem> </ElDescriptionsItem>
@@ -157,11 +155,13 @@
<ElDescriptionsItem label="最大卡槽数">{{ <ElDescriptionsItem label="最大卡槽数">{{
cardInfo?.max_sim_slots || '-' cardInfo?.max_sim_slots || '-'
}}</ElDescriptionsItem> }}</ElDescriptionsItem>
<ElDescriptionsItem label="实名认证策略"> <ElDescriptionsItem v-if="canShowSensitiveFields" label="实名认证策略">
{{ getRealnamePolicyName(cardInfo?.realname_policy) }} {{ getRealnamePolicyName(cardInfo?.realname_policy) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="设备类型">{{ cardInfo?.device_type || '-' }}</ElDescriptionsItem> <ElDescriptionsItem label="设备类型">{{ cardInfo?.device_type || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="制造商">{{ cardInfo?.manufacturer || '-' }}</ElDescriptionsItem> <ElDescriptionsItem v-if="canShowSensitiveFields" label="制造商">{{
cardInfo?.manufacturer || '-'
}}</ElDescriptionsItem>
<ElDescriptionsItem label="最后在线时间">{{ <ElDescriptionsItem label="最后在线时间">{{
formatDateTime(cardInfo?.last_online_time) || '-' formatDateTime(cardInfo?.last_online_time) || '-'
}}</ElDescriptionsItem> }}</ElDescriptionsItem>
@@ -524,6 +524,7 @@
iccid?: string iccid?: string
imei?: string imei?: string
msisdn?: string msisdn?: string
carrier_type?: string
carrier_name?: string carrier_name?: string
real_name_status?: number real_name_status?: number
activation_status_name?: string activation_status_name?: string
@@ -643,6 +644,22 @@
return ![3, 4].includes(userType) && hasAuth(CARD_MONTH_USAGE_PERMISSION) return ![3, 4].includes(userType) && hasAuth(CARD_MONTH_USAGE_PERMISSION)
}) })
const isAgentOrEnterpriseAccount = computed(() => {
const userType = Number(userStore.info.user_type ?? 0)
return [3, 4].includes(userType)
})
const canShowSensitiveFields = computed(() => {
return !isAgentOrEnterpriseAccount.value
})
const carrierAccountDisplay = computed(() => {
if (isAgentOrEnterpriseAccount.value) {
return getCarrierTypeName(props.cardInfo?.carrier_type)
}
return props.cardInfo?.carrier_name || '-'
})
const descriptionsColumn = computed(() => { const descriptionsColumn = computed(() => {
if (width.value <= 640) return 1 if (width.value <= 640) return 1
if (width.value <= 1024) return 2 if (width.value <= 1024) return 2
@@ -679,6 +696,7 @@
getRealNameStatusName, getRealNameStatusName,
getRealNameStatusType, getRealNameStatusType,
getRealnamePolicyName, getRealnamePolicyName,
getCarrierTypeName,
getAssetStatusName, getAssetStatusName,
getAssetStatusType, getAssetStatusType,
getOnlineStatusName, getOnlineStatusName,

View File

@@ -2,6 +2,9 @@
* 资产信息格式化工具函数 * 资产信息格式化工具函数
*/ */
import { getCarrierTypeLabel } from '@/config/constants/carrierTypes'
import type { CarrierType } from '@/types/api'
type TagType = 'primary' | 'success' | 'warning' | 'info' | 'danger' type TagType = 'primary' | 'success' | 'warning' | 'info' | 'danger'
export function useAssetFormatters() { export function useAssetFormatters() {
@@ -76,6 +79,13 @@ export function useAssetFormatters() {
return policyMap[policy] || '未知' return policyMap[policy] || '未知'
} }
// 获取运营商类型名称
const getCarrierTypeName = (carrierType?: string | null) => {
const normalizedCarrierType = carrierType?.trim()
if (!normalizedCarrierType) return '-'
return getCarrierTypeLabel(normalizedCarrierType as CarrierType)
}
// 获取资产状态名称 // 获取资产状态名称
const getAssetStatusName = (status?: number) => { const getAssetStatusName = (status?: number) => {
if (status === undefined || status === null) return '未知' if (status === undefined || status === null) return '未知'
@@ -252,6 +262,7 @@ export function useAssetFormatters() {
getRealNameStatusName, getRealNameStatusName,
getRealNameStatusType, getRealNameStatusType,
getRealnamePolicyName, getRealnamePolicyName,
getCarrierTypeName,
getAssetStatusName, getAssetStatusName,
getAssetStatusType, getAssetStatusType,
getOnlineStatusName, getOnlineStatusName,

View File

@@ -14,6 +14,7 @@ export interface AssetInfo {
iccid?: string iccid?: string
imei?: string imei?: string
msisdn?: string msisdn?: string
carrier_type?: string
carrier_name?: string carrier_name?: string
real_name_status?: number real_name_status?: number
activation_status_name?: string activation_status_name?: string