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

chore: update

上级 f77be006
# VITE_API_URL_WORD = https://zijingebook.ezijing.com/file/
VITE_API_URL_WORD = https://book-admin-web.ezijing.com/api
VITE_API_URL_WORD = https://zijingebook.ezijing.com/api
VITE_API_BASE_API_PREFFIX = /api
# VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com
VITE_API_WEBSOCKET_URL = wss://book-admin-web.ezijing.com/ws
VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com/ws
VITE_API_OPENAI_URL = https://model-platform-skyagents.tiangong.cn
\ No newline at end of file
# VITE_API_URL_WORD = https://zijingebook.ezijing.com/file/
VITE_API_URL_WORD = https://book-admin-web.ezijing.com/api
# VITE_API_URL_WORD = http://ebook-pc.ezijing.com:7419
VITE_API_URL_WORD = https://zijingebook.ezijing.com/api
VITE_API_BASE_API_PREFFIX = /api
# VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com
VITE_API_WEBSOCKET_URL = wss://book-admin-web.ezijing.com/ws
VITE_API_WEBSOCKET_URL = wss://zijingebook.ezijing.com/ws
VITE_API_OPENAI_URL = https://model-platform-skyagents.tiangong.cn
\ No newline at end of file
差异被折叠。
......@@ -11,7 +11,7 @@
"lint": "eslint --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore ./src"
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@ant-design/icons": "^5.4.0",
"@chuangkit/chuangkit-design": "^2.0.8",
"@fortaine/fetch-event-source": "^3.0.6",
"@reduxjs/toolkit": "^1.9.7",
......@@ -20,7 +20,7 @@
"@wangeditor/plugin-link-card": "^1.0.0",
"ahooks": "^3.8.0",
"ali-oss": "^6.20.0",
"antd": "^5.18.1",
"antd": "^5.20.6",
"axios": "^1.6.2",
"dayjs": "^1.11.11",
"easy-formula-editor": "^0.0.2-alpha.1",
......@@ -32,7 +32,7 @@
"lodash-es": "^4.17.21",
"qs": "^6.11.2",
"rc-slider-captcha": "^1.3.0",
"react": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
"react-redux": "^8.1.3",
......@@ -42,15 +42,15 @@
"xml-formatter": "^3.6.2"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react": "^7.35.2",
"eslint-plugin-react-hooks": "^4.6.2",
"less": "^4.2.0",
"vite": "^5.3.1",
"vite-plugin-mkcert": "^1.17.5"
"vite": "^5.4.4",
"vite-plugin-mkcert": "^1.17.6"
},
"browserslist": [
"> 1%",
......@@ -58,6 +58,6 @@
"not ie <= 10"
],
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.18.0"
"@rollup/rollup-linux-x64-gnu": "^4.21.2"
}
}
......@@ -52,9 +52,6 @@ dd {
width: 25% !important;
min-width: 475px;
}
.ant-modal .ant-modal-close {
inset-inline-end: initial;
}
.submit {
background: #aa1941;
......@@ -108,7 +105,7 @@ dd {
margin-bottom: 15px;
}
}
.ant-space {
.ant-space {
align-items: flex-start;
}
}
......@@ -139,28 +136,28 @@ dd {
// }
@font-face {
font-family: "思源黑体";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131326219-q886x58lgm.ttf") format('truetype');
font-family: '思源黑体';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131326219-q886x58lgm.ttf') format('truetype');
}
@font-face {
font-family: "思源宋体";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131348680-t3miidf3sxs.ttf") format('truetype');
font-family: '思源宋体';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131348680-t3miidf3sxs.ttf') format('truetype');
}
@font-face {
font-family: "楷体";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131272079-k30tgmq5m7.ttf") format('truetype');
font-family: '楷体';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131272079-k30tgmq5m7.ttf') format('truetype');
}
@font-face {
font-family: "仿宋";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131169707-0x5pgjxiburq.ttf") format('truetype');
font-family: '仿宋';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131169707-0x5pgjxiburq.ttf') format('truetype');
}
@font-face {
font-family: "宋体";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131298815-mogsisbs1vl.ttf") format('truetype');
font-family: '宋体';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131298815-mogsisbs1vl.ttf') format('truetype');
}
@font-face {
font-family: "黑体";
src: url("https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131247994-gp497hgawyb.ttf") format('truetype');
font-family: '黑体';
src: url('https://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131247994-gp497hgawyb.ttf') format('truetype');
}
// 仿宋 http://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131169707-0x5pgjxiburq.ttf
......@@ -170,13 +167,14 @@ dd {
// 思源黑体 http://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131326219-q886x58lgm.ttf
// 思源宋体 http://zxts-book-file.zijingebook.com/2024/04/03/fonts-1712131348680-t3miidf3sxs.ttf
.ant-table-wrapper .ant-table-container table>thead>tr:first-child >*:first-child {
.ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:first-child {
border-start-start-radius: 0;
}
.ant-table-wrapper table, .ant-table-wrapper .ant-table .ant-table-header {
.ant-table-wrapper table,
.ant-table-wrapper .ant-table .ant-table-header {
border-radius: 0;
}
.ant-table-wrapper .ant-table-container table>thead>tr:first-child >*:last-child {
.ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:last-child {
border-start-end-radius: 0;
&.ant-table-cell-scrollbar {
background-color: #f5f5f5;
......
import axios from '@/utils/axios'
// 获取教师列表
export function getTeacherList(data) {
return axios.post('/api/user/teacher/getList', data)
}
import { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import { Button, Divider, Space, Modal, Drawer } from 'antd'
import { Button, Divider, Space, Modal } from 'antd'
import { EyeOutlined, SaveOutlined, CloseOutlined, HistoryOutlined } from '@ant-design/icons'
import AliOSS from 'ali-oss'
import dayjs from 'dayjs'
......@@ -161,7 +161,6 @@ const WangEditorCustomer = (props, ref) => {
const [content, setContent] = useState(html)
const saveRef = useRef()
const [selectText, setSelectText] = useState('') //
const [titleVisible, setTitleVisible] = useState(false) // 章头
const [titleInfo, setTitleInfo] = useState({})
const [sectionVisible, setSectionVisible] = useState(false)
......@@ -179,8 +178,6 @@ const WangEditorCustomer = (props, ref) => {
const [expandVisible, setExpandVisible] = useState(false) // 扩展阅读
const [expandInfo, setExpandInfo] = useState({}) // 扩展阅读内容
const [aiVisible, setAIVisible] = useState(false) // ai对话弹窗
const [priviewVisible, setPreviewVisible] = useState(false)
const [historyVisible, setHistoryVisible] = useState(false) //点击历史
......@@ -437,7 +434,7 @@ const WangEditorCustomer = (props, ref) => {
}, 50)
}
editorConfig.onFocus = editor => {
editorConfig.onFocus = () => {
clearTimeout(saveRef.current)
}
editorConfig.onBlur = editor => {
......@@ -536,9 +533,7 @@ const WangEditorCustomer = (props, ref) => {
let info = {}
for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry
// console.log('选中了 paragraph 节点', node)
// console.log('节点 path 是', path)
node.children.forEach((item, index) => {
node.children.forEach(item => {
if (item.type === 'image') {
info.image = item
}
......@@ -572,12 +567,6 @@ const WangEditorCustomer = (props, ref) => {
setExpandVisible(true)
})
// ai对话
editor.on('AISelectTextClick', () => {
setSelectText(editor.getSelectionText())
setAIVisible(true)
})
editor.addMark('fontSize', '18px')
editor.addMark('fontFamily', '黑体')
editor.addMark('lineHeight', 1.5)
......
import { useRef, useState } from 'react';
const useWebsocket = ({}) => {
const ws = useRef();
const [wsMessage, setWsMessage] = useState({});
const [wsReadyState, setWsReadyState] = useState({ key: 0, value: '正在连接中' });
const createWebSocket = (url) => {
const stateArr = [
{ key: 0, value: '正在连接中' },
{ key: 1, value: '已经连接并且可以通讯' },
{ key: 2, value: '连接正在关闭' },
{ key: 3, value: '连接已关闭或者没有连接成功' },
];
try {
ws.current = new WebSocket(url);
ws.current.onopen = () => {
console.log('websocket open');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onclose = () => {
console.log('websocket close');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onerror = () => {
console.log('websocket error');
setWsReadyState(stateArr[ws.current?.readyState ?? 0]);
};
ws.current.onmessage = (e) => {
console.log('onmessage', e);
setWsMessage(JSON.parse(e.data));
};
} catch (error) {
console.log(error);
}
};
const webSocketInit = (url) => {
if (!ws.current || ws.current.readyState === 3) {
createWebSocket(url);
}
};
// 关闭 WebSocket
const closeWebSocket = () => {
ws.current?.close();
};
// 发送数据
const sendMessage = (str) => {
ws.current?.send(str);
};
//重连
const reconnect = () => {
try {
closeWebSocket();
ws.current = null;
createWebSocket();
} catch (e) {
console.log(e);
}
};
// useEffect(() => {
// webSocketInit();
// return () => {
// ws.current?.close(1000);
// };
// }, [ws]);
return {
webSocketInit,
wsMessage,
setWsMessage,
wsReadyState,
reconnect,
sendMessage,
closeWebSocket,
};
};
export default useWebsocket;
import { useState, useEffect } from 'react'
import { Spin } from 'antd'
import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import './styles.less'
import { fontFamilyList, fontSizeList, lineHeightList } from '@/common/wangeditor-customer/utils/setting'
import { uploadFile } from '@/utils/oss'
function WangEditorCustomer() {
export default function WangEditor({ value, onChange }) {
// editor 实例
const [editor, setEditor] = useState(null) // JS 语法
// 编辑器内容
const [html, setHtml] = useState('<p>hello</p>')
// 模拟 ajax 请求,异步设置 html
useEffect(() => {
setTimeout(() => {
setHtml('<p>hello world</p>')
}, 1500)
}, [])
const [loading, setLoading] = useState(false)
// 工具栏配置
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' // 引用
]
excludeKeys: ['insertVideo', 'insertImage', 'emotion', 'table', 'codeBlock', 'blockquote', 'code', 'group-more-style', 'insertTable'] //删除工具栏
}
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...'
// JS 语法
placeholder: '请输入内容...',
MENU_CONF: {
// 配置默认字号
fontSize: { fontSizeList },
fontFamily: { fontFamilyList },
lineHeight: { lineHeightList },
// 配置上传图片
uploadImage: {
timeout: 5 * 1000, // 5s
fieldName: 'image',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + localStorage.getItem('token') || ''
},
maxFileSize: 10 * 1024 * 1024, // 10M
base64LimitSize: 5 * 1024, // 5kb 以下插入 base64 // 用户自定义上传图片
customUpload: async (file, insertFn) => {
setLoading(true)
const url = await uploadFile(file)
insertFn(url, '图片')
setLoading(false)
}
},
uploadVideo: {
timeout: 15 * 1000, // 5s
fieldName: 'video',
headers: {
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + localStorage.getItem('token') || ''
},
allowedFileTypes: ['video/mp4', 'video/ogg', 'video/webm'],
maxFileSize: 10 * 1024 * 1024, // 10M
base64LimitSize: 5 * 1024, // 5kb 以下插入 base64 // 用户自定义上传图片
customUpload: async (file, insertFn) => {
setLoading(true)
const url = await uploadFile(file)
insertFn(url, '视频')
setLoading(false)
}
}
},
hoverbarKeys: {
image: {
menuKeys: ['imageWidth30', 'imageWidth50', 'imageWidth100', 'deleteImage']
},
text: {
menuKeys: ['headerSelect', 'insertLink', '|', 'bold', 'underline', 'italic', '|', 'color', 'bgColor', 'clearStyle']
}
}
}
useEffect(() => {
return () => {
if (editor === null) return
......@@ -64,52 +78,14 @@ function WangEditorCustomer() {
}
}, [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 (
<>
<div className="editor-wrapper">
<div className="editor-left">
<div className="editor-header"></div>
<div className="editor-main">
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={onCreated}
onChange={editor => setHtml(editor.getHtml())}
mode="default"
style={{ height: '500px', overflowY: 'hidden' }}
/>
</div>
</div>
<div className="editor-right">
<Spin spinning={loading}>
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" className="editor-toolbar" />
</div>
<Editor defaultConfig={editorConfig} value={value} onChange={onChange} onCreated={setEditor} mode="default" className="editor-main" />
</Spin>
</div>
</>
)
}
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: 1px solid #d9d9d9;
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;
overflow: hidden;
.editor-toolbar {
border-bottom: 1px solid #d9d9d9;
}
.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;
}
}
}
.editor-main {
height: 400px;
overflow-y: hidden;
}
}
import { useMemo, useRef, useState } from 'react'
import { Select, Spin } from 'antd'
import { debounce } from 'lodash-es'
import { getTeacherList } from '@/api/user'
function DebounceSelect({ fetchOptions, debounceTimeout = 800, ...props }) {
const [fetching, setFetching] = useState(false)
const [options, setOptions] = useState([])
const fetchRef = useRef(0)
const debounceFetcher = useMemo(() => {
const loadOptions = value => {
fetchRef.current += 1
const fetchId = fetchRef.current
setOptions([])
setFetching(true)
fetchOptions(value).then(newOptions => {
if (fetchId !== fetchRef.current) {
// for fetch callback order
return
}
setOptions(newOptions)
setFetching(false)
})
}
return debounce(loadOptions, debounceTimeout)
}, [fetchOptions, debounceTimeout])
return (
<Select
labelInValue
filterOption={false}
onSearch={debounceFetcher}
notFoundContent={fetching ? <Spin size="small" /> : null}
{...props}
options={options}
/>
)
}
// Usage of DebounceSelect
async function fetchUserList(username) {
console.log('fetching user', username)
return getTeacherList({ real_name: username, page_size: 40 }).then(res => {
return res.data.list
})
}
const SelectTeacher = props => {
const [value, setValue] = useState([])
return (
<DebounceSelect
mode="multiple"
value={value}
placeholder="Select users"
fetchOptions={fetchUserList}
fieldNames={{ label: 'real_name', value: 'id' }}
onChange={newValue => {
setValue(newValue)
}}
style={{ width: '100%' }}
{...props}
/>
)
}
export default SelectTeacher
import { Button, Upload, message } from 'antd'
import { useState } from 'react'
import { uploadFile } from '@/utils/oss'
import './index.less'
export default function UploadImage({ value, onChange }) {
const [uploading, setUploading] = useState(false)
const uploadProps = {
name: 'file',
accept: '.jpg,.png',
showUploadList: false,
beforeUpload: async file => {
const suffixArr = ['jpg', 'png']
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
if (!suffixArr.includes(suffix)) {
message.error(`请上传以${suffixArr.join('、')}格式的书籍`)
return false
} else if (file.size >= 1024 * 1024) {
message.error(`请上传大小在1M以内的图片`)
return false
}
},
customRequest: async ({ file }) => {
try {
setUploading(true)
const url = await uploadFile(file)
setUploading(false)
onChange(url)
return true
} catch (error) {
message.error('上传文件失败,请重试')
setUploading(false)
return false
}
}
}
return (
<div className="upload-image">
{value && <img src={value} width={88} height={88} style={{ borderRadius: 5 }} />}
<div className="upload-image-main">
<Upload {...uploadProps}>
<Button type="primary" ghost loading={uploading}>
点击上传
</Button>
</Upload>
<span className="tips">尺寸:只能上传jpg/png文件,且不超过1M</span>
</div>
</div>
)
}
.upload-image {
.upload-image-main {
display: flex;
align-items: center;
gap: 10px;
.tips {
font-size: 10px;
font-family: 'PingFangSC, PingFang SC';
font-weight: 400;
color: #999999;
line-height: 1;
font-size: 12px;
}
}
}
import { useState, useEffect } from 'react'
import { getTeacherList } from '@/api/user'
export function useTeachers() {
const [teachers, setTeachers] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchTeachers = async () => {
try {
const response = await getTeacherList({ page_size: 40 })
setTeachers(response.data.list)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
}
fetchTeachers()
}, [])
return { teachers, loading, error }
}
.ant-modal .ant-modal-close {
inset-inline-end: initial;
}
.delModal,
.footer {
.ant-btn-primary {
......
.upload {
background: rgba(170, 25, 65, 0.03);
border-radius: 4px;
border: 1px solid #aa1941;
color: #aa1941;
font-size: 12px;
&:hover {
background: rgba(170, 25, 65, 0.03) !important;
border-radius: 4px !important;
opacity: 0.7;
border: 1px solid #aa1941 !important;
color: #aa1941 !important;
font-size: 12px !important;
}
}
.uploadPar {
.descBox {
display: flex;
align-items: flex-end; /* 让子元素底部对齐 */
.desc {
font-size: 10px;
font-family: 'PingFangSC, PingFang SC';
font-weight: 400;
color: #999999;
line-height: 14px;
font-size: 12px;
margin-left: 6px;
margin-bottom: 2px;
}
}
}
.back-icon {
position: absolute;
top: 10px;
left: 10px;
cursor: pointer;
display: none;
}
.editor-box {
.w-e-scroll {
font-family: "黑体";
font-size: 18px;
line-height: 1.5;
}
}
\ No newline at end of file
import axios from '@/utils/axios'
// 获取教师书籍列表
export function getBookList(data) {
return axios.post('/api/book/teacher/getList', data)
}
// 获取教师书籍详情
export function getBook(data) {
return axios.post('/api/book/teacher/getInfoById', data)
}
// 添加教师书籍
export function addBook(data) {
return axios.post('/api/book/teacher/add', data)
}
// 添加教师书籍
export function updateBook(data) {
return axios.post('/api/book/teacher/edit', data)
}
// 删除教师书籍
export function deleteBook(data) {
return axios.post('/api/book/teacher/del', data)
}
import axios from '@/utils/axios'
// 获取章节编辑者信息
export function getChapterEditors(data) {
return axios.post('/api/book/teacher/chapter/getEditors', data)
}
// 保存章节编辑者
export function updateChapterEditors(data) {
return axios.post('/api/book/teacher/chapter/saveEditors', data)
}
import { useState, useEffect } from 'react'
import { Modal, Form, Radio, Space, message } from 'antd'
import { getChapterEditors, updateChapterEditors } from '../api'
const EditChapterEditors = ({ chapter = {}, onChange, ...props }) => {
const [editors, setEditors] = useState([])
const [value, setValue] = useState('')
useEffect(() => {
getChapterEditors({ book_id: chapter.book_id, chapter_id: chapter.id }).then(res => {
setEditors(res.data.editors || [])
const value = res.data.selected_editor_ids[0] || ''
setValue(value)
})
}, [chapter])
const handleSubmit = async () => {
await updateChapterEditors({ book_id: chapter.book_id, chapter_id: chapter.id, editor_id: value })
message.success('设置成功')
onChange && onChange()
}
return (
<Modal title="设置章节编写者" {...props} onOk={handleSubmit}>
<Form labelCol={{ span: 8 }}>
<Form.Item label="当前章节">{chapter.title}</Form.Item>
<Form.Item label="编写者">
<Radio.Group value={value} onChange={e => setValue(e.target.value)}>
<Space direction="vertical">
{editors.map(item => {
return (
<Radio value={item.editor_id} key={item.editor_id}>
{item.real_name}
</Radio>
)
})}
</Space>
</Radio.Group>
</Form.Item>
</Form>
</Modal>
)
}
export default EditChapterEditors
import React, { useState, useEffect, useRef } from 'react';
import { Form, Button, Space, Input, message } from 'antd';
import { setTreeChapter } from '@/store/modules/user';
import { useDispatch, useSelector } from 'react-redux';
import { chapterEdit, chapterAdd, sectionAdd } from '../request';
import { useState, useEffect, useRef } from 'react'
import { Form, Button, Space, Input } from 'antd'
import { setTreeChapter } from '@/store/modules/user'
import { useDispatch } from 'react-redux'
import { chapterEdit, chapterAdd, sectionAdd } from '../request'
const EditChapterTitle = (props) => {
const {
editValue,
setEditValue,
editKey,
setEditKey,
parentId,
bookId,
getChapterTreeList,
expandedKeys,
setExpandedKeys,
editorLoading,
newChapterChange,
} = props;
const dispatch = useDispatch();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [initValues, setInitValues] = useState({ title: editValue });
const inputRef = useRef();
const EditChapterTitle = props => {
const { editValue, setEditValue, editKey, setEditKey, parentId, bookId, getChapterTreeList, expandedKeys, setExpandedKeys, editorLoading, newChapterChange } =
props
const dispatch = useDispatch()
const [form] = Form.useForm()
const [loading, setLoading] = useState(false)
const [initValues, setInitValues] = useState({ title: editValue })
const inputRef = useRef()
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.focus()
}
}, []);
}, [])
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.focus()
}
}, [editorLoading]);
}, [editorLoading])
useEffect(() => {
form.setFieldsValue({ title: editValue });
setInitValues({ title: editValue });
}, [editValue]);
form.setFieldsValue({ title: editValue })
setInitValues({ title: editValue })
}, [editValue])
const onFinish = async (values) => {
setLoading(true);
let data = null;
const onFinish = async values => {
setLoading(true)
let data = null
if (parentId) {
data = await chapterAdd({
pid: parentId === -1 ? 0 : parentId,
book_id: bookId,
name: values.title,
});
name: values.title
})
} else {
data = await chapterEdit({ id: editKey, name: values.title });
data = await chapterEdit({ id: editKey, name: values.title })
}
await dispatch(setTreeChapter({ saveBookId: bookId, saveChapterId: data.id }));
await dispatch(setTreeChapter({ saveBookId: bookId, saveChapterId: data.id }))
if (data) {
if (parentId) {
await sectionAdd({ book_id: bookId, chapter_id: data.id, content: '', word_count: 0 });
await sectionAdd({ book_id: bookId, chapter_id: data.id, content: '', word_count: 0 })
}
setEditKey(false);
setEditValue(null);
await getChapterTreeList();
setEditKey(false)
setEditValue(null)
await getChapterTreeList()
if (parentId) {
let temp = [...expandedKeys, parentId];
setExpandedKeys(temp);
let temp = [...expandedKeys, parentId]
setExpandedKeys(temp)
await newChapterChange(data.id, values.title);
await newChapterChange(data.id, values.title)
}
}
setLoading(false);
};
setLoading(false)
}
return (
<Form layout='vertical' form={form} onFinish={onFinish} initialValues={initValues}>
<Form.Item
label='章节标题'
name='title'
rules={[{ required: true, message: '请输入章节标题' }]}
extra='最多30个字符'
>
<Input ref={inputRef} autoFocus maxLength={30} placeholder='' allowClear />
<Form layout="vertical" form={form} onFinish={onFinish} initialValues={initValues}>
<Form.Item label="章节标题" name="title" rules={[{ required: true, message: '请输入章节标题' }]} extra="最多30个字符">
<Input ref={inputRef} autoFocus maxLength={30} placeholder="" allowClear />
</Form.Item>
<Form.Item className='editor-form-buttons' wrapperCol={{ offset: 9, span: 16 }}>
<Form.Item className="editor-form-buttons" wrapperCol={{ offset: 9, span: 16 }}>
<Space size={20}>
<Button type='default' disabled={loading} onClick={() => setEditKey(false)}>
<Button type="default" disabled={loading} onClick={() => setEditKey(false)}>
取消
</Button>
<Button type='primary' loading={loading} htmlType='submit'>
<Button type="primary" loading={loading} htmlType="submit">
确认
</Button>
</Space>
</Form.Item>
</Form>
);
};
)
}
export default EditChapterTitle;
export default EditChapterTitle
import React, { useState, useEffect, useRef } from 'react'
import { Divider, Button, Row, Col, Descriptions, Tree, Tooltip, Dropdown, Space, Input, Popconfirm, Modal, Spin } from 'antd'
import {
DiffOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
EyeOutlined,
DashOutlined,
CloseOutlined,
CheckOutlined,
EllipsisOutlined
} from '@ant-design/icons'
import { useState, useEffect, useRef } from 'react'
import { Divider, Button, Row, Col, Descriptions, Tree, Dropdown, Space, Modal, Spin } from 'antd'
import { EllipsisOutlined, DiffOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
import WangEditorCustomer from '@/common/wangeditor-customer'
import { useLocation, useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { setAutosaveTime } from '@/store/modules/editor'
import { setTreeChapter } from '@/store/modules/user'
import EditChapterTitle from './components/form-chapter-title'
import EditChapterEditors from './components/EditChapterEditors'
import { get } from 'lodash-es'
import md5 from 'js-md5'
......@@ -34,7 +26,6 @@ const Examine = () => {
const [gData, setGData] = useState([])
const [chapterId, setChapterId] = useState(0)
const [bookId, setBookId] = useState(0)
const [nameList, setNameList] = useState([])
const [openDel, setOpenDel] = useState(false)
const [delNode, setDelNode] = useState({})
const [recordList, setRecordList] = useState([])
......@@ -53,6 +44,12 @@ const Examine = () => {
const [contentMd5, setContentMd5] = useState('')
const [quanXian, setQuanXian] = useState(false)
// 设置编者
const [currentChapter, setCurrentChapter] = useState(null)
const [editChapterEditorsIsOpen, setEditChapterEditorsIsOpen] = useState(false)
const onChapterEditorsChange = () => {
setEditChapterEditorsIsOpen(false)
}
// 编辑器内容
const editorRef = useRef()
const saveInterRef = useRef()
......@@ -262,36 +259,6 @@ const Examine = () => {
setEditValue('')
setParentId(-1)
}
const dropDownMenuHandler = async (e, node) => {
e.domEvent.stopPropagation()
e.domEvent.preventDefault()
if (parseInt(e.key) === 1) {
// 展开
const ids = findNodeById(gData, 'key', node.key)
const expandedKeysTemp = [...expandedKeys, node.key]
ids.forEach((item, index) => {
if (!expandedKeysTemp.includes(item)) {
expandedKeysTemp.push(item)
}
})
setExpandedKeys(expandedKeysTemp)
} else if (parseInt(e.key) === 2) {
// 插入子节
// 编辑
setEditKey(-1)
setEditValue('')
setParentId(node.key)
} else if (parseInt(e.key) === 3) {
// 编辑
setEditKey(node.key)
setEditValue(node.title)
setParentId(false)
} else if (parseInt(e.key) === 4) {
setDelNode(node)
setOpenDel(true)
}
}
const delChapter = async node => {
setDelLoading(true)
......@@ -350,90 +317,48 @@ const Examine = () => {
}
}, [gData, treeChapter])
useEffect(() => {
console.log(expandedKeys)
}, [expandedKeys])
const chapterMenuItems = [
{ key: '1', label: '展开全部' },
{ key: '2', label: '添加子节' },
{ key: '5', label: '设置编者' },
{ key: '3', label: '编辑' },
{ key: '4', label: '删除' }
]
// 编辑章节名称
const titleRenderDom = node => {
const handleMenuClick = async (e, node) => {
e.domEvent.stopPropagation()
setCurrentChapter(node)
if (parseInt(e.key) === 1) {
// 展开全部
const ids = findNodeById([node], 'key', node.key)
const expandedKeysTemp = [...expandedKeys, node.key, ...ids]
setExpandedKeys(expandedKeysTemp)
} else if (parseInt(e.key) === 2) {
// 添加子节
setEditKey(-1)
setEditValue('')
setParentId(node.key)
} else if (parseInt(e.key) === 3) {
// 编辑
setEditKey(node.key)
setEditValue(node.title)
setParentId(false)
} else if (parseInt(e.key) === 4) {
setDelNode(node)
setOpenDel(true)
} else if (e.key == 5) {
setEditChapterEditorsIsOpen(true)
}
}
// 章节名称
const chapterTitleRender = node => {
return (
<div className="tree-customer-item">
<div className="title">
<div className="title-node">{node.title}</div>
</div>
<div className="opaeration">
{quanXian ? (
<Dropdown
trigger={['click']}
overlayClassName="dropmenu_list"
destroyPopupOnHide={true}
menu={{
items: [
{
key: '1',
label: <Button type="text">展开全部</Button>
},
{
key: '2',
label: <Button type="text">添加子节</Button>
},
{
key: '5',
label: <Button type="text">设置编者</Button>
},
{
key: '3',
label: <Button type="text">重命名</Button>
},
{
key: '4',
label: <Button type="text">删除</Button>
}
],
onClick: e => dropDownMenuHandler(e, node)
}}>
<div className="dashed">
<span></span>
<span></span>
<span></span>
</div>
{/* <EllipsisOutlined /> */}
</Dropdown>
) : (
<Dropdown
trigger={['click']}
overlayClassName="dropmenu_list"
destroyPopupOnHide={true}
menu={{
items: [
{
key: '1',
label: <Button type="text">展开全部</Button>
},
{
key: '2',
label: <Button type="text">添加子节</Button>
},
{
key: '3',
label: <Button type="text">编辑</Button>
},
{
key: '4',
label: <Button type="text">删除</Button>
}
],
onClick: e => dropDownMenuHandler(e, node)
}}>
<div className="dashed">
<span></span>
<span></span>
<span></span>
</div>
{/* <EllipsisOutlined /> */}
</Dropdown>
)}
</div>
<div className="chapter-tree-item">
<p className="title">{node.title}</p>
<Dropdown trigger={['click']} menu={{ items: chapterMenuItems, onClick: e => handleMenuClick(e, node) }}>
<EllipsisOutlined onClick={e => e.stopPropagation()} />
</Dropdown>
</div>
)
}
......@@ -534,7 +459,7 @@ const Examine = () => {
disabled={loading}
treeData={gData}
onExpand={onExpand}
titleRender={nodeData => titleRenderDom(nodeData)}
titleRender={nodeData => chapterTitleRender(nodeData)}
/>
)}
</>
......@@ -623,8 +548,8 @@ const Examine = () => {
wrapper: 'chapter-title-modal'
}}>
<Divider />
<div className="">确认删除子节【{delNode.title}】?</div>
<div className="" style={{ display: 'flex', justifyContent: 'flex-end' }}>
<div>确认删除子节【{delNode.title}】?</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Space>
<Button type="primary" danger loading={delLoading} onClick={() => delChapter(delNode)}>
确定
......@@ -640,6 +565,15 @@ const Examine = () => {
</Space>
</div>
</Modal>
{/* 设置章节编写者 */}
{currentChapter && editChapterEditorsIsOpen && (
<EditChapterEditors
open={editChapterEditorsIsOpen}
chapter={currentChapter}
onCancel={() => setEditChapterEditorsIsOpen(false)}
onChange={onChapterEditorsChange}></EditChapterEditors>
)}
</div>
)
}
......
......@@ -30,7 +30,7 @@
.section-left-top {
height: 85px;
}
.ant-descriptions.ant-descriptions-bordered >.ant-descriptions-view .ant-descriptions-row {
.ant-descriptions.ant-descriptions-bordered > .ant-descriptions-view .ant-descriptions-row {
border-bottom: 1px solid #e5e5e5;
&:nth-of-type(2) {
display: none;
......@@ -39,13 +39,14 @@
}
.editor-right {
height: 100%;
.ant-spin-nested-loading, .ant-spin-container {
.ant-spin-nested-loading,
.ant-spin-container {
height: 100%;
}
}
}
.draggable-tree {
flex:1;
flex: 1;
height: calc(100vh - 285px);
overflow-y: auto;
overflow-x: hidden;
......@@ -61,11 +62,10 @@
overflow: hidden;
}
}
.tree-customer-item {
.chapter-tree-item {
display: flex;
justify-content: space-between;
overflow: hidden;
width: 100%;
.title {
flex: 1;
overflow: hidden;
......@@ -74,46 +74,10 @@
max-height: 24px;
line-height: 24px;
white-space: nowrap;
.title-node {
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
max-height: 24px;
line-height: 24px;
}
}
.opaeration {
flex: 0 0 20px;
width: 20px;
align-items: center;
.dashed {
height: 24px;
line-height: 24px;
text-align: center;
align-items: center;
display: flex;
span {
margin: 0 1px;
display: inline-block;
width: 2px;
height: 2px;
// border: 1px solid #999;
background-color: #999;
border-radius: 50%;
}
}
}
}
}
.dropmenu_list {
.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item:hover {
}
.ant-btn-text:not(:disabled):not(.ant-btn-disabled):hover {
background: transparent !important;
}
}
.chapter-title-modal {
.ant-modal {
.ant-modal-close {
......
import { useEffect, useState, useRef } from 'react';
import { Input, Button, Space, Popover, Switch, Image, App } from 'antd';
import { getCoupon, setCoupon } from './request';
import Add from '@/assets/images/icon/add.png';
import coupon from '@/assets/images/icon/coupon.png';
import coupon2 from '@/assets/images/icon/coupon2.png';
import { useSelector } from 'react-redux';
import AppList from '@/components/AppList';
import FormDrawer from './components/FormDrawer';
import SendCouponDrawer from './components/SendCouponDrawer';
import * as api from './api';
import { useEffect, useState, useRef } from 'react'
import { Input, Button, Space, Popover, Switch, Image, App } from 'antd'
import { getCoupon, setCoupon } from './request'
import Add from '@/assets/images/icon/add.png'
import coupon from '@/assets/images/icon/coupon.png'
import coupon2 from '@/assets/images/icon/coupon2.png'
import { useSelector } from 'react-redux'
import AppList from '@/components/list'
import FormDrawer from './components/FormDrawer'
import SendCouponDrawer from './components/SendCouponDrawer'
import * as api from './api'
const Coupon = () => {
const { modal } = App.useApp();
const { modal } = App.useApp()
const appListRef = useRef(null);
const appListRef = useRef(null)
const reload = async () => {
await appListRef.current.handleReload(true);
};
const [drawerOpen, setDrawerOpen] = useState(false);
await appListRef.current.handleReload(true)
}
const [drawerOpen, setDrawerOpen] = useState(false)
// 获取操作权限
const { operationPermissionsList } = useSelector((state) => state.user);
const { operationPermissionsList } = useSelector(state => state.user)
// 操作某一项的id
const [id, setId] = useState('');
const [id, setId] = useState('')
const [switchFlag, setSwitchFlag] = useState(false);
const [couponList, setCouponList] = useState([]);
const [sendCouponDrawerOpen, setSendCouponDrawerOpen] = useState(false);
const [switchFlag, setSwitchFlag] = useState(false)
const [couponList, setCouponList] = useState([])
const [sendCouponDrawerOpen, setSendCouponDrawerOpen] = useState(false)
// 添加
const handleAdd = () => {
setId('');
setDrawerOpen(true);
};
setId('')
setDrawerOpen(true)
}
// 编辑
const handleEdit = async (id) => {
setId(id);
setDrawerOpen(true);
};
const handleEdit = async id => {
setId(id)
setDrawerOpen(true)
}
const onClose = () => {
setDrawerOpen(false);
};
setDrawerOpen(false)
}
// 删除
const handleDelete = async (id) => {
const handleDelete = async id => {
modal.confirm({
title: '提示',
content: '优惠券将永久删除,是否继续?',
onOk: async () => {
await api.delCoupon({ id });
reload();
},
});
};
await api.delCoupon({ id })
reload()
}
})
}
// 获取优惠券开关状态
const handleCoupon = async () => {
const { coupon_switch } = await getCoupon();
coupon_switch == '1' ? setSwitchFlag(true) : setSwitchFlag(false);
};
const [swithcLoad, setSwitchLoad] = useState(false);
const { coupon_switch } = await getCoupon()
coupon_switch == '1' ? setSwitchFlag(true) : setSwitchFlag(false)
}
const [swithcLoad, setSwitchLoad] = useState(false)
// 改变优惠券开关
const changeCoupon = async (val) => {
setSwitchLoad(true);
let coupon_switch = val ? '1' : '0';
const bool = await setCoupon({ coupon_switch });
const changeCoupon = async val => {
setSwitchLoad(true)
let coupon_switch = val ? '1' : '0'
const bool = await setCoupon({ coupon_switch })
setTimeout(() => {
setSwitchLoad(false);
}, 500);
handleCoupon();
};
setSwitchLoad(false)
}, 500)
handleCoupon()
}
useEffect(() => {
handleCoupon();
}, []);
handleCoupon()
}, [])
const remote = {
request: api.getList,
afterRequest(data) {
setCouponList(data.list);
return data;
},
};
setCouponList(data.list)
return data
}
}
const filters = [
{
label: '优惠券名称',
name: 'name',
element: <Input placeholder='请输入优惠券名称' allowClear />,
},
];
element: <Input placeholder="请输入优惠券名称" allowClear />
}
]
const columns = [
{
title: 'ID',
key: 'id',
align: 'center',
dataIndex: 'id',
dataIndex: 'id'
},
{
title: '优惠券名称',
......@@ -100,7 +100,7 @@ const Coupon = () => {
align: 'center',
dataIndex: 'name',
width: 200,
render: (text) => (
render: text => (
<Popover content={text} overlayStyle={{ maxWidth: '400px' }}>
<div
style={{
......@@ -109,49 +109,48 @@ const Coupon = () => {
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'center',
}}
>
textAlign: 'center'
}}>
{text}
</div>
</Popover>
),
)
},
{
title: '达标金额',
key: 'norm_price',
align: 'center',
dataIndex: 'norm_price',
dataIndex: 'norm_price'
},
{
title: '满减金额',
key: 'reduced_price',
align: 'center',
dataIndex: 'reduced_price',
dataIndex: 'reduced_price'
},
{
title: '有效期开始时间',
key: 'start_time',
align: 'center',
dataIndex: 'start_time',
dataIndex: 'start_time'
},
{
title: '有效期结束时间',
key: 'end_time',
align: 'center',
dataIndex: 'end_time',
dataIndex: 'end_time'
},
{
title: '领取人数',
key: 'receive_num',
align: 'center',
dataIndex: 'receive_num',
dataIndex: 'receive_num'
},
{
title: '使用人数',
key: 'use_num',
align: 'center',
dataIndex: 'use_num',
dataIndex: 'use_num'
},
{
title: '操作',
......@@ -161,30 +160,25 @@ const Coupon = () => {
render: (_, { id }) => {
return (
<Space>
{operationPermissionsList.includes('/setting/coupon/getInfoById') && (
<Button onClick={() => handleEdit(id)}>编辑</Button>
)}
{operationPermissionsList.includes('/setting/coupon/del') && (
<Button onClick={() => handleDelete(id)}>删除</Button>
)}
{operationPermissionsList.includes('/setting/coupon/getInfoById') && <Button onClick={() => handleEdit(id)}>编辑</Button>}
{operationPermissionsList.includes('/setting/coupon/del') && <Button onClick={() => handleDelete(id)}>删除</Button>}
</Space>
);
},
},
];
)
}
}
]
const filterAside = (
<Space>
<label htmlFor='coupon'>优惠券开关</label>
<label htmlFor="coupon">优惠券开关</label>
<Switch
checked={switchFlag}
loading={swithcLoad}
onChange={changeCoupon}
disabled={!operationPermissionsList.includes('/setting/coupon/getCoupon')}
></Switch>
disabled={!operationPermissionsList.includes('/setting/coupon/getCoupon')}></Switch>
{operationPermissionsList.includes('/setting/coupon/add') && (
<Button
type='primary'
type="primary"
disabled={!switchFlag}
ghost
icon={
......@@ -194,19 +188,18 @@ const Coupon = () => {
display: 'inline-block',
width: '13px',
height: '11px',
pointerEvents: 'none',
pointerEvents: 'none'
}}
src={!switchFlag ? coupon2 : coupon}
/>
}
onClick={() => setSendCouponDrawerOpen(true)}
>
onClick={() => setSendCouponDrawerOpen(true)}>
优惠券发放
</Button>
)}
{operationPermissionsList.includes('/setting/coupon/give') && (
<Button
type='primary'
type="primary"
ghost
icon={
<span
......@@ -214,33 +207,27 @@ const Coupon = () => {
display: 'inline-block',
width: '13px',
height: '12px',
pointerEvents: 'none',
}}
>
pointerEvents: 'none'
}}>
<Image src={Add} />
</span>
}
onClick={handleAdd}
>
onClick={handleAdd}>
添加
</Button>
)}
</Space>
);
)
return (
<div className='classify'>
<div className="classify">
<AppList {...{ remote, filters, columns, filterAside }} ref={appListRef}></AppList>
{/* 添加编辑 */}
<FormDrawer open={drawerOpen} id={id} onClose={onClose} onComplete={reload}></FormDrawer>
{/* 优惠券发送 */}
<SendCouponDrawer
open={sendCouponDrawerOpen}
couponList={couponList}
onClose={() => setSendCouponDrawerOpen(false)}
></SendCouponDrawer>
<SendCouponDrawer open={sendCouponDrawerOpen} couponList={couponList} onClose={() => setSendCouponDrawerOpen(false)}></SendCouponDrawer>
</div>
);
};
)
}
export default Coupon;
export default Coupon
import { Input, DatePicker, Popover } from 'antd';
import AppList from '@/components/AppList';
import { getList } from './api';
const { RangePicker } = DatePicker;
import { Input, DatePicker, Popover } from 'antd'
import AppList from '@/components/list'
import { getList } from './api'
const { RangePicker } = DatePicker
export default function FeedBack() {
const remote = {
request: getList,
beforeRequest(params) {
const { time, ...rest } = params;
const { time, ...rest } = params
if (time) {
rest.start_time = new Date(time[0]).getTime() / 1000;
rest.end_time = new Date(time[1]).getTime() / 1000;
rest.start_time = new Date(time[0]).getTime() / 1000
rest.end_time = new Date(time[1]).getTime() / 1000
}
return rest;
},
};
return rest
}
}
const filters = [
{
label: '用户名称',
name: 'name',
element: <Input placeholder='请输入用户名称' allowClear />,
element: <Input placeholder="请输入用户名称" allowClear />
},
{
label: '手机号',
name: 'phone',
element: <Input placeholder='请输入手机号' allowClear />,
element: <Input placeholder="请输入手机号" allowClear />
},
{
label: '反馈时间',
name: 'time',
element: <RangePicker allowClear />,
},
];
element: <RangePicker allowClear />
}
]
const columns = [
{
title: 'ID',
key: 'id',
dataIndex: 'id',
dataIndex: 'id'
},
{
title: '用户名称',
key: 'name',
dataIndex: 'name',
align: 'center',
align: 'center'
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone',
align: 'center',
align: 'center'
},
{
title: '反馈时间',
key: 'create_time',
dataIndex: 'create_time',
align: 'center',
align: 'center'
},
{
title: '反馈内容',
......@@ -62,7 +62,7 @@ export default function FeedBack() {
dataIndex: 'proposal',
align: 'center',
width: 400,
render: (text) => (
render: text => (
<Popover content={text} overlayStyle={{ maxWidth: '400px' }}>
<div
style={{
......@@ -71,15 +71,14 @@ export default function FeedBack() {
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
textAlign: 'center',
}}
>
textAlign: 'center'
}}>
{text}
</div>
</Popover>
),
},
];
)
}
]
return <AppList {...{ remote, filters, columns }}></AppList>;
return <AppList {...{ remote, filters, columns }}></AppList>
}
import axios from 'axios';
import md5 from 'js-md5';
import qs from 'qs';
import { Modal, notification } from 'antd';
import axios from 'axios'
import md5 from 'js-md5'
import qs from 'qs'
import { Modal, notification } from 'antd'
function getToken() {
return window.localStorage.getItem('kiwi.token') ? window.localStorage.getItem('kiwi.token') : '';
return window.localStorage.getItem('kiwi.token') ? window.localStorage.getItem('kiwi.token') : ''
}
function showLoginModal() {
Modal.warning({
title: '登录状态已过期,请重新登录',
onOk() {
localStorage.removeItem('kiwi.gpt.token');
location.href = '/login';
},
});
localStorage.removeItem('kiwi.gpt.token')
location.href = '/login'
}
})
}
// 排序
function alphabeticalSort(a, b) {
return a.localeCompare(b);
return a.localeCompare(b)
}
const httpRequest = axios.create({
// timeout: 60000,
withCredentials: true,
});
withCredentials: true
})
// 请求拦截器
httpRequest.interceptors.request.use(
(config) => {
const token = getToken();
config => {
const token = getToken()
// 应用设置
const appId = 'TzEU5jPk2tu80266';
const appSecret = '0a006048a4480481b18fef1405120b83';
const timestamp = Math.floor(Date.now() / 1000);
const appId = 'TzEU5jPk2tu80266'
const appSecret = '0a006048a4480481b18fef1405120b83'
const timestamp = Math.floor(Date.now() / 1000)
const signData = {
...config.data,
......@@ -42,60 +42,60 @@ httpRequest.interceptors.request.use(
appSecret,
timestamp,
token,
url: config.url,
};
url: config.url
}
const signStr = qs.stringify(signData, { sort: alphabeticalSort });
const signStr = qs.stringify(signData, { sort: alphabeticalSort })
const salt = '&4F6g4Y6b5L4R9';
const sign = md5(md5(signStr + salt));
const salt = '&4F6g4Y6b5L4R9'
const sign = md5(md5(signStr + salt))
const defaultHeaders = {
Authorization: token,
AppId: appId,
AppSecret: appSecret,
Timestamp: timestamp,
Sign: sign,
};
config.headers = { ...config.headers, ...defaultHeaders };
Sign: sign
}
config.headers = { ...config.headers, ...defaultHeaders }
return config;
return config
},
(error) => {
return Promise.reject(error);
},
);
error => {
return Promise.reject(error)
}
)
// 响应拦截器
httpRequest.interceptors.response.use(
(response) => {
const { data } = response;
response => {
const { data } = response
if (data.code === 401 || data.code === 403) {
if (location.pathname !== '/login') {
showLoginModal();
showLoginModal()
}
return Promise.reject(data);
return Promise.reject(data)
}
if (data.code !== 200) {
notification.error({ message: data.message || '请求错误' });
return Promise.reject(data);
notification.error({ message: data.message || '请求错误' })
return Promise.reject(data)
}
return data;
return data
},
(error) => {
error => {
if (error.response) {
const { status, data = {} } = error.response;
const { status, data = {} } = error.response
if (status === 401 || status === 403) {
showLoginModal();
showLoginModal()
} else {
notification.error({ message: data.message || '请求错误' });
console.error(`${status}: ${data.message}`);
notification.error({ message: data.message || '请求错误' })
console.error(`${status}: ${data.message}`)
}
} else {
console.log(error);
console.log(error)
}
return Promise.reject(error.response || error);
},
);
return Promise.reject(error.response || error)
}
)
export default httpRequest;
export default httpRequest
差异被折叠。
......@@ -44,7 +44,8 @@ async function createOSSInstance() {
export async function uploadFile(file) {
try {
const oss = await createOSSInstance()
const fileName = `${new Date().getTime()}-${Math.random() * 1000}.jpg`
const fileExt = file.name.substring(file.name.lastIndexOf('.'))
const fileName = `${new Date().getTime()}-${Math.random() * 1000}${fileExt}`
const result = await oss.put(fileName, file)
return result.url
} catch (error) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论