feat: 系统优化和功能完善
主要更新: - 调整并发配置为50人(数据库连接池30,Tomcat线程150,异步线程池5/20) - 实现无界阻塞队列(LinkedBlockingQueue)任务处理 - 实现分镜视频保存功能(保存到uploads目录) - 统一管理页面导航栏和右上角样式 - 添加日活用户统计功能 - 优化视频拼接和保存逻辑 - 添加部署文档和快速部署指南 - 更新.gitignore排除敏感配置文件
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user