fix(external-admin): refine authenticated branding

This commit is contained in:
zhx 2026-07-15 19:57:01 +08:00
parent 7bc1b6a423
commit c67d2e2d4b
6 changed files with 60 additions and 33 deletions

View File

@ -22,7 +22,7 @@ const displayScopeOptions = [
];
export function BannerCreateDialog({ loading, onClose, onSubmit, open }) {
const { locale, t } = useExternalI18n();
const { direction, locale, t } = useExternalI18n();
const [form, setForm] = useState(initialForm);
useEffect(() => setForm(initialForm), [open]);
@ -79,6 +79,7 @@ export function BannerCreateDialog({ loading, onClose, onSubmit, open }) {
<TextField disabled={loading} label={t("banners.sort")} slotProps={{ htmlInput: { min: 0 } }} type="number" value={form.sortOrder} onChange={(event) => setForm({ ...form, sortOrder: event.target.value })} />
</Stack>
<TimeRangeFilter
direction={direction}
disabled={loading}
label={t("banners.schedule")}
locale={locale}

View File

@ -138,7 +138,18 @@ export function ExternalAdminLayout() {
}}
>
<Toolbar>
<Typography className="external-header-title" component="h2" noWrap>{mobile && !current ? t("app.shortTitle") : t(current?.labelKey || "app.shortTitle")}</Typography>
{mobile ? (
<AppIdentity
appCode={session?.appCode}
appName={session?.appName || session?.appCode}
className="external-mobile-brand"
logoUrl={session?.logoUrl}
showLogoFallback={false}
size="small"
/>
) : (
<Typography className="external-header-title" component="h2" noWrap>{t(current?.labelKey || "app.shortTitle")}</Typography>
)}
<Box flex={1} />
<Stack direction="row" spacing={1.5} sx={{ alignItems: "center" }}>
<LanguageSwitcher compact={mobile} />
@ -193,7 +204,7 @@ export function ExternalAdminLayout() {
})}
</Box>
<Divider sx={{ my: 1.5 }} />
<Stack direction="row" spacing={1.5}>
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
<Button color="inherit" fullWidth startIcon={<KeyOutlined />} onClick={() => { setSheetOpen(false); navigate("/change-password"); }}>{t("auth.changePassword")}</Button>
<Button fullWidth startIcon={<LogoutOutlined />} variant="outlined" onClick={handleLogout}>{t("auth.logout")}</Button>
</Stack>

View File

