Compare commits

...

10 Commits
main ... dev

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>养护建议 - 植物智识</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF', // 蓝色主色调
}
}
}
}
</script>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<!-- 导航栏 -->
<nav class="bg-white shadow-sm fixed w-full top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center">
<i class="fa fa-leaf text-primary text-2xl mr-2"></i>
<h1 class="text-xl font-bold">植物智识</h1>
</div>
<div class="hidden md:flex space-x-6">
<a href="index.html" class="text-gray-600 hover:text-primary">首页</a>
<a href="history.html" class="text-gray-600 hover:text-primary">历史记录</a>
</div>
</div>
</nav>
<!-- 主内容 -->
<main class="container mx-auto px-4 pt-24 pb-16 flex-grow">
<div class="max-w-2xl mx-auto">
<!-- 返回按钮 -->
<a href="result.html" class="inline-flex items-center text-gray-600 mb-6 hover:text-primary">
<i class="fa fa-arrow-left mr-2"></i>返回结果页
</a>
<!-- 建议卡片 -->
<div class="bg-white rounded-xl shadow-sm p-6">
<h2 id="advice-title" class="text-2xl font-bold mb-6"></h2>
<div id="advice-list" class="space-y-4 mb-8">
<!-- 建议内容将动态渲染 -->
</div>
<button onclick="window.location.href='index.html'" class="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary/90 transition-colors">
<i class="fa fa-refresh mr-2"></i>识别其他植物
</button>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 植物智识 - 学生开发的植物识别与养护工具</p>
</div>
</footer>
<script>
window.onload = () => {
const data = JSON.parse(localStorage.getItem("currentAdvice"));
if (!data || !data.advice) {
alert("未找到养护建议");
window.location.href = "index.html";
return;
}
// 渲染标题
document.getElementById("advice-title").textContent = `${data.plantName} 的养护建议`;
// 渲染建议列表
const adviceList = document.getElementById("advice-list");
const adviceItems = data.advice.split("\n")
.filter(item => item.trim())
.map((item, index) => {
const content = item.replace(/^\d+\.\s*/, "").trim();
return `
<div class="flex items-start gap-3 p-4 border border-gray-100 rounded-lg">
<div class="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-primary font-bold flex-shrink-0">${index + 1}</div>
<p class="text-gray-700">${content}</p>
</div>
`;
});
adviceList.innerHTML = adviceItems.join("");
};
</script>
</body>
</html>

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>历史记录 - 植物智识</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF', // 蓝色主色调
}
}
}
}
</script>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<!-- 导航栏 -->
<nav class="bg-white shadow-sm fixed w-full top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center">
<i class="fa fa-leaf text-primary text-2xl mr-2"></i>
<h1 class="text-xl font-bold">植物智识</h1>
</div>
<div class="hidden md:flex space-x-6">
<a href="index.html" class="text-gray-600 hover:text-primary">首页</a>
<a href="history.html" class="text-primary font-medium">历史记录</a>
</div>
</div>
</nav>
<!-- 主内容 -->
<main class="container mx-auto px-4 pt-24 pb-16 flex-grow">
<div class="max-w-4xl mx-auto">
<!-- 返回按钮 -->
<a href="index.html" class="inline-flex items-center text-gray-600 mb-6 hover:text-primary">
<i class="fa fa-arrow-left mr-2"></i>返回首页
</a>
<h2 class="text-2xl font-bold mb-6">识别历史记录</h2>
<!-- 历史记录列表 -->
<div id="history-container" class="space-y-4">
<!-- 无记录提示 -->
<div id="empty-tip" class="text-center py-12 text-gray-500">
<i class="fa fa-history text-4xl mb-4"></i>
<p>暂无识别记录,快去首页上传图片吧</p>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 植物智识 - 学生开发的植物识别与养护工具</p>
</div>
</footer>
<script>
window.onload = () => {
const history = JSON.parse(localStorage.getItem("plantHistory") || "[]");
const container = document.getElementById("history-container");
const emptyTip = document.getElementById("empty-tip");
if (history.length === 0) return;
// 隐藏空提示,渲染记录
emptyTip.remove();
container.innerHTML = history.map((item, index) => `
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<div class="md:flex">
<div class="md:w-1/4 p-4">
<img src="${item.imageBase64}" alt="${item.plantName}" class="w-full h-32 object-cover rounded-lg">
</div>
<div class="md:w-3/4 p-4">
<h3 class="font-bold text-lg mb-1">${item.plantName}</h3>
<div class="flex items-center text-sm text-gray-500 mb-3">
<span class="mr-3">置信度 ${item.plantConfidence}%</span>
<span>${item.identifyTime}</span>
</div>
<button onclick="viewDetail(${index})" class="text-primary hover:underline text-sm">
<i class="fa fa-eye mr-1"></i>查看详情
</button>
</div>
</div>
</div>
`).join("");
};
// 查看历史详情
function viewDetail(index) {
const history = JSON.parse(localStorage.getItem("plantHistory") || "[]");
if (index >= 0 && index < history.length) {
localStorage.setItem("currentResult", JSON.stringify(history[index]));
window.location.href = "result.html";
}
}
</script>
</body>
</html>

