diff --git a/stash-web/src/App.css b/stash-web/src/App.css index e7019eb..0a66434 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -795,15 +795,7 @@ gap: var(--su-space-2); } -.admin-item-row > :first-child:not(.admin-item-title) { - grid-column: span 1; -} -.admin-item-title { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} .admin-nav-row { grid-template-columns: auto minmax(0, 1fr) auto; @@ -927,10 +919,6 @@ padding-left: var(--su-space-6); } -.admin-nav-group-row .admin-item-title { - color: var(--su-text-sub); - font-size: var(--su-font-size-sm); -} /* 归类页:一行一个分区,标题 / 类型 / 图标 / 色条 / 条数 / 保存 / 删除 */ .admin-category-row { @@ -977,62 +965,15 @@ justify-content: flex-start; } -.group-table-wrap { - overflow: hidden; - border: 1px solid var(--su-card-border); - border-radius: var(--su-radius-md); - background-color: var(--su-bg-card); -} -.group-table { - width: 100%; - border-collapse: collapse; - font-size: var(--su-font-size-md); -} -.group-table th { - padding: var(--su-space-3) var(--su-space-4); - background-color: var(--su-bg-subtle); - border-bottom: 1px solid var(--su-card-border); - color: var(--su-text-muted); - font-size: var(--su-font-size-sm); - font-weight: 600; - text-align: left; - white-space: nowrap; -} -.group-table td { - padding: var(--su-space-3) var(--su-space-4); - border-bottom: 1px solid var(--su-card-border); - vertical-align: middle; -} -.group-table tbody tr:last-child td { - border-bottom: none; -} -.group-table tbody tr:hover { - background-color: var(--su-bg-subtle); -} -.group-col-sort { - width: 110px; -} -.group-col-count { - width: 120px; - text-align: center !important; -} -.group-col-status { - width: 90px; - text-align: center !important; -} -.group-col-actions { - width: 190px; - text-align: right !important; -} .group-name-cell { display: flex; @@ -1183,49 +1124,12 @@ font-size: var(--su-font-size-sm); } -.group-sort { - color: var(--su-text-muted); - font-family: var(--su-font-mono); - font-size: var(--su-font-size-sm); -} -.group-count { - color: var(--su-text-sub); - text-align: center; -} -.group-actions { - display: flex; - justify-content: flex-end; - gap: var(--su-space-3); -} -.group-action { - display: inline-flex; - align-items: center; - gap: var(--su-space-1); - padding: 0; - border: none; - background: none; - color: var(--su-color-primary); - font-size: var(--su-font-size-sm); - cursor: pointer; - white-space: nowrap; -} -.group-action:hover { - text-decoration: underline; -} -.group-action-danger { - color: var(--su-color-danger); -} -.group-empty { - padding: var(--su-space-6) 0 !important; - color: var(--su-text-muted); - text-align: center; -} .group-icon-field { display: flex; @@ -1251,14 +1155,7 @@ gap: var(--su-space-3); } -/* 内容页:与分组管理一致的列表 */ -.entry-table .entry-col-href { - width: 36%; -} -.entry-table .entry-col-format { - width: 80px; -} .entry-format { display: inline-block; @@ -1269,9 +1166,6 @@ font-size: var(--su-font-size-sm); } -.entry-table .entry-col-group { - width: 180px; -} .entry-title { color: var(--su-text-main); @@ -1303,19 +1197,7 @@ a.entry-href:hover { font-size: var(--su-font-size-sm); } -/* 侧边栏勾选:标题在左,开关在右 */ -.sidebar-toggle-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--su-space-3); - padding: var(--su-space-1) 0; -} -.sidebar-toggle-row .admin-item-title { - flex: 1; - min-width: 0; -} /* 首页:左右栏共用一个滚动区,左侧吸顶交给 CSS sticky */ #page-scroll { @@ -1500,44 +1382,12 @@ a.entry-href:hover { min-width: 0; } -.cards-table-head { - display: flex; - align-items: center; - gap: var(--su-space-2); - margin-bottom: var(--su-space-2); -} -.cards-table-wrap { - margin-top: var(--su-space-2); - /* 列多了以后表格会比面板宽,横向滚动而不是把名字挤成两三行 */ - overflow-x: auto; -} -.cards-table th, -.cards-table td { - padding: var(--su-space-2); -} -.cards-col-limit { - width: 100px; -} -/* 分组名不换行,超长省略;列宽固定,保证五列都落在面板内 */ -.cards-table { - table-layout: fixed; -} -.cards-table td:first-child .group-name { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} -.cards-table th, -.cards-table td { - padding: var(--su-space-2) var(--su-space-4); -} /* 首页 Tab:侧边栏设置 + 首页卡片上下两段 */ .admin-home { @@ -1552,14 +1402,7 @@ a.entry-href:hover { gap: var(--su-space-3); } -/* 首页卡片:分组展示顺序(表格里的「顺序」列,上下箭头即时保存) */ -.cards-col-state { - width: 72px; -} -.cards-col-order { - width: 112px; -} .card-order-cell { display: flex; @@ -1682,17 +1525,7 @@ a.entry-href:hover { border-bottom: 1px solid var(--su-border); } -/* 删除按钮:可删的用危险色,不可删的用弱化色(都取主题令牌) */ -.admin-item-actions .admin-remove, -.cards-table-head .admin-remove { - color: var(--su-color-danger); -} -.admin-item-actions .admin-remove:not(:disabled):hover, -.cards-table-head .admin-remove:not(:disabled):hover { - color: var(--su-color-danger); - background-color: color-mix(in srgb, var(--su-color-danger) 10%, transparent); -} .admin-item-actions .admin-remove:disabled { color: var(--su-text-muted); @@ -1700,3 +1533,26 @@ a.entry-href:hover { opacity: 0.45; } + +/* 后台表格里的操作列与卡片标题栏(其余表格样式已交给 @stash/ui 的 Table) */ +.group-actions { + display: flex; + justify-content: flex-end; + gap: var(--su-space-3); +} + +.cards-table-head { + display: flex; + align-items: center; + gap: var(--su-space-2); + margin-bottom: var(--su-space-2); +} + +.cards-table-head .admin-remove { + color: var(--su-color-danger); +} + +.cards-table-head .admin-remove:not(:disabled):hover { + color: var(--su-color-danger); + background-color: color-mix(in srgb, var(--su-color-danger) 10%, transparent); +} diff --git a/stash-web/src/components/admin/AdminNext.tsx b/stash-web/src/components/admin/AdminNext.tsx index bb778fd..420fed5 100644 --- a/stash-web/src/components/admin/AdminNext.tsx +++ b/stash-web/src/components/admin/AdminNext.tsx @@ -2,6 +2,7 @@ import { Brand, Button, Card, + Empty, FormField, Icon, ICON_NAMES, @@ -14,11 +15,14 @@ import { Select, Space, Switch, + Table, Tabs, Tag, Text, Textarea, + TextButton, Title, + ToggleRow, Toaster, toast, type IconName, @@ -578,92 +582,75 @@ function GroupsPanel({ -
- - - - - - - - - - - - {rows.map(({ node, level }) => { - const hasChildren = node.children.length > 0 - const open = !collapsed.has(node.key) - return ( - - - - - - - - ) - })} - - {rows.length === 0 && ( - - - - )} - -
分组名称排序权重关联网页数状态操作
-
- {hasChildren ? ( -
-
{node.sort}{countGroupEntries(node)} - void toggleEnabled(node, next)} - label={`启用 ${node.title}`} - size="sm" - /> - -
- - - -
-
- 暂无分组数据,点击左侧「新建根分组」 -
-
+ { + const hasChildren = node.children.length > 0 + const open = !collapsed.has(node.key) + return { + key: node.key, + name: ( +
+ {hasChildren ? ( +
+ ), + sort: node.sort, + count: countGroupEntries(node), + status: ( + void toggleEnabled(node, next)} + label={`启用 ${node.title}`} + size="sm" + /> + ), + actions: ( +
+ } + onClick={() => setDialog({ mode: 'child', parent: node })} + > + 新建子分组 + + } + onClick={() => setDialog({ mode: 'edit', group: node })} + > + 编辑 + + } + onClick={() => onDelete(node.key, node.title)} + > + 删除 + +
+ ), + } + })} + emptyText={} + /> -
-
- - - - - - - - - - - {rows.map(({ entry, group: owner }) => ( - - - - - - - - ))} - - {rows.length === 0 && ( - - - - )} - -
标题格式跳转链接所属分组操作
- {entry.name ?? entry.title ?? entry.id} - - - {FORMAT_LABEL[(entry.format ?? 'apps') as GroupFormat]} - - - - - {owner.title} - -
- - -
-
- 该分组下还没有内容 -
- + ({ + key: entry.id, + title: {entry.name ?? entry.title ?? entry.id}, + format: ( + + {FORMAT_LABEL[(entry.format ?? 'apps') as GroupFormat]} + + ), + href: , + group: {owner.title}, + actions: ( +
+ } + onClick={() => setDialog({ key: entry.id, groupKey: owner.key })} + > + 编辑 + + } + onClick={async () => { + const result = await deleteItem(entry.id) + if (!result.ok) { + toast.error(result.error) + return + } + toast.success('条目已删除') + onReload() + }} + > + 删除 + +
+ ), + }))} + emptyText={} + /> 侧栏卡片 -
- 本地管理员 - onToggleCard('profile', visible)} - label="显示本地管理员卡片" - disabled={saving} - /> -
-
- 本地服务 - onToggleCard('service', visible)} - label="显示本地服务卡片" - disabled={saving} - /> -
-
- 分组导航 - onToggleCard('nav', visible)} - label="显示分组导航卡片" - disabled={saving} - /> -
+ onToggleCard('profile', visible)} + switchLabel="显示本地管理员卡片" + disabled={saving} + /> + onToggleCard('service', visible)} + switchLabel="显示本地服务卡片" + disabled={saving} + /> + onToggleCard('nav', visible)} + switchLabel="显示分组导航卡片" + disabled={saving} + /> ) } @@ -1634,42 +1594,23 @@ function LocalServiceTable({ projects }: { projects: Project[] }) { 本地服务卡片由托管目录自动生成,随项目扫描更新;后台只能查看,不能删除或改名。 -
-
- - - - - - - - {projects.map((project) => ( - - - - - ))} - {projects.length === 0 && ( - - - - )} - -
项目名称访问地址
- {project.name} - - - {project.route_path} - -
- 还没有托管项目 -
- + ({ + name: {project.name}, + href: ( + + {project.route_path} + + ), + }))} + emptyText={} + /> ) } @@ -1795,88 +1736,80 @@ function CardTable({ 说明:开关和条数修改后即时保存;条数留空使用默认。 -
-
- - - - - - - - - - - {options.map((option) => { - const checked = option.key in selected - return ( - - - - - - - - ) - })} - -
分组名称状态预览条数顺序展示
-
- {option.title} -
-
- - {option.enabled ? '启用' : '已停用'} - - - {checked ? ( - - setSelected((current) => ({ - ...current, - [option.key]: event.target.value, - })) - } - onBlur={() => void persistGroups(selected, order)} - /> - ) : ( - — - )} - - {checked ? ( -
- {order.indexOf(option.key) + 1} - } - size="xs" - disabled={busy || order.indexOf(option.key) === 0} - onClick={() => moveGroup(option.key, -1)} - /> - } - size="xs" - disabled={busy || order.indexOf(option.key) === order.length - 1} - onClick={() => moveGroup(option.key, 1)} - /> -
- ) : ( - — - )} -
- toggle(option.key, next)} - label={`卡片包含 ${option.title}`} - disabled={busy || !option.enabled} - /> -
- + { + const checked = option.key in selected + return { + key: option.key, + name: ( +
+ {option.title} +
+ ), + state: ( + + {option.enabled ? '启用' : '已停用'} + + ), + limit: checked ? ( + + setSelected((current) => ({ + ...current, + [option.key]: event.target.value, + })) + } + onBlur={() => void persistGroups(selected, order)} + /> + ) : ( + — + ), + order: checked ? ( +
+ {order.indexOf(option.key) + 1} + } + size="xs" + disabled={busy || order.indexOf(option.key) === 0} + onClick={() => moveGroup(option.key, -1)} + /> + } + size="xs" + disabled={busy || order.indexOf(option.key) === order.length - 1} + onClick={() => moveGroup(option.key, 1)} + /> +
+ ) : ( + — + ), + show: ( + toggle(option.key, next)} + label={`卡片包含 ${option.title}`} + disabled={busy || !option.enabled} + /> + ), + } + })} + emptyText={} + /> ) } @@ -1993,59 +1926,48 @@ function HeaderPanel({ 说明:这里控制页头右侧显示哪些图标按钮;关掉的按钮不再出现,也不影响其它页面。 -
- 明暗切换 - - onChangeSite({ ...site, actions: { ...site.actions, theme: next } }) - } - label="页头显示明暗切换按钮" - /> -
-
- 刷新 - - onChangeSite({ ...site, actions: { ...site.actions, refresh: next } }) - } - label="页头显示刷新按钮" - /> -
-
- 站点设置 - - onChangeSite({ ...site, actions: { ...site.actions, settings: next } }) - } - label="页头显示站点设置按钮" - /> -
-
- 登录 / 退出 - - onChangeSite({ ...site, actions: { ...site.actions, session: next } }) - } - label="页头显示登录或退出按钮" - /> -
+ + onChangeSite({ ...site, actions: { ...site.actions, theme: next } }) + } + switchLabel="页头显示明暗切换按钮" + /> + + onChangeSite({ ...site, actions: { ...site.actions, refresh: next } }) + } + switchLabel="页头显示刷新按钮" + /> + + onChangeSite({ ...site, actions: { ...site.actions, settings: next } }) + } + switchLabel="页头显示站点设置按钮" + /> + + onChangeSite({ ...site, actions: { ...site.actions, session: next } }) + } + switchLabel="页头显示登录或退出按钮" + /> )} {tab === 'ticker' && ( -
- 显示页头滚动信息 - onChange({ ...value, enabled })} - label="显示页头滚动信息" - /> -
+ onChange({ ...value, enabled })} + />