diff --git a/.env.development b/.env.development index f333e5e..962e72f 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ -VITE_API_HOST=http://117.72.14.166:3010 +VITE_API_HOST=http://117.72.10.84:5000 +# VITE_API_HOST=http://117.72.14.166:3010 VITE_IMG_HOST=http://117.72.14.166:9000 diff --git a/index.html b/index.html index de19e4e..8220868 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Component: lazy(() => import('@views/user-info')) + }, + { + path: 'search-detail', + Component: lazy(() => import('@views/search-details')) + }, + { + path: 'search-question', + Component: lazy(() => import('@views/search-question')) } ] } diff --git a/src/utils/request.ts b/src/utils/request.ts index a2372eb..130b6eb 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -34,7 +34,7 @@ export default function request(config, url) { res => { let { code } = res.data if (code === 500) { - message.error(res.data.message) + // message.error(res.data.message) } if (code === 401) { window.location.replace('/login') diff --git a/src/views/header/index.tsx b/src/views/header/index.tsx index d30591f..2fd8371 100644 --- a/src/views/header/index.tsx +++ b/src/views/header/index.tsx @@ -83,14 +83,15 @@ const Header = () => { } } + const handleJump = value => { + navigate('/search-detail?t=' + value) + } + return (
-
(window.location.href = '/question-bank')} - > +
@@ -101,7 +102,11 @@ const Header = () => {
console.log(value)} + onSearch={value => { + if (value) { + handleJump(value) + } + }} style={{ width: 300, borderRadius: '10px' }} />
diff --git a/src/views/question-bank/components/contribution-list/index.jsx b/src/views/question-bank/components/contribution-list/index.jsx index ef28431..1e513ce 100644 --- a/src/views/question-bank/components/contribution-list/index.jsx +++ b/src/views/question-bank/components/contribution-list/index.jsx @@ -1,86 +1,96 @@ -import React, { Fragment, Component } from 'react'; -// import { withRouter } from 'react-router-dom'; -import req from '@utils/request'; -import RankingBox from '../ranking-box'; -import { apiName, RankingType } from '../../constant'; -import { mockRankingModuleList } from '../../mock'; +import req from '@utils/request' +import { message } from 'antd' +import React, { Fragment, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { RankingType, apiName } from '../../constant' +import { mockRankingModuleList } from '../../mock' +import RankingBox from '../ranking-box' -class ContributionList extends Component { - constructor(props) { - super(props); - this.state = { - contributionList: mockRankingModuleList[1].rankingList || [], - contributeType: 0, - isLoading: false, - }; +const ContributionList = props => { + const [contributionList, setContributionList] = useState(mockRankingModuleList[1].rankingList) + const [loading, setLoading] = useState(false) + const [contributeType, setContributeType] = useState(0) + const navigate = useNavigate() + + /** + * 获得贡献榜 + */ + const getContributeList = () => { + req({ + method: 'post', + data: {}, + url: apiName.getContributeList + }) + .then(res => { + if (res.data && res.data.length > 0) { + this.setState({ + contributionList: res.data, + isLoading: false + }) + } else { + this.setState({ + contributionList: [], + isLoading: false + }) + } + }) + .catch(err => console.log(err)) + } + + /** + * 切换排行榜 + * @param {*} index + * @returns + */ + const onChangeRanking = index => { + setContributeType(index) + } + + /** + * 去录题 + */ + const onChangeJump = () => { + const userInfoStorage = localStorage.getItem('userInfo') + if (!userInfoStorage) { + return message.info('请登录') } + const { loginId } = JSON.parse(userInfoStorage) + req( + { + method: 'get', + url: '/permission/getPermission', + params: { + userName: loginId + } + }, + '/auth' + ).then(res => { + if (res.success && res.data) { + if (res.data.includes('subject:add')) { + navigate('/upload-questions') + } else { + message.info('敬请期待') + } + } else { + message.info('敬请期待') + } + }) + } - componentDidMount() { - // this.getContributeList(); - } - - /** - * 获得贡献榜 - */ - getContributeList() { - req({ - method: 'post', - data: {}, - url: apiName.getContributeList, - }) - .then((res) => { - if (res.data && res.data.length > 0) { - this.setState({ - contributionList: res.data, - isLoading: false, - }); - } else { - this.setState({ - contributionList: [], - isLoading: false, - }); - } - }) - .catch((err) => console.log(err)); - } - - /** - * 切换排行榜 - * @param {*} index - * @returns - */ - onChangeRanking = (index) => { - console.log(index, 'contribute index') - - this.setState({ - contributeType: index, - }); - }; - - /** - * 去录题 - */ - onChangeJump = () => { - this.props.history.push('/upload-questions'); - }; - - render() { - const { contributionList, isLoading, contributeType } = this.state; - return ( - - {contributionList?.length > 0 && ( - - )} - - ); - } + return ( + + {contributionList?.length > 0 && ( + + )} + + ) } -export default ContributionList; +export default ContributionList diff --git a/src/views/question-bank/components/ranking-box/index.jsx b/src/views/question-bank/components/ranking-box/index.jsx index a255149..119647b 100644 --- a/src/views/question-bank/components/ranking-box/index.jsx +++ b/src/views/question-bank/components/ranking-box/index.jsx @@ -22,8 +22,12 @@ export default function RankingBox(props) { props.onHandleRanking && props.onHandleRanking(index) }) const onJump = debounce(() => { + if (props.onHandleJump) { + props.onHandleJump() + } else { + message.info('敬请期待') + } // props.onHandleJump && props.onHandleJump() - message.info('敬请期待') }) const tabList = [ { diff --git a/src/views/question-bank/index.tsx b/src/views/question-bank/index.tsx index fea0471..41c8acc 100644 --- a/src/views/question-bank/index.tsx +++ b/src/views/question-bank/index.tsx @@ -14,7 +14,6 @@ const QuestionBank = () => { const [difficulty, setDiffculty] = useState('') //困难度(全部) const [total, setTotal] = useState(0) // 总条数 const [pageIndex, setPageIndex] = useState(0) - const [secondCategoryId, setSecondCategoryId] = useState('') const [selectedValue, setSelectedValue] = useState({ primaryId: '', // 大类ID diff --git a/src/views/search-details/index.jsx b/src/views/search-details/index.jsx new file mode 100644 index 0000000..0801755 --- /dev/null +++ b/src/views/search-details/index.jsx @@ -0,0 +1,205 @@ +import { ExclamationCircleOutlined } from '@ant-design/icons' +import { queryParse } from '@utils' +import { Card, Input, Pagination, Skeleton, message } from 'antd' +import React, { useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import './index.less' + +const { Search } = Input + +const mockList = [ + { + id: 100, + subjectName: 'Redis支持哪几种数据类型?', + subjectDifficult: 1, + subjectType: 4, + subjectScore: 1, + subjectParse: '解析什么', + subjectAnswer: + '


  1. String(字符串)
  2. List(列表)
  3. Hash(字典)
  4. Set(集合)
  5. Sorted Set(有序集合)


