feat: 优化界面显示,对deck添加编辑功能
This commit is contained in:
parent
02a1752a13
commit
12f1084612
@ -27,6 +27,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^14.1.2",
|
"marked": "^14.1.2",
|
||||||
@ -42,8 +43,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.11.0",
|
"@eslint/js": "^9.11.0",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||||
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.5.5",
|
"@types/node": "^22.5.5",
|
||||||
"@types/react": "^18.3.8",
|
"@types/react": "^18.3.8",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
@ -53,6 +56,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.12",
|
"eslint-plugin-react-refresh": "^0.4.12",
|
||||||
"globals": "^15.9.0",
|
"globals": "^15.9.0",
|
||||||
|
"postcss-import": "^16.1.0",
|
||||||
"react-is": "^18.3.1",
|
"react-is": "^18.3.1",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
|
25
plugins/flex.js
Normal file
25
plugins/flex.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
const plugin = require('tailwindcss/plugin');
|
||||||
|
|
||||||
|
const flexCenterBaseStyles = {
|
||||||
|
display: 'flex',
|
||||||
|
'justify-content': 'center',
|
||||||
|
'align-items': 'center',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** flex 居中 */
|
||||||
|
const flexCenter = plugin(function ({ addUtilities }) {
|
||||||
|
addUtilities({
|
||||||
|
/** flex 居中 */
|
||||||
|
'.flex-row-center': flexCenterBaseStyles,
|
||||||
|
'.flex-col-center': { ...flexCenterBaseStyles, 'flex-direction': 'column' },
|
||||||
|
'.layout-menu': {},
|
||||||
|
'.scrollbar': {},
|
||||||
|
'.card': {},
|
||||||
|
'.card-title': {},
|
||||||
|
'.card-subtitle': {},
|
||||||
|
'.card-body': {},
|
||||||
|
'.card-footer': {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = flexCenter;
|
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
@ -53,6 +53,9 @@ importers:
|
|||||||
d3:
|
d3:
|
||||||
specifier: ^7.9.0
|
specifier: ^7.9.0
|
||||||
version: 7.9.0
|
version: 7.9.0
|
||||||
|
eventemitter3:
|
||||||
|
specifier: ^5.0.1
|
||||||
|
version: 5.0.1
|
||||||
immer:
|
immer:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.1
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
@ -93,12 +96,18 @@ importers:
|
|||||||
'@tailwindcss/aspect-ratio':
|
'@tailwindcss/aspect-ratio':
|
||||||
specifier: ^0.4.2
|
specifier: ^0.4.2
|
||||||
version: 0.4.2(tailwindcss@3.4.13)
|
version: 0.4.2(tailwindcss@3.4.13)
|
||||||
|
'@tailwindcss/line-clamp':
|
||||||
|
specifier: ^0.4.4
|
||||||
|
version: 0.4.4(tailwindcss@3.4.13)
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.15
|
specifier: ^0.5.15
|
||||||
version: 0.5.15(tailwindcss@3.4.13)
|
version: 0.5.15(tailwindcss@3.4.13)
|
||||||
'@types/d3':
|
'@types/d3':
|
||||||
specifier: ^7.4.3
|
specifier: ^7.4.3
|
||||||
version: 7.4.3
|
version: 7.4.3
|
||||||
|
'@types/lodash-es':
|
||||||
|
specifier: ^4.17.12
|
||||||
|
version: 4.17.12
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.5.5
|
specifier: ^22.5.5
|
||||||
version: 22.5.5
|
version: 22.5.5
|
||||||
@ -126,6 +135,9 @@ importers:
|
|||||||
globals:
|
globals:
|
||||||
specifier: ^15.9.0
|
specifier: ^15.9.0
|
||||||
version: 15.9.0
|
version: 15.9.0
|
||||||
|
postcss-import:
|
||||||
|
specifier: ^16.1.0
|
||||||
|
version: 16.1.0(postcss@8.4.47)
|
||||||
react-is:
|
react-is:
|
||||||
specifier: ^18.3.1
|
specifier: ^18.3.1
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
@ -733,6 +745,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
||||||
|
|
||||||
|
'@tailwindcss/line-clamp@0.4.4':
|
||||||
|
resolution: {integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
||||||
|
|
||||||
'@tailwindcss/typography@0.5.15':
|
'@tailwindcss/typography@0.5.15':
|
||||||
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
|
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -855,6 +872,12 @@ packages:
|
|||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
|
'@types/lodash-es@4.17.12':
|
||||||
|
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||||
|
|
||||||
|
'@types/lodash@4.17.9':
|
||||||
|
resolution: {integrity: sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||||
|
|
||||||
@ -1861,6 +1884,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.0.0
|
postcss: ^8.0.0
|
||||||
|
|
||||||
|
postcss-import@16.1.0:
|
||||||
|
resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.0.0
|
||||||
|
|
||||||
postcss-js@4.0.1:
|
postcss-js@4.0.1:
|
||||||
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
|
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
|
||||||
engines: {node: ^12 || ^14 || >= 16}
|
engines: {node: ^12 || ^14 || >= 16}
|
||||||
@ -3142,6 +3171,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.13
|
tailwindcss: 3.4.13
|
||||||
|
|
||||||
|
'@tailwindcss/line-clamp@0.4.4(tailwindcss@3.4.13)':
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 3.4.13
|
||||||
|
|
||||||
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)':
|
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.castarray: 4.4.0
|
lodash.castarray: 4.4.0
|
||||||
@ -3300,6 +3333,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/lodash-es@4.17.12':
|
||||||
|
dependencies:
|
||||||
|
'@types/lodash': 4.17.9
|
||||||
|
|
||||||
|
'@types/lodash@4.17.9': {}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@ -4428,6 +4467,13 @@ snapshots:
|
|||||||
read-cache: 1.0.0
|
read-cache: 1.0.0
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
|
|
||||||
|
postcss-import@16.1.0(postcss@8.4.47):
|
||||||
|
dependencies:
|
||||||
|
postcss: 8.4.47
|
||||||
|
postcss-value-parser: 4.2.0
|
||||||
|
read-cache: 1.0.0
|
||||||
|
resolve: 1.22.8
|
||||||
|
|
||||||
postcss-js@4.0.1(postcss@8.4.47):
|
postcss-js@4.0.1(postcss@8.4.47):
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase-css: 2.0.1
|
camelcase-css: 2.0.1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
|
||||||
import { ConfigProvider } from 'antd';
|
import { ConfigProvider, App as AntApp } from 'antd';
|
||||||
import { App as ContainerApp } from './pages/container';
|
import { App as ContainerApp } from './pages/container';
|
||||||
import { App as PanelApp } from './pages/panel';
|
import { App as PanelApp } from './pages/panel';
|
||||||
import { App as PublishApp } from './pages/publish';
|
import { App as PublishApp } from './pages/publish';
|
||||||
|
17
src/components/card/CardBlank.tsx
Normal file
17
src/components/card/CardBlank.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import twMerge from 'tailwind-merge';
|
||||||
|
|
||||||
|
type CardBlankProps = {
|
||||||
|
number?: number;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
export const CardBlank = (props: CardBlankProps) => {
|
||||||
|
const { number = 4, className } = props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{new Array(number).fill(0).map((_, index) => {
|
||||||
|
return <div key={index} className={clsx('w-[300px] flex-shrink-0', className)}></div>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -3,6 +3,13 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
}
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-2xl font-bold;
|
@apply text-2xl font-bold;
|
||||||
}
|
}
|
||||||
@ -12,7 +19,40 @@
|
|||||||
h3 {
|
h3 {
|
||||||
@apply text-lg font-bold;
|
@apply text-lg font-bold;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.btn {
|
||||||
|
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
@apply bg-white shadow-md rounded-lg p-4;
|
||||||
|
.card-title {
|
||||||
|
@apply text-lg font-bold;
|
||||||
|
}
|
||||||
|
.card-subtitle {
|
||||||
|
@apply text-sm text-gray-500;
|
||||||
|
}
|
||||||
|
.card-description {
|
||||||
|
@apply text-gray-700 break-words;
|
||||||
|
}
|
||||||
|
.card-code {
|
||||||
|
@apply bg-gray-100 p-2;
|
||||||
|
}
|
||||||
|
.card-body {
|
||||||
|
@apply text-gray-700;
|
||||||
|
}
|
||||||
|
.card-footer {
|
||||||
|
@apply text-sm text-gray-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
.layout-menu {
|
.layout-menu {
|
||||||
@apply bg-gray-900 p-2 text-white flex justify-between h-12;
|
@apply bg-gray-900 p-2 text-white flex justify-between h-12;
|
||||||
}
|
}
|
||||||
|
.bg-custom-blue {
|
||||||
|
background-color: #3490dc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1
src/hooks/index.ts
Normal file
1
src/hooks/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './message';
|
22
src/hooks/message.tsx
Normal file
22
src/hooks/message.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { App } from 'antd';
|
||||||
|
|
||||||
|
export const useMessage = () => {
|
||||||
|
const { message: antMessage, modal, notification } = App.useApp();
|
||||||
|
return {
|
||||||
|
success: antMessage.success,
|
||||||
|
error: antMessage.error,
|
||||||
|
warning: antMessage.warning,
|
||||||
|
info: antMessage.info,
|
||||||
|
loading: antMessage.loading,
|
||||||
|
open: antMessage.open,
|
||||||
|
destroy: antMessage.destroy,
|
||||||
|
modal: modal,
|
||||||
|
notification: notification,
|
||||||
|
message: antMessage,
|
||||||
|
com: (
|
||||||
|
<>
|
||||||
|
<App />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
@ -1,36 +1,110 @@
|
|||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { useAiStore } from './store/ai-store';
|
import { useAiStore } from './store/ai-store';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { Button } from 'antd';
|
import { Button, Form, Input } from 'antd';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { TextArea } from '../container/components/TextArea';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
export const AiMoudle = () => {
|
export const AiMoudle = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
const aiStore = useAiStore(
|
const aiStore = useAiStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
return {
|
return {
|
||||||
open: state.open,
|
open: state.open,
|
||||||
setOpen: state.setOpen,
|
setOpen: state.setOpen,
|
||||||
runAi: state.runAi,
|
runAi: state.runAi,
|
||||||
|
formData: state.formData,
|
||||||
|
setFormData: state.setFormData,
|
||||||
|
messages: state.messages,
|
||||||
|
setMessages: state.setMessage,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (!aiStore.open) {
|
|
||||||
return null;
|
useEffect(() => {
|
||||||
}
|
if (!aiStore.open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isNull = JSON.stringify(aiStore.formData) === '{}';
|
||||||
|
if (!isNull) {
|
||||||
|
form.setFieldsValue(aiStore.formData);
|
||||||
|
} else {
|
||||||
|
form.setFieldsValue({ inputs: [] });
|
||||||
|
}
|
||||||
|
}, [aiStore.open, aiStore.formData]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!aiStore.open) {
|
||||||
|
aiStore.setMessages([]);
|
||||||
|
}
|
||||||
|
}, [aiStore.open]);
|
||||||
|
const onSend = () => {
|
||||||
|
const data = form.getFieldsValue();
|
||||||
|
aiStore.setFormData(data);
|
||||||
|
aiStore.runAi();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className='w-96 flex-shrink-0 bg-gray-100 border-l-2 shadow-lg flex flex-col'>
|
<div className={clsx('w-[600px] flex-shrink-0 bg-gray-100 border-l-2 shadow-lg flex flex-col', !aiStore?.open && 'hidden')}>
|
||||||
<div className='flex gap-4 bg-slate-400 p-2'>
|
<div className='flex gap-4 bg-slate-400 p-2'>
|
||||||
<Button className='position ml-4 !bg-slate-400 !border-black' onClick={() => aiStore.setOpen(false)} icon={<CloseOutlined />}></Button>
|
<Button className='position ml-4 !bg-slate-400 !border-black' onClick={() => aiStore.setOpen(false)} icon={<CloseOutlined />}></Button>
|
||||||
<h1 className='ml-10'>Ai Moudle</h1>
|
<h1 className='ml-10'>Ai Moudle</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-grow p-2'>
|
<div className='flex-grow p-2 overflow-hidden h-full flex flex-col'>
|
||||||
<div> chat message</div>
|
<div className='flex flex-col'>
|
||||||
<div>
|
<div className='mb-3'> chat message</div>
|
||||||
<Button
|
{aiStore?.messages?.map((message, index) => {
|
||||||
onClick={() => {
|
const html = marked.parse(message?.content);
|
||||||
aiStore.runAi();
|
return (
|
||||||
}}>
|
<div key={index} className=' justify-between px-4 w-full'>
|
||||||
Send
|
<div className='h3 font-bold'>{message?.role}</div>
|
||||||
</Button>
|
<div className='p-4 text-xs border shadow-sm rounded-sm scrollbar max-h-[200px] w-full overflow-scroll' dangerouslySetInnerHTML={{ __html: html }}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className='mt-4'>
|
||||||
|
<Form form={form}>
|
||||||
|
<Form.Item hidden name='id'>
|
||||||
|
<Input placeholder='message' />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name='messages' hidden>
|
||||||
|
<Input placeholder='message' />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name='key' hidden>
|
||||||
|
<Input placeholder='key' />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.List name={'inputs'} initialValue={[]}>
|
||||||
|
{(fields, { add, remove }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fields.map((field, index) => {
|
||||||
|
const key = form.getFieldValue(['inputs', index, 'key']);
|
||||||
|
console.log('key', key);
|
||||||
|
const isTitle = key === 'title';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={field.name + '-' + index} className=''>
|
||||||
|
<Form.Item name={[field.name, 'key']} rules={[{ required: true, message: 'Missing name' }]} hidden noStyle>
|
||||||
|
<Input placeholder='name' />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item className='!mb-0' name={[field.name, 'value']} rules={[{ required: true, message: 'Missing value' }]}>
|
||||||
|
<TextArea className='scrollbar' style={{ minHeight: isTitle ? 40 : 300 }} placeholder={key} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
</Form>
|
||||||
|
<div className='p-4'>
|
||||||
|
<Button className='mt-4' onClick={onSend}>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
import { query } from '@/modules';
|
import { query } from '@/modules';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
type ResData = {
|
||||||
|
created_at: string;
|
||||||
|
done?: boolean;
|
||||||
|
done_reason?: string;
|
||||||
|
eval_count?: number;
|
||||||
|
eval_duration?: number;
|
||||||
|
load_duration?: number;
|
||||||
|
message?: { role?: string; content?: string }[];
|
||||||
|
model?: string;
|
||||||
|
prompt_eval_count?: number;
|
||||||
|
prompt_eval_duration?: number;
|
||||||
|
total_duration?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type AiStore = {
|
export type AiStore = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -12,9 +25,13 @@ export type AiStore = {
|
|||||||
sendMsg: (msg: string) => void;
|
sendMsg: (msg: string) => void;
|
||||||
formData: any;
|
formData: any;
|
||||||
setFormData: (data: any) => void;
|
setFormData: (data: any) => void;
|
||||||
|
data: any;
|
||||||
|
setData: (data: any) => void;
|
||||||
runAi: () => any;
|
runAi: () => any;
|
||||||
title: string;
|
title: string;
|
||||||
setTitle: (title: string) => void;
|
setTitle: (title: string) => void;
|
||||||
|
messages: { role: string; content: string }[];
|
||||||
|
setMessage: (message: { role: string; content: string }[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAiStore = create<AiStore>((set, get) => {
|
export const useAiStore = create<AiStore>((set, get) => {
|
||||||
@ -28,31 +45,60 @@ export const useAiStore = create<AiStore>((set, get) => {
|
|||||||
},
|
},
|
||||||
formData: {},
|
formData: {},
|
||||||
setFormData: (data) => set({ formData: data }),
|
setFormData: (data) => set({ formData: data }),
|
||||||
|
data: {},
|
||||||
|
setData: (data) => {
|
||||||
|
const { key, presetData = {} } = data;
|
||||||
|
console.log('key', presetData, data);
|
||||||
|
if (!key) {
|
||||||
|
console.error('key is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { inputs = [] } = presetData.data || {};
|
||||||
|
const formData = {
|
||||||
|
key,
|
||||||
|
inputs: inputs.map((input) => {
|
||||||
|
return {
|
||||||
|
key: input.key,
|
||||||
|
value: input.value,
|
||||||
|
type: 'string',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
console.log('formData', formData);
|
||||||
|
set({ key, data, formData });
|
||||||
|
},
|
||||||
runAi: async () => {
|
runAi: async () => {
|
||||||
const { formData } = get();
|
const { formData, messages } = get();
|
||||||
|
const loading = message.loading('loading');
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'ai',
|
path: 'ai',
|
||||||
key: 'run',
|
key: 'run',
|
||||||
data: {
|
data: {
|
||||||
key: formData.key,
|
key: formData.key,
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
// {
|
||||||
key: 'title',
|
// key: 'title',
|
||||||
value: '根据描述生成代码',
|
// value: '根据描述生成代码',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
key: 'description',
|
// key: 'description',
|
||||||
value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试',
|
// value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试',
|
||||||
},
|
// },
|
||||||
|
...formData.inputs,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
loading();
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
console.log(res.data);
|
console.log(res.data);
|
||||||
message.success('Success');
|
message.success('Success');
|
||||||
|
set({ messages: [...messages, res.data.message] });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: '',
|
title: '',
|
||||||
setTitle: (title) => set({ title }),
|
setTitle: (title) => set({ title }),
|
||||||
|
messages: [],
|
||||||
|
setMessage: (messages) => set({ messages }),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -4,13 +4,15 @@ import { useLocation, useNavigate } from 'react-router';
|
|||||||
import { useCodeEditorStore, ParseData } from './store';
|
import { useCodeEditorStore, ParseData } from './store';
|
||||||
import { useToCodeEditor } from './hooks/use-to-code-editor';
|
import { useToCodeEditor } from './hooks/use-to-code-editor';
|
||||||
export { useToCodeEditor };
|
export { useToCodeEditor };
|
||||||
import { Button, message } from 'antd';
|
import { Button, message, Tooltip } from 'antd';
|
||||||
|
import { LeftOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const editorRef = useRef<typeof editor>(null);
|
const editorRef = useRef<typeof editor>(null);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const store = useCodeEditorStore();
|
const store = useCodeEditorStore();
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const navigator = useNavigate();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initEditor();
|
initEditor();
|
||||||
const state = location.state as ParseData;
|
const state = location.state as ParseData;
|
||||||
@ -63,12 +65,27 @@ export const App = () => {
|
|||||||
}
|
}
|
||||||
store.onUpdate(value);
|
store.onUpdate(value);
|
||||||
}, [store.dataType]);
|
}, [store.dataType]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full bg-gray-400'>
|
<div className='w-full h-full bg-gray-400 flex flex-col'>
|
||||||
<div className='px-2 bg-white mb-4'>Code Editor</div>
|
<div className='layout-menu'>Code Editor</div>
|
||||||
<Button onClick={onSave}>Save</Button>
|
<div className='p-4 flex-grow'>
|
||||||
<div className='w-full p-4 rounded-md bg-white' style={{ height: 'calc(100% - 130px)' }}>
|
<Button.Group className='mb-2'>
|
||||||
<div className='w-full h-full' ref={ref}></div>
|
<Tooltip title='Go Back' placement='bottom'>
|
||||||
|
<Button
|
||||||
|
className=''
|
||||||
|
icon={<LeftOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
navigator(-1);
|
||||||
|
}}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='Save' placement='bottom'>
|
||||||
|
<Button className='' onClick={onSave} icon={<SaveOutlined />}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Button.Group>
|
||||||
|
<div className='w-full p-4 rounded-md bg-white' style={{ height: 'calc(100% - 130px)' }}>
|
||||||
|
<div className='w-full h-full' ref={ref}></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -58,6 +58,9 @@ const FormModal = () => {
|
|||||||
<Form.Item name='title' label='title'>
|
<Form.Item name='title' label='title'>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name='description' label='description'>
|
||||||
|
<Input.TextArea rows={4} />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item name='code' label='code'>
|
<Form.Item name='code' label='code'>
|
||||||
<TextArea />
|
<TextArea />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@ -127,7 +130,7 @@ export const ContainerList = () => {
|
|||||||
}}>
|
}}>
|
||||||
{item.title || '-'}
|
{item.title || '-'}
|
||||||
</div>
|
</div>
|
||||||
<div className='font-light'>{item.description ? item.description : '-'}</div>
|
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full text-xs'>
|
<div className='w-full text-xs'>
|
||||||
<TextArea className='max-h-[240px] scrollbar' value={item.code} readonly />
|
<TextArea className='max-h-[240px] scrollbar' value={item.code} readonly />
|
||||||
|
@ -9,9 +9,10 @@ export const App = () => {
|
|||||||
<Route path='/' element={<Navigate to='/container/edit/list' />}></Route>
|
<Route path='/' element={<Navigate to='/container/edit/list' />}></Route>
|
||||||
<Route path='edit/list' element={<ContainerList />} />
|
<Route path='edit/list' element={<ContainerList />} />
|
||||||
<Route path='preview/:id/wrapper' element={<PreviewWrapper />} />
|
<Route path='preview/:id/wrapper' element={<PreviewWrapper />} />
|
||||||
<Route path='/' element={<div>Home</div>} />
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path='preview/:id' element={<Preview />} />
|
<Route path='preview/:id' element={<Preview />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export * from './module/Select';
|
||||||
|
39
src/pages/container/module/Select.tsx
Normal file
39
src/pages/container/module/Select.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { query } from '@/modules';
|
||||||
|
import { Select as AntSelect, message, SelectProps } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const Select = (props: SelectProps) => {
|
||||||
|
const [options, setOptions] = useState<{ value: string; id: string }[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
fetch();
|
||||||
|
}, []);
|
||||||
|
const fetch = async () => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'container',
|
||||||
|
key: 'list',
|
||||||
|
});
|
||||||
|
if (res.code !== 200) {
|
||||||
|
message.error(res.message || '获取容器列表失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = res.data || [];
|
||||||
|
setOptions(
|
||||||
|
data.map((item: any) => {
|
||||||
|
return {
|
||||||
|
label: item.title,
|
||||||
|
value: item.id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AntSelect
|
||||||
|
{...props}
|
||||||
|
options={options}
|
||||||
|
// onChange={(e) => {
|
||||||
|
// const labelValue = options.find((item) => item.value === e);
|
||||||
|
// props.onChange?.(e, options);
|
||||||
|
// }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,68 @@
|
|||||||
export const App = () => {
|
import clsx from 'clsx';
|
||||||
const serverList = ['container', 'panel', 'publish', 'code-editor', 'map'];
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
const serverList = ['container', 'panel', 'publish', 'code-editor', 'map', 'ai-chat'];
|
||||||
|
const serverPath = [
|
||||||
|
{
|
||||||
|
path: 'container',
|
||||||
|
links: ['edit/list', 'preview/:id', 'edit/:id'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'panel',
|
||||||
|
links: ['edit/list', 'flow/:id', 'deck/:id'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'publish',
|
||||||
|
links: ['edit/list'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'map',
|
||||||
|
links: ['/'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ai-chat',
|
||||||
|
links: ['/'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const ServerPath = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<div className='p-2 w-full h-full bg-gray-200'>
|
||||||
|
<h1 className='p-4 w-1/2 m-auto h1'>Map</h1>
|
||||||
|
<div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'>
|
||||||
|
{serverPath.map((item) => {
|
||||||
|
const links = item.links.map((link) => {
|
||||||
|
const hasId = link.includes(':id');
|
||||||
|
const _path = link === '/' ? item.path : item.path + '/' + link;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={link}
|
||||||
|
className={clsx('flex flex-col', !hasId && 'cursor-pointer')}
|
||||||
|
onClick={() => {
|
||||||
|
if (hasId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (link) {
|
||||||
|
navigate(`/${item.path}/${link}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/${item.path}`);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<div className={clsx('border rounded-md p-2 m-2', hasId && 'bg-gray-200')}>{_path}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div key={item.path} className='flex'>
|
||||||
|
{links}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const App = ServerPath;
|
||||||
|
export const ServerList = () => {
|
||||||
return (
|
return (
|
||||||
<div className='p-2 w-full h-full bg-gray-200'>
|
<div className='p-2 w-full h-full bg-gray-200'>
|
||||||
<div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'>
|
<div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'>
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { useEditStore } from '../store';
|
import { useEditStore } from '../store';
|
||||||
import { Button, Input, message, Modal, Table } from 'antd';
|
import { Button, Input, message, Modal, Table, Tooltip } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { useToCodeEditor } from '@/pages/code-editor';
|
import { useToCodeEditor } from '@/pages/code-editor';
|
||||||
|
import { CardBlank } from '@/components/card/CardBlank';
|
||||||
|
import { DeleteOutlined, EditOutlined, ForkOutlined, GoldOutlined, PlusOutlined, ToolOutlined } from '@ant-design/icons';
|
||||||
|
import { isObjectNull } from '@/utils/is-null';
|
||||||
const FormModal = () => {
|
const FormModal = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const editStore = useEditStore(
|
const editStore = useEditStore(
|
||||||
@ -15,6 +17,7 @@ const FormModal = () => {
|
|||||||
showEdit: state.showEditModal,
|
showEdit: state.showEditModal,
|
||||||
setShowEdit: state.setShowEditModal,
|
setShowEdit: state.setShowEditModal,
|
||||||
formData: state.formData,
|
formData: state.formData,
|
||||||
|
setFormData: state.setFormData,
|
||||||
updateData: state.updateData,
|
updateData: state.updateData,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@ -22,17 +25,26 @@ const FormModal = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const open = editStore.showEdit;
|
const open = editStore.showEdit;
|
||||||
if (open) {
|
if (open) {
|
||||||
form.setFieldsValue(editStore.formData || {});
|
if (isObjectNull(editStore.formData.data)) {
|
||||||
} else {
|
form.setFieldsValue({});
|
||||||
form.resetFields();
|
} else form.setFieldsValue(editStore.formData);
|
||||||
}
|
}
|
||||||
}, [editStore.showEdit]);
|
}, [editStore.showEdit]);
|
||||||
const onFinish = async (values: any) => {
|
const onFinish = async (values: any) => {
|
||||||
|
let defaultData = {
|
||||||
|
nodes: [],
|
||||||
|
edges: [],
|
||||||
|
viewport: {},
|
||||||
|
};
|
||||||
|
if (!isEdit) {
|
||||||
|
values.data = defaultData;
|
||||||
|
}
|
||||||
editStore.updateData(values);
|
editStore.updateData(values);
|
||||||
};
|
};
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
editStore.setShowEdit(false);
|
editStore.setShowEdit(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
editStore.setFormData({});
|
||||||
};
|
};
|
||||||
const isEdit = editStore.formData.id;
|
const isEdit = editStore.formData.id;
|
||||||
return (
|
return (
|
||||||
@ -52,9 +64,15 @@ const FormModal = () => {
|
|||||||
<Form.Item name='title' label='title'>
|
<Form.Item name='title' label='title'>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{/* <Form.Item name='code' label='code'>
|
<Form.Item name='description' label='description'>
|
||||||
<TextArea value={containerStore.formData.code} />
|
<Input.TextArea lang={'markdown'} />
|
||||||
</Form.Item> */}
|
</Form.Item>
|
||||||
|
<Form.Item name='type' label='type' noStyle hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name='data' label='data' noStyle hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label=' ' colon={false}>
|
<Form.Item label=' ' colon={false}>
|
||||||
<Button type='primary' htmlType='submit'>
|
<Button type='primary' htmlType='submit'>
|
||||||
Submit
|
Submit
|
||||||
@ -86,99 +104,80 @@ export const List = () => {
|
|||||||
editStore.getList();
|
editStore.getList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: 'ID',
|
|
||||||
dataIndex: 'id',
|
|
||||||
render: (text: string) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='w-40 truncate cursor-pointer'
|
|
||||||
title={text}
|
|
||||||
onClick={() => {
|
|
||||||
copy(text);
|
|
||||||
message.success('copy success');
|
|
||||||
}}>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Title',
|
|
||||||
dataIndex: 'title',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: 'Operation',
|
|
||||||
dataIndex: 'operation',
|
|
||||||
render: (text: string, record: any) => {
|
|
||||||
return (
|
|
||||||
<div className='flex gap-2'>
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
onClick={() => {
|
|
||||||
editStore.setFormData(record);
|
|
||||||
editStore.setShowEdit(true);
|
|
||||||
}}>
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
navicate('/panel/flow/' + record.id);
|
|
||||||
}}>
|
|
||||||
Flow
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
navicate('/panel/deck/' + record.id);
|
|
||||||
}}>
|
|
||||||
Deck
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
toCodeEditor.toPagePage(record);
|
|
||||||
}}>
|
|
||||||
Source Data Editor
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
onClick={() => {
|
|
||||||
editStore.deleteData(record.id);
|
|
||||||
}}>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full flex flex-col'>
|
<div className='w-full h-full flex'>
|
||||||
<div className='mb-2 w-full p-2 bg-white rounded-lg'>
|
<div className='p-2 bg-white rounded-r-lg'>
|
||||||
<Button
|
<Button
|
||||||
className='w-20 '
|
className='w-10 '
|
||||||
type='primary'
|
type='primary'
|
||||||
|
icon={<PlusOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editStore.setFormData({});
|
editStore.setFormData({});
|
||||||
editStore.setShowEdit(true);
|
editStore.setShowEdit(true);
|
||||||
}}>
|
}}></Button>
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-grow overflow-scroll'>
|
<div className='flex-grow overflow-scroll scrollbar mt-4'>
|
||||||
<Table
|
<div className=''>
|
||||||
pagination={false}
|
<div className=' flex flex-wrap gap-10 justify-center h-full overflow-auto scrollbar'>
|
||||||
scroll={{
|
{editStore.list.length > 0 &&
|
||||||
y: 600,
|
editStore.list.map((item, index) => {
|
||||||
}}
|
return (
|
||||||
loading={editStore.loading}
|
<div className='card w-[300px]' key={index}>
|
||||||
dataSource={editStore.list}
|
<div className='card-title'>{item.title}</div>
|
||||||
rowKey='id'
|
<div className='card-subtitle'> {item.description}</div>
|
||||||
columns={columns}
|
<div className='mt-4'>
|
||||||
/>
|
<Button.Group>
|
||||||
|
<Tooltip title='Edit'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
editStore.setFormData(item);
|
||||||
|
editStore.setShowEdit(true);
|
||||||
|
}}
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='to flow'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
navicate('/panel/flow/' + item.id);
|
||||||
|
}}
|
||||||
|
icon={<ForkOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='to deck'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
navicate('/panel/deck/' + item.id);
|
||||||
|
}}
|
||||||
|
icon={<GoldOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='to code editor'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
toCodeEditor.toPagePage(item);
|
||||||
|
}}
|
||||||
|
icon={<ToolOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='delete'>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
editStore.deleteData(item.id);
|
||||||
|
}}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Button.Group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<CardBlank className='w-[300px]' />
|
||||||
|
{editStore.list.length === 0 && <div className='text-center text-gray-500'>No data</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='h-2'></div>
|
|
||||||
<FormModal />
|
<FormModal />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -19,8 +19,13 @@ import { useShallow } from 'zustand/react/shallow';
|
|||||||
import { Container, useAddNode, ContainerMenusList, useMenuFlow } from '@abearxiong/flows';
|
import { Container, useAddNode, ContainerMenusList, useMenuFlow } from '@abearxiong/flows';
|
||||||
import { useMenuEmitter, ContainerMenusKeys } from '@abearxiong/flows';
|
import { useMenuEmitter, ContainerMenusKeys } from '@abearxiong/flows';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { Button } from 'antd';
|
import { Button, message, Tooltip } from 'antd';
|
||||||
import { usePanelStore } from '../store';
|
import { usePanelStore } from '../store';
|
||||||
|
import { CompassOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
|
import { generateId } from '@/utils/nanoid';
|
||||||
|
import { useMessage } from '@/hooks';
|
||||||
|
import { NodeProperties } from './properties/NodeProperties';
|
||||||
|
import { emitter } from '@abearxiong/container';
|
||||||
// router: Router
|
// router: Router
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
container: Container,
|
container: Container,
|
||||||
@ -36,7 +41,6 @@ export const Flow = () => {
|
|||||||
const ReactFlowApp = () => {
|
const ReactFlowApp = () => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]);
|
||||||
|
|
||||||
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
|
||||||
const panelStore = usePanelStore(
|
const panelStore = usePanelStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
@ -49,15 +53,47 @@ const ReactFlowApp = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (panelStore.data?.id) {
|
if (panelStore.data?.id) {
|
||||||
const { data } = panelStore.data || {};
|
const { data } = panelStore.data || {};
|
||||||
const nodes = data.nodes || [];
|
console.log('data', panelStore.data);
|
||||||
const edges = data.edges || [];
|
const nodes = [...data.nodes];
|
||||||
setNodes(nodes);
|
const edges = [...data.edges];
|
||||||
|
console.log('nodes', nodes);
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
nodes.push({
|
||||||
|
id: panelStore.data.id,
|
||||||
|
data: {
|
||||||
|
label: '容器',
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
},
|
||||||
|
type: 'container',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('nodes', nodes);
|
||||||
|
setNodes(nodes as any);
|
||||||
setEdges(edges);
|
setEdges(edges);
|
||||||
} else {
|
} else {
|
||||||
setNodes([]);
|
setNodes([]);
|
||||||
setEdges([]);
|
setEdges([]);
|
||||||
}
|
}
|
||||||
}, [panelStore.data]);
|
}, [panelStore.data]);
|
||||||
|
useEffect(() => {
|
||||||
|
emitter.on('setNodes', _setNodes);
|
||||||
|
emitter.on('setEdges', _setEdges);
|
||||||
|
return () => {
|
||||||
|
emitter.off('setNodes', _setNodes);
|
||||||
|
emitter.off('setEdges', _setEdges);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const _setNodes = (data: any) => {
|
||||||
|
setNodes(data);
|
||||||
|
};
|
||||||
|
const _setEdges = (data: any) => {
|
||||||
|
setEdges(data);
|
||||||
|
};
|
||||||
|
|
||||||
const { menuCom, onContextMenu, onClose } = useMenuFlow(
|
const { menuCom, onContextMenu, onClose } = useMenuFlow(
|
||||||
(item, cacheData) => {
|
(item, cacheData) => {
|
||||||
emit({
|
emit({
|
||||||
@ -72,11 +108,37 @@ const ReactFlowApp = () => {
|
|||||||
|
|
||||||
const { emit } = useMenuEmitter<ContainerMenusKeys>({
|
const { emit } = useMenuEmitter<ContainerMenusKeys>({
|
||||||
preview: ({ menu, data }) => {
|
preview: ({ menu, data }) => {
|
||||||
console.log('preview', data);
|
console.log('preview', data, message);
|
||||||
if (data?.data?.cid) {
|
if (data?.data?.cid) {
|
||||||
window.open(`/container/preview/${data.data.cid}`, '_blank');
|
window.open(`/container/preview/${data.data.cid}`, '_blank');
|
||||||
|
} else {
|
||||||
|
message.error('未绑定容器');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
code: ({ menu, data }) => {
|
||||||
|
// console.log('edit', data);
|
||||||
|
const nodeData = data?.data;
|
||||||
|
if (!nodeData.cid) {
|
||||||
|
message.error('请先绑定容器');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error('developing');
|
||||||
|
},
|
||||||
|
copy: ({ menu, data }) => {
|
||||||
|
message.error('developing');
|
||||||
|
},
|
||||||
|
delete: ({ menu, data }) => {
|
||||||
|
console.log('delete', data);
|
||||||
|
const nodeData = data?.data;
|
||||||
|
if (nodeData.root) {
|
||||||
|
message.error('root node can not be deleted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNodes((nodes) => nodes.filter((item: any) => item.id !== data.id));
|
||||||
|
},
|
||||||
|
internalData: ({ menu, data }) => {
|
||||||
|
message.error('developing');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const { onNeedAdd, onAdd, onMouseMove, adding } = useAddNode();
|
const { onNeedAdd, onAdd, onMouseMove, adding } = useAddNode();
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
@ -124,26 +186,41 @@ const ReactFlowApp = () => {
|
|||||||
}}>
|
}}>
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
<Controls />
|
<Controls />
|
||||||
{/* <Background gap={[14, 14]} size={2} color='#E4E5E7' /> */}
|
|
||||||
<Background color='#000' />
|
<Background color='#000' />
|
||||||
<Panel>{menuCom}</Panel>
|
<Panel>{menuCom}</Panel>
|
||||||
<Panel>
|
<Panel>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2'>
|
||||||
<Button
|
<Button.Group>
|
||||||
type='primary'
|
<Tooltip title='添加节点'>
|
||||||
onClick={(e) => {
|
<Button
|
||||||
onNeedAdd({ id: nanoid(6), data: { label: '容器' }, type: 'container' }, e);
|
icon={<PlusOutlined />}
|
||||||
}}>
|
onClick={(e) => {
|
||||||
测试添加按钮
|
onNeedAdd({ id: 'flow' + generateId(), data: { label: '容器' }, type: 'container' }, e);
|
||||||
</Button>
|
}}></Button>
|
||||||
<Button
|
</Tooltip>
|
||||||
onClick={() => {
|
<Tooltip title='save'>
|
||||||
onSave();
|
<Button
|
||||||
}}>
|
icon={<SaveOutlined />}
|
||||||
保存
|
onClick={() => {
|
||||||
</Button>
|
onSave();
|
||||||
|
}}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title='preview'>
|
||||||
|
<Button
|
||||||
|
icon={<CompassOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
const id = panelStore.data?.id;
|
||||||
|
if (!id) {
|
||||||
|
message.error('ID is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(`/panel/deck/${id}`, '_blank');
|
||||||
|
}}></Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Button.Group>
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
<NodeProperties />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
2
src/pages/panel/flow/message.ts
Normal file
2
src/pages/panel/flow/message.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
export const emitter = new EventEmitter();
|
103
src/pages/panel/flow/properties/NodeProperties.tsx
Normal file
103
src/pages/panel/flow/properties/NodeProperties.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Panel, useReactFlow, useStore, useStoreApi } from '@xyflow/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
import { Button, Form, Input, message } from 'antd';
|
||||||
|
import { Select } from '@/pages/container/module/Select';
|
||||||
|
import { SaveOutlined } from '@ant-design/icons';
|
||||||
|
import { emitter } from '@abearxiong/container';
|
||||||
|
import { usePanelStore } from '../../store';
|
||||||
|
export const NodeProperties = () => {
|
||||||
|
const reactflow = useReactFlow();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const panelStore = usePanelStore((state) => {
|
||||||
|
return {
|
||||||
|
updateNodeData: state.updateNodeData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const store = useStore((state) => {
|
||||||
|
const setNode = (node: any) => {
|
||||||
|
const newNodes = state.nodes.map((item) => {
|
||||||
|
if (item.id === node.id) {
|
||||||
|
// return { ...item, ...node };
|
||||||
|
return { ...node };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
// console.log('newNodes', newNodes);
|
||||||
|
// state.setNodes(newNodes); // 会丢失数据,因为最终没有调用context的setNodes方法
|
||||||
|
emitter.emit('setNodes', newNodes);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
nodesFocusable: state.nodesFocusable,
|
||||||
|
selected: state.nodes.filter((node) => node.selected),
|
||||||
|
setNode: setNode,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const [nodeData] = store.selected as any[];
|
||||||
|
useEffect(() => {
|
||||||
|
if (!nodeData) return;
|
||||||
|
// console.log('nodeData', nodeData);
|
||||||
|
const { data } = nodeData || {};
|
||||||
|
form.setFieldsValue({
|
||||||
|
cid: data.cid,
|
||||||
|
title: data.title,
|
||||||
|
});
|
||||||
|
}, [nodeData]);
|
||||||
|
const onSave = () => {
|
||||||
|
const values = form.getFieldsValue();
|
||||||
|
// console.log('values', values);
|
||||||
|
const { cid, title } = values;
|
||||||
|
if (!cid) {
|
||||||
|
message.error('请选择容器');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data, id, position, type } = nodeData || {};
|
||||||
|
// console.log('data', data, cid);
|
||||||
|
const newNodeData = {
|
||||||
|
id,
|
||||||
|
position,
|
||||||
|
selected: true,
|
||||||
|
type,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
cid,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// console.log('newNodeData', newNodeData, nodeData);
|
||||||
|
store.setNode(newNodeData);
|
||||||
|
panelStore.updateNodeData(newNodeData);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Panel title='节点属性' position='bottom-center' className='w-full'>
|
||||||
|
<div className={clsx('w-full h-[200px] card', store.selected.length > 0 ? '' : 'hidden')}>
|
||||||
|
<div className='card-title'>
|
||||||
|
{nodeData?.data?.label}
|
||||||
|
<Button.Group className='ml-2'>
|
||||||
|
<Button onClick={onSave} icon={<SaveOutlined />}></Button>
|
||||||
|
</Button.Group>
|
||||||
|
</div>
|
||||||
|
<div className='p-4'>
|
||||||
|
<Form form={form}>
|
||||||
|
<Form.Item label='名称' name='cid'>
|
||||||
|
<Select
|
||||||
|
onChange={(e, options) => {
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
} else {
|
||||||
|
const title = options?.label;
|
||||||
|
form.setFieldsValue({ title });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label='标题' name='title'>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
@ -3,16 +3,10 @@ import { Outlet } from 'react-router';
|
|||||||
export const Main = () => {
|
export const Main = () => {
|
||||||
return (
|
return (
|
||||||
<div className='flex w-full h-full flex-col bg-gray-200'>
|
<div className='flex w-full h-full flex-col bg-gray-200'>
|
||||||
<div className='h-12 bg-white p-2 mb-2'>Deck And Flow</div>
|
<div className='layout-menu'>Deck And Flow</div>
|
||||||
<div
|
<div className='flex-grow w-full'>
|
||||||
className='flex'
|
<div className='w-full h-full overflow-hidden'>
|
||||||
style={{
|
<Outlet />
|
||||||
height: 'calc(100vh - 4rem)',
|
|
||||||
}}>
|
|
||||||
<div className='flex-grow overflow-hidden mx-2'>
|
|
||||||
<div className='w-full h-full rounded-lg'>
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@ type PanelStore = {
|
|||||||
setData: (data: any) => void;
|
setData: (data: any) => void;
|
||||||
getPanel: (id?: string) => Promise<void>;
|
getPanel: (id?: string) => Promise<void>;
|
||||||
saveNodesEdges: (data: { nodes?: any[]; edges?: any[]; viewport?: any }) => Promise<void>;
|
saveNodesEdges: (data: { nodes?: any[]; edges?: any[]; viewport?: any }) => Promise<void>;
|
||||||
|
updateNodeData: (data: any) => Promise<void>;
|
||||||
};
|
};
|
||||||
export const usePanelStore = create<PanelStore>((set, get) => {
|
export const usePanelStore = create<PanelStore>((set, get) => {
|
||||||
return {
|
return {
|
||||||
@ -78,5 +79,23 @@ export const usePanelStore = create<PanelStore>((set, get) => {
|
|||||||
message.error(res.msg || 'Request failed');
|
message.error(res.msg || 'Request failed');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateNodeData: async (data) => {
|
||||||
|
// const { getList } = get();
|
||||||
|
const { id, data: panelData } = get();
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'page',
|
||||||
|
key: 'updateNode',
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
nodeData: data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('Success');
|
||||||
|
// getList();
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -109,6 +109,7 @@ export const List = () => {
|
|||||||
return {
|
return {
|
||||||
open: state.open,
|
open: state.open,
|
||||||
setOpen: state.setOpen,
|
setOpen: state.setOpen,
|
||||||
|
setData: state.setData,
|
||||||
key: state.key,
|
key: state.key,
|
||||||
setKey: state.setKey,
|
setKey: state.setKey,
|
||||||
sendMsg: state.sendMsg,
|
sendMsg: state.sendMsg,
|
||||||
@ -243,8 +244,9 @@ export const List = () => {
|
|||||||
icon={<CaretRightOutlined />}
|
icon={<CaretRightOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// navicate(`/prompt/${item.id}`);
|
// navicate(`/prompt/${item.id}`);
|
||||||
promptStore.setFormData(item);
|
// promptStore.setFormData(item);
|
||||||
// promptStore.runAi();
|
// promptStore.runAi();
|
||||||
|
aiStore.setData(item);
|
||||||
aiStore.setOpen(true);
|
aiStore.setOpen(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,8 @@ import { message } from 'antd';
|
|||||||
type PromptStore = {
|
type PromptStore = {
|
||||||
showEdit: boolean;
|
showEdit: boolean;
|
||||||
setShowEdit: (showEdit: boolean) => void;
|
setShowEdit: (showEdit: boolean) => void;
|
||||||
|
data: any;
|
||||||
|
setData: (data: any) => void;
|
||||||
formData: any;
|
formData: any;
|
||||||
setFormData: (formData: any) => void;
|
setFormData: (formData: any) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -19,7 +21,13 @@ export const usePromptStore = create<PromptStore>((set, get) => {
|
|||||||
showEdit: false,
|
showEdit: false,
|
||||||
setShowEdit: (showEdit) => set({ showEdit }),
|
setShowEdit: (showEdit) => set({ showEdit }),
|
||||||
formData: {},
|
formData: {},
|
||||||
setFormData: (formData) => set({ formData }),
|
setFormData: (formData) => {
|
||||||
|
set({ formData });
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
setData: (data) => {
|
||||||
|
set({ data });
|
||||||
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
setLoading: (loading) => set({ loading }),
|
setLoading: (loading) => set({ loading }),
|
||||||
list: [],
|
list: [],
|
||||||
|
1
src/utils/index.ts
Normal file
1
src/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './nanoid';
|
9
src/utils/is-null.ts
Normal file
9
src/utils/is-null.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const isObjectNull = (value: any) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (JSON.stringify(value) === '{}') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
5
src/utils/nanoid.ts
Normal file
5
src/utils/nanoid.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { customAlphabet, nanoid } from 'nanoid';
|
||||||
|
const number = '0123456789';
|
||||||
|
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
export const generateId = customAlphabet(number + alphabet, 6);
|
@ -1,10 +1,26 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
darkMode: ['class'],
|
darkMode: ['class'],
|
||||||
content: ['./src/**/*.{ts,tsx}', './node_modules/@abearxiong/flows/**/*.{ts,tsx}'],
|
mod: 'jit',
|
||||||
plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/typography')],
|
content: ['./src/**/*.{ts,tsx}', './node_modules/@abearxiong/flows/**/*.{ts,tsx}', './src/**/*.css'],
|
||||||
|
plugins: [
|
||||||
|
require('@tailwindcss/aspect-ratio'), //
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
require('@tailwindcss/line-clamp'),
|
||||||
|
require('tailwindcss-animate'),
|
||||||
|
require('./plugins/flex'),
|
||||||
|
],
|
||||||
|
safelist: ['layout-menu', 'bg-custom-blue'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族
|
||||||
|
rob: ['Roboto', 'sans-serif'],
|
||||||
|
int: ['Inter', 'sans-serif'],
|
||||||
|
orb: ['Orbitron', 'sans-serif'],
|
||||||
|
din: ['DIN', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
screen: {
|
screen: {
|
||||||
sm: '640px',
|
sm: '640px',
|
||||||
// => @media (min-width: 640px) { ... }
|
// => @media (min-width: 640px) { ... }
|
||||||
@ -26,5 +42,4 @@ export default {
|
|||||||
// => @media (min-width: 2560) { ... }
|
// => @media (min-width: 2560) { ... }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('tailwindcss-animate')],
|
|
||||||
};
|
};
|
||||||
|
2
theme
2
theme
@ -1 +1 @@
|
|||||||
Subproject commit a04e057119db510b36286c8585de97592467e6d4
|
Subproject commit cae4f74f6c93dd2b96cd4c40551ac1be969c2bc2
|
@ -11,7 +11,11 @@ export default defineConfig({
|
|||||||
|
|
||||||
css: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: [nesting, tailwindcss, autoprefixer],
|
plugins: [
|
||||||
|
nesting, // 作用是可以使用@import导入css文件
|
||||||
|
tailwindcss,
|
||||||
|
autoprefixer,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user