diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0669699 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ + +# OpenSpec Instructions + +These instructions are for AI assistants working in this project. + +Always open `@/openspec/AGENTS.md` when the request: +- Mentions planning or proposals (words like proposal, spec, change, plan) +- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work +- Sounds ambiguous and you need the authoritative spec before coding + +Use `@/openspec/AGENTS.md` to learn: +- How to create and apply change proposals +- Spec format and conventions +- Project structure and guidelines + +Keep this managed block so 'openspec update' can refresh the instructions. + + \ No newline at end of file diff --git a/docs/代理首页可提现佣金-spec.md b/docs/代理首页可提现佣金-spec.md new file mode 100644 index 0000000..eb4e780 --- /dev/null +++ b/docs/代理首页可提现佣金-spec.md @@ -0,0 +1,207 @@ +# 代理首页佣金展示 Spec + +## 1. 背景 + +代理首页当前已经会请求 `getFundSummary()`,但佣金区域使用了 `v-if="isAgent && commissionSummary"`。 + +这会带来两个问题: + +- 只要 `commissionSummary` 为空,首页代理佣金模块就整块不显示 +- 首页顶部还保留了“佣金概览”标题文案,这个文案本次不再需要 + +本次需求明确为: + +- 首页里去掉“佣金概览”这个标题文案 +- 只要当前账号是代理账号,就显示首页代理佣金区域 +- 不管接口有没有值,模块都要显示 + +--- + +## 2. 目标 + +- 首页代理佣金区域的外层显示条件改为仅判断 `isAgent` +- 首页不再展示“佣金概览”标题 +- 当佣金概览数据未返回、为空或请求失败时,佣金卡片仍然保留,金额位置显示占位内容 + +--- + +## 3. 范围 + +### 页面 + +- `src/pages/agent-system/home/index.vue` + +### 用户 + +- 代理账号显示:`user_type === 3` +- 企业账号不显示:`user_type === 4` + +--- + +## 4. 数据来源与约束 + +### 数据来源 + +- 接口:`GET /api/admin/shops/fund-summary` +- 方法:`getFundSummary()` +- 现有状态:`commissionSummary` + +### 关键字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `available_commission` | number | 可提现佣金,单位分 | +| `total_commission` | number | 累计佣金,单位分 | +| `withdrawn_commission` | number | 已提现佣金,单位分 | +| `frozen_commission` | number | 冻结佣金,单位分 | + +### 约束 + +1. 首页不得新增新的佣金概览接口请求 +2. 首页直接复用现有 `commissionSummary` +3. 本次不改后端接口、不改接口字段 + +--- + +## 5. 展示规则 + +### 5.1 外层显示条件 + +首页代理佣金区域显示条件改为: + +- `isAgent === true` + +不再要求: + +- `commissionSummary` 必须有值 + +### 5.2 标题文案 + +首页移除“佣金概览”标题文案,包括: + +- 不再在卡片上方展示“佣金概览” +- 保留卡片内部的“可提现佣金”主标题 + +### 5.3 金额展示 + +佣金卡片继续展示以下内容: + +- `可提现佣金` +- `累计佣金` +- `已提现` +- `冻结` + +展示规则: + +- 有值时,按现有金额格式化规则显示 +- 无值时,显示 `--` +- 不允许因为空值显示错误金额 + +### 5.4 今日佣金区域 + +“今日佣金”区域继续跟随代理佣金模块一起显示。 + +展示规则保持现状: + +- 日期无值显示 `--` +- 佣金金额无值显示 `¥0.00` +- 订单笔数无值显示 `0` +- 无当日数据时显示“今日暂无佣金数据” + +--- + +## 6. 交互与状态 + +### 6.1 跳转 + +点击佣金卡片后跳转到: + +- `/pages/agent-system/commission-center/index` + +### 6.2 加载态 + +当 `commissionSummary` 还未返回时: + +- 佣金卡片仍然显示 +- 金额区域显示 `--` + +### 6.3 异常态 + +当 `getFundSummary()` 请求失败时: + +- 佣金卡片仍然显示 +- 金额区域显示 `--` +- 不影响首页其他模块渲染 + +--- + +## 7. 非目标 + +- 不修改佣金中心页面结构 +- 不修改提现管理页面结构 +- 不新增首页专用佣金接口 +- 不修改企业账号首页展示逻辑 + +--- + +## 8. 验收标准 + +### 用例 1:代理账号且接口有值 + +前置条件: + +- `user_type === 3` +- `commissionSummary.available_commission = 85000` + +期望: + +- 首页显示代理佣金卡片 +- 首页不显示“佣金概览”标题 +- 主金额显示 `¥850.00` + +### 用例 2:代理账号但接口无值 + +前置条件: + +- `user_type === 3` +- `commissionSummary = null` + +期望: + +- 首页仍显示代理佣金卡片 +- `可提现佣金 / 累计佣金 / 已提现 / 冻结` 均显示 `--` +- 今日佣金区域仍显示 + +### 用例 3:代理账号且接口失败 + +前置条件: + +- `user_type === 3` +- `getFundSummary()` 请求失败 + +期望: + +- 首页仍显示代理佣金卡片 +- 金额不显示错误数据 +- 页面其他内容正常渲染 + +### 用例 4:企业账号 + +前置条件: + +- `user_type === 4` + +期望: + +- 首页不显示代理佣金区域 + +### 用例 5:不重复请求 + +前置条件: + +- 首页已执行 `getFundSummary()` + +期望: + +- 不因本次需求新增第二次佣金接口请求 +- 页面继续复用 `commissionSummary` diff --git a/docs/资产详情修改.md b/docs/资产详情修改.md new file mode 100644 index 0000000..4575526 --- /dev/null +++ b/docs/资产详情修改.md @@ -0,0 +1,60 @@ +1. 资产列表页面的IoT卡, 响应新增了返回了一个device_virtual_no这个字段 + 如果这个字段为空显示的就是未绑定设备, 如果是有值就显示已绑定设备 + +2.资产列表里面加一个店铺联级查询条件 +/api/admin/iot-cards/standalone?shop_id=xxxx + +# 店铺联级查询 + +## 基本信息 + +| 项目 | 内容 | +|------|------| +| 接口地址 | `GET /api/admin/shops/cascade` | +| 认证方式 | Bearer Token (JWT) | +| 所属模块 | 店铺管理 | + +## 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `shop_name` | string | 否 | 店铺名称(模糊查询),最大长度 100 | +| `parent_id` | integer | 否 | 上级店铺 ID,不传则查询顶级店铺 | + +## 响应参数 + +### 成功响应(200) + +{ + "code": 0, + "data": [ + { + "id": 1, + "shop_name": "总店", + "has_children": true + }, + { + "id": 2, + "shop_name": "分店 A", + "has_children": false + } + ], + "msg": "success", + "timestamp": "2026-05-09T14:28:00Z" +} + +#### DtoShopCascadeItem + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | integer | 店铺 ID | +| `shop_name` | string | 店铺名称 | +| `has_children` | boolean | 是否有下级店铺 | + +3. 资产列表是设备的情况下让默认选择的搜索类型是虚拟号, 并把这个虚拟号改成设备号, 然后那个选择搜索类型的不要了,如果是IoT卡的时候就是ICCID, 也不要那个选择搜索类型去掉 + +4. 在资产详情里面制造商,批次号字段去掉, 然后绑定的IoT卡里面电话改成MSISDN, 实名改显眼一点, 加一个运营商(carrier_name)返回里面有返回这个字段 + +5. 设备实时状态里面的下载流量,上传流量去掉. +6. 套餐列表里面的生肖中的那xxx至xxx 时间要明显一点 +7. 创建时间和网关同步删掉, 把最后在线改成最后上线时间放到设备实时状态里面 diff --git a/openspec/AGENTS.md b/openspec/AGENTS.md new file mode 100644 index 0000000..96ab0bb --- /dev/null +++ b/openspec/AGENTS.md @@ -0,0 +1,456 @@ +# OpenSpec Instructions + +Instructions for AI coding assistants using OpenSpec for spec-driven development. + +## TL;DR Quick Checklist + +- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search) +- Decide scope: new capability vs modify existing capability +- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`) +- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability +- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement +- Validate: `openspec validate [change-id] --strict` and fix issues +- Request approval: Do not start implementation until proposal is approved + +## Three-Stage Workflow + +### Stage 1: Creating Changes +Create proposal when you need to: +- Add features or functionality +- Make breaking changes (API, schema) +- Change architecture or patterns +- Optimize performance (changes behavior) +- Update security patterns + +Triggers (examples): +- "Help me create a change proposal" +- "Help me plan a change" +- "Help me create a proposal" +- "I want to create a spec proposal" +- "I want to create a spec" + +Loose matching guidance: +- Contains one of: `proposal`, `change`, `spec` +- With one of: `create`, `plan`, `make`, `start`, `help` + +Skip proposal for: +- Bug fixes (restore intended behavior) +- Typos, formatting, comments +- Dependency updates (non-breaking) +- Configuration changes +- Tests for existing behavior + +**Workflow** +1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes//`. +3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement. +4. Run `openspec validate --strict` and resolve any issues before sharing the proposal. + +### Stage 2: Implementing Changes +Track these steps as TODOs and complete them one by one. +1. **Read proposal.md** - Understand what's being built +2. **Read design.md** (if exists) - Review technical decisions +3. **Read tasks.md** - Get implementation checklist +4. **Implement tasks sequentially** - Complete in order +5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses +6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality +7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved + +### Stage 3: Archiving Changes +After deployment, create separate PR to: +- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/` +- Update `specs/` if capabilities changed +- Use `openspec archive --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly) +- Run `openspec validate --strict` to confirm the archived change passes checks + +## Before Any Task + +**Context Checklist:** +- [ ] Read relevant specs in `specs/[capability]/spec.md` +- [ ] Check pending changes in `changes/` for conflicts +- [ ] Read `openspec/project.md` for conventions +- [ ] Run `openspec list` to see active changes +- [ ] Run `openspec list --specs` to see existing capabilities + +**Before Creating Specs:** +- Always check if capability already exists +- Prefer modifying existing specs over creating duplicates +- Use `openspec show [spec]` to review current state +- If request is ambiguous, ask 1–2 clarifying questions before scaffolding + +### Search Guidance +- Enumerate specs: `openspec spec list --long` (or `--json` for scripts) +- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available) +- Show details: + - Spec: `openspec show --type spec` (use `--json` for filters) + - Change: `openspec show --json --deltas-only` +- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs` + +## Quick Start + +### CLI Commands + +```bash +# Essential commands +openspec list # List active changes +openspec list --specs # List specifications +openspec show [item] # Display change or spec +openspec validate [item] # Validate changes or specs +openspec archive [--yes|-y] # Archive after deployment (add --yes for non-interactive runs) + +# Project management +openspec init [path] # Initialize OpenSpec +openspec update [path] # Update instruction files + +# Interactive mode +openspec show # Prompts for selection +openspec validate # Bulk validation mode + +# Debugging +openspec show [change] --json --deltas-only +openspec validate [change] --strict +``` + +### Command Flags + +- `--json` - Machine-readable output +- `--type change|spec` - Disambiguate items +- `--strict` - Comprehensive validation +- `--no-interactive` - Disable prompts +- `--skip-specs` - Archive without spec updates +- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive) + +## Directory Structure + +``` +openspec/ +├── project.md # Project conventions +├── specs/ # Current truth - what IS built +│ └── [capability]/ # Single focused capability +│ ├── spec.md # Requirements and scenarios +│ └── design.md # Technical patterns +├── changes/ # Proposals - what SHOULD change +│ ├── [change-name]/ +│ │ ├── proposal.md # Why, what, impact +│ │ ├── tasks.md # Implementation checklist +│ │ ├── design.md # Technical decisions (optional; see criteria) +│ │ └── specs/ # Delta changes +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # Completed changes +``` + +## Creating Change Proposals + +### Decision Tree + +``` +New request? +├─ Bug fix restoring spec behavior? → Fix directly +├─ Typo/format/comment? → Fix directly +├─ New feature/capability? → Create proposal +├─ Breaking change? → Create proposal +├─ Architecture change? → Create proposal +└─ Unclear? → Create proposal (safer) +``` + +### Proposal Structure + +1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique) + +2. **Write proposal.md:** +```markdown +# Change: [Brief description of change] + +## Why +[1-2 sentences on problem/opportunity] + +## What Changes +- [Bullet list of changes] +- [Mark breaking changes with **BREAKING**] + +## Impact +- Affected specs: [list capabilities] +- Affected code: [key files/systems] +``` + +3. **Create spec deltas:** `specs/[capability]/spec.md` +```markdown +## ADDED Requirements +### Requirement: New Feature +The system SHALL provide... + +#### Scenario: Success case +- **WHEN** user performs action +- **THEN** expected result + +## MODIFIED Requirements +### Requirement: Existing Feature +[Complete modified requirement] + +## REMOVED Requirements +### Requirement: Old Feature +**Reason**: [Why removing] +**Migration**: [How to handle] +``` +If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs//spec.md`—one per capability. + +4. **Create tasks.md:** +```markdown +## 1. Implementation +- [ ] 1.1 Create database schema +- [ ] 1.2 Implement API endpoint +- [ ] 1.3 Add frontend component +- [ ] 1.4 Write tests +``` + +5. **Create design.md when needed:** +Create `design.md` if any of the following apply; otherwise omit it: +- Cross-cutting change (multiple services/modules) or a new architectural pattern +- New external dependency or significant data model changes +- Security, performance, or migration complexity +- Ambiguity that benefits from technical decisions before coding + +Minimal `design.md` skeleton: +```markdown +## Context +[Background, constraints, stakeholders] + +## Goals / Non-Goals +- Goals: [...] +- Non-Goals: [...] + +## Decisions +- Decision: [What and why] +- Alternatives considered: [Options + rationale] + +## Risks / Trade-offs +- [Risk] → Mitigation + +## Migration Plan +[Steps, rollback] + +## Open Questions +- [...] +``` + +## Spec File Format + +### Critical: Scenario Formatting + +**CORRECT** (use #### headers): +```markdown +#### Scenario: User login success +- **WHEN** valid credentials provided +- **THEN** return JWT token +``` + +**WRONG** (don't use bullets or bold): +```markdown +- **Scenario: User login** ❌ +**Scenario**: User login ❌ +### Scenario: User login ❌ +``` + +Every requirement MUST have at least one scenario. + +### Requirement Wording +- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative) + +### Delta Operations + +- `## ADDED Requirements` - New capabilities +- `## MODIFIED Requirements` - Changed behavior +- `## REMOVED Requirements` - Deprecated features +- `## RENAMED Requirements` - Name changes + +Headers matched with `trim(header)` - whitespace ignored. + +#### When to use ADDED vs MODIFIED +- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement. +- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details. +- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name. + +Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead. + +Authoring a MODIFIED requirement correctly: +1) Locate the existing requirement in `openspec/specs//spec.md`. +2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios). +3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior. +4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`. + +Example for RENAMED: +```markdown +## RENAMED Requirements +- FROM: `### Requirement: Login` +- TO: `### Requirement: User Authentication` +``` + +## Troubleshooting + +### Common Errors + +**"Change must have at least one delta"** +- Check `changes/[name]/specs/` exists with .md files +- Verify files have operation prefixes (## ADDED Requirements) + +**"Requirement must have at least one scenario"** +- Check scenarios use `#### Scenario:` format (4 hashtags) +- Don't use bullet points or bold for scenario headers + +**Silent scenario parsing failures** +- Exact format required: `#### Scenario: Name` +- Debug with: `openspec show [change] --json --deltas-only` + +### Validation Tips + +```bash +# Always use strict mode for comprehensive checks +openspec validate [change] --strict + +# Debug delta parsing +openspec show [change] --json | jq '.deltas' + +# Check specific requirement +openspec show [spec] --json -r 1 +``` + +## Happy Path Script + +```bash +# 1) Explore current state +openspec spec list --long +openspec list +# Optional full-text search: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) Choose change id and scaffold +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md + +# 3) Add deltas (example) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: Two-Factor Authentication +Users MUST provide a second factor during login. + +#### Scenario: OTP required +- **WHEN** valid credentials are provided +- **THEN** an OTP challenge is required +EOF + +# 4) Validate +openspec validate $CHANGE --strict +``` + +## Multi-Capability Example + +``` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: Two-Factor Authentication + └── notifications/ + └── spec.md # ADDED: OTP email notification +``` + +auth/spec.md +```markdown +## ADDED Requirements +### Requirement: Two-Factor Authentication +... +``` + +notifications/spec.md +```markdown +## ADDED Requirements +### Requirement: OTP Email Notification +... +``` + +## Best Practices + +### Simplicity First +- Default to <100 lines of new code +- Single-file implementations until proven insufficient +- Avoid frameworks without clear justification +- Choose boring, proven patterns + +### Complexity Triggers +Only add complexity with: +- Performance data showing current solution too slow +- Concrete scale requirements (>1000 users, >100MB data) +- Multiple proven use cases requiring abstraction + +### Clear References +- Use `file.ts:42` format for code locations +- Reference specs as `specs/auth/spec.md` +- Link related changes and PRs + +### Capability Naming +- Use verb-noun: `user-auth`, `payment-capture` +- Single purpose per capability +- 10-minute understandability rule +- Split if description needs "AND" + +### Change ID Naming +- Use kebab-case, short and descriptive: `add-two-factor-auth` +- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-` +- Ensure uniqueness; if taken, append `-2`, `-3`, etc. + +## Tool Selection Guide + +| Task | Tool | Why | +|------|------|-----| +| Find files by pattern | Glob | Fast pattern matching | +| Search code content | Grep | Optimized regex search | +| Read specific files | Read | Direct file access | +| Explore unknown scope | Task | Multi-step investigation | + +## Error Recovery + +### Change Conflicts +1. Run `openspec list` to see active changes +2. Check for overlapping specs +3. Coordinate with change owners +4. Consider combining proposals + +### Validation Failures +1. Run with `--strict` flag +2. Check JSON output for details +3. Verify spec file format +4. Ensure scenarios properly formatted + +### Missing Context +1. Read project.md first +2. Check related specs +3. Review recent archives +4. Ask for clarification + +## Quick Reference + +### Stage Indicators +- `changes/` - Proposed, not yet built +- `specs/` - Built and deployed +- `archive/` - Completed changes + +### File Purposes +- `proposal.md` - Why and what +- `tasks.md` - Implementation steps +- `design.md` - Technical decisions +- `spec.md` - Requirements and behavior + +### CLI Essentials +```bash +openspec list # What's in progress? +openspec show [item] # View details +openspec validate --strict # Is it correct? +openspec archive [--yes|-y] # Mark complete (add --yes for automation) +``` + +Remember: Specs are truth. Changes are proposals. Keep them in sync. diff --git a/openspec/changes/update-agent-asset-list-and-detail-display/proposal.md b/openspec/changes/update-agent-asset-list-and-detail-display/proposal.md new file mode 100644 index 0000000..4fd4bc8 --- /dev/null +++ b/openspec/changes/update-agent-asset-list-and-detail-display/proposal.md @@ -0,0 +1,34 @@ +# Change: Update agent asset list and detail display + +## Why + +The current agent-side asset experience exposes too much low-value information, hides some high-value state, and requires unnecessary search-type switching. The latest requests also require shop-based filtering, clearer detail presentation, explicit pagination, and a fixed top control area so search and filters remain available while the user browses long lists. + +## What Changes + +- Add IoT card device-binding status to the agent asset list +- Add cascaded shop filtering for the agent IoT card and device lists +- Remove the manual search-type selector and fix search semantics by active tab +- Keep the search input and filter controls fixed at the top while the asset list scrolls underneath +- Replace bottom-reached auto pagination with an explicit load-more button for both asset tabs +- Simplify asset detail basic fields and strengthen bound-card status presentation + - Remove `保护状态` + - Remove `切卡模式` + - Move the bound-card real-name status badge into the slot-title row +- Remove low-value device realtime traffic items and move "last online" into the realtime block as `最后上线时间` +- Make package effective-period text more prominent in asset detail package lists + - Only active packages should show the effective-period line +- Adjust the current active package header content + - Remove the order number line below the package name + - Show the active period there as `生效期:开始时间 - 到期时间` + +## Impact + +- Affected specs: + - `agent-asset-list` + - `agent-asset-detail` +- Affected code: + - `src/pages/agent-system/assets/index.vue` + - `src/pages/agent-system/asset-search/index.vue` + - `src/pages/agent-system/asset-detail/index.vue` + - `src/api/assets.ts` diff --git a/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-detail/spec.md b/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-detail/spec.md new file mode 100644 index 0000000..872b52d --- /dev/null +++ b/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-detail/spec.md @@ -0,0 +1,108 @@ +## ADDED Requirements + +### Requirement: Agent Asset Detail SHALL Remove Redundant Basic Fields + +The agent asset detail experience SHALL remove fields that are no longer required from the basic information area. + +#### Scenario: Viewing an IoT card detail + +- **WHEN** the current asset is an IoT card +- **THEN** the basic information section SHALL NOT show `批次号` + +#### Scenario: Viewing a device detail + +- **WHEN** the current asset is a device +- **THEN** the basic information section SHALL NOT show `制造商` +- **AND** the basic information section SHALL NOT show `批次号` +- **AND** the basic information section SHALL NOT show `保护状态` +- **AND** the basic information section SHALL NOT show `切卡模式` + +### Requirement: Agent Asset Detail SHALL Emphasize Bound IoT Card Identity And Status + +The agent device detail view SHALL make bound IoT card identity and real-name status easier to scan. + +#### Scenario: Rendering a bound IoT card item + +- **WHEN** a device detail contains a bound IoT card entry +- **THEN** the label `电话` SHALL be renamed to `MSISDN` +- **AND** the item SHALL display `carrier_name` as `运营商` +- **AND** missing `msisdn` or `carrier_name` values SHALL render as `-` + +#### Scenario: Rendering real-name status + +- **WHEN** a bound IoT card has a `real_name_status` +- **THEN** the UI SHALL render that status with stronger visual emphasis than ordinary low-contrast metadata text +- **AND** the meaning of existing status values SHALL remain unchanged + +#### Scenario: Rendering the bound-card header row + +- **WHEN** a bound IoT card item is rendered in the device detail page +- **THEN** the real-name status badge SHALL be placed in the same row as the slot title +- **AND** the badge SHALL appear after the slot title text +- **AND** the lower metadata area SHALL NOT render a second standalone real-name field for the same card + +### Requirement: Agent Device Realtime Status SHALL Prioritize High-Value Realtime Information + +The agent device detail realtime block SHALL remove low-value traffic rows and absorb the "last online" field as a realtime-oriented field. + +#### Scenario: Rendering device realtime traffic statistics + +- **WHEN** the device realtime block is shown +- **THEN** it SHALL continue to show `今日用量` +- **AND** it SHALL continue to show `限速` +- **AND** it SHALL NOT show `下载流量` +- **AND** it SHALL NOT show `上传流量` + +#### Scenario: Rendering last online time + +- **WHEN** a device detail page has a realtime section +- **THEN** the page SHALL show `最后上线时间` inside the realtime section +- **AND** it SHALL prefer `realtimeStatus.device_realtime.last_online_time` +- **AND** it SHALL fall back to `deviceInfo.last_online_time` when the realtime value is absent + +#### Scenario: Rendering the legacy time-information block + +- **WHEN** the device detail page previously showed `创建时间`, `最后在线`, or `网关同步` in a separate time-information block +- **THEN** that block SHALL no longer show `创建时间` +- **AND** that block SHALL no longer show `最后在线` +- **AND** that block SHALL no longer show `网关同步` + +### Requirement: Agent Asset Detail Package List SHALL Emphasize Effective Period + +The package list in agent asset detail SHALL make the effective period line visually prominent. + +#### Scenario: Package has both start and end time + +- **WHEN** a package list item has both `activated_at` and `expires_at` +- **AND** the package is in active status +- **THEN** the page SHALL render a dedicated effective-period line with the meaning `生效期:开始时间 至 结束时间` +- **AND** that line SHALL have stronger visual prominence than the current weak auxiliary timestamp style + +#### Scenario: Package is missing part of the effective period + +- **WHEN** either `activated_at` or `expires_at` is missing +- **AND** the package is in active status +- **THEN** the effective-period line SHALL still be rendered +- **AND** the missing value SHALL display as `-` + +#### Scenario: Package is not in active status + +- **WHEN** a package list item is not in active status +- **THEN** the page SHALL NOT render the dedicated `生效期` line for that package + +### Requirement: Agent Asset Detail Current Package SHALL Prioritize Effective Period Over Order Number + +The current active package card in agent asset detail SHALL remove the order number from the package header area and use that space to surface the package active period. + +#### Scenario: Rendering the current active package card + +- **WHEN** the current active package card is rendered +- **THEN** the package name SHALL remain visible +- **AND** the order number line below the package name SHALL NOT be shown +- **AND** the card SHALL show a labeled `生效期:开始时间 - 到期时间` line in that position instead + +#### Scenario: Current package is missing part of the active period + +- **WHEN** either the current package `activated_at` or `expires_at` is missing +- **THEN** the labeled `生效期:开始时间 - 到期时间` line SHALL still be rendered +- **AND** the missing value SHALL display as `-` diff --git a/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-list/spec.md b/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-list/spec.md new file mode 100644 index 0000000..6b6f0c0 --- /dev/null +++ b/openspec/changes/update-agent-asset-list-and-detail-display/specs/agent-asset-list/spec.md @@ -0,0 +1,123 @@ +## ADDED Requirements + +### Requirement: Agent IoT Card List SHALL Surface Device Binding Status + +The agent-side IoT card list SHALL derive a device-binding status from the `device_virtual_no` field returned by `GET /api/admin/iot-cards/standalone` and show that status in each IoT card list item. + +#### Scenario: Card is not bound to a device + +- **WHEN** an IoT card item has `device_virtual_no` equal to `null`, `undefined`, an empty string, or a whitespace-only string +- **THEN** the list item SHALL show a status tag with `未绑定设备` +- **AND** the UI SHALL NOT display a fabricated device identifier +- **AND** the UI SHALL NOT prepend an extra `设备绑定` label before the tag + +#### Scenario: Card is bound to a device + +- **WHEN** an IoT card item has a non-empty `device_virtual_no` +- **THEN** the list item SHALL show a status tag with `已绑定设备` +- **AND** the UI MAY keep the raw `device_virtual_no` hidden + +### Requirement: Agent Asset Lists SHALL Support Cascaded Shop Filtering + +The agent-side asset lists SHALL provide a cascaded shop filter that is backed by `GET /api/admin/shops/cascade` and applies the selected shop ID to the active list request. + +#### Scenario: User chooses a shop node under the IoT card tab + +- **WHEN** the user opens the shop filter under the `IoT卡` tab and confirms a shop node +- **THEN** the selected node `id` SHALL be sent as `shop_id` in the subsequent IoT card list request +- **AND** the list SHALL refresh using that shop filter + +#### Scenario: User chooses a shop node under the device tab + +- **WHEN** the user opens the shop filter under the `设备` tab and confirms a shop node +- **THEN** the selected node `id` SHALL be sent as `shop_id` in the subsequent device list request +- **AND** the device list SHALL refresh using that shop filter +- **AND** the device list request SHALL keep its existing search semantics and append `shop_id` in addition to them + +#### Scenario: Selected shop has children + +- **WHEN** a shop node returned from `GET /api/admin/shops/cascade` has `has_children = true` +- **THEN** the UI SHALL allow the user to continue drilling down to child shops +- **AND** the UI SHALL use `parent_id` to load the next level + +#### Scenario: User clears the shop filter + +- **WHEN** the user clears the shop filter +- **THEN** subsequent IoT card list requests SHALL omit `shop_id` +- **AND** subsequent device list requests SHALL omit `shop_id` + +#### Scenario: Device tab is active + +- **WHEN** the active asset tab is `设备` +- **THEN** the shop filter SHALL still be shown +- **AND** device list requests SHALL support `shop_id` + +### Requirement: Agent Asset Search Input SHALL Be Fixed By Active Tab + +The agent asset list SHALL remove the manual search-type selector and bind search semantics directly to the active tab. + +#### Scenario: IoT card tab search + +- **WHEN** the active tab is `IoT卡` +- **THEN** the search field SHALL target `iccid` +- **AND** the search placeholder SHALL be `搜索ICCID` +- **AND** the UI SHALL NOT show a search-type switcher + +#### Scenario: Device tab search + +- **WHEN** the active tab is `设备` +- **THEN** the search field SHALL target `virtual_no` +- **AND** the user-facing search label SHALL be `设备号` +- **AND** the search placeholder SHALL be `搜索设备号` +- **AND** the UI SHALL NOT show a search-type switcher + +### Requirement: Agent Asset List Controls SHALL Stay Fixed At The Top + +The agent-side asset list page SHALL keep the search input and the filter/tab control area fixed at the top of the page while the list content scrolls independently underneath. + +#### Scenario: User scrolls a long IoT card list + +- **WHEN** the active tab is `IoT卡` and the user scrolls the list +- **THEN** the search input SHALL remain visible at the top +- **AND** the filter and tab controls directly below it SHALL remain visible at the top +- **AND** only the list content area SHALL scroll + +#### Scenario: User scrolls a long device list + +- **WHEN** the active tab is `设备` and the user scrolls the list +- **THEN** the search input SHALL remain visible at the top +- **AND** the filter and tab controls directly below it SHALL remain visible at the top +- **AND** only the list content area SHALL scroll + +#### Scenario: Fixed controls do not cover list content + +- **WHEN** the fixed top control area is rendered +- **THEN** the list content SHALL start below that area +- **AND** the first list item SHALL remain fully visible and operable + +### Requirement: Agent Asset Lists SHALL Use Explicit Load-More Pagination + +The agent-side IoT card list and device list SHALL stop automatic pagination triggered by scrolling to the bottom and SHALL use an explicit footer button to load the next page. + +#### Scenario: IoT card tab still has more pages + +- **WHEN** the active tab is `IoT卡` and the current IoT card result set has more pages available +- **THEN** the footer SHALL show a button labeled `加载更多` +- **AND** the next page SHALL NOT be requested until the user clicks that button + +#### Scenario: Device tab still has more pages + +- **WHEN** the active tab is `设备` and the current device result set has more pages available +- **THEN** the footer SHALL show a button labeled `加载更多` +- **AND** the next page SHALL NOT be requested until the user clicks that button + +#### Scenario: User clicks the load-more button + +- **WHEN** the user clicks the `加载更多` button for the active tab +- **THEN** the UI SHALL request the next page for that tab using the existing active filters and search parameters +- **AND** the returned items SHALL append to the existing list instead of replacing it + +#### Scenario: No more pages remain + +- **WHEN** the active list has no more pages available +- **THEN** the `加载更多` button SHALL NOT be shown diff --git a/openspec/changes/update-agent-asset-list-and-detail-display/tasks.md b/openspec/changes/update-agent-asset-list-and-detail-display/tasks.md new file mode 100644 index 0000000..5aa850e --- /dev/null +++ b/openspec/changes/update-agent-asset-list-and-detail-display/tasks.md @@ -0,0 +1,20 @@ +## 1. Spec + +- [x] 1.1 Initialize OpenSpec in the repository +- [x] 1.2 Capture project context in `openspec/project.md` +- [x] 1.3 Create change proposal and delta specs for asset list and asset detail +- [x] 1.4 Review and approve the proposal before implementation + +## 2. Implementation + +- [x] 2.1 Extend asset list typing and request parameters for `device_virtual_no` and `shop_id` +- [x] 2.2 Update agent asset list UI for binding status, shop filter, and fixed search semantics +- [x] 2.3 Update asset detail UI for field removal, bound-card emphasis, and realtime-status changes +- [x] 2.4 Update package-list date presentation in detail pages +- [ ] 2.5 Verify card and device flows manually after implementation +- [x] 2.6 Remove `保护状态` and `切卡模式`, and move bound-card real-name status next to the slot title +- [x] 2.7 Restrict package effective-period display to packages in active status only +- [x] 2.8 Remove the current-package order number and replace it with a `生效期:开始时间 - 到期时间` line +- [x] 2.9 Apply the cascaded shop filter to the device list request and device tab UI +- [x] 2.10 Replace bottom-reached auto pagination with an explicit `加载更多` button for the IoT card and device lists +- [x] 2.11 Keep the asset-list search input and filter controls fixed at the top while the list scrolls underneath diff --git a/openspec/project.md b/openspec/project.md new file mode 100644 index 0000000..0837ee2 --- /dev/null +++ b/openspec/project.md @@ -0,0 +1,68 @@ +# Project Context + +## Purpose + +This project is a Uni-app based agent and enterprise management frontend for IoT assets. It provides asset list, asset search/detail, package display, commission, withdrawal, and related operational pages for two user roles: agent accounts and enterprise accounts. + +## Tech Stack + +- TypeScript +- Vue 3 with ` + + + + diff --git a/src/pages/agent-system/asset-detail/index.vue b/src/pages/agent-system/asset-detail/index.vue index f15dbc5..3970094 100644 --- a/src/pages/agent-system/asset-detail/index.vue +++ b/src/pages/agent-system/asset-detail/index.vue @@ -76,6 +76,14 @@ function getPackageTypeText(type?: AssetPackage['package_type']) { return type ? map[type] || type : '-' } +function formatPackageEffectivePeriod(activatedAt?: string | null, expiresAt?: string | null) { + return `生效期:${formatTime(activatedAt || undefined, 'date')} 至 ${formatTime(expiresAt || undefined, 'date')}` +} + +function formatCurrentPackagePeriodRange(activatedAt?: string | null, expiresAt?: string | null) { + return `生效期:${formatTime(activatedAt || undefined, 'date')} - ${formatTime(expiresAt || undefined, 'date')}` +} + // 页面加载 onMounted(() => { // 从路由参数获取 assetType 和 assetIdentifier @@ -506,8 +514,8 @@ function viewWallet() { {{ currentPackage.package_name || '-' }} - - 订单号:{{ currentPackage.order_no || '-' }} + + {{ formatCurrentPackagePeriodRange(currentPackage.activated_at, currentPackage.expires_at) }} 套餐类型 {{ getPackageTypeText(currentPackage.package_type) }} - - 开始时间 - {{ formatTime(currentPackage.activated_at) }} - - - 到期时间 - {{ formatTime(currentPackage.expires_at) }} - 下单时间 {{ formatTime(currentPackage.created_at) }} @@ -642,9 +642,11 @@ function viewWallet() { 金额 {{ formatPackagePrice(pkg.paid_amount) }} - - {{ pkg.activated_at || '-' }} ~ {{ pkg.expires_at || '-' }} - + + + {{ formatPackageEffectivePeriod(pkg.activated_at, pkg.expires_at) }} + + diff --git a/src/pages/agent-system/asset-search/index.vue b/src/pages/agent-system/asset-search/index.vue index 869160d..28df4cb 100644 --- a/src/pages/agent-system/asset-search/index.vue +++ b/src/pages/agent-system/asset-search/index.vue @@ -1,7 +1,7 @@ diff --git a/src/pages/agent-system/home/index.vue b/src/pages/agent-system/home/index.vue index 082f06b..3b9b540 100644 --- a/src/pages/agent-system/home/index.vue +++ b/src/pages/agent-system/home/index.vue @@ -5,7 +5,7 @@ import type { UserInfo } from '@/api/auth' import { getFundSummary, getCommissionDailyStats } from '@/api/commission' import type { FundSummary, DailyCommissionStats } from '@/api/commission' - + // 用户信息 const userInfo = ref(null) @@ -36,6 +36,10 @@ return `¥${parts.join('.')}` } + function formatAmountDisplay(amount?: number | null) { + return typeof amount === 'number' ? formatAmount(amount) : '--' + } + // 格式化日期 (MM-DD) // 根据用户类型动态生成菜单 function formatDate(dateStr : string) { @@ -104,7 +108,7 @@ // 加载佣金数据 async function loadCommissionData(shopName?: string) { - if (!isAgent.value || !shopId.value) return + if (!isAgent.value) return loadingCommission.value = true @@ -112,6 +116,12 @@ // 1. 加载佣金概览 (新接口 /shops/fund-summary) const summary = await getFundSummary(shopName ? { shop_name: shopName } : undefined) commissionSummary.value = summary + + if (!shopId.value) { + dailyStats.value = [] + return + } + const today = getTodayDate() const todayStats = await getCommissionDailyStats(shopId.value, { days: 1, @@ -143,11 +153,8 @@ userInfo.value = user // 设置店铺ID - if (user.shop_id) { - shopId.value = user.shop_id - } - const loginUserInfo = getLoginUserInfo() + shopId.value = user.shop_id || loginUserInfo?.shop_id || 0 const fundSummaryShopName = loginUserInfo?.username || user.username console.log('首页数据加载成功:', { user }) @@ -244,33 +251,47 @@ - - - 可提现佣金 - - - - 累计 - - {{ formatAmount(commissionSummary.total_commission) }} + + + + + + 可提现佣金 + + {{ formatAmountDisplay(commissionSummary?.available_commission) }} + + + 当前可直接发起提现的佣金金额 - - 冻结 - - {{ formatAmount(commissionSummary.frozen_commission) }} + + + + 佣金中心 + + + + + + + 累计佣金 + + {{ formatAmountDisplay(commissionSummary?.total_commission) }} - - 已提现 - - {{ formatAmount(commissionSummary.withdrawn_commission) }} + + 已提现 + + {{ formatAmountDisplay(commissionSummary?.withdrawn_commission) }} - - 可提现 - - {{ formatAmount(commissionSummary.available_commission) }} + + 冻结 + + {{ formatAmountDisplay(commissionSummary?.frozen_commission) }} @@ -280,7 +301,7 @@ 今日佣金 - 更多 + 明细 diff --git a/types/components.d.ts b/types/components.d.ts index 4623108..46ae5ef 100644 --- a/types/components.d.ts +++ b/types/components.d.ts @@ -12,6 +12,7 @@ declare module 'vue' { LangSelect: typeof import('./../src/components/lang-select/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + ShopCascadePicker: typeof import('./../src/components/ShopCascadePicker.vue')['default'] SimplePicker: typeof import('./../src/components/SimplePicker.vue')['default'] Skeleton: typeof import('./../src/components/Skeleton.vue')['default'] ThemePicker: typeof import('./../src/components/theme-picker/index.vue')['default']