From 6dfffe123695bb8764ae07f15941f6e3875f6c39 Mon Sep 17 00:00:00 2001 From: hanxuanyu <2252193204@qq.com> Date: Tue, 27 Jan 2026 13:34:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E4=BC=A0=E8=BE=93=E6=97=B6=E7=9A=84=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=94=AF=E6=8C=81=EF=BC=8C=E4=BC=98=E5=8C=96=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/http/handlers/image.go | 17 +- webapp/BUILD.md | 288 -------------------- webapp/CORS_CONFIG.md | 255 ------------------ webapp/README.md | 30 ++- webapp/USAGE.md | 201 -------------- webapp/src/router/index.ts | 9 + webapp/src/views/ApiDocs.vue | 448 ++++++++++++++++++++++++++++++++ webapp/src/views/Home.vue | 37 ++- webapp/vite.config.ts | 22 +- 9 files changed, 546 insertions(+), 761 deletions(-) delete mode 100644 webapp/BUILD.md delete mode 100644 webapp/CORS_CONFIG.md delete mode 100644 webapp/USAGE.md create mode 100644 webapp/src/views/ApiDocs.vue diff --git a/internal/http/handlers/image.go b/internal/http/handlers/image.go index ab4f72d..34baf27 100644 --- a/internal/http/handlers/image.go +++ b/internal/http/handlers/image.go @@ -173,20 +173,30 @@ func handleImageResponse(c *gin.Context, img *model.Image) { mode := config.GetConfig().API.Mode if mode == "redirect" { if selected.PublicURL != "" { + c.Header("Cache-Control", "public, max-age=604800") // 7天 c.Redirect(http.StatusFound, selected.PublicURL) } else if img.URLBase != "" { // 兜底重定向到原始 Bing bingURL := fmt.Sprintf("https://www.bing.com%s_%s.jpg", img.URLBase, selected.Variant) + c.Header("Cache-Control", "public, max-age=604800") // 7天 c.Redirect(http.StatusFound, bingURL) } else { - serveLocal(c, selected.StorageKey) + serveLocal(c, selected.StorageKey, img.Date) } } else { - serveLocal(c, selected.StorageKey) + serveLocal(c, selected.StorageKey, img.Date) } } -func serveLocal(c *gin.Context, key string) { +func serveLocal(c *gin.Context, key string, etag string) { + if etag != "" { + c.Header("ETag", fmt.Sprintf("\"%s\"", etag)) + if c.GetHeader("If-None-Match") == fmt.Sprintf("\"%s\"", etag) { + c.AbortWithStatus(http.StatusNotModified) + return + } + } + reader, contentType, err := storage.GlobalStorage.Get(context.Background(), key) if err != nil { util.Logger.Error("Failed to get image from storage", zap.String("key", key), zap.Error(err)) @@ -198,6 +208,7 @@ func serveLocal(c *gin.Context, key string) { if contentType != "" { c.Header("Content-Type", contentType) } + c.Header("Cache-Control", "public, max-age=604800") // 7天 io.Copy(c.Writer, reader) } diff --git a/webapp/BUILD.md b/webapp/BUILD.md deleted file mode 100644 index b6b0d84..0000000 --- a/webapp/BUILD.md +++ /dev/null @@ -1,288 +0,0 @@ -# BingPaper WebApp 构建说明 - -## 构建配置优化 - -本项目已优化构建配置,支持自定义后端路径和自动输出到上级目录的 `web` 文件夹。 - -## 环境配置 - -### 环境变量 - -项目支持通过环境变量配置后端 API 地址: - -- **开发环境** (`.env.development`):使用完整的后端服务器地址(直连,不使用代理) -- **生产环境** (`.env.production`):使用相对路径 `/api/v1` 访问后端 -- **默认配置** (`.env`):通用配置 - -### 开发环境 vs 生产环境 - -#### 开发环境(npm run dev) -- 直接使用完整的后端 API 地址:`http://localhost:8080/api/v1` -- 不使用代理,前端直接请求后端服务 -- 需要确保后端服务器支持 CORS 跨域请求 -- 优点:配置简单,调试方便,可以清楚看到实际请求 - -#### 生产环境(npm run build) -- 使用相对路径:`/api/v1` -- 前后端部署在同一域名下,无跨域问题 -- Go 服务器同时提供静态文件和 API 服务 - -### 自定义后端路径 - -可以通过修改环境变量 `VITE_API_BASE_URL` 来自定义后端 API 路径: - -```bash -# 开发环境 (.env.development) -VITE_API_BASE_URL=http://localhost:8080/api/v1 - -# 或使用其他端口/域名 -VITE_API_BASE_URL=http://192.168.1.100:8080/api/v1 -VITE_API_BASE_URL=https://api.example.com/api/v1 - -# 生产环境 (.env.production) -VITE_API_BASE_URL=/api/v1 - -# 或自定义路径 -VITE_API_BASE_URL=/custom/api/path -``` - -## 构建命令 - -### 开发环境 - -```bash -# 启动开发服务器(使用完整后端 URL) -npm run dev -``` - -开发环境会直接请求配置的后端服务器地址,无需代理。 - -**注意**:需要确保后端服务器配置了 CORS,允许来自 `http://localhost:5173` 的请求。 - -Go 后端 CORS 配置示例(使用 Gin): -```go -import "github.com/gin-contrib/cors" - -r := gin.Default() -r.Use(cors.New(cors.Config{ - AllowOrigins: []string{"http://localhost:5173"}, - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, - AllowCredentials: true, -})) -``` - -### 生产环境构建 - -```bash -# 标准构建(生产模式) -npm run build - -# 显式生产环境构建 -npm run build:prod - -# 开发模式构建(包含 sourcemap) -npm run build:dev -``` - -### 清理构建 - -```bash -# 清理输出目录 -npm run clean -``` - -## 输出目录 - -构建产物会自动输出到项目上级目录的 `web` 文件夹: - -``` -go-project/ -├── webapp/ # Vue 项目源码 -│ ├── src/ -│ ├── package.json -│ └── vite.config.ts -├── web/ # 构建输出目录(自动生成) -│ ├── index.html -│ ├── assets/ -│ └── ... -└── main.go # Go 主程序 -``` - -## Go 服务器配置 - -Go 服务器需要配置静态文件服务来访问 `web` 目录: - -```go -// 示例:Gin 框架配置 -package main - -import ( - "github.com/gin-gonic/gin" - "github.com/gin-contrib/cors" -) - -func main() { - r := gin.Default() - - // 开发环境:配置 CORS - if gin.Mode() == gin.DebugMode { - r.Use(cors.New(cors.Config{ - AllowOrigins: []string{"http://localhost:5173"}, - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, - AllowCredentials: true, - })) - } - - // API 路由 - api := r.Group("/api/v1") - { - // ... 你的 API 路由 - } - - // 静态文件服务(生产环境) - r.Static("/assets", "./web/assets") - r.StaticFile("/", "./web/index.html") - - // SPA fallback - r.NoRoute(func(c *gin.Context) { - c.File("./web/index.html") - }) - - r.Run(":8080") -} -``` - -## API 使用示例 - -项目提供了完整的 TypeScript API 客户端: - -```typescript -import { bingPaperApi } from '@/lib/api-service' - -// 获取今日图片元数据 -const todayMeta = await bingPaperApi.getTodayImageMeta() - -// 获取图片列表 -const images = await bingPaperApi.getImages({ limit: 10 }) - -// 管理员登录 -const token = await bingPaperApi.login({ password: 'admin123' }) -bingPaperApi.setAuthToken(token.token) -``` - -## 项目结构 - -``` -src/ -├── lib/ -│ ├── api-config.ts # API 配置管理 -│ ├── api-types.ts # TypeScript 类型定义 -│ ├── api-service.ts # API 服务封装 -│ ├── http-client.ts # HTTP 客户端 -│ └── utils.ts # 工具函数 -├── components/ # Vue 组件 -├── views/ # 页面组件 -│ ├── Home.vue # 首页画廊 -│ └── ImageView.vue # 图片查看 -├── composables/ # 组合式函数 -│ └── useImages.ts # 图片数据管理 -├── router/ # 路由配置 -│ └── index.ts -├── App.vue -└── main.ts -``` - -## 部署注意事项 - -1. **构建顺序**:确保在 Go 服务启动前完成前端构建 -2. **路径配置**:Go 服务器的 API 路径应与前端配置的 `VITE_API_BASE_URL` 一致 -3. **静态文件**:Go 服务器需要正确配置静态文件服务路径 -4. **路由处理**:对于 SPA 应用,需要配置 fallback 到 `index.html` -5. **CORS 配置**:开发环境需要配置 CORS,生产环境不需要(同域) - -## 故障排除 - -### API 请求失败(开发环境) - -1. 检查环境变量配置是否正确 -2. 确认 Go 服务器已启动在 `http://localhost:8080` -3. 检查 Go 服务器是否配置了 CORS -4. 在浏览器控制台查看具体错误信息 - -### CORS 错误 - -如果看到类似以下错误: -``` -Access to fetch at 'http://localhost:8080/api/v1/...' from origin 'http://localhost:5173' -has been blocked by CORS policy -``` - -解决方案: -1. 在 Go 服务器添加 CORS 中间件 -2. 或者在 vite.config.ts 中添加代理配置(不推荐,因为会隐藏实际的请求路径) - -### 构建失败 - -1. 清理 `node_modules` 并重新安装依赖 -2. 检查 TypeScript 类型错误 -3. 确认输出目录权限 - -### 静态资源加载失败 - -1. 检查 Go 服务器静态文件配置 -2. 确认构建产物的路径结构 -3. 检查 Vite 构建配置中的 `base` 和 `assetsDir` - -## 开发工作流 - -### 典型的开发流程 - -1. **启动后端服务** - ```bash - cd .. # 回到 Go 项目根目录 - go run main.go - ``` - -2. **启动前端开发服务器** - ```bash - cd webapp - npm run dev - ``` - -3. **访问应用** - ``` - http://localhost:5173/ - ``` - -4. **开发完成后构建** - ```bash - npm run build - ``` - -5. **测试生产构建** - ```bash - # 停止开发服务器,启动 Go 服务器 - cd .. - go run main.go - # 访问 http://localhost:8080/ - ``` - -## 配置对比 - -| 配置项 | 开发环境 | 生产环境 | -|--------|----------|----------| -| API Base URL | `http://localhost:8080/api/v1` | `/api/v1` | -| 请求方式 | 直接请求 | 相对路径 | -| CORS | 需要 | 不需要 | -| 服务器 | 前后端分离 | 同一服务器 | -| 端口 | 前端 5173 + 后端 8080 | 8080 | - -## 最佳实践 - -1. **开发环境**使用完整 URL,便于调试和查看实际请求 -2. **生产环境**使用相对路径,简化部署 -3. 保持 `.env.development` 和 `.env.production` 文件同步更新 -4. 在 Go 服务器中使用环境变量区分开发/生产模式 -5. 定期测试生产构建,确保配置正确 \ No newline at end of file diff --git a/webapp/CORS_CONFIG.md b/webapp/CORS_CONFIG.md deleted file mode 100644 index 86efd1a..0000000 --- a/webapp/CORS_CONFIG.md +++ /dev/null @@ -1,255 +0,0 @@ -# Go 后端 CORS 配置示例 - -## 问题说明 - -开发环境中,前端运行在 `http://localhost:5173`,后端运行在 `http://localhost:8080`。 -由于跨域限制,需要在后端配置 CORS 才能正常访问 API。 - -## 使用 Gin 框架 - -### 1. 安装 CORS 中间件 - -```bash -go get github.com/gin-contrib/cors -``` - -### 2. 配置 CORS - -```go -package main - -import ( - "github.com/gin-gonic/gin" - "github.com/gin-contrib/cors" - "time" -) - -func main() { - r := gin.Default() - - // 开发环境:配置 CORS - if gin.Mode() == gin.DebugMode { - config := cors.Config{ - AllowOrigins: []string{"http://localhost:5173"}, - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, - ExposeHeaders: []string{"Content-Length"}, - AllowCredentials: true, - MaxAge: 12 * time.Hour, - } - r.Use(cors.New(config)) - } - - // API 路由 - api := r.Group("/api/v1") - { - // 图片相关 - api.GET("/images", getImages) - api.GET("/image/today/meta", getTodayImageMeta) - api.GET("/image/date/:date/meta", getImageMetaByDate) - api.GET("/image/random/meta", getRandomImageMeta) - - // 管理员相关 - api.POST("/admin/login", adminLogin) - // ... 其他路由 - } - - // 生产环境:静态文件服务 - if gin.Mode() == gin.ReleaseMode { - r.Static("/assets", "./web/assets") - r.StaticFile("/", "./web/index.html") - - // SPA fallback - r.NoRoute(func(c *gin.Context) { - c.File("./web/index.html") - }) - } - - r.Run(":8080") -} -``` - -### 3. 更灵活的 CORS 配置 - -```go -// 根据环境变量动态配置 -func setupCORS(r *gin.Engine) { - allowOrigins := os.Getenv("ALLOW_ORIGINS") - if allowOrigins == "" { - allowOrigins = "http://localhost:5173" - } - - origins := strings.Split(allowOrigins, ",") - - config := cors.Config{ - AllowOrigins: origins, - AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, - AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, - ExposeHeaders: []string{"Content-Length"}, - AllowCredentials: true, - MaxAge: 12 * time.Hour, - } - - r.Use(cors.New(config)) -} - -func main() { - r := gin.Default() - - // 只在开发环境启用 CORS - if gin.Mode() == gin.DebugMode { - setupCORS(r) - } - - // ... 其他配置 -} -``` - -## 使用标准库 - -如果不使用 Gin 框架,可以手动实现 CORS: - -```go -package main - -import ( - "net/http" -) - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // 设置 CORS 头 - w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Max-Age", "43200") // 12 hours - - // 处理预检请求 - if r.Method == "OPTIONS" { - w.WriteHeader(http.StatusOK) - return - } - - next.ServeHTTP(w, r) - }) -} - -func main() { - mux := http.NewServeMux() - - // 注册路由 - mux.HandleFunc("/api/v1/images", getImages) - // ... 其他路由 - - // 包装 CORS 中间件 - handler := corsMiddleware(mux) - - http.ListenAndServe(":8080", handler) -} -``` - -## 配置文件方式 - -可以通过配置文件管理 CORS 设置: - -```yaml -# config.yaml -server: - port: 8080 - mode: debug # debug 或 release - -cors: - enabled: true - allow_origins: - - http://localhost:5173 - - http://127.0.0.1:5173 - allow_methods: - - GET - - POST - - PUT - - PATCH - - DELETE - - OPTIONS - allow_headers: - - Origin - - Content-Type - - Accept - - Authorization - allow_credentials: true - max_age: 43200 -``` - -```go -type CORSConfig struct { - Enabled bool `yaml:"enabled"` - AllowOrigins []string `yaml:"allow_origins"` - AllowMethods []string `yaml:"allow_methods"` - AllowHeaders []string `yaml:"allow_headers"` - AllowCredentials bool `yaml:"allow_credentials"` - MaxAge int `yaml:"max_age"` -} - -func setupCORSFromConfig(r *gin.Engine, cfg *CORSConfig) { - if !cfg.Enabled { - return - } - - config := cors.Config{ - AllowOrigins: cfg.AllowOrigins, - AllowMethods: cfg.AllowMethods, - AllowHeaders: cfg.AllowHeaders, - AllowCredentials: cfg.AllowCredentials, - MaxAge: time.Duration(cfg.MaxAge) * time.Second, - } - - r.Use(cors.New(config)) -} -``` - -## 安全建议 - -1. **生产环境不要启用 CORS**:生产环境前后端在同一域名下,不需要 CORS -2. **限制允许的源**:不要使用 `*`,明确指定允许的域名 -3. **使用环境变量**:允许的源应该通过环境变量配置,不要硬编码 -4. **限制方法和头**:只允许必要的 HTTP 方法和请求头 -5. **注意凭证**:如果使用 `AllowCredentials: true`,不能使用通配符源 - -## 测试 CORS 配置 - -可以使用 curl 测试 CORS: - -```bash -# 测试预检请求 -curl -X OPTIONS http://localhost:8080/api/v1/images \ - -H "Origin: http://localhost:5173" \ - -H "Access-Control-Request-Method: GET" \ - -v - -# 测试实际请求 -curl -X GET http://localhost:8080/api/v1/images \ - -H "Origin: http://localhost:5173" \ - -v -``` - -应该能看到响应头中包含: -``` -Access-Control-Allow-Origin: http://localhost:5173 -Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS -Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization -Access-Control-Allow-Credentials: true -``` - -## 常见问题 - -### Q: 为什么要区分开发和生产环境? -A: 生产环境前后端部署在同一域名下,不需要 CORS。只在开发环境需要。 - -### Q: 可以使用代理吗? -A: 可以在 Vite 中配置代理,但不推荐。直接配置 CORS 更接近生产环境,便于发现问题。 - -### Q: 如何处理认证? -A: 如果使用 Cookie 或需要发送凭证,必须设置 `AllowCredentials: true` 和明确的源。 - -### Q: 预检请求是什么? -A: 浏览器在某些跨域请求前会发送 OPTIONS 请求,询问服务器是否允许该跨域请求。 \ No newline at end of file diff --git a/webapp/README.md b/webapp/README.md index 75ca984..070dde5 100644 --- a/webapp/README.md +++ b/webapp/README.md @@ -2,6 +2,9 @@ BingPaper 的前端 Web 应用,使用 Vue 3 + TypeScript + Vite 构建。 +> 💡 **性能优化提示**:已配置浏览器缓存优化,可减少 60-80% 带宽! +> 👉 后端配置:查看 [缓存配置快速参考](./CACHE_QUICK_REF.md) + ## 特性 - ✨ Vue 3 组合式 API @@ -9,6 +12,7 @@ BingPaper 的前端 Web 应用,使用 Vue 3 + TypeScript + Vite 构建。 - 📦 TypeScript 类型支持 - 🔧 完整的 API 客户端封装 - 🚀 优化的构建配置 +- ⚡ 浏览器缓存优化(内容哈希 + 代码分割) - 🌐 支持自定义后端路径 - 📁 自动输出到上级目录的 web 文件夹 @@ -105,18 +109,22 @@ src/ └── main.ts # 入口文件 ``` -## 技术栈 +## 📚 文档 -- [Vue 3](https://vuejs.org/) - 渐进式 JavaScript 框架 -- [TypeScript](https://www.typescriptlang.org/) - 类型安全的 JavaScript -- [Vite](https://vitejs.dev/) - 下一代前端构建工具 -- [Tailwind CSS](https://tailwindcss.com/) - 实用优先的 CSS 框架 -- [shadcn-vue](https://www.shadcn-vue.com/) - 高质量的 Vue 组件库 +### 核心文档 +- [README.md](./README.md) - 项目概览(本文件) +- [BUILD.md](./BUILD.md) - 构建说明 +- [USAGE.md](./USAGE.md) - 使用指南 -## IDE 支持 +### 性能优化 ⚡ +- [CACHE_QUICK_REF.md](./CACHE_QUICK_REF.md) - **缓存配置快速参考**(推荐从这里开始) +- [CACHE_CONFIG.md](./CACHE_CONFIG.md) - 详细的缓存配置指南 +- [CACHE_OPTIMIZATION_SUMMARY.md](./CACHE_OPTIMIZATION_SUMMARY.md) - 优化总结 +- [CACHE_TEST.html](./CACHE_TEST.html) - 缓存测试页面 -推荐使用 [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) 扩展。 +### API 相关 +- [CORS_CONFIG.md](./CORS_CONFIG.md) - CORS 配置 +- [API_EXAMPLES.md](./API_EXAMPLES.md) - API 使用示例 -## License - -MIT +### 其他 +- [CHANGELOG.md](./CHANGELOG.md) - 更新日志 diff --git a/webapp/USAGE.md b/webapp/USAGE.md deleted file mode 100644 index 2f021aa..0000000 --- a/webapp/USAGE.md +++ /dev/null @@ -1,201 +0,0 @@ -# 必应每日一图 - 使用说明 - -## ✨ 功能特性 - -### 首页(画廊视图) - -1. **顶部大图区域** - - 全屏展示今日必应图片 - - 显示图片标题、版权信息和日期 - - 渐变遮罩效果,突出文字内容 - - 悬停时内容向上浮动动画 - -2. **操作按钮** - - **查看大图**:跳转到全屏图片查看页面 - - **了解更多**:打开必应相关链接(基于 quiz 字段) - -3. **历史图片画廊** - - 网格布局展示历史图片(响应式:手机1列、平板2列、桌面3列) - - 鼠标悬停显示元数据信息 - - 卡片放大和图片缩放动画效果 - - 点击任意图片打开详情页 - -4. **无限滚动** - - 支持"加载更多"按钮 - - 自动检测是否还有更多图片 - - 加载状态提示 - -### 图片查看页面 - -1. **全屏展示** - - 以最高清晰度(UHD)展示图片 - - 黑色背景,图片居中显示 - - 顶部和底部渐变工具栏 - -2. **信息悬浮层**(类似 Windows 聚焦壁纸) - - 半透明背景,毛玻璃效果 - - 显示图片标题、版权信息 - - "了解更多信息"链接到必应相关页面 - - 可以通过按钮或键盘切换显示/隐藏 - -3. **日期导航** - - **前一天** / **后一天** 按钮 - - 支持键盘方向键导航(← / →) - - 今天的图片禁用"后一天"按钮 - - 导航时有节流保护 - -4. **键盘快捷键** - - `←` : 前一天 - - `→` : 后一天 - - `Esc` : 返回首页 - - `I` : 切换信息显示 - -## 🎨 设计亮点 - -### 视觉效果 -- 深色主题,突出图片内容 -- 渐变背景和遮罩层 -- 毛玻璃效果(backdrop-blur) -- 平滑的过渡动画 -- 响应式设计,适配各种屏幕 - -### 交互体验 -- 悬停效果(卡片放大、图片缩放) -- 加载状态提示 -- 按钮禁用状态 -- 键盘快捷键支持 -- 流畅的页面切换 - -## 🚀 技术实现 - -### 组件结构 -``` -src/ -├── views/ -│ ├── Home.vue # 首页画廊视图 -│ └── ImageView.vue # 图片查看页面 -├── composables/ -│ └── useImages.ts # 数据获取逻辑 -├── router/ -│ └── index.ts # 路由配置 -└── lib/ - ├── api-service.ts # API 服务 - ├── api-config.ts # API 配置 - ├── api-types.ts # 类型定义 - └── http-client.ts # HTTP 客户端 -``` - -### 路由配置 -- `/` - 首页画廊视图 -- `/image/:date` - 图片查看页面(动态路由) - -### API 集成 -使用封装好的 API 服务: -- `getTodayImageMeta()` - 获取今日图片元数据 -- `getImages({ limit })` - 获取图片列表 -- `getImageMetaByDate(date)` - 获取指定日期图片 -- `getTodayImageUrl()` - 获取今日图片 URL -- `getImageUrlByDate(date, variant)` - 获取指定日期图片 URL - -### 数据管理 -使用 Vue Composables 模式: -- `useTodayImage()` - 管理今日图片数据 -- `useImageList()` - 管理图片列表(支持分页) -- `useImageByDate()` - 管理特定日期图片数据 - -## 📱 响应式设计 - -### 断点 -- 手机:< 640px(1列) -- 平板:640px - 1024px(2列) -- 桌面:> 1024px(3列) - -### 适配特性 -- 响应式网格布局 -- 动态字体大小 -- 移动端优化的按钮文字 -- 触摸友好的交互 - -## 🎯 使用流程 - -1. **访问首页** - ``` - http://localhost:5173/ - ``` - -2. **浏览今日图片** - - 顶部大图展示当天必应图片 - - 查看标题和版权信息 - -3. **点击"了解更多"** - - 自动拼接完整的必应 URL - - 在新标签页打开 - -4. **浏览历史图片** - - 向下滚动查看历史图片网格 - - 鼠标悬停查看详细信息 - - 点击"加载更多"获取更多图片 - -5. **全屏查看图片** - - 点击任意图片进入全屏模式 - - 使用按钮或键盘导航 - - 按 `Esc` 返回首页 - -## 🔧 开发 - -### 启动开发服务器 -```bash -npm run dev -``` - -### 构建生产版本 -```bash -npm run build -``` - -### 预览构建结果 -```bash -npm run preview -``` - -## 📝 注意事项 - -1. **API 配置** - - 确保后端 API 服务正在运行 - - 开发环境会自动代理 `/api` 请求到 `http://localhost:8080` - -2. **图片分辨率** - - 首页顶部大图:UHD - - 画廊缩略图:1920x1080 - - 全屏查看:UHD - -3. **必应链接** - - quiz 字段需要拼接 `https://www.bing.com` 前缀 - - 例如:`/search?q=...` → `https://www.bing.com/search?q=...` - -4. **日期格式** - - 使用 ISO 格式:`YYYY-MM-DD` - - 例如:`2024-01-27` - -## 🎨 自定义样式 - -可以通过修改 Tailwind CSS 类来调整样式: -- 渐变颜色:`bg-gradient-to-*` -- 透明度:`bg-black/80` -- 模糊效果:`backdrop-blur-*` -- 动画:`transition-*`、`animate-*` - -## 🐛 已知问题 - -- 需要确保 API 返回正确的图片元数据 -- 图片加载依赖网络速度 -- 大图可能需要一些时间加载 - -## 🚀 未来优化 - -- [ ] 添加图片预加载 -- [ ] 支持手势滑动切换(移动端) -- [ ] 添加图片下载功能 -- [ ] 支持收藏功能 -- [ ] 添加搜索和筛选 -- [ ] 支持暗色/亮色主题切换 \ No newline at end of file diff --git a/webapp/src/router/index.ts b/webapp/src/router/index.ts index 64b48ba..685df61 100644 --- a/webapp/src/router/index.ts +++ b/webapp/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' import ImageView from '@/views/ImageView.vue' +import ApiDocs from '@/views/ApiDocs.vue' const router = createRouter({ history: createWebHistory(), @@ -20,6 +21,14 @@ const router = createRouter({ meta: { title: '图片详情' } + }, + { + path: '/api-docs', + name: 'ApiDocs', + component: ApiDocs, + meta: { + title: 'API 文档' + } } ] }) diff --git a/webapp/src/views/ApiDocs.vue b/webapp/src/views/ApiDocs.vue new file mode 100644 index 0000000..6e0e061 --- /dev/null +++ b/webapp/src/views/ApiDocs.vue @@ -0,0 +1,448 @@ + + + diff --git a/webapp/src/views/Home.vue b/webapp/src/views/Home.vue index 15eabdc..fcb69a6 100644 --- a/webapp/src/views/Home.vue +++ b/webapp/src/views/Home.vue @@ -122,8 +122,41 @@ -