String


String是简单的 key-value 键值对,value 不仅可以是 String,也可以是数字。String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。


List


Redis列表是简单的字符串列表,可以类比到C++中的std::list,简单的说就是一个链表或者说是一个队列。可以从头部或尾部向Redis列表添加元素。列表的最大长度为2^32 - 1,也即每个列表支持超过40亿个元素。


Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。


Hash


Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap。


Set


set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。


Sorted Set


Redis有序集合类似Redis集合,不同的是增加了一个功能,即集合是有序的。一个有序集合的每个成员带有分数,用于进行排序。


Redis有序集合添加、删除和测试的时间复杂度均为O(1)(固定时间,无论里面包含的元素集合的数量)。列表的最大长度为2^32- 1元素(4294967295,超过40亿每个元素的集合)。


Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

', + labelName: ['Redis'] + }, + { + id: 101, + subjectName: 'Redis的高级数据类型有什么?', + subjectDifficult: 2, + subjectType: 4, + subjectScore: 1, + subjectParse: '解析什么', + subjectAnswer: + '


bitmap:bitmap是一种位数据类型,常常用于统计,大家比较知名的就是布隆过滤器。也可以统计一些大数据量的东西,比如每天有多少优惠券被使用。


hyperloglog:用于基数统计,基数是数据集去重后元素个数,运用了LogLog的算法。{1,3,5,7,5,7,8}   基数集:{1,3,5,7,8}  基数:5


