diff --git a/RuoYi-Vue/easycode-web/src/views/GenerateView.vue b/RuoYi-Vue/easycode-web/src/views/GenerateView.vue index 56080c6..08d1501 100644 --- a/RuoYi-Vue/easycode-web/src/views/GenerateView.vue +++ b/RuoYi-Vue/easycode-web/src/views/GenerateView.vue @@ -903,38 +903,18 @@ function solutionPreviewClasses(preset) { const solution = visualSolutionDefinition(preset) const navigation = solutionNavigation(solution.shell, 'frontend') const header = String(solution.shell.header || '') - const listLayout = String(solution.layoutPolicy['frontend.list'] || '') - const layoutClass = listLayout.includes('card') - ? 'preview-layout-card' - : listLayout.includes('split') - ? 'preview-layout-split' - : listLayout.includes('dense') - ? 'preview-layout-dense' - : listLayout - ? 'preview-layout-table' - : 'preview-layout-unconfigured' return [ navigation ? `preview-navigation-${navigation}` : 'preview-navigation-unconfigured', header ? `preview-header-${header}` : 'preview-header-unconfigured', - layoutClass + 'preview-layout-adaptive' ] } function solutionMetaLabels(preset) { const solution = visualSolutionDefinition(preset) - const listLayout = String(solution.layoutPolicy['frontend.list'] || '') const frontendNavigation = navigationLabel(solutionNavigation(solution.shell, 'frontend'), '前台') const adminNavigation = navigationLabel(solutionNavigation(solution.shell, 'admin'), '后台') - const layout = listLayout.includes('card') - ? '卡片列表' - : listLayout.includes('split') - ? '分栏列表' - : listLayout.includes('dense') - ? '高密度列表' - : listLayout - ? '标准表格' - : '布局未配置' - return [`${frontendNavigation} / ${adminNavigation}`, layout] + return [`${frontendNavigation} / ${adminNavigation}`, '列表按图片自适应'] } function solutionNavigation(shell, scope) { diff --git a/RuoYi-Vue/easycode-web/src/views/generateView.test.mjs b/RuoYi-Vue/easycode-web/src/views/generateView.test.mjs index 7e479ff..a5b7384 100644 --- a/RuoYi-Vue/easycode-web/src/views/generateView.test.mjs +++ b/RuoYi-Vue/easycode-web/src/views/generateView.test.mjs @@ -411,6 +411,8 @@ test('generate page presents style presets as visual themes with previews', () = assert.match(source, /navigationLabel\(solutionNavigation\(solution\.shell, 'frontend'\), '前台'\)/) assert.match(source, /navigationLabel\(solutionNavigation\(solution\.shell, 'admin'\), '后台'\)/) assert.match(source, /scope === 'admin' \? 'adminNavigation' : 'frontendNavigation'/) + assert.match(source, /列表按图片自适应/) + assert.match(source, /'preview-layout-adaptive'/) }) test('generate page shows one managed-theme error and never falls back to local presets', () => { diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImpl.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImpl.java index cbde0fa..7824935 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImpl.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImpl.java @@ -208,6 +208,8 @@ public class PageThemeDefinitionServiceImpl implements IPageThemeDefinitionServi Map layoutPolicy = parseObject( StringUtils.defaultIfEmpty(theme.getLayoutPolicyJson(), "{}"), "布局策略"); + layoutPolicy.remove("frontend.list"); + layoutPolicy.remove("frontend.current_user_list"); for (Map.Entry entry : layoutPolicy.entrySet()) { String[] parts = entry.getKey().split("\\.", 2); diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageCompositionService.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageCompositionService.java index 3247794..72080eb 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageCompositionService.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageCompositionService.java @@ -275,29 +275,23 @@ public class PageCompositionService } if ("current_user_list".equals(pageType)) { - boolean useCard = imageCount > 0 || browsingScene || listCount <= 6; + boolean useCard = imageCount > 0; decision.canvas = useCard ? "frontend-current-user-card-v1" : "frontend-current-user-list-v1"; - decision.addReason(useCard ? "个人记录适合卡片式浏览" : "个人记录字段较多,保留表格效率"); - decision.confidence = 0.84D; + decision.addReason(useCard ? "检测到图片字段,个人记录采用卡片列表" : "未检测到图片字段,个人记录采用标准列表"); + decision.confidence = 0.98D; return decision; } - if (categoryField && queryCount >= 2) - { - decision.canvas = "frontend-list-split-v1"; - decision.addReason("分类字段和筛选条件明显,采用分栏列表"); - decision.confidence = 0.88D; - } - else if (imageCount > 0 && (browsingScene || listCount <= 8)) + if (imageCount > 0) { decision.canvas = "frontend-list-card-v1"; - decision.addReason("记录包含图片且偏浏览场景,采用卡片列表"); - decision.confidence = 0.91D; + decision.addReason("检测到图片字段,前台采用卡片列表"); + decision.confidence = 0.98D; } else { decision.canvas = "frontend-list-v1"; - decision.addReason("数据维护和检索特征更强,采用标准表格"); - decision.confidence = 0.72D; + decision.addReason("未检测到图片字段,前台采用标准列表"); + decision.confidence = 0.98D; } return decision; } diff --git a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageStylePresetRegistry.java b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageStylePresetRegistry.java index d8b4911..15caef3 100644 --- a/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageStylePresetRegistry.java +++ b/RuoYi-Vue/ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/PageStylePresetRegistry.java @@ -504,8 +504,15 @@ public class PageStylePresetRegistry public String preferredLayout(String scope, String pageType) { + String normalizedScope = StringUtils.defaultIfEmpty(scope, "frontend"); + String normalizedPageType = StringUtils.defaultIfEmpty(pageType, "list"); + if ("frontend".equalsIgnoreCase(normalizedScope) + && ("list".equals(normalizedPageType) || "current_user_list".equals(normalizedPageType))) + { + return ""; + } return StringUtils.defaultString(layoutPolicy.get( - StringUtils.defaultIfEmpty(scope, "frontend") + "." + StringUtils.defaultIfEmpty(pageType, "list"))); + normalizedScope + "." + normalizedPageType)); } public String shellValue(String name, String fallback) diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImplTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImplTest.java index 16b91fc..dcb0e3e 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImplTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/PageThemeDefinitionServiceImplTest.java @@ -60,6 +60,20 @@ public class PageThemeDefinitionServiceImplTest assertFalse(theme.getShellJson().contains("\"navigation\"")); } + @Test + public void insertRemovesFrontendListLayoutOverrides() + { + PageThemeDefinition theme = customTheme(); + theme.setLayoutPolicyJson("{\"frontend.list\":\"frontend-list-card-v1\"," + + "\"frontend.current_user_list\":\"frontend-current-user-card-v1\"}"); + when(mapper.selectPageThemeByCode("ocean-blue")).thenReturn(null); + + service.insertPageTheme(theme); + + assertFalse(theme.getLayoutPolicyJson().contains("frontend.list")); + assertFalse(theme.getLayoutPolicyJson().contains("frontend.current_user_list")); + } + @Test public void insertRejectsInvalidTokenJson() { diff --git a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/PageCompositionServiceStylePresetTest.java b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/PageCompositionServiceStylePresetTest.java index 7d8b5b7..8f9255e 100644 --- a/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/PageCompositionServiceStylePresetTest.java +++ b/RuoYi-Vue/ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/PageCompositionServiceStylePresetTest.java @@ -24,7 +24,7 @@ public class PageCompositionServiceStylePresetTest private final PageCompositionService service = new PageCompositionService(); @Test - public void cardPortalPresetKeepsAutomaticallySelectedFrontendCanvas() + public void visualPresetDoesNotForceCardLayoutWithoutImage() { FrontProjectPageDesign design = design("frontend", "list"); @@ -33,7 +33,7 @@ public class PageCompositionServiceStylePresetTest column("status", "status", "1", "1", "1")), null, null, true, "card_portal"); Map layout = readLayout(design); - assertEquals("frontend-list-card-v1", layout.get("canvas")); + assertEquals("frontend-list-v1", layout.get("canvas")); assertEquals("card_portal", layout.get("stylePreset")); assertTrue(((Map) layout.get("styleTokens")).containsKey("primary")); Map composition = (Map) layout.get("composition"); @@ -41,6 +41,22 @@ public class PageCompositionServiceStylePresetTest assertTrue(String.valueOf(composition.get("reasons")).contains("card_portal")); } + @Test + public void visualPresetDoesNotForceStandardLayoutWithImage() + { + FrontProjectPageDesign design = design("frontend", "list"); + FrontProjectColumn cover = column("cover_url", "coverUrl", "0", "1", "1"); + cover.setHtmlType("imageUpload"); + + service.compose(design, table(), Arrays.asList( + column("title", "title", "1", "1", "1"), cover), + null, null, true, "minimal-white"); + + Map layout = readLayout(design); + assertEquals("frontend-list-card-v1", layout.get("canvas")); + assertEquals("minimal-white", layout.get("stylePreset")); + } + @Test public void dataDensePresetKeepsAutomaticallySelectedAdminCanvas() { @@ -109,7 +125,25 @@ public class PageCompositionServiceStylePresetTest Map layout = readLayout(design); assertEquals("current_user_list", design.getPageType()); - assertEquals("frontend-current-user-card-v1", layout.get("canvas")); + assertEquals("frontend-current-user-list-v1", layout.get("canvas")); + } + + @Test + public void myReaderListUsesCardsWhenItHasAnImage() + { + FrontProjectPageDesign design = design("frontend", "list"); + design.setPageCode("my_borrows_page"); + design.setPageName("My Borrows"); + design.setRoutePath("/my/borrows"); + FrontProjectColumn cover = column("book_cover", "bookCover", "0", "1", "0"); + cover.setHtmlType("imageUpload"); + + service.compose(design, table(), Arrays.asList( + column("reader_id", "readerId", "0", "1", "0"), cover), + null, null, true, "classic-admin"); + + assertEquals("current_user_list", design.getPageType()); + assertEquals("frontend-current-user-card-v1", readLayout(design).get("canvas")); } @Test @@ -122,10 +156,10 @@ public class PageCompositionServiceStylePresetTest "modern-blue", PageLayoutTemplateRegistry.STABLE_RANDOM, Long.valueOf(42L)); Map layout = readLayout(design); - assertTrue(!"frontend-list-card-v1".equals(layout.get("canvas"))); + assertTrue(!"frontend-list-v1".equals(layout.get("canvas"))); Map composition = (Map) layout.get("composition"); assertEquals(PageLayoutTemplateRegistry.STABLE_RANDOM, composition.get("variantMode")); - assertEquals("frontend-list-card-v1", composition.get("recommendedCanvas")); + assertEquals("frontend-list-v1", composition.get("recommendedCanvas")); assertEquals(layout.get("canvas"), composition.get("selectedCanvas")); assertEquals(42L, ((Number) composition.get("variantSeed")).longValue()); diff --git a/RuoYi-Vue/ruoyi-ui/src/views/generator/pageTheme/index.vue b/RuoYi-Vue/ruoyi-ui/src/views/generator/pageTheme/index.vue index cbcbc4e..d2832a9 100644 --- a/RuoYi-Vue/ruoyi-ui/src/views/generator/pageTheme/index.vue +++ b/RuoYi-Vue/ruoyi-ui/src/views/generator/pageTheme/index.vue @@ -189,6 +189,13 @@ 页面布局策略 + @@ -258,12 +265,6 @@ function defaultAssets() { return { iconStyle: 'outline', illustrationStyle: 'clean', chartPalette: ['#2563eb', '#06b6d4', '#7c3aed', '#f59e0b', '#10b981'] } } -const FRONT_LIST_LAYOUTS = [ - { label: '标准表格', value: 'frontend-list-v1' }, { label: '卡片列表', value: 'frontend-list-card-v1' }, { label: '分栏列表', value: 'frontend-list-split-v1' } -] -const FRONT_MINE_LAYOUTS = [ - { label: '我的列表', value: 'frontend-current-user-list-v1' }, { label: '我的卡片', value: 'frontend-current-user-card-v1' } -] const FRONT_DETAIL_LAYOUTS = [ { label: '基础详情', value: 'frontend-detail-v1' }, { label: '图文详情', value: 'frontend-detail-media-v1' } ] @@ -297,8 +298,6 @@ export default { assetForm: defaultAssets(), chartPaletteText: defaultAssets().chartPalette.join(', '), layoutPolicyFields: [ - { key: 'frontend.list', label: '前台列表', options: FRONT_LIST_LAYOUTS }, - { key: 'frontend.current_user_list', label: '个人列表', options: FRONT_MINE_LAYOUTS }, { key: 'frontend.detail', label: '前台详情', options: FRONT_DETAIL_LAYOUTS }, { key: 'frontend.form', label: '前台表单', options: FRONT_FORM_LAYOUTS }, { key: 'admin.list', label: '后台列表', options: ADMIN_LIST_LAYOUTS }, @@ -383,6 +382,12 @@ export default { delete result.navigation return result }, + normalizeLayoutPolicy(policy) { + const result = Object.assign({}, policy || {}) + delete result['frontend.list'] + delete result['frontend.current_user_list'] + return result + }, rowPreviewStyle(row) { const tokens = this.parseObject(row.tokensJson, defaultTokens()) return { '--mini-primary': tokens.primary, '--mini-accent': tokens.accent, '--mini-bg': tokens.background, '--mini-surface': tokens.surface, '--mini-border': tokens.border } @@ -401,7 +406,7 @@ export default { this.tokenForm = this.parseObject(this.form.tokensJson, defaultTokens()) this.componentForm = this.parseObject(this.form.componentDefaultsJson, defaultComponents()) this.shellForm = this.parseShell(this.form.shellJson) - this.layoutPolicyForm = this.parseObject(this.form.layoutPolicyJson, defaultLayoutPolicy()) + this.layoutPolicyForm = this.normalizeLayoutPolicy(this.parseObject(this.form.layoutPolicyJson, defaultLayoutPolicy())) this.assetForm = this.parseObject(this.form.assetPolicyJson, defaultAssets()) this.chartPaletteText = Array.isArray(this.assetForm.chartPalette) ? this.assetForm.chartPalette.join(', ') : '' this.title = '修改视觉方案' @@ -416,8 +421,9 @@ export default { const shell = Object.assign({}, this.shellForm) delete shell.navigation this.form.shellJson = JSON.stringify(shell) - this.form.layoutPolicyJson = JSON.stringify(Object.keys(this.layoutPolicyForm).reduce((result, key) => { - if (this.layoutPolicyForm[key]) result[key] = this.layoutPolicyForm[key] + const layoutPolicy = this.normalizeLayoutPolicy(this.layoutPolicyForm) + this.form.layoutPolicyJson = JSON.stringify(Object.keys(layoutPolicy).reduce((result, key) => { + if (layoutPolicy[key]) result[key] = layoutPolicy[key] return result }, {})) this.form.assetPolicyJson = JSON.stringify(Object.assign({}, this.assetForm, {