李富豪 9 kuukautta sitten
vanhempi
commit
a36cfc4691

+ 1 - 1
env/.env.development

@@ -2,4 +2,4 @@
 VITE_ENV = 'development'
 
 # Api地址
-VITE_API_URL = 'http://xia0miduo.gicp.net:8091'
+VITE_API_URL = 'http://192.168.3.27:8091'

+ 17 - 17
src/apis/index.ts

@@ -74,10 +74,11 @@ export type ModifyDocumentApiParams = {
     parse_image: number
 };
 
-export type FetchDialogApiParams = {
+export type FetchChatHistoryListApiParams = Partial<{
+    appId: string,
     pageNumber: number,
     pageSize: number,
-};
+}>;
 
 // Api函数类型
 export type LoginApi = (data: LoginApiParams) => Promise<any>;
@@ -101,9 +102,9 @@ export type FetchDocumentLibListApi = (data: FetchDocumentLibListApiParams) => P
 export type FetchDocumentLibApi = (id: string) => Promise<any>;
 export type ModifyDocumentLibApi = (id: string, data: ModifyDocumentApiParams) => Promise<any>;
 export type DeleteDocumentLibApi = (id: string) => Promise<any>;
-export type FetchApplicationDataLibApi = () => Promise<any>;
-export type FetchDialogListLibApi = (appId: string, data: FetchDialogApiParams) => Promise<any>;
-export type FetchDialogListLibAllApi = (data: FetchDialogApiParams) => Promise<any>;
+export type FetchApplicationListApi = () => Promise<any>;
+export type FetchChatHistoryListApi = (data: FetchChatHistoryListApiParams) => Promise<any>;
+export type ExportChatHistoryApi = (id: string) => Promise<any>;
 
 // 登录
 const loginApi: LoginApi = async (data) => {
@@ -112,7 +113,7 @@ const loginApi: LoginApi = async (data) => {
 
 // 登出
 const logoutApi: LogoutApi = async () => {
-    return api.post('/logout', {});
+    return api.post('/logout');
 };
 
 // 获取应用列表
@@ -211,21 +212,20 @@ const deleteDocumentApi: DeleteDocumentLibApi = async (id) => {
 };
 
 // 获取应用列表
-const fetchApplicationDataApi: FetchApplicationDataLibApi = async () => {
+const fetchApplicationListApi: FetchApplicationListApi = async () => {
     return api.get('bigmodel/api/application/list');
 };
 
-// 获取对话列表
-const fetchDialogListApi: FetchDialogListLibApi = async (appId, data) => {
-    return api.post(`bigmodel/api/dialog/${appId}`, data);
+// 获取聊天记录列表
+const fetchChatHistoryListApi: FetchChatHistoryListApi = async (data) => {
+    return api.post('bigmodel/api/chatHistory/list', data);
 };
 
-// 获取所有应用对话列表
-const fetchDialogAllListApi: FetchDialogListLibAllApi = async (data) => {
-    return api.post(`bigmodel/api/allDialog`, data);
+// 导出聊天记录
+const exportChatHistoryApi: ExportChatHistoryApi = async (id) => {
+    return api.post(`bigmodel/api/dialog/export/${id}`, {}, { responseType: 'blob' });
 };
 
-
 export const apis = {
     login: loginApi,
     logout: logoutApi,
@@ -248,7 +248,7 @@ export const apis = {
     fetchDocumentDetailLibApi: fetchDocumentLibApi,
     modifyDocumentLibApi: modifyDocumentApi,
     deleteDocumentLibApi: deleteDocumentApi,
-    fetchApplicationDataLibApi: fetchApplicationDataApi,
-    fetchDialogListLibApi: fetchDialogListApi,
-    fetchDialogAllListLibApi: fetchDialogAllListApi,
+    fetchApplicationList: fetchApplicationListApi,
+    fetchChatHistoryList: fetchChatHistoryListApi,
+    exportChatHistory: exportChatHistoryApi,
 };

+ 165 - 0
src/pages/dataExport/components/InfoModal.tsx

@@ -0,0 +1,165 @@
+import * as React from 'react';
+import { Modal, Spin, Form, Input, Select, message } from 'antd';
+import { apis, CreateOrModifyUserApiParams } from '@/apis';
+import { regex } from '@/utils';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+
+interface Props {
+    id: string,
+    open: boolean,
+    onClickConfirm: (id: string, data: CreateOrModifyUserApiParams) => Promise<any>,
+    onClickCancel: () => void,
+};
+
+const InfoModal: React.FC<Props> = (props: Props) => {
+    const {
+        id,
+        open,
+        onClickConfirm,
+        onClickCancel
+    } = props;
+
+    const [form] = Form.useForm();
+
+    const [loading, setLoading] = React.useState<boolean>(false);
+
+    const getTitle = () => {
+        if (id) {
+            return '修改用户';
+        } else {
+            return '创建用户';
+        }
+    };
+
+    const fetchDetail = async () => {
+        try {
+            const res = await apis.fetchUserDetail(props.id);
+            form.setFieldsValue(res.data);
+        } catch (error: any) {
+            message.error(error.msg);
+        }
+    };
+
+    const init = async () => {
+        setLoading(true);
+        if (props.id) {
+            await fetchDetail();
+        }
+        setLoading(false);
+    };
+
+    React.useEffect(() => {
+        init();
+    }, []);
+
+    // 检查手机号码
+    const checkPhoneNumber = (rule: any, value: string) => {
+        if (value) {
+            const phoneNumberRegex = new RegExp(regex.phoneNumber);
+            if (phoneNumberRegex.test(value)) {
+                return Promise.resolve();
+            } else {
+                return Promise.reject(new Error('手机号码格式不正确'));
+            }
+        } else {
+            return Promise.reject('手机号码不能为空');
+        }
+    };
+
+    // 检查登录密码
+    const checkPassword = (rule: any, value: string) => {
+        if (value) {
+            const passwordRegex = new RegExp(regex.password);
+            if (passwordRegex.test(value)) {
+                return Promise.resolve();
+            } else {
+                return Promise.reject('登录密码格式不正确');
+            }
+        } else {
+            return Promise.reject('登录密码不能为空');
+        }
+    };
+
+    // 点击确定
+    const handleClickConfirm = () => {
+        form.validateFields().then(async (values) => {
+            const data = { ...values };
+            await onClickConfirm(props.id, data);
+        }).catch((error) => {
+            console.error(error);
+        });
+    };
+
+    return (
+        <Modal
+            width={600}
+            title={getTitle()}
+            destroyOnClose={true}
+            maskClosable={false}
+            centered={true}
+            open={open}
+            onOk={handleClickConfirm}
+            onCancel={onClickCancel}
+        >
+            <div className='infoModal'>
+                <Spin spinning={loading}>
+                    <Form form={form} layout='vertical'>
+                        <FormItem
+                            label='姓名'
+                            name='user_name'
+                            rules={[{ required: true, message: '姓名不能为空', whitespace: true }]}
+                        >
+                            <Input
+                                style={{ width: '100%' }}
+                                placeholder='请输入姓名'
+                            />
+                        </FormItem>
+                        <FormItem
+                            label='手机号码'
+                            name='phone_number'
+                            rules={[{ validator: checkPhoneNumber, required: true }]}
+                        >
+                            <Input
+                                style={{ width: '100%' }}
+                                placeholder='请输入手机号码'
+                                maxLength={11}
+                            />
+                        </FormItem>
+                        <FormItem
+                            label='登录密码'
+                            name='password'
+                            rules={[{ validator: checkPassword, required: true }]}
+                        >
+                            <Input.Password
+                                style={{ width: '100%' }}
+                                placeholder='6-16位字母或数字组合'
+                            />
+                        </FormItem>
+                        <FormItem
+                            label='角色'
+                            name='role'
+                            rules={[{ required: true, message: '角色不能为空' }]}
+                        >
+                            <Select
+                                style={{ width: '100%' }}
+                                placeholder='请选择角色'
+                                allowClear={true}
+                            >
+                                <Option value='USER'>
+                                    用户
+                                </Option>
+                                <Option value='ADMIN'>
+                                    管理员
+                                </Option>
+                            </Select>
+                        </FormItem>
+                    </Form>
+                </Spin>
+            </div>
+        </Modal>
+    );
+};
+
+export default InfoModal;

+ 98 - 0
src/pages/dataExport/components/Search.tsx

@@ -0,0 +1,98 @@
+import * as React from 'react';
+import { Form, Select, Button } from 'antd';
+import { apis } from '@/apis';
+import { Query } from '../types';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+
+interface Props {
+    onClickSearch: (query: Query) => Promise<any>,
+    onClickReset: (query: Query) => Promise<any>,
+};
+
+const Search: React.FC<Props> = (props: Props) => {
+    const {
+        onClickSearch,
+        onClickReset
+    } = props;
+
+    const [form] = Form.useForm();
+
+    type ApplicationList = {
+        label: string,
+        value: string,
+    }[];
+
+    const [applicationList, setApplicationList] = React.useState<ApplicationList>([]);
+
+    const fetchApplicationList = async () => {
+        try {
+            const res = await apis.fetchApplicationList();
+            const list = res.data.map((item: any) => {
+                return {
+                    label: item.name,
+                    value: item.appId,
+                }
+            });
+            setApplicationList(list);
+        } catch (error: any) {
+            console.error(error);
+        }
+    }
+
+    const init = async () => {
+        await fetchApplicationList();
+    };
+
+    React.useEffect(() => {
+        init();
+    }, []);
+
+    // 点击查询
+    const handleClickSearch = async () => {
+        const values = form.getFieldsValue();
+        await onClickSearch(values);
+    };
+
+    // 点击重置
+    const handleClickReset = async () => {
+        form.resetFields();
+        const values = form.getFieldsValue();
+        await onClickReset(values);
+    };
+
+    return (
+        <Form form={form} layout='inline' colon={false}>
+            <FormItem label='应用' name='appId'>
+                <Select
+                    style={{ width: 200 }}
+                    placeholder='全部'
+                    allowClear={true}
+                >
+                    {
+                        applicationList.map((item, index) => {
+                            return <Option value={item.value} key={index}>
+                                {item.label}
+                            </Option>
+                        })
+                    }
+                </Select>
+            </FormItem>
+            <FormItem>
+                <Button
+                    style={{ marginRight: 16 }}
+                    type='primary'
+                    onClick={handleClickSearch}
+                >
+                    查询
+                </Button>
+                <Button onClick={handleClickReset}>
+                    重置
+                </Button>
+            </FormItem>
+        </Form>
+    );
+};
+
+export default Search;

+ 31 - 76
src/pages/dataExport/index.tsx

@@ -1,66 +1,34 @@
 import * as React from 'react';
-import { useParams } from 'react-router-dom';
 import { observer } from 'mobx-react';
-import store from './store';
-import './style.less';
-import { Select, TableColumnsType, Table, Button, TablePaginationConfig } from 'antd';
-import { CloudDownloadOutlined } from '@ant-design/icons';
+import { Table, TableColumnsType, TablePaginationConfig, Modal } from 'antd';
+import { DownloadOutlined } from '@ant-design/icons';
+import Search from './components/Search';
 import dayjs from 'dayjs';
+import store from './store';
 import { Record } from './types';
-import { apis } from '@/apis';
-import { set } from 'mobx';
-import { downloadFile } from '@/utils';
-import api from '@/apis/api';
-
-const { Option } = Select;
+import './style.less';
 
 const DataExport: React.FC = () => {
     const {
         state,
-        init,
+        onClickSearch,
+        onClickReset,
         onChangePagination,
+        onClickDownload,
+        init,
         reset
     } = store;
-
     const {
-        pageLoading,
+        listLoading,
         list,
-        appId,
-        page,
+        page
     } = state;
 
     React.useEffect(() => {
         init();
-        fetchApplicationDataApi();
         return () => reset();
     }, []);
 
-    type AppList = {
-        label: string;
-        value: string,
-    }[];
-
-    const [loading, setLoading] = React.useState<boolean>(false);
-    const [applicationList, setApplicationList] = React.useState<AppList>([]);
-
-    // 获取应用列表
-    const fetchApplicationDataApi = async () => {
-        try {
-            setLoading(true);
-            const res = await apis.fetchApplicationDataLibApi();
-            const list = res.data.map((item: any) => {
-                return {
-                    label: item.name,
-                    value: item.appId,
-                }
-            });
-            setApplicationList(list);
-        } catch (error: any) {
-            setLoading(false);
-            console.error(error);
-        }
-    }
-
     const columns: TableColumnsType<Record> = [
         {
             title: '序号',
@@ -77,6 +45,9 @@ const DataExport: React.FC = () => {
         {
             title: '消息长度',
             dataIndex: 'length',
+            render: (text) => {
+                return text + '条';
+            }
         },
         {
             title: '聊天时间',
@@ -93,23 +64,22 @@ const DataExport: React.FC = () => {
         {
             title: '操作',
             dataIndex: 'operation',
-            width: 150,
+            width: 80,
             fixed: 'right',
             render: (text, record) => {
                 return (
                     <a
-                        style={{ marginRight: 16 }}
-                        onClick={async () => {
-                            try {
-                                const blob = await api.post(`/bigmodel/api/dialog/export/${record.id}`, {}, { responseType: 'blob' });
-                                const fileName = `${record.dialog_name}.xlsx`;
-                                downloadFile(blob, fileName);
-                            } catch (error) {
-                                console.error(error);
-                            }
+                        onClick={() => {
+                            Modal.confirm({
+                                title: '导出',
+                                content: '确定导出Excel吗?',
+                                onOk: async () => {
+                                    await onClickDownload(record.id, record.dialog_name);
+                                }
+                            });
                         }}
                     >
-                        <CloudDownloadOutlined />
+                        <DownloadOutlined />
                     </a >
                 )
             }
@@ -137,32 +107,17 @@ const DataExport: React.FC = () => {
 
     return (
         <div className='dataExport'>
-            <div style={{ display: 'flex', justifyContent: 'space-between', alignContent: 'center' }}>
-                
-                <Select
-                    style={{ width: '20%' }}
-                    placeholder='请选择应用'
-                    allowClear={true}
-                    value={appId}
-                    onChange={(value) => {
-                            init(value); 
-                    }}
-                >
-                    {
-                        applicationList.map((item, index) => {
-                            return <Option value={item.value} key={index}>
-                                {item.label}
-                            </Option>
-                        })
-                    }
-                </Select>
+            <div className='dataExport-search'>
+                <Search
+                    onClickSearch={onClickSearch}
+                    onClickReset={onClickReset}
+                />
             </div>
-
-            <div className='dataExport-content'>
+            <div className='dataExport-table'>
                 <Table
                     scroll={{ x: 'max-content' }}
                     rowKey={(record) => record.id}
-                    loading={pageLoading}
+                    loading={listLoading}
                     columns={columns}
                     dataSource={list}
                     pagination={paginationConfig}

+ 67 - 47
src/pages/dataExport/store.ts

@@ -1,12 +1,14 @@
 import { makeAutoObservable } from 'mobx';
+import { message } from 'antd';
 import { apis } from '@/apis';
+import { downloadFile } from '@/utils';
 import { State, ReadonlyState, StateAction, DataExportStore } from './types';
 
 // 定义状态
 const stateGenerator = (): ReadonlyState => ({
-    pageLoading: false,
+    query: undefined,
+    listLoading: false,
     list: [],
-    appId: '',
     page: {
         pageNumber: 1,
         pageSize: 10,
@@ -17,19 +19,18 @@ const stateGenerator = (): ReadonlyState => ({
 // 修改状态
 const stateActionsGenerator = (state: State): StateAction => {
     return {
-        setPageLoading: (loading) => {
-            state.pageLoading = loading;
+        setQuery: (query) => {
+            state.query = query;
+        },
+        setListLoading: (loading) => {
+            state.listLoading = loading;
         },
         setList: (list) => {
             state.list = list;
         },
-        setAppId: (appId) => {
-            state.appId = appId;
-        },
         setPage: (page) => {
             state.page = page;
         },
-
     };
 };
 
@@ -39,50 +40,63 @@ const useDataExportStore = (): DataExportStore => {
     const actions = stateActionsGenerator(state);
 
     const api = {
-         // 获取应用聊天列表
-         fetchDocumentLibList: async () => {
-            actions.setPageLoading(true);
+        // 获取聊天记录列表
+        fetchChatHistoryList: async () => {
+            actions.setListLoading(true);
             try {
                 const data = {
+                    ...state.query,
                     pageNumber: state.page.pageNumber,
                     pageSize: state.page.pageSize,
                 };
-                const res = await apis.fetchDialogListLibApi(state.appId, data);
+                const res = await apis.fetchChatHistoryList(data);
                 actions.setList(res.rows);
                 actions.setPage({
                     ...state.page,
                     total: res.total,
                 });
-                
             } catch (error: any) {
                 console.error(error);
             } finally {
-                actions.setPageLoading(false);
+                actions.setListLoading(false);
             }
         },
-
-        // 获取所有聊天列表
-        fetchDocumentLibAllList: async () => {
-            actions.setPageLoading(true);
+        // 导出聊天记录
+        exportChatHistory: async (id: string, fileName: string) => {
             try {
-                const data = {
-                    pageNumber: state.page.pageNumber,
-                    pageSize: state.page.pageSize,
-                };
-                const res = await apis.fetchDialogAllListLibApi(data);
-                actions.setList(res.rows);
-                actions.setPage({
-                    ...state.page,
-                    total: res.total,
-                });
+                const blob = await apis.exportChatHistory(id);
+                fileName = `${fileName}.xlsx`;
+                downloadFile(blob, fileName);
+                message.success('导出成功');
             } catch (error: any) {
-                console.error(error);
-            } finally {
-                actions.setPageLoading(false);
+                message.error(error.msg);
             }
         },
     }
 
+    // 点击查询
+    const onClickSearch: DataExportStore['onClickSearch'] = async (query) => {
+        const initialPageNumber = stateGenerator().page.pageNumber;
+
+        actions.setQuery(query);
+        actions.setPage({
+            ...state.page,
+            pageNumber: initialPageNumber,
+        });
+        // 获取聊天记录列表
+        await api.fetchChatHistoryList();
+    }
+
+    // 点击重置
+    const onClickReset: DataExportStore['onClickReset'] = async (query) => {
+        const initialPage = stateGenerator().page;
+
+        actions.setQuery(query);
+        actions.setPage(initialPage);
+        // 获取聊天记录列表
+        await api.fetchChatHistoryList();
+    }
+
     // 更改分页
     const onChangePagination: DataExportStore['onChangePagination'] = async (pageNumber, pageSize) => {
         actions.setPage({
@@ -90,36 +104,42 @@ const useDataExportStore = (): DataExportStore => {
             pageNumber: pageNumber,
             pageSize: pageSize,
         });
-        // 获取知识库列表
-        init();
+        // 获取聊天记录列表
+        await api.fetchChatHistoryList();
+    }
+
+    // 点击导出
+    const onClickDownload: DataExportStore['onClickDownload'] = async (id, name) => {
+        // 导出聊天记录
+        await api.exportChatHistory(id, name);
     }
 
     // 初始渲染
-    const init: DataExportStore['init'] = (appId) => {
-        // 获取知识列表
-        if(appId){
-            actions.setAppId(appId);
-            api.fetchDocumentLibList();
-        }else{
-            api.fetchDocumentLibAllList();
-        }
+    const init: DataExportStore['init'] = async () => {
+        // 获取聊天记录列表
+        await api.fetchChatHistoryList();
     }
 
     // 状态重置
-    const reset = () => {
-        const initialPageLoading = stateGenerator().pageLoading;
+    const reset: DataExportStore['reset'] = () => {
+        const initialQuery = stateGenerator().query;
+        const initialListLoading = stateGenerator().listLoading;
+        const initialList = stateGenerator().list;
         const initialPage = stateGenerator().page;
 
-        actions.setPageLoading(initialPageLoading);
-        actions.setList([]);
-        actions.setAppId('');
+        actions.setQuery(initialQuery);
+        actions.setListLoading(initialListLoading);
+        actions.setList(initialList);
         actions.setPage(initialPage);
     }
 
     return {
         state,
-        init,
+        onClickSearch,
+        onClickReset,
         onChangePagination,
+        onClickDownload,
+        init,
         reset
     };
 };

+ 11 - 10
src/pages/dataExport/style.less

@@ -1,13 +1,14 @@
 .dataExport {
-  width: 100%;
-  height: 100%;
-  background: #FFFFFF;
-  border-radius: @border-radius-base;
-}
+  &-search {
+    padding: 20px 20px 4px;
+    background: #FFFFFF;
+    border-radius: @border-radius-base;
+    margin-bottom: 20px;
+  }
 
-.dataExport-content {
-  width: 100%;
-  background: #FFFFFF;
-  border-radius: @border-radius-base;
-  padding-top: 20px;
+  &-table {
+    padding: 20px;
+    background: #FFFFFF;
+    border-radius: @border-radius-base;
+  }
 }

+ 12 - 13
src/pages/dataExport/types.ts

@@ -1,23 +1,19 @@
-import { set } from "mobx";
+export type Query = Partial<{
+    appId: string,
+}>;
 
 export type Record = {
     id: string,
-    appId: string,
-    knowledgeIdd: string,
-    userId: string,
-    did: string,
     dialog_name: string,
-    dialog_id: string,
-    type: string,
-    content: string,
+    length: number,
     create_time: string
 };
 
 // 定义状态
 export type State = {
-    pageLoading: boolean,
+    query?: Query,
+    listLoading: boolean,
     list: Record[],
-    appId: string,
     page: {
         pageNumber: number,
         pageSize: number,
@@ -30,16 +26,19 @@ export type ReadonlyState = Readonly<State>;
 
 // 修改状态
 export type StateAction = {
-    setPageLoading: (loading: boolean) => void,
+    setQuery: (query: State['query']) => void,
+    setListLoading: (loading: State['listLoading']) => void,
     setList: (list: State['list']) => void,
-    setAppId: (appId: string) => void,
     setPage: (page: State['page']) => void,
 };
 
 // 仓库类型
 export type DataExportStore = {
     state: ReadonlyState,
-    init: (appId?: string) => void,
+    onClickSearch: (query: Query) => Promise<any>,
+    onClickReset: (query: Query) => Promise<any>,
     onChangePagination: (pageNumber: number, pageSize: number) => Promise<any>,
+    onClickDownload: (id: string, name: string) => Promise<any>,
+    init: () => Promise<any>,
     reset: () => void,
 };

+ 2 - 1
src/utils/index.ts

@@ -3,7 +3,8 @@ export const regex = {
     password: /^[a-zA-Z0-9]{6,16}$/,// 密码
 };
 
-export const downloadFile = (blob: any, fileName: string) => {
+// 下载文件
+export const downloadFile = (blob: Blob, fileName: string) => {
     const downUrl = window.URL.createObjectURL(new Blob([blob]));
     const elementA = document.createElement('a');
     elementA.href = downUrl;