From f1b7a7aae8af3b59d589a8c982f91135a1c6cfbb Mon Sep 17 00:00:00 2001 From: supernijia Date: Fri, 24 Jul 2026 13:26:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shopkey/service/ShopManageService.java | 14 ++++ .../service/ZiniaoShopSwitchService.java | 20 ++++- .../service/ShopManageServiceTest.java | 50 +++++++++++ .../service/ZiniaoShopSwitchServiceTest.java | 83 +++++++++++++++++++ .../src/pages/image-video/ImageVideoPage.vue | 9 +- .../components/ZiniaoVersionSetting.vue | 15 ++-- frontend-vue/src/styles/main.css | 41 +++------ frontend-vue/src/styles/reset.css | 3 +- 8 files changed, 192 insertions(+), 43 deletions(-) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/shopkey/service/ShopManageServiceTest.java create mode 100644 backend-java/src/test/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchServiceTest.java diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java index 5ab2b6f7..7f5d1d8e 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java @@ -152,6 +152,20 @@ public class ShopManageService { return vo; } + /** + * 查询匹配业务允许使用的后台店铺;不存在时返回统一中文业务错误。 + */ + public ShopManageEntity requireShopByName(String shopName) { + String normalizedShopName = normalizeRequired(shopName, "店铺名称不能为空"); + ShopManageEntity entity = shopManageMapper.selectOne(new LambdaQueryWrapper() + .eq(ShopManageEntity::getShopName, normalizedShopName) + .last("limit 1")); + if (entity == null) { + throw new BusinessException("后台店铺管理中未找到店铺:" + normalizedShopName + ",请先添加店铺信息"); + } + return entity; + } + public String findMallNameByShopName(String shopName) { String normalizedShopName = normalizeRequired(shopName, "shopName required"); ShopManageEntity entity = shopManageMapper.selectOne(new LambdaQueryWrapper() diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java index 250a60db..05f98cc7 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchService.java @@ -1,6 +1,7 @@ package com.nanri.aiimage.modules.ziniao.service; import com.nanri.aiimage.common.exception.BusinessException; +import com.nanri.aiimage.modules.shopkey.service.ShopManageService; import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -11,9 +12,10 @@ public class ZiniaoShopSwitchService { private final ZiniaoAuthService ziniaoAuthService; private final ZiniaoShopIndexService ziniaoShopIndexService; + private final ShopManageService shopManageService; public ZiniaoShopMatchResultVo matchStoreByNameAcrossStaff(String targetShopName, Long preferUserId) { - String normalizedShopName = normalizeShopName(targetShopName); + String normalizedShopName = requireManagedShopName(targetShopName); if (normalizedShopName.isBlank()) { return emptyMatchResult(); } @@ -31,11 +33,15 @@ public class ZiniaoShopSwitchService { } public ZiniaoShopMatchResultVo findIndexedStoreByName(String targetShopName) { - return ziniaoShopIndexService.findIndexedStoreByName(targetShopName); + return findIndexedStoreByName(targetShopName, true); } public ZiniaoShopMatchResultVo findIndexedStoreByName(String targetShopName, boolean buildOpenStoreUrl) { - return ziniaoShopIndexService.findIndexedStoreByName(targetShopName, buildOpenStoreUrl); + String normalizedShopName = requireManagedShopName(targetShopName); + if (normalizedShopName.isBlank()) { + return emptyMatchResult(); + } + return ziniaoShopIndexService.findIndexedStoreByName(normalizedShopName, buildOpenStoreUrl); } public ZiniaoShopMatchResultVo emptyMatchResult() { @@ -53,6 +59,14 @@ public class ZiniaoShopSwitchService { return value.replace(" ", " ").trim(); } + private String requireManagedShopName(String targetShopName) { + String normalizedShopName = normalizeShopName(targetShopName); + if (!normalizedShopName.isBlank()) { + shopManageService.requireShopByName(normalizedShopName); + } + return normalizedShopName; + } + public boolean isSkippableMatchError(BusinessException ex) { String message = ex == null ? null : ex.getMessage(); if (message == null || message.isBlank()) { diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/shopkey/service/ShopManageServiceTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/shopkey/service/ShopManageServiceTest.java new file mode 100644 index 00000000..6e6850b6 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/shopkey/service/ShopManageServiceTest.java @@ -0,0 +1,50 @@ +package com.nanri.aiimage.modules.shopkey.service; + +import com.nanri.aiimage.common.exception.BusinessException; +import com.nanri.aiimage.common.security.ShopCredentialCryptoService; +import com.nanri.aiimage.modules.shopkey.mapper.ShopManageMapper; +import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageEntity; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ShopManageServiceTest { + + @Mock + private ShopManageMapper shopManageMapper; + @Mock + private ShopManageGroupService shopManageGroupService; + @Mock + private ShopCredentialCryptoService shopCredentialCryptoService; + + @InjectMocks + private ShopManageService service; + + @Test + void requireShopByNameReturnsManagedShop() { + ShopManageEntity entity = new ShopManageEntity(); + entity.setShopName("测试店铺"); + when(shopManageMapper.selectOne(any())).thenReturn(entity); + + assertSame(entity, service.requireShopByName(" 测试店铺 ")); + } + + @Test + void requireShopByNameReturnsChineseErrorWhenMissing() { + when(shopManageMapper.selectOne(any())).thenReturn(null); + + BusinessException exception = assertThrows(BusinessException.class, + () -> service.requireShopByName("缺失店铺")); + + assertEquals("后台店铺管理中未找到店铺:缺失店铺,请先添加店铺信息", exception.getMessage()); + } +} diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchServiceTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchServiceTest.java new file mode 100644 index 00000000..0fac9ea7 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopSwitchServiceTest.java @@ -0,0 +1,83 @@ +package com.nanri.aiimage.modules.ziniao.service; + +import com.nanri.aiimage.common.exception.BusinessException; +import com.nanri.aiimage.modules.shopkey.service.ShopManageService; +import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ZiniaoShopSwitchServiceTest { + + @Mock + private ZiniaoAuthService ziniaoAuthService; + @Mock + private ZiniaoShopIndexService ziniaoShopIndexService; + @Mock + private ShopManageService shopManageService; + + @Test + void missingManagedShopStopsIndexedLookup() { + String message = "后台店铺管理中未找到店铺:缺失店铺,请先添加店铺信息"; + when(shopManageService.requireShopByName("缺失店铺")) + .thenThrow(new BusinessException(message)); + ZiniaoShopSwitchService service = new ZiniaoShopSwitchService( + ziniaoAuthService, ziniaoShopIndexService, shopManageService); + + BusinessException exception = assertThrows(BusinessException.class, + () -> service.findIndexedStoreByName("缺失店铺", false)); + + assertEquals(message, exception.getMessage()); + verifyNoInteractions(ziniaoShopIndexService); + } + + @Test + void managedShopIsCheckedBeforeIndexedLookup() { + ZiniaoShopMatchResultVo expected = new ZiniaoShopMatchResultVo(); + expected.setMatched(true); + when(ziniaoShopIndexService.findIndexedStoreByName("测试店铺", false)).thenReturn(expected); + ZiniaoShopSwitchService service = new ZiniaoShopSwitchService( + ziniaoAuthService, ziniaoShopIndexService, shopManageService); + + ZiniaoShopMatchResultVo actual = service.findIndexedStoreByName(" 测试店铺 ", false); + + assertSame(expected, actual); + InOrder order = inOrder(shopManageService, ziniaoShopIndexService); + order.verify(shopManageService).requireShopByName("测试店铺"); + order.verify(ziniaoShopIndexService).findIndexedStoreByName("测试店铺", false); + } + + @Test + void missingManagedShopStopsDirectStaffMatch() { + when(shopManageService.requireShopByName("缺失店铺")) + .thenThrow(new BusinessException("后台店铺管理中未找到店铺:缺失店铺,请先添加店铺信息")); + ZiniaoShopSwitchService service = new ZiniaoShopSwitchService( + ziniaoAuthService, ziniaoShopIndexService, shopManageService); + + assertThrows(BusinessException.class, + () -> service.matchStoreByNameAcrossStaff("缺失店铺", 12L)); + verifyNoInteractions(ziniaoAuthService); + } + + @Test + void blankShopNameDoesNotQueryManagedShopOrZiniao() { + ZiniaoShopSwitchService service = new ZiniaoShopSwitchService( + ziniaoAuthService, ziniaoShopIndexService, shopManageService); + + ZiniaoShopMatchResultVo result = service.findIndexedStoreByName("  ", false); + + assertEquals(ZiniaoShopIndexService.MATCH_STATUS_PENDING, result.getMatchStatus()); + assertEquals("店铺名为空,无法匹配索引", result.getMatchMessage()); + verifyNoInteractions(shopManageService, ziniaoShopIndexService, ziniaoAuthService); + } +} diff --git a/frontend-vue/src/pages/image-video/ImageVideoPage.vue b/frontend-vue/src/pages/image-video/ImageVideoPage.vue index d1332799..45ae0685 100644 --- a/frontend-vue/src/pages/image-video/ImageVideoPage.vue +++ b/frontend-vue/src/pages/image-video/ImageVideoPage.vue @@ -387,7 +387,10 @@ async function launchDesktop() { } .delivery-page { - min-height: 100vh; + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; background: #0f0f0f; } @@ -399,7 +402,9 @@ async function launchDesktop() { } .delivery-page__body { - min-height: calc(100vh - 64px); + flex: 1 1 auto; + min-height: 0; + overflow: auto; padding: 12px 20px 24px; background: #0f0f0f; } diff --git a/frontend-vue/src/shared/components/ZiniaoVersionSetting.vue b/frontend-vue/src/shared/components/ZiniaoVersionSetting.vue index c04fa13b..1394f941 100644 --- a/frontend-vue/src/shared/components/ZiniaoVersionSetting.vue +++ b/frontend-vue/src/shared/components/ZiniaoVersionSetting.vue @@ -1,13 +1,12 @@ @@ -17,7 +16,6 @@ import type { ZiniaoVersion } from '@/shared/utils/ziniao-version' defineProps<{ modelValue: ZiniaoVersion }>() const emit = defineEmits<{ 'update:modelValue': [value: ZiniaoVersion] }>() -const radioName = `ziniao-version-${Math.random().toString(36).slice(2)}` const options: Array<{ label: string; value: ZiniaoVersion }> = [ { label: '新版', value: 'new' }, { label: '旧版', value: 'old' }, @@ -28,8 +26,7 @@ const options: Array<{ label: string; value: ZiniaoVersion }> = [ .ziniao-version-setting { margin: 16px 0; } .setting-title { margin-bottom: 10px; color: #bbb; font-size: 13px; } .version-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; min-height: 40px; padding: 3px; border: 1px solid #3a3a3a; border-radius: 6px; background: #202020; } -.version-option { display: flex; min-width: 0; align-items: center; justify-content: center; border-radius: 4px; color: #aaa; cursor: pointer; font-size: 13px; transition: background-color .15s ease, color .15s ease; } +.version-option { display: flex; min-width: 0; align-items: center; justify-content: center; border: 0; border-radius: 4px; background: transparent; color: #aaa; cursor: pointer; font-size: 13px; transition: background-color .15s ease, color .15s ease; } .version-option:hover { color: #eee; } .version-option.active { background: #409eff; color: #fff; } -.version-option input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; } diff --git a/frontend-vue/src/styles/main.css b/frontend-vue/src/styles/main.css index cfb7d1b2..1137ead4 100644 --- a/frontend-vue/src/styles/main.css +++ b/frontend-vue/src/styles/main.css @@ -2,7 +2,7 @@ @import './reset.css'; body { - background: var(--color-bg-light); + background: #1a1a1a; font-family: var(--font-family-base); } @@ -39,35 +39,29 @@ html { .page-shell { min-height: 100vh; - min-height: 100dvh; } -/* - * The desktop client uses a resizable WebView. Brand modules have a dynamic - * navigation header, so their content must consume the remaining space - * instead of subtracting the old fixed 56px header height. - */ +/* Match the desktop client shell in web_source/brand.html. */ @media (min-width: 1101px) { - html:has(.module-page), - body:has(.module-page) { - height: 100%; + body { + display: flex; + flex-direction: column; overflow: hidden; - background: #1a1a1a; } - #app:has(> .module-page) { - height: 100vh; - height: 100dvh; + #app { + flex: 1 1 auto; + min-height: 0; + } + + #app > .page-shell { + height: 100%; min-height: 0; - overflow: hidden; - background: #1a1a1a; } .module-page { display: flex; flex-direction: column; - height: 100vh; - height: 100dvh; min-height: 0 !important; overflow: hidden; } @@ -78,8 +72,8 @@ html { .module-page > .main-content { flex: 1 1 auto; - height: auto !important; min-height: 0 !important; + height: auto !important; overflow: hidden; } @@ -96,15 +90,6 @@ html { } } -@media (max-width: 1100px) { - body:has(.module-page), - #app:has(> .module-page) { - min-height: 100vh; - min-height: 100dvh; - background: #1a1a1a; - } -} - .theme-light { background: var(--color-bg-light); color: var(--color-text-primary); diff --git a/frontend-vue/src/styles/reset.css b/frontend-vue/src/styles/reset.css index 898e95ae..5fd5e09c 100644 --- a/frontend-vue/src/styles/reset.css +++ b/frontend-vue/src/styles/reset.css @@ -6,7 +6,8 @@ html, body, #app { margin: 0; - min-height: 100%; + height: 100%; + min-height: 0; } body {