chore: update project index and meeting notes for April 2, 2026

- Added new entries for meetings discussing work progress and requirement synchronization across various project components.
- Updated the last modified date to April 2, 2026, in multiple project index files.
- Documented changes in the sync log for assistant-doc-sync regarding the meeting outcomes.
This commit is contained in:
Alex-larget
2026-04-02 11:03:23 +08:00
parent 360bba697b
commit a696b0cefc
25 changed files with 1391 additions and 1207 deletions

View File

@@ -0,0 +1,7 @@
# 产品经理 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **场景**4 月初全员同步进度,衔接 2026-03-31 超级个体与 @ 列表融合待办。
- **要点**融合类需求需同步明确「一期验收口径」UsersPage @ 状态、列表入口是否收敛),并写入需求汇总/运营与变更,避免实现先行、验收滞后。
- **待办**:问题与作答区 Q1 由产品补充作答后闭环。

View File

@@ -0,0 +1,8 @@
# 后端工程师 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **场景**:进度同步 + deploy 目录变更与业务待续项并行。
- **要点**`soul-api/deploy` 改动合并前应用 compose或等价验证README 与 compose 环境变量保持一致vip-members 聚合 Person 字段优先,满足管理端 UsersPage 联调。
- **路由**miniprogram / admin / db 分组边界不变。
- **待办**:问题与作答区 Q2、Q3 作答后闭环。

View File

@@ -0,0 +1,8 @@
# 团队 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **跨角色决议**
- 融合方案落地顺序:后端 vip-members 聚合 → 管理端 UsersPage → 测试全量回归清单;小程序回归为主。
- 部署变更与业务 PR 区分 scopedeploy 合并前环境验证必做。
- **详见**`.cursor/meeting/2026-04-02_工作进度与需求同步会.md`

View File

@@ -0,0 +1,6 @@
# 小程序开发工程师 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **场景**:超级个体与 @ 列表融合对 C 端以回归为主。
- **要点**`app.js` / 全局 `app.request` 类改动需列出必测页面(读/我的/登录/提现主路径);融合不默认改 miniprogram 接口路径。

View File

@@ -29,9 +29,10 @@ Soul 创业派对产品定位:面向创业者的社区/工具型小程序。
| 2026-03-24 | 会议:开发进度同步;项目索引补齐 2026-03-19/20 及之后记录;文档同步原则确认 | 已完成 |
| 2026-03-24 | 会议:需求与进度及三端闭环评审;规则/风控缺口清单化VIP 支付前资料规则待拍板 | 已完成 |
| 2026-03-31 | 会议:超级个体列表与 @ 列表融合方案(先数据互链再单页);一期验收口径待拍板 | 待续 |
| 2026-04-02 | 会议:工作进度与需求同步;一期融合验收口径澄清、各端索引与需求 delta 跟进 | 进行中 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-31
**最后更新**2026-04-02

View File

