diff --git a/Dockerfile b/Dockerfile
index 16cec99..b28b866 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,5 +9,5 @@ COPY . .
RUN pnpm install && pnpm run build
FROM nginx:stable-alpine
-COPY --from=builder /app/.output/public /usr/share/nginx/html
+COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
diff --git a/app/components/tags/DishLabel.vue b/app/components/tags/DishLabel.vue
index 9d8aa47..ba2c7c3 100644
--- a/app/components/tags/DishLabel.vue
+++ b/app/components/tags/DishLabel.vue
@@ -8,18 +8,26 @@ const props = defineProps<{
dish: RecipeItem | DbRecipeItem
}>()
-const dishLabel = computed(() => {
- const emojis = getEmojisFromStuff(props.dish.stuff)
- return `${props.dish.tags?.includes('杂烩') ? '🍲' : emojis.join(' ')} ${props.dish.name}`
+const dishEmojis = computed(() => {
+ return getEmojisFromStuff(props.dish.stuff)
})
-
- {{ dishLabel }}
+
+
+ {{ dish.tags?.includes('杂烩') ? '🍲' : dishEmojis.join(' ') }}
+
+
+
+ {{ dish.name }}
+
-
+
diff --git a/app/composables/store/favorite.ts b/app/composables/store/favorite.ts
new file mode 100644
index 0000000..f1bc495
--- /dev/null
+++ b/app/composables/store/favorite.ts
@@ -0,0 +1,68 @@
+import type { RecipeItem } from '~/types'
+import type { DbRecipeItem } from '~/utils/db'
+import { useStorage } from '@vueuse/core'
+import { namespace } from '~/constants'
+
+export interface FavoriteEntry { id: number, time: number }
+
+// Store favorite entries with timestamp in localStorage
+const rawFavorites = useStorage(`${namespace}:favorites`, [] as any)
+
+// Migration: if old format number[] exists, convert to FavoriteEntry[] with current time
+function ensureFavoriteEntries(): FavoriteEntry[] {
+ const now = Date.now()
+ const v = rawFavorites.value
+ if (Array.isArray(v)) {
+ if (v.length === 0)
+ return []
+ // old format: array of numbers
+ if (typeof v[0] === 'number') {
+ const migrated: FavoriteEntry[] = (v as number[]).map(id => ({ id, time: now }))
+ rawFavorites.value = migrated
+ return migrated
+ }
+ // new format
+ if (typeof v[0] === 'object' && v[0] && 'id' in v[0])
+ return v as FavoriteEntry[]
+ }
+ // fallback
+ rawFavorites.value = []
+ return []
+}
+
+export const favoriteEntries = computed(() => ensureFavoriteEntries())
+export const favoriteRecipeIds = computed(() => favoriteEntries.value.map(e => e.id))
+
+function getId(item: RecipeItem | DbRecipeItem): number | null {
+ // Only support DbRecipeItem with numeric id for now
+ return typeof (item as DbRecipeItem).id === 'number' ? (item as DbRecipeItem).id! : null
+}
+
+export function isFavorited(item: RecipeItem | DbRecipeItem) {
+ const id = getId(item)
+ if (id == null)
+ return false
+ return favoriteRecipeIds.value.includes(id)
+}
+
+export function toggleFavorite(item: RecipeItem | DbRecipeItem) {
+ const id = getId(item)
+ if (id == null)
+ return
+ const list = ensureFavoriteEntries()
+ const idx = list.findIndex(e => e.id === id)
+ if (idx >= 0)
+ list.splice(idx, 1)
+ else
+ list.push({ id, time: Date.now() })
+ rawFavorites.value = list
+}
+
+export function getFavoriteTime(item: RecipeItem | DbRecipeItem): number | null {
+ const id = getId(item)
+ if (id == null)
+ return null
+ const list = ensureFavoriteEntries()
+ const entry = list.find(e => e.id === id)
+ return entry?.time ?? null
+}
diff --git a/app/pages/changelog.vue b/app/pages/changelog.vue
new file mode 100644
index 0000000..61c1945
--- /dev/null
+++ b/app/pages/changelog.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+ 帮助
+
+
+
+
+
+
+ 开发日志
+
+
+
+ 功能日志
+
+
+
+ v2.0.0-beta (2025-10-07)
+ Beta App 功能
+ 全新原生界面 UI 适配
+ 新增历史记录和收藏功能
+
+
+
+
+
+
diff --git a/app/pages/recipes/collect.vue b/app/pages/recipes/collect.vue
deleted file mode 100644
index a8f2edd..0000000
--- a/app/pages/recipes/collect.vue
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
- 施工中...
-
-
diff --git a/app/pages/recipes/favorites.vue b/app/pages/recipes/favorites.vue
new file mode 100644
index 0000000..248a8bf
--- /dev/null
+++ b/app/pages/recipes/favorites.vue
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+ 我的收藏
+
+
+
+
+
+
+
+
+
+ ) => (keyword = ev.detail.value ?? '')"
+ @ion-clear="keyword = ''"
+ />
+
+
+
+ (sortKey = (e.detail.value as 'time' | 'name') ?? 'time')"
+ >
+
+ 按收藏时间
+
+
+ 按名称
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消收藏
+
+
+
+
+
+
+ 还没有收藏的菜谱
+
+
+
+
diff --git a/app/pages/tabs.vue b/app/pages/tabs.vue
index da9eebd..f752cfe 100644
--- a/app/pages/tabs.vue
+++ b/app/pages/tabs.vue
@@ -42,6 +42,11 @@ onMounted(() => {
做菜
+
+
+ 菜谱
+
+
吃什么
diff --git a/app/pages/tabs/home/index.vue b/app/pages/tabs/home/index.vue
index 45ac90b..917f875 100644
--- a/app/pages/tabs/home/index.vue
+++ b/app/pages/tabs/home/index.vue
@@ -30,6 +30,12 @@ const rStore = useRecipeStore()
+
+
+
+
+
+
diff --git a/app/pages/tabs/library/index.vue b/app/pages/tabs/library/index.vue
new file mode 100644
index 0000000..f218902
--- /dev/null
+++ b/app/pages/tabs/library/index.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+ 菜谱列表
+
+
+
+
+
+
+
+ (view = (e.detail.value as 'all' | 'fav') ?? 'all')"
+ >
+
+ 全部
+
+
+ 收藏 ({{ favCount }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ isFavorited(item) ? '取消收藏' : '添加收藏' }}
+
+
+
+
+
+
+ 没有找到相关菜谱
+
+
+
+
+
diff --git a/app/pages/tabs/my/index.vue b/app/pages/tabs/my/index.vue
index ccec03d..acf06b1 100644
--- a/app/pages/tabs/my/index.vue
+++ b/app/pages/tabs/my/index.vue
@@ -21,11 +21,11 @@ definePageMeta({
历史记录
-
@@ -47,7 +47,7 @@ definePageMeta({
-
+
更新日志
diff --git a/edgeone.json b/edgeone.json
index 2d641e5..f620ef1 100644
--- a/edgeone.json
+++ b/edgeone.json
@@ -1,6 +1,6 @@
{
"installCommand": "corepack enable && pnpm install",
"buildCommand": "pnpm build",
- "outputDirectory": ".output/public",
+ "outputDirectory": "dist",
"nodeVersion": "22.20.0"
}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index f65e038..c528da0 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -125,7 +125,7 @@ export default defineNuxtConfig({
css: {
core: true,
basic: true,
- // utilities: true,
+ utilities: true,
},
config: {
mode: 'ios',
diff --git a/package.json b/package.json
index 43a5cf3..0d9ca58 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"open:android": "cap open android",
"docs:dev": "pnpm -C docs run docs:dev",
"generate": "nuxt generate",
- "start:generate": "npx serve .output/public",
+ "start:generate": "npx serve dist",
"start": "node .output/server/index.mjs",
"lint": "eslint .",
"postinstall": "nuxt prepare",