diff --git a/openspec/changes/update-asset-information-responsive-layout/proposal.md b/openspec/changes/update-asset-information-responsive-layout/proposal.md new file mode 100644 index 0000000..4a656e5 --- /dev/null +++ b/openspec/changes/update-asset-information-responsive-layout/proposal.md @@ -0,0 +1,28 @@ +# Change: 资产信息页全页面响应式适配 + +## Why + +资产信息页从资产查询、基础信息、当前生效套餐、套餐列表、钱包流水到操作审计日志包含大量描述项、操作按钮、筛选器和表格。当前小屏幕下存在固定宽度输入框、横向统计项和卡片头部内容挤压的问题,导致页面显示混乱。 + +## What Changes + +- 对资产信息页从搜索框到最底部操作审计日志进行响应式适配。 +- 小屏幕下搜索输入、查询、同步、返回上次搜索等操作应换行或纵向排列,避免横向溢出。 +- 基础信息、设备实时信息和当前生效套餐描述项应根据屏幕宽度减少列数。 +- 当前生效套餐的真/虚流量统计应在小屏下纵向堆叠,并保持进度条和百分比可读。 +- 套餐列表、钱包流水、操作审计日志等表格区域应在小屏下使用容器横向滚动承载表格,而不是撑破页面。 +- 钱包和操作审计日志的筛选器、分页器、卡片头部操作应在窄屏下换行并占满可用宽度。 +- 不改变现有接口、权限、业务数据和操作行为。 + +## Impact + +- Affected specs: + - `asset-information-responsive-layout` +- Affected code: + - `src/views/asset-management/asset-information/index.vue` + - `src/views/asset-management/asset-information/components/AssetSearchCard.vue` + - `src/views/asset-management/asset-information/components/BasicInfoCard.vue` + - `src/views/asset-management/asset-information/components/CurrentPackageCard.vue` + - `src/views/asset-management/asset-information/components/PackageListCard.vue` + - `src/views/asset-management/asset-information/components/WalletTransactionCard.vue` + - `src/views/asset-management/asset-information/components/OperationLogsCard.vue` diff --git a/openspec/changes/update-asset-information-responsive-layout/specs/asset-information-responsive-layout/spec.md b/openspec/changes/update-asset-information-responsive-layout/specs/asset-information-responsive-layout/spec.md new file mode 100644 index 0000000..fd52b22 --- /dev/null +++ b/openspec/changes/update-asset-information-responsive-layout/specs/asset-information-responsive-layout/spec.md @@ -0,0 +1,49 @@ +## ADDED Requirements + +### Requirement: Asset Information Responsive Layout + +The asset information page SHALL provide responsive layouts from the asset search area through the bottom operation audit log area so the page remains readable and operable on desktop, tablet, and mobile-width screens. + +The responsive layout MUST NOT change existing API calls, permission checks, emitted events, business calculations, or displayed data semantics. + +#### Scenario: Search area fits small screens + +- **GIVEN** 用户在小屏幕设备上打开资产信息页 +- **WHEN** 页面渲染资产查询区域 +- **THEN** 搜索输入框 MUST NOT use a fixed width that exceeds the viewport +- **AND** 查询、同步、返回上次搜索等按钮 MUST wrap or stack without causing page-level horizontal overflow +- **AND** ICCID 放大提示 MUST fit within the viewport width + +#### Scenario: Information cards reduce columns on narrow screens + +- **GIVEN** 用户在平板或手机宽度打开资产信息页 +- **WHEN** 页面渲染基础信息、设备实时信息或当前生效套餐详情 +- **THEN** 描述项列数 MUST reduce according to available width +- **AND** long identifiers or status text MUST wrap or truncate within their container instead of pushing the page wider + +#### Scenario: Flow usage sections remain readable + +- **GIVEN** 用户在小屏幕设备上查看当前生效套餐 +- **WHEN** 页面渲染真流量或虚流量使用区域 +- **THEN** flow statistic sections MUST stack vertically when horizontal space is insufficient +- **AND** progress bars and percentages MUST remain visible and aligned within the card + +#### Scenario: Tables scroll within their card on small screens + +- **GIVEN** 用户在小屏幕设备上查看套餐列表、钱包流水或操作审计日志 +- **WHEN** table columns exceed the available content width +- **THEN** the table container MUST provide horizontal scrolling +- **AND** the overall page MUST NOT be widened by the table + +#### Scenario: Filters and pagination adapt to narrow screens + +- **GIVEN** 用户在小屏幕设备上查看钱包流水或操作审计日志 +- **WHEN** 页面渲染筛选器或分页器 +- **THEN** filter controls MUST wrap or stack within the card +- **AND** pagination MUST remain reachable without overlapping adjacent content + +#### Scenario: Desktop layout remains unchanged in behavior + +- **GIVEN** 用户在桌面宽度打开资产信息页 +- **WHEN** 页面渲染资产信息内容 +- **THEN** existing business actions, permissions, table operations, and data displays MUST continue to behave as before diff --git a/openspec/changes/update-asset-information-responsive-layout/tasks.md b/openspec/changes/update-asset-information-responsive-layout/tasks.md new file mode 100644 index 0000000..a0ee60c --- /dev/null +++ b/openspec/changes/update-asset-information-responsive-layout/tasks.md @@ -0,0 +1,10 @@ +## 1. Implementation + +- [x] 1.1 适配资产查询卡片的输入框、按钮组和放大提示在小屏下的布局 +- [x] 1.2 适配资产信息页主容器、卡片间距和卡片头部在小屏下的布局 +- [x] 1.3 适配基础信息与设备实时信息描述项在中小屏下的列数和操作按钮换行 +- [x] 1.4 适配当前生效套餐流量统计和套餐详情描述项在小屏下的布局 +- [x] 1.5 适配套餐列表表格、流量列和分页器在小屏下的横向滚动与换行 +- [x] 1.6 适配钱包流水筛选器、表格和分页器在小屏下的布局 +- [x] 1.7 适配操作审计日志筛选器、表格和分页器在小屏下的布局 +- [x] 1.8 完成 OpenSpec 严格校验 diff --git a/openspec/changes/update-asset-virtual-traffic-enabled-visibility/proposal.md b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/proposal.md new file mode 100644 index 0000000..d5b7738 --- /dev/null +++ b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/proposal.md @@ -0,0 +1,20 @@ +# Change: 按虚流量启用状态隐藏资产套餐虚流量展示 + +## Why + +资产信息页的当前生效套餐和套餐列表已经会展示虚流量使用、停机阈值和百分比。但当接口返回 `enable_virtual_data: false` 时,该套餐并未启用虚流量,上述虚流量相关展示会造成误解。 + +## What Changes + +- 在资产信息页“当前生效套餐”中,当当前套餐 `enable_virtual_data` 为 `false` 时隐藏“虚流量使用”区块。 +- 在资产信息页“套餐列表”中,当套餐记录 `enable_virtual_data` 为 `false` 时隐藏“已使用虚流量 / 停机阈值 / 虚流量进度百分比”展示。 +- 现有权限 `asset_info:view_current_package_virtual_usage` 仍继续生效;只有同时满足拥有权限且 `enable_virtual_data !== false` 时才展示虚流量信息。 +- 不改变真流量展示和非管理员合并流量摘要的既有逻辑。 + +## Impact + +- Affected specs: + - `asset-package-traffic-permissions` +- Affected code: + - `src/views/asset-management/asset-information/components/CurrentPackageCard.vue` + - `src/views/asset-management/asset-information/components/PackageListCard.vue` diff --git a/openspec/changes/update-asset-virtual-traffic-enabled-visibility/specs/asset-package-traffic-permissions/spec.md b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/specs/asset-package-traffic-permissions/spec.md new file mode 100644 index 0000000..2bd6804 --- /dev/null +++ b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/specs/asset-package-traffic-permissions/spec.md @@ -0,0 +1,52 @@ +## ADDED Requirements + +### Requirement: Asset Virtual Traffic Enabled Visibility + +The asset information page SHALL hide package virtual-traffic displays when the related package record has `enable_virtual_data` equal to `false`. + +The existing permission `asset_info:view_current_package_virtual_usage` MUST still be required for virtual-traffic displays. A virtual-traffic display MUST be visible only when the user has `asset_info:view_current_package_virtual_usage` and the related package record does not have `enable_virtual_data` equal to `false`. + +#### Scenario: Hide current-package virtual traffic when disabled + +- **GIVEN** 当前生效套餐接口返回 `enable_virtual_data: false` +- **AND** 用户拥有 `asset_info:view_current_package_virtual_usage` +- **WHEN** 页面渲染资产信息的 `当前生效套餐` +- **THEN** 页面 MUST NOT 显示 `虚流量使用` +- **AND** 页面 MUST NOT 显示该虚流量区块内的 `已使用` +- **AND** 页面 MUST NOT 显示该虚流量区块内的 `停机阈值` +- **AND** 页面 MUST NOT 显示该虚流量区块内的进度百分比 + +#### Scenario: Show current-package virtual traffic when enabled and permitted + +- **GIVEN** 当前生效套餐接口返回 `enable_virtual_data: true` +- **AND** 用户拥有 `asset_info:view_current_package_virtual_usage` +- **WHEN** 页面渲染资产信息的 `当前生效套餐` +- **THEN** 页面 MUST 显示 `虚流量使用` +- **AND** 页面 MUST 显示该虚流量区块内的 `已使用` +- **AND** 页面 MUST 显示该虚流量区块内的 `停机阈值` +- **AND** 页面 MUST 显示该虚流量区块内的进度百分比 + +#### Scenario: Hide package-list virtual traffic when disabled + +- **GIVEN** 套餐列表中某条套餐记录返回 `enable_virtual_data: false` +- **AND** 用户拥有 `asset_info:view_current_package_virtual_usage` +- **WHEN** 页面渲染该套餐记录的流量列 +- **THEN** 页面 MUST NOT 显示 `已使用虚流量` +- **AND** 页面 MUST NOT 显示 `停机阈值` +- **AND** 页面 MUST NOT 显示该套餐记录的虚流量进度百分比 + +#### Scenario: Show package-list virtual traffic when enabled and permitted + +- **GIVEN** 套餐列表中某条套餐记录返回 `enable_virtual_data: true` +- **AND** 用户拥有 `asset_info:view_current_package_virtual_usage` +- **WHEN** 页面渲染该套餐记录的流量列 +- **THEN** 页面 MUST 显示 `已使用虚流量` +- **AND** 页面 MUST 显示 `停机阈值` +- **AND** 页面 MUST 显示该套餐记录的虚流量进度百分比 + +#### Scenario: Keep hiding virtual traffic without permission + +- **GIVEN** 套餐记录返回 `enable_virtual_data: true` +- **AND** 用户不拥有 `asset_info:view_current_package_virtual_usage` +- **WHEN** 页面渲染资产信息的套餐流量展示 +- **THEN** 页面 MUST NOT 显示虚流量相关展示 diff --git a/openspec/changes/update-asset-virtual-traffic-enabled-visibility/tasks.md b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/tasks.md new file mode 100644 index 0000000..5724f19 --- /dev/null +++ b/openspec/changes/update-asset-virtual-traffic-enabled-visibility/tasks.md @@ -0,0 +1,6 @@ +## 1. Implementation + +- [x] 1.1 在“当前生效套餐”中按 `enable_virtual_data` 控制虚流量区块显示 +- [x] 1.2 在“套餐列表”中按每条记录的 `enable_virtual_data` 控制虚流量展示和进度百分比显示 +- [x] 1.3 确认现有虚流量权限仍参与显示判断 +- [x] 1.4 完成 OpenSpec 严格校验 diff --git a/src/App.vue b/src/App.vue index 5c1dfd4..5d45794 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,6 +16,7 @@ import { setThemeTransitionClass } from '@/utils' import { checkStorageCompatibility } from '@/utils' import ChunkErrorBoundary from '@/components/core/others/ChunkErrorBoundary.vue' + import { ElMessageBox } from 'element-plus' const userStore = useUserStore() const { language } = storeToRefs(userStore) @@ -25,6 +26,12 @@ en: en } + const VERSION_CHECK_INTERVAL = 5 * 60 * 1000 + let versionCheckTimer: number | undefined + let currentEntrySignature = '' + let upgradePromptVisible = false + let removeVisibilityChangeListener: (() => void) | undefined + onBeforeMount(() => { setThemeTransitionClass(true) }) @@ -36,10 +43,19 @@ setThemeTransitionClass(false) // 系统升级 systemUpgrade() + // 检查前端版本更新 + startVersionUpdateCheck() // 获取用户信息 getUserInfo() }) + onBeforeUnmount(() => { + if (versionCheckTimer) { + window.clearInterval(versionCheckTimer) + } + removeVisibilityChangeListener?.() + }) + // 获取用户信息 const getUserInfo = async () => { if (userStore.isLogin && userStore.accessToken) { @@ -55,4 +71,68 @@ } } } + + const getEntrySignature = (html: string) => { + const matches = html.match(/(?:src|href)="[^"]*\/assets\/[^""]+\.(?:js|css)"/g) + return matches?.sort().join('|') || '' + } + + const getLoadedEntrySignature = () => { + return Array.from(document.querySelectorAll('script[src],link[href]')) + .map((element) => ('src' in element ? element.src : element.href)) + .filter((url) => /\/assets\/.*\.(js|css)(\?|$)/.test(url)) + .sort() + .join('|') + } + + const checkVersionUpdate = async () => { + if (upgradePromptVisible || document.hidden) return + + try { + const response = await fetch(`${window.location.origin}${import.meta.env.BASE_URL}`, { + cache: 'no-store' + }) + const html = await response.text() + const latestSignature = getEntrySignature(html) + + if (!latestSignature) return + + if (!currentEntrySignature) { + currentEntrySignature = latestSignature + return + } + + if (currentEntrySignature !== latestSignature) { + upgradePromptVisible = true + await ElMessageBox.alert('系统已发布新版本,请刷新页面后继续使用。', '发现新版本', { + confirmButtonText: '立即刷新', + type: 'warning', + closeOnClickModal: false, + closeOnPressEscape: false + }) + window.location.reload() + } + } catch (error) { + console.warn('检查版本更新失败:', error) + } finally { + upgradePromptVisible = false + } + } + + const startVersionUpdateCheck = () => { + if (import.meta.env.DEV) return + + currentEntrySignature = getLoadedEntrySignature() + checkVersionUpdate() + versionCheckTimer = window.setInterval(checkVersionUpdate, VERSION_CHECK_INTERVAL) + const handleVisibilityChange = () => { + if (!document.hidden) { + checkVersionUpdate() + } + } + document.addEventListener('visibilitychange', handleVisibilityChange) + removeVisibilityChangeListener = () => { + document.removeEventListener('visibilitychange', handleVisibilityChange) + } + } diff --git a/src/views/asset-management/asset-information/components/AssetSearchCard.vue b/src/views/asset-management/asset-information/components/AssetSearchCard.vue index d5b6f68..5e21c43 100644 --- a/src/views/asset-management/asset-information/components/AssetSearchCard.vue +++ b/src/views/asset-management/asset-information/components/AssetSearchCard.vue @@ -12,7 +12,7 @@ v-model="searchIccid" placeholder="请输入虚拟号、ICCID、IMEI、SN或MSISDN" clearable - style="width: 500px" + class="asset-search-input" @focus="iccidInputFocused = true" @blur="iccidInputFocused = false" @keyup.enter="handleSearch" @@ -27,7 +27,7 @@ - + 查询 @@ -141,6 +141,7 @@ .card-header { display: flex; + gap: 12px; align-items: center; justify-content: space-between; } @@ -159,6 +160,12 @@ .iccid-input-wrapper { position: relative; + flex: 1 1 360px; + min-width: 240px; + + .asset-search-input { + width: 100%; + } .iccid-magnifier { position: absolute; @@ -182,6 +189,7 @@ // 内容区域 .magnifier-content { + max-width: min(720px, calc(100vw - 48px)); padding: 16px 24px; font-family: 'Courier New', Consolas, monospace; font-size: 28px; @@ -189,6 +197,8 @@ line-height: 1.4; color: var(--el-text-color-primary); letter-spacing: 3px; + overflow-wrap: anywhere; + white-space: normal; background-color: var(--el-bg-color, #fff); border: 2px solid var(--el-color-primary); border-radius: 8px; @@ -239,6 +249,56 @@ :deep(.el-card__body) { padding: 12px 16px; } + + :deep(.el-card__header) .card-header { + align-items: stretch; + flex-direction: column; + + .el-button { + width: 100%; + margin-left: 0; + } + } + + .iccid-search { + align-items: stretch; + flex-direction: column; + + .iccid-input-wrapper { + flex-basis: auto; + min-width: 0; + width: 100%; + + .iccid-magnifier { + right: 0; + left: 0; + white-space: normal; + + .magnifier-arrow { + left: 24px; + } + + .magnifier-content { + display: block; + width: 100%; + padding: 12px 14px; + font-size: 18px; + letter-spacing: 1px; + } + } + } + + .search-action-button, + .operation-button-group, + .operation-button-group .el-button { + width: 100%; + margin-left: 0; + } + + .operation-button-group { + margin-left: 0; + } + } } } diff --git a/src/views/asset-management/asset-information/components/BasicInfoCard.vue b/src/views/asset-management/asset-information/components/BasicInfoCard.vue index 65822e6..c7aafcc 100644 --- a/src/views/asset-management/asset-information/components/BasicInfoCard.vue +++ b/src/views/asset-management/asset-information/components/BasicInfoCard.vue @@ -25,7 +25,7 @@ - +