From 04ede952ac6318798f6d7906970de8374fe2446a Mon Sep 17 00:00:00 2001 From: old burden Date: Thu, 23 Apr 2026 13:01:38 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=20nanobanana=20=E6=96=B0=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- portal-ui/src/api/ai.js | 22 ++ portal-ui/src/assets/styles/base.less | 214 +++++++++++++ portal-ui/src/assets/styles/var.less | 16 +- portal-ui/src/lang/en_US/common.js | 5 + portal-ui/src/lang/en_US/route.js | 3 +- portal-ui/src/lang/i18n.js | 24 +- portal-ui/src/lang/zh_HK/common.js | 5 + portal-ui/src/lang/zh_HK/route.js | 3 +- portal-ui/src/layout/components/navBar.vue | 148 +++++---- portal-ui/src/layout/components/sideBar.vue | 68 ++--- portal-ui/src/layout/default.vue | 16 +- portal-ui/src/router/index.js | 84 +++-- portal-ui/src/views/GeneratedImages.vue | 154 ++++++---- portal-ui/src/views/Image.vue | 133 +++++++- .../java/com/ruoyi/api/ByteApiController.java | 286 +++++++++++++++--- .../com/ruoyi/api/request/ByteApiRequest.java | 10 + .../request/NanoBananaPortalImgRequest.java | 43 +++ .../api/request/NanoBananaPortalRequest.java | 35 +++ .../src/main/resources/application.yml | 6 +- .../ruoyi/ai/domain/NanoBananaRequest.java | 66 ++-- .../ruoyi/ai/domain/NanoBananaResponse.java | 5 +- .../com/ruoyi/ai/service/IByteService.java | 11 +- .../ruoyi/ai/service/impl/ByteService.java | 120 +++++++- 23 files changed, 1135 insertions(+), 342 deletions(-) create mode 100644 web-api/ruoyi-admin/src/main/java/com/ruoyi/api/request/NanoBananaPortalImgRequest.java create mode 100644 web-api/ruoyi-admin/src/main/java/com/ruoyi/api/request/NanoBananaPortalRequest.java diff --git a/portal-ui/src/api/ai.js b/portal-ui/src/api/ai.js index 12ace33..296a0a6 100644 --- a/portal-ui/src/api/ai.js +++ b/portal-ui/src/api/ai.js @@ -13,6 +13,28 @@ export function generateImage(data) { }) } +/** + * Portal 文生图:nanoApiType 为 v1 | v2 | pro,对应不同 NanoBanana 路径 + */ +export function nanoBananaGenerate(data) { + return request({ + url: '/api/ai/nano/generate', + method: 'post', + data: data + }) +} + +/** + * Portal 图生图:需传 imageUrl 或 imageUrls + */ +export function nanoBananaImgToImg(data) { + return request({ + url: '/api/ai/nano/imgToImg', + method: 'post', + data: data + }) +} + export function imgToImg(data) { return request({ url: '/api/ai/imgToImg', diff --git a/portal-ui/src/assets/styles/base.less b/portal-ui/src/assets/styles/base.less index 88e72d7..8388446 100644 --- a/portal-ui/src/assets/styles/base.less +++ b/portal-ui/src/assets/styles/base.less @@ -271,4 +271,218 @@ input:-webkit-autofill { .arco-modal-simple { width: calc(100% - 20px); } +} + +/* ====================== 新粗野主义风格 (New Brutalism) ====================== */ +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; +} + +/* 拥挤排版 & 硬派标题 */ +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; +} + +/* 所有主要元素:硬边、无圆角、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; + 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; +} + +/* 主按钮:深灰底 + 鲜红强调,冲突感 */ +.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; +} + +.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); +} + +/* 菜单 & 侧边 */ +.arco-menu, +.sidebar-container { + background: #e8e8e8 !important; + border-right: 4px solid #2c2c2c !important; +} + +.arco-menu-item, +.sidebar-container :deep(.arco-menu-item) { + border: 2px solid #2c2c2c !important; + margin: 4px 8px !important; + padding: 16px 20px !important; + font-weight: 900; + font-size: 15px; + background: #f0f0f0 !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; +} + +/* 模态 & 弹窗 - 更厚重 */ +.arco-modal-content, +.arco-drawer-content, +.arco-modal-simple { + border: 4px solid #2c2c2c !important; + box-shadow: 8px 8px 0 #2c2c2c !important; + background: #f0f0f0 !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; +} + +/* 导航栏特定 */ +.navbar { + background: #f0f0f0 !important; + border-bottom: 4px solid #2c2c2c !important; + box-shadow: 0 4px 0 #2c2c2c !important; + padding: 0 20px !important; + height: 72px !important; /* 略高以强调 */ +} + +.right-menu-item.user { + border: 2px solid #2c2c2c !important; + background: #f0f0f0 !important; + border-radius: 0 !important; + box-shadow: 4px 4px 0 #2c2c2c; +} + +/* 非对称布局辅助类 */ +.asymmetric-layout { + display: flex; + gap: 0; +} + +.asymmetric-sidebar { + margin-right: -12px; /* 轻微重叠/非对称 */ + z-index: 10; +} + +.main-content { + margin-left: 12px; + padding: 20px 32px 20px 20px; + background: #f0f0f0; + border-left: 4px solid #ff0033; +} + +/* 高对比图片,无滤镜但增强 */ +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; +} + +/* 滚动条适配粗野风格 - 硬边 */ +::-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; +} + +.logo-wrap { + border: 2px solid #2c2c2c; + padding: 4px; + box-shadow: 4px 4px 0 #2c2c2c; } \ 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 d4a8e20..e056164 100644 --- a/portal-ui/src/assets/styles/var.less +++ b/portal-ui/src/assets/styles/var.less @@ -1,10 +1,16 @@ body { - // 背景色 - 浅 - --mf-color-bg-3: #f8f8fa; - --his-border-color: rgb(166, 124, 82, 0.3); + // 新粗野主义风格 - 浅灰背景,硬边框 + --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; } body[arco-theme='dark'] { - // 背景色 - 浅 - --mf-color-bg-3: #17171a; + /* 保留暗黑支持,但优先粗野主义浅色 */ + --mf-color-bg-3: #2c2c2c; + --primary-6: 255 0 51; } \ No newline at end of file diff --git a/portal-ui/src/lang/en_US/common.js b/portal-ui/src/lang/en_US/common.js index fc55fe9..e35db76 100644 --- a/portal-ui/src/lang/en_US/common.js +++ b/portal-ui/src/lang/en_US/common.js @@ -14,6 +14,11 @@ export default { generateImage: 'Generate Now (costs {score} balance)', generateImageNow: 'Generate Now', generateTip: 'Tip: After submission, you can view it in "My Works"', + aspectRatioLabel: 'Aspect ratio', + resolutionLabel: 'Resolution', + proResolutionRequired: 'Pro API requires resolution (1K / 2K / 4K)', + nanoTaskSubmitted: 'Task submitted. ID: {id}. Results will update when ready; you can also check "My Works".', + nanoTaskSubmittedNoId: 'Task submitted. Check "My Works" later for results.', generateVideo: 'Generate Video', imageFace: 'Image Face Swap', videoFace: 'Video Face Swap', diff --git a/portal-ui/src/lang/en_US/route.js b/portal-ui/src/lang/en_US/route.js index ed15100..50704f8 100644 --- a/portal-ui/src/lang/en_US/route.js +++ b/portal-ui/src/lang/en_US/route.js @@ -8,5 +8,6 @@ export default { fastVideo: 'Gen Video', recharge: 'Quick Recharge', help: 'Help Center', - moneyInvite: 'Reward Invitation' + moneyInvite: 'Reward Invitation', + 'AI文生图': 'AI Text-to-Image' } \ No newline at end of file diff --git a/portal-ui/src/lang/i18n.js b/portal-ui/src/lang/i18n.js index 4511743..ee77072 100644 --- a/portal-ui/src/lang/i18n.js +++ b/portal-ui/src/lang/i18n.js @@ -2,25 +2,13 @@ import { createI18n } from 'vue-i18n' import Cookies from 'js-cookie' import zh_HK from '@/lang/zh_HK/index.js' import en_US from '@/lang/en_US/index.js' -import es_ES from '@/lang/es_ES/index.js' -import pt_BR from '@/lang/pt_BR/index.js' -import hi_IN from '@/lang/hi_IN/index.js' -import ru_RU from '@/lang/ru_RU/index.js' -import ar_SA from '@/lang/ar_SA/index.js' -import fr_FR from '@/lang/fr_FR/index.js' let locale = Cookies.get('language') || 'en_US' -/** 各语言在界面上的显示名称 */ +/** 各语言在界面上的显示名称 - 仅保留繁体中文和英文 */ export const LOCALE_NAMES = { zh_HK: '繁体中文', - en_US: 'English', - es_ES: 'Español', - pt_BR: 'Português', - hi_IN: 'हिन्दी', - ru_RU: 'Русский', - ar_SA: 'العربية', - fr_FR: 'Français' + en_US: 'English' } const i18n = createI18n({ @@ -28,13 +16,7 @@ const i18n = createI18n({ locale, messages: { zh_HK, - en_US, - es_ES, - pt_BR, - hi_IN, - ru_RU, - ar_SA, - fr_FR + en_US } }) diff --git a/portal-ui/src/lang/zh_HK/common.js b/portal-ui/src/lang/zh_HK/common.js index fd25525..9c9e533 100644 --- a/portal-ui/src/lang/zh_HK/common.js +++ b/portal-ui/src/lang/zh_HK/common.js @@ -14,6 +14,11 @@ export default { generateImage: '立即生成(消耗 {score} 餘額)', generateImageNow: '立即生成', generateTip: '溫馨提示:提交後可在「我的作品」中查看', + aspectRatioLabel: '寬高比', + resolutionLabel: '解析度', + proResolutionRequired: 'Pro 接口必須選擇解析度(1K / 2K / 4K)', + nanoTaskSubmitted: '任務已提交,任務 ID:{id}。生成完成後結果將更新,也可在「我的作品」查看。', + nanoTaskSubmittedNoId: '任務已提交,請稍後在「我的作品」查看結果。', generateVideo: '生成視頻', imageFace: '圖片換臉', videoFace: '視頻換臉', diff --git a/portal-ui/src/lang/zh_HK/route.js b/portal-ui/src/lang/zh_HK/route.js index daa151e..31eca36 100644 --- a/portal-ui/src/lang/zh_HK/route.js +++ b/portal-ui/src/lang/zh_HK/route.js @@ -8,5 +8,6 @@ export default { fastVideo: '快捷生視頻', recharge: '快速充值', help: '幫助中心', - moneyInvite: '有獎邀請' + moneyInvite: '有獎邀請', + 'AI文生图': 'AI 文生圖' } \ No newline at end of file diff --git a/portal-ui/src/layout/components/navBar.vue b/portal-ui/src/layout/components/navBar.vue index 869d5a4..958568b 100644 --- a/portal-ui/src/layout/components/navBar.vue +++ b/portal-ui/src/layout/components/navBar.vue @@ -246,8 +246,12 @@ export default { align-items: center; justify-content: space-between; box-sizing: content-box; - padding-left: 30px; - padding-right: 30px; + padding-left: 24px; + padding-right: 24px; + height: 72px; + background: #f0f0f0; + border-bottom: 4px solid #2c2c2c; + box-shadow: 0 4px 0 #2c2c2c; .left { display: flex; @@ -256,7 +260,9 @@ export default { &-collapse { display: none; - transition: 0.25s; + transition: 0.1s linear; + color: #2c2c2c; + font-size: 28px; &.isCollapse { transform: rotate(-180deg); @@ -267,34 +273,46 @@ export default { padding-left: 8px; display: flex; align-items: center; - height: 60px; + height: 72px; .logo { display: flex; align-items: flex-end; cursor: pointer; - color: var(--color-text-1); + color: #2c2c2c; + font-weight: 900; + letter-spacing: -1px; + font-size: 26px; + text-transform: uppercase; &-wrap { display: flex; align-items: center; + padding: 4px 12px; + border: 3px solid #2c2c2c; + box-shadow: 4px 4px 0 #ff0033; + background: #f0f0f0; } } } } .right-menu { - height: 60px; + height: 72px; display: flex; align-items: center; justify-content: flex-end; flex: 1; + gap: 12px; &-item { display: flex; align-items: center; - height: 60px; - margin-right: 12px; + height: 48px; + margin-right: 8px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.5px; &:last-child { margin-right: 0; @@ -302,45 +320,57 @@ export default { &.language { .mf-button { - font-size: 14px; - color: #999999; - background-color: transparent; + font-size: 15px; + color: #2c2c2c; + background-color: #f0f0f0; + border: 2px solid #2c2c2c; + box-shadow: 4px 4px 0 #2c2c2c; &:hover { - background-color: transparent; + background-color: #ff0033; + color: #f0f0f0; + transform: translate(2px, 2px); + box-shadow: 2px 2px 0 #2c2c2c; } } } &.logout { .mf-button { - background-color: transparent; - + background-color: #f0f0f0; + border: 2px solid #2c2c2c; + color: #2c2c2c; + box-shadow: 4px 4px 0 #2c2c2c; &:hover { - background-color: transparent; + background-color: #ff0033; + color: #f0f0f0; + transform: translate(2px, 2px); + box-shadow: 2px 2px 0 #2c2c2c; } } } .login { - width: 100px; - border-radius: 10px; + width: 110px; cursor: pointer; + /* 粗野主义按钮由全局base.less样式覆盖 */ } &.user { - width: 150px; - color: rgb(var(--primary-6)); - border: 2px solid transparent; - height: 32px; - border: 1px solid #5c5d68; - background: #26272e; - border-radius: 16px; + width: 170px; + color: #2c2c2c; + height: 44px; display: flex; align-items: center; justify-content: center; - padding-left: 16px; - padding-right: 4px; + padding-left: 12px; + padding-right: 8px; cursor: pointer; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.5px; + border: 2px solid #2c2c2c; + background: #f0f0f0; + box-shadow: 4px 4px 0 #2c2c2c; span { display: flex; @@ -348,6 +378,7 @@ export default { flex-grow: 1; .arco-image { margin-right: 8px; + border: 1px solid #2c2c2c; } } } @@ -356,54 +387,57 @@ export default { } .nav-point-name { - color: var(--color-text-3); + color: #2c2c2c; + font-weight: 900; } .user-info { - width: 220px; + width: 240px; margin-left: -8px; + border: 2px solid #2c2c2c; + background: #f0f0f0; + box-shadow: 4px 4px 0 #2c2c2c; .arco-dropdown-option { .mf-divider { - margin: 5px 0; + margin: 8px 0; + border-color: #2c2c2c; } .user-info-wrap { - padding-top: 8px; - padding-bottom: 8px; + padding: 16px; + border: 2px solid #2c2c2c; + background: #f0f0f0; .mf-avatar { - margin-right: 8px; + margin-right: 12px; flex-shrink: 0; + border: 2px solid #2c2c2c; } .user-info-name { - width: 150px; - font-weight: 600; - color: var(--color-text-1); - font-size: 14px; - margin-bottom: 0px; - } - - .user-info-company { - width: 150px; - font-size: 12px; - color: var(--color-text-3); - // margin-top: 3px; - margin-bottom: 0px; + width: 160px; + font-weight: 900; + color: #2c2c2c; + font-size: 16px; + letter-spacing: -0.5px; + margin-bottom: 4px; } + .user-info-company, .user-info-service { - width: 150px; - font-size: 12px; - color: var(--color-text-4); - // margin-top: 3px; - margin-bottom: 0px; + width: 160px; + font-size: 13px; + color: #2c2c2c; + font-weight: 700; + margin-bottom: 4px; + letter-spacing: -0.3px; } } &:first-child { &:hover { - background-color: transparent; + background-color: #ff0033; + color: #f0f0f0; } cursor: text; } @@ -412,11 +446,13 @@ export default { @media (max-width: 576px) { .navbar { - padding: 0 10px; + padding: 0 12px; + height: auto; .left { &-collapse { display: block; - font-size: 18px; + font-size: 24px; + color: #2c2c2c; } &-menu { @@ -424,15 +460,13 @@ export default { .logo { display: none; } - .mf-icon { - color: #fff; - } } } .right-menu-item { &.user { - width: 136px; + width: 140px; + font-size: 13px; } } } diff --git a/portal-ui/src/layout/components/sideBar.vue b/portal-ui/src/layout/components/sideBar.vue index e02bb05..fab116e 100644 --- a/portal-ui/src/layout/components/sideBar.vue +++ b/portal-ui/src/layout/components/sideBar.vue @@ -110,65 +110,52 @@ export default { .sidebar-container { height: 100%; position: relative; - transition: width 0.3s; + transition: width 0.1s linear; z-index: 12; - background-color: #000; - border-right: 1px solid; /* 宽度随你调整 */ - border-image: linear-gradient( - to bottom, - #0f0f12 0%, - rgba(255,255,255, 0.7) 40%, - rgba(255,255,255, 0.7) 60%, - #0f0f12 100% - ) - 1 100%; + background-color: #f0f0f0; + border-right: 4px solid #2c2c2c; + box-shadow: 4px 0 0 #ff0033; /* 非对称强调 */ &.collapsed { - width: 50px !important; + width: 56px !important; overflow: hidden; :deep(.arco-menu) { - padding-left: 0px; + padding-left: 4px; } } :deep(.arco-menu) { - background-color: transparent; - padding-left: 16px; + background-color: #f0f0f0; + padding-left: 8px; + border: 2px solid #2c2c2c; &-item { width: 180px; - background-color: transparent; - color: rgba(255, 255, 255, 0.7); - border-radius: 10px; - margin-bottom: 12px; + background-color: #f0f0f0; + color: #2c2c2c; + border: 2px solid #2c2c2c; + margin: 8px 4px; + padding: 14px 18px; + font-weight: 900; + font-size: 15px; + box-shadow: 3px 3px 0 #2c2c2c; + transition: all 0.1s linear; &.arco-menu-selected, &:hover { - background-color: rgb(var(--primary-6)); + background-color: #ff0033 !important; + color: #f0f0f0 !important; + border-color: #2c2c2c; + transform: translate(3px, 3px); + box-shadow: 1px 1px 0 #2c2c2c; } } } + /* 隐藏旧的toggle,如果需要可重新启用 */ .toogle-menu { - position: absolute; - bottom: 16px; - right: 10px; - .navicon { - color: #fff; - transition: 0.25s; - padding: 5px; - height: auto; - line-height: normal; - - &:hover { - background-color: rgba(255, 255, 255, 0.2); - } - - &.isCollapsed { - transform: rotate(-180deg); - } - } + display: none; } } @@ -176,10 +163,11 @@ export default { .sidebar-container { position: fixed; left: 0px; - top: 60px; + top: 72px; z-index: 10; - height: calc(100% - 60px); + height: calc(100% - 72px); overflow: hidden; + border-right: 4px solid #2c2c2c; &.collapsed { width: 0px !important; diff --git a/portal-ui/src/layout/default.vue b/portal-ui/src/layout/default.vue index 2645d08..6ad85a3 100644 --- a/portal-ui/src/layout/default.vue +++ b/portal-ui/src/layout/default.vue @@ -1,5 +1,5 @@