【新增】系统用户界面
This commit is contained in:
467
src/main/resources/templates/admin/sys/user/index.html
Normal file
467
src/main/resources/templates/admin/sys/user/index.html
Normal file
@@ -0,0 +1,467 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户管理系统</title>
|
||||
<!-- 外部引入库文件 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<!-- 外部引入自定义样式 -->
|
||||
<link rel="stylesheet" href="/assets/css/table.css">
|
||||
</head>
|
||||
<body id="app">
|
||||
<div class="main-container">
|
||||
<div class="table-container">
|
||||
<!-- 页面标题 -->
|
||||
<h3 class="mb-4">用户管理</h3>
|
||||
|
||||
<!-- 多参数搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<!-- 搜索参数1:关键词 -->
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
用户名
|
||||
</span>
|
||||
<input
|
||||
v-model="searchParams.keyword"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="关键词搜索(名称/ID)"
|
||||
@keyup.enter="fetchData()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索参数2:状态筛选 -->
|
||||
<div class="search-item">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
状态
|
||||
</span>
|
||||
<select
|
||||
v-model="searchParams.status"
|
||||
class="form-select"
|
||||
@change="fetchData()"
|
||||
>
|
||||
<option value="">全部状态</option>
|
||||
<option value="1">启用</option>
|
||||
<option value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和重置按钮 -->
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @click="fetchData()">
|
||||
<i class="bi bi-search me-1"></i> 搜索
|
||||
</button>
|
||||
<button class="btn btn-success" @click="resetSearch()">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-info" @click="openAddModal()">
|
||||
<i class="bi bi-plus-circle me-1"></i> 新增
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<!-- 全选复选框 -->
|
||||
<th style="width: 50px;">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll()"
|
||||
>
|
||||
</th>
|
||||
<th>ID</th>
|
||||
<th>名称</th>
|
||||
<th>状态</th>
|
||||
<th>用户类型</th>
|
||||
<th>所属部门</th>
|
||||
<th>创建时间</th>
|
||||
<th style="width: 120px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 加载中状态 -->
|
||||
<tr v-if="loading">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="loading-container">
|
||||
<div class="spinner-border text-primary" role="status" style="width: 2rem; height: 2rem;">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">加载中,请稍候...</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 无数据状态 -->
|
||||
<tr v-else-if="tableData.length === 0">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="no-data">
|
||||
<i class="bi bi-folder-x"></i>
|
||||
<h5>暂无匹配数据</h5>
|
||||
<p class="text-muted">请尝试调整搜索条件或重置查询</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<tr v-else v-for="(item, index) in tableData" :key="item.id" :class="{ selected: selectedIds.includes(item.id) }">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="selectedIds"
|
||||
:value="item.id"
|
||||
@change="toggleSelectItem(item.id)"
|
||||
>
|
||||
</td>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.status === 1 ? 'bg-success' : 'bg-danger'">
|
||||
{{ item.status === 1 ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary">
|
||||
{{ item.userType === 'admin' ? '管理员' : item.userType === 'editor' ? '编辑' : '查看者' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">
|
||||
{{ item.deptId === '1' ? '技术部' : item.deptId === '2' ? '运营部' : item.deptId === '3' ? '市场部' : '人事部' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatTime(item.createTime) }}</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-sm btn-primary" @click="openEditModal(item)">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="handleDelete(item.id)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作(表格下方左侧) -->
|
||||
<div class="batch-actions mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted">已选中 {{ selectedIds.length }} 条数据</span>
|
||||
|
||||
<!-- 使用select标签实现批量操作选择 -->
|
||||
<select
|
||||
class="form-select"
|
||||
v-model="batchAction"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@change="handleBatchOperation"
|
||||
style="width: auto; min-width: 160px;"
|
||||
>
|
||||
<option value="">-- 批量操作 --</option>
|
||||
<option value="delete">批量删除</option>
|
||||
<option value="enable">批量启用</option>
|
||||
<option value="disable">批量禁用</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="clearSelected()"
|
||||
:disabled="selectedIds.length === 0"
|
||||
>
|
||||
<i class="bi bi-x-circle me-1"></i> 取消选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="page-info">
|
||||
共 {{ total }} 条数据,当前第 {{ pageNum }}/{{ totalPages }} 页
|
||||
</div>
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(1)">首页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === 1 }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum - 1)">上一页</a>
|
||||
</li>
|
||||
<li class="page-item active" v-for="page in pageList" :key="page">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(pageNum + 1)">下一页</a>
|
||||
</li>
|
||||
<li class="page-item" :class="{ disabled: pageNum === totalPages }">
|
||||
<a class="page-link" href="#" @click.prevent="changePage(totalPages)">末页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 外部引入库文件 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- 外部引入模拟请求文件 -->
|
||||
<script src="/assets/js/axiosRequest.js"></script>
|
||||
|
||||
<!-- 页面核心逻辑 -->
|
||||
<script>
|
||||
const {createApp} = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
tableData: [], // 表格数据源
|
||||
loading: false, // 加载状态
|
||||
// 多搜索参数绑定(与搜索栏对应)
|
||||
searchParams: {
|
||||
userName: '',
|
||||
},
|
||||
// 分页参数
|
||||
pageNum: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0, // 总数据量
|
||||
totalPages: 0, // 总页数
|
||||
// 批量操作
|
||||
selectedIds: [], // 选中的ID集合
|
||||
selectAll: false, // 全选状态
|
||||
batchAction: '', // 批量操作选择
|
||||
// 分页显示范围(最多显示5个页码)
|
||||
pageRange: 5
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算当前显示的页码列表
|
||||
pageList() {
|
||||
const list = [];
|
||||
if (this.totalPages === 0) return list;
|
||||
|
||||
// 总页数小于等于显示范围,直接显示所有页码
|
||||
if (this.totalPages <= this.pageRange) {
|
||||
for (let i = 1; i <= this.totalPages; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
} else {
|
||||
// 总页数大于显示范围,显示当前页前后2个页码
|
||||
let start = Math.max(1, this.pageNum - 2);
|
||||
let end = Math.min(this.totalPages, this.pageNum + 2);
|
||||
|
||||
// 确保显示5个页码
|
||||
if (end - start < this.pageRange - 1) {
|
||||
if (start === 1) {
|
||||
end = this.pageRange;
|
||||
} else if (end === this.totalPages) {
|
||||
start = this.totalPages - this.pageRange + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 格式化时间(毫秒转字符串)
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
const date = new Date(time);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
},
|
||||
|
||||
// 获取表格数据(核心方法)
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 调用模拟接口获取数据
|
||||
const response = await request.get('/sys/user/page', {
|
||||
...this.searchParams, // 传递所有搜索参数
|
||||
current: this.pageNum,
|
||||
size: this.pageSize
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data; // 列表数据
|
||||
this.total = response.data.total; // 总条数
|
||||
this.totalPages = Math.ceil(this.total / this.pageSize); // 总页数
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
this.tableData = [];
|
||||
this.total = 0;
|
||||
this.totalPages = 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.clearSelected(); // 每次加载数据清空选中状态
|
||||
}
|
||||
},
|
||||
|
||||
// 切换页码
|
||||
changePage(page) {
|
||||
// 边界判断
|
||||
if (page < 1 || page > this.totalPages || page === this.pageNum) return;
|
||||
this.pageNum = page;
|
||||
this.fetchData(); // 切换页码后重新加载数据
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
resetSearch() {
|
||||
// 重置所有搜索参数
|
||||
this.searchParams = {
|
||||
keyword: '',
|
||||
status: '',
|
||||
createTimeStart: '',
|
||||
createTimeEnd: '',
|
||||
userType: '',
|
||||
deptId: ''
|
||||
};
|
||||
this.pageNum = 1; // 重置到第一页
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.selectAll) {
|
||||
// 全选:收集所有数据的ID
|
||||
this.selectedIds = this.tableData.map(item => item.id);
|
||||
} else {
|
||||
// 取消全选:清空选中ID
|
||||
this.selectedIds = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 单个选中/取消选中
|
||||
toggleSelectItem(id) {
|
||||
// 当选中状态变化时,更新全选状态
|
||||
this.selectAll = this.selectedIds.length === this.tableData.length && this.tableData.length > 0;
|
||||
},
|
||||
|
||||
// 清空选中状态
|
||||
clearSelected() {
|
||||
this.selectedIds = [];
|
||||
this.selectAll = false;
|
||||
this.batchAction = ''; // 重置批量操作选择
|
||||
},
|
||||
|
||||
// 处理批量操作
|
||||
handleBatchOperation() {
|
||||
if (!this.batchAction || this.selectedIds.length === 0) return;
|
||||
|
||||
switch (this.batchAction) {
|
||||
case 'delete':
|
||||
this.handleBatchDelete();
|
||||
break;
|
||||
case 'enable':
|
||||
this.handleBatchStatus(1);
|
||||
break;
|
||||
case 'disable':
|
||||
this.handleBatchStatus(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// 执行操作后重置选择
|
||||
this.batchAction = '';
|
||||
},
|
||||
|
||||
// 批量删除
|
||||
async handleBatchDelete() {
|
||||
if (!confirm(`确定要删除选中的 ${this.selectedIds.length} 条数据吗?`)) return;
|
||||
|
||||
try {
|
||||
const response = await request.post('/sys/data/batchDelete', {
|
||||
ids: this.selectedIds
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
alert('删除成功!');
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error);
|
||||
alert('删除失败,请重试!');
|
||||
}
|
||||
},
|
||||
|
||||
// 批量修改状态(启用/禁用)
|
||||
async handleBatchStatus(status) {
|
||||
try {
|
||||
const response = await request.post('/sys/data/batchUpdateStatus', {
|
||||
ids: this.selectedIds,
|
||||
status: status
|
||||
});
|
||||
|
||||
if (response.code === 200) {
|
||||
alert(`已${status === 1 ? '启用' : '禁用'}选中数据!`);
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量更新状态失败:', error);
|
||||
alert('操作失败,请重试!');
|
||||
}
|
||||
},
|
||||
|
||||
// 单个删除
|
||||
async handleDelete(id) {
|
||||
if (!confirm('确定要删除这条数据吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await request.post('/sys/data/delete', {id});
|
||||
if (response.code === 200) {
|
||||
alert('删除成功!');
|
||||
this.fetchData(); // 重新加载数据
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
alert('删除失败,请重试!');
|
||||
}
|
||||
},
|
||||
|
||||
// 打开新增模态框
|
||||
openAddModal() {
|
||||
alert('打开新增模态框');
|
||||
// 实际项目中添加模态框显示逻辑
|
||||
},
|
||||
|
||||
// 打开编辑模态框
|
||||
openEditModal(item) {
|
||||
alert(`打开编辑模态框,编辑ID: ${item.id}`);
|
||||
// 实际项目中添加模态框显示和数据回显逻辑
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载完成后初始化数据
|
||||
this.fetchData();
|
||||
}
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user