-
-
-
+ {/* 页头下方占满剩余高度;每个 Tab 面板在内部滚动,页面本身不滚,「保存」始终可见 */}
+
>
)
diff --git a/app/(app)/layout.module.scss b/app/(app)/layout.module.scss
index 4872f25..c874109 100644
--- a/app/(app)/layout.module.scss
+++ b/app/(app)/layout.module.scss
@@ -1,8 +1,12 @@
/* ===== 应用骨架:自绘侧边栏 ===== */
+/* 应用壳:整体占满视口且自身不滚动;侧栏是固定宽度的普通子元素,
+ 主内容区(.main)独立滚动。页面本身不滚,长内容不会把侧栏顶出视口(#1705) */
.app {
display: flex;
- min-height: 100vh;
+ height: 100vh;
+ height: 100dvh;
+ overflow: hidden;
background: var(--semi-color-bg-0);
}
@@ -17,14 +21,13 @@
}
/* ===== 侧边栏 ===== */
+/* 撑满应用壳高度;导航项多于一屏时只有 .nav 内部滚动 */
.sider {
width: 220px;
flex-shrink: 0;
display: flex;
flex-direction: column;
- position: sticky;
- top: 0;
- height: 100vh;
+ height: 100%;
background: var(--semi-color-bg-1);
border-right: 1px solid var(--semi-color-border);
transition: width 0.18s ease, transform 0.2s ease;
@@ -265,12 +268,14 @@
height: 16px;
}
-/* ===== 主内容区 ===== */
+/* ===== 主内容区:唯一的滚动容器,页头(PageHeader)的 position: sticky 相对它吸顶 ===== */
.main {
flex: 1;
min-width: 0;
+ min-height: 0;
display: flex;
flex-direction: column;
+ overflow: auto;
}
/* ===== 移动端抽屉 ===== */
diff --git a/app/styles/dashboard.module.scss b/app/styles/dashboard.module.scss
index 75fa238..0de54a4 100644
--- a/app/styles/dashboard.module.scss
+++ b/app/styles/dashboard.module.scss
@@ -1,325 +1,76 @@
-@media screen and (min-width: 640px) {
- .rootConfigPanel {
- padding: 0px 50px 0px 30px;
- //align-items: center;
- //background: var(--semi-color-bg-0);
- //flex-direction: column;
- //height: auto;
- //overflow: auto;
- //row-gap: 0;
- //min-width: auto;
- //max-width: auto;
- //display: flex;
- //color: var(--semi-color-text-0);
- //font-family: Inter;
- //font-size: 20px;
- //font-weight: 600;
- //letter-spacing: 0.4px;
- //line-height: 28px;
- //text-align: left;
-
- .main {
- //align-items: center;
- display: flex;
- flex-basis: 0;
- flex-direction: column;
- flex-grow: 1;
- //padding: 40px 0px 0px;
- row-gap: 32px;
- align-self: stretch;
-
- .content {
- //align-items: flex-start;
- //align-items: center;
- display: inline-flex;
- flex-direction: column;
- row-gap: 32px;
- flex-shrink: 0;
-
- .item {
- min-width: 175px;
- vertical-align: middle;
- flex-shrink: 0;
- }
-
- .form {
- //align-items: flex-start;
- //align-items: center;
- //display: flex;
- //flex-direction: column;
- //row-gap: 16px;
- //min-width: 50px;
- //max-width: 965px;
- flex-shrink: 0;
- //color: var(--semi-color-text-0);
- //font-family: "PingFang SC";
- //font-size: 16px;
- //font-weight: 500;
- //letter-spacing: 0.32px;
- //line-height: 22px;
- //text-align: left;
-
- .frameDownload {
- //align-items: flex-start;
- border-color: rgb(var(--semi-grey-2));
- border-radius: 6px;
- border-style: solid;
- border-width: 1px;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- padding: 25px 50px 50px;
- row-gap: 10px;
- align-self: stretch;
- min-width: 12px;
- max-width: auto;
-
- .frameInside {
- align-items: center;
- column-gap: 4px;
- display: inline-flex;
- flex-shrink: 0;
-
- .group {
- height: 20px;
- position: relative;
- width: 20px;
- flex-shrink: 0;
-
- .buttonOnlyIconSecond {
- background: rgb(16, 192, 25);
- border-radius: 8px;
- height: 20px;
- left: 0;
- overflow: hidden;
- top: 0;
- width: 20px;
- position: absolute;
- }
-
- .lineStory {
- height: 10px;
- left: 4.5px;
- overflow: hidden;
- top: 4.5px;
- width: 12px;
- position: absolute;
- }
- }
-
- .meegoSharedWebWorkIt {
- min-width: 68px;
- vertical-align: bottom;
- flex-shrink: 0;
- }
- }
- }
- .frameUpload {
- align-items: flex-start;
- border-color: rgb(var(--semi-grey-2));
- border-radius: 6px;
- border-style: solid;
- border-width: 1px;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- padding: 25px 50px 50px;
- row-gap: 10px;
- align-self: stretch;
-
- .frameInside {
- align-items: center;
- column-gap: 4px;
- display: inline-flex;
- flex-shrink: 0;
-
- .group {
- height: 20px;
- position: relative;
- width: 20px;
- flex-shrink: 0;
-
- .buttonOnlyIconSecond {
- background: rgb(236, 23, 66);
- border-radius: 8px;
- height: 20px;
- left: 0;
- overflow: hidden;
- top: 0;
- width: 20px;
- position: absolute;
- }
-
- .lineStory {
- height: 10px;
- left: 4.5px;
- overflow: hidden;
- top: 4.5px;
- width: 12px;
- position: absolute;
- }
- }
-
- .meegoSharedWebWorkIt {
- min-width: 68px;
- vertical-align: bottom;
- flex-shrink: 0;
- }
- }
- }
-
- .framePlatformConfig {
- align-items: flex-start;
- border-color: rgb(var(--semi-grey-2));
- border-radius: 6px;
- border-style: solid;
- border-width: 1px;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- padding: 25px 50px 50px;
- row-gap: 10px;
- align-self: stretch;
-
- .frameInside {
- align-items: center;
- column-gap: 4px;
- display: inline-flex;
- flex-shrink: 0;
-
- .group {
- height: 20px;
- position: relative;
- width: 20px;
- flex-shrink: 0;
-
- .outlinedVersion {
- height: 10px;
- left: 5px;
- overflow: hidden;
- top: 5px;
- width: 10px;
- position: absolute;
- }
-
- .buttonOnlyIconSecond {
- background: rgb(47, 76, 206);
- border-radius: 8px;
- height: 20px;
- left: 0;
- overflow: hidden;
- top: 0;
- width: 20px;
- position: absolute;
- }
- .lineStory {
- height: 15px;
- left: 4.5px;
- overflow: hidden;
- top: 4.1px;
- width: 12px;
- position: absolute;
- }
- }
-
- .meegoSharedWebSettin {
- min-width: 68px;
- vertical-align: bottom;
- flex-shrink: 0;
- }
- }
- }
-
- .frameDeveloper {
- align-items: flex-start;
- border-color: rgb(var(--semi-grey-2));
- border-radius: 6px;
- border-style: solid;
- border-width: 1px;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- padding: 25px 50px 50px;
- row-gap: 10px;
- align-self: stretch;
-
- .frameInside {
- align-items: center;
- column-gap: 4px;
- display: inline-flex;
- flex-shrink: 0;
-
- .group {
- height: 20px;
- position: relative;
- width: 20px;
- flex-shrink: 0;
-
- .outlinedVersion {
- height: 10px;
- left: 5px;
- overflow: hidden;
- top: 5px;
- width: 10px;
- position: absolute;
- }
-
- .buttonOnlyIconSecond {
- background: rgb(200, 109, 5);
- border-radius: 8px;
- height: 20px;
- left: 0;
- overflow: hidden;
- top: 0;
- width: 20px;
- position: absolute;
- }
- .lineStory {
- height: 15px;
- left: 4.5px;
- overflow: hidden;
- top: 4.1px;
- width: 12px;
- position: absolute;
- }
- }
-
- .meegoSharedWebSettin {
- min-width: 68px;
- vertical-align: bottom;
- flex-shrink: 0;
- }
- }
- }
- .line {
- flex-shrink: 0;
- height: 0;
- align-self: stretch;
- }
- }
- }
- }
- }
+/* 空间配置页
+ 页头下方是 Tabs,每个 Tab 面板占满剩余高度并在自己内部滚动;
+ 页面本身(应用壳的 .main)不滚动,页头的「保存」始终可见。 */
+.page {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ max-width: 1280px;
+ margin: 0 auto;
+ box-sizing: border-box;
+ padding: 12px 28px 20px;
}
-/* 表单卡片:必须加实色背景,避免全局背景图穿透导致文字不可读 */
+/* Semi 的 Form / Tabs 默认按内容撑开,这里让它们把剩余高度让给当前面板
+ (semi-tabs-pane-motion-overlay 是开启切换动画时 Semi 多包的一层) */
+.form,
+.tabs,
+.tabs :global(.semi-tabs-content),
+.tabs :global(.semi-tabs-pane),
+.tabs :global(.semi-tabs-pane-motion-overlay) {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+}
+.tabs :global(.semi-tabs-content) {
+ padding: 0;
+}
+/* 上面的 display: flex 特异性高于 Semi 自己的 .semi-tabs-pane-inactive,未选中的面板要补回隐藏 */
+.tabs :global(.semi-tabs-pane-inactive) {
+ display: none;
+}
+
+/* 全局设置 / 开发者选项:面板自身滚动 */
+.pane {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ padding: 16px 4px 32px;
+}
+
+/* 表单分区:不再套卡片,只用分区标题 + 分隔线区分 */
.frameDownload,
.frameUpload,
-.framePlatformConfig,
.frameDeveloper {
- background: var(--semi-color-bg-1);
+ display: flex;
+ flex-direction: column;
+ row-gap: 10px;
+}
+.frameUpload {
+ margin-top: 24px;
+ padding-top: 24px;
+ border-top: 1px solid var(--semi-color-border);
}
-/* 平台设置:左列平台名 + 右栏选中表单 */
+/* 平台设置:左列平台列表 + 右栏选中平台的字段。两栏并排、各自独立滚动,
+ 右栏再长,列表也不会跟着滚走 */
.platformLayout {
+ flex: 1;
+ min-height: 0;
display: flex;
gap: 16px;
- align-items: flex-start;
- margin-top: 16px;
+ padding-top: 16px;
}
.platformNav {
width: 160px;
flex-shrink: 0;
+ align-self: flex-start;
+ max-height: 100%;
+ box-sizing: border-box;
+ overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
@@ -351,30 +102,44 @@
.platformBody {
flex: 1;
min-width: 0;
+ min-height: 0;
+ overflow-y: auto;
+ padding-bottom: 32px;
+}
+/* 右栏:只显示选中平台的字段;不设 display,以免覆盖 hidden 属性 */
+.platformPanel {
+ padding: 4px 16px 8px 8px;
}
-/* 移动端:卡片内边距收紧,避免左右留白/溢出 */
+/* 自定义背景开启时表单直接铺在背景图上,给滚动面板垫一层半透明底色保证可读 */
+:global(body.app-has-bg) .pane,
+:global(body.app-has-bg) .platformBody {
+ background: color-mix(in srgb, var(--semi-color-bg-1) 75%, transparent);
+ border-radius: 12px;
+}
+
+/* 移动端:两栏改为上下堆叠,平台列表变成横向滑动的标签行,仍不随内容滚走 */
@media (max-width: 680px) {
- .rootConfigPanel {
- padding: 0 12px !important;
- }
- .frameDownload,
- .frameUpload,
- .framePlatformConfig,
- .frameDeveloper {
- padding: 16px 14px 24px !important;
- border-radius: 10px;
+ .page {
+ padding: 8px 12px 12px;
}
.platformLayout {
flex-direction: column;
+ gap: 10px;
+ padding-top: 10px;
}
.platformNav {
width: 100%;
+ max-height: none;
+ align-self: stretch;
flex-direction: row;
overflow-x: auto;
- flex-shrink: 0;
+ overflow-y: hidden;
}
.platformNavItem {
white-space: nowrap;
}
+ .platformPanel {
+ padding: 4px 4px 8px;
+ }
}
diff --git a/app/ui/plugins/PlatformPanel.tsx b/app/ui/plugins/PlatformPanel.tsx
new file mode 100644
index 0000000..eb6f4b9
--- /dev/null
+++ b/app/ui/plugins/PlatformPanel.tsx
@@ -0,0 +1,28 @@
+'use client'
+import React from 'react'
+import { Collapse } from '@douyinfe/semi-ui'
+
+type Props = {
+ header: string
+ itemKey: string
+ /**
+ * 只输出字段,不套 Collapse.Panel。
+ * 空间配置页由左侧平台列表负责切换,右侧再出现一排带平台名的折叠栏就是重复导航(#1705);
+ * 直播管理页的「配置覆写」弹窗仍以 Collapse.Panel 形式嵌在 Collapse 里。
+ */
+ bare?: boolean
+ children: React.ReactNode
+}
+
+const PlatformPanel: React.FC
= ({ header, itemKey, bare, children }) => {
+ if (bare) {
+ return <>{children}>
+ }
+ return (
+
+ {children}
+
+ )
+}
+
+export default PlatformPanel
diff --git a/app/ui/plugins/bilibili.tsx b/app/ui/plugins/bilibili.tsx
index 19db194..94d6f4e 100644
--- a/app/ui/plugins/bilibili.tsx
+++ b/app/ui/plugins/bilibili.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Bilibili: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Bilibili: React.FC = props => {
return (
<>
-
+
= props => {
}}
showClear={true}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/cc.tsx b/app/ui/plugins/cc.tsx
index ec4b475..4bcd03a 100644
--- a/app/ui/plugins/cc.tsx
+++ b/app/ui/plugins/cc.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const CC: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const CC: React.FC = props => {
return (
<>
-
+
= props => {
flv
hls(默认)
-
+
>
)
}
diff --git a/app/ui/plugins/cookie.tsx b/app/ui/plugins/cookie.tsx
index 8d57be2..b34e090 100644
--- a/app/ui/plugins/cookie.tsx
+++ b/app/ui/plugins/cookie.tsx
@@ -1,10 +1,12 @@
'use client'
import React from 'react'
-import { Form, Select, Collapse } from '@douyinfe/semi-ui'
+import { Form, Select } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
+ bare?: boolean
}
const Cookie: React.FC = props => {
@@ -12,7 +14,7 @@ const Cookie: React.FC = props => {
const list = props.list
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/douyin.tsx b/app/ui/plugins/douyin.tsx
index f47a008..8c9de6d 100644
--- a/app/ui/plugins/douyin.tsx
+++ b/app/ui/plugins/douyin.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Douyin: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Douyin: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/douyu.tsx b/app/ui/plugins/douyu.tsx
index 4f1d8b3..6ed190a 100644
--- a/app/ui/plugins/douyu.tsx
+++ b/app/ui/plugins/douyu.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Douyu: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Douyu: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/huya.tsx b/app/ui/plugins/huya.tsx
index 17f4052..7697dbb 100644
--- a/app/ui/plugins/huya.tsx
+++ b/app/ui/plugins/huya.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Huya: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Huya: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/index.ts b/app/ui/plugins/index.ts
index 1ac53c5..9fba8e8 100644
--- a/app/ui/plugins/index.ts
+++ b/app/ui/plugins/index.ts
@@ -40,7 +40,8 @@ const plugins = {
/**
* 空间配置「平台设置」左栏的展示顺序与文案。
- * key 必须与各插件 Collapse.Panel 的 itemKey 一致;新增插件时在这里登记一行即可出现在 UI 里。
+ * 各插件组件接受 `bare` 属性:为 true 时只渲染字段(空间配置页),否则渲染成 Collapse.Panel(配置覆写弹窗)。
+ * 新增插件时在这里登记一行即可出现在 UI 里。
*/
export const PlatformPanels: { key: string; name: string; Component: React.FC }[] = [
{ key: 'bilibili', name: '哔哩哔哩', Component: Bilibili },
diff --git a/app/ui/plugins/kilakila.tsx b/app/ui/plugins/kilakila.tsx
index ff1d98e..cf0af7a 100644
--- a/app/ui/plugins/kilakila.tsx
+++ b/app/ui/plugins/kilakila.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Kilakila: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Kilakila: React.FC = props => {
return (
<>
-
+
= props => {
hls
flv
-
+
>
)
}
diff --git a/app/ui/plugins/twitcasting.tsx b/app/ui/plugins/twitcasting.tsx
index fd943c3..921c55c 100644
--- a/app/ui/plugins/twitcasting.tsx
+++ b/app/ui/plugins/twitcasting.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const TwitCasting: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const TwitCasting: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/twitch.tsx b/app/ui/plugins/twitch.tsx
index dc2ea91..f6eeea1 100644
--- a/app/ui/plugins/twitch.tsx
+++ b/app/ui/plugins/twitch.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const Twitch: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const Twitch: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}
diff --git a/app/ui/plugins/youtube.tsx b/app/ui/plugins/youtube.tsx
index 1143fad..2b855c5 100644
--- a/app/ui/plugins/youtube.tsx
+++ b/app/ui/plugins/youtube.tsx
@@ -1,15 +1,17 @@
'use client'
import React, { useEffect } from 'react'
-import { Form, Select, Collapse, useFormApi } from '@douyinfe/semi-ui'
+import { Form, Select, useFormApi } from '@douyinfe/semi-ui'
+import PlatformPanel from './PlatformPanel'
type Props = {
entity: any
list: any
initValues?: Record
+ bare?: boolean
}
const YouTube: React.FC = props => {
- const { entity, list, initValues } = props
+ const { entity, list, initValues, bare } = props
const formApi = useFormApi()
useEffect(() => {
@@ -22,7 +24,7 @@ const YouTube: React.FC = props => {
return (
<>
-
+
= props => {
padding: 0,
}}
/>
-
+
>
)
}