refactor: extract image form to standalone page and implement tags view store

- Created ImageForm.vue as standalone page for add/edit image functionality
- Removed dialog-based image form from VmImages.vue
- Implemented tagsViewStore for global tab state management
- Added automatic tab closing on form cancel/back
- Fixed data persistence issue when switching between image edits
- Removed quick actions section from ImageForm
- Updated router configuration for new image form route
This commit is contained in:
2025-11-28 14:15:29 +08:00
parent 067e0539ba
commit f7c3be1d30
45 changed files with 8776 additions and 6881 deletions
+242 -82
View File
@@ -1,65 +1,93 @@
<template>
<div class="admin-group-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<el-row :gutter="20">
<el-col :span="8">
<el-input
v-model="queryParams.key"
placeholder="搜索关键词"
clearable
@clear="fetchGroupList"
@keyup.enter="fetchGroupList"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="16">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增管理员组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>
</el-button>
</el-col>
</el-row>
</el-card>
<!-- 主容器 -->
<el-card class="main-container" shadow="never">
<!-- 操作栏 -->
<div class="filter-section">
<div class="filter-content">
<div class="search-form">
<el-input
v-model="queryParams.key"
placeholder="搜索关键词"
clearable
@clear="fetchGroupList"
@keyup.enter="fetchGroupList"
style="width: 200px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>增管理员组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</div>
</div>
<!-- 管理员组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
style="width: 100%"
>
<el-table-column prop="id" label="组ID" width="100" />
<el-table-column prop="name" label="组名称" min-width="200" />
<el-table-column prop="note" label="备注" min-width="250" />
<!-- <el-table-column prop="member_count" label="成员数量" width="120" /> -->
<el-table-column prop="CreatedAt" label="创建时间" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
<!-- 管理员组列表 -->
<div class="table-section">
<!-- 骨架屏 -->
<div v-if="loading" class="skeleton-container">
<div v-for="i in 5" :key="i" class="skeleton-row">
<div class="skeleton-cell skeleton-id"></div>
<div class="skeleton-cell skeleton-name"></div>
<div class="skeleton-cell skeleton-note"></div>
<div class="skeleton-cell skeleton-time"></div>
<div class="skeleton-cell skeleton-action"></div>
</div>
</div>
<el-table
v-else
v-loading="loading"
:data="groupList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column prop="id" label="组ID" width="100" />
<el-table-column prop="name" label="组名称" min-width="200">
<template #default="{ row }">
<span class="group-name">{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
<el-table-column prop="CreatedAt" label="创建时间" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<div class="action-buttons">
<el-button type="primary" link @click="handleEdit(row)">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button type="success" link @click="handleViewMembers(row)">
<el-icon><User /></el-icon>成员
</el-button>
<el-button type="danger" link @click="handleDelete(row)">
<el-icon><Delete /></el-icon>删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</div>
</el-card>
<!-- 管理员组表单对话框 -->
@@ -67,6 +95,7 @@
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增管理员组' : '编辑管理员组'"
width="600px"
append-to-body
>
<el-form
ref="groupFormRef"
@@ -82,8 +111,10 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</template>
</el-dialog>
@@ -92,26 +123,36 @@
v-model="memberDialogVisible"
title="管理员组成员"
width="900px"
append-to-body
>
<el-input
v-model="memberParams.key"
placeholder="搜索成员"
clearable
@clear="fetchMemberList"
@keyup.enter="fetchMemberList"
style="margin-bottom: 16px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<div class="member-search">
<el-input
v-model="memberParams.key"
placeholder="搜索成员"
clearable
@clear="fetchMemberList"
@keyup.enter="fetchMemberList"
style="width: 200px"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
<el-table
v-loading="memberLoading"
:data="memberList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column prop="UserId" label="用户ID" width="100" />
<el-table-column prop="UserName" label="用户名" min-width="150" />
<el-table-column prop="UserName" label="用户名" min-width="150">
<template #default="{ row }">
<div class="user-info">
<span class="username">{{ row.UserName }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Email" label="邮箱" min-width="200" />
<el-table-column prop="Phone" label="手机号" width="130" />
<el-table-column label="性别" width="80">
@@ -146,7 +187,7 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
import { Plus, Refresh, Search, Edit, User, Delete } from '@element-plus/icons-vue'
import {
getAdminGroupList,
getAdminGroupMemberList,
@@ -341,18 +382,137 @@ onMounted(() => {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.table-container {
border-radius: 8px;
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.search-form {
margin: 0;
flex: 1;
display: flex;
align-items: center;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-section {
padding: 0;
}
.group-name {
font-weight: 500;
color: #2c3e50;
}
.action-buttons {
display: flex;
gap: 8px;
align-items: center;
}
.pagination {
margin-top: 24px;
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
</style>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 0;
}
.member-search {
margin-bottom: 16px;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
/* 骨架屏样式 */
.skeleton-container {
padding: 20px;
}
.skeleton-row {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
gap: 16px;
}
.skeleton-row:last-child {
border-bottom: none;
}
.skeleton-cell {
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
border-radius: 4px;
}
.skeleton-id { width: 100px; }
.skeleton-name { width: 200px; }
.skeleton-note { flex: 1; min-width: 250px; }
.skeleton-time { width: 180px; }
.skeleton-action { width: 250px; height: 32px; }
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
+43 -2
View File
@@ -1062,7 +1062,8 @@ onBeforeUnmount(() => {
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
border: 1px solid #e1e8ed;
background: #fafbfc;
}
.balance-info-card {
@@ -1071,7 +1072,8 @@ onBeforeUnmount(() => {
}
.table-container {
border-radius: 8px;
border: 1px solid #e1e8ed;
background: #ffffff;
}
.card-header {
@@ -1449,6 +1451,45 @@ onBeforeUnmount(() => {
flex-direction: column;
}
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 20px;
}
.pagination {
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
</style>
<style>
File diff suppressed because it is too large Load Diff
+254 -186
View File
@@ -1,87 +1,120 @@
<template>
<div class="user-group-container">
<!-- 操作栏 -->
<el-card class="filter-container" shadow="never">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</el-card>
<!-- 主容器 -->
<el-card class="main-container" shadow="never">
<!-- 操作栏 -->
<div class="filter-section">
<div class="filter-content">
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户组
</el-button>
<el-button type="success" @click="fetchGroupList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
</div>
</div>
<!-- 用户组列表 -->
<el-card class="table-container" shadow="never">
<el-table
v-loading="loading"
:data="groupList"
style="width: 100%"
>
<el-table-column label="组ID" width="100">
<template #default="{ row }">
{{ row.group_id || row.GroupId || row.id || row.Id }}
</template>
</el-table-column>
<el-table-column label="组名称" min-width="200">
<template #default="{ row }">
{{ row.group_name || row.name || row.Name }}
</template>
</el-table-column>
<el-table-column label="权限" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="info">{{ row.auth || row.Auth || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="升级金额" width="120">
<template #default="{ row }">
<span v-if="row.floor_price || row.FloorPrice">¥{{ row.floor_price || row.FloorPrice }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="下一级组ID" width="120">
<template #default="{ row }">
{{ row.higher_level_id || row.HigherLevelId || '-' }}
</template>
</el-table-column>
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(row.fixed || row.Fixed) ? 'warning' : 'success'" size="small">
{{ (row.fixed || row.Fixed) ? '固定' : '可升级' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="成员数量" width="100" align="center">
<template #default="{ row }">
{{ row.member_count || row.MemberCount || 0 }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="160" show-overflow-tooltip>
<template #default="{ row }">
{{ row.create_time || row.CreateTime || row.CreatedAt || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link @click="handleViewMembers(row)">查看成员</el-button>
<!-- <el-button type="warning" link @click="handleAddMember(row)">添加成员</el-button> -->
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
<!-- 用户组列表 -->
<div class="table-section">
<!-- 骨架屏 -->
<div v-if="loading" class="skeleton-container">
<div v-for="i in 5" :key="i" class="skeleton-row">
<div class="skeleton-cell skeleton-id"></div>
<div class="skeleton-cell skeleton-name"></div>
<div class="skeleton-cell skeleton-auth"></div>
<div class="skeleton-cell skeleton-price"></div>
<div class="skeleton-cell skeleton-level"></div>
<div class="skeleton-cell skeleton-type"></div>
<div class="skeleton-cell skeleton-count"></div>
<div class="skeleton-cell skeleton-time"></div>
<div class="skeleton-cell skeleton-action"></div>
</div>
</div>
<el-table
v-else
v-loading="loading"
:data="groupList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column label="组ID" width="100">
<template #default="{ row }">
{{ row.group_id || row.GroupId || row.id || row.Id }}
</template>
</el-table-column>
<el-table-column label="组名称" min-width="200">
<template #default="{ row }">
<span class="group-name">{{ row.group_name || row.name || row.Name }}</span>
</template>
</el-table-column>
<el-table-column label="权限" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<el-tag type="info" effect="plain">{{ row.auth || row.Auth || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="升级金额" width="120">
<template #default="{ row }">
<span v-if="row.floor_price || row.FloorPrice" class="price-text">¥{{ row.floor_price || row.FloorPrice }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="下一级组ID" width="120">
<template #default="{ row }">
{{ row.higher_level_id || row.HigherLevelId || '-' }}
</template>
</el-table-column>
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
<el-tag :type="(row.fixed || row.Fixed) ? 'warning' : 'success'" size="small">
{{ (row.fixed || row.Fixed) ? '固定' : '可升级' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="成员数量" width="100" align="center">
<template #default="{ row }">
<el-tag type="info" size="small" effect="plain">
{{ row.member_count || row.MemberCount || 0 }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ row.create_time || row.CreateTime || row.CreatedAt || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<div class="action-buttons">
<el-button type="primary" link @click="handleEdit(row)">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button type="success" link @click="handleViewMembers(row)">
<el-icon><User /></el-icon>成员
</el-button>
<el-button type="danger" link @click="handleDelete(row)">
<el-icon><Delete /></el-icon>删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</div>
</el-card>
<!-- 用户组表单对话框 -->
@@ -90,6 +123,7 @@
:title="dialogType === 'add' ? '新增用户组' : '编辑用户组'"
width="650px"
destroy-on-close
append-to-body
>
<el-form
ref="groupFormRef"
@@ -137,8 +171,10 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</template>
</el-dialog>
@@ -147,11 +183,13 @@
v-model="memberDialogVisible"
title="用户组成员"
width="800px"
append-to-body
>
<el-table
v-loading="memberLoading"
:data="memberList"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column label="用户ID" width="100">
<template #default="{ row }">
@@ -160,7 +198,9 @@
</el-table-column>
<el-table-column label="用户名" min-width="150">
<template #default="{ row }">
{{ row.username || row.Username || row.UserName || row.name || row.Name }}
<div class="user-info">
<span class="username">{{ row.username || row.Username || row.UserName || row.name || row.Name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="邮箱" min-width="200">
@@ -193,6 +233,7 @@
v-model="addMemberDialogVisible"
title="添加用户组成员"
width="500px"
append-to-body
>
<el-form
ref="memberFormRef"
@@ -205,8 +246,10 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addMemberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddMember">确定</el-button>
<div class="dialog-footer">
<el-button @click="addMemberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAddMember">确定</el-button>
</div>
</template>
</el-dialog>
</div>
@@ -215,7 +258,7 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import { Plus, Refresh, Edit, User, Delete } from '@element-plus/icons-vue'
import {
getUserGroupList,
getUserGroupMemberList,
@@ -286,16 +329,9 @@ const memberFormRef = ref(null)
// 获取用户组列表
const fetchGroupList = async () => {
console.log('=== 获取用户组列表 ===')
console.log('请求参数:', queryParams)
loading.value = true
try {
const res = await getUserGroupList(queryParams)
console.log('用户组列表响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
console.log('响应数据:', res.data?.data || res.data)
const code = res.data?.code || res.code
if (code === 200) {
let responseData = res.data?.data || res.data
@@ -303,7 +339,6 @@ const fetchGroupList = async () => {
item.CreatedAt = formatTime(item.CreatedAt)
})
// 处理不同的数据结构
if (Array.isArray(responseData)) {
groupList.value = responseData
total.value = responseData.length
@@ -317,20 +352,10 @@ const fetchGroupList = async () => {
groupList.value = []
total.value = 0
}
console.log('用户组列表数据:', groupList.value)
console.log('总数:', total.value)
if (groupList.value.length > 0) {
console.log('第一个用户组示例:', groupList.value[0])
}
} else {
console.error('获取用户组列表失败:', res.data)
ElMessage.error(res.data?.message || '获取用户组列表失败')
}
} catch (error) {
console.error('获取用户组列表错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('获取用户组列表失败')
} finally {
loading.value = false
@@ -339,21 +364,12 @@ const fetchGroupList = async () => {
// 获取成员列表
const fetchMemberList = async () => {
console.log('=== 获取用户组成员列表 ===')
console.log('请求参数:', memberParams)
memberLoading.value = true
try {
const res = await getUserGroupMemberList(memberParams)
console.log('成员列表响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
console.log('响应数据:', res.data?.data || res.data)
const code = res.data?.code || res.code
if (code === 200) {
const responseData = res.data?.data || res.data
// 处理不同的数据结构
if (Array.isArray(responseData)) {
memberList.value = responseData
memberTotal.value = responseData.length
@@ -367,20 +383,10 @@ const fetchMemberList = async () => {
memberList.value = []
memberTotal.value = 0
}
console.log('成员列表数据:', memberList.value)
console.log('成员总数:', memberTotal.value)
if (memberList.value.length > 0) {
console.log('第一个成员示例:', memberList.value[0])
}
} else {
console.error('获取成员列表失败:', res.data)
ElMessage.error(res.data?.message || '获取成员列表失败')
}
} catch (error) {
console.error('获取成员列表错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('获取成员列表失败')
} finally {
memberLoading.value = false
@@ -410,7 +416,6 @@ const handleMemberCurrentChange = (page) => {
// 新增用户组
const handleAdd = () => {
console.log('=== 打开新增用户组对话框 ===')
dialogType.value = 'add'
dialogVisible.value = true
Object.assign(groupForm, {
@@ -426,13 +431,9 @@ const handleAdd = () => {
// 编辑用户组
const handleEdit = (row) => {
console.log('=== 打开编辑用户组对话框 ===')
console.log('用户组数据:', row)
dialogType.value = 'edit'
dialogVisible.value = true
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
const groupName = row.group_name || row.name || row.Name
const groupAuth = row.auth || row.Auth || ''
@@ -448,19 +449,11 @@ const handleEdit = (row) => {
floor_price: floorPrice,
fixed: fixed
})
console.log('表单数据:', groupForm)
}
// 查看成员
const handleViewMembers = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
console.log('=== 查看用户组成员 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
memberParams.group_id = groupId
memberParams.page = 1
memberDialogVisible.value = true
@@ -469,13 +462,7 @@ const handleViewMembers = (row) => {
// 添加成员
const handleAddMember = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
console.log('=== 打开添加成员对话框 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
memberForm.group_id = groupId
memberForm.user_ids = ''
addMemberDialogVisible.value = true
@@ -483,15 +470,9 @@ const handleAddMember = (row) => {
// 删除用户组
const handleDelete = (row) => {
// 适配不同的字段名
const groupId = row.group_id || row.GroupId || row.id || row.Id
const groupName = row.group_name || row.name || row.Name
console.log('=== 删除用户组 ===')
console.log('用户组数据:', row)
console.log('组ID:', groupId)
console.log('组名称:', groupName)
ElMessageBox.confirm(`确认删除用户组 ${groupName} 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -499,20 +480,14 @@ const handleDelete = (row) => {
}).then(async () => {
try {
const res = await deleteUserGroup({ group_id: groupId })
console.log('删除响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
} else {
console.error('删除失败:', res.data)
ElMessage.error(res.data?.message || '删除失败')
}
} catch (error) {
console.error('删除错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('删除失败')
}
}).catch(() => {})
@@ -522,10 +497,6 @@ const handleDelete = (row) => {
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
console.log('=== 提交用户组表单 ===')
console.log('操作类型:', dialogType.value)
console.log('表单数据:', groupForm)
try {
let res
const submitData = {
@@ -533,40 +504,30 @@ const submitForm = () => {
auth: groupForm.auth
}
// 添加可选字段(如果有值才添加)
if (groupForm.higher_level_id !== undefined && groupForm.higher_level_id !== null) {
submitData.higher_level_id = groupForm.higher_level_id
}
if (groupForm.floor_price !== undefined && groupForm.floor_price !== null) {
submitData.floor_price = groupForm.floor_price
}
// fixed 字段总是传递(boolean 值)
submitData.fixed = groupForm.fixed
if (dialogType.value === 'add') {
console.log('新增用户组,提交数据:', submitData)
res = await createUserGroup(submitData)
} else {
submitData.group_id = groupForm.group_id
console.log('更新用户组,提交数据:', submitData)
res = await updateUserGroupInfo(submitData)
}
console.log('提交响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
dialogVisible.value = false
fetchGroupList()
} else {
console.error('操作失败:', res.data)
ElMessage.error(res.data?.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('操作失败')
}
}
@@ -577,26 +538,17 @@ const submitForm = () => {
const submitAddMember = () => {
memberFormRef.value?.validate(async (valid) => {
if (valid) {
console.log('=== 提交添加成员 ===')
console.log('表单数据:', memberForm)
try {
const res = await addUserGroupMember(memberForm)
console.log('添加成员响应:', res)
console.log('响应状态码:', res.data?.code || res.code)
const code = res.data?.code || res.code
if (code === 200) {
ElMessage.success('添加成功')
addMemberDialogVisible.value = false
fetchGroupList()
} else {
console.error('添加失败:', res.data)
ElMessage.error(res.data?.message || '添加失败')
}
} catch (error) {
console.error('添加错误:', error)
console.error('错误详情:', error.response || error.message)
ElMessage.error('添加失败')
}
}
@@ -605,7 +557,6 @@ const submitAddMember = () => {
// 初始化
onMounted(() => {
console.log('=== 用户组管理页面初始化 ===')
fetchGroupList()
})
</script>
@@ -615,18 +566,135 @@ onMounted(() => {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.table-container {
border-radius: 8px;
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-section {
padding: 0;
}
.group-name {
font-weight: 500;
color: #2c3e50;
}
.price-text {
color: #f56c6c;
font-weight: 500;
}
.action-buttons {
display: flex;
gap: 8px;
align-items: center;
}
.pagination {
margin-top: 24px;
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
</style>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 0;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
/* 骨架屏样式 */
.skeleton-container {
padding: 20px;
}
.skeleton-row {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
gap: 16px;
}
.skeleton-row:last-child {
border-bottom: none;
}
.skeleton-cell {
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
border-radius: 4px;
}
.skeleton-id { width: 100px; }
.skeleton-name { width: 200px; }
.skeleton-auth { flex: 1; min-width: 200px; }
.skeleton-price { width: 120px; }
.skeleton-level { width: 120px; }
.skeleton-type { width: 100px; }
.skeleton-count { width: 100px; }
.skeleton-time { width: 180px; }
.skeleton-action { width: 280px; height: 32px; }
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
+284 -45
View File
@@ -1,38 +1,68 @@
<template>
<div class="user-list-container">
<!-- 搜索和操作栏 -->
<el-card class="filter-container" shadow="never">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键字">
<el-input v-model="queryParams.key" placeholder="请输入用户名/邮箱" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="success" @click="fetchUserList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
<!-- 搜索和用户列表 -->
<el-card class="main-container" shadow="never">
<!-- 搜索和操作栏 -->
<div class="filter-section">
<div class="filter-content">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键字">
<el-input v-model="queryParams.key" placeholder="请输入用户名/邮箱" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="success" @click="fetchUserList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增用户
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</div>
</div>
</el-card>
<!-- 用户列表 -->
<el-card class="table-container" shadow="never">
<!-- 用户列表 -->
<div class="table-section">
<!-- 骨架屏 -->
<div v-if="loading" class="skeleton-container">
<div v-for="i in 5" :key="i" class="skeleton-row">
<div class="skeleton-cell skeleton-checkbox"></div>
<div class="skeleton-cell skeleton-id"></div>
<div class="skeleton-cell skeleton-user">
<div class="skeleton-avatar"></div>
<div class="skeleton-text-group">
<div class="skeleton-text skeleton-text-primary"></div>
<div class="skeleton-text skeleton-text-secondary"></div>
</div>
</div>
<div class="skeleton-cell skeleton-avatar"></div>
<div class="skeleton-cell skeleton-phone"></div>
<div class="skeleton-cell skeleton-realname">
<div class="skeleton-text skeleton-text-primary"></div>
<div class="skeleton-text skeleton-text-secondary"></div>
</div>
<div class="skeleton-cell skeleton-group"></div>
<div class="skeleton-cell skeleton-status"></div>
<div class="skeleton-cell skeleton-time"></div>
<div class="skeleton-cell skeleton-action"></div>
</div>
</div>
<!-- 数据表格 -->
<el-table
v-loading="loading"
v-else
:data="userList"
@selection-change="handleSelectionChange"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="UserId" label="用户ID" width="100" />
@@ -123,6 +153,7 @@
background
class="pagination"
/>
</div>
</el-card>
<!-- 用户编辑对话框 -->
@@ -894,22 +925,198 @@ onMounted(() => {
padding: 0;
}
.filter-container {
margin-bottom: 20px;
border-radius: 8px;
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.search-form {
margin-bottom: 15px;
margin: 0;
flex: 1;
display: flex;
align-items: center;
gap: 12px;
min-width: 400px;
}
.search-form :deep(.el-form-item) {
margin-bottom: 0;
}
.search-form :deep(.el-form-item__label) {
margin-right: 8px;
white-space: nowrap;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-container {
border-radius: 8px;
@media (max-width: 768px) {
.filter-content {
flex-direction: column;
align-items: stretch;
}
.search-form {
min-width: 100%;
}
.action-bar {
width: 100%;
justify-content: flex-end;
}
}
.table-section {
padding: 0;
}
/* 骨架屏样式 */
.skeleton-container {
padding: 20px;
}
.skeleton-row {
display: flex;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
gap: 16px;
}
.skeleton-row:last-child {
border-bottom: none;
}
.skeleton-cell {
display: flex;
align-items: center;
}
.skeleton-checkbox {
width: 55px;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-id {
width: 100px;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-user {
flex: 1;
min-width: 150px;
gap: 12px;
}
.skeleton-avatar {
width: 40px;
height: 40px;
flex-shrink: 0;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-text-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.skeleton-text {
height: 14px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-text-primary {
width: 80px;
}
.skeleton-text-secondary {
width: 120px;
}
.skeleton-phone {
width: 130px;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-realname {
width: 150px;
flex-direction: column;
gap: 8px;
}
.skeleton-group {
width: 120px;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-status {
width: 100px;
height: 24px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-time {
width: 180px;
height: 20px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
.skeleton-action {
width: 200px;
height: 32px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s ease-in-out infinite;
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.user-info {
@@ -926,31 +1133,36 @@ onMounted(() => {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
color: #2c3e50;
}
.email {
font-size: 12px;
color: #999;
color: #7f8c8d;
}
.real-name {
font-size: 14px;
font-weight: 500;
margin-bottom: 2px;
color: #2c3e50;
}
.id-card {
font-size: 12px;
color: #666;
color: #7f8c8d;
}
.text-gray {
color: #999;
color: #95a5a6;
font-size: 12px;
}
.pagination {
margin-top: 24px;
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
@@ -958,12 +1170,9 @@ onMounted(() => {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 0;
}
.text-gray {
color: #999;
font-size: 12px;
}
.action-buttons {
display: flex;
@@ -980,13 +1189,12 @@ onMounted(() => {
.avatar-preview {
position: relative;
cursor: pointer;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
transition: opacity 0.2s ease;
}
.avatar-preview:hover {
transform: scale(1.05);
opacity: 0.8;
}
.avatar-preview:hover .avatar-overlay {
@@ -1006,7 +1214,7 @@ onMounted(() => {
justify-content: center;
gap: 4px;
opacity: 0;
transition: opacity 0.3s ease;
transition: opacity 0.2s ease;
color: white;
}
@@ -1026,5 +1234,36 @@ onMounted(() => {
font-size: 14px;
color: #606266;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
</style>