diff --git a/app/(app)/bg-global.css b/app/(app)/bg-global.css index 2ecdcf5..4fc4cbb 100644 --- a/app/(app)/bg-global.css +++ b/app/(app)/bg-global.css @@ -1,16 +1,11 @@ /* 全局自定义背景:当 body 带 app-has-bg 时生效 */ -html, -body { - overflow-x: hidden; -} +/* 两个背景层用 position: fixed + inset: 0 铺满视口。页面滚动由应用壳的主内容区承担 + (layout.module.scss .app / .main),文档本身不滚,这里不需要再给 html / body 设 overflow */ body.app-has-bg::before { content: ''; position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; + inset: 0; z-index: -2; background-image: var(--app-bg-image); background-repeat: no-repeat; @@ -23,10 +18,7 @@ body.app-has-bg::before { body.app-has-bg::after { content: ''; position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; + inset: 0; background: var(--semi-color-bg-0); opacity: var(--app-bg-mask-opacity, 0.35); z-index: -1; diff --git a/app/(app)/changelog/changelog.module.scss b/app/(app)/changelog/changelog.module.scss index 0614206..caacd4e 100644 --- a/app/(app)/changelog/changelog.module.scss +++ b/app/(app)/changelog/changelog.module.scss @@ -3,12 +3,6 @@ min-height: calc(100vh - 69px); } -.feed, -.center { - position: relative; - z-index: 1; -} - .center { display: flex; align-items: center; diff --git a/app/(app)/changelog/page.tsx b/app/(app)/changelog/page.tsx index def8422..3da11aa 100644 --- a/app/(app)/changelog/page.tsx +++ b/app/(app)/changelog/page.tsx @@ -9,7 +9,7 @@ import { formatVersion } from '@/app/lib/status' import { SLOW_REFRESH_MS } from '@/app/lib/use-dashboard' import styles from './changelog.module.scss' -const { Header, Content } = Layout +const { Content } = Layout const { Title, Text } = Typography // 数据源:biliup 官方 CHANGELOG(随版本自动更新,零后端改动) @@ -210,20 +210,13 @@ export default function Changelog() { return ( <> -
- } - title="更新日志" - description="biliup 版本更新记录" - /> -
+ {/* PageHeader 自带 sticky 与 z-index,不要再包一层带 position / z-index 的容器, + 否则会形成更低层级的 stacking context,滚动时被下方内容盖住 */} + } + title="更新日志" + description="biliup 版本更新记录" + />
{isLoading && ( diff --git a/app/(app)/dashboard/page.tsx b/app/(app)/dashboard/page.tsx index 3fb99b8..52ee9c2 100644 --- a/app/(app)/dashboard/page.tsx +++ b/app/(app)/dashboard/page.tsx @@ -1,19 +1,16 @@ 'use client' -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { Button, Form, - Collapse, Avatar, - Select, - Space, Toast, Notification, Typography, Tabs, TabPane, } from '@douyinfe/semi-ui' -import { IconPlusCircle, IconStar, IconGlobe } from '@douyinfe/semi-icons' +import { IconPlusCircle, IconStar } from '@douyinfe/semi-icons' import useSWR from 'swr' import { fetcher, put } from '@/app/lib/api-streamer' import useSWRMutation from 'swr/mutation' @@ -21,14 +18,28 @@ import { FormApi } from '@douyinfe/semi-ui/lib/es/form' import { useBiliUsers } from '../../lib/use-streamers' import styles from '../../styles/dashboard.module.scss' import PageHeader from '../components/PageHeader' -import SectionTitle from '../components/SectionTitle' -import dc from '@/app/ui/data-card.module.scss' // 注册各平台组件 import { PlatformPanels } from '../../ui/plugins' import Global from '../../ui/plugins/global' import Developer from '../../ui/plugins/developer' +const TAB_GLOBAL = '1' +const TAB_PLATFORM = '2' +const TAB_DEVELOPER = '3' + +/** Semi 把校验错误按字段路径存成嵌套对象({ user: { bili_cookie: '…' } }),拍平成 x-field-id 形式 */ +function errorFieldPaths(errors: unknown, prefix = ''): string[] { + if (!errors || typeof errors !== 'object' || Array.isArray(errors) || '$$typeof' in errors) { + return prefix ? [prefix] : [] + } + return Object.entries(errors as Record).flatMap(([k, v]) => + errorFieldPaths(v, prefix ? `${prefix}.${k}` : k), + ) +} + +const fieldElement = (path: string) => + document.querySelector(`.semi-form-field[x-field-id="${path}"]`) const Dashboard: React.FC = () => { const { data: entity, error, isLoading } = useSWR('/v1/configuration', fetcher) @@ -61,8 +72,34 @@ const { data: entity, error, isLoading } = useSWR('/v1/configuration', fetcher) const { biliUsers } = useBiliUsers() - // 平台设置:左列平台名 + 右栏仅展开选中平台。列表来自插件注册表 PlatformPanels + // 平台设置:左列平台名是唯一的导航,右栏只显示选中平台的字段。列表来自插件注册表 PlatformPanels const [activePlatform, setActivePlatform] = useState(PlatformPanels[0].key) + const [activeTab, setActiveTab] = useState(TAB_GLOBAL) + + // 校验失败时出错字段可能藏在未选中的 Tab / 平台面板里(字段全部挂载、只切 hidden), + // 用户看不到红字也不知道为什么保存没反应。这里切到第一个出错字段所在的面板并滚过去。 + const [pendingField, setPendingField] = useState(null) + const handleSubmitFail = (errors: Record) => { + const fields = errorFieldPaths(errors) + .map(path => ({ path, el: fieldElement(path) })) + .filter((f): f is { path: string; el: HTMLElement } => !!f.el) + .sort((a, b) => (a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1)) + const first = fields[0] + if (!first) return + const tab = first.el.closest('[data-tab]')?.dataset.tab + const platform = first.el.closest('[data-platform]')?.dataset.platform + if (tab) setActiveTab(tab) + if (platform) setActivePlatform(platform) + setPendingField(first.path) + Toast.warning(`有 ${fields.length} 项未通过校验,已定位到第一项`) + } + useEffect(() => { + if (!pendingField) return + const el = fieldElement(pendingField) + el?.scrollIntoView({ block: 'center', behavior: 'smooth' }) + el?.querySelector('input, textarea')?.focus({ preventScroll: true }) + setPendingField(null) + }, [pendingField]) if (isLoading) { return <>Loading @@ -105,92 +142,86 @@ const { data: entity, error, isLoading } = useSWR('/v1/configuration', fetcher) } /> -
-
-
-
-
{ - try { - const payload = { ...values } - if (payload.file_size === undefined || payload.file_size === '') { - payload.file_size = null - } - if (payload.segment_time === undefined || payload.segment_time === '') { - payload.segment_time = null - } - await trigger(payload) - Toast.success('保存成功') - } catch (e: any) { - // error handling - Notification.error({ - title: '保存失败', - content: {e.message}, - // theme: 'light', - // duration: 0, - style: { width: 'min-content' }, - }) - throw e - } - }} - getFormApi={formApi => (formRef.current = formApi)} - > - - - {/* 全局设置 */} - - - - {/* 平台设置:左列平台名 + 右栏选中表单 */} -
- } title="平台设置" /> -
- -
- {/* 所有平台组件保持挂载(keepDOM),仅展开当前平台面板。 - 卸载会注销 Semi Form 字段状态,提交时仅剩挂载字段;后端 PUT /configuration - 整表覆盖保存,会清空其他平台的参数与凭据 */} - - {PlatformPanels.map(p => ( - - ))} - -
-
-
-
- - {/* 开发者选项 */} - - -
- - - -
-
-
+ {/* 页头下方占满剩余高度;每个 Tab 面板在内部滚动,页面本身不滚,「保存」始终可见 */} +
+
{ + try { + const payload = { ...values } + if (payload.file_size === undefined || payload.file_size === '') { + payload.file_size = null + } + if (payload.segment_time === undefined || payload.segment_time === '') { + payload.segment_time = null + } + await trigger(payload) + Toast.success('保存成功') + } catch (e: any) { + // error handling + Notification.error({ + title: '保存失败', + content: {e.message}, + // theme: 'light', + // duration: 0, + style: { width: 'min-content' }, + }) + throw e + } + }} + onSubmitFail={handleSubmitFail} + getFormApi={formApi => (formRef.current = formApi)} + > + + +
+ +
+
+ + {/* 左列平台列表 + 右栏选中平台的字段,两栏并排、各自独立滚动 */} +
+ +
+ {/* 所有平台的字段始终保持挂载,只用 hidden 切换显示。 + 卸载会注销 Semi Form 字段状态,提交时仅剩挂载字段;后端 PUT /configuration + 整表覆盖保存,会清空其他平台的参数与凭据 */} + {PlatformPanels.map(p => ( + + ))} +
+
+
+ +
+ +
+
+
+
) 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, }} /> - + ) }