mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-17 16:00:59 +00:00
Merge pull request #1351 from baicai99/feat/webui-refactor
feat:重构并改进应用的用户界面组件
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
justify-self: flex-start;
|
justify-self: flex-start;
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 60px);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: repeat(auto-fill, minmax(220px, 1fr));
|
grid-template-rows: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||||
|
|||||||
+147
-141
@@ -1,162 +1,168 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import {useEffect, useState} from "react";
|
import { useEffect, useState } from "react";
|
||||||
import styles from "./botConfig.module.css";
|
import styles from "./botConfig.module.css";
|
||||||
import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent";
|
import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent";
|
||||||
import {useRouter} from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO";
|
import { BotCardVO } from "@/app/home/bots/components/bot-card/BotCardVO";
|
||||||
import {Modal, notification, Spin} from "antd";
|
import { Modal, notification, Spin } from "antd";
|
||||||
import BotForm from "@/app/home/bots/components/bot-form/BotForm";
|
import BotForm from "@/app/home/bots/components/bot-form/BotForm";
|
||||||
import BotCard from "@/app/home/bots/components/bot-card/BotCard";
|
import BotCard from "@/app/home/bots/components/bot-card/BotCard";
|
||||||
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"
|
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
|
||||||
import {httpClient} from "@/app/infra/http/HttpClient";
|
import { httpClient } from "@/app/infra/http/HttpClient";
|
||||||
import { Bot } from "@/app/infra/api/api-types";
|
import { Bot } from "@/app/infra/api/api-types";
|
||||||
|
|
||||||
export default function BotConfigPage() {
|
export default function BotConfigPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [pageShowRule, setPageShowRule] = useState<BotConfigPageShowRule>(BotConfigPageShowRule.NO_BOT)
|
const [pageShowRule, setPageShowRule] = useState<BotConfigPageShowRule>(
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
BotConfigPageShowRule.NO_BOT
|
||||||
const [botList, setBotList] = useState<BotCardVO[]>([])
|
);
|
||||||
const [isEditForm, setIsEditForm] = useState(false)
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
const [nowSelectedBotCard, setNowSelectedBotCard] = useState<BotCardVO>()
|
const [botList, setBotList] = useState<BotCardVO[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isEditForm, setIsEditForm] = useState(false);
|
||||||
|
const [nowSelectedBotCard, setNowSelectedBotCard] = useState<BotCardVO>();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// TODO:补齐加载转圈逻辑
|
||||||
|
setIsLoading(true);
|
||||||
|
checkHasLLM().then((hasLLM) => {
|
||||||
|
if (hasLLM) {
|
||||||
|
getBotList();
|
||||||
|
} else {
|
||||||
|
setPageShowRule(BotConfigPageShowRule.NO_LLM);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
async function checkHasLLM(): Promise<boolean> {
|
||||||
// TODO:补齐加载转圈逻辑
|
// NOT IMPL
|
||||||
setIsLoading(true)
|
return true;
|
||||||
checkHasLLM().then((hasLLM) => {
|
}
|
||||||
if (hasLLM) {
|
|
||||||
getBotList()
|
|
||||||
} else {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.NO_LLM)
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
async function checkHasLLM(): Promise<boolean> {
|
function getBotList() {
|
||||||
// NOT IMPL
|
httpClient
|
||||||
return true
|
.getBots()
|
||||||
}
|
.then((resp) => {
|
||||||
|
const botList: BotCardVO[] = resp.bots.map((bot: Bot) => {
|
||||||
|
return new BotCardVO({
|
||||||
|
adapter: bot.adapter,
|
||||||
|
description: bot.description,
|
||||||
|
id: bot.uuid || "",
|
||||||
|
name: bot.name,
|
||||||
|
updateTime: bot.updated_at || "",
|
||||||
|
pipelineName: bot.use_pipeline_name || ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (botList.length === 0) {
|
||||||
|
setPageShowRule(BotConfigPageShowRule.NO_BOT);
|
||||||
|
} else {
|
||||||
|
setPageShowRule(BotConfigPageShowRule.HAVE_BOT);
|
||||||
|
}
|
||||||
|
setBotList(botList);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("get bot list error", err);
|
||||||
|
// TODO HACK: need refactor to hook mode Notification, but it's not working under render
|
||||||
|
notification.error({
|
||||||
|
message: "获取机器人列表失败",
|
||||||
|
description: err.message,
|
||||||
|
placement: "bottomRight"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getBotList() {
|
function handleCreateBotClick() {
|
||||||
httpClient.getBots().then((resp) => {
|
setIsEditForm(false);
|
||||||
const botList: BotCardVO[] = resp.bots.map((bot: Bot) => {
|
setNowSelectedCard(undefined);
|
||||||
return new BotCardVO({
|
setModalOpen(true);
|
||||||
adapter: bot.adapter,
|
}
|
||||||
description: bot.description,
|
|
||||||
id: bot.uuid || "",
|
|
||||||
name: bot.name,
|
|
||||||
updateTime: bot.updated_at || "",
|
|
||||||
pipelineName: bot.use_pipeline_name || "",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if (botList.length === 0) {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.NO_BOT)
|
|
||||||
} else {
|
|
||||||
setPageShowRule(BotConfigPageShowRule.HAVE_BOT)
|
|
||||||
}
|
|
||||||
setBotList(botList)
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error("get bot list error", err)
|
|
||||||
// TODO HACK: need refactor to hook mode Notification, but it's not working under render
|
|
||||||
notification.error({
|
|
||||||
message: "获取机器人列表失败",
|
|
||||||
description: err.message,
|
|
||||||
placement: "bottomRight",
|
|
||||||
})
|
|
||||||
}).finally(() => {
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleCreateBotClick() {
|
function setNowSelectedCard(cardVO: BotCardVO | undefined) {
|
||||||
setIsEditForm(false)
|
setNowSelectedBotCard(cardVO);
|
||||||
setNowSelectedCard(undefined)
|
}
|
||||||
setModalOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setNowSelectedCard(cardVO: BotCardVO | undefined) {
|
function selectBot(cardVO: BotCardVO) {
|
||||||
setNowSelectedBotCard(cardVO)
|
setIsEditForm(true);
|
||||||
}
|
setNowSelectedCard(cardVO);
|
||||||
|
console.log("set now vo", cardVO);
|
||||||
|
setModalOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
function selectBot(cardVO: BotCardVO) {
|
return (
|
||||||
setIsEditForm(true)
|
<div className={styles.configPageContainer}>
|
||||||
setNowSelectedCard(cardVO)
|
<Spin spinning={isLoading} tip="加载中..." size="large">
|
||||||
console.log("set now vo", cardVO)
|
<Modal
|
||||||
setModalOpen(true)
|
title={isEditForm ? "编辑机器人" : "创建机器人"}
|
||||||
}
|
centered
|
||||||
|
open={modalOpen}
|
||||||
|
onOk={() => setModalOpen(false)}
|
||||||
|
onCancel={() => setModalOpen(false)}
|
||||||
|
width={700}
|
||||||
|
footer={null}
|
||||||
|
destroyOnClose={true}
|
||||||
|
>
|
||||||
|
<BotForm
|
||||||
|
initBotId={nowSelectedBotCard?.id}
|
||||||
|
onFormSubmit={() => {
|
||||||
|
getBotList();
|
||||||
|
setModalOpen(false);
|
||||||
|
}}
|
||||||
|
onFormCancel={() => setModalOpen(false)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
{pageShowRule === BotConfigPageShowRule.NO_LLM && (
|
||||||
|
<EmptyAndCreateComponent
|
||||||
|
title={"需要先创建大模型才能配置机器人哦~"}
|
||||||
|
subTitle={"快去创建一个吧!"}
|
||||||
|
buttonText={"创建大模型 GO!"}
|
||||||
|
onButtonClick={() => {
|
||||||
|
router.push("/home/models");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
{pageShowRule === BotConfigPageShowRule.NO_BOT && (
|
||||||
<Spin spinning={isLoading}>
|
<EmptyAndCreateComponent
|
||||||
<div className={styles.configPageContainer}>
|
title={"您还未配置机器人哦~"}
|
||||||
<Modal
|
subTitle={"快去创建一个吧!"}
|
||||||
title={isEditForm ? "编辑机器人" : "创建机器人"}
|
buttonText={"创建机器人 +"}
|
||||||
centered
|
onButtonClick={handleCreateBotClick}
|
||||||
open={modalOpen}
|
/>
|
||||||
onOk={() => setModalOpen(false)}
|
)}
|
||||||
onCancel={() => setModalOpen(false)}
|
</Spin>
|
||||||
width={700}
|
{/* 注意:其余的返回内容需要保持在Spin组件外部 */}
|
||||||
footer={null}
|
{pageShowRule === BotConfigPageShowRule.HAVE_BOT && (
|
||||||
destroyOnClose={true}
|
<div className={`${styles.botListContainer}`}>
|
||||||
>
|
{botList.map((cardVO) => {
|
||||||
<BotForm
|
return (
|
||||||
initBotId={nowSelectedBotCard?.id}
|
<div
|
||||||
onFormSubmit={() => {
|
key={cardVO.id}
|
||||||
getBotList()
|
onClick={() => {
|
||||||
setModalOpen(false)
|
selectBot(cardVO);
|
||||||
}}
|
}}
|
||||||
onFormCancel={() => setModalOpen(false)}
|
>
|
||||||
/>
|
<BotCard botCardVO={cardVO} />
|
||||||
</Modal>
|
</div>
|
||||||
{pageShowRule === BotConfigPageShowRule.NO_LLM &&
|
);
|
||||||
<EmptyAndCreateComponent
|
})}
|
||||||
title={"需要先创建大模型才能配置机器人哦~"}
|
<CreateCardComponent
|
||||||
subTitle={"快去创建一个吧!"}
|
height={200}
|
||||||
buttonText={"创建大模型 GO!"}
|
plusSize={90}
|
||||||
onButtonClick={() => {
|
onClick={handleCreateBotClick}
|
||||||
router.push("/home/models");
|
/>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{pageShowRule === BotConfigPageShowRule.NO_BOT &&
|
|
||||||
<EmptyAndCreateComponent
|
|
||||||
title={"您还未配置机器人哦~"}
|
|
||||||
subTitle={"快去创建一个吧!"}
|
|
||||||
buttonText={"创建机器人 +"}
|
|
||||||
onButtonClick={handleCreateBotClick}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{pageShowRule === BotConfigPageShowRule.HAVE_BOT &&
|
|
||||||
<div className={`${styles.botListContainer}`}
|
|
||||||
>
|
|
||||||
{botList.map(cardVO => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={cardVO.id}
|
|
||||||
onClick={() => {selectBot(cardVO)}}
|
|
||||||
>
|
|
||||||
<BotCard botCardVO={cardVO} />
|
|
||||||
</div>)
|
|
||||||
})}
|
|
||||||
<CreateCardComponent
|
|
||||||
width={360}
|
|
||||||
height={200}
|
|
||||||
plusSize={90}
|
|
||||||
onClick={handleCreateBotClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
)}
|
||||||
)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BotConfigPageShowRule {
|
enum BotConfigPageShowRule {
|
||||||
NO_LLM,
|
NO_LLM,
|
||||||
NO_BOT,
|
NO_BOT,
|
||||||
HAVE_BOT,
|
HAVE_BOT
|
||||||
}
|
}
|
||||||
@@ -67,34 +67,42 @@ export default function HomeSidebar({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${styles.sidebarContainer}`}>
|
return (
|
||||||
{/* LangBot、ICON区域 */}
|
<div className={`${styles.sidebarContainer}`}>
|
||||||
<div className={`${styles.langbotIconContainer}`}>
|
{/* LangBot、ICON区域 */}
|
||||||
{/* icon */}
|
<div className={`${styles.langbotIconContainer}`}>
|
||||||
<div className={`${styles.langbotIcon}`}>L</div>
|
{/* icon */}
|
||||||
<div className={`${styles.langbotText}`}>Langbot</div>
|
<div className={`${styles.langbotIcon}`}>
|
||||||
</div>
|
L
|
||||||
{/* 菜单列表,后期可升级成配置驱动 */}
|
</div>
|
||||||
<div>
|
<div className={`${styles.langbotText}`}>
|
||||||
{sidebarConfigList.map((config) => {
|
Langbot
|
||||||
return (
|
</div>
|
||||||
<div
|
|
||||||
key={config.id}
|
|
||||||
onClick={() => {
|
|
||||||
console.log("click:", config.id);
|
|
||||||
handleChildClick(config);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SidebarChild
|
|
||||||
isSelected={selectedChild.id === config.id}
|
|
||||||
icon={config.icon}
|
|
||||||
name={config.name}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
{/* 菜单列表,后期可升级成配置驱动 */}
|
||||||
})}
|
<div>
|
||||||
</div>
|
{
|
||||||
</div>
|
sidebarConfigList.map(config => {
|
||||||
);
|
return (
|
||||||
|
<div
|
||||||
|
key={config.id}
|
||||||
|
onClick={() => {
|
||||||
|
console.log('click:', config.id)
|
||||||
|
handleChildClick(config)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SidebarChild
|
||||||
|
isSelected={selectedChild.id === config.id}
|
||||||
|
icon={config.icon}
|
||||||
|
name={config.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* 主布局容器 */
|
||||||
.homeLayoutContainer {
|
.homeLayoutContainer {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -5,14 +6,12 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 主内容区域 */
|
||||||
.main {
|
.main {
|
||||||
|
background-color: #f5f5f7;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #FAFBFB;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.mainContent {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
height: calc(100% - 110px);
|
|
||||||
margin: 20px;
|
|
||||||
}
|
}
|
||||||
+33
-15
@@ -1,30 +1,48 @@
|
|||||||
"use client";
|
"use client"
|
||||||
|
|
||||||
import "@ant-design/v5-patch-for-react-19";
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
import styles from "./layout.module.css";
|
import styles from "./layout.module.css"
|
||||||
import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar";
|
import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar";
|
||||||
import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar";
|
import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild";
|
import { SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild";
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
|
||||||
|
const { Sider, Content } = Layout;
|
||||||
|
|
||||||
export default function HomeLayout({
|
export default function HomeLayout({
|
||||||
children
|
children
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const [title, setTitle] = useState<string>("");
|
const router = useRouter();
|
||||||
const onSelectedChange = (child: SidebarChildVO) => {
|
const [title, setTitle] = useState<string>("")
|
||||||
setTitle(child.name);
|
const onSelectedChangeAction = (child: SidebarChildVO) => {
|
||||||
};
|
setTitle(child.name)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.homeLayoutContainer}`}>
|
<Layout className={styles.homeLayoutContainer}>
|
||||||
<HomeSidebar onSelectedChangeAction={onSelectedChange} />
|
{/* homeLayoutContainer 是整个容器的入口,使用 flex 的左右布局 */}
|
||||||
<div className={`${styles.main}`}>
|
|
||||||
|
<Sider className="left">
|
||||||
|
<HomeSidebar
|
||||||
|
onSelectedChangeAction={onSelectedChangeAction}
|
||||||
|
/>
|
||||||
|
{/* HomeSidebar 为侧边栏 */}
|
||||||
|
</Sider>
|
||||||
|
|
||||||
|
<Layout className="right">
|
||||||
|
{/* right 为内容显示区域,right使用 flex 上下布局,right 使用 flex 布局吃掉剩余部分 */}
|
||||||
|
|
||||||
<HomeTitleBar title={title} />
|
<HomeTitleBar title={title} />
|
||||||
{/* 主页面 */}
|
|
||||||
<div className={`${styles.mainContent}`}>{children}</div>
|
<Content className={styles.main}>
|
||||||
</div>
|
{/* mainContent 为主页面 */}
|
||||||
</div>
|
{children}
|
||||||
);
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { Radio } from 'antd';
|
import { Radio } from 'antd';
|
||||||
import {useState} from "react";
|
import { useState } from "react";
|
||||||
import PluginInstalledComponent from "@/app/home/plugins/plugin-installed/PluginInstalledComponent";
|
import PluginInstalledComponent from "@/app/home/plugins/plugin-installed/PluginInstalledComponent";
|
||||||
import PluginMarketComponent from "@/app/home/plugins/plugin-market/PluginMarketComponent";
|
import PluginMarketComponent from "@/app/home/plugins/plugin-market/PluginMarketComponent";
|
||||||
import styles from './plugins.module.css'
|
import styles from './plugins.module.css'
|
||||||
@@ -14,33 +14,24 @@ export default function PluginConfigPage() {
|
|||||||
const [nowPageType, setNowPageType] = useState(PageType.INSTALLED)
|
const [nowPageType, setNowPageType] = useState(PageType.INSTALLED)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.pageContainer}`}>
|
<div className={styles.pageContainer}>
|
||||||
<div>
|
<Radio.Group
|
||||||
<Radio.Group
|
block
|
||||||
block
|
options={[
|
||||||
options={[
|
{ label: '已安装', value: PageType.INSTALLED },
|
||||||
{ label: '已安装', value: PageType.INSTALLED },
|
{ label: '插件市场', value: PageType.MARKET },
|
||||||
{ label: '插件市场', value: PageType.MARKET },
|
]}
|
||||||
]}
|
defaultValue={PageType.INSTALLED}
|
||||||
defaultValue={PageType.INSTALLED}
|
value={nowPageType}
|
||||||
value={nowPageType}
|
optionType="button"
|
||||||
optionType="button"
|
buttonStyle="solid"
|
||||||
buttonStyle="solid"
|
style={{ marginBottom: '20px' }}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
// 这里静态类型检测有问题
|
setNowPageType(e.target.value as PageType)
|
||||||
setNowPageType(e.target.value)
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={`${styles.pageContainer}`}>
|
|
||||||
{
|
|
||||||
nowPageType === PageType.INSTALLED && <PluginInstalledComponent/>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
nowPageType === PageType.MARKET && <PluginMarketComponent/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{nowPageType === PageType.INSTALLED ? <PluginInstalledComponent /> : <PluginMarketComponent />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
|
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
|
||||||
import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
|
import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent";
|
import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent";
|
||||||
import styles from "@/app/home/plugins/plugins.module.css";
|
import styles from "@/app/home/plugins/plugins.module.css";
|
||||||
import { Modal, Input } from "antd";
|
import { Modal, Input } from "antd";
|
||||||
@@ -66,16 +66,12 @@ export default function PluginInstalledComponent() {
|
|||||||
fontSize: "30px",
|
fontSize: "30px",
|
||||||
marginRight: "20px"
|
marginRight: "20px"
|
||||||
}}
|
}}
|
||||||
type="setting"
|
|
||||||
/>
|
/>
|
||||||
<span>从 GitHub 安装插件</span>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
centered
|
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
onOk={() => handleModalConfirm()}
|
onOk={handleModalConfirm}
|
||||||
onCancel={() => setModalOpen(false)}
|
onCancel={() => setModalOpen(false)}
|
||||||
width={500}
|
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
>
|
>
|
||||||
<div className={`${styles.modalBody}`}>
|
<div className={`${styles.modalBody}`}>
|
||||||
@@ -95,7 +91,6 @@ export default function PluginInstalledComponent() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<CreateCardComponent
|
<CreateCardComponent
|
||||||
width={360}
|
|
||||||
height={140}
|
height={140}
|
||||||
plusSize={90}
|
plusSize={90}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -46,19 +46,22 @@ export default function PluginCardComponent({
|
|||||||
</div>
|
</div>
|
||||||
{/* footer */}
|
{/* footer */}
|
||||||
<div className={`${styles.cardFooter}`}>
|
<div className={`${styles.cardFooter}`}>
|
||||||
<div className={`${styles.linkSettingContainer}`}>
|
<div className={`${styles.footerContainer}`}>
|
||||||
<div className={`${styles.link}`}>
|
<div className={`${styles.linkAndToolContainer}`}>
|
||||||
<LinkOutlined style={{ fontSize: "22px" }} />
|
<div className={`${styles.link}`}>
|
||||||
<span>1</span>
|
<LinkOutlined style={{ fontSize: "22px" }} />
|
||||||
|
<span>1</span>
|
||||||
|
</div>
|
||||||
|
<ToolOutlined style={{ fontSize: "22px" }} />
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.switchContainer}`}>
|
||||||
|
<Switch
|
||||||
|
value={initialized}
|
||||||
|
onClick={handleEnable}
|
||||||
|
disabled={!switchEnable}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ToolOutlined style={{ fontSize: "22px" }} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch
|
|
||||||
value={initialized}
|
|
||||||
onClick={handleEnable}
|
|
||||||
disabled={!switchEnable}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.cardContainer {
|
.cardContainer {
|
||||||
width: 360px;
|
width: 100%;
|
||||||
|
/* 修改为 100% 以撑满整个网格单元 */
|
||||||
height: 140px;
|
height: 140px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
@@ -40,12 +41,29 @@
|
|||||||
.cardFooter {
|
.cardFooter {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footerContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.linkAndToolContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.fontGray {
|
.fontGray {
|
||||||
color: #6C6C6C;
|
color: #6C6C6C;
|
||||||
|
|||||||
@@ -8,80 +8,100 @@ import { Input, Pagination } from "antd";
|
|||||||
import { spaceClient } from "@/app/infra/http/HttpClient";
|
import { spaceClient } from "@/app/infra/http/HttpClient";
|
||||||
|
|
||||||
export default function PluginMarketComponent() {
|
export default function PluginMarketComponent() {
|
||||||
const [marketPluginList, setMarketPluginList] = useState<
|
const [marketPluginList, setMarketPluginList] = useState<
|
||||||
PluginMarketCardVO[]
|
PluginMarketCardVO[]
|
||||||
>([]);
|
>([]);
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
const [totalCount, setTotalCount] = useState(0);
|
||||||
const [nowPage, setNowPage] = useState(1);
|
const [nowPage, setNowPage] = useState(1);
|
||||||
const [searchKeyword, setSearchKeyword] = useState("");
|
const [searchKeyword, setSearchKeyword] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initData();
|
initData();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function initData() {
|
function initData() {
|
||||||
getPluginList();
|
getPluginList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInputSearchKeyword(keyword: string) {
|
function onInputSearchKeyword(keyword: string) {
|
||||||
// 这里记得加防抖,暂时没加
|
// 这里记得加防抖,暂时没加
|
||||||
setSearchKeyword(keyword);
|
setSearchKeyword(keyword);
|
||||||
setNowPage(1);
|
setNowPage(1);
|
||||||
getPluginList(1, keyword);
|
getPluginList(1, keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPluginList(
|
function getPluginList(
|
||||||
page: number = nowPage,
|
page: number = nowPage,
|
||||||
keyword: string = searchKeyword
|
keyword: string = searchKeyword
|
||||||
) {
|
) {
|
||||||
spaceClient.getMarketPlugins(page, 10, keyword).then((res) => {
|
setLoading(true);
|
||||||
setMarketPluginList(
|
spaceClient.getMarketPlugins(page, pageSize, keyword).then((res) => {
|
||||||
res.plugins.map(
|
setMarketPluginList(
|
||||||
(marketPlugin) =>
|
res.plugins.map(
|
||||||
new PluginMarketCardVO({
|
(marketPlugin) =>
|
||||||
author: marketPlugin.author,
|
new PluginMarketCardVO({
|
||||||
description: marketPlugin.description,
|
author: marketPlugin.author,
|
||||||
githubURL: marketPlugin.repository,
|
description: marketPlugin.description,
|
||||||
name: marketPlugin.name,
|
githubURL: marketPlugin.repository,
|
||||||
pluginId: String(marketPlugin.ID),
|
name: marketPlugin.name,
|
||||||
starCount: marketPlugin.stars
|
pluginId: String(marketPlugin.ID),
|
||||||
})
|
starCount: marketPlugin.stars,
|
||||||
)
|
version: "version" in marketPlugin ? String(marketPlugin.version) : "1.0.0", // Default version if not provided
|
||||||
);
|
})
|
||||||
setTotalCount(res.total);
|
)
|
||||||
console.log("market plugins:", res);
|
);
|
||||||
});
|
setTotalCount(res.total);
|
||||||
}
|
setLoading(false);
|
||||||
|
console.log("market plugins:", res);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("获取插件列表失败:", error);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
function handlePageChange(page: number) {
|
||||||
<div className={`${styles.marketComponentBody}`}>
|
setNowPage(page);
|
||||||
<Input
|
getPluginList(page);
|
||||||
style={{
|
}
|
||||||
width: "300px",
|
|
||||||
marginTop: "10px"
|
return (
|
||||||
}}
|
<div className={`${styles.marketComponentBody}`}>
|
||||||
value={searchKeyword}
|
<Input
|
||||||
placeholder="搜索插件"
|
style={{
|
||||||
onChange={(e) => onInputSearchKeyword(e.target.value)}
|
width: '300px',
|
||||||
/>
|
marginBottom: '10px',
|
||||||
<div className={`${styles.pluginListContainer}`}>
|
}}
|
||||||
{marketPluginList.map((vo, index) => {
|
value={searchKeyword}
|
||||||
return (
|
placeholder="搜索插件"
|
||||||
<div key={index}>
|
onChange={(e) => onInputSearchKeyword(e.target.value)}
|
||||||
<PluginMarketCardComponent cardVO={vo} />
|
/>
|
||||||
|
<div className={`${styles.pluginListContainer}`}>
|
||||||
|
{loading ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>
|
||||||
|
) : marketPluginList.length === 0 ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '20px' }}>没有找到匹配的插件</div>
|
||||||
|
) : (
|
||||||
|
marketPluginList.map((vo, index) => (
|
||||||
|
<div key={`${vo.pluginId}-${index}`}>
|
||||||
|
<PluginMarketCardComponent cardVO={vo} />
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
{totalCount > 0 && (
|
||||||
})}
|
<div style={{ display: 'flex', justifyContent: 'center', width: '100%', marginTop: '20px' }}>
|
||||||
</div>
|
<Pagination
|
||||||
<Pagination
|
current={nowPage}
|
||||||
defaultCurrent={1}
|
total={totalCount}
|
||||||
total={totalCount}
|
pageSize={pageSize}
|
||||||
onChange={(pageNumber) => {
|
onChange={handlePageChange}
|
||||||
setNowPage(pageNumber);
|
showSizeChanger={false}
|
||||||
getPluginList(pageNumber);
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-49
@@ -1,56 +1,49 @@
|
|||||||
import styles from "./pluginMarketCard.module.css"
|
import styles from "./pluginMarketCard.module.css";
|
||||||
import {GithubOutlined, StarOutlined} from '@ant-design/icons';
|
import { GithubOutlined, StarOutlined } from "@ant-design/icons";
|
||||||
import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
|
import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
|
||||||
import {Button} from "antd";
|
import { Button } from "antd";
|
||||||
|
|
||||||
export default function PluginMarketCardComponent({
|
export default function PluginMarketCardComponent({
|
||||||
cardVO
|
cardVO
|
||||||
}: {
|
}: {
|
||||||
cardVO: PluginMarketCardVO
|
cardVO: PluginMarketCardVO;
|
||||||
}) {
|
}) {
|
||||||
|
function handleInstallClick(pluginId: string) {
|
||||||
|
console.log("Install plugin: ", pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
function handleInstallClick (pluginId: string) {
|
<div className={`${styles.cardContainer}`}>
|
||||||
console.log("Install plugin: ", pluginId)
|
{/* header */}
|
||||||
}
|
<div className={`${styles.cardHeader}`}>
|
||||||
|
{/* left author */}
|
||||||
return (
|
<div className={`${styles.fontGray}`}>{cardVO.author}</div>
|
||||||
<div className={`${styles.cardContainer}`}>
|
{/* right icon */}
|
||||||
{/* header */}
|
<GithubOutlined style={{ fontSize: "26px" }} type="setting" />
|
||||||
<div className={`${styles.cardHeader}`}>
|
</div>
|
||||||
{/* left author */}
|
{/* content */}
|
||||||
<div className={`${styles.fontGray}`}>{cardVO.author}</div>
|
<div className={`${styles.cardContent}`}>
|
||||||
{/* right icon */}
|
<div className={`${styles.boldFont}`}>{cardVO.name}</div>
|
||||||
<GithubOutlined
|
<div className={`${styles.fontGray}`}>{cardVO.description}</div>
|
||||||
style={{fontSize: '26px'}}
|
</div>
|
||||||
type="setting"
|
{/* footer */}
|
||||||
/>
|
<div className={`${styles.cardFooter}`}>
|
||||||
</div>
|
<div className={`${styles.linkSettingContainer}`}>
|
||||||
{/* content */}
|
<div className={`${styles.link}`}>
|
||||||
<div className={`${styles.cardContent}`}>
|
<StarOutlined style={{ fontSize: "22px" }} />
|
||||||
<div className={`${styles.boldFont}`}>{cardVO.name}</div>
|
<span style={{ paddingLeft: "5px" }}>{cardVO.starCount}</span>
|
||||||
<div className={`${styles.fontGray}`}>{cardVO.description}</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/* footer */}
|
|
||||||
<div className={`${styles.cardFooter}`}>
|
|
||||||
<div className={`${styles.linkSettingContainer}`}>
|
|
||||||
<div className={`${styles.link}`}>
|
|
||||||
<StarOutlined
|
|
||||||
style={{fontSize: '22px'}}
|
|
||||||
/>
|
|
||||||
<span>{cardVO.starCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size={"small"}
|
|
||||||
onClick={() => {
|
|
||||||
handleInstallClick(cardVO.pluginId)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
安装
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size={"small"}
|
||||||
|
onClick={() => {
|
||||||
|
handleInstallClick(cardVO.pluginId);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
安装
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface IPluginMarketCardVO {
|
|||||||
description: string,
|
description: string,
|
||||||
starCount: number,
|
starCount: number,
|
||||||
githubURL: string,
|
githubURL: string,
|
||||||
|
version: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PluginMarketCardVO implements IPluginMarketCardVO {
|
export class PluginMarketCardVO implements IPluginMarketCardVO {
|
||||||
@@ -14,6 +15,7 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
|
|||||||
author: string;
|
author: string;
|
||||||
githubURL: string;
|
githubURL: string;
|
||||||
starCount: number;
|
starCount: number;
|
||||||
|
version: string;
|
||||||
|
|
||||||
constructor(prop: IPluginMarketCardVO) {
|
constructor(prop: IPluginMarketCardVO) {
|
||||||
this.description = prop.description
|
this.description = prop.description
|
||||||
@@ -22,5 +24,6 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
|
|||||||
this.githubURL = prop.githubURL
|
this.githubURL = prop.githubURL
|
||||||
this.starCount = prop.starCount
|
this.starCount = prop.starCount
|
||||||
this.pluginId = prop.pluginId
|
this.pluginId = prop.pluginId
|
||||||
|
this.version = prop.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,4 @@
|
|||||||
.cardContainer {
|
.cardContainer {
|
||||||
width: 360px;
|
|
||||||
height: 140px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
@@ -73,5 +71,6 @@
|
|||||||
color: #6062E7;
|
color: #6062E7;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
.pageContainer {
|
.pageContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 30px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.marketComponentBody {
|
.marketComponentBody {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
@@ -11,17 +11,13 @@
|
|||||||
.pluginListContainer {
|
.pluginListContainer {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
justify-self: flex-start;
|
justify-self: flex-start;
|
||||||
width: calc(100% - 60px);
|
margin: auto;
|
||||||
height: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
margin: auto;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: repeat(auto-fill, minmax(160px, 1fr));
|
grid-template-rows: repeat(auto-fill, minmax(160px, 1fr));
|
||||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
justify-items: center;
|
/* justify-items: center; */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modalTitle {
|
.modalTitle {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import styles from "./createCartComponent.module.css";
|
import styles from "./createCartComponent.module.css";
|
||||||
|
|
||||||
export default function CreateCardComponent({
|
export default function CreateCardComponent({
|
||||||
width,
|
|
||||||
height,
|
height,
|
||||||
plusSize,
|
plusSize,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
width: number;
|
|
||||||
height: number;
|
height: number;
|
||||||
plusSize: number;
|
plusSize: number;
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -15,7 +13,7 @@ export default function CreateCardComponent({
|
|||||||
<div
|
<div
|
||||||
className={`${styles.cardContainer} ${styles.createCardContainer} `}
|
className={`${styles.cardContainer} ${styles.createCardContainer} `}
|
||||||
style={{
|
style={{
|
||||||
width: `${width}px`,
|
width: `100%`,
|
||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
fontSize: `${plusSize}px`
|
fontSize: `${plusSize}px`
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ import {
|
|||||||
ApiRespSystemInfo,
|
ApiRespSystemInfo,
|
||||||
ApiRespAsyncTasks,
|
ApiRespAsyncTasks,
|
||||||
ApiRespAsyncTask,
|
ApiRespAsyncTask,
|
||||||
ApiRespUserToken, MarketPluginResponse
|
ApiRespUserToken,
|
||||||
|
MarketPluginResponse
|
||||||
} from "../api/api-types";
|
} from "../api/api-types";
|
||||||
import { notification } from "antd";
|
import { notification } from "antd";
|
||||||
|
|
||||||
@@ -50,22 +51,19 @@ export interface RequestConfig extends AxiosRequestConfig {
|
|||||||
|
|
||||||
class HttpClient {
|
class HttpClient {
|
||||||
private instance: AxiosInstance;
|
private instance: AxiosInstance;
|
||||||
private disableToken: boolean = false
|
private disableToken: boolean = false;
|
||||||
// 暂不需要SSR
|
// 暂不需要SSR
|
||||||
// private ssrInstance: AxiosInstance | null = null
|
// private ssrInstance: AxiosInstance | null = null
|
||||||
|
|
||||||
constructor(
|
constructor(baseURL?: string, disableToken?: boolean) {
|
||||||
baseURL?: string,
|
|
||||||
disableToken?: boolean
|
|
||||||
) {
|
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: baseURL || this.getBaseUrl(),
|
baseURL: baseURL || this.getBaseUrl(),
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.disableToken = disableToken || false
|
this.disableToken = disableToken || false;
|
||||||
this.initInterceptors();
|
this.initInterceptors();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +356,7 @@ class HttpClient {
|
|||||||
public getMarketPlugins(
|
public getMarketPlugins(
|
||||||
page: number,
|
page: number,
|
||||||
page_size: number,
|
page_size: number,
|
||||||
query: string,
|
query: string
|
||||||
): Promise<MarketPluginResponse> {
|
): Promise<MarketPluginResponse> {
|
||||||
return this.post(`/api/v1/market/plugins`, {
|
return this.post(`/api/v1/market/plugins`, {
|
||||||
page,
|
page,
|
||||||
@@ -366,7 +364,7 @@ class HttpClient {
|
|||||||
query,
|
query,
|
||||||
sort_by: "stars",
|
sort_by: "stars",
|
||||||
sort_order: "DESC"
|
sort_order: "DESC"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
public installPluginFromGithub(
|
public installPluginFromGithub(
|
||||||
source: string
|
source: string
|
||||||
@@ -415,4 +413,4 @@ class HttpClient {
|
|||||||
export const httpClient = new HttpClient("https://version-4.langbot.dev");
|
export const httpClient = new HttpClient("https://version-4.langbot.dev");
|
||||||
|
|
||||||
// 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法
|
// 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法
|
||||||
export const spaceClient = new HttpClient("https://space.langbot.app")
|
export const spaceClient = new HttpClient("https://space.langbot.app");
|
||||||
|
|||||||
+59
-56
@@ -10,66 +10,69 @@ export default function NotFound() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: '100vh', display: 'flex', background: 'white', justifyContent: 'center' }}> <Row justify="center" align="middle" style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: '100vh', background: 'white' }}>
|
||||||
<div className="error-container" style={{ width: '100%', textAlign: 'center' }}>
|
<Row justify="center" align="middle" style={{ minHeight: '100vh' }}>
|
||||||
<div className="error-card" style={{
|
<Col xs={22} sm={20} md={18} lg={14} xl={10}>
|
||||||
display: 'flex',
|
<div className="error-container" style={{ width: '100%', padding: '20px 0', textAlign: 'center' }}>
|
||||||
flexDirection: 'column',
|
<div className="error-card" style={{
|
||||||
alignItems: 'center',
|
display: 'flex',
|
||||||
padding: '24px'
|
flexDirection: 'column',
|
||||||
}}>
|
alignItems: 'center',
|
||||||
{/* Ant Design 图标,可以换成 Langbot 的 Logo */}
|
padding: '24px'
|
||||||
<div style={{ marginBottom: '20px', maxWidth: '100%', height: 'auto' }}>
|
}}>
|
||||||
<Result
|
{/* Ant Design 图标,可以换成 Langbot 的 Logo */}
|
||||||
status="404"
|
<div style={{ marginBottom: '20px', maxWidth: '100%', height: 'auto' }}>
|
||||||
title={null}
|
<Result
|
||||||
subTitle={null}
|
status="404"
|
||||||
style={{ padding: 0 }}
|
title={null}
|
||||||
/>
|
subTitle={null}
|
||||||
</div>
|
style={{ padding: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="error-text" style={{ textAlign: 'center', marginBottom: '24px' }}>
|
<div className="error-text" style={{ textAlign: 'center', marginBottom: '24px' }}>
|
||||||
<Title level={1} style={{ margin: '0 0 16px 0', fontSize: '72px', fontWeight: 'bold', color: '#333' }}>
|
<Title level={1} style={{ margin: '0 0 16px 0', fontSize: '72px', fontWeight: 'bold', color: '#333' }}>
|
||||||
404
|
404
|
||||||
</Title>
|
</Title>
|
||||||
<Title level={3} style={{ margin: '0 0 8px 0', fontWeight: 'normal', color: '#333' }}>
|
<Title level={3} style={{ margin: '0 0 8px 0', fontWeight: 'normal', color: '#333' }}>
|
||||||
页面不存在
|
页面不存在
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph style={{ fontSize: '16px', color: '#666', maxWidth: '450px', margin: '0 auto 32px auto' }}>
|
<Paragraph style={{ fontSize: '16px', color: '#666', maxWidth: '450px', margin: '0 auto 32px auto' }}>
|
||||||
您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。
|
您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="error-button" style={{ marginBottom: '24px' }}>
|
<div className="error-button" style={{ marginBottom: '24px' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" style={{
|
<Button type="primary" style={{
|
||||||
backgroundColor: '#2288ee',
|
backgroundColor: '#2288ee',
|
||||||
borderColor: '#2288ee',
|
borderColor: '#2288ee',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
height: '36px',
|
height: '36px',
|
||||||
padding: '0 16px'
|
padding: '0 16px'
|
||||||
}} onClick={() => router.back()}>
|
}} onClick={() => router.back()}>
|
||||||
上一级
|
上一级
|
||||||
</Button>
|
</Button>
|
||||||
<Button style={{
|
<Button style={{
|
||||||
borderColor: '#d9d9d9',
|
borderColor: '#d9d9d9',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
height: '36px',
|
height: '36px',
|
||||||
padding: '0 16px'
|
padding: '0 16px'
|
||||||
}} onClick={() => router.push('/')}>
|
}} onClick={() => router.push('/')}>
|
||||||
返回主页
|
返回主页
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="error-support" style={{ textAlign: 'center', marginTop: '16px' }}>
|
<div className="error-support" style={{ textAlign: 'center', marginTop: '16px' }}>
|
||||||
<Paragraph style={{ fontSize: '14px', color: '#666' }}>
|
<Paragraph style={{ fontSize: '14px', color: '#666' }}>
|
||||||
需要帮助吗?您可以联系 <a href="mailto:support@qq.com" style={{ color: '#000', textDecoration: 'none' }}>support@qq.com</a>
|
需要帮助吗?您可以联系 <a href="mailto:support@qq.com" style={{ color: '#000', textDecoration: 'none' }}>support@qq.com</a>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
</div>
|
</Row>
|
||||||
</Row>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user