提交 81f68bb5 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 824af8e1
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
VITE_API_URL_WORD = https://zijingebook.ezijing.com/file/
# VITE_API_URL_WORD = http://ebook-pc.ezijing.com:7419
VITE_API_BASE_API_PREFFIX = /api
VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com
VITE_API_OPENAI_URL = https://model-platform-skyagents.tiangong.cn
\ No newline at end of file
差异被折叠。
......@@ -19,15 +19,16 @@
"prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\""
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/icons": "^5.3.7",
"@fortaine/fetch-event-source": "^3.0.6",
"@reduxjs/toolkit": "^1.9.7",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-react": "^1.0.6",
"@wangeditor/plugin-link-card": "^1.0.0",
"ali-oss": "^6.20.0",
"antd": "^5.17.0",
"antd": "^5.18.0",
"axios": "^1.6.2",
"dayjs": "^1.11.10",
"dayjs": "^1.11.11",
"easy-formula-editor": "^0.0.2-alpha.1",
"echarts": "^5.4.3",
"highlight.js": "^11.9.0",
......@@ -38,6 +39,7 @@
"rc-slider-captcha": "^1.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-redux": "^8.1.3",
"react-router-dom": "^6.18.0",
"redux-persist": "^6.0.0",
......@@ -45,18 +47,18 @@
"xml-formatter": "^3.6.2"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@umijs/fabric": "^4.0.1",
"@umijs/lint": "^4.0.88",
"@vitejs/plugin-react-swc": "^3.6.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"eslint": "^8.53.0",
"husky": "^8.0.3",
"less": "^4.2.0",
"lint-staged": "^15.1.0",
"prettier": "^3.1.0",
"stylelint-config-standard": "^34.0.0",
"vite": "^5.2.11",
"vite": "^5.2.12",
"vite-plugin-mkcert": "^1.17.5"
},
"browserslist": [
......
import { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Button } from 'antd';
import './App.less';
import { GetroutesDyamic } from '@/routes/index';
import { ErrorBoundary } from '@/common/errorBoundary';
import { ErrorFallback } from '@/common/errorBoundary/ErrorFallback';
import MakeError from '@/common/errorBoundary/error/MakeError';
function fallbackRender({ error, resetErrorBoundary }) {
return (
<div role='alert'>
<p>出错啦:</p>
<pre style={{ color: 'red' }}>{error.message}</pre>
<Button type='link' onClick={resetErrorBoundary}>
Try again
</Button>
</div>
);
}
function App() {
const [token, setToken] = useState('');
......@@ -17,25 +27,8 @@ function App() {
setToken('');
}
}, []);
const [hasError, setHasError] = useState(false);
const onError = (error) => {
// 日志上報
console.log(error);
setHasError(true);
};
const onReset = () => {
console.log('尝试恢复错误');
setHasError(false);
};
return (
<ErrorBoundary
fallbackRender={(fallbackProps) => <ErrorFallback {...fallbackProps} />}
onError={onError}
onReset={onReset}
>
<ErrorBoundary fallbackRender={fallbackRender}>
<GetroutesDyamic />
</ErrorBoundary>
);
......
import axios from '@/utils/axios';
import fetchEventSource from '@/utils/fetchEventSource';
// 流式编辑接口
export function aiEdit(options) {
return fetchEventSource('/api/ai/sky3/edit', options);
}
// 流式对话接口
export function aiChat(data) {
return axios.post('/api/ai/sky3/chat', data);
}
// 文本生成图片接口
export function aiGenerateImage(data) {
return axios.post('/api/ai/sky3/generateImage', data);
}
import {Dropdown,Space} from 'antd';
import { DownOutlined } from '@ant-design/icons';
const DropDownCom=(props)=>{
const {items,defaultText=""}=props
return (
<div>
<Dropdown menu={{
items,
}}>
<a onClick={(e) => e.preventDefault()}>
<Space>
{defaultText}
<DownOutlined />
</Space>
</a>
</Dropdown>
</div>
)
}
export default DropDownCom;
\ No newline at end of file
import React from 'react';
import { Button } from 'antd';
/**
* 出错后现时的组件
* @param error
* @param resetErrorBoundary
* @constructor
*/
export const ErrorFallback = ({ error, resetErrorBoundary }) => {
return (
<div role='alert'>
<p>出错啦</p>
<pre>{error.message}</pre>
<Button type='link' onClick={resetErrorBoundary}>
Try again
</Button>
</div>
);
};
import { useEffect, useState } from 'react';
import { useErrorHandler } from '../ErrorBoundary';
const AsyncError = () => {
const handleError = useErrorHandler();
const [number, setNumber] = useState(0);
const randomlyFetchData = async () => {
return Math.random();
};
useEffect(() => {
randomlyFetchData()
.then((number) => {
if (number > 0.5) {
throw new Error('async 大于 0.5');
} else {
setNumber(number);
}
})
.catch(handleError);
}, []);
return <div>{number}</div>;
};
export default AsyncError;
import { useEffect } from 'react';
const MakeError = () => {
useEffect(() => {
const number = Math.random();
if (number > 0.5) {
throw new Error('大于0.5');
}
}, []);
return <div />;
};
export default MakeError;
import React, { useState } from 'react';
import MakeError from '../error/MakeError';
import { ErrorFallback } from '../ErrorFallback';
import { ErrorBoundary } from '../index';
const FallbackComponentExample = () => {
const [hasError, setHasError] = useState(false);
const onError = (error) => {
// 日志上報
console.log(error);
setHasError(true);
};
const onReset = () => {
console.log('尝试恢复错误');
setHasError(false);
};
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onError={onError} onReset={onReset}>
{!hasError ? <MakeError /> : null}
</ErrorBoundary>
);
};
export default FallbackComponentExample;
import React, { useState } from 'react';
import MakeError from '../error/MakeError';
import { ErrorBoundary } from '../index';
const FallbackExample = () => {
const [hasError, setHasError] = useState(false);
const onError = (error) => {
// 日志上報
console.log(error);
setHasError(true);
};
const onReset = () => {
console.log('尝试恢复错误');
setHasError(false);
};
return (
<ErrorBoundary fallback={<div>出错啦</div>} onError={onError} onReset={onReset}>
{!hasError ? <MakeError /> : null}
</ErrorBoundary>
);
};
export default FallbackExample;
import React, { useState } from 'react';
import MakeError from '../error/MakeError';
import { ErrorFallback } from '../ErrorFallback';
import { ErrorBoundary } from '../index';
const FallbackRenderExample = () => {
const [hasError, setHasError] = useState(false);
const onError = (error) => {
// 日志上報
console.log(error);
setHasError(true);
};
const onReset = () => {
console.log('尝试恢复错误');
setHasError(false);
};
return (
<ErrorBoundary
fallbackRender={(fallbackProps) => <ErrorFallback {...fallbackProps} />}
onError={onError}
onReset={onReset}
>
{!hasError ? <MakeError /> : null}
</ErrorBoundary>
);
};
export default FallbackRenderExample;
import React, { useState } from 'react';
import MakeError from '../error/MakeError';
import { ErrorFallback } from '../ErrorFallback';
import { ErrorBoundary } from '../index';
const FallbackExample = () => {
const [retry, setRetry] = useState(0);
return (
<div>
<button onClick={() => setRetry(retry + 1)}>retry</button>
<ErrorBoundary FallbackComponent={ErrorFallback} resetKeys={[retry]}>
<MakeError />
</ErrorBoundary>
</div>
);
};
export default FallbackExample;
import React, { useState } from 'react';
import { ErrorFallback } from '../ErrorFallback';
import { ErrorBoundary } from '../index';
import AsyncError from '../error/AsyncError';
const UseErrorHandlerExample = () => {
const [hasError, setHasError] = useState(false);
const onError = (error) => {
// 日志上報
console.log(error);
setHasError(true);
};
const onReset = () => {
console.log('尝试恢复错误');
setHasError(false);
};
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onError={onError} onReset={onReset}>
{!hasError ? <AsyncError /> : null}
</ErrorBoundary>
);
};
export default UseErrorHandlerExample;
import React from 'react';
import MakeError from '../error/MakeError';
import { ErrorFallback } from '../ErrorFallback';
import { withErrorBoundary } from '../index';
const WithErrorBoundaryExample = withErrorBoundary(MakeError, {
FallbackComponent: ErrorFallback,
onError: (error, info) => console.log(error, info),
});
export default WithErrorBoundaryExample;
import React from 'react';
import FallbackComponentexample from '@/common/errorBoundary/example/FallbackComponentexample';
import FallbackRenderexample from '@/common/errorBoundary/example/FallbackRenderexample';
import Fallbackexample from '@/common/errorBoundary/example/Fallbackexample';
import WithErrorBoundaryexample from '@/common/errorBoundary/example/WithErrorBoundaryexample';
import UseErrorHandlerexample from '@/common/errorBoundary/example/UseErrorHandlerexample';
import ResetKeysexample from '@/common/errorBoundary/example/ResetKeysexample';
const ExampleErrorBox = () => {
return (
<div className='App'>
<h2>fallback 例子</h2>
<Fallbackexample />
<h2>FallbackComponent 例子</h2>
<FallbackComponentexample />
<h2>fallbackRender 例子</h2>
<FallbackRenderexample />
<h2>withErrorBoundary 例子</h2>
<WithErrorBoundaryexample />
<h2>useErrorHandler 例子</h2>
<UseErrorHandlerexample />
<h2>ResetKeys 例子</h2>
<ResetKeysexample />
</div>
);
};
export default ExampleErrorBox;
import * as React from 'react';
const changedArray = (a = [], b = []) => {
return a.length !== b.length || a.some((item, index) => !Object.is(item, b[index]));
};
// 初始状态
const initialState = {
error: null,
};
class ErrorBoundary extends React.Component {
state = initialState;
updatedWithError = false;
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, errorInfo) {
if (this.props.onError) {
this.props.onError(error, errorInfo.componentStack);
}
}
componentDidUpdate(prevProps) {
const { error } = this.state;
const { resetKeys, onResetKeysChange } = this.props;
if (error !== null && !this.updatedWithError) {
this.updatedWithError = true;
return;
}
if (error !== null && changedArray(prevProps.resetKeys, resetKeys)) {
if (onResetKeysChange) {
onResetKeysChange(prevProps.resetKeys, resetKeys);
}
this.reset();
}
}
reset = () => {
this.updatedWithError = false;
this.setState(initialState);
};
resetErrorBoundary = () => {
if (this.props.onReset) {
this.props.onReset();
}
this.reset();
};
render() {
const { fallback, FallbackComponent, fallbackRender } = this.props;
const { error } = this.state;
if (error !== null) {
const fallbackProps = {
error,
resetErrorBoundary: this.resetErrorBoundary,
};
if (React.isValidElement(fallback)) {
return fallback;
}
if (typeof fallbackRender === 'function') {
return fallbackRender(fallbackProps);
}
if (FallbackComponent) {
return <FallbackComponent {...fallbackProps} />;
}
throw new Error(
'ErrorBoundary 组件需要传入 fallback, fallbackRender, FallbackComponent 其中一个',
);
}
return this.props.children;
}
}
/**
* with 写法
* @param Component 业务组件
* @param errorBoundaryProps error boundary 的 props
*/
function withErrorBoundary(Component, errorBoundaryProps) {
const Wrapped = (props) => {
return (
<ErrorBoundary {...errorBoundaryProps}>
<Component {...props} />
</ErrorBoundary>
);
};
// DevTools 显示的组件名
const name = Component.displayName || Component.name || 'Unknown';
Wrapped.displayName = `withErrorBoundary(${name})`;
return Wrapped;
}
/**
* 自定义错误的 handler
* @param givenError
*/
function useErrorHandler(givenError) {
const [error, setError] = React.useState(null);
if (givenError) throw givenError;
if (error) throw error;
return setError;
}
export { ErrorBoundary, withErrorBoundary, useErrorHandler };
import React from 'react';
const setIcon = (props) => {
const { width = 14, alt = '', src = '' } = props;
return <img src={src} alt={alt} width={width} />;
};
export default setIcon;
差异被折叠。
import React from 'react';
import { createFromIconfontCN } from '@ant-design/icons';
import '@/common/iconfont/iconfont.js';
const IconFont = createFromIconfontCN({
scriptUrl: [],
});
const CreateIcon = (props) => {
const { type, style = { color: "#000", fontSize: 16 } } = props;
return <IconFont type={`icon-${type}`} style={style} />
}
export default CreateIcon;
\ No newline at end of file
import React from 'react';
const NotFound = () => {
return <div>404 Not Found</div>;
};
export default NotFound;
import React from 'react';
import { Button, Result } from 'antd';
import { useNavigate } from 'react-router-dom'
const NotFound = () => {
const navigate = useNavigate();
const backHome = () => {
navigate('/');
}
return (
<Result
status="error"
title="NO Permission"
subTitle="没有权限"
extra={<Button type="primary" onClick={ backHome }>回首页</Button>}
/>
);
};
export default NotFound;
......@@ -15,7 +15,6 @@ const auth_key = 'f3846153ba784b6d86bdcd5533259c88';
const auth_secret = 'HO4IyLEwEOHpeOXBxaLQUOqWslJRGs1M';
const AIDrawerComponent = (props) => {
const { selectText } = props;
const dispatch = useDispatch();
const { userInfo } = useSelector((state) => state.user); // 用户状态信息
......@@ -25,14 +24,14 @@ const AIDrawerComponent = (props) => {
const [loading, setLoading] = useState(false); // loading
const [chatId, setChatId] = useState(null); // 对话的id
const [chatContentList, setChatContentList] = useState([]); // 对话数据
const element = document.querySelector("#chat-content");
const element = document.querySelector('#chat-content');
let str = '';
useEffect(() => {
if (editorAi && Object.entries(editorAi).length > 0) {
let tempJSON = JSON.parse(JSON.stringify(chatContentList));
let conversationId = editorAi.conversationId;
const item = tempJSON.filter(item => item.conversationId === conversationId);
const item = tempJSON.filter((item) => item.conversationId === conversationId);
if (item && item.length > 0) {
tempJSON[tempJSON.length - 1] = editorAi;
} else {
......@@ -44,7 +43,7 @@ const AIDrawerComponent = (props) => {
} else {
setLoading(false);
}
}, [editorAi])
}, [editorAi]);
useEffect(() => {
if (selectText) {
......@@ -55,7 +54,7 @@ const AIDrawerComponent = (props) => {
setValue('');
setChatContentList([]);
setChatId(null);
}
};
}, []);
let cData = [];
......@@ -72,7 +71,7 @@ const AIDrawerComponent = (props) => {
processBuffer(buffer);
return;
}
await sleep(80)
await sleep(80);
buffer += decoder.decode(value, { stream: true });
// 检查缓冲区中是否有完整的消息
const messages = buffer.split('\n\n'); // 假设每个消息以换行符结束
......@@ -84,7 +83,7 @@ const AIDrawerComponent = (props) => {
buffer = messages[messages.length - 1]; // 更新缓冲区
readBuffer(reader, decoder);
});
}
};
const processBuffer = (text) => {
let aiContent = {};
......@@ -100,15 +99,13 @@ const AIDrawerComponent = (props) => {
chatId: itemContent.chatId,
avatar: itemContent.role.avatar,
conversationId: itemContent.conversationId,
}
};
dispatch(setEditorAi(aiContent));
}
}
} catch {
} catch {}
};
}
}
// 提交对话
const targetChat = async () => {
if (value) {
......@@ -116,39 +113,41 @@ const AIDrawerComponent = (props) => {
const timestamp = Date.now();
fetch('/openapi/agent/chat/stream/v1', {
method: 'POST',
responseType: 'stream', // 设置响应类型为 'stream'
headers: {
'Content-Type': 'application/json',
authKey: auth_key,
timestamp: timestamp,
sign: md5(`${auth_key}${auth_secret}${timestamp}`),
'Content-Type': 'application/json',
"stream": true // or change to "false" 不处理流式返回内容
},
body: JSON.stringify({
agentId: UUID,
chatId: chatId,
userChatInput: value,
}),
mode: 'cors',
}).then(response => {
method: 'POST',
responseType: 'stream', // 设置响应类型为 'stream'
headers: {
'Content-Type': 'application/json',
authKey: auth_key,
timestamp: timestamp,
sign: md5(`${auth_key}${auth_secret}${timestamp}`),
stream: true, // or change to "false" 不处理流式返回内容
},
body: JSON.stringify({
agentId: UUID,
chatId: chatId,
userChatInput: value,
}),
mode: 'cors',
})
.then((response) => {
// response.body 是一个 ReadableStream 对象
const stream = response.body;
// 使用流式数据
const reader = stream.getReader();
const decoder = new TextDecoder(); // 用于解码二进制数据流
let userContent = {
type: 'user', content: value, time: Date.now()
let userContent = {
type: 'user',
content: value,
time: Date.now(),
};
let tempJSON = JSON.parse(JSON.stringify(chatContentList));
tempJSON.push(userContent)
tempJSON.push(userContent);
setChatContentList(tempJSON);
// 开始读取流数据
readBuffer(reader, decoder);
}).catch(error => {
})
.catch((error) => {
// 处理请求错误
console.error('Request failed:', error);
});
......@@ -156,12 +155,11 @@ const AIDrawerComponent = (props) => {
message.error('请输入对话内容!');
}
};
return (
<div className='ai-drawer-content'>
<div className='ai-chat-container'>
<div className='chat-content' id="chat-content">
<div className='chat-content' id='chat-content'>
<div className='chat-content-padd'>
{chatContentList &&
chatContentList.length > 0 &&
......@@ -175,7 +173,7 @@ const AIDrawerComponent = (props) => {
<div className='inside'>
<div className='ai-in-content'>
<div className='img'>
<img src={item.avatar} alt="" />
<img src={item.avatar} alt='' />
</div>
<div className='ask-content'>{item.content}</div>
</div>
......
......@@ -21,7 +21,7 @@ class ExpandArticleAuto {
return false; // or true
}
isDisabled(editor) {
return true; // or true
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
......
// Extend menu
class PolishingAuto {
constructor() {
this.title = '润色';
this.title = '缩写';
this.iconSvg = `<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-536, -221)" fill="#333333" fill-rule="nonzero">
<g id="润色-(1)" transform="translate(536, 221)">
<g id="缩写-(1)" transform="translate(536, 221)">
<path d="M1.95471575,0 L44.9584623,0 C46.2616061,0 46.913178,0.678713837 46.913178,2.03614151 L46.913178,2.03614151 C46.913178,3.39356919 46.2616061,4.07228302 44.9584623,4.07228302 L1.95471575,4.07228302 C0.651571917,4.07228302 0,3.39356919 0,2.03614151 L0,2.03614151 C0,0.678713837 0.651571917,0 1.95471575,0 Z" id="路径"></path>
<path d="M1.95471575,12.2168491 L29.3207363,12.2168491 C30.6238801,12.2168491 31.275452,12.8955629 31.275452,14.2529906 L31.275452,14.2529906 C31.275452,15.6104183 30.6238801,16.2891321 29.3207363,16.2891321 L1.95471575,16.2891321 C0.651571917,16.2891321 0,15.6104183 0,14.2529906 L0,14.2529906 C0,12.8955629 0.651571917,12.2168491 1.95471575,12.2168491 Z" id="路径"></path>
<path d="M1.95471575,24.4336981 L17.5924418,24.4336981 C18.8955856,24.4336981 19.5471575,25.112412 19.5471575,26.4698397 L19.5471575,26.4698397 C19.5471575,27.8272673 18.8955856,28.5059812 17.5924418,28.5059812 L1.95471575,28.5059812 C0.651571917,28.5059812 0,27.8272673 0,26.4698397 L0,26.4698397 C0,25.112412 0.651571917,24.4336981 1.95471575,24.4336981 Z" id="路径"></path>
......@@ -18,13 +18,13 @@ class PolishingAuto {
this.tag = 'button';
}
getValue(editor) {
return 'hello, 润色';
return 'hello, 缩写';
}
isActive(editor) {
return false; // or true
}
isDisabled(editor) {
return true; // or true
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
......
......@@ -3,7 +3,6 @@ class RewriteAuto {
constructor() {
this.title = '改写';
this.iconSvg = `<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-731, -218)" fill="#333333" fill-rule="nonzero">
<g id="文稿改写" transform="translate(730.8462, 217.8462)">
......@@ -22,7 +21,7 @@ class RewriteAuto {
return false; // or true
}
isDisabled(editor) {
return true; // or true
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
......
......@@ -23,7 +23,7 @@ class SummaryAuto {
return false; // or true
}
isDisabled(editor) {
return true; // or true
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
......
import { useEffect, useState } from 'react';
import { Modal, Input, Button, Flex, Spin } from 'antd';
import { useAIEdit } from '@/hooks/useAI';
const { TextArea } = Input;
const actionMap = {
rewrite: '改写',
expand: '扩写',
abbreviate: '缩写',
summary: '总结',
};
export default function AIWrite({ editor, docAction, ...rest }) {
const [content, setContent] = useState('');
const { text, fetch, isLoading } = useAIEdit();
const actionText = actionMap[docAction];
const [selectionText, setSelectionText] = useState('');
useEffect(() => {
if (rest.open) {
const selection = editor.getSelectionText();
if (selection) {
setSelectionText(selection);
setContent(selection);
fetch({ content: selection, doc_action: docAction, full_text: false });
}
}
}, [rest.open]);
useEffect(() => {
setContent(text);
}, [text]);
const handleFetch = () => {
fetch({ content: selectionText, doc_action: docAction, full_text: false });
};
const handlePrimary = () => {
editor.restoreSelection();
editor.insertText(text);
rest.onCancel();
};
return (
<Modal
title={`以下是AI${actionText}结果:`}
{...rest}
footer={null}
classNames={{
header: 'editor-header-customer',
body: 'editor-body-customer',
wrapper: 'editor-wrapper-customer',
}}
>
<Spin spinning={isLoading}>
<TextArea
autoSize={{ minRows: 4 }}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</Spin>
<br />
<Flex gap='small' justify='center'>
<Button type='primary' onClick={handlePrimary}>
替换内容
</Button>
<Button type='primary' onClick={handleFetch}>
重新{actionText}
</Button>
<Button type='primary' onClick={rest.onCancel}>
取消
</Button>
</Flex>
</Modal>
);
}
......@@ -53,6 +53,7 @@ import TooltipModal from './components/tooltip';
import LinkModal from './components/link';
import ExpandModal from './components/expand';
import AIDrawerComponent from './ai-drawer/index';
import AIWrite from './components/aiWrite';
import chapterSectionModule from './node/chapterItem';
import chapterHeaderModule from './node/chapterTitle';
......@@ -90,7 +91,7 @@ const menuArr0 = ['重做', '撤销'];
const menuArr1 = ['字体样式', '字号', '行高'];
const menuArr2 = ['图片', '画廊', '视频', '音频', '表格'];
const menuArr3 = ['代码块', '引用', '链接', '公式', '章头', '节头', '交互练习', '气泡', '扩展阅读'];
const menuArr4 = ['润色', '扩写', '改写', '总结'];
const menuArr4 = ['改写', '扩写', '缩写', '总结'];
const colorList = ['#ab1941', '#2970f6', '#2ad882', '#eb3351'];
const bookBucketName = 'zxts-book-file';
......@@ -394,9 +395,9 @@ const WangEditorCustomer = forwardRef((props, ref) => {
'Practice',
'TooltipAuto',
'ExpandRead',
'PolishingAuto',
'ExpandArticleAuto',
'RewriteAuto',
'ExpandArticleAuto',
'PolishingAuto',
'SummaryAuto',
],
};
......@@ -433,7 +434,7 @@ const WangEditorCustomer = forwardRef((props, ref) => {
'color',
'bgColor',
'clearStyle',
'AISelectTextAuto'
'AISelectTextAuto',
],
},
image: {
......@@ -491,7 +492,6 @@ const WangEditorCustomer = forwardRef((props, ref) => {
setTimeout(() => {
const toolbarElement = toolbarRef.current && toolbarRef.current.children[0].children[0];
const allChildren = toolbarElement.children;
const oHDiv_1 = document.createElement('div');
oHDiv_1.setAttribute('class', 'custom-bar-box two');
toolbarElement.insertBefore(oHDiv_1, allChildren[0]);
......@@ -502,7 +502,6 @@ const WangEditorCustomer = forwardRef((props, ref) => {
oH6_1.setAttribute('class', 'w-auto type-heading');
oH6_1.innerHTML = '常用格式';
toolbarElement.insertBefore(oH6_1, allChildren[2]);
// 行高字体字号设置
const itemBox = '<div class="custom-bar-box two"></div>';
$(itemBox).insertBefore($(allChildren[3]));
......@@ -737,6 +736,39 @@ const WangEditorCustomer = forwardRef((props, ref) => {
}
setExpandVisible(true);
});
// 改写
editor.on('RewriteMenuClick', () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setAiWriteOpen(true);
setAiWriteAction('rewrite');
});
// 扩写
editor.on('ExpandArticleMenuClick', () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setAiWriteOpen(true);
setAiWriteAction('expand');
});
// 缩写
editor.on('PolishingMenuClick', () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setAiWriteOpen(true);
setAiWriteAction('abbreviate');
});
// 总结
editor.on('SummaryMenuClick', () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setAiWriteOpen(true);
setAiWriteAction('summary');
});
// ai对话
editor.on('AISelectTextClick', () => {
setSelectText(editor.getSelectionText());
......@@ -880,6 +912,8 @@ const WangEditorCustomer = forwardRef((props, ref) => {
});
};
const [aiWriteOpen, setAiWriteOpen] = useState(false);
const [aiWriteAction, setAiWriteAction] = useState('rewrite');
return (
<div className='wangeditor-customer-container'>
<div className='editor-content-container'>
......@@ -909,12 +943,20 @@ const WangEditorCustomer = forwardRef((props, ref) => {
>
保存
</Button>
<Button icon={<EyeOutlined />} className='history' onClick={previewIt}
disabled={bookId && contentId ? false : true}>
<Button
icon={<EyeOutlined />}
className='history'
onClick={previewIt}
disabled={bookId && contentId ? false : true}
>
预览
</Button>
<Button icon={<HistoryOutlined />} className='history' onClick={historyIt}
disabled={bookId && contentId ? false : true}>
<Button
icon={<HistoryOutlined />}
className='history'
onClick={historyIt}
disabled={bookId && contentId ? false : true}
>
历史
</Button>
</Space>
......@@ -1299,17 +1341,25 @@ const WangEditorCustomer = forwardRef((props, ref) => {
</Modal>
{/* ai对话 */}
<Drawer
<Drawer
open={aiVisible}
width="600px"
title="AI对话"
width='600px'
title='AI对话'
destroyOnClose
onClose={() => setAIVisible(false)}
rootClassName="ai-drawer-wrapper"
className="ai-drawer-container"
rootClassName='ai-drawer-wrapper'
className='ai-drawer-container'
>
<AIDrawerComponent setAIVisible={setAIVisible} selectText={selectText} />
</Drawer>
<AIWrite
open={aiWriteOpen}
docAction={aiWriteAction}
onCancel={() => setAiWriteOpen(false)}
editor={editor}
chapterId={chapterId}
bookId={bookId}
></AIWrite>
</div>
);
});
......
import { useRef, useState, useEffect } from 'react';
import { useRef, useState } from 'react';
const useWebsocket = ({}) => {
const ws = useRef();
......@@ -6,7 +6,7 @@ const useWebsocket = ({}) => {
const [wsMessage, setWsMessage] = useState({});
const [wsReadyState, setWsReadyState] = useState({ key: 0, value: '正在连接中' });
const creatWebSocket = (url) => {
const createWebSocket = (url) => {
const stateArr = [
{ key: 0, value: '正在连接中' },
{ key: 1, value: '已经连接并且可以通讯' },
......@@ -17,15 +17,15 @@ const useWebsocket = ({}) => {
try {
ws.current = new WebSocket(url);
ws.current.onopen = () => {
console.log('webcocket open');
console.log('websocket open');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onclose = () => {
console.log('webcocket close');
console.log('websocket close');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onerror = () => {
console.log('webcocket error');
console.log('websocket error');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onmessage = (e) => {
......@@ -39,7 +39,7 @@ const useWebsocket = ({}) => {
const webSocketInit = (url) => {
if (!ws.current || ws.current.readyState === 3) {
creatWebSocket(url);
createWebSocket(url);
}
};
......@@ -58,19 +58,19 @@ const useWebsocket = ({}) => {
try {
closeWebSocket();
ws.current = null;
creatWebSocket();
createWebSocket();
} catch (e) {
console.log(e);
}
};
// useEffect(() => {
// webSocketInit();
// useEffect(() => {
// webSocketInit();
// return () => {
// ws.current?.close(1000);
// };
// }, [ws]);
// return () => {
// ws.current?.close(1000);
// };
// }, [ws]);
return {
webSocketInit,
......@@ -83,4 +83,4 @@ const useWebsocket = ({}) => {
};
};
export default useWebsocket;
\ No newline at end of file
export default useWebsocket;
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import React, { useState, useEffect } from 'react';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
function MyEditor() {
// editor 实例
const [editor, setEditor] = useState(null); // JS 语法
// 编辑器内容
const [html, setHtml] = useState('<p>hello</p>');
// 模拟 ajax 请求,异步设置 html
useEffect(() => {
setTimeout(() => {
setHtml('<p>hello world</p>');
}, 1500);
}, []);
// 工具栏配置
const toolbarConfig = {};
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...',
};
useEffect(() => {
return () => {
if (editor === null) return;
editor.destroy();
setEditor(null);
};
}, [editor]);
return (
<>
<div style={{ border: '1px solid #ccc', zIndex: 100 }}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode='default'
style={{ borderBottom: '1px solid #ccc' }}
/>
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={setEditor}
onChange={(editor) => setHtml(editor.getHtml())}
mode='default'
style={{ height: '500px', overflowY: 'hidden' }}
/>
</div>
<div style={{ marginTop: '15px' }}>{html}</div>
</>
);
}
export default MyEditor;
import { useState } from 'react';
import { aiEdit, aiChat, aiGenerateImage } from '@/api/ai';
export function useAIEdit() {
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const latestMessage = messages[messages.length - 1] || {};
const { text = '' } = latestMessage;
const fetch = async (params) => {
setIsLoading(true);
try {
const res = await aiEdit({
body: JSON.stringify({ params }),
onmessage: (res) => {
let message = JSON.parse(res.data);
message = { ...message.data, request_id: message.request_id };
setMessages((prevMessages) => {
const messageIndex = prevMessages.findIndex((m) => m.request_id === message.request_id);
if (messageIndex === -1) {
return [...prevMessages, message];
} else {
return prevMessages.map((m) => (m.request_id === message.request_id ? message : m));
}
});
},
});
} catch (error) {
console.error('Error fetching AI edit:', error);
} finally {
setIsLoading(false);
}
};
return { messages, text, setMessages, fetch, isLoading };
}
export function useAIChat() {
const [messages, setMessages] = useState([]);
const fetch = async (params) => {
const res = await aiChat({
body: JSON.stringify({ params }),
onmessage: (res) => {
const message = JSON.parse(res.data);
console.log(message);
setMessages((messages) => [...messages, message]);
},
});
};
}
export function useAIGenerateImage() {
const [messages, setMessages] = useState([]);
const fetch = async (params) => {
const res = await aiGenerateImage(params);
setMessages(res);
};
}
import { useEffect, useState } from 'react';
import { fetchEventSource } from '@fortaine/fetch-event-source';
function getToken() {
return window.localStorage.getItem('kiwi.token') || '';
}
const getHeaders = () => {
const token = getToken();
const appId = 'TzEU5jPk2tu80266';
const appSecret = '0a006048a4480481b18fef1405120b83';
const timestamp = Math.floor(Date.now() / 1000);
return {
Authorization: token,
AppId: appId,
AppSecret: appSecret,
Timestamp: timestamp,
'Content-Type': 'application/json',
};
};
const defaultOnOpen = async (response) => {
if (!response.ok) {
throw response;
}
return response;
};
const defaultOnMessage = (setMessages) => (res) => {
const message = JSON.parse(res.data);
setMessages((prevMessages) => [...prevMessages, message]);
};
export function useFetchEventSource(url, options = {}) {
const [isLoading, setLoading] = useState(false);
const [messages, setMessages] = useState([]);
const [error, setError] = useState(null);
const fetch = () => {
setLoading(true);
const headers = getHeaders();
const defaultOptions = {
method: 'POST',
headers,
onopen: defaultOnOpen,
onmessage: defaultOnMessage(setMessages),
onerror: (err) => setError(err),
};
fetchEventSource(url, { ...defaultOptions, ...options }).finally(() => setLoading(false));
};
useEffect(() => {
fetch();
}, [url, options]);
return { isLoading, error, messages };
}
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
import React, { createContext, useContext, useState } from 'react';
const GlobalContext = createContext();
export const useGlobalContext = () => useContext(GlobalContext);
export const GlobalProvider = ({ children }) => {
const [unreadMessagesNum, setUnreadMessagesNum] = useState(0);
return (
<GlobalContext.Provider value={{ unreadMessagesNum, setUnreadMessagesNum }}>
{children}
</GlobalContext.Provider>
);
};
import React, { useState, useEffect } from 'react';
import { Space, Breadcrumb, Button, Row, Col, Menu } from 'antd';
import { Button, Row, Col, Menu } from 'antd';
import { useLocation, useNavigate, Link } from 'react-router-dom';
import { getRouteList, getRouteItem } from '@/utils/breadCrumb';
import { useSelector } from 'react-redux';
import { LeftOutlined } from '@ant-design/icons';
import IconFont from '@/common/iconfont';
import UserMenu from './menu';
const BreadCrumbMenu = () => {
const { pathname } = useLocation();
const location = useLocation();
const navigate = useNavigate();
const [breadcrumbList, setBreadcrumbList] = useState([]);
......@@ -38,7 +34,6 @@ const BreadCrumbMenu = () => {
menuTemp.push({
label: transLinkMenu(cItem),
key: cItem.path,
icon: cItem.icon ? <IconFont type={cItem.icon} /> : '',
});
});
setBreadcrumbList(menuTemp);
......@@ -47,50 +42,6 @@ const BreadCrumbMenu = () => {
}
}, [location.pathname]);
// useEffect(() => {
// const routeList = getRouteList(
// [],
// getRouteItem(baseRootRouter, pathname) ? [getRouteItem(baseRootRouter, pathname)] : [],
// pathname,
// );
// if (routeList.length === 0) {
// setBreadcrumbList([
// { path: '/', name: '首页', key: '/', isSubMenu: false },
// { path: '404', name: 'Not Found', key: '/404', isSubMenu: false },
// ]);
// } else {
// setBreadcrumbList([...routeList]);
// }
// }, [pathname]);
// const linkTo = (path) => {
// navigate(path);
// };
// const treeDataToArray = (source) => {
// const res = [];
// // 第二种方式
// source.forEach((item) => {
// res.push(item);
// item.children && res.push(...treeDataToArray(item.children));
// });
// return res;
// };
useEffect(() => {
// const flatRoutes = treeDataToArray(rootRouter);
// const routeMeta = flatRoutes.find((item) => item.path === pathname);
// if (routeMeta.back == true) {
// setIsShowBack(true);
// } else {
// setIsShowBack(false);
// }
// if (arr.includes(location.pathname)) {
// return setisShowMenu(false);
// }
// setisShowMenu(true);
}, [pathname]);
useEffect(() => {
if (
window.location.pathname === '/books/management/add-edit' ||
......
......@@ -4,8 +4,6 @@ import { Space, Menu } from 'antd';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { findTreeElementByKey } from '@/utils/common.js';
import IconFont from '@/common/iconfont';
const UserMenu = (props) => {
const { flag, breadmenu, menuList } = props;
const location = useLocation();
......@@ -28,7 +26,6 @@ const UserMenu = (props) => {
menuItem = {
title: route.meta.title,
key: route.path,
icon: route.icon ? <IconFont type={route.icon} /> : null,
};
if (route[children] && route[children].length > 0) {
const childrenItems = generateMenuItems(route.children);
......@@ -74,7 +71,6 @@ const UserMenu = (props) => {
const newItem = {
label: depth === 1 ? transLinkMenu(item, true) : transLinkMenu(item),
key: item.path,
icon: item.icon ? <IconFont type={route.icon} /> : null,
};
if (depth < 2 && item.children.length) {
newItem.children = toAntdMenus(item.children, depth + 1);
......
......@@ -11,8 +11,6 @@ import point1 from '../../assets/images/point1.png';
import quit from '../../assets/images/quit.png';
import slide from '../../assets/images/slide.png';
import { useGlobalContext } from '../GlobalContext';
const items = [
{
key: 'userinfo',
......@@ -35,8 +33,7 @@ const UserInfo = ({ props, extraSlot }) => {
const [page, setPage] = useState(1);
const [page_size, setpage_size] = useState(10);
const [drawerScrolled, setDrawerScrolled] = useState(false);
// const [unreadMessagesNum, setUnreadMessagesNum] = useState(0);
const { unreadMessagesNum, setUnreadMessagesNum } = useGlobalContext();
const [unreadMessagesNum, setUnreadMessagesNum] = useState(0);
const heartbeatInterval = useRef(null);
const [isReadSet, setIsReadSet] = useState(false);
const {
......@@ -50,11 +47,10 @@ const UserInfo = ({ props, extraSlot }) => {
} = msgWebsocket({});
const [messageListLoaded, setMessageListLoaded] = useState(false); // 新增状态来表示消息列表是否已加载
// const [userInfos]
// 推出登录
const signOut = () => {
localStorage.clear();
navigator('/user/login');
navigator('/login');
};
const getInfo = async () => {
const data = await getUserInfo();
......@@ -106,20 +102,20 @@ const UserInfo = ({ props, extraSlot }) => {
}, [showDrawer, unreadMessagesNum, isReadSet]);
// 加载消息的websocket
useEffect(() => {
if (userInfos.id > 0) {
const wsUrl = `${import.meta.env.VITE_API_WEBSOCKET_URL}/ws`;
// 建立websokect链接
webSocketInit(wsUrl);
// 获取websocket消息
if (Object.entries(wsMessage).length > 0) {
console.log('收到消息了');
console.log(wsMessage);
// 收到消息一个未读消息
setUnreadMessagesNum(unreadMessagesNum + 1);
}
}
}, [userInfos, wsMessage, wsReadyState]);
// useEffect(() => {
// if (userInfos.id > 0) {
// const wsUrl = `${import.meta.env.VITE_API_WEBSOCKET_URL}/ws`;
// // 建立websokect链接
// webSocketInit(wsUrl);
// // 获取websocket消息
// if (Object.entries(wsMessage).length > 0) {
// console.log('收到消息了');
// console.log(wsMessage);
// // 收到消息一个未读消息
// setUnreadMessagesNum(unreadMessagesNum + 1);
// }
// }
// }, [userInfos, wsMessage, wsReadyState]);
// 重连websocket
// useEffect(() => {
......@@ -482,7 +478,6 @@ const UserInfo = ({ props, extraSlot }) => {
marginTop: '10px',
}}
>
{' '}
<Button
style={{
border: 'none',
......
......@@ -72,7 +72,7 @@ const LayoutComponent = () => {
<Header className='layout-header'>
<div className='header-logo'>
<Link to='/'>
<img src={logo} alt='logo' />
<img src={logo} />
</Link>
</div>
<UserMenu menuList={menuList} />
......
......@@ -19,7 +19,7 @@
height: 50px;
line-height: 50px;
background-color: #fff;
padding: 0 25px;
padding: 0 20px;
.ant-menu-overflow {
margin-left: 100px;
......@@ -37,12 +37,6 @@
height: 32px;
vertical-align: middle;
}
span {
vertical-align: middle;
height: 50px;
width: 0;
display: inline-block;
}
}
.header-menu {
......
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
......@@ -16,24 +16,20 @@ import zhCN from 'antd/locale/zh_CN';
import MyApp from './App.jsx';
import { GlobalProvider } from './layout/GlobalContext.jsx';
import 'dayjs/locale/zh-cn';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#ab1941' } }}>
<App>
<Router>
<GlobalProvider>
<MyApp />
</GlobalProvider>
</Router>
</App>
</ConfigProvider>
</PersistGate>
</Provider>
</React.StrictMode>,
// <React.StrictMode>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#ab1941' } }}>
<App>
<BrowserRouter>
<MyApp />
</BrowserRouter>
</App>
</ConfigProvider>
</PersistGate>
</Provider>,
// </React.StrictMode>,
);
......@@ -10,10 +10,8 @@ import userStatus3 from '@/assets/images/user_status3.png';
import defaultUser from '@/assets/images/defaultUser.png';
import { useSelector } from 'react-redux';
import { useGlobalContext } from '../../../layout/GlobalContext';
const AuditDetail = (props) => {
const { setUnreadMessagesNum } = useGlobalContext();
const location = useLocation();
const {
state: { id },
......@@ -23,7 +21,6 @@ const AuditDetail = (props) => {
const [examineList, setExamineList] = useState([]);
const [quanXian, setQuanXian] = useState(true);
// const [unreadMessagesNum, setUnreadMessagesNum] = useState(0);
// 获取操作权限
const { operationPermissionsList } = useSelector((state) => state.user);
......@@ -222,7 +219,6 @@ const AuditDetail = (props) => {
bool = false;
} else {
message.success(msg);
setUnreadMessagesNum(1); // 更新全局状态中的 unreadMessagesNum
}
setLoading(false);
if (!bool) return;
......
import React from 'react';
import { Row, Col } from 'antd';
import { Routes, Route, Navigate, Outlet, Link } from 'react-router-dom';
import Login from './login';
import Reg from './reg';
import { Outlet } from 'react-router-dom';
import './index.less';
import logo from '@/assets/images/logo.png';
import homeBanner from '@/assets/images/home_left_banner.png';
const User = () => {
return (
<div className='user-content'>
<div className='form-container'>
<div className="left-container"></div>
<div className='left-container'></div>
<div className='form-field-box'>
<div className='form-field'>
<Outlet />
......
import React, { useState, useEffect, useRef } from 'react';
import { Form, Input, Button, Row, Col, Space, Image, message } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { CloseOutlined, UserOutlined, LockOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { sendSMS, userNameLogin, phoneLogin, checkSSOLogin } from '../request/request';
import { checkSSOLogin } from '../request/request';
import './index.less';
import SetIcon from '@/common/icon';
import IconUser from '@/assets/images/icon/icon_user.png';
import IconPass from '@/assets/images/icon/icon_password.png';
import loginBtn from '@/assets/images/loginBtn.png';
import loginBtn2 from '@/assets/images/loginBtn2.png';
......@@ -65,18 +62,18 @@ const Login = () => {
const timer = useRef();
const getSSOInfo = async () => {
const isSSOData = await checkSSOLogin();
const isSSOData = await checkSSOLogin();
if (isSSOData.run === 'login') {
} else if (isSSOData.token) {
localStorage.setItem('kiwi.token', isSSOData.token);
navigate('/');
return;
}
}
};
useEffect(() => {
getSSOInfo();
}, [])
}, []);
// 登录
const toLayout = async (obj) => {
......@@ -88,13 +85,13 @@ const Login = () => {
code: code,
disLf: disLf,
});
if (data.code === 200) {
localStorage.setItem('kiwi.token', data.token);
navigate('/');
return;
} else {
form.setFields([{ name: 'password', errors: [data.msg] }])
form.setFields([{ name: 'password', errors: [data.msg] }]);
}
} else {
message.error('请先完成按钮验证');
......@@ -223,20 +220,15 @@ const Login = () => {
>
<Input
autoComplete='off'
prefix={<SetIcon src={IconUser} />}
prefix={<UserOutlined />}
size='large'
placeholder='请输入手机号'
/>
</Form.Item>
<Form.Item
name='password'
rules={[
{ required: true, message: '请输入密码!' },
]}
>
<Form.Item name='password' rules={[{ required: true, message: '请输入密码!' }]}>
<Input.Password
autoComplete='off'
prefix={<SetIcon src={IconPass} />}
prefix={<LockOutlined />}
size='large'
placeholder='请输入密码'
/>
......
import React from 'react';
const Reg = () => {
return <div> Reg </div>;
};
export default Reg;
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Upload, Space, Modal, Row, Col, Drawer, message } from 'antd';
import { Form, Input, Button, Upload, Space, Row, Col, Drawer, message } from 'antd';
import users from '@/assets/images/icon/userinfo.png';
import { changePass, changeInfo, getUserInfo } from '@/layout/request';
import { uploadFiles } from '@/utils/upload';
import AliOSS from 'ali-oss';
import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
......@@ -180,20 +179,21 @@ const UserInfo = () => {
<div style={{ padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 2 }} onFinish={submitForm}>
<Form.Item label='头像' className='uploadPar' name='pic'>
<img
src={imageUrl ? imageUrl : users}
style={{ borderRadius: '50%' }}
width={80}
height={80}
id='avatar'
/>
<div>
<Upload {...props} valuePropName='file'>
<Button className='upload'>点击上传</Button>
</Upload>
<span className='desc' style={{ marginLeft: 19, color: '#999', fontSize: '14px' }}>
尺寸:只能上传jpg/png文件,且不超过1M
</span>
<img
src={imageUrl ? imageUrl : users}
style={{ borderRadius: '50%' }}
width={80}
height={80}
/>
<div>
<Upload {...props} valuePropName='file'>
<Button className='upload'>点击上传</Button>
</Upload>
<span className='desc' style={{ marginLeft: 19, color: '#999', fontSize: '14px' }}>
尺寸:只能上传jpg/png文件,且不超过1M
</span>
</div>
</div>
</Form.Item>
<Form.Item
......@@ -248,13 +248,14 @@ const UserInfo = () => {
rules={[
{ required: true, message: '请输入新密码' },
{
pattern: /^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
'密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!',
trigger: ['change', 'blur']
trigger: ['change', 'blur'],
},
{
validator (_, value) {
validator(_, value) {
if (!value || form2.getFieldValue('re_password') === value) {
form2.setFields([{ name: 're_password', errors: [''] }]);
return Promise.resolve();
......@@ -273,13 +274,14 @@ const UserInfo = () => {
rules={[
{ required: true, message: '请确认新密码' },
{
pattern: /^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
'密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!',
trigger: ['change', 'blur']
trigger: ['change', 'blur'],
},
{
validator (_, value) {
validator(_, value) {
if (!value || form2.getFieldValue('password') === value) {
return Promise.resolve();
}
......
import { lazy } from 'react';
import { Navigate } from 'react-router-dom';
import { lazyLoad, authComponent } from './lazyLoadAndDelay'; // 添加一个固定的延迟时间,以便你可以看到加载状态
import Layout from '@/layout/index';
import UserModule from '@/pages/user-module';
import UserLogin from '@/pages/user-module/login';
import UserInfo from '@/pages/user-module/userInfo';
import NotFound from '@/common/notFound';
import notPermission from '@/common/notPermission';
const baseRouter = [
{
path: '/',
key: 'layout',
element: authComponent(Layout),
meta: {
title: '控制台',
},
children: [
{ path: '/userinfo', element: lazyLoad(UserInfo) },
// { path: '*', element: lazyLoad(notPermission) },
{
path: '/userinfo',
Component: lazy(() => import('@/pages/user-module/userInfo')),
},
],
},
{
path: '/user',
key: 'user',
path: '/login',
element: lazyLoad(UserModule),
hidden: true,
meta: {
title: '用户',
},
// Component: lazy(() => import('@/pages/user-module')),
children: [
{
index: true,
element: <Navigate to='/user/login' />,
},
{
path: '/user/login',
element: lazyLoad(UserLogin),
Component: lazy(() => import('@/pages/user-module/login')),
},
],
},
{
path: '/404',
key: '404',
hidden: true,
element: lazyLoad(NotFound),
meta: {},
},
// { path: '*', hidden: true, element: <Navigate to='/404' /> },
{ path: '*', element: <Navigate to='/' /> },
];
export default baseRouter;
import { useRoutes, Navigate } from 'react-router-dom';
import { lazyLoad } from './lazyLoadAndDelay'; // 添加一个固定的延迟时间,以便你可以看到加载状态
import Layout from '@/layout/index';
import UserModule from '@/pages/user-module';
import UserLogin from '@/pages/user-module/login';
import AuthGroups from '@/pages/setting';
import UserInfo from '@/pages/user-module/userInfo';
import BookModule from '@/pages/books'
import BookClassify from '@/pages/books/classify'
import BookAudit from '@/pages/books/audit/index.jsx'
import AuditTable from '@/pages/books/audit/table.jsx'
import AuditDetail from '@/pages/books/audit/detail.jsx';
import BookLabel from '@/pages/books/label'
// import BookAuditDetail from '@/pages/books/auditDetail'
import BookManagement from '@/pages/books/management/index.jsx'
import BookManagementList from '@/pages/books/management/list'
import BookAddEdit from '@/pages/books/management/addedit';
import BookSale from '@/pages/books/sale';
import SaleDetail from '@/pages/books/sale/edit.jsx';
import SaleTable from '@/pages/books/sale/table.jsx';
import DiscussList from '@/pages/books/sale/discussList';
import DiscussDetail from '@/pages/books/sale/discussDetail';
import BookSection from '@/pages/books/section'
import OrderManagement from '@/pages/books/order-management'
import OrderTable from '@/pages/books/order-management/tab/table.jsx'
import OrderSalesStatistics from '@/pages/books/order-management/tab/salesStatistics';
import User from '@/pages/member'
import UserHome from '@/pages/member/list/list.jsx'
import UserList from '@/pages/member/list'
import Userlevel from '@/pages/member/userlevel';
import UserDetail from '@/pages/member/detail'
import CouponRecord from '@/pages/member/detail/CouponRecord';
import IntegralRecord from '@/pages/member/detail/IntegralRecord';
import OrderRecord from '@/pages/member/detail/orderRecord';
import AdvertisingModule from '@/pages/advertisement'
import Advert from '@/pages/advertisement/Advert'
import Adsense from '@/pages/advertisement/adsense';
import Jurisdiction from '@/pages/jurisdiction';
import Administrator from '@/pages/jurisdiction/admin';
import RoleList from '@/pages/jurisdiction/role';
import QuestionBank from '@/pages/books/question-bank'
import Applied from '@/pages/setting/applied'
import Personalized from '@/pages/setting/personalized-setting'
import Point from '@/pages/setting/point-management';
import AppVersion from '@/pages/setting/app-version';
import Coupon from '@/pages/setting/coupon';
import Sensitive from '@/pages/setting/sensitive';
import FeedBack from '@/pages/setting/feedback'
import Help from '@/pages/setting/help';
import HelpAddEdit from '@/pages/setting/help/addedit/index'
import HelpTable from '@/pages/setting/help/table.jsx';
import Discuss from '@/pages/books/discussion'
import TeacherModule from '@/pages/teacher';
import NotFound from '@/common/notFound'; //公共
import { getMenuList } from '@/common/request';
const authComponent = (Com) => {
const token = localStorage.getItem('kiwi.token') || true;
return token ? lazyLoad(Com) : <Navigate to='/user' />;
};
const GetRoutes = () => {
const [dynamicRoutes, setDynamicRoutes] = useState(null);
useEffect(() => {
const fetchData = async () => {
// 获取菜单列表
const menuData = await getMenuList();
// 处理菜单数据,生成动态路由配置
const dynamicRoutes = generateDynamicRoutes(menuData);
// 设置动态路由
setDynamicRoutes(dynamicRoutes);
};
fetchData();
}, []);
// 如果动态路由尚未加载完成,可以返回一个加载状态或其他内容
if (!dynamicRoutes) {
return <div>Loading...</div>;
}
// 使用动态路由
const routes = useRoutes(dynamicRoutes);
return routes;
};
// 生成动态路由的函数,根据菜单数据生成路由配置
const generateDynamicRoutes = (menuData) => {
// 这里根据你的菜单数据结构进行处理,生成动态路由配置
// 返回一个类似于 rootRouter 的结构,但基于动态数据
// 示例:假设菜单数据的格式为 [{ path: '/dynamic', title: '动态菜单', children: [...] }]
const dynamicRoutes = menuData.map((menuItem) => {
return {
path: menuItem.path,
key: menuItem.key, // 确保每个路由有唯一的 key
element: lazyLoad(getComponentByType(menuItem.type)), // 根据类型获取组件,你可能需要自己实现这个函数
meta: {
title: menuItem.title,
},
children: generateDynamicRoutes(menuItem.children), // 递归处理子菜单
};
});
return dynamicRoutes;
};
// 根据菜单项的类型返回对应的组件,你可能需要根据实际情况自行实现这个函数
const getComponentByType = (type) => {
// 根据类型返回对应的组件
// 例如:假设你的菜单项有 type 字段,根据不同的 type 返回不同的组件
switch (type) {
case 'book':
return BookModule;
case 'user':
return User;
// 其他类型的组件处理...
default:
return NotFound; // 默认返回 NotFound 组件
}
};
export default GetRoutes;
import { useEffect } from 'react';
import { useRoutes, Navigate } from 'react-router-dom';
import { filterAsyncRouter } from './lazyLoadAndDelay';
import { filterAsyncRouter } from './lazyLoadAndDelay';
import { useSelector } from 'react-redux';
import baseRouter from './baseRouter';
const addDefaultNavigation = (menuRoutes) => {
menuRoutes.forEach(item => {
menuRoutes.forEach((item) => {
const children = item.children;
if (children && children.length > 0) {
......@@ -15,7 +15,7 @@ const addDefaultNavigation = (menuRoutes) => {
// 在 children 数组的开头添加默认导航项
children.unshift({
index: true,
element: <Navigate to={children[0].path} />
element: <Navigate to={children[0].path} />,
});
}
});
......@@ -24,8 +24,7 @@ const addDefaultNavigation = (menuRoutes) => {
const GetroutesDyamic = () => {
// 合并路由
const { menuRouter } = useSelector(state => state.user);
// console.log(menuRouter);
const { menuRouter } = useSelector((state) => state.user);
useEffect(() => {
if (menuRouter?.length) {
const menuRoutes = filterAsyncRouter(menuRouter);
......@@ -33,21 +32,14 @@ const GetroutesDyamic = () => {
const newMenus = addDefaultNavigation(menuRoutes);
const firstObj = {
index: true,
element: <Navigate to={newMenus[0].path} />
}
element: <Navigate to={newMenus[0].path} />,
};
baseRouter[0].children = [firstObj, ...newMenus, ...hasRoute];
}
}, [menuRouter]);
const element = useRoutes(baseRouter);
return (
<>
{element}
</>
)
}
return <>{element}</>;
};
export {
GetroutesDyamic
}
export { GetroutesDyamic };
......@@ -47,7 +47,7 @@ const filterAsyncRouter = (menuData) => {
const authComponent = (Com) => {
const token = localStorage.getItem('kiwi.token') || true;
return token ? lazyLoad(Com) : <Navigate to='/user' />;
return token ? lazyLoad(Com) : <Navigate to='/login' />;
};
export { lazyLoad, lazyLoadModule, delayForDemo, authComponent, filterAsyncRouter };
......@@ -12,7 +12,7 @@ function showLoginModal() {
title: '登录状态已过期,请重新登录',
onOk() {
localStorage.removeItem('kiwi.gpt.token');
location.href = '/user/login';
location.href = '/login';
},
});
}
......@@ -71,7 +71,7 @@ httpRequest.interceptors.response.use(
(response) => {
const { data } = response;
if (data.code === 401 || data.code === 403) {
if (location.pathname !== '/user/login') {
if (location.pathname !== '/login') {
showLoginModal();
}
return Promise.reject(data);
......
/* eslint-disable */
// 取消重复请求的数组
const cancelRequestArr = [];
export default cancelRequestArr;
差异被折叠。
import { fetchEventSource } from '@fortaine/fetch-event-source';
function getToken() {
return window.localStorage.getItem('kiwi.token') ? window.localStorage.getItem('kiwi.token') : '';
}
export default async function fetchEventSourceFn(url, options) {
const token = getToken();
// 应用设置
const appId = 'TzEU5jPk2tu80266';
const appSecret = '0a006048a4480481b18fef1405120b83';
const timestamp = Math.floor(Date.now() / 1000);
const defaultHeaders = {
Authorization: token,
AppId: appId,
AppSecret: appSecret,
Timestamp: timestamp,
'Content-Type': 'application/json',
};
const defaultOptions = {
method: 'POST',
headers: { ...defaultHeaders },
async onopen(response) {
if (response.ok) {
return response;
} else {
throw response;
}
},
onmessage(res) {
const message = JSON.parse(res.data);
console.log(res.data);
},
};
await fetchEventSource(url, { ...defaultOptions, ...options });
}
......@@ -3,7 +3,6 @@ import Axios from 'axios';
import { message, Modal, notification } from 'antd';
import md5 from 'js-md5';
import { get } from 'lodash-es';
import cancelRequestArr from './cancelRequestArr';
import { sortObjASCII } from '@/utils/common.js';
import qs from 'qs';
......@@ -14,10 +13,7 @@ let CancelToken = Axios.CancelToken;
// 取消请求 方法
let cancelPending = (config) => {
pending.forEach((item) => {
// cancelRequestArr包含的请求,重复请求 让他取消请求
if (cancelRequestArr.includes(item.url)) {
item.cancel(); // 取消请求
}
});
pending = []; // 清空数组
};
......@@ -121,7 +117,7 @@ axios.interceptors.response.use(
if (code === 401 || code === 403) {
if (!axios.errorNotified) {
axios.errorNotified = true; // 添加标志,表示通知已经弹出
if (pathname === '/user/login') {
if (pathname === '/login') {
return;
}
Modal.warning({
......@@ -129,7 +125,7 @@ axios.interceptors.response.use(
// content: '请重新登录',
onOk() {
localStorage.removeItem('kiwi.gpt.token');
window.location.pathname = '/user/login';
window.location.pathname = '/login';
},
});
cancelPending(response.config); // 取消后续请求
......@@ -154,7 +150,7 @@ axios.interceptors.response.use(
content: '请重新登录',
onOk() {
localStorage.removeItem('kiwi.gpt.token');
window.location.href = '/user/login';
window.location.href = '/login';
},
});
}
......@@ -169,7 +165,7 @@ axios.interceptors.response.use(
content: '请重新登录',
onOk() {
localStorage.removeItem('kiwi.gpt.token');
window.location.href = '/user/login';
window.location.href = '/login';
},
});
}
......
import axios from './request';
import qs from 'qs';
import { get } from 'lodash-es';
import { message } from 'antd';
// 统一上传
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论