Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5efd7b0533 | ||
|
|
a80d9163b8 | ||
|
|
d25148c731 |
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
SD_API_BASE_URL=
|
||||
OPENAI_API_KEY=
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 16.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 16.x
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [lts/*]
|
||||
node-version: [16.x]
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: false
|
||||
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
# after pnpm
|
||||
- name: Use Node.js LTS
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
cache: pnpm
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,4 +17,3 @@ dist
|
||||
.nuxt
|
||||
.env
|
||||
.idea/
|
||||
.vercel
|
||||
|
||||
2
.npmrc
2
.npmrc
@@ -1,5 +1,3 @@
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
shell-emulator=true
|
||||
auto-install-peers=false
|
||||
ignore-workspace-root-check=true
|
||||
|
||||
48
.vscode/settings.json
vendored
48
.vscode/settings.json
vendored
@@ -19,6 +19,8 @@
|
||||
"*.css": "postcss"
|
||||
},
|
||||
|
||||
// Enable the ESlint flat config support
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
@@ -29,17 +31,43 @@
|
||||
},
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off" },
|
||||
{ "rule": "*-indent", "severity": "off" },
|
||||
{ "rule": "*-spacing", "severity": "off" },
|
||||
{ "rule": "*-spaces", "severity": "off" },
|
||||
{ "rule": "*-order", "severity": "off" },
|
||||
{ "rule": "*-dangle", "severity": "off" },
|
||||
{ "rule": "*-newline", "severity": "off" },
|
||||
{ "rule": "*quotes", "severity": "off" },
|
||||
{ "rule": "*semi", "severity": "off" }
|
||||
{
|
||||
"rule": "style/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-indent",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spacing",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spaces",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-order",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-dangle",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-newline",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*quotes",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*semi",
|
||||
"severity": "off"
|
||||
}
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
|
||||
### 网页版本
|
||||
|
||||
- 网站链接(Cloudflare):[cook.yunyoujun.cn](https://cook.yunyoujun.cn)
|
||||
- 国内加速(腾讯云):[cook.yunle.fun](https://cook.yunle.fun)
|
||||
- 备用(Netlify):[cook.yyj.moe](https://cook.yyj.moe)
|
||||
- 开发版(Vercel):[cook.yunle.app](https://cook.yunle.app)
|
||||
- 网站链接:[cook.yunyoujun.cn](https://cook.yunyoujun.cn)
|
||||
- 备用:[cook.yyj.moe](https://cook.yyj.moe)
|
||||
- 开发版:[cook.yunle.app](https://cook.yunle.app)
|
||||
|
||||
### 小程序版本
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useIndexedDB } from '~/composables/db'
|
||||
import { appName } from '~/constants'
|
||||
import { installPrompt } from './utils/pwa'
|
||||
import { appName } from '~/constants'
|
||||
|
||||
// https://nuxt.com/docs/api/composables/use-head
|
||||
useHead({
|
||||
@@ -1,29 +0,0 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="tagSize pureTag rounded"
|
||||
p="x-2"
|
||||
border="~ yellow-200 dark:yellow-800"
|
||||
bg="yellow-300 opacity-20"
|
||||
text="yellow-800 dark:yellow-200"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<HelpAbout />
|
||||
|
||||
<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 />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,24 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
const rStore = useRecipeStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<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 />
|
||||
</div>
|
||||
</template>
|
||||
@@ -8,10 +8,7 @@ const props = defineProps({
|
||||
const rStore = useRecipeStore()
|
||||
const { displayedRecipe } = storeToRefs(rStore)
|
||||
|
||||
/**
|
||||
* Show basket button if there are recipes in the basket
|
||||
*/
|
||||
const showBasketBtn = computed(() => {
|
||||
const showBasketBtn = computed(async () => {
|
||||
return displayedRecipe.value.length !== rStore.recipesLength && props.isVisible
|
||||
})
|
||||
</script>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StuffItem } from '~/types'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useEmojiAnimation } from '~/composables/animation'
|
||||
|
||||
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)
|
||||
178
components/ai/AiChooseFood.vue
Normal file
178
components/ai/AiChooseFood.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StuffItem } from '~/types'
|
||||
import { meat, staple, vegetable } from '~/data/food'
|
||||
|
||||
import { useEmojiAnimation } from '~/composables/animation'
|
||||
import type { AIRecipeInfo } from '~/packages/ai/src'
|
||||
import { generateRecipeInfo, getRecipeImage } from '~/utils/api'
|
||||
|
||||
const rStore = useRecipeStore()
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
// cook recipe
|
||||
const cooking = ref(false)
|
||||
const recipeImg = ref('')
|
||||
|
||||
const aiRecipeInfo = ref<AIRecipeInfo>({
|
||||
名称: '名称',
|
||||
介绍: '介绍',
|
||||
})
|
||||
|
||||
async function cook() {
|
||||
cooking.value = true
|
||||
const foods = rStore.selectedStuff
|
||||
|
||||
// reset
|
||||
aiRecipeInfo.value = ({
|
||||
名称: '起名中...',
|
||||
介绍: '正在思考怎么介绍...',
|
||||
})
|
||||
recipeImg.value = ''
|
||||
|
||||
// generate
|
||||
const [info, img] = await Promise.all([generateRecipeInfo(foods), getRecipeImage(foods)])
|
||||
aiRecipeInfo.value = info
|
||||
recipeImg.value = img
|
||||
|
||||
cooking.value = false
|
||||
}
|
||||
</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>
|
||||
|
||||
<button
|
||||
m-auto
|
||||
flex items-center justify-center
|
||||
class="rounded bg-yellow px-4 py-2 text-orange-900 font-black shadow hover:shadow-md active:shadow-inset"
|
||||
@click="cook()"
|
||||
>
|
||||
<div v-if="cooking" class="mr-2 inline-flex" i-svg-spinners:clock />
|
||||
<span>做黑暗料理 🥘</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="recipe-panel relative shadow transition"
|
||||
m="x-2 y-4" p="2"
|
||||
bg="gray-400/8"
|
||||
>
|
||||
<div text="xl" font="bold" p="1">
|
||||
「{{ aiRecipeInfo['名称'] }}」
|
||||
</div>
|
||||
|
||||
<div class="cook-recipes text-center" p="2">
|
||||
<img
|
||||
v-if="recipeImg"
|
||||
class="m-auto w-25 rounded shadow transition hover:shadow-md"
|
||||
:src="recipeImg"
|
||||
alt="recipes"
|
||||
>
|
||||
<div v-else class="m-auto h-25 w-25 rounded bg-gray shadow transition hover:shadow-md" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ aiRecipeInfo['介绍'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -25,7 +25,7 @@ const filteredRecipes = computedAsync(async () => {
|
||||
|
||||
<template>
|
||||
<YlfIconButton
|
||||
absolute right-4 top-4
|
||||
absolute right-3 top-4
|
||||
class="icon-btn hover:text-yellow-400 !outline-none"
|
||||
text-xl
|
||||
title="切换" @click="openModal"
|
||||
@@ -89,9 +89,9 @@ const filteredRecipes = computedAsync(async () => {
|
||||
@click="keyword = ''"
|
||||
/>
|
||||
</div>
|
||||
<button op="70" ml-2 inline-flex cursor-pointer text-base @click="closeModal">
|
||||
<div op="70" ml-2 inline-flex cursor-pointer text-base @click="closeModal">
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<div flex="~ col grow" overflow="auto" class="mt-2" text-xs>
|
||||
<DishTag v-for="item, i in filteredRecipes" :key="i" :dish="item" />
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { VueAboutMe } from 'vue-about-me'
|
||||
import VueAboutMe from 'vue-about-me'
|
||||
import 'vue-about-me/style.css'
|
||||
|
||||
const color = useColorMode()
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import pkg from '~/../package.json'
|
||||
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
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RecipeItem } from '~/types'
|
||||
import type { DbRecipeItem } from '~/utils/db'
|
||||
import { recipeHistories } from '~/composables/store/history'
|
||||
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
|
||||
@@ -5,12 +5,12 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<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 />
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
@@ -5,11 +5,11 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<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 />
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
@@ -5,12 +5,12 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<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 />
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
@@ -5,12 +5,12 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<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 />
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { isClient } from '@vueuse/core'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export function useEmojiAnimation(recipeBtn: Ref<HTMLButtonElement | undefined>) {
|
||||
const { x, y } = usePointer()
|
||||
@@ -1,6 +1,5 @@
|
||||
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)
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DbRecipeItem } from '~/utils/db'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { namespace } from '~/constants'
|
||||
import type { DbRecipeItem } from '~/utils/db'
|
||||
|
||||
/**
|
||||
* 随机几道菜
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { namespace } from '../../constants'
|
||||
import { defaultSettings } from '../../utils/settings'
|
||||
import { namespace } from '../../constants'
|
||||
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
const deferredPrompt = ref<Event | any>()
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RecipeItem } from '~/types'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { namespace } from '~/constants'
|
||||
import type { RecipeItem } from '~/types'
|
||||
|
||||
export interface RecipeHistoryItem {
|
||||
recipe: RecipeItem
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { RecipeItem, StuffItem } from '~/types'
|
||||
import { useGtm } from '@gtm-support/vue-gtm'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
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'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
||||
import process from 'node:process'
|
||||
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
||||
import { appDescription, appName } from '../constants/index'
|
||||
|
||||
const scope = '/'
|
||||
@@ -7,63 +7,78 @@ export const vegetable: StuffItem[] = [
|
||||
{
|
||||
name: '土豆',
|
||||
emoji: '🥔',
|
||||
en: 'potato',
|
||||
},
|
||||
{
|
||||
name: '胡萝卜',
|
||||
emoji: '🥕',
|
||||
en: 'carrot',
|
||||
},
|
||||
{
|
||||
name: '花菜',
|
||||
emoji: '🥦',
|
||||
en: 'cauliflower',
|
||||
},
|
||||
{
|
||||
name: '白萝卜',
|
||||
emoji: '🥣',
|
||||
en: 'radish',
|
||||
},
|
||||
{
|
||||
name: '西葫芦',
|
||||
emoji: '🥒',
|
||||
en: 'zucchini',
|
||||
},
|
||||
{
|
||||
name: '番茄',
|
||||
emoji: '🍅',
|
||||
alias: '西红柿',
|
||||
en: 'tomato',
|
||||
},
|
||||
{
|
||||
name: '芹菜',
|
||||
emoji: '🥬',
|
||||
en: 'celery',
|
||||
},
|
||||
{
|
||||
name: '黄瓜',
|
||||
emoji: '🥒',
|
||||
en: 'cucumber',
|
||||
},
|
||||
{
|
||||
name: '洋葱',
|
||||
emoji: '🧅',
|
||||
en: 'onion',
|
||||
},
|
||||
{
|
||||
name: '莴笋',
|
||||
emoji: '🎍',
|
||||
en: 'lettuce',
|
||||
},
|
||||
{
|
||||
name: '菌菇',
|
||||
emoji: '🍄',
|
||||
en: 'mushroom',
|
||||
},
|
||||
{
|
||||
name: '茄子',
|
||||
emoji: '🍆',
|
||||
en: 'eggplant',
|
||||
},
|
||||
{
|
||||
name: '豆腐',
|
||||
emoji: '🍲',
|
||||
en: 'tofu',
|
||||
},
|
||||
{
|
||||
name: '包菜',
|
||||
emoji: '🥗',
|
||||
en: 'cabbage',
|
||||
},
|
||||
{
|
||||
name: '白菜',
|
||||
emoji: '🥬',
|
||||
en: 'cabbage',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -74,42 +89,52 @@ export const meat: StuffItem[] = [
|
||||
{
|
||||
name: '午餐肉',
|
||||
emoji: '🥓',
|
||||
en: 'bacon',
|
||||
},
|
||||
{
|
||||
name: '香肠',
|
||||
emoji: '🌭',
|
||||
en: 'sausage',
|
||||
},
|
||||
{
|
||||
name: '腊肠',
|
||||
emoji: '🌭',
|
||||
en: 'sausage',
|
||||
},
|
||||
{
|
||||
name: '鸡肉',
|
||||
emoji: '🐤',
|
||||
en: 'chicken',
|
||||
},
|
||||
{
|
||||
name: '猪肉',
|
||||
emoji: '🐷',
|
||||
en: 'pork',
|
||||
},
|
||||
{
|
||||
name: '鸡蛋',
|
||||
emoji: '🥚',
|
||||
en: 'egg',
|
||||
},
|
||||
{
|
||||
name: '虾',
|
||||
emoji: '🦐',
|
||||
en: 'shrimp',
|
||||
},
|
||||
{
|
||||
name: '牛肉',
|
||||
emoji: '🐮',
|
||||
en: 'beef',
|
||||
},
|
||||
{
|
||||
name: '骨头',
|
||||
emoji: '🦴',
|
||||
en: 'bone',
|
||||
},
|
||||
{
|
||||
name: '鱼(Todo)',
|
||||
emoji: '🐟',
|
||||
en: 'fish',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -120,18 +145,22 @@ export const staple: StuffItem[] = [
|
||||
{
|
||||
name: '面食',
|
||||
emoji: '🍝',
|
||||
en: 'noodles',
|
||||
},
|
||||
{
|
||||
name: '面包',
|
||||
emoji: '🍞',
|
||||
en: 'bread',
|
||||
},
|
||||
{
|
||||
name: '米',
|
||||
emoji: '🍚',
|
||||
en: 'rice',
|
||||
},
|
||||
{
|
||||
name: '方便面',
|
||||
emoji: '🍜',
|
||||
en: 'instant noodles',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config'
|
||||
import nuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default nuxt(
|
||||
antfu(
|
||||
export default antfu(
|
||||
{
|
||||
unocss: true,
|
||||
formatters: true,
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'app/data/*.json',
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<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 />
|
||||
<DarkToggle absolute left-3 top-5 />
|
||||
<SearchRecipe />
|
||||
<TheBottomMenu fixed bottom-0 left-0 right-0 />
|
||||
</main>
|
||||
6
layouts/preview.vue
Normal file
6
layouts/preview.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<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-3 top-4 />
|
||||
</main>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
import process from 'node:process'
|
||||
import { pwa } from './app/config/pwa'
|
||||
import { appDescription } from './app/constants/index'
|
||||
import { pwa } from './config/pwa'
|
||||
import { appDescription } from './constants/index'
|
||||
|
||||
Object.assign(process.env, {
|
||||
VITE_COMMIT_REF: process.env.CF_PAGES_COMMIT_SHA || '',
|
||||
@@ -9,50 +9,29 @@ Object.assign(process.env, {
|
||||
// add build time to env
|
||||
import.meta.env.VITE_APP_BUILD_TIME = new Date().getTime().toString()
|
||||
export default defineNuxtConfig({
|
||||
ssr: false,
|
||||
|
||||
modules: [
|
||||
'@vueuse/nuxt',
|
||||
'@unocss/nuxt',
|
||||
'@pinia/nuxt',
|
||||
|
||||
'@nuxt/test-utils/module',
|
||||
'@nuxtjs/color-mode',
|
||||
'@vite-pwa/nuxt',
|
||||
'@nuxt/eslint',
|
||||
'@nuxt/test-utils/module',
|
||||
|
||||
'@zadigetvoltaire/nuxt-gtm',
|
||||
|
||||
'@yunlefun/vue/nuxt',
|
||||
|
||||
// fix QQ in iOS, Done
|
||||
// See https://github.com/unjs/ofetch/pull/366
|
||||
// 'nuxt-fix-ofetch',
|
||||
],
|
||||
ssr: false,
|
||||
|
||||
components: [
|
||||
{ path: '~/components', pathPrefix: false },
|
||||
],
|
||||
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
app: {
|
||||
head: {
|
||||
viewport: 'width=device-width,initial-scale=1',
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no' },
|
||||
{ name: 'description', content: appDescription },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
// when using generate, payload js assets included in sw precache manifest
|
||||
// but missing on offline, disabling extraction it until fixed
|
||||
payloadExtraction: false,
|
||||
// inlineSSRStyles: false,
|
||||
renderJsonPayloads: true,
|
||||
typedPages: true,
|
||||
},
|
||||
|
||||
css: [
|
||||
@@ -65,25 +44,6 @@ export default defineNuxtConfig({
|
||||
classSuffix: '',
|
||||
},
|
||||
|
||||
future: {
|
||||
compatibilityVersion: 4,
|
||||
},
|
||||
|
||||
features: {
|
||||
// For UnoCSS
|
||||
inlineStyles: false,
|
||||
},
|
||||
|
||||
experimental: {
|
||||
// when using generate, payload js assets included in sw precache manifest
|
||||
// but missing on offline, disabling extraction it until fixed
|
||||
payloadExtraction: false,
|
||||
renderJsonPayloads: true,
|
||||
typedPages: true,
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-08-14',
|
||||
|
||||
nitro: {
|
||||
esbuild: {
|
||||
options: {
|
||||
@@ -97,18 +57,32 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
|
||||
eslint: {
|
||||
config: {
|
||||
standalone: false,
|
||||
nuxt: {
|
||||
sortConfigKeys: true,
|
||||
},
|
||||
app: {
|
||||
head: {
|
||||
viewport: 'width=device-width,initial-scale=1',
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no' },
|
||||
{ name: 'description', content: appDescription },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
components: [
|
||||
{ path: '~/components', pathPrefix: false },
|
||||
],
|
||||
|
||||
gtm: {
|
||||
id: 'GTM-5FJSV46',
|
||||
},
|
||||
|
||||
pwa,
|
||||
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
})
|
||||
|
||||
99
package.json
99
package.json
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.2",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"packageManager": "pnpm@8.15.5",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run convert && nuxt build",
|
||||
"build:static": "npm run convert && nuxt generate",
|
||||
"convert": "pnpm -C scripts run convert",
|
||||
"convert": "tsx scripts/convert.ts",
|
||||
"dev": "nuxt dev --host",
|
||||
"dev:pwa": "VITE_PLUGIN_PWA=true nuxi dev",
|
||||
"generate": "nuxt generate",
|
||||
@@ -24,61 +24,50 @@
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.13",
|
||||
"vue-about-me": "^1.4.0"
|
||||
"dayjs": "^1.11.10",
|
||||
"vue-about-me": "^1.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.16.0",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@iconify-json/carbon": "^1.2.5",
|
||||
"@iconify-json/fe": "^1.2.2",
|
||||
"@iconify-json/gg": "^1.2.2",
|
||||
"@iconify-json/ic": "^1.2.2",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/ri": "^1.2.5",
|
||||
"@iconify-json/twemoji": "^1.2.2",
|
||||
"@nuxt/devtools": "^1.7.0",
|
||||
"@nuxt/eslint": "^0.7.5",
|
||||
"@nuxt/test-utils": "^3.15.4",
|
||||
"@nuxtjs/color-mode": "^3.5.2",
|
||||
"@pinia/nuxt": "^0.9.0",
|
||||
"@pinia/testing": "^0.1.7",
|
||||
"@unocss/eslint-config": "^65.4.3",
|
||||
"@unocss/nuxt": "^65.4.3",
|
||||
"@vite-pwa/nuxt": "^0.10.6",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vueuse/nuxt": "^12.5.0",
|
||||
"@antfu/eslint-config": "^2.9.0",
|
||||
"@headlessui/vue": "^1.7.19",
|
||||
"@iconify-json/carbon": "^1.1.31",
|
||||
"@iconify-json/fe": "^1.1.10",
|
||||
"@iconify-json/gg": "^1.1.9",
|
||||
"@iconify-json/ic": "^1.1.17",
|
||||
"@iconify-json/mdi": "^1.1.64",
|
||||
"@iconify-json/ri": "^1.1.20",
|
||||
"@iconify-json/svg-spinners": "^1.1.2",
|
||||
"@iconify-json/twemoji": "^1.1.15",
|
||||
"@nuxt/devtools": "^1.1.3",
|
||||
"@nuxt/test-utils": "^3.12.0",
|
||||
"@nuxtjs/color-mode": "^3.3.3",
|
||||
"@pinia/nuxt": "^0.5.1",
|
||||
"@pinia/testing": "^0.1.3",
|
||||
"@unocss/eslint-config": "^0.58.6",
|
||||
"@unocss/nuxt": "^0.58.6",
|
||||
"@vite-pwa/nuxt": "^0.6.0",
|
||||
"@vue/test-utils": "^2.4.5",
|
||||
"@vueuse/nuxt": "^10.9.0",
|
||||
"@yunlefun/vue": "^0.1.1",
|
||||
"@zadigetvoltaire/nuxt-gtm": "^0.0.13",
|
||||
"bumpp": "^9.10.2",
|
||||
"consola": "^3.4.0",
|
||||
"dexie": "^4.0.11",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-format": "^1.0.1",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"lint-staged": "^15.4.2",
|
||||
"nuxt": "^3.15.2",
|
||||
"pinia": "^2.3.1",
|
||||
"sass": "^1.83.4",
|
||||
"serve": "^14.2.4",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"star-markdown-css": "^0.5.3",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "5.7.2",
|
||||
"unocss": "^65.4.3",
|
||||
"vitest": "^3.0.4",
|
||||
"vue-tsc": "^2.2.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"unplugin": "^2.1.2",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-inspect": "^10.1.0"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "eslint --fix"
|
||||
"bumpp": "^9.4.0",
|
||||
"consola": "^3.2.3",
|
||||
"dexie": "^3.2.7",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-format": "^0.1.0",
|
||||
"fake-indexeddb": "^5.0.2",
|
||||
"happy-dom": "^14.3.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"nuxt": "^3.9.3",
|
||||
"nuxt-vitest": "^0.11.5",
|
||||
"pinia": "^2.1.7",
|
||||
"sass": "^1.72.0",
|
||||
"serve": "^14.2.1",
|
||||
"star-markdown-css": "^0.4.2",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.4.3",
|
||||
"unocss": "^0.58.6",
|
||||
"vitest": "^1.4.0",
|
||||
"vue-tsc": "^2.0.7"
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/ai/.env.example
Normal file
1
packages/ai/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=
|
||||
5
packages/ai/package.json
Normal file
5
packages/ai/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"openai": "^4.29.2"
|
||||
}
|
||||
}
|
||||
197
packages/ai/pnpm-lock.yaml
generated
Normal file
197
packages/ai/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,197 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
devDependencies:
|
||||
openai:
|
||||
specifier: ^4.29.2
|
||||
version: 4.29.2
|
||||
|
||||
packages:
|
||||
|
||||
/@types/node-fetch@2.6.11:
|
||||
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
|
||||
dependencies:
|
||||
'@types/node': 18.19.26
|
||||
form-data: 4.0.0
|
||||
dev: true
|
||||
|
||||
/@types/node@18.19.26:
|
||||
resolution: {integrity: sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
dev: true
|
||||
|
||||
/agentkeepalive@4.5.0:
|
||||
resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
dev: true
|
||||
|
||||
/asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: true
|
||||
|
||||
/base-64@0.1.0:
|
||||
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
|
||||
dev: true
|
||||
|
||||
/charenc@0.0.2:
|
||||
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||
dev: true
|
||||
|
||||
/combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
dev: true
|
||||
|
||||
/crypt@0.0.2:
|
||||
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||
dev: true
|
||||
|
||||
/delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/digest-fetch@1.3.0:
|
||||
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
|
||||
dependencies:
|
||||
base-64: 0.1.0
|
||||
md5: 2.3.0
|
||||
dev: true
|
||||
|
||||
/event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
dev: true
|
||||
|
||||
/form-data@4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
dev: true
|
||||
|
||||
/formdata-node@4.4.1:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
dev: true
|
||||
|
||||
/humanize-ms@1.2.1:
|
||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/is-buffer@1.1.6:
|
||||
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||
dev: true
|
||||
|
||||
/md5@2.3.0:
|
||||
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||
dependencies:
|
||||
charenc: 0.0.2
|
||||
crypt: 0.0.2
|
||||
is-buffer: 1.1.6
|
||||
dev: true
|
||||
|
||||
/mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: true
|
||||
|
||||
/mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
dev: true
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: true
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: true
|
||||
|
||||
/openai@4.29.2:
|
||||
resolution: {integrity: sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.19.26
|
||||
'@types/node-fetch': 2.6.11
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.5.0
|
||||
digest-fetch: 1.3.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.4.1
|
||||
node-fetch: 2.7.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: true
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
dev: true
|
||||
|
||||
/web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: true
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: true
|
||||
|
||||
/whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: true
|
||||
53
packages/ai/src/api.ts
Normal file
53
packages/ai/src/api.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import consola from 'consola'
|
||||
import type OpenAI from 'openai'
|
||||
import { baseChatCompletionCreateParams, baseModel, config, openai } from './config'
|
||||
|
||||
// TODO: pass params
|
||||
|
||||
export async function getCompletion(msg: string) {
|
||||
const chatCompletion = await openai.chat.completions.create({
|
||||
...baseChatCompletionCreateParams,
|
||||
messages: [{ role: 'user', content: msg }],
|
||||
model: baseModel,
|
||||
})
|
||||
|
||||
return chatCompletion.choices
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ai 生成的菜谱信息
|
||||
*/
|
||||
export async function getAIRecipeInfo(zhFoods: string[]) {
|
||||
/**
|
||||
* 限制输入长度
|
||||
*/
|
||||
const promptFoods = zhFoods.join('、').slice(0, config.inputMaxLength)
|
||||
|
||||
// 尽可能少的 token
|
||||
const tooltip = `
|
||||
使用以下材料【${promptFoods}】做一道菜,请为这道菜起个名字,最好带有文化底蕴。
|
||||
不要使用生僻字和标点符号。
|
||||
并给出一个有趣的不超过100字的介绍。
|
||||
格式类型:{
|
||||
"名称": "",
|
||||
"介绍": ""
|
||||
}
|
||||
直接给出可以被 JSON.parse 解析的字符串,不需要解释内容。`
|
||||
|
||||
const messages: OpenAI.ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: tooltip,
|
||||
},
|
||||
]
|
||||
|
||||
const chatCompletion = await openai.chat.completions.create({
|
||||
...baseChatCompletionCreateParams,
|
||||
messages,
|
||||
model: baseModel,
|
||||
// stream: true
|
||||
})
|
||||
|
||||
consola.debug(chatCompletion)
|
||||
return chatCompletion.choices[0].message
|
||||
}
|
||||
26
packages/ai/src/config.ts
Normal file
26
packages/ai/src/config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dotenv/config'
|
||||
import process from 'node:process'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
const deepseekApiUrl = 'https://api.deepseek.com/v1'
|
||||
|
||||
const aiServiceUrl = process.env.AI_SERVICE_URL || deepseekApiUrl
|
||||
|
||||
export const config = {
|
||||
inputMaxLength: 300,
|
||||
}
|
||||
|
||||
export const openai = new OpenAI({
|
||||
baseURL: aiServiceUrl,
|
||||
apiKey: process.env.OPENAI_API_KEY, // This is the default and can be omitted
|
||||
})
|
||||
|
||||
export const baseModel = process.env.MODEL_NAME || 'deepseek-chat'
|
||||
|
||||
export const baseChatCompletionCreateParams: Partial<OpenAI.ChatCompletionCreateParamsNonStreaming> = {
|
||||
max_tokens: 100,
|
||||
// TODO: for use control
|
||||
// presence_penalty: 0,
|
||||
// frequency_penalty: 0,
|
||||
// stream: true
|
||||
}
|
||||
3
packages/ai/src/index.ts
Normal file
3
packages/ai/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './api'
|
||||
export * from './config'
|
||||
export * from './types'
|
||||
7
packages/ai/src/types.ts
Normal file
7
packages/ai/src/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* AI 食谱
|
||||
*/
|
||||
export interface AIRecipeInfo {
|
||||
名称: string
|
||||
介绍: string
|
||||
}
|
||||
25
pages/ai.vue
Normal file
25
pages/ai.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: 'preview',
|
||||
})
|
||||
|
||||
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>
|
||||
|
||||
<AiChooseFood />
|
||||
</template>
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import { defaultCookbook } from '~/utils'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'child',
|
||||
title: '自定义菜谱',
|
||||
165
pages/help.vue
Normal file
165
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
pages/index.vue
Normal file
22
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>
|
||||
@@ -16,21 +16,21 @@ function clearAllHistory() {
|
||||
|
||||
<template>
|
||||
<div pt-2>
|
||||
<button
|
||||
<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"
|
||||
>
|
||||
<span i-ri-eraser-line />
|
||||
<div i-ri-eraser-line />
|
||||
<span class="ml-1">清空记录</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div flex="~ col">
|
||||
<div v-for="history in recipeHistories" :key="history.recipe.name" mt-2>
|
||||
<DateTag>
|
||||
<StapleTag :active="false">
|
||||
{{ dayjs(history.time).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</DateTag>
|
||||
</StapleTag>
|
||||
<DishTag :dish="history.recipe" />
|
||||
</div>
|
||||
</div>
|
||||
20275
pnpm-lock.yaml
generated
20275
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user