From d060edb69833bebde76a5da334f266bfee0e905d Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 10:25:13 +0800 Subject: [PATCH 1/7] feat: add notification feature --- docs/通知.md | 305 ++++++++++++++++++ .../design.md | 29 ++ .../proposal.md | 20 ++ .../specs/notifications/spec.md | 91 ++++++ .../tasks.md | 18 ++ src/api/notifications.ts | 72 +++++ src/pages.json | 8 + src/pages/agent-system/home/index.vue | 97 +++++- .../agent-system/notifications/index.vue | 153 +++++++++ src/pages/common/login/index.vue | 2 + src/static/icons/通知.png | Bin 0 -> 6267 bytes 11 files changed, 785 insertions(+), 10 deletions(-) create mode 100644 docs/通知.md create mode 100644 openspec/changes/add-notification-entry-and-unread-reminder/design.md create mode 100644 openspec/changes/add-notification-entry-and-unread-reminder/proposal.md create mode 100644 openspec/changes/add-notification-entry-and-unread-reminder/specs/notifications/spec.md create mode 100644 openspec/changes/add-notification-entry-and-unread-reminder/tasks.md create mode 100644 src/api/notifications.ts create mode 100644 src/pages/agent-system/notifications/index.vue create mode 100644 src/static/icons/通知.png diff --git a/docs/通知.md b/docs/通知.md new file mode 100644 index 0000000..05a5270 --- /dev/null +++ b/docs/通知.md @@ -0,0 +1,305 @@ +# 通知接口 + +服务地址:`https://cmp-api.boss160.cn` + +认证方式:`Bearer ` + +## 通知枚举 + +### 通知类别 + +| 值 | 说明 | +| ---------- | --- | +| `approval` | 审批 | +| `expiry` | 临期 | +| `sync` | 同步 | +| `system` | 系统 | + +### 通知级别 + +| 值 | 说明 | +| ---------- | --- | +| `info` | 提示 | +| `warning` | 警告 | +| `error` | 错误 | +| `critical` | 严重 | + +## 查询通知未读数 + +### GET /api/admin/notifications/unread-count + +查询当前认证后台账号的未过期未读通知数量,超过 99 条时显示 `99+`。 + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "count": 120, + "display_count": "99+" + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +| 字段 | 类型 | 说明 | +| ------------------ | ------- | --------------------- | +| data.count | integer | 未读通知数量 | +| data.display_count | string | 徽标显示文本,超过 99 时为 `99+` | + +## 查询通知未读分类汇总 + +### GET /api/admin/notifications/unread-summary + +查询当前账号未过期通知的总未读数及分类数量。 + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "approval": 2, + "expiry": 3, + "sync": 1, + "system": 0, + "total": 6 + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +| 字段 | 类型 | 说明 | +| ------------- | ------- | ------- | +| data.approval | integer | 审批类未读数量 | +| data.expiry | integer | 临期类未读数量 | +| data.sync | integer | 同步类未读数量 | +| data.system | integer | 系统类未读数量 | +| data.total | integer | 未读通知总数 | + +## 查询通知列表 + +### GET /api/admin/notifications + +查询当前认证后台账号的未过期通知,按创建时间和通知 ID 倒序返回。 + +#### 请求参数 + +| 参数 | 类型 | 必填 | 说明 | +| --------- | ------- | --- | ---------------------------------------- | +| category | string | 否 | 通知类别:`approval`、`expiry`、`sync`、`system` | +| type | string | 否 | 稳定通知类型 | +| severity | string | 否 | 通知级别:`info`、`warning`、`error`、`critical` | +| is_read | boolean | 否 | 已读状态;不传查询全部 | +| page | integer | 否 | 页码,默认 1,范围 1~10000 | +| page_size | integer | 否 | 每页数量,默认 20,范围 1~50 | + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "items": [ + { + "id": 1, + "category": "approval", + "type": "refund_approval_pending", + "severity": "warning", + "title": "退款审批待处理", + "body": "退款单 REFUND202607250001 正在审批中", + "is_read": false, + "read_at": null, + "ref_type": "refund", + "ref_id": "100", + "ref_key": "REFUND202607250001", + "created_at": "2026-07-25T00:00:00Z" + } + ], + "page": 1, + "size": 20, + "total": 1 + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +#### 返回字段 + +| 字段 | 类型 | 说明 | +| ------------------ | ----------- | ---------------- | +| data.items | array | 通知列表 | +| data.page | integer | 当前页码 | +| data.size | integer | 每页数量 | +| data.total | integer | 总数量 | +| items[].id | integer | 通知 ID | +| items[].category | string | 通知类别 | +| items[].type | string | 稳定通知类型 | +| items[].severity | string | 通知级别 | +| items[].title | string | 纯文本标题 | +| items[].body | string | 纯文本正文 | +| items[].is_read | boolean | 是否已读 | +| items[].read_at | string/null | 首次已读时间 | +| items[].ref_type | string | 受控资源类型,不是前端路由 | +| items[].ref_id | string | 受控资源数字 ID 字符串 | +| items[].ref_key | string | 受控资源稳定 Key 或展示快照 | +| items[].created_at | string | 创建时间 | + +## 标记单条通知已读 + +### PUT /api/admin/notifications/{id}/read + +标记当前账号的一条通知为已读。通知不存在、属于其他账号或已经已读时均幂等成功。 + +#### 路径参数 + +| 参数 | 类型 | 必填 | 说明 | +| --- | ------- | --- | ------------ | +| id | integer | 是 | 通知 ID,最小值为 0 | + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | +| --- | ------- | --- | --------------- | +| id | integer | 是 | 通知 ID,必须与路径参数一致 | + +请求示例: + +```json +{ + "id": 1 +} +``` + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "success": true + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +## 批量标记通知已读 + +### PUT /api/admin/notifications/read-all + +批量标记当前账号通知为已读。未传类别时更新全部未过期未读通知,传类别时只更新对应类别。 + +#### 请求体 + +| 字段 | 类型 | 必填 | 说明 | +| -------- | ------ | --- | ---------------------------------------- | +| category | string | 否 | 通知类别:`approval`、`expiry`、`sync`、`system` | + +请求示例: + +```json +{ + "category": "approval" +} +``` + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "updated_count": 2 + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +| 字段 | 类型 | 说明 | +| ------------------ | ------- | ----------- | +| data.updated_count | integer | 本次实际更新的通知数量 | + +## 解析通知受控目标 + +### GET /api/admin/notifications/{id}/target + +校验通知属于当前账号后,复核目标资源当前权限,返回白名单结构化目标。不会返回任意 URL。 + +#### 路径参数 + +| 参数 | 类型 | 必填 | 说明 | +| --- | ------- | --- | ------------ | +| id | integer | 是 | 通知 ID,最小值为 0 | + +#### 成功响应 + +```json +{ + "code": 0, + "data": { + "available": true, + "target_type": "refund_detail", + "target_id": 100, + "target_key": "REFUND202607250001" + }, + "msg": "success", + "timestamp": "2026-07-25T00:00:00Z" +} +``` + +| 字段 | 类型 | 说明 | +| ---------------- | ------------ | ------------------ | +| data.available | boolean | 当前账号是否仍可访问目标 | +| data.target_type | string | 前端白名单目标类型,空表示不支持跳转 | +| data.target_id | integer/null | ID 型目标业务主键 | +| data.target_key | string | Key 型目标稳定定位值 | + +### target_type 白名单 + +| 值 | 说明 | +| ----------------------- | ------- | +| `refund_detail` | 退款详情 | +| `agent_recharge_detail` | 代理充值详情 | +| `wecom_approval_detail` | 企微审批详情 | +| `iot_card_detail` | IoT 卡详情 | +| `device_detail` | 设备详情 | +| `expiring_asset_list` | 临期资产列表 | +| `shop_fund_summary` | 店铺资金概况 | +| `system_config` | 系统配置 | + +## 业务规则 + +- 通知只返回当前认证后台账号的未过期通知。 +- `ref_type`、`ref_id` 和 `ref_key` 是受控资源引用,不是前端 URL。 +- 点击通知后必须调用目标解析接口,由 `target_type` 和 `available` 决定是否跳转。 +- `available=false` 或 `target_type` 为空时,只展示通知正文,不执行跳转。 +- 前端维护 `target_type` 到页面的白名单映射,不得根据通知字段拼接任意 URL。 +- 已读操作必须支持幂等调用。 + +## 错误响应 + +适用于以上接口: + +| HTTP 状态码 | 说明 | +| -------- | --------- | +| 400 | 请求参数错误 | +| 401 | 未认证或认证已过期 | +| 403 | 无权访问 | +| 500 | 服务器内部错误 | + +错误响应示例: + +```json +{ + "code": 1001, + "data": {}, + "msg": "参数验证失败", + "timestamp": "2026-07-25T00:00:00Z" +} +``` diff --git a/openspec/changes/add-notification-entry-and-unread-reminder/design.md b/openspec/changes/add-notification-entry-and-unread-reminder/design.md new file mode 100644 index 0000000..bb6b40d --- /dev/null +++ b/openspec/changes/add-notification-entry-and-unread-reminder/design.md @@ -0,0 +1,29 @@ +## Context + +The notification API returns the response payload through the existing request wrapper, so frontend API methods should expose the typed `data` payload directly. The home page is shared by agent and enterprise accounts and is loaded after login through `/pages/agent-system/home/index`. + +## Goals / Non-Goals + +- Goals: expose notifications, show the newest unread message after login, and keep server read state aligned with what the user has actually seen. +- Non-Goals: notification target resolution, controlled-resource routing, push notifications, or a separate manual read button. + +## Decisions + +- Decision: add a dedicated notification page for the notification entry. The entry is placed in the home page's top user-information card, and the page renders notification content only; notification rows have no click navigation. +- Decision: the login page writes a short-lived pending-reminder marker after storing a valid session. The home page consumes the marker after loading the authenticated user and requests the newest unread notification. +- Decision: use `GET /api/admin/notifications/unread-count` for the entry badge and `GET /api/admin/notifications?is_read=false&page=1&page_size=1` for the login reminder. The notification page requests the newest notification page with `page_size=50`. +- Decision: call `PUT /api/admin/notifications/{id}/read` as soon as an unread notification becomes visible in the reminder or notification list, update local state optimistically, and treat the API as idempotent. +- Decision: if the unread-count or reminder request fails, keep the home page usable and do not show a fabricated notification. The normal request interceptor continues to handle authentication failures. + +## Risks / Trade-offs + +- Marking on render means a message can become read before the user finishes reading it; this follows the requested “seen means read” behavior. +- A notification page displays the first 50 newest records; additional pagination is outside this change unless the API data requires it. + +## Migration Plan + +No data migration is needed. Deploy the frontend after the notification endpoints and icon asset are available. Rolling back removes the entry and UI without changing notification records. + +## Open Questions + +- None for the stated behavior. The entry opens the notification page, while notification content itself never navigates to a controlled target. diff --git a/openspec/changes/add-notification-entry-and-unread-reminder/proposal.md b/openspec/changes/add-notification-entry-and-unread-reminder/proposal.md new file mode 100644 index 0000000..294fba1 --- /dev/null +++ b/openspec/changes/add-notification-entry-and-unread-reminder/proposal.md @@ -0,0 +1,20 @@ +# Change: Add notification entry and unread reminder + +## Why + +The backend notification contract is documented in `docs/通知.md`, but the frontend does not expose notifications to authenticated agent or enterprise users. Users also have no immediate way to notice newly issued unread messages after signing in. + +## What Changes + +- Add a notification shortcut to the home page using `src/static/icons/通知.png`. +- Add a notification page that lists the current account's notifications in newest-first order. +- Show the newest unread notification in a reminder modal after a successful login redirects to the home page. +- Mark a notification as read when it is rendered as visible to the user; no manual “mark as read” action is required. +- Do not resolve `target` data and do not navigate from a notification to any controlled resource. +- Display the unread count on the home-page notification entry when it is available. + +## Impact + +- Affected specs: `notifications` (new capability) +- Affected code: `src/api/notifications.ts`, `src/pages/agent-system/home/index.vue`, `src/pages/agent-system/notifications/index.vue`, `src/pages.json`, and the login-to-home session flow +- Existing user-supplied assets remain in place: `docs/通知.md` and `src/static/icons/通知.png` diff --git a/openspec/changes/add-notification-entry-and-unread-reminder/specs/notifications/spec.md b/openspec/changes/add-notification-entry-and-unread-reminder/specs/notifications/spec.md new file mode 100644 index 0000000..7cfee9f --- /dev/null +++ b/openspec/changes/add-notification-entry-and-unread-reminder/specs/notifications/spec.md @@ -0,0 +1,91 @@ +## ADDED Requirements + +### Requirement: Home notification entry + +The authenticated home page SHALL expose a notification entry for both supported account types. The entry MUST use `src/static/icons/通知.png`, display the current unread count when available, and open the notification page. Notification content MUST NOT resolve controlled targets or navigate to business resources. + +#### Scenario: Agent sees notification entry + +- **GIVEN** an authenticated agent is viewing the home page +- **WHEN** the unread-count request succeeds +- **THEN** the home page shows the notification icon and the returned unread badge text +- **AND** activating the entry opens the notification page without resolving a target + +#### Scenario: Enterprise sees notification entry + +- **GIVEN** an authenticated enterprise user is viewing the home page +- **WHEN** the home page renders its shortcuts +- **THEN** the notification entry is available with the same icon and behavior + +#### Scenario: Unread-count failure does not block home + +- **GIVEN** the authenticated home page has loaded +- **WHEN** the unread-count request fails +- **THEN** the home page remains usable +- **AND** no fabricated unread count is displayed + +### Requirement: Notification list display + +The notification page SHALL request the current account's non-expired notifications from `GET /api/admin/notifications`, display them newest first using the server order, and show each notification's title, body, category, severity, and creation time. Notification rows MUST be display-only and MUST NOT invoke `GET /api/admin/notifications/{id}/target` or navigate to another business page. + +#### Scenario: Latest notifications are displayed + +- **GIVEN** the notification page is opened +- **WHEN** the notification list request succeeds +- **THEN** the newest notification is rendered first +- **AND** each rendered notification shows its text content and read state + +#### Scenario: No notifications + +- **GIVEN** the notification page is opened +- **WHEN** the server returns an empty item list +- **THEN** the page shows an empty state +- **AND** it does not attempt target resolution + +### Requirement: Post-login unread reminder + +After a successful login redirects to the home page, the system SHALL request the newest unread notification and show it in a reminder modal when one exists. The newest unread notification MUST be the default displayed message. The reminder MUST not navigate to a controlled target. + +#### Scenario: Newest unread notification is shown after login + +- **GIVEN** a user has successfully logged in and is redirected to the home page +- **AND** at least one unread notification exists +- **WHEN** the home page finishes loading the authenticated session +- **THEN** a reminder modal displays the newest unread notification's title and body by default + +#### Scenario: No unread notification + +- **GIVEN** a user has successfully logged in and is redirected to the home page +- **WHEN** the unread notification query returns no items +- **THEN** no reminder modal is shown + +#### Scenario: Reminder query failure + +- **GIVEN** a user has successfully logged in and is redirected to the home page +- **WHEN** the newest unread notification query fails +- **THEN** the home page remains usable +- **AND** no reminder modal is shown + +### Requirement: Read on visibility + +The system SHALL mark an unread notification as read when its content becomes visible in the reminder modal or notification list. The UI MUST NOT require a manual read button, and it MUST update the local read state after initiating the read request. + +#### Scenario: Reminder marks visible notification read + +- **GIVEN** the newest unread notification is shown in the post-login reminder modal +- **WHEN** the modal becomes visible +- **THEN** the client calls `PUT /api/admin/notifications/{id}/read` for that notification +- **AND** the notification is treated as read locally without a manual action + +#### Scenario: List marks visible unread notifications read + +- **GIVEN** the notification page renders one or more unread notifications +- **WHEN** those notification rows become visible +- **THEN** the client initiates an idempotent read request for each visible unread notification +- **AND** no manual read control is rendered + +#### Scenario: Read request is idempotent + +- **GIVEN** a notification has already been marked read +- **WHEN** the client repeats the read request +- **THEN** the UI remains in the read state and the repeated request does not create a user-visible error diff --git a/openspec/changes/add-notification-entry-and-unread-reminder/tasks.md b/openspec/changes/add-notification-entry-and-unread-reminder/tasks.md new file mode 100644 index 0000000..58dcf7b --- /dev/null +++ b/openspec/changes/add-notification-entry-and-unread-reminder/tasks.md @@ -0,0 +1,18 @@ +## 1. API and session flow + +- [x] 1.1 Add typed notification models and list, unread-count, and single-read API methods from `docs/通知.md`. +- [x] 1.2 Set a pending notification-reminder marker after a valid login session is stored. + +## 2. Notification UI + +- [x] 2.1 Add the notification page and register it in `src/pages.json`. +- [x] 2.2 Add the notification shortcut and unread badge to the top user-information card using `src/static/icons/通知.png`. +- [x] 2.3 Render notification content without target resolution or controlled-resource navigation. +- [x] 2.4 Show the newest unread notification in a post-login home-page reminder modal. +- [x] 2.5 Mark each notification read when it becomes visible and remove the manual read action. + +## 3. Verification + +- [x] 3.1 Run `pnpm type-check`. +- [x] 3.2 Run the relevant ESLint and style checks. +- [x] 3.3 Verify the login-to-home reminder, newest-first display, entry badge, and read-on-visible behavior. diff --git a/src/api/notifications.ts b/src/api/notifications.ts new file mode 100644 index 0000000..79a86a6 --- /dev/null +++ b/src/api/notifications.ts @@ -0,0 +1,72 @@ +/** + * 通知模块 API + */ +import { get, put } from '@/utils/request'; + +export type NotificationCategory = 'approval' | 'expiry' | 'sync' | 'system'; +export type NotificationSeverity = 'info' | 'warning' | 'error' | 'critical'; + +/** + * 通知记录 + */ +export interface NotificationItem { + id: number; + category: NotificationCategory; + type: string; + severity: NotificationSeverity; + title: string; + body: string; + is_read: boolean; + read_at: string | null; + ref_type?: string | null; + ref_id?: string | null; + ref_key?: string | null; + created_at: string; +} + +export interface NotificationListParams { + category?: NotificationCategory; + type?: string; + severity?: NotificationSeverity; + is_read?: boolean; + page?: number; + page_size?: number; +} + +export interface NotificationListResponse { + items: NotificationItem[]; + page: number; + size: number; + total: number; +} + +export interface NotificationUnreadCountResponse { + count: number; + display_count: string; +} + +/** + * 查询通知列表 + * GET /api/admin/notifications + */ +export function getNotifications(params?: NotificationListParams) { + return get('/api/admin/notifications', { params }); +} + +/** + * 查询通知未读数 + * GET /api/admin/notifications/unread-count + */ +export function getNotificationUnreadCount() { + return get('/api/admin/notifications/unread-count'); +} + +/** + * 标记单条通知已读 + * PUT /api/admin/notifications/{id}/read + */ +export function markNotificationRead(id: number) { + return put<{ success: boolean }>(`/api/admin/notifications/${id}/read`, { + data: { id }, + }); +} diff --git a/src/pages.json b/src/pages.json index 0dcf042..0312ca1 100644 --- a/src/pages.json +++ b/src/pages.json @@ -15,6 +15,14 @@ "navigationStyle": "custom" } }, + { + "path": "pages/agent-system/notifications/index", + "style": { + "navigationBarTitleText": "通知", + "navigationBarBackgroundColor": "#ffffff", + "navigationBarTextStyle": "black" + } + }, { "path": "pages/agent-system/assets/index", "style": { diff --git a/src/pages/agent-system/home/index.vue b/src/pages/agent-system/home/index.vue index 27237f0..845bd29 100644 --- a/src/pages/agent-system/home/index.vue +++ b/src/pages/agent-system/home/index.vue @@ -1,12 +1,14 @@ @@ -175,17 +244,25 @@ - - - {{ userInfo?.username || '欢迎回来' }} - - - {{ userInfo?.phone || '-' }} - + + + {{ userInfo?.username || '欢迎回来' }} + + + {{ userInfo?.phone || '-' }} + + + + + + + + {{ notificationUnreadDisplayCount }} + diff --git a/src/pages/agent-system/notifications/index.vue b/src/pages/agent-system/notifications/index.vue new file mode 100644 index 0000000..08bfca0 --- /dev/null +++ b/src/pages/agent-system/notifications/index.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/src/pages/common/login/index.vue b/src/pages/common/login/index.vue index b1460b7..3837a8b 100644 --- a/src/pages/common/login/index.vue +++ b/src/pages/common/login/index.vue @@ -74,6 +74,7 @@ const accountFocused = ref(false); const passwordFocused = ref(false); const showPassword = ref(false); const loading = ref(false); +const NOTIFICATION_REMINDER_PENDING_KEY = 'notification_reminder_pending'; let redirect = HOME_PATH; const isFormValid = computed(() => { @@ -139,6 +140,7 @@ async function submit() { // 保存用户信息到本地存储 uni.setStorageSync('user_info', sessionUser); + uni.setStorageSync(NOTIFICATION_REMINDER_PENDING_KEY, true); uni.showToast({ title: '登录成功', diff --git a/src/static/icons/通知.png b/src/static/icons/通知.png new file mode 100644 index 0000000000000000000000000000000000000000..8315cdba5cc7579f5641e70b924a35106411fce8 GIT binary patch literal 6267 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 A(f|Me literal 0 HcmV?d00001 From 81f2724a2b28856fb526c104590f3370dab32eed Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 10:52:35 +0800 Subject: [PATCH 2/7] fix: config --- .gitea/workflows/deploy.yaml | 14 +++++--------- Dockerfile | 13 ++++++++----- docker-compose.prod.yml | 3 +++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index b7d56fb..fb52fb7 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -1,9 +1,9 @@ -name: 构建并部署前端到测试环境 +name: 构建并部署前端到生产环境 on: push: branches: - - main + - develop - dev - test @@ -27,7 +27,7 @@ jobs: - name: 设置镜像标签 id: tag run: | - if [ "${{ github.ref }}" = "refs/heads/main" ]; then + if [ "${{ github.ref }}" = "refs/heads/develop" ]; then echo "tag=latest" >> $GITHUB_OUTPUT elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then echo "tag=dev" >> $GITHUB_OUTPUT @@ -51,15 +51,11 @@ jobs: docker push ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} docker push ${{ env.IMAGE_NAME }}:${{ github.sha }} - - name: 部署到本地(仅 main 分支) - if: github.ref == 'refs/heads/main' + - name: 部署到本地(仅 develop 分支) + if: github.ref == 'refs/heads/develop' run: | mkdir -p ${{ env.DEPLOY_DIR }} - echo "当前工作目录: $(pwd)" - echo "当前目录内容:" - ls -la - echo "更新部署配置文件..." cp -v docker-compose.prod.yml ${{ env.DEPLOY_DIR }}/ diff --git a/Dockerfile b/Dockerfile index 218ed88..4eece0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,25 +12,28 @@ WORKDIR /build # 安装 pnpm RUN corepack enable && corepack prepare pnpm@10.15.0 --activate -# 设置 npm 镜像源 +# 设置 pnpm 镜像源 RUN pnpm config set registry https://registry.npmmirror.com # 复制 package.json 和 pnpm-lock.yaml(利用 Docker 缓存) COPY package.json pnpm-lock.yaml ./ -# 安装依赖 +# 安装所有依赖 RUN pnpm install --frozen-lockfile +# 安装 git (用于获取 commit 信息) +RUN apk add --no-cache git + # 复制源代码 COPY . . -# 构建 H5 测试版本 -RUN pnpm build:h5-test +# 构建 H5 生产版本 +RUN pnpm run build:h5 # ================================ # 阶段 2: 运行阶段 # ================================ -FROM --platform=linux/amd64 nginx:alpine +FROM --platform=linux/amd64 nginx:1.31.2-alpine # 使用阿里云镜像源加速 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 6bc4bd3..0b3bd1f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,6 +9,9 @@ services: - '3002:80' networks: - junhong-network + tmpfs: + - /run:rw + - /tmp:rw healthcheck: test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://127.0.0.1:80/health'] interval: 30s From e85d54683fe1e83f55c6b62f5866d192ed5552e9 Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 11:00:01 +0800 Subject: [PATCH 3/7] fix: config --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4eece0d..fbfea31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ RUN apk add --no-cache git # 复制源代码 COPY . . -# 构建 H5 生产版本 -RUN pnpm run build:h5 +# 使用 development 环境构建 H5 +RUN pnpm run build:h5 --mode development # ================================ # 阶段 2: 运行阶段 From 181ba473759a21bbbe586d33d35b01b362b4f3ae Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 11:13:54 +0800 Subject: [PATCH 4/7] fix: config --- Dockerfile | 2 +- types/components.d.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index fbfea31..98a0e61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ RUN apk add --no-cache git COPY . . # 使用 development 环境构建 H5 -RUN pnpm run build:h5 --mode development +RUN pnpm run build:h5 --mode default # ================================ # 阶段 2: 运行阶段 diff --git a/types/components.d.ts b/types/components.d.ts index 6ff5ea7..c4555d8 100644 --- a/types/components.d.ts +++ b/types/components.d.ts @@ -11,8 +11,6 @@ declare module 'vue' { AgentFundSummaryCard: typeof import('./../src/components/AgentFundSummaryCard.vue')['default'] AgreePrivacy: typeof import('./../src/components/agree-privacy/index.vue')['default'] 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'] From f56e7cb92266c00d82b0101fa9e4ce8d176eb6e6 Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 11:16:17 +0800 Subject: [PATCH 5/7] fix: config --- env/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env/.env b/env/.env index 2fd8ecc..55b4e77 100644 --- a/env/.env +++ b/env/.env @@ -11,7 +11,7 @@ VITE_API_BASE_URL=https://cmp-api.boss160.cn VITE_APP_PORT=9527 # h5是否需要配置代理 (开发环境H5使用代理解决跨域) -VITE_APP_PROXY=true +VITE_APP_PROXY=false # API代理前缀 VITE_API_PREFIX=/api From ecb594dbb69beb2fec5e7ddc3aac32295a43672a Mon Sep 17 00:00:00 2001 From: luo Date: Wed, 29 Jul 2026 11:22:10 +0800 Subject: [PATCH 6/7] fix: config --- docker/nginx.conf | 10 ++++++++++ env/.env | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docker/nginx.conf b/docker/nginx.conf index 807571c..8a75535 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -17,6 +17,16 @@ server { add_header Content-Type text/plain; } + location /api/ { + proxy_pass https://cmp-api.boss160.cn; + proxy_ssl_server_name on; + proxy_ssl_name cmp-api.boss160.cn; + proxy_set_header Host cmp-api.boss160.cn; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location /h5/ { try_files $uri $uri/ /h5/index.html; } diff --git a/env/.env b/env/.env index 55b4e77..2fd8ecc 100644 --- a/env/.env +++ b/env/.env @@ -11,7 +11,7 @@ VITE_API_BASE_URL=https://cmp-api.boss160.cn VITE_APP_PORT=9527 # h5是否需要配置代理 (开发环境H5使用代理解决跨域) -VITE_APP_PROXY=false +VITE_APP_PROXY=true # API代理前缀 VITE_API_PREFIX=/api From 5216530714b1f69e5a04470d353d3a883edf05b2 Mon Sep 17 00:00:00 2001 From: luo Date: Mon, 24 Aug 2026 09:37:36 +0800 Subject: [PATCH 7/7] fix: ui --- src/components/AgentFundSummaryCard.vue | 202 +- src/components/ShopCascadePicker.vue | 250 +-- src/components/SimplePicker.vue | 198 +- src/components/Skeleton.vue | 40 +- src/pages/agent-system/asset-detail/index.vue | 1757 +++++++++------- src/pages/agent-system/asset-wallet/index.vue | 271 +-- src/pages/agent-system/assets/index.vue | 1768 +++++++++-------- .../agent-system/commission-center/index.vue | 450 +++-- src/pages/agent-system/home/index.vue | 667 +++---- .../agent-system/notifications/index.vue | 4 +- src/pages/agent-system/user-info/index.vue | 286 +-- src/pages/agent-system/withdraw/index.vue | 550 ++--- src/pages/common/login/index.vue | 35 +- src/static/icons/佣金中心.png | Bin 145640 -> 17823 bytes src/static/icons/修改密码.png | Bin 111617 -> 6151 bytes src/static/icons/右边.png | Bin 0 -> 4420 bytes src/static/icons/复制.png | Bin 0 -> 3602 bytes src/static/icons/授权卡列表.png | Bin 117376 -> 10633 bytes src/static/icons/授权设备列表.png | Bin 89754 -> 9715 bytes src/static/icons/提现管理.png | Bin 176913 -> 8092 bytes src/static/icons/资产列表.png | Bin 139634 -> 11571 bytes src/static/icons/退出登录.png | Bin 112148 -> 4613 bytes src/static/styles/theme.scss | 36 +- 23 files changed, 3608 insertions(+), 2906 deletions(-) create mode 100644 src/static/icons/右边.png create mode 100644 src/static/icons/复制.png diff --git a/src/components/AgentFundSummaryCard.vue b/src/components/AgentFundSummaryCard.vue index ddafca6..d6a13e8 100644 --- a/src/components/AgentFundSummaryCard.vue +++ b/src/components/AgentFundSummaryCard.vue @@ -1,24 +1,142 @@ - - - diff --git a/src/components/ShopCascadePicker.vue b/src/components/ShopCascadePicker.vue index 293ee2d..db57606 100644 --- a/src/components/ShopCascadePicker.vue +++ b/src/components/ShopCascadePicker.vue @@ -1,107 +1,3 @@ - -