u
This commit is contained in:
1 parent
173eb2dd37
commit
26ff2381b7
17 files changed
+596
-305
No files matched your search
+196
-166
@@ -1,177 +1,207 @@
|
||||
import type { FinanceRecord } from '@/types/finance/record'
|
||||
import Big from 'big.js'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Keyboard, Pressable, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'
|
||||
import { ADatePicker, AInput, LucideIcon } from '@/components/common/ao/index'
|
||||
import { useKeyboardHeight } from '@/hooks/useKeyboardHeight'
|
||||
import { getCurrentDate } from '@/utils/time'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>家庭菜单</title>
|
||||
<style>
|
||||
body{
|
||||
margin:0;
|
||||
font-family: sans-serif;
|
||||
background:#f2f2f2;
|
||||
}
|
||||
.header{
|
||||
background:#4CAF50;
|
||||
color:white;
|
||||
padding:15px;
|
||||
text-align:center;
|
||||
font-size:18px;
|
||||
}
|
||||
.page{
|
||||
display:none;
|
||||
padding:15px;
|
||||
padding-bottom:80px;
|
||||
}
|
||||
.page.active{
|
||||
display:block;
|
||||
}
|
||||
.card{
|
||||
background:white;
|
||||
padding:12px;
|
||||
margin-bottom:10px;
|
||||
border-radius:8px;
|
||||
box-shadow:0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
button{
|
||||
background:#4CAF50;
|
||||
border:none;
|
||||
color:white;
|
||||
padding:6px 10px;
|
||||
border-radius:6px;
|
||||
margin-top:5px;
|
||||
}
|
||||
.tabs{
|
||||
position:fixed;
|
||||
bottom:0;
|
||||
width:100%;
|
||||
display:flex;
|
||||
background:white;
|
||||
border-top:1px solid #ddd;
|
||||
}
|
||||
.tab{
|
||||
flex:1;
|
||||
padding:12px;
|
||||
text-align:center;
|
||||
}
|
||||
.tab.active{
|
||||
background:#dcedc8;
|
||||
}
|
||||
input,select{
|
||||
width:100%;
|
||||
padding:6px;
|
||||
margin-top:5px;
|
||||
}
|
||||
.small{
|
||||
font-size:12px;
|
||||
color:#666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
interface KeydownItem {
|
||||
key: string | number
|
||||
content: React.ReactNode
|
||||
<div class="header">家庭做饭助手</div>
|
||||
|
||||
<!-- 菜谱 -->
|
||||
<div class="page active" id="recipePage">
|
||||
<h3>菜谱</h3>
|
||||
<div id="recipeList"></div>
|
||||
</div>
|
||||
|
||||
<!-- 下单 -->
|
||||
<div class="page" id="orderPage">
|
||||
<h3>下单</h3>
|
||||
<div id="orderList"></div>
|
||||
</div>
|
||||
|
||||
<!-- 库存 -->
|
||||
<div class="page" id="inventoryPage">
|
||||
<h3>库存</h3>
|
||||
<input id="name" placeholder="食材名称">
|
||||
<input id="qty" type="number" placeholder="数量">
|
||||
<button onclick="addInventory()">添加主料</button>
|
||||
<div id="inventoryList"></div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchTab(0)">菜谱</div>
|
||||
<div class="tab" onclick="switchTab(1)">下单</div>
|
||||
<div class="tab" onclick="switchTab(2)">库存</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let inventory = [
|
||||
{ id:1, name:"鸡蛋", qty:4 },
|
||||
{ id:2, name:"番茄", qty:2 }
|
||||
];
|
||||
|
||||
let recipes = [
|
||||
{
|
||||
id:1,
|
||||
name:"番茄炒蛋",
|
||||
ingredients:[
|
||||
{ name:"鸡蛋", qty:2, type:"main" },
|
||||
{ name:"番茄", qty:1, type:"main" },
|
||||
{ name:"盐", type:"seasoning" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function renderInventory(){
|
||||
let div=document.getElementById("inventoryList");
|
||||
div.innerHTML="";
|
||||
inventory.forEach(i=>{
|
||||
div.innerHTML+=`
|
||||
<div class="card">
|
||||
${i.name} - 剩余 ${i.qty}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
interface AmountKeyboardProps {
|
||||
show?: boolean
|
||||
record: FinanceRecord
|
||||
|
||||
// 可选:点击完成按钮触发
|
||||
onDone?: (record: FinanceRecord) => void
|
||||
function renderRecipes(){
|
||||
let div=document.getElementById("recipeList");
|
||||
div.innerHTML="";
|
||||
recipes.forEach(r=>{
|
||||
div.innerHTML+=`
|
||||
<div class="card">
|
||||
<b>${r.name}</b>
|
||||
<div class="small">
|
||||
${r.ingredients.map(i=> i.type==="main" ? i.name+" "+i.qty : i.name+"(调料)").join(", ")}
|
||||
</div>
|
||||
<button onclick="order(${r.id})">做这道菜</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
export default function AmountKeyboard({
|
||||
show = false,
|
||||
record,
|
||||
onDone,
|
||||
}: AmountKeyboardProps) {
|
||||
// 自定义键盘计算过程
|
||||
const [keydownInput, setKeydownInput] = useState('')
|
||||
// 日期选择
|
||||
const [showDatePicker, setShowDatePicker] = useState(false)
|
||||
// 备注聚焦
|
||||
const [remarkIsFocused, setRemarkIsFocused] = useState(false)
|
||||
// 键盘高度
|
||||
const keyboardHeight = useKeyboardHeight()
|
||||
function order(id){
|
||||
let recipe=recipes.find(r=>r.id===id);
|
||||
let canCook=true;
|
||||
|
||||
const [innerRecord, setInnerRecord] = useState<FinanceRecord>({
|
||||
id: 0,
|
||||
category_id: 0,
|
||||
amount: '',
|
||||
remark: '',
|
||||
record_time: '',
|
||||
member_id: 0,
|
||||
member_name: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setInnerRecord(record)
|
||||
setKeydownInput(record.amount || '0.00')
|
||||
}, [record])
|
||||
|
||||
useEffect(() => {
|
||||
if (!keydownInput) {
|
||||
setInnerRecord({
|
||||
...innerRecord,
|
||||
amount: '0',
|
||||
})
|
||||
return
|
||||
recipe.ingredients.forEach(i=>{
|
||||
if(i.type==="main"){
|
||||
let stock=inventory.find(s=>s.name===i.name);
|
||||
if(!stock || stock.qty<i.qty){
|
||||
canCook=false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
setInnerRecord({
|
||||
...innerRecord,
|
||||
amount: calculateWithBig(keydownInput),
|
||||
})
|
||||
if(!canCook){
|
||||
alert("主料不足,做不了 😢");
|
||||
return;
|
||||
}
|
||||
|
||||
recipe.ingredients.forEach(i=>{
|
||||
if(i.type==="main"){
|
||||
let stock=inventory.find(s=>s.name===i.name);
|
||||
stock.qty-=i.qty;
|
||||
}
|
||||
catch {
|
||||
setInnerRecord({
|
||||
...innerRecord,
|
||||
amount: '0',
|
||||
})
|
||||
}
|
||||
}, [keydownInput])
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
backgroundColor: '#fff',
|
||||
elevation: 20,
|
||||
}}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View className="h-28 flex pl-4 pr-4 justify-center">
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Pressable
|
||||
className="flex flex-row items-center gap-1.5 px-2 py-1.5 rounded-md bg-gray-50 active:opacity-70"
|
||||
onPress={() => setShowDatePicker(true)}
|
||||
>
|
||||
<LucideIcon icon="Calendar" size={16} color="#60a5fa" clickable={false} />
|
||||
<Text className="text-gray-600 text-sm">{innerRecord.record_time}</Text>
|
||||
<LucideIcon icon="ChevronDown" size={12} color="#aaa" clickable={false} />
|
||||
</Pressable>
|
||||
<Text className="text-3xl text-blue-700 text-right">{innerRecord.amount}</Text>
|
||||
</View>
|
||||
|
||||
<Text className="text-gray-400 text-right">{keydownInput}</Text>
|
||||
<AInput
|
||||
value={innerRecord.remark}
|
||||
placeholder="备注"
|
||||
className="bg-[#f2f3f5] border-0"
|
||||
size="sm"
|
||||
onChangeText={(val) => {
|
||||
setInnerRecord({
|
||||
...innerRecord,
|
||||
remark: val,
|
||||
})
|
||||
}}
|
||||
onFocus={() => setRemarkIsFocused(true)}
|
||||
onBlur={() => setRemarkIsFocused(false)}
|
||||
/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<View className="flex flex-row flex-wrap" style={{ height: 220 }}>
|
||||
{keys.map((item, idx) => {
|
||||
const isFirstCol = idx % 4 === 0
|
||||
const isFirstRow = idx < 4
|
||||
const isLastCol = (idx + 1) % 4 === 0
|
||||
const isLastRow = idx >= 12
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={item.key}
|
||||
onPress={() => handlePress(item.key)}
|
||||
style={{
|
||||
width: '25%',
|
||||
height: '25%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: isFirstRow ? 0.3 : 0,
|
||||
borderLeftWidth: isFirstCol ? 0.3 : 0,
|
||||
borderRightWidth: !isLastCol ? 0.3 : 0,
|
||||
borderBottomWidth: !isLastRow ? 0.3 : 0,
|
||||
borderColor: '#ddd',
|
||||
}}
|
||||
>
|
||||
{({ pressed }) => {
|
||||
const defaultBg = item.key === '/' ? '#e6d7b8' : '#fff'
|
||||
const pressedBg = item.key === '/' ? '#f5e9d4' : '#f2f2f2'
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: pressed ? pressedBg : defaultBg,
|
||||
}}
|
||||
/>
|
||||
{item.content}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
{remarkIsFocused && keyboardHeight > 220 && (
|
||||
<View style={{ height: keyboardHeight + 10 - 220 }} />
|
||||
)}
|
||||
|
||||
<ADatePicker
|
||||
pickerTitle="记账时间"
|
||||
mode="date"
|
||||
date={innerRecord.record_time}
|
||||
show={showDatePicker}
|
||||
onClose={() => {
|
||||
setShowDatePicker(false)
|
||||
}}
|
||||
onConfirm={(date) => {
|
||||
setShowDatePicker(false)
|
||||
|
||||
setInnerRecord({
|
||||
...innerRecord,
|
||||
record_time: date,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
alert("开做!🍳");
|
||||
renderInventory();
|
||||
}
|
||||
|
||||
function addInventory(){
|
||||
let name=document.getElementById("name").value;
|
||||
let qty=parseInt(document.getElementById("qty").value);
|
||||
|
||||
if(!name||!qty) return;
|
||||
|
||||
let existing=inventory.find(i=>i.name===name);
|
||||
if(existing){
|
||||
existing.qty+=qty;
|
||||
}else{
|
||||
inventory.push({id:inventory.length+1,name,qty});
|
||||
}
|
||||
|
||||
renderInventory();
|
||||
}
|
||||
|
||||
function switchTab(i){
|
||||
document.querySelectorAll(".page").forEach((p,index)=>{
|
||||
p.classList.toggle("active",index===i);
|
||||
});
|
||||
document.querySelectorAll(".tab").forEach((t,index)=>{
|
||||
t.classList.toggle("active",index===i);
|
||||
});
|
||||
}
|
||||
|
||||
renderInventory();
|
||||
renderRecipes();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user