@ -55,20 +55,9 @@ const CAPABILITY_ICONS = {
export default function OverviewPage() {
const { session } = useExternalAuth();
const { t } = useExternalI18n();
const enabledCount = EXTERNAL_CAPABILITIES.filter((item) => hasExternalCapability(session, item.code)).length;
return (
<ExternalPage title={t("nav.overview")}>
<Box className="external-hero">
<Typography className="external-hero-eyebrow">{t("overview.welcome")}</Typography>
<Typography className="external-hero-title" component="h2">{session.appName || session.appCode}</Typography>
<Box className="external-hero-meta">
<Box><Typography color="text.secondary" variant="caption">{t("overview.currentApp")}</Typography><Typography fontWeight={700}>{session.appName || session.appCode} ({session.appCode})</Typography></Box>
<Box><Typography color="text.secondary" variant="caption">{t("overview.account")}</Typography><Typography fontWeight={700}>{session.account}</Typography></Box>
<Box><Typography color="text.secondary" variant="caption">{t("overview.permissions")}</Typography><Typography fontWeight={700}>{enabledCount} / {EXTERNAL_CAPABILITIES.length}</Typography></Box>
</Box>
</Box>
<Typography className="external-section-title" component="h3">{t("overview.features")}</Typography>
<Box className="external-capability-grid">
{EXTERNAL_CAPABILITIES.map((item) => {
const enabled = hasExternalCapability(session, item.code);

View File

@ -224,6 +224,22 @@ body {
letter-spacing: 0.03em;
}
.external-mobile-brand {
max-width: min(48vw, 180px);
color: var(--primary-strong);
font-family: var(--font-display);
font-size: 15px;
font-weight: 750;
letter-spacing: 0.04em;
}
.external-mobile-brand img,
.external-sidebar-brand img,
.external-change-password-brand img {
border-color: var(--primary-border);
box-shadow: 0 0 0 1px rgba(214, 181, 110, 0.08), 0 8px 24px rgba(0, 0, 0, 0.38);
}
.external-user-avatar {
width: 34px !important;
height: 34px !important;

View File

@ -49,7 +49,7 @@ const defaultMessages = {
// withTime=false /
// / 00:00:00.000 23:59:59.999
export function TimeRangeFilter({ className = "", disabled = false, label = "时间", locale = "zh-CN", messages, onChange, value, withTime = true }) {
export function TimeRangeFilter({ className = "", direction = "ltr", disabled = false, label = "时间", locale = "zh-CN", messages, onChange, value, withTime = true }) {
const copy = useMemo(() => ({ ...defaultMessages, ...messages }), [messages]);
const range = useMemo(() => normalizeRange(value), [value]);
const [anchorEl, setAnchorEl] = useState(null);
@ -69,6 +69,12 @@ export function TimeRangeFilter({ className = "", disabled = false, label = "时
const rangeDistance = formatLocalizedRangeDistance(draft.startMs, draft.endMs, withTime, copy);
const weekLabels = useMemo(() => localizedWeekLabels(locale), [locale]);
const hasRange = Boolean(range.startMs || range.endMs);
// CSS Modules do not pass through the external-admin Emotion RTL cache. Keep
// popover anchoring and directional glyphs explicit so Arabic mirrors as a unit.
const rtl = direction === "rtl";
const horizontalOrigin = rtl ? "right" : "left";
const PreviousMonthIcon = rtl ? ChevronRightOutlined : ChevronLeftOutlined;
const NextMonthIcon = rtl ? ChevronLeftOutlined : ChevronRightOutlined;
useEffect(() => {
if (!open) {
@ -183,9 +189,9 @@ export function TimeRangeFilter({ className = "", disabled = false, label = "时
<Popover
anchorEl={anchorEl}
open={open}
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
slotProps={{ paper: { className: styles.paper } }}
transformOrigin={{ horizontal: "left", vertical: "top" }}
anchorOrigin={{ horizontal: horizontalOrigin, vertical: "bottom" }}
slotProps={{ paper: { className: styles.paper, dir: direction } }}
transformOrigin={{ horizontal: horizontalOrigin, vertical: "top" }}
onClose={closePopover}
>
<form className={styles.panel} onSubmit={submitDraft}>
@ -233,11 +239,11 @@ export function TimeRangeFilter({ className = "", disabled = false, label = "时
</div>
<div className={styles.calendarHeader}>
<IconButton aria-label={copy.previousMonth} size="small" onClick={() => changeMonth(-1)}>
<ChevronLeftOutlined fontSize="small" />
<PreviousMonthIcon fontSize="small" />
</IconButton>
<span className={styles.calendarTitle}>{monthTitle(viewDate, locale)}</span>
<IconButton aria-label={copy.nextMonth} size="small" onClick={() => changeMonth(1)}>
<ChevronRightOutlined fontSize="small" />
<NextMonthIcon fontSize="small" />
</IconButton>
</div>
<div className={`${styles.calendarGrid} ${styles.rangeCalendarGrid}`}>

View File

@ -9,7 +9,8 @@
width: auto;
min-width: 132px !important;
max-width: min(420px, 100%);
padding: 7px 28px 3px var(--control-padding-x) !important;
padding-block: 7px 3px !important;
padding-inline: var(--control-padding-x) 28px !important;
border-color: var(--border) !important;
background: var(--bg-input-strong) !important;
box-shadow: none !important;
@ -29,8 +30,8 @@
.triggerMeta {
position: absolute;
top: -7px;
left: var(--space-2);
inset-block-start: -7px;
inset-inline-start: var(--space-2);
display: inline-flex;
max-width: calc(100% - var(--space-4));
align-items: center;
@ -58,15 +59,15 @@
color: var(--text-primary);
font-size: var(--control-font-size);
line-height: var(--control-line-height);
text-align: left;
text-align: start;
text-overflow: ellipsis;
white-space: nowrap;
}
.triggerChevron {
position: absolute;
top: 50%;
right: var(--space-2);
inset-block-start: 50%;
inset-inline-end: var(--space-2);
color: var(--text-tertiary);
transform: translateY(-50%);
}
@ -94,8 +95,7 @@
.quickButton {
min-width: 0 !important;
padding-right: var(--space-2) !important;
padding-left: var(--space-2) !important;
padding-inline: var(--space-2) !important;
}
.rangeFields {
@ -115,7 +115,7 @@
border-radius: var(--radius-control);
background: var(--bg-input-strong);
color: var(--text-primary);
text-align: left;
text-align: start;
transition:
border-color var(--motion-fast) var(--ease-standard),
box-shadow var(--motion-fast) var(--ease-standard),
@ -275,11 +275,15 @@
}
.dayRangeStart {
border-radius: var(--radius-control) 0 0 var(--radius-control);
border-radius: 0;
border-start-start-radius: var(--radius-control);
border-end-start-radius: var(--radius-control);
}
.dayRangeEnd {
border-radius: 0 var(--radius-control) var(--radius-control) 0;
border-radius: 0;
border-start-end-radius: var(--radius-control);
border-end-end-radius: var(--radius-control);
}
.dayRangeStart.dayRangeEnd {
@ -304,8 +308,8 @@
.daySelected::after {
position: absolute;
top: 1px;
right: 3px;
inset-block-start: 1px;
inset-inline-end: 3px;
color: currentcolor;
content: attr(data-range-edge);
font-size: 8px;