定义
This commit is contained in:
parent
b689233e91
commit
bc42edd38e
@ -25,13 +25,13 @@ pub struct InstallReplyData {
|
|||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/install", format = "application/json", data = "<data>")]
|
#[post("/sql", format = "application/json", data = "<data>")]
|
||||||
pub async fn install(
|
pub async fn steup_sql(
|
||||||
data: Json<InstallData>,
|
data: Json<InstallData>,
|
||||||
state: &State<Arc<AppState>>,
|
state: &State<Arc<AppState>>,
|
||||||
) -> AppResult<status::Custom<Json<InstallReplyData>>> {
|
) -> AppResult<status::Custom<Json<InstallReplyData>>> {
|
||||||
let mut config = config::Config::read().unwrap_or_default();
|
let mut config = config::Config::read().unwrap_or_default();
|
||||||
if config.info.install {
|
if config.init.sql {
|
||||||
return Err(status::Custom(
|
return Err(status::Custom(
|
||||||
Status::BadRequest,
|
Status::BadRequest,
|
||||||
"Database already initialized".to_string(),
|
"Database already initialized".to_string(),
|
||||||
@ -39,7 +39,7 @@ pub async fn install(
|
|||||||
}
|
}
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
let sql = {
|
let sql = {
|
||||||
config.info.install = true;
|
config.init.sql = true;
|
||||||
config.sql_config = data.sql_config.clone();
|
config.sql_config = data.sql_config.clone();
|
||||||
sql::Database::initial_setup(data.sql_config.clone())
|
sql::Database::initial_setup(data.sql_config.clone())
|
||||||
.await
|
.await
|
||||||
|
@ -7,7 +7,7 @@ use std::{env, fs};
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub port: u32,
|
pub port: u32,
|
||||||
pub info: Info,
|
pub init: Init,
|
||||||
pub sql_config: SqlConfig,
|
pub sql_config: SqlConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,23 +16,25 @@ impl Default for Config {
|
|||||||
Self {
|
Self {
|
||||||
address: "0.0.0.0".to_string(),
|
address: "0.0.0.0".to_string(),
|
||||||
port: 22000,
|
port: 22000,
|
||||||
info: Info::default(),
|
init: Init::default(),
|
||||||
sql_config: SqlConfig::default(),
|
sql_config: SqlConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||||
pub struct Info {
|
pub struct Init {
|
||||||
pub install: bool,
|
pub sql: bool,
|
||||||
pub non_relational: bool,
|
pub no_sql: bool,
|
||||||
|
pub administrator: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Info {
|
impl Default for Init {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
install: false,
|
sql: false,
|
||||||
non_relational: false,
|
no_sql: false,
|
||||||
|
administrator: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
frontend/app/env.d.ts
vendored
7
frontend/app/env.d.ts
vendored
@ -8,9 +8,10 @@
|
|||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_SERVER_API: string; // 用于访问API的基础URL
|
readonly VITE_SERVER_API: string; // 用于访问API的基础URL
|
||||||
readonly VITE_SYSTEM_PORT: number; // 系统端口
|
readonly VITE_ADDRESS: string; // 前端地址
|
||||||
VITE_SYSTEM_USERNAME: string; // 前端账号名称
|
readonly VITE_PORT: number; // 前端系统端口
|
||||||
VITE_SYSTEM_PASSWORD: string; // 前端账号密码
|
VITE_USERNAME: string; // 前端账号名称
|
||||||
|
VITE_PASSWORD: string; // 前端账号密码
|
||||||
VITE_INIT_STATUS: boolean; // 系统是否进行安装
|
VITE_INIT_STATUS: boolean; // 系统是否进行安装
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
frontend/app/index.css
Normal file
3
frontend/app/index.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
@ -1,3 +1,198 @@
|
|||||||
export default function page(){
|
import React, { useContext, createContext, useState } from "react";
|
||||||
return <>安装中</>
|
|
||||||
|
interface SetupContextType {
|
||||||
|
currentStep: number;
|
||||||
|
setCurrentStep: (step: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SetupContext = createContext<SetupContextType>({
|
||||||
|
currentStep: 1,
|
||||||
|
setCurrentStep: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 步骤组件的通用属性接口
|
||||||
|
interface StepProps {
|
||||||
|
onNext: () => void;
|
||||||
|
onPrev?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用的步骤容器组件
|
||||||
|
const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<div className="mx-auto max-w-5xl">
|
||||||
|
<h2 className="text-2xl font-semibold text-custom-title-light dark:text-custom-title-dark mb-5">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<div className="bg-custom-box-light dark:bg-custom-box-dark rounded-lg shadow-lg p-8">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 通用的导航按钮组件
|
||||||
|
const NavigationButtons: React.FC<StepProps> = ({ onNext, onPrev }) => (
|
||||||
|
<div className="flex gap-4 mt-6">
|
||||||
|
{onPrev && (
|
||||||
|
<button
|
||||||
|
onClick={onPrev}
|
||||||
|
className="px-6 py-2 rounded-lg bg-gray-500 hover:bg-gray-600 text-white transition-colors"
|
||||||
|
>
|
||||||
|
上一步
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={onNext}
|
||||||
|
className="px-6 py-2 rounded-lg bg-blue-500 hover:bg-blue-600 text-white transition-colors"
|
||||||
|
>
|
||||||
|
下一步
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 输入框组件
|
||||||
|
const InputField: React.FC<{
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
defaultValue?: string | number;
|
||||||
|
hint?: string;
|
||||||
|
}> = ({ label, name, defaultValue, hint }) => (
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-xl text-custom-title-light dark:text-custom-title-dark mb-2">
|
||||||
|
{label}
|
||||||
|
</h3>
|
||||||
|
<input
|
||||||
|
name={name}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700"
|
||||||
|
/>
|
||||||
|
{hint && (
|
||||||
|
<p className="text-xs text-custom-p-light dark:text-custom-p-dark mt-1">
|
||||||
|
{hint}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Introduction: React.FC<StepProps> = ({ onNext }) => (
|
||||||
|
<StepContainer title="安装说明">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<p className="text-xl text-custom-p-light dark:text-custom-p-dark">
|
||||||
|
欢迎使用 Echoes
|
||||||
|
</p>
|
||||||
|
<NavigationButtons onNext={onNext} />
|
||||||
|
</div>
|
||||||
|
</StepContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const DatabaseConfig: React.FC<StepProps> = ({ onNext, onPrev }) => {
|
||||||
|
const [dbType, setDbType] = useState("postgresql");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StepContainer title="数据库配置">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-xl text-custom-title-light dark:text-custom-title-dark mb-2">
|
||||||
|
数据库类型
|
||||||
|
</h3>
|
||||||
|
<select
|
||||||
|
value={dbType}
|
||||||
|
onChange={(e) => setDbType(e.target.value)}
|
||||||
|
className="w-full p-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700"
|
||||||
|
>
|
||||||
|
<option value="postgresql">PostgreSQL</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{dbType === "postgresql" && (
|
||||||
|
<>
|
||||||
|
<InputField
|
||||||
|
label="数据库地址"
|
||||||
|
name="db_host"
|
||||||
|
defaultValue="localhost"
|
||||||
|
hint="通常使用 localhost"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="端口"
|
||||||
|
name="db_port"
|
||||||
|
defaultValue={5432}
|
||||||
|
hint="PostgreSQL 默认端口为 5432"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="用户名"
|
||||||
|
name="db_user"
|
||||||
|
defaultValue="postgres"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="密码"
|
||||||
|
name="db_password"
|
||||||
|
defaultValue="postgres"
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
label="数据库名"
|
||||||
|
name="db_name"
|
||||||
|
defaultValue="echoes"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<NavigationButtons onNext={onNext} onPrev={onPrev} />
|
||||||
|
</div>
|
||||||
|
</StepContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AdminConfig: React.FC<StepProps> = ({ onNext, onPrev }) => (
|
||||||
|
<StepContainer title="创建管理员账号">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<InputField label="用户名" name="admin_username" />
|
||||||
|
<InputField label="密码" name="admin_password" />
|
||||||
|
<InputField label="邮箱" name="admin_email" />
|
||||||
|
<NavigationButtons onNext={onNext} onPrev={onPrev} />
|
||||||
|
</div>
|
||||||
|
</StepContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SetupComplete: React.FC = () => (
|
||||||
|
<StepContainer title="安装完成">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-xl text-custom-p-light dark:text-custom-p-dark">
|
||||||
|
恭喜!安装已完成,系统即将重启...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</StepContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function SetupPage() {
|
||||||
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen w-full bg-custom-bg-light dark:bg-custom-bg-dark">
|
||||||
|
<div className="container mx-auto px-4 py-4">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-custom-title-light dark:text-custom-title-dark mb-4">
|
||||||
|
Echoes
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<SetupContext.Provider value={{ currentStep, setCurrentStep }}>
|
||||||
|
{currentStep === 1 && (
|
||||||
|
<Introduction onNext={() => setCurrentStep(currentStep + 1)} />
|
||||||
|
)}
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<DatabaseConfig
|
||||||
|
onNext={() => setCurrentStep(currentStep + 1)}
|
||||||
|
onPrev={() => setCurrentStep(currentStep - 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{currentStep === 3 && (
|
||||||
|
<AdminConfig
|
||||||
|
onNext={() => setCurrentStep(currentStep + 1)}
|
||||||
|
onPrev={() => setCurrentStep(currentStep - 1)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{currentStep === 4 && <SetupComplete />}
|
||||||
|
</SetupContext.Provider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -2,13 +2,13 @@ import {
|
|||||||
Links,
|
Links,
|
||||||
Meta,
|
Meta,
|
||||||
Outlet,
|
Outlet,
|
||||||
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
|
||||||
import { BaseProvider } from "hooks/servicesProvider";
|
import { BaseProvider } from "hooks/servicesProvider";
|
||||||
import { LinksFunction } from "@remix-run/react/dist/routeModules";
|
|
||||||
|
|
||||||
import "~/tailwind.css";
|
import "~/index.css";
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
@ -20,19 +20,49 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body suppressHydrationWarning={true}>
|
||||||
{children}
|
<BaseProvider>
|
||||||
|
<Outlet />
|
||||||
|
</BaseProvider>
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(function() {
|
||||||
|
function getInitialColorMode() {
|
||||||
|
const persistedColorPreference = window.localStorage.getItem('theme');
|
||||||
|
const hasPersistedPreference = typeof persistedColorPreference === 'string';
|
||||||
|
if (hasPersistedPreference) {
|
||||||
|
return persistedColorPreference;
|
||||||
|
}
|
||||||
|
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const hasMediaQueryPreference = typeof mql.matches === 'boolean';
|
||||||
|
if (hasMediaQueryPreference) {
|
||||||
|
return mql.matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
}
|
||||||
|
const colorMode = getInitialColorMode();
|
||||||
|
document.documentElement.classList.toggle('dark', colorMode === 'dark');
|
||||||
|
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||||
|
const newColorMode = e.matches ? 'dark' : 'light';
|
||||||
|
document.documentElement.classList.toggle('dark', newColorMode === 'dark');
|
||||||
|
localStorage.setItem('theme', newColorMode);
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<BaseProvider>
|
<Layout>
|
||||||
<Layout>
|
<Outlet />
|
||||||
<Outlet />
|
</Layout>
|
||||||
</Layout>
|
|
||||||
</BaseProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from "react-dom/server";
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const MyComponent = () => {
|
const MyComponent = () => {
|
||||||
return <div>Hello, World!</div>;
|
return <div>Hello, World!</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function Routes() {
|
export default function Routes() {
|
||||||
const htmlString = ReactDOMServer.renderToString(<MyComponent />);
|
const htmlString = ReactDOMServer.renderToString(<MyComponent />);
|
||||||
|
|
||||||
return (<div>安装重构</div>)
|
return <div>安装重构</div>;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
@apply bg-white dark:bg-gray-950;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,4 +18,3 @@ export interface PathDescription {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,9 +55,7 @@ export class ApiService {
|
|||||||
|
|
||||||
private async getToken(username: string, password: string): Promise<string> {
|
private async getToken(username: string, password: string): Promise<string> {
|
||||||
if (username.split(" ").length === 0 || password.split(" ").length === 0) {
|
if (username.split(" ").length === 0 || password.split(" ").length === 0) {
|
||||||
throw new Error(
|
throw new Error("Username or password cannot be empty");
|
||||||
"Username or password cannot be empty",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -4,7 +4,6 @@ export interface CapabilityProps<T> {
|
|||||||
execute: (...args: any[]) => Promise<T>;
|
execute: (...args: any[]) => Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class CapabilityService {
|
export class CapabilityService {
|
||||||
private capabilities: Map<
|
private capabilities: Map<
|
||||||
string,
|
string,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
export interface PluginConfig {
|
export interface PluginConfig {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@ -15,8 +14,6 @@ export interface PluginConfig {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class PluginManager {
|
export class PluginManager {
|
||||||
private configurations: Map<string, PluginConfig> = new Map();
|
private configurations: Map<string, PluginConfig> = new Map();
|
||||||
|
|
||||||
@ -49,9 +46,7 @@ export class PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPluginConfig(
|
async getPluginConfig(pluginName: string): Promise<PluginConfig | undefined> {
|
||||||
pluginName: string,
|
|
||||||
): Promise<PluginConfig | undefined> {
|
|
||||||
const dbConfig = await this.fetchConfigFromDB(pluginName);
|
const dbConfig = await this.fetchConfigFromDB(pluginName);
|
||||||
if (dbConfig) {
|
if (dbConfig) {
|
||||||
return dbConfig;
|
return dbConfig;
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { ReactNode } from "react"; // Import React
|
import { ReactNode } from "react"; // Import React
|
||||||
import { LoaderFunction } from "react-router-dom";
|
import { LoaderFunction } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
interface RouteElement {
|
interface RouteElement {
|
||||||
element: ReactNode,
|
element: ReactNode;
|
||||||
loader?: LoaderFunction,
|
loader?: LoaderFunction;
|
||||||
children?: RouteElement[],
|
children?: RouteElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RouteManager {
|
export class RouteManager {
|
||||||
@ -13,7 +12,7 @@ export class RouteManager {
|
|||||||
private routes = new Map<string, RouteElement>();
|
private routes = new Map<string, RouteElement>();
|
||||||
private routesCache = new Map<string, string>();
|
private routesCache = new Map<string, string>();
|
||||||
|
|
||||||
private constructor() { }
|
private constructor() {}
|
||||||
|
|
||||||
public static getInstance(): RouteManager {
|
public static getInstance(): RouteManager {
|
||||||
if (!RouteManager.instance) {
|
if (!RouteManager.instance) {
|
||||||
@ -22,10 +21,7 @@ export class RouteManager {
|
|||||||
return RouteManager.instance;
|
return RouteManager.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createRouteElement(
|
private createRouteElement(path: string, element: RouteElement) {
|
||||||
path: string,
|
|
||||||
element: RouteElement
|
|
||||||
) {
|
|
||||||
this.routes.set(path, element);
|
this.routes.set(path, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,6 @@ export interface ThemeConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface Template {
|
export interface Template {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -39,8 +37,6 @@ export interface Template {
|
|||||||
element: () => React.ReactNode;
|
element: () => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ThemeService {
|
export class ThemeService {
|
||||||
private static instance: ThemeService;
|
private static instance: ThemeService;
|
||||||
private currentTheme?: ThemeConfig;
|
private currentTheme?: ThemeConfig;
|
||||||
@ -70,12 +66,10 @@ export class ThemeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public getThemeConfig(): ThemeConfig | undefined {
|
public getThemeConfig(): ThemeConfig | undefined {
|
||||||
return this.currentTheme;
|
return this.currentTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> {
|
public async updateThemeConfig(config: Partial<ThemeConfig>): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const updatedConfig = await this.api.request<ThemeConfig>(
|
const updatedConfig = await this.api.request<ThemeConfig>(
|
||||||
|
BIN
frontend/public/echoes.png
Normal file
BIN
frontend/public/echoes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
@ -1,22 +1,37 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
content: ["./app/**/*.{js,jsx,ts,tsx}"],
|
content: [
|
||||||
|
"./app/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"./common/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"./core/**/*.{js,jsx,ts,tsx}",
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: ["Inter", "system-ui", "sans-serif"],
|
||||||
"Inter",
|
},
|
||||||
"ui-sans-serif",
|
colors: {
|
||||||
"system-ui",
|
custom: {
|
||||||
"sans-serif",
|
bg: {
|
||||||
"Apple Color Emoji",
|
light: "#F5F5FB",
|
||||||
"Segoe UI Emoji",
|
dark: "#0F172A"
|
||||||
"Segoe UI Symbol",
|
},
|
||||||
"Noto Color Emoji",
|
box: {
|
||||||
],
|
light: "#FFFFFF",
|
||||||
|
dark: "#1E293B"
|
||||||
|
},
|
||||||
|
p: {
|
||||||
|
light: "#4b5563",
|
||||||
|
dark: "#94A3B8"
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
light: "#111827",
|
||||||
|
dark: "#F1F5F9"
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
darkMode: "class",
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { vitePlugin as remix } from "@remix-run/dev";
|
import { vitePlugin as remix } from "@remix-run/dev";
|
||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
import Routes from "~/routes"
|
import { resolve } from "path";
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), '');
|
const env = loadEnv(mode, process.cwd(), "");
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
remix({
|
remix({
|
||||||
@ -20,24 +20,31 @@ export default defineConfig(({ mode }) => {
|
|||||||
if (!env.VITE_INIT_STATUS) {
|
if (!env.VITE_INIT_STATUS) {
|
||||||
route("/", "init.tsx", { id: "index-route" });
|
route("/", "init.tsx", { id: "index-route" });
|
||||||
route("*", "init.tsx", { id: "catch-all-route" });
|
route("*", "init.tsx", { id: "catch-all-route" });
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
route("/", "routes.tsx", { id: "index-route" });
|
route("/", "routes.tsx", { id: "index-route" });
|
||||||
route("*", "routes.tsx", { id: "catch-all-route" });
|
route("*", "routes.tsx", { id: "catch-all-route" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
"import.meta.env.VITE_SYSTEM_STATUS": JSON.stringify(false),
|
"import.meta.env.VITE_INIT_STATUS": JSON.stringify(false),
|
||||||
"import.meta.env.VITE_SERVER_API": JSON.stringify("localhost:22000"),
|
"import.meta.env.VITE_SERVER_API": JSON.stringify("localhost:22000"),
|
||||||
"import.meta.env.VITE_SYSTEM_PORT": JSON.stringify(22100),
|
"import.meta.env.VITE_PORT": JSON.stringify(22100),
|
||||||
|
"import.meta.env.VITE_ADDRESS": JSON.stringify("localhost"),
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
host: true,
|
||||||
|
address: "localhost",
|
||||||
port: Number(env.VITE_SYSTEM_PORT ?? 22100),
|
port: Number(env.VITE_SYSTEM_PORT ?? 22100),
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
|
hmr: true, // 确保启用热更新
|
||||||
|
watch: {
|
||||||
|
usePolling: true, // 添加这个配置可以解决某些系统下热更新不工作的问题
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
publicDir: resolve(__dirname, "public"),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export function createServiceHook<T>(name: string, getInstance: () => T) {
|
|
||||||
const Context = createContext<T | null>(null);
|
|
||||||
|
|
||||||
const Provider: FC<PropsWithChildren> = ({ children }) => (
|
|
||||||
<Context.Provider value={getInstance()}>
|
|
||||||
{children}
|
|
||||||
</Context.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
const useService = () => {
|
|
||||||
const service = useContext(Context);
|
|
||||||
if (!service) {
|
|
||||||
throw new Error(`use${name} must be used within ${name}Provider`);
|
|
||||||
}
|
|
||||||
return service;
|
|
||||||
};
|
|
||||||
|
|
||||||
return [Provider, useService] as const;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
export type Json =
|
|
||||||
| null
|
|
||||||
| number
|
|
||||||
| string
|
|
||||||
| boolean
|
|
||||||
| { [key: string]: Json }
|
|
||||||
| Json[];
|
|
||||||
|
|
||||||
export type Config = Record<string, {
|
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
value: Json;
|
|
||||||
}>;
|
|
@ -1,17 +0,0 @@
|
|||||||
export interface PluginDefinition {
|
|
||||||
meta: {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
displayName: string;
|
|
||||||
description?: string;
|
|
||||||
author?: string;
|
|
||||||
icon?: string;
|
|
||||||
};
|
|
||||||
config?: Config;
|
|
||||||
routes: {
|
|
||||||
path: string;
|
|
||||||
description?: string;
|
|
||||||
}[];
|
|
||||||
enabled: boolean;
|
|
||||||
managePath?: string;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user