main
zzy 3 months ago
parent 4cad75cf45
commit c2fbb54744

@ -0,0 +1,15 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

@ -0,0 +1,17 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-msedge",
"request": "launch",
"name": "Launch Edge against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,54 @@
{
"name": "flower-trading-system",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"antd": "^5.20.6",
"axios": "^1.7.7",
"classnames": "^2.5.1",
"https-browserify": "^1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"stream-http": "^3.2.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
]
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>花卉交易系统</title>
</head>
<body>
<noscript>使用JavaScript运行该系统</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@ -0,0 +1,52 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { Layout, Menu, ConfigProvider } from 'antd';
import { HomeOutlined, SearchOutlined, OrderedListOutlined, ScanOutlined, UploadOutlined } from '@ant-design/icons';
import HomePage from './pages/HomePage';
import SearchPage from './pages/SearchPage';
import ProductDetailPage from './pages/ProductDetailPage';
import OrderPage from './pages/OrderPage';
import ScannerPage from './pages/ScannerPage';
import UploadFlowerPage from './pages/UploadFlowerPage'; // 新增
import { theme } from './theme';
const { Header, Content, Footer } = Layout;
function App() {
return (
<ConfigProvider theme={theme}>
<Router>
<Layout className="layout" style={{ minHeight: '100vh' }}>
<Header style={{ background: theme.token.colorPrimary }}>
<div className="logo" />
<Menu mode="horizontal" defaultSelectedKeys={['1']} style={{ background: '#fff', borderBottom: 'none' }}>
<Menu.Item key="1" icon={<HomeOutlined />}><Link to="/">主页面</Link></Menu.Item>
<Menu.Item key="2" icon={<SearchOutlined />}><Link to="/search">搜索</Link></Menu.Item>
<Menu.Item key="3" icon={<OrderedListOutlined />}><Link to="/orders">我的订单</Link></Menu.Item>
<Menu.Item key="5" icon={<UploadOutlined />}><Link to="/upload">上传花卉</Link></Menu.Item>
<Menu.Item key="4" icon={<ScanOutlined />}><Link to="/scanner">识别花卉</Link></Menu.Item>
</Menu>
</Header>
<Content style={{ padding: '0 50px', marginTop: 64 }}>
<div className="site-layout-content" style={{ background: '#fff', padding: 24, minHeight: 380 }}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/search" element={<SearchPage />} />
<Route path="/product/:id" element={<ProductDetailPage />} />
<Route path="/order/:id" element={<OrderPage />} />
<Route path="/orders" element={<OrderPage />} />
<Route path="/scanner" element={<ScannerPage />} />
<Route path="/upload" element={<UploadFlowerPage />} />
</Routes>
</div>
</Content>
<Footer style={{ textAlign: 'center', background: theme.token.colorBgBase }}>
Flower Trading System ©2024
</Footer>
</Layout>
</Router>
</ConfigProvider>
);
}
export default App;

@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

@ -0,0 +1,60 @@
import React, { useState } from 'react';
import { Modal, Input, InputNumber, Button, message } from 'antd';
import axios from 'axios';
const EditProductModal = ({ visible, product, onCancel, onUpdate }) => {
const [editedProduct, setEditedProduct] = useState(product);
const handleInputChange = (field, value) => {
setEditedProduct({ ...editedProduct, [field]: value });
};
const handleUpdate = async () => {
try {
const response = await axios.put(`http://localhost:5000/api/products/${product.id}`, editedProduct);
if (response.data.message === "Product updated successfully") {
message.success('Product updated successfully');
onUpdate(editedProduct);
} else {
throw new Error('Update failed');
}
} catch (error) {
console.error('Error updating product:', error);
message.error('Failed to update product: ' + (error.response?.data?.error || error.message));
}
};
return (
<Modal
title={`Edit ${product.name}`}
visible={visible}
onCancel={onCancel}
footer={null}
>
<div className="p-4">
<p>Name: <Input value={editedProduct.name} onChange={(e) => handleInputChange('name', e.target.value)} /></p>
<p>Price: $<InputNumber
value={editedProduct.price}
onChange={(value) => handleInputChange('price', value)}
min={0}
step={0.01}
/></p>
<p>Available: <InputNumber
value={editedProduct.quantity}
onChange={(value) => handleInputChange('quantity', value)}
min={0}
/></p>
<p>Description: <Input.TextArea
value={editedProduct.description}
onChange={(e) => handleInputChange('description', e.target.value)}
/></p>
<div className="flex justify-end mt-4">
<Button onClick={onCancel} className="mr-2">取消</Button>
<Button type="primary" onClick={handleUpdate}>完成</Button>
</div>
</div>
</Modal>
);
};
export default EditProductModal;

