FoodOrderer: Basics

This commit is contained in:
Kagura 2024-11-22 18:14:22 +08:00
parent 7d395f5d15
commit 4fefa48079
3 changed files with 98 additions and 37 deletions

View file

@ -19,6 +19,7 @@
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },
"devDependencies": { "devDependencies": {
"decimal.js": "^10.4.3",
"react-router-dom": "^6.28.0", "react-router-dom": "^6.28.0",
"tailwindcss": "^3.4.15" "tailwindcss": "^3.4.15"
}, },

View file

@ -1,72 +1,100 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Decimal from 'decimal.js';
interface ShopItem { interface ShopItem {
name: string, name: string,
need: number, // 需求数量 need: number, // 需求数量
provide: number, // 提供的数量 provider: {
name: string
price: number
storage: number // 库存
}, // 提供者的信息
image: { image: {
src: string src: string
} }
} }
const getNeedPrice = (item: ShopItem) => {
let result = new Decimal(item.provider.price).times(item.need)
return result.toNumber()
}
const shopItems: ShopItem[] = [ const shopItems: ShopItem[] = [
{ {
name: "Apple", name: "Apple",
need: 10, need: 999,
provide: 5, provider: {
name: "Fresh Farms",
price: 2.5,
storage: 1000,
},
image: { image: {
src: "https://i.imgur.com/FsNUv2x.jpeg" src: "https://i.imgur.com/FsNUv2x.jpeg",
} },
}, },
{ {
name: "Orange Jam", name: "Orange Jam",
need: 8, need: 8,
provide: 10, provider: {
name: "Sweet Treats Co.",
price: 4.0,
storage: 50,
},
image: { image: {
src: "https://i.imgur.com/hB46Z9s.png" src: "https://i.imgur.com/hB46Z9s.png",
} },
}, },
{ {
name: "Milk (Gone bad)", name: "Milk (Gone bad)",
need: 0, need: 0,
provide: 3, provider: {
name: "Dairy Delights",
price: 1.2,
storage: 20,
},
image: { image: {
src: "https://i.imgur.com/ZV7vzDu.png" src: "https://i.imgur.com/ZV7vzDu.png",
} },
}, },
{ {
name: "Bread", name: "Bread",
need: 12, need: 12,
provide: 6, provider: {
name: "Baker's Best",
price: 3.0,
storage: 30,
},
image: { image: {
src: "https://i.imgur.com/qBn8jWS.jpeg" src: "https://i.imgur.com/qBn8jWS.jpeg",
} },
} },
]; ];
function FoodList({ list, setList }: { function FoodList({ list, setList }: {
list: ShopItem[], list: ShopItem[],
setList: React.Dispatch<React.SetStateAction<ShopItem[]>> setList: React.Dispatch<React.SetStateAction<ShopItem[]>>
}) { }) {
enum ButtonOperation {INCREASE , DECREASE} enum ButtonOperation { INCREASE, DECREASE }
const handleVolumeButton = (item: ShopItem ,operation: ButtonOperation) => { const handleVolumeButton = (item: ShopItem, operation: ButtonOperation) => {
const nowNeed = item.need const nowNeed = item.need
switch (operation) { switch (operation) {
case ButtonOperation.INCREASE: case ButtonOperation.INCREASE:
if (nowNeed >= item.provide){ if (nowNeed >= item.provider.storage) {
return return
} }
setList(list.map((it)=> setList(list.map((it) =>
it == item? {...it,need: nowNeed+1}:it it == item ? { ...it, need: nowNeed + 1 } : it
)) ))
break break
case ButtonOperation.DECREASE: case ButtonOperation.DECREASE:
if (nowNeed <= 0){ if (nowNeed <= 0) {
return return
} }
setList(list.map((it)=> setList(list.map((it) =>
it == item? {...it,need: nowNeed-1}:it it == item ? { ...it, need: nowNeed - 1 } : it
)) ))
break break
} }
@ -76,21 +104,51 @@ function FoodList({ list, setList }: {
return ( return (
<div className="table gap-3 min-w-max space-y-3 mx-auto items-center"> <div className="table gap-3 min-w-max space-y-3 mx-auto items-center">
{list.map((item) => {list.map((item) =>
<div key={item.name} className="flex gap-5 bg-slate-200 rounded-md shadow-md border-spacing-1 px-5 py-2"> <div
key={item.name}
className="flex gap-5 bg-slate-200 rounded-md shadow-md border-spacing-1 px-5 py-3 relative"
>
<img className="size-24 rounded-full" {...item.image} /> <img className="size-24 rounded-full" {...item.image} />
<div> <div className="flex-1">
<p className="text-3xl">{item.name}</p> <p className="text-3xl">{item.name}</p>
<div className="flex"> <p className="text-base text-gray-400">{item.provider.name}</p>
<button <p className="text-sm text-zinc-500">{item.provider.price} /</p>
onClick={()=>handleVolumeButton(item,ButtonOperation.DECREASE)} <p className="text-sm text-neutral-500">: {item.provider.storage}</p>
className="size-6 bg-lime-50 rounded-2xl">-</button>
<p>{item.need}</p> </div>
<button
onClick={()=>handleVolumeButton(item,ButtonOperation.INCREASE)} <div className="flex flex-col justify-between items-end gap-2">
className="size-6 bg-red-50 rounded-2xl">+</button> <p className="text-xl text-stone-700 text-center">{getNeedPrice(item)} </p>
<div className="flex items-center gap-1">
<button
onClick={() => handleVolumeButton(item, ButtonOperation.DECREASE)}
className="w-8 h-8 flex items-center justify-center bg-lime-50 rounded-full text-lg font-bold"
>
-
</button>
<input
type="number"
value={item.need}
onChange={(e) => {
const value = parseInt(e.target.value, 10) || 0;
if (value <= item.provider.storage && value >= 0) {
setList(list.map((it) =>
it == item ? { ...it, need: value } : it
))
}
}}
className="text-center w-16 h-8 bg-gray-50 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-blue-400 text-base appearance-none pl-1.5"
/>
<button
onClick={() => handleVolumeButton(item, ButtonOperation.INCREASE)}
className="w-8 h-8 flex items-center justify-center bg-red-50 rounded-full text-lg font-bold"
>
+
</button>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
) )
@ -100,8 +158,10 @@ function FoodList({ list, setList }: {
const FoodOrderer = () => { const FoodOrderer = () => {
const [items, setItems] = useState<ShopItem[]>(shopItems) const [items, setItems] = useState<ShopItem[]>(shopItems)
return ( return (
<div className="items-center"> <div className="flex flex-col items-center">
<p className="text-center"></p> <p className="text-center text-2xl sm:text-5xl py-5 sm:py-10 bg-gradient-to-r from-blue-600 to-pink-600 via-green-700 inline-block text-transparent bg-clip-text">
</p>
<FoodList list={items} setList={setItems} /> <FoodList list={items} setList={setItems} />
</div> </div>
) )

View file

@ -3616,7 +3616,7 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
decimal.js@^10.2.1: decimal.js@^10.2.1, decimal.js@^10.4.3:
version "10.4.3" version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==