feat: 移除mock并接入真实权限控制

This commit is contained in:
湛兮
2026-05-26 16:24:03 +08:00
parent 5003628017
commit 304589bf8b
30 changed files with 965 additions and 1037 deletions
+107 -37
View File
@@ -9,12 +9,12 @@ import {
import {
createRole,
deleteRole,
listRoles,
listRolePage,
updateRole,
type Role,
type RolePayload
} from "@/api/access";
import { hasPerms } from "@/utils/auth";
import { hasMenuAction } from "@/utils/auth";
import Plus from "~icons/ep/plus";
import Search from "~icons/ep/search";
@@ -40,7 +40,16 @@ const formRef = ref<FormInstance>();
const roles = ref<Role[]>([]);
const query = reactive({
keyword: ""
keyword: "",
isSystem: undefined as boolean | undefined,
page: 1,
pageSize: 20
});
/** 角色管理列表由后端分页,这里只记录当前筛选结果的分页摘要。 */
const pagination = reactive({
total: 0,
totalPages: 0
});
const form = reactive<RoleFormState>({
@@ -72,26 +81,15 @@ const describedCount = computed(
() => roles.value.filter(item => Boolean(item.description)).length
);
const systemRoleCount = computed(
() => roles.value.filter(item => item.code === "admin").length
() => roles.value.filter(item => item.isSystem).length
);
const dialogTitle = computed(() => (form.id ? "编辑角色" : "新增角色"));
const canManageRoles = computed(() => hasPerms("role:manage"));
function applyRoleQuery(items: Role[]) {
const keyword = query.keyword.trim().toLowerCase();
if (keyword.length === 0) {
return items;
}
return items.filter(role => {
return (
role.code.toLowerCase().includes(keyword) ||
role.name.toLowerCase().includes(keyword) ||
(role.description ?? "").toLowerCase().includes(keyword)
);
});
}
const canCreateRole = computed(() => hasMenuAction("roles", "create"));
const canUpdateRole = computed(() => hasMenuAction("roles", "update"));
const canDeleteRole = computed(() => hasMenuAction("roles", "delete"));
const canOperateRole = computed(
() => canUpdateRole.value || canDeleteRole.value
);
function getErrorMessage(error: unknown, fallback: string) {
const message = (
@@ -134,12 +132,19 @@ function buildPayload(): RolePayload {
};
}
/** 角色查询必须走接口,避免搜索条件只在前端过滤当前缓存。 */
/** 角色筛选、分页必须走接口,避免查询条件只在前端过滤当前缓存。 */
async function fetchRoles() {
tableLoading.value = true;
try {
const result = await listRoles();
roles.value = applyRoleQuery(result.data);
const result = await listRolePage({
keyword: query.keyword.trim() || undefined,
isSystem: query.isSystem,
page: query.page,
pageSize: query.pageSize
});
roles.value = result.data.items;
pagination.total = result.data.pagination.total;
pagination.totalPages = result.data.pagination.totalPages;
} catch (error) {
ElMessage.error(getErrorMessage(error, "加载角色列表失败"));
} finally {
@@ -149,22 +154,37 @@ async function fetchRoles() {
function handleReset() {
query.keyword = "";
query.isSystem = undefined;
query.page = 1;
query.pageSize = 20;
fetchRoles();
}
function handleSearch() {
query.keyword = query.keyword.trim();
query.page = 1;
fetchRoles();
}
function handlePageChange(page: number) {
query.page = page;
fetchRoles();
}
function handleSizeChange(pageSize: number) {
query.page = 1;
query.pageSize = pageSize;
fetchRoles();
}
function openCreateDialog() {
if (!canManageRoles.value) return;
if (!canCreateRole.value) return;
resetFormState();
dialogVisible.value = true;
}
function openEditDialog(row: Role) {
if (!canManageRoles.value || row.isSystem) return;
if (!canUpdateRole.value || row.isSystem) return;
Object.assign(form, {
id: row.id,
code: row.code,
@@ -176,7 +196,7 @@ function openEditDialog(row: Role) {
}
async function submitForm() {
if (!canManageRoles.value) return;
if (form.id ? !canUpdateRole.value : !canCreateRole.value) return;
await formRef.value?.validate();
submitLoading.value = true;
@@ -201,7 +221,7 @@ async function submitForm() {
}
async function removeRole(row: Role) {
if (!canManageRoles.value || row.isSystem) return;
if (!canDeleteRole.value || row.isSystem) return;
try {
await ElMessageBox.confirm(
`删除后角色「${row.name}」无法再绑定员工,确认继续?`,
@@ -214,6 +234,10 @@ async function removeRole(row: Role) {
);
await deleteRole(row.id);
ElMessage.success("角色已删除");
if (roles.value.length === 1 && query.page > 1) {
query.page -= 1;
}
fetchRoles();
} catch (error) {
if (error !== "cancel") {
@@ -233,7 +257,7 @@ onMounted(fetchRoles);
<h1>角色管理</h1>
</div>
<el-button
v-if="canManageRoles"
v-if="canCreateRole"
type="primary"
:icon="Plus"
@click="openCreateDialog"
@@ -245,14 +269,14 @@ onMounted(fetchRoles);
<div class="summary-strip">
<div class="summary-item">
<span>总角色</span>
<strong>{{ roles.length }}</strong>
<strong>{{ pagination.total }}</strong>
</div>
<div class="summary-item">
<span>已配置说明</span>
<span>当前页有说明</span>
<strong>{{ describedCount }}</strong>
</div>
<div class="summary-item">
<span>系统角色</span>
<span>当前页系统角色</span>
<strong>{{ systemRoleCount }}</strong>
</div>
<div class="summary-item">
@@ -262,6 +286,16 @@ onMounted(fetchRoles);
</div>
<div class="toolbar">
<el-select
v-model="query.isSystem"
clearable
placeholder="全部类型"
class="toolbar-control"
@change="handleSearch"
>
<el-option label="系统内置" :value="true" />
<el-option label="自定义角色" :value="false" />
</el-select>
<el-input
v-model="query.keyword"
clearable
@@ -305,14 +339,14 @@ onMounted(fetchRoles);
</template>
</el-table-column>
<el-table-column
v-if="canManageRoles"
v-if="canOperateRole"
label="操作"
width="170"
fixed="right"
>
<template #default="{ row }">
<el-button
v-if="!row.isSystem"
v-if="canUpdateRole && !row.isSystem"
link
type="primary"
:icon="EditPen"
@@ -321,7 +355,7 @@ onMounted(fetchRoles);
编辑
</el-button>
<el-button
v-if="!row.isSystem"
v-if="canDeleteRole && !row.isSystem"
link
type="danger"
:icon="Delete"
@@ -333,6 +367,22 @@ onMounted(fetchRoles);
</template>
</el-table-column>
</el-table>
<div class="pagination-row">
<span
> {{ pagination.total }} {{ pagination.totalPages }} </span
>
<el-pagination
v-model:current-page="query.page"
v-model:page-size="query.pageSize"
background
layout="sizes, prev, pager, next"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<el-dialog
@@ -391,9 +441,9 @@ onMounted(fetchRoles);
.page-heading {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
h1 {
@@ -459,6 +509,10 @@ onMounted(fetchRoles);
width: 280px;
}
.toolbar-control {
width: 180px;
}
.toolbar-actions {
display: flex;
gap: 8px;
@@ -474,6 +528,16 @@ onMounted(fetchRoles);
width: 100%;
}
.pagination-row {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
padding: 14px 16px 0;
font-size: 13px;
color: #64748b;
}
.role-cell {
display: flex;
flex-direction: column;
@@ -510,8 +574,8 @@ onMounted(fetchRoles);
}
.page-heading {
align-items: flex-start;
flex-direction: column;
align-items: flex-start;
}
.summary-strip {
@@ -519,6 +583,7 @@ onMounted(fetchRoles);
}
.keyword-input,
.toolbar-control,
.toolbar-actions {
width: 100%;
margin-left: 0;
@@ -528,6 +593,11 @@ onMounted(fetchRoles);
justify-content: flex-end;
}
.pagination-row {
flex-direction: column;
align-items: flex-start;
}
.form-grid {
grid-template-columns: 1fr;
}