opt
This commit is contained in:
parent
8f02cffaad
commit
1ef05e18d3
|
|
@ -4,38 +4,55 @@ inclusion: always
|
|||
|
||||
# 项目开发规范
|
||||
|
||||
## 语言规范
|
||||
- 所有回复和对话使用中文(简体中文)
|
||||
- 文档、注释和说明使用中文
|
||||
- 代码注释使用中文,保持简洁明了
|
||||
## 语言和交流规范
|
||||
- 所有回复、对话、文档和代码注释使用中文(简体中文)
|
||||
- 保持技术术语的准确性和一致性
|
||||
|
||||
## 代码规范
|
||||
## 代码更改约定
|
||||
- 修复问题后,对需不需要进行修复文档的新增进行询问,如果选择不新增,在对话框中修复情况进行说明即可
|
||||
|
||||
### Java 后端规范
|
||||
- 遵循 Spring Boot 最佳实践
|
||||
- 使用 MyBatis Plus 进行数据库操作
|
||||
- 统一异常处理和返回结果格式
|
||||
- 接口文档使用 Swagger 注解
|
||||
- 实体类使用 Lombok 简化代码
|
||||
- 服务层方法需要事务注解
|
||||
## 后端开发规范 (Spring Boot + MyBatis Plus)
|
||||
|
||||
### Vue 前端规范
|
||||
- 使用 TypeScript 进行类型安全开发
|
||||
- 组件命名使用 PascalCase
|
||||
- 使用 Pinia 进行状态管理
|
||||
- API 调用统一封装在 api 目录
|
||||
- 路由配置支持权限控制
|
||||
### 项目结构
|
||||
- Controller层:处理HTTP请求,参数验证
|
||||
- Service层:业务逻辑处理,必须添加 `@Transactional` 注解
|
||||
- Mapper层:数据访问层,使用MyBatis Plus
|
||||
- Entity层:数据实体,使用Lombok注解简化代码
|
||||
- DTO层:数据传输对象,用于API请求响应
|
||||
|
||||
## 架构模式
|
||||
- 前后端分离架构
|
||||
- RESTful API 设计
|
||||
- JWT 身份认证
|
||||
- RBAC 权限控制模型
|
||||
- 统一的错误处理和日志记录
|
||||
### 代码约定
|
||||
- 统一返回格式:使用 `Result<T>` 包装所有API响应
|
||||
- 异常处理:使用 `@ControllerAdvice` 全局异常处理
|
||||
- API文档:所有接口必须添加Swagger注解
|
||||
- 数据库表名:统一使用 `sys_` 前缀
|
||||
- API路径:统一使用 `/api` 前缀
|
||||
|
||||
## 开发约定
|
||||
- 数据库表名使用 sys_ 前缀
|
||||
- API 接口统一使用 /api 前缀
|
||||
- 分页查询统一返回格式
|
||||
- 密码加密使用 BCrypt
|
||||
- 时间格式统一使用 ISO 8601
|
||||
## 前端开发规范 (Vue 3 + TypeScript)
|
||||
|
||||
### 项目结构
|
||||
- 组件命名:使用PascalCase(如 `UserManagement.vue`)
|
||||
- 状态管理:使用Pinia,store文件放在 `src/stores/`
|
||||
- API调用:统一封装在 `src/api/` 目录
|
||||
- 类型定义:放在 `src/types/` 目录
|
||||
|
||||
### 开发约定
|
||||
- 所有组件必须使用TypeScript
|
||||
- 路由配置支持基于角色的权限控制
|
||||
- 使用组合式API(Composition API)
|
||||
|
||||
## 架构和安全规范
|
||||
|
||||
### 认证授权
|
||||
- JWT Token认证机制
|
||||
- RBAC(基于角色的访问控制)权限模型
|
||||
- 密码使用BCrypt加密存储
|
||||
|
||||
### API设计
|
||||
- 遵循RESTful设计原则
|
||||
- 分页查询统一返回 `PageResult<T>` 格式
|
||||
- 时间格式统一使用ISO 8601标准
|
||||
|
||||
### 数据库设计
|
||||
- 主键统一使用Long类型的id
|
||||
- 创建时间和更新时间字段必须包含
|
||||
- 逻辑删除使用deleted字段标识
|
||||
|
|
@ -86,7 +86,7 @@ public class SecurityConfig {
|
|||
.antMatchers("/favicon.ico").permitAll()
|
||||
// 其他请求需要认证
|
||||
.anyRequest().authenticated())
|
||||
|
||||
|
||||
// 允许iframe嵌入
|
||||
.headers(headers -> headers
|
||||
.frameOptions(frame -> frame.disable())
|
||||
|
|
|
|||
|
|
@ -69,12 +69,15 @@
|
|||
@click="toggleSidebar"
|
||||
>
|
||||
<el-icon size="20">
|
||||
<!-- 隐藏状态:显示菜单图标,点击展开 -->
|
||||
<template v-if="isHidden">
|
||||
<Menu />
|
||||
</template>
|
||||
<!-- 折叠状态:显示左箭头图标,点击隐藏 -->
|
||||
<template v-else-if="autoCollapse">
|
||||
<Expand />
|
||||
<ArrowLeft />
|
||||
</template>
|
||||
<!-- 展开状态:显示收起图标,点击折叠 -->
|
||||
<template v-else>
|
||||
<Fold />
|
||||
</template>
|
||||
|
|
@ -146,7 +149,7 @@ import { useTabsStore } from '@/stores/tabs'
|
|||
import { useUserStore } from '@/stores/user'
|
||||
import {
|
||||
ArrowDown,
|
||||
Expand,
|
||||
ArrowLeft,
|
||||
Fold,
|
||||
House,
|
||||
Loading,
|
||||
|
|
@ -158,7 +161,7 @@ import {
|
|||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
|
@ -176,7 +179,6 @@ const cachedViews = computed(() => {
|
|||
})
|
||||
|
||||
// 侧边栏状态
|
||||
const isCollapse = ref(false)
|
||||
const isHidden = ref(false)
|
||||
const sidebarWidthValue = ref(200) // 实际宽度值
|
||||
const isResizing = ref(false)
|
||||
|
|
@ -208,16 +210,19 @@ const breadcrumbs = computed(() => {
|
|||
|
||||
// 切换侧边栏
|
||||
const toggleSidebar = () => {
|
||||
if (!isHidden.value && !autoCollapse.value) {
|
||||
// 展开 → 折叠
|
||||
sidebarWidthValue.value = MIN_WIDTH
|
||||
} else if (!isHidden.value && autoCollapse.value) {
|
||||
// 折叠 → 隐藏
|
||||
if (isHidden.value) {
|
||||
// 隐藏状态 → 展开状态(显示菜单图标时点击展开)
|
||||
isHidden.value = false
|
||||
// 使用 nextTick 确保 DOM 更新后再设置宽度,避免动画冲突
|
||||
nextTick(() => {
|
||||
sidebarWidthValue.value = 200
|
||||
})
|
||||
} else if (autoCollapse.value) {
|
||||
// 折叠状态 → 隐藏状态(显示折叠图标时点击隐藏)
|
||||
isHidden.value = true
|
||||
} else {
|
||||
// 隐藏 → 展开
|
||||
isHidden.value = false
|
||||
sidebarWidthValue.value = 200
|
||||
// 展开状态 → 折叠状态(显示展开图标时点击折叠)
|
||||
sidebarWidthValue.value = MIN_WIDTH
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -404,6 +409,24 @@ watch(
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 优化菜单展开动画 - 减少Element Plus默认的过渡时间 */
|
||||
.sidebar-menu .el-menu-item,
|
||||
.sidebar-menu .el-sub-menu {
|
||||
transition: all 0.15s ease !important; /* 缩短过渡时间 */
|
||||
}
|
||||
|
||||
.sidebar-menu .el-menu-item .el-menu-item__title,
|
||||
.sidebar-menu .el-sub-menu__title {
|
||||
transition: all 0.15s ease !important; /* 缩短文本显示时间 */
|
||||
}
|
||||
|
||||
/* 当菜单从折叠状态展开时,立即显示文本 */
|
||||
.sidebar-menu:not(.el-menu--collapse) .el-menu-item span,
|
||||
.sidebar-menu:not(.el-menu--collapse) .el-sub-menu__title span {
|
||||
opacity: 1 !important;
|
||||
transition: opacity 0.1s ease !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@
|
|||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# 侧边菜单交互按钮修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
前端菜单栏的展开隐藏交互按钮存在反直觉的问题:
|
||||
- 当按钮显示展开图标时,用户期望点击后会展开菜单
|
||||
- 但实际上点击后菜单会折叠,与用户预期相反
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 原始逻辑问题
|
||||
1. **图标显示逻辑**:
|
||||
- 隐藏时显示 `Menu` 图标 ✓
|
||||
- 折叠时显示 `Expand` 图标 ❌(应该显示折叠相关图标)
|
||||
- 展开时显示 `Fold` 图标 ❌(应该显示展开相关图标)
|
||||
|
||||
2. **交互逻辑**:
|
||||
- 展开 → 折叠 ✓
|
||||
- 折叠 → 隐藏 ✓
|
||||
- 隐藏 → 展开 ✓
|
||||
|
||||
### 根本原因
|
||||
图标显示的含义与实际操作不匹配,导致用户困惑。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 修复后的图标逻辑
|
||||
```typescript
|
||||
// 图标显示逻辑:显示当前可执行的操作
|
||||
<template v-if="isHidden">
|
||||
<Menu /> <!-- 隐藏状态:显示菜单图标,点击展开 -->
|
||||
</template>
|
||||
<template v-else-if="autoCollapse">
|
||||
<ArrowLeft /> <!-- 折叠状态:显示左箭头图标,点击隐藏 -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<Fold /> <!-- 展开状态:显示折叠图标,点击收起 -->
|
||||
</template>
|
||||
```
|
||||
|
||||
### 修复后的交互逻辑
|
||||
```typescript
|
||||
const toggleSidebar = () => {
|
||||
if (isHidden.value) {
|
||||
// 隐藏状态 → 展开状态(显示菜单图标时点击展开)
|
||||
isHidden.value = false
|
||||
sidebarWidthValue.value = 200
|
||||
} else if (autoCollapse.value) {
|
||||
// 折叠状态 → 隐藏状态(显示折叠图标时点击隐藏)
|
||||
isHidden.value = true
|
||||
} else {
|
||||
// 展开状态 → 折叠状态(显示展开图标时点击折叠)
|
||||
sidebarWidthValue.value = MIN_WIDTH
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 文件修改
|
||||
- **文件**:`admin-frontend/src/layout/LayoutView.vue`
|
||||
- **修改内容**:
|
||||
1. 调整图标显示逻辑,让图标表示当前可执行的操作
|
||||
2. 重新排列交互逻辑的判断顺序,提高代码可读性
|
||||
3. 添加详细注释说明每种状态的图标和操作
|
||||
4. **图标优化**:将展开状态的拉伸图标改为折叠图标,折叠状态使用左箭头图标
|
||||
5. **动画优化**:解决菜单展开时文本延迟显示的问题
|
||||
- 缩短 Element Plus 菜单的过渡动画时间
|
||||
- 优化文本显示的时机
|
||||
- 使用 nextTick 避免 DOM 更新冲突
|
||||
|
||||
### 交互流程
|
||||
1. **隐藏状态**:显示菜单图标 → 点击展开菜单
|
||||
2. **展开状态**:显示折叠图标 → 点击折叠为图标模式
|
||||
3. **折叠状态**:显示左箭头图标 → 点击完全隐藏
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **功能测试**:
|
||||
- 验证三种状态间的切换是否正常
|
||||
- 确认图标显示与实际操作一致
|
||||
|
||||
2. **用户体验测试**:
|
||||
- 确认图标的直观性
|
||||
- 验证交互的流畅性
|
||||
|
||||
## 图标选择说明
|
||||
|
||||
### 图标语义
|
||||
- **Menu 图标**:表示菜单,用于隐藏状态,暗示可以展开菜单
|
||||
- **Fold 图标**:表示折叠/收起,用于展开状态,暗示可以收起菜单
|
||||
- **ArrowLeft 图标**:表示向左隐藏,用于折叠状态,暗示可以完全隐藏
|
||||
|
||||
### 为什么不用 Expand 图标
|
||||
原来的 `Expand` 图标在展开状态下显示会让用户困惑,因为:
|
||||
1. 视觉上像拉伸符号,不够直观
|
||||
2. 用户看到展开图标会期望点击后进一步展开,而不是收起
|
||||
3. `Fold` 图标更清楚地表达"收起"的含义
|
||||
|
||||
## 动画优化说明
|
||||
|
||||
### 问题分析
|
||||
菜单展开时文本延迟显示的原因:
|
||||
1. Element Plus 的 `el-menu` 组件有内置的折叠/展开动画
|
||||
2. 默认的过渡时间较长(约 0.3s)
|
||||
3. 文本显示需要等待宽度动画完成
|
||||
|
||||
### 解决方案
|
||||
1. **缩短过渡时间**:将菜单项的过渡时间从默认的 0.3s 缩短到 0.15s
|
||||
2. **优化文本显示**:确保文本在菜单展开时立即显示
|
||||
3. **DOM 更新优化**:使用 `nextTick` 避免状态变化时的冲突
|
||||
|
||||
### CSS 优化
|
||||
```css
|
||||
/* 缩短Element Plus默认的过渡时间 */
|
||||
.sidebar-menu .el-menu-item,
|
||||
.sidebar-menu .el-sub-menu {
|
||||
transition: all 0.15s ease !important;
|
||||
}
|
||||
|
||||
/* 立即显示文本 */
|
||||
.sidebar-menu:not(.el-menu--collapse) .el-menu-item span,
|
||||
.sidebar-menu:not(.el-menu--collapse) .el-sub-menu__title span {
|
||||
opacity: 1 !important;
|
||||
transition: opacity 0.1s ease !important;
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过调整图标显示逻辑、选择更合适的图标,以及优化菜单展开动画,现在侧边栏的交互体验更加流畅和直观:
|
||||
1. 按钮图标准确反映点击后的操作结果
|
||||
2. 菜单展开时文本立即显示,无延迟
|
||||
3. 整体交互更符合用户预期
|
||||
Loading…
Reference in New Issue