feat: 接入真实登录鉴权流程

This commit is contained in:
湛兮
2026-05-26 14:45:15 +08:00
parent a6c9f5dee3
commit 5003628017
21 changed files with 572 additions and 305 deletions
+39 -5
View File
@@ -21,6 +21,7 @@ import {
type RoleOption,
type StoreOption
} from "@/api/access";
import { hasPerms } from "@/utils/auth";
import Plus from "~icons/ep/plus";
import Search from "~icons/ep/search";
@@ -41,6 +42,13 @@ type EmployeeFormState = EmployeePayload & {
/** 员工手机号按中国大陆手机号做前端第一层校验,最终唯一性仍由后端保证。 */
const phonePattern = /^1[3-9]\d{9}$/;
const bindableRoleCodes = new Set([
"store_manager",
"cashier",
"kitchen",
"part_time",
"admin"
]);
const tableLoading = ref(false);
const catalogLoading = ref(false);
const submitLoading = ref(false);
@@ -99,6 +107,8 @@ const inactiveCount = computed(
() => employees.value.filter(item => item.status === "INACTIVE").length
);
const dialogTitle = computed(() => (form.id ? "编辑员工" : "新增员工"));
const canManageEmployees = computed(() => hasPerms("employee:manage"));
const canViewAllEmployees = computed(() => hasPerms("employee:view:all"));
function getErrorMessage(error: unknown, fallback: string) {
const message = (
@@ -147,14 +157,22 @@ function buildPayload(): EmployeePayload {
/** 门店和角色是员工表单的基础字典,打开弹窗前必须先加载。 */
async function fetchCatalog() {
const shouldLoadStores =
canViewAllEmployees.value || canManageEmployees.value;
const shouldLoadRoles = canManageEmployees.value;
if (!shouldLoadStores && !shouldLoadRoles) return;
catalogLoading.value = true;
try {
const [storeResult, roleResult] = await Promise.all([
listStores(),
listRoles()
shouldLoadStores ? listStores() : Promise.resolve({ data: [] }),
shouldLoadRoles ? listRoles() : Promise.resolve({ data: [] })
]);
stores.value = storeResult.data;
roles.value = roleResult.data;
roles.value = roleResult.data.filter(role =>
bindableRoleCodes.has(role.code)
);
} catch (error) {
ElMessage.error(getErrorMessage(error, "加载门店和角色选项失败"));
} finally {
@@ -209,12 +227,14 @@ function handleSizeChange(pageSize: number) {
}
function openCreateDialog() {
if (!canManageEmployees.value) return;
resetFormState();
dialogVisible.value = true;
}
/** 编辑前重新拉详情,确保角色绑定不是来自列表摘要的过期数据。 */
async function openEditDialog(row: Employee) {
if (!canManageEmployees.value) return;
try {
const result = await getEmployee(row.id);
const employee = result.data;
@@ -235,6 +255,7 @@ async function openEditDialog(row: Employee) {
}
async function submitForm() {
if (!canManageEmployees.value) return;
await formRef.value?.validate();
submitLoading.value = true;
@@ -259,6 +280,7 @@ async function submitForm() {
}
async function toggleStatus(row: Employee) {
if (!canManageEmployees.value) return;
const nextStatus: EmployeeStatus =
row.status === "ACTIVE" ? "INACTIVE" : "ACTIVE";
const action = nextStatus === "ACTIVE" ? "启用" : "停用";
@@ -284,6 +306,7 @@ async function toggleStatus(row: Employee) {
}
async function removeEmployee(row: Employee) {
if (!canManageEmployees.value) return;
try {
await ElMessageBox.confirm(
`删除后员工「${row.name}」会被软删除并停用,确认继续?`,
@@ -321,7 +344,12 @@ onMounted(async () => {
<p class="eyebrow">门店员工权限管理</p>
<h1>员工管理</h1>
</div>
<el-button type="primary" :icon="Plus" @click="openCreateDialog">
<el-button
v-if="canManageEmployees"
type="primary"
:icon="Plus"
@click="openCreateDialog"
>
新增员工
</el-button>
</div>
@@ -347,6 +375,7 @@ onMounted(async () => {
<div class="toolbar">
<el-select
v-if="canViewAllEmployees"
v-model="query.storeId"
clearable
filterable
@@ -440,7 +469,12 @@ onMounted(async () => {
{{ formatTime(row.updatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<el-table-column
v-if="canManageEmployees"
label="操作"
width="260"
fixed="right"
>
<template #default="{ row }">
<el-button
link