feat: 增加练题

This commit is contained in:
秋水浮尘
2023-12-17 22:03:08 +08:00
parent 0d4f9226c3
commit d0225795da
79 changed files with 10134 additions and 2742 deletions

View File

@@ -21,6 +21,7 @@
}
},
"dependencies": {
"@ant-design/charts": "^1.4.3",
"@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.7",
"@wangeditor/editor": "^5.1.23",

3914
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
.app-main {
display: flex;
flex-direction: row;
flex-direction: column;
border-radius: 4px;
margin: 0 auto;
position: absolute;
@@ -12,7 +12,7 @@
right: 0;
bottom: 0;
background-color: #f3f4f6;
padding: 66px 16px 32px;
padding: 66px 16px 0;
overflow: hidden;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
@@ -25,5 +25,11 @@
width: 1439px;
margin: 0 auto;
overflow: auto;
flex-grow: 1;
}
.copyright {
height: 40px;
line-height: 40px;
text-align: center;
}
}

View File

@@ -1,11 +1,46 @@
import req from '@utils/request'
import Header from '@views/header'
import { Suspense, memo, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
// 引入对应的方法
import './App.less'
import { saveUserInfo } from './store/features/userInfoSlice.ts'
const apiName = {
update: '/user/update',
queryInfo: '/user/getUserInfo'
}
const App = () => {
const userInfoStorage = localStorage.getItem('userInfo')
const { loginId = '' } = userInfoStorage ? JSON.parse(userInfoStorage) : {}
const dispatch = useDispatch()
const location = useLocation()
const navigate = useNavigate()
const getUserInfo = async () => {
req(
{
method: 'post',
url: apiName.queryInfo,
data: {
userName: loginId
}
},
'/auth'
).then(res => {
if (res?.success && res?.data) {
dispatch(saveUserInfo(res.data))
}
})
}
useEffect(() => {
getUserInfo()
}, [])
useEffect(() => {
if (location.pathname === '/') {
const userInfoStorage = localStorage.getItem('userInfo')
@@ -18,7 +53,7 @@ const App = () => {
return (
<div
className='app-main'
style={{ padding: location.pathname === '/login' ? '66px 0 0' : '66px 16px 32px' }}
style={{ padding: location.pathname === '/login' ? '66px 0 0' : '66px 16px 0' }}
>
<Header />
<div
@@ -29,6 +64,11 @@ const App = () => {
<Outlet />
</Suspense>
</div>
<div className='copyright'>
<a href='http://beian.miit.gov.cn/' target='_blank'>
ICP备2023035579号
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,71 @@
import { Radar } from '@ant-design/charts'
import React, { memo } from 'react'
//atlasList 列表数据 [{name:'',star:''}]
//aliasStr 鼠标浮上去显示的框框内的别名
//fillOpacity 填充区域的透明度
//fill 填充区域的颜色
//alternateColor 图形相间的颜色
//lineColor 线的颜色
//atlasWidth 分析图宽
//atlasHeight 分析图长
//atlasSpan 分析图跨度
//atlasParag 你需要有几个圈
export default memo(function ({
atlasList = [],
aliasStr,
fillOpacity = 0.2,
fill = 'rgb(60, 110, 238)',
alternateColor = 'rgba(0, 0, 0, 0.04)',
lineColor = 'rgb(60, 110, 238)',
atlasWidth = 250,
atlasHeight = 250,
atlasSpan = 25,
atlasParag = 4,
atlasMin = 0,
atlasMax = 100
}) {
let spanList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
.splice(0, atlasParag + 1)
.map(item => item * atlasSpan + '')
const config = {
data: atlasList,
xField: 'name',
yField: 'star',
width: atlasWidth,
height: atlasHeight,
appendPadding: [0, 15, 15, 15],
meta: {
star: {
alias: aliasStr, //字段别名
min: atlasMin,
max: atlasMax,
nice: true,
formatter: v => v,
values: spanList //用来控制有几个圈
}
},
xAxis: {
tickLine: null
},
yAxis: {
label: false,
grid: {
alternateColor: alternateColor
}
},
// 开启辅助点
point: {
size: 2
},
lineStyle: {
fill: fill,
fillOpacity: fillOpacity,
cursor: 'pointer',
stroke: lineColor
}
}
return <Radar {...config} />
})

View File

@@ -1,304 +1,303 @@
import React, { Component } from 'react';
import React, { Component } from 'react'
import req from '@utils/request';
import { Input, Tag, Tooltip, message } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { apiName, ModuleType } from './constant';
import './index.less';
import { PlusOutlined } from '@ant-design/icons'
import req from '@utils/request'
import { Input, Tag, Tooltip, message } from 'antd'
import { ModuleType, apiName } from './constant'
import './index.less'
const apiNameModule = {
[ModuleType.second]: [apiName.addInterviewCategory, apiName.deleteInterviewCategory],
[ModuleType.third]: [apiName.addInterviewLabel, apiName.deleteInterviewLabel],
};
[ModuleType.second]: [apiName.addInterviewCategory, apiName.deleteInterviewCategory],
[ModuleType.third]: [apiName.addInterviewLabel, apiName.deleteInterviewLabel]
}
export default class TagsEditor extends Component {
saveInputRef = (input) => (this.input = input);
saveInputRef = input => (this.input = input)
constructor(props) {
super(props);
this.state = {
inputVisible: false,
inputValue: '',
};
constructor(props) {
super(props)
this.state = {
inputVisible: false,
inputValue: ''
}
}
/**
* 点击X号
* @param {*} index 当前index
* @param {*} categoryId 当前id
*/
handleClose = (index, categoryId) => {
const { moduleType, categoryList } = this.props;
let params = {
id: categoryId,
};
let url = apiNameModule[moduleType][1];
req({
method: 'post',
data: params,
url: url,
})
.then((res) => {
if (res.data) {
let list = categoryList.filter((item) => {
return item.id !== categoryId;
});
this.props.onChangeLabel(list, this.formatList(list));
}
})
.catch((err) => {
console.log(err);
});
};
/**
* 展示输入框
*/
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus());
};
/**
* 输入框改变内容
* @param {*} e
*/
handleInputChange = (e) => {
this.setState({ inputValue: e.target.value });
};
/**
* 增加标签
*/
handleInputConfirm = () => {
let { categoryList } = this.props;
let { inputValue } = this.state;
let equalList = [],
formatInputValue = inputValue.trim();
if (!formatInputValue) {
this.setState({
inputValue: '',
inputVisible: false,
});
return;
/**
* 点击X号
* @param {*} index 当前index
* @param {*} categoryId 当前id
*/
handleClose = (index, categoryId) => {
const { moduleType, categoryList } = this.props
let params = {
id: categoryId
}
let url = apiNameModule[moduleType][1]
req({
method: 'post',
data: params,
url: url
})
.then(res => {
if (res.data) {
let list = categoryList.filter(item => {
return item.id !== categoryId
})
this.props.onChangeLabel(list, this.formatList(list))
}
if (categoryList.length > 0) {
equalList = categoryList.filter((item) => {
return item.categoryName.toLowerCase() === formatInputValue.toLowerCase();
});
}
if (equalList.length <= 0) {
this.postAddInterviewCategory(formatInputValue);
} else {
message.info('所增内容已存在', 0.3);
this.setState({
inputValue: '',
});
}
};
})
.catch(err => {
console.log(err)
})
}
/**
* 增加标签
* @param {*} inputValue 当前的值
*/
postAddInterviewCategory = (inputValue) => {
const { parentCategoryValue, moduleType, categoryList } = this.props;
let params_2 = {
categoryName: inputValue,
categoryType: 2,
parentId: parentCategoryValue[0],
};
let params_3 = {
labelName: inputValue,
primaryCategoryId: parentCategoryValue[0],
};
let params = moduleType == ModuleType.third ? params_3 : params_2;
req({
method: 'post',
data: params,
url: apiNameModule[moduleType][0],
})
.then((res) => {
if (res.data) {
let id = res.data;
let list = [
...categoryList,
{
categoryName: inputValue,
categoryId: id,
isShowClose: true,
},
];
let formatList = this.onHandleLabelSelectState(list, list.length - 1, false);
this.setState(
{
inputVisible: false,
inputValue: '',
},
() => {
this.props.onChangeLabel(formatList, this.formatList(formatList));
}
);
}
})
.catch((err) => {
console.log(err);
});
};
/**
* 展示输入框
*/
showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus())
}
/**
* 选中/未选中-标签
* @param {*} tagIndex 选择的标签
* @param {*} active 选择的标签的当前状态
* @returns
*/
onChangeLabel = (tagIndex, active) => () => {
let { categoryList, isDisabledReverseSelection } = this.props;
if (active && isDisabledReverseSelection) {
return;
}
let formatLabelList = this.onHandleLabelSelectState(categoryList, tagIndex, active);
this.props.onChangeLabel(formatLabelList, this.formatList(formatLabelList));
};
/**
* 输入框改变内容
* @param {*} e
*/
handleInputChange = e => {
this.setState({ inputValue: e.target.value })
}
/**
* 处理数据选中/未选中(单选/多选)
* @param {*} list 分类列表
* @param {*} tagIndex 当前索引
* @param {*} active 当前选中状态
* @returns
*/
onHandleLabelSelectState = (list, tagIndex, active) => {
const { isSingleChoice } = this.props;
let formatLabelList = [];
// 单选
if (isSingleChoice) {
formatLabelList = list.map((item, index) => {
let flag = false;
if (index == tagIndex) {
flag = !active; // 将三级标签设置选中/未选中
}
return {
...item,
active: flag,
};
});
} else {
// 多选
formatLabelList = list.map((item, index) => {
let flag = item.active;
if (index == tagIndex) {
flag = !active; // 将三级标签设置选中/未选中
}
return {
...item,
active: flag,
};
});
}
return formatLabelList;
};
/**
* 增加标签
*/
handleInputConfirm = () => {
let { categoryList } = this.props
let { inputValue } = this.state
let equalList = [],
formatInputValue = inputValue.trim()
if (!formatInputValue) {
this.setState({
inputValue: '',
inputVisible: false
})
return
}
if (categoryList.length > 0) {
equalList = categoryList.filter(item => {
return item.categoryName.toLowerCase() === formatInputValue.toLowerCase()
})
}
if (equalList.length <= 0) {
this.postAddInterviewCategory(formatInputValue)
} else {
message.info('所增内容已存在', 0.3)
this.setState({
inputValue: ''
})
}
}
/**
* 格式化数据-获得选中项id列表
* @param {*} list
* @returns
*/
formatList = (list) => {
let labelList = [];
list.forEach((item) => {
if (item.active) {
labelList.push(item.id);
/**
* 增加标签
* @param {*} inputValue 当前的值
*/
postAddInterviewCategory = inputValue => {
const { parentCategoryValue, moduleType, categoryList } = this.props
let params_2 = {
categoryName: inputValue,
categoryType: 2,
parentId: parentCategoryValue[0]
}
let params_3 = {
labelName: inputValue,
primaryCategoryId: parentCategoryValue[0]
}
let params = moduleType == ModuleType.third ? params_3 : params_2
req({
method: 'post',
data: params,
url: apiNameModule[moduleType][0]
})
.then(res => {
if (res.data) {
let id = res.data
let list = [
...categoryList,
{
categoryName: inputValue,
categoryId: id,
isShowClose: true
}
});
return labelList;
};
render() {
const { moduleType, categoryList, isAddTag, isDeleteTag } = this.props;
const { inputVisible, inputValue } = this.state;
let labelList = categoryList;
// 数组中存在 -9999 表示暂无数据,需要支持新增
if (
categoryList.filter((item) => {
return item.id === -9999;
}).length > 0
) {
labelList = categoryList.slice(1, categoryList.length);
]
let formatList = this.onHandleLabelSelectState(list, list.length - 1, false)
this.setState(
{
inputVisible: false,
inputValue: ''
},
() => {
this.props.onChangeLabel(formatList, this.formatList(formatList))
}
)
}
return (
<div className="tags-editor-box">
{labelList?.length > 0 &&
labelList.map((item, index) => {
const isLongTag = item.categoryName?.length > 20;
const tagElem = (
<Tag
style={{
margin: '4px',
height: '40px',
lineHeight: '40px',
fontSize: '13px',
padding: '0px 16px',
border: '1px solid #d9d9d9',
backgroundColor: '#fff',
cursor: 'pointer',
}}
key={item.id}
// 支持删除标签
closable={item.isShowClose && isDeleteTag}
className={`tags-editor-item ${item.active ? 'tag-active' : ''}`}
onClick={this.onChangeLabel(index, item.active)}
onClose={() => this.handleClose(index, item.id)}>
{isLongTag
? `${item.categoryName.slice(0, 20)}...`
: item.categoryName}
</Tag>
);
return isLongTag ? (
<Tooltip title={item.categoryName} key={item.id}>
{tagElem}
</Tooltip>
) : (
<div key={item.id}>
{tagElem}
</div>
);
})}
{inputVisible && (
<Input
ref={this.saveInputRef}
type="text"
size="small"
style={{ width: 78, height: 40, marginTop: '4px' }}
max={10}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{/* 支持手动增加标签 */}
{!inputVisible && isAddTag && (
<Tag
onClick={this.showInput}
style={{
background: '#fff',
borderColor: 'rgba(60, 110, 238, 1)',
borderStyle: 'dashed',
display: 'flex',
alignItems: 'center',
fontSize: '14px',
height: '40px',
padding: '0px 16px',
marginTop: '4px',
}}>
<PlusOutlined />
&nbsp;新增{moduleType == ModuleType.third ? '标签' : '分类'}
</Tag>
)}
{!isAddTag && labelList.length === 0 && (
<div className="tag-empty-tip">暂无数据呦</div>
)}
</div>
);
})
.catch(err => {
console.log(err)
})
}
/**
* 选中/未选中-标签
* @param {*} tagIndex 选择的标签
* @param {*} active 选择的标签的当前状态
* @returns
*/
onChangeLabel = (tagIndex, active) => () => {
let { categoryList, isDisabledReverseSelection } = this.props
if (active && isDisabledReverseSelection) {
return
}
let formatLabelList = this.onHandleLabelSelectState(categoryList, tagIndex, active)
this.props.onChangeLabel(formatLabelList, this.formatList(formatLabelList))
}
/**
* 处理数据选中/未选中(单选/多选)
* @param {*} list 分类列表
* @param {*} tagIndex 当前索引
* @param {*} active 当前选中状态
* @returns
*/
onHandleLabelSelectState = (list, tagIndex, active) => {
const { isSingleChoice } = this.props
let formatLabelList = []
// 单选
if (isSingleChoice) {
formatLabelList = list.map((item, index) => {
let flag = false
if (index == tagIndex) {
flag = !active // 将三级标签设置选中/未选中
}
return {
...item,
active: flag
}
})
} else {
// 多选
formatLabelList = list.map((item, index) => {
let flag = item.active
if (index == tagIndex) {
flag = !active // 将三级标签设置选中/未选中
}
return {
...item,
active: flag
}
})
}
return formatLabelList
}
/**
* 格式化数据-获得选中项id列表
* @param {*} list
* @returns
*/
formatList = list => {
let labelList = []
list.forEach(item => {
if (item.active) {
labelList.push(item.id)
}
})
return labelList
}
render() {
const { moduleType, categoryList, isAddTag, isDeleteTag } = this.props
const { inputVisible, inputValue } = this.state
let labelList = categoryList
// 数组中存在 -9999 表示暂无数据,需要支持新增
if (
categoryList.filter(item => {
return item.id === -9999
}).length > 0
) {
labelList = categoryList.slice(1, categoryList.length)
}
return (
<div className='tags-editor-box'>
{labelList?.length > 0 &&
labelList.map((item, index) => {
const isLongTag = item.categoryName?.length > 20
const tagElem = (
<Tag
style={{
margin: '4px',
height: '40px',
lineHeight: '40px',
fontSize: '13px',
padding: '0px 16px',
border: '1px solid #d9d9d9',
backgroundColor: '#fff',
cursor: 'pointer'
}}
key={(item.id || item.categoryId) + '_' + item.categoryName}
// 支持删除标签
closable={item.isShowClose && isDeleteTag}
className={`tags-editor-item ${item.active ? 'tag-active' : ''}`}
onClick={this.onChangeLabel(index, item.active)}
onClose={() => this.handleClose(index, item.id)}
>
{isLongTag ? `${item.categoryName.slice(0, 20)}...` : item.categoryName}
</Tag>
)
return isLongTag ? (
<Tooltip
title={item.categoryName}
key={(item.id || item.categoryId) + '_' + item.categoryName}
>
{tagElem}
</Tooltip>
) : (
<div key={(item.id || item.categoryId) + '_' + item.categoryName}>{tagElem}</div>
)
})}
{inputVisible && (
<Input
ref={this.saveInputRef}
type='text'
size='small'
style={{ width: 78, height: 40, marginTop: '4px' }}
max={10}
value={inputValue}
onChange={this.handleInputChange}
onBlur={this.handleInputConfirm}
onPressEnter={this.handleInputConfirm}
/>
)}
{/* 支持手动增加标签 */}
{!inputVisible && isAddTag && (
<Tag
onClick={this.showInput}
style={{
background: '#fff',
borderColor: 'rgba(60, 110, 238, 1)',
borderStyle: 'dashed',
display: 'flex',
alignItems: 'center',
fontSize: '14px',
height: '40px',
padding: '0px 16px',
marginTop: '4px'
}}
>
<PlusOutlined />
&nbsp;新增{moduleType == ModuleType.third ? '标签' : '分类'}
</Tag>
)}
{!isAddTag && labelList.length === 0 && <div className='tag-empty-tip'>暂无数据呦</div>}
</div>
)
}
}

View File

@@ -0,0 +1,118 @@
import React, { Component } from 'react'
import Flipper from './Flipper'
import './flipClock.less'
class FlipClock extends Component {
constructor(props) {
super(props)
this.timer = null
this.flipObjs = []
this.state = {
hou: 0,
second: 0,
minutes: 0,
strikes: 0,
oneHour: false,
halfHour: false
}
}
render() {
let { oneHour, halfHour } = this.state
return (
<div className='FlipClock'>
<Flipper ref='flipperHour1' oneHour={oneHour} halfHour={halfHour} />
<Flipper ref='flipperHour2' oneHour={oneHour} halfHour={halfHour} />
<em>:</em>
<Flipper ref='flipperMinute1' oneHour={oneHour} halfHour={halfHour} />
<Flipper ref='flipperMinute2' oneHour={oneHour} halfHour={halfHour} />
<em>:</em>
<Flipper ref='flipperSecond1' oneHour={oneHour} halfHour={halfHour} />
<Flipper ref='flipperSecond2' oneHour={oneHour} halfHour={halfHour} />
</div>
)
}
componentDidMount() {
this.flipObjs = [
this.refs.flipperHour1,
this.refs.flipperHour2,
this.refs.flipperMinute1,
this.refs.flipperMinute2,
this.refs.flipperSecond1,
this.refs.flipperSecond2
]
this.init()
}
// 初始化数字
init() {
for (let i = 0; i < this.flipObjs.length; i++) {
this.flipObjs[i].setFront(0)
}
}
// 开始计时
run = () => {
this.timer = setInterval(() => {
// 获取当前时间
const nextstrikes = this.state.strikes + 1000
const o_nextstrikes = nextstrikes + 1000
let hou = parseInt(nextstrikes / 3600000) % 24,
minetes = parseInt(nextstrikes / 60000) % 60,
second = parseInt(nextstrikes / 1000) % 60
let o_hou = parseInt(o_nextstrikes / 3600000) % 24,
o_minetes = parseInt(o_nextstrikes / 60000) % 60,
o_second = parseInt(o_nextstrikes / 1000) % 60
let n_hou = o_hou <= 9 ? '0' + o_hou : o_hou,
n_minetes = o_minetes <= 9 ? '0' + o_minetes : o_minetes,
n_second = o_second <= 9 ? '0' + o_second : o_second
let nextTimeStr = n_hou + n_minetes + n_second
this.setState(
{
hou: hou <= 9 ? '0' + hou : hou,
minutes: minetes <= 9 ? '0' + minetes : minetes,
second: second <= 9 ? '0' + second : second,
strikes: nextstrikes,
oneHour: 10000 <= nextTimeStr,
halfHour: 3000 <= nextTimeStr
},
() => {
let { hou, minutes, second } = this.state
let nowTimeStr = hou + minutes + second
for (let i = 0; i < this.flipObjs.length; i++) {
if (nowTimeStr[i] === nextTimeStr[i]) {
continue
}
this.flipObjs[i].flipDown(nowTimeStr[i], nextTimeStr[i])
}
}
)
}, 1000)
}
end = () => {
clearInterval(this.timer)
}
stop = () => {
clearInterval(this.timer)
}
/**
* 计时的时间段
* @returns
*/
getUseTime = () => {
const { hou, minutes, second } = this.state
const nowTimeStr = hou + minutes + second
return nowTimeStr
}
//清除定时器
componentWillUnmount() {
clearInterval(this.timer)
}
}
export default FlipClock

View File

@@ -0,0 +1,106 @@
/*
* 翻牌数字
* @author 兔子先生
* @createDate: 2019-11-24
*/
// import PropTypes from 'prop-types'
import React, { Component } from 'react'
import './flipper.less'
class Flipper extends Component {
constructor(props) {
super(props)
this.state = {
isFlipping: false,
flipType: 'down',
frontTextFromData: 0,
backTextFromData: 1
}
}
render() {
const { isFlipping, flipType, frontTextFromData, backTextFromData } = this.state
return (
<div className={['M-Flipper', flipType, isFlipping ? 'go' : null].join(' ')}>
<div
className={`digital front ` + this._thisTimeClass() + this._textClass(frontTextFromData)}
></div>
<div
className={'digital back ' + this._thisTimeClass() + this._textClass(backTextFromData)}
></div>
</div>
)
}
_textClass(number) {
return 'number' + number
}
_thisTimeClass() {
let { oneHour, halfHour } = this.props
return (halfHour ? 'digital30 ' : '') + (oneHour ? 'digital60 ' : ' ')
}
_flip(type, front, back) {
// 如果处于翻转中,则不执行
if (this.isFlipping) {
return false
}
this.setState({
frontTextFromData: front,
backTextFromData: back,
// 根据传递过来的type设置翻转方向
flipType: type,
// 设置翻转状态为true
isFlipping: true
})
setTimeout(() => {
this.setState({
frontTextFromData: back,
isFlipping: false
})
}, this.props.duration)
}
// 下翻牌
flipDown(front, back) {
this._flip('down', front, back)
}
// 上翻牌
flipUp(front, back) {
this._flip('up', front, back)
}
// 设置前牌文字
setFront(text) {
this.setState({
frontTextFromData: text
})
}
// 设置后牌文字
setBack(text) {
this.setState({
backTextFromData: text
})
}
}
// props类型校验
// Flipper.propTypes = {
// frontText: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
// backText: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
// duration: PropTypes.number
// }
// props默认值
Flipper.defaultProps = {
// front paper text
// 前牌文字
frontText: 0,
// back paper text
// 后牌文字
backText: 1,
// flipping duration, please be consistent with the CSS animation-duration value.
// 翻牌动画时间与CSS中设置的animation-duration保持一致
duration: 600
}
export default Flipper

View File

@@ -0,0 +1,17 @@
.FlipClock {
text-align: center;
}
.FlipClock .M-Flipper {
margin: 0 3px;
}
.FlipClock em {
display: inline-block;
line-height: 27px;
font-size: 40px;
font-style: normal;
vertical-align: top;
margin: 0 3px;
color: rgba(0, 0, 0, 0.65);
}

View File

@@ -0,0 +1,196 @@
.M-Flipper {
display: inline-block;
position: relative;
width: 36px;
height: 35px;
line-height: 34px;
/* border: solid 1px #000; */
border-radius: 10px;
background: #fff;
font-size: 30px;
color: #000;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
text-align: center;
font-family: "Helvetica Neue";
}
.M-Flipper .digital:before,
.M-Flipper .digital:after {
content: "";
position: absolute;
left: 0;
right: 0;
background: #fff;
overflow: hidden;
box-sizing: border-box;
}
.M-Flipper .digital30:before,
.M-Flipper .digital30:after {
background: #ff9e20;
color: #fff;
}
.M-Flipper .digital60:before,
.M-Flipper .digital60:after {
background: rgb(240, 76, 76);
color: #fff;
}
.M-Flipper .digital:before {
top: 0;
bottom: 50%;
border-radius: 10px 10px 0 0;
border-bottom: solid 0.5px #ccc;
}
.M-Flipper .digital:after {
top: 50%;
bottom: 0;
border-radius: 0 0 10px 10px;
line-height: 0;
}
/*向下翻*/
.M-Flipper.down .front:before {
z-index: 3;
}
.M-Flipper.down .back:after {
z-index: 2;
transform-origin: 50% 0%;
transform: perspective(160px) rotateX(180deg);
}
.M-Flipper.down .front:after,
.M-Flipper.down .back:before {
z-index: 1;
}
.M-Flipper.down.go .front:before {
transform-origin: 50% 100%;
animation: frontFlipDown 0.6s ease-in-out both;
box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
backface-visibility: hidden;
}
.M-Flipper.down.go .back:after {
animation: backFlipDown 0.6s ease-in-out both;
}
/*向上翻*/
.M-Flipper.up .front:after {
z-index: 3;
}
.M-Flipper.up .back:before {
z-index: 2;
transform-origin: 50% 100%;
transform: perspective(160px) rotateX(-180deg);
}
.M-Flipper.up .front:before,
.M-Flipper.up .back:after {
z-index: 1;
}
.M-Flipper.up.go .front:after {
transform-origin: 50% 0;
animation: frontFlipUp 0.6s ease-in-out both;
/* box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3); */
backface-visibility: hidden;
}
.M-Flipper.up.go .back:before {
animation: backFlipUp 0.6s ease-in-out both;
}
@keyframes frontFlipDown {
0% {
transform: perspective(160px) rotateX(0deg);
}
100% {
transform: perspective(160px) rotateX(-180deg);
}
}
@keyframes backFlipDown {
0% {
transform: perspective(160px) rotateX(180deg);
}
100% {
transform: perspective(160px) rotateX(0deg);
}
}
@keyframes frontFlipUp {
0% {
transform: perspective(160px) rotateX(0deg);
}
100% {
transform: perspective(160px) rotateX(180deg);
}
}
@keyframes backFlipUp {
0% {
transform: perspective(160px) rotateX(-180deg);
}
100% {
transform: perspective(160px) rotateX(0deg);
}
}
.M-Flipper .number0:before,
.M-Flipper .number0:after {
content: "0";
}
.M-Flipper .number1:before,
.M-Flipper .number1:after {
content: "1";
}
.M-Flipper .number2:before,
.M-Flipper .number2:after {
content: "2";
}
.M-Flipper .number3:before,
.M-Flipper .number3:after {
content: "3";
}
.M-Flipper .number4:before,
.M-Flipper .number4:after {
content: "4";
}
.M-Flipper .number5:before,
.M-Flipper .number5:after {
content: "5";
}
.M-Flipper .number6:before,
.M-Flipper .number6:after {
content: "6";
}
.M-Flipper .number7:before,
.M-Flipper .number7:after {
content: "7";
}
.M-Flipper .number8:before,
.M-Flipper .number8:after {
content: "8";
}
.M-Flipper .number9:before,
.M-Flipper .number9:after {
content: "9";
}

View File

@@ -0,0 +1,34 @@
import React, { Component } from "react";
export default class timerCom extends Component {
constructor(props) {
super(props);
this.state = {
hou: 0,
second: 0,
minutes: 0,
strikes: 0,
};
}
timer = () => {
const nextstrikes = this.state.strikes + 50;
this.setState({
hou: parseInt(nextstrikes / 3600000) % 24,
minutes: parseInt(nextstrikes / 60000) % 60,
second: parseInt(nextstrikes / 1000) % 60,
strikes: this.state.strikes + 50,
});
}
componentDidMount() {
setInterval(this.timer, 50);
}
render() {
return (
<div>
<h1>
{this.state.hou}:{this.state.minutes}:{this.state.second}
</h1>
</div>
);
}
}

View File

@@ -7,22 +7,26 @@ const MENULIST = [
{
key: 'questionBank',
title: '刷题',
route: '/question-bank'
route: '/question-bank',
finished: true
},
{
key: 'prictiseQuestion',
title: '练题',
route: '/brush-question'
route: '/practice-questions',
finished: true
},
{
key: 'practiceQuestions',
title: '鸡圈',
route: '/practice-questions'
route: '/jichi-club',
finished: false
},
{
key: 'interList',
title: '模拟面试',
route: '/inter-list'
route: '/inter-list',
finished: false
}
]
@@ -55,10 +59,10 @@ const TopMenu = () => {
if (!userInfoStorage) {
return message.info('请登录')
}
if (item.key === 'questionBank') {
if (location.pathname === '/question-bank') return
navigate('/question-bank')
if (item.finished) {
if (location.pathname === item.route) return
setCurrentKey(item.key)
navigate(item.route)
} else {
return message.info('敬请期待')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -36,8 +36,20 @@ const router = createBrowserRouter([
Component: lazy(() => import('@views/search-details'))
},
{
path: 'personal-center',
path: 'personal-center/:tab',
Component: lazy(() => import('@views/personal-center'))
},
{
path: 'practice-questions',
Component: lazy(() => import('@views/practise/practice-questions'))
},
{
path: 'practice-detail/:id',
Component: lazy(() => import('@views/practise/practice-details/index1.jsx'))
},
{
path: 'practice-analytic/:id',
Component: lazy(() => import('@views/practise/practice-analytic'))
}
]
}

View File

@@ -1,23 +1,33 @@
import { createSlice } from '@reduxjs/toolkit'
export interface CounterState {
value: number
title: string
export interface UserInfo {
nickName?: string
phone?: string
email?: string
sex?: string | number
introduce?: string
avatar?: string
}
const initialState: CounterState = {
value: 0,
title: 'redux toolkit pre'
const initialState: UserInfo = {
nickName: '',
phone: '',
email: '',
sex: undefined,
introduce: '',
avatar: ''
}
// 创建一个 Slice
export const userInfoSlice = createSlice({
name: 'userInfo',
initialState,
initialState: {
userInfo: initialState
},
// 定义 reducers 并生成关联的操作
reducers: {
// 定义一个加的方法
saveUserInfo: (state, { payload }) => {
state.value = payload
state.userInfo = { ...state.userInfo, ...payload }
}
}
})

View File

@@ -1,10 +1,10 @@
import Head from '@/imgs/head.jpg'
import Logo from '@/imgs/logo.jpg'
import { HeartOutlined, LikeOutlined, LoginOutlined, UserOutlined } from '@ant-design/icons'
import TopMenu from '@components/top-menu'
import LoginQrcode from '@imgs/login_qrcode.jpg'
import LoginQrcode from '@imgs/personal_qr_code.jpg'
import req from '@utils/request'
import { Button, Dropdown, Input, Modal, Popover, Space, message } from 'antd'
import { useSelector } from 'react-redux'
import { useNavigate } from 'react-router-dom'
import './index.less'
@@ -15,17 +15,20 @@ const menuItems = [
{
label: '个人中心',
key: 1,
icon: <UserOutlined style={{ fontSize: '16px' }} />
icon: <UserOutlined style={{ fontSize: '16px' }} />,
path: '/user-info'
},
{
label: '我的收藏',
key: 2,
icon: <HeartOutlined style={{ fontSize: '16px' }} />
icon: <HeartOutlined style={{ fontSize: '16px' }} />,
path: '/personal-center/0'
},
{
label: '我的点赞',
key: 3,
icon: <LikeOutlined style={{ fontSize: '16px' }} />
icon: <LikeOutlined style={{ fontSize: '16px' }} />,
path: '/personal-center/1'
},
{
type: 'divider'
@@ -47,19 +50,19 @@ const discoverItems = [
title: '跟我做',
subTitle: '从0到1做鸡翅Club项目',
key: 'club',
path: ''
path: 'https://www.yuque.com/jingdianjichi/gb9bgl/zod2490hhixazf6n?singleDoc#%20%E3%80%8A%E9%B8%A1%E7%BF%85Club%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D%E3%80%8B'
},
{
title: '更深入',
subTitle: '从0到1做企业级框架项目',
key: 'deep',
path: ''
path: 'https://www.yuque.com/jingdianjichi/gb9bgl/rliblb1tvthnl6k3?singleDoc#%20%E3%80%8Aape-frame%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D%E3%80%8B'
},
{
title: '加星球',
subTitle: '一键进入鸡哥的知识星球',
key: 'star',
path: ''
path: 'https://www.yuque.com/jingdianjichi/gb9bgl/ko3bclqhtuu8uif0?singleDoc#%20%E3%80%8A%E7%9F%A5%E8%AF%86%E6%98%9F%E7%90%83%E5%AE%A3%E4%BC%A0%E3%80%8B'
}
]
@@ -67,48 +70,43 @@ const Header = () => {
const { pathname } = window.location
const navigate = useNavigate()
const { userInfo } = useSelector(store => store.userInfo)
const handleMenuClick = e => {
const userInfoStorage = localStorage.getItem('userInfo')
if (!userInfoStorage) {
return message.info('请登录')
}
const { loginId } = JSON.parse(userInfoStorage)
switch (e.key) {
case '1':
navigate('/user-info')
break
case '4':
// 退出
Modal.confirm({
title: '退出提示',
content: '确定退出当前用户登录吗?',
okText: '确定',
cancelText: '取消',
onOk: () => {
req(
{
method: 'get',
url: '/user/logOut',
params: {
userName: loginId
}
},
'/auth'
).then(res => {
if (res.success) {
localStorage.removeItem('userInfo')
message.info('退出成功')
setTimeout(() => {
navigate('/login')
}, 500)
if (e.key == 4) {
Modal.confirm({
title: '退出提示',
content: '确定退出当前用户登录吗?',
okText: '确定',
cancelText: '取消',
onOk: () => {
req(
{
method: 'get',
url: '/user/logOut',
params: {
userName: loginId
}
})
}
})
break
default:
message.info('敬请期待')
break
},
'/auth'
).then(res => {
if (res.success) {
localStorage.removeItem('userInfo')
message.info('退出成功')
setTimeout(() => {
navigate('/login')
}, 500)
}
})
}
})
} else {
navigate(e.item.props.path)
}
}
@@ -134,7 +132,7 @@ const Header = () => {
<div className='head-navigator-user-box'>
<Dropdown
placement='bottom'
trigger={['click']}
// trigger={['click']}
destroyPopupOnHide
dropdownRender={() => {
return (
@@ -174,7 +172,7 @@ const Header = () => {
)
}}
>
<Button type='link'></Button>
<Button type='link'></Button>
</Dropdown>
{'/question-bank' == pathname && (
<div className='head-navigator-input-box'>
@@ -189,22 +187,29 @@ const Header = () => {
/>
</div>
)}
<div className='head-navigator-user-img'>
<Dropdown
menu={{
items: menuItems,
onClick: handleMenuClick
}}
placement='bottom'
trigger={['click']}
destroyPopupOnHide
overlayStyle={{
width: '150px'
}}
>
<img src={Head} style={{ width: 36, height: 36 }} />
</Dropdown>
</div>
{'/login' !== pathname && (
<div className='head-navigator-user-img'>
<Dropdown
menu={{
items: menuItems,
onClick: handleMenuClick
}}
placement='bottom'
trigger={['click']}
destroyPopupOnHide
overlayStyle={{
width: '150px'
}}
>
{userInfo.avatar ? (
<img src={userInfo.avatar} style={{ width: 36, height: 36 }} />
) : (
<div />
)}
{/* <img src={userInfo.avatar || Head} style={{ width: 36, height: 36 }} /> */}
</Dropdown>
</div>
)}
</div>
</div>
</div>

View File

@@ -1,7 +1,9 @@
import { saveUserInfo } from '@features/userInfoSlice.ts'
import LoginQrcode from '@imgs/login_qrcode.jpg'
import req from '@utils/request'
import { Button, Input, Space, message } from 'antd'
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { useNavigate } from 'react-router-dom'
import './index.less'
@@ -11,13 +13,30 @@ const loginApiName = '/user/doLogin'
const Login = () => {
const [validCode, setValidCode] = useState('')
const navigate = useNavigate()
const dispatch = useDispatch()
const changeCode = e => {
setValidCode(e.target.value)
}
const getUserInfo = async loginId => {
req(
{
method: 'post',
url: '/user/getUserInfo',
data: {
userName: loginId
}
},
'/auth'
).then(res => {
if (res?.success && res?.data) {
dispatch(saveUserInfo(res.data))
}
})
}
const doLogin = () => {
console.log(validCode)
if (!validCode) return
req(
{
@@ -26,13 +45,14 @@ const Login = () => {
params: { validCode }
},
'/auth'
).then(res => {
).then(async res => {
if (res.success && res.data) {
message.success('登录成功')
localStorage.setItem('userInfo', JSON.stringify(res.data))
await getUserInfo(res.data.loginId)
setTimeout(() => {
navigate('/question-bank')
}, 1000)
}, 500)
} else {
message.error('登录失败,请重试')
}

View File

@@ -1,8 +1,8 @@
import { Card, Pagination, Spin } from 'antd'
import React, { Component } from 'react'
import { collectTabType } from '../../constant'
import CollectionQuestion from '../collection-question'
import EmptyBox from '../empty-box'
import QuestionList from '../question-list'
import './index.less'
const tabList = [
{
@@ -21,7 +21,7 @@ export default class CollectionBag extends Component {
this.state = {
currentKey: collectTabType.testQuestions, // 选中的tab 默认选中第一个
collectionList: [],
isShowSpin: true,
isShowSpin: false,
isShowSkeleton: true
}
}
@@ -53,7 +53,25 @@ export default class CollectionBag extends Component {
/**
* 获取一级分类数据
*/
getCollectionList() {}
getCollectionList() {
this.total = 3
this.setState({
collectionList: [
{
id: 100,
subjectName: 'Redis支持哪几种数据类型'
},
{
id: 101,
subjectName: 'Redis的高级数据类型有什么'
},
{
id: 102,
subjectName: 'Redis的优点有什么'
}
]
})
}
/**
* 分页
@@ -109,7 +127,7 @@ export default class CollectionBag extends Component {
case collectTabType.testQuestions:
return (
<div>
<CollectionQuestion collectionList={collectionList} collectionTotal={this.total} />
<QuestionList list={collectionList} total={this.total} name='收藏' />
</div>
)
}

View File

@@ -1,103 +0,0 @@
import { SnippetsTwoTone } from '@ant-design/icons'
import { debounce, splicingQuery } from '@utils'
import React, { Component } from 'react'
import './index.less'
class CollectionQuestion extends Component {
constructor(props) {
super(props)
this.state = {
isModalVisible: false //对话框默认不可见
}
}
handleCancelCollection = () => {
console.log('取消收藏')
this.setState({
isModalVisible: true
})
}
handleCancel = () => {
// console.log('点了取消');
this.setState({
isModalVisible: false
})
}
handleOk = () => {
console.log('点了确认')
this.setState({
isModalVisible: false
})
}
handleJump = id =>
debounce(() => {
this.props.history.push(
splicingQuery('good-collection-question', {
subjectType: 4,
subjectId: id
})
)
})
render() {
const { collectionList, collectionTotal } = this.props
const { isModalVisible } = this.state
return (
<div className='collection-bag-component-tab1-body'>
<div className='collection-bag-component-tab1-head-title'>
<div className='collection-bag-component-tab1-head-title-icon'>
<SnippetsTwoTone twoToneColor='#FF0000' />
</div>
<div>收藏的题目({collectionTotal})</div>
</div>
{collectionList.map(item => {
return (
<div
className='collection-bag-component-tab1-body-item'
key={`collection_question_${item.id}`}
>
<div className='collection-bag-component-tab1-body-item-question'>
<span
className='collection-bag-component-tab1-body-item-question-content'
onClick={this.handleJump(item.id)}
>
{item.subjectName}
</span>
</div>
{/* <div className="collection-bag-component-tab1-body-item-foot">
<span
className="collection-bag-component-tab1-body-item-foot-button"
onClick={this.handleCancelCollection}
>
取消收藏
</span>
<Modal
title="提示"
visible={isModalVisible}
cancelText="取消"
okText="确认"
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p
style={{
fontSize: '17px',
textAlign: 'center',
}}
>
确认取消收藏吗?
</p>
</Modal>
</div> */}
</div>
)
})}
</div>
)
}
}
export default CollectionQuestion

View File

@@ -3,7 +3,7 @@ import './index.less'
export default function EmptyBox() {
return (
<div className='empty-box'>
<img className='empty-inco' />
{/* <img className='empty-inco' /> */}
<span className='empty-text'>这里什么也没有哦</span>
</div>
)

View File

@@ -2,7 +2,7 @@ import { Card, Pagination, Spin } from 'antd'
import React, { Component } from 'react'
import { goodTabType } from '../../constant'
import EmptyBox from '../empty-box'
import GoodQuestion from '../good-question'
import QuestionList from '../question-list'
import './index.less'
const tabList = [
{
@@ -21,7 +21,7 @@ export default class GoodBag extends Component {
this.state = {
currentKey: goodTabType.testQuestions, // 选中的tab 默认选中第一个
goodList: [],
isShowSpin: true
isShowSpin: false
}
}
@@ -52,7 +52,25 @@ export default class GoodBag extends Component {
/**
* 获取一级分类数据
*/
getGoodList() {}
getGoodList() {
this.total = 3
this.setState({
goodList: [
{
id: 100,
subjectName: 'Redis支持哪几种数据类型'
},
{
id: 101,
subjectName: 'Redis的高级数据类型有什么'
},
{
id: 102,
subjectName: 'Redis的优点有什么'
}
]
})
}
/**
* 分页
@@ -106,7 +124,7 @@ export default class GoodBag extends Component {
switch (type) {
// 收藏的试题
case goodTabType.testQuestions:
return <GoodQuestion goodList={goodList} goodTotal={this.total} />
return <QuestionList list={goodList} total={this.total} name='点赞' />
}
}
}

View File

@@ -1,97 +0,0 @@
import { SnippetsTwoTone } from '@ant-design/icons'
import { debounce, splicingQuery } from '@utils'
import React, { Component } from 'react'
import './index.less'
class GoodQuestion extends Component {
constructor(props) {
super(props)
this.state = {
isModalVisible: false //对话框默认不可见
}
}
handleCancelGood = () => {
console.log('取消点赞')
this.setState({
isModalVisible: true
})
}
handleCancel = () => {
// console.log('点了取消');
this.setState({
isModalVisible: false
})
}
handleOk = () => {
console.log('点了确认')
this.setState({
isModalVisible: false
})
}
handleJump = id =>
debounce(() => {
this.props.history.push(
splicingQuery('good-collection-question', {
subjectType: 4,
subjectId: id
})
)
})
render() {
const { goodList, goodTotal } = this.props
const { isModalVisible } = this.state
return (
<div className='good-bag-component-tab1-body'>
<div className='good-bag-component-tab1-head-title'>
<div className='good-bag-component-tab1-head-title-icon'>
<SnippetsTwoTone twoToneColor='#FF0000' />
</div>
<div>点赞的题目{goodTotal}</div>
</div>
{goodList.map(item => {
return (
<div className='good-bag-component-tab1-body-item' key={`good_question_${item.id}`}>
<div className='good-bag-component-tab1-body-item-question'>
<span
className='good-bag-component-tab1-body-item-question-content'
onClick={this.handleJump(item.id)}
>
{item.subjectName}
</span>
</div>
{/* <div className="good-bag-component-tab1-body-item-foot">
<span className="good-bag-component-tab1-body-item-foot-button" onClick={this.handleCancelGood}>
取消点赞
</span>
<Modal
title="提示"
visible={isModalVisible}
cancelText="取消"
okText="确认"
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<p
style={{
fontSize: '17px',
textAlign: 'center',
}}
>
确认取消点赞吗?
</p>
</Modal>
</div> */}
</div>
)
})}
</div>
)
}
}
export default GoodQuestion

View File

@@ -1,44 +0,0 @@
.good-bag-component-tab1-body {
padding: 0 20px 20px 20px;
.good-bag-component-tab1-head-title {
display: flex;
color: #333;
font-size: 16px;
font-weight: 400;
.good-bag-component-tab1-head-title-icon {
margin-right: 8px;
}
}
.good-bag-component-tab1-body-item {
margin: 10px 0;
overflow: auto;
position: relative;
clear: both;
// height: 60px;
border-bottom: 1px solid #e5e5e5;
.good-bag-component-tab1-body-item-question {
font-size: 14px;
line-height: 1.8;
.good-bag-component-tab1-body-item-question-content {
width: 600px;
cursor: pointer;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
&:hover {
color: #3c6eee;
}
}
}
.good-bag-component-tab1-body-item-foot {
margin-top: 10px;
float: right;
.good-bag-component-tab1-body-item-foot-button {
cursor: pointer;
color: #3c6eee;
}
}
}
}

View File

@@ -0,0 +1,44 @@
import { SnippetsTwoTone } from '@ant-design/icons'
import React from 'react'
import './index.less'
const CollectionQuestion = props => {
const { total, list, name } = props
const handleJump = id => {
window.open('/brush-question/' + id)
}
return (
<div className='collection-bag-component-tab1-body'>
<div className='collection-bag-component-tab1-head-title'>
<div className='collection-bag-component-tab1-head-title-icon'>
<SnippetsTwoTone twoToneColor='#FF0000' />
</div>
<div>
{name}的题目({total})
</div>
</div>
{list.map(item => {
return (
<div
className='collection-bag-component-tab1-body-item'
key={`collection_question_${item.id}`}
>
<div className='collection-bag-component-tab1-body-item-question'>
<span
className='collection-bag-component-tab1-body-item-question-content'
onClick={() => handleJump(item.id)}
>
{item.subjectName}
</span>
</div>
</div>
)
})}
</div>
)
}
export default CollectionQuestion

View File

@@ -11,9 +11,10 @@
}
.collection-bag-component-tab1-body-item {
margin: 10px 0;
padding: 10px 0;
overflow: auto;
position: relative;
clear: both;
// clear: both;
// height: 60px;
border-bottom: 1px solid #e5e5e5;

View File

@@ -1,135 +1,97 @@
import { IdcardOutlined, LikeTwoTone, MailOutlined, StarTwoTone } from '@ant-design/icons'
import { LikeTwoTone, StarTwoTone } from '@ant-design/icons'
import { Menu } from 'antd'
import PubSub from 'pubsub-js'
import React, { Component } from 'react'
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { useParams } from 'react-router-dom'
import CollectionBag from './components/collection-bag'
import GoodBag from './components/good-bag'
import './index.less'
export default class PersonalCenter extends Component {
constructor(props) {
super(props)
this.state = {
currentKeyMap: 0, //选中的menu
userName: '', //姓名
intervieweEamil: '', //邮箱
headImg: '', //头像
department: '', //部门
practiceAmount: 0, //练题数
inputAmount: 0, //录题数
goodAmount: 0, //点赞数
collectionAmount: 0, //收藏数
subMenuList: []
}
const personList = {
0: '收藏',
1: '点赞'
}
const PersonalCenter = props => {
const [currentKeyMap, setCurrentKeyMap] = useState(0)
const { userInfo } = useSelector(store => store.userInfo)
const [selectedKeys, setSelectedKeys] = useState('0')
const { tab } = useParams()
useEffect(() => {
setCurrentKeyMap(+tab)
setSelectedKeys(tab)
}, [tab])
const handleClick = ({ key }) => {
setCurrentKeyMap(Number(key))
setSelectedKeys(key)
}
personList = {
// 0: '刷题',
0: '收藏',
1: '点赞'
}
componentDidMount() {
PubSub.subscribe('handleToRender', () => {
this.setState({})
})
}
/**
* 切换菜单
* @param {*} e
*/
handleClick = e => {
console.log('--------', e)
//截取_后的字符
let index = e.keyPath[0].lastIndexOf('_')
let index2 = e.keyPath[0].substring(index + 1, e.keyPath[0].length)
//
console.log('index2>>>>', index2)
this.setState({
currentKeyMap: Number(index2)
})
}
render() {
let {
headImg,
userName,
intervieweEamil,
department,
goodAmount,
collectionAmount,
practiceAmount,
inputAmount
} = this.state
const { currentKeyMap } = this.state
return (
<div className='personal-center-box'>
<div className='personal-center-introduction'>
<div className='personal-center-introduction-detail'>
<div className='personal-center-introduction-detail-headImg'>
<img src={headImg} style={{ width: 60, height: 60, borderRadius: '50%' }} />
</div>
<div className='personal-center-introduction-detail-text'>
<div className='personal-center-introduction-detail-name'>{userName}</div>
<div className='personal-center-introduction-detail-information'>
<span className='personal-center-introduction-detail-information-content'>
<IdcardOutlined style={{ color: 'blue', marginRight: '3px' }} />
</span>
<span className='personal-center-introduction-detail-information-content'>
<MailOutlined style={{ color: 'blue', marginRight: '3px' }} />
</span>
</div>
</div>
return (
<div className='personal-center-box'>
<div className='personal-center-introduction'>
<div className='personal-center-introduction-detail'>
<div className='personal-center-introduction-detail-headImg'>
<img src={userInfo.avatar} style={{ width: 60, height: 60, borderRadius: '50%' }} />
</div>
<div className='personal-center-introduction-result'>
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>
{practiceAmount}
</div>
<div>练题</div>
</div>
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{inputAmount}</div>
<div>录题</div>
</div>
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{goodAmount}</div>
<div>点赞</div>
</div>
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>
{collectionAmount}
</div>
<div>收藏</div>
</div>
<div className='personal-center-introduction-detail-text'>
<div className='personal-center-introduction-detail-name'>{userInfo.nickName}</div>
{/* <div className='personal-center-introduction-detail-information'>
<span className='personal-center-introduction-detail-information-content'>
<IdcardOutlined style={{ color: 'blue', marginRight: '3px' }} />
</span>
<span className='personal-center-introduction-detail-information-content'>
<MailOutlined style={{ color: 'blue', marginRight: '3px' }} />
</span>
</div> */}
</div>
</div>
<div className='personal-center-content'>
<div className='personal-center-content-left'>
<Menu
mode='inline'
onClick={this.handleClick}
style={{ width: 256 }}
defaultSelectedKeys={['personList_0']}
>
{/* <Menu.Item key={`personList_0`}>
<MailOutlined style={{ color: 'rgb(171,214,97)' }} />
<span>{this.personList[0]}</span>
</Menu.Item> */}
<Menu.Item key={`personList_0`}>
<StarTwoTone twoToneColor='rgb(252,132,67)' />
<span>{this.personList[0]}</span>
</Menu.Item>
<Menu.Item key={`personList_1`}>
<LikeTwoTone twoToneColor='#99bbff' />
<span>{this.personList[1]}</span>
</Menu.Item>
</Menu>
<div className='personal-center-introduction-result'>
{/* <div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{10}</div>
<div>练题</div>
</div> */}
{/* <div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{inputAmount}</div>
<div>录题</div>
</div> */}
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{20}</div>
<div>点赞</div>
</div>
<div className='personal-center-content-right'>
{/* {currentKeyMap === 0 && <BrushQuestion />} */}
{currentKeyMap === 0 && <CollectionBag />}
{currentKeyMap === 1 && <GoodBag />}
<div className='personal-center-introduction-result-item'>
<div className='personal-center-introduction-result-item-number'>{30}</div>
<div>收藏</div>
</div>
</div>
</div>
)
}
<div className='personal-center-content'>
<div className='personal-center-content-left'>
<Menu
mode='inline'
onClick={handleClick}
style={{ width: 256 }}
selectedKeys={[selectedKeys]}
>
<Menu.Item key={0}>
<StarTwoTone twoToneColor='rgb(252,132,67)' />
<span>{personList[0]}</span>
</Menu.Item>
<Menu.Item key={1}>
<LikeTwoTone twoToneColor='#99bbff' />
<span>{personList[1]}</span>
</Menu.Item>
</Menu>
</div>
<div className='personal-center-content-right'>
{currentKeyMap === 0 && <CollectionBag />}
{currentKeyMap === 1 && <GoodBag />}
</div>
</div>
</div>
)
}
export default PersonalCenter

View File

@@ -1,7 +1,6 @@
.personal-center-box {
margin: 0 auto;
width: 1439px;
// padding: 20px 50px;
border-radius: 5px;
overflow: auto;
.personal-center-introduction {
@@ -11,10 +10,12 @@
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.personal-center-introduction-detail {
margin-left: 50px;
padding-top: 20px;
// padding-top: 20px;
display: flex;
align-items: center;
.personal-center-introduction-detail-headImg {
margin-right: 20px;
align-items: center;
@@ -34,8 +35,8 @@
}
.personal-center-introduction-result {
margin-right: 50px;
padding-top: 30px;
display: flex;
align-items: center;
.personal-center-introduction-result-item {
text-align: center;
padding-right: 10px;
@@ -54,7 +55,7 @@
overflow-y: auto;
border-radius: 3px;
width: 300px;
height: 720px;
min-height: 320px;
}
.personal-center-content-right {
background-color: #fff;

View File

@@ -0,0 +1,216 @@
import req from '@utils/request'
import { Spin } from 'antd'
import React, { Component } from 'react'
import { ApiName, IdKeyLetterKey } from '../../constant'
import './index.less'
export default class AnswerAnalysis extends Component {
constructor(props) {
super(props)
this.state = {
subjectList: [], // 题目列表
subjectName: '',
optionList: [], // 选项列表
labelNames: '', // 标签列表
respondAnswer: [], // 你的答案
subjectParse: '', // 解析
correctAnswer: [], // 正确答案
currentIndex: 0, // 当前选中的下标
isLoading: false
}
}
componentDidMount() {
this.getScoreDetail()
}
/**
* 答案解析-获得题目列表
*/
getScoreDetail = () => {
const { practiceId } = this.props
let params = {
practiceId: practiceId
}
req({
method: 'post',
data: params,
url: ApiName.getScoreDetail
})
.then(res => {
if (res?.data && res?.data?.length > 0) {
this.setState(
{
subjectList: res.data
},
() => {
this.getSubjectDetail(0, res.data[0])
}
)
}
})
.catch(err => console.log(err))
}
/**
* 答案解析-获得答案详情
* @param {*} index 当前index
* @param {*} subjectItem 当前item
*/
getSubjectDetail = (index, subjectItem) => {
const { practiceId } = this.props
let params = {
practiceId: practiceId,
subjectId: subjectItem.subjectId,
subjectType: subjectItem.subjectType
}
JDreq({
method: 'post',
data: params,
url: ApiName.getSubjectDetail
})
.then(res => {
if (res.data) {
let respondAnswer = res.data.respondAnswer
let optionList = []
res.data.optionList.forEach(element => {
let obj = {
isCorrect: element.isCorrect,
optionContent: element.optionContent,
optionType: element.optionType,
isAnswer: 0
}
if (respondAnswer.includes(element.optionType)) {
obj.isAnswer = 1
}
optionList.push(obj)
})
this.setState({
isLoading: false,
currentIndex: index,
subjectName: res.data.subjectName,
optionList: optionList, // 选项列表
labelNames: res.data.labelNames, // 标签列表
respondAnswer: respondAnswer, // 你的答案
subjectParse: res.data.subjectParse, // 解析
correctAnswer: res.data.correctAnswer // 正确答案
})
}
})
.catch(err => console.log(err))
}
onChangeOption = (index, item) => () => {
let { currentIndex } = this.state
if (index === currentIndex) {
return
}
this.getSubjectDetail(index, item)
}
render() {
const {
subjectList,
subjectName,
optionList,
labelNames,
respondAnswer,
subjectParse,
correctAnswer,
currentIndex,
isLoading
} = this.state
const isRight = correctAnswer.join('') !== respondAnswer.join('')
return (
<Spin spinning={isLoading}>
<div className='answer-analysis-box'>
{subjectList?.length > 0 && (
<div className='answer-analysis-paging'>
<div className='answer-analysis-paging-tip'>每题得分</div>
<div className='answer-analysis-paging-list'>
{subjectList.map((item, index) => {
return (
<div
className={`answer-analysis-paging-item ${
item.isCorrect === 1 ? 'answer-analysis-rigth' : 'answer-analysis-error'
} ${currentIndex == index ? 'answer-analysis-paging-item-active' : ''}`}
onClick={this.onChangeOption(index, item)}
key={`${item.subjectId}_${index}`}
>
{index + 1}
</div>
)
})}
</div>
</div>
)}
<div className='answer-analysis-name'>
<div className='answer-analysis-name-num'>{currentIndex + 1}</div>
<div className='answer-analysis-name-text'>{subjectName}</div>
</div>
<div className='answer-analysis-answer'>
正确答案
{correctAnswer?.length > 0
? correctAnswer.map((item, index) => {
return <span key={`correct_answer_${index}`}>{IdKeyLetterKey[item] + ' '}</span>
})
: '空'}
你的答案
{respondAnswer?.length > 0
? respondAnswer.map((item, index) => {
return <span key={`respond_answer_${index}`}>{IdKeyLetterKey[item] + ' '}</span>
})
: '空'}
<span style={isRight ? { color: '#ff431e' } : { color: 'rgba(60, 110, 238, 1)' }}>
&nbsp;({isRight ? '错误' : '正确'})
</span>
</div>
{optionList?.length > 0 && (
<div className='answer-analysis-option-list'>
{optionList.map((item, index) => {
return (
<div
key={`option_${index}`}
className={`answer-analysis-option-item ${
item.isCorrect === 1
? 'answer-analysis-option-item-rigth'
: item.isAnswer === 1
? 'answer-analysis-option-item-error'
: ''
}`}
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
)
})}
</div>
)}
{labelNames?.length > 0 && (
<div className='answer-analysis-points'>
<div className='answer-analysis-points-tip'>本题知识点</div>
<div className='answer-analysis-points-list'>
{labelNames.map((item, index) => {
return (
<div
key={`answer_analysis_points_${index}`}
className='answer-analysis-points-item'
>
{item}
</div>
)
})}
</div>
</div>
)}
{!!subjectParse && (
<div className='answer-analysis-parse'>
<div className='answer-analysis-parse-tip'>参考解析</div>
<div className='answer-analysis-parse-text'>{subjectParse}</div>
</div>
)}
</div>
</Spin>
)
}
}

View File

@@ -0,0 +1,146 @@
.answer-analysis-box {
padding-bottom: 30px;
// 分页
.answer-analysis-paging {
padding: 20px 30px;
background-color: #fff;
border-bottom: 1px solid #ededed;
.answer-analysis-paging-tip {
margin-bottom: 20px;
color: #666;
font-size: 14px;
}
.answer-analysis-paging-list {
display: flex;
flex-wrap: wrap;
.answer-analysis-paging-item {
display: flex;
justify-content: center;
align-items: center;
margin-right: 4px;
margin-bottom: 20px;
width: 32px;
height: 32px;
font-size: 14px;
color: white;
cursor: pointer;
transition: all 0.3s;
&:hover {
margin-top: -6px;
margin-bottom: -6px;
height: 44px;
}
}
// 选中
.answer-analysis-paging-item-active {
margin-top: -6px;
margin-bottom: -6px;
height: 44px;
}
// 答错
.answer-analysis-error {
background-color: #ff431e;
}
// 答对
.answer-analysis-rigth {
background-color: rgba(60, 110, 238, 1);
}
}
}
// 题名
.answer-analysis-name {
display: flex;
align-items: center;
padding: 20px 30px;
font-size: 14px;
background-color: #fff;
border-bottom: 1px solid #ededed;
.answer-analysis-name-num {
padding: 5px 7px;
display: flex;
justify-content: center;
align-items: center;
min-width: 30px;
color: #fff;
font-weight: 400;
border-radius: 50%;
background-color: rgba(60, 110, 238, 1);
}
.answer-analysis-name-text {
margin-left: 10px;
}
}
// 答案
.answer-analysis-answer {
padding: 20px 30px;
}
// 选项
.answer-analysis-option-list {
display: flex;
flex-direction: column;
padding: 0 30px;
.answer-analysis-option-item {
padding: 10px;
margin-bottom: 20px;
font-size: 14px;
border-radius: 6px;
border: 1px solid #d4d4d4;
background: #fff;
}
// 正确
.answer-analysis-option-item-rigth {
border-color: rgba(60, 110, 238, 1);
}
// 错误
.answer-analysis-option-item-error {
border-color: #ff431e;
}
}
// 知识点
.answer-analysis-points {
padding: 0 30px 20px;
font-size: 14px;
background-color: rgba(60, 110, 238, 0.08);
.answer-analysis-points-tip {
margin-bottom: 20px;
padding-top: 20px;
color: rgb(102, 102, 102);
}
.answer-analysis-points-list {
display: flex;
justify-content: flex-start;
align-items: center;
.answer-analysis-points-item {
display: flex;
justify-content: center;
align-items: center;
margin-right: 4px;
padding: 0 8px;
height: 22px;
color: #999;
font-size: 12px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 12px;
&:hover {
color: rgba(60, 110, 238, 1);
border-color: rgba(60, 110, 238, 1);
}
}
}
}
// 解析
.answer-analysis-parse {
padding: 0 30px 20px;
font-size: 14px;
background-color: #fff;
.answer-analysis-parse-tip {
margin-bottom: 20px;
padding-top: 20px;
color: rgb(102, 102, 102);
}
.answer-analysis-parse-text {
color: #333;
}
}
}

View File

@@ -0,0 +1,148 @@
import req from '@utils/request'
import AnalysisAtlas from '@components/analysis-atlas'
import { splicingQuery } from '@utils'
import { Button, Spin } from 'antd'
import React, { Component } from 'react'
import { ApiName, ModuleName } from '../../constant'
import RecommendList from '../recommend-list'
import './index.less'
class AssessmentReport extends Component {
constructor(props) {
super(props)
this.state = {
correctSubject: '3',
recommendSetList: [],
skill: [],
title: '测试试卷',
isLoading: false
}
}
componentDidMount() {
this.getReport()
}
/**
* 答案解析-获得评估报告
*/
getReport = async () => {
const { practiceId } = this.props
let params = {
practiceId: practiceId
}
await req({
method: 'post',
data: params,
url: ApiName.getReport
})
.then(res => {
if (res?.data) {
let list = res.data.skill || []
let len = res.data.skill.length
if (len === 1) {
let l1 = [
{ name: res.data.skill[0].name + ' ', star: res.data.skill[0].star },
{
name: ' ' + res.data.skill[0].name + ' ',
star: res.data.skill[0].star
}
]
list = list.concat(l1)
} else if (len === 2) {
let l1 = [{ name: res.data.skill[1].name + ' ', star: res.data.skill[1].star }]
list = list.concat(l1)
}
this.setState({
isLoading: false,
title: res.data.title,
correctSubject: res.data.correctSubject,
recommendSetList: res.data.recommendSetList,
skill: list
})
}
})
.catch(err => console.log(err))
}
/**
* 练习其他技能
*/
onChangePracticeOther = () => {
this.props.history.push('/practice-questions')
}
/**
* 查看答案解析
*/
onChangeAnswerAnalysis = () => {
this.props.onHandleAnswerAnalysis && this.props.onHandleAnswerAnalysis(ModuleName.analysis)
}
/**
* 点击推荐套题
* @param {*} setId
* @returns
*/
onChangeSetId = setId => {
this.props.history.push(
splicingQuery('/practice-details', {
setId
})
)
}
render() {
const { correctSubject, recommendSetList, skill, title, isLoading } = this.state
return (
<Spin spinning={isLoading}>
<div className='assessment-report-box'>
<div className='assessment-report-top'>
<div className='assessment-report-main'>
{/* <div className="assessment-report-defen">
<img src={ImgObj.defen} className="assessment-report-defen-icon" />
得分12
</div> */}
<div className='assessment-report-item'>试卷{title}</div>
<div className='assessment-report-item'>正确题数{correctSubject}</div>
<Button
className='assessment-report-submit'
type='primary'
onClick={this.onChangePracticeOther}
>
练习其他技能
</Button>
</div>
<div className='assessment-report-tupu'>
<div className='assessment-report-tupu-tip'>你的技能图谱</div>
<div className='assessment-report-tupu-content'>
<AnalysisAtlas
aliasStr='正确率'
atlasList={skill || []}
atlasMin={-25}
atlasWidth={200}
atlasHeight={200}
/>
</div>
</div>
</div>
{recommendSetList?.length > 0 && (
<RecommendList recommendSetList={recommendSetList} onHandleSetId={this.onChangeSetId} />
)}
<div className='assessment-report-answer-analysis'>
<Button
className='assessment-report-answer-btn'
type='primary'
onClick={this.onChangeAnswerAnalysis}
>
查看答案解析
</Button>
</div>
</div>
</Spin>
)
}
}
export default AssessmentReport

View File

@@ -0,0 +1,63 @@
.assessment-report-box {
.assessment-report-top {
display: flex;
padding: 30px 24px;
background-color: #fff;
.assessment-report-main {
flex: 1;
padding: 20px 0 20px 16px;
// // 得分
// .assessment-report-defen {
// display: flex;
// align-items: center;
// margin-bottom: 20px;
// color: #888;
// .assessment-report-defen-icon {
// margin-right: 4px;
// width: 28px;
// height: 28px;
// }
// }
.assessment-report-item {
margin-bottom: 20px;
font-size: 16px;
font-weight: 600;
}
// 再练一套
.assessment-report-submit {
height: 40px;
border-color: rgba(60, 110, 238, 1);
background-color: rgba(60, 110, 238, 1);
}
}
// 图谱
.assessment-report-tupu {
flex: 1;
padding: 20px 0 20px 30px;
border-left: 1px solid #ddd;
.assessment-report-tupu-tip {
margin-bottom: 20px;
color: #888;
font-size: 16px;
}
.assessment-report-tupu-content {
margin: 0 auto;
width: 200px;
height: 200px;
}
}
}
// 查看答案解析
.assessment-report-answer-analysis {
display: flex;
align-items: center;
justify-content: center;
margin: 30px 0;
.assessment-report-answer-btn {
width: 260px;
height: 40px;
border-color: rgba(60, 110, 238, 1);
background-color: rgba(60, 110, 238, 1);
}
}
}

View File

@@ -0,0 +1,39 @@
import { debounce } from '@utils'
import React from 'react'
import { RecommendBackImg } from '../../constant'
import './index.less'
export default function RecommendList(props) {
const { recommendSetList } = props
/**
* 点击推荐套题
* @param {*} setId
* @returns
*/
const onChangeSetId = setId =>
debounce(() => {
props.onHandleSetId && props.onHandleSetId(setId)
})
return (
<div className='assessment-report-recommend'>
<div className='assessment-report-recommend-tip'>根据本次练习为你推荐以下内容</div>
<div className='assessment-report-recommend-list'>
{recommendSetList.map((item, index) => {
return (
<div
className='recommend-item'
key={item.setId}
style={{
backgroundImage: `url(${RecommendBackImg[index % 4]})`
}}
onClick={onChangeSetId(item.setId)}
>
<div className='recommend-item-name'>{item.setName}</div>
<div className='recommend-item-heat'>热度指数{item.setHeat}</div>
</div>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,64 @@
// 推荐
.assessment-report-recommend {
margin-top: 30px;
padding: 30px 40px;
background-color: #fff;
.assessment-report-recommend-tip {
position: relative;
margin-bottom: 30px;
margin-left: 16px;
font-size: 16px;
color: #333;
&:before {
content: '';
position: absolute;
left: -16px;
top: 8px;
width: 8px;
height: 8px;
background: rgba(255, 158, 32, 1);
}
}
// 推荐列表
.assessment-report-recommend-list {
display: flex;
.recommend-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
margin-right: 10px;
padding: 20px;
width: 220px;
height: 130px;
border-radius: 10px;
background-size: 100% 100%;
background-repeat: no-repeat;
cursor: pointer;
transition: all 0.5s;
&:hover {
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.08);
}
.recommend-item-name {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
width: 100%;
color: #fff;
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis; //省略
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.recommend-item-heat {
display: flex;
justify-content: flex-end;
width: 100%;
color: #fff;
font-size: 14px;
}
}
}
}

View File

@@ -0,0 +1,53 @@
export const ModuleName = {
/**
* 评估报告
*/
assessment: 'assessment',
/**
* 答案解析
*/
analysis: 'analysis',
};
export const ImgObj = {
defen: 'https://img10.360buyimg.com/imagetools/jfs/t1/197806/1/18639/2369/61a095a8E09f4e860/493d753073a4a9fa.png',
};
export const ApiName = {
/**
* 获得评价
*/
getReport: '/admin/practice/detail/getReport',
/**
* 答案详情
*/
getSubjectDetail: '/admin/practice/detail/getSubjectDetail',
/**
* 获得题号
*/
getScoreDetail: 'admin/practice/detail/getScoreDetail',
};
/**
* id对应字母
*/
export const IdKeyLetterKey = {
1: 'A',
2: 'B',
3: 'C',
4: 'D',
5: 'E',
6: 'F',
7: 'G',
8: 'H',
};
/**
* 推荐对应的背景图
*/
export const RecommendBackImg = {
0: 'https://img11.360buyimg.com/imagetools/jfs/t1/202713/11/17151/312/61a5dea1E5623fea6/2922d716cae01b28.png',
1: 'https://img10.360buyimg.com/imagetools/jfs/t1/142359/35/22866/2975/61a5dea0E3fcd563c/78ee45555dd19cda.png',
2: 'https://img13.360buyimg.com/imagetools/jfs/t1/161380/34/26207/1078/61a5dea0E7824463e/4e18f74e8c6439ad.png',
3: 'https://img14.360buyimg.com/imagetools/jfs/t1/160815/15/27226/880/61a5e1e6E152b08b7/fb698b8321d08246.png',
};

View File

@@ -0,0 +1,69 @@
import { Tabs } from 'antd'
import React, { Component } from 'react'
import AnswerAnalysis from './components/answer-analysis'
import AssessmentReport from './components/assessment-report'
import { ModuleName } from './constant'
import './index.less'
const { TabPane } = Tabs
const practiceAnalyticTabList = [
{ tab: '评估报告', key: ModuleName.assessment },
{ tab: '答案解析', key: ModuleName.analysis }
]
export default class PracticeAnalytic extends Component {
constructor(props) {
super(props)
this.state = { currentKey: ModuleName.assessment }
}
/**
* 切换card tab
* @param {*} key
*/
onTabChange = key => {
this.setState({ currentKey: key })
}
render() {
const { currentKey } = this.state
console.log(this.props)
// const urlParams = queryParse(this.props.location.search)
// if (!urlParams.practiceId) {
// return null
// }
return (
<div className='practice-analytic-box'>
<Tabs
size='default'
type='card'
style={{ width: '100%' }}
activeKey={currentKey}
defaultActiveKey={currentKey}
tabBarStyle={{
height: '41px',
background: '#fff',
borderBottom: '1px solid #1890ff',
margin: 0
}}
onChange={key => {
this.onTabChange(key, 'key')
}}
>
{practiceAnalyticTabList.map(item => {
return <TabPane tab={item.tab} key={item.key}></TabPane>
})}
</Tabs>
{currentKey == ModuleName.assessment ? (
<AssessmentReport
onHandleAnswerAnalysis={key => {
this.onTabChange(key, 'key')
}}
practiceId={1}
/>
) : (
<AnswerAnalysis practiceId={1} />
)}
</div>
)
}
}

View File

@@ -0,0 +1,20 @@
.practice-analytic-box {
margin: 0 auto;
width: 1430px;
border: 1px solid #e0e0e0;
overflow-y: auto;
.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab {
background-color: #fff;
border-radius: 0;
border: none;
}
.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active {
color: #fff;
background-color: #1890ff;
}
// 将body背景重置作为灰色
.ant-card-body {
background-color: #f3f4f6;
padding: 0;
}
}

View File

@@ -0,0 +1,79 @@
import { debounce } from '@utils'
import { Button } from 'antd'
import React, { Fragment } from 'react'
import { ImgObj, mark } from '../../constant'
import './index.less'
export default function PracticeAction(props) {
const { isLast, isMark } = props
/**
* 标记一下
* @returns
*/
const onChangeMark = debounce(() => {
props.onHandleMark && props.onHandleMark()
})
/**
* 交卷
* @returns
*/
const onChangeOver = debounce(() => {
props.onHandleOver && props.onHandleOver()
})
/**
* 提前交卷
* @returns
*/
const onChangeAdvanceOver = debounce(() => {
props.onHandleAdvanceOver && props.onHandleAdvanceOver()
})
/**
* 下一题
* @returns
*/
const onChangeNext = debounce(() => {
props.onHandleNext && props.onHandleNext()
})
return (
<Fragment>
<div className='practice-action-box'>
<div className='practice-action-list'>
<div className='practice-action-item' onClick={onChangeMark}>
<img src={ImgObj.mark} className='action-mark-icon' />
{mark[isMark]}
</div>
</div>
<div className='practice-action-list'>
<div className='practice-action-button'>
{isLast ? (
<Button className='action-button-submit' type='primary' onClick={onChangeOver}>
交卷
</Button>
) : (
<Button
className='action-button-advance-submit'
type='primary'
onClick={onChangeAdvanceOver}
>
提前交卷
</Button>
)}
</div>
{!isLast && (
<div className='practice-action-button'>
<Button type='primary' onClick={onChangeNext}>
下一题
</Button>
</div>
)}
</div>
</div>
<div className='practice-action-tips'>*交卷即可查看全部答案和解析</div>
</Fragment>
)
}

View File

@@ -0,0 +1,42 @@
// 标记/提前交卷/交卷/下一题
.practice-action-box {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
.practice-action-list {
display: flex;
// 标记
.practice-action-item {
margin-right: 30px;
line-height: 20px;
cursor: pointer;
.action-mark-icon {
margin-right: 4px;
width: 20px;
height: 20px;
}
}
// 按钮
.practice-action-button {
margin: 0 15px;
.action-button-advance-submit {
background-color: #ff6547;
border-color: #ff6547;
color: white;
}
.action-button-submit {
background-color: rgba(60, 110, 238, 1);
border-color: rgba(60, 110, 238, 1);
color: white;
}
}
}
}
// 提示
.practice-action-tips {
margin-top: 10px;
color: #888;
font-size: 14px;
text-align: right;
}

View File

@@ -0,0 +1,36 @@
import { Modal } from 'antd'
import React from 'react'
import { ImgObj } from '../../constant'
import './index.less'
export default function PracticeAdvance(props) {
const { isShowModalBox } = props
const onSubmitModal = () => {
props.onHandleSubmitModal && props.onHandleSubmitModal()
}
const onCancelModal = () => {
props.onHandleCancelModal && props.onHandleCancelModal()
}
return (
<Modal
className='practice-advance-box'
closable={false}
maskClosable={false}
open={isShowModalBox}
title='提前交卷提示'
onOk={onSubmitModal}
onCancel={onCancelModal}
okText='立即交卷'
cancelText='继续做题'
>
<div className='practice-advance'>
<div className='practice-advance-img'>
<img className='practice-advance-icon' src={ImgObj.advanceTip} />
</div>
<div className='practice-advance-text'>
你还有部分题目未完成交卷即可查看试卷全部答案及解析是否立即交卷
</div>
</div>
</Modal>
)
}

View File

@@ -0,0 +1,21 @@
.practice-advance-box {
.practice-advance {
display: flex;
flex-direction: column;
align-items: center;
padding: 28px 0;
.practice-advance-img {
width: 80px;
height: 80px;
.practice-advance-icon {
width: 100%;
height: 100%;
}
}
.practice-advance-text {
margin: 10px;
padding: 0 50px;
text-align: center;
}
}
}

View File

@@ -0,0 +1,50 @@
import { AppstoreOutlined } from '@ant-design/icons'
import { debounce } from '@utils'
import React, { Fragment } from 'react'
import './index.less'
export default function PracticePaging(props) {
const { subjectList, singleLength, multipleLength, judgeLength } = props
const onChangePaging = index =>
debounce(() => {
props.onHandlePaging && props.onHandlePaging(index)
})
return (
<Fragment>
{subjectList?.length > 0 && (
<div className='practice-paging-box'>
<div className='practice-paging-tips'>
<div>答题卡</div>
<div className='practice-paging-tip'>
<AppstoreOutlined style={{ marginRight: 4 }} />
单选题{singleLength} 多选题{multipleLength} 判断题
{judgeLength}
</div>
</div>
<div className='practice-paging-list'>
{subjectList.map((item, index) => {
return (
<div
key={`paging_${item.subjectId}`}
className={`practice-paging-item
${
item?.isMark == 1 ? 'practice-paging-item-mark' : ''
} ${
item?.active
? 'practice-paging-item-active'
: item?.activeList?.length > 0
? 'practice-paging-item-answer'
: 'practice-paging-item-unactive'
} `}
onClick={onChangePaging(index)}
>
{index + 1}
</div>
)
})}
</div>
</div>
)}
</Fragment>
)
}

View File

@@ -0,0 +1,70 @@
.practice-paging-box {
padding: 20px;
border-top: 1px dashed #ddd;
.practice-paging-tips {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 16px;
color: #666;
.practice-paging-tip {
padding: 5px 10px;
font-size: 14px;
background: #f2f2f2;
}
}
.practice-paging-list {
display: flex;
flex-wrap: wrap;
.practice-paging-item {
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
width: 40px;
height: 40px;
font-size: 16px;
cursor: pointer;
&:hover {
color: white;
background-color: rgba(60, 110, 238, 1);
}
}
// 未选中
.practice-paging-item-unactive {
color: #dce4ec;
&:after {
background: #aaa;
}
}
// 选中
.practice-paging-item-active {
color: white;
background-color: rgba(60, 110, 238, 1);
&:after {
background: #fff;
}
}
// 有答案
.practice-paging-item-answer {
color: rgba(60, 110, 238, 1);
&:after {
background: rgba(60, 110, 238, 1);
}
}
// 被标记
.practice-paging-item-mark {
position: relative;
&:after {
content: '';
position: absolute;
right: 3px;
top: 3px;
width: 4px;
height: 4px;
border-radius: 50%;
}
}
}
}

View File

@@ -0,0 +1,43 @@
export const mark = {
0: '标记一下',
1: '已标记',
};
export const collection = {
0: '未收藏',
1: '已收藏',
};
export const quetionsType = {
1: '单选题',
2: '多选题',
3: '判断题',
};
export const ApiName = {
/**
* 获取练习题目
*/
getSubjects: '/admin/practice/set/getSubjects',
/**
* 获取练习题目详情
*/
getPracticeSubject: '/admin/practice/set/getPracticeSubject',
/**
* 交卷
*/
submitSubject: '/admin/practice/detail/submit',
};
export const ImgObj = {
stop: 'https://img10.360buyimg.com/imagetools/jfs/t1/206561/1/10729/2819/619f783cE77dd49ed/54eb1fc4b3144a97.png',
run: 'https://img11.360buyimg.com/imagetools/jfs/t1/161735/6/25253/2598/619f783cEa897a673/fbf4e8c05d40feb5.png',
info: 'https://img13.360buyimg.com/imagetools/jfs/t1/217399/4/5733/2641/619f7b91E0894649e/2f6353fe0d35fb46.png',
questionMark:
'https://img12.360buyimg.com/imagetools/jfs/t1/201809/8/16630/2674/61a04963E92475548/ede8a7f006113cae.png',
mark: 'https://img12.360buyimg.com/imagetools/jfs/t1/207329/30/11079/2474/61a70ad0E64730d1c/ed75ee746fb33926.png',
advanceTip:
'https://img11.360buyimg.com/imagetools/jfs/t1/161028/16/25609/6746/61a08d83E06659dfa/e6418acdab948134.png',
};

View File

@@ -0,0 +1,511 @@
import Timer from '@components/timerCom/FlipClock'
import { getCurrentTime, splicingQuery } from '@utils'
import req from '@utils/request'
import { Checkbox, Modal, Radio } from 'antd'
import _ from 'lodash'
import React, { Component } from 'react'
import PracticeAction from './components/practice-action'
import PracticeAdvance from './components/practice-advance'
import PracticePaging from './components/practice-paging'
import { ApiName, ImgObj, quetionsType } from './constant'
import './index.less'
export default class PracticeDetails extends Component {
constructor(props) {
super(props)
this.state = {
isMark: 0, // 是否标记
isCollection: 0,
currentActive: '',
subjectList: [], // 总题目列表
subjectObject: {}, //题目
currentIndex: 0,
isShowAdvanOverceBox: false,
isShowStopBox: false
}
}
timerRef = React.createRef()
subjectTitle = ''
singleLength = 0
multipleLength = 0
judgeLength = 0
setId = ''
componentDidMount() {
// const urlParams = queryParse(this.props.location.search)
// this.setId = urlParams.setId
this.getSubjectList()
this.timerRef.current.run()
// window.addEventListener('beforeunload', this.listener);
}
componentWillUnmount() {
// window.addEventListener('beforeunload', this.listener);
}
//监听屏幕刷新,关闭默认事件,界面没有动,不会拦截
listener = ev => {
ev.preventDefault()
ev.returnValue = ''
}
/**
* 获得题目列表
*/
getSubjectList = () => {
let params = {
setId: this.setId
}
this.subjectTitle = '热门题目练习'
this.singleLength = 1
this.multipleLength = 1
this.judgeLength = 1
const list = [
{
subjectType: 1,
subjectId: 1
},
{
subjectType: 2,
subjectId: 2
},
{
subjectType: 3,
subjectId: 3
}
]
this.setState({
subjectList: [...list]
})
_.set(list, [0, 'active'], true)
this.getPracticeSubject(list[0], list, 0, [])
// req({
// method: 'post',
// data: params,
// url: ApiName.getSubjects
// })
// .then(res => {
// if (res.data && res.data?.subjectList?.length > 0) {
// let list = res.data.subjectList
// this.singleLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 1).length : 0
// this.multipleLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 2).length : 0
// this.judgeLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 3).length : 0
// this.subjectTitle = res.data?.title || '' // 总题目列表
// this.getPracticeSubject(list[0], list, 0, [])
// }
// })
// .catch(err => console.log(err))
}
/**
* 获得题目详情
* @param {*} item 选择的项
* @param {*} subjectList 题目列表
* @param {*} index 选择的下标
* @param {*} activeList 选中的列表
* @param {*} isMark 是否被标记
*/
getPracticeSubject = (item, subjectList, index, activeList, isMark = 0) => {
let params = {
subjectId: item.subjectId,
subjectType: item.subjectType
}
const optionList =
item.subjectType === 3
? [
{
optionContent: '正确',
optionType: 1
},
{
optionContent: '错误',
optionType: 0
}
]
: [
{
optionType: 1,
optionContent: '<p>题目答案1</p>'
},
{
optionType: 2,
optionContent: '<p>题目答案2</p>'
},
{
optionType: 3,
optionContent: '<p>题目答案3</p>'
}
]
return this.setState({
currentIndex: index,
currentActive: item.subjectType === 2 ? activeList : activeList[0],
subjectObject: {
subjectName: '题干内容',
subjectType: item.subjectType,
optionList,
// subjectList: subjectList,
// currentIndex: index,
// currentActive: item.sub??jectType === 2 ? activeList : activeList[0],
isMark: isMark
}
})
req({
method: 'post',
data: params,
url: ApiName.getPracticeSubject
})
.then(res => {
if (res.data) {
let subjectObject = res.data
if (item.subjectType === 3) {
subjectObject.optionList = [
{
optionContent: '正确',
optionType: 1
},
{
optionContent: '错误',
optionType: 0
}
]
}
this.setState({
subjectObject: res.data,
subjectList: subjectList,
currentIndex: index,
currentActive: item.subjectType === 2 ? activeList : activeList[0],
isMark: isMark
})
}
})
.catch(err => console.log(err))
}
/**
* 选择单选
* @param {*} e
* @returns
*/
onChangeRadio = e => () => {
let { currentIndex, subjectList } = this.state
_.set(subjectList, [currentIndex, 'activeList'], [e])
this.setState({
currentActive: e,
subjectList
})
}
/**
* 选择多选
* @param {*} e
* @returns
*/
onChangeCheck = e => {
let { currentIndex, subjectList } = this.state
_.set(subjectList, [currentIndex, 'activeList'], e)
this.setState({
currentActive: e,
subjectList
})
}
/**
* 暂停计时
*/
onChangeStop = () => {
this.setState({ isShowStopBox: true })
this.timerRef.current.stop()
}
/**
* 标记一下
*/
onChangeMark = () => {
let { currentIndex, subjectList, subjectObject } = this.state
let flag = 1
if (subjectList[currentIndex]?.isMark) {
flag = 0
}
_.set(subjectList, [currentIndex, 'isMark'], flag)
this.setState({
subjectList,
isMark: flag,
subjectObject
})
}
/**
* 选择答题卡
* @param {*} index
* @param {*} item
* @returns
*/
onChangePaging = index => {
let { currentIndex } = this.state
// 如果点击当前题目直接return
if (currentIndex === index) {
return
}
this.changeData(index)
}
/**
* 交卷
*/
onChangeOver = () => {
const { subjectList } = this.state
let answerDetails = []
subjectList.forEach(item => {
let obj = {
subjectId: item.subjectId,
subjectType: item.subjectType,
answerContents: []
}
if (item?.activeList && item?.activeList?.length > 0) {
obj.answerContents = item.activeList
}
answerDetails.push(obj)
})
let params = {
setId: this.setId,
timeUse: this.timerRef.current.getUseTime(),
submitTime: getCurrentTime(),
answerDetails: answerDetails
}
req({
method: 'post',
data: params,
url: ApiName.submitSubject
})
.then(res => {
if (res.data && res.data.practiceId) {
//关闭定时器
this.timerRef.current.end()
this.props.history.replace(
splicingQuery('/practice-analytic', {
practiceId: res.data.practiceId
})
)
}
})
.catch(err => console.log(err))
}
/**
* 提前交卷弹框-直接交卷
*/
onHandleSubmitModal = () => {
this.onChangeOver()
}
/**
* 提前交卷弹框-继续做题
*/
onHandleCancelModal = () => {
this.setState({ isShowAdvanOverceBox: false })
}
/**
* 提前交卷
*/
onChangeAdvanceOver = () => {
this.setState({
isShowAdvanOverceBox: true
})
}
/**
* 下一题
* @returns
*/
onChangeNext = () => {
let { currentIndex } = this.state
currentIndex += 1
this.changeData(currentIndex)
}
/**
* 改变数据
* @param {*} index 当前点击下标
*/
changeData = index => {
let { subjectList } = this.state
let subObj = subjectList[index]
let activeList = [] // 多选 选中的答案项
let isMark = 0 // 是否被标记
// 将其他item设置为未选中
subjectList.forEach(item => {
item.active = false
})
_.set(subjectList, [index, 'active'], true)
// if当前选择的有选答案则直接显示出来
if (subObj?.activeList?.length > 0) {
activeList = subObj?.activeList
}
// if当前已被标记则直接显示出来
if (subObj?.isMark == 1) {
isMark = 1
}
this.getPracticeSubject(subObj, subjectList, index, activeList, isMark)
}
/**
* 暂停弹框-继续做题
*/
onChangeSubmitModal = () => {
this.timerRef.current.run()
this.setState({ isShowStopBox: false })
}
/**
* 暂停弹框-再次再做
*/
onChangeCancelModal = () => {
this.props.history.goBack()
}
render() {
const {
isMark,
isCollection,
currentIndex,
currentActive,
subjectList,
subjectObject,
isShowAdvanOverceBox,
isShowStopBox
} = this.state
const isLast = currentIndex === subjectList?.length - 1
// 获得已答的题目个数
const noAnswerNum =
subjectList.filter(item => item.activeList && item.activeList.length > 0).length || 0
return (
<div className='details-container'>
<div className='container-box'>
<div className='container-box-title'>
<div className='title-title'>{this.subjectTitle}</div>
<div className='title-time'>
<div className='title-timer-img' onClick={this.onChangeStop}>
<img src={isShowStopBox ? ImgObj.stop : ImgObj.run} className='title-timer-icon' />
</div>
<Timer ref={this.timerRef} />
</div>
</div>
<div className='details-question-number'>
<div className='question-number-number'>
{currentIndex + 1}/{subjectList?.length}
</div>
<img src={ImgObj.questionMark} className='question-number-mark' />
<div className='question-number-type'>[{quetionsType[subjectObject.subjectType]}]</div>
</div>
<div className='practice-main'>
<div className='practice-text'>
<div className='practice-question'>{subjectObject.subjectName}</div>
{subjectObject.subjectType === 2 ? (
<Checkbox.Group
className='practice-answer-list'
onChange={this.onChangeCheck}
value={currentActive || []}
key={currentIndex}
>
{subjectObject?.optionList?.length > 0 &&
subjectObject?.optionList.map(item => {
return (
<Checkbox
key={item.optionType}
className={`practice-answer-item ${
currentActive.includes(item.optionType)
? 'practice-answer-item-active'
: ''
}`}
value={item.optionType}
>
<div
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
</Checkbox>
)
})}
</Checkbox.Group>
) : (
<Radio.Group
className='practice-answer-list'
value={currentActive}
key={currentIndex}
>
{subjectObject?.optionList?.length > 0 &&
subjectObject?.optionList.map(item => {
return (
<Radio
key={item.optionType}
onClick={this.onChangeRadio(item.optionType)}
className={`practice-answer-item ${
currentActive === item.optionType ? 'practice-answer-item-active' : ''
}`}
value={item.optionType}
>
<div
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
</Radio>
)
})}
</Radio.Group>
)}
</div>
<PracticeAction
isLast={isLast}
isMark={isMark}
onHandleMark={this.onChangeMark}
onHandleOver={this.onChangeOver}
onHandleAdvanceOver={this.onChangeAdvanceOver}
onHandleNext={this.onChangeNext}
/>
</div>
<PracticePaging
subjectList={subjectList}
onHandlePaging={this.onChangePaging}
singleLength={this.singleLength}
multipleLength={this.multipleLength}
judgeLength={this.judgeLength}
/>
</div>
<PracticeAdvance
isShowModalBox={isShowAdvanOverceBox}
onHandleSubmitModal={this.onHandleSubmitModal}
onHandleCancelModal={this.onHandleCancelModal}
/>
<Modal
closable={false}
maskClosable={false}
style={{ padding: 20 }}
open={isShowStopBox}
onOk={this.onChangeSubmitModal}
onCancel={this.onChangeCancelModal}
okText='继续做题'
cancelText='下次再做'
>
<div style={{ padding: 40 }}>
<img src={ImgObj.info} className='details-container-box-info' />
休息一下吧{subjectList?.length}道题还剩
{subjectList?.length - noAnswerNum}道没做哦
</div>
</Modal>
</div>
)
}
}

View File

@@ -0,0 +1,107 @@
.details-container {
width: 1439px;
margin: 0 auto;
overflow: auto;
background-color: #ffffff;
flex-direction: column;
margin-bottom: 20px;
border: 1px solid #e0e0e0;
border-radius: 3px;
.container-box {
background-color: #ffffff;
.container-box-title {
padding: 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
.title-title {
font-size: 20px;
vertical-align: middle;
overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
// word-wrap: normal;
max-width: 1000px;
}
.title-time {
display: flex;
align-items: center;
.title-timer-img {
margin: -4px 10px 0 0;
width: 30px;
height: 30px;
.title-timer-icon {
width: 100%;
height: 100%;
}
}
}
}
.details-question-number {
padding: 20px;
display: flex;
.question-number-number {
background-color: #e5e5e5;
padding: 0 5px;
border-radius: 5px;
font-size: 16px;
}
.question-number-mark {
margin: 0 8px;
width: 24px;
height: 24px;
}
.question-number-type {
background-color: #fff;
line-height: 1.5;
font-size: 16px;
border-radius: 3px;
}
}
.practice-main {
background-color: #fff;
padding: 20px;
padding: 10px 20px 20px;
.practice-text {
margin-bottom: 20px;
.practice-question {
margin-bottom: 20px;
font-size: 14px;
line-height: 1.6;
}
.practice-answer-list {
width: 100%;
.practice-answer-item {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 12px 20px 13px;
word-break: break-all;
border: 1px solid #d4d4d4;
background-color: #fff;
border-radius: 4px;
&:hover {
cursor: pointer;
color: #1890ff;
background: #f3f3f3;
text-decoration: none;
}
}
.practice-answer-item-active {
color: #1890ff;
text-decoration: none;
border-color: #1890ff;
}
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
margin-left: 0;
}
}
}
}
}
}
.details-container-box-info {
margin-right: 10px;
width: 36px;
height: 36px;
}

View File

@@ -0,0 +1,485 @@
import Timer from '@components/timerCom/FlipClock'
import req from '@utils/request'
import { Checkbox, Modal, Radio } from 'antd'
import _ from 'lodash'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import PracticeAction from './components/practice-action'
import PracticeAdvance from './components/practice-advance'
import PracticePaging from './components/practice-paging'
import { ApiName, ImgObj, quetionsType } from './constant'
import './index.less'
const PracticeDetails = props => {
const navigate = useNavigate()
const [isMark, setIsMark] = useState(0) // 是否标记
const [currentActive, setCurrentActive] = useState('')
const [subjectList, setSubjectList] = useState([])
const [subjectObject, setSubjectObject] = useState({})
const [currentIndex, setCurrentIndex] = useState(0)
const [isShowAdvanOverceBox, setIsShowAdvanOverceBox] = useState(false)
const [isShowStopBox, setIsShowStopBox] = useState(false)
const timerRef = React.createRef()
let subjectTitle = ''
let singleLength = 0
let multipleLength = 0
let judgeLength = 0
const setId = ''
const isLast = currentIndex === subjectList?.length - 1
/**
* 获得题目列表
*/
const getSubjectList = () => {
let params = {
setId: setId
}
subjectTitle = '热门题目练习'
singleLength = 1
multipleLength = 1
judgeLength = 1
const list = [
{
subjectType: 1,
subjectId: 1,
active: true
},
{
subjectType: 2,
subjectId: 2
},
{
subjectType: 3,
subjectId: 3
}
]
setSubjectList([...list])
// _.set(list, [0, 'active'], true)
getPracticeSubject(list[0], list, 0, [])
// req({
// method: 'post',
// data: params,
// url: ApiName.getSubjects
// })
// .then(res => {
// if (res.data && res.data?.subjectList?.length > 0) {
// let list = res.data.subjectList
// singleLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 1).length : 0
// multipleLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 2).length : 0
// judgeLength =
// list?.length > 0 ? list.filter(item => item.subjectType === 3).length : 0
// subjectTitle = res.data?.title || '' // 总题目列表
// getPracticeSubject(list[0], list, 0, [])
// }
// })
// .catch(err => console.log(err))
}
useEffect(() => {
timerRef.current.run()
getSubjectList()
}, [])
/**
* 获得题目详情
* @param {*} item 选择的项
* @param {*} subjectList 题目列表
* @param {*} index 选择的下标
* @param {*} activeList 选中的列表
* @param {*} isMark 是否被标记
*/
const getPracticeSubject = (item, subjectList, index, activeList, isMark = 0) => {
let params = {
subjectId: item.subjectId,
subjectType: item.subjectType
}
const optionList =
item.subjectType === 3
? [
{
optionContent: '正确',
optionType: 1
},
{
optionContent: '错误',
optionType: 0
}
]
: [
{
optionType: 1,
optionContent: '<p>题目答案1</p>'
},
{
optionType: 2,
optionContent: '<p>题目答案2</p>'
},
{
optionType: 3,
optionContent: '<p>题目答案3</p>'
}
]
setCurrentActive(item.subjectType === 2 ? activeList : activeList[0])
setCurrentIndex(index)
setSubjectObject({
subjectName: '题干内容',
subjectType: item.subjectType,
optionList,
// subjectList: subjectList,
// currentIndex: index,
// currentActive: item.sub??jectType === 2 ? activeList : activeList[0],
isMark: isMark
})
return
req({
method: 'post',
data: params,
url: ApiName.getPracticeSubject
})
.then(res => {
if (res.data) {
let subjectObject = res.data
if (item.subjectType === 3) {
subjectObject.optionList = [
{
optionContent: '正确',
optionType: 1
},
{
optionContent: '错误',
optionType: 0
}
]
}
setState({
subjectObject: res.data,
subjectList: subjectList,
currentIndex: index,
currentActive: item.subjectType === 2 ? activeList : activeList[0],
isMark: isMark
})
}
})
.catch(err => console.log(err))
}
/**
* 选择单选
* @param {*} e
* @returns
*/
const onChangeRadio = e => () => {
const list = [...subjectList]
_.set(list, [currentIndex, 'activeList'], [e])
setCurrentActive(e)
setSubjectList([...list])
}
/**
* 选择多选
* @param {*} e
* @returns
*/
const onChangeCheck = e => {
// let { currentIndex, subjectList } = state
const list = [...subjectList]
_.set(list, [currentIndex, 'activeList'], e)
setCurrentActive(e)
setSubjectList([...list])
}
/**
* 暂停计时
*/
const onChangeStop = () => {
// setState({ isShowStopBox: true })
setIsShowStopBox(true)
timerRef.current.stop()
}
/**
* 标记一下
*/
const onChangeMark = () => {
const list = [...subjectList]
let flag = 1
if (list[currentIndex]?.isMark) {
flag = 0
}
_.set(list, [currentIndex, 'isMark'], flag)
setSubjectList([...list])
setIsMark(flag)
}
/**
* 选择答题卡
* @param {*} index
* @param {*} item
* @returns
*/
const onChangePaging = index => {
// 如果点击当前题目直接return
if (currentIndex === index) {
return
}
changeData(index)
}
/**
* 交卷
*/
const onChangeOver = () => {
timerRef.current.end()
navigate('/practice-analytic/1', { replace: true })
// const list = [...subjectList]
// let answerDetails = []
// list.forEach(item => {
// let obj = {
// subjectId: item.subjectId,
// subjectType: item.subjectType,
// answerContents: []
// }
// if (item?.activeList && item?.activeList?.length > 0) {
// obj.answerContents = item.activeList
// }
// answerDetails.push(obj)
// })
// let params = {
// setId: setId,
// timeUse: timerRef.current.getUseTime(),
// submitTime: getCurrentTime(),
// answerDetails: answerDetails
// }
// req({
// method: 'post',
// data: params,
// url: ApiName.submitSubject
// })
// .then(res => {
// if (res.data && res.data.practiceId) {
// //关闭定时器
// timerRef.current.end()
// props.history.replace(
// splicingQuery('/practice-analytic', {
// practiceId: res.data.practiceId
// })
// )
// }
// })
// .catch(err => console.log(err))
}
/**
* 提前交卷弹框-直接交卷
*/
const onHandleSubmitModal = () => {
onChangeOver()
}
/**
* 提前交卷弹框-继续做题
*/
const onHandleCancelModal = () => {
setIsShowAdvanOverceBox(false)
// setState({ isShowAdvanOverceBox: false })
}
/**
* 提前交卷
*/
const onChangeAdvanceOver = () => {
setIsShowAdvanOverceBox(true)
// setState({
// isShowAdvanOverceBox: true
// })
}
/**
* 下一题
* @returns
*/
const onChangeNext = () => {
// let { currentIndex } = state
// currentIndex += 1
setCurrentIndex(currentIndex + 1)
changeData(currentIndex + 1)
}
/**
* 改变数据
* @param {*} index 当前点击下标
*/
const changeData = index => {
// let { subjectList } = state
const list = [...subjectList]
let subObj = list[index]
let activeList = [] // 多选 选中的答案项
let isMark = 0 // 是否被标记
// 将其他item设置为未选中
list.forEach(item => {
item.active = false
})
_.set(list, [index, 'active'], true)
// if当前选择的有选答案则直接显示出来
if (subObj?.activeList?.length > 0) {
activeList = subObj?.activeList
}
// if当前已被标记则直接显示出来
if (subObj?.isMark == 1) {
isMark = 1
}
getPracticeSubject(subObj, list, index, activeList, isMark)
}
/**
* 暂停弹框-继续做题
*/
const onChangeSubmitModal = () => {
timerRef.current.run()
setIsShowStopBox(false)
// setState({ isShowStopBox: false })
}
/**
* 暂停弹框-再次再做
*/
const onChangeCancelModal = () => {
// props.history.goBack()
navigate(-1)
}
const noAnswerNum =
subjectList.filter(item => item.activeList && item.activeList.length > 0).length || 0
return (
<div className='details-container'>
<div className='container-box'>
<div className='container-box-title'>
<div className='title-title'>{subjectTitle}</div>
<div className='title-time'>
<div className='title-timer-img' onClick={onChangeStop}>
<img src={isShowStopBox ? ImgObj.stop : ImgObj.run} className='title-timer-icon' />
</div>
<Timer ref={timerRef} />
</div>
</div>
<div className='details-question-number'>
<div className='question-number-number'>
{currentIndex + 1}/{subjectList?.length}
</div>
<img src={ImgObj.questionMark} className='question-number-mark' />
<div className='question-number-type'>[{quetionsType[subjectObject.subjectType]}]</div>
</div>
<div className='practice-main'>
<div className='practice-text'>
<div className='practice-question'>{subjectObject.subjectName}</div>
{subjectObject.subjectType === 2 ? (
<Checkbox.Group
className='practice-answer-list'
onChange={onChangeCheck}
value={currentActive || []}
key={currentIndex}
>
{subjectObject?.optionList?.length > 0 &&
subjectObject?.optionList.map(item => {
return (
<Checkbox
key={item.optionType}
className={`practice-answer-item ${
currentActive.includes(item.optionType)
? 'practice-answer-item-active'
: ''
}`}
value={item.optionType}
>
<div
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
</Checkbox>
)
})}
</Checkbox.Group>
) : (
<Radio.Group
className='practice-answer-list'
value={currentActive}
key={currentIndex}
>
{subjectObject?.optionList?.length > 0 &&
subjectObject?.optionList.map(item => {
return (
<Radio
key={item.optionType}
onClick={onChangeRadio(item.optionType)}
className={`practice-answer-item ${
currentActive === item.optionType ? 'practice-answer-item-active' : ''
}`}
value={item.optionType}
>
<div
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
</Radio>
)
})}
</Radio.Group>
)}
</div>
<PracticeAction
isLast={isLast}
isMark={isMark}
onHandleMark={onChangeMark}
onHandleOver={onChangeOver}
onHandleAdvanceOver={onChangeAdvanceOver}
onHandleNext={onChangeNext}
/>
</div>
<PracticePaging
subjectList={subjectList}
onHandlePaging={onChangePaging}
singleLength={singleLength}
multipleLength={multipleLength}
judgeLength={judgeLength}
/>
</div>
<PracticeAdvance
isShowModalBox={isShowAdvanOverceBox}
onHandleSubmitModal={onHandleSubmitModal}
onHandleCancelModal={onHandleCancelModal}
/>
<Modal
closable={false}
maskClosable={false}
style={{ padding: 20 }}
open={isShowStopBox}
onOk={onChangeSubmitModal}
onCancel={onChangeCancelModal}
okText='继续做题'
cancelText='下次再做'
>
<div style={{ padding: 40 }}>
<img src={ImgObj.info} className='details-container-box-info' />
休息一下吧{subjectList?.length}道题还剩
{subjectList?.length - noAnswerNum}道没做哦
</div>
</Modal>
</div>
)
}
export default PracticeDetails

View File

@@ -0,0 +1,236 @@
import { debounce, splicingQuery } from '@utils'
import req from '@utils/request'
import { Button, Card, Checkbox, Descriptions, Spin } from 'antd'
import _ from 'lodash'
import React, { Component, Fragment } from 'react'
import { ApiName } from '../../constant'
import './index.less'
const CheckboxGroup = Checkbox.Group
class FrontEnd extends Component {
constructor(props) {
super(props)
this.state = {
tabList: [],
secondCategoryList: [],
isShowSpin: false,
currentKey: '', //当前的大类id
difficulty: 1
}
}
allCategoryMap = {}
selectList = []
componentDidMount() {
this.getSpecialPracticeContent()
}
/**
* 获取菜单对应的内容
*/
getSpecialPracticeContent() {
const { menuId, menuType } = this.props
let params = {
menuId: menuId,
menuType: menuType
}
req({
method: 'post',
data: params,
url: ApiName.getSpecialPracticeContent
})
.then(res => {
if (res.data && res.data?.length > 0) {
let tabList = res.data.map(item => {
return {
tab: item.primaryCategoryName,
key: item.primaryCategoryId
}
})
res.data.forEach(item => {
this.allCategoryMap[item.primaryCategoryId] = item.categoryList
})
this.setState({
currentKey: tabList[0].key,
tabList: tabList,
secondCategoryList: this.allCategoryMap[res.data[0].primaryCategoryId],
isShowSpin: false
})
} else {
this.setState({
tabList: [],
secondCategoryList: [],
isShowSpin: false
})
}
})
.catch(err => console.log(err))
}
/**
* 生成setId接口
*/
addPractice = debounce(() => {
const { menuId } = this.props
let params = {
difficulty: menuId,
assembleIds: this.selectList
}
req({
method: 'post',
data: params,
url: '/admin/practice/set/addPractice'
})
.then(res => {
if (res.data) {
this.props.history.push(
splicingQuery('/practice-details', {
setId: res.data.setId
})
)
}
})
.catch(err => console.log(err))
})
/**
* 复选框-选中/未选中
* @param {*} index 二级分类下标
* @returns
*/
onChange = index => list => {
let { secondCategoryList } = this.state
_.set(secondCategoryList, [index, 'activeList'], list)
this.setState({ secondCategoryList })
this.getAssembleIdList(secondCategoryList)
}
/**
* 切换一级分类
* @param {*} key
*/
onTabChange = debounce(key => {
let { secondCategoryList } = this.state
// 切换一级大类时将activeList全重置为空
secondCategoryList.forEach(item => {
item.activeList = []
})
this.getAssembleIdList([])
this.setState({
currentKey: key,
secondCategoryList: this.allCategoryMap[key]
})
})
/**
* 获取选中的assembleId列表
* @param {*} list
*/
getAssembleIdList = list => {
let activeList = []
list.forEach(item => {
if (item.activeList && item.activeList?.length !== 0) {
activeList = _.concat(activeList, item.activeList)
}
})
this.selectList = activeList
}
/**
* 全选
* @param {*} index 二级分类下标
* @returns
*/
onCheckAllChange = index => e => {
let { secondCategoryList } = this.state
let activeList = []
if (e.target.checked) {
activeList = secondCategoryList[index].labelList.map(item => item.assembleId)
}
_.set(secondCategoryList, [index, 'activeList'], activeList)
this.setState({ secondCategoryList })
this.getAssembleIdList(secondCategoryList)
}
render() {
const { secondCategoryList, isShowSpin, currentKey, tabList } = this.state
return (
<Spin spinning={isShowSpin}>
{tabList?.length > 0 && (
<div className='front-box'>
<Card
style={{ width: '100%' }}
tabList={tabList}
bordered={false}
activeTabKey={currentKey + ''}
onTabChange={key => {
this.onTabChange(key, 'key')
}}
>
<div>
{secondCategoryList?.length > 0 &&
secondCategoryList.map((secondItem, secondIndex) => {
let labelList = secondItem.labelList.map(item => {
return {
label: item.labelName,
value: item.assembleId
}
})
return (
<Descriptions
key={`second_category_${secondIndex}`}
size='default'
title={
<Fragment>
<div className='box'>
<div id={secondItem.categoryId} className='box1'>
{secondItem.categoryName}
</div>
<div className='box2'>
<Checkbox
checked={
secondItem?.activeList?.length == secondItem.labelList.length
}
onChange={this.onCheckAllChange(secondIndex)}
>
全选
</Checkbox>
</div>
</div>
</Fragment>
}
>
<Descriptions.Item label=''>
<CheckboxGroup
value={secondItem.activeList}
options={labelList}
onChange={this.onChange(secondIndex)}
/>
</Descriptions.Item>
</Descriptions>
)
})}
</div>
</Card>
<div className='but'>
<Button
disabled={this.selectList.length === 0}
style={{ opacity: this.selectList.length === 0 ? 0.5 : 1 }}
className='button'
size='large'
onClick={() => {
this.addPractice()
}}
>
开始练习
</Button>
</div>
</div>
)}
</Spin>
)
}
}
export default FrontEnd

View File

@@ -0,0 +1,24 @@
.front-box {
.ant-descriptions-title {
display: flex;
justify-content: space-between;
font-size: 18px;
color: rgba(51, 51, 51, 0.7);
}
.ant-checkbox-wrapper {
font-size: 14px;
color: rgba(51, 51, 51, 0.5);
}
.box{
display: flex;
width: 1439px;
.box1{
flex: 1;
}
.box2{
flex: 1;
// float: right;
text-align: right;
}
}
}

View File

@@ -0,0 +1,90 @@
import req from '@utils/request'
import { Card, Checkbox, Descriptions } from 'antd'
import { useEffect, useState } from 'react'
const apiName = {
/**
* 查询大类
*/
queryPrimaryCategory: '/category/queryPrimaryCategory',
// 查询分类及标签
queryCategoryAndLabel: '/category/queryCategoryAndLabel'
}
const PracticeHome = () => {
const [primaryList, setPrimaryList] = useState<Record<string, any>[]>([])
const [cateAndLabelList, setCateAndLabelList] = useState<Record<string, any>[]>([])
const [currentPrimaryId, setCurrentPrimaryId] = useState()
const queryPrimaryList = () => {
req({
method: 'post',
url: apiName.queryPrimaryCategory,
data: { categoryType: 1 }
})
.then((res: Record<string, any>) => {
if (res.data && res.data.length > 0) {
setCurrentPrimaryId(res.data[0].id)
setPrimaryList([...res.data].map(t => ({ tab: t.categoryName, key: t.id })))
}
})
.catch(err => {
console.log(err)
})
}
useEffect(() => {
queryPrimaryList()
}, [])
const getCategoryAndLabel = () => {
req({
method: 'post',
url: apiName.queryCategoryAndLabel,
data: { id: currentPrimaryId }
})
.then(res => {
if (res.data && res.data.length > 0) {
res.data = res.data.map(item => {
return {
...item,
children: item.labelDTOList.map(t => ({ label: t.labelName, value: t.id }))
}
})
setCateAndLabelList([...res.data])
}
})
.catch(err => {
console.log(err)
})
}
useEffect(() => {
if (currentPrimaryId) {
getCategoryAndLabel()
}
}, [currentPrimaryId])
return (
<div>
<Card tabList={primaryList}>
{cateAndLabelList.map(item => {
return (
<Descriptions title={item.categoryName} extra={<Checkbox></Checkbox>}>
<Descriptions.Item label=''>
<Checkbox.Group
// value={secondItem.activeList}
options={item.children}
// onChange={this.onChange(secondIndex)}
/>
</Descriptions.Item>
</Descriptions>
)
})}
</Card>
</div>
)
}
export default PracticeHome

View File

@@ -0,0 +1,155 @@
// import req from '@utils/request'
import { Card, Input, Pagination, Spin, Tooltip } from 'antd'
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import './index.less'
const { Search } = Input
const tabList = [
{
key: '0',
tab: '默认'
}
// {
// key: '1',
// tab: '最新'
// },
// {
// key: '2',
// tab: '最热'
// }
]
const PaperView = props => {
const navigate = useNavigate()
const [spinning, setSpinning] = useState(false)
const [paperList, setPaperList] = useState([
{
setId: 1,
setName: '测试试卷',
setHeat: 1,
setDesc: '描述'
}
])
const [orderType, setOrderType] = useState(0)
const [setId, setSetId] = useState(0)
const [pageInfo, setPageInfo] = useState({
total: 0,
pageIndex: 1
})
const [searchText, setSearchText] = useState('')
const getPreSetContent = () => {
// const { menuId, menuType } = this.props
// const { orderType, searchText } = this.state
// let params = {
// menuId: menuId,
// menuType: menuType,
// orderType: orderType,
// pageInfo: {
// pageIndex: this.pageIndex,
// pageSize: 8
// },
// setName: searchText
// }
// req({
// method: 'post',
// data: params,
// url: 'admin/practice/set/getPreSetContent'
// })
// .then(res => {
// if (res.data.pageList && res.data.pageList?.length > 0) {
// this.setState({
// paperList: res.data.pageList,
// total: res.data.pageInfo.total,
// isShowSpin: false,
// setId: res.data.pageList.setId
// })
// }
// })
// .catch(err => console.log(err))
}
const onTabChange = key => {
setOrderType(key)
}
const onChangePagination = pageIndex => {
setPageInfo({
...pageInfo,
pageIndex
})
}
const handleJump = setId => navigate('/practice-detail/' + setId)
const onSearch = value => {
setSearchText(value)
}
return (
<Spin spinning={spinning}>
{/* <div className='paper-box-search'>
<Search
placeholder='请输入试卷名'
onSearch={onSearch}
style={{ width: 300, borderRadius: '10px' }}
/>
</div> */}
<div className='paper-box'>
<div className='paper-box-cardlist'>
<Card
style={{ width: '100%' }}
tabList={tabList}
bordered={false}
activeTabKey={tabList.key}
onTabChange={onTabChange}
>
<div className='ant-card-body'>
{paperList?.length > 0 &&
paperList.map((item, index) => {
return (
<div
className='paper-box-cardlist-body-item'
onClick={() => handleJump(item.setId)}
key={`paperList_${index}`}
>
<h1 className='paper-box-cardlist-body-item-title'>{item.setName}</h1>
<div className='paper-box-cardlist-body-item-hot'>
热度指数{item.setHeat}
</div>
<div className='paper-box-cardlist-body-item-describe'>
<Tooltip placement='topLeft' title={item.setDesc}>
<span className='hide-3-line'>{item.setDesc}</span>
</Tooltip>
</div>
</div>
)
})}
</div>
</Card>
</div>
<div className='paper-box-paging'>
{pageInfo.total > 8 && (
<Pagination
style={{
padding: '24px 0',
textAlign: 'center'
}}
showQuickJumper
current={pageInfo.pageIndex}
defaultCurrent={1}
total={pageInfo.total}
defaultPageSize={10}
onChange={onChangePagination}
/>
)}
</div>
</div>
</Spin>
)
}
export default PaperView

View File

@@ -0,0 +1,76 @@
.paper-box-search {
margin-left: 600px;
}
.paper-box {
.paper-box-cardlist {
border-radius: 3px;
margin-bottom: 10px;
.ant-card-body {
padding: 20px 10px;
display: flex;
flex-wrap: wrap;
.paper-box-cardlist-body-item {
height: 190px;
width: 210px;
padding: 20px 30px;
margin: 0 15px 10px 0px;
border-radius: 3px;
background-image: url(../../../../../imgs/badcf6d37c476233.png);
background-size: 100% 100%;
cursor: pointer;
transition: all 0.5s;
&:hover {
transform: scale(1.04);
}
.paper-box-cardlist-body-item-title {
max-height: 40px;
margin-top: 15px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
vertical-align: middle;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
font-size: 14px;
color: #666;
margin-bottom: 10px;
height: 100px;
line-height: 1.4;
font-weight: 400;
font-weight: bolder;
}
.paper-box-cardlist-body-item-logo {
width: 80px;
height: 80px;
text-align: center;
line-height: 80px;
margin: 10px auto 0;
border-radius: 50px;
overflow: hidden;
}
.paper-box-cardlist-body-item-hot {
font-size: 12px;
color: #666;
text-align: center;
margin: 10px 0;
}
.paper-box-cardlist-body-item-describe {
text-align: center;
width: 100%;
font-size: 12px;
color: #888;
box-sizing: border-box;
padding-top: 10px;
.hide-3-line {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
/**
* 接口
*/
export const ApiName = {
/**
* 获取左侧菜单
*/
getPracticeMenu: '/admin/practice/set/getPracticeMenu',
/**
* 获取右侧内容
*/
getSpecialPracticeContent: '/admin/practice/set/getSpecialPracticeContent',
/**
* 查询大类
*/
queryPrimaryCategory: '/category/queryPrimaryCategory',
// 查询分类及标签
queryCategoryAndLabel: '/category/queryCategoryAndLabel'
}

View File

@@ -0,0 +1,159 @@
import { MailOutlined } from '@ant-design/icons'
import req from '@utils/request'
import { Menu } from 'antd'
import React, { Component, Fragment } from 'react'
import PaperEnd from './components/paper-end/index'
import { ApiName } from './constant'
import './index.less'
const { SubMenu } = Menu
export default class PracticeQuestions extends Component {
constructor(props) {
super(props)
this.state = {
currentKey: '', // 选中的menu
subMenuList: []
}
}
/**
* 当前选择的二级菜单
*/
currentKeyMap = {}
/**
* 当前选择的二级菜单对应的一级菜单
*/
currentKeyFirstMenuType = 1
componentDidMount() {
this.getPracticeMenu()
}
/**
* 获取左侧菜单
*/
getPracticeMenu() {
this.currentKeyMap = {
menuName: '试卷',
menuId: '10',
menuType: 2
}
this.setState({
currentKey: '10',
subMenuList: [
{
title: '模拟套卷',
detailVOS: [
{
menuName: '后端',
menuId: '10',
menuType: 2
}
]
}
]
})
return
// return new Promise(resolve => {
// resolve()
// })
req({
method: 'post',
data: {},
url: ApiName.getPracticeMenu
})
.then(res => {
if (res.data && res.data.length > 0) {
this.currentKeyMap = res.data[0].detailVOS[0]
this.setState({
subMenuList: res.data,
currentKey: this.currentKeyMap.menuId
})
}
})
.catch(err => console.log(err))
}
/**
* 切换菜单
* @param {*} e
*/
handleClick = e => {
console.log(e)
let { subMenuList, currentKey } = this.state
if (currentKey === e.key) {
return
}
// 获得当前选择的下标
let index = e.keyPath[1].split('_')[2]
// this.currentKeyFirstMenuType = subMenuList[index].id;
this.currentKeyFirstMenuType = index // 记录一级菜单
subMenuList[index].detailVOS.forEach(element => {
// 获得当前选中的菜单项
if (element.menuId == e.key) {
this.currentKeyMap = element
}
})
this.setState({
currentKey: e.key
})
}
render() {
const { currentKey, subMenuList } = this.state
return (
<Fragment>
{subMenuList?.length > 0 && (
<div className='practice-questions-container'>
<div className='practice-questions-menu'>
<Menu
mode='inline'
onClick={this.handleClick}
style={{ width: 256 }}
defaultOpenKeys={['sub_menu_0']}
defaultSelectedKeys={[currentKey + '']}
>
{subMenuList.map((subMenuItem, subMenuIndex) => {
return (
<SubMenu
key={`sub_menu_${subMenuIndex}`}
title={subMenuItem.title}
icon={<MailOutlined />}
>
{subMenuItem?.detailVOS?.length > 0 &&
subMenuItem?.detailVOS?.map(menuItem => {
return (
<Menu.Item key={menuItem.menuId}>
{menuItem.menuType == 1 ? 'GRADE ' : ''}
{menuItem.menuName}
</Menu.Item>
)
})}
</SubMenu>
)
})}
</Menu>
</div>
<div className='practice-questions-box' key={this.currentKeyMap.menuId}>
{/* {this.currentKeyFirstMenuType == 0 ? (
<FrontEnd
menuId={this.currentKeyMap.menuId}
menuType={this.currentKeyMap.menuType}
/>
) : (
<PaperEnd
menuId={this.currentKeyMap.menuId}
menuType={this.currentKeyMap.menuType}
/>
)} */}
<PaperEnd menuId={this.currentKeyMap.menuId} menuType={this.currentKeyMap.menuType} />
</div>
</div>
)}
</Fragment>
)
}
}

View File

@@ -0,0 +1,50 @@
.practice-questions-container {
margin: 0 auto;
display: flex;
width: 1439px;
// padding: 20px 50px;
border-radius: 5px;
// flex-direction: column;
overflow: auto;
.practice-questions-menu {
margin-right: 20px;
padding: 10px;
overflow-y: auto;
background-color: white;
.ant-menu-submenu > .ant-menu {
background-color: #fafafa;
}
// 右边线去掉
.ant-menu-inline,
.ant-menu-vertical,
.ant-menu-vertical-left {
border-right: 0;
}
}
.practice-questions-box {
flex: 1;
margin: 0 auto;
background-color: white;
padding: 20px 50px;
border-radius: 5px;
flex-direction: column;
overflow: auto;
.tab-name {
color: rgba(60, 110, 238, 1) !important;
}
.but {
margin-bottom: 50px;
.button {
// size: 25px;
width: 120px;
block-size: 40px;
color: white;
// background-color: rgba(60, 110, 238, 0.7) !important;
background-color: rgba(60, 110, 238, 1);
border-radius: 36px;
float: right;
}
}
}
}

View File

@@ -1,39 +1,39 @@
.question-bank-box {
display: flex;
width: 1439px;
margin: 0 auto;
background-color: #f3f4f6;
border-radius: 5px;
height: calc(100vh - 90px);
overflow-y: scroll;
.mask-box {
flex: 1;
overflow-y: auto;
border-radius: 8px;
.question-box {
.category-list-box {
padding: 24px 24px 6px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background-color: #fff;
}
.question-list-box {
margin-top: 1px;
}
.loading-more{
background-color: white;
text-align: center;
padding-bottom: 15px;
}
}
}
.ranking-box {
margin-left: 16px;
overflow-y: auto;
width: 310px;
}
.ant-spin-nested-loading {
display: flex;
width: 1439px;
margin: 0 auto;
background-color: #f3f4f6;
border-radius: 5px;
height: calc(100vh - 90px);
overflow-y: scroll;
.mask-box {
flex: 1;
overflow-y: auto;
border-radius: 8px;
.question-box {
.category-list-box {
padding: 24px 24px 6px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background-color: #fff;
}
.question-list-box {
margin-top: 1px;
}
.loading-more {
background-color: white;
text-align: center;
padding-bottom: 15px;
}
}
}
.ranking-box {
margin-left: 16px;
overflow-y: auto;
width: 310px;
}
.ant-spin-nested-loading {
background-color: #fff;
}
}

View File

@@ -31,7 +31,6 @@ const QuestionBank = () => {
* @param {*} assembleIds 三级标签 assembleIds
*/
const onChangeLabel = values => {
console.log(values)
setSelectedValue(values)
setQuestionList([])
setTotal(0)

View File

@@ -1,341 +1,341 @@
import React, { Component, Fragment, createRef } from 'react';
import { Input, Modal, message, Spin } from 'antd';
import { Input, Modal, message } from 'antd'
import React, { Component, Fragment, createRef } from 'react'
import req from '@utils/request';
import { debounce } from '@utils';
import KindEditor from '../kind-editor';
import RankLabelBox from '../rank-label-box';
import RepeatContentBox from '../repeat-content-box';
import { apiName } from '../../constant';
import { debounce } from '@utils'
import req from '@utils/request'
import { apiName } from '../../constant'
import QuestionEditor from '../question-editor'
import './index.less';
import RankLabelBox from '../rank-label-box'
import RepeatContentBox from '../repeat-content-box'
import './index.less'
export default class BriefQuestions extends Component {
constructor(props) {
super(props);
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true, // 是否支持提交
};
constructor(props) {
super(props)
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true // 是否支持提交
}
kindEditor = createRef();
rankLabelBox = createRef();
rankId = 1; //职级
subjectAnswer = ''; // 答案
firstCategoryValue = ''; // 一级分类的值
secondCategoryValue = []; // 级分类的值
thirdCategoryValue = []; // 三级标签的值
repeatInfo = {}; // 重复率
}
kindEditor = createRef()
rankLabelBox = createRef()
rankId = 1 //职级
subjectAnswer = '' // 答案
firstCategoryValue = '' // 级分类的值
secondCategoryValue = [] // 二级分类的值
thirdCategoryValue = [] // 三级标签的值
repeatInfo = {} // 重复率
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = (e) => {
let str = e.target.value.trim();
this.setState(
{
subjectName: str,
},
() => {
// this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
}
);
};
/**
* 富文本编辑器
* @param {*} e
*/
onChangeEditor = (e) => {
this.subjectAnswer = e;
let isDisabledSubmit = this.checkData();
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = e => {
let str = e.target.value.trim()
this.setState(
{
subjectName: str
},
() => {
// this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit,
});
};
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
console.log(this.rankId)
const { subjectName, isDisabledSubmit, isSubmit } = this.state;
if (isDisabledSubmit || !isSubmit) {
return;
}
if (!isSubmit) {
return;
}
if (!!!subjectName) {
message.warning('请输入题目名称');
return;
}
if (!!!this.subjectAnswer) {
message.warning('请输入题目答案');
return;
}
if (!!!this.firstCategoryValue) {
message.warning('请选择一级分类');
return;
}
if (this.secondCategoryValue.length <= 0) {
message.warning('请选择二级分类');
return;
}
if (this.thirdCategoryValue.length <= 0) {
message.warning('请选择三级标签');
return;
}
this.setState({
isSubmit: false,
});
let params = {
subjectName: subjectName,
subjectDifficult: this.rankId,
subjectType: 4,
subjectScore: 1,
subjectParse: '解析什么',
subjectAnswer: this.subjectAnswer,
categoryIds: this.secondCategoryValue.filter(item => item.active).map(t => t.id),
labelIds: this.thirdCategoryValue.filter(item => item.active).map(t => t.id),
};
req({
method: 'post',
data: params,
url: apiName.add,
isDisabledSubmit
})
.then((res) => {
this.setState({
isSubmit: true,
}, () => {
this.successModalConfirm();
});
})
.catch((err) => {
this.setState({
isSubmit: true,
});
console.log(err);
});
});
}
)
}
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state;
let isDisabledSubmit = false;
if (
!!!subjectName ||
!!!this.subjectAnswer ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0
// ||
// this.thirdCategoryValue.length <= 0
) {
isDisabledSubmit = true;
}
return isDisabledSubmit;
};
/**
* 富文本编辑器
* @param {*} e
*/
onChangeEditor = e => {
this.subjectAnswer = e
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 取消
*/
onCancel = () => {
console.log(this.kindEditor)
this.subjectAnswer = ''; // 答案
this.rankId = 1;
this.firstCategoryValue = '';
this.secondCategoryValue = [];
this.thirdCategoryValue = [];
this.repeatInfo = {};
this.kindEditor.current.onClear();
this.rankLabelBox.current.initRankLabel();
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true, // 是否支持提交
});
};
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId,
};
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject,
})
.then((res) => {
if (res.data) {
this.successModalConfirm();
} else {
message.info('请再次确认');
}
})
.catch((err) => {
console.log(err);
message.error('请再次确认');
});
},
300,
true
);
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {};
this.setState({
isShowModalBox: false,
});
};
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16,
}}>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm',
});
};
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel();
};
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/question-bank';
};
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue; // 一级分类的值
this.secondCategoryValue = secondCategoryValue; // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue; // 三级标签的值
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = (list) => {
this.rankId = list[0].categoryId;
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state;
const { questionsType } = this.props;
// this.successModalConfirm();
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className="brief-questions-container">
<div className="brief-questions-title">题目名称</div>
<div className="brief-questions-main">
<Input
placeholder="输入题目"
className="brief-questions-input"
value={subjectName}
maxLength={64}
onChange={this.onChangeSubjectName}
/>
</div>
</div>
<div className="brief-questions-container">
<div className="brief-questions-title">题目答案</div>
{this.reanderAnser()}
</div>
<RankLabelBox
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
ref={this.rankLabelBox}
/>
<div className="brief-questions-btns-container">
<div className="brief-questions-btn" onClick={this.onCancel}>
清空
</div>
<div
className={`brief-questions-btn brief-questions-submit ${isDisabledSubmit && 'brief-questions-disabled-submit'
}`}
onClick={this.onSubmit}>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
);
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
console.log(this.rankId)
const { subjectName, isDisabledSubmit, isSubmit } = this.state
if (isDisabledSubmit || !isSubmit) {
return
}
if (!isSubmit) {
return
}
if (!!!subjectName) {
message.warning('请输入题目名称')
return
}
if (!!!this.subjectAnswer) {
message.warning('请输入题目答案')
return
}
if (!!!this.firstCategoryValue) {
message.warning('请选择一级分类')
return
}
if (this.secondCategoryValue.length <= 0) {
message.warning('请选择二级分类')
return
}
if (this.thirdCategoryValue.length <= 0) {
message.warning('请选择三级标签')
return
}
this.setState({
isSubmit: false
})
let params = {
subjectName: subjectName,
subjectDifficult: this.rankId,
subjectType: 4,
subjectScore: 1,
subjectParse: '解析什么',
subjectAnswer: this.subjectAnswer,
categoryIds: this.secondCategoryValue.filter(item => item.active).map(t => t.id),
labelIds: this.thirdCategoryValue.filter(item => item.active).map(t => t.id)
}
req({
method: 'post',
data: params,
url: apiName.add
})
.then(res => {
this.setState(
{
isSubmit: true
},
() => {
this.successModalConfirm()
}
)
})
.catch(err => {
this.setState({
isSubmit: true
})
console.log(err)
})
})
/**
* 问答题-答案
*/
reanderAnser = () => {
return (
<div className="brief-questions-main">
<QuestionEditor
onChange={this.onChangeEditor}
ref={this.kindEditor}
/>
</div>
);
};
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state
let isDisabledSubmit = false
if (
!!!subjectName ||
!!!this.subjectAnswer ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0
// ||
// this.thirdCategoryValue.length <= 0
) {
isDisabledSubmit = true
}
return isDisabledSubmit
}
/**
* 取消
*/
onCancel = () => {
this.subjectAnswer = '' // 答案
this.rankId = 1
this.firstCategoryValue = ''
this.secondCategoryValue = []
this.thirdCategoryValue = []
this.repeatInfo = {}
this.kindEditor.current.onClear()
this.rankLabelBox.current.initRankLabel()
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true // 是否支持提交
})
}
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId
}
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject
})
.then(res => {
if (res.data) {
this.successModalConfirm()
} else {
message.info('请再次确认')
}
})
.catch(err => {
console.log(err)
message.error('请再次确认')
})
},
300,
true
)
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {}
this.setState({
isShowModalBox: false
})
}
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16
}}
>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm'
})
}
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel()
}
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/question-bank'
}
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue // 一级分类的值
this.secondCategoryValue = secondCategoryValue // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue // 三级标签的值
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = list => {
this.rankId = list[0].categoryId
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state
const { questionsType } = this.props
// this.successModalConfirm();
return (
// <Spin spinning={!isSubmit}>
<Fragment>
<div className='brief-questions-container'>
<div className='brief-questions-title'>题目名称</div>
<div className='brief-questions-main'>
<Input
placeholder='输入题目'
className='brief-questions-input'
value={subjectName}
maxLength={64}
onChange={this.onChangeSubjectName}
/>
</div>
</div>
<div className='brief-questions-container'>
<div className='brief-questions-title'>题目答案</div>
{this.reanderAnser()}
</div>
<RankLabelBox
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
ref={this.rankLabelBox}
/>
<div className='brief-questions-btns-container'>
<div className='brief-questions-btn' onClick={this.onCancel}>
清空
</div>
<div
className={`brief-questions-btn brief-questions-submit ${
isDisabledSubmit && 'brief-questions-disabled-submit'
}`}
onClick={this.onSubmit}
>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
// {/* </Spin> */}
)
}
/**
* 问答题-答案
*/
reanderAnser = () => {
return (
<div className='brief-questions-main'>
<QuestionEditor onChange={this.onChangeEditor} ref={this.kindEditor} />
</div>
)
}
}