geo:应用于地理位置计算,主要是经纬度的计算,常见的就是附近的人,可以以当前人的坐标获取周围附近的成员,可以计算经纬度,计算地理位置

', + labelName: ['Redis'] + }, + { + id: 102, + subjectName: 'Redis的优点有什么?', + subjectDifficult: 1, + subjectType: 4, + subjectScore: 1, + subjectParse: '解析什么', + subjectAnswer: + '


(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)


(2) 支持丰富数据类型,支持string,list,set,Zset,hash等


(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行


(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

', + labelName: ['Redis'] + }, + { + id: 103, + subjectName: 'Redis相比Memcached有哪些优势?', + subjectDifficult: 1, + subjectType: 4, + subjectScore: 1, + subjectParse: '解析什么', + subjectAnswer: + '


(1) Memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型


(2) Redis的速度比Memcached快很多


(3) Redis可以持久化其数据

', + labelName: ['Redis'] + }, + { + id: 106, + subjectName: 'redis过期策略都有哪些?', + subjectDifficult: 1, + subjectType: 4, + subjectScore: 1, + subjectParse: '解析什么', + subjectAnswer: + '


noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错。


allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key。


allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。


volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key。


volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。


volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

', + labelName: ['Redis'] + } +] + +const defaultValue = queryParse(location.search).t + +const SearchDetails = () => { + const navigate = useNavigate() + const [isShowSkeleton, setIsShowSkeleton] = useState(true) + const [questionList, setQuestionList] = useState(mockList) + const [total, setTotal] = useState(0) + const [pageIndex, setPageIndex] = useState(0) + const [searchValue, setSearchValue] = useState(defaultValue) + + const searchSubject = () => { + setIsShowSkeleton(false) + } + + const handleJump = id => { + navigate('/brush-question/' + id) + } + + const onChangePagination = () => {} + + useEffect(() => { + searchSubject() + }, [searchValue]) + + return ( +
+
+
+ { + if (value) { + // this.state.searchText = value + // this.pageIndex = 1 + // this.searchSubject() + } else { + message.info('搜索词不能为空') + } + }} + enterButton + defaultValue={defaultValue} + /> +
+
+ +
+
+ { + // this.onTabChange(key, 'key') + // }} + > +
+ {questionList?.length > 0 ? ( + questionList.map((item, index) => { + if (item.subjectAnswer) { + return ( +
+
handleJump(item.id)} + dangerouslySetInnerHTML={{ + __html: item.subjectName + }} + >
+
handleJump(item.id)} + dangerouslySetInnerHTML={{ + __html: item.subjectAnswer + '...' + }} + >
+
+ ) + } else { + return ( +
+
handleJump(item.id)} + dangerouslySetInnerHTML={{ + __html: item.subjectName + }} + >
+
handleJump(item.id)} + dangerouslySetInnerHTML={{ + __html: item.subjectAnswer + }} + >
+
+ ) + } + }) + ) : ( +
+ +
很抱歉,没有找到与你搜索相关的内容
+
+ )} +
+
+
+
+ {total > 20 && ( + + )} +
+
+
+
+ ) +} + +export default SearchDetails diff --git a/src/views/search-details/index.less b/src/views/search-details/index.less new file mode 100644 index 0000000..ca6da7e --- /dev/null +++ b/src/views/search-details/index.less @@ -0,0 +1,46 @@ +.search-details-box { + width: 1439px; + margin: 0 auto; + background-color: #fff; + padding: 20px 50px; + border-radius: 5px; + overflow: auto; + .ant-card-body { + padding: 0 24px; + } + .search-details-box-search { + background-color: #f5f5f5; + padding: 20px 40px; + border-radius: 3px 3px 0 0; + font-size: 14px; + } + .search-details-box-content { + .search-details-box-content-main-item { + margin-top: 10px; + padding: 10px 0; + font-weight: 400; + border-bottom: 1px solid #e5e5e5; + .search-details-box-content-main-item-question { + color: #3c6eee; + cursor: pointer; + font-size: 18px; + margin-bottom: 5px; + } + .search-details-box-content-main-item-answer { + font-size: 13px; + cursor: pointer; + letter-spacing: 0; + overflow: hidden; + display: -webkit-box; + text-overflow: ellipsis; + -webkit-line-clamp: 2; /*要显示的行数*/ + -webkit-box-orient: vertical; + } + } + .search-null { + display: block; + text-align: center; + margin: 50px 0; + } + } +} diff --git a/src/views/upload-questions/components/option-input-box/index.jsx b/src/views/upload-questions/components/option-input-box/index.jsx index de96709..e5873cb 100644 --- a/src/views/upload-questions/components/option-input-box/index.jsx +++ b/src/views/upload-questions/components/option-input-box/index.jsx @@ -1,438 +1,422 @@ -import React, { Component, Fragment } from 'react'; -import { Input, message, Tooltip, Select } from 'antd'; -import _ from 'lodash'; -import { debounce } from '@utils'; -import { optionLetter } from '../../constant'; -import KindEditor from '../kind-editor'; -import './index.less'; -const { TextArea } = Input; -const { Option } = Select; -const defalutLabel = '请使用富文本编辑器输入选项内容'; +import { debounce } from '@utils' +import { Input, Select, Tooltip, message } from 'antd' +import _ from 'lodash' +import React, { Component, Fragment } from 'react' +import { optionLetter } from '../../constant' +import KindEditor from '../kind-editor' +import './index.less' +const { TextArea } = Input +const { Option } = Select +const defalutLabel = '请使用富文本编辑器输入选项内容' // 判断题 const judgeList = [ - { - label: '错误', - value: 0, - }, - { - label: '正确', - value: 1, - }, -]; -const optionLetterLength = 7; // ABCD的长度 -const showDeleteLength = 3; // 展示删除icon的最短长度 + { + label: '错误', + value: 0 + }, + { + label: '正确', + value: 1 + } +] +const optionLetterLength = 7 // ABCD的长度 +const showDeleteLength = 3 // 展示删除icon的最短长度 export default class OptionInputBox extends Component { - constructor(props) { - super(props); - this.state = { - optionList: [ - { - label: defalutLabel, - value: 1, - }, - { - label: defalutLabel, - value: 2, - }, - { - label: defalutLabel, - value: 3, - }, - { - label: defalutLabel, - value: 4, - }, - ], // 选项列表 - currentActiveList: [], // 当前选中的项 - scoreValue: '', // 分数 - subjectAnalysis: '', //试题解析 - }; + constructor(props) { + super(props) + this.state = { + optionList: [ + { + label: defalutLabel, + value: 1 + }, + { + label: defalutLabel, + value: 2 + }, + { + label: defalutLabel, + value: 3 + }, + { + label: defalutLabel, + value: 4 + } + ], // 选项列表 + currentActiveList: [], // 当前选中的项 + scoreValue: '', // 分数 + subjectAnalysis: '' //试题解析 } + } - kindEditor = KindEditor | null; - subjectAnswer = ''; // 选项内容 + kindEditor = KindEditor | null + subjectAnswer = '' // 选项内容 - /** - * 新增/删除 - * @param {*} len - * @param {*} type add-新增 / del-删除 - * @returns - */ - onChangeAddOption = (len, type) => () => { - let { optionList, currentActiveList } = this.state; - let list = []; - // 新增 - if (type === 'add') { - if (len === optionLetterLength) { - return; - } - optionList.push({ label: defalutLabel, value: optionLetter[len].value }); - } else { - // 删除 - currentActiveList = []; - optionList.splice(len, 1); - // 重新初始化ABCD对应的id - list = optionList.map((item, index) => { - return { - label: item.label, - value: optionLetter[index].value, - }; - }); + /** + * 新增/删除 + * @param {*} len + * @param {*} type add-新增 / del-删除 + * @returns + */ + onChangeAddOption = (len, type) => () => { + let { optionList, currentActiveList } = this.state + let list = [] + // 新增 + if (type === 'add') { + if (len === optionLetterLength) { + return + } + optionList.push({ label: defalutLabel, value: optionLetter[len].value }) + } else { + // 删除 + currentActiveList = [] + optionList.splice(len, 1) + // 重新初始化ABCD对应的id + list = optionList.map((item, index) => { + return { + label: item.label, + value: optionLetter[index].value } - this.setState( - { - optionList: type === 'add' ? optionList : list, - currentActiveList, - }, - () => { - this.handleChangeOption(); - } - ); - }; - - /** - * 确认/取消 编辑框 - * @param {*} index - * @param {*} type submit/cancel - * @returns - */ - onChangeOptEditor = (index, type) => () => { - let { optionList } = this.state; - this.kindEditor && this.kindEditor.onClear(); - if (type === 'submit') { - _.set( - optionList, - [index, 'label'], - !!this.subjectAnswer ? this.subjectAnswer : defalutLabel - ); - } - _.set(optionList, [index, 'isShowEditor'], false); - this.subjectAnswer = ''; - this.setState( - { - optionList, - }, - () => { - this.handleChangeOption(); - } - ); - }; - - /** - * 展开 编辑项 - * @param {*} index - * @returns - */ - onChangeShowEditor = (index) => - debounce(() => { - let { optionList } = this.state; - if (optionList.filter((item) => item.isShowEditor).length > 0) { - return message.info('请先确认正在编辑的选项内容'); - } - _.set(optionList, [index, 'isShowEditor'], true); - this.setState( - { - optionList, - }, - () => { - this.kindEditor && this.kindEditor.onCashBack(); - } - ); - }); - - /** - * 富文本编辑器 - * @param {*} e - */ - onChangeEditor = (index) => (e) => { - this.subjectAnswer = e; - }; - - /** - * 正确选项 - * @param {*} value - */ - onChangeSelect = (value) => { - const { isMultiple } = this.props; - let str = value; - if (!isMultiple) { - // 单选,格式化成数组 - str = [value]; - } - this.setState( - { - currentActiveList: str, - }, - () => { - this.handleChangeOption(); - } - ); - }; - - /** - * 本题分值 - */ - onChangeScore = (e) => { - this.setState( - { - scoreValue: e.target.value.trim(), - }, - () => { - this.handleChangeOption(); - } - ); - }; - - /** - * 试题解析 - * @param {*} e - */ - onChangeSubjectAnalysis = (e) => { - this.setState( - { - subjectAnalysis: e.target.value.trim(), - }, - () => { - this.handleChangeOption(); - } - ); - }; - - /** - * 清空 - */ - handleClearOption = () => { - this.subjectAnswer = ''; // 选项内容 - this.setState({ - optionList: [ - { - label: defalutLabel, - value: 1, - }, - { - label: defalutLabel, - value: 2, - }, - { - label: defalutLabel, - value: 3, - }, - { - label: defalutLabel, - value: 4, - }, - ], // 选项列表 - currentActiveList: [], // 当前选中的项 - scoreValue: '', // 分数 - subjectAnalysis: '', //试题解析 - }); - }; - - /** - * 向父组件传值 - */ - handleChangeOption = () => { - let { currentActiveList, scoreValue, subjectAnalysis, optionList } = this.state; - const { isJudge } = this.props; - let activeList = []; - if (!isJudge) { - // 单选/多选 - activeList = optionList.map((item) => { - let flag = 0; - if (currentActiveList.includes(item.value)) { - flag = 1; - } - return { - optionType: item.value, - optionContent: item.label, - isCorrect: flag, - }; - }); - } else { - // 判断 - activeList = currentActiveList; - } - console.log('向父组件传值', activeList, scoreValue, subjectAnalysis); - // this.props.handleChangeOption(activeList, scoreValue, subjectAnalysis); - this.props.handleChangeOption(activeList, 1, subjectAnalysis); - }; - - render() { - const { subjectAnalysis } = this.state; - const { isJudge } = this.props; - return ( - - {!isJudge && this.renderOption()} - {this.renderOptionBtn()} -
-
试题解析:
-