@@ -24,9 +24,10 @@
| 2026-03-17 | 吸收新需求代付统一到代付页gift=1&ref redirectTo→ 需求汇总、找朋友代付流程、运营与变更 | 已完成 |
| 2026-03-17 | 会议收尾:新版管理端迁移到稳定版实施方案确认;纪要、各角色经验入库、项目索引更新 | 已完成 |
| 2026-03-17 | 会议收尾:源码优化完成与测试流程定稿;纪要、经验入库、开发文档同步 | 已完成 |
| 2026-04-02 | 会议收尾:工作进度与需求同步会;纪要、各角色 evolution、项目索引与会议索引更新 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-17(会议收尾:源码优化完成与测试流程定稿
**最后更新**2026-04-02(会议收尾:工作进度与需求同步会

View File

@@ -42,9 +42,10 @@ soul-apiGo + Gin + GORM + MySQL提供三组路由`/api/miniprogram/*`
| 2026-03-24 | 会议开发进度同步项目索引补齐router/ossConfig 待确认 | 已完成 |
| 2026-03-24 | 会议:三端闭环评审;提现/CKB/匹配后端校验与 errorCode 为 P0/P1 | 已完成 |
| 2026-03-31 | 会议:超级个体与 @ 列表融合;优先扩展 vip-members 聚合 Person 关联字段 | 待续 |
| 2026-04-02 | 会议工作进度与需求同步deploy 验证后提交vip-members 聚合优先router/ossConfig 历史待办跟进 | 进行中 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-31
**最后更新**2026-04-02

View File

@@ -19,7 +19,6 @@ Soul 创业派对全项目架构与约定路由隔离miniprogram/admin/db
| 2026-03-08 | 文章阅读付费规则澄清:免费章节以 free_chapters 为准VIP 全章免费;后端 book.go 合并配置修复 | 已完成 |
| 2026-03-10 | 会议:管理端迁移 Mycontent-temp新菜单/布局作为团队规范基线,避免两套后台并行发散 | 待续 |
| 2026-03-10 | 新旧版代码对比方法论:以功能完整性而非日期判断优劣,批量 diff + 分类取舍 + 移植修复 | 已完成 |
| 2026-03-11 | 以界面定需求文档建立;需求基准与业务逻辑对齐(用户/VIP 资料、三端路由);会议收尾纪要与各角色经验入库 | 已完成 |
| 2026-03-12 | 密钥/token 设计:关联小程序 key、@ 人物 token不暴露真实密钥、服务端兑换 | 已完成 |
| 2026-03-12 | 9.9 买断团队约定:后端统一输出 hasFullBook/has_full_book小程序和管理端只认该信号支持通过用户资料开关 OR 订单赠送全书且不影响 VIP 逻辑 | 已完成 |
@@ -33,9 +32,10 @@ Soul 创业派对全项目架构与约定路由隔离miniprogram/admin/db
| 2026-03-18 | 会议:超级个体开通后自动创建@人统一走 Person幂等键绑定 userId默认资料 flags 后端输出 | 已完成 |
| 2026-03-24 | 会议:需求与进度及三端闭环评审;主路径闭环成立、规则闭环待加固;失败响应与 err.response 契约 | 已完成 |
| 2026-03-31 | 会议:超级个体与 @ 列表融合;先数据与入口融合、路由边界不变 | 已完成 |
| 2026-04-02 | 会议:工作进度与需求同步;融合落地顺序与 deploy 验证门禁、问题与作答区待闭环 | 已完成 |
> **格式说明**:每次架构级讨论后在此追加一行,日期格式 YYYY-MM-DD
---
**最后更新**2026-03-31
**最后更新**2026-04-02

View File

@@ -48,9 +48,10 @@
| 2026-03-24 | 落地app.request 挂 err.responsesoulBridge 留资扩展;阅读页带码与 referral/my 提现 catch 对齐 | 已完成 |
| 2026-03-30 | 会议:文章详情 # 跳转关联小程序(如 MBTI时 path 追加 `phone`read.js 已实现 | 已完成 |
| 2026-03-31 | 会议:超级个体与 @ 列表融合C 端默认不改接口,仅回归 | 已完成 |
| 2026-04-02 | 会议:工作进度与需求同步;全局 app/request 类改动需主路径冒烟;融合仍以回归为主 | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-31
**最后更新**2026-04-02

View File

@@ -35,7 +35,8 @@
| 2026-03-24 | 会议:开发进度同步;项目索引补齐;提现/登录/收益/推广设置用例补充singlePage、getPhoneNumber 边界 | 已完成 |
| 2026-03-24 | 会议三端闭环评审API 绕过负例与双入口提现回归待契约落地后执行 | 已完成 |
| 2026-03-31 | 会议:超级个体与 @ 列表融合;回归清单含排序/@/绑定/Webhook 边界 | 待续 |
| 2026-04-02 | 会议工作进度与需求同步融合清单待联调后执行deploy PR 增加 compose/health/smoke | 待续 |
---
**最后更新**2026-03-31
**最后更新**2026-04-02

View File

@@ -47,11 +47,12 @@
| 2026-03-24 | 会议开发进度同步项目索引补齐DistributionPage Order.description 待修 | 已完成 |
| 2026-03-24 | 会议:三端闭环评审;管理端与小程序配套整体齐全;类型债择机修 | 已完成 |
| 2026-03-31 | 会议:超级个体与 @ 列表融合UsersPage 表格加 @ 状态与跳转内容页 | 待续 |
| 2026-04-02 | 会议工作进度与需求同步UsersPage 待接口字段就绪后联调deploy 文档基址与后端对齐 | 进行中 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-31
**最后更新**2026-04-02
> soul-admin 构建仍有 DistributionPage Order.description 类型错误(与本次迁移无关),待修复。

View File

@@ -0,0 +1,6 @@
# 管理端开发工程师 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **场景**UsersPage @ 状态与跳转内容页依赖后端 vip-members 扩展。
- **要点**先锁定聚合接口字段契约再改表格列与跳转deploy 文档若涉及管理端访问基址,与后端 README 对齐。

View File

@@ -0,0 +1,6 @@
# 软件测试 经验记录 - 2026-04-02
## 会议:工作进度与需求同步会
- **场景**:融合方案回归与部署类变更验证策略。
- **要点**3/31 清单(排序、@、绑定、Webhook待联调就绪后执行`deploy/` 的 PR 建议增加 compose 起服务、`/health`、核心阅读/下单 smoke。

View File

@@ -0,0 +1,116 @@
# 会议纪要 - 2026-04-02 | 工作进度与需求同步会
> 本文件由**助理橙子**在会议结束后自动生成。
---
## 基本信息
- **时间**2026-04-02
- **议题**:同步各端工作进度与需求口径,衔接 2026-03-31 以来待办
- **触发方式**:开个会议,同步大家工作进度和需求
- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员、团队(乘风主持)
---
## 各角色发言
### 【产品经理】
- 自 [2026-03-31 超级个体与 @ 列表融合](2026-03-31_超级个体与@列表融合.md) 后,**一期验收口径**UsersPage @ 状态展示、vip-members 与 Person 关联展示边界)仍需与业务方确认后写入需求汇总/运营与变更。
- 《以界面定需求》仍为验收基准;若 4 月有新入口或列表合并,需补界面清单与优先级。
- 建议各端在索引中标注「3/31 融合方案」相关项的**完成度**(未开始/开发中/已联调)。
### 【后端开发】
- 当前仓库工作区显示 **`soul-api/deploy/`**Dockerfile、docker-compose.yml、README有未提交修改属**部署与运维文档**迭代,合并前应在目标环境或本地 compose 跑通并核对环境变量说明。
- 3/31 纪要待续:**vip-members 聚合 Person 关联字段**优先于管理端表格增强;历史待办 **router 补齐、ossConfig 确认**仍建议按迁移清单闭环,避免预发与生产配置漂移。
- 路由分组原则不变:小程序仅 `miniprogram`,管理端 `admin`/`db`,禁止混挂。
### 【管理端开发工程师】
- 3/31 待续:**UsersPage** 表格增加 @ 状态与跳转内容页依赖后端 vip-members或等价接口字段稳定后再收尾联调。
- 若 deploy 文档更新涉及管理端访问地址或 CORS/网关,与后端对齐 README 中的示例 URL。
### 【小程序开发工程师】
- 融合方案约定:**C 端默认不改接口、以回归为主**;若 `app.js` 或全局 `app.request` 有变更,需同步走提现/登录/阅读主路径冒烟。
- 文章详情、`#` 跳转关联小程序传 `phone` 等已落地能力纳入常规回归,避免大文件改动时漏测。
### 【测试人员】
- 3/31 会议已列回归关注点:**排序、@ 展示、绑定、Webhook 边界**;待后端与管理端联调就绪后执行一轮三端联调。
- **部署变更**合并后建议增加:**compose 起服务、`/health`、核心下单/阅读** 的轻量 smoke防止仅文档改动的 PR 遗漏镜像/端口问题。
---
## 讨论过程
- **【乘风】**:衔接 2026-03-24 同步会与 2026-03-31 融合会4 月初重点是把「融合方案待续」从纸面推进到**可测状态**,同时不阻塞部署侧改进。
- **【后端开发】回复【产品经理】**vip-members 扩展字段确定后,可在接口文档或 OpenAPI 注释中标明,便于产品写验收点。
- **【测试人员】回复【后端开发】**deploy 变更若影响数据库或 Redis 连接串示例,请在 README 标明「必填/可选」,减少环境误配。
---
## 会议决议
1. **进度同步机制**:自 2026-04-02 起,各角色在实现跨 3/31 边界的改动时,**当日或次日**在对应 `项目索引/*.md` 追加一行进度;产品侧大块需求变更同步《需求汇总》或《运营与变更》。
2. **部署变更**`soul-api/deploy/*` 合并前完成 **compose或等价验证**README 与 compose 中镜像名、端口、环境变量保持一致。
3. **超级个体与 @ 列表融合**:执行顺序为 **后端 vip-members 聚合字段 → 管理端 UsersPage 联调 → 测试按 3/31 清单回归**;小程序按原约定以回归为主。
4. **待确认项**:一期验收口径(@ 状态定义、列表合并后是否隐藏旧入口)由产品在本周内澄清并回写「问题与作答区」。
---
## 待办事项
| 责任角色 | 任务 | 优先级 | 截止建议 |
|---------|------|--------|---------|
| 产品经理 | 澄清一期融合验收口径并更新需求/运营与变更 | 高 | 2026-04-09 前 |
| 后端开发 | vip-members 聚合 Person 关联deploy 改动验证后提交 | 高 | 按迭代节奏 |
| 管理端开发工程师 | UsersPage @ 状态与跳转,待接口字段就绪后联调 | 中 | 接口就绪后 3 个工作日内 |
| 小程序开发工程师 | 全局请求/登录相关改动后主路径冒烟 | 中 | 每次相关 PR 前 |
| 测试人员 | 融合清单回归 + deploy 合并后 smoke | 中 | 联调就绪后 |
---
## 问题与作答区
> 会议中提出的待确认问题在此列出;作答区域供后续补充答案,便于追溯闭环。
| # | 问题 | 责任角色 | 作答 |
|---|------|---------|------|
| 1 | 超级个体与 @ 列表融合「一期」验收UsersPage @ 状态的具体展示规则(文案/图标/空态)是否已定? | 产品经理 | (待补充) |
| 2 | soul-api deploy 修改是否计划同步到预发/生产流水线CI 变量、镜像仓库)?由谁 owner | 后端开发 / 团队 | (待补充) |
| 3 | router 补齐users/rfm、journey-stats、shensheshou与 ossConfig 当前环境是否已全部一致? | 后端开发 | (待补充) |
---
## 各角色经验与业务理解更新
### 产品经理
- 大方案(如列表融合)会后需**显式一期验收口径**,避免开发按技术合并做完后产品口径滞后。
### 后端开发
- **Deploy 与业务代码**同仓时PR 应区分「纯部署」与「接口变更」,便于测试选 smoke 范围。
### 管理端开发工程师
- 表格增强依赖聚合接口时,先锁定**字段契约**再改列与跳转,减少联调往返。
### 小程序开发工程师
- 全局 `app.js` 改动影响面大,变更说明中写明**必测页面列表**。
### 测试人员
- 部署类 PR 默认增加 **compose + health + 主路径** 最小验证集。
### 团队共享
- **融合方案执行顺序**:后端数据与接口 → 管理端展示 → 小程序回归 → 全量回归清单;三端路由隔离不变。
---
*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-04-02.md`*

View File

@@ -84,3 +84,4 @@ YYYY-MM-DD_会议主题.md
| 2026-03-24 | 需求与进度及三端闭环评审(业务闭环、核心功能、小程序与管理端配套) | 产品、后端、管理端、小程序、测试 | [2026-03-24_需求与进度及三端闭环评审.md](2026-03-24_需求与进度及三端闭环评审.md) |
| 2026-03-30 | 文章详情 # 标签跳转 MBTI 小程序传手机号 | 产品、后端、管理端、小程序、测试 | [2026-03-30_文章详情跳转MBTI小程序传手机号.md](2026-03-30_文章详情跳转MBTI小程序传手机号.md) |
| 2026-03-31 | 超级个体列表与 @ 列表融合方案 | 产品、后端、管理端、小程序、测试、团队 | [2026-03-31_超级个体与@列表融合.md](2026-03-31_超级个体与@列表融合.md) |
| 2026-04-02 | 工作进度与需求同步会 | 产品、后端、管理端、小程序、测试、团队 | [2026-04-02_工作进度与需求同步会.md](2026-04-02_工作进度与需求同步会.md) |

View File

@@ -6,6 +6,10 @@
---
## 2026-04-02
- 会议收尾:工作进度与需求同步会;纪要 `.cursor/meeting/2026-04-02_工作进度与需求同步会.md`;各角色 evolution、项目索引产品/后端/管理端/小程序/测试/团队、meeting/README 已更新
## 2026-03-31
- 会议收尾:超级个体列表与 @ 列表融合;纪要 `.cursor/meeting/2026-03-31_超级个体与@列表融合.md`;各角色 evolution 与项目索引已更新

View File

@@ -12,7 +12,9 @@ const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
// baseUrl 手动切换(注释方式):
// const API_BASE_URL = 'http://localhost:9090'
const API_BASE_URL = 'https://soulapi.quwanzhi.com'
// const API_BASE_URL = 'https://soulapi.quwanzhi.com'
const API_BASE_URL = 'https://souldev.quwanzhi.com'
const CONFIG_CACHE_KEY = 'mpConfigCacheV1'
/** 新用户未完成头像+昵称前,强制留在资料引导(与 avatar-nickname 页配合) */
const PROFILE_GUIDE_REQUIRED_KEY = 'profile_guide_required'

File diff suppressed because one or more lines are too long

1047
soul-admin/dist/assets/index-zk0czXxa.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管理后台 - Soul创业派对</title>
<script type="module" crossorigin src="/assets/index-BSvDfqaj.js"></script>
<script type="module" crossorigin src="/assets/index-zk0czXxa.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CuUAjfcM.css">
</head>
<body>

View File

@@ -51,6 +51,7 @@ import {
Pencil,
Users,
Share2,
Webhook,
} from 'lucide-react'
import { get, put, post, del, SAVE_REQUEST_TIMEOUT } from '@/api/client'
import { ChapterTree } from './ChapterTree'
@@ -317,6 +318,11 @@ export function ContentPage() {
const [personModalOpen, setPersonModalOpen] = useState(false)
const [editingPerson, setEditingPerson] = useState<PersonItem | null>(null)
const [personToDelete, setPersonToDelete] = useState<PersonItem | null>(null)
/** 超级个体用户 id → 飞书群 Webhook与 /api/db/vip-members 一致,供 @列表展示与编辑) */
const [vipWebhookByUserId, setVipWebhookByUserId] = useState<Record<string, string>>({})
const [personVipWebhookTarget, setPersonVipWebhookTarget] = useState<{ userId: string; name: string } | null>(null)
const [personVipWebhookInput, setPersonVipWebhookInput] = useState('')
const [personVipWebhookSaving, setPersonVipWebhookSaving] = useState(false)
// CKB 获客统计(按人物 token 聚合)
const [ckbLeadCounts, setCkbLeadCounts] = useState<Record<string, number>>({})
const [ckbLeadDetailOpen, setCkbLeadDetailOpen] = useState(false)
@@ -585,7 +591,21 @@ export function ContentPage() {
personSource?: string
isPinned?: boolean
}
const data = await get<{ success?: boolean; persons?: PersonResp[] }>('/api/db/persons')
type VipRow = { id: string; webhookUrl?: string | null }
const [data, vipRes] = await Promise.all([
get<{ success?: boolean; persons?: PersonResp[] }>('/api/db/persons'),
get<{ success?: boolean; data?: VipRow[]; error?: string }>('/api/db/vip-members?limit=500').catch(() => null),
])
const whMap: Record<string, string> = {}
if (vipRes?.success && Array.isArray(vipRes.data)) {
for (const m of vipRes.data) {
const uid = (m.id || '').trim()
if (!uid) continue
const w = m.webhookUrl != null ? String(m.webhookUrl).trim() : ''
if (w) whMap[uid] = w
}
}
setVipWebhookByUserId(whMap)
if (data?.success && data.persons) {
setPersons(
data.persons.map((p) => {
@@ -643,6 +663,50 @@ export function ContentPage() {
[loadPersons],
)
const openPersonVipWebhook = useCallback((p: PersonItem) => {
const uid = (p.userId || '').trim()
if (!uid) {
toast.info('未绑定会员的人物无法配置:飞书群 Webhook 按「超级个体用户」维度保存,请先为该人物绑定会员用户。')
return
}
setPersonVipWebhookTarget({ userId: uid, name: p.name })
setPersonVipWebhookInput((vipWebhookByUserId[uid] || '').trim())
}, [vipWebhookByUserId])
const handleSavePersonVipWebhook = useCallback(async () => {
if (!personVipWebhookTarget) return
const uid = personVipWebhookTarget.userId
const val = personVipWebhookInput.trim()
if (val && !/^https?:\/\//i.test(val)) {
toast.error('Webhook 地址需以 http/https 开头')
return
}
setPersonVipWebhookSaving(true)
try {
const res = await put<{ success?: boolean; error?: string }>('/api/db/vip-members/webhook', {
userId: uid,
webhookUrl: val,
})
if (!res?.success) {
toast.error(res?.error || '保存飞书群 Webhook 失败')
return
}
toast.success(val ? '已保存该超级个体的飞书群 Webhook' : '已清空该超级个体的飞书群 Webhook')
setPersonVipWebhookTarget(null)
setPersonVipWebhookInput('')
setVipWebhookByUserId((prev) => {
const next = { ...prev }
if (val) next[uid] = val
else delete next[uid]
return next
})
} catch {
toast.error('保存飞书群 Webhook 失败')
} finally {
setPersonVipWebhookSaving(false)
}
}, [personVipWebhookTarget, personVipWebhookInput])
const loadLinkTags = useCallback(async () => {
try {
const data = await get<{
@@ -2764,6 +2828,9 @@ export function ContentPage() {
<p>
<span className="text-purple-300"></span> @ plan/apiKey
</p>
<p>
<span className="text-[#38bdac]"> Webhook</span>线 Webhook
</p>
<p>SOUL链接人与事-</p>
</div>
</CardHeader>
@@ -2806,6 +2873,7 @@ export function ContentPage() {
<th className="text-left py-1.5 px-3 w-[100px] font-normal text-xs"></th>
<th className="py-1.5 px-3 w-16 font-normal text-center"></th>
<th className="text-left py-1.5 px-3 font-normal"></th>
<th className="text-left py-1.5 px-2 w-[72px] font-normal text-xs"></th>
<th className="text-center py-1.5 px-2 w-14 font-normal text-xs"></th>
<th className="text-left py-1.5 px-3 w-16 font-normal"></th>
<th className="text-left py-1.5 px-2 w-24 font-normal"></th>
@@ -2874,6 +2942,17 @@ export function ContentPage() {
</span>
</div>
</td>
<td className="py-2 px-2 text-xs">
{p.userId ? (
vipWebhookByUserId[(p.userId || '').trim()] ? (
<span className="text-[#38bdac]" title={vipWebhookByUserId[(p.userId || '').trim()]}></span>
) : (
<span className="text-gray-500"></span>
)
) : (
<span className="text-gray-600"></span>
)}
</td>
<td className="py-2 px-2 text-center">
<Button
type="button"
@@ -2899,6 +2978,16 @@ export function ContentPage() {
</td>
<td className="py-2 px-2">
<div className="flex items-center gap-0">
<Button
variant="ghost"
size="sm"
className={`h-6 px-2 ${p.userId ? 'text-[#38bdac] hover:text-[#5fe0cd]' : 'text-gray-600 cursor-not-allowed opacity-50'}`}
title={p.userId ? '设置飞书群 Webhook点击头像留资推送' : '未绑定会员,无法配置'}
disabled={!p.userId}
onClick={() => openPersonVipWebhook(p)}
>
<Webhook className="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="sm"
@@ -3631,6 +3720,59 @@ export function ContentPage() {
}}
/>
{/* @列表 — 超级个体飞书群 Webhook用户维度PUT /api/db/vip-members/webhook */}
<Dialog
open={!!personVipWebhookTarget}
onOpenChange={(open) => {
if (!open) {
setPersonVipWebhookTarget(null)
setPersonVipWebhookInput('')
}
}}
>
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-xl">
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<Webhook className="w-5 h-5 text-[#38bdac]" />
Webhook {personVipWebhookTarget?.name}
</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<Label className="text-gray-300 text-sm">VOX Webhook </Label>
<Input
className="bg-[#0a1628] border-gray-700 text-white"
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..."
value={personVipWebhookInput}
onChange={(e) => setPersonVipWebhookInput(e.target.value)}
/>
<p className="text-xs text-gray-500">
线线
</p>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setPersonVipWebhookTarget(null)
setPersonVipWebhookInput('')
}}
className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
>
<X className="w-4 h-4 mr-2" />
</Button>
<Button
onClick={() => void handleSavePersonVipWebhook()}
disabled={personVipWebhookSaving}
className="bg-[#38bdac] hover:bg-[#2da396] text-white"
>
<Save className="w-4 h-4 mr-2" />
{personVipWebhookSaving ? '保存中...' : '保存'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 删除人物二次确认弹窗 */}
<Dialog open={!!personToDelete} onOpenChange={(open) => { if (!open) setPersonToDelete(null) }}>
<DialogContent showCloseButton={true} className="bg-[#0f2137] border-gray-700 text-white max-w-md p-4 gap-3">

View File

@@ -152,7 +152,6 @@ interface VipMember {
mbti?: string | null
vipRole?: string | null
vipSort?: number | null
webhookUrl?: string | null
/** 首页超级个体卡片点击次数(/api/db/vip-members 聚合 user_tracks */
clickCount?: number | null
/** 绑定人物后的去重获客人数 */
@@ -826,11 +825,6 @@ export function UsersPage() {
const [vipRoleModalMember, setVipRoleModalMember] = useState<VipMember | null>(null)
const [vipRoleInput, setVipRoleInput] = useState('')
const [vipRoleSaving, setVipRoleSaving] = useState(false)
const [showVipWebhookModal, setShowVipWebhookModal] = useState(false)
const [vipWebhookModalMember, setVipWebhookModalMember] = useState<VipMember | null>(null)
const [vipWebhookInput, setVipWebhookInput] = useState('')
const [vipWebhookSaving, setVipWebhookSaving] = useState(false)
const VIP_ROLE_PRESETS = ['创业者', '资源整合者', '技术达人', '投资人', '产品经理', '流量操盘手']
const openVipRoleModal = (member: VipMember) => {
@@ -839,12 +833,6 @@ export function UsersPage() {
setShowVipRoleModal(true)
}
const openVipWebhookModal = (member: VipMember) => {
setVipWebhookModalMember(member)
setVipWebhookInput((member.webhookUrl || '').trim())
setShowVipWebhookModal(true)
}
const handleSetVipRole = async (value: string) => {
const trimmed = value.trim()
if (!vipRoleModalMember) return
@@ -873,34 +861,6 @@ export function UsersPage() {
}
}
const handleSetVipWebhook = async () => {
if (!vipWebhookModalMember) return
const val = vipWebhookInput.trim()
if (val && !/^https?:\/\//i.test(val)) {
toast.error('Webhook 地址需以 http/https 开头')
return
}
setVipWebhookSaving(true)
try {
const res = await put<{ success?: boolean; error?: string }>('/api/db/vip-members/webhook', {
userId: vipWebhookModalMember.id,
webhookUrl: val,
})
if (!res?.success) {
toast.error(res?.error || '保存飞书群 Webhook 失败')
return
}
toast.success(val ? '已保存该超级个体的飞书群 Webhook' : '已清空该超级个体的飞书群 Webhook')
setShowVipWebhookModal(false)
setVipWebhookModalMember(null)
await loadVipMembers()
} catch {
toast.error('保存飞书群 Webhook 失败')
} finally {
setVipWebhookSaving(false)
}
}
const [showVipSortModal, setShowVipSortModal] = useState(false)
const [vipSortModalMember, setVipSortModalMember] = useState<VipMember | null>(null)
const [vipSortInput, setVipSortInput] = useState('')
@@ -2212,6 +2172,11 @@ export function UsersPage() {
<p className="text-gray-400 text-sm">
VIP
</p>
<p className="text-xs text-gray-500">
Webhook线{' '}
<span className="text-[#38bdac]"> @列表</span>
</p>
<p className="text-xs text-[#38bdac]">
</p>
@@ -2253,8 +2218,7 @@ export function UsersPage() {
<TableHead className="text-gray-400 w-16 text-center"></TableHead>
<TableHead className="text-gray-400 w-16 text-center"></TableHead>
<TableHead className="text-gray-400 w-20"></TableHead>
<TableHead className="text-gray-400 w-36"></TableHead>
<TableHead className="text-gray-400 w-36 text-right"></TableHead>
<TableHead className="text-gray-400 w-28 text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -2315,15 +2279,6 @@ export function UsersPage() {
<TableCell className="text-gray-300">
{m.vipSort ?? index + 1}
</TableCell>
<TableCell className="text-xs">
{m.webhookUrl ? (
<span className="text-[#38bdac] truncate block max-w-[180px]" title={m.webhookUrl}>
</span>
) : (
<span className="text-gray-500"></span>
)}
</TableCell>
<TableCell className="text-right text-xs text-gray-300">
<div className="inline-flex items-center gap-1.5">
<Button
@@ -2335,15 +2290,6 @@ export function UsersPage() {
>
<Tag className="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-7 w-7 px-0 text-[#38bdac] hover:text-[#5fe0cd]"
onClick={() => openVipWebhookModal(m)}
title="编辑飞书群Webhook"
>
<Edit3 className="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
@@ -2454,38 +2400,6 @@ export function UsersPage() {
</DialogContent>
</Dialog>
{/* 设置超级个体飞书群 Webhook */}
<Dialog open={showVipWebhookModal} onOpenChange={(open) => { setShowVipWebhookModal(open); if (!open) setVipWebhookModalMember(null) }}>
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-xl">
<DialogHeader>
<DialogTitle className="text-white flex items-center gap-2">
<Edit3 className="w-5 h-5 text-[#38bdac]" />
Webhook {vipWebhookModalMember?.name}
</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<Label className="text-gray-300 text-sm">VOX Webhook </Label>
<Input
className="bg-[#0a1628] border-gray-700 text-white"
placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..."
value={vipWebhookInput}
onChange={(e) => setVipWebhookInput(e.target.value)}
/>
<p className="text-xs text-gray-500">
线
</p>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setShowVipWebhookModal(false)} className="border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent">
<X className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleSetVipWebhook} disabled={vipWebhookSaving} className="bg-[#38bdac] hover:bg-[#2da396] text-white">
<Save className="w-4 h-4 mr-2" />{vipWebhookSaving ? '保存中...' : '保存'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={showUserModal} onOpenChange={setShowUserModal}>
<DialogContent className="bg-[#0f2137] border-gray-700 text-white max-w-lg">
<DialogHeader><DialogTitle className="text-white flex items-center gap-2">{editingUser ? <Edit3 className="w-5 h-5 text-[#38bdac]" /> : <UserPlus className="w-5 h-5 text-[#38bdac]" />}{editingUser ? '编辑用户' : '添加用户'}</DialogTitle></DialogHeader>

View File

@@ -1,3 +1,5 @@
# 以下由「部署管理器」根据项目配置在保存时自动生成/更新
# 根据「构建命令」解析编译目标;可在项目表单中修改 buildCmd
FROM golang:1.25 AS builder
WORKDIR /src
@@ -7,7 +9,6 @@ RUN go mod download
COPY . .
# Build server binary
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/soul-api ./cmd/server
FROM alpine:3.19
@@ -18,17 +19,16 @@ RUN apk add --no-cache ca-certificates tzdata wget && \
addgroup -S app && adduser -S -G app app
COPY --from=builder /out/soul-api /app/soul-api
COPY certs/ /app/certs/
# Runtime directories used by the app
# 若需要微信支付证书,创建 certs/ 后取消下行注释
# COPY certs/ /app/certs/
RUN mkdir -p /app/uploads /app/log && chown -R app:app /app
ENV APP_ENV=production
ENV GIN_MODE=release
ENV PORT=8080
ENV UPLOAD_DIR=/app/uploads
ENV WECHAT_CERT_PATH=/app/certs/apiclient_cert.pem
ENV WECHAT_KEY_PATH=/app/certs/apiclient_key.pem
EXPOSE 8080

View File

@@ -1,56 +1,8 @@
# soul-api Docker 部署说明
# 以下由「部署管理器」根据项目配置在保存时自动生成/更新
# 测试环境soul · deploy
## 1) 打包运行后必需的文件和目录
- **.env**Compose 变量(`COMPOSE_PROJECT`、宿主机上传/日志目录、网关镜像等),与部署管理器远端流程一致。
- **docker-compose.bg.yml** / **nginx.bg.conf**:蓝绿 + 网关;容器端口 **8080**,健康检查 **/health**。
- **Dockerfile**`deploy/Dockerfile`):由表单「构建命令」解析 `go build -o` 目标。
### 必需文件
- `soul-api` 可执行文件Docker 镜像内已包含)
- `.env`(或等价环境变量;本方案使用 `deploy/app.env`
- `certs/apiclient_cert.pem`
- `certs/apiclient_key.pem`
### 必需目录
- `uploads/`:上传文件持久化目录
- `log/`:订单同步等日志目录
> 说明:`/app/uploads`、`/app/log` 是容器内目录;本方案已挂载到服务器目录:
> - `/www/wwwroot/self/soul-dev/uploads -> /app/uploads`
> - `/www/wwwroot/self/soul-dev/log -> /app/log`
## 2) 本地/服务器快速启动
`soul-api/deploy` 目录下执行:
1. 复制环境文件:
- `cp app.env.example app.env`
- 按实际环境填写 `DB_DSN`、微信和管理端密钥
2. 启动:
- `docker compose up -d --build`
3. 查看状态:
- `docker compose ps`
- `docker compose logs -f app`
4. 健康检查:
- `http://服务器IP:APP_PORT/health`
## 3) 给宝塔的推荐方式
- 方式 A推荐在宝塔 Docker/Compose 项目里直接使用 `deploy/docker-compose.yml`
- 方式 B`docker build -f deploy/Dockerfile -t soul-api:latest ..`,再在宝塔按镜像创建容器
## 4) 目录结构deploy
- `deploy/Dockerfile`:多阶段构建 Go 应用镜像
- `deploy/docker-compose.yml`:单容器运行与数据卷持久化
- `deploy/app.env.example`:环境变量模板
## 5) 离线镜像部署(不走远端拉取)
你可以把本地导出的 `deploy/soul-api_latest.tar` 上传到服务器,然后:
1. 导入镜像:
- `docker load -i soul-api_latest.tar`
2. 准备配置:
-`deploy/app.env` 放到服务器同目录(或按需改 `env_file`
3. 启动:
- `docker compose up -d --no-build`
> 这种方式不会再从 Docker Hub 拉取 `soul-api` 业务镜像;直接使用你导入的本地镜像运行。
修改构建配置后请再次保存项目以同步本目录。

View File

@@ -1,3 +1,4 @@
# 以下由「部署管理器」根据项目配置在保存时自动生成/更新
version: "3.8"
services:
@@ -5,13 +6,13 @@ services:
build:
context: ..
dockerfile: deploy/Dockerfile
image: soul-api:latest
container_name: soul-api
image: soul-dev:latest
container_name: soul-dev
restart: unless-stopped
env_file:
- ./app.env
ports:
- "${APP_PORT:-8080}:${PORT:-8080}"
- "${APP_PORT:-9092}:${PORT:-8080}"
volumes:
- "${HOST_UPLOAD_DIR:-./data/uploads}:/app/uploads"
- "${HOST_LOG_DIR:-./data/log}:/app/log"