View File

@@ -1,327 +1,312 @@
import React, { Component, Fragment } from 'react';
import { Input, Modal, message, Spin } from 'antd';
import req from '@utils/request';
import { debounce } from '@utils';
import RankLabelBox from '../rank-label-box';
import OptionInputBox from '../option-input-box';
import RepeatContentBox from '../repeat-content-box';
import { apiName } from '../../constant';
import './index.less';
import { debounce } from '@utils'
import req from '@utils/request'
import { Input, Modal, Spin, message } from 'antd'
import React, { Component, Fragment, createRef } from 'react'
import { apiName } from '../../constant'
import OptionInputBox from '../option-input-box'
import RankLabelBox from '../rank-label-box'
import RepeatContentBox from '../repeat-content-box'
import './index.less'
export default class JudgeQuestions extends Component {
constructor(props) {
super(props);
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true, // 是否支持提交
};
constructor(props) {
super(props)
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true // 是否支持提交
}
rankLabelBox = RankLabelBox | null;
optionInputBox = OptionInputBox | null;
currentActive = []; // 当前选中的项
scoreValue = ''; // 分数
subjectAnalysis = ''; //试题解析
rankId = 1; //职级
firstCategoryValue = ''; // 一级分类的值
secondCategoryValue = []; // 二级分类的值
thirdCategoryValue = []; // 三级标签的值
repeatInfo = {}; // 重复率
}
// rankLabelBox = RankLabelBox | null
rankLabelBox = createRef()
optionInputBox = OptionInputBox | null
currentActive = [] // 当前选中的项
scoreValue = '' // 分数
subjectAnalysis = '' //试题解析
rankId = 1 //职级
firstCategoryValue = '' // 一级分类的值
secondCategoryValue = [] // 二级分类的值
thirdCategoryValue = [] // 三级标签的值
repeatInfo = {} // 重复率
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = (e) => {
let str = e.target.value.trim();
this.setState(
{
subjectName: str,
},
() => {
this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
}
);
};
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state;
if (isDisabledSubmit || !isSubmit) {
return;
}
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = e => {
let str = e.target.value.trim()
this.setState(
{
subjectName: str
},
() => {
// this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData()
this.setState({
isSubmit: false,
});
let params = {
subjectName: subjectName,
difficulty: this.rankId,
subjectType: 3,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
isCorrect: this.currentActive[0],
categoryIds: this.secondCategoryValue,
labelIds: this.thirdCategoryValue,
};
console.log('判断录入 ----', params);
req({
method: 'post',
data: params,
url: apiName.addInterviewSubject,
isDisabledSubmit
})
.then((res) => {
this.repeatInfo = {};
if (res.data && res.data.insertStatus) {
this.setState(
{
isSubmit: true,
},
() => {
this.successModalConfirm();
}
);
} else if (!res.data.insertStatus) {
this.repeatInfo = {
repeatDocId: res.data.docId, // 重复题目id
repeatRate: res.data.repeatRate, // 重复率
repeatIsCorrect: res.data.isCorrect, // 答案
repeatSubjectName: res.data.subjectName, // 重复题目
repeatSetterErp: res.data.subjectSetterErp, // 出题人erp
repeatSetterName: res.data.subjectSetterName, // 出题人姓名
};
this.setState({
isShowModalBox: true,
isSubmit: true,
});
}
})
.catch((err) => {
this.setState({
isSubmit: true,
});
console.log(err);
});
});
}
)
}
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state;
let isDisabledSubmit = false;
if (
!!!subjectName ||
this.currentActive?.length <= 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true;
}
return isDisabledSubmit;
};
/**
* 取消
*/
onCancel = () => {
this.currentActive = []; // 选项列表
this.scoreValue = ''; // 分数
this.subjectAnalysis = ''; //试题解析
this.rankId = 1;
this.firstCategoryValue = ''; // 一级分类的值
this.secondCategoryValue = []; // 二级分类的值
this.thirdCategoryValue = []; // 三级标签的值
this.repeatInfo = {};
this.rankLabelBox.initRankLabel();
this.optionInputBox.handleClearOption();
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true, // 是否支持提交
});
};
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId,
};
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject,
})
.then((res) => {
if (res.data) {
this.successModalConfirm();
} else {
message.info('请再次确认');
}
})
.catch((err) => {
console.log(err);
message.error('请再次确认');
});
},
300,
true
);
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {};
this.setState({
isShowModalBox: false,
});
};
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16,
}}>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm',
});
};
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel();
};
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
// this.onCancel();
window.location.href = '/cms-supplier/question-bank';
};
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue; // 一级分类的值
this.secondCategoryValue = secondCategoryValue; // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue; // 三级标签的值
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = (list) => {
this.rankId = list[0];
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive;
this.scoreValue = scoreValue;
this.subjectAnalysis = subjectAnalysis;
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state;
const { questionsType } = this.props;
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className="judge-questions-container">
<div className="judge-questions-title">题目名称</div>
<Input
placeholder="输入题目"
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={(e) => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
ref={(ref) => {
this.optionInputBox = ref;
}}
isJudge={true}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={(ref) => {
this.rankLabelBox = ref;
}}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className="judge-questions-btns-container">
<div className="judge-questions-btn" onClick={this.onCancel}>
清空
</div>
<div
className={`judge-questions-btn judge-questions-submit ${isDisabledSubmit && 'judge-questions-disabled-submit'
}`}
onClick={this.onSubmit}>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
);
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state
if (isDisabledSubmit || !isSubmit) {
return
}
this.setState({
isSubmit: false
})
let params = {
subjectName: subjectName,
subjectDifficult: this.rankId,
subjectType: 3,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
optionList: [{ isCorrect: this.currentActive[0] }],
categoryIds: this.secondCategoryValue.filter(item => item.active).map(t => t.id),
labelIds: this.thirdCategoryValue.filter(item => item.active).map(t => t.id)
}
console.log('判断录入 ----', params)
req({
method: 'post',
data: params,
url: apiName.add
})
.then(res => {
this.setState(
{
isSubmit: true
},
() => {
this.successModalConfirm()
}
)
})
.catch(err => {
this.setState({
isSubmit: true
})
console.log(err)
})
})
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state
let isDisabledSubmit = false
if (
!!!subjectName ||
this.currentActive?.length <= 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true
}
return isDisabledSubmit
}
/**
* 取消
*/
onCancel = () => {
this.currentActive = [] // 选项列表
this.scoreValue = '' // 分数
this.subjectAnalysis = '' //试题解析
this.rankId = 1
this.firstCategoryValue = '' // 一级分类的值
this.secondCategoryValue = [] // 二级分类的值
this.thirdCategoryValue = [] // 三级标签的值
this.repeatInfo = {}
this.rankLabelBox.current.initRankLabel()
this.optionInputBox.handleClearOption()
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true // 是否支持提交
})
}
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId
}
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject
})
.then(res => {
if (res.data) {
this.successModalConfirm()
} else {
message.info('请再次确认')
}
})
.catch(err => {
console.log(err)
message.error('请再次确认')
})
},
300,
true
)
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {}
this.setState({
isShowModalBox: false
})
}
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16
}}
>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm'
})
}
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel()
}
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/question-bank'
}
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue // 一级分类的值
this.secondCategoryValue = secondCategoryValue // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue // 三级标签的值
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = list => {
this.rankId = list[0]
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive
this.scoreValue = scoreValue
this.subjectAnalysis = subjectAnalysis
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state
const { questionsType } = this.props
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className='judge-questions-container'>
<div className='judge-questions-title'>题目名称</div>
<Input
placeholder='输入题目'
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={e => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
ref={ref => {
this.optionInputBox = ref
}}
isJudge={true}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={this.rankLabelBox}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className='judge-questions-btns-container'>
<div className='judge-questions-btn' onClick={this.onCancel}>
清空
</div>
<div
className={`judge-questions-btn judge-questions-submit ${
isDisabledSubmit && 'judge-questions-disabled-submit'
}`}
onClick={this.onSubmit}
>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
)
}
}

