我要开发一个h5的项目。 目标平台是PC浏览器和手机浏览器。 分为前端和后端。 后端采用python 不需要数据库,直接用文件系统,启动的时候读取到内存就可以。

功能
1、是前端能从后端拉起一组图片,分类为“男”,“女”。 这个图片是保存到oss文件系统的,后端需要提供图片的url。
2、前端可以选择一个图片,手动端可以拍摄图片或者选择图片。
3、前端发起更换发型的请求,把选择的图片和对应要更换的发型ID发给后端。
4、前端收到后端更换发型的请求后处理图片返回给前端。
5、后端实际处理更换发型请求的时候会调用另外一个远程的http接口来实现。
先帮我把项目结构,产品文档,系统框架等做出来。
This commit is contained in:
xsl
2026-01-18 18:22:58 +08:00
commit 9a00ac3907
12 changed files with 1694 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
// API调用封装
const API_BASE_URL = '/api';
// 获取发型列表
export async function getHairstyles() {
try {
const response = await axios.get(`${API_BASE_URL}/hairstyles`);
return response.data;
} catch (error) {
console.error('获取发型列表失败:', error);
throw error;
}
}
// 更换发型
export async function changeHair(imageFile, hairstyleId) {
try {
const formData = new FormData();
formData.append('image', imageFile);
formData.append('hairstyle_id', hairstyleId);
const response = await axios.post(`${API_BASE_URL}/change-hair`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
return response.data;
} catch (error) {
console.error('更换发型失败:', error);
throw error;
}
}
+318
View File
@@ -0,0 +1,318 @@
// API调用函数
async function getHairstyles() {
try {
const response = await axios.get('/api/hairstyles');
return response.data;
} catch (error) {
console.error('获取发型列表失败:', error);
throw error;
}
}
async function changeHair(imageFile, hairstyleId) {
try {
const formData = new FormData();
formData.append('image', imageFile);
formData.append('hairstyle_id', hairstyleId);
const response = await axios.post('/api/change-hair', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
return response.data;
} catch (error) {
console.error('更换发型失败:', error);
throw error;
}
}
// 全局变量
let uploadedImage = null;
let selectedHairstyle = null;
let currentGender = 'male';
let hairstylesData = {
male: [],
female: []
};
// DOM元素
const elements = {
// 首页元素
homePage: document.getElementById('homePage'),
imagePreview: document.getElementById('imagePreview'),
imageUpload: document.getElementById('imageUpload'),
maleBtn: document.getElementById('maleBtn'),
femaleBtn: document.getElementById('femaleBtn'),
// 发型选择页元素
hairstylePage: document.getElementById('hairstylePage'),
navMaleBtn: document.getElementById('navMaleBtn'),
navFemaleBtn: document.getElementById('navFemaleBtn'),
hairstyleGrid: document.getElementById('hairstyleGrid'),
confirmBtn: document.getElementById('confirmBtn'),
// 结果预览页元素
resultPage: document.getElementById('resultPage'),
resultPreview: document.getElementById('resultPreview'),
reselectHairstyleBtn: document.getElementById('reselectHairstyleBtn'),
reuploadImageBtn: document.getElementById('reuploadImageBtn'),
saveResultBtn: document.getElementById('saveResultBtn'),
// 通用元素
backBtn: document.getElementById('backBtn'),
loading: document.getElementById('loading')
};
// 初始化函数
function init() {
// 绑定事件监听器
bindEventListeners();
// 加载发型数据
loadHairstyles();
}
// 绑定事件监听器
function bindEventListeners() {
// 图片上传
elements.imageUpload.addEventListener('change', handleImageUpload);
// 性别分类按钮
elements.maleBtn.addEventListener('click', () => navigateToHairstylePage('male'));
elements.femaleBtn.addEventListener('click', () => navigateToHairstylePage('female'));
// 导航栏性别切换
elements.navMaleBtn.addEventListener('click', () => switchGender('male'));
elements.navFemaleBtn.addEventListener('click', () => switchGender('female'));
// 确认按钮
elements.confirmBtn.addEventListener('click', handleConfirmHairstyle);
// 返回按钮
elements.backBtn.addEventListener('click', handleBack);
// 结果页操作按钮
elements.reselectHairstyleBtn.addEventListener('click', handleReselectHairstyle);
elements.reuploadImageBtn.addEventListener('click', handleReuploadImage);
elements.saveResultBtn.addEventListener('click', handleSaveResult);
}
// 加载发型数据
async function loadHairstyles() {
try {
showLoading();
const response = await getHairstyles();
if (response.success) {
hairstylesData.male = response.data.male;
hairstylesData.female = response.data.female;
renderHairstyles(currentGender);
}
} catch (error) {
alert('加载发型数据失败,请刷新页面重试');
} finally {
hideLoading();
}
}
// 处理图片上传
function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const imageUrl = e.target.result;
uploadedImage = file;
// 更新预览
elements.imagePreview.innerHTML = `
<img src="${imageUrl}" alt="上传的图片">
`;
};
reader.readAsDataURL(file);
}
// 导航到发型选择页
function navigateToHairstylePage(gender) {
if (!uploadedImage) {
alert('请先上传图片');
return;
}
currentGender = gender;
switchGender(gender);
showPage(elements.hairstylePage);
showBackBtn();
}
// 切换性别
function switchGender(gender) {
currentGender = gender;
// 更新导航按钮状态
elements.navMaleBtn.classList.toggle('active', gender === 'male');
elements.navFemaleBtn.classList.toggle('active', gender === 'female');
// 重新渲染发型列表
renderHairstyles(gender);
}
// 渲染发型列表
function renderHairstyles(gender) {
const hairstyles = hairstylesData[gender];
elements.hairstyleGrid.innerHTML = '';
hairstyles.forEach(hairstyle => {
const hairstyleItem = document.createElement('div');
hairstyleItem.className = 'hairstyle-item';
hairstyleItem.dataset.id = hairstyle.id;
hairstyleItem.innerHTML = `
<img src="${hairstyle.image_url}" alt="${hairstyle.name}">
<p>${hairstyle.name}</p>
`;
// 点击选择发型
hairstyleItem.addEventListener('click', () => selectHairstyle(hairstyle.id));
elements.hairstyleGrid.appendChild(hairstyleItem);
});
}
// 选择发型
function selectHairstyle(hairstyleId) {
// 移除之前的选择状态
document.querySelectorAll('.hairstyle-item.selected').forEach(item => {
item.classList.remove('selected');
});
// 添加新的选择状态
const selectedItem = document.querySelector(`.hairstyle-item[data-id="${hairstyleId}"]`);
if (selectedItem) {
selectedItem.classList.add('selected');
selectedHairstyle = hairstyleId;
elements.confirmBtn.disabled = false;
}
}
// 处理确认发型选择
async function handleConfirmHairstyle() {
if (!uploadedImage || !selectedHairstyle) {
alert('请上传图片并选择发型');
return;
}
try {
showLoading();
const response = await changeHair(uploadedImage, selectedHairstyle);
if (response.success) {
// 显示结果
showResult(response.data.result_image);
} else {
alert(response.message || '处理失败,请重试');
}
} catch (error) {
alert('处理失败,请重试');
} finally {
hideLoading();
}
}
// 显示结果
function showResult(resultImage) {
elements.resultPreview.innerHTML = `
<img src="${resultImage}" alt="发型更换结果">
`;
showPage(elements.resultPage);
showBackBtn();
}
// 处理重新选择发型
function handleReselectHairstyle() {
showPage(elements.hairstylePage);
}
// 处理重新上传图片
function handleReuploadImage() {
// 重置图片上传
elements.imageUpload.value = '';
uploadedImage = null;
elements.imagePreview.innerHTML = `
<div class="upload-placeholder">
<div class="upload-icon">📷</div>
<p>点击上传图片或拍摄照片</p>
<input type="file" id="imageUpload" accept="image/*" capture="camera">
</div>
`;
// 重新绑定事件
document.getElementById('imageUpload').addEventListener('change', handleImageUpload);
// 重置选择状态
selectedHairstyle = null;
elements.confirmBtn.disabled = true;
document.querySelectorAll('.hairstyle-item.selected').forEach(item => {
item.classList.remove('selected');
});
showPage(elements.homePage);
hideBackBtn();
}
// 处理保存结果
function handleSaveResult() {
const resultImage = elements.resultPreview.querySelector('img');
if (resultImage) {
// 创建下载链接
const link = document.createElement('a');
link.href = resultImage.src;
link.download = 'hairstyle-result.jpg';
link.click();
}
}
// 处理返回按钮
function handleBack() {
if (elements.resultPage.classList.contains('active')) {
showPage(elements.hairstylePage);
} else if (elements.hairstylePage.classList.contains('active')) {
showPage(elements.homePage);
hideBackBtn();
}
}
// 显示页面
function showPage(page) {
// 隐藏所有页面
[elements.homePage, elements.hairstylePage, elements.resultPage].forEach(p => {
p.classList.remove('active');
});
// 显示目标页面
page.classList.add('active');
}
// 显示返回按钮
function showBackBtn() {
elements.backBtn.style.display = 'block';
}
// 隐藏返回按钮
function hideBackBtn() {
elements.backBtn.style.display = 'none';
}
// 显示加载指示器
function showLoading() {
elements.loading.style.display = 'flex';
}
// 隐藏加载指示器
function hideLoading() {
elements.loading.style.display = 'none';
}
// 初始化应用
init();