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({
});
-
+
主页
{{ currentRoute.query._mt || x.title || "" }}
diff --git a/renren-ui/src/layout/header/expand.vue b/renren-ui/src/layout/header/expand.vue
index 3ec7a6522d7eac89e205b2ab24913797c118bcc0..d4f7d68569aead721b808b62e9407940b34a941f 100644
--- a/renren-ui/src/layout/header/expand.vue
+++ b/renren-ui/src/layout/header/expand.vue
@@ -5,8 +5,6 @@ import { useFullscreen } from "@vueuse/core";
import { defineComponent } from "vue";
import { useRouter } from "vue-router";
import { useAppStore } from "@/store";
-import userLogo from "@/assets/images/user.png";
-import "@/assets/css/header.less";
import { ElMessageBox } from "element-plus";
interface IExpand {
@@ -50,7 +48,6 @@ export default defineComponent({
props,
store,
isFullscreen,
- userLogo,
onClickUserMenus,
toggle
};
@@ -59,23 +56,12 @@ export default defineComponent({