View File

@@ -1,52 +1,52 @@
.judge-questions-container {
width: 1000px;
// width: 1000px;
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.judge-questions-title {
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.judge-questions-title {
display: flex;
align-items: center;
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
}
}
.judge-questions-btns-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.judge-questions-btn {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.judge-questions-btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.judge-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.judge-questions-disabled-submit {
opacity: 0.5;
}
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.judge-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.judge-questions-disabled-submit {
opacity: 0.5;
}
}

View File

@@ -1,343 +1,366 @@
import React, { Component, Fragment } from 'react';
import { Input, Modal, message, Spin } from 'antd';
import _ from 'lodash';
import req from '@utils/request';
import { debounce } from '@utils';
import KindEditor from '../kind-editor';
import RankLabelBox from '../rank-label-box';
import OptionInputBox from '../option-input-box';
import RepeatContentBox from '../repeat-content-box';
import { apiName } from '../../constant';
import './index.less';
import { debounce } from '@utils'
import req from '@utils/request'
import { Input, Modal, Spin, message } from 'antd'
import React, { Component, Fragment, createRef } from 'react'
import { apiName } from '../../constant'
import OptionInputBox from '../option-input-box'
import RankLabelBox from '../rank-label-box'
import RepeatContentBox from '../repeat-content-box'
import './index.less'
const defalutLabel = '请使用富文本编辑器输入选项内容';
const defalutLabel = '请使用富文本编辑器输入选项内容'
export default class MultipleQuestions extends Component {
constructor(props) {
super(props);
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true, // 是否支持提交
};
constructor(props) {
super(props)
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true // 是否支持提交
}
kindEditor = KindEditor | null;
rankLabelBox = RankLabelBox | null;
optionInputBox = OptionInputBox | null;
currentActive = []; // 选项列表
scoreValue = ''; // 分数
subjectAnalysis = ''; //试题解析
rankId = 1; //职级
subjectAnswer = ''; // 选项内容
firstCategoryValue = ''; // 一级分类的值
secondCategoryValue = []; // 二级分类的值
thirdCategoryValue = []; // 三级标签的值
repeatInfo = {}; // 重复率
}
rankLabelBox = createRef()
optionInputBox = OptionInputBox | null
currentActive = [] // 选项列表
scoreValue = '' // 分数
subjectAnalysis = '' //试题解析
rankId = 1 //职级
subjectAnswer = '' // 选项内容
firstCategoryValue = '' // 一级分类的值
secondCategoryValue = [] // 二级分类的值
thirdCategoryValue = [] // 三级标签的值
repeatInfo = {} // 重复率
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = (e) => {
let str = e.target.value.trim();
this.setState(
{
subjectName: str,
},
() => {
this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
}
);
};
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state;
if (isDisabledSubmit || !isSubmit) {
return;
}
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = e => {
let str = e.target.value.trim()
this.setState(
{
subjectName: str
},
() => {
// this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData()
this.setState({
isSubmit: false,
});
let params = {
subjectName: subjectName,
difficulty: this.rankId,
subjectType: 2,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
categoryIds: this.secondCategoryValue,
labelIds: this.thirdCategoryValue,
optionList: this.currentActive,
};
console.log('多选录入 ----', params);
req({
method: 'post',
data: params,
url: apiName.addInterviewSubject,
isDisabledSubmit
})
.then((res) => {
this.repeatInfo = {};
if (res.data && res.data.insertStatus) {
this.setState(
{
isSubmit: true,
},
() => {
this.successModalConfirm();
}
);
} else if (!res.data.insertStatus) {
this.repeatInfo = {
repeatDocId: res.data.docId, // 重复题目id
repeatRate: res.data.repeatRate, // 重复率
repeatSubjectName: res.data.subjectName, // 重复题目
repeatOptionList: res.data.optionList, // 重复列表项
repeatSetterErp: res.data.subjectSetterErp, // 出题人erp
repeatSetterName: res.data.subjectSetterName, // 出题人姓名
};
this.setState({
isShowModalBox: true,
isSubmit: true,
});
}
})
.catch((err) => {
this.setState({
isSubmit: true,
});
console.log(err);
});
});
}
)
}
/**
* 校验是否支持点击按钮
* @returns
*/
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state;
let list = this.currentActive.filter((item) => item.optionContent === defalutLabel);
let isDisabledSubmit = false;
if (
!!!subjectName ||
list.length > 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true;
}
return isDisabledSubmit;
};
/**
* 取消
*/
onCancel = () => {
this.currentActive = []; // 选项列表
this.scoreValue = ''; // 分数
this.subjectAnalysis = ''; //试题解析
this.rankId = 1;
this.subjectAnswer = ''; // 选项内容
this.firstCategoryValue = ''; // 一级分类的值
this.secondCategoryValue = []; // 二级分类的值
this.thirdCategoryValue = []; // 三级标签的值
this.repeatInfo = {};
this.kindEditor && this.kindEditor.onClear();
this.rankLabelBox.initRankLabel();
this.optionInputBox.handleClearOption();
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true, // 是否支持提交
});
};
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId,
};
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject,
})
.then((res) => {
if (res.data) {
this.successModalConfirm();
} else {
message.info('请再次确认');
}
})
.catch((err) => {
console.log(err);
message.error('请再次确认');
});
},
300,
true
);
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {};
this.setState({
isShowModalBox: false,
});
};
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16,
}}>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm',
});
};
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel();
};
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
// this.onCancel();
window.location.href = '/cms-supplier/question-bank';
};
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue; // 一级分类的值
this.secondCategoryValue = secondCategoryValue; // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue; // 三级标签的值
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = (list) => {
this.rankId = list[0];
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive;
this.scoreValue = scoreValue;
this.subjectAnalysis = subjectAnalysis;
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state;
const { questionsType } = this.props;
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className="multiple-questions-container">
<div className="multiple-questions-title">题目名称</div>
<Input
placeholder="输入题目"
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={(e) => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
key="multiple-option-input"
ref={(ref) => {
this.optionInputBox = ref;
}}
isMultiple={true}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={(ref) => {
this.rankLabelBox = ref;
}}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className="multiple-questions-btns-container">
<div className="multiple-questions-btn" onClick={this.onCancel}>
清空
</div>
<div
className={`multiple-questions-btn multiple-questions-submit ${isDisabledSubmit && 'multiple-questions-disabled-submit'
}`}
onClick={this.onSubmit}>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
);
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state
if (isDisabledSubmit || !isSubmit) {
return
}
if (!!!subjectName) {
message.warning('请输入题目名称')
return
}
}
if (!this.currentActive.length) {
message.warning('请录入答案')
return
}
if (!!!this.firstCategoryValue) {
message.warning('请选择一级分类')
return
}
if (this.secondCategoryValue.length <= 0) {
message.warning('请选择二级分类')
return
}
if (this.thirdCategoryValue.length <= 0) {
message.warning('请选择三级标签')
return
}
this.setState({
isSubmit: false
})
let params = {
subjectName: subjectName,
subjectDifficult: this.rankId,
subjectType: 2,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
categoryIds: this.secondCategoryValue.filter(item => item.active).map(t => t.id),
labelIds: this.thirdCategoryValue.filter(item => item.active).map(t => t.id),
optionList: this.currentActive
}
req({
method: 'post',
data: params,
url: apiName.add
})
.then(res => {
// this.repeatInfo = {}
// if (res.data && res.data.insertStatus) {
// this.setState(
// {
// isSubmit: true
// },
// () => {
// this.successModalConfirm()
// }
// )
// } else if (!res.data.insertStatus) {
// this.repeatInfo = {
// repeatDocId: res.data.docId, // 重复题目id
// repeatRate: res.data.repeatRate, // 重复率
// repeatSubjectName: res.data.subjectName, // 重复题目
// repeatOptionList: res.data.optionList, // 重复列表项
// repeatSetterErp: res.data.subjectSetterErp, // 出题人erp
// repeatSetterName: res.data.subjectSetterName // 出题人姓名
// }
// this.setState({
// isShowModalBox: true,
// isSubmit: true
// })
// }
this.setState(
{
isSubmit: true
},
() => {
this.successModalConfirm()
}
)
})
.catch(err => {
this.setState({
isSubmit: true
})
console.log(err)
})
})
/**
* 校验是否支持点击按钮
* @returns
*/
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state
let list = this.currentActive.filter(item => item.optionContent === defalutLabel)
let isDisabledSubmit = false
if (
!!!subjectName ||
list.length > 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true
}
return isDisabledSubmit
}
/**
* 取消
*/
onCancel = () => {
this.currentActive = [] // 选项列表
this.scoreValue = '' // 分数
this.subjectAnalysis = '' //试题解析
this.rankId = 1
this.subjectAnswer = '' // 选项内容
this.firstCategoryValue = '' // 一级分类的值
this.secondCategoryValue = [] // 二级分类的值
this.thirdCategoryValue = [] // 三级标签的值
this.repeatInfo = {}
this.rankLabelBox.current.initRankLabel()
this.optionInputBox.handleClearOption()
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true // 是否支持提交
})
}
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId
}
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject
})
.then(res => {
if (res.data) {
this.successModalConfirm()
} else {
message.info('请再次确认')
}
})
.catch(err => {
console.log(err)
message.error('请再次确认')
})
},
300,
true
)
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {}
this.setState({
isShowModalBox: false
})
}
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16
}}
>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm'
})
}
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel()
}
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/question-bank'
}
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue // 一级分类的值
this.secondCategoryValue = secondCategoryValue // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue // 三级标签的值
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = list => {
this.rankId = list[0]
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive
this.scoreValue = scoreValue
this.subjectAnalysis = subjectAnalysis
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state
const { questionsType } = this.props
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className='multiple-questions-container'>
<div className='multiple-questions-title'>题目名称</div>
<Input
placeholder='输入题目'
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={e => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
key='multiple-option-input'
ref={ref => {
this.optionInputBox = ref
}}
isMultiple={true}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={this.rankLabelBox}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className='multiple-questions-btns-container'>
<div className='multiple-questions-btn' onClick={this.onCancel}>
清空
</div>
<div
className={`multiple-questions-btn multiple-questions-submit ${
isDisabledSubmit && 'multiple-questions-disabled-submit'
}`}
onClick={this.onSubmit}
>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
)
}
}

