feat: use indexDB instead of array filter

This commit is contained in:
YunYouJun
2023-07-30 18:25:54 +08:00
parent 70787d3a80
commit c2b1ffdd9e
16 changed files with 181 additions and 88 deletions

View File

@@ -2,9 +2,7 @@
import { installPrompt } from './utils/pwa'
import { appName } from '~/constants'
// https://github.com/vueuse/head
// you can use this to manipulate the document head in any components,
// they will be rendered correctly in the html results with vite-ssg
// https://nuxt.com/docs/api/composables/use-head
useHead({
title: appName,
meta: [
@@ -15,8 +13,11 @@ useHead({
],
})
const indexedDB = useIndexedDB()
onMounted(() => {
installPrompt()
indexedDB.init()
})
</script>

View File

@@ -1,18 +1,21 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import recipes from '~/data/recipe.json'
defineProps({
const props = defineProps({
isVisible: Boolean,
})
const rStore = useRecipeStore()
const { displayedRecipe } = storeToRefs(rStore)
const showBasketBtn = computed(async () => {
return displayedRecipe.value.length !== rStore.recipesLength && props.isVisible
})
</script>
<template>
<button
v-show="displayedRecipe.length !== recipes.length && isVisible"
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="18" right="4"

View File

@@ -9,8 +9,8 @@ const rStore = useRecipeStore()
const { curTool } = storeToRefs(rStore)
const curStuff = computed(() => rStore.selectedStuff)
const recipeBtn = ref<HTMLButtonElement>()
const { playAnimation } = useEmojiAnimation(recipeBtn)
const recipeBtnRef = ref<HTMLButtonElement>()
const { playAnimation } = useEmojiAnimation(recipeBtnRef)
const gtm = useGtm()
const recipePanelRef = ref()
@@ -44,65 +44,73 @@ function toggleStuff(item: StuffItem, category = '', _e?: Event) {
<h2 opacity="90" text="base" font="bold" p="1">
🥬 菜菜们
</h2>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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="recipeBtn" :is-visible="isVisible" @click="show" />
<BasketButton ref="recipeBtnRef" :is-visible="isVisible" @click="show" />
</Transition>
<RecipePanel ref="recipePanelRef" />
</div>

View File

@@ -34,8 +34,8 @@ const showTooltip = computed(() => !selectedStuff.value.length && !curTool.value
你要先选食材或工具哦
</span>
<div v-else-if="displayedRecipe.length">
<DishTag v-for="item, i in displayedRecipe" :key="i" :dish="item" />
<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">

View File

@@ -28,7 +28,7 @@ onMounted(() => {
p="x4 y2"
w="full"
text="center"
bg="transparent"
bg="white dark:dark-800"
border="~ rounded gray-200 dark:gray-700"
class="focus:dark:gray-500"
>

18
composables/db.ts Normal file
View File

@@ -0,0 +1,18 @@
import { useStorage } from '@vueuse/core'
import { lastDbUpdated, namespace } from '~/constants'
export function useIndexedDB() {
const dbUpdated = useStorage(`${namespace}:lastDbUpdated`, lastDbUpdated)
return {
// db,
// initDb,
init: async () => {
const count = await db.recipes.count()
if (!count || dbUpdated.value !== lastDbUpdated) {
dbUpdated.value = lastDbUpdated
initDb()
}
},
}
}

View File

@@ -2,14 +2,11 @@ import { acceptHMRUpdate, defineStore } from 'pinia'
import { useStorage } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useGtm } from '@gtm-support/vue-gtm'
import recipeData from '../../data/recipe.json'
import type { RecipeItem } from 'types'
import type { StuffItem } from '../../data/food'
import type { Recipes } from '~/types'
const namespace = 'cook'
const recipes = recipeData as Recipes
/**
* survival: 生存模式
* strict: 严格
@@ -72,22 +69,27 @@ export const useRecipeStore = defineStore('recipe', () => {
curStuff.value.add(name)
}
// 默认严格模式
const displayedRecipe = computed(() => {
if (keyword.value)
return recipes.filter(item => item.name.includes(keyword.value))
/**
* 搜索菜谱
* @returns
*/
async function searchRecipes() {
if (keyword.value) {
const result = await db.recipes.filter(item => item.name.includes(keyword.value)).toArray()
return result
}
if (curMode.value === 'strict') {
return recipes.filter((item) => {
return await db.recipes.filter((item) => {
const stuffFlag = selectedStuff.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
})
}).toArray()
}
else if (curMode.value === 'loose') {
return recipes.filter((item) => {
return await db.recipes.filter((item) => {
const stuffFlag = selectedStuff.value.some(stuff => item.stuff.includes(stuff))
const toolFlag = item.tools?.includes(curTool.value)
const toolFlag = Boolean(item.tools?.includes(curTool.value))
// 同时存在 厨具和材料,则同时判断
if (curTool.value && selectedStuff.value.length) {
@@ -101,16 +103,22 @@ export const useRecipeStore = defineStore('recipe', () => {
return false
}
})
}).toArray()
}
// survival
else {
return recipes.filter((item) => {
return await db.recipes.filter((item) => {
const stuffFlag = item.stuff.every(stuff => selectedStuff.value.includes(stuff))
const toolFlag = item.tools?.includes(curTool.value)
return curTool.value ? (stuffFlag && toolFlag) : stuffFlag
})
return Boolean(curTool.value ? (stuffFlag && toolFlag) : stuffFlag)
}).toArray()
}
}
// 默认严格模式
const displayedRecipe = ref<RecipeItem[]>([])
watch([keyword, curStuff, curTool, curMode], async () => {
displayedRecipe.value = await searchRecipes()
})
/**
@@ -133,8 +141,17 @@ export const useRecipeStore = defineStore('recipe', () => {
})
}
const recipesLength = ref(0)
onMounted(async () => {
db.recipes.count().then((count) => {
recipesLength.value = count
})
displayedRecipe.value = await searchRecipes()
})
return {
recipes,
recipesLength,
keyword,
curTool,

View File

@@ -1,4 +1,7 @@
export const appName = '隔离食用手册'
export const appDescription = '好的,今天我们来做菜!'
export const namespace = 'cook'
export const lastDbUpdated = '2022-07-27 03:05:02'
export * from './links'

View File

@@ -11,3 +11,9 @@
- tags: 标签
- 杂烩:不会显示所有材料 emoji而是显示 🍲
## 数据库
使用 Dexie.js 重构数据查询
- [Dexie.js](https://github.com/dexie/Dexie.js): A Minimalistic Wrapper for IndexedDB

View File

@@ -15,7 +15,6 @@
电饭煲卤菜(开店级别),鸡肉、鸡蛋、米,BV1ZA411E7KT,简单,小吃,,电饭煲,
电饭煲卤肉蛋豆腐,鸡肉、鸡蛋、豆腐、米,BV1aM4y1L7QR,简单,小吃,,电饭煲,
电饭煲版罗宋汤,牛肉、番茄、洋葱、芹菜、胡萝卜、土豆、包菜、香肠,BV16Q4y1m7nU,简单,杂烩,,电饭煲,
电饭煲版罗宋汤,牛肉、番茄、土豆、包菜、香肠,BV16Q4y1m7nU,,杂烩,,电饭煲,
电饭煲版一只番茄饭,土豆、胡萝卜、香肠、番茄、鸡蛋、米,BV1dj411f7sR,简单,杂烩,,电饭煲,
电饭煲茄子烧肉焖饭,猪肉、茄子、番茄、土豆、洋葱、米,BV18T4y1c7s1,普通,杂烩,,电饭煲,
电饭煲版茶叶蛋,鸡蛋,BV12W411R7LA,,,,电饭煲,
1 name stuff bv difficulty tags methods tools
15 电饭煲卤菜(开店级别) 鸡肉、鸡蛋、米 BV1ZA411E7KT 简单 小吃 电饭煲
16 电饭煲卤肉蛋豆腐 鸡肉、鸡蛋、豆腐、米 BV1aM4y1L7QR 简单 小吃 电饭煲
17 电饭煲版罗宋汤 牛肉、番茄、洋葱、芹菜、胡萝卜、土豆、包菜、香肠 BV16Q4y1m7nU 简单 杂烩 电饭煲
电饭煲版罗宋汤 牛肉、番茄、土豆、包菜、香肠 BV16Q4y1m7nU 杂烩 电饭煲
18 电饭煲版一只番茄饭 土豆、胡萝卜、香肠、番茄、鸡蛋、米 BV1dj411f7sR 简单 杂烩 电饭煲
19 电饭煲茄子烧肉焖饭 猪肉、茄子、番茄、土豆、洋葱、米 BV18T4y1c7s1 普通 杂烩 电饭煲
20 电饭煲版茶叶蛋 鸡蛋 BV12W411R7LA 电饭煲

File diff suppressed because one or more lines are too long

View File

@@ -45,6 +45,7 @@
"@zadigetvoltaire/nuxt-gtm": "^0.0.13",
"consola": "^3.2.3",
"cross-env": "^7.0.3",
"dexie": "^3.2.4",
"eslint": "^8.46.0",
"jsdom": "^22.1.0",
"nuxt": "^3.6.5",

View File

@@ -3,20 +3,18 @@ const rStore = useRecipeStore()
</script>
<template>
<div>
<div pt-4 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 />
<div pt-4 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 />
</template>

8
pnpm-lock.yaml generated
View File

@@ -79,6 +79,9 @@ devDependencies:
cross-env:
specifier: ^7.0.3
version: 7.0.3
dexie:
specifier: ^3.2.4
version: 3.2.4
eslint:
specifier: ^8.46.0
version: 8.46.0
@@ -5160,6 +5163,11 @@ packages:
resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==}
dev: true
/dexie@3.2.4:
resolution: {integrity: sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==}
engines: {node: '>=6.0'}
dev: true
/diff-sequences@29.4.3:
resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}

View File

@@ -36,7 +36,7 @@ export interface RecipeItem {
/**
* 工具
*/
tools?: string[]
tools: string[]
}
export type Recipes = RecipeItem[]

31
utils/db.ts Normal file
View File

@@ -0,0 +1,31 @@
import type { Table } from 'dexie'
import Dexie from 'dexie'
import recipeData from '../data/recipe.json'
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 function initDb() {
db.recipes.bulkPut(
(recipeData as RecipeItem[]).map((item, i) => ({
id: i,
...item,
})),
)
}