Compare commits
No commits in common. "1d3c2b064558275b47cfd59db343cd3dd8c95757" and "2a818cba7f582a857fa2fc2b6210d844b9802bf3" have entirely different histories.
1d3c2b0645
...
2a818cba7f
28
package.json
28
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/official-website",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"basename": "/root/official",
|
||||
@ -8,32 +8,26 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"pub": "envision deploy ./dist -k official -v 0.0.2 -u -o root"
|
||||
"pub": "envision deploy ./dist -k official -v 0.0.1 -u -o root"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"antd": "^5.26.2",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.522.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"rollup-plugin-visualizer": "^6.0.3"
|
||||
"lucide-react": "^0.483.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/cache": "^0.0.3",
|
||||
"@kevisual/codemirror": "^0.0.12",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"react-feather": "^2.0.10",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"vite": "^7.0.0"
|
||||
"tailwindcss": "^4.0.15",
|
||||
"vite": "^6.2.3"
|
||||
}
|
||||
}
|
2097
pnpm-lock.yaml
generated
2097
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@tailwindcss/oxide'
|
||||
- esbuild
|
||||
|
114
src/App.tsx
114
src/App.tsx
@ -1,57 +1,16 @@
|
||||
import { Mail, Phone, MapPin, Book, Globe, Brain, Save } from 'lucide-react';
|
||||
import { chain, TextEditor } from './components/TextEditor';
|
||||
import { toast, ToastContainer } from 'react-toastify';
|
||||
import { Provider } from './Provider';
|
||||
// import { ToastContainer } from 'react-toastify';
|
||||
// @ts-ignore
|
||||
import Logo from './assets/logo-1.png';
|
||||
import { useState } from 'react';
|
||||
import { CodeDescModal } from './modules/CodeDescModal';
|
||||
import { query } from './modules/query.ts';
|
||||
import { toastSuccess, toastWeChat } from './modules/RedirectSuccess.tsx';
|
||||
import { WeChat } from './components/Icon.tsx';
|
||||
export const Main = () => {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [html, setHtml] = useState<string>('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [resultUrl, setResultUrl] = useState<string>('');
|
||||
const [url] = useState<string>('https://kevisual.cn');
|
||||
const onSubmit = async (values: { title: string; description: string }) => {
|
||||
setResultUrl('');
|
||||
const uploadData = {
|
||||
title: values?.title,
|
||||
description: values?.description,
|
||||
content: chain.getContent(),
|
||||
};
|
||||
const res = await query.post({
|
||||
path: 'app',
|
||||
key: 'public-upload-html',
|
||||
data: uploadData,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
const url = res.data?.url;
|
||||
if (url) {
|
||||
let origin = window.location.origin;
|
||||
if (origin.includes('www.kevisual.cn')) {
|
||||
origin = origin.replace('www.kevisual.cn', 'kevisual.cn');
|
||||
}
|
||||
const newUrl = new URL(url, origin);
|
||||
// toast.success('创建成功, 访问地址' + newUrl.toString(), { autoClose: 3000 });
|
||||
toastSuccess(newUrl.toString());
|
||||
setResultUrl(newUrl.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='min-h-screen bg-gray-50'>
|
||||
{/* Hero Section */}
|
||||
<header className=''>
|
||||
<nav className='px-4 mx-auto h-16 flex justify-between items-center bg-white border-b border-b-gray-200 w-full'>
|
||||
<div
|
||||
className='flex items-center space-x-4 cursor-pointer'
|
||||
onClick={() => {
|
||||
window.open(url, '_blank');
|
||||
}}>
|
||||
<img src={Logo} alt='可视化助手 Logo' className='h-10 w-30 ' />
|
||||
<div className='flex items-center space-x-4'>
|
||||
<img src={Logo} alt='可视化助手 Logo' className='h-10 w-20 ' />
|
||||
</div>
|
||||
<div className='hidden md:flex space-x-6'>
|
||||
<a href='#features' className='hover:text-gray-400'>
|
||||
@ -68,54 +27,16 @@ export const Main = () => {
|
||||
style={{
|
||||
height: 'calc(100vh - 64px)',
|
||||
}}>
|
||||
<nav className='h-12 bg-white flex'>
|
||||
<button
|
||||
className='flex items-center px-4 h-full bg-white border-b border-b-gray-200 hover:bg-gray-50 cursor-pointer'
|
||||
onClick={() => {
|
||||
const content = chain.getContent();
|
||||
if (!content) {
|
||||
toast.error('内容不能为空', { position: 'top-center', autoClose: 1000 });
|
||||
return;
|
||||
}
|
||||
setOpen(true);
|
||||
}}>
|
||||
<nav className='h-12 bg-white'>
|
||||
<button className='flex items-center px-4 h-full bg-white border-b border-b-gray-200 hover:bg-gray-50 cursor-pointer' onClick={() => {}}>
|
||||
<span className='text-gray-700'>创建</span>
|
||||
<Save className='ml-2 w-4 h-4 text-gray-500' />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className='flex items-center px-4 h-full bg-white border-b border-b-gray-200 hover:bg-gray-50 cursor-pointer'
|
||||
style={{
|
||||
backgroundColor: showPreview ? '#f0f0f0' : 'white',
|
||||
}}
|
||||
onClick={() => {
|
||||
setShowPreview(!showPreview);
|
||||
}}>
|
||||
<span className='text-gray-700'>预览</span>
|
||||
<Globe className='ml-2 w-4 h-4 text-gray-500' />
|
||||
</button>
|
||||
</nav>
|
||||
<div className='p-2 rounded shadow flex' style={{ height: 'calc(100% - 48px - 48px)' }}>
|
||||
<div className='h-full overflow-auto flex-1'>
|
||||
<TextEditor content={''} chain={chain} onChange={setHtml} />
|
||||
</div>
|
||||
{showPreview && (
|
||||
<div className='w-1/2 shrink-1 border-l border-gray-200 h-full overflow-auto'>
|
||||
<iframe className='w-full h-full border-0' srcDoc={html} title='预览' sandbox='allow-scripts allow-same-origin allow-popups' />
|
||||
</div>
|
||||
)}
|
||||
<div className='p-2 rounded shadow' style={{ height: 'calc(100% - 48px - 48px)' }}>
|
||||
<TextEditor content='' chain={chain} />
|
||||
</div>
|
||||
<footer className='h-12'>
|
||||
{resultUrl && (
|
||||
<div className='flex items-center gap-2 px-4 h-full bg-white border-t border-t-gray-200'>
|
||||
<span className='text-gray-700'>生成的链接:</span>
|
||||
<a href={resultUrl} target='_blank' rel='noopener noreferrer' className='text-blue-600 hover:underline'>
|
||||
{resultUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</footer>
|
||||
<CodeDescModal open={open} onClose={() => setOpen(false)} onSubmit={onSubmit} />
|
||||
<footer className='h-12'></footer>
|
||||
</main>
|
||||
|
||||
{/* Features Section */}
|
||||
@ -209,20 +130,9 @@ export const Main = () => {
|
||||
<div>
|
||||
<h3 className='text-lg font-semibold mb-4'>联系我们</h3>
|
||||
<div className='flex space-x-4'>
|
||||
<a href='mailto:feedback@kevisual.cn' className='text-gray-400 hover:text-white cursor-pointer'>
|
||||
<a href='mailto:feedback@kevisual.cn' className='text-gray-400 hover:text-white'>
|
||||
<Mail className='w-6 h-6' />
|
||||
</a>
|
||||
<a href='tel:18324451015' className='text-gray-400 hover:text-white cursor-pointer'>
|
||||
<Phone className='w-6 h-6' />
|
||||
</a>
|
||||
<a
|
||||
className='text-gray-400 hover:text-white cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
toastWeChat();
|
||||
}}>
|
||||
<WeChat className='w-6 h-6' />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -243,10 +153,8 @@ export const Main = () => {
|
||||
export const App = () => {
|
||||
return (
|
||||
<>
|
||||
<Provider>
|
||||
<ToastContainer autoClose={2000}></ToastContainer>
|
||||
<Main />
|
||||
</Provider>
|
||||
{/* <ToastContainer></ToastContainer> */}
|
||||
<App />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +0,0 @@
|
||||
import ConfigProvider from 'antd/lib/config-provider';
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
|
||||
export const Provider = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: '#1677ff',
|
||||
colorTextBase: '#ffffff',
|
||||
colorBgBase: '#1f1f1f',
|
||||
colorBgContainer: '#2c2c2c',
|
||||
colorBorder: '#3a3a3a',
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
File diff suppressed because one or more lines are too long
@ -5,20 +5,3 @@ export const Github = () => {
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const WeChat = (props: React.SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<div className={'relative ' + props.className}>
|
||||
<svg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' className='w-8 h-8 absolute -top-1 left-0'>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M289.8 367.2c0-13.6 7.8-25.4 19.8-31.2 21.3-10.2 49.2 3.5 49.2 32.5 0 18-16 33.5-34.2 33.5-19.4 0-34.8-15.3-34.8-34.8z m174.2 0.7c0-13.7 8.5-26.2 18.9-31.4 12.7-6.3 28.1-4.6 38.6 4.8 11.8 10.5 15 29.4 7.8 42.3-14.3 25.5-49.9 24.2-61.9-1.7-1.5-3.4-3.4-9.6-3.4-14zM149.9 433c0 27.3 2.4 43 10.7 66.7 8.4 24 24 49.6 41.4 68.3 1.6 1.7 2.1 2.7 3.9 4.5l22 20.6c1.6 1.3 3.1 2.4 4.7 3.7 1.7 1.3 2.9 2.3 4.7 3.7 14.3 10.7 10.8 17.2 5.3 36.7l-8.1 30c-2.8 9.5 1.9 14.7 8.2 14.4 4.1-0.2 20.8-10.9 24.6-13.1l40.6-23.3c11.2-5 19-0.9 32.4 2.5 18.6 4.7 28.6 5.3 47.5 7.3l38.1 0.4c-0.8-9.7-8.4-17.1-8.4-58.5 0-16 3.9-33.9 7.8-46.4 5.7-18 16.3-38.3 27.8-53.5l18.8-21.9 14.2-12.9c2.7-2.3 5-4.1 7.8-6.4l39.3-24.6c39.3-18.7 75.6-28.5 124.9-28.5l9.7 0.7c3.1-0.2 3 1.2-0.6-13.1-4.4-17.7-12.1-35.2-21.5-50.8-12.7-21.2-22.5-31.3-39.1-47.9-15.6-15.6-41.7-32.5-60.6-42-15.7-7.8-25.1-11.3-41.7-16.9-14.8-5-30.2-8.1-47.2-10.8-8.6-1.4-17.5-1.9-26.2-2.9-4.5-0.5-10.1 0.2-14.7-0.1-43.9-2.5-103.7 11.4-141.6 31.8-1.9 1-2.5 1.5-4.5 2.6l-23 13.8c-12.4 9.4-20.3 13.9-32.6 26.1l-27.1 30.9c-20.5 30.3-37.5 64.8-37.5 108.9z'
|
||||
p-id='1534'></path>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M554.2 543.9c0-16.4 12.2-29 29.7-29 23 0 35.1 27 22.9 44.9-0.7 1-0.7 0.8-1.4 1.8-0.9 1.2-0.8 1.2-1.7 2.2-1.9 2.1-5.3 4.3-8 5.6-11 5.3-23.7 3.6-32.4-4.8-5-5-9.1-11.6-9.1-20.7z m172.2-29h3.9c12.9 0 26.4 13 26.4 25.8 0 9.7-0.7 16-8.5 23.7-21.5 21.1-58.7-3-46.5-31.6 2.8-6.6 7.2-11.6 13.5-14.9 2.9-1.5 7.1-3 11.2-3z m-288.9 81.2c0 22.7 0.9 30.3 6.8 51.2 4.8 16.9 14.2 34 24.1 48.1l10.8 13.7c4.5 4.6 9.7 11.1 14.6 15.1 1.6 1.3 2 1.4 3.5 2.9 5.9 5.9 19.5 15.3 27.3 20.3 2.5 1.6 5.2 3.2 7.7 4.6 32.4 18.5 75.8 31.6 113.5 31.6 32.4 0 45.6-0.6 76.6-8.5 14.5-3.7 16.8-2.2 29.6 5.5l39.5 23c8.3 4.8 13.8-1.8 12.4-7.9l-2.6-9.7c-1.7-6.8-3.5-13.2-5.3-19.9-2.6-10-7.3-19.7 2.6-27 18.5-13.6 30.2-25.7 43.8-43.8 19.1-25.3 31.5-61.5 31.5-93.6 0-7.2-1-16.1-1.9-22.6-3.8-27.5-17.1-56.9-34.4-77.8l-24.5-25.8c-2.3-2.1-4.7-3.6-7.1-5.8-5.6-5-22.9-16.3-29.7-19.9-74.4-39.8-165.2-41.3-239.4-1.3-5.3 2.8-10.4 5.9-15.3 9.2-5.5 3.7-16.7 11.4-21.5 15.9-1.2 1.2-1.5 1.6-3 2.8-1.7 1.3-2 1.5-3.4 3-14.7 14.9-22 21.8-33.4 40.8-12.3 20.7-22.8 50.2-22.8 75.9z'
|
||||
p-id='1535'></path>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { createEditor } from '@kevisual/codemirror';
|
||||
import { Chain } from '@kevisual/codemirror/utils';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { CacheWorkspace } from '@kevisual/cache';
|
||||
|
||||
export const chain = new Chain();
|
||||
type TextEditorProps = {
|
||||
content: string;
|
||||
chain?: Chain;
|
||||
onChange?: (content: string) => void;
|
||||
};
|
||||
export const TextEditor = ({ content, chain, onChange }: TextEditorProps) => {
|
||||
export const TextEditor = ({ content, chain }: TextEditorProps) => {
|
||||
const editorElRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<ReturnType<typeof createEditor>>(null);
|
||||
useEffect(() => {
|
||||
@ -27,25 +25,15 @@ export const TextEditor = ({ content, chain, onChange }: TextEditorProps) => {
|
||||
}, [content]);
|
||||
const initEditor = async () => {
|
||||
if (!editorElRef.current) return;
|
||||
const cache = new CacheWorkspace();
|
||||
const editor = createEditor(editorElRef.current, {
|
||||
type: 'html',
|
||||
onChange: (value) => {
|
||||
cache.set('editor-content-home', value);
|
||||
onChange?.(value);
|
||||
},
|
||||
});
|
||||
|
||||
const value = await cache.get('editor-content-home');
|
||||
const cmScroller = editorElRef.current.querySelector('.cm-scroller');
|
||||
if (cmScroller) {
|
||||
cmScroller.classList.add('scrollbar');
|
||||
}
|
||||
chain?.setEditor?.(editor);
|
||||
editorRef.current = editor;
|
||||
if (value) {
|
||||
chain?.setContent?.(value);
|
||||
}
|
||||
};
|
||||
return <div className='h-full overflow-hidden' ref={editorElRef}></div>;
|
||||
};
|
||||
|
@ -6,4 +6,3 @@ import './index.css';
|
||||
const root = createRoot(document.getElementById('root') as HTMLElement);
|
||||
|
||||
root.render(<App />);
|
||||
|
||||
|
@ -1,51 +0,0 @@
|
||||
import Modal from 'antd/es/modal/Modal';
|
||||
import Form, { useForm } from 'antd/es/form/Form';
|
||||
import FormItem from 'antd/es/form/FormItem';
|
||||
import Input from 'antd/es/input';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type CodeDescModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit?: (values: { title: string; description: string }) => void;
|
||||
initialValues?: { title: string; description: string };
|
||||
};
|
||||
export const CodeDescModal = (props: CodeDescModalProps) => {
|
||||
const [form] = useForm();
|
||||
useEffect(() => {
|
||||
if (!props.open) {
|
||||
return;
|
||||
}
|
||||
if (props.initialValues) {
|
||||
form.setFieldsValue(props.initialValues || { title: '', description: '' });
|
||||
}
|
||||
}, [props.open, props.initialValues, form]);
|
||||
return (
|
||||
<Modal title='代码描述' open={props.open} onCancel={props.onClose} footer={null}>
|
||||
<p className='text-gray-500 text-sm mb-4'>登陆用户的数据长存,非登陆的用户,保留30天自动删除。</p>
|
||||
<Form form={form} layout='vertical'>
|
||||
<FormItem label='标题' name='title'>
|
||||
<Input />
|
||||
</FormItem>
|
||||
<FormItem label='描述' name='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<div className='flex justify-end mt-4'>
|
||||
<button
|
||||
className='px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 cursor-pointer'
|
||||
onClick={() => {
|
||||
form.validateFields().then((values) => {
|
||||
props.onSubmit?.(values);
|
||||
props.onClose();
|
||||
});
|
||||
}}>
|
||||
提交
|
||||
</button>
|
||||
<button className='ml-2 px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 cursor-pointer' onClick={props.onClose}>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
import { EvWechat } from '../components/EvWechat';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export const RedirectSuccess = ({ url }: { url: string }) => {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center p-2'>
|
||||
<div className='flex flex-col gap-2 mb-3'>
|
||||
<div className=' font-semibold'>创建成功</div>
|
||||
<a
|
||||
href={url}
|
||||
className='text-blue-600 hover:text-blue-800 transition-colors duration-200 hover:underline block truncate'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'>
|
||||
跳转到新应用
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const toastSuccess = (url: string) => {
|
||||
toast.success(<RedirectSuccess url={url} />, {
|
||||
autoClose: 5000,
|
||||
className: 'rounded-md shadow-lg',
|
||||
// icon: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const toastWeChat = () => {
|
||||
toast.success(<EvWechat />, {
|
||||
autoClose: 10000,
|
||||
className: 'rounded-md shadow-lg',
|
||||
icon: false,
|
||||
});
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
import { QueryClient } from '@kevisual/query';
|
||||
|
||||
export const query = new QueryClient();
|
@ -1,7 +1,6 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
// import { visualizer } from "rollup-plugin-visualizer";
|
||||
// https://vitejs.dev/config/
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
@ -11,14 +10,4 @@ export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: ['lucide-react'],
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:4005',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user