View File

@@ -1,52 +1,52 @@
.multiple-questions-container {
width: 1000px;
// width: 1000px;
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.multiple-questions-title {
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.multiple-questions-title {
display: flex;
align-items: center;
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
}
}
.multiple-questions-btns-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.multiple-questions-btn {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.multiple-questions-btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.multiple-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.multiple-questions-disabled-submit {
opacity: 0.5;
}
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.multiple-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.multiple-questions-disabled-submit {
opacity: 0.5;
}
}

View File

@@ -1,9 +1,11 @@
import { CloseCircleFilled } from '@ant-design/icons'
import { debounce } from '@utils'
import { Input, Select, Tooltip, message } from 'antd'
import _ from 'lodash'
import React, { Component, Fragment } from 'react'
import React, { Component, Fragment, createRef } from 'react'
import { optionLetter } from '../../constant'
import KindEditor from '../kind-editor'
import QuestionEditor from '../question-editor'
import './index.less'
const { TextArea } = Input
const { Option } = Select
@@ -49,7 +51,8 @@ export default class OptionInputBox extends Component {
}
}
kindEditor = KindEditor | null
// kindEditor = KindEditor | null
kindEditor = createRef()
subjectAnswer = '' // 选项内容
/**
@@ -98,10 +101,10 @@ export default class OptionInputBox extends Component {
*/
onChangeOptEditor = (index, type) => () => {
let { optionList } = this.state
this.kindEditor && this.kindEditor.onClear()
if (type === 'submit') {
_.set(optionList, [index, 'label'], !!this.subjectAnswer ? this.subjectAnswer : defalutLabel)
}
this.kindEditor && this.kindEditor.current.onClear()
_.set(optionList, [index, 'isShowEditor'], false)
this.subjectAnswer = ''
this.setState(
@@ -131,7 +134,7 @@ export default class OptionInputBox extends Component {
optionList
},
() => {
this.kindEditor && this.kindEditor.onCashBack()
this.kindEditor && this.kindEditor.current.onCashBack()
}
)
})
@@ -140,7 +143,7 @@ export default class OptionInputBox extends Component {
* 富文本编辑器
* @param {*} e
*/
onChangeEditor = index => e => {
onChangeEditor = e => {
this.subjectAnswer = e
}
@@ -318,7 +321,7 @@ export default class OptionInputBox extends Component {
<div className='option-input-item-delete'>
{listLen > showDeleteLength && (
<Tooltip title='删除选项' onClick={this.onChangeAddOption(index, 'del')}>
<img className='option-input-item-delete-icon' src='' />
<CloseCircleFilled style={{ fontSize: '18px' }} />
</Tooltip>
)}
</div>
@@ -331,15 +334,7 @@ export default class OptionInputBox extends Component {
style={{ marginTop: 19 }}
>
<div className='option-input-editor'>
<KindEditor
ref={ref => {
this.kindEditor = ref
}}
bodyHeight={145}
borderRadius={12}
onChange={this.onChangeEditor(index)}
cashBackText={isShowTip ? '' : item.label}
/>
<QuestionEditor onChange={this.onChangeEditor} ref={this.kindEditor} />
<div className='option-input-editor-btns'>
<Tooltip title='取消后内容将不会更新到选项框内'>
<div
@@ -395,7 +390,7 @@ export default class OptionInputBox extends Component {
defaultActiveFirstOption={false}
value={currentActiveList}
placeholder='请选择'
style={{ minWidth: isMultiple ? '64px' : '68px', marginLeft: 4 }}
style={{ minWidth: isMultiple ? '84px' : '88px', marginLeft: 4 }}
onChange={this.onChangeSelect}
>
{isJudge

View File

@@ -1,77 +1,82 @@
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import React, {
useState, useEffect, forwardRef,
useImperativeHandle,
} from 'react'
import { Editor, Toolbar } from '@wangeditor/editor-for-react'
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
// 当前菜单排序和分组
function MyEditor(props, ref) {
// editor 实例
const [editor, setEditor] = useState(null)
// editor 实例
const [editor, setEditor] = useState(null)
// 编辑器内容
const [html, setHtml] = useState('<p></p>')
// 编辑器内容
const [html, setHtml] = useState('<p></p>')
// 工具栏配置
const toolbarConfig = {
excludeKeys: ['group-image', 'group-video']
}
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...'
}
// 工具栏配置
const toolbarConfig = {
excludeKeys: [
'group-image', 'group-video'
]
const changeValue = html => {
setHtml(html)
props.onChange(html)
}
const onClear = () => setHtml('')
/**
* 回现代码
*/
const onCashBack = () => {
let { cashBackText } = props
if (!!!cashBackText) {
return
}
setHtml(`${cashBackText}`)
// editor.fo;
}
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...',
// 此处注意useImperativeHandle方法的的第一个参数是目标元素的ref引用
useImperativeHandle(ref, () => ({
// onCallback 就是暴露给父组件的方法
onClear,
onCashBack
}))
// 及时销毁 editor ,重要!
useEffect(() => {
return () => {
if (editor == null) return
editor.destroy()
setEditor(null)
}
}, [editor])
const changeValue = (html) => {
setHtml(html)
props.onChange(html)
}
const onClear = () => setHtml('')
// 此处注意useImperativeHandle方法的的第一个参数是目标元素的ref引用
useImperativeHandle(ref, () => ({
// onCallback 就是暴露给父组件的方法
onClear
}));
// 及时销毁 editor ,重要!
useEffect(() => {
return () => {
if (editor == null) return
editor.destroy()
setEditor(null)
}
}, [editor])
return (
<>
<div style={{ border: '1px solid #ccc', zIndex: 100 }}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: '1px solid #ccc' }}
/>
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={setEditor}
onChange={editor => changeValue(editor.getHtml())}
mode="default"
style={{ height: '300px', overflowY: 'scroll' }}
/>
</div>
</>
)
return (
<>
<div style={{ border: '1px solid #ccc', zIndex: 100 }}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode='default'
style={{ borderBottom: '1px solid #ccc' }}
/>
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={setEditor}
onChange={editor => changeValue(editor.getHtml())}
mode='default'
style={{ height: '300px', overflowY: 'scroll' }}
/>
</div>
</>
)
}
export default forwardRef(MyEditor)
export default forwardRef(MyEditor)

View File

@@ -1,234 +1,190 @@
import React, { Fragment } from 'react';
import { Modal, Tooltip } from 'antd';
import { letterList, judgeList } from '../../constant';
import './index.less';
import { Modal, Tooltip } from 'antd'
import React, { Fragment } from 'react'
import { judgeList, letterList } from '../../constant'
import './index.less'
export default function RepeatContentBox(props) {
const { isShowModalBox, repeatInfo, repeatQuestionsType } = props;
// const { isShowModalBox, repeatQuestionsType } = props;
// const repeatInfo = {
// repeatSubjectName:
// 'Chrome如Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字何支持小于12px的字',
// repeatSubjectAnswe:
// 'Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字',
// repeatSetterErp: 'suchunping3',
// repeatSetterName: '苏春萍',
// };
const { isShowModalBox, repeatInfo, repeatQuestionsType } = props
/**
* 确认录入
*/
const onSubmitRepeatModal = e => {
props.handleSubmitRepeatModal && props.handleSubmitRepeatModal()
}
/**
* 取消录入
*/
const onCancelRepeatModal = () => {
props.handleCancelRepeatModal && props.handleCancelRepeatModal()
}
// const repeatInfo = {
// repeatSubjectName:
// 'Chrome如Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字Chrome如何支持小于12px的字何支持小于12px的字',
// repeatOptionList: [
// {
// isCorrect: '',
// optionContent: 'Chrome如何支持小于12px的字Chrome如何支持小于12px的',
// optionType: 1,
// },
// {
// isCorrect: '',
// optionContent: 'Chrome如何支持小于12px的字Chrome如何支持小于12px的',
// optionType: 2,
// },
// {
// isCorrect: 1,
// optionContent: 'Chrome如何支持小于12px的字Chrome如何支持小于12px的',
// optionType: 3,
// },
// ],
// repeatSetterErp: 'suchunping3',
// repeatSetterName: '苏春萍',
// };
/**
* 确认录入
*/
const onSubmitRepeatModal = (e) => {
props.handleSubmitRepeatModal && props.handleSubmitRepeatModal();
};
/**
* 取消录入
*/
const onCancelRepeatModal = () => {
props.handleCancelRepeatModal && props.handleCancelRepeatModal();
};
const renderRepeat = (type, repeatInfo) => {
switch (type) {
case 1:
return renderBriefQuestions(repeatInfo)
case 2:
case 3:
return renderSelectQuestions(type, repeatInfo)
case 4:
return renderJudgeQuestions(repeatInfo)
}
}
const renderRepeat = (type, repeatInfo) => {
switch (type) {
case 1:
return renderBriefQuestions(repeatInfo);
case 2:
case 3:
return renderSelectQuestions(type, repeatInfo);
case 4:
return renderJudgeQuestions(repeatInfo);
}
};
/**
* 展示重复内容-问答型
* @returns
*/
const renderBriefQuestions = repeatInfo => {
return (
<div className='repeat-content-box'>
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>问答题</div>
<div className='repeat-subject-text'>{repeatInfo.repeatSubjectName}</div>
</div>
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>参考答案</div>
<div
className='repeat-subject-text'
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe
}}
></div>
</div>
<div className='repeat-subject-box repeat-subject-info-box'>
<div className='repeat-subject-title'>来自</div>
<Tooltip title={repeatInfo.repeatSetterErp} placement='right' style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
</div>
)
}
/**
* 展示重复内容-问答型
* @returns
*/
const renderBriefQuestions = (repeatInfo) => {
return (
<div className="repeat-content-box">
<div className="repeat-subject-box">
<div className="repeat-subject-title">问答题</div>
<div className="repeat-subject-text">{repeatInfo.repeatSubjectName}</div>
</div>
<div className="repeat-subject-box">
<div className="repeat-subject-title">参考答案</div>
<div
className="repeat-subject-text"
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe,
}}></div>
</div>
<div className="repeat-subject-box repeat-subject-info-box">
<div className="repeat-subject-title">来自</div>
<Tooltip
title={repeatInfo.repeatSetterErp}
placement="right"
style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
</div>
);
};
/**
* 展示重复内容-单选/多选
* @returns
*/
const renderSelectQuestions = (type, repeatInfo) => {
// 过滤获得正确选项
let repeatRightKey = repeatInfo?.repeatOptionList?.filter((item) => item.isCorrect === 1);
return (
<div className="repeat-content-box">
<div className="repeat-subject-box">
<div className="repeat-subject-title">{type === 2 ? '单选题' : '多选题'}</div>
<div className="repeat-subject-text">{repeatInfo.repeatSubjectName}</div>
</div>
{repeatInfo?.repeatOptionList?.length > 0 && (
<div className="repeat-subject-box">
<div className="repeat-subject-title">选项内容</div>
<div className="repeat-subject-list">
{repeatInfo.repeatOptionList.map((item, index) => {
return (
<div
className="repeat-subject-item"
key={`repeat_option_${index}`}>
{/* <div className="repeat-subject-label">
/**
* 展示重复内容-单选/多选
* @returns
*/
const renderSelectQuestions = (type, repeatInfo) => {
// 过滤获得正确选项
let repeatRightKey = repeatInfo?.repeatOptionList?.filter(item => item.isCorrect === 1)
return (
<div className='repeat-content-box'>
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>{type === 2 ? '单选题' : '多选题'}</div>
<div className='repeat-subject-text'>{repeatInfo.repeatSubjectName}</div>
</div>
{repeatInfo?.repeatOptionList?.length > 0 && (
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>选项内容</div>
<div className='repeat-subject-list'>
{repeatInfo.repeatOptionList.map((item, index) => {
return (
<div className='repeat-subject-item' key={`repeat_option_${index}`}>
{/* <div className="repeat-subject-label">
{letterList[item.optionType]}
</div> */}
<div
className="repeat-subject-text"
dangerouslySetInnerHTML={{
__html: item.optionContent,
}}></div>
</div>
);
})}
</div>
</div>
)}
{repeatRightKey?.length > 0 && (
<div className="repeat-subject-box">
<div className="repeat-subject-title">答案</div>
<div className="repeat-subject-list">
{repeatRightKey.map((item, index) => {
return (
<span key={`repeat_answe_${index}`}>
{letterList[item.optionType]}{' '}
</span>
);
})}
</div>
</div>
)}
{!!repeatInfo.repeatSubjectAnswe && (
<div className="repeat-subject-box">
<div className="repeat-subject-title">题目解析</div>
<div
className="repeat-subject-text"
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe,
}}></div>
</div>
)}
<div className="repeat-subject-box repeat-subject-info-box">
<div className="repeat-subject-title">来自</div>
<Tooltip
title={repeatInfo.repeatSetterErp}
placement="right"
style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
<div
className='repeat-subject-text'
dangerouslySetInnerHTML={{
__html: item.optionContent
}}
></div>
</div>
)
})}
</div>
);
};
/**
* 展示重复内容-判断
* @returns
*/
const renderJudgeQuestions = (repeatInfo) => {
return (
<div className="repeat-content-box">
<div className="repeat-subject-box">
<div className="repeat-subject-title">判断题</div>
<div className="repeat-subject-text">{repeatInfo.repeatSubjectName}</div>
</div>
<div className="repeat-subject-box">
<div className="repeat-subject-title">答案</div>
<div className="repeat-subject-list">
{judgeList[repeatInfo.repeatIsCorrect]}
</div>
</div>
{!!repeatInfo.repeatSubjectAnswe && (
<div className="repeat-subject-box">
<div className="repeat-subject-title">题目解析</div>
<div
className="repeat-subject-text"
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe,
}}></div>
</div>
)}
<div className="repeat-subject-box repeat-subject-info-box">
<div className="repeat-subject-title">来自</div>
<Tooltip
title={repeatInfo.repeatSetterErp}
placement="right"
style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
</div>
)}
{repeatRightKey?.length > 0 && (
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>答案</div>
<div className='repeat-subject-list'>
{repeatRightKey.map((item, index) => {
return <span key={`repeat_answe_${index}`}>{letterList[item.optionType]} </span>
})}
</div>
);
};
</div>
)}
{!!repeatInfo.repeatSubjectAnswe && (
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>题目解析</div>
<div
className='repeat-subject-text'
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe
}}
></div>
</div>
)}
<div className='repeat-subject-box repeat-subject-info-box'>
<div className='repeat-subject-title'>来自</div>
<Tooltip title={repeatInfo.repeatSetterErp} placement='right' style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
</div>
)
}
/**
* 展示重复内容-判断
* @returns
*/
const renderJudgeQuestions = repeatInfo => {
return (
<Modal
className="repeat-content-repeat-box"
visible={isShowModalBox}
title={
<Fragment>
<span
style={{
color: 'rgba(60, 110, 238, 0.8)',
fontSize: 50,
marginRight: 10,
}}>
{repeatInfo.repeatRate || '10%'}
</span>
重复率
</Fragment>
}
onOk={onSubmitRepeatModal}
onCancel={onCancelRepeatModal}
okText="确认录入"
cancelText="取消录入">
{renderRepeat(repeatQuestionsType, repeatInfo)}
</Modal>
);
<div className='repeat-content-box'>
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>判断题</div>
<div className='repeat-subject-text'>{repeatInfo.repeatSubjectName}</div>
</div>
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>答案</div>
<div className='repeat-subject-list'>{judgeList[repeatInfo.repeatIsCorrect]}</div>
</div>
{!!repeatInfo.repeatSubjectAnswe && (
<div className='repeat-subject-box'>
<div className='repeat-subject-title'>题目解析</div>
<div
className='repeat-subject-text'
dangerouslySetInnerHTML={{
__html: repeatInfo.repeatSubjectAnswe
}}
></div>
</div>
)}
<div className='repeat-subject-box repeat-subject-info-box'>
<div className='repeat-subject-title'>来自</div>
<Tooltip title={repeatInfo.repeatSetterErp} placement='right' style={{ fontSize: 14 }}>
{repeatInfo.repeatSetterName}
</Tooltip>
</div>
</div>
)
}
return (
<Modal
className='repeat-content-repeat-box'
open={isShowModalBox}
title={
<Fragment>
<span
style={{
color: 'rgba(60, 110, 238, 0.8)',
fontSize: 50,
marginRight: 10
}}
>
{repeatInfo.repeatRate || '10%'}
</span>
重复率
</Fragment>
}
onOk={onSubmitRepeatModal}
onCancel={onCancelRepeatModal}
okText='确认录入'
cancelText='取消录入'
>
{renderRepeat(repeatQuestionsType, repeatInfo)}
</Modal>
)
}

