diff --git a/SRS_需求规格说明书.md b/SRS_需求规格说明书.md index 5ee568d..7f97b24 100644 --- a/SRS_需求规格说明书.md +++ b/SRS_需求规格说明书.md @@ -3,16 +3,17 @@ ## 1. 引言 ### 1.1 目的 -本文档旨在定义奥迪中国小红书小程序门户系统的功能性和非功能性需求,为系统设计、开发、测试和验收提供基准。本系统将支持奥迪中国在小红书平台展示汽车产品信息并收集潜在客户联系信息。 +本文档旨在定义奥迪中国小红书小程序门户系统的功能性和非功能性需求,为系统设计、开发、测试和验收提供基准。本系统将支持奥迪中国在小红书平台展示汽车产品信息,并将用户留资信息直接提交至企业 CRM 系统。 ### 1.2 范围 本系统范围包括: - 小红书小程序门户前端应用(基于H5技术) - 内容管理系统(CMS)平台 - 私有云环境部署 +- 约70个页面的内容建设(基于甲方设计稿) - 三年期运维服务(2026-2028年) -系统将允许奥迪市场部门管理展示内容,用户可浏览汽车型号信息并提交联系信息表单。 +系统将允许奥迪市场部门管理展示内容,用户可浏览汽车型号信息并提交联系信息表单。表单数据将直接进入企业 CRM 系统,当前平台不保存潜客个人信息。 ### 1.3 定义、首字母缩写词和缩略语 @@ -27,131 +28,161 @@ ## 2. 总体描述 ### 2.1 产品愿景 -建立一个安全、可靠、易于维护的小红书小程序门户,使奥迪中国能够在小红书平台有效展示产品信息,收集潜在客户数据,并与官方渠道保持内容一致性。 +建立一个安全、可靠、易于维护、便于迁移的小红书小程序门户,使奥迪中国能够在小红书平台高效展示产品信息,通过成熟平台完成内容运营与发布,并将用户留资直接提交至企业 CRM 系统。 ### 2.2 产品功能 -- **内容管理**:通过第三方平台管理小红书小程序显示内容 -- **用户交互**:用户可浏览汽车型号信息并提交联系信息 -- **API集成**:连接小红书小程序后端 -- **运维监控**:确保系统高可用性和性能 -- **定期更新**:每周同步官方渠道的车型图片 +- **内容管理**:通过成熟的第三方 CMS 平台管理小红书小程序显示内容 +- **页面建设**:基于甲方设计稿建设约 70 个页面,采用模板化方式提升交付效率 +- **内容预览与发布**:支持预览、发布及版本回溯,审批和定时发布为优选能力 +- **用户交互**:用户可浏览车型信息并提交联系信息,数据直接进入企业 CRM +- **环境与账号管理**:在专有云/私有云环境部署,并提供后台账号与权限管理 +- **内容迁移**:支持内容、素材和配置的全量导出,为后续迁移至其他平台预留条件 +- **运营维护**:每周人工巡检 JV 官网并同步素材,保障平台可用性和内容持续更新 ### 2.3 用户特征 | 用户角色 | 描述 | 技术水平 | |---------|------|---------| -| 奥迪业务/市场负责人 | 负责内容方向决策、审核与月度运营结果确认 | 业务管理与内容审核能力 | -| 内容运营专员(供应商/联合团队) | 负责内容制作发布与每周素材同步 | 基础内容运营与系统操作能力 | -| 供应商项目与运维负责人 | 负责项目推进、风险管理、监控与故障响应 | 专业项目管理与运维技术背景 | +| 奥迪业务/市场负责人 | 负责内容方向决策、关键内容审核、月度运营结果确认 | 业务管理与内容审核能力 | +| 内容运营专员(供应商/联合团队) | 负责页面搭建、内容制作、预览发布及每周人工素材同步 | 基础内容运营与系统操作能力 | +| 供应商项目与运维负责人 | 负责项目推进、风险管理、私有云环境维护、账号权限与迁移准备 | 专业项目管理与运维技术背景 | | 终端用户(小红书用户) | 浏览车型信息并提交联系表单 | 普通智能手机用户 | ### 2.4 约束 -- 必须部署在安全的私有云环境中 +- 必须部署在安全的私有云或专有云环境中 - 必须符合小红书平台技术规范 -- 必须支持与奥迪官方渠道的内容同步 +- 用户留资必须直接提交到企业 CRM 系统,当前平台不得保存潜客个人信息 +- 必须支持与奥迪官方渠道内容保持一致,但本期以人工周更为主,不依赖自动接口同步 - 必须满足个人信息保护法规要求 +- 当前范围不包含多品牌内容隔离,仅保留未来扩展可能 ### 2.5 假设和依赖关系 - 奥迪中国已获得小红书企业专业号认证和外链权限 -- 小红书平台API保持稳定 -- 奥迪官方渠道提供标准化的车型数据接口 +- 企业 CRM 系统可提供可调用的留资接收接口或等效接入机制 +- JV 官网内容可供运营团队每周人工巡检与更新使用 +- 甲方将提供约 70 个页面所需的设计稿、文案和素材指引 +- 私有云/专有云资源申请、账号开通和网络策略可按项目计划完成 + +### 2.6 数据边界与实施拆分 +- 平台保存的内容包括页面内容、媒体素材、配置数据、操作日志和发布记录 +- 平台不保存终端用户留资数据;用户表单提交后数据直接进入企业 CRM +- 平台应支持内容、素材和配置的全量导出,以支持后续迁移到其他平台 +- 项目实施与报价按以下三部分拆分: + - Part 1:平台与环境(今年 7 月到明年 6 月) + - Part 2:约 70 个页面的搭建(基于设计稿) + - Part 3:日常运维与每周人工同步 ## 3. 具体需求 ### 3.1 功能需求 #### 3.1.1 平台准备 -**FR-001**: 系统应提供一个第三方内容管理平台,允许授权用户编辑小红书小程序显示内容。 +**FR-001**: 系统应提供一个成熟的第三方内容管理平台,允许授权用户编辑小红书小程序显示内容。 -**FR-002**: 内容管理平台应支持以下内容类型: +**FR-002**: 内容管理平台应支持以下内容能力: - 文本内容编辑 - 图片上传和管理 -- 表单字段配置 +- 页面组件或模板配置 - 页面布局调整 +- 预览能力 -**FR-003**: 系统应提供API接口,将内容管理平台与小红书小程序前端连接。 +**FR-003**: 系统应支持后台账号创建、角色分配与权限控制。 + +**FR-004**: 系统应提供接口或发布机制,将内容管理平台与小红书小程序前端连接。 + +**FR-005**: 供应商应提供可用于评标演示的 Demo,覆盖关键内容管理和前台浏览留资流程。 #### 3.1.2 环境准备 -**FR-004**: 系统必须部署在安全的私有云环境中。 +**FR-006**: 系统必须部署在安全的私有云或专有云环境中。 -**FR-005**: 云环境应包含以下组件: +**FR-007**: 云环境应包含以下组件: - Web服务器 - 应用服务器 -- 数据库服务器 +- 内容与配置数据库 +- 素材存储 - 监控和日志系统 -**FR-006**: 系统应实施网络安全措施,包括防火墙、入侵检测和数据加密。 +**FR-008**: 系统应实施网络安全措施,包括防火墙、访问控制、数据传输加密和日志审计。 -#### 3.1.3 门户维护 -**FR-007**: 系统应保证99.5%的正常运行时间。 +**FR-009**: 系统应支持平台登录账号的开通、停用和权限维护。 -**FR-008**: 系统应实现自动备份机制,保留源环境至少1个月。 +#### 3.1.3 门户维护与迁移准备 +**FR-010**: 系统应保证 99.5% 的正常运行时间。 -**FR-009**: 系统应具备性能监控和优化能力,防止性能下降。 +**FR-011**: 系统应实现自动备份机制,保留内容与配置源环境至少 1 个月。 -**FR-010**: 系统应建立集成测试和故障排除机制。 +**FR-012**: 系统应具备性能监控和优化能力,防止性能下降。 -**FR-011**: 系统应准备回滚和事件响应程序。 +**FR-013**: 系统应建立集成测试、故障排除、回滚和事件响应机制。 -#### 3.1.4 内容运营 -**FR-012**: 系统应支持每周定期更新内容,与奥迪官方渠道的车型图片保持同步。 +**FR-014**: 系统应支持内容、素材和关键配置数据的全量导出,以便未来迁移到 Audi 其他平台或第三方平台。 -**FR-013**: 内容更新应支持以下操作: +#### 3.1.4 内容建设与运营 +**FR-015**: 系统应支持基于甲方设计稿搭建约 70 个页面,采用可复用的模板和内容模型提高交付效率。 + +**FR-016**: 供应商应每周人工巡检 JV 官网内容,并根据巡检结果更新小程序中的车型图片和相关内容。 + +**FR-017**: 内容更新应支持以下操作: - 批量图片替换 - 文案更新 -- 新车型添加 -- 旧车型下架 +- 新页面或新车型页面添加 +- 旧页面或旧车型页面下架 +- 发布前预览 -**FR-014**: 系统应记录所有内容变更历史,支持版本回溯。 +**FR-018**: 系统应记录所有内容变更历史,支持版本回溯。 -#### 3.1.5 项目管理 -**FR-015**: 系统供应商应提供日常账户服务、时间线管理、风险管理、质量控制和会议材料准备。 +**FR-019**: 系统宜支持草稿审批和定时发布;若所选成熟平台不具备该能力,不应作为本期上线阻断条件。 -**FR-016**: 供应商应每周举行进度更新会议,解决出现的问题。 +#### 3.1.5 项目管理与服务交付 +**FR-020**: 系统供应商应提供日常账户服务、时间线管理、风险管理、质量控制和会议材料准备。 -**FR-017**: 供应商应每月提供运营报告,并与奥迪中国进行审查。 +**FR-021**: 供应商应每周举行进度更新会议,解决出现的问题。 -**FR-018**: 对于关键/高优先级事件,供应商应提供专门的事件报告。 +**FR-022**: 供应商应每月提供运营报告,并与奥迪中国进行审查。 + +**FR-023**: 对于关键/高优先级事件,供应商应提供专门的事件报告。 + +**FR-024**: 项目实施与报价应明确拆分为平台与环境、约 70 个页面搭建、日常运维与每周同步三部分。 #### 3.1.6 运维支持与值班 -**FR-019**: 系统应提供关键/高优先级事件支持机制:工作日服务时间 08:30-17:30,非服务时间 17:30-08:30 待命支持。 +**FR-025**: 系统应提供关键/高优先级事件支持机制:工作日服务时间 08:30-17:30,非服务时间 17:30-08:30 待命支持。 -**FR-020**: 系统应在周末及法定节假日提供关键/高优先级事件 7x24 待命支持。 +**FR-026**: 系统应在周末及法定节假日提供关键/高优先级事件 7x24 待命支持。 ### 3.2 非功能需求 #### 3.2.1 性能需求 -**NFR-001**: 系统响应时间不应超过3秒(在正常网络条件下)。 +**NFR-001**: 小程序前端关键页面响应时间不应超过 3 秒(在正常网络条件下)。 -**NFR-002**: 系统应支持同时处理1000个并发用户。 +**NFR-002**: 系统应支持同时处理 1000 个并发用户浏览和留资提交请求。 #### 3.2.2 安全需求 **NFR-003**: 系统必须实施用户身份验证和授权机制。 -**NFR-004**: 所有敏感数据(如用户联系信息)必须加密存储。 +**NFR-004**: 用户留资数据在传输过程中必须加密,并直接提交至企业 CRM;当前平台不得持久化存储潜客个人信息。 **NFR-005**: 系统必须符合《个人信息保护法》要求,明确告知用户数据用途并获得同意。 #### 3.2.3 可用性需求 -**NFR-006**: 内容管理界面应直观易用,新用户可在30分钟内掌握基本操作。 +**NFR-006**: 内容管理界面应直观易用,新用户可在 30 分钟内掌握基本操作。 **NFR-007**: 系统应提供详细的帮助文档和操作指南。 #### 3.2.4 可维护性需求 **NFR-008**: 系统应采用模块化设计,便于功能扩展和维护。 -**NFR-009**: 代码应有充分注释,遵循编码规范。 +**NFR-009**: 系统应保留必要的操作日志、发布记录和维护文档,便于运维和交接。 #### 3.2.5 可移植性需求 **NFR-010**: 系统应使用标准开放平台接口,支持与其他系统进行数据交换。 -**NFR-011**: 内容应能轻松迁移到其他平台,避免供应商锁定。 +**NFR-011**: 内容、素材和配置应能轻松导出并迁移到其他平台,避免供应商锁定。 #### 3.2.6 供应商能力与交接需求 **NFR-012**: 供应商团队应具备小红书小程序集成、应用、云、安全与数据相关实践经验。 **NFR-013**: 项目管理核心角色应具备 PMP 或同等级项目管理资质。 -**NFR-014**: 供应商应说明对现有技术平台、基础设施和 IT 能力的复用方案,降低重复建设成本。 +**NFR-014**: 供应商应说明对现有技术平台、基础设施和 IT 能力的复用方案,并优先采用成熟现成平台降低重复建设成本。 **NFR-015**: 供应商应保证项目资源充足,关键岗位需设置备份人选以确保按期上线。 @@ -162,20 +193,22 @@ #### 3.3.1 用户接口 **UI-001**: 小红书小程序前端应采用响应式设计,适配各种移动设备屏幕。 -**UI-002**: 内容管理后台应提供清晰的导航和操作反馈。 +**UI-002**: 内容管理后台应提供清晰的导航、预览和操作反馈。 #### 3.3.2 硬件接口 无特殊硬件接口需求。 #### 3.3.3 软件接口 -**SI-001**: 系统应提供RESTful API接口,用于内容同步。 +**SI-001**: 系统应提供 CMS 与小程序前端之间的内容发布或内容获取接口。 -**SI-002**: 系统应支持与奥迪官方渠道的数据接口对接。 +**SI-002**: 小程序留资流程应支持直接对接企业 CRM 接收接口或等效机制,平台不保留留资数据副本。 + +**SI-003**: 与 JV 官网的内容更新流程以人工巡检和人工同步为主,本期不将自动对接官网接口作为前置条件。 #### 3.3.4 通信接口 -**CI-001**: 所有外部通信必须使用HTTPS协议。 +**CI-001**: 所有外部通信必须使用 HTTPS 协议。 -**CI-002**: API调用应实施速率限制和认证机制。 +**CI-002**: 内容接口和 CRM 提交接口应实施认证、速率限制或等效安全控制机制。 ## 4. 业务流程 @@ -183,18 +216,17 @@ ```mermaid graph TD - A[内容运营专员登录CMS] --> B{操作类型} - B -->|新增内容| C[创建新页面/模块] - B -->|编辑内容| D[修改现有内容] - B -->|删除内容| E[标记内容为删除] - C --> F[上传图片/输入文案] - D --> F - F --> G[预览内容并提交审核] - G --> H[奥迪业务/市场负责人审核] - H --> I{审核通过?} - I -->|是| J[发布到生产环境] - I -->|否| K[返回内容运营专员修订] - J --> L[同步到小红书小程序] + A[内容运营专员登录CMS] --> B[按设计稿新建或维护页面] + B --> C[上传素材并编辑图文内容] + C --> D[预览页面效果] + D --> E{是否启用审批流程} + E -->|是| F[提交业务/市场负责人审核] + E -->|否| G[直接发布] + F --> H{审核通过?} + H -->|是| G + H -->|否| I[返回内容运营专员修订] + G --> J[发布到小红书小程序] + J --> K[记录版本与操作日志] ``` ### 4.2 用户交互流程 @@ -210,22 +242,22 @@ graph TD G --> H{验证通过?} H -->|是| I[显示感谢页面] H -->|否| J[显示错误信息] - I --> K[数据存储到数据库] + I --> K[数据直接提交至企业CRM] ``` ### 4.3 运维管理流程 ```mermaid graph TD - A[系统监控] --> B{异常检测?} - B -->|是| C[触发告警] - C --> D[运维团队响应] - D --> E{问题级别} - E -->|关键/高| F[立即处理 + 生成事件报告] - E -->|中/低| G[记录并安排处理] - B -->|否| H[正常运行] - H --> I[定期备份] - I --> J[月度报告生成] + A[内容运营专员每周巡检JV官网] --> B[识别素材或页面变更] + B --> C[在CMS中更新对应内容] + C --> D[预览并发布] + D --> E[项目与运维负责人记录周更结果] + E --> F[系统监控与备份巡检] + F --> G{发现异常?} + G -->|是| H[触发告警并组织响应] + G -->|否| I[继续正常运营] + H --> J[输出事件报告或月报输入] ``` ## 5. 术语表 @@ -237,14 +269,15 @@ graph TD | 私有云 | Private Cloud | 专为单一组织构建的云计算环境,提供更高的安全性和控制 | | 外链 | External Link | 从小红书笔记指向外部网站的链接 | | 运维 | Operations and Maintenance | 系统上线后的日常监控、维护和支持活动 | +| 企业 CRM | Enterprise CRM | 用于接收和管理用户留资信息的企业客户关系管理系统 | | 表单 | Form | 用于收集用户输入信息的网页元素集合 | | API集成 | API Integration | 不同软件系统之间通过应用程序编程接口进行数据交换和功能调用 | -| 内容同步 | Content Synchronization | 保持多个系统或平台之间内容一致性的过程 | +| 内容同步 | Content Synchronization | 保持多个系统或平台之间内容一致性的过程,本期以人工周更为主 | ## 附录A: 异常流程 ### A.1 内容更新异常流程 -1. **异常情况**: 官方渠道图片无法获取 +1. **异常情况**: JV 官网图片或文案无法获取 - **处理流程**: - 系统记录错误日志 - 发送告警通知给运维团队 @@ -266,12 +299,12 @@ graph TD - 保留用户已输入的正确信息 - 允许用户修正后重新提交 -2. **异常情况**: 数据库写入失败 +2. **异常情况**: CRM 提交失败 - **处理流程**: - 显示友好的错误页面 - 自动重试机制(最多3次) - - 如果仍失败,保存数据到临时存储 - - 后台任务定期重试写入 + - 如果仍失败,提示用户稍后重试或联系官方渠道 + - 记录失败日志并通知相关接口负责人排查 ### A.3 系统故障异常流程 1. **异常情况**: 服务器宕机 diff --git a/audi-content-portal/index.html b/audi-content-portal/index.html index 21dfe69..9c0d78f 100644 --- a/audi-content-portal/index.html +++ b/audi-content-portal/index.html @@ -3,7 +3,7 @@ - My Google AI Studio App + Audi Rednote Mini App Backend
diff --git a/audi-content-portal/public/images/audi_logo.png b/audi-content-portal/public/images/audi_logo.png new file mode 100644 index 0000000..9d197d7 Binary files /dev/null and b/audi-content-portal/public/images/audi_logo.png differ diff --git a/audi-content-portal/public/images/cars/a3-1.jpg b/audi-content-portal/public/images/cars/a3-1.jpg new file mode 100644 index 0000000..c3bee82 Binary files /dev/null and b/audi-content-portal/public/images/cars/a3-1.jpg differ diff --git a/audi-content-portal/public/images/cars/a3-2.jpg b/audi-content-portal/public/images/cars/a3-2.jpg new file mode 100644 index 0000000..c3bee82 Binary files /dev/null and b/audi-content-portal/public/images/cars/a3-2.jpg differ diff --git a/audi-content-portal/public/images/cars/a4-1.jpg b/audi-content-portal/public/images/cars/a4-1.jpg new file mode 100644 index 0000000..1586138 Binary files /dev/null and b/audi-content-portal/public/images/cars/a4-1.jpg differ diff --git a/audi-content-portal/public/images/cars/a4-2.jpg b/audi-content-portal/public/images/cars/a4-2.jpg new file mode 100644 index 0000000..56a99c8 Binary files /dev/null and b/audi-content-portal/public/images/cars/a4-2.jpg differ diff --git a/audi-content-portal/public/images/cars/a6-1.jpg b/audi-content-portal/public/images/cars/a6-1.jpg new file mode 100644 index 0000000..5a0f346 Binary files /dev/null and b/audi-content-portal/public/images/cars/a6-1.jpg differ diff --git a/audi-content-portal/public/images/cars/a6-2.jpg b/audi-content-portal/public/images/cars/a6-2.jpg new file mode 100644 index 0000000..a3d9593 Binary files /dev/null and b/audi-content-portal/public/images/cars/a6-2.jpg differ diff --git a/audi-content-portal/public/images/cars/a7-1.jpg b/audi-content-portal/public/images/cars/a7-1.jpg new file mode 100644 index 0000000..88b3c83 Binary files /dev/null and b/audi-content-portal/public/images/cars/a7-1.jpg differ diff --git a/audi-content-portal/public/images/cars/a7-2.jpg b/audi-content-portal/public/images/cars/a7-2.jpg new file mode 100644 index 0000000..ecad179 Binary files /dev/null and b/audi-content-portal/public/images/cars/a7-2.jpg differ diff --git a/audi-content-portal/public/images/cars/a7-3.jpg b/audi-content-portal/public/images/cars/a7-3.jpg new file mode 100644 index 0000000..c4ce8ea Binary files /dev/null and b/audi-content-portal/public/images/cars/a7-3.jpg differ diff --git a/audi-content-portal/public/images/cars/a7spb-侧身.png b/audi-content-portal/public/images/cars/a7spb-侧身.png new file mode 100644 index 0000000..000d8c6 Binary files /dev/null and b/audi-content-portal/public/images/cars/a7spb-侧身.png differ diff --git a/audi-content-portal/public/images/cars/a8-1.jpg b/audi-content-portal/public/images/cars/a8-1.jpg new file mode 100644 index 0000000..944afab Binary files /dev/null and b/audi-content-portal/public/images/cars/a8-1.jpg differ diff --git a/audi-content-portal/public/images/cars/a8-2.jpg b/audi-content-portal/public/images/cars/a8-2.jpg new file mode 100644 index 0000000..944afab Binary files /dev/null and b/audi-content-portal/public/images/cars/a8-2.jpg differ diff --git a/audi-content-portal/public/images/cars/q3-1.jpg b/audi-content-portal/public/images/cars/q3-1.jpg new file mode 100644 index 0000000..60ecb58 Binary files /dev/null and b/audi-content-portal/public/images/cars/q3-1.jpg differ diff --git a/audi-content-portal/public/images/cars/q3-2.jpg b/audi-content-portal/public/images/cars/q3-2.jpg new file mode 100644 index 0000000..60ecb58 Binary files /dev/null and b/audi-content-portal/public/images/cars/q3-2.jpg differ diff --git a/audi-content-portal/public/images/cars/q5-1.jpg b/audi-content-portal/public/images/cars/q5-1.jpg new file mode 100644 index 0000000..8ac2d67 Binary files /dev/null and b/audi-content-portal/public/images/cars/q5-1.jpg differ diff --git a/audi-content-portal/public/images/cars/q5-2.jpg b/audi-content-portal/public/images/cars/q5-2.jpg new file mode 100644 index 0000000..8ac2d67 Binary files /dev/null and b/audi-content-portal/public/images/cars/q5-2.jpg differ diff --git a/audi-content-portal/public/images/cars/rs7-1.jpg b/audi-content-portal/public/images/cars/rs7-1.jpg new file mode 100644 index 0000000..9ac181f Binary files /dev/null and b/audi-content-portal/public/images/cars/rs7-1.jpg differ diff --git a/audi-content-portal/public/images/cars/rs7-2.jpg b/audi-content-portal/public/images/cars/rs7-2.jpg new file mode 100644 index 0000000..3f86a38 Binary files /dev/null and b/audi-content-portal/public/images/cars/rs7-2.jpg differ diff --git a/audi-content-portal/src/App.tsx b/audi-content-portal/src/App.tsx index bcbf4a4..12c105d 100644 --- a/audi-content-portal/src/App.tsx +++ b/audi-content-portal/src/App.tsx @@ -1,22 +1,14 @@ import * as React from "react" import { SidebarProvider, SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" import { AppSidebar } from "@/components/layout/AppSidebar" -import { Dashboard } from "@/components/dashboard/Dashboard" import { CarLibrary } from "@/components/car-library/CarLibrary" import { MiniAppContentLibrary } from "@/components/content-library/MiniAppContentLibrary" -import { LeadManagement } from "@/components/leads/LeadManagement" import { UserAccessManagement } from "@/components/users/UserAccessManagement" import { SystemSettings } from "@/components/settings/SystemSettings" import { Separator } from "@/components/ui/separator" import { Bell, Search, User } from "lucide-react" import { Button } from "@/components/ui/button" -type NewContentRequest = { - sourceCarId: string - sourceCarName: string - requestId: string -} - export type RoleView = "biz-market" | "content-ops" | "pm-ops" export type WorkflowConfig = { @@ -24,14 +16,14 @@ export type WorkflowConfig = { allowRecall: boolean publishStrategy: "manual" | "scheduled" defaultPublishTime: string - syncFrequency: "daily" | "weekly" + syncFrequency: "weekly" syncExecutionTime: string retryCount: number manualConfirmSync: boolean } export default function App() { - const [activeTab, setActiveTab] = React.useState("dashboard") + const [activeTab, setActiveTab] = React.useState("car-library") const [roleView, setRoleView] = React.useState("content-ops") const [workflowConfig, setWorkflowConfig] = React.useState({ enablePrePublish: true, @@ -48,34 +40,20 @@ export default function App() { "业务/市场负责人批准 Audi Q5L 进入预发布(2026-04-10 15:45)", "内容运营专员发布 Audi A3 Sportback(2026-04-09 11:30)", ]) - const [pendingNewContent, setPendingNewContent] = React.useState(null) const appendLog = (message: string) => { const now = new Date().toLocaleString("zh-CN", { hour12: false }) setAuditLogs((prev) => [`${message}(${now})`, ...prev].slice(0, 12)) } - const handleCreateContentFromCar = (sourceCarId: string, sourceCarName: string) => { - setPendingNewContent({ - sourceCarId, - sourceCarName, - requestId: String(Date.now()), - }) - appendLog(`内容运营专员基于官网 ${sourceCarName} 新建内容草稿`) - setActiveTab("mini-content") - } - const renderContent = () => { switch (activeTab) { - case "dashboard": - return case "car-library": return ( ) case "mini-content": @@ -84,11 +62,9 @@ export default function App() { roleView={roleView} workflowConfig={workflowConfig} onAddAuditLog={appendLog} - newContentRequest={pendingNewContent} + newContentRequest={null} /> ) - case "leads": - return case "user-access": return case "settings": @@ -102,7 +78,13 @@ export default function App() { /> ) default: - return + return ( + + ) } } @@ -115,10 +97,8 @@ export default function App() {
- {activeTab === "dashboard" && "仪表盘"} - {activeTab === "car-library" && "车型库"} + {activeTab === "car-library" && "官网车型库"} {activeTab === "mini-content" && "小程序内容库"} - {activeTab === "leads" && "潜客管理"} {activeTab === "user-access" && "用户与权限"} {activeTab === "settings" && "系统设置"}
diff --git a/audi-content-portal/src/components/car-library/CarLibrary.tsx b/audi-content-portal/src/components/car-library/CarLibrary.tsx index 662ffa0..248763b 100644 --- a/audi-content-portal/src/components/car-library/CarLibrary.tsx +++ b/audi-content-portal/src/components/car-library/CarLibrary.tsx @@ -1,5 +1,5 @@ import * as React from "react" -import { AlertCircle, CheckCircle2, Clock, Filter, Plus, RefreshCw, Search } from "lucide-react" +import { AlertCircle, CheckCircle2, ChevronLeft, ChevronRight, Clock, Eye, Pencil, RefreshCw, Search, X } from "lucide-react" import type { RoleView, WorkflowConfig } from "@/App" import { Badge } from "@/components/ui/badge" @@ -21,10 +21,30 @@ type SyncStatus = "synced" | "pending" | "error" type SourceCar = { id: string name: string + pageType: "车型页" | "活动页" | "专题页" + title: string + subtitle: string + highlights: string + description: string + ctaText: string + scheduledPublishAt: string officialTagline: string sourceUpdatedAt: string + updatedBy: string syncStatus: SyncStatus imageCount: number + imageUrls: string[] +} + +type SourceCarDraft = { + pageType: "车型页" | "活动页" | "专题页" + title: string + subtitle: string + highlights: string + description: string + ctaText: string + scheduledPublishAt: string + imageUrls: string[] } type SyncLog = { @@ -38,46 +58,152 @@ type CarLibraryProps = { roleView: RoleView workflowConfig: WorkflowConfig onAddAuditLog: (message: string) => void - onCreateContent?: (sourceCarId: string, sourceCarName: string) => void } const SOURCE_CARS: SourceCar[] = [ { id: "a3", name: "Audi A3 Sportback", + pageType: "车型页", + title: "Audi A3 Sportback", + subtitle: "进取,不负期待", + highlights: "数字座舱\n城市通勤\n智能互联", + description: "适合城市用户的豪华紧凑车型,兼顾效率与智能体验。", + ctaText: "立即预约试驾", + scheduledPublishAt: "", officialTagline: "进取,不负期待", sourceUpdatedAt: "2026-04-11 09:30", + updatedBy: "内容运营专员", syncStatus: "synced", - imageCount: 8, + imageCount: 2, + imageUrls: [ + "/images/cars/a3-1.jpg", + "/images/cars/a3-2.jpg", + ], }, { id: "a4l", name: "Audi A4L", + pageType: "车型页", + title: "Audi A4L", + subtitle: "做更强大的自己", + highlights: "quattro\n商务舒适\n智能互联", + description: "面向商务人群的主推内容版本,突出舒适与操控。", + ctaText: "了解更多", + scheduledPublishAt: "", officialTagline: "做更强大的自己", sourceUpdatedAt: "2026-04-11 09:30", + updatedBy: "内容运营专员", syncStatus: "synced", - imageCount: 6, + imageCount: 1, + imageUrls: [ + "/images/cars/a4-1.jpg", + ], }, { id: "a6l", name: "Audi A6L", + pageType: "车型页", + title: "Audi A6L", + subtitle: "懂你,更懂未来", + highlights: "行政旗舰\n长轴空间\n智能辅助", + description: "正在进行文案优化,待业务审核。", + ctaText: "预约顾问回电", + scheduledPublishAt: "", officialTagline: "懂你,更懂未来", sourceUpdatedAt: "2026-04-10 14:20", + updatedBy: "内容运营专员", syncStatus: "pending", - imageCount: 5, + imageCount: 2, + imageUrls: [ + "/images/cars/a6-1.jpg", + "/images/cars/a6-2.jpg", + ], }, { id: "q5l", name: "Audi Q5L", + pageType: "车型页", + title: "Audi Q5L", + subtitle: "自由,由我定义", + highlights: "四驱性能\n家庭空间\n全场景出行", + description: "因权益文案与活动规则不一致,当前版本待修订。", + ctaText: "预约试驾", + scheduledPublishAt: "", officialTagline: "自由,由我定义", sourceUpdatedAt: "2026-04-10 09:15", + updatedBy: "项目与运维负责人", syncStatus: "error", - imageCount: 0, + imageCount: 2, + imageUrls: [ + "/images/cars/q5-1.jpg", + "/images/cars/q5-2.jpg", + ], + }, + { + id: "q3", + name: "Audi Q3", + pageType: "车型页", + title: "Audi Q3", + subtitle: "活出生命的辽阔", + highlights: "紧凑SUV\n智能互联\n都市通勤", + description: "新建草稿,待进一步补充图文和活动权益信息。", + ctaText: "预约试驾", + scheduledPublishAt: "", + officialTagline: "活出生命的辽阔", + sourceUpdatedAt: "2026-04-10 17:30", + updatedBy: "内容运营专员", + syncStatus: "synced", + imageCount: 2, + imageUrls: [ + "/images/cars/q3-1.jpg", + "/images/cars/q3-2.jpg", + ], + }, + { + id: "a8l", + name: "Audi A8L", + pageType: "车型页", + title: "Audi A8L", + subtitle: "旗舰格局,沉稳之选", + highlights: "旗舰行政\n豪华座舱\n专属服务", + description: "旗舰车型物料已完成本周核对。", + ctaText: "预约专属顾问", + scheduledPublishAt: "", + officialTagline: "旗舰格局,沉稳之选", + sourceUpdatedAt: "2026-04-10 16:20", + updatedBy: "内容运营专员", + syncStatus: "synced", + imageCount: 2, + imageUrls: [ + "/images/cars/a8-1.jpg", + "/images/cars/a8-2.jpg", + ], + }, + { + id: "rs7", + name: "Audi RS7", + pageType: "车型页", + title: "Audi RS7 Performance", + subtitle: "高性能美学,锋芒尽释", + highlights: "V8双涡轮\nquattro四驱\nRS专属运动套件", + description: "高性能车型内容新增,本周已补齐素材并进入巡检列表。", + ctaText: "预约性能试驾", + scheduledPublishAt: "", + officialTagline: "高性能美学,锋芒尽释", + sourceUpdatedAt: "2026-04-12 10:10", + updatedBy: "内容运营专员", + syncStatus: "pending", + imageCount: 2, + imageUrls: [ + "/images/cars/rs7-1.jpg", + "/images/cars/rs7-2.jpg", + ], }, ] const INITIAL_LOGS: SyncLog[] = [ - { id: "l1", time: "2026-04-11 09:30", status: "success", message: "同步成功:4 个车型,素材 19 张" }, + { id: "l1", time: "2026-04-11 09:30", status: "success", message: "同步成功:7 个车型,素材 31 张" }, { id: "l2", time: "2026-04-10 09:15", status: "failed", message: "Audi Q5L 图片拉取失败:CDN 超时" }, ] @@ -107,14 +233,53 @@ function nowString() { return new Date().toLocaleString("zh-CN", { hour12: false }) } -export function CarLibrary({ onAddAuditLog, onCreateContent }: CarLibraryProps) { +export function CarLibrary({ roleView, workflowConfig, onAddAuditLog }: CarLibraryProps) { const [sourceCars, setSourceCars] = React.useState(SOURCE_CARS) const [query, setQuery] = React.useState("") const [isSyncing, setIsSyncing] = React.useState(false) const [progress, setProgress] = React.useState(0) const [syncLogs, setSyncLogs] = React.useState(INITIAL_LOGS) + const [previewCarId, setPreviewCarId] = React.useState(null) + const [previewImageIndex, setPreviewImageIndex] = React.useState(0) + const [viewMode, setViewMode] = React.useState<"list" | "editor">("list") + const [editingCarId, setEditingCarId] = React.useState(null) + const [editorDraft, setEditorDraft] = React.useState(null) + const [newImageUrl, setNewImageUrl] = React.useState("") + const initialDraftRef = React.useRef(null) + const listPageSize = 7 + const listTotalPages = 10 + const listPage = 1 const filtered = sourceCars.filter((car) => car.name.toLowerCase().includes(query.toLowerCase())) + const pagedCars = filtered.slice((listPage - 1) * listPageSize, listPage * listPageSize) + const previewCar = sourceCars.find((car) => car.id === previewCarId) ?? null + const previewImages = previewCar?.imageUrls ?? [] + const canEdit = roleView === "content-ops" + + const isEditorDirty = React.useMemo(() => { + if (!editorDraft || !initialDraftRef.current) return false + return JSON.stringify(editorDraft) !== JSON.stringify(initialDraftRef.current) + }, [editorDraft]) + + React.useEffect(() => { + if (!previewCarId || previewImages.length === 0) return + + const handleKeydown = (event: KeyboardEvent) => { + if (event.key === "ArrowLeft") { + setPreviewImageIndex((curr) => Math.max(curr - 1, 0)) + } + if (event.key === "ArrowRight") { + setPreviewImageIndex((curr) => Math.min(curr + 1, previewImages.length - 1)) + } + if (event.key === "Escape") { + setPreviewCarId(null) + setPreviewImageIndex(0) + } + } + + window.addEventListener("keydown", handleKeydown) + return () => window.removeEventListener("keydown", handleKeydown) + }, [previewCarId, previewImages.length]) const handleSync = () => { setIsSyncing(true) @@ -129,15 +294,16 @@ export function CarLibrary({ onAddAuditLog, onCreateContent }: CarLibraryProps) curr.map((car) => ({ ...car, sourceUpdatedAt: time, + updatedBy: "内容运营专员", syncStatus: "synced", - imageCount: car.imageCount || 4, + imageCount: car.imageUrls.length || car.imageCount || 1, })) ) setSyncLogs((curr) => [ - { id: String(Date.now()), time, status: "success", message: "手动同步完成:4 个车型已更新" }, + { id: String(Date.now()), time, status: "success", message: "本周人工周更完成:7 个车型素材已核对并更新" }, ...curr, ]) - onAddAuditLog("内容运营专员执行官网车型同步") + onAddAuditLog("内容运营专员执行 JV 官网人工周更") setTimeout(() => setIsSyncing(false), 300) return 100 } @@ -154,21 +320,231 @@ export function CarLibrary({ onAddAuditLog, onCreateContent }: CarLibraryProps) ) ) setSourceCars((curr) => - curr.map((car) => (car.syncStatus === "error" ? { ...car, syncStatus: "synced", sourceUpdatedAt: time, imageCount: 4 } : car)) + curr.map((car) => + car.syncStatus === "error" + ? { + ...car, + syncStatus: "synced", + sourceUpdatedAt: time, + imageCount: car.imageUrls.length || car.imageCount || 1, + updatedBy: "项目与运维负责人", + } + : car + ) + ) + onAddAuditLog("项目与运维负责人重试 JV 官网素材异常任务") + } + + const openEditor = (car: SourceCar) => { + const draft: SourceCarDraft = { + pageType: car.pageType, + title: car.title, + subtitle: car.subtitle, + highlights: car.highlights, + description: car.description, + ctaText: car.ctaText, + scheduledPublishAt: car.scheduledPublishAt, + imageUrls: [...car.imageUrls], + } + + initialDraftRef.current = draft + setEditorDraft(draft) + setEditingCarId(car.id) + setNewImageUrl("") + setViewMode("editor") + } + + const updateDraft = (patch: Partial) => { + setEditorDraft((prev) => (prev ? { ...prev, ...patch } : prev)) + } + + const addImageToDraft = () => { + if (!editorDraft) return + const url = newImageUrl.trim() + if (!url) return + updateDraft({ imageUrls: [...editorDraft.imageUrls, url] }) + setNewImageUrl("") + } + + const removeImageFromDraft = (index: number) => { + if (!editorDraft) return + updateDraft({ imageUrls: editorDraft.imageUrls.filter((_, idx) => idx !== index) }) + } + + const handleCancelEditor = () => { + if (isEditorDirty && !window.confirm("当前有未保存修改,确认取消并返回车型库吗?")) { + return + } + setViewMode("list") + setEditingCarId(null) + setEditorDraft(null) + setNewImageUrl("") + initialDraftRef.current = null + } + + const handleSaveEditor = () => { + if (!editingCarId || !editorDraft) return + const now = nowString() + + setSourceCars((curr) => + curr.map((car) => { + if (car.id !== editingCarId) return car + return { + ...car, + pageType: editorDraft.pageType, + title: editorDraft.title, + subtitle: editorDraft.subtitle, + highlights: editorDraft.highlights, + description: editorDraft.description, + ctaText: editorDraft.ctaText, + scheduledPublishAt: editorDraft.scheduledPublishAt, + officialTagline: editorDraft.subtitle || car.officialTagline, + imageUrls: editorDraft.imageUrls, + imageCount: editorDraft.imageUrls.length, + sourceUpdatedAt: now, + updatedBy: "内容运营专员", + } + }) + ) + + const carName = sourceCars.find((car) => car.id === editingCarId)?.name ?? "目标车型" + onAddAuditLog(`内容运营专员编辑并保存 ${carName} 车型内容`) + + setViewMode("list") + setEditingCarId(null) + setEditorDraft(null) + setNewImageUrl("") + initialDraftRef.current = null + } + + if (viewMode === "editor" && editingCarId && editorDraft) { + const editingCar = sourceCars.find((car) => car.id === editingCarId) + + return ( +
+
+
+ +
+

官网车型编辑

+

编辑车型内容与素材图片,不含预览与审核动作。

+
+
+ {editingCar?.name ?? "车型"} +
+ + + + 编辑字段 + 字段结构与小程序内容编辑页一致。 + + +
+ + +
+ +
+ + +
+ +
+ + updateDraft({ title: e.target.value })} /> +
+ +
+ + updateDraft({ subtitle: e.target.value })} /> +
+ +
+ +