This commit is contained in:
zhaofengchao 2024-01-10 17:04:07 +08:00
parent 6f1c5d6ef5
commit e0b6261596
15 changed files with 289 additions and 180 deletions

View file

@ -19,6 +19,7 @@
"umi-request": "^1.4.0"
},
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@umijs/plugins": "^4.1.0",
@ -2557,6 +2558,12 @@
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.202.tgz",
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true
},
"node_modules/@types/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.5.tgz",

View file

@ -22,6 +22,7 @@
"umi-request": "^1.4.0"
},
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@umijs/plugins": "^4.1.0",

View file

@ -29,7 +29,7 @@ const routes = [
name: '知识库',
icon: 'home',
auth: [3, 4, 100],
path: '/knowledge/add',
path: '/knowledge/add/*',
component: '@/pages/add-knowledge',
pathname: 'knowledge',
// routes: [{

BIN
client/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -3,19 +3,19 @@ import { history, Outlet, useLocation, useNavigate } from 'umi';
import { useTranslation, Trans } from 'react-i18next'
import classnames from 'classnames'
import '../locales/config';
import logo from '@/assets/logo.png'
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
RedditOutlined
} from '@ant-design/icons';
import { Layout, Button, theme, Space, } from 'antd';
import styles from './index.less'
import User from './components/user'
import { head } from 'lodash';
const { Header, Content } = Layout;
const App: React.FC = (props) => {
const { t } = useTranslation()
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate()
const {
token: { colorBgContainer, borderRadiusLG },
@ -40,17 +40,9 @@ const App: React.FC = (props) => {
return (
<Layout className={styles.layout} >
<Layout>
<Header style={{ padding: 0, background: colorBgContainer, display: 'flex', justifyContent: 'space-between' }}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{
fontSize: '16px',
width: 64,
height: 64,
}}
/>
<Header style={{ padding: '0 8px', background: colorBgContainer, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<img src={logo} alt="" style={{ height: 30, width: 30 }} />
<Space size={[0, 8]} wrap>
{tagsData.map((item) =>
(<span key={item.name} className={classnames(styles['tag'], {
@ -65,7 +57,7 @@ const App: React.FC = (props) => {
<Content
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,

View file

@ -11,4 +11,14 @@
.operate {
width: 200px;
}
}
.img {
height: 16px;
width: 16px;
margin-right: 6px;
}
.column {
min-width: 200px
}

View file

@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import { Space, Table, Tag, Input, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons'
import { Space, Table, Tag, Input, Button, Switch, Popover, Dropdown, } from 'antd';
import type { MenuProps } from 'antd';
import { PlusOutlined, DownOutlined } from '@ant-design/icons'
import { debounce } from 'lodash';
import type { ColumnsType } from 'antd/es/table';
import styles from './idnex.less'
@ -10,57 +11,10 @@ interface DataType {
name: string;
age: number;
address: string;
tags: string[];
status: boolean;
}
const onChangeStatus = () => { }
const columns: ColumnsType<DataType> = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
render: (text) => <a>{text}</a>,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
render: (_, { tags }) => (
<>
{tags.map((tag) => {
let color = tag.length > 5 ? 'geekblue' : 'green';
if (tag === 'loser') {
color = 'volcano';
}
return (
<Tag color={color} key={tag}>
{tag.toUpperCase()}
</Tag>
);
})}
</>
),
},
{
title: 'Action',
key: 'action',
render: (_, record) => (
<Space size="middle">
<a>Invite {record.name}</a>
<a>Delete</a>
</Space>
),
},
];
const data: DataType[] = [
{
@ -68,21 +22,62 @@ const data: DataType[] = [
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
status: true,
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
status: true,
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sydney No. 1 Lake Park',
tags: ['cool', 'teacher'],
status: true,
},
{
key: '4',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
status: true,
},
{
key: '5',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
status: true,
},
{
key: '6',
name: 'Joe Black',
age: 32,
address: 'Sydney No. 1 Lake Park',
status: true,
}, {
key: '7',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
status: true,
},
{
key: '8',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
status: true,
},
{
key: '9',
name: 'Joe Black',
age: 32,
address: 'Sydney No. 1 Lake Park',
status: true,
},
];
@ -92,7 +87,6 @@ const App: React.FC = () => {
const changeValue = (value: string) => {
{
console.log(value)
setLoading(false)
}
}
@ -105,22 +99,88 @@ const App: React.FC = () => {
debounceCallback(e.target.value)
}
const actionItems: MenuProps['items'] = [
{
key: '1',
label: (
<div>
<Button type="link"></Button>
</div>
),
},
{
key: '2',
label: (
<div>
<Button type="link"> </Button>
</div>
),
// disabled: true,
},
];
const columns: ColumnsType<DataType> = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
render: (text) => <a><img className={styles.img} src='https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg' alt="" />{text}</a>,
className: `${styles.column}`
},
{
title: '数据总量',
dataIndex: 'total',
key: 'total',
className: `${styles.column}`
},
{
title: 'Tokens',
dataIndex: 'tokens',
key: 'tokens',
className: `${styles.column}`
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
className: `${styles.column}`,
render: (_, { status }) => (
<>
<Switch defaultChecked onChange={onChangeStatus} />
</>
),
},
{
title: 'Action',
key: 'action',
className: `${styles.column}`,
render: (_, record) => (
<Space size="middle">
<Dropdown menu={{ items: actionItems }}>
<a>
<DownOutlined />
</a>
</Dropdown>
</Space>
),
},
];
return <>
<div className={styles.filter}>
<div className="search">
<Input placeholder="搜索" value={inputValue} allowClear onChange={handleInputChange} />
</div>
<div className="operate">
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => { }}
>
</Button>
<Dropdown menu={{ items: actionItems }}>
<a>
<DownOutlined />
</a>
</Dropdown>
</div>
</div>
<Table columns={columns} dataSource={data} loading={loading} />
<Table columns={columns} dataSource={data} loading={loading} pagination={false} scroll={{ scrollToFirstRowOnChange: true, x: true }} />
</>
};

View file

@ -48,7 +48,7 @@ const App: React.FC = () => {
{...layout}
name="nest-messages"
onFinish={onFinish}
style={{ maxWidth: 600 }}
style={{ maxWidth: 1000, padding: 14 }}
validateMessages={validateMessages}
>
<Form.Item name={['user', 'name']} label="知识库名称" rules={[{ required: true }]}>

View file

@ -1,7 +1,19 @@
.tab {
:global {
.ant-tabs-tabpane {
margin: 20px;
.container {
display: flex;
.menu {
.defaultWidth {
width: 256px;
}
.minWidth {
width: 50px
}
}
.content {
flex: 1;
overflow-x: auto;
height: 100%;
}
}

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import type { RadioChangeEvent } from 'antd';
import { Radio, Space, Tabs } from 'antd';
import React, { useMemo, useState, useEffect } from 'react';
import type { MenuProps } from 'antd';
import { Radio, Space, Tabs, Menu } from 'antd';
import {
ToolOutlined,
BarsOutlined,
@ -10,40 +10,78 @@ import File from './components/knowledge-file'
import Setting from './components/knowledge-setting'
import Search from './components/knowledge-search'
import styles from './index.less'
import { getWidth } from '@/utils'
const App: React.FC = () => {
type keyType = 'setting' | 'file' | 'search'
const [activeKey, setActiveKey] = useState<keyType>('file')
// type tab = { label: string, icon: Element, tag: string }
const tabs = [{ label: '配置', icon: <ToolOutlined />, tag: 'setting' }, { label: '知识库', icon: <BarsOutlined />, tag: 'file' }, { label: '搜索测试', icon: <SearchOutlined />, tag: 'search' }]
const [activeKey, setActiveKey] = useState<string>('file')
const [collapsed, setCollapsed] = useState(false);
const [windowWidth, setWindowWidth] = useState(getWidth());
const onTabClick = (activeKey: keyType) => {
setActiveKey(activeKey)
// 标记一下
useEffect(() => {
const widthSize = () => {
const width = getWidth()
console.log(width)
setWindowWidth(width);
};
window.addEventListener("resize", widthSize);
return () => {
window.removeEventListener("resize", widthSize);
};
}, []);
useEffect(() => {
if (windowWidth.width > 957) {
setCollapsed(false)
} else {
setCollapsed(true)
}
}, [windowWidth.width])
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
} as MenuItem;
}
// type stringKey = Record<string, Element>
const mapComponent = {
file: <File />,
setting: <Setting />,
search: <Search />
const items: MenuItem[] = [
getItem('配置', 'setting', <ToolOutlined />),
getItem('知识库', 'file', <BarsOutlined />),
getItem('搜索测试', 'search', <SearchOutlined />),
];
const handleSelect: MenuProps['onSelect'] = (e) => {
setActiveKey(e.key)
}
return (
<>
<Tabs
tabPosition='left'
activeKey={activeKey}
onTabClick={(activeKey: keyType, e: KeyboardEvent<Element> | MouseEvent<Element, MouseEvent>) => { onTabClick(activeKey) }}
className={styles.tab}
items={tabs.map((item) => {
return {
label: item.label,
icon: item.icon,
key: item.tag,
children: mapComponent[activeKey] as Element,
};
})}
/>
<div className={styles.container}>
<div className={styles.menu}>
<Menu
selectedKeys={[activeKey]}
mode="inline"
className={windowWidth.width > 957 ? styles.defaultWidth : styles.minWidth}
inlineCollapsed={collapsed}
items={items}
onSelect={handleSelect}
/>
</div>
<div className={styles.content}>
{activeKey === 'file' && <File />}
{activeKey === 'setting' && <Setting />}
{activeKey === 'search' && <Search />}
</div>
</div>
</>
);
};

View file

@ -1,3 +1,7 @@
.knowledge {
padding: 24px;
}
.container {
height: 100px;
display: flex;
@ -28,6 +32,8 @@
padding: 10px;
margin: 0;
}
margin-bottom: 10px;
}

View file

@ -1,6 +1,6 @@
import React, { useState, } from 'react';
import { useNavigate } from 'umi'
import { Card, List, Popconfirm, message, FloatButton } from 'antd';
import { Card, List, Popconfirm, message, FloatButton, Row, Col } from 'antd';
import { MinusSquareOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import styles from './index.less'
@ -41,47 +41,52 @@ const App: React.FC = () => {
};
const handleAddKnowledge = () => {
// setDatas((datas) => [...datas, ...dd])
navigate('add');
navigate('add/file');
}
return (<><FloatButton onClick={handleAddKnowledge} icon={<PlusOutlined />} type="primary" style={{ right: 24, top: 100 }} />
<List
grid={{ gutter: 16, column: 4 }}
dataSource={datas}
renderItem={(item, index) => (
<List.Item>
<Card className={styles.card}>
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
content
</span>
<span className={styles.delete}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={() => { confirm(index) }}
okText="Yes"
cancelText="No"
>
<DeleteOutlined />
</Popconfirm>
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />{item.text}
</span>
<span className={styles.text}>
<MinusSquareOutlined />{item.des}
</span>
</div>
return (<>
<div className={styles.knowledge}>
<FloatButton onClick={handleAddKnowledge} icon={<PlusOutlined />} type="primary" style={{ right: 24, top: 100 }} />
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
{
data.map((item, index) => {
return (<Col className="gutter-row" key={item.title} xs={24} sm={12} md={8} lg={6}>
<Card className={styles.card}>
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
content
</span>
<span className={styles.delete}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={() => { confirm(index) }}
okText="Yes"
cancelText="No"
>
<DeleteOutlined />
</Popconfirm>
</div>
</Card>
</List.Item>
)}
/></>
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />{item.text}
</span>
<span className={styles.text}>
<MinusSquareOutlined />{item.des}
</span>
</div>
</div>
</Card>
</Col>)
})
}
</Row>
</div>
</>
)
};

View file

@ -1,5 +1,4 @@
import { message } from 'antd';
import store from '@/utils/persistStore';
import { addParam } from '@/utils';
import loginService from '@/services/loginService';

View file

@ -2,32 +2,7 @@ import { debounce } from 'lodash';
import semver from 'semver';
import merge from 'lodash/merge';
import isPlainObject from 'lodash/isPlainObject';
/**
* localStorage存储管理器
*
* :
* lazy: boolean localstoragelocalstorage
* namespace: string localstorage的keymanager时区分
* version: string 'x.x.x'localstorage
*
* 使:
* , redux里的state
* const store = {
* token: '',
* };
*
* manager对象, version便于标明版本号便
* const manager = new StorageManager(store, { version: '0.0.0' });
*
* localstorage, 使debounce减少io操作
* manager.token = '123456'
* manager.token = '123'
* manager.token = '456'
*
* localstorage中的值
* console.log(manager.token)
*
*/
class StorageManager {
constructor(store, options) {
options = Object.assign(

View file

@ -147,9 +147,13 @@ export const formatRequestUrlByDomainPrefix = url => {
}
return `${prefix}${url}`;
};
export const getWidth = () => {
return { width: window.innerWidth };
};
export default {
parseQuery,
downloadWithIframe,
formatRequestUrlByDomainPrefix
formatRequestUrlByDomainPrefix,
getWidth
};