feat: add survival mode
This commit is contained in:
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -17,6 +17,7 @@ declare module '@vue/runtime-core' {
|
|||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
StapleTag: typeof import('./components/tags/StapleTag.vue')['default']
|
StapleTag: typeof import('./components/tags/StapleTag.vue')['default']
|
||||||
Switch: typeof import('./components/Switch.vue')['default']
|
Switch: typeof import('./components/Switch.vue')['default']
|
||||||
|
ToggleMode: typeof import('./components/ToggleMode.vue')['default']
|
||||||
ToolTag: typeof import('./components/tags/ToolTag.vue')['default']
|
ToolTag: typeof import('./components/tags/ToolTag.vue')['default']
|
||||||
VegetableTag: typeof import('./components/tags/VegetableTag.vue')['default']
|
VegetableTag: typeof import('./components/tags/VegetableTag.vue')['default']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useGtm } from '@gtm-support/vue-gtm'
|
import { useGtm } from '@gtm-support/vue-gtm'
|
||||||
import { isClient } from '@vueuse/core'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import Switch from './Switch.vue'
|
|
||||||
import type { StuffItem } from '~/data/food'
|
import type { StuffItem } from '~/data/food'
|
||||||
import { meat, staple, tools, vegetable } from '~/data/food'
|
import { meat, staple, tools, vegetable } from '~/data/food'
|
||||||
import recipeData from '~/data/recipe.json'
|
import recipeData from '~/data/recipe.json'
|
||||||
@@ -10,22 +8,25 @@ import type { Recipe } from '~/types'
|
|||||||
import { useRecipeStore } from '~/stores/recipe'
|
import { useRecipeStore } from '~/stores/recipe'
|
||||||
|
|
||||||
import { useInvisibleElement } from '~/composables/helper'
|
import { useInvisibleElement } from '~/composables/helper'
|
||||||
|
import { useEmojiAnimation } from '~/composables/animation'
|
||||||
|
|
||||||
const recipe = ref<Recipe>(recipeData as Recipe)
|
const recipe = ref<Recipe>(recipeData as Recipe)
|
||||||
|
|
||||||
const rStore = useRecipeStore()
|
const rStore = useRecipeStore()
|
||||||
const { strict, curTool } = storeToRefs(rStore)
|
const { curMode, curTool } = storeToRefs(rStore)
|
||||||
const curStuff = computed(() => rStore.selectedStuff)
|
const curStuff = computed(() => rStore.selectedStuff)
|
||||||
|
|
||||||
// 默认严格模式
|
// 默认严格模式
|
||||||
const displayedRecipe = computed(() => {
|
const displayedRecipe = computed(() => {
|
||||||
const recipes = recipe.value.filter((item) => {
|
if (curMode.value === 'strict') {
|
||||||
if (strict.value) {
|
return recipe.value.filter((item) => {
|
||||||
const stuffFlag = curStuff.value.every(stuff => item.stuff.includes(stuff))
|
const stuffFlag = curStuff.value.every(stuff => item.stuff.includes(stuff))
|
||||||
const toolFlag = item.tools?.includes(curTool.value)
|
const toolFlag = item.tools?.includes(curTool.value)
|
||||||
return curTool.value ? stuffFlag && toolFlag : stuffFlag
|
return curTool.value ? stuffFlag && toolFlag : stuffFlag
|
||||||
|
})
|
||||||
}
|
}
|
||||||
else {
|
else if (curMode.value === 'loose') {
|
||||||
|
return recipe.value.filter((item) => {
|
||||||
const stuffFlag = curStuff.value.some(stuff => item.stuff.includes(stuff))
|
const stuffFlag = curStuff.value.some(stuff => item.stuff.includes(stuff))
|
||||||
const toolFlag = item.tools?.includes(curTool.value)
|
const toolFlag = item.tools?.includes(curTool.value)
|
||||||
|
|
||||||
@@ -41,47 +42,20 @@ const displayedRecipe = computed(() => {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// survival
|
||||||
|
else {
|
||||||
|
return recipe.value.filter((item) => {
|
||||||
|
const stuffFlag = item.stuff.every(stuff => curStuff.value.includes(stuff))
|
||||||
|
const toolFlag = item.tools?.includes(curTool.value)
|
||||||
|
return curTool.value ? stuffFlag && toolFlag : stuffFlag
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return recipes
|
|
||||||
})
|
|
||||||
|
|
||||||
const { x, y } = usePointer()
|
|
||||||
|
|
||||||
const recipeBtn = ref<HTMLButtonElement>()
|
const recipeBtn = ref<HTMLButtonElement>()
|
||||||
|
const { playAnimation } = useEmojiAnimation(recipeBtn)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gtm = useGtm()
|
const gtm = useGtm()
|
||||||
|
|
||||||
@@ -233,7 +207,9 @@ const { isVisible, show } = useInvisibleElement(recipePanel)
|
|||||||
<div i-ri-compass-line />
|
<div i-ri-compass-line />
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<Switch />
|
<ToggleMode />
|
||||||
|
|
||||||
|
<!-- <Switch /> -->
|
||||||
<div p="2">
|
<div p="2">
|
||||||
<Transition mode="out-in">
|
<Transition mode="out-in">
|
||||||
<span v-if="!curStuff.length && !curTool" text="sm" p="2">
|
<span v-if="!curStuff.length && !curTool" text="sm" p="2">
|
||||||
|
|||||||
34
src/components/ToggleMode.vue
Normal file
34
src/components/ToggleMode.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SearchMode } from '~/stores/recipe'
|
||||||
|
import { useRecipeStore } from '~/stores/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="tag rounded px-2"
|
||||||
|
: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>
|
||||||
42
src/composables/animation.ts
Normal file
42
src/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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@ const namespace = 'cook'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* survival: 生存模式
|
* survival: 生存模式
|
||||||
|
* strict: 严格
|
||||||
|
* loose: 模糊
|
||||||
*/
|
*/
|
||||||
type CookMode = 'survival' | ''
|
export type SearchMode = 'survival' | 'loose' | 'strict'
|
||||||
|
|
||||||
export const useRecipeStore = defineStore('recipe', () => {
|
export const useRecipeStore = defineStore('recipe', () => {
|
||||||
const strict = useStorage(`${namespace}:strict`, false)
|
|
||||||
|
|
||||||
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`, '')
|
||||||
@@ -18,7 +18,7 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
// const selectedTools = computed(() => Array.from(curTools.value))
|
// const selectedTools = computed(() => Array.from(curTools.value))
|
||||||
// const selectedTools = ref('')
|
// const selectedTools = ref('')
|
||||||
|
|
||||||
const mode = ref<CookMode>('')
|
const curMode = useStorage<SearchMode>(`${namespace}:mode`, 'loose')
|
||||||
|
|
||||||
function toggleStuff(name: string) {
|
function toggleStuff(name: string) {
|
||||||
if (!curStuff)
|
if (!curStuff)
|
||||||
@@ -40,6 +40,10 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
// curTools.value.add(name)
|
// curTools.value.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMode(mode: SearchMode) {
|
||||||
|
curMode.value = mode
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置
|
* 重置
|
||||||
*/
|
*/
|
||||||
@@ -50,12 +54,14 @@ export const useRecipeStore = defineStore('recipe', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
strict,
|
|
||||||
curTool,
|
curTool,
|
||||||
|
curMode,
|
||||||
selectedStuff,
|
selectedStuff,
|
||||||
|
|
||||||
toggleStuff,
|
toggleStuff,
|
||||||
toggleTools,
|
toggleTools,
|
||||||
reset,
|
reset,
|
||||||
|
setMode,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user