feat: 修改首页分类

This commit is contained in:
秋水浮尘
2023-11-23 01:46:50 +08:00
parent 4ff59f5eda
commit 2f330b22cb
10 changed files with 383 additions and 74 deletions

View File

@@ -31,6 +31,7 @@
"react": "^18.1.0", "react": "^18.1.0",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.16.0",
"swiper": "^11.0.4",
"wangeditor": "^4.7.15" "wangeditor": "^4.7.15"
}, },
"devDependencies": { "devDependencies": {

10
pnpm-lock.yaml generated
View File

@@ -35,6 +35,9 @@ dependencies:
react-router-dom: react-router-dom:
specifier: ^6.16.0 specifier: ^6.16.0
version: registry.npmmirror.com/react-router-dom@6.16.0(react-dom@18.1.0)(react@18.1.0) version: registry.npmmirror.com/react-router-dom@6.16.0(react-dom@18.1.0)(react@18.1.0)
swiper:
specifier: ^11.0.4
version: registry.npmmirror.com/swiper@11.0.4
wangeditor: wangeditor:
specifier: ^4.7.15 specifier: ^4.7.15
version: registry.npmmirror.com/wangeditor@4.7.15 version: registry.npmmirror.com/wangeditor@4.7.15
@@ -7093,6 +7096,13 @@ packages:
has-flag: registry.npmmirror.com/has-flag@4.0.0 has-flag: registry.npmmirror.com/has-flag@4.0.0
dev: true dev: true
registry.npmmirror.com/swiper@11.0.4:
resolution: {integrity: sha512-qtUxILrD4aD++rpKzGrkz3IAWL92f9uTrDwjb6HaNLmPvJhZCE/83DL+9w4kIgDDJeF6QKalV47rMBN77UOVYQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/swiper/-/swiper-11.0.4.tgz}
name: swiper
version: 11.0.4
engines: {node: '>= 4.7.0'}
dev: false
registry.npmmirror.com/tapable@2.2.1: registry.npmmirror.com/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz} resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz}
name: tapable name: tapable

View File

