diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..72080e8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,243 @@ +# 小程序重构开发计划 + +## 项目概述 +将现有的图生图/图生视频小程序重构为模板卡片式首页 + 历史记录页面的架构。 + +## 开发阶段规划 + +### 阶段一:项目架构重构 (优先级:高) + +#### 1.1 导航结构调整 +- [ ] 修改 `app.config.ts` 添加tabBar配置 + - 首页 (pages/home/index) + - 历史记录 (pages/history/index) +- [ ] 移除原有的 result 页面跳转逻辑 +- [ ] 更新页面路由配置 + +#### 1.2 页面文件结构创建 +``` +src/pages/ +├── home/ # 新首页 (模板卡片) +│ ├── index.tsx +│ ├── index.config.ts +│ └── index.css +├── history/ # 历史记录页面 +│ ├── index.tsx +│ ├── index.config.ts +│ └── index.css +├── generate/ # 生成处理页面 (原index页面重构) +│ ├── index.tsx +│ ├── index.config.ts +│ └── index.css +└── result/ # 结果页面 (保留并优化) + ├── index.tsx + ├── index.config.ts + └── index.css +``` + +### 阶段二:数据存储和状态管理 (优先级:高) + +#### 2.1 本地存储设计 +- [ ] 设计历史记录数据结构 +```typescript +interface HistoryRecord { + id: string + type: 'image-to-image' | 'image-to-video' + templateName: string + inputImage: string + outputResult: string | string[] + status: 'generating' | 'completed' | 'failed' + createTime: string + updateTime: string +} +``` + +#### 2.2 状态管理 +- [ ] 创建 `src/store/` 目录 +- [ ] 实现 Zustand 状态管理 + - [ ] `src/store/historyStore.ts` - 历史记录管理 + - [ ] `src/store/templateStore.ts` - 模板配置管理 +- [ ] 添加数据持久化功能 + +### 阶段三:模板卡片首页开发 (优先级:高) + +#### 3.1 模板配置和数据 +- [ ] 设计8个模板的配置数据 +```typescript +interface Template { + id: string + name: string + description: string + type: 'image-to-image' | 'image-to-video' + thumbnail: string + category: string +} +``` +- [ ] 添加模板缩略图资源 +- [ ] 创建模板配置文件 `src/config/templates.ts` + +#### 3.2 首页组件开发 +- [ ] 创建模板卡片组件 `src/components/TemplateCard/` + - [ ] 支持2列网格布局 + - [ ] 卡片hover效果 + - [ ] 模板名称和描述显示 +- [ ] 实现首页布局 `src/pages/home/index.tsx` + - [ ] 2列4行网格布局 + - [ ] 模板卡片点击跳转到生成页面 +- [ ] 添加首页样式 `src/pages/home/index.css` + +### 阶段四:生成页面重构 (优先级:中) + +#### 4.1 原index页面重构 +- [ ] 将 `src/pages/index/` 重命名为 `src/pages/generate/` +- [ ] 修改生成页面接收模板参数 +```typescript +// 从路由参数获取模板信息 +interface GeneratePageParams { + templateId: string + templateName: string + templateType: 'image-to-image' | 'image-to-video' +} +``` + +#### 4.2 生成流程优化 +- [ ] 添加模板信息显示 +- [ ] 优化上传和生成流程 +- [ ] 添加生成进度到历史记录 +- [ ] 生成完成后更新历史记录状态 + +### 阶段五:历史记录页面开发 (优先级:中) + +#### 5.1 历史记录页面布局 +- [ ] 创建历史记录列表组件 `src/components/HistoryList/` +- [ ] 实现记录卡片组件 `src/components/HistoryCard/` + - [ ] 显示模板名称、生成时间 + - [ ] 生成状态指示器 + - [ ] 结果预览图 + +#### 5.2 历史记录功能 +- [ ] 实现历史记录分页加载 +- [ ] 添加筛选功能 (按类型、状态) +- [ ] 添加删除记录功能 +- [ ] 点击记录查看详细结果 + +### 阶段六:组件优化和重构 (优先级:中) + +#### 6.1 现有组件适配 +- [ ] 优化 `UploadButton` 组件 + - [ ] 适配不同模板类型 + - [ ] 添加模板上下文信息 +- [ ] 优化 `LoadingOverlay` 组件 + - [ ] 显示当前模板信息 + - [ ] 显示生成进度百分比 +- [ ] 优化 `ErrorOverlay` 组件 + - [ ] 提供重试和返回首页选项 + +#### 6.2 新增通用组件 +- [ ] 创建 `NavigationBar` 组件 (如需要自定义导航) +- [ ] 创建 `StatusBadge` 组件 (状态指示器) +- [ ] 创建 `ImagePreview` 组件 (图片预览) + +### 阶段七:用户体验优化 (优先级:低) + +#### 7.1 交互优化 +- [ ] 添加页面间过渡动画 +- [ ] 添加加载骨架屏 +- [ ] 优化触摸反馈 +- [ ] 添加操作确认对话框 + +#### 7.2 性能优化 +- [ ] 实现图片懒加载 +- [ ] 添加历史记录虚拟滚动 +- [ ] 优化打包体积 +- [ ] 添加缓存策略 + +### 阶段八:测试和文档 (优先级:低) + +#### 8.1 单元测试 (遵循TDD原则) +- [ ] 为历史记录Store添加测试 +- [ ] 为模板配置添加测试 +- [ ] 为通用工具函数添加测试 +- [ ] 为核心业务逻辑添加测试 + +#### 8.2 集成测试 +- [ ] 测试页面跳转流程 +- [ ] 测试数据持久化 +- [ ] 测试错误处理 + +## 技术实现细节 + +### 路由配置更新 +```typescript +// app.config.ts +export default defineAppConfig({ + pages: [ + 'pages/home/index', // 新首页 + 'pages/generate/index', // 生成页面 + 'pages/result/index', // 结果页面 + 'pages/history/index' // 历史记录页面 + ], + tabBar: { + list: [ + { + pagePath: 'pages/home/index', + text: '首页', + iconPath: 'assets/icons/home.png', + selectedIconPath: 'assets/icons/home-active.png' + }, + { + pagePath: 'pages/history/index', + text: '历史记录', + iconPath: 'assets/icons/history.png', + selectedIconPath: 'assets/icons/history-active.png' + } + ] + }, + // ...其他配置 +}) +``` + +### 数据流设计 +``` +首页模板选择 → 生成页面(携带模板信息) → 生成过程记录到历史 → 完成后更新历史状态 → 历史页面展示 +``` + +## 开发优先级说明 + +### 高优先级 (第1-3周) +- 导航结构和基础页面创建 +- 数据存储结构设计 +- 模板卡片首页实现 + +### 中优先级 (第4-6周) +- 生成页面重构 +- 历史记录页面开发 +- 组件优化 + +### 低优先级 (第7-8周) +- 用户体验优化 +- 性能优化 +- 测试覆盖 + +## 里程碑检查点 + +1. **Week 2**: 基础页面结构和导航完成 +2. **Week 4**: 模板首页和基础功能完成 +3. **Week 6**: 历史记录功能完整实现 +4. **Week 8**: 完整功能测试和优化完成 + +## 注意事项 + +1. **遵循TDD开发模式** - 先写测试,后写实现 +2. **保持代码简洁** - 每个文件不超过500行 +3. **类型安全** - 严格使用TypeScript类型定义 +4. **平台兼容** - 确保微信小程序、H5、APP多端兼容 +5. **用户体验** - 保持流畅的交互体验 +6. **错误处理** - 完善的错误边界和用户反馈 + +## 依赖和资源需求 + +- **图标资源**: tabBar图标、模板缩略图 +- **样式主题**: 统一的设计风格和颜色规范 +- **API适配**: 确保现有SDK接口兼容新的页面流程 +- **存储方案**: 本地存储和可能的云端同步需求 \ No newline at end of file diff --git a/mini-app.md b/mini-app.md new file mode 100644 index 0000000..79524d4 --- /dev/null +++ b/mini-app.md @@ -0,0 +1,305 @@ +# Taro 抖音小程序发布视频解决方案 + +## 概述 + +抖音小程序提供了官方的视频发布能力,允许用户通过小程序直接拍摄并发布视频到抖音。在 Taro 框架中,我们需要通过特定的方式来调用这个官方 API。 + +## 核心原理 + +抖音小程序的视频发布功能基于两个核心要素: +1. **Button 组件**:设置 `open-type="uploadDouyinVideo"` +2. **Page 钩子**:在页面中配置 `onUploadDouyinVideo` 钩子函数 + +## 实现方案 + +### 方案一:函数组件 + useReady Hook(推荐) + +```typescript +import React, { useState } from 'react'; +import { View, Button, Text } from '@tarojs/components'; +import Taro, { useReady } from '@tarojs/taro'; + +const VideoPublishPage: React.FC = () => { + const [uploadStatus, setUploadStatus] = useState(''); + + useReady(() => { + // 获取当前页面实例 + const pages = Taro.getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + // 配置抖音视频上传钩子 + currentPage.onUploadDouyinVideo = function(uploadOptions) { + console.log('抖音视频上传参数:', uploadOptions); + + setUploadStatus('正在处理视频...'); + + // 处理上传结果 + if (uploadOptions.errMsg) { + if (uploadOptions.errMsg.includes('ok')) { + setUploadStatus('视频发布成功!'); + Taro.showToast({ + title: '发布成功', + icon: 'success' + }); + } else { + setUploadStatus('视频发布失败'); + Taro.showToast({ + title: '发布失败', + icon: 'error' + }); + } + } + }; + }); + + return ( + + + 发布抖音视频 + + + + {/* 关键:Button 组件设置 openType */} + + + {uploadStatus && ( + + {uploadStatus} + + )} + + + ); +}; + +export default VideoPublishPage; +``` + +### 方案二:类组件实现 + +```typescript +import { Component } from 'react'; +import { View, Button } from '@tarojs/components'; +import Taro from '@tarojs/taro'; + +class VideoPublishPage extends Component { + + onReady() { + // 在页面 onReady 生命周期中配置钩子 + this.$instance.page.onUploadDouyinVideo = (uploadOptions) => { + console.log('抖音视频上传参数:', uploadOptions); + this.handleVideoUpload(uploadOptions); + }; + } + + handleVideoUpload = (uploadOptions: any) => { + console.log('处理视频上传:', uploadOptions); + + Taro.showToast({ + title: '视频发布中...', + icon: 'loading' + }); + }; + + render() { + return ( + + + + ); + } +} + +export default VideoPublishPage; +``` + +### 方案三:自定义 Hook 封装 + +```typescript +// hooks/useUploadDouyinVideo.ts +import { useReady } from '@tarojs/taro'; +import Taro from '@tarojs/taro'; + +interface UploadOptions { + errMsg?: string; + [key: string]: any; +} + +export const useUploadDouyinVideo = ( + onUpload: (options: UploadOptions) => void +) => { + useReady(() => { + const pages = Taro.getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + if (currentPage) { + currentPage.onUploadDouyinVideo = onUpload; + } + }); +}; + +// 使用示例 +import React from 'react'; +import { View, Button } from '@tarojs/components'; +import { useUploadDouyinVideo } from '../hooks/useUploadDouyinVideo'; + +const VideoPublishPage: React.FC = () => { + + useUploadDouyinVideo((uploadOptions) => { + console.log('视频上传参数:', uploadOptions); + // 处理上传逻辑 + }); + + return ( + + + + ); +}; +``` + +## 配置要求 + +### 1. app.config.ts 配置 + +```typescript +export default { + pages: [ + 'pages/video-publish/index' + ], + window: { + backgroundTextStyle: 'light', + navigationBarBackgroundColor: '#fff', + navigationBarTitleText: '抖音视频发布', + navigationBarTextStyle: 'black' + }, + // 抖音小程序权限配置 + permission: { + 'scope.camera': { + desc: '需要使用摄像头拍摄视频' + }, + 'scope.writePhotosAlbum': { + desc: '需要保存视频到相册' + } + } +} +``` + +### 2. 样式配置 + +```scss +// video-publish/index.scss +.video-publish-container { + padding: 40px 32px; + + .header { + text-align: center; + margin-bottom: 60px; + + .title { + font-size: 36px; + font-weight: bold; + color: #333; + } + } + + .content { + .upload-button { + width: 100%; + height: 88px; + font-size: 32px; + border-radius: 12px; + margin-bottom: 40px; + } + + .status { + text-align: center; + padding: 20px; + background-color: #f5f5f5; + border-radius: 8px; + + text { + color: #666; + font-size: 28px; + } + } + } +} +``` + +## 关键要点 + +### 1. 必须配置的两个要素 +- **Button 组件**:`openType="uploadDouyinVideo"` +- **钩子函数**:`onUploadDouyinVideo` + +### 2. 钩子函数配置时机 +- 必须在页面的 `onReady` 生命周期或之后配置 +- 函数组件使用 `useReady` Hook +- 类组件在 `onReady` 方法中配置 + +### 3. 参数说明 +```typescript +// uploadOptions 参数结构 +interface UploadOptions { + errMsg: string; // 上传结果信息 + // 其他抖音官方返回的参数 +} +``` + +## 故障排除 + +### 问题1:钩子函数不触发 + +**解决方案**:确保在正确的时机配置钩子 + +```typescript +useReady(() => { + // 延迟配置,确保页面完全加载 + setTimeout(() => { + const pages = Taro.getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + if (currentPage) { + currentPage.onUploadDouyinVideo = function(uploadOptions) { + console.log('视频上传回调:', uploadOptions); + }; + } + }, 100); +}); +``` + +### 问题2:Button 点击无反应 + +**检查清单**: +- [ ] `openType` 属性是否正确设置为 `"uploadDouyinVideo"` +- [ ] 是否在真实的抖音小程序环境中测试 +- [ ] 权限配置是否正确 + +### 问题3:Taro 版本兼容性 + +**建议**: +- 使用 Taro 3.x 以上版本 +- 确保抖音小程序基础库版本支持该功能 + +## 注意事项 + +1. **测试环境**:该功能需要在真实的抖音小程序环境中测试,开发工具可能无法完全模拟 +2. **权限申请**:需要在小程序后台申请相应的视频发布权限 +3. **用户体验**:建议添加加载状态和错误处理 +4. **版本兼容**:确保 Taro 版本和抖音小程序基础库版本的兼容性 + +## 参考资料 + +- [抖音小程序官方文档 - 发布抖音视频](https://developer.open-douyin.com/docs/resource/zh-CN/mini-app/develop/api/open-interface/video-capacity/upload-douyin-video) +- [Taro GitHub Issue #13882](https://github.com/NervJS/taro/issues/13882) diff --git a/package.json b/package.json index c7aeb14..1927a7b 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ "@tarojs/shared": "4.1.6", "@tarojs/taro": "4.1.6", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "zustand": "^5.0.8" }, "devDependencies": { "@babel/core": "^7.24.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ed0eb2..c6143e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: react-dom: specifier: ^18.0.0 version: 18.3.1(react@18.3.1) + zustand: + specifier: ^5.0.8 + version: 5.0.8(@types/react@18.3.24)(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.24.4 @@ -6143,6 +6146,24 @@ packages: yup@1.7.0: resolution: {integrity: sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==} + zustand@5.0.8: + resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@adobe/css-tools@4.4.4': {} @@ -13069,3 +13090,8 @@ snapshots: tiny-case: 1.0.3 toposort: 2.0.2 type-fest: 2.19.0 + + zustand@5.0.8(@types/react@18.3.24)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.24 + react: 18.3.1 diff --git a/src/app.config.ts b/src/app.config.ts index aabf258..21d9157 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,12 +1,36 @@ + export default defineAppConfig({ pages: [ - 'pages/index/index', + 'pages/home/index', // 新的模板卡片首页 + 'pages/history/index', // 历史记录页面 + 'pages/generate/index', // 生成处理页面 + 'pages/index/index', // 保留原有首页,暂时作为模板页面 'pages/result/index' ], + tabBar: { + color: '#999999', + selectedColor: '#007AFF', + backgroundColor: '#FFFFFF', + borderStyle: 'black', + list: [ + { + pagePath: 'pages/home/index', + text: '首页', + iconPath: 'assets/icons/default/shouye.png', + selectedIconPath: 'assets/icons/activate/shouye.png' + }, + { + pagePath: 'pages/history/index', + text: '历史记录', + iconPath: 'assets/icons/default/shoulijindu.png', + selectedIconPath: 'assets/icons/activate/shoulijindu.png' + } + ] + }, window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', - navigationBarTitleText: 'WeChat', + navigationBarTitleText: '图生视频', navigationBarTextStyle: 'black' - } + }, }) diff --git a/src/assets/icons/activate/fuwuchengnuo.png b/src/assets/icons/activate/fuwuchengnuo.png new file mode 100644 index 0000000..f646949 Binary files /dev/null and b/src/assets/icons/activate/fuwuchengnuo.png differ diff --git a/src/assets/icons/activate/fuwuwangdian.png b/src/assets/icons/activate/fuwuwangdian.png new file mode 100644 index 0000000..ddc48cb Binary files /dev/null and b/src/assets/icons/activate/fuwuwangdian.png differ diff --git a/src/assets/icons/activate/fuwuzixun-weixuanzhong.png b/src/assets/icons/activate/fuwuzixun-weixuanzhong.png new file mode 100644 index 0000000..3cc614d Binary files /dev/null and b/src/assets/icons/activate/fuwuzixun-weixuanzhong.png differ diff --git a/src/assets/icons/activate/fuwuzixun.png b/src/assets/icons/activate/fuwuzixun.png new file mode 100644 index 0000000..247d41b Binary files /dev/null and b/src/assets/icons/activate/fuwuzixun.png differ diff --git a/src/assets/icons/activate/guohushenqing.png b/src/assets/icons/activate/guohushenqing.png new file mode 100644 index 0000000..01fa90f Binary files /dev/null and b/src/assets/icons/activate/guohushenqing.png differ diff --git a/src/assets/icons/activate/qifeijiaona.png b/src/assets/icons/activate/qifeijiaona.png new file mode 100644 index 0000000..9641ced Binary files /dev/null and b/src/assets/icons/activate/qifeijiaona.png differ diff --git a/src/assets/icons/activate/quanbuyewu-weixuanzhong.png b/src/assets/icons/activate/quanbuyewu-weixuanzhong.png new file mode 100644 index 0000000..305fb95 Binary files /dev/null and b/src/assets/icons/activate/quanbuyewu-weixuanzhong.png differ diff --git a/src/assets/icons/activate/quanbuyewu.png b/src/assets/icons/activate/quanbuyewu.png new file mode 100644 index 0000000..2bb4081 Binary files /dev/null and b/src/assets/icons/activate/quanbuyewu.png differ diff --git a/src/assets/icons/activate/rexiandianhua.png b/src/assets/icons/activate/rexiandianhua.png new file mode 100644 index 0000000..e757f21 Binary files /dev/null and b/src/assets/icons/activate/rexiandianhua.png differ diff --git a/src/assets/icons/activate/shoufeibiaozhun.png b/src/assets/icons/activate/shoufeibiaozhun.png new file mode 100644 index 0000000..79c6890 Binary files /dev/null and b/src/assets/icons/activate/shoufeibiaozhun.png differ diff --git a/src/assets/icons/activate/shoulijindu-xuanzhong.png b/src/assets/icons/activate/shoulijindu-xuanzhong.png new file mode 100644 index 0000000..872acf6 Binary files /dev/null and b/src/assets/icons/activate/shoulijindu-xuanzhong.png differ diff --git a/src/assets/icons/activate/shoulijindu.png b/src/assets/icons/activate/shoulijindu.png new file mode 100644 index 0000000..059e1fe Binary files /dev/null and b/src/assets/icons/activate/shoulijindu.png differ diff --git a/src/assets/icons/activate/shoulijindu_1.png b/src/assets/icons/activate/shoulijindu_1.png new file mode 100644 index 0000000..1a66d11 Binary files /dev/null and b/src/assets/icons/activate/shoulijindu_1.png differ diff --git a/src/assets/icons/activate/shouye-weixuanzhong.png b/src/assets/icons/activate/shouye-weixuanzhong.png new file mode 100644 index 0000000..71afd30 Binary files /dev/null and b/src/assets/icons/activate/shouye-weixuanzhong.png differ diff --git a/src/assets/icons/activate/shouye.png b/src/assets/icons/activate/shouye.png new file mode 100644 index 0000000..df653e1 Binary files /dev/null and b/src/assets/icons/activate/shouye.png differ diff --git a/src/assets/icons/activate/wode-weixuanzhong.png b/src/assets/icons/activate/wode-weixuanzhong.png new file mode 100644 index 0000000..fa0d8cf Binary files /dev/null and b/src/assets/icons/activate/wode-weixuanzhong.png differ diff --git a/src/assets/icons/activate/wode.png b/src/assets/icons/activate/wode.png new file mode 100644 index 0000000..39da60b Binary files /dev/null and b/src/assets/icons/activate/wode.png differ diff --git a/src/assets/icons/activate/wodedizhi.png b/src/assets/icons/activate/wodedizhi.png new file mode 100644 index 0000000..5e53295 Binary files /dev/null and b/src/assets/icons/activate/wodedizhi.png differ diff --git a/src/assets/icons/activate/wodetongzhi.png b/src/assets/icons/activate/wodetongzhi.png new file mode 100644 index 0000000..ec6546a Binary files /dev/null and b/src/assets/icons/activate/wodetongzhi.png differ diff --git a/src/assets/icons/activate/yewuzhinan.png b/src/assets/icons/activate/yewuzhinan.png new file mode 100644 index 0000000..f6e6610 Binary files /dev/null and b/src/assets/icons/activate/yewuzhinan.png differ diff --git a/src/assets/icons/activate/yijianfankui.png b/src/assets/icons/activate/yijianfankui.png new file mode 100644 index 0000000..fa191ce Binary files /dev/null and b/src/assets/icons/activate/yijianfankui.png differ diff --git a/src/assets/icons/activate/yonghubaozhuang.png b/src/assets/icons/activate/yonghubaozhuang.png new file mode 100644 index 0000000..8cb72c9 Binary files /dev/null and b/src/assets/icons/activate/yonghubaozhuang.png differ diff --git a/src/assets/icons/activate/yonghugaizhuang.png b/src/assets/icons/activate/yonghugaizhuang.png new file mode 100644 index 0000000..1c5bdde Binary files /dev/null and b/src/assets/icons/activate/yonghugaizhuang.png differ diff --git a/src/assets/icons/activate/yuangongshibie.png b/src/assets/icons/activate/yuangongshibie.png new file mode 100644 index 0000000..c1bf516 Binary files /dev/null and b/src/assets/icons/activate/yuangongshibie.png differ diff --git a/src/assets/icons/activate/yuyuedianhuo.png b/src/assets/icons/activate/yuyuedianhuo.png new file mode 100644 index 0000000..5c23ba1 Binary files /dev/null and b/src/assets/icons/activate/yuyuedianhuo.png differ diff --git a/src/assets/icons/activate/yuyuekaitong.png b/src/assets/icons/activate/yuyuekaitong.png new file mode 100644 index 0000000..3f4b685 Binary files /dev/null and b/src/assets/icons/activate/yuyuekaitong.png differ diff --git a/src/assets/icons/activate/yuyueweixiu.png b/src/assets/icons/activate/yuyueweixiu.png new file mode 100644 index 0000000..a09d657 Binary files /dev/null and b/src/assets/icons/activate/yuyueweixiu.png differ diff --git a/src/assets/icons/activate/yuyuezhongxin.png b/src/assets/icons/activate/yuyuezhongxin.png new file mode 100644 index 0000000..81b6ba8 Binary files /dev/null and b/src/assets/icons/activate/yuyuezhongxin.png differ diff --git a/src/assets/icons/activate/zhangdanchaxun.png b/src/assets/icons/activate/zhangdanchaxun.png new file mode 100644 index 0000000..cbfb19b Binary files /dev/null and b/src/assets/icons/activate/zhangdanchaxun.png differ diff --git a/src/assets/icons/default/fuwuchengnuo.png b/src/assets/icons/default/fuwuchengnuo.png new file mode 100644 index 0000000..652cf4a Binary files /dev/null and b/src/assets/icons/default/fuwuchengnuo.png differ diff --git a/src/assets/icons/default/fuwuwangdian.png b/src/assets/icons/default/fuwuwangdian.png new file mode 100644 index 0000000..086b91c Binary files /dev/null and b/src/assets/icons/default/fuwuwangdian.png differ diff --git a/src/assets/icons/default/fuwuzixun-weixuanzhong.png b/src/assets/icons/default/fuwuzixun-weixuanzhong.png new file mode 100644 index 0000000..f7ff1da Binary files /dev/null and b/src/assets/icons/default/fuwuzixun-weixuanzhong.png differ diff --git a/src/assets/icons/default/fuwuzixun.png b/src/assets/icons/default/fuwuzixun.png new file mode 100644 index 0000000..2bf598a Binary files /dev/null and b/src/assets/icons/default/fuwuzixun.png differ diff --git a/src/assets/icons/default/guohushenqing.png b/src/assets/icons/default/guohushenqing.png new file mode 100644 index 0000000..e24e5ab Binary files /dev/null and b/src/assets/icons/default/guohushenqing.png differ diff --git a/src/assets/icons/default/qifeijiaona.png b/src/assets/icons/default/qifeijiaona.png new file mode 100644 index 0000000..b80f11f Binary files /dev/null and b/src/assets/icons/default/qifeijiaona.png differ diff --git a/src/assets/icons/default/quanbuyewu-weixuanzhong.png b/src/assets/icons/default/quanbuyewu-weixuanzhong.png new file mode 100644 index 0000000..6b6b6c4 Binary files /dev/null and b/src/assets/icons/default/quanbuyewu-weixuanzhong.png differ diff --git a/src/assets/icons/default/quanbuyewu.png b/src/assets/icons/default/quanbuyewu.png new file mode 100644 index 0000000..e22cd60 Binary files /dev/null and b/src/assets/icons/default/quanbuyewu.png differ diff --git a/src/assets/icons/default/rexiandianhua.png b/src/assets/icons/default/rexiandianhua.png new file mode 100644 index 0000000..34b7578 Binary files /dev/null and b/src/assets/icons/default/rexiandianhua.png differ diff --git a/src/assets/icons/default/shoufeibiaozhun.png b/src/assets/icons/default/shoufeibiaozhun.png new file mode 100644 index 0000000..4ff3752 Binary files /dev/null and b/src/assets/icons/default/shoufeibiaozhun.png differ diff --git a/src/assets/icons/default/shoulijindu-xuanzhong.png b/src/assets/icons/default/shoulijindu-xuanzhong.png new file mode 100644 index 0000000..fca484c Binary files /dev/null and b/src/assets/icons/default/shoulijindu-xuanzhong.png differ diff --git a/src/assets/icons/default/shoulijindu.png b/src/assets/icons/default/shoulijindu.png new file mode 100644 index 0000000..77a366c Binary files /dev/null and b/src/assets/icons/default/shoulijindu.png differ diff --git a/src/assets/icons/default/shoulijindu_1.png b/src/assets/icons/default/shoulijindu_1.png new file mode 100644 index 0000000..ee99c6d Binary files /dev/null and b/src/assets/icons/default/shoulijindu_1.png differ diff --git a/src/assets/icons/default/shouye-weixuanzhong.png b/src/assets/icons/default/shouye-weixuanzhong.png new file mode 100644 index 0000000..6e7291a Binary files /dev/null and b/src/assets/icons/default/shouye-weixuanzhong.png differ diff --git a/src/assets/icons/default/shouye.png b/src/assets/icons/default/shouye.png new file mode 100644 index 0000000..3bcc91e Binary files /dev/null and b/src/assets/icons/default/shouye.png differ diff --git a/src/assets/icons/default/wode-weixuanzhong.png b/src/assets/icons/default/wode-weixuanzhong.png new file mode 100644 index 0000000..7818210 Binary files /dev/null and b/src/assets/icons/default/wode-weixuanzhong.png differ diff --git a/src/assets/icons/default/wode.png b/src/assets/icons/default/wode.png new file mode 100644 index 0000000..1fb5dab Binary files /dev/null and b/src/assets/icons/default/wode.png differ diff --git a/src/assets/icons/default/wodedizhi.png b/src/assets/icons/default/wodedizhi.png new file mode 100644 index 0000000..9f7dfdd Binary files /dev/null and b/src/assets/icons/default/wodedizhi.png differ diff --git a/src/assets/icons/default/wodetongzhi.png b/src/assets/icons/default/wodetongzhi.png new file mode 100644 index 0000000..8e52af4 Binary files /dev/null and b/src/assets/icons/default/wodetongzhi.png differ diff --git a/src/assets/icons/default/yewuzhinan.png b/src/assets/icons/default/yewuzhinan.png new file mode 100644 index 0000000..dc99412 Binary files /dev/null and b/src/assets/icons/default/yewuzhinan.png differ diff --git a/src/assets/icons/default/yijianfankui.png b/src/assets/icons/default/yijianfankui.png new file mode 100644 index 0000000..94a89ab Binary files /dev/null and b/src/assets/icons/default/yijianfankui.png differ diff --git a/src/assets/icons/default/yonghubaozhuang.png b/src/assets/icons/default/yonghubaozhuang.png new file mode 100644 index 0000000..e0caf8c Binary files /dev/null and b/src/assets/icons/default/yonghubaozhuang.png differ diff --git a/src/assets/icons/default/yonghugaizhuang.png b/src/assets/icons/default/yonghugaizhuang.png new file mode 100644 index 0000000..4d9dc19 Binary files /dev/null and b/src/assets/icons/default/yonghugaizhuang.png differ diff --git a/src/assets/icons/default/yuangongshibie.png b/src/assets/icons/default/yuangongshibie.png new file mode 100644 index 0000000..3d33f0f Binary files /dev/null and b/src/assets/icons/default/yuangongshibie.png differ diff --git a/src/assets/icons/default/yuyuedianhuo.png b/src/assets/icons/default/yuyuedianhuo.png new file mode 100644 index 0000000..5e0cfa5 Binary files /dev/null and b/src/assets/icons/default/yuyuedianhuo.png differ diff --git a/src/assets/icons/default/yuyuekaitong.png b/src/assets/icons/default/yuyuekaitong.png new file mode 100644 index 0000000..125068f Binary files /dev/null and b/src/assets/icons/default/yuyuekaitong.png differ diff --git a/src/assets/icons/default/yuyueweixiu.png b/src/assets/icons/default/yuyueweixiu.png new file mode 100644 index 0000000..287de25 Binary files /dev/null and b/src/assets/icons/default/yuyueweixiu.png differ diff --git a/src/assets/icons/default/yuyuezhongxin.png b/src/assets/icons/default/yuyuezhongxin.png new file mode 100644 index 0000000..4cac0f1 Binary files /dev/null and b/src/assets/icons/default/yuyuezhongxin.png differ diff --git a/src/assets/icons/default/zhangdanchaxun.png b/src/assets/icons/default/zhangdanchaxun.png new file mode 100644 index 0000000..68044c6 Binary files /dev/null and b/src/assets/icons/default/zhangdanchaxun.png differ diff --git a/src/assets/templates/3d-animation.png b/src/assets/templates/3d-animation.png new file mode 100644 index 0000000..093193d --- /dev/null +++ b/src/assets/templates/3d-animation.png @@ -0,0 +1 @@ +# 3D动画缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/animation.png b/src/assets/templates/animation.png new file mode 100644 index 0000000..af45937 --- /dev/null +++ b/src/assets/templates/animation.png @@ -0,0 +1 @@ +# 动画效果缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/art-style.png b/src/assets/templates/art-style.png new file mode 100644 index 0000000..b5ede9e --- /dev/null +++ b/src/assets/templates/art-style.png @@ -0,0 +1 @@ +# 艺术风格缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/cartoon.png b/src/assets/templates/cartoon.png new file mode 100644 index 0000000..55b6c70 --- /dev/null +++ b/src/assets/templates/cartoon.png @@ -0,0 +1 @@ +# 卡通风格缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/sketch.png b/src/assets/templates/sketch.png new file mode 100644 index 0000000..e119163 --- /dev/null +++ b/src/assets/templates/sketch.png @@ -0,0 +1 @@ +# 素描风格缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/video-gen.png b/src/assets/templates/video-gen.png new file mode 100644 index 0000000..14ba048 --- /dev/null +++ b/src/assets/templates/video-gen.png @@ -0,0 +1 @@ +# 视频生成缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/vintage.png b/src/assets/templates/vintage.png new file mode 100644 index 0000000..4f90a37 --- /dev/null +++ b/src/assets/templates/vintage.png @@ -0,0 +1 @@ +# 复古滤镜缩略图 - 需要替换为实际图片 diff --git a/src/assets/templates/watercolor.png b/src/assets/templates/watercolor.png new file mode 100644 index 0000000..f6e4103 --- /dev/null +++ b/src/assets/templates/watercolor.png @@ -0,0 +1 @@ +# 水彩画缩略图 - 需要替换为实际图片 diff --git a/src/components/TemplateCard/index.css b/src/components/TemplateCard/index.css new file mode 100644 index 0000000..f85dacc --- /dev/null +++ b/src/components/TemplateCard/index.css @@ -0,0 +1,104 @@ +.template-card { + background: #fff; + border-radius: 16px; + padding: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + transition: all 0.2s ease; + cursor: pointer; + display: flex; + flex-direction: column; + height: 160px; + position: relative; + overflow: hidden; +} + +.template-card:active { + transform: scale(0.98); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.template-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(0, 122, 255, 0.05), rgba(90, 200, 250, 0.05)); + pointer-events: none; +} + +.template-thumbnail { + width: 48px; + height: 48px; + border-radius: 12px; + background: linear-gradient(135deg, #007AFF, #5AC8FA); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 12px; + position: relative; + overflow: hidden; +} + +.template-icon { + font-size: 20px; + z-index: 1; +} + +.template-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.template-info { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.template-name { + font-size: 16px; + font-weight: 600; + color: #333; + line-height: 1.3; + margin-bottom: 6px; + display: block; +} + +.template-desc { + font-size: 13px; + color: #666; + line-height: 1.4; + margin-bottom: 12px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.template-meta { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; +} + +.template-type { + font-size: 11px; + font-weight: 500; + padding: 4px 8px; + border-radius: 12px; + display: inline-block; + white-space: nowrap; +} + +.template-category { + font-size: 11px; + color: #8E8E93; + font-weight: 400; +} \ No newline at end of file diff --git a/src/components/TemplateCard/index.tsx b/src/components/TemplateCard/index.tsx new file mode 100644 index 0000000..34ad8f8 --- /dev/null +++ b/src/components/TemplateCard/index.tsx @@ -0,0 +1,57 @@ +import { View, Text, Image } from '@tarojs/components' +import { Template } from '../../store/types' +import './index.css' + +interface TemplateCardProps { + template: Template + onClick: (template: Template) => void +} + +export default function TemplateCard({ template, onClick }: TemplateCardProps) { + const handleClick = () => { + onClick(template) + } + + const getTypeText = (type: Template['type']) => { + return type === 'image-to-video' ? '图生视频' : '图生图' + } + + const getTypeColor = (type: Template['type']) => { + return type === 'image-to-video' ? '#FF6B35' : '#007AFF' + } + + return ( + + + {/* 暂时使用emoji作为占位符,后续替换为真实图片 */} + + {template.type === 'image-to-video' ? '🎬' : '🎨'} + + {/* + + */} + + + + {template.name} + {template.description} + + + {getTypeText(template.type)} + + {template.category} + + + + ) +} \ No newline at end of file diff --git a/src/config/templates.ts b/src/config/templates.ts new file mode 100644 index 0000000..cac989b --- /dev/null +++ b/src/config/templates.ts @@ -0,0 +1,84 @@ +import { Template } from '../store/types' + +// 模板配置数据 +export const TEMPLATE_CONFIG: Template[] = [ + { + id: '1', + name: '艺术风格', + description: '将照片转为艺术风格', + type: 'image-to-image', + thumbnail: '/assets/templates/art-style.png', + category: '风格转换' + }, + { + id: '2', + name: '视频生成', + description: '图片生成动态视频', + type: 'image-to-video', + thumbnail: '/assets/templates/video-gen.png', + category: '视频生成' + }, + { + id: '3', + name: '卡通风格', + description: '真人照片卡通化', + type: 'image-to-image', + thumbnail: '/assets/templates/cartoon.png', + category: '风格转换' + }, + { + id: '4', + name: '复古滤镜', + description: '添加复古质感', + type: 'image-to-image', + thumbnail: '/assets/templates/vintage.png', + category: '滤镜效果' + }, + { + id: '5', + name: '动画效果', + description: '制作动画视频', + type: 'image-to-video', + thumbnail: '/assets/templates/animation.png', + category: '视频生成' + }, + { + id: '6', + name: '素描风格', + description: '转换为素描效果', + type: 'image-to-image', + thumbnail: '/assets/templates/sketch.png', + category: '风格转换' + }, + { + id: '7', + name: '水彩画', + description: '水彩画风格转换', + type: 'image-to-image', + thumbnail: '/assets/templates/watercolor.png', + category: '风格转换' + }, + { + id: '8', + name: '3D动画', + description: '生成3D动画效果', + type: 'image-to-video', + thumbnail: '/assets/templates/3d-animation.png', + category: '视频生成' + } +] + +// 根据类型获取模板 +export const getTemplatesByType = (type: 'image-to-image' | 'image-to-video'): Template[] => { + return TEMPLATE_CONFIG.filter(template => template.type === type) +} + +// 根据分类获取模板 +export const getTemplatesByCategory = (category: string): Template[] => { + return TEMPLATE_CONFIG.filter(template => template.category === category) +} + +// 根据ID获取模板 +export const getTemplateById = (id: string): Template | undefined => { + return TEMPLATE_CONFIG.find(template => template.id === id) +} \ No newline at end of file diff --git a/src/hooks/index.ts b/src/hooks/index.ts index bde49ec..4a80df8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,5 @@ export { useAd } from './useAd' -export { useSdk } from './useSdk' \ No newline at end of file +export { useSdk } from './useSdk' +export { useUploadVideo } from './useUploadVideo' \ No newline at end of file diff --git a/src/hooks/useUploadVideo.ts b/src/hooks/useUploadVideo.ts new file mode 100644 index 0000000..13fdf7e --- /dev/null +++ b/src/hooks/useUploadVideo.ts @@ -0,0 +1,20 @@ +import { useReady } from '@tarojs/taro'; +import Taro from '@tarojs/taro'; + +interface UploadOptions { + errMsg?: string; + [key: string]: any; +} + +export const useUploadVideo = ( + onUpload: (options: UploadOptions) => void +) => { + useReady(() => { + const pages = Taro.getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + if (currentPage) { + currentPage.onUploadDouyinVideo = onUpload; + } + }); +}; \ No newline at end of file diff --git a/src/pages/generate/index.config.ts b/src/pages/generate/index.config.ts new file mode 100644 index 0000000..2f3d912 --- /dev/null +++ b/src/pages/generate/index.config.ts @@ -0,0 +1,8 @@ +export default definePageConfig({ + navigationBarTitleText: '生成中', + navigationBarBackgroundColor: '#2c3e50', + navigationBarTextStyle: 'white', + backgroundColorTop: '#2c3e50', + backgroundColorBottom: '#34495e', + backgroundColor: '#2c3e50' +}) \ No newline at end of file diff --git a/src/pages/generate/index.css b/src/pages/generate/index.css new file mode 100644 index 0000000..5b610c4 --- /dev/null +++ b/src/pages/generate/index.css @@ -0,0 +1,4 @@ +.generate { + padding: 20px; + text-align: center; +} \ No newline at end of file diff --git a/src/pages/generate/index.tsx b/src/pages/generate/index.tsx new file mode 100644 index 0000000..0ab7d1f --- /dev/null +++ b/src/pages/generate/index.tsx @@ -0,0 +1,11 @@ +import { View, Text } from '@tarojs/components' +import './index.css' + +// 暂时的占位页面,后续会迁移原有的生成逻辑 +export default function Generate() { + return ( + + 生成处理页面(开发中) + + ) +} \ No newline at end of file diff --git a/src/pages/history/index.config.ts b/src/pages/history/index.config.ts new file mode 100644 index 0000000..d34f74e --- /dev/null +++ b/src/pages/history/index.config.ts @@ -0,0 +1,8 @@ +export default definePageConfig({ + navigationBarTitleText: '历史记录', + navigationBarBackgroundColor: '#2c3e50', + navigationBarTextStyle: 'white', + backgroundColorTop: '#2c3e50', + backgroundColorBottom: '#34495e', + backgroundColor: '#2c3e50' +}) \ No newline at end of file diff --git a/src/pages/history/index.css b/src/pages/history/index.css new file mode 100644 index 0000000..f3c1af6 --- /dev/null +++ b/src/pages/history/index.css @@ -0,0 +1,134 @@ +.history { + background-color: #f8f9fa; + min-height: 100vh; +} + +.history-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 16px 16px; + background: #fff; + border-bottom: 1px solid #eee; +} + +.history-title { + font-size: 20px; + font-weight: 600; + color: #333; +} + +.clear-btn { + font-size: 16px; + color: #007AFF; + padding: 8px; + cursor: pointer; +} + +.history-list { + height: calc(100vh - 160px); + padding: 16px; +} + +.empty-state { + text-align: center; + padding: 80px 20px; +} + +.empty-icon { + font-size: 60px; + display: block; + margin-bottom: 20px; +} + +.empty-text { + font-size: 18px; + color: #333; + display: block; + margin-bottom: 8px; +} + +.empty-desc { + font-size: 14px; + color: #666; + display: block; +} + +.history-item { + background: #fff; + border-radius: 12px; + padding: 16px; + margin-bottom: 12px; + display: flex; + align-items: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.item-thumbnail { + width: 50px; + height: 50px; + background: linear-gradient(135deg, #007AFF, #5AC8FA); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 16px; + flex-shrink: 0; +} + +.thumbnail-icon { + font-size: 20px; +} + +.item-content { + flex: 1; + min-width: 0; +} + +.item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.item-title { + font-size: 16px; + font-weight: 600; + color: #333; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.item-status { + font-size: 12px; + font-weight: 500; + padding: 4px 8px; + border-radius: 10px; + background: rgba(142, 142, 147, 0.1); + margin-left: 12px; + flex-shrink: 0; +} + +.item-info { + display: flex; + justify-content: space-between; + align-items: center; +} + +.item-type { + font-size: 13px; + color: #666; + background: rgba(0, 122, 255, 0.1); + color: #007AFF; + padding: 3px 8px; + border-radius: 8px; + font-size: 12px; +} + +.item-time { + font-size: 12px; + color: #8E8E93; +} \ No newline at end of file diff --git a/src/pages/history/index.tsx b/src/pages/history/index.tsx new file mode 100644 index 0000000..c0f0ca8 --- /dev/null +++ b/src/pages/history/index.tsx @@ -0,0 +1,123 @@ +import { View, Text, ScrollView } from '@tarojs/components' +import { useEffect } from 'react' +import Taro from '@tarojs/taro' +import { useHistoryStore } from '../../store' +import './index.css' + +export default function History() { + const { + records, + loadRecords, + clearRecords + } = useHistoryStore() + + useEffect(() => { + loadRecords() + }, [loadRecords]) + + const clearHistory = () => { + Taro.showModal({ + title: '确认清空', + content: '是否清空所有历史记录?', + success: async (res) => { + if (res.confirm) { + try { + await clearRecords() + Taro.showToast({ + title: '清空成功', + icon: 'success' + }) + } catch (error) { + Taro.showToast({ + title: '清空失败', + icon: 'error' + }) + } + } + } + }) + } + + const getStatusText = (status: string) => { + switch (status) { + case 'generating': return '生成中' + case 'completed': return '已完成' + case 'failed': return '生成失败' + default: return '未知状态' + } + } + + const getStatusColor = (status: string) => { + switch (status) { + case 'generating': return '#FF9500' + case 'completed': return '#34C759' + case 'failed': return '#FF3B30' + default: return '#8E8E93' + } + } + + const formatTime = (timeStr: string) => { + const date = new Date(timeStr) + return date.toLocaleString('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }) + } + + return ( + + + 历史记录 + {records.length > 0 && ( + 清空 + )} + + + + {records.length === 0 ? ( + + 📝 + 暂无历史记录 + 开始创作你的第一个作品吧 + + ) : ( + records.map((record) => ( + + + + {record.type === 'image-to-video' ? '🎬' : '🖼️'} + + + + + + {record.templateName} + + {getStatusText(record.status)} + + + + + + {record.type === 'image-to-video' ? '图生视频' : '图生图'} + + {formatTime(record.createTime)} + + + + )) + )} + + + ) +} \ No newline at end of file diff --git a/src/pages/home/index.config.ts b/src/pages/home/index.config.ts new file mode 100644 index 0000000..ca3a363 --- /dev/null +++ b/src/pages/home/index.config.ts @@ -0,0 +1,8 @@ +export default definePageConfig({ + navigationBarTitleText: '首页', + navigationBarBackgroundColor: '#2c3e50', + navigationBarTextStyle: 'white', + backgroundColorTop: '#2c3e50', + backgroundColorBottom: '#34495e', + backgroundColor: '#2c3e50' +}) \ No newline at end of file diff --git a/src/pages/home/index.css b/src/pages/home/index.css new file mode 100644 index 0000000..e2c2845 --- /dev/null +++ b/src/pages/home/index.css @@ -0,0 +1,32 @@ +.home { + padding: 20px 16px; + background-color: #f8f9fa; + min-height: 100vh; +} + +.home-header { + text-align: center; + margin-bottom: 32px; +} + +.home-title { + font-size: 28px; + font-weight: bold; + color: #333; + display: block; + margin-bottom: 8px; +} + +.home-subtitle { + font-size: 16px; + color: #666; + display: block; +} + +.template-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + max-width: 400px; + margin: 0 auto; +} \ No newline at end of file diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx new file mode 100644 index 0000000..eaf375b --- /dev/null +++ b/src/pages/home/index.tsx @@ -0,0 +1,43 @@ +import { View, Text } from '@tarojs/components' +import { useEffect } from 'react' +import { navigateTo } from '@tarojs/taro' +import { useTemplateStore, Template } from '../../store/index' +import TemplateCard from '../../components/TemplateCard' +import './index.css' + +export default function Home() { + const { templates, initTemplates, setSelectedTemplate } = useTemplateStore() + + useEffect(() => { + initTemplates() + }, [initTemplates]) + + const handleTemplateClick = (template: Template) => { + // 设置选中的模板 + setSelectedTemplate(template) + + // 跳转到原有的功能页面,保持老功能可用 + navigateTo({ + url: `/pages/index/index?templateId=${template.id}&templateName=${encodeURIComponent(template.name)}&templateType=${template.type}` + }) + } + + return ( + + + 选择模板 + 选择你喜欢的风格模板 + + + + {templates.map((template) => ( + + ))} + + + ) +} \ No newline at end of file diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 801d76b..7946915 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -2,10 +2,11 @@ import { View } from '@tarojs/components' import { useState } from 'react' import { navigateTo } from '@tarojs/taro' import './index.css' -import { useSdk } from '../../hooks/index' +import { useSdk, useUploadVideo } from '../../hooks/index' import UploadButton from '../../components/UploadButton' import LoadingOverlay from '../../components/LoadingOverlay' import ErrorOverlay from '../../components/ErrorOverlay' +import { createPlatformFactory } from '../../platforms' type PageStep = 'upload' | 'loading' | 'error' @@ -21,8 +22,36 @@ export default function Index() { error: null }) + const [videoPath, setVidePath] = useState() + + useUploadVideo((res) => { + console.log(res) + return { + videoPath: videoPath, + titleConfig: { + title: `视频发布` + }, + success() { + console.log("视频发布/挂载成功"); + }, + fail() { + console.log("视频发布/挂载失败"); + }, + } + }) + + const imageToVideo = async () => { + const imgUrl = `https://cdn.roasmax.cn/upload/966d254b1859453e80e7de5f155a06fb.png` + console.log(`img url is : ${imgUrl}`) + const res = await sdk.imageToVideo({ img_url: imgUrl, duration: 2 }) + console.log({ imageToVideoResult: res }) + const tempFilePath = await createPlatformFactory().createMedia().downloadFile(res).then(res => res.tempFilePath) + setVidePath(tempFilePath) + } + const chooseAndGenerateImage = async () => { try { + return await imageToVideo() // 选择图片,选择完成后会自动触发loading状态 const task_id = await sdk.chooseAndGenerateImage({ onImageSelected: () => { @@ -52,6 +81,8 @@ export default function Index() { setState({ step: 'upload', error: null }) } + + const renderCurrentStep = () => { switch (state.step) { case 'upload': diff --git a/src/platforms/core.ts b/src/platforms/core.ts index 29191be..99a57df 100644 --- a/src/platforms/core.ts +++ b/src/platforms/core.ts @@ -166,7 +166,13 @@ export abstract class UserInfo { abstract checkSession(): Promise; } - +export interface IDownloadFile { + errMsg: string; + header: { [key: string]: string }; + profile: {}; + statusCode: number; + tempFilePath: string; +} export abstract class Media { - abstract downloadFile(url: string): Promise; + abstract downloadFile(url: string): Promise; } \ No newline at end of file diff --git a/src/platforms/tt.ts b/src/platforms/tt.ts index a4b4923..c001fa7 100644 --- a/src/platforms/tt.ts +++ b/src/platforms/tt.ts @@ -7,7 +7,8 @@ import { UserInfo, IUserProfile, ICheckSession, - Media + Media, + IDownloadFile } from "./core"; /** @@ -244,7 +245,7 @@ export class UserInfoTT extends UserInfo { export class MediaTT extends Media { - downloadFile(url: string): Promise { + downloadFile(url: string): Promise { return new Promise((resolve, reject) => { tt.downloadFile({ url, success: (res: any) => { diff --git a/src/platforms/weapp.ts b/src/platforms/weapp.ts index a41a238..b7d7095 100644 --- a/src/platforms/weapp.ts +++ b/src/platforms/weapp.ts @@ -7,7 +7,8 @@ import { UserInfo, IUserProfile, ICheckSession, - Media + Media, + IDownloadFile } from "./core"; /** @@ -243,7 +244,7 @@ export class UserInfoWeApp extends UserInfo { export class MediaWeApp extends Media { - downloadFile(url: string): Promise { + downloadFile(url: string): Promise { return new Promise((resolve, reject) => { wx.downloadFile({ url, success: (res: any) => { diff --git a/src/store/historyStore.ts b/src/store/historyStore.ts new file mode 100644 index 0000000..0b93708 --- /dev/null +++ b/src/store/historyStore.ts @@ -0,0 +1,137 @@ +import { create } from 'zustand' +import Taro from '@tarojs/taro' +import { HistoryRecord } from './types' + +interface HistoryStore { + records: HistoryRecord[] + loading: boolean + error: string | null + + // Actions + loadRecords: () => Promise + addRecord: (record: Omit) => Promise + updateRecord: (id: string, updates: Partial) => Promise + deleteRecord: (id: string) => Promise + clearRecords: () => Promise + getRecordById: (id: string) => HistoryRecord | undefined + getRecordsByStatus: (status: HistoryRecord['status']) => HistoryRecord[] +} + +const STORAGE_KEY = 'historyRecords' + +export const useHistoryStore = create((set, get) => ({ + records: [], + loading: false, + error: null, + + // 加载历史记录 + loadRecords: async () => { + set({ loading: true, error: null }) + try { + const savedRecords = Taro.getStorageSync(STORAGE_KEY) || [] + set({ records: savedRecords, loading: false }) + } catch (error) { + const errorMsg = error instanceof Error ? error.message : '加载历史记录失败' + set({ error: errorMsg, loading: false }) + console.error('加载历史记录失败:', error) + } + }, + + // 添加新记录 + addRecord: async (recordData) => { + const id = `record_${Date.now()}_${Math.random().toString(36).substr(2, 9)}` + const now = new Date().toISOString() + + const newRecord: HistoryRecord = { + ...recordData, + id, + createTime: now, + updateTime: now + } + + try { + const currentRecords = get().records + const updatedRecords = [newRecord, ...currentRecords] + + await Taro.setStorage({ + key: STORAGE_KEY, + data: updatedRecords + }) + + set({ records: updatedRecords }) + return id + } catch (error) { + const errorMsg = error instanceof Error ? error.message : '添加记录失败' + set({ error: errorMsg }) + console.error('添加记录失败:', error) + throw error + } + }, + + // 更新记录 + updateRecord: async (id, updates) => { + try { + const currentRecords = get().records + const updatedRecords = currentRecords.map(record => + record.id === id + ? { ...record, ...updates, updateTime: new Date().toISOString() } + : record + ) + + await Taro.setStorage({ + key: STORAGE_KEY, + data: updatedRecords + }) + + set({ records: updatedRecords }) + } catch (error) { + const errorMsg = error instanceof Error ? error.message : '更新记录失败' + set({ error: errorMsg }) + console.error('更新记录失败:', error) + throw error + } + }, + + // 删除记录 + deleteRecord: async (id) => { + try { + const currentRecords = get().records + const updatedRecords = currentRecords.filter(record => record.id !== id) + + await Taro.setStorage({ + key: STORAGE_KEY, + data: updatedRecords + }) + + set({ records: updatedRecords }) + } catch (error) { + const errorMsg = error instanceof Error ? error.message : '删除记录失败' + set({ error: errorMsg }) + console.error('删除记录失败:', error) + throw error + } + }, + + // 清空所有记录 + clearRecords: async () => { + try { + await Taro.removeStorage({ key: STORAGE_KEY }) + set({ records: [] }) + } catch (error) { + const errorMsg = error instanceof Error ? error.message : '清空记录失败' + set({ error: errorMsg }) + console.error('清空记录失败:', error) + throw error + } + }, + + // 获取单个记录 + getRecordById: (id) => { + return get().records.find(record => record.id === id) + }, + + // 按状态筛选记录 + getRecordsByStatus: (status) => { + return get().records.filter(record => record.status === status) + } +})) \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..a6123f6 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,4 @@ +// 统一导出所有store +export * from './types' +export * from './historyStore' +export * from './templateStore' \ No newline at end of file diff --git a/src/store/templateStore.ts b/src/store/templateStore.ts new file mode 100644 index 0000000..bcd321c --- /dev/null +++ b/src/store/templateStore.ts @@ -0,0 +1,39 @@ +import { create } from 'zustand' +import { Template } from './types' +import { TEMPLATE_CONFIG } from '../config/templates' + +interface TemplateStore { + templates: Template[] + selectedTemplate: Template | null + + // Actions + initTemplates: () => void + getTemplateById: (id: string) => Template | undefined + setSelectedTemplate: (template: Template | null) => void + getTemplatesByType: (type: 'image-to-image' | 'image-to-video') => Template[] +} + +export const useTemplateStore = create((set, get) => ({ + templates: [], + selectedTemplate: null, + + // 初始化模板数据 + initTemplates: () => { + set({ templates: TEMPLATE_CONFIG }) + }, + + // 根据ID获取模板 + getTemplateById: (id) => { + return get().templates.find(template => template.id === id) + }, + + // 设置选中的模板 + setSelectedTemplate: (template) => { + set({ selectedTemplate: template }) + }, + + // 根据类型筛选模板 + getTemplatesByType: (type) => { + return get().templates.filter(template => template.type === type) + } +})) \ No newline at end of file diff --git a/src/store/types.ts b/src/store/types.ts new file mode 100644 index 0000000..ffbd45f --- /dev/null +++ b/src/store/types.ts @@ -0,0 +1,28 @@ +// 数据类型定义 +export interface HistoryRecord { + id: string + type: 'image-to-image' | 'image-to-video' + templateId: string + templateName: string + inputImage: string + outputResult?: string | string[] + status: 'generating' | 'completed' | 'failed' + createTime: string + updateTime: string + errorMessage?: string +} + +export interface Template { + id: string + name: string + description: string + type: 'image-to-image' | 'image-to-video' + thumbnail: string + category: string +} + +export interface GenerateParams { + templateId: string + imageUrl: string + additionalParams?: Record +} \ No newline at end of file