优化前端代码架构 - 提取CSS变量和工具类
主要改进: - 新增 variables.css 统一管理所有主题相关的CSS变量 - 新增 utilities.css 提供可复用的工具类组件 - 重构所有Vue组件,移除重复的CSS代码 - 统一使用CSS变量实现一致的粉色主题(#FF6B9D) - 改进代码组织结构,提升可维护性 - 优化样式继承和复用机制 修改文件: - 新增:frontend/src/styles/variables.css, utilities.css - 重构:App.vue, 所有视图组件和组件文件 - 更新:style.css, element-plus.css 技术亮点: - 模块化CSS架构,使用@import导入 - 统一的颜色、间距、阴影、过渡效果变量 - 卡片、按钮、布局等通用工具类 - 响应式设计支持
This commit is contained in:
@@ -1,64 +1,148 @@
|
||||
<template>
|
||||
<div class="settings">
|
||||
<PageHeader title="设置" icon="⚙️" />
|
||||
<div class="page-container">
|
||||
<PageHeader title="系统设置" icon="⚙️" />
|
||||
|
||||
<el-card class="settings-card" shadow="hover">
|
||||
<el-card class="settings-card" shadow="hover" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">📝 关于</span>
|
||||
<span class="card-title">🎨 基础配置</span>
|
||||
<el-button type="primary" @click="handleSave" size="large" :loading="saving">
|
||||
<span class="btn-icon">💾</span>
|
||||
保存配置
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="about-content">
|
||||
<div class="about-item">
|
||||
<span class="about-label">项目名称</span>
|
||||
<span class="about-value">代理池管理系统</span>
|
||||
</div>
|
||||
<div class="about-item">
|
||||
<span class="about-label">版本号</span>
|
||||
<span class="about-value">v1.0.0</span>
|
||||
</div>
|
||||
<div class="about-item">
|
||||
<span class="about-label">后端API</span>
|
||||
<span class="about-value">http://localhost:3000</span>
|
||||
</div>
|
||||
<div class="about-item">
|
||||
<span class="about-label">前端服务</span>
|
||||
<span class="about-value">http://localhost:8080</span>
|
||||
</div>
|
||||
<div class="about-item">
|
||||
<span class="about-label">数据库</span>
|
||||
<span class="about-value">SQLite</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form :model="settings" label-width="150px" class="settings-form">
|
||||
<el-form-item label="数据库路径">
|
||||
<el-input v-model="settings.db_path" placeholder="数据库文件路径" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="爬取超时">
|
||||
<el-input-number
|
||||
v-model="settings.crawl_timeout"
|
||||
:min="5"
|
||||
:max="120"
|
||||
:step="5"
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-suffix">秒</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="验证超时">
|
||||
<el-input-number
|
||||
v-model="settings.validation_timeout"
|
||||
:min="3"
|
||||
:max="60"
|
||||
:step="1"
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-suffix">秒</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最大重试次数">
|
||||
<el-input-number
|
||||
v-model="settings.max_retries"
|
||||
:min="0"
|
||||
:max="10"
|
||||
class="setting-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="默认并发数">
|
||||
<el-input-number
|
||||
v-model="settings.default_concurrency"
|
||||
:min="10"
|
||||
:max="200"
|
||||
:step="10"
|
||||
class="setting-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="最低代理分数">
|
||||
<el-input-number
|
||||
v-model="settings.min_proxy_score"
|
||||
:min="0"
|
||||
:max="10"
|
||||
:step="1"
|
||||
class="setting-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="代理过期时间">
|
||||
<el-input-number
|
||||
v-model="settings.proxy_expiry_days"
|
||||
:min="1"
|
||||
:max="30"
|
||||
:step="1"
|
||||
class="setting-input"
|
||||
/>
|
||||
<span class="setting-suffix">天</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const settings = reactive({
|
||||
db_path: '',
|
||||
crawl_timeout: 30,
|
||||
validation_timeout: 10,
|
||||
max_retries: 3,
|
||||
default_concurrency: 50,
|
||||
min_proxy_score: 5,
|
||||
proxy_expiry_days: 7
|
||||
})
|
||||
|
||||
async function fetchSettings() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await fetch('http://localhost:8923/api/settings')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
Object.assign(settings, data)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await fetch('http://localhost:8923/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
ElMessage.success('配置保存成功啦~')
|
||||
} else {
|
||||
ElMessage.error('配置保存失败呢~')
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings {
|
||||
padding: 20px;
|
||||
background: var(--theme-bg);
|
||||
min-height: 100vh;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
background: var(--theme-bg-card);
|
||||
border: 1px solid var(--theme-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.settings-card:hover {
|
||||
box-shadow: 0 4px 16px rgba(255, 107, 157, 0.15);
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--theme-primary);
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -69,132 +153,26 @@ import PageHeader from '../components/PageHeader.vue'
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--theme-primary);
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
.settings-form {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.setting-item:hover {
|
||||
background-color: var(--theme-bg-light);
|
||||
border-radius: 8px;
|
||||
padding: 20px 10px;
|
||||
margin: 0 -10px;
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
flex: 1;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--theme-text);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
font-size: 14px;
|
||||
color: var(--theme-text-secondary);
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.setting-input {
|
||||
width: 150px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.setting-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
padding: 12px 30px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--theme-primary);
|
||||
border: none;
|
||||
color: var(--theme-bg);
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
padding: 12px 30px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--theme-bg-light);
|
||||
border: 1px solid var(--theme-border);
|
||||
color: var(--theme-text);
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
.setting-suffix {
|
||||
margin-left: 10px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 18px;
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.about-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.about-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.about-item:hover {
|
||||
background-color: var(--theme-bg-light);
|
||||
border-radius: 8px;
|
||||
padding: 15px 10px;
|
||||
margin: 0 -10px;
|
||||
}
|
||||
|
||||
.about-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.about-label {
|
||||
font-size: 16px;
|
||||
color: var(--theme-text-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.about-value {
|
||||
font-size: 16px;
|
||||
color: var(--theme-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user