@@ -1,4 +1,8 @@
export const apiName = { export const apiName = {
/**
* 查询分类
*/
queryPrimaryCategory: '/category/queryPrimaryCategory',
/** /**
* 获取二级和三级标签 * 获取二级和三级标签
*/ */
@@ -15,3 +19,66 @@ export const apiName = {
// 查询分类及标签 // 查询分类及标签
queryCategoryAndLabel: '/category/queryCategoryAndLabel' queryCategoryAndLabel: '/category/queryCategoryAndLabel'
} }
/**
* 大分类中的背景图颜色
*/
export const categoryBackColor = {
0: '#23b2ff',
1: '#ea7d4d',
2: '#e93532',
3: '#343d71',
4: '#dc4ad6',
5: '#72b633',
6: '#9047de',
7: '#dc4077',
8: '#dc4077'
}
export const mockCategoryList = [
{
title: '后端',
total: 50,
id: 1
},
{
title: '前端1',
total: 50,
id: 2
},
{
title: '前端2',
total: 50,
id: 3
},
{
title: '前端3',
total: 50,
id: 4
},
{
title: '前端4',
total: 50,
id: 5
},
{
title: '前端5',
total: 50,
id: 6
},
{
title: '前端6',
total: 50,
id: 7
},
{
title: '前端7',
total: 50,
id: 8
},
{
title: '前端8',
total: 50,
id: 9
}
]

View File

@@ -0,0 +1,38 @@
import { useState } from 'react'
import LabelList from './label-list'
import PrimaryList from './primary-list'
const CategoryList = props => {
const [selectValue, setSelectValue] = useState({
primaryId: '', // 大类ID
categoryId: '', // 二级分类ID,
labelId: '' // 标签ID
})
const changePrimaryId = primaryId => {
setSelectValue({
...selectValue,
primaryId
})
}
const changeLabel = ids => {
const [categoryId, labelId] = ids.split('_')
const values = {
...selectValue,
categoryId,
labelId
}
setSelectValue({ ...values })
props.onChangeLabel({ ...values })
}
return (
<div className='category-list-container'>
<PrimaryList changePrimaryId={changePrimaryId} />
<LabelList primaryId={selectValue.primaryId} changeLabel={changeLabel} />
</div>
)
}
export default CategoryList

View File

@@ -0,0 +1,32 @@
.category-card {
transition: all 0.5s;
color: white;
&-title {
font-size: 16px;
}
&-count {
font-size: 12px;
}
&:hover {
transform: translateY(-8px);
}
&.active {
transform: translateY(-8px);
}
}
/** label-list style **/
.label-list-box {
padding-top: 15px;
.label-list-item {
line-height: 40px;
display: flex;
align-items: center;
.label-title {
width: 120px;
font-weight: bold;
color: #333;
font-size: 14px;
}
}
}

View File

@@ -0,0 +1,112 @@
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'
import req from '@utils/request'
import { Divider, Spin, Tag } from 'antd'
import { useEffect, useState } from 'react'
import { apiName } from './constant'
const { CheckableTag } = Tag
const maxCount = 4
const LabelList = props => {
const { primaryId, changeLabel } = props
const [spinning, setSpinning] = useState(false)
const [categoryAndLabelList, setCategoryAndLabelList] = useState([])
const [isPutAway, setIsPutAway] = useState(true)
const [checkedLabelId, setCheckedLabelId] = useState()
const getCategoryAndLabel = () => {
// setSpinning(true)
req({
method: 'post',
url: apiName.queryCategoryAndLabel,
data: { id: primaryId }
})
.then(res => {
if (res.data && res.data.length > 0) {
const ids = `${res.data[0].id}_${res.data[0].labelDTOList[0].id}`
setCategoryAndLabelList(res.data)
setCheckedLabelId(ids)
changeLabel(ids)
} else {
setCategoryAndLabelList([])
setCheckedLabelId('')
changeLabel('')
}
// setSpinning(false)
})
.catch(err => {
// setSpinning(false)
console.log(err)
})
}
const onChangePutAway = () => {
setIsPutAway(!isPutAway)
}
const changeChecked = (categoryId, labelId) => {
const ids = `${categoryId}_${labelId}`
if (ids === checkedLabelId) return
setCheckedLabelId(ids)
changeLabel(ids)
}
useEffect(() => {
if (primaryId) {
getCategoryAndLabel()
}
}, [primaryId])
return (
<Spin spinning={spinning}>
<div className='label-list-box'>
{categoryAndLabelList.map((categoryItem, index) => {
return (
<div
className='label-list-item'
key={categoryItem.id}
style={{
display: index >= maxCount && isPutAway ? 'none' : 'flex'
}}
>
<div className='label-title'>{categoryItem.categoryName}</div>
{categoryItem.labelDTOList.map(labelItem => {
return (
<CheckableTag
key={labelItem.id}
checked={checkedLabelId === `${categoryItem.id}_${labelItem.id}`}
className='label-tag'
onChange={() => changeChecked(categoryItem.id, labelItem.id)}
>
{labelItem.labelName}
</CheckableTag>
)
})}
</div>
)
})}
</div>
{categoryAndLabelList.length > maxCount ? (
<Divider
onClick={onChangePutAway}
dashed
style={{
marginTop: 10,
marginBottom: 10,
fontSize: 13,
cursor: 'pointer'
}}
>
{isPutAway ? '展开' : '收起'}
{isPutAway ? (
<CaretDownOutlined style={{ marginLeft: 4 }} />
) : (
<CaretUpOutlined style={{ marginLeft: 4 }} />
)}
</Divider>
) : null}
</Spin>
)
}
export default LabelList

View File

@@ -0,0 +1,89 @@
import req from '@utils/request'
import { Card, Space, Spin } from 'antd'
import { useEffect, useState } from 'react'
import { Navigation } from 'swiper/modules'
import { Swiper, SwiperSlide } from 'swiper/react'
import { apiName, categoryBackColor, mockCategoryList } from './constant'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import 'swiper/css/scrollbar'
import './index-new.less'
const PrimaryList = props => {
const [loading, setLoading] = useState(false)
const [primaryCategoryId, setPrimaryCategoryId] = useState()
const [categoryList, setCategoryList] = useState([])
/**
* 获取大类分类
*/
const getPrimaryCategoryInfo = () => {
setLoading(true)
req({
method: 'post',
url: apiName.queryPrimaryCategory,
data: { categoryType: 1 }
})
.then(res => {
if (res.data && res.data.length > 0) {
setPrimaryCategoryId(res.data[0].id)
setCategoryList([...res.data])
props.changePrimaryId(res.data[0].id)
} else {
setPrimaryCategoryId(mockCategoryList[0].id)
setCategoryList(mockCategoryList)
}
setLoading(false)
})
.catch(err => {
console.log(err)
setLoading(false)
})
}
useEffect(() => {
getPrimaryCategoryInfo()
}, [])
return (
<Spin spinning={loading}>
<div className='category-box'>
<Swiper
spaceBetween={14}
slidesPerView={6.5}
style={{ paddingTop: '10px' }}
modules={[Navigation]}
navigation
>
{categoryList.map((item, index) => {
return (
<SwiperSlide
key={index}
style={{ cursor: 'pointer' }}
onClick={() => {
setPrimaryCategoryId(item.id)
props.changePrimaryId(item.id)
}}
>
<Card
style={{ backgroundColor: `${categoryBackColor[index]}` }}
bodyStyle={{ padding: '10px 14px' }}
className={`category-card ${item.id === primaryCategoryId ? 'active' : ''}`}
>
<Space direction='vertical' size='middle'>
<div className='category-card-title'>{item.categoryName}</div>
<div className='category-card-count'>{item.count}道题</div>
</Space>
</Card>
</SwiperSlide>
)
})}
</Swiper>
</div>
</Spin>
)
}
export default PrimaryList

View File

@@ -1,4 +1,5 @@
import CategoryList from '@components/category-list' // import CategoryList from '@components/category-list'
import CategoryList from '@components/category-list/index-new.jsx'
import QuestionList from '@components/question-list' import QuestionList from '@components/question-list'
import req from '@utils/request' import req from '@utils/request'
import { memo, useEffect, useState } from 'react' import { memo, useEffect, useState } from 'react'
@@ -8,59 +9,31 @@ import { apiName } from './constant'
import './index.less' import './index.less'
const QuestionBank = () => { const QuestionBank = () => {
const [firstCategoryList, setFirstCategoryList] = useState<Record<string, any>[]>([])
const [questionList, setQuestionList] = useState([]) const [questionList, setQuestionList] = useState([])
const [labelList, setLabelList] = useState<string | number>() // 选中的标签列表 const [labelList, setLabelList] = useState<string | number>() // 选中的标签列表
const [difficulty, setDiffculty] = useState('') //困难度(全部) const [difficulty, setDiffculty] = useState('') //困难度(全部)
const [total, setTotal] = useState(0) // 总条数 const [total, setTotal] = useState(0) // 总条数
const [pageIndex, setPageIndex] = useState(0) const [pageIndex, setPageIndex] = useState(0)
const [primaryCategoryId, setPromaryCategoryId] = useState('') //第一个大类id
const [secondCategoryId, setSecondCategoryId] = useState('') const [secondCategoryId, setSecondCategoryId] = useState('')
const [selectedValue, setSelectedValue] = useState({
primaryId: '', // 大类ID
categoryId: '', // 二级分类ID,
labelId: '' // 标签ID
})
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [finished, setFinished] = useState(false) const [finished, setFinished] = useState(false)
const [switchFlag, setSwitchFlag] = useState(false) const [switchFlag, setSwitchFlag] = useState(false)
/**
* 获取大类分类
*/
const getPrimaryCategoryInfo = () => {
req({
method: 'post',
url: apiName.queryPrimaryCategory,
data: { categoryType: 1 }
})
.then((res: Record<string, any>) => {
if (res.data && res.data.length > 0) {
setPromaryCategoryId(res.data[0].id)
setFirstCategoryList(res.data)
}
})
.catch((err: string) => {
console.log(err)
})
}
/**
* 切换一级分类
* @param {*} e
*/
const onChangeCategory = (item: Record<string, any>) => {
setLabelList('')
setPromaryCategoryId(item.id)
setQuestionList([])
setTotal(0)
setPageIndex(1)
}
/** /**
* 选择标签时,请求列表数据 * 选择标签时,请求列表数据
* @param {*} secondCategoryId 一级分类id * @param {*} secondCategoryId 一级分类id
* @param {*} assembleIds 三级标签 assembleIds * @param {*} assembleIds 三级标签 assembleIds
*/ */
const onChangeLabel = (secondCategoryId: any, assembleIds: string) => { const onChangeLabel = values => {
setSecondCategoryId(secondCategoryId) console.log(values)
setLabelList(assembleIds) setSelectedValue(values)
setQuestionList([]) setQuestionList([])
setTotal(0) setTotal(0)
setPageIndex(1) setPageIndex(1)
@@ -71,8 +44,8 @@ const QuestionBank = () => {
const params = { const params = {
pageNo: pageIndex, pageNo: pageIndex,
pageSize: 20, pageSize: 20,
labelId: labelList, labelId: selectedValue.labelId,
categoryId: secondCategoryId, categoryId: selectedValue.categoryId,
subjectDifficult: difficulty || '' subjectDifficult: difficulty || ''
} }
req({ req({
@@ -105,29 +78,10 @@ const QuestionBank = () => {
} }
useEffect(() => { useEffect(() => {
if (!primaryCategoryId) { if (selectedValue.labelId) {
getPrimaryCategoryInfo()
}
}, [])
useEffect(() => {
if (labelList && secondCategoryId) {
queryQuestionList() queryQuestionList()
} }
}, [labelList, pageIndex, secondCategoryId, difficulty]) }, [pageIndex, selectedValue.labelId, difficulty])
/**
* 更多分类切换
* @param {*} e
*/
const onChangeCategoryMore = (id: string, categoryList: Record<string, any>[]) => {
setFirstCategoryList(categoryList)
setPromaryCategoryId(id)
setLabelList('')
setQuestionList([])
setPageIndex(1)
setTotal(0)
}
const scrollHandler = e => { const scrollHandler = e => {
let scrollTop = e.target.scrollTop // listBox 滚动条向上卷曲出去的长度,随滚动变化 let scrollTop = e.target.scrollTop // listBox 滚动条向上卷曲出去的长度,随滚动变化
@@ -158,16 +112,7 @@ const QuestionBank = () => {
<div className='mask-box' onScroll={scrollHandler}> <div className='mask-box' onScroll={scrollHandler}>
<div className='question-box'> <div className='question-box'>
<div className='category-list-box'> <div className='category-list-box'>
{firstCategoryList?.length > 0 && ( <CategoryList onChangeLabel={onChangeLabel} />
<CategoryList
onChangeCategory={onChangeCategory}
categoryList={firstCategoryList}
onChangeLabel={onChangeLabel}
primaryCategoryId={primaryCategoryId}
isMultipleChoice={false}
onChangeCategoryMore={onChangeCategoryMore}
/>
)}
</div> </div>
<div className='question-list-box'> <div className='question-list-box'>
<QuestionList <QuestionList

View File

@@ -42,9 +42,16 @@ const Sex: Record<string, any> = {
2: '女' 2: '女'
} }
const normFile = (e: any) => {
if (Array.isArray(e)) {
return e
}
return e?.fileList
}
const UserInfo = () => { const UserInfo = () => {
const userInfoStorage = localStorage.getItem('userInfo') const userInfoStorage = localStorage.getItem('userInfo')
const { loginId = '' } = userInfoStorage ? JSON.parse(userInfoStorage) : {} const { loginId = '', tokenValue = '' } = userInfoStorage ? JSON.parse(userInfoStorage) : {}
const [form] = Form.useForm() const [form] = Form.useForm()
const [editFlag, setEditFlag] = useState(false) const [editFlag, setEditFlag] = useState(false)
@@ -130,14 +137,18 @@ const UserInfo = () => {
<Row> <Row>
<Col span={16}> <Col span={16}>
{editFlag ? ( {editFlag ? (
<Form.Item label='用户头像' name='avatar'> <Form.Item label='用户头像' valuePropName='fileList' getValueFromEvent={normFile}>
<Upload <Upload
name='uploadFile' name='uploadFile'
listType='picture-card' listType='picture-card'
className='avatar-uploader' className='avatar-uploader'
accept='image/*' accept='image/*'
showUploadList={false} showUploadList={false}
action='http://117.72.14.166:4000/upload' withCredentials
action='/oss/upload'
headers={{
satoken: 'jichi ' + tokenValue
}}
data={{ data={{
bucket: 'jichi', bucket: 'jichi',
objectName: 'icon' objectName: 'icon'

View File

@@ -38,6 +38,10 @@ export default ({ mode }) => {
'/auth': { '/auth': {
target: env.VITE_API_HOST, target: env.VITE_API_HOST,
changeOrigin: true changeOrigin: true
},
'/oss': {
target: env.VITE_API_HOST,
changeOrigin: true
} }
} }
} }