generated from template/astro-template
upload
This commit is contained in:
parent
0b19ccb700
commit
56e3d08869
@ -27,6 +27,7 @@ let proxy = {
|
|||||||
'/client': apiProxy,
|
'/client': apiProxy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const basename = isDev ? '' : pkgs.basename;
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// ...
|
// ...
|
||||||
// site: 'https://kevisual.xiongxiao.me/root/astro',
|
// site: 'https://kevisual.xiongxiao.me/root/astro',
|
||||||
@ -41,7 +42,7 @@ export default defineConfig({
|
|||||||
vite: {
|
vite: {
|
||||||
plugins,
|
plugins,
|
||||||
define: {
|
define: {
|
||||||
BASE_NAME: JSON.stringify(pkgs.basename),
|
BASE_NAME: JSON.stringify(basename),
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 7008,
|
port: 7008,
|
||||||
|
@ -3,8 +3,8 @@ import { TextEditor } from '@kevisual/markdown-editor/tiptap/editor.ts';
|
|||||||
import { Select } from '@/components/a/select.tsx';
|
import { Select } from '@/components/a/select.tsx';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { getSuggestionItems } from '../ai-chat/editor/suggestion/item';
|
import { getSuggestionItems } from '../ai-chat/editor/suggestion/item';
|
||||||
import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts';
|
// import { html2md } from '@kevisual/markdown-editor/tiptap/index.ts';
|
||||||
import { chatId } from '../ai-chat/utils/uuid';
|
// import { chatId } from '../ai-chat/utils/uuid';
|
||||||
import '../ai-chat/index.css';
|
import '../ai-chat/index.css';
|
||||||
import { links } from './data/link.ts';
|
import { links } from './data/link.ts';
|
||||||
// const testImport = async () => {
|
// const testImport = async () => {
|
||||||
@ -44,6 +44,10 @@ export const App = (props: any) => {
|
|||||||
return (
|
return (
|
||||||
<div className='w-full h-full flex flex-col'>
|
<div className='w-full h-full flex flex-col'>
|
||||||
<ShowLinks links={links} />
|
<ShowLinks links={links} />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<i>登陆的demo账号: demo 密码为: 123456 </i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
81
src/apps/layout/index.tsx
Normal file
81
src/apps/layout/index.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { Layout, Header } from '@/components/b/layouts/main/layout';
|
||||||
|
import { basename } from '@/modules/basename';
|
||||||
|
import { queryLogin } from '@/modules/query';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
export const AppLayout = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
header={
|
||||||
|
<Header
|
||||||
|
title={
|
||||||
|
<div
|
||||||
|
className='px-2 cursor-pointer'
|
||||||
|
onClick={() => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const pathname = url.pathname;
|
||||||
|
if (pathname === basename + '/') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果当前路径不是根路径,则跳转到根路径
|
||||||
|
location.href = basename + '/';
|
||||||
|
}}>
|
||||||
|
AI Generative
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className='bg-background border-b border-b-gray-200 text-foreground'
|
||||||
|
right={<UserInfo />}
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
{props.children}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type User = Awaited<ReturnType<typeof queryLogin.checkLocalUser>>;
|
||||||
|
|
||||||
|
export const UserInfo = () => {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
const init = async () => {
|
||||||
|
const userInfo = await queryLogin.checkLocalUser();
|
||||||
|
const token = await queryLogin.getToken();
|
||||||
|
if (userInfo) {
|
||||||
|
setUser(userInfo);
|
||||||
|
} else if (token) {
|
||||||
|
const userinfo = await queryLogin.getLoginUserByToken(token);
|
||||||
|
if (userinfo) {
|
||||||
|
setUser(userinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{!user && (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
const loginUrl = '/user/login/';
|
||||||
|
if (currentUrl.pathname !== loginUrl) {
|
||||||
|
const newUrl = new URL(loginUrl, window.location.origin);
|
||||||
|
newUrl.searchParams.set('redirect', currentUrl.pathname + currentUrl.search);
|
||||||
|
location.href = newUrl.toString();
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
login
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{user && (
|
||||||
|
<>
|
||||||
|
<img src={user?.avatar || '/root/center/panda.jpg'} alt='User Avatar' className='w-8 h-8 rounded-full' />
|
||||||
|
<span className='text-sm text-gray-700'>{user?.username}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -237,7 +237,7 @@ export const EditMark = () => {
|
|||||||
}
|
}
|
||||||
return <div className='w-full h-full'></div>;
|
return <div className='w-full h-full'></div>;
|
||||||
};
|
};
|
||||||
export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?: React.ReactNode; open?: boolean }) => {
|
export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?: React.ReactNode; open?: boolean; hasTopTitle?: boolean }) => {
|
||||||
const getDocumentHeight = () => {
|
const getDocumentHeight = () => {
|
||||||
return document.documentElement.scrollHeight;
|
return document.documentElement.scrollHeight;
|
||||||
};
|
};
|
||||||
@ -261,13 +261,16 @@ export const LayoutMain = (props: { children?: React.ReactNode; expandChildren?:
|
|||||||
const isEdit = !!markData;
|
const isEdit = !!markData;
|
||||||
const hasExpandChildren = !!props.expandChildren;
|
const hasExpandChildren = !!props.expandChildren;
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
|
const top = props.hasTopTitle ? 70 : 0; // Adjust top based on whether there's a title
|
||||||
if (!hasExpandChildren || openMenu) {
|
if (!hasExpandChildren || openMenu) {
|
||||||
return {};
|
return {
|
||||||
|
top,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
top: getDocumentHeight() / 2 + 10,
|
top: getDocumentHeight() / 2 + 10 + top,
|
||||||
};
|
};
|
||||||
}, [getDocumentHeight, hasExpandChildren, openMenu]);
|
}, [getDocumentHeight, hasExpandChildren, openMenu, props.hasTopTitle]);
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full flex'>
|
<div className='w-full h-full flex'>
|
||||||
<div className={clsx('absolute top-4 z-10', openMenu ? 'left-4' : '-left-4')} style={style}>
|
<div className={clsx('absolute top-4 z-10', openMenu ? 'left-4' : '-left-4')} style={style}>
|
||||||
@ -313,12 +316,13 @@ export type AppProps = {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
showSelect?: boolean;
|
showSelect?: boolean;
|
||||||
openMenu?: boolean;
|
openMenu?: boolean;
|
||||||
|
hasTopTitle?: boolean; // 是否有顶部标题
|
||||||
};
|
};
|
||||||
export const ProviderManagerName = 'mark-manager';
|
export const ProviderManagerName = 'mark-manager';
|
||||||
export const App = (props: AppProps) => {
|
export const App = (props: AppProps) => {
|
||||||
return (
|
return (
|
||||||
<ManagerProvider id={props.managerId}>
|
<ManagerProvider id={props.managerId}>
|
||||||
<LayoutMain expandChildren={props.children} open={props.openMenu}>
|
<LayoutMain expandChildren={props.children} open={props.openMenu} hasTopTitle={props.hasTopTitle}>
|
||||||
<Manager
|
<Manager
|
||||||
markType={props.markType}
|
markType={props.markType}
|
||||||
showSearch={props.showSearch}
|
showSearch={props.showSearch}
|
||||||
|
49
src/components/b/layouts/main/layout.tsx
Normal file
49
src/components/b/layouts/main/layout.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
header?: React.ReactNode;
|
||||||
|
footer?: React.ReactNode;
|
||||||
|
mainClassName?: string;
|
||||||
|
mainStyle?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
export const Layout = (props: LayoutProps) => {
|
||||||
|
return (
|
||||||
|
<div className='w-full h-full'>
|
||||||
|
{props.header}
|
||||||
|
<main
|
||||||
|
className={cn(props.mainClassName, 'main-content')}
|
||||||
|
style={{
|
||||||
|
height: props.header ? 'calc(100vh - 64px)' : '100vh', // Assuming header height is 64px
|
||||||
|
overflowY: 'auto',
|
||||||
|
...props.mainStyle,
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</main>
|
||||||
|
{props.footer}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
title?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
right?: React.ReactNode;
|
||||||
|
left?: React.ReactNode;
|
||||||
|
center?: React.ReactNode;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
export const Header = (props: HeaderProps) => {
|
||||||
|
return (
|
||||||
|
<header className={cn('h-16 w-full flex items-center', props.className)} style={props.style}>
|
||||||
|
{props.title}
|
||||||
|
<div className='grow flex justify-between items-center px-4 '>
|
||||||
|
{props.left && <div className='flex-1'>{props.left}</div>}
|
||||||
|
{!props.left && <div> </div>}
|
||||||
|
{props.center && <div className='flex-1 text-center'>{props.center}</div>}
|
||||||
|
{props.right && <>{props.right}</>}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
@ -16,7 +16,7 @@ import { MarkdownPreview } from '@/components/html/md/Preview';
|
|||||||
|
|
||||||
<Blank>
|
<Blank>
|
||||||
<div class="p-4 h-full">
|
<div class="p-4 h-full">
|
||||||
<MarkdownPreview>
|
<MarkdownPreview client:only>
|
||||||
<Readme />
|
<Readme />
|
||||||
</MarkdownPreview>
|
</MarkdownPreview>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Blank from '@/components/html/blank.astro';
|
import Blank from '@/components/html/blank.astro';
|
||||||
import { App as AssistantHome } from '@/apps/assistant-home/index.tsx';
|
import { App as AssistantHome } from '@/apps/assistant-home/index.tsx';
|
||||||
|
import { AppLayout } from '@/apps/layout';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Blank>
|
<Blank>
|
||||||
|
<AppLayout client:only>
|
||||||
<AssistantHome client:only />
|
<AssistantHome client:only />
|
||||||
|
</AppLayout>
|
||||||
</Blank>
|
</Blank>
|
||||||
|
@ -3,8 +3,11 @@ import '@/styles/theme.css';
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Blank from '@/components/html/blank.astro';
|
import Blank from '@/components/html/blank.astro';
|
||||||
import { App } from '@/apps/mark/manager/Manager';
|
import { App } from '@/apps/mark/manager/Manager';
|
||||||
|
import { AppLayout } from '@/apps/layout';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Blank>
|
<Blank>
|
||||||
<App client:only openMenu={true} />
|
<AppLayout client:only>
|
||||||
|
<App client:only openMenu={true} hasTopTitle={true}/>
|
||||||
|
</AppLayout>
|
||||||
</Blank>
|
</Blank>
|
||||||
|
@ -3,6 +3,7 @@ import '@/styles/theme.css';
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import Blank from '@/components/html/blank.astro';
|
import Blank from '@/components/html/blank.astro';
|
||||||
import { App } from '@/apps/preview/md.tsx';
|
import { App } from '@/apps/preview/md.tsx';
|
||||||
|
import { AppLayout } from '@/apps/layout';
|
||||||
---
|
---
|
||||||
|
|
||||||
<link
|
<link
|
||||||
@ -14,5 +15,7 @@ import { App } from '@/apps/preview/md.tsx';
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Blank>
|
<Blank>
|
||||||
|
<AppLayout client:only>
|
||||||
<App client:only />
|
<App client:only />
|
||||||
|
</AppLayout>
|
||||||
</Blank>
|
</Blank>
|
||||||
|
@ -16,7 +16,7 @@ export interface Cache {
|
|||||||
*/
|
*/
|
||||||
init?: () => Promise<any>;
|
init?: () => Promise<any>;
|
||||||
}
|
}
|
||||||
type User = {
|
export type User = {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Query, BaseQuery } from '@kevisual/query';
|
import { Query, BaseQuery } from '@kevisual/query';
|
||||||
import type { Result, DataOpts } from '@kevisual/query/query';
|
import type { Result, DataOpts } from '@kevisual/query/query';
|
||||||
import { setBaseResponse } from '@kevisual/query/query';
|
import { setBaseResponse } from '@kevisual/query/query';
|
||||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
import { LoginCacheStore, CacheStore, User } from './login-cache.ts';
|
||||||
import { Cache } from './login-cache.ts';
|
import { Cache } from './login-cache.ts';
|
||||||
|
|
||||||
export type QueryLoginOpts = {
|
export type QueryLoginOpts = {
|
||||||
@ -397,8 +397,22 @@ export class QueryLogin extends BaseQuery {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
async getLoginUserByToken(token: string): Promise<User | null> {
|
||||||
|
const me = await this.getMe(token, false);
|
||||||
|
if (me.code === 200) {
|
||||||
|
const user = me.data;
|
||||||
|
this.cacheStore.setLoginUser({
|
||||||
|
user,
|
||||||
|
id: user.id,
|
||||||
|
accessToken: token,
|
||||||
|
refreshToken: me.data?.refreshToken || '',
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 检查登录状态
|
* 检查登录状态,根据 login by web
|
||||||
* @param token
|
* @param token
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user