From abccca43977c1a2fb084cd7e1d025781a9c07857 Mon Sep 17 00:00:00 2001 From: chenxing Date: Sun, 20 Sep 2026 02:42:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E7=AB=99=E7=82=B9=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E4=B8=89=E5=A4=84=E4=BD=93=E9=AA=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20+=20=E4=B8=BB=E9=A2=98=E5=8F=82=E6=95=B0=E6=A8=AA?= =?UTF-8?q?=E6=8E=92=E8=89=B2=E5=8D=A1=20+=20=E6=96=B0=E7=9A=AE=E8=82=A4?= =?UTF-8?q?=20mock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户验收反馈三处: 1. 左侧锚点导航不固定——根因 .base-wrap-main .main-container 的 legacy overflow:hidden 使 sticky 相对不滚动的隐藏滚动盒定位失效(实测滚动 600px 导航 top=-387);本页运行时改写为 visible、离开还原,修复后滚动 800px 导航稳定停在 top:72px。窄屏(≤1080)导航转横排胶囊。 2. 皮肤颜色参数竖排长蛇(真实主题 12 个色参数 = 12 行)——ThemeParamForm 重排:color 参数转 150px 起步 auto-fill 色卡网格(宽屏一行 4~5 张, 色卡含取色器+名称+HEX),select/image/text 转两列行式;非法值红框提示。 3. 「换肤/改参数保存后门户立即生效」提示框样式突兀且与按钮拥挤——移除 皮肤区内联 form-hint,并入卡片 footer 的 card-hint(与基本信息卡一致)。 整页配套:分区卡 10px 圆角+柔和阴影+品牌竖条标题、锚点导航品牌指示条、 footer hint 自适应换行。 mock THEMES 补 poly-blue/agri-forest/jade-normal 三套新皮肤条目。 vitest 147/147 绿;vite build 通过。 --- mock/index.ts | 42 +++++ src/components/theme/ThemeParamForm.vue | 223 ++++++++++++++++++------ src/views/cms/SiteSetting.vue | 115 +++++++++--- 3 files changed, 307 insertions(+), 73 deletions(-) diff --git a/mock/index.ts b/mock/index.ts index eaca49f..4c0c393 100644 --- a/mock/index.ts +++ b/mock/index.ts @@ -131,6 +131,48 @@ const THEMES: MockTheme[] = [ { key: "accent", cssVar: "--cz-accent", label: "点缀青绿", type: "color", defaultValue: "#2b9c8f" }, ], }, + { + name: "poly-blue", + title: "理工蓝(poly-blue)", + description: "深宝蓝+活力橙的理工院校风:受众品牌行 + 白底吸顶导航 + 双栏要闻 + 日期方块 + 快捷通道宫格(参考浙江理工大学官网)。", + version: "1.0.0", + author: "yunhai", + bannerSize: "1920x560", + screenshotUrl: "/api/theme/poly-blue/asset/screenshot.png", + params: [ + { key: "primary", cssVar: "--pb-primary", label: "主色", type: "color", defaultValue: "#0558A5" }, + { key: "primaryDark", cssVar: "--pb-primary-dark", label: "主色加深", type: "color", defaultValue: "#124489" }, + { key: "accent", cssVar: "--pb-accent", label: "活力橙", type: "color", defaultValue: "#FF7B00" }, + ], + }, + { + name: "agri-forest", + title: "农林绿(agri-forest)", + description: "墨绿+麦穗金的农林院校风:白页头 + 墨绿通栏导航 + SVG 图标栏目标题 + 日期方块 + 专题缩略卡(参考信阳农林学院官网)。", + version: "1.0.0", + author: "yunhai", + bannerSize: "1920x560", + screenshotUrl: "/api/theme/agri-forest/asset/screenshot.png", + params: [ + { key: "primary", cssVar: "--af-primary", label: "主色", type: "color", defaultValue: "#1C6E19" }, + { key: "primaryDark", cssVar: "--af-primary-dark", label: "主色加深", type: "color", defaultValue: "#14510F" }, + { key: "accent", cssVar: "--af-accent", label: "麦穗金", type: "color", defaultValue: "#C8A45C" }, + ], + }, + { + name: "jade-normal", + title: "师大青(jade-normal)", + description: "翡翠青+暖橙点缀的师范院校风:橙色日期徽标 + 快捷通道图标宫格 + 融媒行(参考信阳师范大学官网)。", + version: "1.0.0", + author: "yunhai", + bannerSize: "1920x560", + screenshotUrl: "/api/theme/jade-normal/asset/screenshot.png", + params: [ + { key: "primary", cssVar: "--jn-primary", label: "主色", type: "color", defaultValue: "#008D3F" }, + { key: "primaryDark", cssVar: "--jn-primary-dark", label: "主色加深", type: "color", defaultValue: "#04682E" }, + { key: "accent", cssVar: "--jn-accent", label: "暖橙", type: "color", defaultValue: "#F18D00" }, + ], + }, ]; function themeByName(name: string): MockTheme | undefined { diff --git a/src/components/theme/ThemeParamForm.vue b/src/components/theme/ThemeParamForm.vue index 8048b5b..b2cddf1 100644 --- a/src/components/theme/ThemeParamForm.vue +++ b/src/components/theme/ThemeParamForm.vue @@ -5,8 +5,10 @@ import { effectiveValue, invalidParamKeys, FormModelValue } from "@/utils/themeP /** * 主题参数微调表单(方案 §6.3):按 theme.json params schema 动态生成。 - * color→ColorPicker、select→Select、image→Input(手填 /cms/upload/ 路径,走附件上传后回填)、 - * text→Input。父级 v-model 绑定 FormModelValue(声明过的 key,值已兜底为默认)。 + * 2026-09-20 布局重排(用户反馈"颜色设置竖着太长"): + * - color 参数 → 色卡网格(横排自动换行,auto-fill 150px;真实主题 12 个色参数不再拉出 12 行长蛇); + * - select/image/text → 两列行式(label + 控件同行); + * - 非法值红框 + 行内提示;父级 v-model 绑定 FormModelValue(声明过的 key,值已兜底为默认)。 */ const props = defineProps<{ theme?: ThemeVo; @@ -19,6 +21,8 @@ const emit = defineEmits<{ const themeName = computed(() => props.theme?.title || props.theme?.name || ""); const params = computed(() => props.theme?.params || []); +const colorParams = computed(() => params.value.filter(p => p.type === "color")); +const otherParams = computed(() => params.value.filter(p => p.type !== "color")); const badKeys = computed(() => props.theme ? invalidParamKeys(props.theme, props.modelValue) : [] ); @@ -38,71 +42,186 @@ function isBad(key: string): boolean { diff --git a/src/views/cms/SiteSetting.vue b/src/views/cms/SiteSetting.vue index 7c8282b..9c38dc2 100644 --- a/src/views/cms/SiteSetting.vue +++ b/src/views/cms/SiteSetting.vue @@ -126,10 +126,32 @@ onMounted(async () => { basic.parentSiteId = lockParentId.value; } loading.value = false; + patchAncestorOverflow(); nextTick(setupAnchorObserver); }); -onBeforeUnmount(() => observer?.disconnect()); +/** + * 左侧锚点导航 sticky 的生效前提:布局遗留的 .main-container 带 overflow:hidden + * (清浮动时代产物),sticky 会改为相对这个"自身不滚动的隐藏滚动盒"定位 → 表现为 + * 跟随内容滚走(2026-09-20 实测滚动 600px 导航 top=-387)。本页内容为 flex/常规流, + * 不依赖该 overflow 裁剪浮动,故进入本页时运行时改写、离开时还原,零全局影响。 + */ +let patchedContainer: HTMLElement | null = null; +function patchAncestorOverflow() { + const el = document.querySelector(".base-wrap-main .main-container"); + if (el && getComputedStyle(el).overflow === "hidden") { + patchedContainer = el; + el.style.overflow = "visible"; + } +} + +onBeforeUnmount(() => { + observer?.disconnect(); + if (patchedContainer) { + patchedContainer.style.overflow = ""; + patchedContainer = null; + } +}); async function loadSites() { sites.value = (await siteApi.list().send()) || []; @@ -458,7 +480,6 @@ watch(() => store.currentSchool?.schId, () => loadSites()); -

换肤/改参数保存后门户立即生效(缓存已自动失效)。

@@ -480,7 +501,7 @@ watch(() => store.currentSchool?.schId, () => loadSites()); @@ -603,32 +624,48 @@ watch(() => store.currentSchool?.schId, () => loadSites()); align-items: flex-start; } .ss-nav { - width: 180px; + width: 184px; flex: none; position: sticky; - top: 84px; + top: 72px; display: flex; flex-direction: column; - gap: 4px; + gap: 2px; + padding: 6px; + border: 1px solid #e9edf5; + border-radius: 10px; + background: #fff; + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 14px 30px -22px rgba(16, 24, 40, 0.4); .ss-nav-item { - padding: 8px 12px; - border-radius: 6px; + position: relative; + padding: 8px 10px 8px 14px; + border-radius: 7px; cursor: pointer; transition: background 0.2s; &:hover { - background: #f0faff; + background: #f0f4ff; } &.active { - background: #e8f4ff; + background: #eef2ff; + &::before { + content: ""; + position: absolute; + left: 4px; + top: 10px; + bottom: 10px; + width: 3px; + border-radius: 2px; + background: #577afb; + } .ss-nav-title { - color: #2d8cf0; + color: #4264e4; } } .ss-nav-title { display: block; font-size: 13px; font-weight: 600; - color: #515a6e; + color: #1e2635; } .ss-nav-lock { color: #c5c8ce; @@ -636,12 +673,33 @@ watch(() => store.currentSchool?.schId, () => loadSites()); } .ss-nav-desc { display: block; - color: #999; + color: #8a93a6; font-size: 12px; margin-top: 2px; } } } + @media (max-width: 1080px) { + .ss-layout { + flex-direction: column; + } + .ss-nav { + position: static; + width: 100%; + flex-direction: row; + flex-wrap: wrap; + gap: 4px; + .ss-nav-item { + flex: none; + .ss-nav-desc { + display: none; + } + &.active::before { + display: none; + } + } + } + } .ss-cards { flex: 1; min-width: 0; @@ -650,11 +708,12 @@ watch(() => store.currentSchool?.schId, () => loadSites()); gap: 14px; } .setting-card { - border: 1px solid #e8eaec; - border-radius: 8px; + border: 1px solid #e9edf5; + border-radius: 10px; background: #fff; - padding: 16px 18px; - scroll-margin-top: 76px; + padding: 18px 20px; + scroll-margin-top: 84px; + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04); &.locked { .card-body { opacity: 0.55; @@ -669,13 +728,25 @@ watch(() => store.currentSchool?.schId, () => loadSites()); padding-bottom: 10px; border-bottom: 1px solid #f0f2f5; .card-title { + position: relative; font-size: 15px; font-weight: 600; - color: #17233d; + color: #1e2635; + padding-left: 10px; + &::before { + content: ""; + position: absolute; + left: 0; + top: 3px; + bottom: 3px; + width: 3px; + border-radius: 2px; + background: #577afb; + } } .card-subtitle { font-size: 12px; - color: #999; + color: #8a93a6; } } .card-footer { @@ -686,9 +757,11 @@ watch(() => store.currentSchool?.schId, () => loadSites()); padding-top: 12px; border-top: 1px solid #f0f2f5; .card-hint { - color: #999; + flex: 1; + min-width: 0; + color: #8a93a6; font-size: 12px; - line-height: 1.5; + line-height: 1.6; } } .ss-lock-tip {