@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>植物智识 - 首页</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF', // 蓝色主色调
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto { content-visibility: auto; }
.upload-area { @apply border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer transition-all duration-300; }
.upload-area-hover { @apply border-primary bg-primary/5; }
.plant-card { @apply rounded-lg overflow-hidden shadow-md transition-transform duration-300 hover:scale-105; }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<!-- 导航栏 -->
<nav class="bg-white shadow-sm fixed w-full top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center">
<i class="fa fa-leaf text-primary text-2xl mr-2"></i>
<h1 class="text-xl font-bold">植物智识</h1>
</div>
<div class="hidden md:flex space-x-6">
<a href="index.html" class="text-primary font-medium">首页</a>
<a href="history.html" class="text-gray-600 hover:text-primary transition-colors">历史记录</a>
</div>
</div>
</nav>
<!-- 主内容 -->
<main class="container mx-auto px-4 pt-24 pb-16 flex-grow">
<!-- 系统介绍区域 -->
<section class="max-w-4xl mx-auto text-center mb-16 mt-8">
<div class="flex justify-center mb-6">
<!-- 校徽位置 -->
<img src="./favicon.ico" alt="学校校徽" class="w-16 h-16 rounded-full border-2 border-primary mr-4">
<!-- 系统标题 -->
<div>
<h1 class="text-4xl font-bold text-gray-800 mb-2">植物智识</h1>
<p class="text-primary font-medium">植物智慧识别</p>
</div>
</div>
<h2 class="text-2xl font-bold text-gray-800 mb-4">智能植物识别与养护系统</h2>
<p class="text-gray-600 max-w-2xl mx-auto mb-8 text-lg">
本系统能够快速识别各类植物,提供专业养护建议,帮助您更好地了解和照顾身边的植物。
无论是校园里的花草,还是家中的盆栽,都能轻松识别。
</p>
<!-- 开始使用按钮 -->
<button id="start-btn" class="bg-primary text-white px-8 py-3 rounded-lg font-medium hover:bg-primary/90 transition-colors text-lg shadow-md">
<i class="fa fa-arrow-down mr-2"></i>开始使用
</button>
</section>
<!-- 学校照片展示 -->
<section class="max-w-4xl mx-auto mb-16">
<img src="./banner主图校园.jpg" alt="学校照片" class="w-full h-64 md:h-80 object-cover rounded-xl shadow-md">
</section>
<!-- 植物装饰图片区域 -->
<section class="max-w-4xl mx-auto mb-16">
<h3 class="text-xl font-bold text-center mb-6 text-gray-800">常见植物展示</h3>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="plant-card">
<img src="./1.webp" alt="植物图片1" class="w-full h-40 object-cover">
</div>
<div class="plant-card">
<img src="./2.jpg" alt="植物图片2" class="w-full h-40 object-cover">
</div>
<div class="plant-card">
<img src="./v2-3b77ec5116e63d28054741a7ae2439f9_r.jpg" alt="植物图片3" class="w-full h-40 object-cover">
</div>
<div class="plant-card">
<img src="./4.jpg" alt="植物图片4" class="w-full h-40 object-cover">
</div>
</div>
</section>
<!-- 上传区域 -->
<div id="upload-anchor" class="max-w-2xl mx-auto">
<div id="upload-container" class="bg-white rounded-xl shadow-sm p-6 mb-8">
<div id="drop-area" class="upload-area">
<input type="file" id="file-input" accept="image/*" class="hidden">
<i class="fa fa-picture-o text-5xl text-gray-400 mb-4"></i>
<h3 class="text-lg font-medium mb-2">点击或拖拽图片到此处</h3>
<p class="text-gray-500 mb-6">支持JPG、PNG格式大小不超过5MB</p>
<button id="select-btn" class="bg-primary text-white px-6 py-2 rounded-lg font-medium hover:bg-primary/90 transition-colors">
<i class="fa fa-upload mr-2"></i>选择图片
</button>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 植物智识 - 学生开发的植物识别与养护工具</p>
</div>
</footer>
<script>
let accessToken = "";
// 页面加载时获取百度Token
window.onload = async () => {
try {
const res = await fetch('http://localhost:3000/baidu/token');
const data = await res.json();
if (data.error) throw new Error(data.error_description);
accessToken = data.access_token;
} catch (err) {
alert("初始化失败:" + err.message);
console.error("Token获取失败", err);
}
// 开始使用按钮点击事件 - 滚动到上传区域
document.getElementById('start-btn').addEventListener('click', () => {
document.getElementById('upload-anchor').scrollIntoView({
behavior: 'smooth'
});
});
};
// 图片转Base64
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// 选择图片按钮点击事件
document.getElementById('select-btn').addEventListener('click', async () => {
const file = document.getElementById('file-input').files[0];
if (!file) {
alert("请先选择图片");
return;
}
if (!accessToken) {
alert("Token未初始化请刷新页面");
return;
}
try {
const imageBase64 = await fileToBase64(file);
// 调用植物识别接口
const res = await fetch('http://localhost:3000/baidu/plant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ access_token: accessToken, image: imageBase64 })
});
const result = await res.json();
if (result.error_code) throw new Error(result.error_msg || "识别失败");
if (!result.result || result.result.length === 0) throw new Error("未识别到植物");
// 整理结果
const plantResult = {
plantName: result.result[0].name,
plantConfidence: (result.result[0].score * 100).toFixed(0),
identifyTime: new Date().toLocaleString(),
imageBase64: imageBase64
};
// 保存到历史记录
const history = JSON.parse(localStorage.getItem("plantHistory") || "[]");
history.unshift(plantResult);
localStorage.setItem("plantHistory", JSON.stringify(history.slice(0, 10)));
// 保存当前结果并跳转
localStorage.setItem("currentResult", JSON.stringify(plantResult));
window.location.href = "result.html";
} catch (err) {
alert("识别失败:" + err.message);
console.error("识别错误:", err);
}
});
// 拖拽上传逻辑
const dropArea = document.getElementById('drop-area');
dropArea.addEventListener('click', () => document.getElementById('file-input').click());
dropArea.addEventListener('dragover', (e) => {
e.preventDefault();
dropArea.classList.add('upload-area-hover');
});
dropArea.addEventListener('dragleave', () => dropArea.classList.remove('upload-area-hover'));
dropArea.addEventListener('drop', (e) => {
e.preventDefault();
dropArea.classList.remove('upload-area-hover');
if (e.dataTransfer.files.length) {
document.getElementById('file-input').files = e.dataTransfer.files;
document.getElementById('select-btn').click();
}
});
// 选择文件后触发识别
document.getElementById('file-input').addEventListener('change', (e) => {
if (e.target.files.length) document.getElementById('select-btn').click();
});
</script>
</body>
</html>

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>识别结果 - 植物智识</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF', // 蓝色主色调
}
}
}
}
</script>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<!-- 导航栏 -->
<nav class="bg-white shadow-sm fixed w-full top-0 z-50">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center">
<i class="fa fa-leaf text-primary text-2xl mr-2"></i>
<h1 class="text-xl font-bold">植物智识</h1>
</div>
<div class="hidden md:flex space-x-6">
<a href="index.html" class="text-gray-600 hover:text-primary">首页</a>
<a href="history.html" class="text-gray-600 hover:text-primary">历史记录</a>
</div>
</div>
</nav>
<!-- 主内容 -->
<main class="container mx-auto px-4 pt-24 pb-16 flex-grow">
<div class="max-w-4xl mx-auto">
<!-- 返回按钮 -->
<a href="index.html" class="inline-flex items-center text-gray-600 mb-6 hover:text-primary">
<i class="fa fa-arrow-left mr-2"></i>返回首页
</a>
<!-- 结果卡片 -->
<div class="bg-white rounded-xl shadow-sm overflow-hidden">
<div class="md:flex">
<!-- 图片区域 -->
<div class="md:w-1/2 p-6">
<h3 class="text-gray-700 font-medium mb-3">识别图片</h3>
<img id="plant-image" src="" alt="植物图片" class="w-full h-64 object-cover rounded-lg">
</div>
<!-- 信息区域 -->
<div class="md:w-1/2 p-6">
<h2 id="plant-name" class="text-2xl font-bold mb-3"></h2>
<div class="flex items-center mb-4">
<span id="confidence" class="bg-primary/10 text-primary px-3 py-1 rounded-full text-sm font-medium mr-3"></span>
<span id="time" class="text-gray-500 text-sm"></span>
</div>
<div class="mb-6">
<h3 class="text-gray-700 font-medium mb-2">植物信息</h3>
<p class="text-gray-600">已成功识别植物种类,点击下方按钮获取养护建议</p>
</div>
<!-- 操作按钮 -->
<div class="flex gap-3">
<button id="advice-btn" class="flex-1 bg-primary text-white py-2 rounded-lg font-medium hover:bg-primary/90 transition-colors">
<i class="fa fa-lightbulb-o mr-2"></i>养护建议
</button>
<button onclick="window.location.href='index.html'" class="border border-gray-300 px-4 py-2 rounded-lg font-medium hover:border-primary hover:text-primary transition-colors">
<i class="fa fa-refresh"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-gray-800 text-white py-8">
<div class="container mx-auto px-4 text-center">
<p>© 2025 植物智识 - 学生开发的植物识别与养护工具</p>
</div>
</footer>
<script>
window.onload = () => {
// 获取当前识别结果
const result = JSON.parse(localStorage.getItem("currentResult"));
if (!result) {
alert("未找到识别结果");
window.location.href = "index.html";
return;
}
// 渲染结果
document.getElementById("plant-name").textContent = result.plantName;
document.getElementById("confidence").textContent = `置信度 ${result.plantConfidence}%`;
document.getElementById("time").textContent = result.identifyTime;
document.getElementById("plant-image").src = result.imageBase64;
// 养护建议按钮
document.getElementById("advice-btn").addEventListener("click", async () => {
try {
const res = await fetch("http://localhost:3000/get-advice", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ plantName: result.plantName })
});
const adviceData = await res.json();
if (adviceData.error) throw new Error(adviceData.error);
if (!adviceData.output?.text) throw new Error("未获取到建议");
// 保存建议并跳转
localStorage.setItem("currentAdvice", JSON.stringify({
...result,
advice: adviceData.output.text
}));
window.location.href = "advice.html";
} catch (err) {
alert("生成建议失败:" + err.message);
}
});
};
</script>
</body>
</html>