@ -0,0 +1,27 @@
import axios from 'axios';
const API_URL = 'http://localhost:5000/api';
export const flowerApi = {
async getAllFlowers(query = '') {
try {
const response = await axios.get(`${API_URL}/flowers`, {
params: { query }
});
return response.data;
} catch (error) {
console.error('Error fetching flowers:', error);
throw error;
}
},
async addFlower(flowerData) {
try {
const response = await axios.post(`${API_URL}/flowers`, flowerData);
return response.data;
} catch (error) {
console.error('Error adding flower:', error);
throw error;
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,51 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.App {
text-align: center;
}
nav ul {
list-style-type: none;
padding: 0;
}
nav ul li {
display: inline;
margin: 0 10px;
}
nav ul li a {
text-decoration: none;
color: #333;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
input[type="text"] {
padding: 10px;
margin: 10px 0;
width: 300px;
}

@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,115 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Typography, Button, Space, Card, Row, Col, Carousel } from 'antd';
import { SearchOutlined, ScanOutlined, ShoppingOutlined } from '@ant-design/icons';
import { theme } from '../theme';
// 导入图片
import rosesImage from '../images/rose.jpg';
import tulipsImage from '../images/tulips.jpg';
import orchidsImage from '../images/orchids.jpg';
const { Title, Paragraph } = Typography;
// 样式定义
const styles = {
page: {
padding: '20px',
background: theme.token.colorBgBase,
minHeight: '100vh',
},
carousel: {
height: '300px',
lineHeight: '300px',
textAlign: 'center',
background: theme.token.colorPrimary,
color: '#fff',
},
card: {
height: '100%',
display: 'flex',
flexDirection: 'column',
},
imageContainer: {
height: '200px',
overflow: 'hidden',
},
image: {
width: '100%',
height: '100%',
objectFit: 'cover',
},
footer: {
marginTop: '20px',
textAlign: 'center',
},
};
function HomePage() {
return (
<div style={styles.page}>
<Carousel autoplay>
<div>
<h3 style={styles.carousel}>美丽的玫瑰</h3>
</div>
<div>
<h3 style={styles.carousel}>优雅的郁金香</h3>
</div>
<div>
<h3 style={styles.carousel}>异国情调的兰花</h3>
</div>
</Carousel>
<Card style={{ marginTop: 20, background: theme.token.colorBgBase }}>
<Title level={2} style={{ color: theme.token.colorTextBase }}>欢迎来到花卉交易系统</Title>
<Paragraph style={{ color: theme.token.colorTextBase }}>
探索我们美丽的花朵并开始交易我们提供适合各种场合的各种鲜花
</Paragraph>
<Space size="large">
<Button type="primary" icon={<SearchOutlined />} size="large">
<Link to="/search">搜索花卉</Link>
</Button>
<Button icon={<ScanOutlined />} size="large" style={{ background: theme.token.colorInfo, borderColor: theme.token.colorInfo }}>
<Link to="/scanner">识别花卉</Link>
</Button>
</Space>
</Card>
<Row gutter={16} style={{ marginTop: 20 }}>
<Col span={8}>
<Card hoverable style={styles.card}>
<div style={styles.imageContainer}>
<img alt="玫瑰" src={rosesImage} style={styles.image} />
</div>
<Card.Meta title="玫瑰" description="爱情和激情的象征" />
</Card>
</Col>
<Col span={8}>
<Card hoverable style={styles.card}>
<div style={styles.imageContainer}>
<img alt="郁金香" src={tulipsImage} style={styles.image} />
</div>
<Card.Meta title="郁金香" description="春天的宠儿" />
</Card>
</Col>
<Col span={8}>
<Card hoverable style={styles.card}>
<div style={styles.imageContainer}>
<img alt="兰花" src={orchidsImage} style={styles.image} />
</div>
<Card.Meta title="兰花" description="优雅而富有异国情调" />
</Card>
</Col>
</Row>
<Card style={styles.footer}>
<Title level={4} style={{ color: theme.token.colorTextBase }}>今天就开始您的花卉之旅吧</Title>
<Button type="primary" icon={<ShoppingOutlined />} size="large">
<Link to="/search">开始购物</Link>
</Button>
</Card>
</div>
);
}
export default HomePage;

@ -0,0 +1,183 @@
import React, { useState, useEffect } from 'react';
import { Tabs, List, Card, Button, message, Modal, Form, Input, InputNumber } from 'antd';
import axios from 'axios';
const { TabPane } = Tabs;
const OrderPage = () => {
const [products, setProducts] = useState([]);
const [orders, setOrders] = useState([]);
const [editingProduct, setEditingProduct] = useState(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
useEffect(() => {
fetchProducts();
fetchOrders();
}, []);
const fetchProducts = async () => {
try {
const response = await axios.get('http://localhost:5000/api/my-products');
setProducts(response.data);
} catch (error) {
console.error('获取花卉信息失败:', error);
message.error('获取花卉信息失败');
}
};
const fetchOrders = async () => {
try {
const response = await axios.get('http://localhost:5000/api/orders');
setOrders(response.data);
} catch (error) {
console.error('获取订单信息失败:', error);
message.error('获取订单信息失败');
}
};
const handleEditProduct = (product) => {
console.log('Edit button clicked for product:', product);
setEditingProduct(product);
form.setFieldsValue(product);
setIsModalVisible(true);
};
const handleUpdateProduct = async (values) => {
try {
const response = await axios.put(`http://localhost:5000/api/products/${editingProduct.id}`, values);
if (response.data.message === "Product updated successfully") {
message.success('Product updated successfully');
setIsModalVisible(false);
setEditingProduct(null);
form.resetFields();
fetchProducts(); // Refresh the product list
} else {
throw new Error('Update failed');
}
} catch (error) {
console.error('Error updating product:', error);
message.error('Failed to update product: ' + (error.response?.data?.error || error.message));
}
};
const handleToggleProductStatus = async (productId) => {
try {
const response = await axios.post(`http://localhost:5000/api/products/${productId}/toggle-status`);
if (response.data.message.includes('successfully')) {
message.success(response.data.message);
fetchProducts();
} else {
throw new Error('Toggle status failed');
}
} catch (error) {
console.error('Error toggling product status:', error);
message.error('Failed to update product status');
}
};
const handleCancelOrder = async (orderId) => {
try {
const response = await axios.post(`http://localhost:5000/api/orders/${orderId}/cancel`);
if (response.data.message === "Order cancelled successfully") {
message.success('成功取消订单');
fetchOrders();
} else {
throw new Error('取消订单失败');
}
} catch (error) {
console.error('取消订单失败:', error);
message.error('取消订单失败');
}
};
return (
<div>
<Tabs defaultActiveKey="1">
<TabPane tab="我的上架" key="1">
<List
grid={{ gutter: 16, column: 3 }}
dataSource={products}
renderItem={product => (
<List.Item>
<Card
title={product.name}
extra={<Button onClick={() => handleEditProduct(product)}>编辑</Button>}
actions={[
<Button onClick={() => handleToggleProductStatus(product.id)}>
{product.is_active ? '下架' : '上架'}
</Button>
]}
>
<p>价格: ¥{product.price}</p>
<p>数量: {product.quantity}</p>
<p>状态: {product.is_active ? '上架' : '未上架'}</p>
</Card>
</List.Item>
)}
/>
</TabPane>
<TabPane tab="我的订单" key="2">
<List
dataSource={orders}
renderItem={order => (
<List.Item>
<Card
title={`订单 #${order.id} - ${order.flowerName}`}
extra={
order.status === 'pending' ?
<Button onClick={() => handleCancelOrder(order.id)}>取消订单</Button> :
null
}
>
<p>数量: {order.quantity}</p>
<p>总价: ¥{order.totalPrice}</p>
<p>状态: {order.status}</p>
<p>订单日期: {new Date(order.createdAt).toLocaleString()}</p>
</Card>
</List.Item>
)}
/>
</TabPane>
</Tabs>
<Modal
title="编辑花卉信息"
visible={isModalVisible}
onCancel={() => {
setIsModalVisible(false);
setEditingProduct(null);
form.resetFields();
}}
footer={null}
>
<Form
form={form}
onFinish={handleUpdateProduct}
layout="vertical"
>
<Form.Item name="name" label="花名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="price" label="单价" rules={[{ required: true }]}>
<InputNumber min={0} step={0.01} />
</Form.Item>
<Form.Item name="quantity" label="数量" rules={[{ required: true }]}>
<InputNumber min={0} />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Update Product
</Button>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default OrderPage;

@ -0,0 +1,27 @@
import React from 'react';
import { useParams, useNavigate } from 'react-router-dom';
function ProductDetailPage() {
const { id } = useParams();
const navigate = useNavigate();
// 模拟产品数据
const product = { id, name: 'Sample Flower', price: 10, description: 'A beautiful flower' };
const handleOrder = () => {
// 模拟下单过程
const orderId = Math.floor(Math.random() * 1000);
navigate(`/order/${orderId}`);
};
return (
<div>
<h2>{product.name}</h2>
<p>Price: ${product.price}</p>
<p>{product.description}</p>
<button onClick={handleOrder}>Place Order</button>
</div>
);
}
export default ProductDetailPage;

@ -0,0 +1,87 @@
import React, { useState } from 'react';
import { Upload, Button, Card, Typography, Space } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import axios from 'axios';
const { Title, Text } = Typography;
export default function FlowerScanner() {
const [imageUrl, setImageUrl] = useState('');
const [scanning, setScanning] = useState(false);
const [result, setResult] = useState(null);
const handleUpload = async (info) => {
const { status } = info.file;
if (status === 'done') {
setScanning(true);
const formData = new FormData();
formData.append('image', info.file.originFileObj);
try {
const response = await axios.post('http://localhost:5000/api/identify', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
const url = URL.createObjectURL(info.file.originFileObj);
setImageUrl(url);
setScanning(false);
setResult(response.data);
} catch (error) {
console.error('识别期间出现问题:', error);
setScanning(false);
setResult({ error: '识别失败,请重新尝试' });
}
} else if (status === 'error') {
setResult({ error: `${info.file.name} file upload failed.` });
}
};
return (
<Space direction="vertical" size="large" style={{ display: 'flex' }}>
<Title level={2}>花卉识别</Title>
<Text>上传一张花卉图片以供识别:</Text>
<Upload
name="image"
action="http://localhost:5000/api/identify"
onChange={handleUpload}
accept="image/*"
showUploadList={false}
>
<Button icon={<UploadOutlined />} loading={scanning}>
{scanning ? '识别中' : '上传图片'}
</Button>
</Upload>
{result && (
<Card title="识别结果:" style={{ width: 300 }}>
{result.error ? (
<Text type="danger">{result.error}</Text>
) : (
<>
<img src={imageUrl} alt="花卉识别图片" width={250} height={200}></img>
<Text strong>花名: </Text>
<Text>{result.name}</Text>
<br />
{result.probability && (
<>
<Text strong>识别正确概率: </Text>
<Text>{(result.probability * 100).toFixed(2)}%</Text>
<br />
</>
)}
{result.description && (
<>
<Text strong>描述: </Text>
<Text>{result.description}</Text>
</>
)}
</>
)}
</Card>
)}
</Space>
);
}

@ -0,0 +1,131 @@
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Input, Button, List, Card, message, Tag, Modal, InputNumber } from 'antd';
import axios from 'axios';
const { Search } = Input;
function SearchPage() {
const [flowers, setFlowers] = useState([]);
const [filteredFlowers, setFilteredFlowers] = useState([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [selectedFlower, setSelectedFlower] = useState(null);
const [purchaseQuantity, setPurchaseQuantity] = useState(1);
useEffect(() => {
fetchFlowers();
}, []);
const fetchFlowers = async () => {
setLoading(true);
try {
const response = await axios.get('http://localhost:5000/api/flowers');
const availableFlowers = response.data.filter(flower => flower.quantity > 0);
setFlowers(availableFlowers);
setFilteredFlowers(availableFlowers);
} catch (error) {
console.error('Error fetching flowers:', error);
message.error('Failed to load flowers. Please try again later.');
}
setLoading(false);
};
const handleSearch = (value) => {
const filtered = flowers.filter(flower =>
flower.name.toLowerCase().includes(value.toLowerCase())
);
setFilteredFlowers(filtered);
};
const showPurchaseModal = (flower) => {
setSelectedFlower(flower);
setPurchaseQuantity(1);
setModalVisible(true);
};
const handlePurchase = async () => {
try {
await axios.post('http://localhost:5000/api/orders', {
flowerId: selectedFlower.id,
quantity: purchaseQuantity
});
message.success('Order placed successfully!');
setModalVisible(false);
fetchFlowers(); // Refresh the flower list to update quantities
} catch (error) {
console.error('Error placing order:', error);
message.error('Failed to place order. Please try again.');
}
};
return (
<div style={{ padding: '20px' }}>
<h2>花卉市场</h2>
<Search
placeholder="查找花卉"
onSearch={handleSearch}
style={{ width: 400, marginBottom: 20 }}
enterButton
/>
<List
grid={{
gutter: 16,
xs: 1,
sm: 2,
md: 3,
lg: 3,
xl: 4,
xxl: 4,
}}
dataSource={filteredFlowers}
loading={loading}
renderItem={flower => (
<List.Item>
<Card
hoverable
cover={flower.image_url ? <img alt={flower.name} src={flower.image_url} style={{ height: 200, objectFit: 'cover' }} /> : null}
actions={[
<Button type="primary" onClick={() => showPurchaseModal(flower)}>
购买
</Button>
]}
>
<Card.Meta
title={<Link to={`/product/${flower.id}`}>{flower.name}</Link>}
description={
<>
<p>¥{flower.price.toFixed(2)}</p>
<p>{flower.description}</p>
<Tag color="blue">剩余: {flower.quantity}</Tag>
</>
}
/>
</Card>
</List.Item>
)}
/>
<Modal
title={`Purchase ${selectedFlower?.name}`}
visible={modalVisible}
onOk={handlePurchase}
onCancel={() => setModalVisible(false)}
>
<p>价格: ¥{selectedFlower?.price.toFixed(2)}</p>
<p>是否有余: {selectedFlower?.quantity}</p>
<p>
数量
<InputNumber
min={1}
max={selectedFlower?.quantity}
value={purchaseQuantity}
onChange={value => setPurchaseQuantity(value)}
/>
</p>
<p>总价: ¥{((selectedFlower?.price || 0) * purchaseQuantity).toFixed(2)}</p>
</Modal>
</div>
);
}
export default SearchPage;

@ -0,0 +1,108 @@
import React, { useState } from 'react';
import { Form, Input, InputNumber, Button, message, Upload } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
const UploadFlowerPage = () => {
const [form] = Form.useForm();
const [imageFile, setImageFile] = useState(null);
const navigate = useNavigate();
const onFinish = async (values) => {
try {
const formData = new FormData();
formData.append('name', values.name.trim());
formData.append('description', (values.description || '').trim());
formData.append('price', values.price.toString());
formData.append('quantity', values.quantity.toString());
if (imageFile) {
formData.append('image', imageFile);
}
const response = await axios.post('http://localhost:5000/api/flowers', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
message.success('成功上传花卉!');
form.resetFields();
setImageFile(null);
navigate('/search');
} catch (error) {
console.error('上传花卉时出现错误:', error);
if (error.response && error.response.data && error.response.data.error) {
message.error(`上传花卉失败: ¥{error.response.data.error}`);
} else {
message.error('上传失败,请重新上传');
}
}
};
const handleImageUpload = ({ file, onSuccess }) => {
setImageFile(file);
onSuccess("好");
};
return (
<div>
<h1>上传花卉</h1>
<Form form={form} layout="vertical" onFinish={onFinish}>
<Form.Item
name="name"
label="花名"
rules={[{ required: true, message: '请输入花名!' }]}
>
<Input />
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea />
</Form.Item>
<Form.Item
name="price"
label="单价"
rules={[
{ required: true, message: '请输入价格!' },
{ type: 'number', min: 0, message: '价格需要是有效数字!' }
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item
name="quantity"
label="数量"
rules={[
{ required: true, message: '请输入花卉数量!' },
{ type: 'number', min: 1, message: '数量应该至少为1!' }
]}
>
<InputNumber style={{ width: '100%' }} />
</Form.Item>
<Form.Item label="图片">
<Upload
customRequest={handleImageUpload}
listType="picture"
maxCount={1}
beforeUpload={(file) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('只能上传JPG/PNG文件!');
}
return isJpgOrPng;
}}
>
<Button icon={<UploadOutlined />}>上传图片</Button>
</Upload>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
上传花卉
</Button>
</Form.Item>
</Form>
</div>
);
};
export default UploadFlowerPage;

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

@ -0,0 +1,12 @@
export const theme = {
token: {
colorPrimary: '#FF69B4', // 热粉红色
colorLink: '#FF1493', // 深粉红色
colorSuccess: '#98FB98', // 淡绿色,与粉色搭配
colorWarning: '#FFB6C1', // 浅粉红色
colorError: '#FF69B4', // 热粉红色
colorInfo: '#FFC0CB', // 粉红色
colorTextBase: '#4B0082', // 靛青色,作为主要文字颜色
colorBgBase: '#FFF0F5', // 浅粉红色,作为背景色
},
};
Loading…
Cancel
Save