View File

@@ -1,338 +1,365 @@
import React, { Component, Fragment } from 'react';
import { Input, Modal, message, Spin } from 'antd';
import _ from 'lodash';
import req from '@utils/request';
import { debounce } from '@utils';
import KindEditor from '../kind-editor';
import RankLabelBox from '../rank-label-box';
import OptionInputBox from '../option-input-box';
import RepeatContentBox from '../repeat-content-box';
import { apiName } from '../../constant';
import './index.less';
const defalutLabel = '请使用富文本编辑器输入选项内容';
import { debounce } from '@utils'
import req from '@utils/request'
import { Input, Modal, Spin, message } from 'antd'
import React, { Component, Fragment, createRef } from 'react'
import { apiName } from '../../constant'
import OptionInputBox from '../option-input-box'
import RankLabelBox from '../rank-label-box'
import RepeatContentBox from '../repeat-content-box'
import './index.less'
const defalutLabel = '请使用富文本编辑器输入选项内容'
export default class SingleQuestions extends Component {
constructor(props) {
super(props);
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true, // 是否支持提交
};
constructor(props) {
super(props)
this.state = {
subjectName: '', // 题目
isDisabledSubmit: true, //是否禁止输入
isShowModalBox: false, // 是否展示重复率弹框
isSubmit: true // 是否支持提交
}
kindEditor = KindEditor | null;
rankLabelBox = RankLabelBox | null;
optionInputBox = OptionInputBox | null;
}
rankLabelBox = createRef()
currentActive = []; // 选项列表
scoreValue = ''; // 分数
subjectAnalysis = ''; //试题解析
rankId = 1; //职级
subjectAnswer = ''; // 选项内容
optionInputBox = OptionInputBox | null
firstCategoryValue = ''; // 一级分类的值
secondCategoryValue = []; // 二级分类的值
thirdCategoryValue = []; // 三级标签的值
repeatInfo = {}; // 重复率
currentActive = [] // 选项列表
scoreValue = '' // 分数
subjectAnalysis = '' //试题解析
rankId = 1 //职级
subjectAnswer = '' // 选项内容
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = (e) => {
let str = e.target.value.trim();
this.setState(
{
subjectName: str,
},
() => {
this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
}
);
};
firstCategoryValue = '' // 一级分类的值
secondCategoryValue = [] // 二级分类的值
thirdCategoryValue = [] // 三级标签的值
repeatInfo = {} // 重复率
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state;
if (isDisabledSubmit || !isSubmit) {
return;
}
/**
* 输入题目
* @param {*} e
*/
onChangeSubjectName = e => {
let str = e.target.value.trim()
this.setState(
{
subjectName: str
},
() => {
// this.rankLabelBox.getThirdCategoryList();
let isDisabledSubmit = this.checkData()
this.setState({
isSubmit: false,
});
let params = {
subjectName: subjectName,
difficulty: this.rankId,
subjectType: 1,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
categoryIds: this.secondCategoryValue,
labelIds: this.thirdCategoryValue,
optionList: this.currentActive,
};
console.log('单选题录入 ----', params);
req({
method: 'post',
data: params,
url: apiName.addInterviewSubject,
isDisabledSubmit
})
.then((res) => {
this.repeatInfo = {};
if (res.data && res.data.insertStatus) {
this.setState(
{
isSubmit: true,
},
() => {
this.successModalConfirm();
}
);
} else if (!res.data.insertStatus) {
this.repeatInfo = {
repeatDocId: res.data.docId, // 重复题目id
repeatRate: res.data.repeatRate, // 重复率
repeatSubjectName: res.data.subjectName, // 重复题目
repeatOptionList: res.data.optionList, // 重复列表项
repeatSetterErp: res.data.subjectSetterErp, // 出题人erp
repeatSetterName: res.data.subjectSetterName, // 出题人姓名
};
this.setState({
isShowModalBox: true,
isSubmit: true,
});
}
})
.catch((err) => {
this.setState({
isSubmit: true,
});
console.log(err);
});
});
}
)
}
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state;
let list = this.currentActive.filter((item) => item.optionContent === defalutLabel);
let isDisabledSubmit = false;
if (
!!!subjectName ||
list.length > 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true;
}
return isDisabledSubmit;
};
/**
* 取消
*/
onCancel = () => {
this.currentActive = []; // 选项列表
this.scoreValue = ''; // 分数
this.subjectAnalysis = ''; //试题解析
this.rankId = 1;
this.subjectAnswer = ''; // 选项内容
this.firstCategoryValue = ''; // 一级分类的值
this.secondCategoryValue = []; // 二级分类的值
this.thirdCategoryValue = []; // 三级标签的值
this.kindEditor && this.kindEditor.onClear();
this.rankLabelBox.initRankLabel();
this.optionInputBox.handleClearOption();
this.repeatInfo = {};
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true, // 是否支持提交
});
};
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId,
};
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject,
})
.then((res) => {
if (res.data) {
this.successModalConfirm();
} else {
message.info('请再次确认');
}
})
.catch((err) => {
console.log(err);
message.error('请再次确认');
});
},
300,
true
);
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {};
this.setState({
isShowModalBox: false,
});
};
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16,
}}>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm',
});
};
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel();
};
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/cms-supplier/question-bank';
};
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue; // 一级分类的值
this.secondCategoryValue = secondCategoryValue; // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue; // 三级标签的值
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = (list) => {
this.rankId = list[0];
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive;
this.scoreValue = scoreValue;
this.subjectAnalysis = subjectAnalysis;
let isDisabledSubmit = this.checkData();
this.setState({
isDisabledSubmit,
});
};
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state;
const { questionsType } = this.props;
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className="single-questions-container">
<div className="single-questions-title">题目名称</div>
<Input
placeholder="输入题目"
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={(e) => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
key="single-option-input"
ref={(ref) => {
this.optionInputBox = ref;
}}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={(ref) => {
this.rankLabelBox = ref;
}}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className="single-questions-btns-container">
<div className="single-questions-btn" onClick={this.onCancel}>
清空
</div>
<div
className={`single-questions-btn single-questions-submit ${isDisabledSubmit && 'single-questions-disabled-submit'
}`}
onClick={this.onSubmit}>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
);
/**
* 一次确认录入
*/
onSubmit = debounce(() => {
const { subjectName, isDisabledSubmit, isSubmit } = this.state
if (isDisabledSubmit || !isSubmit) {
return
}
}
if (!isSubmit) {
return
}
if (!!!subjectName) {
message.warning('请输入题目名称')
return
}
if (!this.currentActive.length) {
message.warning('请录入答案')
return
}
if (!!!this.firstCategoryValue) {
message.warning('请选择一级分类')
return
}
if (this.secondCategoryValue.length <= 0) {
message.warning('请选择二级分类')
return
}
if (this.thirdCategoryValue.length <= 0) {
message.warning('请选择三级标签')
return
}
this.setState({
isSubmit: false
})
let params = {
subjectName: subjectName,
subjectDifficult: this.rankId,
subjectType: 1,
subjectScore: this.scoreValue,
subjectParse: this.subjectAnalysis,
categoryIds: this.secondCategoryValue.filter(item => item.active).map(t => t.id),
labelIds: this.thirdCategoryValue.filter(item => item.active).map(t => t.id),
optionList: this.currentActive
}
req({
method: 'post',
data: params,
url: apiName.add
})
.then(res => {
// this.repeatInfo = {}
// if (res.data && res.data.insertStatus) {
// this.setState(
// {
// isSubmit: true
// },
// () => {
// this.successModalConfirm()
// }
// )
// } else if (!res.data.insertStatus) {
// this.repeatInfo = {
// repeatDocId: res.data.docId, // 重复题目id
// repeatRate: res.data.repeatRate, // 重复率
// repeatSubjectName: res.data.subjectName, // 重复题目
// repeatOptionList: res.data.optionList, // 重复列表项
// repeatSetterErp: res.data.subjectSetterErp, // 出题人erp
// repeatSetterName: res.data.subjectSetterName // 出题人姓名
// }
// this.setState({
// isShowModalBox: true,
// isSubmit: true
// })
// }
this.setState(
{
isSubmit: true
},
() => {
this.successModalConfirm()
}
)
})
.catch(err => {
this.setState({
isSubmit: true
})
console.log(err)
})
})
/**
* 校验是否支持点击按钮
* @returns
*/
checkData = () => {
const { subjectName } = this.state
let list = this.currentActive.filter(item => item.optionContent === defalutLabel)
let isDisabledSubmit = false
if (
!!!subjectName ||
list.length > 0 ||
!!!this.firstCategoryValue ||
this.secondCategoryValue.length <= 0 ||
this.thirdCategoryValue.length <= 0 ||
!!!this.scoreValue
) {
isDisabledSubmit = true
}
return isDisabledSubmit
}
/**
* 取消
*/
onCancel = () => {
this.currentActive = [] // 选项列表
this.scoreValue = '' // 分数
this.subjectAnalysis = '' //试题解析
this.rankId = 1
this.subjectAnswer = '' // 选项内容
this.firstCategoryValue = '' // 一级分类的值
this.secondCategoryValue = [] // 二级分类的值
this.thirdCategoryValue = [] // 三级标签的值
this.rankLabelBox.current.initRankLabel()
this.optionInputBox.handleClearOption()
this.repeatInfo = {}
this.setState({
subjectName: '',
isShowModalBox: false,
isSubmit: true // 是否支持提交
})
}
/**
* 重复率弹框-确认录入
*/
onSubmitRepeatModal = debounce(
() => {
let params = {
docId: this.repeatInfo.repeatDocId
}
req({
method: 'post',
data: params,
url: apiName.addRepeatInterviewSubject
})
.then(res => {
if (res.data) {
this.successModalConfirm()
} else {
message.info('请再次确认')
}
})
.catch(err => {
console.log(err)
message.error('请再次确认')
})
},
300,
true
)
/**
* 重复率弹框-取消录入
*/
onCancelRepeatModal = () => {
this.repeatInfo = {}
this.setState({
isShowModalBox: false
})
}
/**
* 录入成功的弹框
*/
successModalConfirm = () => {
Modal.confirm({
title: (
<div
style={{
textAlign: 'center',
color: 'rgba(60, 110, 238, 1)',
fontSize: 16
}}
>
录入成功贡献榜火力值 + 1
</div>
),
closable: false,
maskClosable: false,
icon: ' ',
onOk: this.onAgainSuccessModal,
onCancel: this.onGoHomeSuccessModal,
okText: '再录一题',
cancelText: '去首页',
className: 'questions-success-modal-confirm'
})
}
/**
* 录入成功弹框-再来一题
*/
onAgainSuccessModal = () => {
this.onCancel()
}
/**
* 录入成功弹框-去首页
*/
onGoHomeSuccessModal = () => {
window.location.href = '/question-bank'
}
/**
* 分类选择
* @param {*} e
*/
onChangeRankLabel = (firstCategoryValue, secondCategoryValue, thirdCategoryValue) => {
this.firstCategoryValue = firstCategoryValue // 一级分类的值
this.secondCategoryValue = secondCategoryValue // 二级分类的值
this.thirdCategoryValue = thirdCategoryValue // 三级标签的值
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 职级选择
* @param {*} list
*/
handleChangeRank = list => {
this.rankId = list[0]
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
/**
* 选项操作
* @param {*} currentActive 选项列表
* @param {*} scoreValue 分值
* @param {*} subjectAnalysis 解析
*/
handleChangeOption = (currentActive, scoreValue, subjectAnalysis) => {
this.currentActive = currentActive
this.scoreValue = scoreValue
this.subjectAnalysis = subjectAnalysis
let isDisabledSubmit = this.checkData()
this.setState({
isDisabledSubmit
})
}
render() {
const { subjectName, isDisabledSubmit, isSubmit, isShowModalBox } = this.state
const { questionsType } = this.props
return (
<Spin spinning={!isSubmit}>
<Fragment>
<div className='single-questions-container'>
<div className='single-questions-title'>题目名称</div>
<Input
placeholder='输入题目'
style={{ height: 48, width: '100%' }}
value={subjectName}
maxLength={64}
onChange={e => this.onChangeSubjectName(e)}
/>
</div>
<OptionInputBox
key='single-option-input'
ref={ref => {
this.optionInputBox = ref
}}
handleChangeOption={this.handleChangeOption}
/>
<RankLabelBox
ref={this.rankLabelBox}
subjectName={subjectName}
onChangeRankLabel={this.onChangeRankLabel}
handleChangeRank={this.handleChangeRank}
/>
<div className='single-questions-btns-container'>
<div className='single-questions-btn' onClick={this.onCancel}>
清空
</div>
<div
className={`single-questions-btn single-questions-submit ${
isDisabledSubmit && 'single-questions-disabled-submit'
}`}
onClick={this.onSubmit}
>
提交
</div>
</div>
<RepeatContentBox
isShowModalBox={isShowModalBox}
repeatQuestionsType={questionsType}
repeatInfo={this.repeatInfo}
handleSubmitRepeatModal={this.onSubmitRepeatModal}
handleCancelRepeatModal={this.onCancelRepeatModal}
/>
</Fragment>
</Spin>
)
}
}

View File

@@ -1,53 +1,53 @@
.single-questions-container {
width: 1000px;
// width: 1000px;
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.single-questions-title {
display: flex;
align-items: center;
padding: 0 24px;
padding-top: 36px;
// label名字title
.single-questions-title {
display: flex;
align-items: center;
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
justify-content: flex-end;
width: 140px;
line-height: 40px;
font-size: 16px;
color: rgba(51, 51, 51, 1);
&:before {
display: inline-block;
margin-right: 4px;
margin-top: 1px;
color: #f5222d;
font-size: 16px;
content: '*';
}
}
}
.single-questions-btns-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.single-questions-btn {
display: flex;
justify-content: flex-end;
align-items: center;
margin: 20px auto;
width: 952px;
.single-questions-btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.single-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.single-questions-disabled-submit {
opacity: 0.5;
}
justify-content: center;
width: 150px;
height: 40px;
font-size: 16px;
cursor: pointer;
border: 1px solid #d9d9d9;
border-radius: 10px;
}
.single-questions-submit {
margin-left: 40px;
background-color: #4390f7;
color: #fff;
border: 1px solid #4390f7;
}
.single-questions-disabled-submit {
opacity: 0.5;
}
}

View File

@@ -1,27 +1,29 @@
import React from 'react';
import { Affix } from 'antd';
import { RightOutlined } from '@ant-design/icons';
import './index.less';
import { RightOutlined } from '@ant-design/icons'
import { Affix } from 'antd'
import React from 'react'
import './index.less'
export default function UploadLeftLayout(props) {
return (
<Affix offsetTop={150}>
<div className="upload-left-layout">
{props.layoutList.map((item, index) => {
return (
<div
className={`upload-left-layout-item ${item.active ? 'upload-left-layout-item-active' : ''
}`}
onClick={() => {
props.onChange(index);
}}
key={`upload_left_layout_${item.id}`}>
{item.title}
<RightOutlined style={{ marginLeft: 54 }} />
</div>
);
})}
return (
<Affix offsetTop={150}>
<div className='upload-left-layout'>
{props.layoutList.map((item, index) => {
return (
<div
className={`upload-left-layout-item ${
item.active ? 'upload-left-layout-item-active' : ''
}`}
onClick={() => {
props.onChange(index)
}}
key={`upload_left_layout_${item.id}`}
>
{item.title}
<RightOutlined style={{ marginLeft: 54 }} />
</div>
</Affix>
);
)
})}
</div>
</Affix>
)
}

View File

@@ -18,6 +18,7 @@
background: rgba(255, 255, 255, 1);
border: 1px solid rgba(208, 212, 222, 1);
border-radius: 12px;
cursor: pointer;
}
.upload-left-layout-item-active {
color: rgba(60, 110, 238, 1);

View File

@@ -2,143 +2,142 @@
* API名称
*/
export const apiName = {
/**
* 获取一级
*/
queryPrimaryCategory: '/category/queryPrimaryCategory',
/**
* 获取一级
*/
queryPrimaryCategory: '/category/queryPrimaryCategory',
// 查询二级分类
queryCategoryByPrimary: 'category/queryCategoryByPrimary',
// 查询二级分类
queryCategoryByPrimary: 'category/queryCategoryByPrimary',
// 根据一级分类查询标签
queryLabelByCategoryId: '/label/queryLabelByCategoryId',
// 根据一级分类查询标签
queryLabelByCategoryId: '/label/queryLabelByCategoryId',
// 新增简答
add: '/add',
/**
* 新增题目
*/
addInterviewSubject: '/admin/question/subject/add',
/**
* 新增重复题目
*/
addRepeatInterviewSubject: '/admin/question/subject/addRepeatSubject',
};
// 新增简答
add: '/add',
/**
* 新增题目
*/
addInterviewSubject: '/admin/question/subject/add',
/**
* 新增重复题目
*/
addRepeatInterviewSubject: '/admin/question/subject/addRepeatSubject'
}
/**
* 模块类型
*/
export const ModuleType = {
default: 'default',
second: 'second',
third: 'third',
};
default: 'default',
second: 'second',
third: 'third'
}
/**
* 导入职级对应的星
*/
export const starList = [
{
categoryId: 1,
categoryName: '初级',
active: true,
},
{
categoryId: 2,
categoryName: '中级',
},
{
categoryId: 3,
categoryName: '高级',
},
{
categoryId: 4,
categoryName: '资深',
},
{
categoryId: 5,
categoryName: '专家',
},
];
{
categoryId: 1,
categoryName: '初级',
active: true
},
{
categoryId: 2,
categoryName: '中级'
},
{
categoryId: 3,
categoryName: '高级'
},
{
categoryId: 4,
categoryName: '资深'
},
{
categoryId: 5,
categoryName: '专家'
}
]
/**
* 模块类型
*/
export const uploadLayout = [
{
id: 1,
title: '简答题',
active: true,
},
// {
// id: 2,
// title: '单选题',
// active: false,
// },
// {
// id: 3,
// title: '多选题',
// active: false,
// },
// {
// id: 4,
// title: '判断题',
// active: false,
// },
];
{
id: 1,
title: '简答题',
active: true
},
{
id: 2,
title: '单选题',
active: false
},
{
id: 3,
title: '多选题',
active: false
},
{
id: 4,
title: '判断题',
active: false
}
]
/**
* 数组索引对应字母
*/
export const optionLetter = {
0: {
label: 'A',
value: 1,
},
1: {
label: 'B',
value: 2,
},
2: {
label: 'C',
value: 3,
},
3: {
label: 'D',
value: 4,
},
4: {
label: 'E',
value: 5,
},
5: {
label: 'F',
value: 6,
},
6: {
label: 'G',
value: 7,
},
};
0: {
label: 'A',
value: 1
},
1: {
label: 'B',
value: 2
},
2: {
label: 'C',
value: 3
},
3: {
label: 'D',
value: 4
},
4: {
label: 'E',
value: 5
},
5: {
label: 'F',
value: 6
},
6: {
label: 'G',
value: 7
}
}
/**
* 字母id对应字母
*/
export const letterList = {
1: 'A',
2: 'B',
3: 'C',
4: 'D',
5: 'E',
6: 'F',
7: 'G',
};
1: 'A',
2: 'B',
3: 'C',
4: 'D',
5: 'E',
6: 'F',
7: 'G'
}
/**
* 正确/错误
*/
export const judgeList = {
0: '错误',
1: '正确',
};
0: '错误',
1: '正确'
}

