fix(external-admin): refine authenticated branding
This commit is contained in:
parent
7bc1b6a423
commit
c67d2e2d4b
@ -22,7 +22,7 @@ const displayScopeOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function BannerCreateDialog({ loading, onClose, onSubmit, open }) {
|
export function BannerCreateDialog({ loading, onClose, onSubmit, open }) {
|
||||||
const { locale, t } = useExternalI18n();
|
const { direction, locale, t } = useExternalI18n();
|
||||||
const [form, setForm] = useState(initialForm);
|
const [form, setForm] = useState(initialForm);
|
||||||
useEffect(() => setForm(initialForm), [open]);
|
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 })} />
|
<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>
|
</Stack>
|
||||||
<TimeRangeFilter
|
<TimeRangeFilter
|
||||||
|
direction={direction}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
label={t("banners.schedule")}
|
label={t("banners.schedule")}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
|||||||
@ -138,7 +138,18 @@ export function ExternalAdminLayout() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<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} />
|
<Box flex={1} />
|
||||||
<Stack direction="row" spacing={1.5} sx={{ alignItems: "center" }}>
|
<Stack direction="row" spacing={1.5} sx={{ alignItems: "center" }}>
|
||||||
<LanguageSwitcher compact={mobile} />
|
<LanguageSwitcher compact={mobile} />
|
||||||
@ -193,7 +204,7 @@ export function ExternalAdminLayout() {
|
|||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
<Divider sx={{ my: 1.5 }} />
|
<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 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>
|
<Button fullWidth startIcon={<LogoutOutlined />} variant="outlined" onClick={handleLogout}>{t("auth.logout")}</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@ -55,20 +55,9 @@ const CAPABILITY_ICONS = {
|
|||||||
export default function OverviewPage() {
|
export default function OverviewPage() {
|
||||||
const { session } = useExternalAuth();
|
const { session } = useExternalAuth();
|
||||||
const { t } = useExternalI18n();
|
const { t } = useExternalI18n();
|
||||||
const enabledCount = EXTERNAL_CAPABILITIES.filter((item) => hasExternalCapability(session, item.code)).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExternalPage title={t("nav.overview")}>
|
<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">
|
<Box className="external-capability-grid">
|
||||||
{EXTERNAL_CAPABILITIES.map((item) => {
|
{EXTERNAL_CAPABILITIES.map((item) => {
|
||||||
const enabled = hasExternalCapability(session, item.code);
|
const enabled = hasExternalCapability(session, item.code);
|
||||||
|
|||||||
@ -224,6 +224,22 @@ body {
|
|||||||
letter-spacing: 0.03em;
|
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 {
|
.external-user-avatar {
|
||||||
width: 34px !important;
|
width: 34px !important;
|
||||||
height: 34px !important;
|
height: 34px !important;
|
||||||
|
|||||||
@ -49,7 +49,7 @@ const defaultMessages = {
|
|||||||
|
|
||||||
// withTime=false 时是纯日期区间选择器:隐藏时/分与小时级快捷区间,
|
// withTime=false 时是纯日期区间选择器:隐藏时/分与小时级快捷区间,
|
||||||
// 选中的开始/结束日分别取当天 00:00:00.000 与 23:59:59.999 的本地毫秒。
|
// 选中的开始/结束日分别取当天 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 copy = useMemo(() => ({ ...defaultMessages, ...messages }), [messages]);
|
||||||
const range = useMemo(() => normalizeRange(value), [value]);
|
const range = useMemo(() => normalizeRange(value), [value]);
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
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 rangeDistance = formatLocalizedRangeDistance(draft.startMs, draft.endMs, withTime, copy);
|
||||||
const weekLabels = useMemo(() => localizedWeekLabels(locale), [locale]);
|
const weekLabels = useMemo(() => localizedWeekLabels(locale), [locale]);
|
||||||
const hasRange = Boolean(range.startMs || range.endMs);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
@ -183,9 +189,9 @@ export function TimeRangeFilter({ className = "", disabled = false, label = "时
|
|||||||
<Popover
|
<Popover
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
open={open}
|
open={open}
|
||||||
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
|
anchorOrigin={{ horizontal: horizontalOrigin, vertical: "bottom" }}
|
||||||
slotProps={{ paper: { className: styles.paper } }}
|
slotProps={{ paper: { className: styles.paper, dir: direction } }}
|
||||||
transformOrigin={{ horizontal: "left", vertical: "top" }}
|
transformOrigin={{ horizontal: horizontalOrigin, vertical: "top" }}
|
||||||
onClose={closePopover}
|
onClose={closePopover}
|
||||||
>
|
>
|
||||||
<form className={styles.panel} onSubmit={submitDraft}>
|
<form className={styles.panel} onSubmit={submitDraft}>
|
||||||
@ -233,11 +239,11 @@ export function TimeRangeFilter({ className = "", disabled = false, label = "时
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.calendarHeader}>
|
<div className={styles.calendarHeader}>
|
||||||
<IconButton aria-label={copy.previousMonth} size="small" onClick={() => changeMonth(-1)}>
|
<IconButton aria-label={copy.previousMonth} size="small" onClick={() => changeMonth(-1)}>
|
||||||
<ChevronLeftOutlined fontSize="small" />
|
<PreviousMonthIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<span className={styles.calendarTitle}>{monthTitle(viewDate, locale)}</span>
|
<span className={styles.calendarTitle}>{monthTitle(viewDate, locale)}</span>
|
||||||
<IconButton aria-label={copy.nextMonth} size="small" onClick={() => changeMonth(1)}>
|
<IconButton aria-label={copy.nextMonth} size="small" onClick={() => changeMonth(1)}>
|
||||||
<ChevronRightOutlined fontSize="small" />
|
<NextMonthIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.calendarGrid} ${styles.rangeCalendarGrid}`}>
|
<div className={`${styles.calendarGrid} ${styles.rangeCalendarGrid}`}>
|
||||||
|
|||||||
@ -9,7 +9,8 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
min-width: 132px !important;
|
min-width: 132px !important;
|
||||||
max-width: min(420px, 100%);
|
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;
|
border-color: var(--border) !important;
|
||||||
background: var(--bg-input-strong) !important;
|
background: var(--bg-input-strong) !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@ -29,8 +30,8 @@
|
|||||||
|
|
||||||
.triggerMeta {
|
.triggerMeta {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -7px;
|
inset-block-start: -7px;
|
||||||
left: var(--space-2);
|
inset-inline-start: var(--space-2);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: calc(100% - var(--space-4));
|
max-width: calc(100% - var(--space-4));
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -58,15 +59,15 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: var(--control-font-size);
|
font-size: var(--control-font-size);
|
||||||
line-height: var(--control-line-height);
|
line-height: var(--control-line-height);
|
||||||
text-align: left;
|
text-align: start;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.triggerChevron {
|
.triggerChevron {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
inset-block-start: 50%;
|
||||||
right: var(--space-2);
|
inset-inline-end: var(--space-2);
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
@ -94,8 +95,7 @@
|
|||||||
|
|
||||||
.quickButton {
|
.quickButton {
|
||||||
min-width: 0 !important;
|
min-width: 0 !important;
|
||||||
padding-right: var(--space-2) !important;
|
padding-inline: var(--space-2) !important;
|
||||||
padding-left: var(--space-2) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rangeFields {
|
.rangeFields {
|
||||||
@ -115,7 +115,7 @@
|
|||||||
border-radius: var(--radius-control);
|
border-radius: var(--radius-control);
|
||||||
background: var(--bg-input-strong);
|
background: var(--bg-input-strong);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
text-align: left;
|
text-align: start;
|
||||||
transition:
|
transition:
|
||||||
border-color var(--motion-fast) var(--ease-standard),
|
border-color var(--motion-fast) var(--ease-standard),
|
||||||
box-shadow var(--motion-fast) var(--ease-standard),
|
box-shadow var(--motion-fast) var(--ease-standard),
|
||||||
@ -275,11 +275,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dayRangeStart {
|
.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 {
|
.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 {
|
.dayRangeStart.dayRangeEnd {
|
||||||
@ -304,8 +308,8 @@
|
|||||||
|
|
||||||
.daySelected::after {
|
.daySelected::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1px;
|
inset-block-start: 1px;
|
||||||
right: 3px;
|
inset-inline-end: 3px;
|
||||||
color: currentcolor;
|
color: currentcolor;
|
||||||
content: attr(data-range-edge);
|
content: attr(data-range-edge);
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user