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:
Jowe
2025-12-27 22:45:09 +08:00
commit 2fbca6ebc7
32 changed files with 4068 additions and 0 deletions

View 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 %}

View 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 %}

View 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 %}