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

chore: 修改编辑器AI相关内容

上级 1cc62424
...@@ -584,6 +584,7 @@ ...@@ -584,6 +584,7 @@
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz", "resolved": "https://registry.npmjs.org/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz",
"integrity": "sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==", "integrity": "sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==",
"license": "MIT",
"engines": { "engines": {
"node": ">=16.15" "node": ">=16.15"
} }
......
import { useState, useEffect } from 'react'
import { SendOutlined } from '@ant-design/icons'
import { Input, Button, message } from 'antd'
import md5 from 'js-md5'
import dayjs from 'dayjs'
import { sleep } from '@/utils/common'
import './index.less'
import { useSelector, useDispatch } from 'react-redux'
import { setEditorAi } from '@/store/modules/editor'
import normalAvatar from '@/assets/images/icon-normal-avatar.png'
const UUID = 'f3846153ba784b6d86bdcd5533259c88'
const auth_key = 'f3846153ba784b6d86bdcd5533259c88'
const auth_secret = 'HO4IyLEwEOHpeOXBxaLQUOqWslJRGs1M'
const AIDrawerComponent = (props) => {
const { selectText } = props
const dispatch = useDispatch()
const { userInfo } = useSelector((state) => state.user) // 用户状态信息
const { editorAi } = useSelector((state) => state.editor) // ai信息设置
const [value, setValue] = useState(selectText) // 输入值
const [loading, setLoading] = useState(false) // loading
const [chatId, setChatId] = useState(null) // 对话的id
const [chatContentList, setChatContentList] = useState([]) // 对话数据
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)
if (item && item.length > 0) {
tempJSON[tempJSON.length - 1] = editorAi
} else {
tempJSON.push(editorAi)
}
setChatId(editorAi.chatId)
setChatContentList(tempJSON)
element.scrollTo(0, 999999)
} else {
setLoading(false)
}
}, [editorAi])
useEffect(() => {
if (selectText) {
targetChat()
}
return () => {
setValue('')
setChatContentList([])
setChatId(null)
}
}, [])
let buffer = ''
const readBuffer = (reader, decoder) => {
return reader.read().then(async ({ done, value }) => {
if (done) {
// 处理最后的缓冲数据
setLoading(false)
setValue('')
dispatch(setEditorAi({}))
str = ''
processBuffer(buffer)
return
}
await sleep(80)
buffer += decoder.decode(value, { stream: true })
// 检查缓冲区中是否有完整的消息
const messages = buffer.split('\n\n') // 假设每个消息以换行符结束
messages.forEach((message, index) => {
if (index < messages.length - 1) {
processBuffer(message)
}
})
buffer = messages[messages.length - 1] // 更新缓冲区
readBuffer(reader, decoder)
})
}
const processBuffer = (text) => {
let aiContent = {}
try {
const content = text.replace(/\n+/g, '').split('data:')
if (content instanceof Array) {
const itemContent = JSON.parse(content[1])
if (!(itemContent.finish && itemContent.finish === true)) {
str += itemContent.content
aiContent = {
type: 'ai',
content: str,
chatId: itemContent.chatId,
avatar: itemContent.role.avatar,
conversationId: itemContent.conversationId
}
dispatch(setEditorAi(aiContent))
}
}
} catch {}
}
// 提交对话
const targetChat = async () => {
if (value) {
setLoading(true)
const timestamp = Date.now()
fetch('/api/tiangong/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}`),
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 tempJSON = JSON.parse(JSON.stringify(chatContentList))
tempJSON.push(userContent)
setChatContentList(tempJSON)
// 开始读取流数据
readBuffer(reader, decoder)
})
.catch((error) => {
// 处理请求错误
console.error('Request failed:', error)
})
} else {
message.error('请输入对话内容!')
}
}
return (
<div className="ai-drawer-content">
<div className="ai-chat-container">
<div className="chat-content" id="chat-content">
<div className="chat-content-padd">
{chatContentList &&
chatContentList.length > 0 &&
chatContentList.map((item, index) => {
return (
<div className={`chat-content-item`} key={index}>
{item.type === 'user' && <div className="time">{dayjs(item.time).format('YYYY-MM-DD HH:mm')}</div>}
{item.type === 'ai' && (
<div className="inside">
<div className="ai-in-content">
<div className="img">
<img src={item.avatar} alt="" />
</div>
<div className="ask-content">{item.content}</div>
</div>
</div>
)}
{item.type === 'user' && (
<div className="inside inside-user">
<div className="user-in-content">
<div className="ask-content">{item.content}</div>
<div className="img">
<img src={userInfo.pic ? userInfo.pic : normalAvatar} alt="用户头像" />
</div>
</div>
</div>
)}
</div>
)
})}
</div>
</div>
</div>
<div className="ai-chat-ask">
<div className="text">
<Input.TextArea
value={value}
defaultValue={value}
allowClear
disabled={loading}
onChange={(e) => setValue(e.target.value)}
palceholder="请输入内容"
style={{ height: '80px', resize: 'none' }}></Input.TextArea>
</div>
<div className="button">
<Button type="primary" icon={<SendOutlined />} loading={loading} size="large" onClick={targetChat}></Button>
</div>
</div>
</div>
)
}
export default AIDrawerComponent
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import { import { Modal, Divider, Upload, Input, Space, Button, Form, Spin, message, ColorPicker, Select, InputNumber, Row, Col } from 'antd'
Modal, import { DomEditor, SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
Divider, import { fontFamilyList, fontSizeList } from '../utils/setting'
Upload, import $ from 'jquery'
Input, import './index.less'
Space,
Button,
Form,
Spin,
message,
ColorPicker,
Select,
InputNumber,
Row,
Col,
} from 'antd';
import { DomEditor, SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor';
import { fontFamilyList, fontFizeList } from '../utils/setting';
import $ from 'jquery';
import './index.less';
const ChapterItemModal = forwardRef((props, ref) => { const ChapterItemModal = forwardRef((props, ref) => {
const { editor, setSectionInfo, sectionInfo, setSectionVisible } = props; const { editor, setSectionInfo, sectionInfo, setSectionVisible } = props
const [form] = Form.useForm(); const [form] = Form.useForm()
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false)
// 是否更新的判断 // 是否更新的判断
const [toolStatus, setToolStatus] = useState(false); const [toolStatus, setToolStatus] = useState(false)
const [bgColorValue, setBgColorValue] = useState('#000000'); const [bgColorValue, setBgColorValue] = useState('#000000')
const [textColor, setTextColor] = useState('#ffffff'); const [textColor, setTextColor] = useState('#ffffff')
const [nowRandom, setNowRandom] = useState(null); const [nowRandom, setNowRandom] = useState(null)
const [initValues, setInitValues] = useState({ const [initValues, setInitValues] = useState({
title: '', title: '',
textColor: textColor, textColor: textColor,
...@@ -37,34 +22,34 @@ const ChapterItemModal = forwardRef((props, ref) => { ...@@ -37,34 +22,34 @@ const ChapterItemModal = forwardRef((props, ref) => {
size: 24, size: 24,
bgColor: bgColorValue, bgColor: bgColorValue,
height: 100, height: 100,
family: '黑体', family: '黑体'
}); })
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
setVisible, setVisible
}; }
}); })
useEffect(() => { useEffect(() => {
if (sectionInfo && Object.entries(sectionInfo).length > 0) { if (sectionInfo && Object.entries(sectionInfo).length > 0) {
setInitValues({ ...sectionInfo }); setInitValues({ ...sectionInfo })
form.setFieldsValue({ ...sectionInfo }); form.setFieldsValue({ ...sectionInfo })
setNowRandom(sectionInfo.random); setNowRandom(sectionInfo.random)
setTextColor(sectionInfo.textColor); setTextColor(sectionInfo.textColor)
setBgColorValue(sectionInfo.bgColor); setBgColorValue(sectionInfo.bgColor)
setSectionInfo({}); setSectionInfo({})
} }
}, [sectionInfo]); }, [sectionInfo])
const bgColorChange = (value, hex) => { const bgColorChange = (value, hex) => {
setBgColorValue(hex); setBgColorValue(hex)
form.setFieldsValue({ bgColor: hex }); form.setFieldsValue({ bgColor: hex })
}; }
const textColorChange = (value, hex) => { const textColorChange = (value, hex) => {
setTextColor(hex); setTextColor(hex)
form.setFieldsValue({ textColor: hex }); form.setFieldsValue({ textColor: hex })
}; }
const callback = ({ title, random, bgColor, textColor, align, height, size }) => { const callback = ({ title, random, bgColor, textColor, align, height, size }) => {
form.setFieldsValue({ form.setFieldsValue({
...@@ -73,82 +58,82 @@ const ChapterItemModal = forwardRef((props, ref) => { ...@@ -73,82 +58,82 @@ const ChapterItemModal = forwardRef((props, ref) => {
textColor: textColor, textColor: textColor,
align: align, align: align,
height: height, height: height,
size: size, size: size
}); })
setToolStatus(true); setToolStatus(true)
setNowRandom(random); setNowRandom(random)
}; }
// 获取当前节点的上一个节点 // 获取当前节点的上一个节点
function getPreviousNode(editor) { function getPreviousNode(editor) {
const { selection } = editor; const { selection } = editor
if (!selection) return null; if (!selection) return null
const [start] = SlateEditor.nodes(editor, { at: selection }); const [start] = SlateEditor.nodes(editor, { at: selection })
const previous = SlateEditor.previous(editor, { at: start[1] }); const previous = SlateEditor.previous(editor, { at: start[1] })
if (previous) { if (previous) {
return previous; return previous
} }
return null; return null
} }
const onFinish = (values) => { const onFinish = values => {
editor.restoreSelection(); editor.restoreSelection()
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') { if (node.type === 'paragraph') {
return true; // 匹配 paragraph return true // 匹配 paragraph
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
if (node.children[0].text === '') { if (node.children[0].text === '') {
SlateTransforms.removeNodes(editor); SlateTransforms.removeNodes(editor)
} }
} }
if (nowRandom) { if (nowRandom) {
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterSection') { if (node.type === 'chapterSection') {
return true; // 匹配 chapterHeader return true // 匹配 chapterHeader
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
if (nodeEntries) { if (nodeEntries) {
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
// console.log(node, path); // console.log(node, path);
if (parseInt(node.random) === parseInt(nowRandom)) { if (parseInt(node.random) === parseInt(nowRandom)) {
SlateTransforms.setNodes(editor, { ...node, ...values }, { at: path }); SlateTransforms.setNodes(editor, { ...node, ...values }, { at: path })
form.resetFields(); form.resetFields()
setSectionVisible(false); setSectionVisible(false)
return false; return false
} }
} }
} }
return; return
} }
const html = editor.getHtml(); const html = editor.getHtml()
const parser = new DOMParser(); const parser = new DOMParser()
let random = Math.random().toString(10).substring(2, 10); let random = Math.random().toString(10).substring(2, 10)
const docBody = parser.parseFromString(html, 'text/html'); const docBody = parser.parseFromString(html, 'text/html')
const section = docBody.body.querySelectorAll('.chapter-item-section'); // 节头标签 const section = docBody.body.querySelectorAll('.chapter-item-section') // 节头标签
editor.insertNode({ editor.insertNode({
type: 'chapterSection', type: 'chapterSection',
...@@ -161,103 +146,74 @@ const ChapterItemModal = forwardRef((props, ref) => { ...@@ -161,103 +146,74 @@ const ChapterItemModal = forwardRef((props, ref) => {
size: values.size ? values.size : 20, size: values.size ? values.size : 20,
family: values.family, family: values.family,
callback: callback, callback: callback,
children: [{ text: '' }], children: [{ text: '' }]
}); })
form.resetFields(); form.resetFields()
setSectionVisible(false); setSectionVisible(false)
}; }
return ( return (
<div> <div>
<Divider /> <Divider />
<div className='editor-content-form'> <div className="editor-content-form">
<Form <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initValues}>
layout='vertical' <Form.Item label="节头标题" name="title" rules={[{ required: true, message: '请输入节头标题' }]} extra="最多输入30字">
name='validate_other' <Input maxLength={30} placeholder="" allowClear />
form={form}
onFinish={onFinish}
initialValues={initValues}
>
<Form.Item
label='节头标题'
name='title'
rules={[{ required: true, message: '请输入节头标题' }]}
extra='最多输入30字'
>
<Input maxLength={30} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='背景颜色' label="背景颜色"
name='bgColor' name="bgColor"
className='flex-max' className="flex-max"
rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]} rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]}>
> <Input maxLength={100} placeholder="" allowClear />
<Input maxLength={100} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha format="hex" value={bgColorValue} defaultValue={bgColorValue} onChange={bgColorChange} />
disabledAlpha
format='hex'
value={bgColorValue}
defaultValue={bgColorValue}
onChange={bgColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='文本颜色' label="文本颜色"
name='textColor' name="textColor"
className='flex-max' className="flex-max"
rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]} rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha format="hex" value={textColor} defaultValue={textColor} onChange={textColorChange} />
disabledAlpha
format='hex'
value={textColor}
defaultValue={textColor}
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label='字体' name='family'> <Form.Item label="字体" name="family">
<Select> <Select>
{fontFamilyList.map((item, index) => { {fontFamilyList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item label="文字大小" name="size" rules={[{ required: true, message: '请输入文字大小' }]}>
label='文字大小'
name='size'
rules={[{ required: true, message: '请输入文字大小' }]}
>
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
...@@ -271,37 +227,28 @@ const ChapterItemModal = forwardRef((props, ref) => { ...@@ -271,37 +227,28 @@ const ChapterItemModal = forwardRef((props, ref) => {
/> */} /> */}
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item label="对齐方式" name="align" rules={[{ required: true, message: '请选择对齐方式' }]}>
label='对齐方式'
name='align'
rules={[{ required: true, message: '请选择对齐方式' }]}
>
<Select> <Select>
<Select.Option value='left'>左对齐</Select.Option> <Select.Option value="left">左对齐</Select.Option>
<Select.Option value='center'>居中对齐</Select.Option> <Select.Option value="center">居中对齐</Select.Option>
<Select.Option value='right'>右对齐</Select.Option> <Select.Option value="right">右对齐</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label='高度' name='height'> <Form.Item label="高度" name="height">
<InputNumber <InputNumber controls={false} placeholder="" style={{ width: '100%', textAlign: 'left' }} allowClear />
controls={false}
placeholder=''
style={{ width: '100%', textAlign: 'left' }}
allowClear
/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}></Col> <Col span={12}></Col>
</Row> </Row>
</Form.Item> </Form.Item>
<Form.Item className='editor-form-buttons'> <Form.Item className="editor-form-buttons">
<Space> <Space>
<Button type='default' onClick={() => setSectionVisible(false)}> <Button type="default" onClick={() => setSectionVisible(false)}>
取消 取消
</Button> </Button>
<Button type='primary' htmlType='submit'> <Button type="primary" htmlType="submit">
{nowRandom ? '更新' : '插入'} {nowRandom ? '更新' : '插入'}
</Button> </Button>
</Space> </Space>
...@@ -309,7 +256,7 @@ const ChapterItemModal = forwardRef((props, ref) => { ...@@ -309,7 +256,7 @@ const ChapterItemModal = forwardRef((props, ref) => {
</Form> </Form>
</div> </div>
</div> </div>
); )
}); })
export default ChapterItemModal; export default ChapterItemModal
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import { import { Modal, Divider, Upload, Input, Space, Button, Form, Spin, message, ColorPicker, Select, InputNumber, Row, Col } from 'antd'
Modal, import { CloudUploadOutlined } from '@ant-design/icons'
Divider, import { DomEditor, SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
Upload, import { fontFamilyList, fontSizeList } from '../utils/setting'
Input, import './index.less'
Space, import $ from 'jquery'
Button,
Form,
Spin,
message,
ColorPicker,
Select,
InputNumber,
Row,
Col,
} from 'antd';
import { CloudUploadOutlined } from '@ant-design/icons';
import { DomEditor, SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor';
import { fontFamilyList, fontFizeList } from '../utils/setting';
import './index.less';
import $ from 'jquery';
import { partSize, normalUploader, multipartUploader } from '../utils/upload'; import { partSize, normalUploader, multipartUploader } from '../utils/upload'
const { Dragger } = Upload; const { Dragger } = Upload
const imgUrl2 = const imgUrl2 = 'http://zxts-book-file.zijingebook.com/2024/03/05/image-1709606013683-6k5qmzf75zj.png'
'http://zxts-book-file.zijingebook.com/2024/03/05/image-1709606013683-6k5qmzf75zj.png';
const ImageModal = forwardRef((props, ref) => { const ImageModal = forwardRef((props, ref) => {
const { editor, ossClient, setTitleVisible, titleInfo, setTitleInfo } = props; const { editor, ossClient, setTitleVisible, titleInfo, setTitleInfo } = props
const fileAccept = ['.png', '.jpg', '.jpeg', '.svg']; const fileAccept = ['.png', '.jpg', '.jpeg', '.svg']
const [form] = Form.useForm(); const [form] = Form.useForm()
const [bgColorValue, setBgColorValue] = useState('#000000'); const [bgColorValue, setBgColorValue] = useState('#000000')
const [textColor, setTextColor] = useState('#ffffff'); const [textColor, setTextColor] = useState('#ffffff')
const [uploading, setUploading] = useState(false); // 文件上传状态 const [uploading, setUploading] = useState(false) // 文件上传状态
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0)
const [file, setFile] = useState({}); const [file, setFile] = useState({})
const [fileList, setFileList] = useState([]); const [fileList, setFileList] = useState([])
const [imgUrl, setImgUrl] = useState(''); const [imgUrl, setImgUrl] = useState('')
const [initValues, setInitValues] = useState({ const [initValues, setInitValues] = useState({
title: '', title: '',
size: 32, size: 32,
...@@ -48,39 +32,39 @@ const ImageModal = forwardRef((props, ref) => { ...@@ -48,39 +32,39 @@ const ImageModal = forwardRef((props, ref) => {
align: 'left', align: 'left',
bgColor: bgColorValue, bgColor: bgColorValue,
height: 200, height: 200,
family: '黑体', family: '黑体'
}); })
// 是否更新的判断 // 是否更新的判断
const [toolStatus, setToolStatus] = useState(false); const [toolStatus, setToolStatus] = useState(false)
const [nowRandom, setNowRandom] = useState(null); const [nowRandom, setNowRandom] = useState(null)
useEffect(() => { useEffect(() => {
if (titleInfo && Object.entries(titleInfo).length > 0) { if (titleInfo && Object.entries(titleInfo).length > 0) {
setInitValues({ ...titleInfo }); setInitValues({ ...titleInfo })
setImgUrl(titleInfo.bgImgUrl); setImgUrl(titleInfo.bgImgUrl)
form.setFieldsValue({ ...titleInfo }); form.setFieldsValue({ ...titleInfo })
setTextColor(titleInfo.textColor); setTextColor(titleInfo.textColor)
setBgColorValue(titleInfo.bgColor); setBgColorValue(titleInfo.bgColor)
setNowRandom(titleInfo.random); setNowRandom(titleInfo.random)
setTitleInfo({}); setTitleInfo({})
} }
}, [titleInfo]); }, [titleInfo])
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
form, form,
setInitValues, setInitValues
}; }
}); })
const textColorChange = (value, hex) => { const textColorChange = (value, hex) => {
setTextColor(hex); setTextColor(hex)
form.setFieldsValue({ textColor: hex }); form.setFieldsValue({ textColor: hex })
}; }
const bgColorChange = (value, hex) => { const bgColorChange = (value, hex) => {
setBgColorValue(hex); setBgColorValue(hex)
form.setFieldsValue({ bgColor: hex }); form.setFieldsValue({ bgColor: hex })
}; }
const callback = ({ title, random, bgImgUrl, textColor, align, height, size, family }) => { const callback = ({ title, random, bgImgUrl, textColor, align, height, size, family }) => {
form.setFieldsValue({ form.setFieldsValue({
...@@ -90,116 +74,116 @@ const ImageModal = forwardRef((props, ref) => { ...@@ -90,116 +74,116 @@ const ImageModal = forwardRef((props, ref) => {
align: align, align: align,
height: height, height: height,
size: size, size: size,
family: family, family: family
}); })
setImgUrl(bgImgUrl); setImgUrl(bgImgUrl)
setToolStatus(true); setToolStatus(true)
setNowRandom(random); setNowRandom(random)
}; }
const normFile = (e) => { const normFile = e => {
if (Array.isArray(e)) { if (Array.isArray(e)) {
return e; return e
} }
return e?.fileList; return e?.fileList
}; }
const uploadProps = { const uploadProps = {
name: 'file', name: 'file',
maxCount: 1, maxCount: 1,
showUploadList: false, showUploadList: false,
accept: fileAccept.join(','), accept: fileAccept.join(','),
beforeUpload: (file, fileList) => { beforeUpload: (file, fileList) => {
const fileExt = file.name.substring(file.name.lastIndexOf('.')); const fileExt = file.name.substring(file.name.lastIndexOf('.'))
if (!fileAccept.includes(fileExt.toLowerCase())) { if (!fileAccept.includes(fileExt.toLowerCase())) {
message.error('请上传正确格式的图片'); message.error('请上传正确格式的图片')
return false; return false
} }
setFile(file); setFile(file)
setFileList(fileList); setFileList(fileList)
}, },
customRequest: async () => { customRequest: async () => {
setUploading(true); setUploading(true)
let data = null; let data = null
if (file.size >= partSize) { if (file.size >= partSize) {
data = await multipartUploader(file, 'image', ossClient, (progress, checkpoint) => { data = await multipartUploader(file, 'image', ossClient, (progress, checkpoint) => {
console.log(`上传进度 ${progress}`); console.log(`上传进度 ${progress}`)
setProgress(parseInt(progress * 100)); setProgress(parseInt(progress * 100))
}); })
console.log('multipartUploader --> ', data); console.log('multipartUploader --> ', data)
} else { } else {
data = await normalUploader(file, 'image', ossClient); data = await normalUploader(file, 'image', ossClient)
console.log('normalUploader --> ', data); console.log('normalUploader --> ', data)
} }
if (data.status === 200 && data.statusCode === 200) { if (data.status === 200 && data.statusCode === 200) {
const { url, name } = data; const { url, name } = data
setImgUrl(url); setImgUrl(url)
form.setFieldsValue({ bgImgUrl: url }); form.setFieldsValue({ bgImgUrl: url })
} }
setUploading(false); setUploading(false)
}, }
}; }
const onFinish = (values) => { const onFinish = values => {
editor.restoreSelection(); editor.restoreSelection()
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') { if (node.type === 'paragraph') {
return true; // 匹配 paragraph return true // 匹配 paragraph
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
if (node.children[0].text === '') { if (node.children[0].text === '') {
SlateTransforms.removeNodes(editor); SlateTransforms.removeNodes(editor)
} }
} }
if (nowRandom) { if (nowRandom) {
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterHeader') { if (node.type === 'chapterHeader') {
return true; // 匹配 chapterHeader return true // 匹配 chapterHeader
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
if (nodeEntries) { if (nodeEntries) {
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
if (parseInt(node.random) === parseInt(nowRandom)) { if (parseInt(node.random) === parseInt(nowRandom)) {
SlateTransforms.setNodes(editor, { ...node, ...values }, { at: path }); SlateTransforms.setNodes(editor, { ...node, ...values }, { at: path })
setImgUrl(''); setImgUrl('')
setProgress(0); setProgress(0)
setUploading(false); setUploading(false)
form.resetFields(); form.resetFields()
setTitleVisible(false); setTitleVisible(false)
return false; return false
} }
} }
} }
return false; return false
} }
const html = editor.getHtml(); const html = editor.getHtml()
const parser = new DOMParser(); const parser = new DOMParser()
let random = Math.random().toString(10).substring(2, 10); let random = Math.random().toString(10).substring(2, 10)
if (nowRandom) { if (nowRandom) {
random = nowRandom; random = nowRandom
} }
const docBody = parser.parseFromString(html, 'text/html'); const docBody = parser.parseFromString(html, 'text/html')
const section = docBody.body.querySelectorAll('.chapter-item-header'); // 是否存在章头 const section = docBody.body.querySelectorAll('.chapter-item-header') // 是否存在章头
editor.insertNode({ editor.insertNode({
type: 'chapterHeader', type: 'chapterHeader',
...@@ -213,73 +197,60 @@ const ImageModal = forwardRef((props, ref) => { ...@@ -213,73 +197,60 @@ const ImageModal = forwardRef((props, ref) => {
size: values.size ? values.size : 26, size: values.size ? values.size : 26,
family: values.family, family: values.family,
callback: callback, callback: callback,
children: [{ text: '' }], children: [{ text: '' }]
}); })
setImgUrl(''); setImgUrl('')
setProgress(0); setProgress(0)
setUploading(false); setUploading(false)
form.resetFields(); form.resetFields()
setTitleVisible(false); setTitleVisible(false)
}; }
return ( return (
<div> <div>
<Divider /> <Divider />
<div className='editor-content-form'> <div className="editor-content-form">
<Form <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initValues}>
layout='vertical' <Form.Item label="章头背景图片" valuePropName="fileList" getValueFromEvent={normFile} name="bgImgUrl">
name='validate_other'
form={form}
onFinish={onFinish}
initialValues={initValues}
>
<Form.Item
label='章头背景图片'
valuePropName='fileList'
getValueFromEvent={normFile}
name='bgImgUrl'
>
<Spin spinning={uploading} tip={`${progress}%`}> <Spin spinning={uploading} tip={`${progress}%`}>
<div className='editor-dragger'> <div className="editor-dragger">
<Dragger {...uploadProps} showUploadList={false}> <Dragger {...uploadProps} showUploadList={false}>
{!imgUrl && ( {!imgUrl && (
<div className='editor-uploader-process'> <div className="editor-uploader-process">
<p className='ant-upload-drag-icon'> <p className="ant-upload-drag-icon">
<CloudUploadOutlined style={{ fontSize: 40 }} /> <CloudUploadOutlined style={{ fontSize: 40 }} />
</p> </p>
<p className='ant-upload-text'>点击上传或拖拽到此处上传</p> <p className="ant-upload-text">点击上传或拖拽到此处上传</p>
<p className='ant-upload-hint'>支持上传 .png、.jpg、.jpeg、.svg格式的图片</p> <p className="ant-upload-hint">支持上传 .png、.jpg、.jpeg、.svg格式的图片</p>
</div> </div>
)} )}
{imgUrl && ( {imgUrl && (
<> <>
<div className='editor-uploader-result'> <div className="editor-uploader-result">
<div className='editor-uploader-result-img'> <div className="editor-uploader-result-img">
<img src={imgUrl} alt='' /> <img src={imgUrl} alt="" />
</div> </div>
<div className='editor-uploader-result-tips'> <div className="editor-uploader-result-tips">
<Space> <Space>
<Button <Button
size='small' size="small"
type='primary' type="primary"
ghost ghost
onClick={() => { onClick={() => {
setImgUrl(null); setImgUrl(null)
form.setFieldsValue({ bgImgUrl: '' }); form.setFieldsValue({ bgImgUrl: '' })
}} }}>
>
替换 替换
</Button> </Button>
<Button <Button
size='small' size="small"
type='default' type="default"
onClick={(ev) => { onClick={ev => {
ev.stopPropagation(); ev.stopPropagation()
ev.preventDefault(); ev.preventDefault()
setImgUrl(null); setImgUrl(null)
form.setFieldsValue({ bgImgUrl: '' }); form.setFieldsValue({ bgImgUrl: '' })
}} }}>
>
移除 移除
</Button> </Button>
</Space> </Space>
...@@ -291,119 +262,87 @@ const ImageModal = forwardRef((props, ref) => { ...@@ -291,119 +262,87 @@ const ImageModal = forwardRef((props, ref) => {
</div> </div>
</Spin> </Spin>
</Form.Item> </Form.Item>
<Form.Item <Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]} extra="最多输入30字">
label='标题' <Input maxLength={30} placeholder="" allowClear />
name='title'
rules={[{ required: true, message: '请输入标题' }]}
extra='最多输入30字'
>
<Input maxLength={30} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='章头背景色' label="章头背景色"
name='bgColor' name="bgColor"
className='flex-max' className="flex-max"
rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]} rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha value={bgColorValue} defaultValue={bgColorValue} format="hex" onChange={bgColorChange} />
disabledAlpha
value={bgColorValue}
defaultValue={bgColorValue}
format='hex'
onChange={bgColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='文本颜色' label="文本颜色"
name='textColor' name="textColor"
className='flex-max' className="flex-max"
rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]} rules={[{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }]}>
> <Input maxLength={100} placeholder="" allowClear />
<Input maxLength={100} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker value={textColor} defaultValue={textColor} disabledAlpha format="hex" onChange={textColorChange} />
value={textColor}
defaultValue={textColor}
disabledAlpha
format='hex'
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label='字体' name='family'> <Form.Item label="字体" name="family">
<Select> <Select>
{fontFamilyList.map((item, index) => { {fontFamilyList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item label="文字大小" name="size" rules={[{ required: true, message: '请输入文字大小' }]}>
label='文字大小'
name='size'
rules={[{ required: true, message: '请输入文字大小' }]}
>
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item <Form.Item label="对齐方式" name="align" rules={[{ required: true, message: '请选择对齐方式' }]}>
label='对齐方式'
name='align'
rules={[{ required: true, message: '请选择对齐方式' }]}
>
<Select> <Select>
<Select.Option value='left'>左对齐</Select.Option> <Select.Option value="left">左对齐</Select.Option>
<Select.Option value='center'>居中对齐</Select.Option> <Select.Option value="center">居中对齐</Select.Option>
<Select.Option value='right'>右对齐</Select.Option> <Select.Option value="right">右对齐</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Form.Item label='高度' name='height'> <Form.Item label="高度" name="height">
<InputNumber <InputNumber controls={false} placeholder="" style={{ width: '100%', textAlign: 'left' }} allowClear />
controls={false}
placeholder=''
style={{ width: '100%', textAlign: 'left' }}
allowClear
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
</Form.Item> </Form.Item>
<Form.Item className='editor-form-buttons'> <Form.Item className="editor-form-buttons">
<Space> <Space>
<Button type='default' disabled={uploading} onClick={() => setTitleVisible(false)}> <Button type="default" disabled={uploading} onClick={() => setTitleVisible(false)}>
取消 取消
</Button> </Button>
<Button type='primary' disabled={uploading} htmlType='submit'> <Button type="primary" disabled={uploading} htmlType="submit">
{nowRandom ? '更新' : '插入'} {nowRandom ? '更新' : '插入'}
</Button> </Button>
</Space> </Space>
...@@ -411,7 +350,7 @@ const ImageModal = forwardRef((props, ref) => { ...@@ -411,7 +350,7 @@ const ImageModal = forwardRef((props, ref) => {
</Form> </Form>
</div> </div>
</div> </div>
); )
}); })
export default ImageModal; export default ImageModal
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import { import { Modal, Divider, ColorPicker, Input, Space, Button, Form, Spin, message, Select, Row, Col } from 'antd'
Modal, import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor'
Divider,
ColorPicker,
Input,
Space,
Button,
Form,
Spin,
message,
Select,
Row,
Col,
} from 'antd';
import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor';
import { fontFizeList } from '../utils/setting'; import { fontSizeList } from '../utils/setting'
import EditorSimple from '@/common/editor-simple'; import EditorSimple from '@/common/editor-simple'
import './index.less'; import './index.less'
import { addExpandRead, expandReadInfo } from '../utils/request'; import { addExpandRead, expandReadInfo } from '../utils/request'
const ExpandModal = forwardRef((props, ref) => { const ExpandModal = forwardRef((props, ref) => {
const { const { editor, ossClient, bookId, chapterId, setExpandVisible, setExpandInfo, expandInfo, selectionSize = 18 } = props
editor,
ossClient,
bookId,
chapterId,
setExpandVisible,
setExpandInfo,
expandInfo,
selectionSize = 18,
} = props;
const [form] = Form.useForm(); const [form] = Form.useForm()
const flexValue = Form.useWatch('flex', form); const flexValue = Form.useWatch('flex', form)
const [loadLoading, setLoadLoading] = useState(false); const [loadLoading, setLoadLoading] = useState(false)
const [content, setContent] = useState(''); const [content, setContent] = useState('')
const [themeValue, setThemeValue] = useState('#ab1941'); const [themeValue, setThemeValue] = useState('#ab1941')
const [oldFlex, setOldFlex] = useState(null); const [oldFlex, setOldFlex] = useState(null)
const [initvalues, setInitValue] = useState({ const [initvalues, setInitValue] = useState({
flex: 1, flex: 1,
name: '', name: '',
title: '', title: '',
content: '', content: '',
theme: '#ab1941', theme: '#ab1941',
fontSize: selectionSize || 18, fontSize: selectionSize || 18
}); })
const [nowRandom, setNowRandom] = useState(null); const [nowRandom, setNowRandom] = useState(null)
// const [fontsize, setFontsize] = useState(selectionSize); // const [fontsize, setFontsize] = useState(selectionSize);
const getExpandInfo = async (random) => { const getExpandInfo = async random => {
setLoadLoading(true); setLoadLoading(true)
const data = await expandReadInfo({ const data = await expandReadInfo({
book_id: bookId, book_id: bookId,
chapter_id: chapterId, chapter_id: chapterId,
position: nowRandom || random, position: nowRandom || random
}); })
if (data) { if (data) {
setContent(data.content); setContent(data.content)
// setInitValue({ ...data }); // setInitValue({ ...data });
// form.setFieldsValue({ ...data }); // form.setFieldsValue({ ...data });
} }
setLoadLoading(false); setLoadLoading(false)
}; }
const textColorChange = (value, hex) => { const textColorChange = (value, hex) => {
setThemeValue(hex); setThemeValue(hex)
form.setFieldsValue({ theme: hex }); form.setFieldsValue({ theme: hex })
}; }
useEffect(() => { useEffect(() => {
if (Object.entries(expandInfo).length > 0) { if (Object.entries(expandInfo).length > 0) {
(async () => { ;(async () => {
setLoadLoading(true); setLoadLoading(true)
let obj = { ...expandInfo, flex: parseInt(expandInfo.flex), fontSize: expandInfo.fontsize }; let obj = { ...expandInfo, flex: parseInt(expandInfo.flex), fontSize: expandInfo.fontsize }
setThemeValue(expandInfo.theme); setThemeValue(expandInfo.theme)
setOldFlex(parseInt(expandInfo.flex)); setOldFlex(parseInt(expandInfo.flex))
setInitValue(obj); setInitValue(obj)
form.setFieldsValue(obj); form.setFieldsValue(obj)
setNowRandom(expandInfo.random); setNowRandom(expandInfo.random)
await getExpandInfo(expandInfo.random); await getExpandInfo(expandInfo.random)
setExpandInfo({}); setExpandInfo({})
// setFontsize(expandInfo.fontsize); // setFontsize(expandInfo.fontsize);
setLoadLoading(false); setLoadLoading(false)
})(); })()
} }
}, [expandInfo]); }, [expandInfo])
const removeInlineElement = (editor, type) => { const removeInlineElement = (editor, type) => {
const { selection } = editor; const { selection } = editor
if (!selection) return; if (!selection) return
const [inlineNode] = SlateEditor.nodes(editor, { const [inlineNode] = SlateEditor.nodes(editor, {
match: (node) => SlateElement.isElement(node) && node.type === type, match: node => SlateElement.isElement(node) && node.type === type
}); })
console.log('inlineNode', inlineNode); console.log('inlineNode', inlineNode)
if (!inlineNode) return; if (!inlineNode) return
const [parent] = SlateEditor.parent(editor, inlineNode[1]); const [parent] = SlateEditor.parent(editor, inlineNode[1])
if (!parent || SlateElement.isElement(parent)) { if (!parent || SlateElement.isElement(parent)) {
Transforms.removeNodes(editor, { match: (node) => node.type === type }); Transforms.removeNodes(editor, { match: node => node.type === type })
} else { } else {
Transforms.removeNodes(editor, { at: inlineNode[1] }); Transforms.removeNodes(editor, { at: inlineNode[1] })
} }
}; }
const onFinish = async (values) => { const onFinish = async values => {
editor.restoreSelection(); editor.restoreSelection()
if (parseInt(values.flex) === 1) { if (parseInt(values.flex) === 1) {
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') { if (node.type === 'paragraph') {
return true; // 匹配 paragraph return true // 匹配 paragraph
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
if (node.children[0].text === '') { if (node.children[0].text === '') {
SlateTransforms.removeNodes(editor); SlateTransforms.removeNodes(editor)
} }
} }
} }
setLoadLoading(true); setLoadLoading(true)
const { flex, theme, fontSize, ...other } = values; const { flex, theme, fontSize, ...other } = values
if (nowRandom) { if (nowRandom) {
let props = { let props = {
flex: parseInt(values.flex), flex: parseInt(values.flex),
...@@ -135,34 +113,34 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -135,34 +113,34 @@ const ExpandModal = forwardRef((props, ref) => {
title: values.title, title: values.title,
random: nowRandom, random: nowRandom,
theme: values.theme, theme: values.theme,
fontsize: values.fontSize, fontsize: values.fontSize
}; }
if (parseInt(oldFlex) !== flex) { if (parseInt(oldFlex) !== flex) {
SlateTransforms.removeNodes(editor); SlateTransforms.removeNodes(editor)
if (parseInt(values.flex) === 1) { if (parseInt(values.flex) === 1) {
editor.insertNode({ editor.insertNode({
type: 'chapterExpandRead', type: 'chapterExpandRead',
...props, ...props,
children: [{ text: '' }], children: [{ text: '' }]
}); })
} else { } else {
editor.insertNode({ editor.insertNode({
type: 'chapterExpandReadSimple', type: 'chapterExpandReadSimple',
...props, ...props,
children: [{ text: '' }], children: [{ text: '' }]
}); })
} }
} else { } else {
if (parseInt(flex) === 2) { if (parseInt(flex) === 2) {
editor.move(1); editor.move(1)
editor.deleteBackward(); editor.deleteBackward()
editor.insertNode({ editor.insertNode({
type: 'chapterExpandReadSimple', type: 'chapterExpandReadSimple',
...props, ...props,
children: [{ text: '' }], children: [{ text: '' }]
}); })
// const nodeEntries = SlateEditor.nodes(editor, { // const nodeEntries = SlateEditor.nodes(editor, {
// match: (node) => { // match: (node) => {
...@@ -187,22 +165,22 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -187,22 +165,22 @@ const ExpandModal = forwardRef((props, ref) => {
// } // }
} else { } else {
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterExpandRead') { if (node.type === 'chapterExpandRead') {
return true; // 匹配 chapterToolTip return true // 匹配 chapterToolTip
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
// 遍历匹配的节点迭代器,获取匹配的节点路径 // 遍历匹配的节点迭代器,获取匹配的节点路径
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
if (node.random === nowRandom) { if (node.random === nowRandom) {
SlateTransforms.setNodes(editor, props, { at: path }); SlateTransforms.setNodes(editor, props, { at: path })
} }
} }
} }
...@@ -213,22 +191,22 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -213,22 +191,22 @@ const ExpandModal = forwardRef((props, ref) => {
chapter_id: chapterId, chapter_id: chapterId,
position: nowRandom, position: nowRandom,
type: flex, type: flex,
...other, ...other
}); })
setNowRandom(null); setNowRandom(null)
setExpandVisible(false); setExpandVisible(false)
setLoadLoading(false); setLoadLoading(false)
return false; return false
} }
let random = Math.random().toString(10).substring(2, 10); let random = Math.random().toString(10).substring(2, 10)
const data = await addExpandRead({ const data = await addExpandRead({
book_id: bookId, book_id: bookId,
chapter_id: chapterId, chapter_id: chapterId,
position: random, position: random,
type: flex, type: flex,
...other, ...other
}); })
if (parseInt(values.flex) === 1) { if (parseInt(values.flex) === 1) {
editor.insertNode({ editor.insertNode({
...@@ -239,8 +217,8 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -239,8 +217,8 @@ const ExpandModal = forwardRef((props, ref) => {
random: random, random: random,
theme: values.theme, theme: values.theme,
fontsize: values.fontSize || selectionSize, fontsize: values.fontSize || selectionSize,
children: [{ text: '' }], children: [{ text: '' }]
}); })
} else { } else {
editor.insertNode({ editor.insertNode({
type: 'chapterExpandReadSimple', type: 'chapterExpandReadSimple',
...@@ -250,85 +228,57 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -250,85 +228,57 @@ const ExpandModal = forwardRef((props, ref) => {
random: random, random: random,
theme: values.theme, theme: values.theme,
fontsize: values.fontSize || selectionSize, fontsize: values.fontSize || selectionSize,
children: [{ text: '' }], children: [{ text: '' }]
}); })
} }
setNowRandom(null); setNowRandom(null)
setExpandVisible(false); setExpandVisible(false)
setLoadLoading(false); setLoadLoading(false)
}; }
return ( return (
<div> <div>
<Divider /> <Divider />
<div className='editor-content-form'> <div className="editor-content-form">
<Form <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initvalues}>
layout='vertical' <Form.Item label="扩展类型" name="flex" rules={[{ required: true, message: '请选择扩展类型' }]}>
name='validate_other'
form={form}
onFinish={onFinish}
initialValues={initvalues}
>
<Form.Item
label='扩展类型'
name='flex'
rules={[{ required: true, message: '请选择扩展类型' }]}
>
<Select disabled={oldFlex > 0 ? true : false}> <Select disabled={oldFlex > 0 ? true : false}>
<Select.Option value={1}>独立扩展</Select.Option> <Select.Option value={1}>独立扩展</Select.Option>
<Select.Option value={2}>行内扩展</Select.Option> <Select.Option value={2}>行内扩展</Select.Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item label="扩展名称" name="name" rules={[{ required: true, message: '请输入扩展名称' }]} extra="最多输入100字">
label='扩展名称' <Input maxLength={100} placeholder="" allowClear />
name='name'
rules={[{ required: true, message: '请输入扩展名称' }]}
extra='最多输入100字'
>
<Input maxLength={100} placeholder='' allowClear />
</Form.Item> </Form.Item>
{flexValue === 1 && ( {flexValue === 1 && (
<Form.Item <Form.Item
label='扩展标题' label="扩展标题"
name='title' name="title"
rules={[ rules={[
{ required: flexValue === 1 ? true : false, message: '请输入扩展标题' }, { required: flexValue === 1 ? true : false, message: '请输入扩展标题' },
{ max: 100, message: '最多输入100个字符!' }, { max: 100, message: '最多输入100个字符!' }
]} ]}
extra='最多输入100字' extra="最多输入100字">
> <Input.TextArea maxLength={100} autoSize={{ minRows: 2, maxRows: 3 }} placeholder="" allowClear />
<Input.TextArea
maxLength={100}
autoSize={{ minRows: 2, maxRows: 3 }}
placeholder=''
allowClear
/>
</Form.Item> </Form.Item>
)} )}
{flexValue === 1 ? ( {flexValue === 1 ? (
<Form.Item> <Form.Item>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='扩展主题色' label="扩展主题色"
name='theme' name="theme"
className='flex-max' className="flex-max"
rules={[ rules={[
{ required: true, message: '请选择颜色' }, { required: true, message: '请选择颜色' },
{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }, { pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha value={themeValue} defaultValue={themeValue} format="hex" onChange={textColorChange} />
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Form.Item> </Form.Item>
...@@ -336,59 +286,47 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -336,59 +286,47 @@ const ExpandModal = forwardRef((props, ref) => {
<Form.Item> <Form.Item>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<Form.Item label='字号' name='fontSize'> <Form.Item label="字号" name="fontSize">
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='扩展主题色' label="扩展主题色"
name='theme' name="theme"
className='flex-max' className="flex-max"
rules={[ rules={[
{ required: true, message: '请选择颜色' }, { required: true, message: '请选择颜色' },
{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }, { pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha value={themeValue} defaultValue={themeValue} format="hex" onChange={textColorChange} />
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
</Row> </Row>
</Form.Item> </Form.Item>
)} )}
<Form.Item label='扩展内容' name='content'> <Form.Item label="扩展内容" name="content">
<EditorSimple <EditorSimple form={form} fieldName={'content'} content={content} loadLoading={loadLoading} />
form={form}
fieldName={'content'}
content={content}
loadLoading={loadLoading}
/>
</Form.Item> </Form.Item>
<Form.Item className='editor-form-buttons'> <Form.Item className="editor-form-buttons">
<Space> <Space>
<Button type='default' onClick={() => setExpandVisible(false)}> <Button type="default" onClick={() => setExpandVisible(false)}>
取消 取消
</Button> </Button>
<Button type='primary' loadLoading={loadLoading} htmlType='submit'> <Button type="primary" loadLoading={loadLoading} htmlType="submit">
{nowRandom ? '更新' : '插入'} {nowRandom ? '更新' : '插入'}
</Button> </Button>
</Space> </Space>
...@@ -396,7 +334,7 @@ const ExpandModal = forwardRef((props, ref) => { ...@@ -396,7 +334,7 @@ const ExpandModal = forwardRef((props, ref) => {
</Form> </Form>
</div> </div>
</div> </div>
); )
}); })
export default ExpandModal; export default ExpandModal
...@@ -4,11 +4,11 @@ import './index.less' ...@@ -4,11 +4,11 @@ import './index.less'
import { addGallery } from '../utils/request' import { addGallery } from '../utils/request'
import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor' import { SlateEditor, SlateTransforms, SlateElement } from '@wangeditor/editor'
import { findNodeWithParent, fontFizeList } from '../utils/setting' import { findNodeWithParent, fontSizeList } from '../utils/setting'
import GalleryFormItem from './galleryItem' import GalleryFormItem from './galleryItem'
const randomOne = Math.random().toString(16).substring(2, 10) const randomOne = Math.random().toString(16).substring(2, 10)
const GalleryModal = (props) => { const GalleryModal = props => {
const { editor, ossClient, galleryInfo, bookId, chapterId, setGalleryVisible, setGalleryInfo, selectionSize = 18, isOnline = false } = props const { editor, ossClient, galleryInfo, bookId, chapterId, setGalleryVisible, setGalleryInfo, selectionSize = 18, isOnline = false } = props
const [form] = Form.useForm() const [form] = Form.useForm()
...@@ -57,7 +57,7 @@ const GalleryModal = (props) => { ...@@ -57,7 +57,7 @@ const GalleryModal = (props) => {
const [activeKey, setActiveKey] = useState(initialItems[0].key) const [activeKey, setActiveKey] = useState(initialItems[0].key)
const [items, setItems] = useState(initialItems) const [items, setItems] = useState(initialItems)
const onChange = (newActiveKey) => { const onChange = newActiveKey => {
setActiveKey(newActiveKey) setActiveKey(newActiveKey)
} }
const add = () => { const add = () => {
...@@ -81,8 +81,8 @@ const GalleryModal = (props) => { ...@@ -81,8 +81,8 @@ const GalleryModal = (props) => {
setItems(newPanes) setItems(newPanes)
setActiveKey(newActiveKey) setActiveKey(newActiveKey)
} }
const remove = async (targetKey) => { const remove = async targetKey => {
const tempGallery = picList.filter((item) => item.key !== targetKey) const tempGallery = picList.filter(item => item.key !== targetKey)
await setPicList(tempGallery) await setPicList(tempGallery)
console.log(tempGallery) console.log(tempGallery)
form.setFieldsValue({ gallery: tempGallery }) form.setFieldsValue({ gallery: tempGallery })
...@@ -94,7 +94,7 @@ const GalleryModal = (props) => { ...@@ -94,7 +94,7 @@ const GalleryModal = (props) => {
lastIndex = i - 1 lastIndex = i - 1
} }
}) })
let newPanes = items.filter((item) => item.key !== targetKey) let newPanes = items.filter(item => item.key !== targetKey)
if (newPanes.length && newActiveKey === targetKey) { if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) { if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key newActiveKey = newPanes[lastIndex].key
...@@ -184,7 +184,7 @@ const GalleryModal = (props) => { ...@@ -184,7 +184,7 @@ const GalleryModal = (props) => {
} }
}, [galleryInfo]) }, [galleryInfo])
const onFinish = async (values) => { const onFinish = async values => {
editor.restoreSelection() editor.restoreSelection()
// setLoading(true); // setLoading(true);
const { galleryTitle, flex, gallery, fontSize, theme = '' } = values const { galleryTitle, flex, gallery, fontSize, theme = '' } = values
...@@ -193,7 +193,7 @@ const GalleryModal = (props) => { ...@@ -193,7 +193,7 @@ const GalleryModal = (props) => {
if (parseInt(flex) !== 2) { if (parseInt(flex) !== 2) {
// 删除空白的p标签 // 删除空白的p标签
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') { if (node.type === 'paragraph') {
...@@ -310,7 +310,7 @@ const GalleryModal = (props) => { ...@@ -310,7 +310,7 @@ const GalleryModal = (props) => {
<Col span={12}> <Col span={12}>
<Form.Item label="字号" name="fontSize"> <Form.Item label="字号" name="fontSize">
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
......
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import { import { Modal, Divider, Upload, Input, Space, Button, Form, Spin, message, Select, Cascader, ColorPicker, Row, Col } from 'antd'
Modal, import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor'
Divider, import './index.less'
Upload, import { addChapterTooltip, delChapterTooltip } from '../utils/request'
Input, import { fontSizeList } from '../utils/setting'
Space, import { findTreeElementByKey } from '@/utils/common'
Button,
Form,
Spin,
message,
Select,
Cascader,
ColorPicker,
Row,
Col
} from 'antd';
import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor';
import './index.less';
import { addChapterTooltip, delChapterTooltip } from '../utils/request';
import { fontFizeList } from '../utils/setting';
import { findTreeElementByKey } from '@/utils/common';
const LinkModal = forwardRef((props, ref) => { const LinkModal = forwardRef((props, ref) => {
const { editor, ossClient, bookId, chapterId, setLinkVisible, setLinkInfo, linkInfo, gData, selectionSize = 18 } = const { editor, ossClient, bookId, chapterId, setLinkVisible, setLinkInfo, linkInfo, gData, selectionSize = 18 } = props
props;
const [form] = Form.useForm(); const [form] = Form.useForm()
const linktype = Form.useWatch('linktype', form); const linktype = Form.useWatch('linktype', form)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [themeValue, setThemeValue] = useState('#ab1941'); const [themeValue, setThemeValue] = useState('#ab1941')
const [initvalues, setInitValue] = useState({ const [initvalues, setInitValue] = useState({
linktype: 1, linktype: 1,
title: '', title: '',
link: '', link: '',
theme: '#ab1941', theme: '#ab1941',
fontSize: selectionSize || 18 fontSize: selectionSize || 18
}); })
const [toolStatus, setToolStatus] = useState(false); const [toolStatus, setToolStatus] = useState(false)
const [nowRandom, setNowRandom] = useState(null); const [nowRandom, setNowRandom] = useState(null)
const [fontsize, setFontsize] = useState(selectionSize); const [fontsize, setFontsize] = useState(selectionSize)
const linkChange = (value) => { const linkChange = value => {
if (value === 2) { if (value === 2) {
form.setFieldsValue({ chapters : [] }); form.setFieldsValue({ chapters: [] })
} }
} }
useEffect(() => { useEffect(() => {
if (editor.getSelectionText()) { if (editor.getSelectionText()) {
setInitValue({...initvalues, title: editor.getSelectionText() }); setInitValue({ ...initvalues, title: editor.getSelectionText() })
form.setFieldsValue({ title: editor.getSelectionText() }); form.setFieldsValue({ title: editor.getSelectionText() })
} }
}, []) }, [])
useEffect(() => { useEffect(() => {
if (Object.entries(linkInfo).length > 0) { if (Object.entries(linkInfo).length > 0) {
let chapters = []; let chapters = []
try { try {
chapters = linkInfo.chapters.split(',').map((item) => parseInt(item)); chapters = linkInfo.chapters.split(',').map(item => parseInt(item))
} catch (e) { } catch (e) {
chapters = []; chapters = []
} }
let obj = { ...linkInfo, linktype: parseInt(linkInfo.linktype), chapters, fontSize: parseInt(linkInfo.fontsize) }; let obj = { ...linkInfo, linktype: parseInt(linkInfo.linktype), chapters, fontSize: parseInt(linkInfo.fontsize) }
setInitValue(obj); setInitValue(obj)
setThemeValue(linkInfo.theme); setThemeValue(linkInfo.theme)
form.setFieldsValue(obj); form.setFieldsValue(obj)
setNowRandom(linkInfo.random); setNowRandom(linkInfo.random)
// setFontsize(linkInfo.fontsize); // setFontsize(linkInfo.fontsize);
setLinkInfo({}); setLinkInfo({})
} }
}, [linkInfo]); }, [linkInfo])
const textColorChange = (value, hex) => { const textColorChange = (value, hex) => {
setThemeValue(hex); setThemeValue(hex)
form.setFieldsValue({ theme: hex }); form.setFieldsValue({ theme: hex })
}; }
const onFinish = async (values) => { const onFinish = async values => {
editor.restoreSelection(); editor.restoreSelection()
let child = {}; let child = {}
if (values.linktype === 2) { if (values.linktype === 2) {
const last = values.chapters[values.chapters.length - 1]; const last = values.chapters[values.chapters.length - 1]
child = findTreeElementByKey(gData, 'key', last); child = findTreeElementByKey(gData, 'key', last)
if (child.children && child.children.length) { if (child.children && child.children.length) {
form.setFields([{ chapters: { errors: ['请选择正确的子节!'] } }]); form.setFields([{ chapters: { errors: ['请选择正确的子节!'] } }])
return false; return false
} }
} }
setLoading(true); setLoading(true)
const { linktype, link, ...other } = values; const { linktype, link, ...other } = values
let newLink = ''; let newLink = ''
if (link) { if (link) {
if (!/^https*:\/\//.test(link)) { if (!/^https*:\/\//.test(link)) {
newLink = `http://${link}`; newLink = `http://${link}`
} else { } else {
newLink = link; newLink = link
} }
} }
...@@ -108,40 +92,40 @@ const LinkModal = forwardRef((props, ref) => { ...@@ -108,40 +92,40 @@ const LinkModal = forwardRef((props, ref) => {
random: nowRandom, random: nowRandom,
title: parseInt(values.linktype) === 1 ? values.title : child.title, title: parseInt(values.linktype) === 1 ? values.title : child.title,
chapters: values.chapters instanceof Array ? values.chapters.join(',') : '', chapters: values.chapters instanceof Array ? values.chapters.join(',') : '',
fontsize: values.fontSize, fontsize: values.fontSize
}; }
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterLink') { if (node.type === 'chapterLink') {
return true; return true
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
// 遍历匹配的节点迭代器,获取匹配的节点路径 // 遍历匹配的节点迭代器,获取匹配的节点路径
if (nodeEntries === null) { if (nodeEntries === null) {
console.log('当前未选中的 chapterLink'); console.log('当前未选中的 chapterLink')
} else { } else {
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
console.log('linkedit', node); console.log('linkedit', node)
if (node.random === nowRandom) { if (node.random === nowRandom) {
SlateTransforms.setNodes(editor, props, { at: path }); SlateTransforms.setNodes(editor, props, { at: path })
} }
} }
} }
message.success('更新成功!'); message.success('更新成功!')
setLinkVisible(false); setLinkVisible(false)
setNowRandom(null); setNowRandom(null)
setLoading(false); setLoading(false)
return false; return false
} }
let random = Math.random().toString(10).substring(2, 10); let random = Math.random().toString(10).substring(2, 10)
editor.insertNode({ editor.insertNode({
type: 'chapterLink', type: 'chapterLink',
title: parseInt(values.linktype) === 1 ? values.title : child.title, title: parseInt(values.linktype) === 1 ? values.title : child.title,
...@@ -151,71 +135,61 @@ const LinkModal = forwardRef((props, ref) => { ...@@ -151,71 +135,61 @@ const LinkModal = forwardRef((props, ref) => {
chapters: values.chapters instanceof Array ? values.chapters.join(',') : '', chapters: values.chapters instanceof Array ? values.chapters.join(',') : '',
random, random,
fontsize: values.fontSize, fontsize: values.fontSize,
children: [{ text: '' }], children: [{ text: '' }]
}); })
message.success('添加成功!'); message.success('添加成功!')
setNowRandom(null); setNowRandom(null)
setLinkVisible(false); setLinkVisible(false)
setLoading(false); setLoading(false)
}; }
const deleteNowTooltip = async () => { const deleteNowTooltip = async () => {
const data = await delChapterTooltip({ const data = await delChapterTooltip({
book_id: bookId, book_id: bookId,
chapter_id: chapterId, chapter_id: chapterId,
position: nowRandom, position: nowRandom
}); })
if (data) { if (data) {
editor.restoreSelection(); editor.restoreSelection()
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterLink') { if (node.type === 'chapterLink') {
return true; // 匹配 chapterToolTip return true // 匹配 chapterToolTip
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
// 遍历匹配的节点迭代器,获取匹配的节点路径 // 遍历匹配的节点迭代器,获取匹配的节点路径
let nodePaths = []; let nodePaths = []
let nodes = []; let nodes = []
if (nodeEntries === null) { if (nodeEntries === null) {
console.log('当前未选中的 chapterLink'); console.log('当前未选中的 chapterLink')
} else { } else {
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
nodePaths.push(path); nodePaths.push(path)
nodes.push(node); nodes.push(node)
} }
// 将属性应用到匹配的节点 // 将属性应用到匹配的节点
for (const path of nodePaths) { for (const path of nodePaths) {
SlateTransforms.removeNodes(editor, { at: path }); SlateTransforms.removeNodes(editor, { at: path })
} }
} }
setNowRandom(null); setNowRandom(null)
setLinkVisible(false); setLinkVisible(false)
} }
}; }
return ( return (
<div> <div>
<Divider /> <Divider />
<div className='editor-content-form'> <div className="editor-content-form">
<Form <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initvalues}>
layout='vertical' <Form.Item label="链接类型" name="linktype" rules={[{ required: true, message: '请选择链接类型' }]}>
name='validate_other'
form={form}
onFinish={onFinish}
initialValues={initvalues}
>
<Form.Item
label='链接类型'
name='linktype'
rules={[{ required: true, message: '请选择链接类型' }]}
>
<Select onChange={linkChange}> <Select onChange={linkChange}>
<Select.Option value={1}>普通链接</Select.Option> <Select.Option value={1}>普通链接</Select.Option>
<Select.Option value={2}>章节链接</Select.Option> <Select.Option value={2}>章节链接</Select.Option>
...@@ -223,40 +197,25 @@ const LinkModal = forwardRef((props, ref) => { ...@@ -223,40 +197,25 @@ const LinkModal = forwardRef((props, ref) => {
</Form.Item> </Form.Item>
{linktype === 1 && ( {linktype === 1 && (
<> <>
<Form.Item <Form.Item label="链接标题" name="title" rules={[{ required: true, message: '请输入链接标题' }]} extra="最多输入100字">
label='链接标题' <Input maxLength={100} placeholder="" allowClear />
name='title'
rules={[{ required: true, message: '请输入链接标题' }]}
extra='最多输入100字'
>
<Input maxLength={100} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label='链接' label="链接"
name='link' name="link"
rules={[ rules={[
{ required: true, message: '请输入链接地址' }, { required: true, message: '请输入链接地址' },
{ pattern: /^https*:\/\//gi, message: '链接地址需要http(s)://开头' }, { pattern: /^https*:\/\//gi, message: '链接地址需要http(s)://开头' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
</> </>
)} )}
{linktype === 2 && ( {linktype === 2 && (
<> <>
<Form.Item <Form.Item label="子节" name="chapters" rules={[{ required: true, message: '请选择子节' }]}>
label='子节' <Cascader options={gData} fieldNames={{ label: 'title', value: 'key', children: 'children' }} placeholder="请选择子节" allowClear />
name='chapters'
rules={[{ required: true, message: '请选择子节' }]}
>
<Cascader
options={gData}
fieldNames={{ label: 'title', value: 'key', children: 'children' }}
placeholder='请选择子节'
allowClear
/>
</Form.Item> </Form.Item>
</> </>
)} )}
...@@ -264,52 +223,45 @@ const LinkModal = forwardRef((props, ref) => { ...@@ -264,52 +223,45 @@ const LinkModal = forwardRef((props, ref) => {
<Form.Item> <Form.Item>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<Form.Item label='字号' name='fontSize'> <Form.Item label="字号" name="fontSize">
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='扩展主题色' label="扩展主题色"
name='theme' name="theme"
className='flex-max' className="flex-max"
rules={[ rules={[
{ required: true, message: '请选择颜色' }, { required: true, message: '请选择颜色' },
{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }, { pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha value={themeValue} defaultValue={themeValue} format="hex" onChange={textColorChange} />
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
</Row> </Row>
</Form.Item> </Form.Item>
<Form.Item className='editor-form-buttons'> <Form.Item className="editor-form-buttons">
<Space> <Space>
{toolStatus && ( {toolStatus && (
<Button type='default' onClick={deleteNowTooltip}> <Button type="default" onClick={deleteNowTooltip}>
删除 删除
</Button> </Button>
)} )}
<Button type='primary' loading={loading} htmlType='submit'> <Button type="primary" loading={loading} htmlType="submit">
{nowRandom ? '更新' : '插入'} {nowRandom ? '更新' : '插入'}
</Button> </Button>
</Space> </Space>
...@@ -317,7 +269,7 @@ const LinkModal = forwardRef((props, ref) => { ...@@ -317,7 +269,7 @@ const LinkModal = forwardRef((props, ref) => {
</Form> </Form>
</div> </div>
</div> </div>
); )
}); })
export default LinkModal; export default LinkModal
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react'
import { import { Modal, Divider, Upload, Input, Space, Button, Form, Spin, message, Select, ColorPicker, Row, Col } from 'antd'
Modal, import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor'
Divider, import './index.less'
Upload, import { addChapterTooltip, delChapterTooltip } from '../utils/request'
Input, import { findNodeWithParent, fontSizeList } from '../utils/setting'
Space,
Button,
Form,
Spin,
message,
Select,
ColorPicker,
Row,
Col,
} from 'antd';
import { SlateTransforms, SlateEditor, DomEditor, SlateElement } from '@wangeditor/editor';
import './index.less';
import { addChapterTooltip, delChapterTooltip } from '../utils/request';
import { findNodeWithParent, fontFizeList } from '../utils/setting';
const TooltipModal = forwardRef((props, ref) => { const TooltipModal = forwardRef((props, ref) => {
const { editor, ossClient, bookId, chapterId, setTooltipVisible, setTooltipInfo, tooltipInfo, selectionSize = 18 } = const { editor, ossClient, bookId, chapterId, setTooltipVisible, setTooltipInfo, tooltipInfo, selectionSize = 18 } = props
props;
const [form] = Form.useForm(); const [form] = Form.useForm()
const tooltipTypeValue = Form.useWatch('tooltipType', form); const tooltipTypeValue = Form.useWatch('tooltipType', form)
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [themeValue, setThemeValue] = useState('#ab1941'); const [themeValue, setThemeValue] = useState('#ab1941')
const [initvalues, setInitValue] = useState({ const [initvalues, setInitValue] = useState({
tooltipType: 1, tooltipType: 1,
title: '', title: '',
...@@ -34,38 +19,38 @@ const TooltipModal = forwardRef((props, ref) => { ...@@ -34,38 +19,38 @@ const TooltipModal = forwardRef((props, ref) => {
content: '', content: '',
theme: '#ab1941', theme: '#ab1941',
fontSize: selectionSize || 18 fontSize: selectionSize || 18
}); })
const [toolStatus, setToolStatus] = useState(false); const [toolStatus, setToolStatus] = useState(false)
const [nowRandom, setNowRandom] = useState(null); const [nowRandom, setNowRandom] = useState(null)
const [fontsize, setFontsize] = useState(selectionSize); const [fontsize, setFontsize] = useState(selectionSize)
useEffect(() => { useEffect(() => {
if (Object.entries(tooltipInfo).length > 0) { if (Object.entries(tooltipInfo).length > 0) {
let obj = { ...tooltipInfo, tooltipType: parseInt(tooltipInfo.tooltipType), fontSize: parseInt(tooltipInfo.fontsize) }; let obj = { ...tooltipInfo, tooltipType: parseInt(tooltipInfo.tooltipType), fontSize: parseInt(tooltipInfo.fontsize) }
setInitValue(obj); setInitValue(obj)
form.setFieldsValue(obj); form.setFieldsValue(obj)
setThemeValue(tooltipInfo.theme); setThemeValue(tooltipInfo.theme)
setNowRandom(tooltipInfo.random); setNowRandom(tooltipInfo.random)
setFontsize(parseInt(tooltipInfo.fontsize)); setFontsize(parseInt(tooltipInfo.fontsize))
setTooltipInfo({}); setTooltipInfo({})
} }
}, [tooltipInfo]); }, [tooltipInfo])
const textColorChange = (value, hex) => { const textColorChange = (value, hex) => {
setThemeValue(hex); setThemeValue(hex)
form.setFieldsValue({ theme: hex }); form.setFieldsValue({ theme: hex })
}; }
const onFinish = async (values) => { const onFinish = async values => {
editor.restoreSelection(); editor.restoreSelection()
setLoading(true); setLoading(true)
const { tooltipType, link, theme, fontSize, ...other } = values; const { tooltipType, link, theme, fontSize, ...other } = values
let newLink = ''; let newLink = ''
if (link) { if (link) {
if (!/^https*:\/\//.test(link)) { if (!/^https*:\/\//.test(link)) {
newLink = `http://${link}`; newLink = `http://${link}`
} else { } else {
newLink = link; newLink = link
} }
} }
...@@ -76,33 +61,33 @@ const TooltipModal = forwardRef((props, ref) => { ...@@ -76,33 +61,33 @@ const TooltipModal = forwardRef((props, ref) => {
position: nowRandom, position: nowRandom,
link: newLink, link: newLink,
...other, ...other,
type: tooltipType, type: tooltipType
}); })
if (data) { if (data) {
const props = { ...values, link: newLink, random: nowRandom, theme, fontsize: values.fontSize || selectionSize || fontsize }; const props = { ...values, link: newLink, random: nowRandom, theme, fontsize: values.fontSize || selectionSize || fontsize }
const aPath = findNodeWithParent(editor.children, 'chapterTooltip', 'random', nowRandom); const aPath = findNodeWithParent(editor.children, 'chapterTooltip', 'random', nowRandom)
SlateTransforms.setNodes(editor, props, { at: aPath.reverse() }); SlateTransforms.setNodes(editor, props, { at: aPath.reverse() })
message.success('更新成功!'); message.success('更新成功!')
setTooltipVisible(false); setTooltipVisible(false)
setNowRandom(null); setNowRandom(null)
} }
setLoading(false); setLoading(false)
return; return
} }
const text = editor.getSelectionText(); const text = editor.getSelectionText()
let random = Math.random().toString(10).substring(2, 10); let random = Math.random().toString(10).substring(2, 10)
const data = await addChapterTooltip({ const data = await addChapterTooltip({
book_id: bookId, book_id: bookId,
chapter_id: chapterId, chapter_id: chapterId,
position: random, position: random,
link: newLink, link: newLink,
...other, ...other,
type: tooltipType, type: tooltipType
}); })
if (data) { if (data) {
editor.insertNode({ editor.insertNode({
...@@ -114,72 +99,62 @@ const TooltipModal = forwardRef((props, ref) => { ...@@ -114,72 +99,62 @@ const TooltipModal = forwardRef((props, ref) => {
random, random,
theme, theme,
fontsize: values.fontSize || selectionSize || fontsize, fontsize: values.fontSize || selectionSize || fontsize,
children: [{ text: '' }], children: [{ text: '' }]
}); })
message.success('添加成功!'); message.success('添加成功!')
} }
setNowRandom(null); setNowRandom(null)
setTooltipVisible(false); setTooltipVisible(false)
setLoading(false); setLoading(false)
}; }
const deleteNowTooltip = async () => { const deleteNowTooltip = async () => {
const data = await delChapterTooltip({ const data = await delChapterTooltip({
book_id: bookId, book_id: bookId,
chapter_id: chapterId, chapter_id: chapterId,
position: nowRandom, position: nowRandom
}); })
if (data) { if (data) {
editor.restoreSelection(); editor.restoreSelection()
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'chapterTooltip') { if (node.type === 'chapterTooltip') {
return true; // 匹配 chapterToolTip return true // 匹配 chapterToolTip
} }
} }
return false; return false
}, },
universal: true, universal: true
}); })
// 遍历匹配的节点迭代器,获取匹配的节点路径 // 遍历匹配的节点迭代器,获取匹配的节点路径
let nodePaths = []; let nodePaths = []
let nodes = []; let nodes = []
if (nodeEntries === null) { if (nodeEntries === null) {
console.log('当前未选中的 chapterTooltip'); console.log('当前未选中的 chapterTooltip')
} else { } else {
for (let nodeEntry of nodeEntries) { for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry; const [node, path] = nodeEntry
nodePaths.push(path); nodePaths.push(path)
nodes.push(node); nodes.push(node)
} }
// 将属性应用到匹配的节点 // 将属性应用到匹配的节点
for (const path of nodePaths) { for (const path of nodePaths) {
SlateTransforms.removeNodes(editor, { at: path }); SlateTransforms.removeNodes(editor, { at: path })
} }
} }
setNowRandom(null); setNowRandom(null)
setTooltipVisible(false); setTooltipVisible(false)
} }
}; }
return ( return (
<div> <div>
<Divider /> <Divider />
<div className='editor-content-form'> <div className="editor-content-form">
<Form <Form layout="vertical" name="validate_other" form={form} onFinish={onFinish} initialValues={initvalues}>
layout='vertical' <Form.Item label="气泡类型" name="tooltipType" rules={[{ required: true, message: '请选择气泡类型' }]}>
name='validate_other'
form={form}
onFinish={onFinish}
initialValues={initvalues}
>
<Form.Item
label='气泡类型'
name='tooltipType'
rules={[{ required: true, message: '请选择气泡类型' }]}
>
<Select disabled={nowRandom ? true : false}> <Select disabled={nowRandom ? true : false}>
<Select.Option value={1}>文字气泡</Select.Option> <Select.Option value={1}>文字气泡</Select.Option>
<Select.Option value={2}>图标气泡</Select.Option> <Select.Option value={2}>图标气泡</Select.Option>
...@@ -187,82 +162,67 @@ const TooltipModal = forwardRef((props, ref) => { ...@@ -187,82 +162,67 @@ const TooltipModal = forwardRef((props, ref) => {
</Form.Item> </Form.Item>
{tooltipTypeValue === 1 && ( {tooltipTypeValue === 1 && (
<Form.Item <Form.Item
label='气泡标题' label="气泡标题"
name='title' name="title"
rules={[ rules={[{ required: tooltipTypeValue === 1 ? true : false, message: '请输入气泡标题' }]}
{ required: tooltipTypeValue === 1 ? true : false, message: '请输入气泡标题' }, extra="最多输入100字">
]} <Input maxLength={100} placeholder="" allowClear />
extra='最多输入100字'
>
<Input maxLength={100} placeholder='' allowClear />
</Form.Item> </Form.Item>
)} )}
<Form.Item <Form.Item label="描述" name="content" rules={[{ required: true, message: '请输入描述内容' }]}>
label='描述' <Input.TextArea autoSize={{ minRows: 2, maxRows: 4 }} placeholder="" allowClear />
name='content'
rules={[{ required: true, message: '请输入描述内容' }]}
>
<Input.TextArea autoSize={{ minRows: 2, maxRows: 4 }} placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<Form.Item label='字号' name='fontSize'> <Form.Item label="字号" name="fontSize">
<Select> <Select>
{fontFizeList.map((item, index) => { {fontSizeList.map((item, index) => {
return ( return (
<Select.Option value={item.value} key={index}> <Select.Option value={item.value} key={index}>
{item.name} {item.name}
</Select.Option> </Select.Option>
); )
})} })}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={12}> <Col span={12}>
<div className='justcontent-color-inline'> <div className="justcontent-color-inline">
<Form.Item <Form.Item
label='气泡主题色' label="气泡主题色"
name='theme' name="theme"
className='flex-max' className="flex-max"
rules={[ rules={[
{ required: true, message: '请选择颜色' }, { required: true, message: '请选择颜色' },
{ pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }, { pattern: /^#[0-9A-Fa-f]{6}$/i, message: '请输入正确的16进制色值' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item label={` `}> <Form.Item label={` `}>
<ColorPicker <ColorPicker disabledAlpha value={themeValue} defaultValue={themeValue} format="hex" onChange={textColorChange} />
disabledAlpha
value={themeValue}
defaultValue={themeValue}
format='hex'
onChange={textColorChange}
/>
</Form.Item> </Form.Item>
</div> </div>
</Col> </Col>
</Row> </Row>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label='链接(非必须)' label="链接(非必须)"
name='link' name="link"
rules={[ rules={[
{ required: false, message: '请输入气泡内容' }, { required: false, message: '请输入气泡内容' },
{ pattern: /^https*:\/\//gi, message: '链接地址需要http(s)://开头' }, { pattern: /^https*:\/\//gi, message: '链接地址需要http(s)://开头' }
]} ]}>
> <Input placeholder="" allowClear />
<Input placeholder='' allowClear />
</Form.Item> </Form.Item>
<Form.Item className='editor-form-buttons'> <Form.Item className="editor-form-buttons">
<Space> <Space>
{toolStatus && ( {toolStatus && (
<Button type='default' onClick={deleteNowTooltip}> <Button type="default" onClick={deleteNowTooltip}>
删除 删除
</Button> </Button>
)} )}
<Button type='primary' loading={loading} htmlType='submit'> <Button type="primary" loading={loading} htmlType="submit">
{nowRandom ? '更新' : '插入'} {nowRandom ? '更新' : '插入'}
</Button> </Button>
</Space> </Space>
...@@ -270,7 +230,7 @@ const TooltipModal = forwardRef((props, ref) => { ...@@ -270,7 +230,7 @@ const TooltipModal = forwardRef((props, ref) => {
</Form> </Form>
</div> </div>
</div> </div>
); )
}); })
export default TooltipModal; export default TooltipModal
import { DomEditor, SlateTransforms, SlateRange } from '@wangeditor/editor'; import { DomEditor, SlateTransforms, SlateRange } from '@wangeditor/editor'
class ChapterItem { class ChapterItem {
constructor() { constructor() {
this.title = '章节' this.title = '节头'
this.iconSvg = `<svg width="22px" height="23px" viewBox="0 0 22 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> this.iconSvg = `<svg width="22px" height="23px" viewBox="0 0 22 23" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>图标</title> <title>图标</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
...@@ -14,14 +14,14 @@ class ChapterItem { ...@@ -14,14 +14,14 @@ class ChapterItem {
</g> </g>
</g> </g>
</g> </g>
</svg>`; </svg>`
this.tag = 'button' this.tag = 'button'
} }
getValue(editor) { getValue(editor) {
return 'hello, 图片, , , 图片' return 'hello, 图片, , , 图片'
} }
isActive(editor) { isActive(editor) {
return false; return false
} }
isDisabled(editor) { isDisabled(editor) {
const { selection } = editor const { selection } = editor
...@@ -35,7 +35,7 @@ class ChapterItem { ...@@ -35,7 +35,7 @@ class ChapterItem {
// eslint-disable-next-line array-callback-return // eslint-disable-next-line array-callback-return
const hasPreElem = selectedElems.some(elem => { const hasPreElem = selectedElems.some(elem => {
const type = DomEditor.getNodeType(elem); const type = DomEditor.getNodeType(elem)
// 代码块 引用 表格 禁用 // 代码块 引用 表格 禁用
if (type === 'pre' || type === 'blockquote' || type === 'table' || type === 'table-row' || type === 'table-cell') return true if (type === 'pre' || type === 'blockquote' || type === 'table' || type === 'table-row' || type === 'table-cell') return true
}) })
...@@ -45,10 +45,10 @@ class ChapterItem { ...@@ -45,10 +45,10 @@ class ChapterItem {
} }
exec(editor, value) { exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值 // editor.insertText(value) // value 即 this.getValue(editor) 的返回值
if(this.isDisabled(editor)){ if (this.isDisabled(editor)) {
return; return
} }
editor.emit('ChapterItemMenuClick'); editor.emit('ChapterItemMenuClick')
} }
} }
...@@ -56,9 +56,7 @@ export default { ...@@ -56,9 +56,7 @@ export default {
key: 'ChapterItem', // 定义 menu key :要保证唯一、不重复(重要) key: 'ChapterItem', // 定义 menu key :要保证唯一、不重复(重要)
factory() { factory() {
return new ChapterItem() // 把 `YourMenuClass` 替换为你菜单的 class return new ChapterItem() // 把 `YourMenuClass` 替换为你菜单的 class
}, }
} }
export { export { ChapterItem }
ChapterItem
}
\ No newline at end of file
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
import { Input, Spin, message, Button } from 'antd';
import { EyeOutlined } from '@ant-design/icons';
import AliOSS from 'ali-oss';
import dayjs from 'dayjs';
import { Boot } from '@wangeditor/editor';
import { useSelector } from 'react-redux';
import { SlateEditor, SlateNode, SlateElement, SlateText } from '@wangeditor/editor';
import { Editor, Toolbar } from '@wangeditor/editor-for-react';
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import './node/index.less';
import timesave from '@/assets/images/timesave.png';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
import { storageChange } from '@/utils/storage.js';
import linkCardModule from '@wangeditor/plugin-link-card';
import PaddingSpace from './customer/padding';
import ImageAutoConf from './customer/Image';
import GalleryAutoConf from './customer/Gallery';
import VideoAutoConf from './customer/Video';
import AudioAutoConf from './customer/Audio';
import ChapterTitleAutoConf from './customer/ChapterTitle';
import ChapterItemAutoConf from './customer/ChapterItem';
import PracticeAutoConf from './customer/Practice';
import FormulaAutoConf from './customer/Formula';
import TooltipAutoConf from './customer/Tooltip';
import ImageModal from './components/image';
import VideoModal from './components/video';
import GalleryModal from './components/gallery';
import AudioModal from './components/audio';
import ChapterTitleModal from './components/chapter-title';
import ChapterItemModal from './components/chapter-item';
import PracticeModal from './components/practice';
import FormulaModal from './components/formula';
import TooltipModal from './components/tooltip';
import chapterSectionModule from './node/chapterItem';
import chapterHeaderModule from './node/chapterTitle';
import chapterImageModule from './node/image';
import chapterVideoModule from './node/video';
import chapterAudioModule from './node/audio';
import chapterGalleryModule from './node/gallery';
import chapterPracticeModule from './node/practice';
import formulaModule from './node/formula';
import tooltipModule from './node/tooltip';
import PracticeSettingModal from './practice/index';
import $ from 'jquery';
import { getInfoByChapterId } from '@/pages/books/section/request';
import { getAliOSSSTSToken } from './utils/request';
import './index.less';
const menuArr1 = ['字体样式', '字号'];
const menuArr2 = ['图片', '画廊', '视频', '音频'];
const menuArr3 = ['代码块', '链接', '引用', '公式', '章头', '章节', '交互练习', '气泡'];
const colorList = ['#ab1941', '#2970f6', '#2ad882', '#eb3351'];
const bookBucketName = 'zxts-book-file';
const module = {
menus: [
ImageAutoConf,
GalleryAutoConf,
VideoAutoConf,
AudioAutoConf,
FormulaAutoConf,
TooltipAutoConf,
ChapterTitleAutoConf,
ChapterItemAutoConf,
PracticeAutoConf,
],
};
Boot.registerModule(module);
// 注册节头内容
Boot.registerModule(chapterSectionModule);
// 注册章头内容
Boot.registerModule(chapterHeaderModule);
// 注册图片内容
Boot.registerModule(chapterImageModule);
// 注册视频内容
Boot.registerModule(chapterVideoModule);
// 注册音频内容
Boot.registerModule(chapterAudioModule);
// 注册画廊
Boot.registerModule(chapterGalleryModule);
// 注册公式
Boot.registerModule(formulaModule);
// 注册练习
Boot.registerModule(chapterPracticeModule);
// 注册气泡
Boot.registerModule(tooltipModule);
Boot.registerModule(linkCardModule);
const tabsMenu = [
{ key: 'text', title: '文本设置' },
{ key: 'style', title: '样式模版' },
];
storageChange();
const WangEditorCustomer = forwardRef((props, ref) => {
const { chapterId, bookId, contentId, html, setHtml, saveContent } = props;
// 自动保存时间
const { autosaveTime } = useSelector((state) => state.editor);
const toolbarRef = useRef();
const paddingSpaceRef = useRef();
const [cId, setCId] = useState(contentId);
const [ossClient, setOssClient] = useState(null); // oss 操作
const [STSToken, setSTSToken] = useState(null); // oss 过期设置
const [tabKey, setTabKey] = useState('text');
const [loading, setLoading] = useState(true);
// editor 实例
const [editor, setEditor] = useState(null);
const [editorNodes, setEditorNodes] = useState(null);
const [content, setContent] = useState(html);
const saveRef = useRef();
// 工具栏配置
const toolbarConfig = {
toolbarKeys: [
// '|',
// 'redo',
// 'undo',
// 'emotion',
// 'todo',
// 'fullScreen',
// '|',
// 'headerSelect',
// 'lineHeight',
// '|',
'fontFamily',
// 'uploadImage',
'fontSize',
'|',
'justifyLeft',
'justifyRight',
'justifyCenter',
'justifyJustify',
'divider',
'|',
'lineHeight',
'|',
'bold',
'italic',
'through',
'underline',
'sub',
'sup',
'color',
'bgColor',
'bulletedList',
'numberedList',
'indent',
'delIndent',
'|',
// 'insertImage',
// 'uploadImage',
// 'insertVideo',
// 'uploadVideo',
'insertTable',
'|',
'codeBlock', // 代码块
'insertLink', // 链接
// 'insertFormula', // 公式
'blockquote', // 引用
// 'code',
// 'clearStyle',
],
};
useImperativeHandle(ref, () => {
return {
editor,
};
});
const imageRef = useRef();
const galleryRef = useRef();
const videoRef = useRef();
const audioRef = useRef();
const formulaRef = useRef();
const tooltipRef = useRef();
const chapterTitleRef = useRef();
const chapterItemRef = useRef();
const practiceRef = useRef();
const practiceSettingRef = useRef();
toolbarConfig.insertKeys = {
index: 30,
keys: [
'ImageAuto',
'GalleryAuto',
'VideoAuto',
'AudioAuto',
'FormulaAuto',
'ChapterTitle',
'ChapterItem',
'Practice',
'TooltipAuto',
],
};
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...',
// 选中公式时的悬浮菜单
hoverbarKeys: {
// formula: {
// menuKeys: ['editFormula'], // “编辑公式”菜单
// },
// 在编辑器中,选中链接文本时,要弹出的菜单
link: {
menuKeys: [
'editLink',
'unLink',
'viewLink', // 默认的配置可以通过 `editor.getConfig().hoverbarKeys.link` 获取
'convertToLinkCard', // 增加 '转为链接卡片'菜单
],
},
MENU_CONF: {
// '转为链接卡片'菜单的配置
convertToLinkCard: {
// 自定义获取 link-card 信息,可选
// 返回 { title, iconImgSrc }
async getLinkCardInfo(linkText, linkUrl) {
// 1. 可通过 iframe 加载网页,然后获取网页 title 和其中的图片
// 2. 服务端获取(有些网页会设置 `X-Frame-Options` ,无法通过 iframe 加载)
// // 模拟异步返回
// return new Promise(resolve => {
// setTimeout(() => {
// const info = { title: linkText, iconImgSrc: '' }
// resolve(info)
// }, 100)
// })
},
},
// 其他...
},
},
};
// 编辑器按钮重排
const toolSetttingReplace = () => {
setTimeout(() => {
const toolbarElement = toolbarRef.current && toolbarRef.current.children[1].children[0];
const allChildren = toolbarElement.children;
const oH6_1 = document.createElement('h4');
oH6_1.setAttribute('class', 'w-auto type-heading');
oH6_1.innerHTML = '常用格式';
toolbarElement.insertBefore(oH6_1, allChildren[0]);
const oHDiv_3 = document.createElement('div');
oHDiv_3.setAttribute('class', 'custom-bar-box two');
toolbarElement.insertBefore(oHDiv_3, allChildren[1]);
$(allChildren[1]).append($(allChildren[2]).detach());
$(allChildren[1]).append($(allChildren[2]).detach());
const oH6_2 = document.createElement('h4');
oH6_2.setAttribute('class', 'w-auto type-heading');
oH6_2.innerHTML = '媒体资源';
toolbarElement.insertBefore(oH6_2, allChildren[22]);
const oHDiv_1 = document.createElement('div');
oHDiv_1.setAttribute('class', 'custom-bar-box');
toolbarElement.insertBefore(oHDiv_1, allChildren[23]);
const itemBox = '<div class="w-e-bar-boxitem"></div>';
$(itemBox).insertBefore($(allChildren[4]));
const itemBox2 = '<div class="w-e-bar-boxitem you"></div>';
$(itemBox2).insertBefore($(allChildren[10]));
$(allChildren[4]).append($(allChildren[3]).detach());
$(allChildren[3]).append($(allChildren[5]).detach());
$(allChildren[3]).append($(allChildren[5]).detach());
$(allChildren[3]).append($(allChildren[5]).detach());
$(allChildren[3]).append($(allChildren[4]).detach());
const oHDivInput_1 = document.createElement('div');
oHDivInput_1.setAttribute('class', 'custom-bar-box input');
oHDivInput_1.setAttribute('id', 'custom-bar-box-input');
toolbarElement.insertBefore(oHDivInput_1, allChildren[4]);
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[6]).append($(allChildren[7]).detach());
$(allChildren[9]).append($(allChildren[15]).detach());
$(allChildren[9]).append($(allChildren[15]).detach());
$(allChildren[9]).append($(allChildren[15]).detach());
$(allChildren[9]).append($(allChildren[15]).detach());
$(allChildren[9]).append($(allChildren[10]).detach());
const oH6_3 = document.createElement('h4');
oH6_3.setAttribute('class', 'w-auto type-heading');
oH6_3.innerHTML = '高级模块';
toolbarElement.insertBefore(oH6_3, allChildren[11]);
const oHDiv_2 = document.createElement('div');
oHDiv_2.setAttribute('class', 'custom-bar-box');
toolbarElement.insertBefore(oHDiv_2, allChildren[12]);
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$(allChildren[12]).append($(allChildren[13]).detach());
$('.custom-bar-box').each((index, item) => {
$(item)
.find('.w-e-bar-item')
.each((cIndex, cItem) => {
if (index === 2 && cIndex === 4) {
$(cItem).hide();
} else {
const oP = document.createElement('p');
if (index === 0) {
$(oP).insertBefore($(cItem).find('button'));
oP.innerHTML = menuArr1[cIndex];
} else if (index === 2) {
oP.innerHTML = menuArr2[cIndex];
$(cItem).append(oP);
} else if (index === 3) {
oP.innerHTML = menuArr3[cIndex];
$(cItem).append(oP);
}
}
});
});
setLoading(false);
}, 200);
};
editorConfig.onCreated = (editor) => {
setLoading(true);
toolSetttingReplace();
};
editorConfig.onFocus = (editor) => {
clearTimeout(saveRef.current);
};
editorConfig.onBlur = (editor) => {
// 失焦保存
// setHtml(editor.getHtml());
saveContent();
};
editorConfig.onChange = (editor) => {
// setHtml(editor.getHtml());
// clearTimeout(saveRef.current);
// saveRef.current = setTimeout(() => {
// saveContent();
// }, 800);
};
// 及时销毁 editor ,重要!
useEffect(() => {
if (editor) {
// 图片上传
editor.on('ImageMenuClick', () => {
console.log('ImageMenuClick', '----');
imageRef.current.setVisible(true);
});
// 画廊上传
editor.on('GalleryMenuClick', () => {
console.log('GalleryMenuClick', '----');
galleryRef.current.setVisible(true);
});
// 视频上传
editor.on('VideoMenuClick', () => {
console.log('VideoMenuClick', '----');
videoRef.current.setVisible(true);
});
// 音频上传
editor.on('AudioMenuClick', () => {
console.log('AudioMenuClick', '----');
audioRef.current.setVisible(true);
});
// 章节
editor.on('ChapterItemMenuClick', () => {
console.log('ChapterItemMenuClick', '----');
chapterItemRef.current.setVisible(true);
});
// 章头
editor.on('ChapterTitleClick', () => {
console.log('ChapterTitleClick', '----');
chapterTitleRef.current.setVisible(true);
});
// 交互练习
editor.on('PracticeMenuClick', () => {
console.log('PracticeMenuClick', '----');
practiceRef.current.setVisible(true);
});
// 公式
editor.on('FormulaMenuClick', () => {
console.log('FormulaMenuClick', '----');
formulaRef.current.setVisible(true);
});
// 气泡
editor.on('TooltipMenuClick', () => {
if (editor.getSelectionText()) {
const textSelection = editor.selection;
if (textSelection.anchor.path[0] === textSelection.focus.path[0]) {
tooltipRef.current.setVisible(true);
} else {
message.error('气泡操作不能跨段落进行!');
}
} else {
message.error('气泡操作需要选中内容!');
}
});
}
return () => {
if (editor === null) return;
editor.destroy();
setEditor(null);
};
}, [editor]);
useEffect(() => {
if (editor && html) {
setContent(html);
}
}, [html, editor]);
const tabKeyChange = (key) => {
if (key === 'text') {
toolSetttingReplace();
}
setTabKey(key);
};
const getStsAuthToken = async () => {
const data = await getAliOSSSTSToken();
if (data) {
window.sessionStorage.setItem('sts', JSON.stringify(data));
setSTSToken(data);
const ossClientTemp = await new AliOSS({
accessKeyId: data.AccessKeyId,
accessKeySecret: data.AccessKeySecret,
stsToken: data.SecurityToken,
endpoint: 'zijingebook.com',
// region: 'cn-beijing',
// endpoint: data.endpoint,
bucket: bookBucketName,
timeout: 180000,
refreshSTSToken: async () => {
const info = await getAliOSSSTSToken();
return {
AccessKeyId: info.AccessKeyId,
AccessKeySecret: info.AccessKeySecret,
SecurityToken: info.SecurityToken,
};
},
refreshSTSTokenInterval: 14 * 60 * 1000,
});
setOssClient(ossClientTemp);
}
};
useEffect(() => {
(async () => {
const tempStsToken = window.sessionStorage ? window.sessionStorage.getItem('sts') : '';
try {
const stsToken = JSON.parse(tempStsToken);
// 15 分钟过期
if (dayjs(stsToken.Expiration).valueOf() - dayjs().valueOf() >= 14 * 60 * 1000) {
getStsAuthToken();
} else {
const ossClientTemp = await new AliOSS({
accessKeyId: data.AccessKeyId,
accessKeySecret: data.AccessKeySecret,
stsToken: data.SecurityToken,
// endpoint: data.endpoint,
// region: 'cn-beijing',
endpoint: 'zijingebook.com',
bucket: bookBucketName,
timeout: 180000,
refreshSTSToken: async () => {
const info = await getAliOSSSTSToken();
return {
AccessKeyId: info.AccessKeyId,
AccessKeySecret: info.AccessKeySecret,
SecurityToken: info.SecurityToken,
};
},
refreshSTSTokenInterval: 14 * 60 * 1000,
});
setOssClient(ossClientTemp);
setSTSToken(stsToken);
}
} catch (e) {
getStsAuthToken();
}
})();
}, []);
const setColor = (type) => {
$(`#chapter-item-header`).css({ color: '#fff', backgroundColor: colorList[type - 1] });
$(`#chapter-item-section`).css({ color: '#fff', backgroundColor: colorList[type - 1] });
};
return (
<div className='wangeditor-customer-container'>
<div
style={{ height: 'calc(100vh - 250px)', overflowY: 'hidden' }}
className='editor-content-container'
>
<div className='title-head'>
<div className='right'>
<div className='save-time time-on'>
<Button
danger
type='link'
className='timepsave'
onClick={saveContent}
icon={<img src={timesave} />}
>
自动保存
</Button>
<span className='time'>{autosaveTime > 0 ? dayjs(autosaveTime).fromNow() : ''}</span>
</div>
<div className='save-time'>
<Button icon={<EyeOutlined />} className='view'>
预览
</Button>
<span className='time'>&nbsp;</span>
</div>
</div>
</div>
<Spin spinning={loading} className='editor-content-container-loading'>
<Editor
defaultConfig={editorConfig}
value={content}
onCreated={setEditor}
// onChange={(editor) => setHtml(editor.getHtml())}
mode='default'
style={{ height: 'calc(100vh - 250px)', overflowY: 'hidden' }}
/>
</Spin>
</div>
{/* <CustomerMenu editor={editor} /> */}
<div className='menu-tabs-key'>
<div className='tabs'>
{tabsMenu &&
tabsMenu.length &&
tabsMenu.map((item) => {
return (
<div
className={`tabs-item ${item.key === tabKey ? 'active' : ''}`}
key={item.key}
onClick={() => tabKeyChange(item.key)}
>
{item.title}
<span></span>
</div>
);
})}
</div>
<div className='menu-tabs-content'>
{tabKey === 'text' && (
<div ref={toolbarRef} className='toolbox-parent'>
<PaddingSpace ref={paddingSpaceRef} editor={editor} editorNodes={editorNodes} />
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode='default'
style={{ borderBottom: '1px solid #ccc' }}
className='editor-toolbar-container'
></Toolbar>
</div>
)}
{tabKey === 'style' && (
<div className='styletem'>
<p>样式模板</p>
<ul>
<li>
<div className='left'>
<span className='color color1'></span>
<b className='type'>默认</b>
</div>
<Button type='link' className='use' onClick={() => setColor(1)}>
使用
</Button>
</li>
<li>
<div className='left'>
<span className='color color2'></span>
<b className='type'>蓝色</b>
</div>
<Button type='link' className='use' onClick={() => setColor(2)}>
使用
</Button>
</li>
<li>
<div className='left'>
<span className='color color3'></span>
<b className='type'>绿色</b>
</div>
<Button type='link' className='use' onClick={() => setColor(3)}>
使用
</Button>
</li>
<li>
<div className='left'>
<span className='color color4'></span>
<b className='type'>红色</b>
</div>
<Button type='link' className='use' onClick={() => setColor(4)}>
使用
</Button>
</li>
</ul>
</div>
)}
</div>
</div>
<ImageModal ref={imageRef} editor={editor} ossClient={ossClient} STSToken={STSToken} />
<VideoModal ref={videoRef} editor={editor} ossClient={ossClient} STSToken={STSToken} />
<GalleryModal ref={galleryRef} editor={editor} ossClient={ossClient} STSToken={STSToken} />
<AudioModal ref={audioRef} editor={editor} ossClient={ossClient} STSToken={STSToken} />
<ChapterTitleModal
ref={chapterTitleRef}
editor={editor}
ossClient={ossClient}
STSToken={STSToken}
/>
<ChapterItemModal
ref={chapterItemRef}
editor={editor}
ossClient={ossClient}
STSToken={STSToken}
/>
<PracticeModal
ref={practiceRef}
chapterId={chapterId}
bookId={bookId}
editor={editor}
ossClient={ossClient}
STSToken={STSToken}
/>
<FormulaModal ref={formulaRef} editor={editor} ossClient={ossClient} STSToken={STSToken} />
<TooltipModal
ref={tooltipRef}
editor={editor}
chapterId={chapterId}
bookId={bookId}
ossClient={ossClient}
STSToken={STSToken}
/>
<PracticeSettingModal
ref={practiceSettingRef}
nodes={practiceRef.current && practiceRef.current.nodes ? practiceRef.current.nodes : null}
chapterId={chapterId}
bookId={bookId}
editor={editor}
/>
</div>
);
});
export default WangEditorCustomer;
...@@ -13,7 +13,7 @@ import { Boot } from '@wangeditor/editor' ...@@ -13,7 +13,7 @@ import { Boot } from '@wangeditor/editor'
import { SlateEditor, DomEditor, SlateElement, SlateTransforms } from '@wangeditor/editor' import { SlateEditor, DomEditor, SlateElement, SlateTransforms } from '@wangeditor/editor'
import { Editor, Toolbar } from '@wangeditor/editor-for-react' import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import { fontFamilyList } from './utils/setting' import { fontFamilyList, fontSizeList, lineHeightList } from './utils/setting'
import '@wangeditor/editor/dist/css/style.css' // 引入 css import '@wangeditor/editor/dist/css/style.css' // 引入 css
import timesave from '@/assets/images/timesave.png' import timesave from '@/assets/images/timesave.png'
...@@ -22,8 +22,6 @@ dayjs.extend(relativeTime) ...@@ -22,8 +22,6 @@ dayjs.extend(relativeTime)
import { storageChange } from '@/utils/storage.js' import { storageChange } from '@/utils/storage.js'
// import PaddingSpace from './customer/padding';
import ImageAutoOnlineConf from './customer/ImageOnline' import ImageAutoOnlineConf from './customer/ImageOnline'
import GalleryAutoConf from './customer/Gallery' import GalleryAutoConf from './customer/Gallery'
import GalleryAutoOnlineConf from './customer/GalleryOnline' import GalleryAutoOnlineConf from './customer/GalleryOnline'
...@@ -37,11 +35,18 @@ import TooltipAutoConf from './customer/Tooltip' ...@@ -37,11 +35,18 @@ import TooltipAutoConf from './customer/Tooltip'
import ImageEditorConf from './customer/ImageEditor' import ImageEditorConf from './customer/ImageEditor'
import CustomerLinkConf from './customer/CustomerLink' import CustomerLinkConf from './customer/CustomerLink'
import ExpandReadConf from './customer/ExpandRead' import ExpandReadConf from './customer/ExpandRead'
import PolishingConf from './ai/Polishing'
import ExpandArticleConf from './ai/ExpandArticle' // AI对话
import RewriteConf from './ai/Rewrite' import AIChat from './menu/AIChat'
import SummaryConf from './ai/Summary'
import AISelectTextRef from './ai/AI' // AI辅助
import AIRewrite from './menu/AIRewrite'
import AIExpand from './menu/AIExpand'
import AISummary from './menu/AISummary'
import AIPolishing from './menu/AIPolishing'
import AIPunctuation from './menu/AIPunctuation'
import AIContentInspect from './menu/AIContentInspect'
import AIDigitalHuman from './menu/AIDigitalHuman'
import ImageModal from './components/image' import ImageModal from './components/image'
import VideoModal from './components/video' import VideoModal from './components/video'
...@@ -54,8 +59,6 @@ import FormulaModal from './components/formula' ...@@ -54,8 +59,6 @@ import FormulaModal from './components/formula'
import TooltipModal from './components/tooltip' import TooltipModal from './components/tooltip'
import LinkModal from './components/link' import LinkModal from './components/link'
import ExpandModal from './components/expand' import ExpandModal from './components/expand'
import AIDrawerComponent from './ai-drawer/index'
import AIWrite from './components/aiWrite'
import chapterSectionModule from './node/chapterItem' import chapterSectionModule from './node/chapterItem'
import chapterHeaderModule from './node/chapterTitle' import chapterHeaderModule from './node/chapterTitle'
...@@ -79,13 +82,6 @@ import $ from 'jquery' ...@@ -79,13 +82,6 @@ import $ from 'jquery'
import { getAliOSSSTSToken } from './utils/request' import { getAliOSSSTSToken } from './utils/request'
import './index.less' import './index.less'
const menuArr0 = ['重做', '撤销']
const menuArr1 = ['字体样式', '字号', '行高']
const menuArr2 = ['离线图片', '在线图片', '离线画廊', '在线画廊', '视频', '音频', '表格']
const menuArr3 = ['代码块', '引用', '链接', '公式', '章头', '节头', '交互练习', '气泡', '扩展阅读']
const menuArr4 = ['改写', '扩写', '缩写', '总结']
const colorList = ['#ab1941', '#2970f6', '#2ad882', '#eb3351']
const bookBucketName = 'zxts-book-file' const bookBucketName = 'zxts-book-file'
const module = { const module = {
...@@ -104,11 +100,14 @@ const module = { ...@@ -104,11 +100,14 @@ const module = {
ImageEditorConf, ImageEditorConf,
CustomerLinkConf, CustomerLinkConf,
ExpandReadConf, ExpandReadConf,
PolishingConf, AIChat,
ExpandArticleConf, AIRewrite,
RewriteConf, AIExpand,
SummaryConf, AISummary,
AISelectTextRef AIPolishing,
AIPunctuation,
AIContentInspect,
AIDigitalHuman
] ]
} }
Boot.registerModule(module) Boot.registerModule(module)
...@@ -149,7 +148,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -149,7 +148,7 @@ const WangEditorCustomer = (props, ref) => {
const dispatch = useDispatch() const dispatch = useDispatch()
// 自动保存时间 // 自动保存时间
const { autosaveTime } = useSelector((state) => state.editor) const { autosaveTime } = useSelector(state => state.editor)
const toolbarRef = useRef() const toolbarRef = useRef()
...@@ -157,7 +156,6 @@ const WangEditorCustomer = (props, ref) => { ...@@ -157,7 +156,6 @@ const WangEditorCustomer = (props, ref) => {
const [STSToken, setSTSToken] = useState(null) // oss 过期设置 const [STSToken, setSTSToken] = useState(null) // oss 过期设置
const [tabKey, setTabKey] = useState('text') const [tabKey, setTabKey] = useState('text')
const [loading, setLoading] = useState(true)
// editor 实例 // editor 实例
const [editor, setEditor] = useState(null) const [editor, setEditor] = useState(null)
const [content, setContent] = useState(html) const [content, setContent] = useState(html)
...@@ -183,7 +181,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -183,7 +181,7 @@ const WangEditorCustomer = (props, ref) => {
const [aiVisible, setAIVisible] = useState(false) // ai对话弹窗 const [aiVisible, setAIVisible] = useState(false) // ai对话弹窗
const [priviewVisible, setPriviewVisible] = useState(false) const [priviewVisible, setPreviewVisible] = useState(false)
const [historyVisible, setHistoryVisible] = useState(false) //点击历史 const [historyVisible, setHistoryVisible] = useState(false) //点击历史
const [selectionSize, setSelectionSize] = useState(16) // 当前字号大小 const [selectionSize, setSelectionSize] = useState(16) // 当前字号大小
...@@ -248,7 +246,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -248,7 +246,7 @@ const WangEditorCustomer = (props, ref) => {
} }
}) })
const listenNodeStyle = (path) => { const listenNodeStyle = path => {
const children = editor.children const children = editor.children
let node = null let node = null
if (path[1] === 0) { if (path[1] === 0) {
...@@ -303,16 +301,6 @@ const WangEditorCustomer = (props, ref) => { ...@@ -303,16 +301,6 @@ const WangEditorCustomer = (props, ref) => {
// 工具栏配置 // 工具栏配置
const toolbarConfig = { const toolbarConfig = {
toolbarKeys: [ toolbarKeys: [
// '|',
// 'redo',
// 'undo',
// 'emotion',
// 'todo',
// 'fullScreen',
// '|',
// 'headerSelect',
// 'lineHeight',
// '|',
'redo', 'redo',
'undo', 'undo',
'|', '|',
...@@ -337,18 +325,32 @@ const WangEditorCustomer = (props, ref) => { ...@@ -337,18 +325,32 @@ const WangEditorCustomer = (props, ref) => {
'justifyJustify', 'justifyJustify',
'divider', 'divider',
'|', '|',
// 'insertImage', 'ImageAuto',
// 'uploadImage', 'ImageAutoOnline',
// 'insertVideo', 'GalleryAuto',
// 'uploadVideo', 'GalleryAutoOnline',
'VideoAuto',
'AudioAuto',
'insertTable', 'insertTable',
'|', '|',
'codeBlock', // 代码块 'codeBlock', // 代码块
// 'insertLink', // 链接 'blockquote', // 引用
// 'insertFormula', // 公式 'CustomerLink',
'blockquote' // 引用 'FormulaAuto',
// 'code', 'ChapterTitle',
// 'clearStyle', 'ChapterItem',
'Practice',
'TooltipAuto',
'ExpandRead',
'|',
'AIRewrite',
'AIExpand',
'AISummary',
'AIPolishing',
'AIPunctuation',
'AIContentInspect',
'|',
'AIDigitalHuman'
] ]
} }
...@@ -372,47 +374,12 @@ const WangEditorCustomer = (props, ref) => { ...@@ -372,47 +374,12 @@ const WangEditorCustomer = (props, ref) => {
} }
] ]
toolbarConfig.insertKeys = {
index: 30,
keys: [
'ImageAuto',
'ImageAutoOnline',
'GalleryAuto',
'GalleryAutoOnline',
'VideoAuto',
'AudioAuto',
'CustomerLink',
'FormulaAuto',
'ChapterTitle',
'ChapterItem',
'Practice',
'TooltipAuto',
'ExpandRead',
'RewriteAuto',
'ExpandArticleAuto',
'PolishingAuto',
'SummaryAuto'
]
}
// 编辑器配置 // 编辑器配置
let editorConfig = { let editorConfig = {
placeholder: '请输入内容...', placeholder: '请输入内容...',
// 选中公式时的悬浮菜单
hoverbarKeys: { hoverbarKeys: {
// formula: {
// menuKeys: ['editFormula'], // “编辑公式”菜单
// },
// link: {
// menuKeys: [
// 'editLink',
// 'unLink',
// 'viewLink', // 默认的配置可以通过 `editor.getConfig().hoverbarKeys.link` 获取
// ],
// },
text: { text: {
menuKeys: [ menuKeys: [
// 'headerSelect',
'CustomerLink', 'CustomerLink',
'bulletedList', 'bulletedList',
'numberedList', 'numberedList',
...@@ -427,196 +394,58 @@ const WangEditorCustomer = (props, ref) => { ...@@ -427,196 +394,58 @@ const WangEditorCustomer = (props, ref) => {
'color', 'color',
'bgColor', 'bgColor',
'clearStyle', 'clearStyle',
'AISelectTextAuto' 'AIChat'
] ]
}, },
image: { image: {
menuKeys: [ menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'ImageEditor', 'deleteImage']
'imageWidth30',
'imageWidth50',
'imageWidth100',
'ImageEditor',
// 'viewImageLink',
'deleteImage'
]
}, },
ImageAuto: { ImageAuto: {
menuKeys: ['imageWidthChpater100', 'imageWidthChpater50', 'imageWidthChpater30', 'convertToLinkCard'] menuKeys: ['imageWidthChpater100', 'imageWidthChpater50', 'imageWidthChpater30', 'convertToLinkCard']
} }
}, },
MENU_CONF: { MENU_CONF: {
fontSize: { fontSize: { fontSizeList },
fontSizeList: [ fontFamily: { fontFamilyList },
{ name: '初号', value: '56px' }, lineHeight: { lineHeightList }
{ name: '小初', value: '48px' },
{ name: '一号', value: '34px' },
{ name: '小一', value: '32px' },
{ name: '二号', value: '29px' },
{ name: '小二', value: '24px' },
{ name: '三号', value: '21px' },
{ name: '小三', value: '20px' },
{ name: '四号', value: '18px' },
{ name: '小四', value: '16px' },
{ name: '五号', value: '14px' },
{ name: '小五', value: '12px' },
{ name: '六号', value: '10px' },
{ name: '小六', value: '8px' },
{ name: '七号', value: '7px' },
{ name: '八号', value: '6px' }
]
},
fontFamily: {
fontFamilyList: fontFamilyList
},
lineHeight: {
lineHeightList: ['1', '1.25', '1.5', '2', '2.5', '3']
}
// 其他...
} }
} }
// 编辑器按钮重排 // 编辑器按钮重排
const toolSetttingReplace = () => { const toolSettingReplace = () => {
setTimeout(() => { setTimeout(() => {
const toolbarElement = toolbarRef.current && toolbarRef.current.children[0].children[0] const editorToolbar = document.querySelector('.editor-toolbar-container')
const allChildren = toolbarElement.children // 设置菜单模块标题
const oHDiv_1 = document.createElement('div') const dividerElements = editorToolbar.querySelectorAll('.w-e-bar-divider')
oHDiv_1.setAttribute('class', 'custom-bar-box two') const dividerTitles = ['常用格式', '媒体资源', '高级模块', 'AI辅助', 'AI数字人']
toolbarElement.insertBefore(oHDiv_1, allChildren[0]) dividerElements.forEach((element, index) => {
$(allChildren[0]).append($(allChildren[1]).detach()) element.innerHTML = dividerTitles[index]
$(allChildren[0]).append($(allChildren[1]).detach())
const oH6_1 = document.createElement('h4')
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]))
$(allChildren[3]).append($(allChildren[4]).detach())
$(allChildren[3]).append($(allChildren[4]).detach())
$(allChildren[3]).append($(allChildren[4]).detach())
// 加粗之类的
const itemBox2 = '<div class="w-e-bar-boxitem you"></div>'
$(itemBox2).insertBefore($(allChildren[4]))
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
$(allChildren[4]).append($(allChildren[5]).detach())
// 对齐
const itemBox3 = '<div class="w-e-bar-boxitem you"></div>'
$(itemBox3).insertBefore($(allChildren[5]))
$(allChildren[5]).append($(allChildren[6]).detach())
$(allChildren[5]).append($(allChildren[6]).detach())
$(allChildren[5]).append($(allChildren[6]).detach())
$(allChildren[5]).append($(allChildren[6]).detach())
$(allChildren[5]).append($(allChildren[6]).detach())
// 媒体资源
const oH6_2 = document.createElement('h4')
oH6_2.setAttribute('class', 'w-auto type-heading')
oH6_2.innerHTML = '媒体资源'
toolbarElement.insertBefore(oH6_2, allChildren[7])
const itemBox4 = document.createElement('div')
itemBox4.setAttribute('class', 'custom-bar-box media')
toolbarElement.insertBefore(itemBox4, allChildren[8])
console.log($(allChildren[8]), $(allChildren[13]), $(allChildren[9]))
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[13]).detach())
$(allChildren[8]).append($(allChildren[9]).detach())
// 高级模块
const oH6_3 = document.createElement('h4')
oH6_3.setAttribute('class', 'w-auto type-heading')
oH6_3.innerHTML = '高级模块'
toolbarElement.insertBefore(oH6_3, allChildren[10])
const itemBox5 = document.createElement('div')
itemBox5.setAttribute('class', 'custom-bar-box hight')
toolbarElement.insertBefore(itemBox5, allChildren[11])
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
$(allChildren[11]).append($(allChildren[12]).detach())
// ai
const dividerline = document.createElement('div')
dividerline.setAttribute('class', 'w-e-bar-divider')
toolbarElement.insertBefore(dividerline, allChildren[12])
const oH6_4 = document.createElement('h4')
oH6_4.setAttribute('class', 'w-auto type-heading')
oH6_4.innerHTML = 'AI辅助'
toolbarElement.insertBefore(oH6_4, allChildren[13])
const oHDiv_4 = document.createElement('div')
oHDiv_4.setAttribute('class', 'custom-bar-box hight')
toolbarElement.insertBefore(oHDiv_4, allChildren[14])
$(allChildren[14]).append($(allChildren[15]).detach())
$(allChildren[14]).append($(allChildren[15]).detach())
$(allChildren[14]).append($(allChildren[15]).detach())
$(allChildren[14]).append($(allChildren[15]).detach())
$('.custom-bar-box').each((index, item) => {
$(item)
.find('.w-e-bar-item')
.each((cIndex, cItem) => {
const oP = document.createElement('p')
if (index === 0) {
oP.innerHTML = menuArr0[cIndex]
$(oP).insertBefore($(cItem).find('button'))
} else if (index === 1) {
oP.innerHTML = menuArr1[cIndex]
$(oP).insertBefore($(cItem).find('button'))
} else if (index === 2) {
oP.innerHTML = menuArr2[cIndex]
// $(cItem).append(oP);
$(cItem).find('button').append(oP)
} else if (index === 3) {
oP.innerHTML = menuArr3[cIndex]
// $(cItem).append(oP);
$(cItem).find('button').append(oP)
} else if (index === 4) {
oP.innerHTML = menuArr4[cIndex]
// $(cItem).append(oP);
$(cItem).find('button').append(oP)
}
})
}) })
setLoading(false) // 设置菜单标题
}, 350) const menuButtonElements = editorToolbar.querySelectorAll('.w-e-bar-item button')
} menuButtonElements.forEach((element, index) => {
editorConfig.onCreated = (editor) => { if (index > 1 && index < 22) return
setLoading(true) const width = index === 0 || index === 1 ? '50%' : '25%'
element.parentElement.style.width = width
element.classList.add('has-title')
const title = element.getAttribute('data-tooltip')
const span = document.createElement('span')
span.innerHTML = title
span.className = 'title'
element.appendChild(span)
})
}, 50)
} }
editorConfig.onFocus = (editor) => {
editorConfig.onFocus = editor => {
clearTimeout(saveRef.current) clearTimeout(saveRef.current)
} }
editorConfig.onBlur = (editor) => { editorConfig.onBlur = editor => {
// 失焦保存 // 失焦保存
setHtml(editor.getHtml()) setHtml(editor.getHtml())
setContent(editor.getHtml()) setContent(editor.getHtml())
} }
editorConfig.onChange = (editor) => { editorConfig.onChange = editor => {
setHtml(editor.getHtml()) setHtml(editor.getHtml())
setContent(editor.getHtml()) setContent(editor.getHtml())
} }
...@@ -624,10 +453,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -624,10 +453,7 @@ const WangEditorCustomer = (props, ref) => {
// 及时销毁 editor ,重要! // 及时销毁 editor ,重要!
useEffect(() => { useEffect(() => {
if (editor) { if (editor) {
// console.log(editor.getConfig().hoverbarKeys.image); toolSettingReplace()
// console.log(editor, editorConfig);
toolSetttingReplace()
// 图片上传 // 图片上传
editor.on('ImageMenuClick', () => { editor.on('ImageMenuClick', () => {
console.log('ImageMenuClick', '----') console.log('ImageMenuClick', '----')
...@@ -695,7 +521,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -695,7 +521,7 @@ const WangEditorCustomer = (props, ref) => {
console.log('ImageEditorClick', '----') console.log('ImageEditorClick', '----')
const nodeEntries = SlateEditor.nodes(editor, { const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => { match: node => {
// JS syntax // JS syntax
if (SlateElement.isElement(node)) { if (SlateElement.isElement(node)) {
if (node.type === 'paragraph') { if (node.type === 'paragraph') {
...@@ -745,45 +571,12 @@ const WangEditorCustomer = (props, ref) => { ...@@ -745,45 +571,12 @@ const WangEditorCustomer = (props, ref) => {
} }
setExpandVisible(true) 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对话 // ai对话
editor.on('AISelectTextClick', () => { editor.on('AISelectTextClick', () => {
setSelectText(editor.getSelectionText()) setSelectText(editor.getSelectionText())
setAIVisible(true) setAIVisible(true)
}) })
const oldHtml = editor.getHtml()
editor.addMark('fontSize', '18px') editor.addMark('fontSize', '18px')
editor.addMark('fontFamily', '黑体') editor.addMark('fontFamily', '黑体')
...@@ -814,9 +607,9 @@ const WangEditorCustomer = (props, ref) => { ...@@ -814,9 +607,9 @@ const WangEditorCustomer = (props, ref) => {
} }
}, [gData, editor]) }, [gData, editor])
const tabKeyChange = (key) => { const tabKeyChange = key => {
if (key === 'text') { if (key === 'text') {
toolSetttingReplace() toolSettingReplace()
} }
setTabKey(key) setTabKey(key)
editor.focus() editor.focus()
...@@ -825,7 +618,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -825,7 +618,7 @@ const WangEditorCustomer = (props, ref) => {
// 预览 // 预览
const previewIt = async () => { const previewIt = async () => {
await saveContent() await saveContent()
setPriviewVisible(true) setPreviewVisible(true)
} }
// 历史 // 历史
...@@ -838,7 +631,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -838,7 +631,7 @@ const WangEditorCustomer = (props, ref) => {
if (data) { if (data) {
window.sessionStorage.setItem('sts', JSON.stringify(data)) window.sessionStorage.setItem('sts', JSON.stringify(data))
setSTSToken(data) setSTSToken(data)
const ossClientTemp = await new AliOSS({ const ossClientTemp = new AliOSS({
accessKeyId: data.AccessKeyId, accessKeyId: data.AccessKeyId,
accessKeySecret: data.AccessKeySecret, accessKeySecret: data.AccessKeySecret,
stsToken: data.SecurityToken, stsToken: data.SecurityToken,
...@@ -898,23 +691,27 @@ const WangEditorCustomer = (props, ref) => { ...@@ -898,23 +691,27 @@ const WangEditorCustomer = (props, ref) => {
})() })()
}, []) }, [])
const setColor = (type) => { const colorList = [
{ color: '#ab1941', name: '默认' },
{ color: '#2970f6', name: '蓝色' },
{ color: '#2ad882', name: '绿色' },
{ color: '#eb3351', name: '红色' }
]
const setColor = color => {
const headers = document.querySelectorAll(`.w-e-scroll .chapter-item-header`) const headers = document.querySelectorAll(`.w-e-scroll .chapter-item-header`)
const sections = document.querySelectorAll(`.w-e-scroll .chapter-item-section`) const sections = document.querySelectorAll(`.w-e-scroll .chapter-item-section`)
headers.forEach((item) => { headers.forEach(item => {
const node = DomEditor.toSlateNode(editor, item) const node = DomEditor.toSlateNode(editor, item)
const path = DomEditor.findPath(editor, node) const path = DomEditor.findPath(editor, node)
SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path }) SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: color }, { at: path })
}) })
sections.forEach((item) => { sections.forEach(item => {
const node = DomEditor.toSlateNode(editor, item) const node = DomEditor.toSlateNode(editor, item)
const path = DomEditor.findPath(editor, node) const path = DomEditor.findPath(editor, node)
SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: colorList[type - 1] }, { at: path }) SlateTransforms.setNodes(editor, { ...node, textColor: '#ffffff', bgColor: color }, { at: path })
}) })
} }
const [aiWriteOpen, setAiWriteOpen] = useState(false)
const [aiWriteAction, setAiWriteAction] = useState('rewrite')
return ( return (
<div className="wangeditor-customer-container"> <div className="wangeditor-customer-container">
<div className="editor-content-container"> <div className="editor-content-container">
...@@ -963,7 +760,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -963,7 +760,7 @@ const WangEditorCustomer = (props, ref) => {
<div className="tabs"> <div className="tabs">
{tabsMenu && {tabsMenu &&
tabsMenu.length && tabsMenu.length &&
tabsMenu.map((item) => { tabsMenu.map(item => {
return ( return (
<div className={`tabs-item ${item.key === tabKey ? 'active' : ''}`} key={item.key} onClick={() => tabKeyChange(item.key)}> <div className={`tabs-item ${item.key === tabKey ? 'active' : ''}`} key={item.key} onClick={() => tabKeyChange(item.key)}>
{item.title} {item.title}
...@@ -975,54 +772,26 @@ const WangEditorCustomer = (props, ref) => { ...@@ -975,54 +772,26 @@ const WangEditorCustomer = (props, ref) => {
<div className="menu-tabs-content"> <div className="menu-tabs-content">
{tabKey === 'text' && ( {tabKey === 'text' && (
<div ref={toolbarRef} className="toolbox-parent"> <div ref={toolbarRef} className="toolbox-parent">
<Toolbar <Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" className="editor-toolbar-container"></Toolbar>
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: '1px solid #ccc' }}
className="editor-toolbar-container"></Toolbar>
</div> </div>
)} )}
{tabKey === 'style' && ( {tabKey === 'style' && (
<div className="styletem"> <div className="styletem">
<p>样式模板</p> <p>样式模板</p>
<ul> <ul>
<li> {colorList.map(item => {
<div className="left"> return (
<span className="color color1"></span> <li key={item.color}>
<b className="type">默认</b> <div className="left">
</div> <span className="color" style={{ backgroundColor: item.color }}></span>
<Button type="link" className="use" onClick={() => setColor(1)}> <b className="type">{item.name}</b>
使用 </div>
</Button> <Button type="link" className="use" onClick={() => setColor(item.color)}>
</li> 使用
<li> </Button>
<div className="left"> </li>
<span className="color color2"></span> )
<b className="type">蓝色</b> })}
</div>
<Button type="link" className="use" onClick={() => setColor(2)}>
使用
</Button>
</li>
<li>
<div className="left">
<span className="color color3"></span>
<b className="type">绿色</b>
</div>
<Button type="link" className="use" onClick={() => setColor(3)}>
使用
</Button>
</li>
<li>
<div className="left">
<span className="color color4"></span>
<b className="type">红色</b>
</div>
<Button type="link" className="use" onClick={() => setColor(4)}>
使用
</Button>
</li>
</ul> </ul>
</div> </div>
)} )}
...@@ -1275,7 +1044,7 @@ const WangEditorCustomer = (props, ref) => { ...@@ -1275,7 +1044,7 @@ const WangEditorCustomer = (props, ref) => {
classNames={{ body: 'phone-body', wrapper: 'phone-wrapper' }} classNames={{ body: 'phone-body', wrapper: 'phone-wrapper' }}
wrapClassName="wrap-phone-privew" wrapClassName="wrap-phone-privew"
width="494px" width="494px"
onCancel={() => setPriviewVisible(false)}> onCancel={() => setPreviewVisible(false)}>
<PreviewModal ref={previewRef} gData={gData} editor={editor} chapterId={chapterId} bookId={bookId} nowTitle={nowTitle} /> <PreviewModal ref={previewRef} gData={gData} editor={editor} chapterId={chapterId} bookId={bookId} nowTitle={nowTitle} />
</Modal> </Modal>
...@@ -1302,25 +1071,6 @@ const WangEditorCustomer = (props, ref) => { ...@@ -1302,25 +1071,6 @@ const WangEditorCustomer = (props, ref) => {
setContent={setContent} setContent={setContent}
/> />
</Modal> </Modal>
{/* ai对话 */}
<Drawer
open={aiVisible}
width="600px"
title="AI对话"
destroyOnClose
onClose={() => setAIVisible(false)}
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> </div>
) )
} }
......
...@@ -239,174 +239,42 @@ ...@@ -239,174 +239,42 @@
} }
} }
.editor-toolbar-container { .editor-toolbar-container {
flex: 0 0 306px; margin: 10px;
border: none !important;
.w-e-bar { .w-e-bar {
background-color: transparent; padding: 0;
padding: 5px 15px; svg {
} width: 18px;
.w-e-bar-boxitem { height: 18px;
background-color: #fff;
width: 100%;
display: flex;
justify-content: space-around;
&.you {
flex-flow: row wrap;
.w-e-bar-item {
width: 16.6%;
flex: 0 0 16.6%;
}
}
&.line-height {
justify-content: flex-start;
}
}
.custom-bar-box {
background-color: #fff;
width: 100%;
display: flex;
justify-content: flex-start;
flex-flow: row wrap;
.w-e-menu-tooltip-v5 {
height: 50px;
}
&.two {
.w-e-bar-item {
width: 33%;
flex: 0 0 33%;
justify-content: flex-start;
text-align: left;
display: block;
p {
color: #333;
font-weight: bold;
}
button {
padding-left: 0;
}
}
}
&.input {
justify-content: space-around;
padding: 10px 0;
height: 62px;
.input-item {
flex: 1;
padding: 0 10px;
text-align: center;
input {
width: 100%;
color: #333;
text-align: center;
border: 1px solid #d9d9d9;
border-radius: 5px;
height: 30px;
line-height: 30px;
&:hover {
border-color: #b83956;
}
}
.text {
color: #999;
font-size: 12px;
margin-top: 8px;
}
}
}
&.media {
.w-e-bar-item {
width: 25%;
flex: 0 0 25%;
p {
font-size: 10px;
-webkit-transform: scale(1.2);
-moz-transform: scale(1.2);
-o-transform: scale(1.2);
-ms-transform: scale(1.2);
transform: scale(1.2);
}
}
}
&.hight {
.w-e-bar-item {
width: 25%;
flex: 0 0 25%;
p {
font-size: 10px;
-webkit-transform: scale(1.2);
-moz-transform: scale(1.2);
-o-transform: scale(1.2);
-ms-transform: scale(1.2);
transform: scale(1.2);
}
}
}
&.media,
&.hight {
.w-e-bar-item {
button {
display: block;
flex-direction: column;
padding: 10px;
height: 50px;
svg {
font-size: 22px;
}
}
p {
display: block;
width: 100%;
margin: 0px;
}
}
}
.w-e-bar-item {
width: 25%;
flex: 0 0 25%;
display: flex;
flex-direction: column;
height: 60px;
} }
} }
.w-e-bar-divider { .w-e-bar-divider {
display: block; margin: 0;
height: 1px; padding-top: 10px;
width: 100%; width: 100%;
margin: 8px 0; height: auto;
&:nth-of-type(16) { background: #fafafa;
height: 0; font-size: 14px;
} font-weight: 600;
// &:nth-child(odd) {
// height: 0;
// }
}
.w-auto {
display: block;
width: 100%;
height: 40px;
line-height: 40px; line-height: 40px;
&.type-heading {
font-weight: 600;
}
} }
.w-e-bar { .w-e-bar-item {
svg { height: auto;
width: 18px; button {
height: 18px; svg:nth-child(2) {
&:nth-child(2) {
display: none; display: none;
} }
&.has-title {
width: 100%;
height: 100%;
padding: 10px;
flex-direction: column;
align-items: center;
.title {
margin: 5px 0 0 0;
}
}
} }
} }
.w-e-select-list {
z-index: 10001;
width: 180px !important;
}
.w-e-menu-tooltip-v5:before {
z-index: 10001;
}
} }
.ant-spin-nested-loading, .ant-spin-nested-loading,
.ant-spin-container { .ant-spin-container {
...@@ -417,7 +285,7 @@ ...@@ -417,7 +285,7 @@
} }
.menu-tabs-key { .menu-tabs-key {
flex: 1; flex: 0 0 306px;
background: #fafafa; background: #fafafa;
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
min-width: 300px; min-width: 300px;
......
// Extend menu import BaseModalMenu from './common/BaseModalMenu'
class AISelectTextAuto { import AIChatDrawer from './common/AIChatDrawer'
class AIChat extends BaseModalMenu {
constructor() { constructor() {
this.title = 'AI'; super()
this.iconSvg = `<svg t="1713246032755" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12352" width="16" height="16"><path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#cdcdcd" p-id="12353"></path></svg>`; this.title = 'AI'
this.tag = 'button'; this.iconSvg = `<svg t="1713246032755" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12352" width="16" height="16"><path d="M826.368 325.632c0-7.168 2.048-10.24 10.24-10.24h123.904c7.168 0 10.24 2.048 10.24 10.24v621.568c0 7.168-2.048 10.24-10.24 10.24h-122.88c-8.192 0-10.24-4.096-10.24-10.24l-1.024-621.568z m-8.192-178.176c0-50.176 35.84-79.872 79.872-79.872 48.128 0 79.872 32.768 79.872 79.872 0 52.224-33.792 79.872-81.92 79.872-46.08 1.024-77.824-27.648-77.824-79.872zM462.848 584.704C441.344 497.664 389.12 307.2 368.64 215.04h-2.048c-16.384 92.16-58.368 247.808-92.16 369.664h188.416zM243.712 712.704l-62.464 236.544c-2.048 7.168-4.096 8.192-12.288 8.192H54.272c-8.192 0-10.24-2.048-8.192-12.288l224.256-783.36c4.096-13.312 7.168-26.624 8.192-65.536 0-6.144 2.048-8.192 7.168-8.192H450.56c6.144 0 8.192 2.048 10.24 8.192l250.88 849.92c2.048 7.168 0 10.24-7.168 10.24H573.44c-7.168 0-10.24-2.048-12.288-7.168l-65.536-236.544c1.024 1.024-251.904 0-251.904 0z" fill="#cdcdcd" p-id="12353"></path></svg>`
this.tag = 'button'
} }
getValue(editor) { getValue(editor) {
return 'hello, AI'; return <AIChatDrawer key={Date.now()} editor={editor}></AIChatDrawer>
}
isActive(editor) {
return false; // or true
}
isDisabled(editor) {
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
if (this.isDisabled(editor)) {
return;
}
editor.emit('AISelectTextClick');
} }
} }
export default { export default {
key: 'AISelectTextAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'AIChat', // 定义 menu key :要保证唯一、不重复(重要)
factory() { factory() {
return new AISelectTextAuto(); // 把 `YourMenuClass` 替换为你菜单的 class return new AIChat() // 把 `YourMenuClass` 替换为你菜单的 class
}, }
}; }
export { AISelectTextAuto };
import BaseModalMenu from './common/BaseModalMenu'
import AIModal from './common/AIModal'
class AIContentInspect extends BaseModalMenu {
constructor() {
super()
this.title = '内容检查'
this.iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M11.5 13.5q.525 0 .988-.137t.912-.413l1.525 1.575q.2.225.513.213t.537-.213q.225-.225.238-.537T16 13.45l-1.55-1.55q.275-.425.413-.9T15 10q0-1.475-1.038-2.488T11.5 6.5T9.037 7.513T8 10t1.038 2.488T11.5 13.5m0-1.5q-.825 0-1.412-.587T9.5 10t.588-1.412T11.5 8q.8 0 1.4.588T13.5 10t-.587 1.413T11.5 12M2 21q-.425 0-.712-.288T1 20t.288-.712T2 19h20q.425 0 .713.288T23 20t-.288.713T22 21zm2-3q-.825 0-1.412-.587T2 16V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v11q0 .825-.587 1.413T20 18zm0-2h16V5H4zm0 0V5z"/></svg>`
}
getValue(editor) {
return <AIModal key={Date.now()} editor={editor} docAction="contentInspect"></AIModal>
}
}
export default {
key: 'AIContentInspect',
factory() {
return new AIContentInspect()
}
}
// Extend menu
class AIDigitalHuman {
constructor() {
this.title = '数字人'
this.iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 28 28"><path fill="currentColor" d="M12 5.5a2 2 0 0 0 1.491 1.935c.337.053.68.053 1.018 0A2 2 0 1 0 12 5.5m-1.337 1.058a3.5 3.5 0 1 1 6.675 0l4.419-1.436a2.477 2.477 0 1 1 1.53 4.712L18 11.552v3.822c0 .16.03.32.091.468l2.728 6.752a2.477 2.477 0 0 1-4.594 1.856l-2.243-5.553l-2.232 5.56a2.46 2.46 0 0 1-3.21 1.362a2.477 2.477 0 0 1-1.364-3.215l2.734-6.812q.09-.224.09-.466v-3.774L4.712 9.834a2.477 2.477 0 0 1 1.531-4.712zm2.518 2.346a5 5 0 0 1-.649-.162L5.78 6.548a.977.977 0 0 0-.604 1.859l5.46 1.774c.515.168.864.648.864 1.189v3.957c0 .35-.067.698-.198 1.024l-2.734 6.811a.977.977 0 0 0 .538 1.267a.96.96 0 0 0 1.252-.531l2.463-6.136c.42-1.045 1.897-1.047 2.319-.003l2.476 6.129a.977.977 0 1 0 1.812-.732L16.7 16.404a2.8 2.8 0 0 1-.2-1.03V11.37c0-.541.349-1.021.864-1.189l5.46-1.774a.977.977 0 1 0-.604-1.859l-6.752 2.194q-.32.104-.649.162a3.5 3.5 0 0 1-1.639 0"/></svg>`
this.tag = 'button'
}
getValue() {
return 'hello, 音频'
}
isActive() {
return false
}
isDisabled() {
return true
}
exec() {
return
}
}
export default {
key: 'AIDigitalHuman',
factory() {
return new AIDigitalHuman()
}
}
// Extend menu import BaseModalMenu from './common/BaseModalMenu'
class ExpandArticleAuto { import AIModal from './common/AIModal'
class AIExpand extends BaseModalMenu {
constructor() { constructor() {
this.title = '扩写'; super()
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"> 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="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-622, -216)" fill="#333333" fill-rule="nonzero"> <g id="图标" transform="translate(-622, -216)" fill="#333333" fill-rule="nonzero">
<g id="文章扩写" transform="translate(622, 216)"> <g id="文章扩写" transform="translate(622, 216)">
...@@ -11,32 +12,16 @@ class ExpandArticleAuto { ...@@ -11,32 +12,16 @@ class ExpandArticleAuto {
</g> </g>
</g> </g>
</g> </g>
</svg>`; </svg>`
this.tag = 'button';
} }
getValue(editor) { getValue(editor) {
return 'hello, 音频'; return <AIModal key={Date.now()} editor={editor} docAction="expand"></AIModal>
}
isActive(editor) {
return false; // or true
}
isDisabled(editor) {
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
if (this.isDisabled(editor)) {
return;
}
editor.emit('ExpandArticleMenuClick');
} }
} }
export default { export default {
key: 'ExpandArticleAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'AIExpand',
factory() { factory() {
return new ExpandArticleAuto(); // 把 `YourMenuClass` 替换为你菜单的 class return new AIExpand()
}, }
}; }
export { ExpandArticleAuto };
// Extend menu import BaseModalMenu from './common/BaseModalMenu'
class PolishingAuto { import AIModal from './common/AIModal'
constructor() {
this.title = '缩写'; class AIPolishing extends BaseModalMenu {
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"> constructor() {
<title>图标</title> super()
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-536, -221)" fill="#333333" fill-rule="nonzero"> this.title = '润色'
<g id="缩写-(1)" transform="translate(536, 221)"> 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">
<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> <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<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> <g id="图标" transform="translate(-536, -221)" fill="#333333" fill-rule="nonzero">
<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> <g id="润色-(1)" transform="translate(536, 221)">
<path d="M44.5362437,34.9727666 L36.4437205,39.5744464 L32.0221534,48 L27.6005864,39.5744464 L19.5158821,34.9727666 L27.6005864,30.3670145 L32.0221534,21.9373886 L36.439811,30.3670145 L44.5323343,34.9686943 L44.5362437,34.9727666 Z M44.7238964,23.712904 L48,22.7151947 L47.0421893,26.1277679 L48,29.5484856 L44.7238964,28.5385594 L41.4438834,29.5444133 L42.4056035,26.1318402 L41.4438834,22.7151947 L44.719987,23.712904 L44.7238964,23.712904 Z M20.1453005,41.4762026 L22.5965141,40.7228302 L21.8771787,43.280224 L22.5926047,45.8376177 L20.1374817,45.0842454 L17.6862681,45.8376177 L18.4056035,43.280224 L17.6901776,40.7228302 L20.1413911,41.4721303 L20.1413911,41.4762026 L20.1453005,41.4762026 Z" id="形状"></path> <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>
</g> <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>
</g> <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>
</g> <path d="M44.5362437,34.9727666 L36.4437205,39.5744464 L32.0221534,48 L27.6005864,39.5744464 L19.5158821,34.9727666 L27.6005864,30.3670145 L32.0221534,21.9373886 L36.439811,30.3670145 L44.5323343,34.9686943 L44.5362437,34.9727666 Z M44.7238964,23.712904 L48,22.7151947 L47.0421893,26.1277679 L48,29.5484856 L44.7238964,28.5385594 L41.4438834,29.5444133 L42.4056035,26.1318402 L41.4438834,22.7151947 L44.719987,23.712904 L44.7238964,23.712904 Z M20.1453005,41.4762026 L22.5965141,40.7228302 L21.8771787,43.280224 L22.5926047,45.8376177 L20.1374817,45.0842454 L17.6862681,45.8376177 L18.4056035,43.280224 L17.6901776,40.7228302 L20.1413911,41.4721303 L20.1413911,41.4762026 L20.1453005,41.4762026 Z" id="形状"></path>
</svg>`; </g>
this.tag = 'button'; </g>
} </g>
getValue(editor) { </svg>`
return 'hello, 缩写'; }
} getValue(editor) {
isActive(editor) { return <AIModal key={Date.now()} editor={editor} docAction="abbreviate"></AIModal>
return false; // or true }
} }
isDisabled(editor) {
return false; // or true export default {
} key: 'AIPolishing', // 定义 menu key :要保证唯一、不重复(重要)
exec(editor, value) { factory() {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值 return new AIPolishing() // 把 `YourMenuClass` 替换为你菜单的 class
if (this.isDisabled(editor)) { }
return; }
}
editor.emit('PolishingMenuClick');
}
}
export default {
key: 'PolishingAuto', // 定义 menu key :要保证唯一、不重复(重要)
factory() {
return new PolishingAuto(); // 把 `YourMenuClass` 替换为你菜单的 class
},
};
export { PolishingAuto };
import BaseModalMenu from './common/BaseModalMenu'
import AIModal from './common/AIModal'
class AIPunctuation extends BaseModalMenu {
constructor() {
super()
this.title = '标点校对'
this.iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M920 416H616c-4.4 0-8 3.6-8 8v112c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8v-56h60v320h-46c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h164c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8h-46V480h60v56c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V424c0-4.4-3.6-8-8-8M656 296V168c0-4.4-3.6-8-8-8H104c-4.4 0-8 3.6-8 8v128c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-64h168v560h-92c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-92V232h168v64c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8"/></svg>`
}
getValue(editor) {
return <AIModal key={Date.now()} editor={editor} docAction="punctuation"></AIModal>
}
}
export default {
key: 'AIPunctuation',
factory() {
return new AIPunctuation()
}
}
// Extend menu import BaseModalMenu from './common/BaseModalMenu'
class RewriteAuto { import AIModal from './common/AIModal'
class AIRewrite extends BaseModalMenu {
constructor() { constructor() {
this.title = '改写'; super()
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"> 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">
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <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(-731, -218)" fill="#333333" fill-rule="nonzero">
...@@ -11,32 +15,17 @@ class RewriteAuto { ...@@ -11,32 +15,17 @@ class RewriteAuto {
</g> </g>
</g> </g>
</g> </g>
</svg>`; </svg>`
this.tag = 'button'; this.tag = 'button'
} }
getValue(editor) { getValue(editor) {
return 'hello, 音频'; return <AIModal key={Date.now()} editor={editor} docAction="rewrite"></AIModal>
}
isActive(editor) {
return false; // or true
}
isDisabled(editor) {
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
if (this.isDisabled(editor)) {
return;
}
editor.emit('RewriteMenuClick');
} }
} }
export default { export default {
key: 'RewriteAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'AIRewrite',
factory() { factory() {
return new RewriteAuto(); // 把 `YourMenuClass` 替换为你菜单的 class return new AIRewrite()
}, }
}; }
export { RewriteAuto };
// Extend menu import BaseModalMenu from './common/BaseModalMenu'
class SummaryAuto { import AIModal from './common/AIModal'
class AISummary extends BaseModalMenu {
constructor() { constructor() {
this.title = '总结'; super()
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"> 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="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="图标" transform="translate(-821, -216)" fill="#333333" fill-rule="nonzero"> <g id="图标" transform="translate(-821, -216)" fill="#333333" fill-rule="nonzero">
<g id="总结" transform="translate(821, 216)"> <g id="总结" transform="translate(821, 216)">
...@@ -13,32 +16,16 @@ class SummaryAuto { ...@@ -13,32 +16,16 @@ class SummaryAuto {
</g> </g>
</g> </g>
</g> </g>
</svg>`; </svg>`
this.tag = 'button';
} }
getValue(editor) { getValue(editor) {
return 'hello, 音频'; return <AIModal key={Date.now()} editor={editor} docAction="summary"></AIModal>
}
isActive(editor) {
return false; // or true
}
isDisabled(editor) {
return false; // or true
}
exec(editor, value) {
// editor.insertText(value) // value 即 this.getValue(editor) 的返回值
if (this.isDisabled(editor)) {
return;
}
editor.emit('SummaryMenuClick');
} }
} }
export default { export default {
key: 'SummaryAuto', // 定义 menu key :要保证唯一、不重复(重要) key: 'AISummary',
factory() { factory() {
return new SummaryAuto(); // 把 `YourMenuClass` 替换为你菜单的 class return new AISummary()
}, }
}; }
export { SummaryAuto };
import { useState, useEffect } from 'react'
import { SendOutlined } from '@ant-design/icons'
import { Drawer, Input, Button, message } from 'antd'
import dayjs from 'dayjs'
import { useAIChat } from './useAIChat'
import normalAvatar from '@/assets/images/icon-normal-avatar.png'
import './AIChatDrawer.less'
const AIChatDrawer = props => {
const selectText = props.editor.getSelectionText()
const [open, setOpen] = useState(true)
const { messages, post, isLoading } = useAIChat()
const [value, setValue] = useState(selectText) // 输入值
useEffect(() => {
if (selectText) {
targetChat()
}
return () => {
setValue('')
}
}, [selectText])
const element = document.querySelector('#chat-content')
useEffect(() => {
element?.scrollTo(0, 999999)
}, [messages, element])
// 提交对话
const targetChat = async () => {
if (value) {
post({ userChatInput: value })
setValue('')
} else {
message.error('请输入对话内容!')
}
}
return (
<Drawer
open={open}
width="600px"
title="AI对话"
destroyOnClose
onClose={() => setOpen(false)}
rootClassName="ai-drawer-wrapper"
className="ai-drawer-container">
<div className="ai-drawer-content">
<div className="ai-chat-container">
<div className="chat-content" id="chat-content">
<div className="chat-content-padd">
{messages.map((item, index) => {
return (
<div className={`chat-content-item`} key={index}>
{item.role_type === 'user' && <div className="time">{dayjs(item.time).format('YYYY-MM-DD HH:mm')}</div>}
{item.role_type === 'ai' && (
<div className="inside">
<div className="ai-in-content">
<div className="img">
<img src={item.role?.avatar} />
</div>
<div className="ask-content">{item.content}</div>
</div>
</div>
)}
{item.role_type === 'user' && (
<div className="inside inside-user">
<div className="user-in-content">
<div className="ask-content">{item.content}</div>
<div className="img">
<img src={normalAvatar} />
</div>
</div>
</div>
)}
</div>
)
})}
</div>
</div>
</div>
<div className="ai-chat-ask">
<div className="text">
<Input.TextArea
value={value}
defaultValue={value}
allowClear
disabled={isLoading}
onChange={e => setValue(e.target.value)}
placeholder="请输入内容"
style={{ height: '80px', resize: 'none' }}></Input.TextArea>
</div>
<div className="button">
<Button type="primary" icon={<SendOutlined />} loading={isLoading} size="large" onClick={targetChat}></Button>
</div>
</div>
</div>
</Drawer>
)
}
export default AIChatDrawer
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
border-radius: 5px; border-radius: 5px;
} }
} }
.inside-user { .inside-user {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
flex: 1; flex: 1;
} }
.button { .button {
flex: 0 0 60px; flex: 0 0 40px;
padding-left: 10px; padding-left: 10px;
.ant-btn { .ant-btn {
height: 80px; height: 80px;
......
...@@ -6,25 +6,27 @@ const { TextArea } = Input ...@@ -6,25 +6,27 @@ const { TextArea } = Input
const actionMap = { const actionMap = {
rewrite: { name: '改写', prompt: '帮我改写以下文字内容:' }, rewrite: { name: '改写', prompt: '帮我改写以下文字内容:' },
expand: { name: '扩写', prompt: '帮我在以下文字内容基础上进行扩写:' }, expand: { name: '扩写', prompt: '帮我在以下文字内容基础上进行扩写:' },
abbreviate: { name: '缩写', prompt: '帮我缩写以下文字内容:' }, abbreviate: { name: '润色', prompt: '帮我润色以下文字内容:' },
summary: { name: '总结', prompt: '帮我总结以下文字内容:' } summary: { name: '总结', prompt: '帮我总结以下文字内容:' },
punctuation: { name: '标点校对', prompt: '帮我校对以下文字内容中的标点符号:' },
contentInspect: { name: '内容检查', prompt: '帮我检查以下文字内容中的敏感词和错别字:' }
} }
export default function AIWrite({ editor, docAction, ...rest }) { export default function AIModal({ editor, docAction }) {
const [isModalOpen, setIsModalOpen] = useState(true)
const [content, setContent] = useState('') const [content, setContent] = useState('')
const { text, fetch, isLoading } = useAIEdit() const { text, fetch, isLoading } = useAIEdit()
const actionText = actionMap[docAction]?.name const actionText = actionMap[docAction]?.name
const [selectionText, setSelectionText] = useState('') const [selectionText, setSelectionText] = useState('')
useEffect(() => { useEffect(() => {
if (rest.open) { const selection = editor.getSelectionText()
const selection = editor.getSelectionText() if (selection) {
if (selection) { setSelectionText(selection)
setSelectionText(selection) setContent(selection)
setContent(selection) fetch({ messages: [{ role: 'user', content: actionMap[docAction].prompt + selection }] })
fetch({ messages: [{ role: 'user', content: actionMap[docAction].prompt + selection }] })
}
} }
}, [rest.open]) }, [docAction, editor, fetch])
useEffect(() => { useEffect(() => {
setContent(text) setContent(text)
...@@ -37,21 +39,19 @@ export default function AIWrite({ editor, docAction, ...rest }) { ...@@ -37,21 +39,19 @@ export default function AIWrite({ editor, docAction, ...rest }) {
const handlePrimary = () => { const handlePrimary = () => {
editor.restoreSelection() editor.restoreSelection()
editor.insertText(text) editor.insertText(text)
rest.onCancel() setIsModalOpen(false)
} }
return ( return (
<Modal <Modal
title={`以下是AI${actionText}结果:`} title={`以下是AI${actionText}结果:`}
{...rest} open={isModalOpen}
footer={null} footer={null}
classNames={{ classNames={{ header: 'editor-header-customer', body: 'editor-body-customer', wrapper: 'editor-wrapper-customer' }}
header: 'editor-header-customer', onOk={handlePrimary}
body: 'editor-body-customer', onCancel={() => setIsModalOpen(false)}>
wrapper: 'editor-wrapper-customer'
}}>
<Spin spinning={isLoading}> <Spin spinning={isLoading}>
<TextArea autoSize={{ minRows: 4 }} value={content} onChange={(e) => setContent(e.target.value)} /> <TextArea autoSize={{ minRows: 4 }} value={content} onChange={e => setContent(e.target.value)} />
</Spin> </Spin>
<br /> <br />
<Flex gap="small" justify="center"> <Flex gap="small" justify="center">
...@@ -61,7 +61,7 @@ export default function AIWrite({ editor, docAction, ...rest }) { ...@@ -61,7 +61,7 @@ export default function AIWrite({ editor, docAction, ...rest }) {
<Button type="primary" onClick={handleFetch}> <Button type="primary" onClick={handleFetch}>
重新{actionText} 重新{actionText}
</Button> </Button>
<Button type="primary" onClick={rest.onCancel}> <Button type="primary" onClick={() => setIsModalOpen(false)}>
取消 取消
</Button> </Button>
</Flex> </Flex>
......
import ReactDOM from 'react-dom/client'
import { ConfigProvider, App } from 'antd'
import zhCN from 'antd/locale/zh_CN'
export default class BaseModalMenu {
constructor() {
this.tag = 'button'
this.$el = document.createElement('div')
document.body.appendChild(this.$el)
this.$root = ReactDOM.createRoot(this.$el)
}
isActive() {
return false // or true
}
isDisabled() {
return false // or true
}
exec(editor, value) {
this.$root.render(
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#ab1941' } }}>
<App>{value}</App>
</ConfigProvider>
)
}
}
import md5 from 'js-md5'
import { fetchEventSource } from '@fortaine/fetch-event-source'
import { useState, useCallback } from 'react'
export function useAIChat() {
const authKey = 'f3846153ba784b6d86bdcd5533259c88'
const authSecret = 'HO4IyLEwEOHpeOXBxaLQUOqWslJRGs1M'
const [messages, setMessages] = useState([])
const [chatId, setChatId] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const addMessage = useCallback(message => {
setMessages(prevMessages => [...prevMessages, message])
}, [])
const updateMessages = useCallback(newMessage => {
setMessages(prevMessages => {
const existingMessage = prevMessages.find(msg => msg.conversationId === newMessage.conversationId)
const content = newMessage.content === '\n' ? '<br/>' : newMessage.content || ''
if (existingMessage) {
// 更新现有消息
return prevMessages.map(msg => (msg.conversationId === newMessage.conversationId ? { ...msg, content: msg.content + content } : msg))
} else {
// 新增消息
return [...prevMessages, { ...newMessage, content, role_type: 'ai', time: Date.now() }]
}
})
}, [])
const post = useCallback(
async data => {
const timestamp = Date.now()
const sign = md5(`${authKey}${authSecret}${timestamp}`)
// 插入用户消息
addMessage({
role_type: 'user',
content: data.userChatInput,
conversationId: `user-${timestamp}`, // 生成一个唯一的 conversationId
time: Date.now()
})
setIsLoading(true)
try {
await fetchEventSource('/api/tiangong/openapi/agent/chat/stream/v1', {
method: 'POST',
headers: {
authKey,
timestamp,
sign,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ...data, chatId, agentId: authKey }),
onopen(response) {
if (!response.ok) {
throw new Error('Network response was not ok')
}
},
onmessage(event) {
const message = JSON.parse(event.data)
setChatId(message.chatId)
updateMessages(message)
},
onerror(error) {
console.error('Fetch error:', error)
},
onclose() {
setIsLoading(false)
}
})
} catch (error) {
console.error('Post error:', error)
setIsLoading(false)
}
},
[authKey, authSecret, chatId, addMessage, updateMessages]
)
return { messages, isLoading, post }
}
...@@ -4,10 +4,10 @@ export const fontFamilyList = [ ...@@ -4,10 +4,10 @@ export const fontFamilyList = [
{ name: '黑体', value: '黑体' }, { name: '黑体', value: '黑体' },
{ name: '宋体', value: '宋体' }, { name: '宋体', value: '宋体' },
{ name: '楷体', value: '楷体' }, { name: '楷体', value: '楷体' },
{ name: '仿宋', value: '仿宋' }, { name: '仿宋', value: '仿宋' }
]; ]
export const fontFizeList = [ export const fontSizeList = [
{ name: '初号', value: 56 }, { name: '初号', value: 56 },
{ name: '小初', value: 48 }, { name: '小初', value: 48 },
{ name: '一号', value: 34 }, { name: '一号', value: 34 },
...@@ -23,55 +23,58 @@ export const fontFizeList = [ ...@@ -23,55 +23,58 @@ export const fontFizeList = [
{ name: '六号', value: 10 }, { name: '六号', value: 10 },
{ name: '小六', value: 8 }, { name: '小六', value: 8 },
{ name: '七号', value: 7 }, { name: '七号', value: 7 },
{ name: '八号', value: 6 }, { name: '八号', value: 6 }
]; ]
export const lineHeightList = ['1', '1.25', '1.5', '2', '2.5', '3']
export const findElementPath = (editor, elementType) => { export const findElementPath = (editor, elementType) => {
const { children } = editor; const { children } = editor
// 递归函数来遍历节点 // 递归函数来遍历节点
function traverseNodes(node) { function traverseNodes(node) {
if (Array.isArray(node)) { if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) { for (let i = 0; i < node.length; i++) {
const child = node[i]; const child = node[i]
traverseNodes(child); traverseNodes(child)
if (child.type === elementType) { if (child.type === elementType) {
// 当找到匹配的元素类型时,返回其路径 // 当找到匹配的元素类型时,返回其路径
return [i, ...traverseNodes(child)]; return [i, ...traverseNodes(child)]
} }
} }
} else if (node.type === elementType) { } else if (node.type === elementType) {
// 如果当前节点是元素节点且类型匹配,则返回其路径 // 如果当前节点是元素节点且类型匹配,则返回其路径
return [traverseNodes(node.parent)]; return [traverseNodes(node.parent)]
} }
return null; return null
} }
// 从文档的根节点开始遍历 // 从文档的根节点开始遍历
const path = traverseNodes(children); const path = traverseNodes(children)
return path; return path
}; }
export const findNodeWithParent = (node, type, key = 'random', value) => { export const findNodeWithParent = (node, type, key = 'random', value) => {
const path = []; // 用于存储当前节点的路径 const path = [] // 用于存储当前节点的路径
// 递归遍历节点的辅助函数 // 递归遍历节点的辅助函数
function traverse(nodes, type, key, value) { function traverse(nodes, type, key, value) {
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const current = nodes[i]; const current = nodes[i]
if (current.type === type && String(current[key]) === String(value)) { if (current.type === type && String(current[key]) === String(value)) {
// 找到匹配的节点,将索引添加到路径数组中 // 找到匹配的节点,将索引添加到路径数组中
path.push(i); path.push(i)
return true; return true
} }
if (Array.isArray(current.children) && traverse(current.children, type, key, value)) { if (Array.isArray(current.children) && traverse(current.children, type, key, value)) {
// 如果子节点中找到匹配的节点,添加当前节点的索引到路径 // 如果子节点中找到匹配的节点,添加当前节点的索引到路径
path.push(i); path.push(i)
return true; return true
} }
} }
return false; // 未找到匹配的节点 return false // 未找到匹配的节点
} }
// 从根节点开始遍历 // 从根节点开始遍历
traverse(node, type, key, value); traverse(node, type, key, value)
return path; // 返回找到的路径,如果没有找到则返回空数组 return path // 返回找到的路径,如果没有找到则返回空数组
}; }
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Editor, Toolbar } from '@wangeditor/editor-for-react' import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import '@wangeditor/editor/dist/css/style.css' // 引入 css import '@wangeditor/editor/dist/css/style.css' // 引入 css
import './styles.less'
function MyEditor() { function WangEditorCustomer() {
// editor 实例 // editor 实例
const [editor, setEditor] = useState(null) // JS 语法 const [editor, setEditor] = useState(null) // JS 语法
...@@ -17,7 +18,38 @@ function MyEditor() { ...@@ -17,7 +18,38 @@ function MyEditor() {
}, []) }, [])
// 工具栏配置 // 工具栏配置
const toolbarConfig = {} const toolbarConfig = {
toolbarKeys: [
'redo',
'undo',
'|',
'fontFamily',
'fontSize',
'lineHeight',
'color',
'bold',
'italic',
'through',
'underline',
'bgColor',
'bulletedList',
'numberedList',
'indent',
'delIndent',
'sub',
'sup',
'justifyLeft',
'justifyCenter',
'justifyRight',
'justifyJustify',
'divider',
'|',
'insertTable',
'|',
'codeBlock', // 代码块
'blockquote' // 引用
]
}
// 编辑器配置 // 编辑器配置
const editorConfig = { const editorConfig = {
...@@ -32,21 +64,52 @@ function MyEditor() { ...@@ -32,21 +64,52 @@ function MyEditor() {
} }
}, [editor]) }, [editor])
const onCreated = editor => {
setEditor(editor)
setTimeout(() => {
const editorToolbar = document.querySelector('.editor-toolbar')
// 设置菜单模块标题
const dividerElements = editorToolbar.querySelectorAll('.w-e-bar-divider')
const dividerTitles = ['常用格式', '媒体资源', '高级模块', 'AI辅助', 'AI数字人']
dividerElements.forEach((element, index) => {
element.innerHTML = dividerTitles[index]
})
// 设置菜单标题
const menuButtonElements = editorToolbar.querySelectorAll('.w-e-bar-item button')
menuButtonElements.forEach((element, index) => {
if (index > 1 && index < 22) return
element.classList.add('has-title')
const title = element.getAttribute('data-tooltip')
const span = document.createElement('span')
span.innerHTML = title
span.className = 'title'
element.appendChild(span)
})
}, 50)
}
return ( return (
<> <>
<div style={{ border: '1px solid #ccc', zIndex: 100 }}> <div className="editor-wrapper">
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" style={{ borderBottom: '1px solid #ccc' }} /> <div className="editor-left">
<Editor <div className="editor-header"></div>
defaultConfig={editorConfig} <div className="editor-main">
value={html} <Editor
onCreated={setEditor} defaultConfig={editorConfig}
onChange={editor => setHtml(editor.getHtml())} value={html}
mode="default" onCreated={onCreated}
style={{ height: '500px', overflowY: 'hidden' }} onChange={editor => setHtml(editor.getHtml())}
/> mode="default"
style={{ height: '500px', overflowY: 'hidden' }}
/>
</div>
</div>
<div className="editor-right">
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" className="editor-toolbar" />
</div>
</div> </div>
</> </>
) )
} }
export default MyEditor export default WangEditorCustomer
.editor-wrapper {
display: flex;
}
.editor-left {
flex: 1;
border: 1px solid #ccc;
}
.editor-right {
flex: 0 0 300px;
background: #fafafa;
border: 1px solid #e5e5e5;
min-width: 300px;
margin-left: 10px;
border-radius: 6px;
}
.editor-toolbar {
margin: 10px;
.w-e-bar {
padding: 0;
svg {
width: 18px;
height: 18px;
}
}
.w-e-bar-divider {
margin: 0;
padding-top: 10px;
width: 100%;
height: auto;
background: #fafafa;
font-size: 14px;
font-weight: 600;
line-height: 40px;
}
.w-e-bar-item {
height: auto;
button {
svg:nth-child(2) {
display: none;
}
&.has-title {
width: 100%;
height: auto;
padding: 10px;
flex-direction: column;
align-items: center;
.title {
margin: 5px 0 0 0;
}
}
}
}
}
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react'
import { import { Divider, Button, Row, Col, Descriptions, Tree, Tooltip, Dropdown, Space, Input, Popconfirm, Modal, Spin } from 'antd'
Divider,
Button,
Row,
Col,
Descriptions,
Tree,
Tooltip,
Dropdown,
Space,
Input,
Popconfirm,
Modal,
Spin,
} from 'antd';
import { import {
DiffOutlined, DiffOutlined,
MenuFoldOutlined, MenuFoldOutlined,
...@@ -22,251 +8,235 @@ import { ...@@ -22,251 +8,235 @@ import {
DashOutlined, DashOutlined,
CloseOutlined, CloseOutlined,
CheckOutlined, CheckOutlined,
EllipsisOutlined, EllipsisOutlined
} from '@ant-design/icons'; } from '@ant-design/icons'
import WangEditorCustomer from '@/common/wangeditor-customer'; import WangEditorCustomer from '@/common/wangeditor-customer'
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux'
import { setAutosaveTime } from '@/store/modules/editor'; import { setAutosaveTime } from '@/store/modules/editor'
import { setTreeChapter } from '@/store/modules/user'; import { setTreeChapter } from '@/store/modules/user'
import EditChapterTitle from './components/form-chapter-title'; import EditChapterTitle from './components/form-chapter-title'
import { get } from 'lodash-es'; import { get } from 'lodash-es'
import md5 from 'js-md5'; import md5 from 'js-md5'
import { import { convertToAntdTreeData, findTreeElementByKey, findFirstNotHasChildren, findParentLevelOne, findTreeToIndex, findNodeById } from '@/utils/common'
convertToAntdTreeData, import { getAllList, getInfoByChapterId, sectionEdit, chapterDel, dragOrder, getRecordList, getUserInfo, getPowerByRoleId } from './request'
findTreeElementByKey, import './index.less'
findFirstNotHasChildren,
findParentLevelOne,
findTreeToIndex,
findNodeById,
} from '@/utils/common';
import {
getAllList,
getInfoByChapterId,
sectionEdit,
chapterDel,
dragOrder,
getRecordList,
getUserInfo,
getPowerByRoleId,
} from './request';
import './index.less';
const Examine = () => { const Examine = () => {
const location = useLocation(); const location = useLocation()
const id = get(location, 'state.id', ''); const id = get(location, 'state.id', '')
const { treeChapter } = useSelector((state) => state.user); const { treeChapter } = useSelector(state => state.user)
const navigate = useNavigate(); const navigate = useNavigate()
const dispatch = useDispatch(); const dispatch = useDispatch()
const [gData, setGData] = useState([]); const [gData, setGData] = useState([])
const [chapterId, setChapterId] = useState(0); const [chapterId, setChapterId] = useState(0)
const [bookId, setBookId] = useState(0); const [bookId, setBookId] = useState(0)
const [nameList, setNameList] = useState([]); const [nameList, setNameList] = useState([])
const [openDel, setOpenDel] = useState(false); const [openDel, setOpenDel] = useState(false)
const [delNode, setDelNode] = useState({}); const [delNode, setDelNode] = useState({})
const [recordList, setRecordList] = useState([]); const [recordList, setRecordList] = useState([])
// 树节点设置 // 树节点设置
const [expandedKeys, setExpandedKeys] = useState([]); const [expandedKeys, setExpandedKeys] = useState([])
const [checkedKeys, setCheckedKeys] = useState([]); const [checkedKeys, setCheckedKeys] = useState([])
const [nowTitle, setNowTitle] = useState(''); const [nowTitle, setNowTitle] = useState('')
// 编辑操作 // 编辑操作
const [isCollapse, setisCollapse] = useState(false); const [isCollapse, setisCollapse] = useState(false)
const [editKey, setEditKey] = useState(null); const [editKey, setEditKey] = useState(null)
const [parentId, setParentId] = useState(null); const [parentId, setParentId] = useState(null)
const [editValue, setEditValue] = useState(''); const [editValue, setEditValue] = useState('')
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [delLoading, setDelLoading] = useState(false); const [delLoading, setDelLoading] = useState(false)
const [contentMd5, setContentMd5] = useState(''); const [contentMd5, setContentMd5] = useState('')
const [quanXian, setQuanXian] = useState(false); const [quanXian, setQuanXian] = useState(false)
// 编辑器内容 // 编辑器内容
const editorRef = useRef(); const editorRef = useRef()
const saveInterRef = useRef(); const saveInterRef = useRef()
const [html, setHtml] = useState(''); const [html, setHtml] = useState('')
const [contentId, setContentId] = useState(false); const [contentId, setContentId] = useState(false)
// 获取目录结构 // 获取目录结构
const getChapterTreeList = async () => { const getChapterTreeList = async () => {
setLoading(true); setLoading(true)
const { data = [], code } = await getAllList({ book_id: id }); const { data = [], code } = await getAllList({ book_id: id })
if (code !== 200) { if (code !== 200) {
setLoading(false); setLoading(false)
navigate('/books/management/list', { replace: true }); navigate('/books/management/list', { replace: true })
return; return
} }
if (data.length === 0) { if (data.length === 0) {
setLoading(false); setLoading(false)
return; return
} }
const uniqueBookList = [...new Set(data.map((item) => item.book_id))][0]; const uniqueBookList = [...new Set(data.map(item => item.book_id))][0]
setBookId(uniqueBookList); setBookId(uniqueBookList)
const arr = convertToAntdTreeData(data, 'name'); const arr = convertToAntdTreeData(data, 'name')
setGData(arr); setGData(arr)
const allKeys = getAllNodeKeys(arr); const allKeys = getAllNodeKeys(arr)
setExpandedKeys(allKeys); setExpandedKeys(allKeys)
const first = findFirstNotHasChildren(arr); const first = findFirstNotHasChildren(arr)
setNowTitle(first.title); setNowTitle(first.title)
if (treeChapter && Object.entries(treeChapter).length > 0) { if (treeChapter && Object.entries(treeChapter).length > 0) {
if (parseInt(treeChapter.saveBookId) === parseInt(id)) { if (parseInt(treeChapter.saveBookId) === parseInt(id)) {
const childInKey = findTreeElementByKey(arr, 'key', treeChapter.saveChapterId); const childInKey = findTreeElementByKey(arr, 'key', treeChapter.saveChapterId)
if (childInKey) { if (childInKey) {
setChapterId(childInKey.key); setChapterId(childInKey.key)
setCheckedKeys([childInKey.key]); setCheckedKeys([childInKey.key])
setNowTitle(childInKey.title); setNowTitle(childInKey.title)
} else { } else {
setChapterId(first.key); setChapterId(first.key)
setCheckedKeys([first.key]); setCheckedKeys([first.key])
setNowTitle(first.title); setNowTitle(first.title)
} }
} else { } else {
setChapterId(first.key); setChapterId(first.key)
setCheckedKeys([first.key]); setCheckedKeys([first.key])
setNowTitle(first.title); setNowTitle(first.title)
await dispatch(setTreeChapter({ saveBookId: id, saveChapterId: first.key })); await dispatch(setTreeChapter({ saveBookId: id, saveChapterId: first.key }))
} }
} else { } else {
setChapterId(first.key); setChapterId(first.key)
setCheckedKeys([first.key]); setCheckedKeys([first.key])
setNowTitle(first.title); setNowTitle(first.title)
} }
setLoading(false); setLoading(false)
}; }
//选中了章节管理才展示编辑 //选中了章节管理才展示编辑
const getQuanXian = async () => { const getQuanXian = async () => {
const data = await getUserInfo(); const data = await getUserInfo()
const data1 = await getPowerByRoleId({ role_id: data.id }); const data1 = await getPowerByRoleId({ role_id: data.id })
if (!data1.includes(37)) { if (!data1.includes(37)) {
setQuanXian(true); setQuanXian(true)
} }
}; }
const newChapterChange = (id, title) => { const newChapterChange = (id, title) => {
setChapterId(parseInt(id)); setChapterId(parseInt(id))
setCheckedKeys([parseInt(id)]); setCheckedKeys([parseInt(id)])
setNowTitle(title); setNowTitle(title)
}; }
// 递归函数,获取所有节点的key // 递归函数,获取所有节点的key
const getAllNodeKeys = (nodes) => { const getAllNodeKeys = nodes => {
let keys = []; let keys = []
nodes.forEach((node) => { nodes.forEach(node => {
keys.push(node.key); keys.push(node.key)
if (node.children && node.children.length > 0) { if (node.children && node.children.length > 0) {
keys = keys.concat(getAllNodeKeys(node.children)); keys = keys.concat(getAllNodeKeys(node.children))
} }
}); })
return keys; return keys
}; }
// 获取内容 // 获取内容
const getChapterVal = async () => { const getChapterVal = async () => {
setLoading(true); setLoading(true)
const data = await getInfoByChapterId({ chapter_id: chapterId }); const data = await getInfoByChapterId({ chapter_id: chapterId })
dispatch(setTreeChapter({ saveBookId: id, saveChapterId: chapterId })); dispatch(setTreeChapter({ saveBookId: id, saveChapterId: chapterId }))
const { content, id: cId } = data; const { content, id: cId } = data
if (content) { if (content) {
setContentMd5(md5(content)); setContentMd5(md5(content))
} }
setHtml(content); setHtml(content)
setContentId(cId); setContentId(cId)
if (editorRef.current && editorRef.current.editor) { if (editorRef.current && editorRef.current.editor) {
editorRef.current.editor.setHtml(content); editorRef.current.editor.setHtml(content)
} }
setLoading(false); setLoading(false)
}; }
const onExpand = (expandedKeys) => { const onExpand = expandedKeys => {
setExpandedKeys(expandedKeys); setExpandedKeys(expandedKeys)
}; }
const handleSelect = async (checkedKeys, info) => { const handleSelect = async (checkedKeys, info) => {
const { key } = info.node; const { key } = info.node
const childInKey = findTreeElementByKey(gData, 'key', key); const childInKey = findTreeElementByKey(gData, 'key', key)
if (childInKey.children && childInKey.children.length > 0) { if (childInKey.children && childInKey.children.length > 0) {
clearInterval(saveInterRef.current); clearInterval(saveInterRef.current)
// 进行展开操作 // 进行展开操作
const tempExpandeds = JSON.parse(JSON.stringify(expandedKeys)); const tempExpandeds = JSON.parse(JSON.stringify(expandedKeys))
let newExpand = ''; let newExpand = ''
if (expandedKeys.includes(key)) { if (expandedKeys.includes(key)) {
newExpand = tempExpandeds.filter((item) => parseInt(item) !== parseInt(key)); newExpand = tempExpandeds.filter(item => parseInt(item) !== parseInt(key))
} else { } else {
newExpand = [...tempExpandeds, key]; newExpand = [...tempExpandeds, key]
} }
setExpandedKeys(newExpand); setExpandedKeys(newExpand)
setCheckedKeys([key]); setCheckedKeys([key])
return; return
} else { } else {
const { key, title } = info.node; const { key, title } = info.node
if (info.selected === false) { if (info.selected === false) {
setLoading(true); setLoading(true)
setChapterId(key); setChapterId(key)
setCheckedKeys([key]); setCheckedKeys([key])
setNowTitle(title); setNowTitle(title)
setLoading(false); setLoading(false)
} else { } else {
setLoading(true); setLoading(true)
clearInterval(saveInterRef.current); clearInterval(saveInterRef.current)
await saveContent(); await saveContent()
if (key !== chapterId) { if (key !== chapterId) {
editorRef.current.editor.clear(); editorRef.current.editor.clear()
} }
setLoading(true); setLoading(true)
setChapterId(checkedKeys[0]); setChapterId(checkedKeys[0])
setCheckedKeys([checkedKeys[0]]); setCheckedKeys([checkedKeys[0]])
setNowTitle(info.node.title); setNowTitle(info.node.title)
setLoading(false); setLoading(false)
} }
} }
}; }
useEffect(() => { useEffect(() => {
getChapterTreeList(); getChapterTreeList()
getQuanXian(); getQuanXian()
}, []); }, [])
useEffect(() => { useEffect(() => {
if (chapterId) { if (chapterId) {
setCheckedKeys([chapterId]); setCheckedKeys([chapterId])
getChapterVal(); getChapterVal()
} }
}, [chapterId]); }, [chapterId])
// 保存方法 // 保存方法
const saveContent = async () => { const saveContent = async () => {
if ((contentId, editorRef.current, editorRef.current.editor, !loading)) { if ((contentId, editorRef.current, editorRef.current.editor, !loading)) {
const contentHtml = editorRef.current.editor.getHtml(); const contentHtml = editorRef.current.editor.getHtml()
const newMd5 = md5(contentHtml); const newMd5 = md5(contentHtml)
clearInterval(saveInterRef.current); clearInterval(saveInterRef.current)
const word_count = editorRef.current.editor.getText().length; const word_count = editorRef.current.editor.getText().length
// 保存 // 保存
const data = await sectionEdit({ const data = await sectionEdit({
id: contentId, id: contentId,
content: contentHtml, content: contentHtml,
word_count, word_count
}); })
if (data) { if (data) {
if ((data.code && data.code === 3000) || data.code === '3000') { if ((data.code && data.code === 3000) || data.code === '3000') {
navigate('/books/management/list'); navigate('/books/management/list')
return; return
} else { } else {
setContentMd5(newMd5); setContentMd5(newMd5)
autoSaveContent(newMd5); autoSaveContent(newMd5)
dispatch(setAutosaveTime(Date.now())); dispatch(setAutosaveTime(Date.now()))
} }
} }
} }
}; }
// 定时器轮询 // 定时器轮询
const autoSaveContent = (newMd5) => { const autoSaveContent = newMd5 => {
// clearInterval(saveInterRef.current); // clearInterval(saveInterRef.current);
// saveInterRef.current = setInterval(async () => { // saveInterRef.current = setInterval(async () => {
// if (!contentMd5 || (newMd5 !== contentMd5 && contentMd5)) { // if (!contentMd5 || (newMd5 !== contentMd5 && contentMd5)) {
...@@ -276,149 +246,153 @@ const Examine = () => { ...@@ -276,149 +246,153 @@ const Examine = () => {
// // console.log('save', newMd5, '一样的'); // // console.log('save', newMd5, '一样的');
// } // }
// }, 5000); // }, 5000);
}; }
useEffect(() => { useEffect(() => {
if (!loading && contentId) { if (!loading && contentId) {
let newMd5 = md5(html); let newMd5 = md5(html)
clearInterval(saveInterRef.current); clearInterval(saveInterRef.current)
autoSaveContent(newMd5); autoSaveContent(newMd5)
} else { } else {
clearInterval(saveInterRef.current); clearInterval(saveInterRef.current)
} }
}, [contentId, loading, contentMd5, html]); }, [contentId, loading, contentMd5, html])
const addChapterParent = () => { const addChapterParent = () => {
setEditKey(-1); setEditKey(-1)
setEditValue(''); setEditValue('')
setParentId(-1); setParentId(-1)
}; }
const dropDownMenuHandler = async (e, node) => { const dropDownMenuHandler = async (e, node) => {
e.domEvent.stopPropagation(); e.domEvent.stopPropagation()
e.domEvent.preventDefault(); e.domEvent.preventDefault()
if (parseInt(e.key) === 1) { if (parseInt(e.key) === 1) {
// 展开 // 展开
const ids = findNodeById(gData, 'key', node.key); const ids = findNodeById(gData, 'key', node.key)
const expandedKeysTemp = [...expandedKeys, node.key]; const expandedKeysTemp = [...expandedKeys, node.key]
ids.forEach((item, index) => { ids.forEach((item, index) => {
if (!expandedKeysTemp.includes(item)) { if (!expandedKeysTemp.includes(item)) {
expandedKeysTemp.push(item); expandedKeysTemp.push(item)
} }
}); })
setExpandedKeys(expandedKeysTemp); setExpandedKeys(expandedKeysTemp)
} else if (parseInt(e.key) === 2) { } else if (parseInt(e.key) === 2) {
// 插入子节 // 插入子节
// 编辑 // 编辑
setEditKey(-1); setEditKey(-1)
setEditValue(''); setEditValue('')
setParentId(node.key); setParentId(node.key)
} else if (parseInt(e.key) === 3) { } else if (parseInt(e.key) === 3) {
// 编辑 // 编辑
setEditKey(node.key); setEditKey(node.key)
setEditValue(node.title); setEditValue(node.title)
setParentId(false); setParentId(false)
} else if (parseInt(e.key) === 4) { } else if (parseInt(e.key) === 4) {
setDelNode(node); setDelNode(node)
setOpenDel(true); setOpenDel(true)
} }
}; }
const delChapter = async (node) => { const delChapter = async node => {
setDelLoading(true); setDelLoading(true)
const childInKey = findParentLevelOne(gData, 'key', node.key); const childInKey = findParentLevelOne(gData, 'key', node.key)
let current = null; let current = null
if (childInKey && childInKey.children && childInKey.children.length > 0) { if (childInKey && childInKey.children && childInKey.children.length > 0) {
const index = findTreeToIndex(childInKey.children, 'key', node.key); const index = findTreeToIndex(childInKey.children, 'key', node.key)
if (childInKey.children.length === 1) { if (childInKey.children.length === 1) {
current = childInKey; current = childInKey
} else { } else {
// 前面还有 // 前面还有
if (index <= childInKey.children.length) { if (index <= childInKey.children.length) {
if (index === 0) { if (index === 0) {
current = childInKey.children[index + 1]; current = childInKey.children[index + 1]
} else { } else {
current = childInKey.children[index - 1]; current = childInKey.children[index - 1]
} }
} }
} }
} else { } else {
const index = findTreeToIndex(gData, 'key', node.key); const index = findTreeToIndex(gData, 'key', node.key)
let child = null; let child = null
if (index === 0) { if (index === 0) {
child = gData[index + 1]; child = gData[index + 1]
} else { } else {
child = gData[index - 1]; child = gData[index - 1]
} }
if (child && child.children && child.children.length > 0) { if (child && child.children && child.children.length > 0) {
current = child.children[0]; current = child.children[0]
} else { } else {
current = child; current = child
} }
} }
const data = await chapterDel({ id: node.key }); const data = await chapterDel({ id: node.key })
if (data) { if (data) {
await dispatch( await dispatch(setTreeChapter({ saveBookId: id, saveChapterId: current ? current.key : null }))
setTreeChapter({ saveBookId: id, saveChapterId: current ? current.key : null }), setDelNode({})
); setOpenDel(false)
setDelNode({}); await getChapterTreeList()
setOpenDel(false);
await getChapterTreeList();
} }
setDelLoading(false); setDelLoading(false)
}; }
useEffect(() => { useEffect(() => {
if (gData && gData.length > 0 && treeChapter && Object.entries(treeChapter).length > 0) { if (gData && gData.length > 0 && treeChapter && Object.entries(treeChapter).length > 0) {
if (parseInt(treeChapter.saveBookId) === parseInt(id)) { if (parseInt(treeChapter.saveBookId) === parseInt(id)) {
const childInKey = findTreeElementByKey(gData, 'key', treeChapter.saveChapterId); const childInKey = findTreeElementByKey(gData, 'key', treeChapter.saveChapterId)
if (childInKey && Object.entries(childInKey).length > 0) { if (childInKey && Object.entries(childInKey).length > 0) {
setChapterId(parseInt(treeChapter.saveChapterId)); setChapterId(parseInt(treeChapter.saveChapterId))
setCheckedKeys([parseInt(treeChapter.saveChapterId)]); setCheckedKeys([parseInt(treeChapter.saveChapterId)])
setNowTitle(childInKey.title); setNowTitle(childInKey.title)
} }
} }
} }
}, [gData, treeChapter]); }, [gData, treeChapter])
useEffect(() => { useEffect(() => {
console.log(expandedKeys); console.log(expandedKeys)
}, [expandedKeys]); }, [expandedKeys])
// 编辑章节名称 // 编辑章节名称
const titleRenderDom = (node) => { const titleRenderDom = node => {
return ( return (
<div className='tree-customer-item'> <div className="tree-customer-item">
<div className='title'> <div className="title">
<div className='title-node'>{node.title}</div> <div className="title-node">{node.title}</div>
</div> </div>
<div className='opaeration'> <div className="opaeration">
{quanXian ? ( {quanXian ? (
<Dropdown <Dropdown
trigger={['click']} trigger={['click']}
overlayClassName='dropmenu_list' overlayClassName="dropmenu_list"
destroyPopupOnHide={true} destroyPopupOnHide={true}
menu={{ menu={{
items: [ items: [
{ {
key: '1', key: '1',
label: <Button type='text'>展开全部</Button>, label: <Button type="text">展开全部</Button>
}, },
{ {
key: '2', key: '2',
label: <Button type='text'>添加子节</Button>, label: <Button type="text">添加子节</Button>
}, },
{ {
key: '4', key: '5',
label: <Button type='text'>删除</Button>, label: <Button type="text">设置编者</Button>
}, },
{
key: '3',
label: <Button type="text">重命名</Button>
},
{
key: '4',
label: <Button type="text">删除</Button>
}
], ],
onClick: (e) => dropDownMenuHandler(e, node), onClick: e => dropDownMenuHandler(e, node)
}} }}>
> <div className="dashed">
<div className='dashed'>
<span></span> <span></span>
<span></span> <span></span>
<span></span> <span></span>
...@@ -428,31 +402,30 @@ const Examine = () => { ...@@ -428,31 +402,30 @@ const Examine = () => {
) : ( ) : (
<Dropdown <Dropdown
trigger={['click']} trigger={['click']}
overlayClassName='dropmenu_list' overlayClassName="dropmenu_list"
destroyPopupOnHide={true} destroyPopupOnHide={true}
menu={{ menu={{
items: [ items: [
{ {
key: '1', key: '1',
label: <Button type='text'>展开全部</Button>, label: <Button type="text">展开全部</Button>
}, },
{ {
key: '2', key: '2',
label: <Button type='text'>添加子节</Button>, label: <Button type="text">添加子节</Button>
}, },
{ {
key: '3', key: '3',
label: <Button type='text'>编辑</Button>, label: <Button type="text">编辑</Button>
}, },
{ {
key: '4', key: '4',
label: <Button type='text'>删除</Button>, label: <Button type="text">删除</Button>
}, }
], ],
onClick: (e) => dropDownMenuHandler(e, node), onClick: e => dropDownMenuHandler(e, node)
}} }}>
> <div className="dashed">
<div className='dashed'>
<span></span> <span></span>
<span></span> <span></span>
<span></span> <span></span>
...@@ -462,20 +435,20 @@ const Examine = () => { ...@@ -462,20 +435,20 @@ const Examine = () => {
)} )}
</div> </div>
</div> </div>
); )
}; }
const onDrop = async (info) => { const onDrop = async info => {
const dropToGap = info.dropToGap; // true 为同级或最大级 false 为子级 const dropToGap = info.dropToGap // true 为同级或最大级 false 为子级
const nowDropNode = info.dragNode.key; const nowDropNode = info.dragNode.key
const sideDropNode = info.node.key; const sideDropNode = info.node.key
let data = null; let data = null
if (dropToGap) { if (dropToGap) {
// 同级 // 同级
const dragOverGapBottom = info.node.dragOverGapBottom; const dragOverGapBottom = info.node.dragOverGapBottom
const dragOver = info.node.dragOver; const dragOver = info.node.dragOver
const dragOverGapTop = info.node.dragOverGapTop; const dragOverGapTop = info.node.dragOverGapTop
if (!dragOverGapTop && !dragOver && !dragOverGapBottom) { if (!dragOverGapTop && !dragOver && !dragOverGapBottom) {
// 拖到最底部 // 拖到最底部
// 拖拽到最顶层 // 拖拽到最顶层
...@@ -483,8 +456,8 @@ const Examine = () => { ...@@ -483,8 +456,8 @@ const Examine = () => {
book_id: id, book_id: id,
drag_node: nowDropNode, drag_node: nowDropNode,
position_node: sideDropNode, position_node: sideDropNode,
drop_type: 'after', drop_type: 'after'
}); })
} else { } else {
if (dragOverGapTop) { if (dragOverGapTop) {
// 拖拽到最顶层 // 拖拽到最顶层
...@@ -492,15 +465,15 @@ const Examine = () => { ...@@ -492,15 +465,15 @@ const Examine = () => {
book_id: id, book_id: id,
drag_node: nowDropNode, drag_node: nowDropNode,
position_node: sideDropNode, position_node: sideDropNode,
drop_type: 'before', drop_type: 'before'
}); })
} else if (dragOverGapBottom) { } else if (dragOverGapBottom) {
data = await dragOrder({ data = await dragOrder({
book_id: id, book_id: id,
drag_node: nowDropNode, drag_node: nowDropNode,
position_node: sideDropNode, position_node: sideDropNode,
drop_type: 'after', drop_type: 'after'
}); })
} }
} }
} else { } else {
...@@ -509,58 +482,46 @@ const Examine = () => { ...@@ -509,58 +482,46 @@ const Examine = () => {
book_id: id, book_id: id,
drag_node: nowDropNode, drag_node: nowDropNode,
position_node: sideDropNode, position_node: sideDropNode,
drop_type: 'inner', drop_type: 'inner'
}); })
} }
if (data) { if (data) {
await getChapterTreeList(); await getChapterTreeList()
} }
}; }
return ( return (
<div className='examine'> <div className="examine">
<div className='content-box'> <div className="content-box">
<Row gutter={10} style={{ height: '100%' }} className='book-content-row'> <Row gutter={10} style={{ height: '100%' }} className="book-content-row">
<Col span={isCollapse ? 1 : 4} className='book-content-tree'> <Col span={isCollapse ? 1 : 4} className="book-content-tree">
<div className='border'> <div className="border">
{!isCollapse ? ( {!isCollapse ? (
<> <>
<Descriptions <Descriptions
layout='vertical' layout="vertical"
rootClassName='section-left-top' rootClassName="section-left-top"
bordered bordered
items={[ items={[
{ {
key: '1', key: '1',
label: ( label: (
<> <>
<Row <Row gutter={5} justify={'space-between'} style={{ alignItems: 'center' }}>
gutter={5}
justify={'space-between'}
style={{ alignItems: 'center' }}
>
<Col>章节目录</Col> <Col>章节目录</Col>
<Col> <Col>
<Button <Button type="text" icon={<DiffOutlined />} onClick={addChapterParent}></Button>
type='text' <Button type="text" icon={<MenuFoldOutlined />} onClick={() => setisCollapse(true)}></Button>
icon={<DiffOutlined />}
onClick={addChapterParent}
></Button>
<Button
type='text'
icon={<MenuFoldOutlined />}
onClick={() => setisCollapse(true)}
></Button>
</Col> </Col>
</Row> </Row>
</> </>
), )
}, }
]} ]}
/> />
{gData && gData.length > 0 && ( {gData && gData.length > 0 && (
<Tree <Tree
className='draggable-tree' className="draggable-tree"
onSelect={handleSelect} onSelect={handleSelect}
defaultExpandAll defaultExpandAll
defaultExpandedKeys={expandedKeys} defaultExpandedKeys={expandedKeys}
...@@ -573,42 +534,34 @@ const Examine = () => { ...@@ -573,42 +534,34 @@ const Examine = () => {
disabled={loading} disabled={loading}
treeData={gData} treeData={gData}
onExpand={onExpand} onExpand={onExpand}
titleRender={(nodeData) => titleRenderDom(nodeData)} titleRender={nodeData => titleRenderDom(nodeData)}
/> />
)} )}
</> </>
) : ( ) : (
<Descriptions <Descriptions
layout='vertical' layout="vertical"
bordered bordered
items={[ items={[
{ {
key: '1', key: '1',
label: ( label: (
<> <>
<Row <Row gutter={5} justify={'space-between'} style={{ alignItems: 'center' }}>
gutter={5}
justify={'space-between'}
style={{ alignItems: 'center' }}
>
<Col> <Col>
<Button <Button type="text" icon={<MenuUnfoldOutlined />} onClick={() => setisCollapse(false)}></Button>
type='text'
icon={<MenuUnfoldOutlined />}
onClick={() => setisCollapse(false)}
></Button>
</Col> </Col>
</Row> </Row>
</> </>
), )
}, }
]} ]}
/> />
)} )}
</div> </div>
</Col> </Col>
<Col span={isCollapse ? 23 : 20} className='book-content-tree'> <Col span={isCollapse ? 23 : 20} className="book-content-tree">
<div className='editor-right'> <div className="editor-right">
<Spin spinning={loading}> <Spin spinning={loading}>
<WangEditorCustomer <WangEditorCustomer
ref={editorRef} ref={editorRef}
...@@ -639,9 +592,8 @@ const Examine = () => { ...@@ -639,9 +592,8 @@ const Examine = () => {
closeIcon={false} // 添加这一行以隐藏关闭按钮 closeIcon={false} // 添加这一行以隐藏关闭按钮
onCancel={() => setEditKey(false)} onCancel={() => setEditKey(false)}
classNames={{ classNames={{
wrapper: 'chapter-title-modal', wrapper: 'chapter-title-modal'
}} }}>
>
<EditChapterTitle <EditChapterTitle
setEditKey={setEditKey} setEditKey={setEditKey}
editValue={editValue} editValue={editValue}
...@@ -668,30 +620,28 @@ const Examine = () => { ...@@ -668,30 +620,28 @@ const Examine = () => {
closeIcon={false} // 添加这一行以隐藏关闭按钮 closeIcon={false} // 添加这一行以隐藏关闭按钮
onCancel={() => setOpenDel(false)} onCancel={() => setOpenDel(false)}
classNames={{ classNames={{
wrapper: 'chapter-title-modal', wrapper: 'chapter-title-modal'
}} }}>
>
<Divider /> <Divider />
<div className=''>确认删除子节【{delNode.title}】?</div> <div className="">确认删除子节【{delNode.title}】?</div>
<div className='' style={{ display: 'flex', justifyContent: 'flex-end' }}> <div className="" style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space> <Space>
<Button type='primary' danger loading={delLoading} onClick={() => delChapter(delNode)}> <Button type="primary" danger loading={delLoading} onClick={() => delChapter(delNode)}>
确定 确定
</Button> </Button>
<Button <Button
type='default' type="default"
onClick={() => { onClick={() => {
setDelNode({}); setDelNode({})
setOpenDel(false); setOpenDel(false)
}} }}>
>
取消 取消
</Button> </Button>
</Space> </Space>
</div> </div>
</Modal> </Modal>
</div> </div>
); )
}; }
export default Examine; export default Examine
import Editor from '@/components/editor/index'
export default Editor
...@@ -307,6 +307,10 @@ const baseRouter = [ ...@@ -307,6 +307,10 @@ const baseRouter = [
{ {
path: 'userinfo', path: 'userinfo',
Component: lazy(() => import('@/pages/user-module/userInfo')) Component: lazy(() => import('@/pages/user-module/userInfo'))
},
{
path: 'editor',
Component: lazy(() => import('@/pages/editor/index'))
} }
] ]
}, },
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论