View File

@@ -2,9 +2,9 @@
position: relative;
margin: 0 auto;
width: 1439px;
overflow-y: auto;
// overflow-y: auto;
border-radius: 5px;
height: calc(100vh - 100px);
// height: calc(100vh - 100px);
.ant-card-head {
position: sticky;
top: 0;
@@ -14,7 +14,4 @@
z-index: 10;
background-color: #fff;
}
.ant-card-body {
display: flex;
}
}

View File

@@ -2,16 +2,9 @@ import req from '@utils/request'
import { Card, message } from 'antd'
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import BatchleBox from './pages/batch-box'
import SingleBox from './pages/single-box'
import './index.less'
const tabList = [
{
key: 'singleBox',
tab: '单题录入'
}
]
const UploadQuestions = () => {
const [currentKey, setCurrentKey] = useState('singleBox')
const navigate = useNavigate()
@@ -41,23 +34,18 @@ const UploadQuestions = () => {
})
}, [])
const contentList = {
singleBox: <SingleBox />,
batchBox: <BatchleBox />
}
return (
<div className='upload-questions-box'>
<Card
style={{ width: '100%' }}
tabList={tabList}
title='题目录入'
bordered={false}
activeTabKey={currentKey}
onTabChange={key => {
setCurrentKey(key)
}}
>
{contentList[currentKey]}
<SingleBox />
</Card>
</div>
)

