Files
zjpb.net/templates/admin/site/edit.html

664 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'admin/model/edit.html' %}
{% block tail %}
{{ super() }}
<style>
.auto-fetch-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.generate-tags-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.generate-features-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.upload-logo-btn {
margin-top: 10px;
margin-bottom: 15px;
}
.logo-preview {
margin-top: 10px;
max-width: 200px;
max-height: 200px;
display: none;
border: 1px solid #ddd;
border-radius: 8px;
padding: 10px;
}
.logo-preview img {
max-width: 100%;
max-height: 150px;
object-fit: contain;
}
.fetch-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.tags-status {
margin-top: 10px;
padding: 10px;
border-radius: 8px;
display: none;
}
.fetch-status.success, .tags-status.success {
background-color: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.fetch-status.error, .tags-status.error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
.auto-fetch-btn .loading-icon, .generate-tags-btn .loading-icon {
display: none;
}
.auto-fetch-btn.loading .loading-icon, .generate-tags-btn.loading .loading-icon {
display: inline-block;
animation: spin 1s linear infinite;
}
.auto-fetch-btn.loading .normal-icon, .generate-tags-btn.loading .normal-icon {
display: none;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 标签输入框样式 */
.tag-input-wrapper {
margin-top: 10px;
}
.tag-input-field {
width: 100%;
padding: 8px 12px;
border: 1px solid #DCDFE6;
border-radius: 4px;
font-size: 14px;
}
.tag-input-field:focus {
outline: none;
border-color: #0052D9;
}
.tag-input-help {
margin-top: 5px;
font-size: 12px;
color: #606266;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 在URL字段后添加"自动获取"按钮
const urlField = document.querySelector('input[name="url"]');
if (urlField) {
const fetchBtn = document.createElement('button');
fetchBtn.type = 'button';
fetchBtn.className = 'btn btn-info auto-fetch-btn';
fetchBtn.innerHTML = '<span class="normal-icon">↻</span><span class="loading-icon">↻</span> 自动获取网站信息';
const statusDiv = document.createElement('div');
statusDiv.className = 'fetch-status';
urlField.parentNode.appendChild(fetchBtn);
urlField.parentNode.appendChild(statusDiv);
fetchBtn.addEventListener('click', function() {
const url = urlField.value.trim();
if (!url) {
showStatus('请先输入网站URL', 'error');
return;
}
// 显示加载状态
fetchBtn.disabled = true;
fetchBtn.classList.add('loading');
statusDiv.style.display = 'none';
// 调用API获取网站信息
fetch('/api/fetch-website-info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: url })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 自动填充表单字段
const nameField = document.querySelector('input[name="name"]');
const shortDescField = document.querySelector('input[name="short_desc"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const logoField = document.querySelector('input[name="logo"]');
if (nameField && data.data.name) {
nameField.value = data.data.name;
}
if (shortDescField && data.data.description) {
shortDescField.value = data.data.description.substring(0, 100);
}
if (descriptionField && data.data.description) {
descriptionField.value = data.data.description;
}
if (logoField && data.data.logo) {
logoField.value = data.data.logo;
}
showStatus('✓ 网站信息获取成功!已自动填充表单', 'success');
} else {
showStatus('✗ ' + (data.message || '获取失败,请手动填写'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showStatus('✗ 网络请求失败,请手动填写', 'error');
})
.finally(() => {
fetchBtn.disabled = false;
fetchBtn.classList.remove('loading');
});
});
function showStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = 'fetch-status ' + type;
statusDiv.style.display = 'block';
}
}
// 在Logo字段后添加"上传Logo"功能
const logoField = document.querySelector('input[name="logo"]');
if (logoField) {
// 创建文件输入框
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
// 创建上传按钮
const uploadBtn = document.createElement('button');
uploadBtn.type = 'button';
uploadBtn.className = 'btn btn-warning upload-logo-btn';
uploadBtn.innerHTML = '📁 上传Logo图片';
// 创建预览容器
const previewDiv = document.createElement('div');
previewDiv.className = 'logo-preview';
previewDiv.innerHTML = '<img src="" alt="Logo预览"><p style="margin-top:5px; font-size:12px; color:#666;">Logo预览</p>';
logoField.parentNode.appendChild(fileInput);
logoField.parentNode.appendChild(uploadBtn);
logoField.parentNode.appendChild(previewDiv);
// 点击按钮触发文件选择
uploadBtn.addEventListener('click', function() {
fileInput.click();
});
// 文件选择后自动上传
fileInput.addEventListener('change', function() {
const file = fileInput.files[0];
if (!file) return;
// 验证文件类型
if (!file.type.startsWith('image/')) {
alert('请选择图片文件!');
return;
}
// 验证文件大小限制5MB
if (file.size > 5 * 1024 * 1024) {
alert('图片文件不能超过5MB');
return;
}
// 上传文件
const formData = new FormData();
formData.append('logo', file);
uploadBtn.disabled = true;
uploadBtn.textContent = '上传中...';
fetch('/api/upload-logo', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 设置Logo字段值
logoField.value = data.path;
// 显示预览
const img = previewDiv.querySelector('img');
img.src = data.path;
previewDiv.style.display = 'block';
alert('✓ Logo上传成功');
} else {
alert('✗ ' + (data.message || '上传失败'));
}
})
.catch(error => {
console.error('Error:', error);
alert('✗ 上传失败,请重试');
})
.finally(() => {
uploadBtn.disabled = false;
uploadBtn.innerHTML = '📁 上传Logo图片';
fileInput.value = '';
});
});
// 如果Logo字段有值显示预览
if (logoField.value) {
const img = previewDiv.querySelector('img');
img.src = logoField.value;
previewDiv.style.display = 'block';
}
// 监听Logo字段变化更新预览
logoField.addEventListener('input', function() {
if (logoField.value) {
const img = previewDiv.querySelector('img');
img.src = logoField.value;
previewDiv.style.display = 'block';
} else {
previewDiv.style.display = 'none';
}
});
}
// 处理标签字段 - 添加手动输入功能
const tagsSelect = document.querySelector('select[name="tags"]');
if (tagsSelect) {
// 先等待一下,确保 Flask-Admin 已经初始化好 select
setTimeout(function() {
// 保存原始选中的标签(从数据库加载的)
const originalSelectedOptions = Array.from(tagsSelect.selectedOptions);
// 隐藏原始的select字段
tagsSelect.style.display = 'none';
// 创建文本输入框
const tagInputWrapper = document.createElement('div');
tagInputWrapper.className = 'tag-input-wrapper';
const tagInput = document.createElement('input');
tagInput.type = 'text';
tagInput.className = 'tag-input-field';
tagInput.placeholder = '输入标签名称按回车添加AI工具、图像处理、免费';
const tagHelpText = document.createElement('div');
tagHelpText.className = 'tag-input-help';
tagHelpText.textContent = '💡 提示:输入标签名称后按回车键添加,可以添加多个标签。已选标签会自动添加到下方列表。';
const selectedTagsDiv = document.createElement('div');
selectedTagsDiv.className = 'selected-tags';
selectedTagsDiv.style.marginTop = '10px';
tagInputWrapper.appendChild(tagInput);
tagInputWrapper.appendChild(tagHelpText);
tagInputWrapper.appendChild(selectedTagsDiv);
tagsSelect.parentNode.insertBefore(tagInputWrapper, tagsSelect.nextSibling);
// 显示已选标签
function updateSelectedTags() {
selectedTagsDiv.innerHTML = '';
const selectedOptions = Array.from(tagsSelect.selectedOptions);
// 如果没有选中的标签,显示提示
if (selectedOptions.length === 0) {
selectedTagsDiv.innerHTML = '<span style="color:#999; font-size:12px;">暂无已选标签</span>';
return;
}
selectedOptions.forEach(option => {
const tag = document.createElement('span');
tag.style.cssText = 'display:inline-block; background:#0052D9; color:white; padding:4px 10px; margin:4px; border-radius:4px; font-size:12px;';
// 获取标签文本 - 兼容多种方式
let tagText = option.textContent || option.innerText || option.text || option.innerHTML || option.label || `标签${option.value}`;
// 处理 <Tag XXX> 格式,提取出实际标签名称
const match = tagText.match(/<Tag\s+(.+?)>/);
if (match) {
tagText = match[1]; // 提取标签名称
}
tag.innerHTML = tagText + ' <span style="cursor:pointer; margin-left:5px; font-weight:bold;" data-tag-id="' + option.value + '">×</span>';
selectedTagsDiv.appendChild(tag);
// 为删除按钮添加点击事件
const deleteBtn = tag.querySelector('span[data-tag-id]');
if (deleteBtn) {
deleteBtn.addEventListener('click', function(e) {
e.stopPropagation();
option.selected = false;
updateSelectedTags();
});
}
});
}
// 添加标签
tagInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
const tagName = tagInput.value.trim();
if (!tagName) {
return;
}
// 检查标签是否已存在
let existingOption = null;
for (let option of tagsSelect.options) {
if (option.text.toLowerCase() === tagName.toLowerCase()) {
existingOption = option;
break;
}
}
if (existingOption) {
// 选中已存在的标签
existingOption.selected = true;
} else {
// 创建新标签选项使用负数ID表示新标签
const newOption = document.createElement('option');
newOption.value = 'new_' + Date.now();
newOption.text = tagName;
newOption.selected = true;
newOption.setAttribute('data-new-tag', 'true');
tagsSelect.appendChild(newOption);
}
tagInput.value = '';
updateSelectedTags();
}
});
// 初始化显示 - 使用延迟确保数据已加载
updateSelectedTags();
// 再次确保原始选中的标签保持选中状态
originalSelectedOptions.forEach(opt => {
// 在所有选项中找到对应的选项并设置为选中
for (let option of tagsSelect.options) {
if (option.value === opt.value) {
option.selected = true;
}
}
});
// 再次更新显示
setTimeout(function() {
updateSelectedTags();
}, 100);
// 表单提交时处理新标签
const form = tagsSelect.closest('form');
form.addEventListener('submit', function(e) {
// 收集所有新标签名称
const newTags = [];
Array.from(tagsSelect.options).forEach(option => {
if (option.selected && option.hasAttribute('data-new-tag')) {
newTags.push(option.text);
}
});
// 如果有新标签,添加到隐藏字段
if (newTags.length > 0) {
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'new_tags';
hiddenInput.value = newTags.join(',');
form.appendChild(hiddenInput);
}
});
// 在标签字段后添加"AI生成标签"按钮
const generateBtn = document.createElement('button');
generateBtn.type = 'button';
generateBtn.className = 'btn btn-success generate-tags-btn';
generateBtn.innerHTML = '<span class="normal-icon">✨</span><span class="loading-icon">↻</span> AI生成标签';
const tagsStatusDiv = document.createElement('div');
tagsStatusDiv.className = 'tags-status';
tagInputWrapper.appendChild(generateBtn);
tagInputWrapper.appendChild(tagsStatusDiv);
generateBtn.addEventListener('click', function() {
const nameField = document.querySelector('input[name="name"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const name = nameField ? nameField.value.trim() : '';
const description = descriptionField ? descriptionField.value.trim() : '';
if (!name || !description) {
showTagsStatus('请先填写网站名称和描述', 'error');
return;
}
// 显示加载状态
generateBtn.disabled = true;
generateBtn.classList.add('loading');
tagsStatusDiv.style.display = 'none';
// 调用API生成标签
fetch('/api/generate-tags', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
description: description
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.tags && data.tags.length > 0) {
// 自动添加生成的标签
data.tags.forEach(tagName => {
// 检查是否已存在
let exists = false;
for (let option of tagsSelect.options) {
if (option.text.toLowerCase() === tagName.toLowerCase()) {
option.selected = true;
exists = true;
break;
}
}
// 如果不存在,创建新标签
if (!exists) {
const newOption = document.createElement('option');
newOption.value = 'new_' + Date.now() + '_' + Math.random();
newOption.text = tagName;
newOption.selected = true;
newOption.setAttribute('data-new-tag', 'true');
tagsSelect.appendChild(newOption);
}
});
updateSelectedTags();
showTagsStatus('✓ AI已自动添加推荐标签' + data.tags.join(', '), 'success');
} else {
showTagsStatus('✗ ' + (data.message || '标签生成失败'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showTagsStatus('✗ 网络请求失败,请重试', 'error');
})
.finally(() => {
generateBtn.disabled = false;
generateBtn.classList.remove('loading');
});
});
function showTagsStatus(message, type) {
tagsStatusDiv.textContent = message;
tagsStatusDiv.className = 'tags-status ' + type;
tagsStatusDiv.style.display = 'block';
}
}, 50); // setTimeout 结束
}
// 在Description字段后添加"AI生成详细介绍"按钮
const descriptionField = document.querySelector('textarea[name="description"]');
if (descriptionField) {
// 创建生成按钮
const generateDescBtn = document.createElement('button');
generateDescBtn.type = 'button';
generateDescBtn.className = 'btn btn-success generate-features-btn';
generateDescBtn.innerHTML = '<span class="normal-icon">✨</span><span class="loading-icon">↻</span> AI生成详细介绍';
const descStatusDiv = document.createElement('div');
descStatusDiv.className = 'tags-status';
descriptionField.parentNode.appendChild(generateDescBtn);
descriptionField.parentNode.appendChild(descStatusDiv);
generateDescBtn.addEventListener('click', function() {
const nameField = document.querySelector('input[name="name"]');
const shortDescField = document.querySelector('input[name="short_desc"]');
const urlField = document.querySelector('input[name="url"]');
const name = nameField ? nameField.value.trim() : '';
const shortDesc = shortDescField ? shortDescField.value.trim() : '';
const url = urlField ? urlField.value.trim() : '';
if (!name) {
showDescStatus('请先填写网站名称', 'error');
return;
}
// 显示加载状态
generateDescBtn.disabled = true;
generateDescBtn.classList.add('loading');
descStatusDiv.style.display = 'none';
// 调用API生成详细介绍
fetch('/api/generate-description', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
short_desc: shortDesc,
url: url
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.description) {
// 自动填充到description字段
descriptionField.value = data.description;
showDescStatus('✓ AI已生成详细介绍', 'success');
} else {
showDescStatus('✗ ' + (data.message || '详细介绍生成失败'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showDescStatus('✗ 网络请求失败,请重试', 'error');
})
.finally(() => {
generateDescBtn.disabled = false;
generateDescBtn.classList.remove('loading');
});
});
function showDescStatus(message, type) {
descStatusDiv.textContent = message;
descStatusDiv.className = 'tags-status ' + type;
descStatusDiv.style.display = 'block';
}
}
// 在Features字段后添加"AI生成功能"按钮
const featuresField = document.querySelector('textarea[name="features"]');
if (featuresField) {
// 创建生成按钮
const generateFeaturesBtn = document.createElement('button');
generateFeaturesBtn.type = 'button';
generateFeaturesBtn.className = 'btn btn-success generate-features-btn';
generateFeaturesBtn.innerHTML = '<span class="normal-icon">✨</span><span class="loading-icon">↻</span> AI生成主要功能';
const featuresStatusDiv = document.createElement('div');
featuresStatusDiv.className = 'tags-status';
featuresField.parentNode.appendChild(generateFeaturesBtn);
featuresField.parentNode.appendChild(featuresStatusDiv);
generateFeaturesBtn.addEventListener('click', function() {
const nameField = document.querySelector('input[name="name"]');
const descriptionField = document.querySelector('textarea[name="description"]');
const urlField = document.querySelector('input[name="url"]');
const name = nameField ? nameField.value.trim() : '';
const description = descriptionField ? descriptionField.value.trim() : '';
const url = urlField ? urlField.value.trim() : '';
if (!name || !description) {
showFeaturesStatus('请先填写网站名称和描述', 'error');
return;
}
// 显示加载状态
generateFeaturesBtn.disabled = true;
generateFeaturesBtn.classList.add('loading');
featuresStatusDiv.style.display = 'none';
// 调用API生成功能
fetch('/api/generate-features', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
description: description,
url: url
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.features) {
// 自动填充到features字段
featuresField.value = data.features;
showFeaturesStatus('✓ AI已生成主要功能列表', 'success');
} else {
showFeaturesStatus('✗ ' + (data.message || '功能生成失败'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showFeaturesStatus('✗ 网络请求失败,请重试', 'error');
})
.finally(() => {
generateFeaturesBtn.disabled = false;
generateFeaturesBtn.classList.remove('loading');
});
});
function showFeaturesStatus(message, type) {
featuresStatusDiv.textContent = message;
featuresStatusDiv.className = 'tags-status ' + type;
featuresStatusDiv.style.display = 'block';
}
}
});
</script>
{% endblock %}