效果:

所具備的功能:
1、切換學年
2、項目單選
3、前端懶加載(前端分頁)
4、打開彈框可以回顯上一次選中的項目,點選取消不進行操作
5、通過isRadioChange控制,選中後,再次點選可取消
components/ProjectRadio.vue
<template>
<div class="public-project-radio">
<van-popup class="project-radio" v-model="isShow" position="bottom" :style="{ height: '100%' }">
<div class="close">
<van-icon name="cross" @click="handleCancel" />
</div>
<div class="yearList">
<year-select :yearOptions="yearOptions" :isCheckId="isCheckId" @on-select="selectChange"></year-select>
</div>
<!-- 搜尋 -->
<div class="search">
<van-search v-model="searchValue" placeholder="輸入項目名稱" @input='handleInput' />
</div>
<div class="van-list-box">
<van-list ref="scrollContent" :finished="finished" finished-text="沒有更多了" @load="onLoad">
<van-radio-group v-model="radioResult" @change='handleChange'>
<van-radio v-for='ele in personData' :key='ele.value' :name="ele.projectId" @click="handleClick">
{{ele.projectName}}
<template #icon="props">
<div :class="props.checked ? 'activeIcon' : 'inactiveIcon'"><span></span></div>
</template>
</van-radio>
</van-radio-group>
</van-list>
</div>
<div class="van-popup-btns">
<van-button native-type="button" @click="handleCancel">取消</van-button>
<van-button native-type="button" @click="handleConfirm">确定</van-button>
</div>
</van-popup>
</div>
</template>
<script>
import clonedeep from 'lodash.clonedeep'
import { getProjectOptions, getSchoolYearOptions } from '@/api/select'
import YearSelect from '@/components/YearSelect'
const LOAD_NUM = 20
export default {
name: 'ProjectRadio',
components: {
YearSelect
},
data() {
return {
isShow: false,
isRadioChange: false, // 判斷單選的狀态有沒有變化
radioResult: '', // 回顯
searchValue: '', // 提供v-model響應參數
personData: [], // 用于循環展示的list資料
initPersonData: [], // 備份:接口資料
searchpersonData: [], // 搜尋到的資料
tileList: [], // 平鋪資料
yearOptions: [],
isCheckId: undefined,
finished: false,
allProjectList: [] // 所有的學年對應的項目集合
// list: [],
}
},
created() {
this.fetchSchoolYearOptions().then(() => {
this.getData()
})
},
methods: {
onLoad() {
if (this.searchValue) {
this.loadMoreData(this.searchpersonData)
} else {
this.loadMoreData(this.initPersonData)
}
},
// 加載更多資料到select框
loadMoreData(dataList) {
const renderedLen = this.personData.length // 已渲染的下拉清單長度
const totalLen = dataList.length // 全部資料源的長度(總全部或者搜尋到的全部)
let addList = []
// 如果 下拉清單已渲染的資料 < 全部資料 (意味着沒有全部渲染完所有資料)
if (renderedLen < totalLen) {
// 如果 下拉清單已渲染的資料 + 每次想要渲染的數量 <= 全部資料
// (如果小于等于 slice方法第二個參數能取到)
if (renderedLen + LOAD_NUM <= totalLen) {
addList = dataList.slice(renderedLen, renderedLen + LOAD_NUM)
this.finished = false
} else {
// 如果長度不夠 取餘數為 slice最後一個參數
addList = dataList.slice(
renderedLen,
renderedLen + (totalLen % LOAD_NUM)
)
this.finished = true
}
// 把截取到的後30條拼接在循環的清單尾部
this.personData = this.personData.concat(addList)
}
},
// 單選radio選中後,再次點選需要可以取消選擇功能
handleChange() {
this.isRadioChange = true
},
fetchSchoolYearOptions() {
return getSchoolYearOptions().then(({ data }) => {
const options = data.map(({ name, id, isCheck }) => {
if (isCheck) {
this.isCheckId = id
}
return {
name: `${name}學年`,
value: id
}
})
this.yearOptions = options
let years = options.map(item => item.value)
this.getAllProjectList(years)
})
},
// 擷取所有的學年對應的項目集合
getAllProjectList(years) {
let allProjectList = []
years.forEach(async year => {
let res = await getProjectOptions({ isUser: 1, year })
let { projectLetterSelect } = res.data
allProjectList = allProjectList.concat(projectLetterSelect)
this.allProjectList = allProjectList
})
},
selectChange(val) {
this.isCheckId = val
this.searchValue = ''
this.handleInput('')
this.getData()
document.querySelector('.van-list-box').scrollTop = 0
},
handleClick() {
if (!this.isRadioChange) {
this.radioResult = ''
}
this.isRadioChange = false
},
// 打開彈框
handleOpen(projectId) {
this.radioResult = projectId
this.isShow = true
},
// 關閉彈框
handleCancel() {
this.isShow = false
},
// 确定
handleConfirm() {
let tileList = clonedeep(this.allProjectList)
let result = tileList.filter(item => item.projectId === this.radioResult)
this.$emit('projectRadio', result)
this.handleCancel()
},
// 搜尋
handleInput(val) {
document.querySelector('.van-list-box').scrollTop = 0
if (val) {
this.searchpersonData = this.initPersonData.filter(item =>
item.projectName.match(val)
)
this.personData = this.initPersonData
.filter(item => item.projectName.match(val))
.slice(0, LOAD_NUM)
} else {
this.searchpersonData = []
this.personData = this.initPersonData.slice(0, LOAD_NUM)
this.finished = false
}
},
// 請求資料
async getData() {
const { isCheckId } = this
let res = await getProjectOptions({ isUser: 1, year: isCheckId })
if (res.code === '200') {
let { projectLetterSelect } = res.data
let arr = projectLetterSelect.sort((a, b) =>
a.letter.localeCompare(b.letter)
) // 按字母排序
// 假資料
// for(let i = 0; i <=300; i++){
// arr.push({
// "projectId": 1,
// "projectName": `${i}阿壩縣中學2023屆(高一e網通)`,
// "letter": "Z",
// "isChecked": false
// })
// }
this.initPersonData = arr // 存儲原始資料
this.personData = arr.slice(0, LOAD_NUM)
this.tileList = projectLetterSelect
}
}
}
}
</script>
css:
<style lang="less" scoped>
.public-project-radio {
/deep/ .project-radio {
box-sizing: border-box;
padding-top: 135px;
.close {
height: 30px;
position: fixed;
top: 5px;
left: 15px;
font-size: 16px;
z-index: 1005;
}
.yearList {
position: fixed;
top: 40px;
left: 0px;
font-size: 16px;
z-index: 1005;
.year-select {
padding-top: 5px;
padding-bottom: 15px;
}
}
// 選中和未選中樣式-start
.activeIcon {
width: 18px;
height: 18px;
border: 2px solid #198cff;
border-radius: 50%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
> span {
display: block;
width: 10px;
height: 10px;
background: #198cff;
border-radius: 50%;
}
}
.inactiveIcon {
width: 18px;
height: 18px;
border: 2px solid #e0e5f5;
border-radius: 50%;
box-sizing: border-box;
}
// 選中和未選中樣式-end
.search {
display: flex;
align-items: center;
padding: 4px 15px;
position: fixed;
top: 80px;
width: 100%;
box-sizing: border-box;
background-color: #fff;
z-index: 1001;
> .van-icon {
width: 30px;
color: #333333;
font-size: 20px;
}
.van-search {
flex: 1;
padding: 0;
height: 36px;
border-radius: 18px;
overflow: hidden;
background-color: #f3f6f9;
.van-search__content {
padding-right: 12px;
.van-icon {
color: #8e8e93;
}
.van-field__control {
font-size: 17px;
color: #b5b5b5;
}
}
}
}
.van-list-box {
height: calc(100% - 100px);
overflow: auto;
.van-radio-group {
color: red;
.van-radio {
margin-top: 20px;
padding: 0 15px;
.van-radio__label {
margin-left: 20px;
font-size: 16px;
}
}
.van-radio:first-child {
margin-top: 0;
}
.van-radio:last-child {
margin-bottom: 10px;
}
}
}
.van-popup-btns {
background-color: #fff;
display: flex;
justify-content: space-between;
position: fixed;
width: 100%;
box-sizing: border-box;
bottom: 47px;
padding: 0 15px;
> .van-button {
width: 150px;
height: 38px;
line-height: 38px;
border-radius: 19px;
font-size: 14px;
text-align: center;
}
> .van-button:first-child {
background-color: #e0e5f5;
color: #374e64;
}
> .van-button:last-child {
background-color: #1288fe;
color: #fff;
}
}
}
}
</style>
View Code
使用:
引入、注冊:
import ProjectRadio from '@/components/ProjectRadio'
components: { ProjectRadio, SelectUserPopup }
DOM:(通過ref控制子元件的打開)
<van-field v-model='params.projectName' placeholder="選擇項目" readonly is-link @click="$refs.projectRadioRef.handleOpen(params.projectId)" />
<!-- 項目單選彈框 -->
<ProjectRadio @projectRadio='handleProjectRadio' ref='projectRadioRef'></ProjectRadio>
data:
params: {
projectId: -1, // 項目Id 52883
projectName: '', // 項目名稱----僅做回顯使用
contactIdList: [], // 聯系人
}
methods:(聯系人options是基于項目id的,是以切換項目時要清空已選的聯系人)
// 項目單選彈層【确定】按鈕
handleProjectRadio(val) {
if (val.length) {
const { projectId, projectName } = val[0]
if (projectId !== this.params.projectId) this.params.contactIdList = [] // 清空聯系人清單
this.params.projectId = projectId
this.params.projectName = projectName
} else {
this.params.contactIdList = [] // 清空聯系人清單
this.params.projectId = -1
this.params.projectName = ''
}
}
YearSelect.vue
<!-- 學年橫向滾動 -->
<template>
<div class="year-select">
<div v-for="(item, index) in yearOptions" :key="index" :class="{
'tag-item': true,
'selected-item': currentIndex === index
}" @click="() => handleItemClick(item, index)">{{ item.name }}</div>
</div>
</template>
<script>
export default {
name: 'YearSelect',
model: {
prop: 'value',
event: 'on-change'
},
components: {},
props: {
yearOptions: {
type: Array,
default: () => []
},
isCheckId: {
type: Number
},
value: {
type: [String, Number]
},
mode: {
type: String,
default: 'radio' // 'radio', 'checkbox'
}
},
data() {
return { currentIndex: undefined }
},
watch: {
isCheckId: {
handler: function (val, oldVal) {
if (val === undefined) {
this.currentIndex = undefined
} else {
this.currentIndex = this.yearOptions.map(n => n.value).indexOf(val)
}
},
immediate: true
}
},
computed: {},
mounted() {},
methods: {
handleItemClick(item, index) {
const { mode } = this
if (mode === 'radio') {
this.currentIndex = index
// console.log(item)
this.$emit('on-select', item.value)
}
}
}
}
</script>
<style lang='less' scoped>
.year-select {
width: 95%;
padding-left: 15px;
padding-bottom: 5px;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
&::-webkit-scrollbar {
display: none;
}
.tag-item {
display: inline;
padding: 6px 20px;
border-radius: 15px;
margin-right: 10px;
background: #e0e5f5;
font-size: 12px;
}
.selected-item {
background: @theme-color;
color: #fff;
}
}
</style>