mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-28 07:36:38 +08:00
fix(ui): improve date input parsing and add missing back navigation (#284)
## Summary - Improve DatePicker text parsing to accept more input formats (including `YYYYMMDD`) without requiring explicit separators - Add missing H5 back-navigation for panel-sharing by using simple layout route - Add a back button on config page with history fallback (`navigate(-1)` then `/profile`) ## Why These are usability/accessibility improvements that are generic and not project-brand specific: - Mobile keyboards may not easily input `-` in date fields - Certain pages in H5 lacked a consistent return path - Config page had no explicit in-page back action ## Scope - `vite-frontend/src/shadcn-bridge/heroui/date-picker.tsx` - `vite-frontend/src/App.tsx` - `vite-frontend/src/pages/config.tsx` ## Notes No branding/identity/ownership/visual-theme customizations included in this PR.
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
# 017 PR 284 UI Follow-up Fixes
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [x] Review the current frontend route and component state related to PR 284 follow-up fixes.
|
||||||
|
- [x] Restore the intended H5 simple-layout route behavior for panel sharing.
|
||||||
|
- [x] Improve date text parsing to support separator-free and flexible formats without ambiguous fallbacks.
|
||||||
|
- [x] Add config-page back navigation with a safer history fallback and shared icon usage.
|
||||||
|
- [x] Run focused frontend verification for the updated files and record the result.
|
||||||
|
|
||||||
|
## Test Record
|
||||||
|
|
||||||
|
- Command: `cd vite-frontend && npm install`
|
||||||
|
- Result: passed.
|
||||||
|
- Command: `cd vite-frontend && npm run build`
|
||||||
|
- Result: passed.
|
||||||
@@ -188,7 +188,7 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute useSimpleLayout={true}>
|
||||||
<PanelSharingPage />
|
<PanelSharingPage />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,3 +259,29 @@ export const SettingsIcon = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const BackIcon = ({
|
||||||
|
size = 24,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...props
|
||||||
|
}: IconSvgProps) => (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
focusable="false"
|
||||||
|
height={size || height}
|
||||||
|
role="presentation"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size || width}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15 19l-7-7 7-7"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Button } from "@/shadcn-bridge/heroui/button";
|
import { Button } from "@/shadcn-bridge/heroui/button";
|
||||||
|
import { BackIcon } from "@/components/icons";
|
||||||
import { BrandLogo } from "@/components/brand-logo";
|
import { BrandLogo } from "@/components/brand-logo";
|
||||||
import { siteConfig } from "@/config/site";
|
import { siteConfig } from "@/config/site";
|
||||||
import { useScrollTopOnPathChange } from "@/hooks/useScrollTopOnPathChange";
|
import { useScrollTopOnPathChange } from "@/hooks/useScrollTopOnPathChange";
|
||||||
@@ -25,13 +26,7 @@ export default function H5SimpleLayout({
|
|||||||
<header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
<header className="bg-white dark:bg-black shadow-sm border-b border-gray-200 dark:border-gray-600 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button isIconOnly size="sm" variant="light" onPress={handleBack}>
|
<Button isIconOnly size="sm" variant="light" onPress={handleBack}>
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
<BackIcon className="w-5 h-5" />
|
||||||
<path
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
|
||||||
fillRule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
</Button>
|
||||||
<BrandLogo size={20} />
|
<BrandLogo size={20} />
|
||||||
<h1 className="text-sm font-bold text-foreground">
|
<h1 className="text-sm font-bold text-foreground">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
updateAnnouncement,
|
updateAnnouncement,
|
||||||
type AnnouncementData,
|
type AnnouncementData,
|
||||||
} from "@/api";
|
} from "@/api";
|
||||||
import { SettingsIcon } from "@/components/icons";
|
import { BackIcon, SettingsIcon } from "@/components/icons";
|
||||||
import { isAdmin } from "@/utils/auth";
|
import { isAdmin } from "@/utils/auth";
|
||||||
import { getCachedConfigs, configCache, updateSiteConfig } from "@/config/site";
|
import { getCachedConfigs, configCache, updateSiteConfig } from "@/config/site";
|
||||||
import {
|
import {
|
||||||
@@ -234,6 +234,21 @@ export default function ConfigPage() {
|
|||||||
Partial<Record<BrandPreviewKey, boolean>>
|
Partial<Record<BrandPreviewKey, boolean>>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
|
const canGoBack =
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
typeof window.history.state?.idx === "number" &&
|
||||||
|
window.history.state.idx > 0;
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (canGoBack) {
|
||||||
|
navigate(-1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate("/profile", { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
// 权限检查
|
// 权限检查
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAdmin()) {
|
if (!isAdmin()) {
|
||||||
@@ -846,6 +861,16 @@ export default function ConfigPage() {
|
|||||||
<div className="p-6 max-w-4xl mx-auto">
|
<div className="p-6 max-w-4xl mx-auto">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<Button
|
||||||
|
isIconOnly
|
||||||
|
aria-label="返回上一页"
|
||||||
|
className="min-w-0 w-9 h-9"
|
||||||
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
|
onPress={handleBack}
|
||||||
|
>
|
||||||
|
<BackIcon className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
<SettingsIcon className="w-8 h-8 text-primary" />
|
<SettingsIcon className="w-8 h-8 text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">网站配置</h1>
|
<h1 className="text-2xl font-bold">网站配置</h1>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Select, SelectItem } from "@/shadcn-bridge/heroui/select";
|
|||||||
import { Switch } from "@/shadcn-bridge/heroui/switch";
|
import { Switch } from "@/shadcn-bridge/heroui/switch";
|
||||||
import { reinitializeBaseURL } from "@/api/network";
|
import { reinitializeBaseURL } from "@/api/network";
|
||||||
import { getConfigByName, updateConfig } from "@/api";
|
import { getConfigByName, updateConfig } from "@/api";
|
||||||
|
import { BackIcon } from "@/components/icons";
|
||||||
import {
|
import {
|
||||||
type UpdateReleaseChannel,
|
type UpdateReleaseChannel,
|
||||||
getUpdateReleaseChannel,
|
getUpdateReleaseChannel,
|
||||||
@@ -168,20 +169,7 @@ export const SettingsPage = () => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
onPress={() => navigate(-1)}
|
onPress={() => navigate(-1)}
|
||||||
>
|
>
|
||||||
<svg
|
<BackIcon className="w-5 h-5" />
|
||||||
aria-hidden="true"
|
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15 19l-7-7 7-7"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</Button>
|
</Button>
|
||||||
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
面板设置
|
面板设置
|
||||||
|
|||||||
@@ -38,15 +38,33 @@ function parseDateText(value: string) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matched = trimmed.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})$/);
|
const digitsOnly = trimmed.replace(/\D/g, "");
|
||||||
|
|
||||||
if (!matched) {
|
if (/^\d+$/.test(trimmed)) {
|
||||||
|
if (digitsOnly.length !== 8) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const year = Number(digitsOnly.slice(0, 4));
|
||||||
|
const month = Number(digitsOnly.slice(4, 6));
|
||||||
|
const day = Number(digitsOnly.slice(6, 8));
|
||||||
|
|
||||||
|
if (!isValidCalendarDate(year, month, day)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { day, month, year };
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberParts = trimmed.match(/\d+/g);
|
||||||
|
|
||||||
|
if (!numberParts || numberParts.length !== 3 || numberParts[0].length !== 4) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const year = Number(matched[1]);
|
const year = Number(numberParts[0]);
|
||||||
const month = Number(matched[2]);
|
const month = Number(numberParts[1]);
|
||||||
const day = Number(matched[3]);
|
const day = Number(numberParts[2]);
|
||||||
|
|
||||||
if (!isValidCalendarDate(year, month, day)) {
|
if (!isValidCalendarDate(year, month, day)) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user