446baf5939
- 页头平台选择器:PlatformProvider 加载平台目录与平台上下文
(X-Atlas-Platform-Id header),平台 id 仅作为非敏感 UI 偏好存
localStorage,默认取目录第一家(Ozon 优先)
- 店铺管理页覆盖 docs/api/stores-and-platforms.md 已实现接口:
游标分页列表、创建(202+operation)、编辑(If-Match 乐观锁、
409 刷新提示)、设为默认、重新验证凭据、仓库同步(422 脱敏
错误展示)、软删除(DEFAULT_STORE_REPLACEMENT_REQUIRED 提示)
- services/api.ts 新增 getList(保留 {data, meta} 分页信封)与
apiErrorCode;services/stores.ts 按契约生成 Idempotency-Key
- @atlas/contracts 补充只读响应类型 Platform / PlatformContext /
CursorPaginationMeta / StoreOperationResponse(无行为变更)
- API Key 仅存在于提交瞬间的内存表单,不落任何存储
- 浏览器实测:注册→平台切换→创建/编辑/设默认/删除保护/同步
错误路径→刷新会话恢复全部通过;typecheck + production build 通过
87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
import type { ReactNode } from 'react';
|
|
import { Navigate, useLocation, createBrowserRouter } from 'react-router-dom';
|
|
import MainLayout from '@/layouts/MainLayout';
|
|
import SessionSplash from '@/components/SessionSplash';
|
|
import CollectionPage from '@/pages/collection/CollectionPage';
|
|
import LoginPage from '@/pages/auth/LoginPage';
|
|
import OnlineProductsPage from '@/pages/online-products/OnlineProductsPage';
|
|
import PasswordSettingsPage from '@/pages/settings/PasswordSettingsPage';
|
|
import PricingPage from '@/pages/pricing/PricingPage';
|
|
import ProductEditPage from '@/pages/products/ProductEditPage';
|
|
import ProfileSettingsPage from '@/pages/settings/ProfileSettingsPage';
|
|
import RegisterPage from '@/pages/auth/RegisterPage';
|
|
import ShopsPage from '@/pages/shops/ShopsPage';
|
|
import WatermarksPage from '@/pages/watermarks/WatermarksPage';
|
|
import { PlatformProvider } from '@/services/platform';
|
|
import { useSession } from '@/services/session';
|
|
|
|
/** 主布局守卫:会话恢复中显示启动屏;未认证跳登录页并记住来源路径 */
|
|
function RequireSession({ children }: { children: ReactNode }) {
|
|
const { status } = useSession();
|
|
const location = useLocation();
|
|
|
|
if (status === 'restoring') {
|
|
return <SessionSplash />;
|
|
}
|
|
if (status === 'anonymous') {
|
|
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
|
|
}
|
|
return children;
|
|
}
|
|
|
|
/** 认证页互斥:已登录访问 /login、/register 时回到来源页或主布局 */
|
|
function RedirectIfAuthenticated({ children }: { children: ReactNode }) {
|
|
const { status } = useSession();
|
|
const location = useLocation();
|
|
|
|
if (status === 'authenticated') {
|
|
const from = (location.state as { from?: string } | null)?.from;
|
|
return <Navigate to={from && from.startsWith('/') ? from : '/'} replace />;
|
|
}
|
|
return children;
|
|
}
|
|
|
|
export const router = createBrowserRouter([
|
|
{
|
|
path: '/',
|
|
element: (
|
|
<RequireSession>
|
|
{/* 平台上下文仅在登录后可用,随主布局挂载 */}
|
|
<PlatformProvider>
|
|
<MainLayout />
|
|
</PlatformProvider>
|
|
</RequireSession>
|
|
),
|
|
children: [
|
|
{ index: true, element: <Navigate to="/online-products" replace /> },
|
|
{ path: 'online-products', element: <OnlineProductsPage /> },
|
|
{ path: 'collection', element: <CollectionPage /> },
|
|
{ path: 'shops', element: <ShopsPage /> },
|
|
{ path: 'watermarks', element: <WatermarksPage /> },
|
|
{ path: 'pricing', element: <PricingPage /> },
|
|
// 商品 AI 编辑页:从采集箱/在线商品进入,不在左侧菜单中出现
|
|
{ path: 'products/:id', element: <ProductEditPage /> },
|
|
// 个人中心设置页:从页头用户菜单进入,不在左侧菜单中出现
|
|
{ path: 'settings/profile', element: <ProfileSettingsPage /> },
|
|
{ path: 'settings/password', element: <PasswordSettingsPage /> },
|
|
],
|
|
},
|
|
{
|
|
path: 'login',
|
|
element: (
|
|
<RedirectIfAuthenticated>
|
|
<LoginPage />
|
|
</RedirectIfAuthenticated>
|
|
),
|
|
},
|
|
{
|
|
path: 'register',
|
|
element: (
|
|
<RedirectIfAuthenticated>
|
|
<RegisterPage />
|
|
</RedirectIfAuthenticated>
|
|
),
|
|
},
|
|
{ path: '*', element: <Navigate to="/" replace /> },
|
|
]);
|