View File

@@ -1,12 +0,0 @@
import React, { Component } from 'react';
import './index.less';
export default class BatchBox extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return <div className="upload-batch-questions-box">批量上传</div>;
}
}

View File

@@ -1,7 +0,0 @@
.upload-batch-questions-box {
margin: 0 auto;
width: 1000px;
display: flex;
border-radius: 5px;
border: 1px solid #3d3d3d;
}

View File

@@ -1,9 +1,10 @@
import { RightOutlined } from '@ant-design/icons'
import { Button, Space } from 'antd'
import React, { Component } from 'react'
import BriefQuestions from '../../components/brief-questions'
import JudgeQuestions from '../../components/judge-questions'
import MultipleQuestions from '../../components/multiple-questions'
import SingleQuestions from '../../components/single-questions'
import UploadLeftLayout from '../../components/upload-left-layout'
import { uploadLayout } from '../../constant'
import './index.less'
@@ -40,16 +41,6 @@ export default class SingleBox extends Component {
})
}
render() {
const { currentIndex, layoutList } = this.state
return (
<div style={{ display: 'flex' }}>
<UploadLeftLayout layoutList={layoutList} onChange={this.onChangeQuestionsType} />
<div className='upload-questions-modular'>{this.changeReander(currentIndex)}</div>
</div>
)
}
changeReander = i => {
switch (i) {
case 0:
@@ -62,4 +53,32 @@ export default class SingleBox extends Component {
return <JudgeQuestions questionsType={i + 1} key={`question_${i}`} />
}
}
render() {
const { currentIndex, layoutList } = this.state
return (
<div style={{ display: 'flex' }}>
{/* <UploadLeftLayout layoutList={layoutList} onChange={this.onChangeQuestionsType} /> */}
<div style={{ flex: 1 }}>
<Space direction='vertical'>
{layoutList.map((item, index) => {
return (
<Button
key={item.id}
type={item.active ? 'primary' : 'default'}
ghost={item.active}
block
onClick={() => this.onChangeQuestionsType(index)}
>
{item.title}
<RightOutlined style={{ marginLeft: 54 }} />
</Button>
)
})}
</Space>
</div>
<div className='upload-questions-modular'>{this.changeReander(currentIndex)}</div>
</div>
)
}
}

