5b87594e36
- 剧本评测/分辨率提升/数字人/角色迁移/图片工作台/去水印/电商:新增外部拖拽文件上传 - 电商:爆款图复刻上传框支持拖拽+大滚动条,短视频/模特图/详情图滚动条精简回退 - 图片工作台:右侧输出面板移至左侧提示词上方,删除局部重绘遮罩/结果框 - 数字人:生成按钮改为「开始生成」 - 局部重绘:编辑遮罩→编辑页面 - 对话框生成器:新增对话/视频模式、模型/速度/深度选择按钮 - 视频时长默认改为5秒 - 工具箱页面空状态logo统一绿底亮色图标 - 多处CSS滚动条和布局优化
409 lines
17 KiB
HTML
409 lines
17 KiB
HTML
<!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',
|
|
secondary: '#6B7280',
|
|
accent: '#F59E0B'
|
|
},
|
|
fontFamily: {
|
|
sans: ['Inter', 'system-ui', 'sans-serif']
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style type="text/tailwindcss">
|
|
@layer utilities {
|
|
.dialog-item {
|
|
@apply p-3 border rounded-lg cursor-pointer transition-all hover:bg-primary/5 hover:border-primary;
|
|
}
|
|
}
|
|
</style>
|
|
<style>
|
|
.gen-dialog {
|
|
position: absolute;
|
|
min-width: 140px;
|
|
max-width: 280px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 24px rgba(0,0,0,0.12);
|
|
z-index: 10;
|
|
user-select: none;
|
|
transition: box-shadow 0.2s;
|
|
}
|
|
.gen-dialog:hover { box-shadow: 0 6px 32px rgba(0,0,0,0.18); }
|
|
.gen-dialog.active-drag { z-index: 20; box-shadow: 0 8px 40px rgba(0,0,0,0.22); }
|
|
|
|
/* 样式1:白色圆角 */
|
|
.gen-dialog.style1 { background: rgba(255,255,255,0.97); border: 2px solid #CBD5E1; }
|
|
.gen-dialog.style1 .gen-confirm { background: #165DFF; }
|
|
.gen-dialog.style1 .gen-text { color: #1e293b; }
|
|
|
|
/* 样式2:蓝色气泡 */
|
|
.gen-dialog.style2 { background: rgba(22,93,255,0.95); border: 2px solid #4F8AFF; border-radius: 16px 16px 4px 16px; }
|
|
.gen-dialog.style2 .gen-confirm { background: #fff; color: #165DFF; }
|
|
.gen-dialog.style2 .gen-text { color: #fff; }
|
|
.gen-dialog.style2 .gen-text::placeholder { color: rgba(255,255,255,0.6); }
|
|
|
|
/* 样式3:黄色提示 */
|
|
.gen-dialog.style3 { background: rgba(255,247,237,0.97); border: 2px solid #F59E0B; }
|
|
.gen-dialog.style3 .gen-confirm { background: #F59E0B; }
|
|
.gen-dialog.style3 .gen-text { color: #92400e; }
|
|
.gen-dialog.style3 .gen-text::placeholder { color: rgba(146,64,14,0.4); }
|
|
|
|
/* 样式4:灰色简约 */
|
|
.gen-dialog.style4 { background: rgba(248,250,252,0.97); border: 2px solid #6B7280; border-radius: 4px; }
|
|
.gen-dialog.style4 .gen-confirm { background: #6B7280; }
|
|
.gen-dialog.style4 .gen-text { color: #1f2937; }
|
|
|
|
.gen-text {
|
|
width: 100%;
|
|
border: none;
|
|
outline: none;
|
|
background: transparent;
|
|
resize: none;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
padding: 0;
|
|
font-family: inherit;
|
|
}
|
|
.gen-text::placeholder { color: rgba(0,0,0,0.3); }
|
|
|
|
.gen-confirm {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 4px 12px;
|
|
border-radius: 6px;
|
|
border: none;
|
|
color: #fff;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
margin-top: 6px;
|
|
}
|
|
.gen-confirm:hover { filter: brightness(1.1); transform: translateY(-1px); }
|
|
.gen-confirm:active { transform: translateY(0); }
|
|
|
|
/* 已确认状态 */
|
|
.gen-dialog.confirmed .gen-text { cursor: default; }
|
|
.gen-dialog.confirmed .gen-confirm { display: none; }
|
|
.gen-dialog.confirmed .gen-edit-hint { display: inline-block; }
|
|
.gen-dialog:not(.confirmed) .gen-edit-hint { display: none; }
|
|
|
|
.gen-edit-hint {
|
|
font-size: 10px;
|
|
color: rgba(0,0,0,0.3);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* 删除按钮 */
|
|
.gen-delete {
|
|
position: absolute;
|
|
top: -8px;
|
|
right: -8px;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: #EF4444;
|
|
color: #fff;
|
|
border: 2px solid #fff;
|
|
font-size: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.15s;
|
|
z-index: 5;
|
|
}
|
|
.gen-dialog:hover .gen-delete { opacity: 1; }
|
|
|
|
#previewContainer { position: relative; }
|
|
#previewImage { width: 100%; height: 100%; background-size: contain; background-position: center; background-repeat: no-repeat; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen">
|
|
<div class="container mx-auto px-4 py-8">
|
|
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-center mb-8 text-gray-800">
|
|
交互式对话框生成器
|
|
</h1>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-6">
|
|
<!-- 左侧面板 -->
|
|
<div class="lg:w-1/3 bg-white rounded-xl shadow-md p-6">
|
|
<!-- 文件上传区域 -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold mb-4 text-gray-700">
|
|
<i class="fa fa-upload mr-2 text-primary"></i>上传背景图片
|
|
</h2>
|
|
<div id="dropArea" class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer hover:border-primary transition-colors">
|
|
<i class="fa fa-image text-4xl text-gray-400 mb-3"></i>
|
|
<p class="text-gray-500 mb-2">点击或拖拽图片到此处</p>
|
|
<p class="text-xs text-gray-400">支持 JPG、PNG、WEBP 格式</p>
|
|
<input type="file" id="fileInput" accept="image/*" class="hidden">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 对话框选择区域 -->
|
|
<div>
|
|
<h2 class="text-lg font-semibold mb-4 text-gray-700">
|
|
<i class="fa fa-comment mr-2 text-primary"></i>点击添加对话框
|
|
</h2>
|
|
<p class="text-xs text-gray-400 mb-3">每点一次即在预览区新增一个对话框</p>
|
|
<div class="space-y-3" id="dialogList">
|
|
<div class="dialog-item" data-style="style1">
|
|
<span class="inline-block w-3 h-3 rounded bg-white border border-gray-300 mr-2 align-middle"></span>白色圆角对话框
|
|
</div>
|
|
<div class="dialog-item" data-style="style2">
|
|
<span class="inline-block w-3 h-3 rounded bg-blue-500 mr-2 align-middle"></span>蓝色气泡对话框
|
|
</div>
|
|
<div class="dialog-item" data-style="style3">
|
|
<span class="inline-block w-3 h-3 rounded bg-amber-400 mr-2 align-middle"></span>黄色提示对话框
|
|
</div>
|
|
<div class="dialog-item" data-style="style4">
|
|
<span class="inline-block w-3 h-3 rounded bg-gray-400 mr-2 align-middle"></span>灰色简约对话框
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-8">
|
|
<button id="clearBtn" class="w-full bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300 transition-all">
|
|
<i class="fa fa-trash mr-1"></i>清空全部对话框
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 右侧预览面板 -->
|
|
<div class="lg:w-2/3 bg-white rounded-xl shadow-md p-6">
|
|
<h2 class="text-lg font-semibold mb-4 text-gray-700">
|
|
<i class="fa fa-eye mr-2 text-primary"></i>预览区域
|
|
</h2>
|
|
<div id="previewContainer" class="relative w-full h-[500px] border rounded-lg bg-gray-100 overflow-hidden">
|
|
<div id="previewImage"></div>
|
|
<div id="dialogContainer" class="absolute inset-0"></div>
|
|
<div id="emptyTip" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-gray-400 pointer-events-none">
|
|
<i class="fa fa-image text-5xl mb-3"></i>
|
|
<p>上传图片后开始编辑</p>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-gray-500 mt-3">
|
|
<i class="fa fa-info-circle mr-1"></i>提示:对话框可拖动定位,输入文字后点确认即可渲染,双击已确认的框可重新编辑
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let dialogCount = 0;
|
|
|
|
const dropArea = document.getElementById('dropArea');
|
|
const fileInput = document.getElementById('fileInput');
|
|
const previewImage = document.getElementById('previewImage');
|
|
const emptyTip = document.getElementById('emptyTip');
|
|
const dialogList = document.getElementById('dialogList');
|
|
const dialogContainer = document.getElementById('dialogContainer');
|
|
const clearBtn = document.getElementById('clearBtn');
|
|
const previewContainer = document.getElementById('previewContainer');
|
|
|
|
// ===== 文件上传 =====
|
|
dropArea.addEventListener('click', () => fileInput.click());
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(evt => {
|
|
dropArea.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); });
|
|
});
|
|
dropArea.addEventListener('drop', e => handleFile(e.dataTransfer.files[0]));
|
|
fileInput.addEventListener('change', e => handleFile(e.target.files[0]));
|
|
|
|
function handleFile(file) {
|
|
if (!file || !file.type.startsWith('image/')) return;
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
previewImage.style.backgroundImage = `url(${e.target.result})`;
|
|
emptyTip.classList.add('hidden');
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
// ===== 点击添加对话框 =====
|
|
dialogList.addEventListener('click', e => {
|
|
const item = e.target.closest('.dialog-item');
|
|
if (!item) return;
|
|
createDialog(item.dataset.style);
|
|
});
|
|
|
|
function createDialog(style) {
|
|
dialogCount++;
|
|
const dialog = document.createElement('div');
|
|
dialog.className = `gen-dialog ${style}`;
|
|
dialog.dataset.style = style;
|
|
|
|
// 随机偏移避免完全重叠
|
|
const offsetX = 30 + (dialogCount * 25) % 200;
|
|
const offsetY = 30 + (dialogCount * 20) % 150;
|
|
dialog.style.left = offsetX + 'px';
|
|
dialog.style.top = offsetY + 'px';
|
|
dialog.style.padding = '12px 14px';
|
|
|
|
// 删除按钮
|
|
const delBtn = document.createElement('div');
|
|
delBtn.className = 'gen-delete';
|
|
delBtn.innerHTML = '<i class="fa fa-times" style="font-size:9px"></i>';
|
|
delBtn.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
dialog.remove();
|
|
});
|
|
|
|
// 文本输入区
|
|
const textarea = document.createElement('textarea');
|
|
textarea.className = 'gen-text';
|
|
textarea.rows = 2;
|
|
textarea.placeholder = '输入文本...';
|
|
// 阻止拖动冲突
|
|
textarea.addEventListener('mousedown', e => e.stopPropagation());
|
|
textarea.addEventListener('touchstart', e => e.stopPropagation());
|
|
|
|
// 底部:确认按钮 + 编辑提示
|
|
const bottomRow = document.createElement('div');
|
|
bottomRow.style.cssText = 'display:flex; justify-content:flex-end; align-items:center;';
|
|
|
|
const editHint = document.createElement('span');
|
|
editHint.className = 'gen-edit-hint';
|
|
editHint.textContent = '双击编辑';
|
|
|
|
const confirmBtn = document.createElement('button');
|
|
confirmBtn.className = 'gen-confirm';
|
|
confirmBtn.innerHTML = '<i class="fa fa-check" style="font-size:10px"></i> 确认';
|
|
|
|
bottomRow.appendChild(editHint);
|
|
bottomRow.appendChild(confirmBtn);
|
|
|
|
dialog.appendChild(delBtn);
|
|
dialog.appendChild(textarea);
|
|
dialog.appendChild(bottomRow);
|
|
dialogContainer.appendChild(dialog);
|
|
|
|
// ===== 确认 =====
|
|
confirmBtn.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
const text = textarea.value.trim();
|
|
if (!text) return;
|
|
|
|
// 把textarea换成纯文本展示
|
|
const textDisplay = document.createElement('div');
|
|
textDisplay.className = 'gen-text';
|
|
textDisplay.style.whiteSpace = 'pre-wrap';
|
|
textDisplay.textContent = text;
|
|
textarea.replaceWith(textDisplay);
|
|
|
|
dialog.classList.add('confirmed');
|
|
});
|
|
|
|
// ===== 双击重新编辑 =====
|
|
dialog.addEventListener('dblclick', e => {
|
|
if (!dialog.classList.contains('confirmed')) return;
|
|
e.stopPropagation();
|
|
|
|
const textDisplay = dialog.querySelector('.gen-text');
|
|
const currentText = textDisplay.textContent;
|
|
|
|
const newTextarea = document.createElement('textarea');
|
|
newTextarea.className = 'gen-text';
|
|
newTextarea.rows = 2;
|
|
newTextarea.value = currentText;
|
|
newTextarea.addEventListener('mousedown', e => e.stopPropagation());
|
|
newTextarea.addEventListener('touchstart', e => e.stopPropagation());
|
|
|
|
textDisplay.replaceWith(newTextarea);
|
|
dialog.classList.remove('confirmed');
|
|
newTextarea.focus();
|
|
});
|
|
|
|
// ===== 拖动 =====
|
|
bindDrag(dialog);
|
|
|
|
// 自动聚焦输入
|
|
textarea.focus();
|
|
}
|
|
|
|
// ===== 拖动逻辑 =====
|
|
function bindDrag(element) {
|
|
let dragging = false, ox, oy;
|
|
|
|
element.addEventListener('mousedown', e => {
|
|
if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'BUTTON' || e.target.closest('.gen-delete') || e.target.closest('.gen-confirm')) return;
|
|
dragging = true;
|
|
element.classList.add('active-drag');
|
|
const rect = element.getBoundingClientRect();
|
|
ox = e.clientX - rect.left;
|
|
oy = e.clientY - rect.top;
|
|
e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('mousemove', e => {
|
|
if (!dragging) return;
|
|
const cr = dialogContainer.getBoundingClientRect();
|
|
let x = e.clientX - ox - cr.left;
|
|
let y = e.clientY - oy - cr.top;
|
|
x = Math.max(0, Math.min(x, cr.width - element.offsetWidth));
|
|
y = Math.max(0, Math.min(y, cr.height - element.offsetHeight));
|
|
element.style.left = x + 'px';
|
|
element.style.top = y + 'px';
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
if (dragging) {
|
|
dragging = false;
|
|
element.classList.remove('active-drag');
|
|
}
|
|
});
|
|
|
|
// 触摸支持
|
|
element.addEventListener('touchstart', e => {
|
|
if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'BUTTON' || e.target.closest('.gen-delete') || e.target.closest('.gen-confirm')) return;
|
|
dragging = true;
|
|
element.classList.add('active-drag');
|
|
const rect = element.getBoundingClientRect();
|
|
const touch = e.touches[0];
|
|
ox = touch.clientX - rect.left;
|
|
oy = touch.clientY - rect.top;
|
|
}, { passive: true });
|
|
|
|
document.addEventListener('touchmove', e => {
|
|
if (!dragging) return;
|
|
const touch = e.touches[0];
|
|
const cr = dialogContainer.getBoundingClientRect();
|
|
let x = touch.clientX - ox - cr.left;
|
|
let y = touch.clientY - oy - cr.top;
|
|
x = Math.max(0, Math.min(x, cr.width - element.offsetWidth));
|
|
y = Math.max(0, Math.min(y, cr.height - element.offsetHeight));
|
|
element.style.left = x + 'px';
|
|
element.style.top = y + 'px';
|
|
}, { passive: true });
|
|
|
|
document.addEventListener('touchend', () => {
|
|
if (dragging) {
|
|
dragging = false;
|
|
element.classList.remove('active-drag');
|
|
}
|
|
});
|
|
}
|
|
|
|
// ===== 清空 =====
|
|
clearBtn.addEventListener('click', () => {
|
|
dialogContainer.innerHTML = '';
|
|
dialogCount = 0;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|