feat: 完成全站UI优化 - 科技感/未来风设计
- 前台页面全面升级为Tailwind CSS框架 - 引入Google Fonts (Space Grotesk, Noto Sans) - 主色调更新为#25c0f4 (cyan blue) - 实现玻璃态效果和渐变背景 - 优化首页网格卡片布局和悬停动画 - 优化详情页双栏布局和渐变Logo光晕 - 优化管理员登录页,添加科技网格背景 - Flask-Admin后台完整深色主题 - 统一Material Symbols图标系统 - 网站自动抓取功能界面优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
17
templates/admin/custom_base.html
Normal file
17
templates/admin/custom_base.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends 'admin/base.html' %}
|
||||
|
||||
{% block head_css %}
|
||||
{{ super() }}
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Noto+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<!-- Custom Admin Theme -->
|
||||
<link href="{{ url_for('static', filename='css/admin-theme.css') }}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<body class="admin-theme">
|
||||
{{ super() }}
|
||||
</body>
|
||||
{% endblock %}
|
||||
124
templates/admin/site/create.html
Normal file
124
templates/admin/site/create.html
Normal file
@@ -0,0 +1,124 @@
|
||||
{% extends 'admin/model/create.html' %}
|
||||
|
||||
{% block tail %}
|
||||
{{ super() }}
|
||||
<style>
|
||||
.auto-fetch-btn {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.fetch-status {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
.fetch-status.success {
|
||||
background-color: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
color: #4ade80;
|
||||
}
|
||||
.fetch-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 {
|
||||
display: none;
|
||||
}
|
||||
.auto-fetch-btn.loading .loading-icon {
|
||||
display: inline-block;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.auto-fetch-btn.loading .normal-icon {
|
||||
display: none;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</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';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
124
templates/admin/site/edit.html
Normal file
124
templates/admin/site/edit.html
Normal file
@@ -0,0 +1,124 @@
|
||||
{% extends 'admin/model/edit.html' %}
|
||||
|
||||
{% block tail %}
|
||||
{{ super() }}
|
||||
<style>
|
||||
.auto-fetch-btn {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.fetch-status {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
.fetch-status.success {
|
||||
background-color: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
color: #4ade80;
|
||||
}
|
||||
.fetch-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 {
|
||||
display: none;
|
||||
}
|
||||
.auto-fetch-btn.loading .loading-icon {
|
||||
display: inline-block;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
.auto-fetch-btn.loading .normal-icon {
|
||||
display: none;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</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';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user