feat: add custom favourite & search
This commit is contained in:
@@ -9,5 +9,5 @@ COPY . .
|
|||||||
RUN pnpm install && pnpm run build
|
RUN pnpm install && pnpm run build
|
||||||
|
|
||||||
FROM nginx:stable-alpine
|
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
|
EXPOSE 80
|
||||||
|
|||||||
@@ -8,18 +8,26 @@ const props = defineProps<{
|
|||||||
dish: RecipeItem | DbRecipeItem
|
dish: RecipeItem | DbRecipeItem
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const dishLabel = computed(() => {
|
const dishEmojis = computed(() => {
|
||||||
const emojis = getEmojisFromStuff(props.dish.stuff)
|
return getEmojisFromStuff(props.dish.stuff)
|
||||||
return `${props.dish.tags?.includes('杂烩') ? '🍲' : emojis.join(' ')} ${props.dish.name}`
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<span class="inline-flex items-center gap-1">
|
||||||
{{ dishLabel }}
|
<ion-label>
|
||||||
|
{{ dish.tags?.includes('杂烩') ? '🍲' : dishEmojis.join(' ') }}
|
||||||
|
</ion-label>
|
||||||
|
|
||||||
|
<ion-label>
|
||||||
|
{{ dish.name }}
|
||||||
|
</ion-label>
|
||||||
|
|
||||||
<template v-for="tool, i in tools">
|
<template v-for="tool, i in tools">
|
||||||
<span v-if="dish.tools?.includes(tool.name)" :key="i" :class="tool.icon" />
|
<span
|
||||||
|
v-if="dish.tools?.includes(tool.name)"
|
||||||
|
:key="i" class="inline-block" :class="tool.icon"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
68
app/composables/store/favorite.ts
Normal file
68
app/composables/store/favorite.ts
Normal file
@@ -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<FavoriteEntry[]>(() => ensureFavoriteEntries())
|
||||||
|
export const favoriteRecipeIds = computed<number[]>(() => 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
|
||||||
|
}
|
||||||
40
app/pages/changelog.vue
Normal file
40
app/pages/changelog.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { links } from '~/constants'
|
||||||
|
|
||||||
|
// help
|
||||||
|
|
||||||
|
// :href="links.changelog" target="_blank"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button default-href="/my" />
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>帮助</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<ion-list :inset="true">
|
||||||
|
<ion-item :href="links.changelog" target="_blank">
|
||||||
|
<ion-label>开发日志</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<ion-list-header>功能日志</ion-list-header>
|
||||||
|
<ion-list :inset="true">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<h2>v2.0.0-beta (2025-10-07)</h2>
|
||||||
|
<p>Beta App 功能</p>
|
||||||
|
<p>全新原生界面 UI 适配</p>
|
||||||
|
<p>新增历史记录和收藏功能</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'child',
|
|
||||||
title: '我的收藏',
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
施工中...
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
143
app/pages/recipes/favorites.vue
Normal file
143
app/pages/recipes/favorites.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { IonSearchbarCustomEvent, SearchbarInputEventDetail } from '@ionic/core'
|
||||||
|
import type { DbRecipeItem } from '~/utils/db'
|
||||||
|
import { Dialog } from '@capacitor/dialog'
|
||||||
|
import { getFavoriteTime, isFavorited, toggleFavorite } from '~/composables/store/favorite'
|
||||||
|
import { db } from '~/utils/db'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'child',
|
||||||
|
title: '我的收藏',
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const recipes = ref<DbRecipeItem[]>([])
|
||||||
|
const keyword = ref('')
|
||||||
|
const sortKey = ref<'time' | 'name'>('time')
|
||||||
|
|
||||||
|
const displayed = computed(() => {
|
||||||
|
const text = keyword.value.trim()
|
||||||
|
let list = recipes.value
|
||||||
|
if (text)
|
||||||
|
list = list.filter(r => r.name.includes(text))
|
||||||
|
|
||||||
|
if (sortKey.value === 'name')
|
||||||
|
return [...list].sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
|
||||||
|
// time: sort by recorded favorite timestamp (recent first)
|
||||||
|
return [...list].sort((a, b) => {
|
||||||
|
const ta = getFavoriteTime(a) ?? -Infinity
|
||||||
|
const tb = getFavoriteTime(b) ?? -Infinity
|
||||||
|
return tb - ta
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadFavorites() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const all = await db.recipes.toArray()
|
||||||
|
recipes.value = all.filter(r => isFavorited(r))
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadFavorites)
|
||||||
|
|
||||||
|
async function clearAllFavorites() {
|
||||||
|
const result = await Dialog.confirm({
|
||||||
|
title: '清空收藏',
|
||||||
|
message: '确定要取消所有收藏吗?',
|
||||||
|
okButtonTitle: '确认',
|
||||||
|
cancelButtonTitle: '取消',
|
||||||
|
})
|
||||||
|
if (result.value) {
|
||||||
|
for (const item of recipes.value)
|
||||||
|
toggleFavorite(item)
|
||||||
|
await loadFavorites()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggleFavorite(item: DbRecipeItem) {
|
||||||
|
toggleFavorite(item)
|
||||||
|
// update list immediately
|
||||||
|
recipes.value = recipes.value.filter(r => isFavorited(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDishLink(item: DbRecipeItem) {
|
||||||
|
const href = item.link || `https://www.bilibili.com/video/${item.bv}`
|
||||||
|
window.open(href, '_blank')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button default-href="/my" />
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>我的收藏</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button title="清空收藏" @click="clearAllFavorites">
|
||||||
|
<ion-icon slot="icon-only" :icon="ioniconsTrashOutline" />
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-searchbar
|
||||||
|
animated
|
||||||
|
placeholder="搜索收藏"
|
||||||
|
:debounce="300"
|
||||||
|
show-clear-button="focus"
|
||||||
|
@ion-input="(ev: IonSearchbarCustomEvent<SearchbarInputEventDetail>) => (keyword = ev.detail.value ?? '')"
|
||||||
|
@ion-clear="keyword = ''"
|
||||||
|
/>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<ion-toolbar class="pb-1.5 -mt-2">
|
||||||
|
<ion-segment
|
||||||
|
:value="sortKey"
|
||||||
|
@ion-change="e => (sortKey = (e.detail.value as 'time' | 'name') ?? 'time')"
|
||||||
|
>
|
||||||
|
<ion-segment-button value="time">
|
||||||
|
<ion-label>按收藏时间</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="name">
|
||||||
|
<ion-label>按名称</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<div v-if="loading" class="ion-padding text-center">
|
||||||
|
<ion-spinner name="crescent" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-list v-else-if="displayed.length">
|
||||||
|
<ion-item-sliding v-for="item in displayed" :key="item.id ?? item.name">
|
||||||
|
<ion-item @click="openDishLink(item)">
|
||||||
|
<ion-label class="truncate">
|
||||||
|
<DishLabel class="text-sm" :dish="item" />
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon slot="end" :icon="ioniconsStar" color="warning" />
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item-options>
|
||||||
|
<ion-item-option color="medium" @click="onToggleFavorite(item)">
|
||||||
|
取消收藏
|
||||||
|
</ion-item-option>
|
||||||
|
</ion-item-options>
|
||||||
|
</ion-item-sliding>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<div v-else class="ion-padding text-center">
|
||||||
|
<ion-note>还没有收藏的菜谱</ion-note>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
@@ -42,6 +42,11 @@ onMounted(() => {
|
|||||||
<ion-label>做菜</ion-label>
|
<ion-label>做菜</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
|
<IonTabButton tab="library" href="/library">
|
||||||
|
<ion-icon :icon="ioniconsLibraryOutline" />
|
||||||
|
<ion-label>菜谱</ion-label>
|
||||||
|
</IonTabButton>
|
||||||
|
|
||||||
<IonTabButton tab="random" href="/random">
|
<IonTabButton tab="random" href="/random">
|
||||||
<ion-icon :icon="ioniconsRestaurantOutline" />
|
<ion-icon :icon="ioniconsRestaurantOutline" />
|
||||||
<ion-label>吃什么</ion-label>
|
<ion-label>吃什么</ion-label>
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ const rStore = useRecipeStore()
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</ion-title>
|
</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button router-link="/library" title="菜谱库">
|
||||||
|
<ion-icon slot="icon-only" :icon="ioniconsSearchOutline" />
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content ref="ionContentRef" class="text-center">
|
<ion-content ref="ionContentRef" class="text-center">
|
||||||
|
|||||||
160
app/pages/tabs/library/index.vue
Normal file
160
app/pages/tabs/library/index.vue
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { IonSearchbarCustomEvent, SearchbarInputEventDetail } from '@ionic/core'
|
||||||
|
import type { DbRecipeItem } from '~/utils/db'
|
||||||
|
import { watchDebounced } from '@vueuse/core'
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { useIndexedDB } from '~/composables/db'
|
||||||
|
import { isFavorited, toggleFavorite } from '~/composables/store/favorite'
|
||||||
|
import { recipeHistories } from '~/composables/store/history'
|
||||||
|
import { db } from '~/utils/db'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
alias: ['/library'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const keyword = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const recipes = ref<DbRecipeItem[]>([])
|
||||||
|
const view = ref<'all' | 'fav'>('all')
|
||||||
|
|
||||||
|
const displayed = computed(() => {
|
||||||
|
return view.value === 'fav' ? recipes.value.filter(r => isFavorited(r)) : recipes.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const favCount = computed(() => recipes.value.filter(r => isFavorited(r)).length)
|
||||||
|
|
||||||
|
const showToast = ref(false)
|
||||||
|
const toastMessage = ref('')
|
||||||
|
|
||||||
|
async function loadAll() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
recipes.value = await db.recipes.toArray()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runSearch(q: string) {
|
||||||
|
const text = (q || '').trim()
|
||||||
|
if (!text)
|
||||||
|
return loadAll()
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
recipes.value = await db.recipes
|
||||||
|
.filter(r => r.name.includes(text))
|
||||||
|
.toArray()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// ensure IndexedDB has data
|
||||||
|
const { init } = useIndexedDB()
|
||||||
|
await init()
|
||||||
|
await loadAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
watchDebounced(keyword, (q) => {
|
||||||
|
runSearch(q)
|
||||||
|
}, { debounce: 200, maxWait: 500 })
|
||||||
|
|
||||||
|
function onInput(ev: IonSearchbarCustomEvent<SearchbarInputEventDetail>) {
|
||||||
|
// Ionic emits detail.value
|
||||||
|
keyword.value = ev?.detail?.value ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClear() {
|
||||||
|
keyword.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDishLink(dish: DbRecipeItem) {
|
||||||
|
// keep history like DishTag did
|
||||||
|
recipeHistories.value.push({ recipe: dish, time: Date.now() })
|
||||||
|
const href = dish.link || `https://www.bilibili.com/video/${dish.bv}`
|
||||||
|
window.open(href, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggleFavorite(item: DbRecipeItem) {
|
||||||
|
toggleFavorite(item)
|
||||||
|
toastMessage.value = isFavorited(item) ? '已添加到收藏' : '已从收藏移除'
|
||||||
|
showToast.value = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>菜谱列表</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-searchbar
|
||||||
|
animated
|
||||||
|
placeholder="搜索菜谱"
|
||||||
|
:debounce="300"
|
||||||
|
show-clear-button="focus"
|
||||||
|
@ion-input="onInput"
|
||||||
|
@ion-clear="onClear"
|
||||||
|
/>
|
||||||
|
</ion-toolbar>
|
||||||
|
|
||||||
|
<ion-toolbar class="pb-1.5 -mt-2">
|
||||||
|
<ion-segment
|
||||||
|
:value="view"
|
||||||
|
@ion-change="e => (view = (e.detail.value as 'all' | 'fav') ?? 'all')"
|
||||||
|
>
|
||||||
|
<ion-segment-button value="all">
|
||||||
|
<ion-label>全部</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="fav">
|
||||||
|
<ion-label>收藏 ({{ favCount }})</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<div v-if="loading" class="ion-padding text-center">
|
||||||
|
<ion-spinner name="crescent" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-list v-else-if="displayed.length">
|
||||||
|
<ion-item-sliding v-for="item in displayed" :key="item.id ?? item.name">
|
||||||
|
<ion-item @click="openDishLink(item)">
|
||||||
|
<ion-label class="truncate">
|
||||||
|
<DishLabel class="text-sm" :dish="item" />
|
||||||
|
</ion-label>
|
||||||
|
<ion-button slot="end" fill="clear" @click.stop="onToggleFavorite(item)">
|
||||||
|
<ion-icon :icon="isFavorited(item) ? ioniconsStar : ioniconsStarOutline" color="warning" />
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item-options>
|
||||||
|
<ion-item-option color="warning" @click="onToggleFavorite(item)">
|
||||||
|
{{ isFavorited(item) ? '取消收藏' : '添加收藏' }}
|
||||||
|
</ion-item-option>
|
||||||
|
</ion-item-options>
|
||||||
|
</ion-item-sliding>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="(keyword || view === 'fav') && displayed.length === 0"
|
||||||
|
class="ion-padding text-center"
|
||||||
|
>
|
||||||
|
<ion-note>没有找到相关菜谱</ion-note>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
<ion-toast
|
||||||
|
:is-open="showToast"
|
||||||
|
:message="toastMessage"
|
||||||
|
duration="1200"
|
||||||
|
position="top"
|
||||||
|
@did-dismiss="showToast = false"
|
||||||
|
/>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
@@ -21,11 +21,11 @@ definePageMeta({
|
|||||||
<ion-icon slot="start" :icon="ioniconsTimeOutline" />
|
<ion-icon slot="start" :icon="ioniconsTimeOutline" />
|
||||||
<ion-label>历史记录</ion-label>
|
<ion-label>历史记录</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<!-- <ion-item router-link="/recipes/collect">
|
<ion-item router-link="/recipes/favorites">
|
||||||
<ion-icon slot="start" :icon="ioniconsStarOutline" />
|
<ion-icon slot="start" :icon="ioniconsStarOutline" />
|
||||||
<ion-label>我的收藏</ion-label>
|
<ion-label>我的收藏</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item router-link="/cookbooks">
|
<!-- <ion-item router-link="/cookbooks">
|
||||||
<ion-icon slot="start" :icon="ioniconsBookOutline" />
|
<ion-icon slot="start" :icon="ioniconsBookOutline" />
|
||||||
<ion-label>自定义菜谱</ion-label>
|
<ion-label>自定义菜谱</ion-label>
|
||||||
</ion-item> -->
|
</ion-item> -->
|
||||||
@@ -47,7 +47,7 @@ definePageMeta({
|
|||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<ion-list :inset="true">
|
<ion-list :inset="true">
|
||||||
<ion-item :href="links.changelog" target="_blank">
|
<ion-item router-link="/changelog">
|
||||||
<ion-icon slot="start" :icon="ioniconsDocumentTextOutline" />
|
<ion-icon slot="start" :icon="ioniconsDocumentTextOutline" />
|
||||||
<ion-label>更新日志</ion-label>
|
<ion-label>更新日志</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"installCommand": "corepack enable && pnpm install",
|
"installCommand": "corepack enable && pnpm install",
|
||||||
"buildCommand": "pnpm build",
|
"buildCommand": "pnpm build",
|
||||||
"outputDirectory": ".output/public",
|
"outputDirectory": "dist",
|
||||||
"nodeVersion": "22.20.0"
|
"nodeVersion": "22.20.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export default defineNuxtConfig({
|
|||||||
css: {
|
css: {
|
||||||
core: true,
|
core: true,
|
||||||
basic: true,
|
basic: true,
|
||||||
// utilities: true,
|
utilities: true,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
mode: 'ios',
|
mode: 'ios',
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"open:android": "cap open android",
|
"open:android": "cap open android",
|
||||||
"docs:dev": "pnpm -C docs run docs:dev",
|
"docs:dev": "pnpm -C docs run docs:dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"start:generate": "npx serve .output/public",
|
"start:generate": "npx serve dist",
|
||||||
"start": "node .output/server/index.mjs",
|
"start": "node .output/server/index.mjs",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
|
|||||||
Reference in New Issue
Block a user