feat: 系统优化和功能完善

主要更新:
- 调整并发配置为50人(数据库连接池30,Tomcat线程150,异步线程池5/20)
- 实现无界阻塞队列(LinkedBlockingQueue)任务处理
- 实现分镜视频保存功能(保存到uploads目录)
- 统一管理页面导航栏和右上角样式
- 添加日活用户统计功能
- 优化视频拼接和保存逻辑
- 添加部署文档和快速部署指南
- 更新.gitignore排除敏感配置文件
This commit is contained in:
AIGC Developer
2025-11-07 19:09:50 +08:00
parent b5820d9be2
commit 1e71ae6a26
146 changed files with 10720 additions and 3032 deletions

View File

@@ -51,10 +51,13 @@
<input type="text" placeholder="搜索你想要的内容" class="search-input">
</div>
<div class="header-actions">
<el-icon class="notification-icon"><Bell /></el-icon>
<div class="notification-icon-wrapper">
<el-icon class="notification-icon"><Bell /></el-icon>
<span class="notification-badge"></span>
</div>
<div class="user-avatar">
<div class="avatar-placeholder"></div>
<el-icon class="dropdown-icon"><ArrowDown /></el-icon>
<img src="/images/backgrounds/welcome.jpg" alt="用户头像" />
<el-icon class="arrow-down"><ArrowDown /></el-icon>
</div>
</div>
</header>
@@ -89,8 +92,8 @@
<h3>{{ level.name }}</h3>
</div>
<div class="card-body">
<p class="price">${{ level.price }}/</p>
<p class="description">{{ level.description }}</p>
<p class="price">${{ level.price || 0 }}/</p>
<p class="description">{{ level.description || `包含${level.resourcePoints || 0}资源点/月` }}</p>
</div>
<div class="card-footer">
<el-button type="primary" @click="editLevel(level)">编辑</el-button>
@@ -374,7 +377,7 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
@@ -390,6 +393,7 @@ import {
Refresh
} from '@element-plus/icons-vue'
import cleanupApi from '@/api/cleanup'
import { getMembershipLevels, updateMembershipLevel } from '@/api/members'
const router = useRouter()
@@ -397,11 +401,8 @@ const router = useRouter()
const activeTab = ref('membership')
// 会员收费标准相关
const membershipLevels = ref([
{ id: 1, name: '免费版会员', price: '0', resourcePoints: 200, description: '包含200资源点/月' },
{ id: 2, name: '标准版会员', price: '50', resourcePoints: 500, description: '包含500资源点/月' },
{ id: 3, name: '专业版会员', price: '250', resourcePoints: 2000, description: '包含2000资源点/月' }
])
const membershipLevels = ref([])
const loadingLevels = ref(false)
const editDialogVisible = ref(false)
const editFormRef = ref(null)
@@ -473,7 +474,12 @@ const goToSettings = () => {
}
const editLevel = (level) => {
Object.assign(editForm, level)
// 映射后端数据到前端表单
editForm.id = level.id
editForm.level = level.name || level.displayName
editForm.price = level.price ? String(level.price) : '0'
editForm.resourcePoints = level.pointsBonus || level.resourcePoints || 0
editForm.validityPeriod = 'monthly' // 默认月付
editDialogVisible.value = true
}
@@ -491,13 +497,96 @@ const handlePriceInput = (value) => {
const saveEdit = async () => {
const valid = await editFormRef.value.validate()
if (valid) {
if (!valid) return
try {
// 调用后端API更新会员等级配置
const updateData = {
price: parseFloat(editForm.price),
resourcePoints: parseInt(editForm.resourcePoints),
pointsBonus: parseInt(editForm.resourcePoints),
description: `包含${editForm.resourcePoints}资源点/月`
}
await updateMembershipLevel(editForm.id, updateData)
// 更新本地数据
const index = membershipLevels.value.findIndex(level => level.id === editForm.id)
if (index !== -1) {
Object.assign(membershipLevels.value[index], editForm)
ElMessage.success('会员等级更新成功')
editDialogVisible.value = false
membershipLevels.value[index].price = parseFloat(editForm.price)
membershipLevels.value[index].pointsBonus = parseInt(editForm.resourcePoints)
membershipLevels.value[index].resourcePoints = parseInt(editForm.resourcePoints)
membershipLevels.value[index].description = `包含${editForm.resourcePoints}资源点/月`
}
ElMessage.success('会员等级更新成功')
editDialogVisible.value = false
// 重新加载会员等级配置
await loadMembershipLevels()
} catch (error) {
console.error('更新会员等级失败:', error)
ElMessage.error('更新会员等级失败: ' + (error.response?.data?.message || error.message))
}
}
// 加载会员等级配置
const loadMembershipLevels = async () => {
loadingLevels.value = true
try {
const response = await getMembershipLevels()
console.log('会员等级配置响应:', response)
// 检查响应结构
let levels = []
if (response.data) {
if (response.data.success && response.data.data) {
levels = response.data.data
} else if (Array.isArray(response.data)) {
levels = response.data
} else if (response.data.data && Array.isArray(response.data.data)) {
levels = response.data.data
}
}
console.log('解析后的会员等级数据:', levels)
// 映射后端数据到前端显示格式
if (levels.length > 0) {
membershipLevels.value = levels.map(level => ({
id: level.id,
name: level.displayName || level.name,
price: level.price || 0,
resourcePoints: level.pointsBonus || 0,
pointsBonus: level.pointsBonus || 0,
description: level.description || `包含${level.pointsBonus || 0}资源点/月`
}))
console.log('会员等级配置加载成功:', membershipLevels.value)
} else {
// 如果没有数据,使用默认值
console.warn('数据库中没有会员等级数据,使用默认值')
membershipLevels.value = [
{ id: 1, name: '免费版会员', price: 0, resourcePoints: 200, description: '包含200资源点/月' },
{ id: 2, name: '标准版会员', price: 59, resourcePoints: 500, description: '包含500资源点/月' },
{ id: 3, name: '专业版会员', price: 250, resourcePoints: 2000, description: '包含2000资源点/月' }
]
}
} catch (error) {
console.error('加载会员等级配置失败:', error)
console.error('错误详情:', error.response?.data || error.message)
// 显示更详细的错误信息
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || '未知错误'
ElMessage.warning(`加载会员等级配置失败: ${errorMessage},使用默认配置`)
// 使用默认值,确保页面可以正常显示
membershipLevels.value = [
{ id: 1, name: '免费版会员', price: 0, resourcePoints: 200, description: '包含200资源点/月' },
{ id: 2, name: '标准版会员', price: 59, resourcePoints: 500, description: '包含500资源点/月' },
{ id: 3, name: '专业版会员', price: 250, resourcePoints: 2000, description: '包含2000资源点/月' }
]
} finally {
loadingLevels.value = false
}
}
@@ -613,8 +702,11 @@ const saveCleanupConfig = async () => {
}
}
// 页面加载时获取统计信息
refreshStats()
// 页面加载时获取统计信息和会员等级配置
onMounted(() => {
refreshStats()
loadMembershipLevels()
})
</script>
<style scoped>
@@ -627,19 +719,31 @@ refreshStats()
/* 左侧导航栏 */
.sidebar {
width: 320px;
width: 240px;
background: white;
border-right: 1px solid #e2e8f0;
border-right: 1px solid #e9ecef;
display: flex;
flex-direction: column;
padding: 24px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
}
.logo {
display: flex;
align-items: center;
padding: 0 28px;
padding: 0 20px;
margin-bottom: 32px;
}
.logo-icon {
width: 24px;
height: 24px;
background: #3b82f6;
border-radius: 4px;
margin-right: 12px;
}
.logo span {
font-size: 18px;
font-weight: 600;
color: #1e293b;
@@ -647,43 +751,46 @@ refreshStats()
.nav-menu {
flex: 1;
padding: 0 24px;
padding: 0 16px;
}
.nav-item {
display: flex;
align-items: center;
padding: 18px 24px;
margin-bottom: 6px;
border-radius: 10px;
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
color: #64748b;
font-size: 16px;
color: #6b7280;
font-size: 14px;
font-weight: 500;
}
.nav-item:hover {
background: #f1f5f9;
color: #334155;
background: #f3f4f6;
color: #374151;
}
.nav-item.active {
background: #eff6ff;
background: #dbeafe;
color: #3b82f6;
}
.nav-item .el-icon {
margin-right: 16px;
font-size: 22px;
margin-right: 12px;
font-size: 18px;
}
.nav-item span {
font-size: 16px;
font-size: 14px;
font-weight: 500;
}
.sidebar-footer {
padding: 0 32px 20px;
padding: 20px;
border-top: 1px solid #e9ecef;
background: #f8f9fa;
margin-top: auto;
}
@@ -691,13 +798,8 @@ refreshStats()
.system-uptime {
font-size: 14px;
color: #64748b;
margin-bottom: 10px;
line-height: 1.5;
}
.online-users,
.system-uptime {
margin-bottom: 5px;
line-height: 1.5;
}
.highlight {
@@ -748,38 +850,60 @@ refreshStats()
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.notification-icon-wrapper {
position: relative;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: background 0.2s ease;
}
.notification-icon-wrapper:hover {
background: #f3f4f6;
}
.notification-icon {
font-size: 20px;
color: #606266;
margin-right: 20px;
cursor: pointer;
color: #6b7280;
}
.notification-badge {
position: absolute;
top: 4px;
right: 4px;
width: 8px;
height: 8px;
background: #ef4444;
border-radius: 50%;
}
.user-avatar {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 8px;
border-radius: 6px;
transition: background 0.2s ease;
}
.avatar-placeholder {
.user-avatar:hover {
background: #f3f4f6;
}
.user-avatar img {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
font-weight: bold;
object-fit: cover;
}
.dropdown-icon {
.user-avatar .arrow-down {
font-size: 12px;
color: #909399;
color: #6b7280;
}
.content-section {