2025-08-27 13:43:55 +08:00
|
|
|
|
/**
|
|
|
|
|
* Qwen Image Editor API 封装
|
|
|
|
|
* 参考 qwen-image.js 的实现风格
|
|
|
|
|
*/
|
|
|
|
|
class QwenImageEditor {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.baseUrl = '/api/qwenImage';
|
|
|
|
|
this.editUrl = `${this.baseUrl}/edit`;
|
|
|
|
|
this.initEventListeners();
|
|
|
|
|
// 初始化时渲染示例
|
|
|
|
|
this.renderExamples();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化事件监听
|
|
|
|
|
*/
|
|
|
|
|
initEventListeners() {
|
|
|
|
|
// 移除Layui依赖,使用原生JS实现
|
|
|
|
|
document.getElementById('startEditBtn').addEventListener('click', () => this.handleEditSubmit());
|
2025-08-27 14:05:32 +08:00
|
|
|
|
|
|
|
|
|
// 删除JS动态创建按钮的代码
|
|
|
|
|
// 直接绑定静态按钮事件
|
|
|
|
|
document.getElementById('randomExampleBtn').addEventListener('click', () => this.handleRandomExample());
|
2025-08-27 13:43:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理编辑表单提交
|
|
|
|
|
*/
|
|
|
|
|
handleEditSubmit() {
|
|
|
|
|
const imageUrl = document.getElementById('imageUrl').value;
|
|
|
|
|
const prompt = document.getElementById('editPrompt').value;
|
|
|
|
|
const size = document.getElementById('editSize').value;
|
|
|
|
|
const style = document.getElementById('editStyle').value;
|
|
|
|
|
|
|
|
|
|
if (!imageUrl || !prompt) {
|
|
|
|
|
alert('图片URL和编辑提示词不能为空');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadingIndicator = document.createElement('div');
|
|
|
|
|
loadingIndicator.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
|
|
|
|
loadingIndicator.innerHTML = '<i class="fa fa-spinner fa-spin text-white text-4xl"></i>';
|
|
|
|
|
document.body.appendChild(loadingIndicator);
|
|
|
|
|
|
|
|
|
|
// 显示原始图片
|
|
|
|
|
document.getElementById('originalImage').src = imageUrl;
|
|
|
|
|
document.getElementById('originalImage').classList.remove('hidden');
|
|
|
|
|
document.getElementById('originalImagePlaceholder').classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
// 调用编辑接口
|
|
|
|
|
this.editImage(imageUrl, prompt, size, style)
|
|
|
|
|
.then(result => {
|
|
|
|
|
document.body.removeChild(loadingIndicator);
|
|
|
|
|
document.getElementById('editedImage').src = result.edited_image;
|
|
|
|
|
document.getElementById('editedImage').classList.remove('hidden');
|
|
|
|
|
document.getElementById('editedImagePlaceholder').classList.add('hidden');
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
document.body.removeChild(loadingIndicator);
|
|
|
|
|
alert(`编辑失败: ${error.message || '网络请求错误'}`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 调用图片编辑接口
|
|
|
|
|
* @param {string} imageUrl - 原始图片URL
|
|
|
|
|
* @param {string} prompt - 编辑提示词
|
|
|
|
|
* @param {string} size - 图像尺寸
|
|
|
|
|
* @param {string} style - 艺术风格
|
|
|
|
|
* @returns {Promise<Object>} - 编辑结果
|
|
|
|
|
*/
|
|
|
|
|
async editImage(imageUrl, prompt, size, style) {
|
|
|
|
|
try {
|
|
|
|
|
const response = await axios.post(this.editUrl, {
|
|
|
|
|
image_url: imageUrl,
|
|
|
|
|
prompt: prompt,
|
|
|
|
|
size: size,
|
|
|
|
|
style: style
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.data.code !== 200) {
|
|
|
|
|
throw new Error(response.data.message || '编辑接口返回错误');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.data.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('图片编辑失败:', error);
|
|
|
|
|
throw error.response?.data || { message: '网络请求失败,请重试' };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 渲染示例区域
|
|
|
|
|
*/
|
|
|
|
|
renderExamples() {
|
|
|
|
|
// 统一示例数据结构
|
|
|
|
|
const exampleGroups = [
|
|
|
|
|
{
|
|
|
|
|
id: 'bearExamples',
|
|
|
|
|
title: '小熊系列',
|
2025-08-27 13:51:08 +08:00
|
|
|
|
aspectRatio: '1/1', // 新增:小熊系列方形比例
|
2025-08-27 13:43:55 +08:00
|
|
|
|
originalImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999719.png',
|
|
|
|
|
examples: [
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊拿着五彩画板和画笔,站在画板前画画',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999625.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊站在白色背景前,手里拿着锅铲,旁边有蔬菜和调料',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999626.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊坐在地上,怀里抱着一把吉他,手指拨动琴弦',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999628.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊穿着燕尾服,戴着魔术帽,手里拿着魔术棒,做出表演魔术的动作',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999629.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊穿着运动服,手里拿着篮球,单腿弯曲',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999630.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '这只熊戴着草帽,手里拿着花洒和小铲子,正在浇水或种植植物',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999631.png'
|
|
|
|
|
},
|
|
|
|
|
// 添加缺失的5个示例
|
|
|
|
|
{prompt: '这只熊穿着宇航服,伸出手指向远方', resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999632.png'},
|
|
|
|
|
{prompt: '这只熊穿着华丽的舞裙,双臂展开,做出优雅的舞蹈动作', resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p999633.png'},
|
|
|
|
|
{prompt: '把背景改成草原', resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000045.png'},
|
|
|
|
|
{prompt: '给它戴上红色帽子和黑色墨镜', resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000051.png'},
|
|
|
|
|
{prompt: '改成梵高油画风格', resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000061.png'}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'hanfuExamples',
|
|
|
|
|
title: '霓裳汉服社系列',
|
2025-08-27 13:51:08 +08:00
|
|
|
|
aspectRatio: '5/7', // 新增:汉服社系列5:7比例
|
2025-08-27 13:43:55 +08:00
|
|
|
|
originalImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000025.png',
|
|
|
|
|
examples: [
|
|
|
|
|
{
|
|
|
|
|
prompt: '把“霓裳汉服社”改成“通义实验室”',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000026.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '把“活动全程免费”改为“只给学生打折”',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000027.png'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
prompt: '把女人改成穿汉服的男人',
|
|
|
|
|
resultImage: 'https://help-static-aliyun-doc.aliyuncs.com/assets/img/zh-CN/7219965571/p1000028.png'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
2025-08-27 14:05:32 +08:00
|
|
|
|
// 新增:保存示例数据到实例属性
|
|
|
|
|
this.exampleGroups = exampleGroups;
|
|
|
|
|
this.allExamples = [];
|
|
|
|
|
|
|
|
|
|
// 渲染每个系列的示例并收集所有示例
|
2025-08-27 13:43:55 +08:00
|
|
|
|
exampleGroups.forEach(group => {
|
|
|
|
|
const container = document.getElementById(group.id);
|
|
|
|
|
if (!container) return;
|
2025-08-27 14:05:32 +08:00
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
group.examples.forEach(example => {
|
2025-08-27 14:05:32 +08:00
|
|
|
|
// 收集所有示例用于随机选择
|
|
|
|
|
this.allExamples.push({
|
|
|
|
|
originalImage: group.originalImage,
|
|
|
|
|
prompt: example.prompt,
|
|
|
|
|
resultImage: example.resultImage
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
container.appendChild(this.createExampleCard(
|
|
|
|
|
group.originalImage,
|
|
|
|
|
example.prompt,
|
2025-08-27 13:51:08 +08:00
|
|
|
|
example.resultImage,
|
2025-08-27 14:05:32 +08:00
|
|
|
|
group.aspectRatio
|
2025-08-27 13:43:55 +08:00
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-08-27 14:05:32 +08:00
|
|
|
|
|
|
|
|
|
// 新增:自动选择小熊系列第一个示例
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const bearContainer = document.getElementById('bearExamples');
|
|
|
|
|
if (bearContainer && bearContainer.firstElementChild) {
|
|
|
|
|
bearContainer.firstElementChild.click();
|
|
|
|
|
}
|
|
|
|
|
}, 100);
|
2025-08-27 13:43:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建示例卡片
|
|
|
|
|
* @param {string} originalImage - 原始图片URL
|
|
|
|
|
* @param {string} prompt - 编辑提示词
|
|
|
|
|
* @param {string} resultImage - 结果图片URL
|
2025-08-27 13:51:08 +08:00
|
|
|
|
* @param {string} aspectRatio - 宽高比(新增参数)
|
2025-08-27 13:43:55 +08:00
|
|
|
|
* @returns {HTMLElement} - 卡片元素
|
|
|
|
|
*/
|
2025-08-27 13:51:08 +08:00
|
|
|
|
createExampleCard(originalImage, prompt, resultImage, aspectRatio) {
|
2025-08-27 13:43:55 +08:00
|
|
|
|
const card = document.createElement('div');
|
|
|
|
|
card.className = 'example-card bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl cursor-pointer';
|
|
|
|
|
card.innerHTML = `
|
2025-08-27 13:51:08 +08:00
|
|
|
|
<div class="overflow-hidden bg-gray-100 dark:bg-gray-900 aspect-[${aspectRatio}]">
|
2025-08-27 13:43:55 +08:00
|
|
|
|
<img src="${resultImage}" alt="${prompt.substring(0, 20)}..." class="w-full h-full object-cover transition-transform duration-500 hover:scale-110">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-4">
|
|
|
|
|
<p class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2">${prompt}</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2025-08-27 13:51:08 +08:00
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
// 点击事件 - 加载原图和填充表单
|
|
|
|
|
card.addEventListener('click', () => {
|
|
|
|
|
// 填充表单
|
|
|
|
|
document.getElementById('imageUrl').value = originalImage;
|
|
|
|
|
document.getElementById('editPrompt').value = prompt;
|
2025-08-27 13:51:08 +08:00
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
// 更新原始图片预览
|
|
|
|
|
const originalImageElement = document.getElementById('originalImage');
|
|
|
|
|
const originalImagePlaceholder = document.getElementById('originalImagePlaceholder');
|
|
|
|
|
originalImageElement.src = originalImage;
|
|
|
|
|
originalImageElement.classList.remove('hidden');
|
|
|
|
|
originalImagePlaceholder.classList.add('hidden');
|
2025-08-27 13:51:08 +08:00
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
// 添加选中效果
|
|
|
|
|
document.querySelectorAll('.example-card').forEach(c => c.classList.remove('ring-2', 'ring-primary'));
|
|
|
|
|
card.classList.add('ring-2', 'ring-primary');
|
|
|
|
|
});
|
2025-08-27 13:51:08 +08:00
|
|
|
|
|
2025-08-27 13:43:55 +08:00
|
|
|
|
return card;
|
|
|
|
|
}
|
2025-08-27 14:05:32 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 随机选择一个示例并填充表单
|
|
|
|
|
*/
|
|
|
|
|
handleRandomExample() {
|
|
|
|
|
if (!this.allExamples || this.allExamples.length === 0) {
|
|
|
|
|
alert('没有可用的示例数据');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 随机选择一个示例
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * this.allExamples.length);
|
|
|
|
|
const randomExample = this.allExamples[randomIndex];
|
|
|
|
|
|
|
|
|
|
// 填充表单数据
|
|
|
|
|
document.getElementById('imageUrl').value = randomExample.originalImage;
|
|
|
|
|
document.getElementById('editPrompt').value = randomExample.prompt;
|
|
|
|
|
|
|
|
|
|
// 更新原始图片预览
|
|
|
|
|
const originalImage = document.getElementById('originalImage');
|
|
|
|
|
const originalPlaceholder = document.getElementById('originalImagePlaceholder');
|
|
|
|
|
originalImage.src = randomExample.originalImage;
|
|
|
|
|
originalImage.classList.remove('hidden');
|
|
|
|
|
originalPlaceholder.classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
// 移除所有卡片选中状态
|
|
|
|
|
document.querySelectorAll('.example-card').forEach(card => {
|
|
|
|
|
card.classList.remove('ring-2', 'ring-primary');
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-27 13:43:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => new QwenImageEditor());
|