我要开发一个h5的项目。 目标平台是PC浏览器和手机浏览器。 分为前端和后端。 后端采用python 不需要数据库,直接用文件系统,启动的时候读取到内存就可以。
功能 1、是前端能从后端拉起一组图片,分类为“男”,“女”。 这个图片是保存到oss文件系统的,后端需要提供图片的url。 2、前端可以选择一个图片,手动端可以拍摄图片或者选择图片。 3、前端发起更换发型的请求,把选择的图片和对应要更换的发型ID发给后端。 4、前端收到后端更换发型的请求后处理图片返回给前端。 5、后端实际处理更换发型请求的时候会调用另外一个远程的http接口来实现。 先帮我把项目结构,产品文档,系统框架等做出来。
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user