291 lines
7.1 KiB
Vue
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>
|