docs: 清理前端重复文档 + 补全 Enum 汇总

04-前端开发:
- 删除重复的短文件(02-API 交互、03-RBAC)
- 保留完整扩展版本(24KB + 21KB)

08-附录:
- 新增 03-Enum 汇总.md
  - 工单状态枚举(WorkOrderStatusEnum + OrderStatus)
  - 工单类型、优先级、触发来源
  - 状态流转 Mermaid 图
  - Java/TypeScript 对照
This commit is contained in:
lzh
2026-04-07 13:53:45 +08:00
parent b61896d3ba
commit 1c7ea60f1e
3 changed files with 392 additions and 1761 deletions

View File

@@ -1,736 +0,0 @@
# 03-RBAC 权限控制与开发规范
管理后台的安全性要求极高,权限控制必须做实做细,**严禁"靠隐藏按钮防黑客"**。
本文档基于 `yudao-ui-admin-vben` 真实代码,阐述前端 RBAC 权限控制的完整实现。
---
## 一、权限体系架构
AIOT 平台的权限控制分为三个层级:
```
┌─────────────────────────────────────────────────────────────┐
│ 权限控制层级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ 菜单级权限(路由守卫) │
│ │ 页面访问权 │ ────────────────────── │
│ │ (Menu) │ 无权限用户无法访问页面,路由不存在 │
│ └───────────────┘ │
│ │
│ ┌───────────────┐ 按钮级权限(指令/组件) │
│ │ 操作执行权 │ ────────────────────── │
│ │ (Button) │ v-hasPermi 控制按钮显隐 │
│ └───────────────┘ │
│ │
│ ┌───────────────┐ 数据级权限(接口 + 前端展示) │
│ │ 数据可见权 │ ────────────────────── │
│ │ (Data Scope) │ 用户只能看到本部门/本区域的数据 │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 1.1 权限标识命名规范
所有权限标识Permission String必须遵循统一格式
```
<模块>:<业务>:<操作>
示例:
ops:work-order:list # 工单列表查询
ops:work-order:create # 工单创建
ops:work-order:update # 工单修改
ops:work-order:delete # 工单删除
ops:work-order:audit # 工单审核
ops:work-order:dispatch # 工单派单
ops:inspection:record # 巡检记录
iot:device:reboot # 设备重启
iot:device:config # 设备配置
system:user:reset-password # 重置密码
```
**权限标识来源**:后端 Controller 方法上的 `@PreAuthorize` 注解,前端必须与之严格对齐。
```java
// 后端示例SecurityUtils.java
@RestController
@RequestMapping("/ops/work-order")
public class WorkOrderController {
@PreAuthorize("@ss.hasPermi('ops:work-order:list')")
@GetMapping("/list")
public PageResult<WorkOrderDO> list(...) { ... }
@PreAuthorize("@ss.hasPermi('ops:work-order:create')")
@PostMapping("/create")
public Long create(@RequestBody WorkOrderCreateReqVO reqVO) { ... }
}
```
---
## 二、动态路由加载流程
系统的左侧菜单树并非前端写死,而是基于用户的角色权限,登录后从后端动态获取并挂载。
### 2.1 路由加载时序图
```mermaid
sequenceDiagram
participant U as 用户
participant F as 前端 (Vue Router)
participant S as 前端 Store (Pinia)
participant B as 后端 API
U->>F: 访问系统首页 /
F->>S: 检查登录态 (token)
alt 未登录
F->>U: 重定向到登录页 /login
else 已登录
F->>S: 检查是否已加载路由
S->>S: 检查 permissionStore.isRouteAdded
alt 路由已加载
S->>F: 直接放行
else 路由未加载
S->>B: GET /system/menu/get-routers
B-->>S: 返回菜单树(按权限过滤)
S->>S: 将菜单树转换为路由配置<br/>(generateRoutes)
S->>F: router.addRoute(routes)
S->>S: 标记 isRouteAdded = true
S->>F: 重新导航到原目标页
end
end
```
### 2.2 核心代码实现
```typescript
// stores/permission.ts
import { defineStore } from 'pinia';
import type { RouteRecordRaw } from 'vue-router';
import { constantRoutes } from '@/router';
import { getRouters } from '@/api/system/menu';
import { transformMenuToRoutes } from '@/utils/router-helper';
export const usePermissionStore = defineStore('permission', () => {
// State
const permissionRoutes = ref<RouteRecordRaw[]>([]);
const isRouteAdded = ref(false);
const sidebarMenus = ref<MenuInfo[]>([]);
// Actions
/**
* 生成并添加路由
* 在登录后或刷新页面时调用
*/
async function generateRoutes() {
if (isRouteAdded.value) {
return permissionRoutes.value;
}
try {
// 1. 从后端获取菜单树(已按当前用户权限过滤)
const menuTree = await getRouters();
// 2. 将菜单树转换为 Vue Router 配置
const asyncRoutes = transformMenuToRoutes(menuTree);
// 3. 合并静态路由登录页、404 等)
permissionRoutes.value = [...constantRoutes, ...asyncRoutes];
// 4. 生成侧边栏菜单
sidebarMenus.value = menuTree;
// 5. 标记路由已添加(注意:必须在 addRoute 之后)
isRouteAdded.value = true;
return permissionRoutes.value;
} catch (error) {
console.error('[PermissionStore] 路由加载失败:', error);
throw error;
}
}
/**
* 重置权限状态(登出时调用)
*/
function resetPermission() {
permissionRoutes.value = [];
isRouteAdded.value = false;
sidebarMenus.value = [];
}
return {
permissionRoutes,
isRouteAdded,
sidebarMenus,
generateRoutes,
resetPermission
};
});
```
### 2.3 路由守卫Permission Guard
```typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore, usePermissionStore } from '@/stores';
import NProgress from 'nprogress';
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes // 初始只注册静态路由
});
// 白名单(无需登录即可访问)
const whiteList = ['/login', '/register', '/404'];
router.beforeEach(async (to, from, next) => {
NProgress.start();
const userStore = useUserStore();
const permissionStore = usePermissionStore();
const hasToken = userStore.token;
if (hasToken) {
// 已登录
if (to.path === '/login') {
// 已登录用户访问登录页,重定向到首页
next({ path: '/' });
NProgress.done();
} else {
// 检查路由是否已加载
if (permissionStore.isRouteAdded) {
// 路由已加载,直接放行
next();
} else {
try {
// 路由未加载,生成并添加路由
await permissionStore.generateRoutes();
// 动态添加路由
permissionStore.permissionRoutes.forEach(route => {
router.addRoute(route);
});
// 重新导航到原目标(确保路由已注册)
next({ ...to, replace: true });
} catch (error) {
// 路由加载失败token 过期/权限异常)
await userStore.logout();
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
}
} else {
// 未登录
if (whiteList.includes(to.path)) {
// 在白名单内,直接放行
next();
} else {
// 重定向到登录页
next(`/login?redirect=${to.path}`);
NProgress.done();
}
}
});
router.afterEach(() => {
NProgress.done();
});
export default router;
```
### 2.4 菜单树转路由配置
```typescript
// utils/router-helper.ts
import type { RouteRecordRaw } from 'vue-router';
import type { MenuInfo } from '@/types/system';
/**
* 将后端返回的菜单树转换为 Vue Router 配置
*/
export function transformMenuToRoutes(menuTree: MenuInfo[]): RouteRecordRaw[] {
const routes: RouteRecordRaw[] = [];
menuTree.forEach(menu => {
// 只处理目录和菜单类型的节点
if (menu.type === 'dir' || menu.type === 'menu') {
const route: RouteRecordRaw = {
path: menu.path,
name: menu.name,
component: loadComponent(menu.component),
redirect: menu.redirect,
meta: {
title: menu.name,
icon: menu.icon,
hidden: menu.visible === '0', // 1=显示0=隐藏
keepAlive: menu.keepAlive === '1',
permission: menu.permission // 权限标识
},
children: []
};
// 递归处理子菜单
if (menu.children && menu.children.length > 0) {
route.children = transformMenuToRoutes(menu.children);
}
routes.push(route);
}
});
return routes;
}
/**
* 动态加载组件Vite 语法)
*/
function loadComponent(componentPath?: string) {
if (!componentPath) {
return () => import('@/layouts/default/index.vue');
}
// 支持多种组件路径格式
const paths = [
`@/views/${componentPath}`,
`@/views/${componentPath}/index.vue`,
componentPath
];
for (const path of paths) {
try {
return () => import(/* @vite-ignore */ path);
} catch {
continue;
}
}
// 默认返回 404 组件
console.warn(`[RouterHelper] 组件加载失败:${componentPath}`);
return () => import('@/views/error-page/404.vue');
}
```
---
## 三、按钮级权限控制
### 3.1 `v-hasPermi` 指令实现
```typescript
// directives/permission/index.ts
import type { App, Directive, DirectiveBinding } from 'vue';
import { useUserStore } from '@/stores/modules/user';
/**
* 全局权限指令
* 用法:<a-button v-hasPermi="['ops:work-order:create']">新增</a-button>
*/
const hasPermission: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding<string[]>) {
const { value } = binding;
if (value) {
const requiredPermissions = Array.isArray(value) ? value : [value];
const userStore = useUserStore();
const allPermissions = userStore.permissions; // 用户拥有的所有权限标识
// 检查是否拥有任一权限
const hasPermission = requiredPermissions.some(permission =>
allPermissions.includes(permission)
);
if (!hasPermission) {
// 无权限,移除 DOM 元素
el.parentNode?.removeChild(el);
}
} else {
throw new Error('[Directive] v-hasPermi 需要传入权限标识数组');
}
}
};
/**
* 注册全局指令
*/
export function setupPermissionDirective(app: App) {
app.directive('hasPermi', hasPermission);
}
export default hasPermission;
```
### 3.2 `usePermission` Hook
对于不能在模板中使用指令的场景如动态渲染表格列、JS 逻辑判断),使用 Hook
```typescript
// hooks/web/usePermission.ts
import { useUserStore } from '@/stores/modules/user';
export function usePermission() {
const userStore = useUserStore();
/**
* 检查是否拥有指定权限
* @param permission 权限标识
* @returns 是否拥有权限
*/
function hasPermission(permission: string): boolean {
return userStore.permissions.includes(permission);
}
/**
* 检查是否拥有任一权限
* @param permissions 权限标识数组
* @returns 是否拥有任一权限
*/
function hasAnyPermission(permissions: string[]): boolean {
return permissions.some(permission =>
userStore.permissions.includes(permission)
);
}
/**
* 检查是否拥有所有权限
* @param permissions 权限标识数组
* @returns 是否拥有所有权限
*/
function hasAllPermissions(permissions: string[]): boolean {
return permissions.every(permission =>
userStore.permissions.includes(permission)
);
}
return {
hasPermission,
hasAnyPermission,
hasAllPermissions
};
}
```
### 3.3 使用示例
```vue
<template>
<div class="work-order-detail">
<!-- 按钮级权限指令方式 -->
<a-button v-hasPermi="['ops:work-order:update']" @click="handleEdit">
编辑
</a-button>
<a-button v-hasPermi="['ops:work-order:audit']" @click="handleAudit">
审核
</a-button>
<a-button v-hasPermi="['ops:work-order:delete']" @click="handleDelete">
删除
</a-button>
<!-- 表格列权限Hook 方式 -->
<a-table :columns="columns" :data-source="dataSource">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button
v-if="hasPermission('ops:work-order:dispatch')"
@click="handleDispatch(record)"
>
派单
</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { usePermission } from '@/hooks/web/usePermission';
import { ref } from 'vue';
const { hasPermission } = usePermission();
const columns = ref([
{ title: '工单号', dataIndex: 'orderNo', key: 'orderNo' },
{ title: '状态', dataIndex: 'status', key: 'status' },
{ title: '操作', key: 'action' } // 操作列根据权限动态渲染
]);
const dataSource = ref<WorkOrder[]>([]);
function handleDispatch(record: WorkOrder) {
// 派单逻辑
}
</script>
```
---
## 四、数据权限Data Scope
数据权限控制用户能看到哪些数据(如:只能看本部门的工单)。
**数据权限的核心在后端**,但前端需要配合处理展示逻辑。
### 4.1 后端数据范围类型
```java
// 后端数据范围枚举DataScopeEnum
public enum DataScopeEnum {
ALL, // 全部数据权限
CUSTOM_DEPT, // 自定义部门数据权限
SINGLE_DEPT, // 本部门数据权限
DEPT_AND_CHILD, // 本部门及以下数据权限
ONLY_SELF // 仅本人数据权限
}
```
### 4.2 前端处理策略
```typescript
// stores/user.ts
export const useUserStore = defineStore('user', () => {
// State
const user = ref<UserInfo | null>(null);
const permissions = ref<string[]>([]);
const dataScope = ref<DataScopeEnum>('ONLY_SELF');
const deptId = ref<number | null>(null);
// Actions
async function login(username: string, password: string) {
const result = await loginApi({ username, password });
// 保存用户信息
user.value = result.user;
permissions.value = result.permissions;
dataScope.value = result.dataScope;
deptId.value = result.deptId;
// 保存 token
token.value = result.token;
}
return {
user,
permissions,
dataScope,
deptId,
login,
// ...
};
});
```
### 4.3 前端展示适配
```vue
<template>
<div class="work-order-list">
<!-- 根据数据范围显示不同提示 -->
<a-alert
v-if="userStore.dataScope === 'ONLY_SELF'"
message="当前仅显示您创建的工单"
type="info"
show-icon
class="mb-4"
/>
<a-alert
v-else-if="userStore.dataScope === 'SINGLE_DEPT'"
message="当前显示本部门的工单"
type="info"
show-icon
class="mb-4"
/>
<!-- 筛选器根据数据范围控制可选部门 -->
<a-form :model="searchForm" inline>
<a-form-item label="所属部门">
<a-select
v-model:value="searchForm.deptId"
:options="deptOptions"
:disabled="userStore.dataScope === 'ONLY_SELF'"
placeholder="请选择部门"
/>
</a-form-item>
</a-form>
<a-table :columns="columns" :data-source="dataSource" />
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores/modules/user';
import { ref, computed } from 'vue';
const userStore = useUserStore();
const searchForm = ref({
deptId: undefined
});
// 部门下拉选项(根据数据范围过滤)
const deptOptions = computed(() => {
if (userStore.dataScope === 'ONLY_SELF') {
return []; // 仅本人,不显示部门选择
}
// 其他情况显示有权限的部门列表
return deptList.value.map(dept => ({
label: dept.name,
value: dept.id
}));
});
</script>
```
---
## 五、权限配置同步流程
当后端新增权限时,前后端需要同步更新:
```mermaid
flowchart TD
A[后端开发] -->|1. 新增 Controller 方法 | B[添加@PreAuthorize 注解]
B --> C[编写 SQL 插入菜单/按钮记录]
C --> D[提交后端代码]
D --> E{是否需要前端 UI?}
E -->|是 | F[前端开发:新增页面/按钮]
E -->|否 | G[无需前端改动]
F --> H[使用 v-hasPermi 绑定权限标识]
H --> I[提交前端代码]
I --> J[测试环境验证]
J --> K[生产环境部署]
K --> L[管理员分配权限给角色]
```
### 5.1 后端 SQL 示例
```sql
-- 新增菜单记录(系统管理 -> 工单管理 -> 工单审核按钮)
INSERT INTO system_menu (
parent_id, name, type, path, component,
permission, visible, status
) VALUES (
(SELECT id FROM system_menu WHERE name = '工单管理'), -- 父菜单 ID
'工单审核',
'button',
'',
'',
'ops:work-order:audit', -- 权限标识(必须与后端@PreAuthorize 一致)
'1',
'0'
);
```
### 5.2 前端权限标识对齐检查清单
- [ ] 权限标识字符串与后端 `@PreAuthorize` 注解完全一致
- [ ] 权限标识已录入 SQL 初始化脚本
- [ ] 前端按钮使用 `v-hasPermi` 而非 `v-if="role === 'xxx'"`
- [ ] 新增的菜单/按钮已在后端 `system/menu/get-routers` 返回的树中
- [ ] 测试账号已分配对应角色并验证权限生效
---
## 六、常见坑点与排障
### 6.1 路由已加载但菜单不显示
**现象**:用户有权限访问页面,但左侧菜单树看不到入口。
**原因**:后端 `system/menu/get-routers` 接口返回的菜单树中,该菜单的 `visible` 字段为 `0`(隐藏)。
**排查**
```sql
-- 检查菜单可见性
SELECT id, name, visible FROM system_menu WHERE permission = 'ops:work-order:list';
```
**解决**:将 `visible` 改为 `1`,或在前端路由配置中设置 `meta.hidden = false`
### 6.2 按钮不显示但接口能调通
**现象**:按钮被 `v-hasPermi` 隐藏,但直接在浏览器调用接口能成功。
**原因**:用户未分配对应权限,但后端接口未加 `@PreAuthorize` 注解。
**排查**
1. 检查前端 `userStore.permissions` 是否包含该权限标识
2. 检查后端 Controller 方法是否有 `@PreAuthorize` 注解
**风险**:这是严重的安全漏洞!前端隐藏只是防君子,后端必须加权限校验。
### 6.3 刷新页面后权限丢失
**现象**:登录正常,刷新页面后路由加载失败,重定向到登录页。
**原因**Pinia Store 状态在刷新后丢失,但 token 还在 localStorage 中。
**解决**:在 `main.ts` 或路由守卫中,检查 token 存在但 Store 为空时,重新获取用户信息:
```typescript
// stores/user.ts
async function initUserInfo() {
if (!this.token) return;
try {
const userInfo = await getUserInfoApi();
this.user = userInfo.user;
this.permissions = userInfo.permissions;
this.dataScope = userInfo.dataScope;
} catch {
// 获取失败,清除 token 并跳转登录
await this.logout();
}
}
```
### 6.4 权限标识大小写问题
**现象**:后端注解是 `'ops:work-order:list'`,前端写成了 `'ops:Work-Order:List'`
**解决**:权限标识统一使用**小写字母 + 连字符**,在团队规范中明确约定。
---
## 七、相关代码入口
| 模块 | 文件路径 | 说明 |
|-----|---------|------|
| 权限 Store | `src/stores/modules/permission.ts` | 动态路由加载 |
| 用户 Store | `src/stores/modules/user.ts` | 用户信息与权限列表 |
| 权限指令 | `src/directives/permission/index.ts` | `v-hasPermi` 实现 |
| 权限 Hook | `src/hooks/web/usePermission.ts` | JS 内权限判断 |
| 路由守卫 | `src/router/index.ts` | 登录态与路由检查 |
| 路由工具 | `src/utils/router-helper.ts` | 菜单树转路由配置 |
| 菜单 API | `src/api/system/menu.ts` | `getRouters()` 接口 |
---
## 八、安全红线
| 红线项 | 说明 | 后果 |
|-------|------|------|
| **前端隐藏代替后端校验** | 按钮隐藏不等于接口安全 | 未授权访问 |
| **硬编码角色名** | `v-if="role === 'admin'"` | 权限变更需发版 |
| **权限标识不一致** | 前后端字符串对不上 | 权限失效或越权 |
| **菜单 SQL 漏配置** | 新增权限忘记插菜单记录 | 用户看不到入口 |
| **刷新后不恢复权限** | Store 丢失未重新获取 | 用户被迫重新登录 |

View File

@@ -0,0 +1,392 @@
# 03-Enum 汇总
本文档汇总 AIOT 系统中所有业务枚举,包括后端 Java 枚举和前端 TypeScript 枚举。
---
## 一、工单相关枚举
### 1.1 工单状态WorkOrderStatusEnum
```java
// Java: WorkOrderStatusEnum.java
public enum WorkOrderStatusEnum {
PENDING("PENDING", "待分配"),
QUEUED("QUEUED", "排队中"),
DISPATCHED("DISPATCHED", "已推送"),
CONFIRMED("CONFIRMED", "已确认"),
ARRIVED("ARRIVED", "已到岗"),
PAUSED("PAUSED", "已暂停"),
COMPLETED("COMPLETED", "已完成"),
CANCELLED("CANCELLED", "已取消");
}
```
```typescript
// TypeScript: api/ops/order-center/index.ts
export enum OrderStatus {
PENDING = 'PENDING', // 待分配
QUEUED = 'QUEUED', // 排队中
DISPATCHED = 'DISPATCHED', // 已推送
CONFIRMED = 'CONFIRMED', // 已确认
ARRIVED = 'ARRIVED', // 已到岗
PAUSED = 'PAUSED', // 已暂停
COMPLETED = 'COMPLETED', // 已完成
CANCELLED = 'CANCELLED', // 已取消
}
```
**状态流转规则:**
```mermaid
stateDiagram-v2
[*] --> PENDING
PENDING --> QUEUED
PENDING --> DISPATCHED
QUEUED --> DISPATCHED
DISPATCHED --> CONFIRMED
CONFIRMED --> ARRIVED
ARRIVED --> COMPLETED
ARRIVED --> PAUSED
PAUSED --> ARRIVED
PENDING --> CANCELLED
QUEUED --> CANCELLED
DISPATCHED --> CANCELLED
CONFIRMED --> CANCELLED
ARRIVED --> CANCELLED
COMPLETED --> [*]
CANCELLED --> [*]
```
| 方法 | 说明 |
|-----|------|
| `isTerminal()` | 是否为终态COMPLETED/CANCELLED |
| `canConfirm()` | 是否可以确认(仅 DISPATCHED |
| `canStartWorking()` | 是否可以开始作业CONFIRMED/DISPATCHED |
| `canComplete()` | 是否可以完成(仅 ARRIVED |
| `canPause()` | 是否可以暂停ARRIVED/CONFIRMED |
| `canCancel()` | 是否可以取消(非终态) |
### 1.2 工单类型WorkOrderTypeEnum
```java
// Java
public enum WorkOrderTypeEnum {
CLEAN("CLEAN", "保洁"),
REPAIR("REPAIR", "维修"),
SECURITY("SECURITY", "安保"),
SERVICE("SERVICE", "客服");
}
```
```typescript
// TypeScript
export enum OrderType {
CLEAN = 'CLEAN', // 保洁
REPAIR = 'REPAIR', // 维修
SECURITY = 'SECURITY', // 安保
}
```
### 1.3 优先级PriorityEnum
```java
// Java
public enum PriorityEnum {
P0(0, "P0紧急", 0, true), // 可打断
P1(1, "P1重要", 1, false),
P2(2, "P2普通", 2, false),
P3(3, "P3低优", 3, false);
private final Integer priority;
private final String description;
private final Integer queueOrder;
private final Boolean canInterrupt;
}
```
```typescript
// TypeScript
export enum Priority {
P0 = 0, // 紧急
P1 = 1, // 重要
P2 = 2, // 普通
}
```
| 优先级 | 数字值 | 可打断 | 场景 |
|-------|-------|-------|------|
| P0 | 0 | ✅ | 溢水、危险品泄漏、严重安全事故 |
| P1 | 1 | ❌ | VIP投诉、重要访客、设备故障 |
| P2 | 2 | ❌ | 日常清洁超时、周期性保养 |
| P3 | 3 | ❌ | 日常巡检、例行清洁 |
### 1.4 触发来源SourceTypeEnum
```java
// Java
public enum SourceTypeEnum {
TRAFFIC("TRAFFIC", "系统触发"),
INSPECTION("INSPECTION", "巡检发现"),
MANUAL("MANUAL", "手动创建"),
SCHEDULE("SCHEDULE", "定时排班"),
ALARM("ALARM", "告警触发");
}
```
```typescript
// TypeScript
export enum TriggerSource {
IOT_BEACON = 'IOT_BEACON', // 蓝牙信标
IOT_TRAFFIC = 'IOT_TRAFFIC', // 客流阈值
TRAFFIC = 'TRAFFIC', // 客流阈值
VIDEO_ALARM = 'VIDEO_ALARM', // 视频告警
ACCESS_ALARM = 'ACCESS_ALARM', // 门禁告警
PATROL_ALARM = 'PATROL_ALARM', // 巡更告警
PANIC_BUTTON = 'PANIC_BUTTON', // 紧急按钮
MANUAL = 'MANUAL', // 手动创建
}
```
---
## 二、工牌相关枚举
### 2.1 工牌状态BadgeDeviceStatusEnum / CleanerStatusEnum
```java
// Java: BadgeDeviceStatusEnum.java / CleanerStatusEnum.java
public enum BadgeDeviceStatusEnum {
IDLE("idle", "空闲", true), // 可接新单
BUSY("busy", "忙碌", false),
PAUSED("paused", "暂停", false),
OFFLINE("offline", "离线", false);
private final String code;
private final String description;
private final boolean canAcceptNewOrder;
}
```
```typescript
// TypeScript: api/ops/cleaning/index.ts
export enum BadgeStatus {
IDLE = 'IDLE', // 空闲
BUSY = 'BUSY', // 忙碌
OFFLINE = 'OFFLINE', // 离线
PAUSED = 'PAUSED', // 暂停中
}
```
**状态转换规则:**
| 当前状态 | 可转换到 |
|---------|---------|
| OFFLINE | IDLE |
| IDLE | BUSY, OFFLINE |
| BUSY | PAUSED, IDLE, OFFLINE |
| PAUSED | BUSY, IDLE, OFFLINE |
### 2.2 工牌通知类型NotifyTypeEnum
```java
// Java
public enum NotifyTypeEnum {
VOICE("VOICE", "语音通知"),
VIBRATE("VIBRATE", "震动通知");
}
```
```typescript
// TypeScript
export enum NotifyType {
VIBRATE = 'VIBRATE', // 震动
VOICE = 'VOICE', // 语音
}
```
---
## 三、巡检相关枚举
### 3.1 巡检结果InspectionResultEnum
```java
// Java
public enum InspectionResultEnum {
FAILED(0, "不合格"),
PASSED(1, "合格");
}
```
```typescript
// TypeScript: api/ops/inspection/index.ts
// 0=不合格 1=合格
isLocationException: number // 0=正常 1=异常
resultStatus: number // 0=不合格 1=合格
```
### 3.2 巡检归属判定InspectionAttributionEnum
```java
// Java
public enum InspectionAttributionEnum {
PERSONAL(1, "个人责任"),
EMERGENCY(2, "突发状况"),
NORMAL(3, "正常");
}
```
```typescript
// TypeScript
attributionResult?: number // 1=个人责任 2=突发状况 3=正常
```
---
## 四、IoT 设备相关枚举
### 4.1 设备状态IotDeviceStateEnum
```java
// Java
public enum IotDeviceStateEnum {
INACTIVE(0, "未激活"),
ONLINE(1, "在线"),
OFFLINE(2, "离线");
}
```
| 状态 | 数值 | 说明 |
|-----|------|------|
| 未激活 | 0 | 设备未激活 |
| 在线 | 1 | 设备正常连接 |
| 离线 | 2 | 设备断开连接 |
### 4.2 设备消息类型IotDeviceMessageTypeEnum
```java
// Java
@Deprecated
public enum IotDeviceMessageTypeEnum {
STATE("state"), // 设备状态
PROPERTY("property"), // 设备属性
EVENT("event"), // 设备事件
SERVICE("service"), // 设备服务
CONFIG("config"), // 设备配置
OTA("ota"), // OTA 升级
REGISTER("register"), // 设备注册
TOPOLOGY("topology"); // 设备拓扑
}
```
---
## 五、系统相关枚举
### 5.1 通用状态CommonStatusEnum
```java
// Java
public enum CommonStatusEnum {
ENABLE(0, "开启"),
DISABLE(1, "关闭");
}
```
### 5.2 用户类型UserTypeEnum
```java
// Java
public enum UserTypeEnum {
MEMBER(1, "会员"),
ADMIN(2, "管理员");
}
```
### 5.3 终端类型TerminalEnum
```java
// Java
public enum TerminalEnum {
WEB(1, "Web"),
APP(2, "App"),
MINI_PROGRAM(3, "小程序");
}
```
---
## 六、前端专用枚举
### 6.1 HTTP 结果码ResultEnum
```typescript
// http/tools/enum.ts
export enum ResultEnum {
Success0 = 0, // 成功
Success200 = 200, // 成功
Error = 400, // 错误
Unauthorized = 401, // 未授权
Forbidden = 403, // 禁止访问
NotFound = 404, // 未找到
MethodNotAllowed = 405, // 方法不允许
RequestTimeout = 408, // 请求超时
InternalServerError = 500, // 服务器错误
NotImplemented = 501, // 未实现
BadGateway = 502, // 网关错误
ServiceUnavailable = 503, // 服务不可用
GatewayTimeout = 504, // 网关超时
HttpVersionNotSupported = 505, // HTTP版本不支持
}
```
### 6.2 蓝牙扫描状态ScanStatus
```typescript
// pages-ops/inspection/composables/use-bluetooth-scan.ts
export type ScanStatus =
| 'idle' // 空闲
| 'scanning' // 扫描中
| 'success' // 定位成功
| 'failed' // 定位失败
| 'no-permission'; // 缺少权限
```
### 6.3 字典类型DICT_TYPE
```typescript
// utils/constants/dict-enum.ts
export const DICT_TYPE = {
// OPS 模块
OPS_ORDER_PRIORITY: 'ops_order_priority',
// 系统模块
SYSTEM_USER_SEX: 'system_user_sex',
SYSTEM_MENU_TYPE: 'system_menu_type',
SYSTEM_ROLE_TYPE: 'system_role_type',
// 基础设施
INFRA_JOB_STATUS: 'infra_job_status',
INFRA_CONFIG_TYPE: 'infra_config_type',
// BPM
BPM_PROCESS_INSTANCE_STATUS: 'bpm_process_instance_status',
BPM_TASK_STATUS: 'bpm_task_status',
} as const
```
---
## 七、枚举对照表
### 7.1 工单状态对照
| Java | TypeScript | 中文 |
|------|-----------|------|
| PENDING | PENDING | 待分配 |
| QUEUED | QUEUED | 排队中 |
| DISPATCHED | DISPATCHED | 已