fix: useStorage in ssr & extract random
This commit is contained in:
@@ -31,9 +31,9 @@
|
|||||||
- [居家菜谱投稿](https://docs.qq.com/form/page/DWk9GWW9oTmlXZU9V)
|
- [居家菜谱投稿](https://docs.qq.com/form/page/DWk9GWW9oTmlXZU9V)
|
||||||
- [反馈建议分享-兔小巢](https://support.qq.com/products/507827)
|
- [反馈建议分享-兔小巢](https://support.qq.com/products/507827)
|
||||||
|
|
||||||
<!-- ### Features -->
|
### Features
|
||||||
|
|
||||||
<!-- 本项目支持 PWA,使用浏览器打开时,可将其添加到主屏幕以获得近原生 APP 的体验。 -->
|
本项目支持 PWA,使用浏览器打开时,可将其添加到主屏幕以获得近原生 APP 的体验。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,5 @@ const buildDate = (new Date(Number.parseInt(now) * 1000)).toLocaleDateString()
|
|||||||
云乐坊工作室
|
云乐坊工作室
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- 欢迎赞助 -->
|
|
||||||
<!-- <div m="t-2" opacity="80" class="footer-support flex justify-center items-center">
|
|
||||||
<span>本网站由</span><a class="footer-support-logo" href="https://www.upyun.com" target="blank" title="又拍云">
|
|
||||||
<img m="x-1" width="50" src="https://cdn.yunyoujun.cn/img/logo/upyun-logo.png" alt="又拍云">
|
|
||||||
</a><span>提供 CDN 加速</span>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import recipes from '~/data/recipe.json'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
isVisible: Boolean,
|
isVisible: Boolean,
|
||||||
@@ -11,7 +12,7 @@ const { displayedRecipe } = storeToRefs(rStore)
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-show="displayedRecipe.length !== rStore.recipes.length && isVisible"
|
v-show="displayedRecipe.length !== recipes.length && isVisible"
|
||||||
class="fixed z-9 inline-flex cursor-pointer items-center justify-center rounded rounded-full shadow hover:shadow-md"
|
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"
|
bg="green-50 dark:green-900" w="10" h="10"
|
||||||
bottom="18" right="4"
|
bottom="18" right="4"
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ const recipeBtn = ref<HTMLButtonElement>()
|
|||||||
const { playAnimation } = useEmojiAnimation(recipeBtn)
|
const { playAnimation } = useEmojiAnimation(recipeBtn)
|
||||||
|
|
||||||
const gtm = useGtm()
|
const gtm = useGtm()
|
||||||
const recipePanel = ref()
|
const recipePanelRef = ref()
|
||||||
const { isVisible, show } = useInvisibleElement(recipePanel)
|
const { isVisible, show } = useInvisibleElement(recipePanelRef)
|
||||||
|
|
||||||
function toggleStuff(item: StuffItem, category = '', _e?: Event) {
|
function toggleStuff(item: StuffItem, category = '', _e?: Event) {
|
||||||
rStore.toggleStuff(item.name)
|
rStore.toggleStuff(item.name)
|
||||||
@@ -36,86 +36,76 @@ function toggleStuff(item: StuffItem, category = '', _e?: Event) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h2 m="t-4" text="xl" font="bold" p="1">
|
|
||||||
🥘 先选一下食材
|
|
||||||
</h2>
|
|
||||||
<div>
|
<div>
|
||||||
<h2 opacity="90" text="base" font="bold" p="1">
|
<h2 m="t-4" text="xl" font="bold" p="1">
|
||||||
🥬 菜菜们
|
🥘 先选一下食材
|
||||||
</h2>
|
</h2>
|
||||||
<VegetableTag
|
<div>
|
||||||
v-for="item, i in vegetable" :key="i"
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
:active="curStuff.includes(item.name)"
|
🥬 菜菜们
|
||||||
@click="toggleStuff(item, 'vegetable')"
|
</h2>
|
||||||
>
|
<VegetableTag
|
||||||
<span v-if="item.emoji" class="inline-flex">{{ item.emoji }}</span>
|
v-for="item, i in vegetable" :key="i"
|
||||||
<span v-else-if="item.image" class="inline-flex">
|
:active="curStuff.includes(item.name)"
|
||||||
<img class="inline-flex" w="2" h="2" width="10" height="10" :src="item.image" :alt="item.name">
|
@click="toggleStuff(item, 'vegetable')"
|
||||||
</span>
|
>
|
||||||
<span class="inline-flex" m="l-1">
|
<span v-if="item.emoji" class="inline-flex">{{ item.emoji }}</span>
|
||||||
{{
|
<span v-else-if="item.image" class="inline-flex">
|
||||||
item.name
|
<img class="inline-flex" w="2" h="2" width="10" height="10" :src="item.image" :alt="item.name">
|
||||||
}}
|
</span>
|
||||||
</span>
|
<span class="inline-flex" m="l-1">{{ item.name }}</span>
|
||||||
</VegetableTag>
|
</VegetableTag>
|
||||||
</div>
|
</div>
|
||||||
<div m="y-4">
|
<div m="y-4">
|
||||||
<h2 opacity="90" text="base" font="bold" p="1">
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
🥩 肉肉们
|
🥩 肉肉们
|
||||||
</h2>
|
</h2>
|
||||||
<MeatTag
|
<MeatTag
|
||||||
v-for="item, i in meat" :key="i"
|
v-for="item, i in meat" :key="i"
|
||||||
:active="curStuff.includes(item.name)"
|
:active="curStuff.includes(item.name)"
|
||||||
@click="toggleStuff(item, 'meat')"
|
@click="toggleStuff(item, 'meat')"
|
||||||
>
|
>
|
||||||
<span>{{ item.emoji }}</span>
|
<span>{{ item.emoji }}</span>
|
||||||
<span m="l-1">
|
<span m="l-1">{{ item.name }}</span>
|
||||||
{{
|
</MeatTag>
|
||||||
item.name
|
</div>
|
||||||
}}
|
<div m="y-4">
|
||||||
</span>
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
</MeatTag>
|
🍚 主食也要一起下锅吗?(不选也行)
|
||||||
</div>
|
</h2>
|
||||||
<div m="y-4">
|
<StapleTag
|
||||||
<h2 opacity="90" text="base" font="bold" p="1">
|
v-for="item, i in staple" :key="i"
|
||||||
🍚 主食也要一起下锅吗?(不选也行)
|
:active="curStuff.includes(item.name)"
|
||||||
</h2>
|
@click="toggleStuff(item, 'staple')"
|
||||||
<StapleTag
|
>
|
||||||
v-for="item, i in staple" :key="i"
|
<span>{{ item.emoji }}</span>
|
||||||
:active="curStuff.includes(item.name)"
|
<span m="l-1">{{ item.name }}</span>
|
||||||
@click="toggleStuff(item, 'staple')"
|
</StapleTag>
|
||||||
>
|
</div>
|
||||||
<span>{{ item.emoji }}</span>
|
<div m="t-4">
|
||||||
<span m="l-1">
|
<h2 text="xl" font="bold" p="1">
|
||||||
{{
|
🍳 再选一下厨具
|
||||||
item.name
|
</h2>
|
||||||
}}
|
<ToolTag
|
||||||
</span>
|
v-for="item, i in tools" :key="i"
|
||||||
</StapleTag>
|
:active="curTool === item.name"
|
||||||
</div>
|
@click="rStore.clickTool(item)"
|
||||||
<div m="t-4">
|
>
|
||||||
<h2 text="xl" font="bold" p="1">
|
<span v-if="item.emoji" class="inline-flex">
|
||||||
🍳 再选一下厨具
|
{{ item.emoji }}
|
||||||
</h2>
|
</span>
|
||||||
<ToolTag
|
<span v-else-if="item.icon" class="inline-flex">
|
||||||
v-for="item, i in tools" :key="i"
|
<div :class="item.icon" />
|
||||||
:active="curTool === item.name"
|
</span>
|
||||||
@click="rStore.clickTool(item)"
|
<span class="inline-flex" m="l-1">{{ item.label || item.name }}</span>
|
||||||
>
|
</ToolTag>
|
||||||
<span v-if="item.emoji" class="inline-flex">{{ item.emoji }}</span>
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<Transition>
|
<Transition>
|
||||||
<BasketButton ref="recipeBtn" :is-visible="isVisible" @click="show" />
|
<BasketButton ref="recipeBtn" :is-visible="isVisible" @click="show" />
|
||||||
</Transition>
|
</Transition>
|
||||||
<RecipePanel ref="recipePanel" />
|
<div ref="recipePanelRef">
|
||||||
|
<RecipePanel />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const rStore = useRecipeStore()
|
const { random, randomRecipe } = useRandomRecipe()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="inline-flex items-center justify-center">
|
<div v-show="randomRecipe">
|
||||||
今天吃什么?<div class="transition" hover="text-blue-500" i-ri-refresh-line inline-block cursor-pointer @click="rStore.random" />
|
<div class="inline-flex items-center justify-center">
|
||||||
</div>
|
<div>今天吃什么?</div>
|
||||||
<div m="t-2">
|
<div class="transition" hover="text-blue-500" i-ri-refresh-line inline-block cursor-pointer @click="random" />
|
||||||
<DishTag :dish="rStore.randomRecipe" />
|
</div>
|
||||||
|
<div m="t-2">
|
||||||
|
<DishTag v-if="randomRecipe" :dish="randomRecipe" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,13 +6,17 @@ const rStore = useRecipeStore()
|
|||||||
const { displayedRecipe, selectedStuff, curTool } = storeToRefs(rStore)
|
const { displayedRecipe, selectedStuff, curTool } = storeToRefs(rStore)
|
||||||
|
|
||||||
const showSearchInput = ref(false)
|
const showSearchInput = ref(false)
|
||||||
|
|
||||||
|
const showTooltip = computed(() => !selectedStuff.value.length && !curTool.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div m="x-2 y-4" p="2" class="recipe-panel relative shadow transition hover:shadow-md" bg="gray-400/8">
|
<div
|
||||||
<h2 text="xl" font="bold" p="1">
|
class="recipe-panel relative shadow transition hover:shadow-md"
|
||||||
🍲 来看看组合出的菜谱吧!
|
m="x-2 y-4" p="2"
|
||||||
</h2>
|
bg="gray-400/8"
|
||||||
|
>
|
||||||
|
<RecipePanelTitle />
|
||||||
|
|
||||||
<ToggleMode />
|
<ToggleMode />
|
||||||
|
|
||||||
@@ -21,31 +25,34 @@ const showSearchInput = ref(false)
|
|||||||
<div v-else i-ri-search-fill />
|
<div v-else i-ri-search-fill />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- <Switch /> -->
|
|
||||||
<div class="cook-recipes" p="2">
|
<div class="cook-recipes" p="2">
|
||||||
<SearchFoodInput v-if="showSearchInput" />
|
<SearchFoodInput v-if="showSearchInput" />
|
||||||
|
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<div class="cook-filter-recipes">
|
<div class="cook-filter-recipes">
|
||||||
<span v-if="!selectedStuff.length && !curTool" text="sm" p="2">
|
<span v-if="showTooltip" text="sm" p="2">
|
||||||
你要先选食材或工具哦~
|
你要先选食材或工具哦~
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else-if="displayedRecipe.length">
|
<div v-else-if="displayedRecipe.length">
|
||||||
<DishTag v-for="item, i in displayedRecipe" :key="i" :dish="item" />
|
<DishTag v-for="item, i in displayedRecipe" :key="i" :dish="item" />
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<span v-else text="sm">
|
<div v-else text="sm">
|
||||||
还没有完美匹配的菜谱呢……
|
<span>还没有完美匹配的菜谱呢……</span>
|
||||||
<br>
|
<br>
|
||||||
大胆尝试一下,或者<a href="#" @click="rStore.reset()">
|
<span>大胆尝试一下,或者</span>
|
||||||
<strong>换个组合</strong></a>?
|
<a href="#" @click="rStore.reset()">
|
||||||
|
<strong>换个组合</strong>
|
||||||
|
</a>
|
||||||
|
<span>?</span>
|
||||||
<br>
|
<br>
|
||||||
<span m="t-1">欢迎来
|
<div m="t-1">
|
||||||
|
<span>欢迎来</span>
|
||||||
<a class="font-bold text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic" target="_blank">这里</a>
|
<a class="font-bold text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS?tab=uykkic" target="_blank">这里</a>
|
||||||
反馈新的菜谱!
|
<span>反馈新的菜谱!</span>
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
|||||||
5
components/RecipePanelTitle.vue
Normal file
5
components/RecipePanelTitle.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div text="xl" font="bold" p="1">
|
||||||
|
🍲 来看看组合出的菜谱吧!
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
defineProps<{
|
|
||||||
frontmatter: {
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div m="t-4" class="m-auto max-w-900px text-left">
|
|
||||||
<h3 text="center 3xl" font="serif !black">
|
|
||||||
{{ frontmatter?.title }}
|
|
||||||
</h3>
|
|
||||||
<slot class="markdown-body" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
19
composables/recipe.ts
Normal file
19
composables/recipe.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import recipeData from '~/data/recipe.json'
|
||||||
|
import type { RecipeItem, Recipes } from '~/types'
|
||||||
|
|
||||||
|
export function useRandomRecipe() {
|
||||||
|
const randomRecipe = ref<RecipeItem>()
|
||||||
|
function random() {
|
||||||
|
randomRecipe.value = generateRandomRecipe(recipeData as Recipes)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
random()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
random,
|
||||||
|
|
||||||
|
randomRecipe,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,11 @@ import { computed, ref } from 'vue'
|
|||||||
import { useGtm } from '@gtm-support/vue-gtm'
|
import { useGtm } from '@gtm-support/vue-gtm'
|
||||||
import recipeData from '../../data/recipe.json'
|
import recipeData from '../../data/recipe.json'
|
||||||
import type { StuffItem } from '../../data/food'
|
import type { StuffItem } from '../../data/food'
|
||||||
import type { RecipeItem, Recipes } from '~/types'
|
import type { Recipes } from '~/types'
|
||||||
|
|
||||||
const namespace = 'cook'
|
const namespace = 'cook'
|
||||||
|
|
||||||
/**
|
const recipes = recipeData as Recipes
|
||||||
* 生成随机菜谱
|
|
||||||
* @param recipes
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function generateRandomRecipe(recipes: Recipes) {
|
|
||||||
return recipes[Math.floor(Math.random() * recipes.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* survival: 生存模式
|
* survival: 生存模式
|
||||||
@@ -25,7 +18,6 @@ function generateRandomRecipe(recipes: Recipes) {
|
|||||||
export type SearchMode = 'survival' | 'loose' | 'strict'
|
export type SearchMode = 'survival' | 'loose' | 'strict'
|
||||||
|
|
||||||
export const useRecipeStore = defineStore('recipe', () => {
|
export const useRecipeStore = defineStore('recipe', () => {
|
||||||
const recipes = recipeData as Recipes
|
|
||||||
const gtm = useGtm()
|
const gtm = useGtm()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,13 +29,12 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
const curStuff = useStorage(`${namespace}:stuff`, new Set<string>())
|
const curStuff = useStorage(`${namespace}:stuff`, new Set<string>())
|
||||||
// const curTools = ref(new Set<string>())
|
// const curTools = ref(new Set<string>())
|
||||||
const curTool = useStorage(`${namespace}:tool`, '')
|
const curTool = useStorage(`${namespace}:tool`, '')
|
||||||
|
const curMode = useStorage<SearchMode>(`${namespace}:mode`, 'loose')
|
||||||
|
|
||||||
const selectedStuff = computed(() => Array.from(curStuff.value))
|
const selectedStuff = computed(() => Array.from(curStuff.value))
|
||||||
// const selectedTools = computed(() => Array.from(curTools.value))
|
// const selectedTools = computed(() => Array.from(curTools.value))
|
||||||
// const selectedTools = ref('')
|
// const selectedTools = ref('')
|
||||||
|
|
||||||
const curMode = useStorage<SearchMode>(`${namespace}:mode`, 'loose')
|
|
||||||
|
|
||||||
function toggleStuff(name: string) {
|
function toggleStuff(name: string) {
|
||||||
if (!curStuff.value)
|
if (!curStuff.value)
|
||||||
return
|
return
|
||||||
@@ -81,8 +72,6 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
curStuff.value.add(name)
|
curStuff.value.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomRecipe = ref<RecipeItem>(generateRandomRecipe(recipes))
|
|
||||||
|
|
||||||
// 默认严格模式
|
// 默认严格模式
|
||||||
const displayedRecipe = computed(() => {
|
const displayedRecipe = computed(() => {
|
||||||
if (keyword.value)
|
if (keyword.value)
|
||||||
@@ -152,9 +141,6 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
curMode,
|
curMode,
|
||||||
selectedStuff,
|
selectedStuff,
|
||||||
|
|
||||||
randomRecipe,
|
|
||||||
random: () => { randomRecipe.value = generateRandomRecipe(recipes) },
|
|
||||||
|
|
||||||
clearKeyWord: () => { keyword.value = '' },
|
clearKeyWord: () => { keyword.value = '' },
|
||||||
toggleStuff,
|
toggleStuff,
|
||||||
toggleTools,
|
toggleTools,
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ const rStore = useRecipeStore()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div pt-4>
|
<div>
|
||||||
<div text-4xl>
|
<div pt-4 text-4xl>
|
||||||
<button
|
<button
|
||||||
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
||||||
title="重置"
|
title="重置"
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -5535,7 +5535,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
is-core-module: 2.12.1
|
is-core-module: 2.12.1
|
||||||
resolve: 1.22.3
|
resolve: 1.22.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
10
utils/random.ts
Normal file
10
utils/random.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Recipes } from '../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机菜谱
|
||||||
|
* @param recipes
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function generateRandomRecipe(recipes: Recipes) {
|
||||||
|
return recipes[Math.floor(Math.random() * recipes.length)]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user