View File

@@ -1,29 +1,33 @@
.ant-modal-body {
padding: 12px 24px 0;
padding: 12px 24px 0;
}
.upload-questions-modular {
display: flex;
flex-direction: column;
margin: 0 auto;
margin-left: 35px;
background: rgba(249, 250, 252, 1);
border: 2px solid rgba(240, 240, 240, 1);
border-radius: 12px;
// display: flex;
// flex-direction: column;
margin: 0 auto;
margin-left: 35px;
background: rgba(249, 250, 252, 1);
border: 2px solid rgba(240, 240, 240, 1);
border-radius: 12px;
height: calc(100vh - 220px);
overflow-y: scroll;
width: 100%;
// flex: 1 0 auto;
}
.questions-success-modal-confirm {
width: 300px;
// 录入成功弹框
.ant-modal-confirm-btns {
display: flex;
justify-content: center;
width: 100%;
.ant-btn {
width: 100px;
border-radius: 18px;
}
.ant-btn-primary {
background: rgba(60, 110, 238, 1);
margin-left: 60px;
}
width: 300px;
// 录入成功弹框
.ant-modal-confirm-btns {
display: flex;
justify-content: center;
width: 100%;
.ant-btn {
width: 100px;
border-radius: 18px;
}
.ant-btn-primary {
background: rgba(60, 110, 238, 1);
margin-left: 60px;
}
}
}

View File

@@ -1,8 +1,9 @@
import Head from '@/imgs/head.jpg'
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'
import { saveUserInfo } from '@features/userInfoSlice.ts'
import req from '@utils/request'
import { Button, Card, Col, Form, Input, Radio, Row, Upload, message } from 'antd'
import { memo, useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import './index.less'
@@ -54,6 +55,8 @@ const UserInfo = () => {
const userInfoStorage = localStorage.getItem('userInfo')
const { loginId = '', tokenValue = '' } = userInfoStorage ? JSON.parse(userInfoStorage) : {}
const dispatch = useDispatch()
const [form] = Form.useForm()
const [editFlag, setEditFlag] = useState(false)
const [loading, setLoading] = useState(false)
@@ -73,6 +76,7 @@ const UserInfo = () => {
).then(res => {
if (res?.success && res?.data) {
setUserInfo(res.data)
setAvatar(res.data.avatar || '')
form.setFieldsValue(res.data)
}
})
@@ -87,7 +91,6 @@ const UserInfo = () => {
const onFinish = () => {
setLoading(true)
const values = form.getFieldsValue()
// return console.log(values)
if (!Object.values(values).filter(Boolean).length && !avatar) {
setLoading(false)
return
@@ -108,10 +111,13 @@ const UserInfo = () => {
'/auth'
)
.then(res => {
dispatch(saveUserInfo(params))
setUserInfo(params)
setAvatar(params.avatar || '')
if (res.success) {
message.success('更新成功')
setTimeout(() => {
getUserInfo()
// getUserInfo()
setLoading(false)
setEditFlag(false)
}, 500)
@@ -126,7 +132,6 @@ const UserInfo = () => {
}
const handleChange = ({ file }) => {
// console.log(info)
if (file.status === 'done' && file.response.success && file.response.data) {
setAvatar(file.response.data)
}
@@ -174,7 +179,12 @@ const UserInfo = () => {
</Form.Item>
) : (
<Form.Item label='用户头像'>
<img className='user-info_header' src={userInfo.avatar || Head} />
{userInfo.avatar ? (
<img className='user-info_header' src={userInfo.avatar} />
) : (
<div />
)}
{/* <img className='user-info_header' src={userInfo.avatar || Head} /> */}
</Form.Item>
)}
</Col>

View File

@@ -25,7 +25,9 @@ export default ({ mode }) => {
'@utils': path.resolve(__dirname, 'src/utils'),
'@components': path.resolve(__dirname, 'src/components'),
'@imgs': path.resolve(__dirname, 'src/imgs'),
'@constants': path.resolve(__dirname, 'src/constants')
'@constants': path.resolve(__dirname, 'src/constants'),
'@store': path.resolve(__dirname, 'src/store'),
'@features': path.resolve(__dirname, 'src/store/features')
}
},
plugins: [react()],