291 lines
7.1 KiB
Vue

<template>
<el-card class="box-card">
<div class="header-bar">
<h2>角色管理</h2>
<div class="header-actions">
<el-button type="primary" @click="showRoleDialog = true">
<el-icon><Plus /></el-icon>
添加角色
</el-button>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<div class="loading-spinner"></div>
<p>正在加载角色数据...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error-state">
<el-alert title="加载失败" :message="error" type="error" show-icon />
<el-button type="primary" @click="fetchRoles">重试</el-button>
</div>
<!-- 角色列表 -->
<div v-else>
<el-table :data="roles" stripe style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="label" label="角色名称" align="center" />
<el-table-column prop="value" label="角色标识" align="center" />
<el-table-column prop="remark" label="备注" align="center" />
<el-table-column label="操作" width="180" align="center">
<template #default="{ row }">
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination-bar">
<el-pagination
background
:current-page="page"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
layout="total, prev, pager, next"
/>
</div>
</div>
<!-- 新增/编辑角色弹窗 -->
<el-dialog
:title="isEditing ? '编辑角色' : '添加角色'"
v-model="showRoleDialog"
width="420px"
:close-on-click-modal="false"
>
<el-form
:model="roleForm"
:rules="formRules"
ref="roleFormRef"
label-width="90px"
>
<el-form-item label="角色名称" prop="label">
<el-input
v-model="roleForm.label"
:disabled="isEditing && roleForm.value === 'admin'"
/>
</el-form-item>
<el-form-item label="角色标识" prop="value">
<el-input
v-model="roleForm.value"
:disabled="isEditing && roleForm.value === 'admin'"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="roleForm.remark" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showRoleDialog = false">取消</el-button>
<el-button type="primary" @click="submitRoleForm"> 保存 </el-button>
</template>
</el-dialog>
</el-card>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import {
ElMessage,
ElMessageBox,
type FormInstance,
type FormRules,
} from "element-plus";
const roles = ref<any[]>([]);
const loading = ref(false);
const error = ref("");
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const showRoleDialog = ref(false);
const isEditing = ref(false);
const roleForm = reactive({
id: null,
label: "",
value: "",
remark: "",
});
const roleFormRef = ref<FormInstance>();
const formRules: FormRules = {
label: [
{ required: true, message: "请输入角色名称", trigger: "blur" },
{ min: 2, max: 24, message: "名称长度2-24字符", trigger: "blur" },
],
value: [
{ required: true, message: "请输入角色标识", trigger: "blur" },
{
pattern: /^[a-zA-Z0-9_]+$/,
message: "只能包含字母、数字或下划线",
trigger: "blur",
},
],
};
async function fetchRoles() {
loading.value = true;
error.value = "";
try {
// TODO: 替换为真实API
await new Promise((r) => setTimeout(r, 300));
// 假数据
const all = [
{ id: 1, label: "系统管理员", value: "admin", remark: "拥有全部权限" },
{ id: 2, label: "普通用户", value: "user", remark: "普通访问权限" },
{ id: 3, label: "运营", value: "ops", remark: "运营相关权限" },
// ...更多
];
total.value = all.length;
roles.value = all.slice(
(page.value - 1) * pageSize.value,
page.value * pageSize.value
);
} catch (err: any) {
error.value = err.message || "获取角色列表失败";
} finally {
loading.value = false;
}
}
const handlePageChange = (val: number) => {
page.value = val;
fetchRoles();
};
function resetRoleForm() {
roleForm.id = null;
roleForm.label = "";
roleForm.value = "";
roleForm.remark = "";
}
function handleEdit(row: any) {
isEditing.value = true;
roleForm.id = row.id;
roleForm.label = row.label;
roleForm.value = row.value;
roleForm.remark = row.remark;
showRoleDialog.value = true;
}
async function handleDelete(row: any) {
try {
await ElMessageBox.confirm(
`确定要删除角色「${row.label}」? 删除后不可恢复。`,
"警告",
{ type: "warning" }
);
// TODO: 替换为真实API
await new Promise((r) => setTimeout(r, 300));
ElMessage.success("删除成功");
fetchRoles();
} catch {
// 取消删除
}
}
async function submitRoleForm() {
// 表单校验
await roleFormRef.value?.validate();
loading.value = true;
try {
if (isEditing.value) {
// TODO: 替换为真实API更新
await new Promise((r) => setTimeout(r, 300));
ElMessage.success("角色更新成功");
} else {
// TODO: 替换为真实API新增
await new Promise((r) => setTimeout(r, 300));
ElMessage.success("角色添加成功");
}
showRoleDialog.value = false;
fetchRoles();
} catch (e: any) {
ElMessage.error(e.message || "操作失败,请重试");
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchRoles();
});
</script>
<style scoped>
.role-management-module {
box-sizing: border-box;
background: #fff;
padding: 28px 22px 14px 18px;
min-height: 500px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
}
.header-bar h2 {
font-size: 1.18rem;
font-weight: 600;
margin: 0;
color: #24292f;
}
.loading-state,
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 220px;
padding: 32px 0 16px 0;
background: #f5f7fa;
border-radius: 5px;
}
.loading-spinner {
width: 36px;
height: 36px;
border: 4px solid #e5e5e5;
border-top: 4px solid #409eff;
border-radius: 50%;
animation: loading-spin 0.8s linear infinite;
margin-bottom: 14px;
}
@keyframes loading-spin {
to {
transform: rotate(360deg);
}
}
.el-dialog__body {
padding-top: 18px !important;
}
.el-form .el-form-item {
margin-bottom: 16px;
}
.el-form .el-input {
width: 100%;
}
@media (max-width: 600px) {
.role-management-module {
padding: 10px 4px;
min-height: 300px;
}
.header-bar {
flex-direction: column;
align-items: stretch;
row-gap: 8px;
padding-bottom: 2px;
}
}
</style>