diff --git a/.cursor/rules/java-alibaba-standards.mdc b/.cursor/rules/java-alibaba-standards.mdc new file mode 100644 index 0000000..d7aef36 --- /dev/null +++ b/.cursor/rules/java-alibaba-standards.mdc @@ -0,0 +1,56 @@ +--- +description: 若依项目Java代码生成规则 - 阿里巴巴规范、命名、实体类、MyBatis、接口路径 +alwaysApply: true +--- + +# 项目背景 + +你的web-api是一个基于若依(RuoYi)框架的Spring Boot项目。 + +# 代码生成规则 + +## 1. 命名规范 +- 所有Java代码必须符合**阿里巴巴Java开发手册规范**(包括命名、注释、异常处理等)。 +- 类名、方法名、变量名必须严格使用**驼峰命名法**(CamelCase)。 + - 类名:UpperCamelCase(如 `TosAssetService`) + - 方法名、变量名:lowerCamelCase(如 `getAssetById`、`assetList`) + +## 2. 实体类(Entity/Domain) +- 必须使用 **Lombok** 注解简化代码: + ```java + @Data + @TableName("tos_asset") // 或 @Entity + public class TosAsset { + // 字段... + } + ``` +- 所有字段和类**必须添加清晰的中文或英文注释**(推荐使用中文)。 +- 必须包含 `serialVersionUID`(如果适用)。 + +## 3. 数据库操作(MyBatis) +- 必须使用 **MyBatis**(推荐 MyBatis-Plus)进行数据库操作。 +- **禁止**在 `mapper.xml` 中编写过于复杂的SQL语句。 +- 复杂查询应在 Service 层使用 Java 代码组合,或使用 MyBatis-Plus 的 LambdaQueryWrapper。 +- Mapper 接口方法名必须语义清晰,并添加注释。 + +## 4. Controller 接口路径 +- 给 **portal-ui**(前端门户)统一以 `/api` 开头,例如: + ```java + @GetMapping("/api/assets/list") + ``` +- 给 **admin-ui**(管理后台)使用以 `/` 开头(保持若依原有风格),例如: + ```java + @GetMapping("/assets/list") + ``` +- 建议在不同 Controller 中区分(如 `AiPortalController` vs `AiManagerController`)。 + +## 5. 其他要求 +- 所有新增类必须包含完整的 Javadoc 注释。 +- Service 实现类必须实现对应接口。 +- 避免直接在 Controller 中处理业务逻辑,保持分层清晰。 +- 异常处理统一使用 `ServiceException` 或项目自定义异常。 +- 业务逻辑遵循mvc原则放在service +- redis key 统一放在RedisKey类 + + +**始终严格遵守以上规则生成所有Java代码。** diff --git a/.cursor/rules/vue-api-calling.mdc b/.cursor/rules/vue-api-calling.mdc new file mode 100644 index 0000000..7cb683f --- /dev/null +++ b/.cursor/rules/vue-api-calling.mdc @@ -0,0 +1,61 @@ +--- +alwaysApply: false +--- +# portal-ui API调用规范 + +## 1. 接口路径要求 +- 所有调用 **portal-ui** 的接口必须使用 `/api` 开头(与后端 Java Controller 中的 `@RequestMapping("/api/...")` 对应)。 +- 示例: + ```ts + // 正确 + const res = await api.get('/api/assets/list', { params }) + + // 错误(不要省略 /api) + const res = await api.get('/assets/list', { params }) + ``` + +## 2. API封装规范 +- 统一使用项目中已封装的 `api` 实例(通常位于 `src/api/index.ts` 或 `src/utils/request.ts`)。 +- 不要直接使用 `axios` 创建新实例。 +- 请求方法统一使用: + - `GET`、`POST`、`PUT`、`DELETE` + - 复杂查询优先使用 `POST` + 请求体。 + +## 3. 类型定义要求 +- 所有请求和响应必须定义 **TypeScript 接口**(放在 `src/types/` 或对应模块的 `types.ts` 中)。 +- 命名规范: + - 请求:`ListAssetsParams`、`CreateAssetRequest` + - 响应:`ListAssetsResponse`、`ApiResponse` +- 示例: + ```ts + export interface ListAssetsParams { + isTop?: 'Y' | 'N' + beginTime?: string + endTime?: string + pageNum?: number + pageSize?: number + } + + export interface ListAssetsResponse { + code: number + data: { + list: AssetItem[] + total: number + } + message: string + } + ``` + +## 4. 错误处理规范 +- 使用统一的错误处理拦截器(项目已有全局拦截)。 +- 业务错误统一抛出或使用 `ElMessage.error()` / `a-message`。 +- 网络错误、超时、401/403 等状态码必须有清晰的用户提示。 +- 重要操作(删除、收藏等)必须添加二次确认。 + +## 5. 其他最佳实践 +- 使用 `async/await`,避免 `.then().catch()` 链式调用。 +- Loading 状态统一使用 `loading` 变量 + Ant Design `a-spin`。 +- 分页相关参数统一使用 `pageNum` / `pageSize`。 +- 所有API调用必须添加中文注释说明用途。 + +**所有前端与后端交互的代码必须严格遵守以上规则。** diff --git a/.cursor/rules/vue-component-standards.mdc b/.cursor/rules/vue-component-standards.mdc new file mode 100644 index 0000000..ed8c094 --- /dev/null +++ b/.cursor/rules/vue-component-standards.mdc @@ -0,0 +1,57 @@ +--- +description: Vue 3 组件开发规范(portal-ui 项目专用) +alwaysApply: true +--- + +# Vue组件开发规范(portal-ui) + +## 1. 文件结构 +所有 `.vue` 文件必须严格遵循以下结构: + +```vue + + + + + +``` + +## 2. 命名规范 +- 组件文件名和组件名:**PascalCase**(如 `AssetPreviewModal.vue`) +- 事件名:kebab-case(如 `update:visible`、`preview-open`) +- Props:camelCase 定义 +- 组合式函数:`useXXX.ts`(如 `useAssetList.ts`) + +## 3. 技术栈要求 +- **优先使用** ` diff --git a/portal-ui/src/assets/styles/base.less b/portal-ui/src/assets/styles/base.less index 8388446..7197c38 100644 --- a/portal-ui/src/assets/styles/base.less +++ b/portal-ui/src/assets/styles/base.less @@ -17,31 +17,33 @@ body { body { font-size: 14px; -webkit-font-smoothing: antialiased; - color: #1f2329; - font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif; + color: rgba(232, 236, 245, 0.92); + font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif; overflow: hidden; position: relative; + background: #0d0d0d; } // firefox 滚动条样式 * { - scrollbar-color: rgba(144, 147, 153, 0.5) #0f0f12; + scrollbar-color: rgba(136, 153, 255, 0.45) #141418; } /*滚动条整体样式*/ ::-webkit-scrollbar { - background-color: #0f0f12; + background-color: #141418; } /*滚动条里面小方块*/ ::-webkit-scrollbar-thumb { - background-color: red; - border: 3px solid #0f0f12; + background: linear-gradient(180deg, rgba(120, 170, 255, 0.55), rgba(160, 130, 255, 0.5)); + border: 2px solid #141418; + border-radius: 6px; } /*滚动条里面轨道*/ ::-webkit-scrollbar-track { - background: #0f0f12; + background: #141418; } :focus { @@ -240,14 +242,17 @@ input:-webkit-autofill { width: 100vw !important; height: 100vh !important; z-index: 100 !important; - background: #fff; + background: #111111; padding: 12px !important; } .arco-modal-simple { - background: #0f0f12; - border-radius: 10px; - border: 2px solid #e6217a; + background: rgba(20, 20, 20, 0.92); + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border-radius: 12px; + border: 1px solid rgba(140, 180, 255, 0.28); + box-shadow: 0 0 40px rgba(60, 100, 200, 0.15); .arco-modal-title { color: #fff; } @@ -273,216 +278,248 @@ input:-webkit-autofill { } } -/* ====================== 新粗野主义风格 (New Brutalism) ====================== */ +/* ====================== 暗黑实验室风 (Dark Lab) ====================== */ + +.lab-glass() { + background: rgba(20, 20, 20, 0.8) !important; + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); + border: 1px solid rgba(140, 180, 255, 0.28) !important; + box-shadow: + 0 0 0 1px rgba(120, 170, 255, 0.08), + 0 0 28px rgba(80, 120, 220, 0.12) !important; + border-radius: 10px !important; +} + body { - font-size: 16px; - -webkit-font-smoothing: antialiased; - color: #2c2c2c; - font-family: 'Impact', 'Arial Black', 'Helvetica Neue', 'Microsoft YaHei', system-ui, sans-serif; - font-weight: 900; - line-height: 1.05; - letter-spacing: -0.025em; - overflow: hidden; - position: relative; - background: #f0f0f0; - /* 粗糙肌理感:模拟混凝土/纸张纹理,使用细网格噪声 */ - background-image: - linear-gradient(45deg, rgba(44, 44, 44, 0.015) 1px, transparent 1px), - linear-gradient(-45deg, rgba(44, 44, 44, 0.01) 1px, transparent 1px); - background-size: 5px 5px; + font-size: 14px; + line-height: 1.5; + letter-spacing: 0; + font-weight: 400; + background: #0d0d0d; + background-image: + radial-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), + linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px); + background-size: + 12px 12px, + 24px 24px, + 24px 24px; + background-position: 0 0, 0 0, 0 0; } -/* 拥挤排版 & 硬派标题 */ -h1, h2, h3, h4, h5, h6, .arco-typography, .title { - font-family: 'Impact', 'Arial Black', sans-serif; - font-weight: 900; - letter-spacing: -0.05em; - line-height: 0.95; - margin: 0 0 4px 0; - text-transform: uppercase; +h1, +h2, +h3, +h4, +h5, +h6, +.arco-typography, +.title { + font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif; + font-weight: 600; + letter-spacing: -0.02em; + line-height: 1.25; + margin: 0 0 8px 0; + color: rgba(245, 247, 252, 0.96); } -/* 所有主要元素:硬边、无圆角、4px硬阴影、2px边框 */ -.brutalist, -.arco-card, -.arco-btn, -.arco-menu, -.arco-modal-content, -.arco-drawer-content, -.input-wrapper, -.arco-input-wrapper, -.arco-select, -.arco-textarea, -.arco-input, -select, -textarea, -input, -.mf-button, -.mf-pane, -.card, -.panel, -.navbar, -.sidebar-container, -.app-wrapper { - border: 2px solid #2c2c2c !important; +/* 生成画布:5% 层级点阵 + 网格(类可单独用于右侧预览区) */ +.lab-canvas-bg { + background-color: #111111 !important; + background-image: + radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), + linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px) !important; + background-size: 14px 14px, 22px 22px, 22px 22px !important; + background-position: 0 0, 0 0, 0 0 !important; +} + +.lab-glass-inner { + .lab-glass(); + box-shadow: + 0 0 0 1px rgba(120, 170, 255, 0.12), + inset 0 1px 0 rgba(255, 255, 255, 0.04) !important; +} + +.dark-lab .app-wrapper, +.dark-lab, +.app-wrapper.dark-lab { + background: #0d0d0d !important; + border: none !important; + box-shadow: none !important; + color: rgba(232, 236, 245, 0.92) !important; border-radius: 0 !important; - box-shadow: 4px 4px 0 #2c2c2c !important; - background-color: #f0f0f0 !important; - color: #2c2c2c !important; - transition: all 0.1s linear !important; } -.brutalist:active, -.arco-btn:active, -.arco-menu-item:active, -.mf-button:active { - transform: translate(2px, 2px); - box-shadow: 2px 2px 0 #2c2c2c !important; +.dark-lab .arco-card, +.dark-lab .mf-pane, +.dark-lab .card, +.dark-lab .panel { + .lab-glass(); + color: rgba(232, 236, 245, 0.92) !important; + transition: border-color 0.2s ease, box-shadow 0.2s ease !important; } -/* 主按钮:深灰底 + 鲜红强调,冲突感 */ -.arco-btn, -.mf-button { - background: #2c2c2c !important; - color: #f0f0f0 !important; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 1px; - padding: 12px 24px !important; - font-size: 15px; - border: 2px solid #2c2c2c !important; - box-shadow: 4px 4px 0 #2c2c2c !important; +.dark-lab .arco-btn:not(.arco-btn-primary), +.dark-lab .mf-button:not([type='primary']) { + background: rgba(28, 28, 32, 0.9) !important; + color: rgba(232, 236, 245, 0.92) !important; + border: 1px solid rgba(140, 180, 255, 0.22) !important; + box-shadow: 0 0 18px rgba(70, 100, 200, 0.08) !important; + border-radius: 10px !important; + font-weight: 500; + letter-spacing: 0.02em; + text-transform: none; } -.arco-btn:hover, -.arco-btn-primary, -.mf-button:hover, -.mf-button[type="primary"] { - background: #ff0033 !important; - color: #f0f0f0 !important; - border-color: #2c2c2c !important; - box-shadow: 6px 6px 0 #2c2c2c !important; - transform: translate(-1px, -1px); +.dark-lab .arco-btn-primary, +.dark-lab .mf-button[type='primary'], +.dark-lab .mf-button.arco-btn-primary { + background: linear-gradient( + 135deg, + rgba(130, 185, 255, 0.95) 0%, + rgba(175, 145, 255, 0.92) 100% + ) !important; + color: #0d0d0d !important; + border: 1px solid rgba(200, 220, 255, 0.45) !important; + box-shadow: + 0 0 24px rgba(120, 170, 255, 0.35), + 0 0 48px rgba(160, 130, 255, 0.15) !important; + border-radius: 10px !important; + font-weight: 600; + text-transform: none; + letter-spacing: 0.02em; } -/* 菜单 & 侧边 */ -.arco-menu, -.sidebar-container { - background: #e8e8e8 !important; - border-right: 4px solid #2c2c2c !important; +.dark-lab .arco-btn-primary:hover, +.dark-lab .mf-button[type='primary']:hover { + filter: brightness(1.06); + box-shadow: + 0 0 32px rgba(130, 185, 255, 0.45), + 0 0 56px rgba(160, 130, 255, 0.22) !important; + transform: translateY(-1px); } -.arco-menu-item, -.sidebar-container :deep(.arco-menu-item) { - border: 2px solid #2c2c2c !important; +.dark-lab .arco-input-wrapper, +.dark-lab .arco-textarea-wrapper, +.dark-lab .arco-select-view-single, +.dark-lab .arco-select, +.dark-lab .input-wrapper, +.dark-lab input:not([type='checkbox']):not([type='radio']), +.dark-lab select, +.dark-lab textarea { + .lab-glass(); + color: rgba(232, 236, 245, 0.92) !important; + border-radius: 10px !important; + box-shadow: + 0 0 0 1px rgba(120, 170, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.03) !important; +} + +.dark-lab .arco-menu { + background: rgba(18, 18, 22, 0.92) !important; + border-right: 1px solid rgba(140, 180, 255, 0.18) !important; + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.35) !important; + border-radius: 0 !important; +} + +.dark-lab .arco-menu-item { + background: transparent !important; + border: none !important; margin: 4px 8px !important; - padding: 16px 20px !important; - font-weight: 900; - font-size: 15px; - background: #f0f0f0 !important; + padding: 12px 16px !important; + font-weight: 500; + font-size: 14px; + color: rgba(200, 210, 230, 0.88) !important; + box-shadow: none !important; + border-radius: 8px !important; } -.arco-menu-item:hover, -.arco-menu-item.arco-menu-selected, -.sidebar-container :deep(.arco-menu-item:hover), -.sidebar-container :deep(.arco-menu-selected) { - background: #ff0033 !important; - color: #f0f0f0 !important; - border-color: #2c2c2c; - transform: translate(2px, 2px); - box-shadow: 4px 4px 0 #2c2c2c; +.dark-lab .arco-menu-item:hover, +.dark-lab .arco-menu-item.arco-menu-selected { + background: rgba(136, 153, 255, 0.12) !important; + color: #e8eeff !important; + box-shadow: 0 0 20px rgba(120, 170, 255, 0.12) !important; + transform: none; } -/* 模态 & 弹窗 - 更厚重 */ -.arco-modal-content, -.arco-drawer-content, -.arco-modal-simple { - border: 4px solid #2c2c2c !important; - box-shadow: 8px 8px 0 #2c2c2c !important; - background: #f0f0f0 !important; +.dark-lab .arco-modal-content, +.dark-lab .arco-drawer-content { + .lab-glass(); + border-radius: 12px !important; } -.arco-modal-title, -.arco-modal-header { - background: #2c2c2c !important; - color: #f0f0f0 !important; - border-bottom: 4px solid #ff0033 !important; - padding: 16px !important; - font-size: 18px; - text-transform: uppercase; +.dark-lab .arco-modal-header, +.dark-lab .arco-modal-title { + background: rgba(14, 14, 18, 0.95) !important; + color: rgba(245, 247, 252, 0.96) !important; + border-bottom: 1px solid rgba(140, 180, 255, 0.2) !important; + text-transform: none; + font-weight: 600; } -/* 导航栏特定 */ -.navbar { - background: #f0f0f0 !important; - border-bottom: 4px solid #2c2c2c !important; - box-shadow: 0 4px 0 #2c2c2c !important; - padding: 0 20px !important; - height: 72px !important; /* 略高以强调 */ +.dark-lab .navbar { + background: rgba(14, 14, 18, 0.85) !important; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-bottom: 1px solid rgba(140, 180, 255, 0.2) !important; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45) !important; } -.right-menu-item.user { - border: 2px solid #2c2c2c !important; - background: #f0f0f0 !important; - border-radius: 0 !important; - box-shadow: 4px 4px 0 #2c2c2c; +.dark-lab .right-menu-item.user { + border: 1px solid rgba(140, 180, 255, 0.22) !important; + background: rgba(20, 20, 24, 0.75) !important; + border-radius: 10px !important; + box-shadow: 0 0 18px rgba(80, 120, 200, 0.1) !important; + color: rgba(232, 236, 245, 0.92) !important; } -/* 非对称布局辅助类 */ .asymmetric-layout { display: flex; gap: 0; } .asymmetric-sidebar { - margin-right: -12px; /* 轻微重叠/非对称 */ + margin-right: 0; z-index: 10; } .main-content { - margin-left: 12px; - padding: 20px 32px 20px 20px; - background: #f0f0f0; - border-left: 4px solid #ff0033; + margin-left: 0; + padding: 20px 28px; + background: transparent; + border-left: none; } -/* 高对比图片,无滤镜但增强 */ -img:not(.logo img), -.arco-image img, -.preview-image { - filter: contrast(1.15) saturate(1.1); - border: 3px solid #2c2c2c; - box-shadow: 6px 6px 0 #2c2c2c; - image-rendering: crisp-edges; +/* 预览图:柔化边框,略浮起 */ +.dark-lab img:not(.logo img):not(.wallet img), +.dark-lab .arco-image img, +.dark-lab .preview-image { + filter: none; + border: 1px solid rgba(140, 180, 255, 0.2) !important; + box-shadow: + 0 12px 40px rgba(0, 0, 0, 0.55), + 0 0 0 1px rgba(255, 255, 255, 0.04) !important; + border-radius: 10px !important; + image-rendering: auto; } -/* 滚动条适配粗野风格 - 硬边 */ -::-webkit-scrollbar { - width: 12px; - height: 12px; - background: #f0f0f0; - border: 2px solid #2c2c2c; -} - -::-webkit-scrollbar-thumb { - background: #2c2c2c; - border: 2px solid #f0f0f0; - box-shadow: 2px 2px 0 #ff0033; -} - -::-webkit-scrollbar-track { - background: #e0e0e0; - border: 2px solid #2c2c2c; -} - -/* 强调冲突色 */ .accent { - color: #ff0033; - font-weight: 900; + color: #a8bfff; + font-weight: 600; } -.logo-wrap { - border: 2px solid #2c2c2c; - padding: 4px; - box-shadow: 4px 4px 0 #2c2c2c; +.dark-lab .logo-wrap { + border: 1px solid rgba(140, 180, 255, 0.28); + padding: 4px 10px; + border-radius: 10px; + box-shadow: 0 0 20px rgba(100, 140, 220, 0.12); + background: rgba(20, 20, 24, 0.6); +} + +/* 兼容:未挂 dark-lab 的裸组件仍使用实验室输入质感 */ +.arco-input-wrapper, +.arco-textarea-wrapper { + border-radius: 10px !important; } \ No newline at end of file diff --git a/portal-ui/src/assets/styles/var.less b/portal-ui/src/assets/styles/var.less index e056164..159bea7 100644 --- a/portal-ui/src/assets/styles/var.less +++ b/portal-ui/src/assets/styles/var.less @@ -1,16 +1,17 @@ body { - // 新粗野主义风格 - 浅灰背景,硬边框 - --mf-color-bg-3: #f0f0f0; - --his-border-color: #2c2c2c; - --primary-6: 255 0 51; /* #ff0033 鲜红强调色 */ - --color-text-1: #2c2c2c; - --color-text-2: #2c2c2c; - --color-bg-1: #f0f0f0; - --color-bg-2: #e0e0e0; + /* 暗黑实验室:深底 + 霓虹蓝紫主色 */ + --mf-color-bg-3: #111111; + --his-border-color: rgba(140, 180, 255, 0.28); + --primary-6: 136 153 255; /* #8899ff */ + --color-text-1: rgba(232, 236, 245, 0.94); + --color-text-2: rgba(200, 210, 230, 0.85); + --color-bg-1: #0d0d0d; + --color-bg-2: #141418; } body[arco-theme='dark'] { - /* 保留暗黑支持,但优先粗野主义浅色 */ - --mf-color-bg-3: #2c2c2c; - --primary-6: 255 0 51; + --mf-color-bg-3: #141418; + --primary-6: 136 153 255; + --color-text-1: rgba(232, 236, 245, 0.94); + --color-bg-1: #0d0d0d; } \ No newline at end of file diff --git a/portal-ui/src/components/Forbidden.vue b/portal-ui/src/components/Forbidden.vue deleted file mode 100644 index 5b4e8e6..0000000 --- a/portal-ui/src/components/Forbidden.vue +++ /dev/null @@ -1,186 +0,0 @@ - - - - - diff --git a/portal-ui/src/components/PromptHighlightTextarea.vue b/portal-ui/src/components/PromptHighlightTextarea.vue new file mode 100644 index 0000000..7f84546 --- /dev/null +++ b/portal-ui/src/components/PromptHighlightTextarea.vue @@ -0,0 +1,186 @@ +