refactor: use nuxt compatiable 4 folder
This commit is contained in:
31
app/app.vue
Normal file
31
app/app.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { installPrompt } from './utils/pwa'
|
||||
import { appName } from '~/constants'
|
||||
import { useIndexedDB } from '~/composables/db'
|
||||
|
||||
// https://nuxt.com/docs/api/composables/use-head
|
||||
useHead({
|
||||
title: appName,
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: '好的,今天我们来做菜!',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const indexedDB = useIndexedDB()
|
||||
|
||||
onMounted(() => {
|
||||
installPrompt()
|
||||
indexedDB.init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VitePwaManifest />
|
||||
<NuxtLayout>
|
||||
<NuxtLoadingIndicator />
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
43
app/components/BaseFooter.vue
Normal file
43
app/components/BaseFooter.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import { isClient } from '@vueuse/core'
|
||||
|
||||
const displayICP = ref(true)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (isClient)
|
||||
displayICP.value = ['cook.yunyoujun.cn', 'localhost', '127.0.0.1'].includes(window.location.hostname)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div p="4 t-2" class="flex flex-col items-center justify-center" text="sm">
|
||||
<CurrentVersion />
|
||||
<a v-if="displayICP" opacity="80" class="flex" href="https://beian.miit.gov.cn/" target="_blank">
|
||||
苏ICP备17038157号
|
||||
</a>
|
||||
<div m="t-2" class="inline-flex items-center justify-center" text="xs">
|
||||
<a class="inline-flex items-center justify-center" style="color: #ea7b99" href="https://www.bilibili.com/blackboard/dynamic/306882" target="_blank">
|
||||
<span inline-flex>菜谱视频来源:</span>
|
||||
<div class="inline-flex" i-ri-bilibili-line />
|
||||
<span m="l-1" class="inline-flex" style="margin-top: 1px;">B 站</span>
|
||||
</a>
|
||||
</div>
|
||||
<div mt-2>
|
||||
本站点由
|
||||
<a color="#F6821F" href="https://www.cloudflare-cn.com/" target="_blank" title="Cloudflare" border="b-1 dashed">
|
||||
<span>Cloudflare</span>
|
||||
</a>
|
||||
提供 CDN 支持
|
||||
</div>
|
||||
<div m="t-2" opacity="80" class="flex items-center justify-center">
|
||||
©️ <a href="https://github.com/YunYouJun/cook" target="_blank">Cook</a>
|
||||
<div text="xs" m="x-1" i-ri-cloud-line />
|
||||
<a href="https://www.yunyoujun.cn" target="_blank">云游君</a>
|
||||
</div>
|
||||
<div m="t-2" opacity="80">
|
||||
<a href="https://yunle.fun" target="_blank" title="云乐坊">
|
||||
云乐坊工作室
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
35
app/components/BasketButton.vue
Normal file
35
app/components/BasketButton.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const props = defineProps({
|
||||
isVisible: Boolean,
|
||||
})
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
const { displayedRecipe } = storeToRefs(rStore)
|
||||
|
||||
/**
|
||||
* Show basket button if there are recipes in the basket
|
||||
*/
|
||||
const showBasketBtn = computed(() => {
|
||||
return displayedRecipe.value.length !== rStore.recipesLength && props.isVisible
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
v-show="showBasketBtn"
|
||||
class="fixed z-9 inline-flex cursor-pointer items-center justify-center rounded rounded-full shadow hover:shadow-md"
|
||||
bg="green-50 dark:green-900" w="10" h="10"
|
||||
bottom="22"
|
||||
right="4"
|
||||
text="green-600 dark:green-300"
|
||||
>
|
||||
<span v-if="displayedRecipe.length > 0">
|
||||
<div i-mdi-bowl-mix-outline />
|
||||
</span>
|
||||
<span v-else>
|
||||
<div i-mdi-bowl-outline />
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
117
app/components/ChooseFood.vue
Normal file
117
app/components/ChooseFood.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import type { StuffItem } from '~/types'
|
||||
import { meat, staple, tools, vegetable } from '~/data/food'
|
||||
|
||||
import { useEmojiAnimation } from '~/composables/animation'
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
const { curTool } = storeToRefs(rStore)
|
||||
const curStuff = computed(() => rStore.selectedStuff)
|
||||
|
||||
const recipeBtnRef = ref<HTMLButtonElement>()
|
||||
const { playAnimation } = useEmojiAnimation(recipeBtnRef)
|
||||
|
||||
const gtm = useGtm()
|
||||
const recipePanelRef = ref()
|
||||
const { isVisible, show } = useInvisibleElement(recipePanelRef)
|
||||
|
||||
function toggleStuff(item: StuffItem, category = '', _e?: Event) {
|
||||
rStore.toggleStuff(item.name)
|
||||
|
||||
if (curStuff.value.includes(item.name))
|
||||
playAnimation(item.emoji)
|
||||
|
||||
gtm?.trackEvent({
|
||||
event: 'click',
|
||||
category: `${category}_${item.name}`,
|
||||
action: 'click_stuff',
|
||||
label: '食材',
|
||||
})
|
||||
gtm?.trackEvent({
|
||||
event: 'click_stuff',
|
||||
action: item.name,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 m="t-4" text="xl" font="bold" p="1">
|
||||
🥘 先选一下食材
|
||||
</h2>
|
||||
<div>
|
||||
<h2 opacity="90" text="base" font="bold" p="1">
|
||||
🥬 菜菜们
|
||||
</h2>
|
||||
<div>
|
||||
<VegetableTag
|
||||
v-for="item, i in vegetable" :key="i"
|
||||
:active="curStuff.includes(item.name)"
|
||||
@click="toggleStuff(item, 'vegetable')"
|
||||
>
|
||||
<span v-if="item.emoji" class="inline-flex">{{ item.emoji }}</span>
|
||||
<span v-else-if="item.image" class="inline-flex">
|
||||
<img class="inline-flex" w="2" h="2" width="10" height="10" :src="item.image" :alt="item.name">
|
||||
</span>
|
||||
<span class="inline-flex" m="l-1">{{ item.name }}</span>
|
||||
</VegetableTag>
|
||||
</div>
|
||||
</div>
|
||||
<div m="y-4">
|
||||
<h2 opacity="90" text="base" font="bold" p="1">
|
||||
🥩 肉肉们
|
||||
</h2>
|
||||
<div>
|
||||
<MeatTag
|
||||
v-for="item, i in meat" :key="i"
|
||||
:active="curStuff.includes(item.name)"
|
||||
@click="toggleStuff(item, 'meat')"
|
||||
>
|
||||
<span>{{ item.emoji }}</span>
|
||||
<span m="l-1">{{ item.name }}</span>
|
||||
</MeatTag>
|
||||
</div>
|
||||
</div>
|
||||
<div m="y-4">
|
||||
<h2 opacity="90" text="base" font="bold" p="1">
|
||||
🍚 主食也要一起下锅吗?(不选也行)
|
||||
</h2>
|
||||
<div>
|
||||
<StapleTag
|
||||
v-for="item, i in staple" :key="i"
|
||||
:active="curStuff.includes(item.name)"
|
||||
@click="toggleStuff(item, 'staple')"
|
||||
>
|
||||
<span>{{ item.emoji }}</span>
|
||||
<span m="l-1">{{ item.name }}</span>
|
||||
</StapleTag>
|
||||
</div>
|
||||
</div>
|
||||
<div m="t-4">
|
||||
<h2 text="xl" font="bold" p="1">
|
||||
🍳 再选一下厨具
|
||||
</h2>
|
||||
<div>
|
||||
<ToolTag
|
||||
v-for="item, i in tools" :key="i"
|
||||
:active="curTool === item.name"
|
||||
@click="rStore.clickTool(item)"
|
||||
>
|
||||
<span v-if="item.emoji" class="inline-flex">
|
||||
{{ item.emoji }}
|
||||
</span>
|
||||
<span v-else-if="item.icon" class="inline-flex">
|
||||
<div :class="item.icon" />
|
||||
</span>
|
||||
<span class="inline-flex" m="l-1">{{ item.label || item.name }}</span>
|
||||
</ToolTag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition>
|
||||
<BasketButton ref="recipeBtnRef" :is-visible="isVisible" @click="show" />
|
||||
</Transition>
|
||||
<RecipePanel ref="recipePanelRef" />
|
||||
</div>
|
||||
</template>
|
||||
5
app/components/CommonHeader.vue
Normal file
5
app/components/CommonHeader.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<h1 text-2xl font="bold" my="4">
|
||||
<slot />
|
||||
</h1>
|
||||
</template>
|
||||
19
app/components/Counter.vue
Normal file
19
app/components/Counter.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
initial: number
|
||||
}>()
|
||||
|
||||
const { count, inc, dec } = useCounter(props.initial)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ count }}
|
||||
<button class="inc" @click="inc()">
|
||||
+
|
||||
</button>
|
||||
<button class="dec" @click="dec()">
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
26
app/components/FAQItem.vue
Normal file
26
app/components/FAQItem.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts" setup>
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
defaultOpen?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Disclosure v-slot="{ open }" :default-open="defaultOpen" as="div" class="mt-2">
|
||||
<DisclosureButton
|
||||
class="w-full flex justify-between rounded-lg bg-blue-100 px-4 py-2 text-left text-sm text-blue-900 font-medium hover:bg-blue-200 focus:outline-none focus-visible:ring focus-visible:ring-blue-500 focus-visible:ring-opacity-75"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<div
|
||||
i-ri-arrow-drop-up-line
|
||||
:class="open ? 'rotate-180 transform' : ''"
|
||||
class="h-5 w-5 text-blue-500"
|
||||
/>
|
||||
</DisclosureButton>
|
||||
<DisclosurePanel class="px-2 pb-2 pt-4 text-sm">
|
||||
<slot />
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</template>
|
||||
31
app/components/FeedbackActions.vue
Normal file
31
app/components/FeedbackActions.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { links } from '~/constants'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a
|
||||
m="2"
|
||||
class="feedback-button"
|
||||
:href="links.contribute" target="_blank"
|
||||
title="居家菜谱投稿"
|
||||
>
|
||||
立即投稿
|
||||
</a>
|
||||
|
||||
<a
|
||||
m="2"
|
||||
class="feedback-button"
|
||||
:href="links.feedback" target="_blank"
|
||||
alt="通过兔小巢反馈"
|
||||
>
|
||||
立即反馈
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.feedback-button {
|
||||
@apply border-none inline-flex justify-center rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600;
|
||||
}
|
||||
</style>
|
||||
7
app/components/README.md
Normal file
7
app/components/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Components
|
||||
|
||||
Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).
|
||||
|
||||
### Icons
|
||||
|
||||
You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/).
|
||||
31
app/components/RandomRecipe.vue
Normal file
31
app/components/RandomRecipe.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
const { count, inc, dec } = useCount()
|
||||
const { random, randomRecipes } = useRandomRecipe(count)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div inline-flex m="y-3">
|
||||
<button rounded-full p-2 btn @click="dec()">
|
||||
<div i-carbon-subtract />
|
||||
</button>
|
||||
<div font="mono" w="15" m-auto inline-block>
|
||||
{{ count }}
|
||||
</div>
|
||||
<button rounded-full p-2 btn @click="inc()">
|
||||
<div i-carbon-add />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button cursor-pointer class="inline-flex inline-flex items-center justify-center rounded-md border-none bg-blue-600 px-3 py-1.5 text-sm text-white font-semibold leading-6 shadow-sm hover:bg-blue-500 focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 focus-visible:outline" @click="random">
|
||||
<div class="transition" hover="text-blue-500" i-ri-refresh-line mr-1 inline-flex />
|
||||
<div>随机一下</div>
|
||||
</button>
|
||||
|
||||
<div v-show="randomRecipes.length > 0">
|
||||
<div m="t-8" flex="~ col">
|
||||
<template v-for="recipe, i in randomRecipes" :key="i">
|
||||
<DishTag v-if="recipe" :dish="recipe" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
82
app/components/RecipePanel.vue
Normal file
82
app/components/RecipePanel.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
|
||||
const { selectedStuff, curTool } = storeToRefs(rStore)
|
||||
|
||||
const showSearchInput = ref(false)
|
||||
|
||||
const showTooltip = computed(() => !selectedStuff.value.length && !curTool.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="recipe-panel relative shadow transition hover:shadow-md"
|
||||
m="x-2 y-4" p="2"
|
||||
bg="gray-400/8"
|
||||
>
|
||||
<RecipePanelTitle />
|
||||
|
||||
<ToggleMode />
|
||||
|
||||
<button absolute right-4 top-4 @click="showSearchInput = !showSearchInput">
|
||||
<div v-if="!showSearchInput" i-ri-search-line />
|
||||
<div v-else i-ri-search-fill />
|
||||
</button>
|
||||
|
||||
<div class="cook-recipes" p="2">
|
||||
<SearchFoodInput v-if="showSearchInput" />
|
||||
|
||||
<Transition mode="out-in">
|
||||
<span v-if="showTooltip" text="sm" p="2">
|
||||
你要先选食材或工具哦~
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-else-if="rStore.isSearching"
|
||||
relative flex items-center justify-center p-6
|
||||
text-xl
|
||||
>
|
||||
<div class="magnifying-glass" i-ri-search-line inline-flex />
|
||||
</div>
|
||||
|
||||
<div v-else-if="rStore.displayedRecipe.length">
|
||||
<DishTag v-for="item, i in rStore.displayedRecipe" :key="i" :dish="item" />
|
||||
</div>
|
||||
|
||||
<div v-else text="sm">
|
||||
<span>还没有完美匹配的菜谱呢……</span>
|
||||
<br>
|
||||
<span>大胆尝试一下,或者</span>
|
||||
<a href="#" @click="rStore.reset()">
|
||||
<strong>换个组合</strong>
|
||||
</a>
|
||||
<span>?</span>
|
||||
<br>
|
||||
<div m="t-1">
|
||||
<span>欢迎来</span>
|
||||
<a class="text-blue-600 font-bold dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic" target="_blank">这里</a>
|
||||
<span>反馈新的菜谱!</span>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@keyframes circle-rotate {
|
||||
from {
|
||||
transform: rotate(0turn) translateY(60%) rotate(1turn);
|
||||
}
|
||||
to {
|
||||
transform: rotate(1turn) translateY(60%) rotate(0turn);
|
||||
}
|
||||
}
|
||||
|
||||
.magnifying-glass {
|
||||
margin: auto;
|
||||
animation: circle-rotate 4s linear infinite;
|
||||
}
|
||||
</style>
|
||||
5
app/components/RecipePanelTitle.vue
Normal file
5
app/components/RecipePanelTitle.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div text="xl" font="bold" p="1">
|
||||
🍲 来看看组合出的菜谱吧!
|
||||
</div>
|
||||
</template>
|
||||
38
app/components/SearchFoodInput.vue
Normal file
38
app/components/SearchFoodInput.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
const rStore = useRecipeStore()
|
||||
|
||||
const searchInput = ref<HTMLInputElement>()
|
||||
onMounted(() => {
|
||||
searchInput.value?.focus()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div m="auto b-2" max-w="500px">
|
||||
<div relative text-xs>
|
||||
<div
|
||||
v-if="rStore.keyword" cursor="pointer"
|
||||
absolute right-2 inline-flex justify="center" items-center h="full" opacity="70"
|
||||
@click="rStore.clearKeyWord()"
|
||||
>
|
||||
<div i-ri-close-line />
|
||||
</div>
|
||||
<input
|
||||
id="input"
|
||||
ref="searchInput"
|
||||
v-model="rStore.keyword"
|
||||
placeholder="关键字过滤"
|
||||
aria-label="搜索关键字"
|
||||
type="text"
|
||||
autocomplete="false"
|
||||
p="x4 y2"
|
||||
w="full"
|
||||
text="center"
|
||||
bg="white dark:dark-800"
|
||||
border="~ rounded gray-200 dark:gray-700"
|
||||
class="focus:dark:gray-500"
|
||||
>
|
||||
<label class="hidden" for="input">快速搜索</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
82
app/components/Switch.vue
Normal file
82
app/components/Switch.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
strict: boolean
|
||||
toggleStrict: (val: boolean) => void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex items-center justify-center" m="t-2">
|
||||
<span :class="!strict && 'text-orange-600'" font="bold" m="x-1" @click="toggleStrict(false)">
|
||||
模糊匹配
|
||||
</span>
|
||||
<label m="x-1" class="switch">
|
||||
<input :modelValue="strict" type="checkbox" @update:modelValue="toggleStrict">
|
||||
<span class="slider round inline-flex items-center justify-center" />
|
||||
</label>
|
||||
<span :class="strict && 'text-green-600'" font="bold" m="x-1" @click="toggleStrict(true)">
|
||||
精准匹配
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 28px;
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
@apply bg-orange-600;
|
||||
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
$size: 20px;
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: $size;
|
||||
width: $size;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
@apply bg-green-600;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX($size);
|
||||
-ms-transform: translateX($size);
|
||||
transform: translateX($size);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 28px;
|
||||
|
||||
&:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
app/components/TheBottomMenu.vue
Normal file
57
app/components/TheBottomMenu.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BottomMenuItem } from '@yunlefun/vue'
|
||||
|
||||
const items: BottomMenuItem[] = [
|
||||
{
|
||||
icon: 'i-ri-home-line',
|
||||
activeIcon: 'i-ri-home-fill',
|
||||
title: '首页',
|
||||
to: '/',
|
||||
},
|
||||
{
|
||||
icon: 'i-ri-compass-2-line',
|
||||
activeIcon: 'i-ri-compass-2-fill',
|
||||
title: '吃什么',
|
||||
to: '/random',
|
||||
},
|
||||
// {
|
||||
// icon: 'i-ri-compass-2-line',
|
||||
// activeIcon: 'i-ri-compass-2-fill',
|
||||
// title: '吃什么',
|
||||
// to: '/about',
|
||||
// },
|
||||
{
|
||||
icon: 'i-ri-question-line',
|
||||
activeIcon: 'i-ri-question-fill',
|
||||
title: '帮助',
|
||||
to: '/help',
|
||||
},
|
||||
{
|
||||
icon: 'i-ri-user-line',
|
||||
activeIcon: 'i-ri-user-fill',
|
||||
title: '我的',
|
||||
to: '/user',
|
||||
},
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function onClick(item: BottomMenuItem) {
|
||||
// router.push(item.to || '/')
|
||||
router.replace(item.to || '/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YlfBottomMenu shadow-2xl pb="$cook-bottom-menu-padding-bottom">
|
||||
<YlfBottomMenuItem
|
||||
v-for="item in items"
|
||||
:key="item.to"
|
||||
:item="item"
|
||||
:active="route.path === item.to"
|
||||
class="pt-3"
|
||||
@click="onClick"
|
||||
/>
|
||||
</YlfBottomMenu>
|
||||
</template>
|
||||
32
app/components/ToggleMode.vue
Normal file
32
app/components/ToggleMode.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SearchMode } from '~/composables/store/recipe'
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
|
||||
const searchModes: {
|
||||
id: SearchMode
|
||||
name: string
|
||||
}[] = [{
|
||||
id: 'loose',
|
||||
name: '模糊匹配',
|
||||
}, {
|
||||
id: 'strict',
|
||||
name: '严格匹配',
|
||||
}, {
|
||||
id: 'survival',
|
||||
name: '生存模式',
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
v-for="mode in searchModes" :key="mode.id" class="rounded px-2 tag"
|
||||
:bg="mode.id === rStore.curMode ? 'orange-500 dark:orange-600 opacity-100' : 'orange-300 opacity-20'"
|
||||
:text="mode.id === rStore.curMode ? 'orange-100' : 'orange-800 dark:orange-200'"
|
||||
@click="rStore.setMode(mode.id)"
|
||||
>
|
||||
{{ mode.name }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
13
app/components/common/BackBtn.vue
Normal file
13
app/components/common/BackBtn.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script lang="ts" setup>
|
||||
const router = useRouter()
|
||||
function back() {
|
||||
router.back()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YlfIconButton
|
||||
icon="i-ri-arrow-left-s-line"
|
||||
@click="back"
|
||||
/>
|
||||
</template>
|
||||
25
app/components/common/DarkToggle.vue
Normal file
25
app/components/common/DarkToggle.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
const color = useColorMode()
|
||||
|
||||
useHead({
|
||||
meta: [{
|
||||
id: 'theme-color',
|
||||
name: 'theme-color',
|
||||
content: () => color.value === 'dark' ? '#222222' : '#ffffff',
|
||||
}],
|
||||
})
|
||||
|
||||
function toggleDark() {
|
||||
color.preference = color.value === 'dark' ? 'light' : 'dark'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YlfIconButton
|
||||
class="icon-btn hover:text-yellow-400 !outline-none"
|
||||
text-xl
|
||||
title="切换" @click="toggleDark()"
|
||||
>
|
||||
<div i="ri-sun-line dark:ri-moon-line" />
|
||||
</YlfIconButton>
|
||||
</template>
|
||||
105
app/components/common/SearchRecipe.vue
Normal file
105
app/components/common/SearchRecipe.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
||||
|
||||
import { db } from '~/utils/db'
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
function closeModal() {
|
||||
isOpen.value = false
|
||||
}
|
||||
function openModal() {
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
const keyword = ref('')
|
||||
async function getFilterRecipes(keyword: string) {
|
||||
return db.recipes.filter((recipe) => {
|
||||
return recipe.name.includes(keyword)
|
||||
}).toArray()
|
||||
}
|
||||
const filteredRecipes = computedAsync(async () => {
|
||||
return await getFilterRecipes(keyword.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YlfIconButton
|
||||
absolute right-4 top-4
|
||||
class="icon-btn hover:text-yellow-400 !outline-none"
|
||||
text-xl
|
||||
title="切换" @click="openModal"
|
||||
>
|
||||
<div i="ri-search-line" />
|
||||
</YlfIconButton>
|
||||
|
||||
<TransitionRoot appear :show="isOpen" as="template">
|
||||
<Dialog as="div" class="relative z-10" @close="closeModal">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="duration-300 ease-out"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div class="fixed inset-0 bg-black/10" />
|
||||
</TransitionChild>
|
||||
|
||||
<div class="fixed inset-0 overflow-y-auto">
|
||||
<div
|
||||
class="h-full flex justify-center text-center"
|
||||
>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="duration-300 ease-out"
|
||||
enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100"
|
||||
leave="duration-200 ease-in"
|
||||
leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel
|
||||
class="h-full max-w-xl w-full transform overflow-hidden bg-white p-4 text-left align-middle shadow-xl transition-all dark:bg-dark-600"
|
||||
md="rounded-2xl"
|
||||
overflow="auto"
|
||||
flex="~ col"
|
||||
>
|
||||
<DialogTitle
|
||||
as="h3"
|
||||
class="flex items-center justify-center text-lg font-medium leading-6"
|
||||
>
|
||||
<div relative inline-flex flex="grow">
|
||||
<div
|
||||
i-ri-search-line
|
||||
class="absolute left-3 top-2 cursor-pointer text-gray-400"
|
||||
/>
|
||||
<input
|
||||
v-model="keyword"
|
||||
type="text"
|
||||
class="w-full rounded-full bg-transparent text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 placeholder-gray-400"
|
||||
border="~ rounded-full gray-300 op-50 focus:border-blue-500"
|
||||
placeholder="搜索菜谱"
|
||||
autofocus py-2 pl-10 pr-3
|
||||
>
|
||||
<div
|
||||
v-if="keyword" i-ri-close-line
|
||||
class="absolute right-3 top-2 cursor-pointer text-gray-400"
|
||||
@click="keyword = ''"
|
||||
/>
|
||||
</div>
|
||||
<div op="70" ml-2 inline-flex cursor-pointer text-base @click="closeModal">
|
||||
取消
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<div flex="~ col grow" overflow="auto" class="mt-2" text-xs>
|
||||
<DishTag v-for="item, i in filteredRecipes" :key="i" :dish="item" />
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
60
app/components/content/AboutMe.vue
Normal file
60
app/components/content/AboutMe.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts" setup>
|
||||
// @ts-expect-error // Ignore this line
|
||||
import VueAboutMe from 'vue-about-me'
|
||||
import 'vue-about-me/style.css'
|
||||
|
||||
const color = useColorMode()
|
||||
const isDark = computed(() => color.value === 'dark')
|
||||
|
||||
const copyright = {
|
||||
name: 'Cook',
|
||||
repo: 'YunYouJun/cook',
|
||||
color: '#0078E7',
|
||||
iconUrl: 'https://sponsors.yunyoujun.cn',
|
||||
author: '云游君',
|
||||
authorUrl: 'https://www.yunyoujun.cn',
|
||||
}
|
||||
|
||||
const links = [
|
||||
{
|
||||
type: 'github',
|
||||
label: 'GitHub: YunYouJun',
|
||||
href: 'https://github.com/YunYouJun',
|
||||
},
|
||||
{
|
||||
type: 'telegram',
|
||||
label: 'Telegram Channel',
|
||||
href: 'https://t.me/elpsycn',
|
||||
},
|
||||
{
|
||||
type: 'weibo',
|
||||
label: '微博:机智的云游君',
|
||||
href: 'http://weibo.com/jizhideyunyoujun',
|
||||
},
|
||||
{
|
||||
type: 'twitter',
|
||||
label: 'Twitter: YunYouJun',
|
||||
href: 'https://twitter.com/YunYouJun',
|
||||
},
|
||||
{
|
||||
type: 'wechat',
|
||||
label: '微信公众号:云游君',
|
||||
href: '/wechat',
|
||||
target: '_self',
|
||||
},
|
||||
{
|
||||
type: 'bilibili',
|
||||
label: '云游君Official',
|
||||
href: 'https://space.bilibili.com/1579790',
|
||||
},
|
||||
{
|
||||
type: 'blog',
|
||||
label: '博客:yunyoujun.cn',
|
||||
href: 'http://www.yunyoujun.cn',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueAboutMe :is-dark="isDark" :copyright="copyright" :links="links" />
|
||||
</template>
|
||||
19
app/components/content/AboutMenu.vue
Normal file
19
app/components/content/AboutMenu.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLink class="icon-btn mx-2 hover:text-orange-400" to="/help" title="帮助">
|
||||
<div i-ri-question-line />
|
||||
</NuxtLink>
|
||||
|
||||
<NuxtLink class="icon-btn mx-2 hover:text-blue-400" to="/about" title="关于">
|
||||
<div i-ri-information-line />
|
||||
</NuxtLink>
|
||||
|
||||
<a class="icon-btn mx-2 hover:text-pink-400" rel="noreferrer" href="https://space.bilibili.com/1579790" target="_blank" title="BiliBili">
|
||||
<div i-ri-bilibili-line />
|
||||
</a>
|
||||
|
||||
<a class="icon-btn hover:text-black-400 mx-2" rel="noreferrer" href="https://github.com/YunYouJun/cook" target="_blank" title="GitHub">
|
||||
<div i-ri-github-line />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
31
app/components/cookbook/CookbookCard.vue
Normal file
31
app/components/cookbook/CookbookCard.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Cookbook } from '~/types'
|
||||
|
||||
defineProps<{
|
||||
cookbook: Cookbook
|
||||
}>()
|
||||
|
||||
const showDetail = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="bg-$c-bg-alt"
|
||||
h-36 w-full inline-flex cursor-pointer items-center justify-center shadow
|
||||
@click="showDetail = true"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
|
||||
<CookbookDetail
|
||||
v-if="showDetail"
|
||||
absolute bottom-17 left-2 right-2 top-2 z-1 overflow-hidden shadow
|
||||
:cookbook="cookbook"
|
||||
>
|
||||
<YlfIconButton
|
||||
icon="i-ri-close-line"
|
||||
class="absolute right-2 top-2"
|
||||
@click="showDetail = false"
|
||||
/>
|
||||
</CookbookDetail>
|
||||
</template>
|
||||
27
app/components/cookbook/CookbookDetail.vue
Normal file
27
app/components/cookbook/CookbookDetail.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Cookbook } from '~/types'
|
||||
|
||||
const props = defineProps<{
|
||||
cookbook: Cookbook
|
||||
}>()
|
||||
|
||||
const recipes = ref<Cookbook['recipes']>(props.cookbook.recipes)
|
||||
onMounted(async () => {
|
||||
recipes.value = ((await import('../../data/recipe.json')).default) as unknown as Cookbook['recipes']
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-$c-bg-alt" flex="~ col">
|
||||
<h3 mt-4 font-bold>
|
||||
{{ cookbook.title }}
|
||||
</h3>
|
||||
<sub op="90" my-3>
|
||||
{{ cookbook.description }}
|
||||
</sub>
|
||||
<div mx-auto mt-2 p-0 border="1px" overflow-y="scroll">
|
||||
<RecipeTable h="full" :recipes="recipes" />
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
18
app/components/cookbook/NewCookbookCard.vue
Normal file
18
app/components/cookbook/NewCookbookCard.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '新建食谱书',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
class="bg-$c-bg-alt"
|
||||
h-36 w-full inline-flex cursor-pointer items-center justify-center shadow
|
||||
to="/cookbooks/new"
|
||||
>
|
||||
<slot>
|
||||
<div i-ri-add-line />
|
||||
</slot>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
34
app/components/features/InstallPwa.vue
Normal file
34
app/components/features/InstallPwa.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
const app = useAppStore()
|
||||
|
||||
function install() {
|
||||
const deferredPrompt = app.deferredPrompt
|
||||
// Show the install prompt
|
||||
deferredPrompt.prompt()
|
||||
// Wait for the user to respond to the prompt
|
||||
deferredPrompt.userChoice.then((choiceResult: any) => {
|
||||
if (choiceResult.outcome === 'accepted')
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('User accepted the install prompt')
|
||||
else
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('User dismissed the install prompt')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition>
|
||||
<div v-if="app.deferredPrompt" text="center" m="t-2">
|
||||
<button
|
||||
class="shadow"
|
||||
text="white" bg="green-500" p="x-4 y-2" m="2" inline-flex
|
||||
items-center justify-center rounded-md font-bold
|
||||
@click="install"
|
||||
>
|
||||
<div i-ri-install-line mr-1 inline-flex />
|
||||
<span inline-flex>安装到桌面</span>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
63
app/components/features/ReloadPrompt.vue
Normal file
63
app/components/features/ReloadPrompt.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { useRegisterSW } from 'virtual:pwa-register/vue'
|
||||
|
||||
const {
|
||||
offlineReady,
|
||||
needRefresh,
|
||||
updateServiceWorker,
|
||||
} = useRegisterSW()
|
||||
|
||||
async function close() {
|
||||
offlineReady.value = false
|
||||
needRefresh.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="offlineReady || needRefresh"
|
||||
class="pwa-toast rounded shadow-lg transition hover:shadow-md"
|
||||
border="~ stone-200 dark:stone-600"
|
||||
text="center"
|
||||
p="4"
|
||||
m="4"
|
||||
bg="white dark:dark-800"
|
||||
role="alert"
|
||||
>
|
||||
<div class="message" m="b-4">
|
||||
<span v-if="offlineReady">
|
||||
可以离线使用啦!
|
||||
</span>
|
||||
<span v-else>
|
||||
更新了新的内容!
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="needRefresh"
|
||||
m="x-2" p="x-4 y-1" text="sm white"
|
||||
class="rounded shadow transition active:shadow-md"
|
||||
bg="green-500 active:green-600"
|
||||
@click="updateServiceWorker()"
|
||||
>
|
||||
更新
|
||||
</button>
|
||||
<button
|
||||
m="x-2" p="x-4 y-1" text="sm"
|
||||
class="rounded shadow transition active:shadow-md"
|
||||
border="~ stone-200 dark:stone-600"
|
||||
bg="active:(white opacity-20)"
|
||||
@click="close"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.pwa-toast {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
20
app/components/layout/CurrentVersion.vue
Normal file
20
app/components/layout/CurrentVersion.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import pkg from '~/../package.json'
|
||||
|
||||
const commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)
|
||||
const now = import.meta.env.VITE_APP_BUILD_TIME
|
||||
const buildDate = (new Date(Number.parseInt(now))).toLocaleDateString()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="commitSha && buildDate" mb-2 text-sm>
|
||||
<span>
|
||||
当前版本 v{{ pkg.version }}({{ buildDate }}):
|
||||
</span>
|
||||
<span>
|
||||
<a border="b-1 dashed" :href="`https://github.com/YunYouJun/cook/commit/${commitSha}`" target="_blank" alt="Cook | GitHub Commit">
|
||||
{{ commitSha }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
22
app/components/layout/SimpleCopyright.vue
Normal file
22
app/components/layout/SimpleCopyright.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div text="center sm" my-3>
|
||||
<CurrentVersion />
|
||||
<div flex="~" items-center justify-center gap="2">
|
||||
<a
|
||||
href="https://github.com/YunYouJun/cook" target="_blank"
|
||||
class="inline-flex items-center justify-center"
|
||||
>
|
||||
<div i-ri-github-line mr-1 />
|
||||
<span>Code</span>
|
||||
</a>
|
||||
by
|
||||
<a
|
||||
href="https://www.bilibili.com/opus/649847454294868008" target="_blank"
|
||||
class="inline-flex items-center justify-center"
|
||||
>
|
||||
<div i-ri-bilibili-line mr-1 class="text-pink-400" />
|
||||
<span>云游君</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
48
app/components/recipe/RecipeTable.vue
Normal file
48
app/components/recipe/RecipeTable.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Recipes } from '~/types'
|
||||
|
||||
defineProps<{
|
||||
recipes: Recipes
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table
|
||||
class="recipe-table bg-$c-bg"
|
||||
overflow="auto" h="full"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
名称
|
||||
</th>
|
||||
<th>
|
||||
工具
|
||||
</th>
|
||||
<th>
|
||||
材料
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<RecipeTableItem
|
||||
v-for="(recipe, i) in recipes"
|
||||
:key="recipe.name"
|
||||
:index="i" :recipe="recipe"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.recipe-table {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
31
app/components/recipe/RecipeTableItem.vue
Normal file
31
app/components/recipe/RecipeTableItem.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RecipeItem } from '~/types'
|
||||
|
||||
defineProps<{
|
||||
index: number
|
||||
recipe: RecipeItem
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr>
|
||||
<td>
|
||||
{{ index }}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
class="text-blue-500" font-bold
|
||||
:href="recipe.link || `https://www.bilibili.com/video/${recipe.bv}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ recipe.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ recipe.tools.join('、') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ recipe.stuff.join('、') }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
52
app/components/tags/DishTag.vue
Normal file
52
app/components/tags/DishTag.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DbRecipeItem } from '~/utils/db'
|
||||
import { tools } from '~/data/food'
|
||||
import type { RecipeItem } from '~/types'
|
||||
import { getEmojisFromStuff } from '~/utils'
|
||||
import { recipeHistories } from '~/composables/store/history'
|
||||
|
||||
const props = defineProps<{
|
||||
dish: RecipeItem | DbRecipeItem
|
||||
}>()
|
||||
|
||||
const gtm = useGtm()
|
||||
|
||||
function triggerGtm(dish: RecipeItem) {
|
||||
recipeHistories.value.push({
|
||||
recipe: dish,
|
||||
time: Date.now(),
|
||||
})
|
||||
|
||||
gtm?.trackEvent({
|
||||
event: 'click',
|
||||
category: `dish_${dish.name}`,
|
||||
action: 'click_recipe',
|
||||
label: '跳转菜谱',
|
||||
})
|
||||
gtm?.trackEvent({
|
||||
event: 'click_dish',
|
||||
action: dish.name,
|
||||
})
|
||||
}
|
||||
|
||||
const dishLabel = computed(() => {
|
||||
const emojis = getEmojisFromStuff(props.dish.stuff)
|
||||
return `${props.dish.tags?.includes('杂烩') ? '🍲' : emojis.join(' ')} ${props.dish.name}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
:href="dish.link || `https://www.bilibili.com/video/${dish.bv}`" target="_blank" class="dish-tag rounded tag" p="x-2"
|
||||
border="~ blue-200 dark:blue-800"
|
||||
bg="blue-300 opacity-20"
|
||||
@click="triggerGtm(dish)"
|
||||
>
|
||||
<span m="r-1" text="sm blue-700 dark:blue-200">
|
||||
{{ dishLabel }}
|
||||
</span>
|
||||
<template v-for="tool, i in tools">
|
||||
<span v-if="dish.tools?.includes(tool.name)" :key="i" :class="tool.icon" />
|
||||
</template>
|
||||
</a>
|
||||
</template>
|
||||
16
app/components/tags/MeatTag.vue
Normal file
16
app/components/tags/MeatTag.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
active: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="meat-tag rounded tag" p="x-2"
|
||||
border="~ red-200 dark:red-800"
|
||||
:bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'"
|
||||
:text="active ? 'red-100' : 'red-800 dark:red-200'"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
15
app/components/tags/StapleTag.vue
Normal file
15
app/components/tags/StapleTag.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
active: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="rounded tag" p="x-2" border="~ yellow-200 dark:yellow-800"
|
||||
:bg="active ? 'yellow-500 dark:yellow-600 opacity-100' : 'yellow-300 opacity-20'"
|
||||
:text="active ? 'yellow-100' : 'yellow-800 dark:yellow-200'"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
16
app/components/tags/ToolTag.vue
Normal file
16
app/components/tags/ToolTag.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
active: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="rounded tag" p="x-2"
|
||||
border="~ stone-200 dark:stone-600"
|
||||
:bg="active ? 'stone-600 opacity-100' : 'stone-300 opacity-5'"
|
||||
:text="active ? 'stone-100' : 'stone-800 dark:stone-200'"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
16
app/components/tags/VegetableTag.vue
Normal file
16
app/components/tags/VegetableTag.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
active: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="vegetable-tag rounded tag" p="x-2"
|
||||
border="~ green-200 dark:green-800"
|
||||
:bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'"
|
||||
:text="active ? 'green-100' : 'green-800 dark:green-200'"
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
24
app/components/ylf/YlfForm.vue
Normal file
24
app/components/ylf/YlfForm.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="ylf-form" flex="~ col" rounded-md>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.ylf-form {
|
||||
background-color: var(--ylf-c-bg-alt);
|
||||
|
||||
border: 1px solid var(--ylf-c-border);
|
||||
|
||||
margin: 10px 0;
|
||||
overflow: hidden;
|
||||
|
||||
.ylf-form-item {
|
||||
border-bottom: 1px solid var(--ylf-c-border);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
app/components/ylf/YlfFormItem.vue
Normal file
33
app/components/ylf/YlfFormItem.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { NuxtLink } from '#components'
|
||||
|
||||
defineProps<{
|
||||
icon?: string
|
||||
label?: string
|
||||
/**
|
||||
* Router link
|
||||
*/
|
||||
to?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="to ? NuxtLink : 'div'"
|
||||
:to="to"
|
||||
class="ylf-form-item"
|
||||
w-full flex cursor-pointer items-center justify-between p-2
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-dark-400
|
||||
>
|
||||
<div v-if="label" class="text-sm" inline-flex items-center justify-center>
|
||||
<div v-if="icon" :class="icon" mr-2 inline-flex />
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
<div inline-flex>
|
||||
<slot>
|
||||
<div v-if="to" i-ri-arrow-right-s-line />
|
||||
</slot>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
16
app/components/ylf/YlfIconButton.vue
Normal file
16
app/components/ylf/YlfIconButton.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
icon?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="ylf-icon-button hover:(bg-blue-300 bg-opacity-20)"
|
||||
h-10 w-10 inline-flex items-center justify-center rounded-full
|
||||
>
|
||||
<slot>
|
||||
<div v-if="icon" text-xl :class="icon" />
|
||||
</slot>
|
||||
</button>
|
||||
</template>
|
||||
22
app/components/ylf/YlfIconItem.vue
Normal file
22
app/components/ylf/YlfIconItem.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
icon: string
|
||||
label: string
|
||||
to: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLink
|
||||
:to="to"
|
||||
flex="~ col"
|
||||
border="~ solid dark:$ylf-c-border"
|
||||
bg="$ylf-c-bg-alt"
|
||||
class="inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium decoration-none hover-bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500 dark:hover:bg-dark-400"
|
||||
>
|
||||
<div :class="icon" inline-flex text-lg />
|
||||
<div mt-2 inline-flex text-xs>
|
||||
{{ label }}
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
28
app/components/ylf/YlfSwitch.vue
Normal file
28
app/components/ylf/YlfSwitch.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
import { Switch } from '@headlessui/vue'
|
||||
|
||||
defineProps<{
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
function updateModelValue(value: boolean) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Switch
|
||||
:model-value="modelValue"
|
||||
:class="modelValue ? 'bg-blue-600' : 'bg-gray'"
|
||||
class="relative h-6 w-11 inline-flex shrink-0 cursor-pointer border-2 border-transparent rounded-full transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
|
||||
@update:model-value="updateModelValue"
|
||||
>
|
||||
<span class="sr-only">Use setting</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="modelValue ? 'translate-x-5' : 'translate-x-0'"
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out"
|
||||
/>
|
||||
</Switch>
|
||||
</template>
|
||||
42
app/composables/animation.ts
Normal file
42
app/composables/animation.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { isClient } from '@vueuse/core'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export function useEmojiAnimation(recipeBtn: Ref<HTMLButtonElement | undefined>) {
|
||||
const { x, y } = usePointer()
|
||||
const { top, left } = useElementBounding(recipeBtn)
|
||||
|
||||
const playAnimation = (emoji: string) => {
|
||||
if (!isClient)
|
||||
return
|
||||
|
||||
// 单个 Vue 组件实现不适合创建多个元素和清除动画
|
||||
const emojiEl = document.createElement('span')
|
||||
emojiEl.style.position = 'fixed'
|
||||
emojiEl.style.left = `${x.value}px`
|
||||
emojiEl.style.top = `${y.value}px`
|
||||
emojiEl.style.zIndex = '10'
|
||||
emojiEl.style.transition = 'left .4s linear, top .4s cubic-bezier(0.5, -0.5, 1, 1)'
|
||||
emojiEl.textContent = emoji
|
||||
document.body.appendChild(emojiEl)
|
||||
|
||||
setTimeout(() => {
|
||||
// 以防万一,按钮位置没检测出来,就不播放动画了
|
||||
if (!top.value || !left.value) {
|
||||
emojiEl.style.top = `${x.value}px`
|
||||
emojiEl.style.left = `${y.value}px`
|
||||
}
|
||||
else {
|
||||
emojiEl.style.top = `${top.value}px`
|
||||
emojiEl.style.left = `${left.value + 12}px`
|
||||
}
|
||||
}, 1)
|
||||
|
||||
emojiEl.ontransitionend = () => {
|
||||
emojiEl.remove()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
playAnimation,
|
||||
}
|
||||
}
|
||||
20
app/composables/count.ts
Normal file
20
app/composables/count.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export function useCount() {
|
||||
const count = useStorage('count', 5)
|
||||
|
||||
function inc() {
|
||||
count.value += 1
|
||||
}
|
||||
function dec() {
|
||||
if (count.value <= 1)
|
||||
return
|
||||
count.value -= 1
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
inc,
|
||||
dec,
|
||||
}
|
||||
}
|
||||
17
app/composables/db.ts
Normal file
17
app/composables/db.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { lastDbUpdated, namespace } from '~/constants'
|
||||
import { db, initDb } from '~/utils/db'
|
||||
|
||||
export function useIndexedDB() {
|
||||
const dbUpdated = useStorage(`${namespace}:lastDbUpdated`, lastDbUpdated)
|
||||
|
||||
return {
|
||||
init: async () => {
|
||||
const count = await db.recipes.count()
|
||||
if (!count || dbUpdated.value !== lastDbUpdated) {
|
||||
await initDb()
|
||||
dbUpdated.value = lastDbUpdated
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
24
app/composables/helper.ts
Normal file
24
app/composables/helper.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { MaybeComputedElementRef } from '@vueuse/core'
|
||||
import { isClient, useElementBounding } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
* trigger show invisible element
|
||||
* @param target
|
||||
*/
|
||||
export function useInvisibleElement(target: MaybeComputedElementRef<HTMLElement>) {
|
||||
const { top } = useElementBounding(target)
|
||||
|
||||
const isVisible = computed(() => {
|
||||
return isClient ? window.scrollY < top.value : true
|
||||
})
|
||||
|
||||
const show = () => {
|
||||
if (isClient)
|
||||
window.scrollTo(0, top.value)
|
||||
}
|
||||
|
||||
return {
|
||||
isVisible,
|
||||
show,
|
||||
}
|
||||
}
|
||||
3
app/composables/index.ts
Normal file
3
app/composables/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './store'
|
||||
|
||||
// others is auto exported
|
||||
34
app/composables/recipe.ts
Normal file
34
app/composables/recipe.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { namespace } from '~/constants'
|
||||
import type { DbRecipeItem } from '~/utils/db'
|
||||
|
||||
/**
|
||||
* 随机几道菜
|
||||
* @param total
|
||||
*/
|
||||
export function useRandomRecipe(total: Ref<number>) {
|
||||
const randomRecipes = useStorage<(DbRecipeItem | undefined)[]>(`${namespace}:random:recipes`, [])
|
||||
async function random() {
|
||||
const length = await db.recipes.count()
|
||||
const randomArr = generateRandomArray(length, total.value)
|
||||
const result = await db.recipes.bulkGet(randomArr)
|
||||
if (result)
|
||||
randomRecipes.value = result.filter(item => !!item)
|
||||
}
|
||||
|
||||
watch(total, () => {
|
||||
random()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 如果没有随机菜谱,就生成一次
|
||||
if (randomRecipes.value.length <= 0)
|
||||
random()
|
||||
})
|
||||
|
||||
return {
|
||||
random,
|
||||
|
||||
randomRecipes,
|
||||
}
|
||||
}
|
||||
19
app/composables/store/app.ts
Normal file
19
app/composables/store/app.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { defaultSettings } from '../../utils/settings'
|
||||
import { namespace } from '../../constants'
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
const deferredPrompt = ref<Event | any>()
|
||||
const settings = useStorage(`${namespace}:settings`, defaultSettings)
|
||||
|
||||
return {
|
||||
deferredPrompt,
|
||||
|
||||
settings,
|
||||
}
|
||||
})
|
||||
|
||||
if (import.meta.hot)
|
||||
import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot))
|
||||
10
app/composables/store/history.ts
Normal file
10
app/composables/store/history.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { namespace } from '~/constants'
|
||||
import type { RecipeItem } from '~/types'
|
||||
|
||||
export interface RecipeHistoryItem {
|
||||
recipe: RecipeItem
|
||||
time: number
|
||||
}
|
||||
|
||||
export const recipeHistories = useStorage<RecipeHistoryItem[]>(`${namespace}:history`, [])
|
||||
3
app/composables/store/index.ts
Normal file
3
app/composables/store/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './app'
|
||||
export * from './recipe'
|
||||
export * from './user'
|
||||
185
app/composables/store/recipe.ts
Normal file
185
app/composables/store/recipe.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useGtm } from '@gtm-support/vue-gtm'
|
||||
import { db } from '../../utils/db'
|
||||
import { useAppStore } from './app'
|
||||
import type { RecipeItem, StuffItem } from '~/types'
|
||||
|
||||
const namespace = 'cook'
|
||||
|
||||
/**
|
||||
* survival: 生存模式
|
||||
* strict: 严格
|
||||
* loose: 模糊
|
||||
*/
|
||||
export type SearchMode = 'survival' | 'loose' | 'strict'
|
||||
|
||||
export const useRecipeStore = defineStore('recipe', () => {
|
||||
const gtm = useGtm()
|
||||
const { settings } = useAppStore()
|
||||
|
||||
/**
|
||||
* 搜索关键字
|
||||
*/
|
||||
const keyword = ref('')
|
||||
|
||||
// can not exported
|
||||
const curStuff = settings.keepLocalData ? useStorage(`${namespace}:stuff`, new Set<string>()) : ref(new Set<string>())
|
||||
// const curTools = ref(new Set<string>())
|
||||
const curTool = settings.keepLocalData ? useStorage(`${namespace}:tool`, '') : ref('')
|
||||
const curMode = settings.keepLocalData ? useStorage<SearchMode>(`${namespace}:mode`, 'loose') : ref<SearchMode>('loose')
|
||||
|
||||
const selectedStuff = computed(() => Array.from(curStuff.value))
|
||||
// const selectedTools = computed(() => Array.from(curTools.value))
|
||||
// const selectedTools = ref('')
|
||||
|
||||
function toggleStuff(name: string) {
|
||||
if (!curStuff.value)
|
||||
return
|
||||
if (curStuff.value.has(name))
|
||||
curStuff.value.delete(name)
|
||||
else
|
||||
curStuff.value.add(name)
|
||||
}
|
||||
|
||||
function toggleTools(name: string) {
|
||||
if (curTool.value === name)
|
||||
curTool.value = ''
|
||||
else
|
||||
curTool.value = name
|
||||
// if (curTools.value.has(name))
|
||||
// curTools.value.delete(name)
|
||||
// else
|
||||
// curTools.value.add(name)
|
||||
}
|
||||
|
||||
function setMode(mode: SearchMode) {
|
||||
curMode.value = mode
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function reset() {
|
||||
curStuff.value.clear()
|
||||
// curTools.value.clear()
|
||||
curTool.value = ''
|
||||
}
|
||||
|
||||
function addStuff(name: string) {
|
||||
curStuff.value.add(name)
|
||||
}
|
||||
|
||||
const isSearching = ref(false)
|
||||
/**
|
||||
* 搜索菜谱
|
||||
*/
|
||||
async function searchRecipes() {
|
||||
isSearching.value = true
|
||||
let result: RecipeItem[] = []
|
||||
|
||||
if (curMode.value === 'strict') {
|
||||
result = await db.recipes.filter((item) => {
|
||||
const stuffFlag = selectedStuff.value.every(stuff => item.stuff.includes(stuff))
|
||||
const toolFlag = item.tools.includes(curTool.value)
|
||||
return curTool.value ? (stuffFlag && toolFlag) : stuffFlag
|
||||
}).toArray()
|
||||
}
|
||||
else if (curMode.value === 'loose') {
|
||||
result = await db.recipes.filter((item) => {
|
||||
const stuffFlag = selectedStuff.value.some(stuff => item.stuff.includes(stuff))
|
||||
const toolFlag = Boolean(item.tools?.includes(curTool.value))
|
||||
|
||||
// 同时存在 厨具和材料,则同时判断
|
||||
if (curTool.value && selectedStuff.value.length) {
|
||||
return stuffFlag && toolFlag
|
||||
}
|
||||
else {
|
||||
if (selectedStuff.value.length)
|
||||
return stuffFlag
|
||||
else if (curTool.value)
|
||||
return toolFlag
|
||||
|
||||
return false
|
||||
}
|
||||
}).toArray()
|
||||
}
|
||||
// survival
|
||||
else {
|
||||
result = await db.recipes.filter((item) => {
|
||||
const stuffFlag = item.stuff.every(stuff => selectedStuff.value.includes(stuff))
|
||||
const toolFlag = item.tools?.includes(curTool.value)
|
||||
return Boolean(curTool.value ? (stuffFlag && toolFlag) : stuffFlag)
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
if (keyword.value)
|
||||
result = result.filter(item => item.name.includes(keyword.value))
|
||||
|
||||
isSearching.value = false
|
||||
return result
|
||||
}
|
||||
|
||||
// 默认严格模式
|
||||
const displayedRecipe = ref<RecipeItem[]>([])
|
||||
// fix curStuff watch
|
||||
watch(() => [keyword.value, selectedStuff.value, curTool.value, curMode.value], async () => {
|
||||
displayedRecipe.value = [...(await searchRecipes())]
|
||||
})
|
||||
|
||||
/**
|
||||
* toggle tool
|
||||
* @param item
|
||||
*/
|
||||
const clickTool = (item: StuffItem) => {
|
||||
const value = item.name
|
||||
toggleTools(value)
|
||||
|
||||
gtm?.trackEvent({
|
||||
event: 'click',
|
||||
category: `tool_${value}`,
|
||||
action: 'click_tool',
|
||||
label: '工具',
|
||||
})
|
||||
gtm?.trackEvent({
|
||||
event: 'click_tool',
|
||||
action: item.name,
|
||||
})
|
||||
}
|
||||
|
||||
const recipesLength = ref(0)
|
||||
onMounted(async () => {
|
||||
db.recipes.count().then((count) => {
|
||||
recipesLength.value = count
|
||||
})
|
||||
|
||||
displayedRecipe.value = await searchRecipes()
|
||||
})
|
||||
|
||||
return {
|
||||
recipesLength,
|
||||
|
||||
keyword,
|
||||
curTool,
|
||||
curMode,
|
||||
selectedStuff,
|
||||
|
||||
isSearching,
|
||||
|
||||
clearKeyWord: () => { keyword.value = '' },
|
||||
toggleStuff,
|
||||
toggleTools,
|
||||
reset,
|
||||
setMode,
|
||||
|
||||
addStuff,
|
||||
|
||||
// useRecipe
|
||||
displayedRecipe,
|
||||
clickTool,
|
||||
}
|
||||
})
|
||||
|
||||
if (import.meta.hot)
|
||||
import.meta.hot.accept(acceptHMRUpdate(useRecipeStore, import.meta.hot))
|
||||
34
app/composables/store/user.ts
Normal file
34
app/composables/store/user.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
/**
|
||||
* Current name of the user.
|
||||
*/
|
||||
const savedName = ref('')
|
||||
const previousNames = ref(new Set<string>())
|
||||
|
||||
const usedNames = computed(() => Array.from(previousNames.value))
|
||||
const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value))
|
||||
|
||||
/**
|
||||
* Changes the current name of the user and saves the one that was used
|
||||
* before.
|
||||
*
|
||||
* @param name - new name to set
|
||||
*/
|
||||
function setNewName(name: string) {
|
||||
if (savedName.value)
|
||||
previousNames.value.add(savedName.value)
|
||||
|
||||
savedName.value = name
|
||||
}
|
||||
|
||||
return {
|
||||
setNewName,
|
||||
otherNames,
|
||||
savedName,
|
||||
}
|
||||
})
|
||||
|
||||
if (import.meta.hot)
|
||||
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
|
||||
0
app/config/index.ts
Normal file
0
app/config/index.ts
Normal file
79
app/config/pwa.ts
Normal file
79
app/config/pwa.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import process from 'node:process'
|
||||
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
||||
import { appDescription, appName } from '../constants/index'
|
||||
|
||||
const scope = '/'
|
||||
|
||||
export const pwa: ModuleOptions = {
|
||||
registerType: 'autoUpdate',
|
||||
scope,
|
||||
base: scope,
|
||||
manifest: {
|
||||
id: scope,
|
||||
scope,
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
description: appDescription,
|
||||
theme_color: '#ffffff',
|
||||
icons: [
|
||||
{
|
||||
src: 'pwa-192x192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: 'pwa-512x512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
},
|
||||
{
|
||||
src: 'maskable-icon.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
},
|
||||
],
|
||||
},
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,txt,png,ico,svg}'],
|
||||
navigateFallbackDenylist: [/^\/api\//],
|
||||
navigateFallback: '/',
|
||||
cleanupOutdatedCaches: true,
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts.googleapis.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'google-fonts-cache',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /^https:\/\/fonts.gstatic.com\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'gstatic-fonts-cache',
|
||||
expiration: {
|
||||
maxEntries: 10,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
registerWebManifestInRouteRules: true,
|
||||
writePlugin: true,
|
||||
devOptions: {
|
||||
enabled: process.env.VITE_PLUGIN_PWA === 'true',
|
||||
navigateFallback: scope,
|
||||
},
|
||||
}
|
||||
7
app/constants/index.ts
Normal file
7
app/constants/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const appName = '食用手册'
|
||||
export const appDescription = '好的,今天我们来做菜!'
|
||||
|
||||
export const namespace = 'cook'
|
||||
export const lastDbUpdated = '2023-11-11 19:51:02'
|
||||
|
||||
export * from './links'
|
||||
10
app/constants/links.ts
Normal file
10
app/constants/links.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const links = {
|
||||
/**
|
||||
* 菜谱投稿链接
|
||||
*/
|
||||
contribute: 'https://docs.qq.com/form/page/DWk9GWW9oTmlXZU9V',
|
||||
/**
|
||||
* 兔小巢反馈
|
||||
*/
|
||||
feedback: 'https://support.qq.com/product/507827',
|
||||
}
|
||||
19
app/data/README.md
Normal file
19
app/data/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Data
|
||||
|
||||
菜谱数据:<https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS>
|
||||
|
||||
> 下载为 `csv` 转换使用
|
||||
|
||||
- [新菜谱反馈](https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic)
|
||||
- [反馈建议分享-兔小巢](https://support.qq.com/products/507827)
|
||||
|
||||
## 说明
|
||||
|
||||
- tags: 标签
|
||||
- 杂烩:不会显示所有材料 emoji,而是显示 🍲
|
||||
|
||||
## 数据库
|
||||
|
||||
使用 Dexie.js 重构数据查询
|
||||
|
||||
- [Dexie.js](https://github.com/dexie/Dexie.js): A Minimalistic Wrapper for IndexedDB
|
||||
165
app/data/food.ts
Normal file
165
app/data/food.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import type { StuffItem } from '../types'
|
||||
|
||||
/**
|
||||
* 素菜
|
||||
*/
|
||||
export const vegetable: StuffItem[] = [
|
||||
{
|
||||
name: '土豆',
|
||||
emoji: '🥔',
|
||||
},
|
||||
{
|
||||
name: '胡萝卜',
|
||||
emoji: '🥕',
|
||||
},
|
||||
{
|
||||
name: '花菜',
|
||||
emoji: '🥦',
|
||||
},
|
||||
{
|
||||
name: '白萝卜',
|
||||
emoji: '🥣',
|
||||
},
|
||||
{
|
||||
name: '西葫芦',
|
||||
emoji: '🥒',
|
||||
},
|
||||
{
|
||||
name: '番茄',
|
||||
emoji: '🍅',
|
||||
alias: '西红柿',
|
||||
},
|
||||
{
|
||||
name: '芹菜',
|
||||
emoji: '🥬',
|
||||
},
|
||||
{
|
||||
name: '黄瓜',
|
||||
emoji: '🥒',
|
||||
},
|
||||
{
|
||||
name: '洋葱',
|
||||
emoji: '🧅',
|
||||
},
|
||||
{
|
||||
name: '莴笋',
|
||||
emoji: '🎍',
|
||||
},
|
||||
{
|
||||
name: '菌菇',
|
||||
emoji: '🍄',
|
||||
},
|
||||
{
|
||||
name: '茄子',
|
||||
emoji: '🍆',
|
||||
},
|
||||
{
|
||||
name: '豆腐',
|
||||
emoji: '🍲',
|
||||
},
|
||||
{
|
||||
name: '包菜',
|
||||
emoji: '🥗',
|
||||
},
|
||||
{
|
||||
name: '白菜',
|
||||
emoji: '🥬',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 荤菜
|
||||
*/
|
||||
export const meat: StuffItem[] = [
|
||||
{
|
||||
name: '午餐肉',
|
||||
emoji: '🥓',
|
||||
},
|
||||
{
|
||||
name: '香肠',
|
||||
emoji: '🌭',
|
||||
},
|
||||
{
|
||||
name: '腊肠',
|
||||
emoji: '🌭',
|
||||
},
|
||||
{
|
||||
name: '鸡肉',
|
||||
emoji: '🐤',
|
||||
},
|
||||
{
|
||||
name: '猪肉',
|
||||
emoji: '🐷',
|
||||
},
|
||||
{
|
||||
name: '鸡蛋',
|
||||
emoji: '🥚',
|
||||
},
|
||||
{
|
||||
name: '虾',
|
||||
emoji: '🦐',
|
||||
},
|
||||
{
|
||||
name: '牛肉',
|
||||
emoji: '🐮',
|
||||
},
|
||||
{
|
||||
name: '骨头',
|
||||
emoji: '🦴',
|
||||
},
|
||||
{
|
||||
name: '鱼(Todo)',
|
||||
emoji: '🐟',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 主食
|
||||
*/
|
||||
export const staple: StuffItem[] = [
|
||||
{
|
||||
name: '面食',
|
||||
emoji: '🍝',
|
||||
},
|
||||
{
|
||||
name: '面包',
|
||||
emoji: '🍞',
|
||||
},
|
||||
{
|
||||
name: '米',
|
||||
emoji: '🍚',
|
||||
},
|
||||
{
|
||||
name: '方便面',
|
||||
emoji: '🍜',
|
||||
},
|
||||
]
|
||||
|
||||
export const tools: StuffItem[] = [
|
||||
{
|
||||
name: '烤箱',
|
||||
emoji: '',
|
||||
icon: 'i-mdi-toaster-oven',
|
||||
},
|
||||
{
|
||||
name: '空气炸锅',
|
||||
emoji: '',
|
||||
icon: 'i-fe-frying-pan',
|
||||
},
|
||||
{
|
||||
name: '微波炉',
|
||||
emoji: '',
|
||||
icon: 'i-ic-outline-microwave',
|
||||
},
|
||||
{
|
||||
name: '电饭煲',
|
||||
emoji: '',
|
||||
icon: 'i-gg-smart-home-cooker',
|
||||
},
|
||||
{
|
||||
label: '一口能炒又能煮的大锅',
|
||||
name: '一口大锅',
|
||||
emoji: '',
|
||||
icon: 'i-mdi-pot-steam-outline',
|
||||
},
|
||||
]
|
||||
600
app/data/recipe.csv
Normal file
600
app/data/recipe.csv
Normal file
@@ -0,0 +1,600 @@
|
||||
name,stuff,bv,difficulty,tags,methods,tools,
|
||||
电饭煲版广式腊肠煲饭,腊肠、米,BV1NE411Q7Jj,简单,广式,煲,电饭煲,
|
||||
电饭煲版烧鸡,鸡肉、洋葱、菌菇,BV1T54y1U7Cu,简单,好吃,,电饭煲,
|
||||
电饭煲焖面,猪肉、面食,BV14b411q7rM,简单,筋道,,电饭煲,
|
||||
电饭煲版番茄牛腩焖饭,牛肉、番茄、米,BV1Bv411C7X3,简单,懒人,,电饭煲,
|
||||
电饭煲版蜜汁鸡翅,鸡肉,BV1dj411f7sR,简单,懒人,,电饭煲,
|
||||
电饭煲版南瓜鸡腿焖饭,鸡肉、洋葱、菌菇、米,BV1Bv411C7X3,简单,懒人,,电饭煲,
|
||||
电饭煲版土豆排骨焖饭,猪肉、土豆、米、腊肠,BV1Bv411C7X3,简单,懒人,,电饭煲,
|
||||
电饭煲版香菇腊肠焖饭,腊肠、菌菇、洋葱、米、土豆,BV1Bv411C7X3,简单,懒人,,电饭煲,
|
||||
电饭煲版香卤牛肉,牛肉、米,BV1dR4y1F7H2,简单,卤味,煮,电饭煲,
|
||||
电饭煲叉烧排骨,猪肉、洋葱,BV1Ab4y1p7dw,简单,下饭,,电饭煲,
|
||||
电饭煲版红烧肉,猪肉,BV1SW411t7AX,,,,电饭煲,
|
||||
电饭煲版可乐鸡翅,鸡肉,BV16J411U79k,,,,电饭煲,
|
||||
电饭煲版蛋糕(废手版),鸡蛋、面食,BV1H7411V7Pz,简单,香软,,电饭煲,
|
||||
电饭煲卤菜(开店级别),鸡肉、鸡蛋、米,BV1ZA411E7KT,简单,小吃,,电饭煲,
|
||||
电饭煲卤肉蛋豆腐,鸡肉、鸡蛋、豆腐、米,BV1aM4y1L7QR,简单,小吃,,电饭煲,
|
||||
电饭煲版罗宋汤,牛肉、番茄、洋葱、芹菜、胡萝卜、土豆、包菜、香肠,BV16Q4y1m7nU,简单,杂烩,,电饭煲,
|
||||
电饭煲版一只番茄饭,土豆、胡萝卜、香肠、番茄、鸡蛋、米,BV1dj411f7sR,简单,杂烩,,电饭煲,
|
||||
电饭煲茄子烧肉焖饭,猪肉、茄子、番茄、土豆、洋葱、米,BV18T4y1c7s1,普通,杂烩,,电饭煲,
|
||||
电饭煲版茶叶蛋,鸡蛋,BV12W411R7LA,,,,电饭煲,
|
||||
电饭煲版番茄炖排骨,番茄、猪肉、米,BV1av411r7mL,,,,电饭煲,
|
||||
电饭煲版番茄牛腩,番茄、牛肉、米,BV18i4y1g7sp,,,,电饭煲,
|
||||
电饭煲版干妈炒泡面,方便面、鸡蛋,BV1kW411r7Py,简单,,,电饭煲,
|
||||
电饭煲版红烧肉,猪肉、米,BV1SW411t7AX,,,,电饭煲,
|
||||
电饭煲版胡萝卜腊肠饭,胡萝卜、腊肠、米,BV1tW411b7LS,,,,电饭煲,
|
||||
电饭煲版卤鸡翅,鸡肉,BV1UK4y1Y7aC,简单,,卤,电饭煲,
|
||||
电饭煲版焖鸡腿土豆,鸡肉、土豆,BV1tR4y1g7kv,简单,,,电饭煲,
|
||||
电饭煲版蜜汁叉烧肉,猪肉、米,BV1ci4y1j7Rz,简单,,,电饭煲,
|
||||
电饭煲版蜜汁鸡腿,鸡肉,BV1A4411k7iz,普通,,,电饭煲,
|
||||
电饭煲版牛腩萝卜清汤,牛肉、白萝卜,BV16Q4y1m7nU,,,,电饭煲,
|
||||
电饭煲版茄子猪肉焖面,茄子、猪肉、面食,BV1ib41137ig,复杂,,,电饭煲,
|
||||
电饭煲版肉饼蛋,猪肉、鸡蛋,BV13W411a7Ax,普通,,,电饭煲,
|
||||
电饭煲版上海菜饭,猪肉、腊肠、米、包菜、莴笋,BV1mW411x7F7,,,,电饭煲,
|
||||
电饭煲版蒜蓉排骨,猪肉、米、,BV1Dv411T7bD,,,,电饭煲,
|
||||
电饭煲版土豆香肠焖饭,土豆、香肠、米,BV1Kv411q7BM,简单,,,电饭煲,
|
||||
电饭煲版吐司(尽量不做,废手),鸡蛋、面包,BV1vY41137F1,困难,,,电饭煲,
|
||||
电饭煲叉烧肉(叉烧酱版),猪肉、米,BV127411W75D,普通,,,电饭煲,
|
||||
电饭煲温泉蛋,鸡蛋,BV1EJ411Y7fU,,,,电饭煲,
|
||||
电饭煲窝蛋牛肉饭,牛肉、鸡蛋、米,BV1aA411q7b9,简单,,,电饭煲,
|
||||
电饭锅酱牛肉,牛肉、米饭,BV1sv411C7qW,,,,电饭煲,
|
||||
烤箱版M记同款薯条,土豆,BV1nA411P7aX,,同款,,烤箱,
|
||||
空心小薯片,土豆、鸡蛋,BV1N5411D79P,,零食,烘,烤箱,
|
||||
气泡小土豆,土豆,BV1sL41137jN,简单,零食,烘,烤箱,
|
||||
烤箱版M记同款麦乐鸡,土豆、鸡肉、鸡蛋,BV19v411J7MG,,同款,,烤箱,
|
||||
烤箱版KFC同款香辣鸡翅,鸡肉,BV1jA4y197RR,,同款,,烤箱,
|
||||
烤箱版M记同款薯饼,土豆,BV1qy4y1y7Mn,,同款,,烤箱,
|
||||
烤箱烧烤,土豆、胡萝卜、花菜、西葫芦、芹菜、洋葱、莴笋、菌菇、茄子、包菜、午餐肉、香肠、鸡肉、猪肉、虾、牛肉、鸡肉,BV17P4y1W7Ze,,杂烩,,烤箱,
|
||||
烤箱迎客菜,牛肉、茄子、鸡肉、鸡蛋、豆腐、菌菇、番茄,BV1ug4y1B7CR,,杂烩,,烤箱,
|
||||
爆浆鸡腿,鸡肉,BV1ju411X7mx,复杂,,烤,烤箱,
|
||||
BBQ烤鸡腿,鸡肉,BV1Zr4y1B7UQ,简单,,烤,烤箱,
|
||||
BBQ烟熏手撕猪肉,猪肉,BV1DV411x7SH,复杂,,烤,烤箱,
|
||||
馋嘴烤箱版烧烤,土豆、鸡肉,BV1wv411h7d6,普通,,烤,烤箱,
|
||||
脆皮五花肉和蜜汁叉烧,猪肉,BV17u411i7jF,普通,,烤,烤箱,
|
||||
法棍,面包,BV1Kc411h7eJ,普通,,,烤箱,
|
||||
汉堡面包,鸡蛋、面包,BV1ga411h7cw,普通,,,烤箱,
|
||||
火山熔岩蛋,土豆、鸡蛋,BV1Xy4y1L7ok,困难,,烘,烤箱,
|
||||
烤布丁,鸡蛋,BV1TT4y127vu,,,烘,烤箱,
|
||||
烤箱版爆浆鸡腿,鸡肉,BV1ju411X7mx,复杂,,,烤箱,
|
||||
烤箱版橙香鸡翅,鸡肉,BV1Mb4y1Q7DU,简单,,烤,烤箱,
|
||||
烤箱版脆皮五花肉,猪肉,BV1VS4y137dE,普通,,烤,烤箱,
|
||||
烤箱版黑椒牛肉土豆焖饭,牛肉、土豆、胡萝卜、米、鸡蛋,BV14x411D7uN,,,,烤箱,
|
||||
烤箱版烤串,猪肉、花菜,BV17P4y1W7Ze,简单,,烤,烤箱,
|
||||
烤箱版烤花菜,花菜,BV1Nv411Y7kV,简单,,,烤箱,
|
||||
烤箱版烤西葫芦,西葫芦,BV1Nv411Y7kV,简单,,,烤箱,
|
||||
烤箱版烤洋葱,洋葱,BV1Nv411Y7kV,简单,,,烤箱,
|
||||
烤箱版麻薯,鸡蛋,BV1CR4y1t7CM,普通,,,烤箱,
|
||||
烤箱版气泡土豆,土豆,BV1mR4y1E7VH,普通,,烤,烤箱,
|
||||
烤箱版烧烤,鸡肉、番茄、土豆,BV1Ra4y1x7Ua,简单,,烤,烤箱,
|
||||
烤箱版薯条,土豆,BV1nA411P7aX,普通,,,烤箱,
|
||||
烤箱版吮指鸡翅,鸡肉、土豆,BV12W411A7w8,普通,,烤,烤箱,
|
||||
烤箱版蒜蓉烤茄子,茄子,BV1Ff4y1P7ng,简单,,,烤箱,
|
||||
烤箱鸡翅根,鸡肉,BV1et411R7kG,普通,,烤,烤箱,
|
||||
烤箱鸡蛋,鸡蛋,BV1Vp4y167P4,简单,,烤,烤箱,
|
||||
烤箱腊肠焖饭,腊肠、花菜、米,BV14x411D7uN,,,,烤箱,
|
||||
烤箱蜜汁鸡腿,鸡肉,BV1WW411S72C,普通,,烤,烤箱,
|
||||
烤箱面包,面包,BV1wJ411E7YS,简单,,烤,烤箱,
|
||||
烤箱牛肉,牛肉,BV1L64y1r7bk,复杂,,烤,烤箱,
|
||||
烤箱茄子,茄子,BV165411N7fQ,简单,,烤,烤箱,
|
||||
烤箱炸鸡米花,鸡肉,BV1HE411L7T2,普通,,烤,烤箱,
|
||||
炼猪油,猪肉,BV12F411v74c,简单,,烤,烤箱,
|
||||
流心培根吐司,面包、鸡蛋、猪肉,BV1aZ4y1R7SL,普通,,,烤箱,
|
||||
冒油牛排,牛肉,土豆,BV1754y1E7Mj,普通,,烤,烤箱,
|
||||
年轮蛋糕(难度max),面包、鸡蛋,BV1WQ4y1q7iz,困难,,,烤箱,
|
||||
牛奶烤土司,鸡蛋,面包,BV1KF411Y7WP,普通,,烤,烤箱,
|
||||
千层土豆烘蛋,土豆、鸡蛋,BV1fY41187Xo,困难,,烘,烤箱,
|
||||
蔬菜团子,包菜,BV1S44y1P7Qp,简单,,烤,烤箱,
|
||||
蒜香芝士面包虾,面包、虾,BV1gB4y1c7GY,困难,,,烤箱,
|
||||
土豆面包,土豆、面包、鸡蛋,BV1H4411572T,普通,,,烤箱,
|
||||
土豆挞,土豆,BV1fY41187Xo,普通,,烘,烤箱,
|
||||
土豆芝士棒,土豆、香肠,BV1DL4y1j7Nu,困难,,烘,烤箱,
|
||||
早餐白面包(无鸡蛋版),面包,BV1Xg4y187BQ,普通,,,烤箱,
|
||||
猪肉脯,猪肉,BV174411Z7ar,普通,,烤,烤箱,
|
||||
欧洲家庭版欧包,鸡蛋、面包,BV1SJ411L7dG,普通,,,烤箱、电饭煲,
|
||||
土豆杂蔬烘蛋,土豆、番茄、胡萝卜、香肠,BV1Y5411S7mu,简单,早餐,烘,烤箱、空气炸锅,
|
||||
午餐肉串,午餐肉,BV1MF411t7Ba,简单,零食,炸,空气炸锅,
|
||||
午餐肉薯条,午餐肉、鸡蛋、面包,BV1ti4y1f7nE,简单,零食,炸,空气炸锅,
|
||||
空气炸锅蒜蓉茄子,茄子,BV1aQ4y1D77r,简单,深夜美食,炸,空气炸锅,
|
||||
空气炸锅版M记同款薯条,土豆,BV1tq4y1j72x,,同款,,空气炸锅,
|
||||
空气炸锅版M记同款薯饼,土豆,BV1Cb4y167zK,,同款,,空气炸锅,
|
||||
空气炸锅版KFC同款奥尔良烤翅,鸡肉,BV1EQ4y1a7C9,,同款,,空气炸锅,
|
||||
空气炸锅炸包菜,包菜,BV1eR4y1j715,简单,童年风味,炸,空气炸锅,
|
||||
空气炸锅版可乐鸡翅,鸡肉,BV1eT4y1S7Jn,,,,空气炸锅,
|
||||
空气炸锅烤时蔬,菌菇、花菜、胡萝卜、西葫芦、包菜、土豆、芹菜、莴笋,BV1a5411u7TH,简单,杂烩,炸,空气炸锅,
|
||||
空气炸锅炸万物,土豆、胡萝卜、花菜、西葫芦、芹菜、洋葱、莴笋、菌菇、茄子、包菜、午餐肉、香肠、鸡肉、猪肉、虾、牛肉、鸡肉、番茄、豆腐、面包,BV1wL411F7Cd,简单,杂烩,炸,空气炸锅,
|
||||
空气炸锅炸包菜串,包菜,BV1DL4y137CD,简单,,炸,空气炸锅,
|
||||
空气炸锅炸馒头,面食,BV1DL4y137CD,简单,,炸,空气炸锅,
|
||||
空气炸锅炸香肠串,香肠,BV1DL4y137CD,简单,,炸,空气炸锅,
|
||||
虾肉茄子卷,虾、茄子、胡萝卜、鸡蛋,BV1aB4y1U7zQ,普通,,炸,空气炸锅,
|
||||
家庭版麦乐鸡块,鸡肉、鸡蛋、土豆、洋葱,BV1uu411v7Qf,普通,,炸,空气炸锅,
|
||||
焦糖吐司布丁,鸡蛋、面包、豆腐,BV1sq4y197TK,,,,空气炸锅,
|
||||
空气炸锅奥尔良炸鸡腿,鸡肉,BV1JU4y1G7hq,简单,,炸,空气炸锅,
|
||||
空气炸锅爆浆巴斯克芝士蛋糕,鸡蛋,BV1Ja411v7DK,,,,空气炸锅,
|
||||
空气炸锅炒饭,米、胡萝卜、香肠、鸡蛋,BV1oL4y1T7BX,简单,,炸,空气炸锅,
|
||||
空气炸锅炒面,方便面,BV11P4y1u7R1,简单,,炸,空气炸锅,
|
||||
空气炸锅炒牛河,面食、牛肉,BV1k44y1P7VY,简单,,炸,空气炸锅,
|
||||
空气炸锅脆皮烤肉,猪肉,BV1qU4y1f7G1,简单,,炸,空气炸锅,
|
||||
空气炸锅蛋黄酥,虾、土豆,BV1yS4y1u7M6,简单,,炸,空气炸锅,
|
||||
空气炸锅地三鲜,土豆、茄子,BV1pf4y1T7bx,简单,,炸,空气炸锅,
|
||||
空气炸锅法式吐司,面包、鸡蛋,BV1Fh41187GB,,,,空气炸锅,
|
||||
空气炸锅蜂蜜小面包,鸡蛋,BV1Ja411v7DK,,,,空气炸锅,
|
||||
空气炸锅干锅虾烤肉串,虾、猪肉,BV1Ka41117mc,,,,空气炸锅,
|
||||
空气炸锅虾球,土豆、虾,BV1L3411573m,简单,,炸,空气炸锅,
|
||||
空气炸锅虎皮凤爪,鸡肉,BV1VR4y1s7XP,简单,,炸,空气炸锅,
|
||||
空气炸锅鸡蛋豆腐羹,鸡蛋、豆腐、猪肉,BV1Hq4y137Vu,,,,空气炸锅,
|
||||
空气炸锅减脂餐,菌菇、虾,BV1CY4y1p7qP,简单,,炸,空气炸锅,
|
||||
空气炸锅减脂餐,包菜、菌菇、鸡肉,BV1w44y1V7S5,简单,,炸,空气炸锅,
|
||||
空气炸锅烤豆腐,豆腐,BV19L411x7bB,简单,,炸,空气炸锅,
|
||||
空气炸锅烤鸡腿,鸡肉,BV1Zr4y1B7UQ,普通,,炸,空气炸锅,
|
||||
空气炸锅烤鸡胸肉,鸡肉,BV1dF411q7of,简单,,炸,空气炸锅,
|
||||
空气炸锅烤面包,面包,BV1T34y1b7FS,简单,,炸,空气炸锅,
|
||||
空气炸锅烤蘑菇,菌菇,BV1Jq4y147oM,简单,,炸,空气炸锅,
|
||||
空气炸锅烤牛奶,鸡蛋,BV1ub4y1C7bp,简单,,炸,空气炸锅,
|
||||
空气炸锅烤排骨,猪肉,BV1H3411J7TH,简单,,炸,空气炸锅,
|
||||
空气炸锅烤茄子,茄子,BV1gP4y1s7GJ,简单,,炸,空气炸锅,
|
||||
空气炸锅烤肉串,猪肉,BV1WY411G7aX,简单,,炸,空气炸锅,
|
||||
空气炸锅烤土豆,土豆,BV1E5411D7RE,简单,,炸,空气炸锅,
|
||||
空气炸锅烤五花肉,猪肉,BV1Jq4y147oM,简单,,炸,空气炸锅,
|
||||
空气炸锅烤猪蹄,猪肉,BV11q4y1Y7hW,简单,,炸,空气炸锅,
|
||||
空气炸锅辣子鸡,鸡肉,BV1yL4y1L71E,简单,,炸,空气炸锅,
|
||||
空气炸锅嫩鸡排,鸡肉,BV1L3411W7Nr,,,,空气炸锅,
|
||||
空气炸锅气泡面包,面包,BV1Lu411z7FK,简单,,炸,空气炸锅,
|
||||
空气炸锅全麦面包,面包,BV1VY411E74H,简单,,炸,空气炸锅,
|
||||
空气炸锅肉末豆腐,豆腐,BV1Ja411C75G,简单,,炸,空气炸锅,
|
||||
空气炸锅酸奶蛋糕,鸡蛋,BV1HL4y177B3,简单,,炸,空气炸锅,
|
||||
空气炸锅蒜香排骨,猪肉,BV1v54y1B7cA,简单,,炸,空气炸锅,
|
||||
空气炸锅糖醋里脊,猪肉,BV1rS4y137gC,简单,,炸,空气炸锅,
|
||||
空气炸锅甜点(没酵母别选),鸡蛋,BV1Ja411v7DK,,,,空气炸锅,
|
||||
空气炸锅新年硬菜,虾、猪肉,BV1Mq4y1h78M,简单,,炸,空气炸锅,
|
||||
空气炸锅早餐饼,鸡蛋、西葫芦、胡萝卜,BV1mu411q715,简单,,炸,空气炸锅,
|
||||
空气炸锅炸包菜,包菜,BV1NB4y1N7SH,简单,,炸,空气炸锅,
|
||||
空气炸锅炸低卡食材,包菜、菌菇,BV1cF411s7QH,简单,,炸,空气炸锅,
|
||||
空气炸锅炸豆腐,豆腐,BV1eR4y1j715,简单,,炸,空气炸锅,
|
||||
空气炸锅炸个荤素搭配,土豆、香肠、鸡肉、虾、鸡蛋、花菜,BV18q4y117Cm,简单,,炸,空气炸锅,
|
||||
空气炸锅炸鸡,鸡肉,BV1S44y1j7YM,简单,,炸,空气炸锅,
|
||||
空气炸锅炸鸡翅,鸡肉,BV14b4y1p7XS,简单,,炸,空气炸锅,
|
||||
空气炸锅炸家常蔬菜,花菜、豆腐、鸡肉、菌菇,BV1FU4y1Z7T1,简单,,炸,空气炸锅,
|
||||
空气炸锅炸金针菇,菌菇,BV1x4411V7at,简单,,炸,空气炸锅,
|
||||
空气炸锅炸茄盒,茄子、猪肉,BV1xP4y1J7eJ,简单,,炸,空气炸锅,
|
||||
空气炸锅炸蔬菜,鸡蛋、黄瓜,BV1qr4y1p7BX,普通,,炸,空气炸锅,
|
||||
空气炸锅炸薯角,土豆,BV1eR4y1j715,简单,,炸,空气炸锅,
|
||||
空气炸锅炸薯条,土豆,BV1tq4y1j72x,简单,,炸,空气炸锅,
|
||||
空气炸锅炸素菜,豆腐、洋葱、茄子、菌菇、鸡蛋、米,BV1Ap4y1b7a6,简单,,炸,空气炸锅,
|
||||
空气炸锅炸酸奶,鸡蛋,BV1xL411A7xG,简单,,炸,空气炸锅,
|
||||
空气炸锅炸土豆豆腐,豆腐、虾、土豆、鸡蛋,BV15Z4y1D73J,简单,,炸,空气炸锅,
|
||||
空气炸锅炸吐司,面包、鸡蛋,BV1aQ4y1D77r,简单,,炸,空气炸锅,
|
||||
空气炸锅炸五花肉花菜,猪肉、花菜,BV1Jq4y147oM,简单,,炸,空气炸锅,
|
||||
空气炸锅炸鲜奶,鸡蛋,BV1AZ4y1R7T2,简单,,炸,空气炸锅,
|
||||
空气炸锅炸香肠鸡翅,鸡蛋、香肠、鸡肉,BV15r4y1W7vA,简单,,炸,空气炸锅,
|
||||
空气炸锅炸小吃一条街,包菜、香肠、鸡蛋,BV1DL4y137CD,简单,,炸,空气炸锅,
|
||||
空气炸锅做蛋挞,鸡蛋,BV1A44y1P7JY,简单,,炸,空气炸锅,
|
||||
蜜汁脆皮烤鸡,鸡肉,av895289430,简单,,炸,空气炸锅,
|
||||
炸锅版吮指原味鸡,鸡肉,BV155411S7Q1,简单,,炸,空气炸锅,
|
||||
微波炉版番茄焖饭,香肠、番茄,BV193411W7nb,普通,减脂餐,微波加热,微波炉,
|
||||
微波炉版菌菇鸡肉焖饭,鸡肉、胡萝卜、菌菇、米,BV193411W7nb,普通,减脂餐,微波加热,微波炉,
|
||||
微波炉版盐烤土豆,土豆,BV1Cf4y1R7pe,,零食,微波加热,微波炉,
|
||||
微波炉版荷包蛋方便面,鸡蛋、方便面、香肠,BV18v411i7JM,简单,速食,微波加热,微波炉,
|
||||
微波炉版KFC同款土豆泥,土豆,BV1Ji4y1773B,简单,同款,微波加热,微波炉,
|
||||
微波炉版乐事同款薯片,土豆,BV1Ms411D7Qg,,同款,,微波炉,
|
||||
微波炉版FKC同款嫩牛五方,牛肉、番茄、面食,BV1Ji4y1773B,,同款,,微波炉,
|
||||
微波炉版可乐鸡翅,鸡肉、洋葱,BV1jJ41127ML,,,,微波炉,
|
||||
微波炉版蔬菜脆片,土豆、西葫芦、黄瓜,BV1n441187r3,,杂烩,微波加热,微波炉,
|
||||
微波炉火锅,菌菇、牛肉、方便面、虾、鸡肉、猪肉、午餐肉、莴笋、豆腐、土豆,BV1o34y1R7BC,,杂烩,微波加热,微波炉,
|
||||
微波炉版鸡蛋羹,鸡蛋,BV1HL411E7MM,简单,早饭,微波加热,微波炉,
|
||||
微波炉版土豆浓汤,土豆、胡萝卜,BV1HL411E7MM,简单,早饭,微波加热,微波炉,
|
||||
微波炉版煲仔饭,米、香肠、鸡蛋,BV145411f7R4,简单,主食,微波加热,微波炉,
|
||||
微波炉版燕麦粥,米,BV1cF411Y7pd,,主食,微波加热,微波炉,
|
||||
微波炉版叉烧肉,猪肉、米,BV1Hi4y1s7Sc,,,微波加热,微波炉,
|
||||
微波炉版蛋炒饭,米、鸡蛋、香肠,BV1T34y147qU,简单,,微波加热,微波炉,
|
||||
微波炉版番茄鸡蛋汤,番茄、鸡蛋,BV1qx411n7QF,,,,微波炉,
|
||||
微波炉版番茄肉盒,番茄、猪肉、青椒,BV194411R7aH,,,微波加热,微波炉,
|
||||
微波炉版肥牛饭,牛肉、洋葱、鸡蛋、米,BV1gh411Y7TU,,,,微波炉,
|
||||
微波炉版干脆面,面食,BV1pL4y1j7oe,,,微波加热,微波炉,
|
||||
微波炉版虾吃豆腐 ,虾、豆腐,BV1VW41157uy,,,,微波炉,
|
||||
微波炉版红烧茄子,茄子、洋葱、米,BV17Q4y1y7NC,普通,,微波加热,微波炉,
|
||||
微波炉版厚蛋烧,鸡蛋、包菜,BV1bT4y1o7dv,普通,,微波加热,微波炉,
|
||||
微波炉版厚蛋烧(没芝士就别选了),鸡蛋、包菜、香肠,BV1bT4y1o7dv,,,,微波炉,
|
||||
微波炉版胡萝卜蒸菜,胡萝卜、面食,BV1A94y1o7aW,简单,,微波加热,微波炉,
|
||||
微波炉版鸡胸肉脆片,鸡肉,BV1xu41197tU,,,,微波炉,
|
||||
微波炉版鸡胸肉丸,鸡肉,BV1xu41197tU,,,,微波炉,
|
||||
微波炉版烤鸡翅,鸡肉,BV1bT4y1o7dv,,,,微波炉,
|
||||
微波炉版辣烤豆腐,豆腐、虾,BV17Q4y1y7NC,普通,,微波加热,微波炉,
|
||||
微波炉版麻辣排骨,猪肉,BV1c44y1T79A,简单,,微波加热,微波炉,
|
||||
微波炉版蘑菇,菌菇,BV1Nq4y1i72u,简单,,微波加热,微波炉,
|
||||
微波炉版嫩豆腐,豆腐,BV1DZ4y1o7ZM,简单,,微波加热,微波炉,
|
||||
微波炉版牛奶炖饭,米,BV1W7411c7As,简单,,微波加热,微波炉,
|
||||
微波炉版日式嫩蒸鸡胸肉,鸡肉,BV1xu41197tU,,,,微波炉,
|
||||
微波炉版烧烤茄子,茄子,BV19x411H7ig,,,,微波炉,
|
||||
微波炉版蒜香烤茄子,茄子,BV19x411H7ig,简单,,微波加热,微波炉,
|
||||
微波炉版蒜香琵琶腿,鸡肉,BV1qx411777p,,,,微波炉,
|
||||
微波炉版吐司杯,鸡蛋、面包,BV1CS4y1k7Xi,,,微波加热,微波炉,
|
||||
微波炉版五花肉,猪肉、米,BV1hL4y137Zk,,,微波加热,微波炉,
|
||||
微波炉版香干,豆腐,BV1F44y1T7sQ,,,微波加热,微波炉,
|
||||
微波炉版香辣豆腐,豆腐、米,BV1bT4y1o7dv,,,,微波炉,
|
||||
微波炉版香嫩鸡胸肉,鸡肉,BV1xu41197tU,,,,微波炉,
|
||||
微波炉版一个番茄焖饭,番茄、米、胡萝卜、香肠、腊肠、土豆,BV193411W7nb,,,,微波炉,
|
||||
微波炉版鱼香肉块,胡萝卜、鸡肉,BV1qt411X7Do,,,,微波炉,
|
||||
微波炉版照烧鸡腿饭,米、鸡肉、胡萝卜、花菜,BV16Z4y1V774,,,,微波炉,
|
||||
微波炉版蒸蛋羹,鸡蛋,BV19T4y1D7Zd,,,,微波炉,
|
||||
微波炉版煮米饭,米,BV193411W7nb,,,,微波炉,
|
||||
水煮肉片,猪肉、芹菜、莴笋,BV1ZZ4y1379N,普通,川菜,炒,一口大锅,
|
||||
脆口黄瓜,黄瓜,BV1Tb4y1X7ow,简单,脆口,凉拌,一口大锅,
|
||||
白萝卜汤,白萝卜,BV1HJ411L7xA,简单,单一食材,煮,一口大锅,
|
||||
白灼大虾,虾,BV1Sr4y1m7zF,普通,单一食材,灼,一口大锅,
|
||||
炒包菜,包菜,BV1K5411L7Xn,简单,单一食材,炒,一口大锅,
|
||||
番茄酱,番茄、面食、米,BV1h4411c7TV,简单,单一食材,煮,一口大锅,
|
||||
黑椒土豆泥,土豆,BV1q7411K7gr,简单,单一食材,蒸,一口大锅,
|
||||
红烧茄子,茄子,BV1ot4y1S7Hf,普通,单一食材,烧,一口大锅,
|
||||
酱筒骨,骨头,BV1Ai4y187KE,普通,单一食材,炖,一口大锅,
|
||||
酱土豆,土豆,BV1YD4y1c7ip,简单,单一食材,炒,一口大锅,
|
||||
酱油炖白萝卜,白萝卜,BV1us411h7E8,简单,单一食材,炖,一口大锅,
|
||||
椒盐炸蘑菇,菌菇、鸡蛋,BV1P54y1L7FS,简单,单一食材,炸,一口大锅,
|
||||
浇汁西兰花,花菜,BV1jq4y1M7jR,简单,单一食材,煮,一口大锅,
|
||||
金汤虾片,虾,BV1mQ4y1e7T8,困难,单一食材,煮,一口大锅,
|
||||
老奶洋芋,土豆,BV1fY41187Xo,简单,单一食材,炒,一口大锅,
|
||||
麻辣土豆片,土豆,BV1fY41187Xo,简单,单一食材,煮,一口大锅,
|
||||
茄子焖饭,茄子,BV1k7411m7nv,简单,单一食材,烧,一口大锅,
|
||||
清炖肉骨头,骨头,BV1SQ4y1m73v,普通,单一食材,炖,一口大锅,
|
||||
薯球,土豆,BV1bY4y1H7L9,普通,单一食材,炸,一口大锅,
|
||||
蒜蓉西兰花,花菜,BV1fy4y1W7z5,简单,单一食材,炒,一口大锅,
|
||||
土豆派,土豆,BV1bY4y1H7L9,简单,单一食材,烘,一口大锅,
|
||||
炸厚土豆片,土豆,BV1mt4y1i7Zr,简单,单一食材,煎,一口大锅,
|
||||
蒸腊肠,腊肠、米,BV1YK4y1e7bM,简单,单一食材,蒸,一口大锅,
|
||||
蒜香牛奶焗胡萝卜,胡萝卜、洋葱、鸡蛋,BV1yF411u7Me,普通,法餐,焗,一口大锅,
|
||||
胡萝卜炖牛肉,胡萝卜、牛肉、洋葱,BV1UR4y1V7nV,困难,法式,炖,一口大锅,
|
||||
葱油黄瓜,黄瓜,BV1954y1b7dv,简单,国宴菜,,一口大锅,
|
||||
黄瓜翡翠羹,黄瓜、鸡肉,BV1y3411W7Mz,困难,国宴菜,煮,一口大锅,
|
||||
烧汁牛肉粒,牛肉、洋葱、菌菇,BV1NU4y1d7ot,普通,国宴菜,炒,一口大锅,
|
||||
火烧云油焖鸡,土豆、番茄、鸡肉,BV1fY41187Xo,普通,好吃,煮,一口大锅,
|
||||
蒜香口蘑牛肉粒,菌菇、牛肉,BV1Nm4y197ev,简单,荤素搭配,炒,一口大锅,
|
||||
6阶番茄炒蛋,番茄、鸡蛋,BV1Py4y1S7EF,困难,家常菜,炒,一口大锅,
|
||||
白萝卜烧鸡块,白萝卜、鸡肉,BV1U3411e7Jv,普通,家常菜,烧,一口大锅,
|
||||
番茄炒蛋,番茄、鸡蛋,BV1rf4y1872R,普通,家常菜,炒,一口大锅,
|
||||
番茄鸡蛋,番茄、鸡蛋、米,BV11y4y167Wa,简单,家常菜,炒,一口大锅,
|
||||
番茄土豆炖牛腩,番茄、土豆、牛肉,BV1344y1n73V,普通,家常菜,炖,一口大锅,
|
||||
回锅肉炒包菜,包菜、猪肉,BV1EK411s75a,普通,家常菜,炒,一口大锅,
|
||||
腊肠蒸蛋,腊肠、鸡蛋,BV1Zp4y1Q7wu,简单,家常菜,蒸,一口大锅,
|
||||
拍辣椒炒午餐肉,午餐肉,BV1K4411Z7do,简单,家常菜,炒,一口大锅,
|
||||
青椒炒腊肠,腊肠、米,BV1ZK4y1L7pp,普通,家常菜,炒,一口大锅,
|
||||
热炒莴笋叶,莴笋,BV1xJ411M7bc,简单,家常菜,炒,一口大锅,
|
||||
素蟹粉,胡萝卜、土豆,BV1Zu411e7fp,简单,家常菜,炒,一口大锅,
|
||||
酸辣土豆丝,土豆,BV1wq4y1W7QA,普通,家常菜,炒,一口大锅,
|
||||
蒜蓉开背虾,虾,BV1DR4y1p7j4,普通,家常菜,炒,一口大锅,
|
||||
土豆烧包菜,包菜、土豆,BV1Ug4y1B7Uj,普通,家常菜,烧,一口大锅,
|
||||
土豆烧红肉,土豆、猪肉,BV1fY41187Xo,普通,家常菜,炖,一口大锅,
|
||||
莴笋炒蛋,莴笋、鸡蛋、胡萝卜,BV1C7411J7cb,普通,家常菜,炒,一口大锅,
|
||||
油焖大虾,虾、米,BV1DP4y1s7zu,普通,家常菜,焖,一口大锅,
|
||||
陈氏醋卤面,面食、猪肉,BV1tQ4y1M76f,普通,家传秘方,拌,一口大锅,
|
||||
白灼西兰花,花菜,BV1o4411A7dC,简单,健康,煮,一口大锅,
|
||||
芹菜牛肉卷,芹菜、牛肉,BV1Tv411L7V9,,健康餐,,一口大锅,
|
||||
减脂胡萝卜丝,胡萝卜,BV1UQ4y1i7JY,普通,减脂,蒸,一口大锅,
|
||||
醋溜西葫芦,西葫芦,BV1Rv411G7fD,普通,开胃,炒,一口大锅,
|
||||
餐蛋面,午餐肉、鸡蛋、方便面,BV1pT4y1F7p5,简单,快手菜,煮,一口大锅,
|
||||
鸡蛋煎午餐肉,午餐肉、鸡蛋,BV1aK4y177Xm,简单,快手菜,煎,一口大锅,
|
||||
金针菇菜卷,菌菇、胡萝卜、包菜,BV1aW411H7fZ,简单,快手菜,,一口大锅,
|
||||
口蘑鸡胸肉,菌菇、鸡肉,BV1AP4y1w7y6,简单,快手菜,煎,一口大锅,
|
||||
茄汁西葫芦,西葫芦、番茄,BV17W411g71Q,普通,快手菜,炒,一口大锅,
|
||||
番茄浓汤面,面食、番茄、鸡蛋,BV1P54y1H7oG,简单,懒人,煮,一口大锅,
|
||||
茄汁汤面,面食、番茄、鸡蛋,BV1Z7411G7AW,简单,懒人,煮,一口大锅,
|
||||
清汤面,面食、鸡蛋,BV1Yb411T7ED,简单,懒人,煮,一口大锅,
|
||||
鸡蛋拌包菜,包菜、鸡蛋,BV1Q64y1B7eq,简单,凉菜,拌,一口大锅,
|
||||
酱萝卜,白萝卜,BV1BK4y1Z7rF,简单,凉菜,腌,一口大锅,
|
||||
凉拌包菜,包菜,BV1HY411n7dV,简单,凉菜,拌,一口大锅,
|
||||
凉拌芹菜叶,芹菜,av976520411,简单,凉菜,拌,一口大锅,
|
||||
凉拌莴笋丝,莴笋,BV1Es411j7oy,简单,凉菜,凉拌,一口大锅,
|
||||
凉拌西葫芦,西葫芦,BV175411U7LU,简单,凉菜,凉拌,一口大锅,
|
||||
麻酱拌西葫芦丝,西葫芦,BV1Th411U7nW,简单,凉菜,凉拌,一口大锅,
|
||||
炝拌芹菜,芹菜,av845641799,简单,凉菜,拌,一口大锅,
|
||||
土豆拌茄子,土豆、茄子,BV1VT4y1J7Y8,简单,凉菜,蒸,一口大锅,
|
||||
香菜拌牛肉,牛肉,BV1144y1n7ZV,简单,凉菜,凉拌,一口大锅,
|
||||
小菜炝拌萝卜条,白萝卜,BV1nS4y1X7tM,简单,凉菜,拌,一口大锅,
|
||||
海苔虾饼,虾、胡萝卜,BV16T4y1i78D,困难,零食,炸/煎,一口大锅,
|
||||
灵魂土豆丸子,土豆,BV1Z64y1674r,,零食,蒸,一口大锅,
|
||||
薯塔,土豆,BV1uF411v7SQ,普通,零食,炸,一口大锅,
|
||||
盖码莴笋,莴笋、猪肉,BV1aV411v7xw,简单,清爽,盖浇,一口大锅,
|
||||
大根烧,白萝卜,BV1TW411e7Ln,普通,日式,烧,一口大锅,
|
||||
名古屋鸡翅,鸡肉,BV1ET4y1A7Xd,普通,日式,炸,一口大锅,
|
||||
日式炖白萝,白萝卜,BV17b411B7H1,简单,日式,炖,一口大锅,
|
||||
炸虾天妇罗,虾,BV1e5411t7LY,困难,日式,炸,一口大锅,
|
||||
可乐饼,土豆、洋葱、肉、鸡蛋,BV17x411U75q,普通,日式菜,炸,一口大锅,
|
||||
清炒莴笋丝,莴笋、胡萝卜,BV1qK411H7RL,简单,爽口,炒,一口大锅,
|
||||
莴笋泡菜,莴笋,BV1h741127rS,简单,爽口,泡菜,一口大锅,
|
||||
口蘑汤,菌菇,BV1e64y1h776,简单,汤,煎、炖,一口大锅,
|
||||
蘑菇浓汤,菌菇、洋葱、芹菜,BV1Ut411C7Cu,普通,汤,炖,一口大锅,
|
||||
KFC同款香辣鸡翅,鸡肉,BV1A7411u7Ji,普通,同款,炸,一口大锅,
|
||||
M记同款薯条,土豆,BV1Jx411x7iU,,同款,,一口大锅,
|
||||
乐事同款原味薯片,土豆、黄瓜,BV1vR4y1N7fN,简单,同款,,一口大锅,
|
||||
M记同款麦乐鸡,鸡肉、鸡蛋,BV1KA411e7Gr,,同款,,一口大锅,
|
||||
洋芋擦擦,土豆、胡萝卜,BV1fY41187Xo,普通,西北特色,蒸,一口大锅,
|
||||
白萝卜炒肉片,白萝卜、猪肉,BV1qL4y1i7xD,普通,下饭,炒,一口大锅,
|
||||
爆汁茄子,茄子、米,BV1rL4y1L751,简单,下饭,烧,一口大锅,
|
||||
番茄肉酱,番茄、芹菜、洋葱、猪肉、米、面食,BV1hF411t7hS,普通,下饭,烧,一口大锅,
|
||||
番茄土豆炖牛腩,牛肉、土豆、番茄、洋葱,BV1344y1n73V,普通,下饭,煮,一口大锅,
|
||||
干锅土豆片,土豆、猪肉、米,BV1ZZ4y1A7HK,简单,下饭,炒,一口大锅,
|
||||
干锅土豆五花肉,猪肉、土豆、洋葱,BV1CV411W7AD,简单,下饭,炒,一口大锅,
|
||||
红烧肉,猪肉,BV1vk4y127ZB,简单,下饭,煮,一口大锅,
|
||||
胡萝卜炒里脊肉,胡萝卜、猪肉、米,BV1Ff4y1x7FX,普通,下饭,炒,一口大锅,
|
||||
胡萝卜炒肉,猪肉、胡萝卜,BV1Ff4y1x7FX,简单,下饭,炒,一口大锅,
|
||||
胡萝卜炒五花肉,胡萝卜、猪肉、米,BV1jW411Z7yd,普通,下饭,炒,一口大锅,
|
||||
红烧肉,猪肉、鸡肉,BV1MJ411t7pT,,,,一口大锅,
|
||||
可乐鸡翅,鸡肉,BV1pJ411v7S9,,,,一口大锅,
|
||||
可乐鸡翅包虾滑,虾、鸡肉、米,BV1Pb4y1p7mi,困难,下饭,煎,一口大锅,
|
||||
腊肠烧茄子,腊肠、茄子、米,BV1bW41127MW,普通,下饭,烧,一口大锅,
|
||||
腊肠烧土豆,腊肠、土豆、米,BV1TL411V7Cp,普通,下饭,烧,一口大锅,
|
||||
萝卜泡菜,白萝卜,BV1VE411y7dr,普通,下饭,腌,一口大锅,
|
||||
麻辣凉拌包菜,包菜,BV18f4y1e7cy,简单,下饭,拌,一口大锅,
|
||||
炝炒西葫芦,西葫芦、番茄,BV1QU4y1f7sY,普通,下饭,炒,一口大锅,
|
||||
芹菜炒肉,猪肉、芹菜,BV1v7411T7is,简单,下饭,炒,一口大锅,
|
||||
芹菜肥牛,芹菜、牛肉,BV1ML4y1G7ba,,下饭,,一口大锅,
|
||||
芹菜粉,芹菜、面食,BV1Eh411Y7Y3,,下饭,,一口大锅,
|
||||
芹菜鸡翅焖锅,芹菜、鸡肉,av761877317,普通,下饭,烧,一口大锅,
|
||||
芹菜木耳炒蛋,芹菜、鸡蛋,BV1Dt411o7hq,,下饭,,一口大锅,
|
||||
芹菜油豆腐,芹菜、豆腐,BV15J411G7Dx,,下饭,,一口大锅,
|
||||
肉末土豆,土豆、肉、米,BV1ZS4y127wm,,下饭,炒,一口大锅,
|
||||
莴笋炒肉,莴笋、猪肉,BV1d3411H7G5,普通,下饭,炒,一口大锅,
|
||||
西葫芦炒蛋,西葫芦、鸡蛋、番茄,BV1wT4y1Q7Db,简单,下饭,炒,一口大锅,
|
||||
西葫芦炒五花肉,西葫芦、猪肉,BV1TL411T7eX,普通,下饭,炒,一口大锅,
|
||||
香辣土豆片,土豆、米,BV1Sq4y137uw,,下饭,煮,一口大锅,
|
||||
小炒拆骨肉,骨头、米,BV14p411o7zm,困难,下饭,炒,一口大锅,
|
||||
孜然土豆火腿,土豆、香肠、米,BV1k44y147kM,简单,下饭,炸,一口大锅,
|
||||
芹菜猪肉馅料,芹菜、猪肉,BV18b411r7CP,,馅料,,一口大锅,
|
||||
包菜厚蛋烧,包菜、鸡蛋,BV11g411A7QE,普通,小吃,烧,一口大锅,
|
||||
风味茄子,茄子,BV1tZ4y1U7iN,普通,小吃,炸,一口大锅,
|
||||
油炸白萝卜,白萝卜,BV1J54y1x7ie,简单,小吃,炸,一口大锅,
|
||||
炸包菜串,包菜,BV1Ts411F7q3,简单,小吃,炸,一口大锅,
|
||||
日式炒胡萝卜丝,胡萝卜,BV1mY411774Z,简单,消耗囤货,炒,一口大锅,
|
||||
番茄肉酱面,番茄、猪肉、面食,BV1Cm4y1Q7RS,简单,意式,烧,一口大锅,
|
||||
胡萝卜炒蛋,胡萝卜、鸡蛋,BV11E411Q7WB,简单,营养,炒,一口大锅,
|
||||
番茄酸汤火锅,番茄、胡萝卜、菌菇、洋葱、豆腐、面食、白萝卜、土豆、白菜、猪肉、牛肉、午餐肉,BV1yz4y1C7Qu,困难,杂烩,,一口大锅,
|
||||
骨头汤火锅锅底做法(全鸡版),骨头、土豆、胡萝卜、花菜、白萝卜、西葫芦、芹菜、菌菇、豆腐、包菜、白菜、午餐肉、鸡肉、猪肉、虾、牛肉、面食、方便面,BV1bi4y187ro,困难,杂烩,,一口大锅,
|
||||
乱炖鸡胸,鸡肉、洋葱、菌菇、胡萝卜、土豆,BV1Qb411u7Vu,普通,杂烩,,一口大锅,
|
||||
清汤锅万能高汤做法(鸡蛋+猪肉),猪肉、鸡蛋、土豆、胡萝卜、花菜、白萝卜、西葫芦、芹菜、菌菇、豆腐、包菜、白菜、午餐肉、鸡肉、虾、牛肉、面食、方便面,BV1zD4y197Us,困难,杂烩,,一口大锅,
|
||||
无米炒饭,花菜、黄瓜、胡萝卜、鸡蛋、豆腐、虾,BV1Cf4y1V7sv,,杂烩,,一口大锅,
|
||||
胡萝卜蛋饼,胡萝卜、鸡蛋,BV1Nh411Z7hZ,简单,早餐,煎,一口大锅,
|
||||
土豆丝饼,土豆,BV1fY41187Xo,简单,早餐,煎,一口大锅,
|
||||
午餐肉蛋饼,午餐肉、鸡蛋、面包,BV1d34y1X7A5,简单,早餐,煎,一口大锅,
|
||||
午餐肉饭团,午餐肉、米,BV1v7411t7uS,简单,早餐,煎,一口大锅,
|
||||
午餐肉米汉堡,午餐肉、米、鸡蛋、包菜,BV1n5411U78R,简单,早餐,煎,一口大锅,
|
||||
萝卜砂锅粥,白萝卜、米,BV1mz4y1C7H9,普通,早饭,煮,一口大锅,
|
||||
麻辣豆腐包,面食、豆腐,BV1y64y1U7jY,普通,早饭,蒸,一口大锅,
|
||||
棉花馒头,面食,BV1SK4y1r7dr,普通,早饭,蒸,一口大锅,
|
||||
奶黄包,面食、鸡蛋,BV1et4y127jQ,普通,早饭,蒸,一口大锅,
|
||||
蒸花卷,面食,BV1w64y1X7cT,困难,早饭,蒸,一口大锅,
|
||||
茄子焖面,茄子、番茄、面食,BV1BA411H771,简单,主食,烧,一口大锅,
|
||||
芹菜肉饺子,芹菜、猪肉、面食,av546464255,普通,主食,蒸,一口大锅,
|
||||
芹菜蒸面卷,芹菜、胡萝卜、面食,BV1JZ4y1s7Sh,,主食,,一口大锅,
|
||||
蒸芹菜叶,芹菜、面食,BV1Rg411c72w,,主食,,一口大锅,
|
||||
白菜炒土豆片,白菜、土豆,BV17y4y1e76x,普通,,,一口大锅,
|
||||
白菜炒香菇,白菜、菌菇,BV1MS4y117Kp,普通,,,一口大锅,
|
||||
白菜荷包蛋,白菜、鸡蛋、猪肉,BV16W411a7V8,,,,一口大锅,
|
||||
白菜鸡蛋烙,白菜、鸡蛋,BV1rb411F7en,,,,一口大锅,
|
||||
白菜鸡胸肉,白菜、鸡肉,BV1nf4y127Cx,,,,一口大锅,
|
||||
白菜鸡胸肉卷,白菜、鸡肉、鸡蛋,BV1nf4y127Cx,,,,一口大锅,
|
||||
白菜卷火锅,白菜、菌菇、胡萝卜、香肠,BV1wY411n7Vk,,,,一口大锅,
|
||||
白菜酿肉,白菜、猪肉、胡萝卜、菌菇,BV1Q5411J7oT,,,,一口大锅,
|
||||
白菜肉卷,白菜、番茄、洋葱、猪肉、米、鸡蛋,BV1Ru411C7Hn,困难,,,一口大锅,
|
||||
白菜三丝饼,白菜、胡萝卜、西葫芦、鸡蛋,BV1oF411B7Ys,普通,,,一口大锅,
|
||||
白萝卜煲,白萝卜、骨头,BV1dU4y1u7eb,普通,,煲,一口大锅,
|
||||
白萝卜炒鸡蛋,白萝卜、鸡蛋,BV1PK4y157u2,普通,,炒,一口大锅,
|
||||
白萝卜大骨头汤,骨头、白萝卜,BV1HE41137ZY,普通,,炖,一口大锅,
|
||||
白萝卜炖猪脚,白萝卜、猪肉,BV1m7411c7pD,困难,,炖,一口大锅,
|
||||
白萝卜骨头汤,白萝卜、骨头,BV1gf4y1u7ap,普通,,煮,一口大锅,
|
||||
白萝卜焖鸡翅,白萝卜、鸡肉,BV1fs41187XG,普通,,,一口大锅,
|
||||
白萝卜烧肉,白萝卜、猪肉,BV1x54y147mz,普通,,烧,一口大锅,
|
||||
白切肉,猪肉,BV15h411s7Ze,,,,一口大锅,
|
||||
班尼迪克蛋,鸡蛋、面包,BV1TT4y127vu,,,,一口大锅,
|
||||
包菜炒蛋,包菜、鸡蛋,BV1w34y1k7wu,简单,,炒,一口大锅,
|
||||
包菜炒香肠,包菜、腊肠,BV1AW411m77S,普通,,炒,一口大锅,
|
||||
包菜大阪烧,包菜、鸡蛋,BV1tA411F7Eq,普通,,煎,一口大锅,
|
||||
包菜豆腐煲,包菜、豆腐,BV1Cq4y157s1,普通,,煲,一口大锅,
|
||||
包菜烧肉,包菜、猪肉,BV1Wp4y1a7YR,普通,,烧,一口大锅,
|
||||
包菜五花肉,猪肉、包菜,BV1La411F7H4,普通,,炒,一口大锅,
|
||||
抱蛋肥牛饭,洋葱、牛肉、鸡蛋、米,BV1Kb4y147m2,,,,一口大锅,
|
||||
爆汁肉饼,猪肉、鸡蛋,BV1Cm4y1X7ej,,,,一口大锅,
|
||||
爆汁香脆蛋,鸡蛋,BV1yR4y1575Y,,,,一口大锅,
|
||||
biangbiang面,面食,BV1844y157GL,简单,,油泼,一口大锅,
|
||||
菜包鸡胸,鸡肉,BV1h54y1Q7pf,,,,一口大锅,
|
||||
茶叶蛋,鸡蛋,BV1TT4y127vu,,,卤,一口大锅,
|
||||
娼妇意面,番茄、洋葱,BV1hF411t7hS,普通,,烧,一口大锅,
|
||||
炒白萝卜丝,白萝卜,BV1ph411p7Pm,简单,,炒,一口大锅,
|
||||
炒方便面,方便面、鸡蛋、香肠,BV1Q54y1p7n8,普通,,炒,一口大锅,
|
||||
潮汕杂菇煲,菌菇、猪肉,BV15E411K7B5,简单,,炒,一口大锅,
|
||||
川味泡面,方便面、鸡蛋,BV1G7411f7FA,简单,,煮,一口大锅,
|
||||
葱油拌面,面食,BV1H7411c7Xe,简单,,拌,一口大锅,
|
||||
葱油饼,面食,BV1gb411J7Xe,普通,,烙,一口大锅,
|
||||
葱油焖鸡翅,鸡肉、洋葱、菌菇,BV1uP4y1P7oQ,普通,,煎,一口大锅,
|
||||
醋溜白菜,白菜、米,BV12K4y157im,,,,一口大锅,
|
||||
醋卤面,面食、猪肉,BV1XC4y1p7Yn,普通,,拌,一口大锅,
|
||||
脆皮糖醋豆腐,豆腐,BV1dU4y1d7hz,普通,,,一口大锅,
|
||||
脆皮土豆虾,虾、土豆,BV1Kq4y1q7PM,普通,,炸,一口大锅,
|
||||
大餐口味方便面,方便面,BV1s34y1X74a,,,,一口大锅,
|
||||
大虾烧白菜,白菜、虾,BV1yT4y157x5,,,,一口大锅,
|
||||
地三鲜,土豆、茄子,BV1st4y1Y73H,,,,一口大锅,
|
||||
东北鸡蛋酱,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,
|
||||
豆腐饭(蛋炒饭),豆腐、鸡蛋、米,BV1dU4y1d7hz,普通,,,一口大锅,
|
||||
法式炒蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,
|
||||
法式胡萝卜炖牛肉,胡萝卜、牛肉、洋葱,BV1UR4y1V7nV,困难,,炖,一口大锅,
|
||||
番茄肥牛,番茄、牛肉,BV1hF411t7hS,普通,,烧,一口大锅,
|
||||
番茄肥牛面,番茄、牛肉、面食,BV1xb4y167LH,普通,,煮,一口大锅,
|
||||
番茄虾滑汤,番茄、虾、面食,BV1eZ4y1o7TK,困难,,煮,一口大锅,
|
||||
番茄鸡翅焖锅,鸡肉、胡萝卜、土豆、洋葱,BV1Ap4y147Ac,普通,,煮,一口大锅,
|
||||
番茄鸡蛋面,方便面、番茄、鸡蛋,BV1tL4y1b7SM,简单,,煮,一口大锅,
|
||||
番茄鸡蛋油泼面,番茄、鸡蛋、面食,BV1hF411t7hS,普通,,煮,一口大锅,
|
||||
番茄焖面,番茄、面食、香肠、鸡蛋,BV1NL4y1571X,,,,一口大锅,
|
||||
番茄排骨汤,番茄、骨头,BV11y4y167Wa,普通,,煮,一口大锅,
|
||||
番茄烧茄子,茄子、番茄,BV11b4y1i7ck,普通,,烧,一口大锅,
|
||||
番茄土豆牛腩饭,牛肉、土豆、番茄、胡萝卜、米,BV1La411i73b,普通,,煮,一口大锅,
|
||||
番茄芝士蛋饼,番茄、鸡蛋,BV1PQ4y1d7ew,普通,,煎,一口大锅,
|
||||
方便面炒饭,方便面、米、香肠、鸡蛋,BV1Uf4y157RG,简单,,炒,一口大锅,
|
||||
翡翠玉带虾仁,黄瓜、虾、鸡蛋,BV1E4411w7wC,复杂,,,一口大锅,
|
||||
富贵金钱蛋(湖南口味辣),鸡蛋,BV15q4y1A784,,,,一口大锅,
|
||||
干锅花菜,花菜、洋葱、米,BV1Rb4y147bZ,普通,,,一口大锅,
|
||||
干锅莴笋,莴笋,BV1TC4y1W76c,,,,一口大锅,
|
||||
干锅西葫芦,西葫芦、洋葱,BV1eP4y1T76T,普通,,炒,一口大锅,
|
||||
干锅西葫芦,西葫芦、猪肉,BV1Vu411B7o6,,,,一口大锅,
|
||||
宫保鸡丁,鸡肉、黄瓜,BV1wE411T7X5,,,,一口大锅,
|
||||
骨汤面,方便面、鸡蛋、牛肉,BV1Uf4y157RG,简单,,煮,一口大锅,
|
||||
骨头炖肉,骨头、猪肉,BV1bS4y1K7BD,普通,,炖,一口大锅,
|
||||
海鲜泡面,方便面、虾、鸡蛋,BV1G7411f7FA,简单,,煮,一口大锅,
|
||||
黑椒鸡胸肉,鸡肉,BV1PX4y1V7S3,,,,一口大锅,
|
||||
红烧大骨头,骨头,BV1hg4y1i7Ss,普通,,烧,一口大锅,
|
||||
厚蛋烧,鸡蛋,BV1TT4y127vu,,,煎,一口大锅,
|
||||
胡辣莴笋,莴笋,BV1NW411T7Xb,,,,一口大锅,
|
||||
花菜虾仁炒饭,花菜、虾仁、米、洋葱、胡萝卜,BV1z4411a7dy,普通,,,一口大锅,
|
||||
滑蛋牛肉,牛肉、鸡蛋,BV12u411Z72s,简单,,炒,一口大锅,
|
||||
黄瓜炒鸡蛋,黄瓜、鸡蛋,BV1Fi4y1E7pt,简单,,炒,一口大锅,
|
||||
黄瓜鸡蛋卷,黄瓜、鸡蛋,BV1GW411L7Sb,普通,,,一口大锅,
|
||||
黄瓜鸡蛋汤,黄瓜、鸡蛋,BV1zi4y1w7C8,简单,,,一口大锅,
|
||||
黄瓜鸡胸肉,鸡肉、黄瓜、胡萝卜,BV1BF41137JV,普通,,,一口大锅,
|
||||
黄瓜钱炒肉,黄瓜、猪肉,BV1WS4y1r7Tx,普通,,炒,一口大锅,
|
||||
火腿白菜豆腐煲,白菜、豆腐、香肠,BV1o44y1r7hX,,,,一口大锅,
|
||||
鸡蛋饼,鸡蛋,BV1a34y117iz,,,,一口大锅,
|
||||
鸡蛋番茄拌面,方便面、鸡蛋、番茄,BV1SS4y1G7fp,普通,,拌,一口大锅,
|
||||
鸡蛋焖面,方面、鸡蛋,BV1SS4y1G7fp,普通,,焖,一口大锅,
|
||||
鸡蛋蒜,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,
|
||||
鸡蛋西兰花豆腐羹,花菜、豆腐、鸡蛋,BV164411g77n,简单,,炖,一口大锅,
|
||||
鸡蛋洋葱饭,洋葱、鸡蛋、米,BV1TU4y1H7Cr,,,,一口大锅,
|
||||
鸡蛋芝士泡面,方便面、鸡蛋、香肠,BV1SS4y1G7fp,普通,,煮,一口大锅,
|
||||
鸡蛋芝士培根吐司,鸡蛋、面包,BV1hv411E7RW,,,,一口大锅,
|
||||
鸡胸肉蔬菜小饼,鸡肉、胡萝卜、花菜,BV1i7411d7Rd,,,,一口大锅,
|
||||
家常黄焖鸡(多调料版),鸡肉、菌菇、土豆、胡萝卜,BV17u411X7rR,,,,一口大锅,
|
||||
煎蛋方便面,方便面,BV1s34y1X74a,,,,一口大锅,
|
||||
煎蛋焖面,面食、鸡蛋,BV1d54y1v7JE,简单,,煮,一口大锅,
|
||||
煎方便面饼,方便面、鸡蛋、胡萝卜,BV1Ey4y1g7p8,普通,,煎,一口大锅,
|
||||
煎土豆,土豆,BV15s411A7GB,,,炸,一口大锅,
|
||||
酱爆西葫芦,西葫芦,BV175411U7LU,简单,,炒,一口大锅,
|
||||
酱黄瓜,黄瓜,BV1y4411T78H,简单,,腌制,一口大锅,
|
||||
酱鸡蛋,鸡蛋,BV19L411u7H4,,,,一口大锅,
|
||||
椒盐鸡翅,鸡肉,BV1HK4y1o77T,简单,,煎,一口大锅,
|
||||
金丝午餐肉,土豆、午餐肉,BV1W34y1k7Bg,普通,,炸,一口大锅,
|
||||
开胃莴笋叶,莴笋,BV16441147Dc,,,,一口大锅,
|
||||
空气土豆,土豆,BV1RL411E7Uv,困难,,炸,一口大锅,
|
||||
腊肠炒黄瓜,腊肠、黄瓜、米,BV1zA411x7oK,普通,,炒,一口大锅,
|
||||
腊肠炒鸡蛋,腊肠、鸡蛋,BV1x54y127CC,普通,,炒,一口大锅,
|
||||
腊肠豆腐煲,腊肠、豆腐、米,BV1Sf4y1r7mS,普通,,煲,一口大锅,
|
||||
腊肠茄子煲,腊肠、茄子、米,BV1vW411g7td,普通,,煲,一口大锅,
|
||||
腊肠烧麦,腊肠、面食,BV1VV411C7o2,普通,,烧,一口大锅,
|
||||
辣味牛肉凉拌小黄瓜,黄瓜、牛肉、洋葱,BV1qr4y1Y792,简单,,,一口大锅,
|
||||
辣子鸡丁,鸡肉,BV1Ma4y147MA,,,,一口大锅,
|
||||
烂糊白菜,白菜、鸡蛋,BV1x3411y7eN,,,,一口大锅,
|
||||
莲花洋葱(消耗洋葱!),洋葱、鸡蛋,BV1MW411T7Cv,,,,一口大锅,
|
||||
凉拌黄瓜虾滑,黄瓜、虾,BV1RF411W7v7,复杂,,,一口大锅,
|
||||
凉拌莴苣,莴笋,BV1z3411Y7Bs,,,,一口大锅,
|
||||
凉拌胸丝,鸡肉,BV1h54y1Q7pf,,,,一口大锅,
|
||||
凉拌洋葱,洋葱,BV1BS4y1G7XD,,,,一口大锅,
|
||||
流心蛋,鸡蛋,BV1Yt411G7Ut,,,,一口大锅,
|
||||
萝卜牛肉煲,白萝卜、牛肉,BV1a7411L733,困难,,煲,一口大锅,
|
||||
萝卜烧牛腩,白萝卜、牛肉,BV1nK41137Lb,普通,,烧,一口大锅,
|
||||
萝卜烧排骨,白萝卜、骨头,BV1YV411f7L8,普通,,烧,一口大锅,
|
||||
萝卜丝炒牛肉,白萝卜、牛肉,BV1jA411P7LJ,普通,,炒,一口大锅,
|
||||
萝卜鲜虾煲,白萝卜、虾,BV1Mp4y1z7Yr,困难,,煲,一口大锅,
|
||||
罗宋汤,胡萝卜、土豆、番茄、牛肉、包菜,BV1394y1d7f6,普通,,煮,一口大锅,
|
||||
马拉糕,面食,BV1fa4y1a7Xy,困难,,蒸,一口大锅,
|
||||
麻辣烫方便面,方便面、香肠、鸡蛋、包菜,BV1Yp411o7MB,简单,,煮,一口大锅,
|
||||
焖泡面,方便面、猪肉、茄子,BV1s34y1X74a,,,,一口大锅,
|
||||
秘制洋葱炒肥牛,洋葱、牛肉,BV1vT4y1E7fZ,,,,一口大锅,
|
||||
牧羊人派,土豆、猪肉,BV1fY41187Xo,普通,,,一口大锅,
|
||||
奶汤方便面,方便面,BV1s34y1X74a,,,,一口大锅,
|
||||
牛肉蒸黄瓜条,黄瓜、牛肉、鸡蛋,BV1V5411S7Uv,普通,,,一口大锅,
|
||||
拍黄瓜,黄瓜,BV1RV41167WV,简单,,凉拌,一口大锅,
|
||||
铺盖打蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,
|
||||
蒲烧茄子,茄子,BV1q44y1n78y,普通,,烧,一口大锅,
|
||||
千层土豆,土豆,BV1nb4y147D6,困难,,炸,一口大锅,
|
||||
茄汁大虾,虾、番茄、米、面食,BV1aJ411K75X,简单,,烧,一口大锅,
|
||||
茄汁蛋黄浓汤面,方便面、番茄、鸡蛋,BV1s34y1X74a,,,,一口大锅,
|
||||
茄汁豆腐,番茄、豆腐,BV1Xb4y1p7mv,简单,,烧,一口大锅,
|
||||
茄汁花菜,花菜、番茄,BV1J541187QQ,普通,,,一口大锅,
|
||||
茄汁鸡胸肉,鸡肉、番茄,BV1L54y1b7Xq,,,,一口大锅,
|
||||
茄子卷,茄子、猪肉,BV1L3411H7EK,普通,,炸/煎,一口大锅,
|
||||
芹菜炒腊肉,芹菜、猪肉,av209779329,普通,,炒,一口大锅,
|
||||
芹菜炒肉丝,芹菜、猪肉,av806554209,普通,,炒,一口大锅,
|
||||
芹菜炒香干,芹菜,av671691480,普通,,炒,一口大锅,
|
||||
芹菜煎饼,芹菜、鸡蛋,av247131174,简单,,煎,一口大锅,
|
||||
清炖牛肉,牛肉、白萝卜,BV1CL4y1n76E,普通,,炖,一口大锅,
|
||||
青瓜炒火腿肠,黄瓜、香肠,BV1u7411Z79s,简单,,,一口大锅,
|
||||
青椒炒荷包蛋,鸡蛋,BV1wJ41167uH,,,,一口大锅,
|
||||
热拌莴笋叶,莴笋,BV1xJ411M7bc,,,,一口大锅,
|
||||
日式汉堡排(废手),面包、洋葱、猪肉、牛肉、鸡蛋,BV1UV411Y7HM,,,,一口大锅,
|
||||
肉炒莴苣,莴笋、猪肉,BV14E411t7wh,,,,一口大锅,
|
||||
肉末土豆茄子煲,茄子、土豆、猪肉,BV1Fu411S78s,普通,,煲,一口大锅,
|
||||
肉丸萝卜煲,白萝卜、虾、猪肉,BV1gT4y1m7UK,普通,,煲,一口大锅,
|
||||
赛螃蟹,鸡蛋,BV1ab4y1s7wk,,,,一口大锅,
|
||||
三杯鸡(无九层塔版),鸡肉,BV1dp4y1e73R,,,,一口大锅,
|
||||
生煎白萝卜,白萝卜,BV1Yu41127Fa,简单,,煎,一口大锅,
|
||||
薯条,土豆,BV1fY41187Xo,普通,,炸,一口大锅,
|
||||
爽口腌莴笋,莴笋,BV1Vv411V7SE,,,,一口大锅,
|
||||
水波蛋,鸡蛋,BV1TT4y127vu,简单,,煮,一口大锅,
|
||||
水煮牛肉,牛肉、菌菇、莴笋、豆腐,BV11q4y1z7dD,复杂,,,一口大锅,
|
||||
苏格兰蛋,鸡蛋、猪肉,BV1TT4y127vu,,,炸,一口大锅,
|
||||
素烧白萝卜,白萝卜,BV1Z7411b7xo,简单,,烧,一口大锅,
|
||||
蒜香蜂胸扒,鸡肉,BV1h54y1Q7pf,,,,一口大锅,
|
||||
蒜香鸡翅,鸡肉,BV1sy4y1273W,普通,,煎,一口大锅,
|
||||
蒜香鸡胸肉,鸡肉,BV1tf4y1A7xR,,,,一口大锅,
|
||||
蓑衣黄瓜,黄瓜,BV1Wa411C7Gc,,,,一口大锅,
|
||||
泰式卤蛋,鸡蛋,BV1TT4y127vu,,,卤,一口大锅,
|
||||
糖醋荷包蛋,鸡蛋,BV1TT4y127vu,,,煎,一口大锅,
|
||||
糖醋鸡胸肉,鸡肉,BV1h7411C7PZ,,,,一口大锅,
|
||||
糖醋里脊,猪肉,BV1Dt411B78h,普通,,炸,一口大锅,
|
||||
糖醋茄子,茄子、米,BV1TE411L7Gp,普通,,烧,一口大锅,
|
||||
铁板豆腐,豆腐,BV1dU4y1d7hz,简单,,,一口大锅,
|
||||
土豆拌饭,土豆、香肠、米,BV1YD4y1c7ip,简单,,炒,一口大锅,
|
||||
土豆炖茄子,茄子、土豆,BV1uP4y1M7rt,普通,,炖,一口大锅,
|
||||
土豆胡萝卜炖牛腩,牛肉、胡萝卜、土豆、,BV1R54y1E7JX,,,,一口大锅,
|
||||
土豆腊肠煲饭,腊肠、土豆、米,BV1wV411o7Sq,困难,,煲,一口大锅,
|
||||
土豆泥厚蛋烧,土豆、鸡蛋,BV1DL4y1j7Nu,困难,,煎,一口大锅,
|
||||
土豆烧牛腩,土豆、牛肉,BV1qs411u7gj,普通,,炒,一口大锅,
|
||||
吐司进阶吃法,面包、鸡蛋、虾、包菜、香肠,BV1Tq4y1t7DC,简单,,,一口大锅,
|
||||
丸子面,方便面、猪肉、牛肉,BV1s34y1X74a,,,,一口大锅,
|
||||
莴笋菜饭,莴笋,BV1wC4y1Y7iY,,,,一口大锅,
|
||||
莴笋炒虾仁,莴笋、虾、胡萝卜,BV1ZT4y1g7Y2,简单,,炒,一口大锅,
|
||||
莴笋炒鸡丁,莴笋、鸡肉,BV1hb411i7X2,普通,,,一口大锅,
|
||||
莴笋炒腊肉,莴笋、香肠,BV1Ms411F7da,简单,,炒,一口大锅,
|
||||
莴笋胡萝卜酿肉,莴笋、胡萝卜,BV1754y1y7eV,,,,一口大锅,
|
||||
莴笋三吃,莴笋,BV1Bf4y1E7BR,,,,一口大锅,
|
||||
午餐肉蛋炒饭,午餐肉、鸡蛋、米,BV1HE411X7xR,普通,,炒,一口大锅,
|
||||
午餐肉土豆锅,午餐肉、土豆、胡萝卜,BV1gT4y1Q7R8,普通,,煮,一口大锅,
|
||||
西班牙土豆鸡蛋饼,鸡蛋、土豆、洋葱,BV1Vz4y1D7nF,,,,一口大锅,
|
||||
西葫芦鸡蛋饼,西葫芦、鸡蛋,BV1Mt411T7pF,普通,,煎,一口大锅,
|
||||
西葫芦蒸蛋,西葫芦、鸡蛋,BV1R94y1d7dd,普通,,,一口大锅,
|
||||
西兰花炒虾仁,花菜、虾,BV1nv411B78i,简单,,炒,一口大锅,
|
||||
香菇白菜汤,白菜、菌菇,BV1Cs411G7bw,普通,,,一口大锅,
|
||||
香煎鸡胸肉,鸡肉,BV1W4411j71M,,,,一口大锅,
|
||||
香辣鸡胸肉,鸡肉,BV1M34y187r2,,,,一口大锅,
|
||||
小炒鸡胸肉,鸡肉,BV1pF411a7nW,,,,一口大锅,
|
||||
小炒牛肉,黄瓜、牛肉、米,BV1MK411T7NX,普通,,,一口大锅,
|
||||
雪碧拌面(要雪碧+老干妈),方便面、黄瓜,BV1az4y117d1,,,,一口大锅,
|
||||
阳春面,面食,BV11E411C7e7,简单,,煮,一口大锅,
|
||||
洋葱拌鸡丝,洋葱、鸡肉,BV1Ki4y1P7tN,,,,一口大锅,
|
||||
洋葱拌木耳,洋葱、木耳,BV1n5411H7Yo,,,,一口大锅,
|
||||
洋葱炒鸡蛋,洋葱、鸡蛋,BV1Dp4y1k7Mu,,,,一口大锅,
|
||||
洋葱炒肉,洋葱、鸡肉,BV1QV41127ES,,,,一口大锅,
|
||||
洋葱炒土豆,土豆、洋葱,BV1mg4y1b7z3,,,,一口大锅,
|
||||
洋葱虾仁滑蛋,鸡蛋、牛肉、洋葱、虾,BV1uE411N7AN,,,,一口大锅,
|
||||
洋葱排骨,洋葱、猪肉,BV1Q3411j7p6,,,,一口大锅,
|
||||
洋葱烧鸡肉,洋葱、鸡肉,BV1hW411y7Dp,,,,一口大锅,
|
||||
一口酥豆腐,豆腐,BV1dU4y1d7hz,普通,,,一口大锅,
|
||||
一只鸡蛋糕,鸡蛋,BV1X7411a7EQ,,,,一口大锅,
|
||||
英式炒蛋,鸡蛋,BV1TT4y127vu,,,炒,一口大锅,
|
||||
油墩子/腰子饼,白萝卜、芹菜、香肠、面食、鸡蛋,BV12b4y1i7DP,,,,一口大锅,
|
||||
鱼香茄子,茄子、鸡肉,BV1yL411E7oP,普通,,烧,一口大锅,
|
||||
杂粮蒸糕,面食,BV1vf4y1T7gY,普通,,蒸,一口大锅,
|
||||
炸蛋,鸡蛋,BV1TT4y127vu,简单,,炸,一口大锅,
|
||||
炸胡萝卜块,葫芦卜、鸡蛋,BV11a411i7f4,普通,,炸,一口大锅,
|
||||
炸胡萝卜丸,胡萝卜、鸡蛋、香肠,BV12r4y1i7ud,普通,,炸,一口大锅,
|
||||
炸茄盒,茄子,BV1df4y177NB,普通,,炸,一口大锅,
|
||||
炸薯卷,土豆、香肠,BV1Na4y1a7LA,简单,,炸,一口大锅,
|
||||
炸洋葱,洋葱,BV1ib411V7H2,,,,一口大锅,
|
||||
照烧鸡胸肉,鸡肉,BV12Q4y1K7CA,,,,一口大锅,
|
||||
照烧牛肉,牛肉,BV1mq4y1x7o3,普通,,炒,一口大锅,
|
||||
朝鲜冷面(方便面版),方便面,BV1s34y1X74a,,,,一口大锅,
|
||||
蒸蛋羹,鸡蛋,BV1Wt411Z7td,,,,一口大锅,
|
||||
蒸蛋羹(硬核0失败版),鸡蛋,BV1x441117r4,,,,一口大锅,
|
||||
蒸卤面,面食,BV1sZ4y137QG,困难,,蒸,一口大锅,
|
||||
蒸芹菜饼,芹菜、鸡蛋,av809152626,普通,,蒸,一口大锅,
|
||||
正确煮方便面,方便面,BV1s34y1X74a,,,,一口大锅,
|
||||
孜然土豆牛肉,牛肉、土豆,BV1qX4y1T7ax,普通,,炒,一口大锅,
|
||||
自制牛肉干,牛肉,BV1Ht411d71Q,复杂,,,一口大锅,
|
||||
电饭煲番茄牛肉焖饭,番茄、牛肉、米,BV1Bv411C7X3,普通,,,一口大锅、电饭煲,
|
||||
电饭煲排骨土豆焖饭,猪肉、土豆、米,BV1Bv411C7X3,普通,,,一口大锅、电饭煲,
|
||||
米布丁,米、鸡蛋,BV1hr4y1k7A5,简单,,,一口大锅、电饭煲,
|
||||
麻婆豆腐,豆腐,BV1it4y1X75m,简单,,,一口大锅,
|
||||
|
1
app/data/recipe.json
Normal file
1
app/data/recipe.json
Normal file
File diff suppressed because one or more lines are too long
15
app/layouts/README.md
Normal file
15
app/layouts/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## Layouts
|
||||
|
||||
Vue components in this dir are used as layouts.
|
||||
|
||||
By default, `default.vue` will be used unless an alternative is specified in the route meta.
|
||||
|
||||
```html
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'home',
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Learn more on https://nuxt.com/docs/guide/directory-structure/layouts
|
||||
22
app/layouts/child.vue
Normal file
22
app/layouts/child.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
title?: string
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="text-center text-gray-700 dark:text-gray-200" p="t-5 b-15">
|
||||
<div flex items-center justify-between>
|
||||
<BackBtn ml-3 />
|
||||
<h2 flex items-center justify-center text-lg font="bold">
|
||||
{{ route.meta.title }}
|
||||
</h2>
|
||||
<DarkToggle mr-3 />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
<TheBottomMenu fixed bottom-0 left-0 right-0 />
|
||||
</main>
|
||||
</template>
|
||||
8
app/layouts/default.vue
Normal file
8
app/layouts/default.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<main class="cook-main text-center text-gray-700 dark:text-gray-200" p="t-8 b-$cook-bottom-menu-height">
|
||||
<slot />
|
||||
<DarkToggle absolute left-4 top-4 />
|
||||
<SearchRecipe />
|
||||
<TheBottomMenu fixed bottom-0 left-0 right-0 />
|
||||
</main>
|
||||
</template>
|
||||
17
app/pages/404.vue
Normal file
17
app/pages/404.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
// const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main p="x4 y10" text="center green-700 dark:gray-200">
|
||||
<div text-4xl>
|
||||
<div i-ri-error-warning-line inline-block />
|
||||
</div>
|
||||
<div>菜谱消失了</div>
|
||||
<div>
|
||||
<NuxtLink text-sm btn m="3 t8" to="/">
|
||||
返回主页
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
5
app/pages/about.vue
Normal file
5
app/pages/about.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
关于
|
||||
</div>
|
||||
</template>
|
||||
22
app/pages/cookbooks/index.vue
Normal file
22
app/pages/cookbooks/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '自定义菜谱',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>
|
||||
开发中,敬请期待
|
||||
</h3>
|
||||
|
||||
<div grid="~ cols-3" gap="4" p="4">
|
||||
<CookbookCard :cookbook="defaultCookbook">
|
||||
默认菜谱
|
||||
</CookbookCard>
|
||||
|
||||
<NewCookbookCard />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
5
app/pages/cookbooks/new.vue
Normal file
5
app/pages/cookbooks/new.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
新建 Cookbook
|
||||
</div>
|
||||
</template>
|
||||
165
app/pages/help.vue
Normal file
165
app/pages/help.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<CommonHeader>
|
||||
帮助
|
||||
</CommonHeader>
|
||||
|
||||
<InstallPwa />
|
||||
|
||||
<FeedbackActions />
|
||||
|
||||
<div class="mx-auto max-w-md w-full rounded-2xl p-2" text-left>
|
||||
<FAQItem title="未来计划?">
|
||||
计划增加新功能,如自定义菜谱,与使用其他用户分享的菜谱。
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="什么是模式?">
|
||||
<ul>
|
||||
<li><b>模糊匹配</b>:展示所有含当前选中任意食材的菜谱</li>
|
||||
<li><b>精准匹配</b>:展示所有含当前选中所有食材的菜谱</li>
|
||||
<li><b>生存模式</b>:展示当前选中食材即可制作的所有菜谱</li>
|
||||
</ul>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="如何快速清空所选食材和工具?">
|
||||
<div inline-flex items-center justify-center>
|
||||
点击顶部 <div i-mdi-pot-steam-outline mx-1 inline-block /> 图标即可。
|
||||
</div>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="是否有微信小程序?">
|
||||
因不可抗力(小程序因跳转 B 站视频而被判定为导流违规)下架。
|
||||
将不再提供小程序版本。
|
||||
<br>
|
||||
<br>
|
||||
搜索微信公众号<b>「云游君」</b>并发送<b>「做菜」</b>,也可以快速找到本网站。
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="是否有 APP?">
|
||||
<b>暂时没有开发 APP 的计划。</b>
|
||||
<br>
|
||||
但我们正在优化 <b>PWA</b> 的体验,以便您可以直接将本站添加到桌面,并享受<b>类似 APP 的体验</b>。
|
||||
<br>
|
||||
你可以使用浏览器打开,点击上方的<b>「安装到桌面」</b>或在菜单中点击<b>「添加到主屏幕」</b>。
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="未来是否会收费?">
|
||||
该项目将以免费开源的形式运营。
|
||||
<br>
|
||||
您可以考虑赞助本项目,以支持我们的开发。
|
||||
我会将其投入在周边的服务器、域名、CDN 等费用上。
|
||||
<ul mt-1>
|
||||
<li>
|
||||
<a href="https://afdian.net/a/yunyoujun" target="_blank">爱发电赞助</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://sponsors.yunyoujun.cn/" target="_blank">我要直接打钱!</a>
|
||||
</li>
|
||||
</ul>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="页面无法点击、资源加载失败?">
|
||||
<blockquote>
|
||||
试试「无痕模式」是否正常?
|
||||
</blockquote>
|
||||
<br>
|
||||
<ol>
|
||||
<li>
|
||||
<b>清除 Cookie</b>
|
||||
<ol>
|
||||
<li>
|
||||
点击浏览器网址前方的 🔒 图标
|
||||
</li>
|
||||
<li>
|
||||
点击「Cookie」并清除
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
<b>强制刷新缓存</b>
|
||||
<ul>
|
||||
<li>Windows: <code>Ctrl + F5</code></li>
|
||||
<li>macOS: <code>Cmd + Shift + R</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</FAQItem>
|
||||
|
||||
<hr h="1" my="4" bg-black>
|
||||
|
||||
<FAQItem :default-open="true" title="关于">
|
||||
<div text-left>
|
||||
<ul>
|
||||
<li>
|
||||
它诞生于 2022 年 4 月,时值疫情风控期间,希望能帮助期间的伙伴根据现有食材寻找到合适的菜谱。故原名「隔离食用手册」。
|
||||
</li>
|
||||
<li>
|
||||
如今那个时期已离我们远去,故去掉「隔离」二字。但也很高兴能在这里继续与你相遇,希望它能继续发光发热,在日常生活中帮助到大家。
|
||||
</li>
|
||||
<li>
|
||||
<div class="inline-flex items-center justify-center">
|
||||
代码仓库:<a class="inline-flex items-center justify-center" href="https://github.com/YunYouJun/cook" target="_blank">
|
||||
<div m="r-1" i-ri-github-line inline-flex />YunYouJun/cook</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="inline-flex items-center justify-center">
|
||||
菜谱视频来源:
|
||||
<a class="inline-flex items-center text-sm text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS" target="_blank">
|
||||
<div m="r-1" i-ri-bilibili-line inline-flex />
|
||||
<span class="inline-flex">隔离食用手册大全</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="关于我">
|
||||
<div text-left>
|
||||
我的个人微信公众号「云游君」,会分享一些生活和写的<a href="https://sponsors.yunyoujun.cn/projects" target="_blank">
|
||||
小玩具们
|
||||
</a>。
|
||||
|
||||
<a inline-flex py-4 href="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg" target="_blank">
|
||||
<img src="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg">
|
||||
</a>
|
||||
</div>
|
||||
<AboutMe />
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="致谢">
|
||||
<p>
|
||||
感谢以下小伙伴为本项目提供的数据支持和 QA !
|
||||
</p>
|
||||
|
||||
<ul mt-2 text-left text-sm>
|
||||
<li>
|
||||
<a href="https://weibo.com/runny" target="_blank">Runny</a>
|
||||
</li>
|
||||
<li>
|
||||
麒麟
|
||||
</li>
|
||||
<li>
|
||||
晴方啾
|
||||
</li>
|
||||
<li>
|
||||
课代表阿伟
|
||||
</li>
|
||||
</ul>
|
||||
</FAQItem>
|
||||
|
||||
<FAQItem title="赞助者们">
|
||||
<div>
|
||||
感谢至今以来所有的<a href="https://afdian.net/a/yunyoujun" class="text-purple" target="_blank">赞助者</a>们,你们的支持是我持续维护和开发新项目的动力!
|
||||
</div>
|
||||
<div pt-2>
|
||||
<a href="https://sponsors.yunyoujun.cn" target="_blank">
|
||||
<img src="https://sponsors.yunyoujun.cn/sponsors.svg">
|
||||
</a>
|
||||
</div>
|
||||
</FAQItem>
|
||||
</div>
|
||||
</div>
|
||||
<BaseFooter mt-4 />
|
||||
</template>
|
||||
22
app/pages/index.vue
Normal file
22
app/pages/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts" setup>
|
||||
const rStore = useRecipeStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div text-4xl>
|
||||
<button
|
||||
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
||||
title="重置"
|
||||
@click="rStore.reset"
|
||||
>
|
||||
<div v-if="rStore.selectedStuff.length" i-mdi-pot-steam-outline />
|
||||
<div v-else i-mdi-pot-mix-outline />
|
||||
</button>
|
||||
</div>
|
||||
<p text="sm" m="b-4">
|
||||
好的,今天我们来做菜!
|
||||
</p>
|
||||
|
||||
<ChooseFood />
|
||||
<SimpleCopyright />
|
||||
</template>
|
||||
10
app/pages/random.vue
Normal file
10
app/pages/random.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div flex flex-col>
|
||||
<CommonHeader>
|
||||
今天吃什么
|
||||
</CommonHeader>
|
||||
<div flex flex-grow flex-col items-center justify-center>
|
||||
<RandomRecipe />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
12
app/pages/recipes/collect.vue
Normal file
12
app/pages/recipes/collect.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '我的收藏',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
施工中...
|
||||
</div>
|
||||
</template>
|
||||
38
app/pages/recipes/history.vue
Normal file
38
app/pages/recipes/history.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import dayjs from 'dayjs'
|
||||
import { recipeHistories } from '~/composables/store/history'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '历史记录',
|
||||
})
|
||||
|
||||
// todo
|
||||
// clear one history
|
||||
function clearAllHistory() {
|
||||
recipeHistories.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div pt-2>
|
||||
<div
|
||||
text="blue-900 dark:blue-200"
|
||||
bg="blue-300 op-20 hover:(blue-800 op-20) dark:hover:(blue-200 op-20)"
|
||||
class="inline-flex items-center justify-center border border-transparent rounded-md px-4 py-2 text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
||||
@click="clearAllHistory"
|
||||
>
|
||||
<div i-ri-eraser-line />
|
||||
<span class="ml-1">清空记录</span>
|
||||
</div>
|
||||
|
||||
<div flex="~ col">
|
||||
<div v-for="history in recipeHistories" :key="history.recipe.name" mt-2>
|
||||
<StapleTag :active="false">
|
||||
{{ dayjs(history.time).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</StapleTag>
|
||||
<DishTag :dish="history.recipe" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
12
app/pages/recipes/index.vue
Normal file
12
app/pages/recipes/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '菜谱 - ?',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
asd
|
||||
</div>
|
||||
</template>
|
||||
5
app/pages/recipes/new.vue
Normal file
5
app/pages/recipes/new.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
新建 Recipe
|
||||
</div>
|
||||
</template>
|
||||
28
app/pages/settings.vue
Normal file
28
app/pages/settings.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
const app = useAppStore()
|
||||
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CommonHeader>
|
||||
设置
|
||||
</CommonHeader>
|
||||
|
||||
<div
|
||||
class="mx-auto max-w-md w-full"
|
||||
px-2
|
||||
text-left
|
||||
>
|
||||
<YlfForm>
|
||||
<YlfFormItem label="离开网页后保留选中数据">
|
||||
<YlfSwitch v-model="app.settings.keepLocalData" />
|
||||
</YlfFormItem>
|
||||
<YlfFormItem label="更多设置,敬请期待" />
|
||||
</YlfForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
43
app/pages/user.vue
Normal file
43
app/pages/user.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import { links } from '~/constants'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CommonHeader>
|
||||
我的
|
||||
</CommonHeader>
|
||||
|
||||
<div
|
||||
class="mx-auto max-w-md w-full"
|
||||
px-2
|
||||
text-left
|
||||
>
|
||||
<div mt-2 gap="3" grid="~ cols-3">
|
||||
<YlfIconItem to="/recipes/history" icon="i-ri-history-line" label="历史记录" />
|
||||
<YlfIconItem to="/recipes/collect" icon="i-ri-star-line" label="我的收藏" />
|
||||
<YlfIconItem to="/cookbooks" icon="i-ri-article-line" label="自定义菜谱" />
|
||||
</div>
|
||||
|
||||
<YlfForm>
|
||||
<YlfFormItem icon="i-ri-feedback-line" label="立即反馈" :to="links.feedback" target="_blank" />
|
||||
<YlfFormItem icon="i-ri-mail-send-line" label="立即投稿" :to="links.contribute" target="_blank" />
|
||||
</YlfForm>
|
||||
|
||||
<YlfForm>
|
||||
<YlfFormItem icon="i-ri-settings-line" label="设置" to="/settings" />
|
||||
</YlfForm>
|
||||
|
||||
<!-- <YlfForm>
|
||||
<YlfFormItem icon="i-ri-article-line" label="自定义菜谱 TODO" to="/cookbooks/" />
|
||||
</YlfForm> -->
|
||||
|
||||
<YlfForm>
|
||||
<YlfFormItem icon="i-ri-question-line" label="帮助" to="/help" />
|
||||
<YlfFormItem icon="i-ri-information-line" label="关于" to="/help" />
|
||||
</YlfForm>
|
||||
</div>
|
||||
|
||||
<BaseFooter mt-4 />
|
||||
</div>
|
||||
</template>
|
||||
38
app/styles/animation.scss
Normal file
38
app/styles/animation.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
/* we will explain what these classes do next! */
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// scrollbar
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
background-color: rgba(122, 122, 122, 0.3);
|
||||
|
||||
&:window-inactive {
|
||||
background-color: rgba(122, 122, 122, 0.3);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(122, 122, 122, 0.7);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(122, 122, 122, 0.9);
|
||||
}
|
||||
}
|
||||
32
app/styles/css-vars.scss
Normal file
32
app/styles/css-vars.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
:root {
|
||||
--c-primary: #5fc178;
|
||||
--c-text: #333;
|
||||
|
||||
--c-bg: white;
|
||||
--c-bg-alt: #f9fbfd;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--c-text: #fafafa;
|
||||
|
||||
--c-bg: #121212;
|
||||
--c-bg-alt: #333;
|
||||
}
|
||||
|
||||
// ylf
|
||||
:root {
|
||||
--ylf-c-bg-alt: var(--c-bg-alt);
|
||||
--ylf-c-border: #eaeaea;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--ylf-c-border: #666;
|
||||
}
|
||||
|
||||
// cook custom
|
||||
:root {
|
||||
--cook-bottom-menu-padding-bottom: 20px;
|
||||
--cook-bottom-menu-height: calc(
|
||||
64px + var(--cook-bottom-menu-padding-bottom)
|
||||
);
|
||||
}
|
||||
65
app/styles/index.scss
Normal file
65
app/styles/index.scss
Normal file
@@ -0,0 +1,65 @@
|
||||
@use './animation.scss';
|
||||
// markdown
|
||||
@use './markdown.scss';
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: var(--c-bg);
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: rgb(13, 148, 136);
|
||||
opacity: 0.75;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
html {
|
||||
color: var(--c-text);
|
||||
background-color: var(--c-bg);
|
||||
|
||||
scroll-behavior: smooth;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--c-text);
|
||||
}
|
||||
|
||||
button {
|
||||
outline: none;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin: 4px;
|
||||
padding: 2px 4px;
|
||||
// border: 1px solid var(--c-text);
|
||||
}
|
||||
36
app/styles/markdown.scss
Normal file
36
app/styles/markdown.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
@use 'star-markdown-css/src/scss/theme/yun.scss';
|
||||
|
||||
.markdown-body {
|
||||
li {
|
||||
list-style: disc;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 0.25em solid #ddd;
|
||||
padding: 0 1em;
|
||||
color: #777;
|
||||
quotes: '\\201C' '\\201D' '\\2018' '\\2019';
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
padding-left: 2em;
|
||||
|
||||
li {
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
padding-left: 2em;
|
||||
|
||||
li {
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
22
app/types/cookbook.ts
Normal file
22
app/types/cookbook.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Recipes } from './recipe'
|
||||
|
||||
export interface Cookbook {
|
||||
/**
|
||||
* 菜谱 ID,自定义,唯一标识符
|
||||
*/
|
||||
id: string
|
||||
cover?: string
|
||||
/**
|
||||
* 菜谱名称
|
||||
*/
|
||||
title: string
|
||||
description: string
|
||||
author: string | string[]
|
||||
/**
|
||||
* 菜谱
|
||||
*/
|
||||
recipes: Recipes
|
||||
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
2
app/types/index.ts
Normal file
2
app/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './cookbook'
|
||||
export * from './recipe'
|
||||
69
app/types/recipe.ts
Normal file
69
app/types/recipe.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
export type Difficulty = '简单' | '普通' | '困难'
|
||||
|
||||
export interface RecipeItem {
|
||||
/**
|
||||
* 菜名
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* 链接
|
||||
*/
|
||||
link?: string
|
||||
/**
|
||||
* BiliBili video id
|
||||
*/
|
||||
bv?: string
|
||||
/**
|
||||
* 材料
|
||||
*/
|
||||
stuff: string[]
|
||||
/**
|
||||
* 根据材料生成
|
||||
*/
|
||||
emojis?: string[]
|
||||
/**
|
||||
* 难度
|
||||
*/
|
||||
difficulty?: Difficulty | ''
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
tags?: string[]
|
||||
/**
|
||||
* 方式
|
||||
*/
|
||||
methods?: ('炒' | '煎' | '烘' | '炸')[]
|
||||
/**
|
||||
* 工具
|
||||
*/
|
||||
tools: string[]
|
||||
}
|
||||
|
||||
export type Recipes = RecipeItem[]
|
||||
|
||||
export interface StuffItem {
|
||||
/**
|
||||
* 食材名称
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* 例如:🥔
|
||||
*/
|
||||
emoji: string
|
||||
/**
|
||||
* 图片链接
|
||||
*/
|
||||
image?: string
|
||||
/**
|
||||
* 别名,譬如:西红柿/番茄
|
||||
*/
|
||||
alias?: string
|
||||
/**
|
||||
* 图标名称
|
||||
*/
|
||||
icon?: string
|
||||
/**
|
||||
* 显示标签
|
||||
*/
|
||||
label?: string
|
||||
}
|
||||
11
app/utils/cookbook.ts
Normal file
11
app/utils/cookbook.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Cookbook } from '~/types'
|
||||
|
||||
export const defaultCookbook: Cookbook = {
|
||||
id: 'default',
|
||||
title: '默认菜谱',
|
||||
description: '记录了一些特殊时期常用的菜谱',
|
||||
author: [''],
|
||||
recipes: [],
|
||||
updatedAt: '',
|
||||
createdAt: '2021-04-04',
|
||||
}
|
||||
32
app/utils/db.ts
Normal file
32
app/utils/db.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Table } from 'dexie'
|
||||
import Dexie from 'dexie'
|
||||
|
||||
import type { RecipeItem } from '~/types'
|
||||
|
||||
export interface DbRecipeItem extends RecipeItem {
|
||||
id?: number
|
||||
}
|
||||
|
||||
export class MySubClassedDexie extends Dexie {
|
||||
recipes!: Table<DbRecipeItem>
|
||||
|
||||
constructor() {
|
||||
super('cook-db')
|
||||
this.version(1).stores({
|
||||
recipes: '++id, name, stuff, bv, difficulty, tags, methods, tools, link, description', // Primary key and indexed props
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new MySubClassedDexie()
|
||||
|
||||
export async function initDb() {
|
||||
const { default: recipeData } = await import('../data/recipe.json')
|
||||
|
||||
return db.recipes.bulkPut(
|
||||
(recipeData as RecipeItem[]).map((item, i) => ({
|
||||
id: i,
|
||||
...item,
|
||||
})),
|
||||
)
|
||||
}
|
||||
16
app/utils/index.ts
Normal file
16
app/utils/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { meat, staple, vegetable } from '~/data/food'
|
||||
|
||||
const foodItems = [...vegetable, ...meat, ...staple]
|
||||
const foodEmojiMap = new Map()
|
||||
foodItems.forEach((item) => {
|
||||
foodEmojiMap.set(item.name, item.emoji)
|
||||
})
|
||||
|
||||
/**
|
||||
* get emojis from stuff name array
|
||||
* @param stuff
|
||||
*/
|
||||
export function getEmojisFromStuff(stuff: string[]) {
|
||||
const emojis: string[] = stuff.map(name => foodEmojiMap.get(name)).filter(item => !!item)
|
||||
return emojis
|
||||
}
|
||||
35
app/utils/pwa.ts
Normal file
35
app/utils/pwa.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { isClient } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
* - https://web.dev/customize-install/#detect-install
|
||||
* - [Trigger installation from your PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Trigger_install_prompt)
|
||||
*/
|
||||
export function installPrompt() {
|
||||
if (!isClient)
|
||||
return
|
||||
|
||||
const app = useAppStore()
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
// Prevent the mini-infobar from appearing on mobile
|
||||
e.preventDefault()
|
||||
// Stash the event so it can be triggered later.
|
||||
app.deferredPrompt = e
|
||||
|
||||
// Update UI notify the user they can install the PWA
|
||||
// showInstallPromotion()
|
||||
// Optionally, send analytics event that PWA install promo was shown.
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\'beforeinstallprompt\' event was fired.')
|
||||
})
|
||||
|
||||
window.addEventListener('appinstalled', () => {
|
||||
// Hide the app-provided install promotion
|
||||
// hideInstallPromotion()
|
||||
// Clear the deferredPrompt so it can be garbage collected
|
||||
app.deferredPrompt = null
|
||||
// Optionally, send analytics event to indicate successful install
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('PWA was installed')
|
||||
})
|
||||
}
|
||||
15
app/utils/random.ts
Normal file
15
app/utils/random.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 生成随机数组
|
||||
*/
|
||||
export function generateRandomArray(length: number, total = 1) {
|
||||
const randomArr: number[] = []
|
||||
for (let i = 0; i < total; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * length)
|
||||
if (randomArr.includes(randomIndex)) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
randomArr.push(randomIndex)
|
||||
}
|
||||
return randomArr
|
||||
}
|
||||
10
app/utils/settings.ts
Normal file
10
app/utils/settings.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface UserSettings {
|
||||
/**
|
||||
* 保留本地数据
|
||||
*/
|
||||
keepLocalData: boolean
|
||||
}
|
||||
|
||||
export const defaultSettings: UserSettings = {
|
||||
keepLocalData: true,
|
||||
}
|
||||
Reference in New Issue
Block a user