主要更新: - 修复了所有主要的代码逻辑错误 - 实现了完整的任务清理系统 - 添加了系统设置页面的任务清理管理功能 - 修复了API调用认证问题 - 优化了密码加密和验证机制 - 统一了错误处理模式 - 添加了详细的文档和测试工具 新增功能: - 任务清理管理界面 - 任务归档和清理日志 - API监控和诊断工具 - 完整的测试套件 技术改进: - 修复了Repository方法调用错误 - 统一了模型方法调用 - 改进了类型安全性 - 优化了代码结构和可维护性
883 lines
20 KiB
Vue
883 lines
20 KiB
Vue
<template>
|
||
<div class="member-management">
|
||
<!-- 左侧导航栏 -->
|
||
<aside class="sidebar">
|
||
<div class="logo">
|
||
<div class="logo-icon"></div>
|
||
<span>LOGO</span>
|
||
</div>
|
||
<nav class="nav-menu">
|
||
<div class="nav-item" @click="goToDashboard">
|
||
<el-icon><Grid /></el-icon>
|
||
<span>数据仪表台</span>
|
||
</div>
|
||
<div class="nav-item active">
|
||
<el-icon><User /></el-icon>
|
||
<span>会员管理</span>
|
||
</div>
|
||
<div class="nav-item" @click="goToOrders">
|
||
<el-icon><ShoppingCart /></el-icon>
|
||
<span>订单管理</span>
|
||
</div>
|
||
<div class="nav-item" @click="goToAPI">
|
||
<el-icon><Document /></el-icon>
|
||
<span>API管理</span>
|
||
</div>
|
||
<div class="nav-item" @click="goToTasks">
|
||
<el-icon><Document /></el-icon>
|
||
<span>生成任务记录</span>
|
||
</div>
|
||
<div class="nav-item" @click="goToSettings">
|
||
<el-icon><Setting /></el-icon>
|
||
<span>系统设置</span>
|
||
</div>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<div class="online-users">
|
||
当前在线用户: <span class="highlight">87/500</span>
|
||
</div>
|
||
<div class="system-uptime">
|
||
系统运行时间: <span class="highlight">48小时32分</span>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- 主内容区域 -->
|
||
<main class="main-content">
|
||
<!-- 顶部搜索栏 -->
|
||
<header class="top-header">
|
||
<div class="search-bar">
|
||
<el-icon class="search-icon"><Search /></el-icon>
|
||
<input type="text" placeholder="搜索你的想要的内容" class="search-input" />
|
||
</div>
|
||
<div class="header-actions">
|
||
<el-icon class="notification-icon"><Bell /></el-icon>
|
||
<el-icon class="help-icon"><QuestionFilled /></el-icon>
|
||
<div class="user-avatar">
|
||
<img src="/images/backgrounds/welcome.jpg" alt="用户头像" />
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- 会员列表内容 -->
|
||
<section class="member-content">
|
||
<div class="content-header">
|
||
<h2>会员列表</h2>
|
||
<div class="selection-info" v-if="selectedMembers.length > 0">
|
||
已选择{{ selectedMembers.length }}项
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-toolbar">
|
||
<div class="toolbar-left">
|
||
<el-select v-model="selectedLevel" placeholder="全部等级" size="small" @change="handleLevelChange">
|
||
<el-option label="全部等级" value="all" />
|
||
<el-option label="专业会员" value="professional" />
|
||
<el-option label="标准会员" value="standard" />
|
||
</el-select>
|
||
</div>
|
||
<div class="toolbar-right">
|
||
<el-button type="danger" size="small" @click="deleteSelected" :disabled="selectedMembers.length === 0">
|
||
<el-icon><Delete /></el-icon>
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table class="member-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="checkbox-col">
|
||
<input type="checkbox" @change="toggleAllSelection" :checked="isAllSelected" />
|
||
</th>
|
||
<th>用户ID</th>
|
||
<th>用户名</th>
|
||
<th>会员等级</th>
|
||
<th>剩余资源点</th>
|
||
<th>到期时间</th>
|
||
<th>编辑</th>
|
||
<th>删除</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="member in memberList" :key="member.id" class="table-row">
|
||
<td class="checkbox-col">
|
||
<input
|
||
type="checkbox"
|
||
:checked="selectedMembers.some(m => m.id === member.id)"
|
||
@change="toggleMemberSelection(member)" />
|
||
</td>
|
||
<td>{{ member.id }}</td>
|
||
<td>{{ member.username }}</td>
|
||
<td>
|
||
<span class="level-tag" :class="member.level === '专业会员' ? 'professional' : 'standard'">
|
||
{{ member.level }}
|
||
</span>
|
||
</td>
|
||
<td>{{ member.points.toLocaleString() }}</td>
|
||
<td>{{ member.expiryDate }}</td>
|
||
<td>
|
||
<button class="action-btn edit-btn" @click="editMember(member)">编辑</button>
|
||
</td>
|
||
<td>
|
||
<button class="action-btn delete-btn" @click="deleteMember(member)">删除</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-container">
|
||
<div class="pagination">
|
||
<button class="page-btn" @click="prevPage" :disabled="currentPage === 1">‹</button>
|
||
<button
|
||
v-for="page in visiblePages"
|
||
:key="page"
|
||
class="page-btn"
|
||
:class="{ active: page === currentPage }"
|
||
@click="goToPage(page)">
|
||
{{ page }}
|
||
</button>
|
||
<button class="page-btn" @click="nextPage" :disabled="currentPage === totalPages">›</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<!-- 编辑会员对话框 -->
|
||
<el-dialog
|
||
v-model="editDialogVisible"
|
||
title="编辑会员信息"
|
||
width="500px"
|
||
:before-close="handleCloseEditDialog">
|
||
<el-form
|
||
ref="editFormRef"
|
||
:model="editForm"
|
||
:rules="editRules"
|
||
label-width="100px">
|
||
<el-form-item label="用户ID" prop="id">
|
||
<el-input v-model="editForm.id" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="用户名" prop="username">
|
||
<el-input v-model="editForm.username" placeholder="请输入用户名" />
|
||
</el-form-item>
|
||
<el-form-item label="会员等级" prop="level">
|
||
<el-select v-model="editForm.level" placeholder="请选择会员等级">
|
||
<el-option label="专业会员" value="专业会员" />
|
||
<el-option label="标准会员" value="标准会员" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="剩余资源点" prop="points">
|
||
<el-input-number
|
||
v-model="editForm.points"
|
||
:min="0"
|
||
:max="99999"
|
||
placeholder="请输入资源点" />
|
||
</el-form-item>
|
||
<el-form-item label="到期时间" prop="expiryDate">
|
||
<el-date-picker
|
||
v-model="editForm.expiryDate"
|
||
type="date"
|
||
placeholder="选择到期时间"
|
||
format="YYYY-MM-DD"
|
||
value-format="YYYY-MM-DD" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveEdit" :loading="saveLoading">保存</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import {
|
||
Grid,
|
||
User,
|
||
ShoppingCart,
|
||
Document,
|
||
Setting,
|
||
User as Search,
|
||
Bell,
|
||
User as ArrowDown,
|
||
User as Edit,
|
||
User as Delete
|
||
} from '@element-plus/icons-vue'
|
||
import * as memberAPI from '@/api/members'
|
||
|
||
const router = useRouter()
|
||
|
||
// 数据状态
|
||
const selectedMembers = ref([])
|
||
const selectedLevel = ref('all')
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
const totalMembers = ref(50)
|
||
|
||
// 编辑相关状态
|
||
const editDialogVisible = ref(false)
|
||
const editFormRef = ref()
|
||
const saveLoading = ref(false)
|
||
const editForm = ref({
|
||
id: '',
|
||
username: '',
|
||
level: '',
|
||
points: 0,
|
||
expiryDate: ''
|
||
})
|
||
|
||
// 表单验证规则
|
||
const editRules = {
|
||
username: [
|
||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||
{ min: 2, max: 20, message: '用户名长度在 2 到 20 个字符', trigger: 'blur' }
|
||
],
|
||
level: [
|
||
{ required: true, message: '请选择会员等级', trigger: 'change' }
|
||
],
|
||
points: [
|
||
{ required: true, message: '请输入资源点', trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: '资源点不能小于0', trigger: 'blur' }
|
||
],
|
||
expiryDate: [
|
||
{ required: true, message: '请选择到期时间', trigger: 'change' }
|
||
]
|
||
}
|
||
|
||
// 会员数据
|
||
const memberList = ref([])
|
||
|
||
// 导航功能
|
||
const goToDashboard = () => {
|
||
router.push('/')
|
||
}
|
||
|
||
const goToOrders = () => {
|
||
router.push('/orders')
|
||
}
|
||
|
||
const goToAPI = () => {
|
||
router.push('/api-management')
|
||
}
|
||
|
||
const goToTasks = () => {
|
||
router.push('/generate-task-record')
|
||
}
|
||
|
||
const goToSettings = () => {
|
||
router.push('/system-settings')
|
||
}
|
||
|
||
// 表格操作
|
||
const isAllSelected = computed(() => {
|
||
return memberList.value.length > 0 && selectedMembers.value.length === memberList.value.length
|
||
})
|
||
|
||
const totalPages = computed(() => {
|
||
return Math.ceil(totalMembers.value / pageSize.value)
|
||
})
|
||
|
||
const visiblePages = computed(() => {
|
||
const pages = []
|
||
const start = Math.max(1, currentPage.value - 2)
|
||
const end = Math.min(totalPages.value, start + 4)
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
pages.push(i)
|
||
}
|
||
return pages
|
||
})
|
||
|
||
const toggleAllSelection = () => {
|
||
if (isAllSelected.value) {
|
||
selectedMembers.value = []
|
||
} else {
|
||
selectedMembers.value = [...memberList.value]
|
||
}
|
||
}
|
||
|
||
const toggleMemberSelection = (member) => {
|
||
const index = selectedMembers.value.findIndex(m => m.id === member.id)
|
||
if (index > -1) {
|
||
selectedMembers.value.splice(index, 1)
|
||
} else {
|
||
selectedMembers.value.push(member)
|
||
}
|
||
}
|
||
|
||
const prevPage = () => {
|
||
if (currentPage.value > 1) {
|
||
currentPage.value--
|
||
loadMembers()
|
||
}
|
||
}
|
||
|
||
const nextPage = () => {
|
||
if (currentPage.value < totalPages.value) {
|
||
currentPage.value++
|
||
loadMembers()
|
||
}
|
||
}
|
||
|
||
const goToPage = (page) => {
|
||
currentPage.value = page
|
||
loadMembers()
|
||
}
|
||
|
||
const editMember = (member) => {
|
||
// 填充编辑表单
|
||
editForm.value = {
|
||
id: member.id,
|
||
username: member.username,
|
||
level: member.level,
|
||
points: member.points,
|
||
expiryDate: member.expiryDate
|
||
}
|
||
editDialogVisible.value = true
|
||
}
|
||
|
||
const handleCloseEditDialog = () => {
|
||
editDialogVisible.value = false
|
||
// 重置表单
|
||
editFormRef.value?.resetFields()
|
||
}
|
||
|
||
const saveEdit = async () => {
|
||
if (!editFormRef.value) return
|
||
|
||
try {
|
||
// 验证表单
|
||
await editFormRef.value.validate()
|
||
|
||
saveLoading.value = true
|
||
|
||
// 调用API更新会员信息
|
||
await memberAPI.updateMember(editForm.value.id, {
|
||
username: editForm.value.username,
|
||
level: editForm.value.level,
|
||
points: editForm.value.points,
|
||
expiryDate: editForm.value.expiryDate
|
||
})
|
||
|
||
// 更新本地数据
|
||
const index = memberList.value.findIndex(m => m.id === editForm.value.id)
|
||
if (index > -1) {
|
||
memberList.value[index] = { ...editForm.value }
|
||
}
|
||
|
||
ElMessage.success('会员信息更新成功')
|
||
editDialogVisible.value = false
|
||
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
ElMessage.error('保存失败,请检查输入信息')
|
||
} finally {
|
||
saveLoading.value = false
|
||
}
|
||
}
|
||
|
||
const deleteMember = async (member) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除用户 ${member.username} 吗?`,
|
||
'确认删除',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
// 调用API删除会员
|
||
await memberAPI.deleteMember(member.id)
|
||
|
||
// 从本地列表中移除
|
||
const index = memberList.value.findIndex(m => m.id === member.id)
|
||
if (index > -1) {
|
||
memberList.value.splice(index, 1)
|
||
totalMembers.value--
|
||
}
|
||
|
||
ElMessage.success('删除成功')
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error('删除失败:', error)
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
const deleteSelected = async () => {
|
||
if (selectedMembers.value.length === 0) {
|
||
ElMessage.warning('请先选择要删除的会员')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除选中的 ${selectedMembers.value.length} 个会员吗?`,
|
||
'批量删除',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
const ids = selectedMembers.value.map(m => m.id)
|
||
|
||
// 调用API批量删除
|
||
await memberAPI.deleteMembers(ids)
|
||
|
||
// 从本地列表中移除
|
||
memberList.value = memberList.value.filter(m => !ids.includes(m.id))
|
||
totalMembers.value -= ids.length
|
||
selectedMembers.value = []
|
||
|
||
ElMessage.success('批量删除成功')
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error('批量删除失败:', error)
|
||
ElMessage.error('批量删除失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 监听筛选条件变化
|
||
const handleLevelChange = () => {
|
||
currentPage.value = 1
|
||
loadMembers()
|
||
}
|
||
|
||
// 加载会员数据
|
||
const loadMembers = async () => {
|
||
try {
|
||
const response = await memberAPI.getMembers({
|
||
page: currentPage.value,
|
||
pageSize: pageSize.value,
|
||
level: selectedLevel.value === 'all' ? '' : selectedLevel.value
|
||
})
|
||
|
||
// 处理API响应数据
|
||
if (response && response.list) {
|
||
memberList.value = response.list.map(member => ({
|
||
id: member.id,
|
||
username: member.username,
|
||
level: getMembershipLevel(member.membership),
|
||
points: member.points,
|
||
expiryDate: getMembershipExpiry(member.membership)
|
||
}))
|
||
totalMembers.value = response.total || 0
|
||
} else {
|
||
ElMessage.error('API返回数据格式错误')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载会员数据失败:', error)
|
||
ElMessage.error('加载会员数据失败')
|
||
}
|
||
}
|
||
|
||
// 辅助函数:获取会员等级显示名称
|
||
const getMembershipLevel = (membership) => {
|
||
if (!membership) return '标准会员'
|
||
return membership.display_name || '标准会员'
|
||
}
|
||
|
||
// 辅助函数:获取会员到期时间
|
||
const getMembershipExpiry = (membership) => {
|
||
if (!membership) return '2025-12-31'
|
||
return membership.end_date ? membership.end_date.split(' ')[0] : '2025-12-31'
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 初始化数据
|
||
loadMembers()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.member-management {
|
||
display: flex;
|
||
min-height: 100vh;
|
||
background: #f8fafc;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
|
||
/* 左侧导航栏 */
|
||
.sidebar {
|
||
width: 320px;
|
||
background: white;
|
||
border-right: 1px solid #e2e8f0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 24px 0;
|
||
}
|
||
|
||
.logo {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 28px;
|
||
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;
|
||
}
|
||
|
||
.nav-menu {
|
||
flex: 1;
|
||
padding: 0 24px;
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 18px 24px;
|
||
margin-bottom: 6px;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
color: #64748b;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.nav-item:hover {
|
||
background: #f1f5f9;
|
||
color: #334155;
|
||
}
|
||
|
||
.nav-item.active {
|
||
background: #eff6ff;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.nav-item .el-icon {
|
||
margin-right: 16px;
|
||
font-size: 22px;
|
||
}
|
||
|
||
.nav-item span {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.sidebar-footer {
|
||
padding: 0 32px 20px;
|
||
margin-top: auto;
|
||
}
|
||
|
||
.online-users,
|
||
.system-uptime {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-bottom: 10px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.highlight {
|
||
color: #3b82f6;
|
||
font-weight: 600;
|
||
font-size: 15px;
|
||
}
|
||
|
||
/* 主内容区域 */
|
||
.main-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
/* 顶部搜索栏 */
|
||
.top-header {
|
||
background: white;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
padding: 16px 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.search-bar {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.search-icon {
|
||
position: absolute;
|
||
left: 12px;
|
||
color: #94a3b8;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.search-input {
|
||
width: 300px;
|
||
padding: 8px 12px 8px 40px;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
background: #f8fafc;
|
||
outline: none;
|
||
}
|
||
|
||
.search-input:focus {
|
||
border-color: #3b82f6;
|
||
background: white;
|
||
}
|
||
|
||
.search-input::placeholder {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.notification-icon,
|
||
.help-icon {
|
||
font-size: 20px;
|
||
color: #64748b;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.user-avatar {
|
||
display: flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.user-avatar img {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* 会员内容区域 */
|
||
.member-content {
|
||
padding: 24px;
|
||
flex: 1;
|
||
}
|
||
|
||
.content-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.content-header h2 {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
margin: 0;
|
||
}
|
||
|
||
.selection-info {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
background: #f1f5f9;
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.table-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.toolbar-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.table-container {
|
||
background: white;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.member-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.member-table thead {
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.member-table th {
|
||
padding: 12px 16px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #374151;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.member-table td {
|
||
padding: 12px 16px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
color: #374151;
|
||
}
|
||
|
||
.table-row:hover {
|
||
background: #f9fafb;
|
||
}
|
||
|
||
.checkbox-col {
|
||
width: 50px;
|
||
text-align: center;
|
||
}
|
||
|
||
.checkbox-col input[type="checkbox"] {
|
||
width: 16px;
|
||
height: 16px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.level-tag {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: white;
|
||
}
|
||
|
||
.level-tag.professional {
|
||
background: #ec4899;
|
||
}
|
||
|
||
.level-tag.standard {
|
||
background: #3b82f6;
|
||
}
|
||
|
||
.action-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.edit-btn {
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.edit-btn:hover {
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.delete-btn {
|
||
color: #dc2626;
|
||
}
|
||
|
||
.delete-btn:hover {
|
||
background: #fef2f2;
|
||
}
|
||
|
||
.pagination-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.page-btn {
|
||
padding: 8px 12px;
|
||
border: 1px solid #d1d5db;
|
||
background: white;
|
||
color: #374151;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.page-btn:hover:not(:disabled) {
|
||
background: #f3f4f6;
|
||
border-color: #9ca3af;
|
||
}
|
||
|
||
.page-btn.active {
|
||
background: #3b82f6;
|
||
color: white;
|
||
border-color: #3b82f6;
|
||
}
|
||
|
||
.page-btn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 1024px) {
|
||
.member-management {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sidebar {
|
||
width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.nav-menu {
|
||
display: flex;
|
||
overflow-x: auto;
|
||
padding: 0 16px;
|
||
}
|
||
|
||
.nav-item {
|
||
white-space: nowrap;
|
||
margin-right: 16px;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.sidebar-footer {
|
||
display: none;
|
||
}
|
||
|
||
.search-input {
|
||
width: 200px;
|
||
}
|
||
|
||
.member-content {
|
||
padding: 16px;
|
||
}
|
||
}
|
||
</style>
|