Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/publish-ghcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Publish Docker image (GHCR)

on:
push:
branches: [main]
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: read
packages: write

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository_owner }}/whattoeat
tags: |
type=ref,event=tag
type=sha
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM node:20-alpine AS build-stage

WORKDIR /app

RUN corepack enable

COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./
Expand All @@ -15,6 +16,12 @@ FROM node:20-alpine AS production-stage

WORKDIR /app

ENV NODE_ENV=production
ENV NITRO_HOST=0.0.0.0
ENV NITRO_PORT=3000

LABEL org.opencontainers.image.source="https://github.com/ryanuo/whatToEat"

COPY --from=build-stage /app/.output ./.output

EXPOSE 3000
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,31 @@ pnpm preview
[![Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/ryanuo/whatToEat)
[![Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ryanuo/whatToEat)

### Docker 部署

默认使用公开菜单(与原项目一致):`https://eat.ryanuo.cc/recipes.json`。

```bash
docker compose up -d --build
```

### Docker 部署(服务器不构建:从 GHCR 拉镜像运行)

仓库提交到 GitHub 后,会通过 GitHub Actions 自动构建并发布镜像到 GHCR。

在服务器上部署(无需构建):

```bash
docker pull ghcr.io/ryanuo/whattoeat:latest

docker run -d --name whattoeat \
-p 3000:3000 \
ghcr.io/ryanuo/whattoeat:latest
```

## 数据来源

菜谱数据来源于远程 JSON 接口,通过 `server/api/recipes.ts` 进行获取和处理。
菜谱数据来源于远程 JSON 接口,通过 `server/routes/api/recipes.ts` 进行获取和处理。

## 参考

Expand Down
56 changes: 54 additions & 2 deletions app/components/Eat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ import { emojiMap } from '~/constants'
const isPlaying = ref(false)
const currentFood = ref<CurrentFood>()
const shakeTitle = ref(false)
const { data } = await useFetch<RecipeResponse>('/api/recipes')
const { data, error, pending, refresh } = await useFetch<RecipeResponse>('/api/recipes', {
retry: 3,
retryDelay: 1000,
timeout: 10000,
})

// 检查数据是否成功加载
const isDataReady = computed(() => {
return !pending.value && !error.value && data.value && data.value.recipes && data.value.recipes.length > 0
})

const categories = computed(() => (data.value?.categories || []) as string[])
const selectedCategories = useStorage<string[]>('selected-categories', [...categories.value])
const isAllSelected = computed(() => selectedCategories.value.length === categories.value.length)
Expand Down Expand Up @@ -74,6 +84,12 @@ function startRandom() {
if (!import.meta.client)
return

// 确保数据已加载
if (!data.value?.recipes || data.value.recipes.length === 0) {
console.warn('菜单数据未加载,无法开始随机')
return
}

currentFood.value = undefined
shakeTitle.value = true

Expand Down Expand Up @@ -171,7 +187,43 @@ onUnmounted(() => {

<div id="temp_container" class="inset-0 absolute z-10 overflow-hidden" />

<div class="px-4 flex flex-col min-h-screen items-center justify-center relative z-20">
<!-- 加载状态 -->
<div v-if="pending" class="px-4 flex flex-col min-h-screen items-center justify-center relative z-20">
<div class="text-center">
<Loading />
<p class="text-gray-600 mt-4 animate-pulse">正在加载菜单数据...</p>
</div>
</div>

<!-- 错误状态 -->
<div v-else-if="error" class="px-4 flex flex-col min-h-screen items-center justify-center relative z-20">
<div class="text-center">
<p class="text-red-600 mb-4">😞 菜单数据加载失败</p>
<p class="text-gray-600 text-sm mb-4">{{ error.message }}</p>
<button
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
@click="refresh"
>
重新加载
</button>
</div>
</div>

<!-- 数据为空状态 -->
<div v-else-if="!isDataReady" class="px-4 flex flex-col min-h-screen items-center justify-center relative z-20">
<div class="text-center">
<p class="text-gray-600 mb-4">📭 暂无菜单数据</p>
<button
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
@click="refresh"
>
重新加载
</button>
</div>
</div>

<!-- 正常内容显示 -->
<div v-else class="px-4 flex flex-col min-h-screen items-center justify-center relative z-20">
<div class="mb-4 flex flex-wrap gap-3 items-center top-15 justify-center absolute">
<div class="flex flex-wrap gap-2 justify-center">
<button
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
whattoeat:
build: .
container_name: whattoeat
ports:
- "3000:3000"
environment:
# 可选:指向你自己的菜谱服务(支持 base URL 或完整 recipes.json URL)
# - RECIPES_URL=http://your-recipes-host:5000
# - RECIPES_URL=http://your-recipes-host:5000/recipes.json
- RECIPES_URL=${RECIPES_URL:-}
# 容器内监听地址(Docker 环境建议显式设置)
- NITRO_HOST=0.0.0.0
- NITRO_PORT=3000
restart: unless-stopped
Loading