@ -0,0 +1,83 @@
const fetch = require('node-fetch');
const express = require('express');
const app = express();
// 跨域配置(确保前端正常调用)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') return res.sendStatus(200);
next();
});
// 支持大图片上传
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 你的有效密钥(不变)
const BAIDU_API_KEY = "U3RlIPY6NeNvHkivaGQaT9RV";
const BAIDU_SECRET_KEY = "fsOAjmvYyjD4Mtm1eRDhRd5VqP4aqZPC";
const TONGYI_API_KEY = "sk-48bcbd07980c4c8db89129118fedd89f";
// 1. 获取百度Token
app.get('/baidu/token', async (req, res) => {
const tokenUrl = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${BAIDU_API_KEY}&client_secret=${BAIDU_SECRET_KEY}`;
try {
const response = await fetch(tokenUrl);
const data = await response.json();
res.json(data);
} catch (err) {
console.error('获取百度Token失败', err);
res.status(500).json({ error: '获取Token失败' });
}
});
// 2. 植物识别接口
app.post('/baidu/plant', async (req, res) => {
const { access_token, image } = req.body;
const pureBase64 = image.replace(/^data:image\/\w+;base64,/, '');
const plantUrl = `https://aip.baidubce.com/rest/2.0/image-classify/v1/plant?access_token=${access_token}`;
try {
const response = await fetch(plantUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `image=${encodeURIComponent(pureBase64)}&top_num=1`
});
const data = await response.json();
res.json(data);
} catch (err) {
console.error('植物识别失败:', err);
res.status(500).json({ error: '识别出错' });
}
});
// 3. 养护建议接口(稳定版)
app.post('/get-advice', async (req, res) => {
const { plantName } = req.body;
try {
const adviceResponse = await fetch("https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${TONGYI_API_KEY}`
},
body: JSON.stringify({
model: "qwen-turbo",
input: {
prompt: `请给出${plantName}的3条养护建议每条100字以内1.光照温度2.浇水施肥3.常见问题。口语化。`
}
})
});
const adviceData = await adviceResponse.json();
res.json(adviceData);
} catch (error) {
console.error("生成建议失败:", error);
res.status(500).json({ error: "生成建议失败" });
}
});
// 启动服务端口3000
app.listen(3000, () => {
console.log('后端启动http://localhost:3000');
});
Loading…
Cancel
Save