diff --git a/.understand-anything/knowledge-graph.json b/.understand-anything/knowledge-graph.json new file mode 100644 index 0000000000000000000000000000000000000000..3dade769b759f7f17539da1a36f8942f99ce6187 --- /dev/null +++ b/.understand-anything/knowledge-graph.json @@ -0,0 +1,957 @@ +{ + "version": "1.0.0", + "project": { + "name": "renren-security", + "languages": ["java", "typescript", "vue", "xml", "yaml"], + "frameworks": ["Spring Boot", "MyBatis-Plus", "Shiro", "Vue 3", "Element Plus", "Vite", "Pinia"], + "description": "轻量级的,前后端分离的Java快速开发平台,专注于权限管理系统", + "analyzedAt": "2026-05-27T00:00:00Z", + "gitCommitHash": "20440e17330034a707f346e0f10c03c3b7e931d3" + }, + "nodes": [ + { + "id": "module:renren-common", + "type": "module", + "name": "renren-common", + "summary": "公共模块,提供通用工具类、异常处理、验证框架、Redis配置、基础服务等", + "tags": ["module", "common", "utility"] + }, + { + "id": "module:renren-admin", + "type": "module", + "name": "renren-admin", + "summary": "管理后台模块,包含用户管理、角色管理、菜单管理、部门管理、日志管理、定时任务等核心功能", + "tags": ["module", "admin", "backend"] + }, + { + "id": "module:renren-api", + "type": "module", + "name": "renren-api", + "summary": "API服务模块,提供对外RESTful API接口,包含用户登录、注册等功能", + "tags": ["module", "api", "rest"] + }, + { + "id": "module:renren-generator", + "type": "module", + "name": "renren-generator", + "summary": "代码生成器模块,可自动生成entity、xml、dao、service、vue、sql代码", + "tags": ["module", "generator", "tool"] + }, + { + "id": "module:renren-dynamic-datasource", + "type": "module", + "name": "renren-dynamic-datasource", + "summary": "动态数据源模块,支持多数据源切换", + "tags": ["module", "datasource", "database"] + }, + { + "id": "module:renren-ui", + "type": "module", + "name": "renren-ui", + "summary": "Vue3前端工程,使用Element Plus构建管理界面", + "tags": ["module", "frontend", "vue"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/AdminApplication.java", + "type": "file", + "name": "AdminApplication.java", + "filePath": "renren-admin/src/main/java/io/renren/AdminApplication.java", + "summary": "管理后台Spring Boot应用启动类", + "tags": ["entry-point", "spring-boot", "admin"] + }, + { + "id": "file:renren-api/src/main/java/io/renren/ApiApplication.java", + "type": "file", + "name": "ApiApplication.java", + "filePath": "renren-api/src/main/java/io/renren/ApiApplication.java", + "summary": "API服务Spring Boot应用启动类", + "tags": ["entry-point", "spring-boot", "api"] + }, + { + "id": "file:renren-generator/src/main/java/io/renren/GeneratorApplication.java", + "type": "file", + "name": "GeneratorApplication.java", + "filePath": "renren-generator/src/main/java/io/renren/GeneratorApplication.java", + "summary": "代码生成器Spring Boot应用启动类", + "tags": ["entry-point", "spring-boot", "generator"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/AdminApplication.java:AdminApplication", + "type": "class", + "name": "AdminApplication", + "filePath": "renren-admin/src/main/java/io/renren/AdminApplication.java", + "summary": "管理后台应用主类,继承SpringBootServletInitializer支持WAR部署", + "tags": ["class", "spring-boot", "entry-point"] + }, + { + "id": "class:renren-api/src/main/java/io/renren/ApiApplication.java:ApiApplication", + "type": "class", + "name": "ApiApplication", + "filePath": "renren-api/src/main/java/io/renren/ApiApplication.java", + "summary": "API服务应用主类,继承SpringBootServletInitializer支持WAR部署", + "tags": ["class", "spring-boot", "entry-point"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "type": "file", + "name": "LoginController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "summary": "登录控制器,处理验证码生成、用户登录、退出登录等接口", + "tags": ["controller", "security", "authentication"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", + "type": "class", + "name": "LoginController", + "filePath": "renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "summary": "登录管理REST控制器,提供/captcha和/login等端点", + "tags": ["class", "controller", "rest", "authentication"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:captcha", + "type": "endpoint", + "name": "GET /captcha", + "filePath": "renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "summary": "生成验证码接口", + "tags": ["endpoint", "captcha", "security"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", + "type": "endpoint", + "name": "POST /login", + "filePath": "renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "summary": "用户登录接口,验证用户名密码并返回token", + "tags": ["endpoint", "login", "authentication"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "type": "file", + "name": "SysUserController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "用户管理控制器,提供用户CRUD、分页查询、密码修改等功能", + "tags": ["controller", "user-management", "sys"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", + "type": "class", + "name": "SysUserController", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "用户管理REST控制器,映射/sys/user路径", + "tags": ["class", "controller", "rest", "user"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", + "type": "endpoint", + "name": "GET /sys/user/page", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "用户分页查询接口", + "tags": ["endpoint", "user", "pagination"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:get", + "type": "endpoint", + "name": "GET /sys/user/{id}", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "获取用户详情接口", + "tags": ["endpoint", "user", "detail"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:save", + "type": "endpoint", + "name": "POST /sys/user", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "创建用户接口", + "tags": ["endpoint", "user", "create"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:update", + "type": "endpoint", + "name": "PUT /sys/user", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "更新用户接口", + "tags": ["endpoint", "user", "update"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:delete", + "type": "endpoint", + "name": "DELETE /sys/user", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "summary": "删除用户接口", + "tags": ["endpoint", "user", "delete"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", + "type": "file", + "name": "SysRoleController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", + "summary": "角色管理控制器,提供角色CRUD、权限分配等功能", + "tags": ["controller", "role-management", "sys"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:SysRoleController", + "type": "class", + "name": "SysRoleController", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", + "summary": "角色管理REST控制器,映射/sys/role路径", + "tags": ["class", "controller", "rest", "role"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:page", + "type": "endpoint", + "name": "GET /sys/role/page", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", + "summary": "角色分页查询接口", + "tags": ["endpoint", "role", "pagination"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "type": "file", + "name": "SysMenuController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "summary": "菜单管理控制器,提供菜单CRUD、权限标识获取、用户导航菜单等功能", + "tags": ["controller", "menu-management", "sys"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", + "type": "class", + "name": "SysMenuController", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "summary": "菜单管理REST控制器,映射/sys/menu路径", + "tags": ["class", "controller", "rest", "menu"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:nav", + "type": "endpoint", + "name": "GET /sys/menu/nav", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "summary": "获取用户导航菜单接口", + "tags": ["endpoint", "menu", "navigation"] + }, + { + "id": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:permissions", + "type": "endpoint", + "name": "GET /sys/menu/permissions", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "summary": "获取用户权限标识集合接口", + "tags": ["endpoint", "menu", "permissions"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java", + "type": "file", + "name": "SysDeptController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java", + "summary": "部门管理控制器,提供部门树形结构CRUD功能", + "tags": ["controller", "dept-management", "sys"] + }, + { + "id": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java:SysDeptController", + "type": "class", + "name": "SysDeptController", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java", + "summary": "部门管理REST控制器,映射/sys/dept路径", + "tags": ["class", "controller", "rest", "dept"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictTypeController.java", + "type": "file", + "name": "SysDictTypeController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictTypeController.java", + "summary": "字典类型管理控制器", + "tags": ["controller", "dict-management", "sys"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictDataController.java", + "type": "file", + "name": "SysDictDataController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictDataController.java", + "summary": "字典数据管理控制器", + "tags": ["controller", "dict-management", "sys"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysParamsController.java", + "type": "file", + "name": "SysParamsController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/sys/controller/SysParamsController.java", + "summary": "参数管理控制器", + "tags": ["controller", "params-management", "sys"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogLoginController.java", + "type": "file", + "name": "SysLogLoginController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/log/controller/SysLogLoginController.java", + "summary": "登录日志管理控制器", + "tags": ["controller", "log", "login-log"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogOperationController.java", + "type": "file", + "name": "SysLogOperationController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/log/controller/SysLogOperationController.java", + "summary": "操作日志管理控制器", + "tags": ["controller", "log", "operation-log"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogErrorController.java", + "type": "file", + "name": "SysLogErrorController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/log/controller/SysLogErrorController.java", + "summary": "异常日志管理控制器", + "tags": ["controller", "log", "error-log"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobController.java", + "type": "file", + "name": "ScheduleJobController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobController.java", + "summary": "定时任务管理控制器", + "tags": ["controller", "job", "schedule"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobLogController.java", + "type": "file", + "name": "ScheduleJobLogController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobLogController.java", + "summary": "定时任务日志管理控制器", + "tags": ["controller", "job", "log"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/modules/oss/controller/SysOssController.java", + "type": "file", + "name": "SysOssController.java", + "filePath": "renren-admin/src/main/java/io/renren/modules/oss/controller/SysOssController.java", + "summary": "文件上传管理控制器,支持七牛云、阿里云、腾讯云等云存储", + "tags": ["controller", "oss", "file-upload"] + }, + { + "id": "file:renren-api/src/main/java/io/renren/controller/ApiLoginController.java", + "type": "file", + "name": "ApiLoginController.java", + "filePath": "renren-api/src/main/java/io/renren/controller/ApiLoginController.java", + "summary": "API登录控制器,处理API用户登录和退出", + "tags": ["controller", "api", "authentication"] + }, + { + "id": "file:renren-api/src/main/java/io/renren/controller/ApiRegisterController.java", + "type": "file", + "name": "ApiRegisterController.java", + "filePath": "renren-api/src/main/java/io/renren/controller/ApiRegisterController.java", + "summary": "API用户注册控制器", + "tags": ["controller", "api", "register"] + }, + { + "id": "file:renren-generator/src/main/java/io/renren/controller/SysGeneratorController.java", + "type": "file", + "name": "SysGeneratorController.java", + "filePath": "renren-generator/src/main/java/io/renren/controller/SysGeneratorController.java", + "summary": "代码生成器控制器,提供表列表查询和代码生成功能", + "tags": ["controller", "generator", "code-generation"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/service/impl/BaseServiceImpl.java", + "type": "file", + "name": "BaseServiceImpl.java", + "filePath": "renren-common/src/main/java/io/renren/common/service/impl/BaseServiceImpl.java", + "summary": "基础服务实现类,提供通用CRUD操作", + "tags": ["service", "base", "crud"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/service/impl/CrudServiceImpl.java", + "type": "file", + "name": "CrudServiceImpl.java", + "filePath": "renren-common/src/main/java/io/renren/common/service/impl/CrudServiceImpl.java", + "summary": "CRUD服务实现类,对增删改查进行封装", + "tags": ["service", "crud", "base"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/exception/RenException.java", + "type": "file", + "name": "RenException.java", + "filePath": "renren-common/src/main/java/io/renren/common/exception/RenException.java", + "summary": "自定义异常类", + "tags": ["exception", "common"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/utils/Result.java", + "type": "file", + "name": "Result.java", + "filePath": "renren-common/src/main/java/io/renren/common/utils/Result.java", + "summary": "统一响应结果封装类", + "tags": ["utility", "response", "common"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/redis/RedisConfig.java", + "type": "file", + "name": "RedisConfig.java", + "filePath": "renren-common/src/main/java/io/renren/common/redis/RedisConfig.java", + "summary": "Redis配置类", + "tags": ["config", "redis", "cache"] + }, + { + "id": "file:renren-common/src/main/java/io/renren/common/xss/XssFilter.java", + "type": "file", + "name": "XssFilter.java", + "filePath": "renren-common/src/main/java/io/renren/common/xss/XssFilter.java", + "summary": "XSS攻击防护过滤器", + "tags": ["filter", "security", "xss"] + }, + { + "id": "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", + "type": "file", + "name": "DynamicDataSource.java", + "filePath": "renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", + "summary": "动态数据源配置类,支持多数据源切换", + "tags": ["config", "datasource", "database"] + }, + { + "id": "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/aspect/DataSourceAspect.java", + "type": "file", + "name": "DataSourceAspect.java", + "filePath": "renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/aspect/DataSourceAspect.java", + "summary": "数据源切换切面,基于@DataSource注解实现动态数据源切换", + "tags": ["aspect", "datasource", "aop"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/common/config/MybatisPlusConfig.java", + "type": "file", + "name": "MybatisPlusConfig.java", + "filePath": "renren-admin/src/main/java/io/renren/common/config/MybatisPlusConfig.java", + "summary": "MyBatis-Plus配置类", + "tags": ["config", "mybatis", "database"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/common/config/SwaggerConfig.java", + "type": "file", + "name": "SwaggerConfig.java", + "filePath": "renren-admin/src/main/java/io/renren/common/config/SwaggerConfig.java", + "summary": "Swagger API文档配置类", + "tags": ["config", "swagger", "api-docs"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/common/aspect/LogOperationAspect.java", + "type": "file", + "name": "LogOperationAspect.java", + "filePath": "renren-admin/src/main/java/io/renren/common/aspect/LogOperationAspect.java", + "summary": "操作日志切面,基于@LogOperation注解自动记录操作日志", + "tags": ["aspect", "log", "aop"] + }, + { + "id": "file:renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", + "type": "file", + "name": "DataFilterAspect.java", + "filePath": "renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", + "summary": "数据权限过滤切面,基于@DataFilter注解实现数据权限控制", + "tags": ["aspect", "data-permission", "aop"] + }, + { + "id": "file:renren-ui/src/main.ts", + "type": "file", + "name": "main.ts", + "filePath": "renren-ui/src/main.ts", + "summary": "Vue3前端应用入口文件,注册全局组件和插件", + "tags": ["entry-point", "vue", "frontend"] + }, + { + "id": "file:renren-ui/src/App.vue", + "type": "file", + "name": "App.vue", + "filePath": "renren-ui/src/App.vue", + "summary": "Vue3根组件", + "tags": ["component", "vue", "root"] + }, + { + "id": "file:renren-ui/src/router/index.ts", + "type": "file", + "name": "router/index.ts", + "filePath": "renren-ui/src/router/index.ts", + "summary": "Vue Router路由配置", + "tags": ["config", "router", "frontend"] + }, + { + "id": "file:renren-ui/src/store/index.ts", + "type": "file", + "name": "store/index.ts", + "filePath": "renren-ui/src/store/index.ts", + "summary": "Pinia状态管理配置", + "tags": ["config", "store", "pinia"] + }, + { + "id": "file:renren-ui/src/utils/http.ts", + "type": "file", + "name": "http.ts", + "filePath": "renren-ui/src/utils/http.ts", + "summary": "Axios HTTP请求封装", + "tags": ["utility", "http", "axios"] + }, + { + "id": "file:renren-ui/src/views/login.vue", + "type": "file", + "name": "login.vue", + "filePath": "renren-ui/src/views/login.vue", + "summary": "登录页面组件", + "tags": ["view", "login", "vue"] + }, + { + "id": "file:renren-ui/src/views/sys/user.vue", + "type": "file", + "name": "user.vue", + "filePath": "renren-ui/src/views/sys/user.vue", + "summary": "用户管理页面组件", + "tags": ["view", "user", "vue"] + }, + { + "id": "file:renren-ui/src/views/sys/role.vue", + "type": "file", + "name": "role.vue", + "filePath": "renren-ui/src/views/sys/role.vue", + "summary": "角色管理页面组件", + "tags": ["view", "role", "vue"] + }, + { + "id": "file:renren-ui/src/views/sys/menu.vue", + "type": "file", + "name": "menu.vue", + "filePath": "renren-ui/src/views/sys/menu.vue", + "summary": "菜单管理页面组件", + "tags": ["view", "menu", "vue"] + }, + { + "id": "file:renren-ui/src/views/sys/dept.vue", + "type": "file", + "name": "dept.vue", + "filePath": "renren-ui/src/views/sys/dept.vue", + "summary": "部门管理页面组件", + "tags": ["view", "dept", "vue"] + }, + { + "id": "file:renren-ui/src/layout/index.vue", + "type": "file", + "name": "layout/index.vue", + "filePath": "renren-ui/src/layout/index.vue", + "summary": "主布局组件", + "tags": ["layout", "vue", "component"] + }, + { + "id": "file:pom.xml", + "type": "config", + "name": "pom.xml", + "filePath": "pom.xml", + "summary": "Maven父项目配置文件,定义模块结构和公共依赖", + "tags": ["config", "maven", "build"] + }, + { + "id": "file:renren-admin/pom.xml", + "type": "config", + "name": "renren-admin/pom.xml", + "filePath": "renren-admin/pom.xml", + "summary": "管理后台模块Maven配置", + "tags": ["config", "maven", "admin"] + }, + { + "id": "file:renren-api/pom.xml", + "type": "config", + "name": "renren-api/pom.xml", + "filePath": "renren-api/pom.xml", + "summary": "API服务模块Maven配置", + "tags": ["config", "maven", "api"] + }, + { + "id": "file:renren-admin/src/main/resources/application.yml", + "type": "config", + "name": "application.yml", + "filePath": "renren-admin/src/main/resources/application.yml", + "summary": "管理后台应用配置文件,包含数据库、Redis、MyBatis等配置", + "tags": ["config", "yaml", "admin"] + }, + { + "id": "file:renren-api/src/main/resources/application.yml", + "type": "config", + "name": "renren-api/application.yml", + "filePath": "renren-api/src/main/resources/application.yml", + "summary": "API服务应用配置文件", + "tags": ["config", "yaml", "api"] + }, + { + "id": "file:docker-compose.yml", + "type": "config", + "name": "docker-compose.yml", + "filePath": "docker-compose.yml", + "summary": "Docker Compose部署配置", + "tags": ["config", "docker", "deployment"] + }, + { + "id": "file:renren-admin/src/main/resources/mapper/sys/SysUserDao.xml", + "type": "file", + "name": "SysUserDao.xml", + "filePath": "renren-admin/src/main/resources/mapper/sys/SysUserDao.xml", + "summary": "用户DAO的MyBatis XML映射文件", + "tags": ["mapper", "mybatis", "user"] + }, + { + "id": "file:renren-admin/src/main/resources/mapper/sys/SysRoleDao.xml", + "type": "file", + "name": "SysRoleDao.xml", + "filePath": "renren-admin/src/main/resources/mapper/sys/SysRoleDao.xml", + "summary": "角色DAO的MyBatis XML映射文件", + "tags": ["mapper", "mybatis", "role"] + }, + { + "id": "file:renren-admin/src/main/resources/mapper/sys/SysMenuDao.xml", + "type": "file", + "name": "SysMenuDao.xml", + "filePath": "renren-admin/src/main/resources/mapper/sys/SysMenuDao.xml", + "summary": "菜单DAO的MyBatis XML映射文件", + "tags": ["mapper", "mybatis", "menu"] + }, + { + "id": "concept:authentication", + "type": "concept", + "name": "Authentication", + "summary": "基于Shiro的认证机制,使用OAuth2 token进行身份验证", + "tags": ["concept", "security", "authentication"] + }, + { + "id": "concept:authorization", + "type": "concept", + "name": "Authorization", + "summary": "基于RBAC的权限控制模型,支持菜单权限和按钮权限", + "tags": ["concept", "security", "authorization"] + }, + { + "id": "concept:data-permission", + "type": "concept", + "name": "Data Permission", + "summary": "数据权限控制,用户只能访问本部门及子部门数据", + "tags": ["concept", "security", "data-permission"] + }, + { + "id": "concept:multi-datasource", + "type": "concept", + "name": "Multi-Datasource", + "summary": "动态多数据源支持,可运行时切换数据源", + "tags": ["concept", "database", "datasource"] + }, + { + "id": "concept:code-generation", + "type": "concept", + "name": "Code Generation", + "summary": "代码生成机制,根据数据库表结构自动生成前后端代码", + "tags": ["concept", "generator", "productivity"] + }, + { + "id": "concept:restful-api", + "type": "concept", + "name": "RESTful API", + "summary": "RESTful API设计风格,统一响应格式和分页机制", + "tags": ["concept", "api", "design"] + }, + { + "id": "concept:xss-protection", + "type": "concept", + "name": "XSS Protection", + "summary": "XSS跨站脚本攻击防护机制", + "tags": ["concept", "security", "xss"] + } + ], + "edges": [ + {"source": "module:renren-admin", "target": "module:renren-common", "type": "depends_on", "weight": 0.6}, + {"source": "module:renren-api", "target": "module:renren-common", "type": "depends_on", "weight": 0.6}, + {"source": "module:renren-generator", "target": "module:renren-common", "type": "depends_on", "weight": 0.6}, + {"source": "module:renren-admin", "target": "module:renren-dynamic-datasource", "type": "depends_on", "weight": 0.6}, + {"source": "file:renren-admin/src/main/java/io/renren/AdminApplication.java", "target": "class:renren-admin/src/main/java/io/renren/AdminApplication.java:AdminApplication", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/AdminApplication.java:AdminApplication", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-api/src/main/java/io/renren/ApiApplication.java", "target": "class:renren-api/src/main/java/io/renren/ApiApplication.java:ApiApplication", "type": "contains", "weight": 1.0}, + {"source": "class:renren-api/src/main/java/io/renren/ApiApplication.java:ApiApplication", "target": "module:renren-api", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", "target": "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:captcha", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", "target": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:get", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:save", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:update", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:delete", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", "target": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:SysRoleController", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:SysRoleController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:page", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", "target": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:nav", "type": "contains", "weight": 1.0}, + {"source": "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:permissions", "type": "contains", "weight": 1.0}, + {"source": "file:renren-ui/src/main.ts", "target": "file:renren-ui/src/App.vue", "type": "imports", "weight": 0.7}, + {"source": "file:renren-ui/src/main.ts", "target": "file:renren-ui/src/router/index.ts", "type": "imports", "weight": 0.7}, + {"source": "file:renren-ui/src/main.ts", "target": "file:renren-ui/src/store/index.ts", "type": "imports", "weight": 0.7}, + {"source": "file:renren-ui/src/main.ts", "target": "file:renren-ui/src/utils/http.ts", "type": "imports", "weight": 0.7}, + {"source": "file:renren-ui/src/router/index.ts", "target": "file:renren-ui/src/views/login.vue", "type": "routes", "weight": 0.5}, + {"source": "file:renren-ui/src/router/index.ts", "target": "file:renren-ui/src/views/sys/user.vue", "type": "routes", "weight": 0.5}, + {"source": "file:renren-ui/src/router/index.ts", "target": "file:renren-ui/src/views/sys/role.vue", "type": "routes", "weight": 0.5}, + {"source": "file:renren-ui/src/router/index.ts", "target": "file:renren-ui/src/views/sys/menu.vue", "type": "routes", "weight": 0.5}, + {"source": "file:renren-ui/src/router/index.ts", "target": "file:renren-ui/src/views/sys/dept.vue", "type": "routes", "weight": 0.5}, + {"source": "file:renren-ui/src/views/login.vue", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", "type": "calls", "weight": 0.5}, + {"source": "file:renren-ui/src/views/sys/user.vue", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", "type": "calls", "weight": 0.5}, + {"source": "file:renren-ui/src/views/sys/role.vue", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:page", "type": "calls", "weight": 0.5}, + {"source": "file:renren-ui/src/views/sys/menu.vue", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:nav", "type": "calls", "weight": 0.5}, + {"source": "file:renren-admin/src/main/resources/application.yml", "target": "module:renren-admin", "type": "configures", "weight": 0.6}, + {"source": "file:renren-api/src/main/resources/application.yml", "target": "module:renren-api", "type": "configures", "weight": 0.6}, + {"source": "file:pom.xml", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:pom.xml", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:pom.xml", "target": "module:renren-api", "type": "contains", "weight": 1.0}, + {"source": "file:pom.xml", "target": "module:renren-generator", "type": "contains", "weight": 1.0}, + {"source": "file:pom.xml", "target": "module:renren-dynamic-datasource", "type": "contains", "weight": 1.0}, + {"source": "file:docker-compose.yml", "target": "module:renren-admin", "type": "deploys", "weight": 0.5}, + {"source": "file:docker-compose.yml", "target": "module:renren-api", "type": "deploys", "weight": 0.5}, + {"source": "concept:authentication", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", "type": "related", "weight": 0.5}, + {"source": "concept:authorization", "target": "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:permissions", "type": "related", "weight": 0.5}, + {"source": "concept:data-permission", "target": "file:renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", "type": "related", "weight": 0.5}, + {"source": "concept:multi-datasource", "target": "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", "type": "related", "weight": 0.5}, + {"source": "concept:code-generation", "target": "file:renren-generator/src/main/java/io/renren/controller/SysGeneratorController.java", "type": "related", "weight": 0.5}, + {"source": "concept:restful-api", "target": "module:renren-api", "type": "related", "weight": 0.5}, + {"source": "concept:xss-protection", "target": "file:renren-common/src/main/java/io/renren/common/xss/XssFilter.java", "type": "related", "weight": 0.5}, + {"source": "file:renren-common/src/main/java/io/renren/common/service/impl/BaseServiceImpl.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-common/src/main/java/io/renren/common/service/impl/CrudServiceImpl.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-common/src/main/java/io/renren/common/exception/RenException.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-common/src/main/java/io/renren/common/utils/Result.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-common/src/main/java/io/renren/common/redis/RedisConfig.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-common/src/main/java/io/renren/common/xss/XssFilter.java", "target": "module:renren-common", "type": "contains", "weight": 1.0}, + {"source": "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", "target": "module:renren-dynamic-datasource", "type": "contains", "weight": 1.0}, + {"source": "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/aspect/DataSourceAspect.java", "target": "module:renren-dynamic-datasource", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/common/config/MybatisPlusConfig.java", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/common/config/SwaggerConfig.java", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/common/aspect/LogOperationAspect.java", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/resources/mapper/sys/SysUserDao.xml", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/resources/mapper/sys/SysRoleDao.xml", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-admin/src/main/resources/mapper/sys/SysMenuDao.xml", "target": "module:renren-admin", "type": "contains", "weight": 1.0}, + {"source": "file:renren-ui/src/layout/index.vue", "target": "module:renren-ui", "type": "contains", "weight": 1.0} + ], + "layers": [ + { + "id": "layer:frontend", + "name": "Frontend Layer", + "description": "Vue3前端工程,包含页面组件、路由、状态管理和HTTP请求封装", + "nodeIds": [ + "module:renren-ui", + "file:renren-ui/src/main.ts", + "file:renren-ui/src/App.vue", + "file:renren-ui/src/router/index.ts", + "file:renren-ui/src/store/index.ts", + "file:renren-ui/src/utils/http.ts", + "file:renren-ui/src/views/login.vue", + "file:renren-ui/src/views/sys/user.vue", + "file:renren-ui/src/views/sys/role.vue", + "file:renren-ui/src/views/sys/menu.vue", + "file:renren-ui/src/views/sys/dept.vue", + "file:renren-ui/src/layout/index.vue" + ] + }, + { + "id": "layer:api", + "name": "API Layer", + "description": "RESTful API服务层,提供对外接口服务", + "nodeIds": [ + "module:renren-api", + "file:renren-api/src/main/java/io/renren/ApiApplication.java", + "class:renren-api/src/main/java/io/renren/ApiApplication.java:ApiApplication", + "file:renren-api/src/main/java/io/renren/controller/ApiLoginController.java", + "file:renren-api/src/main/java/io/renren/controller/ApiRegisterController.java", + "file:renren-api/src/main/resources/application.yml" + ] + }, + { + "id": "layer:admin", + "name": "Admin Backend Layer", + "description": "管理后台核心业务层,包含用户、角色、菜单、部门、日志、定时任务等管理功能", + "nodeIds": [ + "module:renren-admin", + "file:renren-admin/src/main/java/io/renren/AdminApplication.java", + "class:renren-admin/src/main/java/io/renren/AdminApplication.java:AdminApplication", + "file:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", + "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", + "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:captcha", + "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", + "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:get", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:save", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:update", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:delete", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", + "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:SysRoleController", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:page", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", + "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:nav", + "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:permissions", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java", + "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java:SysDeptController", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictTypeController.java", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDictDataController.java", + "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysParamsController.java", + "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogLoginController.java", + "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogOperationController.java", + "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogErrorController.java", + "file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobController.java", + "file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobLogController.java", + "file:renren-admin/src/main/java/io/renren/modules/oss/controller/SysOssController.java", + "file:renren-admin/src/main/java/io/renren/common/config/MybatisPlusConfig.java", + "file:renren-admin/src/main/java/io/renren/common/config/SwaggerConfig.java", + "file:renren-admin/src/main/java/io/renren/common/aspect/LogOperationAspect.java", + "file:renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", + "file:renren-admin/src/main/resources/application.yml", + "file:renren-admin/src/main/resources/mapper/sys/SysUserDao.xml", + "file:renren-admin/src/main/resources/mapper/sys/SysRoleDao.xml", + "file:renren-admin/src/main/resources/mapper/sys/SysMenuDao.xml" + ] + }, + { + "id": "layer:common", + "name": "Common Layer", + "description": "公共基础层,提供通用工具类、异常处理、验证框架、Redis配置、基础服务等", + "nodeIds": [ + "module:renren-common", + "file:renren-common/src/main/java/io/renren/common/service/impl/BaseServiceImpl.java", + "file:renren-common/src/main/java/io/renren/common/service/impl/CrudServiceImpl.java", + "file:renren-common/src/main/java/io/renren/common/exception/RenException.java", + "file:renren-common/src/main/java/io/renren/common/utils/Result.java", + "file:renren-common/src/main/java/io/renren/common/redis/RedisConfig.java", + "file:renren-common/src/main/java/io/renren/common/xss/XssFilter.java" + ] + }, + { + "id": "layer:datasource", + "name": "Dynamic Datasource Layer", + "description": "动态数据源层,支持多数据源切换", + "nodeIds": [ + "module:renren-dynamic-datasource", + "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", + "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/aspect/DataSourceAspect.java" + ] + }, + { + "id": "layer:generator", + "name": "Code Generator Layer", + "description": "代码生成器层,根据数据库表结构自动生成代码", + "nodeIds": [ + "module:renren-generator", + "file:renren-generator/src/main/java/io/renren/GeneratorApplication.java", + "file:renren-generator/src/main/java/io/renren/controller/SysGeneratorController.java" + ] + }, + { + "id": "layer:config", + "name": "Configuration Layer", + "description": "配置层,包含Maven配置、Docker部署配置等", + "nodeIds": [ + "file:pom.xml", + "file:renren-admin/pom.xml", + "file:renren-api/pom.xml", + "file:docker-compose.yml" + ] + }, + { + "id": "layer:concept", + "name": "Concept Layer", + "description": "概念层,定义系统核心设计理念和架构模式", + "nodeIds": [ + "concept:authentication", + "concept:authorization", + "concept:data-permission", + "concept:multi-datasource", + "concept:code-generation", + "concept:restful-api", + "concept:xss-protection" + ] + } + ], + "tour": [ + { + "order": 1, + "title": "项目概览", + "description": "了解renren-security项目的整体架构和设计理念。这是一个轻量级的、前后端分离的Java快速开发平台,专注于权限管理系统。", + "nodeIds": ["file:pom.xml", "concept:authentication", "concept:authorization", "concept:data-permission"] + }, + { + "order": 2, + "title": "模块结构分析", + "description": "探索项目的多模块结构,包括renren-common公共模块、renren-admin管理后台、renren-api API服务、renren-generator代码生成器和renren-dynamic-datasource动态数据源模块。", + "nodeIds": ["module:renren-common", "module:renren-admin", "module:renren-api", "module:renren-generator", "module:renren-dynamic-datasource", "module:renren-ui"] + }, + { + "order": 3, + "title": "管理后台入口", + "description": "从AdminApplication启动类开始,了解管理后台如何初始化和加载各个模块。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/AdminApplication.java", "class:renren-admin/src/main/java/io/renren/AdminApplication.java:AdminApplication", "file:renren-admin/src/main/resources/application.yml"] + }, + { + "order": 4, + "title": "认证与授权流程", + "description": "深入理解基于Shiro的认证机制和OAuth2 token的授权流程,包括登录验证、验证码生成、token管理等。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java", "class:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:LoginController", "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:captcha", "concept:authentication", "concept:authorization"] + }, + { + "order": 5, + "title": "用户管理模块", + "description": "学习用户管理的完整CRUD操作,包括分页查询、用户创建、更新、删除等功能实现。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java", "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:SysUserController", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:save", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:update", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:delete"] + }, + { + "order": 6, + "title": "角色与权限管理", + "description": "了解RBAC权限模型如何实现角色管理和菜单权限分配。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java", "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:SysRoleController", "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java", "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:SysMenuController", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysMenuController.java:permissions"] + }, + { + "order": 7, + "title": "数据权限控制", + "description": "探索基于@DataFilter注解的数据权限机制,确保用户只能访问本部门及子部门的数据。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/common/aspect/DataFilterAspect.java", "concept:data-permission", "file:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java", "class:renren-admin/src/main/java/io/renren/modules/sys/controller/SysDeptController.java:SysDeptController"] + }, + { + "order": 8, + "title": "日志管理模块", + "description": "了解登录日志、操作日志和异常日志的记录与查询机制。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogLoginController.java", "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogOperationController.java", "file:renren-admin/src/main/java/io/renren/modules/log/controller/SysLogErrorController.java", "file:renren-admin/src/main/java/io/renren/common/aspect/LogOperationAspect.java"] + }, + { + "order": 9, + "title": "定时任务模块", + "description": "学习基于Quartz的定时任务管理功能,包括任务的创建、调度、执行和日志记录。", + "nodeIds": ["file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobController.java", "file:renren-admin/src/main/java/io/renren/modules/job/controller/ScheduleJobLogController.java"] + }, + { + "order": 10, + "title": "前端架构", + "description": "探索Vue3前端工程的结构,包括路由配置、状态管理、HTTP请求封装和页面组件。", + "nodeIds": ["file:renren-ui/src/main.ts", "file:renren-ui/src/App.vue", "file:renren-ui/src/router/index.ts", "file:renren-ui/src/store/index.ts", "file:renren-ui/src/utils/http.ts", "file:renren-ui/src/views/login.vue", "file:renren-ui/src/layout/index.vue"] + }, + { + "order": 11, + "title": "前后端交互", + "description": "理解前端如何通过HTTP请求与后端API进行交互,完成登录、数据查询等操作。", + "nodeIds": ["file:renren-ui/src/utils/http.ts", "file:renren-ui/src/views/login.vue", "endpoint:renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java:login", "file:renren-ui/src/views/sys/user.vue", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysUserController.java:page", "file:renren-ui/src/views/sys/role.vue", "endpoint:renren-admin/src/main/java/io/renren/modules/sys/controller/SysRoleController.java:page"] + }, + { + "order": 12, + "title": "代码生成器", + "description": "了解代码生成器如何根据数据库表结构自动生成前后端代码,提高开发效率。", + "nodeIds": ["module:renren-generator", "file:renren-generator/src/main/java/io/renren/controller/SysGeneratorController.java", "concept:code-generation"] + }, + { + "order": 13, + "title": "动态数据源支持", + "description": "探索动态多数据源切换机制,支持MySQL、Oracle、PostgreSQL、达梦等多种数据库。", + "nodeIds": ["module:renren-dynamic-datasource", "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/config/DynamicDataSource.java", "file:renren-dynamic-datasource/src/main/java/io/renren/commons/dynamic/datasource/aspect/DataSourceAspect.java", "concept:multi-datasource"] + }, + { + "order": 14, + "title": "安全机制", + "description": "了解项目的安全防护机制,包括XSS防护、SQL注入防护、密码加密等。", + "nodeIds": ["file:renren-common/src/main/java/io/renren/common/xss/XssFilter.java", "concept:xss-protection"] + }, + { + "order": 15, + "title": "部署与配置", + "description": "了解项目的部署方式,包括Docker Compose部署和传统部署方式。", + "nodeIds": ["file:docker-compose.yml", "file:renren-admin/src/main/resources/application.yml", "file:renren-api/src/main/resources/application.yml"] + } + ] +} \ No newline at end of file diff --git a/.understand-anything/meta.json b/.understand-anything/meta.json new file mode 100644 index 0000000000000000000000000000000000000000..49dd3cc4559c5f398fdf0de2f266706ca59e4c76 --- /dev/null +++ b/.understand-anything/meta.json @@ -0,0 +1,6 @@ +{ + "lastAnalyzedAt": "2026-05-27T00:00:00Z", + "gitCommitHash": "20440e17330034a707f346e0f10c03c3b7e931d3", + "version": "1.0.0", + "analyzedFiles": 75 +} \ No newline at end of file diff --git a/API_REQUEST_CHAINS.md b/API_REQUEST_CHAINS.md new file mode 100644 index 0000000000000000000000000000000000000000..98615d588b18e5211b72f1e35801a2be6e67c30f --- /dev/null +++ b/API_REQUEST_CHAINS.md @@ -0,0 +1,634 @@ +# Renren Security 接口请求链路图 + +## 完整接口请求链路 + +```mermaid +flowchart TD + %% ==================== 登录管理 ==================== + subgraph 登录管理 + L1[GET /captcha] --> L1A[LoginController.captcha] + L1A --> L1B[AssertUtils.isBlank 校验uuid] + L1B --> L1C[CaptchaService.create] + L1C --> L1D[生成验证码图片] + L1D --> L1E[写入HttpServletResponse] + + L2[POST /login] --> L2A[LoginController.login] + L2A --> L2B[ValidatorUtils.validateEntity] + L2B --> L2C[SysUserService.getByUsername] + L2C --> L2D{用户是否存在?} + L2D -->|否| L2E[记录登录失败日志] + L2E --> L2F[抛出ACCOUNT_PASSWORD_ERROR] + L2D -->|是| L2G[PasswordUtils.matches] + L2G --> L2H{密码是否正确?} + L2H -->|否| L2I[记录登录失败日志] + L2I --> L2J[抛出ACCOUNT_PASSWORD_ERROR] + L2H -->|是| L2K{账号是否停用?} + L2K -->|是| L2L[记录账号锁定日志] + L2L --> L2M[抛出ACCOUNT_DISABLE] + L2K -->|否| L2N[记录登录成功日志] + L2N --> L2O[SysUserTokenService.createToken] + L2O --> L2P[生成Token返回] + + L3[POST /logout] --> L3A[LoginController.logout] + L3A --> L3B[SecurityUser.getUser] + L3B --> L3C[SysUserTokenService.logout] + L3C --> L3D[删除Token] + L3D --> L3E[记录退出日志] + L3E --> L3F[返回成功] + end + + %% ==================== 用户管理 ==================== + subgraph 用户管理_sys_user + U1[GET /sys/user/page] --> U1A[Shiro权限校验:sys:user:page] + U1A --> U1B[SysUserService.page] + U1B --> U1C[BaseDao.selectList] + U1C --> U1D[数据权限过滤DataFilter] + U1D --> U1E[返回PageData] + + U2[GET /sys/user/{id}] --> U2A[Shiro权限校验:sys:user:info] + U2A --> U2B[SysUserService.get] + U2B --> U2C[BaseDao.selectById] + U2C --> U2D[SysRoleUserService.getRoleIdList] + U2D --> U2E[设置用户角色列表] + U2E --> U2F[返回SysUserDTO] + + U3[GET /sys/user/info] --> U3A[SecurityUser.getUser] + U3A --> U3B[ConvertUtils.sourceToTarget] + U3B --> U3C[返回SysUserDTO] + + U4[PUT /sys/user/password] --> U4A[@LogOperation记录操作日志] + U4A --> U4B[ValidatorUtils.validateEntity] + U4B --> U4C[SecurityUser.getUser] + U4C --> U4D[PasswordUtils.matches验证原密码] + U4D --> U4E{原密码是否正确?} + U4E -->|否| U4F[返回PASSWORD_ERROR] + U4E -->|是| U4G[SysUserService.updatePassword] + U4G --> U4H[BCrypt加密新密码] + U4H --> U4I[BaseDao.updateById] + U4I --> U4J[返回成功] + + U5[POST /sys/user] --> U5A[Shiro权限校验:sys:user:save] + U5A --> U5B[@LogOperation记录操作日志] + U5B --> U5C[ValidatorUtils.validateEntity] + U5C --> U5D[SysUserService.save] + U5D --> U5E[BCrypt加密密码] + U5E --> U5F[BaseDao.insert] + U5F --> U5G[返回成功] + + U6[PUT /sys/user] --> U6A[Shiro权限校验:sys:user:update] + U6A --> U6B[@LogOperation记录操作日志] + U6B --> U6C[ValidatorUtils.validateEntity] + U6C --> U6D[SysUserService.update] + U6D --> U6E[BaseDao.updateById] + U6E --> U6F[返回成功] + + U7[DELETE /sys/user] --> U7A[Shiro权限校验:sys:user:delete] + U7A --> U7B[@LogOperation记录操作日志] + U7B --> U7C[AssertUtils.isArrayEmpty校验ids] + U7C --> U7D[SysUserService.deleteBatchIds] + U7D --> U7E[BaseDao.deleteBatchIds] + U7E --> U7F[返回成功] + + U8[GET /sys/user/export] --> U8A[Shiro权限校验:sys:user:export] + U8A --> U8B[@LogOperation记录操作日志] + U8B --> U8C[SysUserService.list] + U8C --> U8D[数据权限过滤] + U8D --> U8E[ExcelUtils.exportExcelToTarget] + U8E --> U8F[写入HttpServletResponse] + end + + %% ==================== 角色管理 ==================== + subgraph 角色管理_sys_role + R1[GET /sys/role/page] --> R1A[Shiro权限校验:sys:role:page] + R1A --> R1B[SysRoleService.page] + R1B --> R1C[BaseDao.selectList] + R1C --> R1D[数据权限过滤] + R1D --> R1E[返回PageData] + + R2[GET /sys/role/list] --> R2A[Shiro权限校验:sys:role:list] + R2A --> R2B[SysRoleService.list] + R2B --> R2C[返回角色列表] + + R3[GET /sys/role/{id}] --> R3A[Shiro权限校验:sys:role:info] + R3A --> R3B[SysRoleService.get] + R3B --> R3C[BaseDao.selectById] + R3C --> R3D[SysRoleMenuService.getMenuIdList] + R3D --> R3E[获取角色菜单权限] + R3E --> R3F[SysRoleDataScopeService.getDeptIdList] + R3F --> R3G[获取角色数据权限] + R3G --> R3H[返回SysRoleDTO] + + R4[POST /sys/role] --> R4A[Shiro权限校验:sys:role:save] + R4A --> R4B[@LogOperation记录操作日志] + R4B --> R4C[ValidatorUtils.validateEntity] + R4C --> R4D[SysRoleService.save] + R4D --> R4E[保存角色基本信息] + R4E --> R4F[SysRoleMenuService.saveOrUpdate] + R4F --> R4G[保存角色菜单关联] + R4G --> R4H[SysRoleDataScopeService.saveOrUpdate] + R4H --> R4I[保存角色数据权限] + R4I --> R4J[返回成功] + + R5[PUT /sys/role] --> R5A[Shiro权限校验:sys:role:update] + R5A --> R5B[@LogOperation记录操作日志] + R5B --> R5C[ValidatorUtils.validateEntity] + R5C --> R5D[SysRoleService.update] + R5D --> R5E[更新角色基本信息] + R5E --> R5F[SysRoleMenuService.saveOrUpdate] + R5F --> R5G[更新角色菜单关联] + R5G --> R5H[SysRoleDataScopeService.saveOrUpdate] + R5H --> R5I[更新角色数据权限] + R5I --> R5J[返回成功] + + R6[DELETE /sys/role] --> R6A[Shiro权限校验:sys:role:delete] + R6A --> R6B[@LogOperation记录操作日志] + R6B --> R6C[AssertUtils.isArrayEmpty] + R6C --> R6D[SysRoleService.delete] + R6D --> R6E[删除角色] + R6E --> R6F[删除角色菜单关联] + R6F --> R6G[删除角色用户关联] + R6G --> R6H[删除角色数据权限] + R6H --> R6I[返回成功] + end + + %% ==================== 菜单管理 ==================== + subgraph 菜单管理_sys_menu + M1[GET /sys/menu/nav] --> M1A[SecurityUser.getUser] + M1A --> M1B[SysMenuService.getUserMenuList] + M1B --> M1C[根据用户角色查询菜单] + M1C --> M1D[TreeUtils.build构建树形结构] + M1D --> M1E[返回菜单树] + + M2[GET /sys/menu/permissions] --> M2A[SecurityUser.getUser] + M2A --> M2B[ShiroService.getUserPermissions] + M2B --> M2C[查询用户角色] + M2C --> M2D[查询角色菜单权限] + M2D --> M2E[提取权限标识集合] + M2E --> M2F[返回Set权限标识] + + M3[GET /sys/menu/list] --> M3A[Shiro权限校验:sys:menu:list] + M3A --> M3B[SysMenuService.getAllMenuList] + M3B --> M3C[BaseDao.selectList] + M3C --> M3D[TreeUtils.build] + M3D --> M3E[返回菜单树] + + M4[GET /sys/menu/{id}] --> M4A[Shiro权限校验:sys:menu:info] + M4A --> M4B[SysMenuService.get] + M4B --> M4C[BaseDao.selectById] + M4C --> M4D[返回SysMenuDTO] + + M5[POST /sys/menu] --> M5A[Shiro权限校验:sys:menu:save] + M5A --> M5B[@LogOperation记录操作日志] + M5B --> M5C[ValidatorUtils.validateEntity] + M5C --> M5D[SysMenuService.save] + M5D --> M5E[BaseDao.insert] + M5E --> M5F[返回成功] + + M6[PUT /sys/menu] --> M6A[Shiro权限校验:sys:menu:update] + M6A --> M6B[@LogOperation记录操作日志] + M6B --> M6C[ValidatorUtils.validateEntity] + M6C --> M6D[SysMenuService.update] + M6D --> M6E[BaseDao.updateById] + M6E --> M6F[返回成功] + + M7[DELETE /sys/menu/{id}] --> M7A[Shiro权限校验:sys:menu:delete] + M7A --> M7B[@LogOperation记录操作日志] + M7B --> M7C[AssertUtils.isNull] + M7C --> M7D[SysMenuService.getListPid] + M7D --> M7E{是否有子菜单?} + M7E -->|是| M7F[返回SUB_MENU_EXIST错误] + M7E -->|否| M7G[SysMenuService.delete] + M7G --> M7H[BaseDao.deleteById] + M7H --> M7I[返回成功] + + M8[GET /sys/menu/select] --> M8A[Shiro权限校验:sys:menu:select] + M8A --> M8B[SecurityUser.getUser] + M8B --> M8C[SysMenuService.getUserMenuList] + M8C --> M8D[查询用户所有菜单权限] + M8D --> M8E[返回菜单列表] + end + + %% ==================== 部门管理 ==================== + subgraph 部门管理_sys_dept + D1[GET /sys/dept/list] --> D1A[Shiro权限校验:sys:dept:list] + D1A --> D1B[SysDeptService.list] + D1B --> D1C[数据权限过滤] + D1C --> D1D[TreeUtils.build] + D1D --> D1E[返回部门树] + + D2[GET /sys/dept/{id}] --> D2A[Shiro权限校验:sys:dept:info] + D2A --> D2B[SysDeptService.get] + D2B --> D2C[BaseDao.selectById] + D2C --> D2D[返回SysDeptDTO] + + D3[POST /sys/dept] --> D3A[Shiro权限校验:sys:dept:save] + D3A --> D3B[@LogOperation记录操作日志] + D3B --> D3C[ValidatorUtils.validateEntity] + D3C --> D3D[SysDeptService.save] + D3D --> D3E[BaseDao.insert] + D3E --> D3F[返回成功] + + D4[PUT /sys/dept] --> D4A[Shiro权限校验:sys:dept:update] + D4A --> D4B[@LogOperation记录操作日志] + D4B --> D4C[ValidatorUtils.validateEntity] + D4C --> D4D[SysDeptService.update] + D4D --> D4E[BaseDao.updateById] + D4E --> D4F[返回成功] + + D5[DELETE /sys/dept/{id}] --> D5A[Shiro权限校验:sys:dept:delete] + D5A --> D5B[@LogOperation记录操作日志] + D5B --> D5C[AssertUtils.isNull] + D5C --> D5D[SysDeptService.delete] + D5D --> D5E[BaseDao.deleteById] + D5E --> D5F[返回成功] + end + + %% ==================== 字典类型管理 ==================== + subgraph 字典类型管理_sys_dict_type + DT1[GET /sys/dict/type/page] --> DT1A[Shiro权限校验:sys:dict:page] + DT1A --> DT1B[SysDictTypeService.page] + DT1B --> DT1C[BaseDao.selectList] + DT1C --> DT1D[返回PageData] + + DT2[GET /sys/dict/type/{id}] --> DT2A[Shiro权限校验:sys:dict:info] + DT2A --> DT2B[SysDictTypeService.get] + DT2B --> DT2C[BaseDao.selectById] + DT2C --> DT2D[返回SysDictTypeDTO] + + DT3[POST /sys/dict/type] --> DT3A[Shiro权限校验:sys:dict:save] + DT3A --> DT3B[@LogOperation记录操作日志] + DT3B --> DT3C[ValidatorUtils.validateEntity] + DT3C --> DT3D[SysDictTypeService.save] + DT3D --> DT3E[BaseDao.insert] + DT3E --> DT3F[返回成功] + + DT4[PUT /sys/dict/type] --> DT4A[Shiro权限校验:sys:dict:update] + DT4A --> DT4B[@LogOperation记录操作日志] + DT4B --> DT4C[ValidatorUtils.validateEntity] + DT4C --> DT4D[SysDictTypeService.update] + DT4D --> DT4E[BaseDao.updateById] + DT4E --> DT4F[返回成功] + + DT5[DELETE /sys/dict/type] --> DT5A[Shiro权限校验:sys:dict:delete] + DT5A --> DT5B[@LogOperation记录操作日志] + DT5B --> DT5C[AssertUtils.isArrayEmpty] + DT5C --> DT5D[SysDictTypeService.delete] + DT5D --> DT5E[BaseDao.deleteBatchIds] + DT5E --> DT5F[返回成功] + + DT6[GET /sys/dict/type/all] --> DT6A[SysDictTypeService.getAllList] + DT6A --> DT6B[BaseDao.selectList] + DT6B --> DT6C[返回所有字典类型及数据] + end + + %% ==================== 字典数据管理 ==================== + subgraph 字典数据管理_sys_dict_data + DD1[GET /sys/dict/data/page] --> DD1A[Shiro权限校验:sys:dict:page] + DD1A --> DD1B[SysDictDataService.page] + DD1B --> DD1C[BaseDao.selectList] + DD1C --> DD1D[返回PageData] + + DD2[GET /sys/dict/data/{id}] --> DD2A[Shiro权限校验:sys:dict:info] + DD2A --> DD2B[SysDictDataService.get] + DD2B --> DD2C[BaseDao.selectById] + DD2C --> DD2D[返回SysDictDataDTO] + + DD3[POST /sys/dict/data] --> DD3A[Shiro权限校验:sys:dict:save] + DD3A --> DD3B[@LogOperation记录操作日志] + DD3B --> DD3C[ValidatorUtils.validateEntity] + DD3C --> DD3D[SysDictDataService.save] + DD3D --> DD3E[BaseDao.insert] + DD3E --> DD3F[返回成功] + + DD4[PUT /sys/dict/data] --> DD4A[Shiro权限校验:sys:dict:update] + DD4A --> DD4B[@LogOperation记录操作日志] + DD4B --> DD4C[ValidatorUtils.validateEntity] + DD4C --> DD4D[SysDictDataService.update] + DD4D --> DD4E[BaseDao.updateById] + DD4E --> DD4F[返回成功] + + DD5[DELETE /sys/dict/data] --> DD5A[Shiro权限校验:sys:dict:delete] + DD5A --> DD5B[@LogOperation记录操作日志] + DD5B --> DD5C[AssertUtils.isArrayEmpty] + DD5C --> DD5D[SysDictDataService.delete] + DD5D --> DD5E[BaseDao.deleteBatchIds] + DD5E --> DD5F[返回成功] + end + + %% ==================== 参数管理 ==================== + subgraph 参数管理_sys_params + P1[GET /sys/params/page] --> P1A[Shiro权限校验:sys:params:page] + P1A --> P1B[SysParamsService.page] + P1B --> P1C[BaseDao.selectList] + P1C --> P1D[返回PageData] + + P2[GET /sys/params/{id}] --> P2A[Shiro权限校验:sys:params:info] + P2A --> P2B[SysParamsService.get] + P2B --> P2C[BaseDao.selectById] + P2C --> P2D[返回SysParamsDTO] + + P3[POST /sys/params] --> P3A[Shiro权限校验:sys:params:save] + P3A --> P3B[@LogOperation记录操作日志] + P3B --> P3C[ValidatorUtils.validateEntity] + P3C --> P3D[SysParamsService.save] + P3D --> P3E[BaseDao.insert] + P3E --> P3F[SysParamsRedis.set更新Redis缓存] + P3F --> P3G[返回成功] + + P4[PUT /sys/params] --> P4A[Shiro权限校验:sys:params:update] + P4A --> P4B[@LogOperation记录操作日志] + P4B --> P4C[ValidatorUtils.validateEntity] + P4C --> P4D[SysParamsService.update] + P4D --> P4E[BaseDao.updateById] + P4E --> P4F[SysParamsRedis.set更新Redis缓存] + P4F --> P4G[返回成功] + + P5[DELETE /sys/params] --> P5A[Shiro权限校验:sys:params:delete] + P5A --> P5B[@LogOperation记录操作日志] + P5B --> P5C[AssertUtils.isArrayEmpty] + P5C --> P5D[SysParamsService.delete] + P5D --> P5E[BaseDao.deleteBatchIds] + P5E --> P5F[SysParamsRedis.delete删除Redis缓存] + P5F --> P5G[返回成功] + + P6[GET /sys/params/export] --> P6A[Shiro权限校验:sys:params:export] + P6A --> P6B[@LogOperation记录操作日志] + P6B --> P6C[SysParamsService.list] + P6C --> P6D[ExcelUtils.exportExcelToTarget] + P6D --> P6E[写入HttpServletResponse] + end + + %% ==================== 登录日志 ==================== + subgraph 登录日志_sys_log_login + LL1[GET /sys/log/login/page] --> LL1A[Shiro权限校验:sys:log:login] + LL1A --> LL1B[SysLogLoginService.page] + LL1B --> LL1C[BaseDao.selectList] + LL1C --> LL1D[返回PageData] + + LL2[GET /sys/log/login/export] --> LL2A[Shiro权限校验:sys:log:login] + LL2A --> LL2B[@LogOperation记录操作日志] + LL2B --> LL2C[SysLogLoginService.list] + LL2C --> LL2D[ExcelUtils.exportExcelToTarget] + LL2D --> LL2E[写入HttpServletResponse] + end + + %% ==================== 操作日志 ==================== + subgraph 操作日志_sys_log_operation + LO1[GET /sys/log/operation/page] --> LO1A[Shiro权限校验:sys:log:operation] + LO1A --> LO1B[SysLogOperationService.page] + LO1B --> LO1C[BaseDao.selectList] + LO1C --> LO1D[返回PageData] + + LO2[GET /sys/log/operation/export] --> LO2A[Shiro权限校验:sys:log:operation] + LO2A --> LO2B[@LogOperation记录操作日志] + LO2B --> LO2C[SysLogOperationService.list] + LO2C --> LO2D[ExcelUtils.exportExcelToTarget] + LO2D --> LO2E[写入HttpServletResponse] + end + + %% ==================== 异常日志 ==================== + subgraph 异常日志_sys_log_error + LE1[GET /sys/log/error/page] --> LE1A[Shiro权限校验:sys:log:error] + LE1A --> LE1B[SysLogErrorService.page] + LE1B --> LE1C[BaseDao.selectList] + LE1C --> LE1D[返回PageData] + + LE2[GET /sys/log/error/export] --> LE2A[Shiro权限校验:sys:log:error] + LE2A --> LE2B[@LogOperation记录操作日志] + LE2B --> LE2C[SysLogErrorService.list] + LE2C --> LE2D[ExcelUtils.exportExcelToTarget] + LE2D --> LE2E[写入HttpServletResponse] + end + + %% ==================== 定时任务 ==================== + subgraph 定时任务_sys_schedule + S1[GET /sys/schedule/page] --> S1A[Shiro权限校验:sys:schedule:page] + S1A --> S1B[ScheduleJobService.page] + S1B --> S1C[BaseDao.selectList] + S1C --> S1D[返回PageData] + + S2[GET /sys/schedule/{id}] --> S2A[Shiro权限校验:sys:schedule:info] + S2A --> S2B[ScheduleJobService.get] + S2B --> S2C[BaseDao.selectById] + S2C --> S2D[返回ScheduleJobDTO] + + S3[POST /sys/schedule] --> S3A[Shiro权限校验:sys:schedule:save] + S3A --> S3B[@LogOperation记录操作日志] + S3B --> S3C[ValidatorUtils.validateEntity] + S3C --> S3D[ScheduleJobService.save] + S3D --> S3E[BaseDao.insert] + S3E --> S3F[ScheduleUtils.createScheduleJob] + S3F --> S3G[创建Quartz定时任务] + S3G --> S3H[返回成功] + + S4[PUT /sys/schedule] --> S4A[Shiro权限校验:sys:schedule:update] + S4A --> S4B[@LogOperation记录操作日志] + S4B --> S4C[ValidatorUtils.validateEntity] + S4C --> S4D[ScheduleJobService.update] + S4D --> S4E[BaseDao.updateById] + S4E --> S4F[ScheduleUtils.updateScheduleJob] + S4F --> S4G[更新Quartz定时任务] + S4G --> S4H[返回成功] + + S5[DELETE /sys/schedule] --> S5A[Shiro权限校验:sys:schedule:delete] + S5A --> S5B[@LogOperation记录操作日志] + S5B --> S5C[ScheduleJobService.deleteBatch] + S5C --> S5D[BaseDao.deleteBatchIds] + S5D --> S5E[ScheduleUtils.deleteScheduleJob] + S5E --> S5F[删除Quartz定时任务] + S5F --> S5G[返回成功] + + S6[PUT /sys/schedule/run] --> S6A[Shiro权限校验:sys:schedule:run] + S6A --> S6B[@LogOperation记录操作日志] + S6B --> S6C[ScheduleJobService.run] + S6C --> S6D[ScheduleUtils.run] + S6D --> S6E[立即执行一次任务] + S6E --> S6F[记录执行日志] + S6F --> S6G[返回成功] + + S7[PUT /sys/schedule/pause] --> S7A[Shiro权限校验:sys:schedule:pause] + S7A --> S7B[@LogOperation记录操作日志] + S7B --> S7C[ScheduleJobService.pause] + S7C --> S7D[ScheduleUtils.pause] + S7D --> S7E[暂停Quartz任务] + S7E --> S7F[返回成功] + + S8[PUT /sys/schedule/resume] --> S8A[Shiro权限校验:sys:schedule:resume] + S8A --> S8B[@LogOperation记录操作日志] + S8B --> S8C[ScheduleJobService.resume] + S8C --> S8D[ScheduleUtils.resume] + S8D --> S8E[恢复Quartz任务] + S8E --> S8F[返回成功] + end + + %% ==================== 定时任务日志 ==================== + subgraph 定时任务日志_sys_scheduleLog + SL1[GET /sys/scheduleLog/page] --> SL1A[Shiro权限校验:sys:schedule:log] + SL1A --> SL1B[ScheduleJobLogService.page] + SL1B --> SL1C[BaseDao.selectList] + SL1C --> SL1D[返回PageData] + + SL2[GET /sys/scheduleLog/{id}] --> SL2A[Shiro权限校验:sys:schedule:log] + SL2A --> SL2B[ScheduleJobLogService.get] + SL2B --> SL2C[BaseDao.selectById] + SL2C --> SL2D[返回ScheduleJobLogDTO] + end + + %% ==================== 文件上传 ==================== + subgraph 文件上传_sys_oss + O1[GET /sys/oss/page] --> O1A[Shiro权限校验:sys:oss:all] + O1A --> O1B[SysOssService.page] + O1B --> O1C[BaseDao.selectList] + O1C --> O1D[返回PageData] + + O2[GET /sys/oss/info] --> O2A[Shiro权限校验:sys:oss:all] + O2A --> O2B[SysParamsService.getValueObject] + O2B --> O2C[SysParamsRedis.get从Redis获取] + O2C --> O2D[返回CloudStorageConfig] + + O3[POST /sys/oss] --> O3A[Shiro权限校验:sys:oss:all] + O3A --> O3B[@LogOperation记录操作日志] + O3B --> O3C[ValidatorUtils.validateEntity] + O3C --> O3D{判断云存储类型} + O3D -->|七牛| O3E[校验QiniuGroup] + O3D -->|阿里| O3F[校验AliyunGroup] + O3D -->|腾讯| O3G[校验QcloudGroup] + O3E --> O3H[SysParamsService.updateValueByCode] + O3F --> O3H + O3G --> O3H + O3H --> O3I[更新Redis缓存] + O3I --> O3J[返回成功] + + O4[POST /sys/oss/upload] --> O4A[Shiro权限校验:sys:oss:all] + O4A --> O4B{文件是否为空?} + O4B -->|是| O4C[返回UPLOAD_FILE_EMPTY] + O4B -->|否| O4D[获取文件后缀] + O4D --> O4E[OSSFactory.build] + O4E --> O4F[获取云存储配置] + O4F --> O4G{选择存储服务} + O4G -->|七牛| O4H[QiniuCloudStorageService.upload] + O4G -->|阿里| O4I[AliyunCloudStorageService.upload] + O4G -->|腾讯| O4J[QcloudCloudStorageService.upload] + O4H --> O4K[返回文件URL] + O4I --> O4K + O4J --> O4K + O4K --> O4L[SysOssEntity保存记录] + O4L --> O4M[BaseDao.insert] + O4M --> O4N[返回文件URL] + + O5[DELETE /sys/oss] --> O5A[Shiro权限校验:sys:oss:all] + O5A --> O5B[@LogOperation记录操作日志] + O5B --> O5C[SysOssService.deleteBatchIds] + O5C --> O5D[BaseDao.deleteBatchIds] + O5D --> O5E[返回成功] + end + + %% ==================== API服务接口 ==================== + subgraph API服务_api + A1[POST /api/register] --> A1A[ApiRegisterController.register] + A1A --> A1B[ValidatorUtils.validateEntity] + A1B --> A1C[创建UserEntity] + A1C --> A1D[DigestUtil.sha256Hex加密密码] + A1D --> A1E[UserService.insert] + A1E --> A1F[BaseDao.insert] + A1F --> A1G[返回成功] + + A2[POST /api/login] --> A2A[ApiLoginController.login] + A2A --> A2B[ValidatorUtils.validateEntity] + A2B --> A2C[UserService.login] + A2C --> A2D[查询用户信息] + A2D --> A2E{用户是否存在?} + A2E -->|否| A2F[抛出异常] + A2E -->|是| A2G[验证密码] + A2G --> A2H{密码是否正确?} + A2H -->|否| A2I[抛出异常] + A2H -->|是| A2J[TokenService.createToken] + A2J --> A2K[生成Token] + A2K --> A2L[返回Token和用户信息] + + A3[POST /api/logout] --> A3A[@Login注解校验Token] + A3A --> A3B[AuthorizationInterceptor拦截] + A3B --> A3C[获取userId] + A3C --> A3D[TokenService.expireToken] + A3D --> A3E[使Token失效] + A3E --> A3F[返回成功] + + A4[GET /api/test] --> A4A[@Login注解校验Token] + A4A --> A4B[返回用户信息] + end + + %% ==================== 代码生成器 ==================== + subgraph 代码生成器_sys_generator + G1[GET /sys/generator/list] --> G1A[SysGeneratorController.list] + G1A --> G1B[SysGeneratorService.queryList] + G1B --> G1C[查询数据库表信息] + G1C --> G1D[返回PageUtils] + + G2[GET /sys/generator/code] --> G2A[SysGeneratorController.code] + G2A --> G2B[SysGeneratorService.generatorCode] + G2B --> G2C[遍历表名] + G2C --> G2D[查询表结构信息] + G2D --> G2E[GenUtils.initColumnEntity] + G2E --> G2F[Velocity模板渲染] + G2F --> G2G[生成Entity/Dao/Service/Controller/Vue/XML] + G2G --> G2H[打包为ZIP] + G2H --> G2I[写入HttpServletResponse] + end + + %% ==================== 全局拦截器链路 ==================== + subgraph 全局拦截器 + F1[HTTP请求] --> F2[XssFilter XSS过滤] + F2 --> F3[MdcTraceFilter 链路追踪] + F3 --> F4[ApiFilter API接口过滤] + F4 --> F5{是否API接口?} + F5 -->|是| F6[AuthorizationInterceptor Token校验] + F5 -->|否| F7[Shiro Filter 权限校验] + F6 --> F8[@Login注解校验] + F7 --> F9[Oauth2Filter OAuth2认证] + F8 --> F10[Controller方法] + F9 --> F10 + F10 --> F11[@LogOperationAspect 操作日志] + F11 --> F12[@DataFilterAspect 数据权限] + F12 --> F13[Service层] + F13 --> F14[Dao层] + F14 --> F15[MyBatis执行SQL] + F15 --> F16[返回结果] + end +``` + +## 接口汇总清单 + +### renren-admin 管理后台 + +| 模块 | 路径前缀 | 接口数量 | 说明 | +|------|---------|---------|------| +| 登录管理 | `/` | 3 | 验证码、登录、退出 | +| 用户管理 | `/sys/user` | 8 | 用户CRUD、分页、导出、修改密码 | +| 角色管理 | `/sys/role` | 6 | 角色CRUD、分页、列表 | +| 菜单管理 | `/sys/menu` | 8 | 菜单CRUD、导航、权限标识 | +| 部门管理 | `/sys/dept` | 5 | 部门CRUD、列表 | +| 字典类型 | `/sys/dict/type` | 6 | 字典类型CRUD、分页、全部列表 | +| 字典数据 | `/sys/dict/data` | 5 | 字典数据CRUD、分页 | +| 参数管理 | `/sys/params` | 6 | 参数CRUD、分页、导出 | +| 登录日志 | `/sys/log/login` | 2 | 分页、导出 | +| 操作日志 | `/sys/log/operation` | 2 | 分页、导出 | +| 异常日志 | `/sys/log/error` | 2 | 分页、导出 | +| 定时任务 | `/sys/schedule` | 8 | 任务CRUD、执行、暂停、恢复 | +| 任务日志 | `/sys/scheduleLog` | 2 | 分页、详情 | +| 文件上传 | `/sys/oss` | 5 | 文件列表、上传、配置、删除 | + +### renren-api API服务 + +| 模块 | 路径前缀 | 接口数量 | 说明 | +|------|---------|---------|------| +| 用户注册 | `/api` | 1 | 注册 | +| 用户登录 | `/api` | 2 | 登录、退出 | +| 测试接口 | `/api` | 1 | 需要登录的测试接口 | + +### renren-generator 代码生成器 + +| 模块 | 路径前缀 | 接口数量 | 说明 | +|------|---------|---------|------| +| 代码生成 | `/sys/generator` | 2 | 表列表、生成代码 | + +**总计**: 68 个接口 diff --git a/PROJECT_ANALYSIS.md b/PROJECT_ANALYSIS.md new file mode 100644 index 0000000000000000000000000000000000000000..bf46d3c65570e5c3c6e3bd924b5cab4dd4342718 --- /dev/null +++ b/PROJECT_ANALYSIS.md @@ -0,0 +1,300 @@ +# Renren Security 项目分析文档 + +## 项目概述 + +**Renren Security** 是一个轻量级、前后端分离的 Java 快速开发平台(版本 5.5.0),专注于权限管理系统。该平台采用现代化技术栈,能够快速开发项目并交付,特别适合企业级后台管理系统的开发。 + +- **项目 URL**: https://www.renren.io +- **演示地址**: http://demo.open.renren.io/renren-security (账号密码: admin/admin) +- **Java 版本**: 17 +- **Spring Boot**: 4.0.5 + +--- + +## 技术栈 + +### 后端技术 + +| 技术 | 版本/说明 | +|------|----------| +| **Spring Boot** | 4.0.5 - 核心框架 | +| **Apache Shiro** | 权限控制框架 | +| **MyBatis-Plus** | 3.5.16 - ORM 框架 | +| **Druid** | 1.2.28 - 数据库连接池 | +| **Redis** | 缓存支持 | +| **Quartz** | 定时任务调度 | +| **Knife4j** | 4.5.0 - API 文档 (Swagger 增强) | +| **Hibernate Validator** | 后端数据校验 | +| **Hutool** | 5.8.43 - Java 工具库 | +| **Lombok** | 简化 Java 代码 | + +### 前端技术 + +| 技术 | 版本/说明 | +|------|----------| +| **Vue** | 3.5.18 - 前端框架 | +| **TypeScript** | 类型安全 | +| **Element Plus** | 2.10.5 - UI 组件库 | +| **Vue Router** | 4.2.5 - 路由管理 | +| **Pinia** | 2.3.1 - 状态管理 | +| **Axios** | 1.11.0 - HTTP 客户端 | +| **Vite** | 构建工具 | +| **ECharts** | 数据可视化 | +| **WangEditor** | 5.1.1 - 富文本编辑器 | + +### 数据库支持 + +- MySQL +- Oracle +- SQL Server +- PostgreSQL +- 达梦 (DM8) - 国产数据库 + +--- + +## 项目架构 + +``` +renren-security +├── renren-common # 公共模块 +├── renren-dynamic-datasource # 动态数据源模块 +├── renren-admin # 管理后台 (核心模块) +├── renren-api # API 服务模块 +├── renren-generator # 代码生成器 +└── renren-ui # 前端项目 +``` + +### 模块详解 + +#### 1. renren-common (公共模块) + +提供项目通用的基础组件和工具类: + +- **BaseDao/BaseService**: 基础 DAO 和 Service 接口 +- **CrudService**: 增删改查封装服务 +- **RedisConfig/RedisUtils**: Redis 配置和工具类 +- **XssFilter/XssUtils**: XSS 攻击防护 +- **PageData**: 分页数据封装 +- **Result**: 统一返回结果 +- **TreeUtils**: 树形结构处理 +- **ValidatorUtils**: 数据校验工具 +- **异常处理**: RenException, ErrorCode + +#### 2. renren-admin (管理后台) + +核心业务模块,包含完整的后台管理系统功能: + +**系统管理模块 (sys)** +- 用户管理 (SysUserController/Service/Dao) +- 角色管理 (SysRoleController/Service/Dao) +- 菜单管理 (SysMenuController/Service/Dao) +- 部门管理 (SysDeptController/Service/Dao) +- 字典管理 (SysDictType/Data Controller/Service/Dao) +- 参数管理 (SysParamsController/Service/Dao) + +**安全模块 (security)** +- Shiro 配置 (ShiroConfig) +- OAuth2 认证 (Oauth2Filter, Oauth2Realm, Oauth2Token) +- BCrypt 密码加密 +- Token 管理 (SysUserTokenService) +- API 过滤器 (ApiFilter, MdcTraceFilter) +- 登录控制器 (LoginController) + +**日志模块 (log)** +- 登录日志 (SysLogLoginController/Service/Dao) +- 操作日志 (SysLogOperationController/Service/Dao) +- 错误日志 (SysLogErrorController/Service/Dao) + +**定时任务模块 (job)** +- 任务管理 (ScheduleJobController/Service/Dao) +- 任务日志 (ScheduleJobLogController/Service/Dao) +- Quartz 集成 + +**云存储模块 (oss)** +- 阿里云存储 (AliyunCloudStorageService) +- 七牛云存储 (QiniuCloudStorageService) +- 腾讯云存储 (QcloudCloudStorageService) +- OSS 工厂模式 (OSSFactory) + +**通用功能** +- 数据权限注解 (@DataFilter) +- 操作日志注解 (@LogOperation) +- Excel 导入导出 (ExcelUtils) + +#### 3. renren-api (API 服务) + +独立的 API 服务模块,用于移动端或第三方接口: + +- 用户注册 (ApiRegisterController) +- 用户登录 (ApiLoginController) +- Token 认证 (AuthorizationInterceptor) +- 自定义参数解析器 (LoginUserHandlerMethodArgumentResolver) + +#### 4. renren-generator (代码生成器) + +可视化代码生成工具,支持: + +- 多数据库适配 (MySQL, Oracle, SQLServer, PostgreSQL, 达梦) +- 自动生成 Entity, DAO, Service, Controller +- 自动生成 Vue 前端页面 +- 自动生成 MyBatis XML 映射文件 +- 基于 Velocity 模板引擎 + +#### 5. renren-dynamic-datasource (动态数据源) + +支持运行时动态切换数据源: + +- @DataSource 注解 +- 数据源切面 (DataSourceAspect) +- 动态数据源工厂 (DynamicDataSourceFactory) + +#### 6. renren-ui (前端项目) + +基于 Vue 3 + TypeScript 的管理后台前端: + +**核心结构** +- `src/router/` - 路由配置 +- `src/store/` - Pinia 状态管理 +- `src/service/` - API 服务层 +- `src/layout/` - 布局组件 (Header, Sidebar, View) +- `src/utils/` - 工具函数 +- `src/components/` - 自定义组件 + +**自定义组件** +- `ren-dept-tree` - 部门树选择器 +- `ren-radio-group` - 单选框组 +- `ren-region-tree` - 区域树选择器 +- `ren-select` - 下拉选择器 +- `wang-editor` - 富文本编辑器 + +**页面模块** +- 系统管理 (用户、角色、菜单、部门、字典、参数) +- 定时任务管理 +- 云存储管理 +- 日志管理 (登录、操作、错误) + +--- + +## 核心设计思想 + +### 数据权限设计 + +| 功能模块 | 权限规则 | +|---------|---------| +| 用户管理、角色管理、部门管理 | 可操作本部门及子部门数据 | +| 菜单管理、定时任务、参数管理、字典管理、系统日志 | 无数据权限限制 | +| 业务功能 | 按用户数据权限查询,无本部门权限也能查看本人数据 | + +### 安全机制 + +1. **XSS 防护**: 通过 XssFilter 拦截并过滤请求参数 +2. **SQL 注入防护**: SqlFilter 过滤 SQL 关键字 +3. **Token 认证**: 基于 OAuth2 的 Token 机制 +4. **密码加密**: BCrypt 强哈希加密 +5. **数据权限**: 基于注解的细粒度数据权限控制 +6. **操作审计**: 完整的登录、操作、错误日志记录 + +### 代码生成机制 + +通过 renren-generator 模块,只需编写约 30% 的代码,其余由系统自动生成: +- 后端: Entity, DAO, Service, Controller, XML +- 前端: Vue 页面组件 +- 数据库: SQL 脚本 + +--- + +## 部署架构 + +### Docker 部署 + +项目提供 Docker 支持: +- `renren-admin/Dockerfile` - 管理后台镜像 +- `renren-api/Dockerfile` - API 服务镜像 +- `renren-ui/Dockerfile` - 前端镜像 +- `docker-compose.yml` - 一键编排部署 + +### 配置文件 + +支持多环境配置: +- `application.yml` - 基础配置 +- `application-dev.yml` - 开发环境 +- `application-test.yml` - 测试环境 +- `application-prod.yml` - 生产环境 + +--- + +## 数据库设计 + +### 核心表结构 + +| 表类别 | 主要表 | +|-------|-------| +| 系统用户 | sys_user | +| 系统角色 | sys_role | +| 系统菜单 | sys_menu | +| 系统部门 | sys_dept | +| 角色用户关联 | sys_role_user | +| 角色菜单关联 | sys_role_menu | +| 角色数据权限 | sys_role_data_scope | +| 字典类型 | sys_dict_type | +| 字典数据 | sys_dict_data | +| 系统参数 | sys_params | +| 用户 Token | sys_user_token | +| 登录日志 | sys_log_login | +| 操作日志 | sys_log_operation | +| 错误日志 | sys_log_error | +| 定时任务 | schedule_job | +| 任务日志 | schedule_job_log | +| 文件存储 | sys_oss | + +--- + +## 开发指南 + +### 快速开始 + +1. **克隆项目** +2. **导入数据库**: 执行 `renren-admin/db/` 下对应数据库的 SQL 脚本 +3. **修改配置**: 编辑 `application-dev.yml` 中的数据库连接信息 +4. **启动后端**: 运行 `AdminApplication.java` +5. **启动前端**: + ```bash + cd renren-ui + npm install + npm run dev + ``` + +### 新增业务模块 + +1. 使用代码生成器生成基础代码 +2. 在对应模块下编写业务逻辑 +3. 前端页面根据需要调整 + +--- + +## 项目统计 + +| 指标 | 数量 | +|------|------| +| 模块数 | 6 | +| 后端模块 | 5 (Java) | +| 前端模块 | 1 (Vue/TypeScript) | +| 支持数据库 | 5 种 | +| 核心 Controller | 20+ | +| 核心 Service | 30+ | +| 核心 DAO | 30+ | + +--- + +## 总结 + +Renren Security 是一个成熟的企业级权限管理系统,具有以下优势: + +1. **技术栈现代化**: Spring Boot 4 + Vue 3 + TypeScript +2. **安全性强**: 多层安全防护,完善的权限控制 +3. **开发效率高**: 代码生成器减少 70% 开发工作量 +4. **扩展性好**: 模块化设计,易于二次开发 +5. **数据库兼容**: 支持主流数据库及国产达梦数据库 +6. **部署便捷**: Docker 容器化部署支持 + +适合用于企业后台管理系统、权限管理平台、多租户 SaaS 系统等场景。 diff --git a/README.md b/README.md index 9d760640c79fde8fadef6c9d26a28ef734bff8c2..cd0ce22c1e128e0834d027d553c86d45de9d64a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -### 项目说明 +q### 项目说明 - renren-security是一个轻量级的,前后端分离的Java快速开发平台,能快速开发项目并交付【接私活利器】 - 采用SpringBoot4.x、Shiro、MyBatis-Plus、Vue3、TypeScript、Element Plus、Vue Router、Pinia、Axios、Vite框架,开发的一套权限系统,极低门槛,拿来即用。设计之初,就非常注重安全性,为企业系统保驾护航,让一切都变得如此简单。 - 提供了代码生成器,只需编写30%左右代码,其余的代码交给系统自动生成,可快速完成开发任务 @@ -93,9 +93,15 @@ renren-security - PostgreSQL 12+ - 达梦8
+**本地部署方式一:docker部署** +```shell +# 注意构建镜像需要梯子 +cd {projectHome}/renren-security +docker-compose up --build +``` -**本地部署** +**本地部署方式二:手动部署** - 通过git下载源码 - 创建数据库renren_security,数据库编码为UTF-8 - 执行db/mysql.sql文件,初始化数据 diff --git a/docker-compose.yml b/docker-compose.yml index f01e567c31dbc70a9c495299582be45aa0425c81..03de643e4cf423927a8e032bdd8844dcb425906c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,118 @@ -version: '2' +version: '3.8' + services: + # MySQL 数据库 + mysql: + image: mysql:8.0 + container_name: renren-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: renren_security + MYSQL_USER: renren + MYSQL_PASSWORD: 123456 + TZ: Asia/Shanghai + LANG: zh_CN.UTF-8 + LC_ALL: zh_CN.UTF-8 + ports: + - "13306:3306" + volumes: + - ./renren-admin/db/mysql.sql:/docker-entrypoint-initdb.d/02-init.sql + - mysql-data:/var/lib/mysql + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-time-zone=+08:00 + - --init-connect=SET NAMES utf8mb4 + - --skip-character-set-client-handshake + networks: + - renren-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + + # Redis 缓存 + redis: + image: redis:7-alpine + container_name: renren-redis + restart: always + ports: + - "6379:6379" + volumes: + - redis-data:/data + command: redis-server --appendonly yes + networks: + - renren-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # renren-admin 应用 renren-admin: - image: renren/renren-admin + build: + context: . + dockerfile: renren-admin/Dockerfile + image: renren-admin:latest + container_name: renren-admin + restart: always ports: - - "8080:8080" + - "8080:8080" environment: - - spring.profiles.active=dev - renren-api: - image: renren/renren-api + - SPRING_PROFILES_ACTIVE=dev + - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://mysql:3306/renren_security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true + - SPRING_DATASOURCE_DRUID_USERNAME=root + - SPRING_DATASOURCE_DRUID_PASSWORD=root + - SPRING_DATA_REDIS_HOST=redis + - SPRING_DATA_REDIS_PORT=6379 + - TZ=Asia/Shanghai + - LANG=zh_CN.UTF-8 + - LC_ALL=zh_CN.UTF-8 + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + networks: + - renren-network + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/renren-admin/swagger-ui.html || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + # renren-ui 前端应用 + + renren-ui: + build: + context: ./renren-ui + dockerfile: Dockerfile + image: renren-ui:latest + container_name: renren-ui + restart: always ports: - - "8081:8081" + - "80:80" environment: - - spring.profiles.active=dev \ No newline at end of file + - TZ=Asia/Shanghai + depends_on: + renren-admin: + condition: service_healthy + networks: + - renren-network + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:80/ || exit 1" ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +volumes: + mysql-data: + redis-data: + +networks: + renren-network: + driver: bridge \ No newline at end of file diff --git a/renren-admin/Dockerfile b/renren-admin/Dockerfile index 9db81f6464457d6b8fe4dbca9640d53160a09fe7..77b84b014ff4ad09c76ec62aa21d443d45010c64 100644 --- a/renren-admin/Dockerfile +++ b/renren-admin/Dockerfile @@ -1,7 +1,25 @@ -FROM java:8 -EXPOSE 8080 +FROM maven:3.9-eclipse-temurin-17 AS builder +WORKDIR /build + +# 复制自定义 Maven 配置 +COPY settings.xml /usr/share/maven/conf/settings.xml +## 复制依赖清单 +COPY pom.xml . +COPY renren-common/pom.xml renren-common/ +COPY renren-dynamic-datasource/pom.xml renren-dynamic-datasource/ +COPY renren-admin/pom.xml renren-admin/ +COPY renren-api/pom.xml renren-api/ +COPY renren-generator/pom.xml renren-generator/ +## 离线下载项目依赖 +RUN mvn dependency:go-offline -B +## 复制项目源码,编译项目生成jar +COPY . . +RUN mvn clean package -DskipTests -B + +FROM eclipse-temurin:17-jre +EXPOSE 8080 VOLUME /tmp -ADD renren-admin.jar /app.jar -RUN bash -c 'touch /app.jar' -ENTRYPOINT ["java","-jar","/app.jar"] +## 使用构建层生成jar构建景象 +COPY --from=builder /build/renren-admin/target/renren-admin.jar /app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/renren-admin/db/mysql.sql b/renren-admin/db/mysql.sql index 1f9c9e3217dddc0994c7542d882caa482fc77bf1..c26611a3decd3788bd4a5ab6aa00205da51bc628 100644 --- a/renren-admin/db/mysql.sql +++ b/renren-admin/db/mysql.sql @@ -1,3 +1,10 @@ +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +SET character_set_connection=utf8mb4; +SET character_set_database=utf8mb4; +SET character_set_results=utf8mb4; +SET character_set_server=utf8mb4; + -- 系统用户 CREATE TABLE sys_user ( id bigint NOT NULL COMMENT 'id', diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/ApiFilter.java b/renren-admin/src/main/java/io/renren/modules/security/config/ApiFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..dd289da70e0285f897a80f8e842d802cb1cd0f8a --- /dev/null +++ b/renren-admin/src/main/java/io/renren/modules/security/config/ApiFilter.java @@ -0,0 +1,144 @@ +package io.renren.modules.security.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingResponseWrapper; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * 请求日志过滤器 + * 以 curl 格式打印所有请求及参数 + */ +@Slf4j +@Component +@Order(2) +public class ApiFilter extends OncePerRequestFilter { + + @SuppressWarnings("NullableProblems") + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + + // 使用可重入的请求包装类,支持多次读取请求体 + ReentrantRequestWrapper wrappedRequest = new ReentrantRequestWrapper(request); + ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); + + long startTime = System.currentTimeMillis(); + + try { + // 打印 curl 格式的请求日志(在 doFilter 之前,此时 body 已经被缓存) + logCurlRequest(wrappedRequest); + + // 继续执行过滤链 + filterChain.doFilter(wrappedRequest, wrappedResponse); + + // 计算执行时间 + long duration = System.currentTimeMillis() - startTime; + + // 打印响应信息 + byte[] responseContent = wrappedResponse.getContentAsByteArray(); + + String method = wrappedRequest.getMethod(); + + if ( +// !HttpMethod.GET.name().equals(method) && + !wrappedRequest.getRequestURI().contains("captcha")) { + log.info("<== [{}s] {}",duration>1000?duration/1000:"<1",responseContent.length>0? + new String(responseContent, StandardCharsets.UTF_8):"{}"); + } + } finally { + // 必须调用 copyBodyToResponse,否则响应体不会发送给客户端 + wrappedResponse.copyBodyToResponse(); + } + } + + /** + * 以 curl 格式打印请求日志 + */ + private void logCurlRequest(ReentrantRequestWrapper request) { + List headers = new ArrayList<>(); + + // Java 8 兼容方式:使用 Enumeration 遍历请求头 + Enumeration headerNames = request.getHeaderNames(); + if (headerNames != null) { + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + String headerValue = request.getHeader(headerName); + if (headerValue != null && !headerValue.isEmpty()) { + headers.add(String.format(" --header '%s: %s'", headerName, headerValue)); + } + } + } + + // 构建 curl 命令 + StringBuilder curlCommand = new StringBuilder(); + curlCommand.append("curl --location --request ") + .append(request.getMethod()) + .append(" '") + .append(getFullUrl(request)) + .append("'"); + + // 添加 headers + if (!headers.isEmpty()) { + curlCommand.append(String.join(" ", headers)); + } + + // 添加请求体 + byte[] content = request.getContentAsByteArray(); + if (content.length > 0) { + String payload = new String(content, StandardCharsets.UTF_8); + curlCommand.append(" --data '").append(payload).append("'"); + } + + log.info("==> {}", curlCommand); + } + + /** + * 获取完整的 URL(包含查询参数) + */ + private String getFullUrl(HttpServletRequest request) { + StringBuilder url = new StringBuilder(); + url.append(request.getRequestURL()); + + String queryString = request.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + url.append("?").append(queryString); + } + + return url.toString(); + } + + /** + * 不过滤静态资源 + */ + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + return path != null && ( + path.startsWith("/static/") || + path.startsWith("/css/") || + path.startsWith("/js/") || + path.startsWith("/images/") || + path.endsWith(".ico") || + path.endsWith(".css") || + path.endsWith(".js") || + path.endsWith(".png") || + path.endsWith(".jpg") || + path.endsWith(".jpeg") || + path.endsWith(".gif") + ); + } +} \ No newline at end of file diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/Constant.java b/renren-admin/src/main/java/io/renren/modules/security/config/Constant.java new file mode 100644 index 0000000000000000000000000000000000000000..a37cd9adfddfcf50a0bdb63896b806728c0a094b --- /dev/null +++ b/renren-admin/src/main/java/io/renren/modules/security/config/Constant.java @@ -0,0 +1,7 @@ +package io.renren.modules.security.config; + +public class Constant { + public static final String TRACE = "trace"; + public static final String TOKEN = "token"; + +} diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/CustomThrowableProxyConverter.java b/renren-admin/src/main/java/io/renren/modules/security/config/CustomThrowableProxyConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..d686c75afc64668e022af31c797fe3ecc362cceb --- /dev/null +++ b/renren-admin/src/main/java/io/renren/modules/security/config/CustomThrowableProxyConverter.java @@ -0,0 +1,56 @@ +package io.renren.modules.security.config; + +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.core.CoreConstants; +import org.slf4j.MDC; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 自定义异常堆栈转换器,在每行异常信息前添加 MDC 信息 + */ +public class CustomThrowableProxyConverter extends ThrowableProxyConverter { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yy-MM-dd HH:mm:ss.SSS"); + + @Override + protected String throwableProxyToString(IThrowableProxy tp) { + // 从当前线程的 MDC 中获取 trace 和 token + String traceId = MDC.get("trace"); + String token = MDC.get("token"); + + if (traceId == null) { + traceId = "NO_TRACE"; + } + if (token == null) { + token = ""; + } + + StringBuilder sb = new StringBuilder(); + String originalOutput = super.throwableProxyToString(tp); + + if (originalOutput == null || originalOutput.isEmpty()) { + return originalOutput; + } + + // 将原始输出按行分割 + String[] lines = originalOutput.split(CoreConstants.LINE_SEPARATOR); + + for (int i = 0; i < lines.length; i++) { + if (i > 0) { + sb.append(CoreConstants.LINE_SEPARATOR); + } + // 在每一行前面添加 trace、token 和时间信息 + String line = lines[i]; + if (!line.trim().isEmpty()) { + String timestamp = LocalDateTime.now().format(FORMATTER); + sb.append(String.format("%s %s %s ", timestamp, traceId, token)); + } + sb.append(line); + } + sb.append(CoreConstants.LINE_SEPARATOR); + return sb.toString(); + } +} \ No newline at end of file diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/MdcTraceFilter.java b/renren-admin/src/main/java/io/renren/modules/security/config/MdcTraceFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..b9fe5c096c14f432f777c4b1d5c9a8cad7e2765c --- /dev/null +++ b/renren-admin/src/main/java/io/renren/modules/security/config/MdcTraceFilter.java @@ -0,0 +1,48 @@ +package io.renren.modules.security.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.UUID; + +/** + * MDC Trace 分配过滤器 + * 为每个请求生成唯一的 trace 并放入 MDC 上下文 + */ +@Slf4j +@Component +@Order(1) +public class MdcTraceFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String traceId = generateTraceId(); + + try { + MDC.put(Constant.TRACE, traceId); + filterChain.doFilter(request, response); + } finally { + MDC.clear(); + } + } + + /** + * 生成 Trace ID + * 使用 UUID 的前 8 位作为 trace ID + */ + private String generateTraceId() { + return UUID.randomUUID() + .toString() + .replace("-", ""); + } +} diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/ReentrantRequestWrapper.java b/renren-admin/src/main/java/io/renren/modules/security/config/ReentrantRequestWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..41949d26d3ddf73d6bf76f3634deea470ac2f829 --- /dev/null +++ b/renren-admin/src/main/java/io/renren/modules/security/config/ReentrantRequestWrapper.java @@ -0,0 +1,87 @@ +package io.renren.modules.security.config; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * 可重入的请求包装类 + * 支持多次读取请求体 + */ +public class ReentrantRequestWrapper extends HttpServletRequestWrapper { + + private final byte[] body; + + public ReentrantRequestWrapper(HttpServletRequest request) throws IOException { + super(request); + this.body = readAllBytes(request.getInputStream()); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new CachedBodyServletInputStream(new ByteArrayInputStream(body)); + } + + /** + * Java 8 兼容的方式读取所有字节 + */ + private byte[] readAllBytes(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + int bytesRead; + java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream(); + + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + return outputStream.toByteArray(); + } + + /** + * 获取缓存的请求体 + */ + public byte[] getContentAsByteArray() { + return body; + } + + /** + * 缓存的 Servlet 输入流 + */ + private static class CachedBodyServletInputStream extends ServletInputStream { + + private final InputStream cachedInputStream; + + public CachedBodyServletInputStream(InputStream cachedInputStream) { + this.cachedInputStream = cachedInputStream; + } + + @Override + public boolean isFinished() { + try { + return cachedInputStream.available() == 0; + } catch (IOException e) { + return true; + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + throw new UnsupportedOperationException("不支持异步读取"); + } + + @Override + public int read() throws IOException { + return cachedInputStream.read(); + } + } +} diff --git a/renren-admin/src/main/java/io/renren/modules/security/config/WebMvcConfig.java b/renren-admin/src/main/java/io/renren/modules/security/config/WebMvcConfig.java index 3d72df90a208b6241501923c6fdbabd40ffc833f..412bbb2af78ca0c6c0343d6cc53f97531719d2ba 100644 --- a/renren-admin/src/main/java/io/renren/modules/security/config/WebMvcConfig.java +++ b/renren-admin/src/main/java/io/renren/modules/security/config/WebMvcConfig.java @@ -9,6 +9,7 @@ package io.renren.modules.security.config; import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -77,4 +78,28 @@ public class WebMvcConfig implements WebMvcConfigurer { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS); } + + /** + * 注册 MDC Trace 过滤器 + */ + @Bean + public FilterRegistrationBean mdcTraceFilterRegistration(MdcTraceFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(filter); + registration.addUrlPatterns("/*"); + registration.setOrder(1); + return registration; + } + + /** + * 注册请求日志过滤器 + */ + @Bean + public FilterRegistrationBean requestLoggingFilterRegistration(ApiFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(filter); + registration.addUrlPatterns("/*"); + registration.setOrder(2); + return registration; + } } \ No newline at end of file diff --git a/renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java b/renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java index 017b430aa580c2582ebca85efae090f3b34ad131..9d4a9d72a6cf7e8e03eebcefc8159fd300fcc96f 100644 --- a/renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java +++ b/renren-admin/src/main/java/io/renren/modules/security/controller/LoginController.java @@ -73,12 +73,11 @@ public class LoginController { public Result login(HttpServletRequest request, @RequestBody LoginDTO login) { //效验数据 ValidatorUtils.validateEntity(login); - //验证码是否正确 - boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha()); - if (!flag) { - return new Result().error(ErrorCode.CAPTCHA_ERROR); - } +// boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha()); +// if (!flag) { +// return new Result().error(ErrorCode.CAPTCHA_ERROR); +// } //用户信息 SysUserDTO user = sysUserService.getByUsername(login.getUsername()); diff --git a/renren-admin/src/main/resources/application-dev.yml b/renren-admin/src/main/resources/application-dev.yml index af80fc7904c44aebdfa1a4b52ac77d9113848771..431fa42907f845dc48a5417e88a354af0790de88 100644 --- a/renren-admin/src/main/resources/application-dev.yml +++ b/renren-admin/src/main/resources/application-dev.yml @@ -4,8 +4,8 @@ spring: #MySQL driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/renren_security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true - username: renren - password: 123456 + username: root + password: root #达梦8 # driver-class-name: dm.jdbc.driver.DmDriver # url: jdbc:dm://192.168.10.10:5236/renren_security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true diff --git a/renren-admin/src/main/resources/banner.txt b/renren-admin/src/main/resources/banner.txt index d9219ddc5698bad7104c79227572065c55170e88..785fc521f2e4b30bb4e9ec0114a4973ef1e57f6b 100644 --- a/renren-admin/src/main/resources/banner.txt +++ b/renren-admin/src/main/resources/banner.txt @@ -1,5 +1,5 @@ -==================================================================================================================== +==================================================================================================================== - 欢迎使用 renren-security - Powered By https://www.renren.io + 欢迎使用 renren-security ==================================================================================================================== \ No newline at end of file diff --git a/renren-admin/src/main/resources/logback-spring.xml b/renren-admin/src/main/resources/logback-spring.xml deleted file mode 100644 index 5deef21056be12b3e15cd89f5e51fad2c3a75f91..0000000000000000000000000000000000000000 --- a/renren-admin/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/renren-admin/src/main/resources/logback.xml b/renren-admin/src/main/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..3f0dd2c06933939f65abb7822215f24c7dbc098a --- /dev/null +++ b/renren-admin/src/main/resources/logback.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + UTF-8 + %d{yy-MM-dd HH:mm:ss.SSS} %X{trace} %X{token} [%thread] %-5p %c.%method:%-4.4line - %m%n%customEx + + + + + ${LOG_HOME}/${APP}.log + + UTF-8 + %d{yy-MM-dd HH:mm:ss.SSS} %X{trace} %X{token} [%thread] %-5p %c.%method:%-4.4line - %m%n%customEx + + + ${LOG_HOME}/${APP}-%d{yyyyMMdd}.log + true + 30 + + + + + + + + + + + + + + diff --git a/renren-common/src/main/java/io/renren/common/entity/BaseEntity.java b/renren-common/src/main/java/io/renren/common/entity/BaseEntity.java index 18d1ec879efa2aef39b7dfcf72c0a78be3ca886c..33509fa19df83c5fa2d491af0b4a739cd688456d 100644 --- a/renren-common/src/main/java/io/renren/common/entity/BaseEntity.java +++ b/renren-common/src/main/java/io/renren/common/entity/BaseEntity.java @@ -25,6 +25,7 @@ import java.util.Date; public abstract class BaseEntity implements Serializable { /** * id + * */ @TableId private Long id; diff --git a/renren-ui/.env.production b/renren-ui/.env.production index 1f1e388d613b62d3b5d17e69793b9b771c61dfc9..fb67d3ea07f2f9a6071f83178bf25bffa5770fcf 100644 --- a/renren-ui/.env.production +++ b/renren-ui/.env.production @@ -1,3 +1,3 @@ NODE_ENV=production -VITE_APP_API=http://localhost:8080/renren-admin +VITE_APP_API=http://localhost:8082/renren-admin diff --git a/renren-ui/APPLE-DESIGN-SYSTEM.md b/renren-ui/APPLE-DESIGN-SYSTEM.md new file mode 100644 index 0000000000000000000000000000000000000000..162cb777a734ffc0af8a3b9d927f5d8b1522253f --- /dev/null +++ b/renren-ui/APPLE-DESIGN-SYSTEM.md @@ -0,0 +1,169 @@ +# Apple 设计系统 - 实施指南 + +## 概述 + +本项目已按照 Apple 设计风格进行全面改造。这是一个摄影优先的界面系统,让 UI 退居幕后,让产品成为主角。 + +## 核心设计理念 + +### 1. 颜色系统 + +**主色调 - Action Blue (#0066cc)** +- 所有交互元素(链接、按钮、焦点)统一使用单一蓝色 +- 浅色背景:#0066cc +- 深色背景:#2997ff +- 悬停状态:#0071e3 + +**表面颜色** +- 纯白画布:#ffffff +- 羊皮纸色:#f5f5f7(Apple 标志性的灰白色) +- 珍珠色:#fafafc(用于次要按钮) +- 深色砖块:#272729, #2a2a2c, #252527 +- 纯黑:#000000(仅用于全局导航栏) + +**文本颜色** +- 墨色:#1d1d1f(所有标题和正文) +- 深色背景上的白色:#ffffff +- 静音文本:#cccccc +- 禁用文本:#7a7a7a + +### 2. 字体系统 + +**字体族** +- Display: `SF Pro Display, system-ui, -apple-system, sans-serif`(≥19px) +- Text: `SF Pro Text, system-ui, -apple-system, sans-serif`(<20px) + +**字号层级** +- Hero Display: 56px / 600 / 1.07行高 / -0.28px字间距 +- Display LG: 40px / 600 / 1.1行高 +- Display MD: 34px / 600 / 1.47行高 / -0.374px字间距 +- Lead: 28px / 400 / 1.14行高 +- Tagline: 21px / 600 / 1.19行高 +- Body: 17px / 400 / 1.47行高 / -0.374px字间距 +- Caption: 14px / 400 / 1.43行高 / -0.224px字间距 +- Fine Print: 12px / 400 / 1.0行高 + +### 3. 间距系统 + +基于 8px 基础单位: +- XXS: 4px +- XS: 8px +- SM: 12px +- MD: 17px +- LG: 24px +- XL: 32px +- XXL: 48px +- Section: 80px + +### 4. 圆角系统 + +- None: 0px +- SM: 8px(紧凑实用按钮) +- MD: 12px(输入框、卡片) +- LG: 18px(实用卡片) +- Pill: 980px(药丸形 CTA 按钮) +- Full: 50%(圆形) + +## 已改造的页面 + +### 1. 登录页面 +- 简洁的居中表单布局 +- 羊皮纸色背景 +- 白色卡片带产品阴影 +- 大尺寸输入框和按钮 +- 药丸形主按钮 + +### 2. 首页 +- Hero 标题使用 Display 字体 +- 卡片网格布局 +- 明暗交替的区域 +- 悬停缩放效果(0.98) + +### 3. 全局组件 +- 按钮:药丸形圆角,悬停缩放 0.95 +- 表格:圆角边框,羊皮纸色表头 +- 表单:圆角输入框,蓝色焦点环 +- 对话框:圆角,产品阴影 +- 分页:圆角按钮 + +## 使用方式 + +### 在新页面中使用 Apple 样式 + +1. **引入样式文件** +```vue + +``` + +2. **使用预定义的 CSS 类** +```vue + +``` + +3. **使用颜色变量** +```less +.my-element { + background: @--apple-canvas; + color: @--apple-ink; + border: 1px solid @--apple-hairline; +} +``` + +### 业务页面通用样式 + +所有业务页面自动应用以下样式: +- 搜索表单区域:`.mod-common__search` +- 数据表格区域:`.mod-common__table` +- 工具栏:`.mod-common__toolbar` +- 操作按钮:`.mod-common__actions` +- 对话框:`.mod-common__dialog` +- 卡片:`.mod-common__card` +- 标签:`.mod-common__tag` + +## 设计原则 + +### Do's ✓ +- 使用 Action Blue (#0066cc) 作为唯一的强调色 +- 标题使用负字间距实现"Apple 紧凑"效果 +- 正文使用 17px(不是 16px) +- 药丸形圆角用于主要 CTA 按钮 +- 使用 transform: scale(0.95) 作为按钮按下状态 +- 明暗交替的区域作为分隔符 +- 产品阴影仅用于产品图片(rgba(0, 0, 0, 0.22) 3px 5px 30px) + +### Don'ts ✗ +- 不要引入第二种强调色 +- 不要给卡片、按钮、文字添加阴影 +- 不要使用渐变作为装饰背景 +- 正文不要使用 500 字重(Apple 使用 300/400/600/700) +- 不要圆角全出血的砖块区域 +- 正文行高不要低于 1.47 +- 不要在浅色背景上使用 #2997ff(仅用于深色背景) + +## 文件结构 + +``` +src/assets/theme/ +├── apple.less # Apple 设计系统变量定义 +├── apple-global.less # 全局组件样式覆盖 +└── apple-pages.less # 业务页面通用样式 + +src/views/ +├── login.vue # Apple 风格登录页面 +└── home.vue # Apple 风格首页 +``` + +## 下一步 + +所有新建页面应遵循此设计系统。现有业务页面已自动应用全局样式,可根据需要进一步优化。 + +## 参考文档 + +完整的设计规范文档:`/Users/yue/Downloads/DESIGN.md` diff --git a/renren-ui/Dockerfile b/renren-ui/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..23be968c6a345d531999b7b4365e685cb152dc0d --- /dev/null +++ b/renren-ui/Dockerfile @@ -0,0 +1,40 @@ + +# 阶段1: 构建阶段 +FROM m.daocloud.io/docker.io/node:18-alpine AS builder + +# 设置工作目录 +WORKDIR /app + +# 配置 npm 使用淘宝镜像 +RUN npm config set registry https://registry.npmmirror.com + +# 复制 package.json 和 package-lock.json +COPY package*.json ./ + +# 安装依赖(使用 npm install 替代 npm ci 以避免版本同步问题) +RUN npm install + +# 复制源代码 +COPY . . + +# 生产环境构建(启用 CDN) +RUN npm run build:prod + +# 阶段2: 生产阶段 +FROM m.daocloud.io/docker.io/nginx:alpine + +# 复制自定义 nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 可选:使用简化配置 +# COPY nginx-simple.conf /etc/nginx/conf.d/default.conf +# COPY nginx-minimal.conf /etc/nginx/conf.d/default.conf + +# 从构建阶段复制打包好的文件到 nginx 目录 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 暴露 80 端口 +EXPOSE 80 + +# 启动 nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/renren-ui/nginx.conf b/renren-ui/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..f0a5e19856ddd1c308cf714b2d63c4de21312929 --- /dev/null +++ b/renren-ui/nginx.conf @@ -0,0 +1,55 @@ +# 后端服务负载均衡 +upstream backend_servers { + server renren-admin:8080 max_fails=3 fail_timeout=30s; + + # 使用轮询算法进行负载均衡 +} + +server { + listen 80; + server_name localhost; + + # 静态文件根目录 + root /usr/share/nginx/html; + index index.html; + + # gzip 压缩 + gzip on; + gzip_types text/plain text/css application/json application/javascript/xml application/xml application/xml+rss text/javascript; + gzip_min_length 1000; + + # 静态资源缓存 + location /assets { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API 代理到后端服务(负载均衡) + location /api/ { + proxy_pass http://backend_servers/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_read_timeout 60s; + + # 失败重试配置 + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; + proxy_next_upstream_timeout 10s; + proxy_next_upstream_tries 3; + } + + # Vue Router 历史模式支持 + location / { + try_files $uri $uri/ /index.html; + } + + # 错误页面 + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/renren-ui/src/App.vue b/renren-ui/src/App.vue index 6eb867e993e0b83c4ffd648200ef37d6f8153157..1c41f598ce50c73645a7794562f34c4eee74e6a5 100644 --- a/renren-ui/src/App.vue +++ b/renren-ui/src/App.vue @@ -2,6 +2,8 @@ import "@/assets/css/app.less"; import "@/assets/theme/index.less"; import "@/assets/theme/mobile.less"; +import "@/assets/theme/apple-global.less"; +import "@/assets/theme/apple-pages.less"; import FullscreenLayout from "@/layout/fullscreen-layout.vue"; import Layout from "@/layout/index.vue"; import { ElConfigProvider } from "element-plus"; diff --git a/renren-ui/src/assets/css/app.less b/renren-ui/src/assets/css/app.less index dec6de5389a66e558aab8f859309d10ad18e581a..b9d8e8d31a2bde5cc7c2b9c3b66c4b935f628bd4 100644 --- a/renren-ui/src/assets/css/app.less +++ b/renren-ui/src/assets/css/app.less @@ -1,22 +1,22 @@ -@import "../theme/base.less"; +@import "../theme/apple.less"; *, :after, :before { box-sizing: border-box; } + html, body { margin: 0; padding: 0; - color: #595959; + color: #1d1d1f; font-size: 14px; - font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", - "微软雅黑", Arial, sans-serif; - color: #595959; - background: #f0f2f5; + font-family: "SF Pro Text", "SF Pro Display", system-ui, -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; + background: #ffffff; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; - //字体 .text { &-2 { color: #8c8c8c; @@ -27,11 +27,13 @@ body { } a { - color: @--color-primary; + color: #0066cc; text-decoration: none; + transition: opacity 0.2s ease; &:focus, &:hover { - color: @--color-primary; + color: #0066cc; + opacity: 0.7; } } } @@ -89,14 +91,15 @@ body { .el-alert__content { padding: 0; } + //menu .el-menu-item a, .el-menu-item span, .el-sub-menu > .el-sub-menu__title a, .el-sub-menu > .el-sub-menu__title span { - color: @dark-text; + color: #1d1d1f; text-decoration: none; - margin-left: 5px; + margin-left: 0; display: inline-flex; width: 100%; } @@ -111,7 +114,7 @@ body { border-width: 0 !important; } .el-sub-menu__icon-arrow { - font-weight: bold; + font-weight: 400; } //pop @@ -124,25 +127,66 @@ body { overflow: auto; } -//表格 -.el-table thead { - color: #303133 !important; - th { - background-color: #f5f7fa !important; +//表格 - Apple 风格 +.el-table { + font-size: 14px; + color: #1d1d1f; + + thead { + color: #1d1d1f !important; + th { + background-color: #f5f5f7 !important; + font-weight: 600; + font-size: 13px; + border-bottom: 1px solid #e0e0e0 !important; + } + } + + td { + border-bottom: 1px solid #f0f0f0 !important; + } + + &__row:hover > td { + background-color: #f5f5f7 !important; } } + .el-table__fixed-right::before { - background: transparent !important; //element-plus表格高度动态计算bug,强制下划线不显示颜色 + background: transparent !important; } -.el-form--inline .el-form-item{ +.el-form--inline .el-form-item { margin-right: 16px !important; } -//分页 +//分页 - Apple 风格 .el-pagination { - margin-top: 15px !important; + margin-top: 24px !important; justify-content: right; + + .el-pager li { + border-radius: 8px; + font-weight: 400; + color: #1d1d1f; + + &.is-active { + background: #0066cc; + color: #ffffff; + } + + &:hover { + color: #0066cc; + } + } + + button { + border-radius: 8px; + color: #1d1d1f; + + &:hover:not(:disabled) { + color: #0066cc; + } + } } //tinymce @@ -159,9 +203,29 @@ body { } } -//弹窗 +//弹窗 - Apple 风格 .rr-dialog { min-width: 600px; + + .el-dialog__header { + padding: 24px 24px 16px; + border-bottom: 1px solid #f0f0f0; + + .el-dialog__title { + font-size: 17px; + font-weight: 600; + color: #1d1d1f; + } + } + + .el-dialog__body { + padding: 24px; + } + + .el-dialog__footer { + padding: 16px 24px 24px; + border-top: 1px solid #f0f0f0; + } } .rr { @@ -180,10 +244,11 @@ body { width: 100vw; &.new-pop-window > div { - padding: 15px; - margin: 15px; - background: #fff; - border-radius: 4px; + padding: 24px; + margin: 24px; + background: #ffffff; + border-radius: 12px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); } } @@ -196,106 +261,52 @@ body { left: 0; width: 100vw; height: 100vh; - background: #fff; + background: #ffffff; z-index: 1200; } &-drawer { .el-drawer__header { - color: #595959; - font-size: 15px; + color: #1d1d1f; + font-size: 17px; + font-weight: 600; margin-bottom: 0; - padding: 13px 16px; - border-bottom: 1px solid #f4f4f4; + padding: 24px; + border-bottom: 1px solid #f0f0f0; } .el-drawer__body { - padding: 15px; + padding: 24px; overflow: auto; } } - //顶部 + //顶部 - 已移除 &-header { - background: #fff; - padding: 0 !important; - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 200; - &-ctx { - display: flex; - height: 50px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - &-logo { - display: flex; - color: #ffffffe6; - background-color: #191a23; - font-size: 19px; - font-weight: 500; - letter-spacing: 1.5px; - width: 230px; - height: 50px; - overflow: hidden; - white-space: nowrap; - justify-content: center; - font-family: Avenir, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, - Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, - Noto Color Emoji; - align-items: center; - position: relative; - transition: width 0.3s; - padding: 0 15px; - - &-img { - width: 32px; - height: 32px; - display: inline-block; - flex-shrink: 0; - &-wrap { - display: flex; - &.enabled-logo { - &-false { - display: none; - } - } - } - } - &-line { - display: inline-block; - width: 10px; - height: 1px; - } - &-text { - display: inline-block; - line-height: 1; - overflow: hidden; - text-transform: uppercase; - font-weight: 700; - font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, - "微软雅黑", Arial, sans-serif; - } - } - } + display: none; } + &-body { flex: 1; display: flex; overflow: hidden; + background: #ffffff; } - //左侧侧边栏 + + //左侧侧边栏 - 登录页风格(纯白背景) &-sidebar { - margin-top: 50px; - width: 230px !important; - min-height: calc(100vh - 50px); + width: 240px !important; + height: 100vh; overflow-x: hidden !important; transition: width 0.3s; z-index: 120; scrollbar-width: none; + background: #ffffff; + border-right: 1px solid #f0f0f0; &-menu { transition: width 0.3s; overflow: hidden; + background: transparent !important; &.el-menu--horizontal { border-bottom: none !important; } @@ -309,13 +320,36 @@ body { } .el-menu { - width: 230px !important; + width: 240px !important; border-right: 0 !important; + background: transparent !important; + padding: 24px 0; + &-item { - height: 45px; - line-height: 45px; - margin: 2px 0; + height: 46px; + line-height: 46px; + margin: 4px 20px; + border-radius: 12px; + padding: 0 24px !important; + font-size: 15px; + font-weight: 400; + letter-spacing: -0.2px; + color: #1d1d1f; + background: transparent !important; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + background: rgba(102, 126, 234, 0.06) !important; + color: #0066cc !important; + } + + &.is-active { + background: rgba(102, 126, 234, 0.1) !important; + color: #0066cc !important; + font-weight: 500; + } } + &-item, .el-sub-menu__title { background: transparent !important; @@ -323,245 +357,280 @@ body { background: transparent !important; } } + &-item, .el-sub-menu__title, &-item-group__title { - font-size: 14px; + font-size: 15px; + font-weight: 400; + letter-spacing: -0.2px; + color: #1d1d1f; } + .el-sub-menu { .el-sub-menu__title { + height: 46px; + line-height: 46px; + margin: 4px 20px; + border-radius: 12px; + padding: 0 24px !important; + background: transparent !important; + + &:hover { + background: rgba(102, 126, 234, 0.06) !important; + color: #0066cc !important; + } + i { color: inherit !important; } } - } - .el-menu-item, - .el-sub-menu .el-sub-menu__title { - margin: 0; - height: 48px; - line-height: 48px; - } - .el-sub-menu { .el-menu-item { - height: 45px; - line-height: 45px; - margin: 2px 0; + height: 42px; + line-height: 42px; + margin: 2px 20px; + padding-left: 52px !important; + font-size: 14px; } } .el-menu-item [class^="el-icon"], .el-sub-menu > .el-sub-menu__title [class^="el-icon"] { - font-size: 17px; - margin-right: 0; + font-size: 20px; + margin-right: 14px; width: auto; + color: inherit; } .el-menu-item a, .el-menu-item span, .el-sub-menu > .el-sub-menu__title a, .el-sub-menu > .el-sub-menu__title span { - margin-left: 10px; + margin-left: 0; > a { margin-left: 0; } } } } + //页面内容区域外层 &-view { flex: 1; display: flex !important; flex-direction: column; padding: 0 !important; - border-top: 1px solid #f4f4f4 !important; + background: #ffffff; + &-container { - margin-top: 50px; + margin-top: 0; } + &-wrap { display: flex; flex-direction: column; } + &-ctx { - margin-top: 39px; - padding: 15px !important; + padding: 24px !important; flex: 1; - //页面内容区域 + background: #ffffff; + &-card { min-height: calc(100% - 5px); border-width: 0 !important; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); } } - //页面内容顶部tab标签栏 - &-tab { - background: #fff; - width: 100%; - height: 39px; - box-sizing: border-box; - position: relative; - overflow: hidden; + } +} - &__header { - &:hover { - background: inherit !important; - } - } +.ql-toolbar.ql-snow { + width: 100% !important; + border-radius: 12px 12px 0 0; + border: 1px solid #e0e0e0 !important; +} - &-wrap { - position: fixed; - top: 50px; - left: 230px; - right: 0; - display: flex; - background: #fff; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); - z-index: 100; - transition: left 0.3s; - } +.el-form--inline { + .el-form-item { + & > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete { + min-width: 200px; + } + } +} - &-ops { - width: 40px; - flex-shrink: 0; - background: #fff; - display: flex !important; - align-items: center; - justify-content: center; - border-left: 1px solid #f4f4f4; - cursor: pointer; - text-align: center; - color: #8c8c8c !important; - font-weight: 400 !important; - font-size: 16px !important; - margin-right: 5px; //element-plus el-dropdown自动定位bug bottom-end指令不生效,临时采用偏移5px - } +// Element Plus 组件统一 Apple 风格 +:deep(.el-input__wrapper) { + border-radius: 10px; + box-shadow: 0 0 0 1px #d2d2d7; + padding: 10px 14px; + transition: box-shadow 0.2s ease; - .el-tabs__active-bar { - height: 0; - } + &:hover { + box-shadow: 0 0 0 1px #86868b; + } - .el-tabs__nav { - &-prev, - &-next { - .el-icon { - display: none; - } - } - .el-tabs__item { - padding: 0 15px !important; - border-right: 1px solid #f4f4f4; - user-select: none; - color: #8c8c8c; - &:hover { - color: #262626; - background-color: rgba(0, 0, 0, 0.02); - } - .is-icon-close { - transition: none !important; - &:hover { - color: #fff; - background-color: #ff4d4f; - } - } - &::before { - content: ""; - width: 9px; - height: 9px; - margin-right: 8px; - display: inline-block; - background-color: #ddd; - border-radius: 50%; - } + &.is-focus { + box-shadow: 0 0 0 1px #0066cc; + } +} - &.is-active { - color: @primary-bg-light; - background-color: @primary-bg-light !important; - &:before { - background-color: @primary-bg-light; - } - } - &:nth-child(2) { - &::before { - content: none; - } - } - } - } +:deep(.el-input__inner) { + font-size: 14px; + font-weight: 400; + color: #1d1d1f; +} - .el-tabs__nav-wrap { - padding: 0px 39px 0 40px !important; - &::before, - &::after { - width: 40px; - height: 40px; - line-height: 44px; - text-align: center; - box-sizing: border-box; - font-size: 16px; - color: #8c8c8c; - transition: background-color 0.2s; - position: absolute; - top: 0; - left: 0; - font-family: element-icons !important; - font-style: normal; - font-weight: 400; - font-variant: normal; - text-transform: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - cursor: not-allowed; - } - &::before { - content: url('data:image/svg+xml;charset=utf-8,'); - border-right: 1px solid #f4f4f4; - } - &::after { - content: url('data:image/svg+xml;charset=utf-8,'); - right: 0; - left: auto; - bottom: auto; - height: auto; - background-color: transparent; - border-left: 1px solid #f4f4f4; - } - } +:deep(.el-input__inner::placeholder) { + color: #8c8c8c; +} - .el-tabs__nav-next, - .el-tabs__nav-prev { - width: 40px; - height: 40px; - line-height: 40px; - text-align: center; - box-sizing: border-box; - font-size: 16px; - color: #8c8c8c; - transition: background-color 0.2s; - z-index: 10; - - i { - vertical-align: middle; - margin-top: -4px; - } +//按钮 - Apple 风格 +:deep(.el-button) { + border-radius: 10px; + font-weight: 400; + letter-spacing: -0.12px; + transition: all 0.2s ease; - &:hover { - background: rgba(0, 0, 0, 0.02); - } - } + &:active { + transform: scale(0.95); + } - .el-tabs__nav-prev { - border-right: 1px solid #f4f4f4; - } + &--primary { + background: #0066cc; + border-color: #0066cc; + + &:hover { + background: #0071e3; + border-color: #0071e3; + } + } + + &--default { + background: #ffffff; + border-color: #d2d2d7; + color: #1d1d1f; + + &:hover { + color: #0066cc; + border-color: #0066cc; + } + } + + &--danger { + background: #ff3b30; + border-color: #ff3b30; + + &:hover { + background: #ff453a; + border-color: #ff453a; } } } -.ql-toolbar.ql-snow{ - width: 100% !important; + +//卡片 - Apple 风格 +:deep(.el-card) { + border-radius: 12px; + border: 1px solid #e0e0e0; + box-shadow: none; + + .el-card__header { + padding: 20px 24px; + border-bottom: 1px solid #f0f0f0; + font-weight: 600; + font-size: 15px; + color: #1d1d1f; + } + + .el-card__body { + padding: 24px; + } } -.el-form--inline { - .el-form-item { - & > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete { - min-width: 200px; +//下拉菜单 - Apple 风格 +:deep(.el-dropdown-menu) { + border-radius: 10px; + border: 1px solid #e0e0e0; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + padding: 6px; + + .el-dropdown-menu__item { + border-radius: 8px; + padding: 8px 16px; + font-size: 14px; + color: #1d1d1f; + + &:hover { + background: rgba(0, 0, 0, 0.04); + color: #0066cc; } } } + +//选择器 - Apple 风格 +:deep(.el-select-dropdown) { + border-radius: 10px; + border: 1px solid #e0e0e0; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); + + .el-select-dropdown__item { + font-size: 14px; + color: #1d1d1f; + + &.selected { + color: #0066cc; + font-weight: 600; + } + + &:hover { + background: rgba(0, 0, 0, 0.04); + } + } +} + +//日期选择器 - Apple 风格 +:deep(.el-picker-panel) { + border-radius: 12px; + border: 1px solid #e0e0e0; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); +} + +//消息提示 - Apple 风格 +:deep(.el-message) { + border-radius: 12px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); +} + +:deep(.el-notification) { + border-radius: 12px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); +} + +//标签 - Apple 风格 +:deep(.el-tag) { + border-radius: 8px; + font-weight: 400; +} + +// 标签页 - 强制修复激活标签文字颜色(覆盖全局 ui-tabStyle-dot 等样式) +.ui-tabStyle-dot .rr-view-tab .el-tabs__item.is-active, +.ui-tabStyle-default .rr-view-tab .el-tabs__item.is-active, +.ui-tabStyle-card .rr-view-tab .el-tabs__item.is-active { + color: #0066cc !important; +} + +.ui-tabStyle-dot .rr-view-tab .el-tabs__item.is-active .el-tabs__label, +.ui-tabStyle-default .rr-view-tab .el-tabs__item.is-active .el-tabs__label, +.ui-tabStyle-card .rr-view-tab .el-tabs__item.is-active .el-tabs__label { + color: #0066cc !important; +} + +// 禁用 ui-tabStyle-dot 的 :before 伪元素(蓝色圆点会覆盖文字) +.ui-tabStyle-dot .rr-view-tab .el-tabs__item::before, +.ui-tabStyle-dot .rr-view-tab .el-tabs__item::after { + content: none !important; + display: none !important; +} \ No newline at end of file diff --git a/renren-ui/src/assets/css/header.less b/renren-ui/src/assets/css/header.less index cf6cc8d1ae79f61d8c3e327f3f31057cd4981bb7..0eef6ac001f31def9ffa6ed18fa61d34a5bb23bc 100644 --- a/renren-ui/src/assets/css/header.less +++ b/renren-ui/src/assets/css/header.less @@ -1,31 +1,50 @@ +@import "../theme/apple.less"; + .rr-header-ctx { + background: #ffffff; + border-bottom: 1px solid #e8e8e8; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + display: flex; + flex-direction: column; + [class^="el-icon"] { - font-size: 18px; + font-size: 16px; width: auto; margin-right: 0; } + .rr-header-right { display: flex; flex: 1; justify-content: space-between; overflow: hidden; align-items: center; + height: @apple-nav-height; > div { height: 100%; } &-items { display: flex; - padding: 0 8px 0 0; + padding: 0 @apple-spacing-xs 0 0; > div { - padding: 0 12px; - height: 50px; - line-height: 56px; + padding: 0 @apple-spacing-sm; + height: @apple-nav-height; + line-height: @apple-nav-height; cursor: pointer; + font-size: 14px; + font-weight: 400; + letter-spacing: 0; + color: #333333; + transition: color 0.2s ease; + + &:hover { + color: #0066ff; + } } &-icon { - height: 50px; - line-height: 56px; + height: @apple-nav-height; + line-height: @apple-nav-height; display: inline-block; } .el-badge { @@ -48,7 +67,7 @@ box-sizing: border-box; &-br { - padding: 0 10px; + padding: 0 @apple-spacing-xs; overflow-x: auto; overflow-y: hidden; flex: 1; @@ -61,14 +80,10 @@ .el-breadcrumb__item:last-child .el-breadcrumb__inner:hover, .el-breadcrumb__item:last-child .el-breadcrumb__inner a, .el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover { - color: #8c8c8c; - } - .el-breadcrumb__item { - float: none !important; - display: inline-block; + color: #666666; } .el-breadcrumb__inner.is-link { - color: #595959; + color: #333333; font-weight: 500; } } @@ -87,15 +102,134 @@ } .el-sub-menu { .el-sub-menu__icon-arrow { - margin-left: 3px; + margin-left: @apple-spacing-xxs; margin-top: 0; } .el-sub-menu__title { - padding: 0 10px 0 12px; - height: 50px; - line-height: 50px; + padding: 0 @apple-spacing-sm; + height: @apple-nav-height; + line-height: @apple-nav-height; + font-size: 14px; + font-weight: 400; + letter-spacing: 0; + color: #333333; + } + } + } + } + + // 标签栏区域 - 华为云风格 + .rr-header-tabs { + background: #f5f7fa; + border-top: 1px solid #e8e8e8; + padding: 0 16px; + height: 40px; + display: flex; + align-items: center; + + .rr-view-tab-wrap { + display: flex; + align-items: center; + width: 100%; + + .el-tabs { + flex: 1; + .el-tabs__header { + margin: 0; + border-bottom: none; + } + .el-tabs__nav-wrap { + &::after { + display: none; + } + } + .el-tabs__item { + height: 36px; + line-height: 36px; + font-size: 13px; + color: #666666; + border: 1px solid #e8e8e8; + border-radius: 4px 4px 0 0; + background: #ffffff; + margin-right: 2px; + padding: 0 16px; + transition: all 0.2s ease; + + &:hover { + color: #0066ff; + } + + &.is-active { + color: #0066ff; + background: #ffffff; + border-bottom-color: #ffffff; + font-weight: 500; + } + + .is-icon-close { + font-size: 12px; + margin-left: 4px; + } + } + } + + .rr-view-tab-ops { + margin-left: 8px; + .el-dropdown-link { + cursor: pointer; + color: #666666; + font-size: 14px; + display: flex; + align-items: center; + + &:hover { + color: #0066ff; + } + } + } + } + } +} + +// Breadcrumb - Huawei Cloud Style +.rr-breadcrumb { + padding-top: 0; + display: flex; + align-items: center; + height: @apple-nav-height; + + .el-breadcrumb__item { + display: inline-flex; + align-items: center; + font-size: 14px; + font-weight: 400; + letter-spacing: 0; + + .el-breadcrumb__inner { + color: #666666; + transition: color 0.2s ease; + + &:hover { + color: #0066ff; + } + + &.is-link { + color: #666666; + + &:hover { + color: #0066ff; } } } + + &:last-child .el-breadcrumb__inner { + color: #333333; + font-weight: 500; + } + } + + .el-breadcrumb__separator { + color: #cccccc; + margin: 0 @apple-spacing-xxs; } } diff --git a/renren-ui/src/assets/icons/svg/renren copy.svg b/renren-ui/src/assets/icons/svg/renren copy.svg new file mode 100644 index 0000000000000000000000000000000000000000..2d5eb6effd8ce11ea2d443786fe912aaff38e56c --- /dev/null +++ b/renren-ui/src/assets/icons/svg/renren copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/renren-ui/src/assets/icons/svg/renren-bak.svg b/renren-ui/src/assets/icons/svg/renren-bak.svg new file mode 100644 index 0000000000000000000000000000000000000000..2d5eb6effd8ce11ea2d443786fe912aaff38e56c --- /dev/null +++ b/renren-ui/src/assets/icons/svg/renren-bak.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/renren-ui/src/assets/icons/svg/renren.svg b/renren-ui/src/assets/icons/svg/renren.svg new file mode 100644 index 0000000000000000000000000000000000000000..38b2af3bc5df0d47a3f089b3003d94f1644c444b --- /dev/null +++ b/renren-ui/src/assets/icons/svg/renren.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/renren-ui/src/assets/theme/apple-global.less b/renren-ui/src/assets/theme/apple-global.less new file mode 100644 index 0000000000000000000000000000000000000000..31ca94b079fa7bfe09c48bc781ed48c729ff7f07 --- /dev/null +++ b/renren-ui/src/assets/theme/apple-global.less @@ -0,0 +1,663 @@ +@import "./apple.less"; + +// Apple Design System - Global Styles + +*, +:after, +:before { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + color: @--apple-ink; + font-size: @apple-body-size; + font-weight: @apple-body-weight; + line-height: @apple-body-line; + letter-spacing: @apple-body-letter; + font-family: @apple-font-text; + background: @--apple-canvas; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + // Typography Utilities + .text-hero { + font-family: @apple-font-display; + font-size: @apple-hero-display-size; + font-weight: @apple-hero-display-weight; + line-height: @apple-hero-display-line; + letter-spacing: @apple-hero-display-letter; + } + + .text-display-lg { + font-family: @apple-font-display; + font-size: @apple-display-lg-size; + font-weight: @apple-display-lg-weight; + line-height: @apple-display-lg-line; + } + + .text-display-md { + font-family: @apple-font-text; + font-size: @apple-display-md-size; + font-weight: @apple-display-md-weight; + line-height: @apple-display-md-line; + letter-spacing: @apple-display-md-letter; + } + + .text-lead { + font-family: @apple-font-display; + font-size: @apple-lead-size; + font-weight: @apple-lead-weight; + line-height: @apple-lead-line; + letter-spacing: @apple-lead-letter; + } + + .text-tagline { + font-family: @apple-font-display; + font-size: @apple-tagline-size; + font-weight: @apple-tagline-weight; + line-height: @apple-tagline-line; + letter-spacing: @apple-tagline-letter; + } + + .text-body { + font-size: @apple-body-size; + font-weight: @apple-body-weight; + line-height: @apple-body-line; + letter-spacing: @apple-body-letter; + } + + .text-body-strong { + font-size: @apple-body-strong-size; + font-weight: @apple-body-strong-weight; + line-height: @apple-body-strong-line; + letter-spacing: @apple-body-strong-letter; + } + + .text-caption { + font-size: @apple-caption-size; + font-weight: @apple-caption-weight; + line-height: @apple-caption-line; + letter-spacing: @apple-caption-letter; + } + + .text-fine-print { + font-size: @apple-fine-print-size; + font-weight: @apple-fine-print-weight; + line-height: @apple-fine-print-line; + letter-spacing: @apple-fine-print-letter; + } + + .text-center { + text-align: center; + } + + a { + color: @--apple-primary; + text-decoration: none; + transition: color 0.2s ease; + + &:focus, + &:hover { + color: @--apple-primary-focus; + } + } +} + +// ==================== Icons ==================== +.iconfont { + cursor: pointer; + font-style: normal; + font-weight: 400; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: text-bottom; + display: inline-block; + fill: currentColor; + width: 17px; + height: 17px; +} + +.icon-svg { + width: 1em; + height: 1em; + fill: currentColor; + vertical-align: middle; +} + +// ==================== Element Plus Overrides ==================== + +// Buttons - Apple Style +.el-button { + font-family: @apple-font-text; + font-weight: @apple-button-utility-weight; + letter-spacing: @apple-button-utility-letter; + border-radius: @apple-rounded-pill; + padding: 11px 22px; + transform: scale(1); + transition: all 0.2s ease; + + &:active { + transform: scale(0.95); + } + + &--primary { + background-color: @--apple-primary; + border-color: @--apple-primary; + color: @--apple-on-primary; + + &:hover, + &:focus { + background-color: @--apple-primary-focus; + border-color: @--apple-primary-focus; + } + } + + &--default { + background-color: @--apple-canvas; + border-color: @--apple-hairline; + color: @--apple-ink; + + &:hover, + &:focus { + background-color: @--apple-surface-pearl; + border-color: @--apple-primary; + color: @--apple-primary; + } + } + + &--small { + padding: 8px 15px; + font-size: @apple-button-utility-size; + } + + &--large { + padding: 14px 28px; + font-size: @apple-button-large-size; + font-weight: @apple-button-large-weight; + } +} + +// Cards - Apple Style +.el-card { + border-radius: @apple-rounded-lg; + border: 1px solid @--apple-hairline; + box-shadow: none; + overflow: hidden; + + &__header { + padding: @apple-spacing-lg; + border-bottom: 1px solid @--apple-divider-soft; + background-color: @--apple-canvas; + } + + &__body { + padding: @apple-spacing-lg; + } +} + +// Tables - Apple Style +.el-table { + border-radius: @apple-rounded-lg; + border: 1px solid @--apple-hairline; + overflow: hidden; + + thead { + color: @--apple-ink; + th { + background-color: @--apple-canvas-parchment; + font-weight: @apple-body-strong-weight; + font-size: @apple-caption-size; + letter-spacing: @apple-caption-letter; + border-bottom: 1px solid @--apple-hairline; + } + } + + td { + font-size: @apple-body-size; + color: @--apple-ink; + } + + .el-table__row { + &:hover { + background-color: @--apple-surface-pearl; + } + } + + &__fixed-right::before { + background: transparent; + } +} + +// Forms - Apple Style +.el-form { + &--inline .el-form-item { + margin-right: @apple-spacing-lg; + } + + &-item__label { + font-weight: @apple-body-strong-weight; + color: @--apple-ink; + } +} + +.el-input { + &__inner { + border-radius: @apple-rounded-md; + border: 1px solid @--apple-hairline; + padding: @apple-spacing-sm @apple-spacing-lg; + font-size: @apple-body-size; + font-family: @apple-font-text; + transition: all 0.2s ease; + + &:focus { + border-color: @--apple-primary; + box-shadow: 0 0 0 3px @--apple-primary-light; + } + } +} + +.el-select { + .el-input__inner { + &:focus { + border-color: @--apple-primary; + } + } +} + +.el-date-editor { + .el-input__inner { + &:focus { + border-color: @--apple-primary; + } + } +} + +// Dialogs - Apple Style +.el-dialog { + border-radius: @apple-rounded-lg; + box-shadow: @apple-shadow-product; + overflow: hidden; + + &__header { + padding: @apple-spacing-lg @apple-spacing-xl; + border-bottom: 1px solid @--apple-divider-soft; + } + + &__title { + font-family: @apple-font-display; + font-size: @apple-tagline-size; + font-weight: @apple-tagline-weight; + color: @--apple-ink; + } + + &__body { + padding: @apple-spacing-xl; + } + + &__footer { + padding: @apple-spacing-lg @apple-spacing-xl; + border-top: 1px solid @--apple-divider-soft; + } +} + +// Drawer - Apple Style +.el-drawer { + border-radius: @apple-rounded-lg 0 0 @apple-rounded-lg; + + &__header { + padding: @apple-spacing-lg @apple-spacing-xl; + border-bottom: 1px solid @--apple-divider-soft; + margin-bottom: 0; + } + + &__title { + font-family: @apple-font-display; + font-size: @apple-tagline-size; + font-weight: @apple-tagline-weight; + color: @--apple-ink; + } + + &__body { + padding: @apple-spacing-xl; + } +} + +// Pagination - Apple Style +.el-pagination { + margin-top: @apple-spacing-lg; + justify-content: right; + + button, + li { + border-radius: @apple-rounded-md; + border: 1px solid @--apple-hairline; + transition: all 0.2s ease; + + &:hover { + border-color: @--apple-primary; + color: @--apple-primary; + } + } + + .el-pager li { + &.is-active { + background-color: @--apple-primary; + color: @--apple-on-primary; + border-color: @--apple-primary; + } + } +} + +// Tabs - Apple Style +.el-tabs { + &__header { + border-bottom: 1px solid @--apple-hairline; + } + + &__nav-wrap { + &::after { + background-color: @--apple-hairline; + } + } + + &__item { + font-weight: @apple-body-weight; + color: @--apple-ink-muted-48; + transition: all 0.2s ease; + + &.is-active { + color: @--apple-on-primary; + font-weight: @apple-body-weight; + } + + &:hover { + color: @--apple-ink; + } + } + + &__active-bar { + background-color: @--apple-primary; + } +} + +// Badges +.el-badge__content { + height: 16px; + line-height: 16px; + padding: 0 5px; + border: none; + background: @--apple-primary; + border-radius: @apple-rounded-full; +} + +// Alerts +.el-alert { + border-radius: @apple-rounded-md; + padding: @apple-spacing-md @apple-spacing-lg; + + &--warning.is-light { + background-color: #fff7e8; + color: #faad14; + border: 1px solid #faad144d; + } + + &__content { + padding: 0; + } +} + +// Popover +.el-popover { + border-radius: @apple-rounded-md; + box-shadow: @apple-shadow-product; + max-height: 300px; + overflow: auto; +} + +// Popper +.el-popper.is-dark a { + color: @--apple-body-on-dark; + text-decoration: none; +} + +// Dropdown - Apple Style +.el-dropdown-menu { + border-radius: @apple-rounded-md; + border: 1px solid @--apple-hairline; + box-shadow: @apple-shadow-product; + padding: @apple-spacing-xxs 0; + + .el-dropdown-menu__item { + font-size: @apple-body-size; + font-weight: @apple-body-weight; + color: @--apple-ink; + padding: @apple-spacing-xs @apple-spacing-lg; + transition: all 0.2s ease; + + &:hover { + background-color: @--apple-surface-pearl; + color: @--apple-ink; + } + + &.is-disabled { + color: @--apple-ink-muted-48; + } + } + + .el-dropdown-menu__item--divided { + border-top: 1px solid @--apple-divider-soft; + margin-top: @apple-spacing-xxs; + padding-top: @apple-spacing-xs; + } +} + +.el-dropdown-link { + color: @--apple-body-on-dark; + font-size: @apple-nav-link-size; + font-weight: @apple-nav-link-weight; + letter-spacing: @apple-nav-link-letter; + cursor: pointer; + transition: opacity 0.2s ease; + + &:hover { + opacity: 0.8; + } +} + +// ==================== Layout Components ==================== + +// Main Container +.rr { + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + overflow: hidden; + background: @--apple-canvas; + + &-loading { + z-index: 9999; + } + + &-error { + display: flex; + justify-content: center; + align-items: center; + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: @--apple-canvas; + z-index: 1200; + } + + // Header - Apple Global Nav Style + &-header { + background: @--apple-surface-black; + padding: 0; + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 200; + height: @apple-nav-height; + + &-ctx { + display: flex; + height: @apple-nav-height; + box-shadow: none; + background: @--apple-surface-black; + align-items: center; + + &-logo { + display: flex; + color: @--apple-body-on-dark; + background-color: @--apple-surface-black; + font-size: @apple-fine-print-size; + font-weight: @apple-fine-print-weight; + letter-spacing: @apple-fine-print-letter; + width: 230px; + height: @apple-nav-height; + overflow: hidden; + white-space: nowrap; + justify-content: center; + font-family: @apple-font-text; + align-items: center; + position: relative; + transition: width 0.3s; + padding: 0 @apple-spacing-sm; + + &-text { + display: inline-block; + line-height: 1; + overflow: hidden; + font-family: @apple-font-text; + font-size: @apple-fine-print-size; + font-weight: @apple-fine-print-weight; + letter-spacing: @apple-fine-print-letter; + color: @--apple-body-on-dark; + } + } + } + } + + // Body + &-body { + flex: 1; + display: flex; + overflow: hidden; + height: 100vh; + margin-top: 0; + } + + // Sidebar - 登录页风格(纯白背景) + &-sidebar { + margin-top: 0; + width: 240px; + height: 100vh; + overflow-x: hidden; + transition: width 0.3s; + z-index: 120; + scrollbar-width: none; + background: #ffffff; + border-right: 1px solid #f0f0f0; + + &-menu { + transition: width 0.3s; + overflow: hidden; + background: transparent; + } + + &::-webkit-scrollbar { + display: none; + } + + // Hide Element Plus menu styles + .el-menu { + display: none; + } + } + + // View Container + &-view { + flex: 1; + display: flex; + flex-direction: column; + padding: 0; + border-top: none; + background: @--apple-canvas; + height: 100vh; + overflow: hidden; + + &-container { + margin-top: 0; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + } + + &-wrap { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + } + + &-ctx { + padding: @apple-spacing-lg; + flex: 1; + background: @--apple-canvas; + overflow: auto; + + &-card { + min-height: calc(100% - 5px); + border-width: 0; + box-shadow: none; + background: transparent; + } + } + + // Tab Bar - styles handled in tabs.vue component + &-tab { + background: @--apple-canvas-parchment; + width: 100%; + box-sizing: border-box; + border-bottom: 1px solid @--apple-hairline; + flex-shrink: 0; + + &-wrap { + display: flex; + align-items: center; + background: @--apple-canvas-parchment; + border-bottom: 1px solid @--apple-hairline; + height: @apple-subnav-height; + } + } + } +} + +// Inline Form +.el-form--inline { + .el-form-item { + & > .el-input, + .el-cascader, + .el-select, + .el-date-editor, + .el-autocomplete { + min-width: 200px; + } + } +} + +// Rich Text Editor +.ql-toolbar.ql-snow { + width: 100%; + border-radius: @apple-rounded-md @apple-rounded-md 0 0; + border: 1px solid @--apple-hairline; +} diff --git a/renren-ui/src/assets/theme/apple-pages.less b/renren-ui/src/assets/theme/apple-pages.less new file mode 100644 index 0000000000000000000000000000000000000000..74571c685a2dec5904fc94a9245a2c02279642f6 --- /dev/null +++ b/renren-ui/src/assets/theme/apple-pages.less @@ -0,0 +1,322 @@ +@import "./apple.less"; + +// Apple Design System - Business Page Styles +// 这个文件定义了业务页面的通用 Apple 风格样式 + +// ==================== 页面容器 ==================== +.mod-common { + font-family: @apple-font-text; + color: @--apple-ink; + + h2, + h3, + h4 { + font-family: @apple-font-display; + color: @--apple-ink; + font-weight: @apple-tagline-weight; + } + + h2 { + font-size: @apple-display-md-size; + line-height: @apple-display-md-line; + letter-spacing: @apple-display-md-letter; + } + + h3 { + font-size: @apple-tagline-size; + line-height: @apple-tagline-line; + letter-spacing: @apple-tagline-letter; + } + + h4 { + font-size: @apple-body-strong-size; + line-height: @apple-body-strong-line; + letter-spacing: @apple-body-strong-letter; + } + + p { + font-size: @apple-body-size; + line-height: @apple-body-line; + letter-spacing: @apple-body-letter; + color: @--apple-ink-muted-48; + } +} + +// ==================== 搜索表单区域 ==================== +.mod-common__search { + background: @--apple-canvas; + padding: @apple-spacing-lg; + border-radius: @apple-rounded-lg; + margin-bottom: @apple-spacing-lg; + border: 1px solid @--apple-hairline; + + .el-form-item { + margin-bottom: @apple-spacing-sm; + } + + .el-form-item__label { + font-weight: @apple-body-strong-weight; + color: @--apple-ink; + } + + .el-button { + margin-left: @apple-spacing-xs; + } +} + +// ==================== 数据表格区域 ==================== +.mod-common__table { + background: @--apple-canvas; + border-radius: @apple-rounded-lg; + overflow: hidden; + border: 1px solid @--apple-hairline; + + .mod-common__toolbar { + padding: @apple-spacing-lg; + border-bottom: 1px solid @--apple-divider-soft; + display: flex; + justify-content: space-between; + align-items: center; + + .el-button { + margin-right: @apple-spacing-xs; + } + } + + .el-table { + border: none; + + &__header { + th { + background-color: @--apple-canvas-parchment; + font-weight: @apple-body-strong-weight; + font-size: @apple-caption-size; + color: @--apple-ink; + } + } + + &__body { + td { + font-size: @apple-body-size; + color: @--apple-ink; + } + + .el-table__row { + &:hover { + background-color: @--apple-surface-pearl; + } + } + } + } + + .el-pagination { + padding: @apple-spacing-lg; + border-top: 1px solid @--apple-divider-soft; + } +} + +// ==================== 操作按钮组 ==================== +.mod-common__actions { + display: flex; + gap: @apple-spacing-xs; + + .el-button { + padding: @apple-spacing-xs @apple-spacing-sm; + font-size: @apple-caption-size; + + &--primary { + background-color: @--apple-primary; + border-color: @--apple-primary; + + &:hover { + background-color: @--apple-primary-focus; + border-color: @--apple-primary-focus; + } + } + + &--danger { + background-color: #ff3b30; + border-color: #ff3b30; + + &:hover { + background-color: #ff453a; + border-color: #ff453a; + } + } + + &--warning { + background-color: #ff9500; + border-color: #ff9500; + + &:hover { + background-color: #ff9f0a; + border-color: #ff9f0a; + } + } + } +} + +// ==================== 对话框样式 ==================== +.mod-common__dialog { + .el-dialog { + border-radius: @apple-rounded-lg; + + &__header { + padding: @apple-spacing-lg @apple-spacing-xl; + border-bottom: 1px solid @--apple-divider-soft; + } + + &__title { + font-family: @apple-font-display; + font-size: @apple-tagline-size; + font-weight: @apple-tagline-weight; + color: @--apple-ink; + } + + &__body { + padding: @apple-spacing-xl; + } + + &__footer { + padding: @apple-spacing-lg @apple-spacing-xl; + border-top: 1px solid @--apple-divider-soft; + } + } + + .el-form { + .el-form-item { + margin-bottom: @apple-spacing-lg; + + &__label { + font-weight: @apple-body-strong-weight; + color: @--apple-ink; + } + } + + .el-input__inner, + .el-textarea__inner { + border-radius: @apple-rounded-md; + border: 1px solid @--apple-hairline; + + &:focus { + border-color: @--apple-primary; + box-shadow: 0 0 0 3px @--apple-primary-light; + } + } + } +} + +// ==================== 卡片样式 ==================== +.mod-common__card { + background: @--apple-canvas; + border-radius: @apple-rounded-lg; + border: 1px solid @--apple-hairline; + padding: @apple-spacing-xl; + + &-header { + padding-bottom: @apple-spacing-lg; + margin-bottom: @apple-spacing-lg; + border-bottom: 1px solid @--apple-divider-soft; + + h3 { + margin: 0; + font-size: @apple-tagline-size; + font-weight: @apple-tagline-weight; + color: @--apple-ink; + } + } + + &-body { + p { + margin: 0; + } + } +} + +// ==================== 标签样式 ==================== +.mod-common__tag { + display: inline-block; + padding: @apple-spacing-xxs @apple-spacing-xs; + border-radius: @apple-rounded-sm; + font-size: @apple-caption-size; + font-weight: @apple-caption-strong-weight; + + &--success { + background-color: #e8f5e9; + color: #2e7d32; + } + + &--warning { + background-color: #fff3e0; + color: #f57c00; + } + + &--danger { + background-color: #ffebee; + color: #c62828; + } + + &--info { + background-color: #e3f2fd; + color: #1565c0; + } +} + +// ==================== 空状态 ==================== +.mod-common__empty { + text-align: center; + padding: @apple-spacing-xxl; + + &-icon { + font-size: 64px; + color: @--apple-hairline; + margin-bottom: @apple-spacing-lg; + } + + &-text { + font-size: @apple-body-size; + color: @--apple-ink-muted-48; + } +} + +// ==================== 加载状态 ==================== +.mod-common__loading { + display: flex; + justify-content: center; + align-items: center; + padding: @apple-spacing-xxl; + + .el-loading-spinner { + .circular { + width: 40px; + height: 40px; + } + + .el-loading-text { + color: @--apple-primary; + font-weight: @apple-body-strong-weight; + } + } +} + +// ==================== 响应式调整 ==================== +@media (max-width: 768px) { + .mod-common { + .el-form--inline { + .el-form-item { + width: 100%; + margin-right: 0; + } + } + + .mod-common__toolbar { + flex-direction: column; + gap: @apple-spacing-sm; + align-items: flex-start; + } + + .mod-common__actions { + flex-wrap: wrap; + } + } +} diff --git a/renren-ui/src/assets/theme/apple.less b/renren-ui/src/assets/theme/apple.less new file mode 100644 index 0000000000000000000000000000000000000000..fa644ee3bec7a1b5ddabaaf110d251874a5cf7dc --- /dev/null +++ b/renren-ui/src/assets/theme/apple.less @@ -0,0 +1,204 @@ +// Apple Design System Theme +// Based on Apple's photography-first interface + +// ==================== Colors ==================== +body { + // Primary Accent - Action Blue + --apple-primary: #0066cc; + --apple-primary-focus: #0071e3; + --apple-primary-on-dark: #2997ff; + --apple-primary-light: rgb(0 102 204 / 8%); + + // Text Colors + --apple-ink: #1d1d1f; + --apple-body: #1d1d1f; + --apple-body-on-dark: #ffffff; + --apple-body-muted: #cccccc; + --apple-ink-muted-80: #333336; + --apple-ink-muted-48: #7a7a7a; + + // Surface Colors + --apple-canvas: #ffffff; + --apple-canvas-parchment: #f5f5f7; + --apple-surface-pearl: #fafafc; + --apple-surface-tile-1: #272729; + --apple-surface-tile-2: #2a2a2c; + --apple-surface-tile-3: #252527; + --apple-surface-black: #000000; + --apple-surface-chip-translucent: #d2d2d7; + + // UI Colors + --apple-divider-soft: #f0f0f0; + --apple-hairline: #e0e0e0; + --apple-on-primary: #ffffff; + --apple-on-dark: #ffffff; + + // Legacy compatibility + --color-primary: var(--apple-primary); + --color-primary-light: var(--apple-primary-light); +} + +@--apple-primary:~ 'var(--apple-primary)'; +@--apple-primary-focus:~ 'var(--apple-primary-focus)'; +@--apple-primary-on-dark:~ 'var(--apple-primary-on-dark)'; +@--apple-primary-light:~ 'var(--apple-primary-light)'; + +@--apple-ink:~ 'var(--apple-ink)'; +@--apple-body:~ 'var(--apple-body)'; +@--apple-body-on-dark:~ 'var(--apple-body-on-dark)'; +@--apple-body-muted:~ 'var(--apple-body-muted)'; +@--apple-ink-muted-80:~ 'var(--apple-ink-muted-80)'; +@--apple-ink-muted-48:~ 'var(--apple-ink-muted-48)'; + +@--apple-canvas:~ 'var(--apple-canvas)'; +@--apple-canvas-parchment:~ 'var(--apple-canvas-parchment)'; +@--apple-surface-pearl:~ 'var(--apple-surface-pearl)'; +@--apple-surface-tile-1:~ 'var(--apple-surface-tile-1)'; +@--apple-surface-tile-2:~ 'var(--apple-surface-tile-2)'; +@--apple-surface-tile-3:~ 'var(--apple-surface-tile-3)'; +@--apple-surface-black:~ 'var(--apple-surface-black)'; +@--apple-surface-chip-translucent:~ 'var(--apple-surface-chip-translucent)'; + +@--apple-divider-soft:~ 'var(--apple-divider-soft)'; +@--apple-hairline:~ 'var(--apple-hairline)'; +@--apple-on-primary:~ 'var(--apple-on-primary)'; +@--apple-on-dark:~ 'var(--apple-on-dark)'; + +// Legacy compatibility +@--color-primary:~ 'var(--apple-primary)'; +@--color-primary-light:~ 'var(--apple-primary-light)'; + +// ==================== Typography ==================== +@apple-font-display: "SF Pro Display", system-ui, -apple-system, sans-serif; +@apple-font-text: "SF Pro Text", system-ui, -apple-system, sans-serif; + +// Display Typography +@apple-hero-display-size: 56px; +@apple-hero-display-weight: 600; +@apple-hero-display-line: 1.07; +@apple-hero-display-letter: -0.28px; + +@apple-display-lg-size: 40px; +@apple-display-lg-weight: 600; +@apple-display-lg-line: 1.1; + +@apple-display-md-size: 34px; +@apple-display-md-weight: 600; +@apple-display-md-line: 1.47; +@apple-display-md-letter: -0.374px; + +@apple-lead-size: 28px; +@apple-lead-weight: 400; +@apple-lead-line: 1.14; +@apple-lead-letter: 0.196px; + +@apple-lead-airy-size: 24px; +@apple-lead-airy-weight: 300; +@apple-lead-airy-line: 1.5; + +@apple-tagline-size: 21px; +@apple-tagline-weight: 600; +@apple-tagline-line: 1.19; +@apple-tagline-letter: 0.231px; + +// Body Typography +@apple-body-strong-size: 17px; +@apple-body-strong-weight: 600; +@apple-body-strong-line: 1.24; +@apple-body-strong-letter: -0.374px; + +@apple-body-size: 17px; +@apple-body-weight: 400; +@apple-body-line: 1.47; +@apple-body-letter: -0.374px; + +@apple-dense-link-size: 17px; +@apple-dense-link-weight: 400; +@apple-dense-link-line: 2.41; + +@apple-caption-size: 14px; +@apple-caption-weight: 400; +@apple-caption-line: 1.43; +@apple-caption-letter: -0.224px; + +@apple-caption-strong-size: 14px; +@apple-caption-strong-weight: 600; +@apple-caption-strong-line: 1.29; +@apple-caption-strong-letter: -0.224px; + +// Button Typography +@apple-button-large-size: 18px; +@apple-button-large-weight: 300; +@apple-button-large-line: 1.0; + +@apple-button-utility-size: 14px; +@apple-button-utility-weight: 400; +@apple-button-utility-line: 1.29; +@apple-button-utility-letter: -0.224px; + +// Fine Print +@apple-fine-print-size: 12px; +@apple-fine-print-weight: 400; +@apple-fine-print-line: 1.0; +@apple-fine-print-letter: -0.12px; + +@apple-micro-legal-size: 10px; +@apple-micro-legal-weight: 400; +@apple-micro-legal-line: 1.3; +@apple-micro-legal-letter: -0.08px; + +// Nav Link +@apple-nav-link-size: 12px; +@apple-nav-link-weight: 400; +@apple-nav-link-line: 1.0; +@apple-nav-link-letter: -0.12px; + +// ==================== Spacing System ==================== +// Base unit: 8px +@apple-spacing-xxs: 4px; +@apple-spacing-xs: 8px; +@apple-spacing-sm: 12px; +@apple-spacing-md: 17px; +@apple-spacing-lg: 24px; +@apple-spacing-xl: 32px; +@apple-spacing-xxl: 48px; +@apple-spacing-section: 80px; + +// ==================== Border Radius ==================== +@apple-rounded-none: 0px; +@apple-rounded-sm: 8px; +@apple-rounded-md: 12px; +@apple-rounded-lg: 18px; +@apple-rounded-pill: 980px; +@apple-rounded-full: 50%; + +// ==================== Shadows ==================== +@apple-shadow-product: 0 3px 5px 30px rgba(0, 0, 0, 0.22); +@apple-shadow-none: none; + +// ==================== Component Heights ==================== +@apple-nav-height: 66px; +@apple-subnav-height: 52px; +@apple-stickybar-height: 64px; + +// ==================== Legacy Theme Colors ==================== +@text: @--apple-ink; +@text-2: @--apple-ink-muted-48; + +// Navigation Menu - Apple Style +@dark-text: @--apple-body-muted; +@dark-text-active: @--apple-body-on-dark; +@dark-bg: @--apple-surface-black; +@dark-bg-active: @--apple-surface-tile-1; + +@light-text: @--apple-ink; +@light-text-active: @--apple-primary; +@light-bg: @--apple-canvas; +@light-bg-active: @--apple-primary-light; + +@primary-text: @--apple-body-on-dark; +@primary-text-2: @--apple-body-muted; +@primary-text-active: @--apple-body-on-dark; +@primary-bg: @--apple-primary; +@primary-bg-light: @--apple-primary-light; +@primary-bg-active: @--apple-primary-light; diff --git a/renren-ui/src/assets/theme/base.less b/renren-ui/src/assets/theme/base.less index 83e822028db1ece439efd9d18b1b67dabe72e426..a597ba1ab4a1783855f30fed5a177324dbe3c269 100644 --- a/renren-ui/src/assets/theme/base.less +++ b/renren-ui/src/assets/theme/base.less @@ -1,32 +1,31 @@ -//定义基础色 - -//主色 +//定义基础色 - Apple 设计风格 +//主色 - Apple Action Blue body { - --color-primary: #409eff; - --color-primary-light: rgb(64 158 255 / 8%); + --color-primary: #0066cc; + --color-primary-light: rgb(0 102 204 / 8%); } @--color-primary:~ 'var(--color-primary)'; @--color-primary-light:~ 'var(--color-primary-light)'; -@text: #595959; -@text-2: #8c8c8c; +@text: #1d1d1f; +@text-2: #86868b; -//导航菜单 +//导航菜单 - Apple 风格 @dark-text: rgb(255 255 255 / 66%); -@dark-text-active: #eee; -@dark-bg: #263238; -@dark-bg-active: @--color-primary; +@dark-text-active: #ffffff; +@dark-bg: #000000; +@dark-bg-active: #1d1d1f; -@light-text: @text; -@light-text-active: @--color-primary; -@light-bg: #fff; -@light-bg-active: @--color-primary-light; +@light-text: #1d1d1f; +@light-text-active: #0066cc; +@light-bg: #ffffff; +@light-bg-active: rgb(0 102 204 / 8%); -@primary-text: rgb(255 255 255 / 66%); +@primary-text: rgb(255 255 255 / 80%); @primary-text-2: rgb(255 255 255 / 65%); -@primary-text-active: #fff; -@primary-bg: @--color-primary; -@primary-bg-light: @--color-primary-light; -@primary-bg-active: @--color-primary-light; +@primary-text-active: #ffffff; +@primary-bg: #000000; +@primary-bg-light: rgb(255 255 255 / 10%); +@primary-bg-active: rgb(255 255 255 / 10%); diff --git a/renren-ui/src/assets/theme/index.less b/renren-ui/src/assets/theme/index.less index 1f9645d037f69aade37055a8143a5c66cebdbaf1..fcacd3704e130ef75c93705a348092de90486964 100644 --- a/renren-ui/src/assets/theme/index.less +++ b/renren-ui/src/assets/theme/index.less @@ -1,55 +1,50 @@ @import "./base.less"; +@import "./apple.less"; -//主题样式 +//主题样式 - Apple 设计风格 //================= -.el-menu--vertical.rr-sidebar-menu-pop-light, -.el-menu--vertical.rr-sidebar-menu-pop-dark { - border-radius: 4px !important; - box-shadow: none !important; - .el-menu.el-menu--popup { - min-width: 160px; - border-radius: 4px !important; - } - .el-menu-item, - .el-sub-menu__title { - height: 45px; - line-height: 45px; - } - .is-active { - &.el-menu-item { - border: 0 !important; - } - } -} -//深色侧边栏 +//深色侧边栏 - Apple Style .ui-sidebar-dark .rr-sidebar, .rr-sidebar-menu-pop-dark { background: @dark-bg !important; - box-shadow: 0 4px 4px rgba(0, 21, 41, 0.35); + box-shadow: none !important; .el-menu { background: @dark-bg !important; .el-menu-item, .el-sub-menu__title { + height: 48px; + line-height: 48px; + margin: 2px 8px; + border-radius: @apple-rounded-sm; + font-size: @apple-body-size; + font-weight: @apple-body-weight; + color: @dark-text; + transition: all 0.2s ease; + &:hover { + background: rgba(255, 255, 255, 0.1) !important; i, a { color: @dark-text-active !important; } } + i, a { color: @dark-text !important; } - &:not(.is-active):hover { - background: inherit !important; - } } + .is-active { &.el-menu-item { border-right: none !important; + border-bottom: none !important; background: @dark-bg-active !important; + color: @dark-text-active !important; + font-weight: @apple-body-strong-weight; } + &.el-menu-item, > .el-sub-menu__title:first-child { i, @@ -60,34 +55,51 @@ } } } -//浅色侧边栏 + +//浅色侧边栏 - Apple Style .ui-sidebar-light .rr-sidebar, .rr-sidebar-menu-pop-light { background: @light-bg !important; - box-shadow: 0 4px 4px rgba(0, 21, 41, 0.25); + box-shadow: none !important; + border-right: 1px solid @--apple-divider-soft !important; + .el-menu { background: @light-bg !important; + .el-menu-item, .el-sub-menu__title { + height: 48px; + line-height: 48px; + margin: 2px 8px; + border-radius: @apple-rounded-sm; + font-size: @apple-body-size; + font-weight: @apple-body-weight; + color: @light-text; + transition: all 0.2s ease; + &:hover { + background: @--apple-surface-pearl !important; i, a { - color: @light-text-active !important; + color: @light-text !important; } } + i, a { color: @light-text !important; } - &:not(.is-active):hover { - background: inherit !important; - } } + .is-active { &.el-menu-item { - border-right: 2px solid @light-text-active !important; + border-right: none !important; + border-bottom: none !important; background: @light-bg-active !important; + color: @light-text-active !important; + font-weight: @apple-body-strong-weight; } + &.el-menu-item, > .el-sub-menu__title:first-child { i, @@ -100,49 +112,60 @@ } //================================ +//顶部导航菜单 - Apple Style .el-menu--horizontal.rr-sidebar-menu-pop-light, .el-menu--horizontal.rr-sidebar-menu-pop-dark { - border-radius: 4px !important; + border-radius: 0 !important; box-shadow: none !important; - background-color: @light-bg !important; + background-color: @dark-bg !important; border: none !important; - margin-top: -5px; + margin-top: 0; margin-left: 0; .el-popper { border: 0 !important; } .el-menu--horizontal { - margin-left: -5px; + margin-left: 0; } .el-menu.el-menu--popup { min-width: 160px; - box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2) !important; + box-shadow: @apple-shadow-product !important; + border-radius: @apple-rounded-md; + border: 1px solid @--apple-hairline; } .el-menu-item, .el-sub-menu__title { - height: 45px; - line-height: 45px; - } + height: @apple-nav-height; + line-height: @apple-nav-height; + font-size: @apple-nav-link-size; + font-weight: @apple-nav-link-weight; + letter-spacing: @apple-nav-link-letter; + color: @--apple-body-on-dark; + + &:hover { + background: rgba(255, 255, 255, 0.1) !important; + } + } + .is-active { &.el-menu-item { border: 0 !important; + border-bottom: 2px solid @--apple-primary-on-dark !important; + background: transparent !important; } } } -//浅色顶栏 + +//浅色顶栏 - Apple Style .ui-topHeader-light { .rr-header-ctx { - box-shadow: 0 1px 1px #f1f1f1; + box-shadow: none; + background: @light-bg !important; &-logo { background: @light-bg !important; - color: #000000bf; - } - } - &.ui-sidebar-dark { - .rr-header-ctx { - box-shadow: 0 1px 3px rgb(0 0 0 / 8%); + color: @--apple-ink; } } .rr-header-right { @@ -153,8 +176,8 @@ } > div { &:hover { - color: #262626 !important; - background: rgba(0, 0, 0, 0.1) !important; + color: @--apple-primary !important; + background: @--apple-surface-pearl !important; } } .el-badge__content { @@ -167,7 +190,7 @@ .el-menu-item, .el-sub-menu__title { &:hover { - background: rgba(0, 0, 0, 0.1) !important; + background: @--apple-surface-pearl !important; i, a { color: @light-text-active !important; @@ -201,7 +224,7 @@ color: @light-text-active !important; } &:hover { - background: rgba(0, 0, 0, 0.1) !important; + background: @--apple-surface-pearl !important; } } &.isLink { @@ -216,20 +239,29 @@ } } } -//深色顶栏 + +//深色顶栏 - Apple Global Nav Style .ui-topHeader-dark { .rr-header-ctx { + background: @dark-bg !important; &-logo { background: @dark-bg !important; + color: @--apple-body-on-dark; } } .rr-header-right { background: @dark-bg !important; .rr-header-right-items { * { - color: @dark-text !important; + color: @--apple-body-on-dark; &:hover { - color: @dark-text-active !important; + color: @--apple-body-on-dark; + opacity: 0.8; + } + } + > div { + &:hover { + background: rgba(255, 255, 255, 0.1) !important; } } .el-badge__content { @@ -242,7 +274,7 @@ .el-menu-item, .el-sub-menu__title { &:hover { - background: @dark-bg !important; + background: rgba(255, 255, 255, 0.1) !important; i, a { color: @dark-text-active !important; @@ -252,9 +284,6 @@ a { color: @dark-text !important; } - &:not(.is-active):hover { - background: inherit !important; - } i:not(.el-sub-menu__icon-arrow) { width: 17px !important; height: 17px !important; @@ -268,12 +297,12 @@ } .is-active { &.el-menu-item { - border-bottom: 2px solid @dark-text-active !important; - background: @dark-bg !important; + border-bottom: 2px solid @--apple-primary-on-dark !important; + background: transparent !important; } &.el-menu-item, .el-sub-menu__title { - border-bottom: 2px solid @dark-text-active !important; + border-bottom: 2px solid @--apple-primary-on-dark !important; i, a { color: @dark-text-active !important; @@ -291,33 +320,35 @@ } } } -//主题色 + +//主题色顶栏 - Apple Style (使用黑色作为主题色) .ui-topHeader-primary { .rr-header-ctx { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important; + box-shadow: none !important; + background: @dark-bg !important; position: relative; z-index: 102; } .rr-header-ctx-logo { - background: @primary-bg !important; + background: @dark-bg !important; + color: @--apple-body-on-dark; } .rr-header-right { - background: @primary-bg !important; + background: @dark-bg !important; .rr-header-right-items, .rr-header-right-left-br { div, span, svg, i { - color: @primary-text !important; + color: @--apple-body-on-dark !important; &:hover { - color: @primary-text-active !important; + opacity: 0.8; } } > div:not(.el-breadcrumb) { &:hover { - color: #262626 !important; - background: rgba(0, 0, 0, 0.1) !important; + background: rgba(255, 255, 255, 0.1) !important; } } .el-badge__content { @@ -327,29 +358,35 @@ .el-breadcrumb__item { &:not(:first-child) { * { - color: @primary-text-2 !important; + color: @--apple-body-muted !important; font-weight: 400 !important; } } + &:last-child { + * { + color: @--apple-body-on-dark !important; + font-weight: @apple-body-strong-weight !important; + } + } } } } .rr-sidebar-menu { &.el-menu { - background: @primary-bg !important; + background: @dark-bg !important; .el-menu-item, .el-sub-menu__title { &:hover, &:focus { - background: rgba(0, 0, 0, 0.1) !important; + background: rgba(255, 255, 255, 0.1) !important; i, a { - color: @primary-text-active !important; + color: @--apple-body-on-dark !important; } } i, a { - color: @primary-text !important; + color: @--apple-body-on-dark !important; } i:not(.el-sub-menu__icon-arrow) { width: 17px !important; @@ -364,24 +401,25 @@ } .is-active { &.el-menu-item { - border-bottom: 2px solid @primary-text-active !important; + border-bottom: 2px solid @--apple-primary-on-dark !important; + background: transparent !important; } &.el-menu-item, .el-sub-menu__title { - border-bottom: 2px solid @primary-text-active !important; + border-bottom: 2px solid @--apple-primary-on-dark !important; i, a { - color: @primary-text-active !important; + color: @--apple-body-on-dark !important; } &:hover { - background: rgba(0, 0, 0, 0.1) !important; + background: rgba(255, 255, 255, 0.1) !important; } } &.isLink { border-bottom: 0 !important; i, a { - color: @primary-text !important; + color: @--apple-body-on-dark !important; } } } @@ -391,11 +429,11 @@ } //============= -//导航模式 +//导航模式 - Apple Style .ui-navLayout-left { &.ui-sidebar-light { .rr-sidebar { - box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08); + box-shadow: none !important; z-index: 101; } } @@ -429,12 +467,12 @@ } &.ui-sidebar-light { .rr-sidebar { - box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08); + box-shadow: none !important; z-index: 101; } } .rr-sidebar { - box-shadow: 1px 2px 3px rgba(0, 0, 0, 0.08); + box-shadow: none !important; z-index: 101; } .rr-header-right-left-br { @@ -443,7 +481,7 @@ } //======== -//内容不铺满 +//内容不铺满 - Apple Style .ui-contentFull-false { .rr-view-ctx { width: 1200px !important; @@ -453,7 +491,7 @@ } //======= -//tab标签栏开关 +//tab标签栏开关 - Apple Style .ui-openTabsPage { &-false { .rr-view-ctx { @@ -463,27 +501,26 @@ } //======= -//logo自动 -//导航模式在顶部时logo自动要取消 +//logo自动 - Apple Style .ui-logoAuto-true, .ui-navLayout-top { .rr-header-ctx-logo { width: inherit !important; - padding: 0 15px 0 20px; + padding: 0 @apple-spacing-sm; box-shadow: none !important; } &.ui-topHeader-primary .rr-header-ctx-logo { - background: @primary-bg !important; - color: #ffffffd9 !important; + background: @dark-bg !important; + color: @--apple-body-on-dark !important; } &.ui-topHeader-dark .rr-header-ctx-logo { background: @dark-bg !important; - color: #ffffffd9 !important; + color: @--apple-body-on-dark !important; } &.ui-topHeader-light .rr-header-ctx-logo { background: @light-bg !important; - color: #000000bf; - box-shadow: 1px 0 3px rgba(0, 0, 0, 0.08); + color: @--apple-ink; + box-shadow: none; } } @@ -703,7 +740,7 @@ } } -//侧边栏收缩状态 +//侧边栏收缩状态 - Apple Style .rr.ui-sidebarCollapse { &-true { .rr-view-tab-wrap { @@ -734,10 +771,9 @@ } } &.ui-navLayout-top { - //导航模式为顶部时自动展开logo状态 .rr-header-ctx-logo { width: inherit !important; - padding: 0 15px 0 20px; + padding: 0 @apple-spacing-sm; box-shadow: none !important; &-text { display: block; @@ -752,7 +788,6 @@ .el-menu { width: 60px !important; } - // 收起效果 .rr-sidebar-menu { .el-menu-item, .el-sub-menu__title, @@ -775,23 +810,23 @@ } } -//tabStyle +//tabStyle - Apple Style .ui-tabStyle-default { .rr-view-tab { .el-tabs__item { border-right: none !important; - padding: 0 15px 0 !important; + padding: 0 @apple-spacing-md 0 !important; &.is-active { - color: @--color-primary !important; + color: @--apple-primary !important; } &:before { content: none; } &:after { content: ""; - height: 3px; + height: 2px; width: 0; - background-color: @--color-primary !important; + background-color: @--apple-primary !important; position: absolute; bottom: 0; left: 0; @@ -806,59 +841,61 @@ &:after, .el-tabs__nav-next, .el-tabs__nav-prev { - height: 40px; - line-height: 44px; + height: @apple-subnav-height; + line-height: @apple-subnav-height; } } } } + .ui-tabStyle-dot { .rr-view-tab-wrap { .rr-view-tab { .el-tabs__item { &.is-active { - color: @--color-primary !important; + color: #ffffff !important; &:before { - background-color: @--color-primary !important; + background-color: #ffffff !important; } } } } } } + .ui-tabStyle-card { .rr-view-tab-wrap { background: transparent !important; box-shadow: none !important; - padding-top: 10px; + padding-top: @apple-spacing-xs; .rr-view-tab { height: 30px; background: transparent !important; &-ops { - border-radius: 4px; + border-radius: @apple-rounded-sm; height: 30px; line-height: 30px; width: 30px; - background-color: #fff; - margin-right: 10px; + background-color: @--apple-canvas; + margin-right: @apple-spacing-xs; .el-icon--right { margin-left: 0; } } .el-tabs__item { - margin-left: 8px; - padding: 0 15px 0 !important; - border-radius: 4px; + margin-left: @apple-spacing-xs; + padding: 0 @apple-spacing-md 0 !important; + border-radius: @apple-rounded-sm; height: 30px; line-height: 30px; - background-color: #fff; + background-color: @--apple-canvas; &:nth-child(2) { margin-left: 0; - padding: 0 15px !important; + padding: 0 @apple-spacing-md !important; } &.is-active { - background-color: @--color-primary !important; - color: #fff; + background-color: @--apple-primary !important; + color: @--apple-on-primary; } &:before { content: none; @@ -874,7 +911,7 @@ .el-tabs__nav-prev { height: 30px; line-height: 30px; - background: #eff2f5 !important; + background: @--apple-canvas-parchment !important; } .el-tabs__nav-next, .el-tabs__nav-prev { diff --git a/renren-ui/src/constants/config.ts b/renren-ui/src/constants/config.ts index 9ca93618fcb62a7cc6125047a6847214ed04dafe..a0d46bfe54712645b0249da392061cede9f62039 100644 --- a/renren-ui/src/constants/config.ts +++ b/renren-ui/src/constants/config.ts @@ -1,16 +1,16 @@ /** - * 主题设置默认值 + * 主题设置默认值 - Apple 设计风格 */ export const themeSetting = { - sidebar: "dark", - topHeader: "primary", - themeColor: "#17B3A3", + sidebar: "light", + topHeader: "dark", + themeColor: "#0066cc", navLayout: "left", contentFull: true, - logoAuto: false, + logoAuto: true, colorIcon: false, sidebarUniOpened: true, openTabsPage: true, - tabStyle: "default", + tabStyle: "dot", sidebarCollapse: false }; diff --git a/renren-ui/src/layout/header/breadcrumb.vue b/renren-ui/src/layout/header/breadcrumb.vue index f8588d006f2655df46b31fc78395b6d219b2e0d2..6677b36f25a1ada607a2aba203bff42e3119be7e 100644 --- a/renren-ui/src/layout/header/breadcrumb.vue +++ b/renren-ui/src/layout/header/breadcrumb.vue @@ -27,7 +27,7 @@ export default defineComponent({ });