添加页面

This commit is contained in:
2024-11-29 17:28:06 +08:00
parent 4b7957256e
commit 804266c6dd
13 changed files with 1935 additions and 245 deletions

View File

@@ -1,3 +1,2 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@unocss preflights;
@unocss default;

View File

@@ -0,0 +1,114 @@
import {
Button,
ConfigProvider,
Dropdown,
Empty,
Input,
Layout,
Menu,
Modal,
Space,
Typography,
} from "@arco-design/web-react";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { IconDesktop, IconMoonFill, IconSunFill } from "@arco-design/web-react/icon";
import React, { ReactNode, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import "./index.css";
const MainLayout: React.FC<{
children: ReactNode;
className: string;
}> = ({ children, className }) => {
const [lightMode, setLightMode] = useState(localStorage.lightMode || "auto");
const importRef = useRef<RefInputType>(null);
const [importVisible, setImportVisible] = useState(false);
const { t } = useTranslation();
return (
<ConfigProvider
renderEmpty={() => {
return <Empty description={t("no_data")} />;
}}
>
<Layout>
<Layout.Header
style={{
height: "50px",
borderBottom: "1px solid var(--color-neutral-3)",
}}
className="flex items-center justify-between px-4"
>
<Modal
title={t("import_link")}
visible={importVisible}
onOk={async () => {
setImportVisible(false);
}}
onCancel={() => {
setImportVisible(false);
}}
>
<Input ref={importRef} defaultValue="" />
</Modal>
<div className="flex row items-center">
<img style={{ height: "40px" }} src="/assets/logo.png" alt="ScriptCat" />
<Typography.Title heading={4} className="!m-0">
ScriptCat
</Typography.Title>
</div>
<Space size="small" className="action-tools">
<Dropdown
droplist={
<Menu
onClickMenuItem={(key) => {
setLightMode(key);
localStorage.lightMode = key;
}}
selectedKeys={[lightMode]}
>
<Menu.Item key="light">
<IconSunFill /> Light
</Menu.Item>
<Menu.Item key="dark">
<IconMoonFill /> Dark
</Menu.Item>
<Menu.Item key="auto">
<IconDesktop /> {t("system_follow")}
</Menu.Item>
</Menu>
}
position="bl"
>
<Button
type="text"
size="small"
icon={
<>
{lightMode === "auto" && <IconDesktop />}
{lightMode === "light" && <IconSunFill />}
{lightMode === "dark" && <IconMoonFill />}
</>
}
style={{
color: "var(--color-text-1)",
}}
className="!text-lg"
/>
</Dropdown>
</Space>
</Layout.Header>
<Layout
className={`absolute top-50px bottom-0 w-full ${className}`}
style={{
background: "var(--color-fill-2)",
}}
>
{children}
</Layout>
</Layout>
</ConfigProvider>
);
};
export default MainLayout;

View File

@@ -0,0 +1,11 @@
.arco-dropdown-menu-selected {
background-color: var(--color-fill-2) !important;
}
.action-tools .arco-dropdown-popup-visible .arco-icon-down {
transform: rotate(180deg);
}
.action-tools>.arco-btn {
padding: 0 8px;
}

View File

@@ -1,10 +1,15 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import MainLayout from "../components/layout/MainLayout.tsx";
import "@arco-design/web-react/dist/css/arco.css";
import "@App/locales/locales";
import "@App/index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
<MainLayout className="!flex-col !px-4 box-border">
<App />
</MainLayout>
</React.StrictMode>
);

17
src/store/hooks.ts Normal file
View File

@@ -0,0 +1,17 @@
// This file serves as a central hub for re-exporting pre-typed Redux hooks.
// These imports are restricted elsewhere to ensure consistent
// usage of typed hooks throughout the application.
// We disable the ESLint rule here because this is the designated place
// for importing and re-exporting the typed versions of hooks.
import { useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "./store";
import { asyncThunkCreator, buildCreateSlice } from "@reduxjs/toolkit";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
// `buildCreateSlice` allows us to create a slice with async thunks.
export const createAppSlice = buildCreateSlice({
creators: { asyncThunk: asyncThunkCreator },
});

35
src/store/store.ts Normal file
View File

@@ -0,0 +1,35 @@
import type { Action, ThunkAction } from "@reduxjs/toolkit";
import { combineSlices, configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
// `combineSlices` automatically combines the reducers using
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
const rootReducer = combineSlices();
// Infer the `RootState` type from the root reducer
export type RootState = ReturnType<typeof rootReducer>;
// The store setup is wrapped in `makeStore` to allow reuse
// when setting up tests that need the same store config
export const makeStore = (preloadedState?: Partial<RootState>) => {
const store = configureStore({
reducer: rootReducer,
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
// middleware: (getDefaultMiddleware) => {
// return getDefaultMiddleware().concat(quotesApiSlice.middleware);
// },
preloadedState,
});
// configure listeners using the provided defaults
// optional, but required for `refetchOnFocus`/`refetchOnReconnect` behaviors
setupListeners(store.dispatch);
return store;
};
export const store = makeStore();
// Infer the type of `store`
export type AppStore = typeof store;
// Infer the `AppDispatch` type from the store itself
export type AppDispatch = AppStore["dispatch"];
export type AppThunk<ThunkReturnType = void> = ThunkAction<ThunkReturnType, RootState, unknown, Action>;