uni-app 开发技巧:复选框list选择后value传多值问题

本文介绍了如何使用JSON.stringify和JSON.parse处理前端checkbox组件的多选状态,通过changeCheckbox和allChoose方法实现全选功能,并展示了如何在列表中存储和操作选择的数据。

 解决方案:1、

将 value 的多条信息用JSON.stringify(item)转化为字符串传入

<checkbox :value="JSON.stringify(item)" class="round blue"
            :checked="checkedArr.includes(item)" :class="{'checked':checkedArr.includes(item)}">
 </checkbox>

2、将checked的列表获取到后,将value值用JSON.parse(item)由字符串转化对象,然后就可以获取到选择的数据了

                checked = e.detail.value;
                checked.map(function(item) {
                    _this.checkedArr.push(JSON.parse(item))
                })

 

			// 多选复选框改变事件
			changeCheckbox(e) {
				this.checkedArr = []
				var checked = []
				var _this = this

				checked = e.detail.value;
				checked.map(function(item) {
					_this.checkedArr.push(JSON.parse(item))
				})
				// this.checkedArr.push(){
				// 	key:e.detail.value,
				// 	value:
				// })
				// console.log(e.toString())
				// 如果选择的数组中有值,并且长度等于列表的长度,就是全选
				if (this.checkedArr.length > 0 && this.checkedArr.length == this.vehicleList.length) {
					this.allChecked = true;
				} else {
					this.allChecked = false;
				}
			},

			// 全选事件
			allChoose(e) {
				let chooseItem = e.detail.value;
				this.checkedArr = []
				// 全选
				if (chooseItem[0] == 'all') {
					this.allChecked = true;
					for (let item of this.vehicleList) {
						// let itemVal = String(item.vehicleId);
						if (!this.checkedArr.includes(item)) {
							this.checkedArr.push(item);
						}
					}
				} else {
					// 取消全选
					this.allChecked = false;
					this.checkedArr = [];
				}
			},

 

<view class="padding-top-sm margin-top-l">
			<block v-if="vehicleList.length > 0">
				<checkbox-group class="block" @change="changeCheckbox">
					<view class="border-item task-item radius" v-for="(item,index) in vehicleList"
						:data-vehicleId="item.vehicleId">
						<view class="flex  item-text-time">
							<view class="basis-df text-left custom-item-inner">
								<checkbox :value="JSON.stringify(item)" class="round blue"
									:checked="checkedArr.includes(item)" :class="{'checked':checkedArr.includes(item)}">
								</checkbox>

								<text class="custom-item-inner">{{item.vehicleName}}</text>
							</view>
							<view class="basis-lg text-right custom-item-inner">
								<text class="custom-item-inner">{{item.vehicleWidth}}米</text>
								<text class="custom-item-inner">{{item.vehicleType}}</text>
								<image style="width: 15px; height: 15px; " mode="aspectFit"
									src="../../static/item/modify.png" @error="imageError"></image>
							</view>

						</view>
						<view class="flex item-text-time">
							<view class="basis-lg flex text-bottom">
								<text>{{item.vehicleOwner}}</text>
								<text>{{item.phone}}</text>
							</view>
							<view class="basis-lg flex text-bottom">
								<text>身份证</text>
								<text>{{item.idNumber}}</text>
							</view>
						</view>

					</view>
				</checkbox-group>
			</block>
			<block v-else>
				<view class="noData margin-top-xxl">
					<image src="../../static/sample/icon_no_data.png" mode="widthFix" class="noDataImg"></image>
					<view>暂无车辆信息数据,请添加</view>
					<slot></slot>
				</view>
			</block>

		</view>
		<view class="foot-btn">
			<block v-if="vehicleList.length > 0">
				<checkbox-group @change="allChoose">
					<label>
						<checkbox value="all" class="round blue all-selected" :class="{'checked':allChecked}"
							:checked="allChecked?true:false">
						</checkbox> 全选
					</label>
				</checkbox-group>
				<button class="btn-qualify" @tap="saveSelected">保存</button>
			</block>
			<block v-else>
				<button class="btn-qualify" @tap="addRecordInfo">新增</button>
			</block>
		</view>

 

<template> <!-- 遮罩层:点击关闭 --> <view v-if="visible" class="modal-overlay" @click="handleOverlayClick"> <!-- 主体内容 --> <view class="modal-content" @click.stop> <!-- 第一部分:标题栏 --> <view class="modal-header"> <view class="title" >{{ title }} <uni-icons class="close-btn" type="close-outline" size="13" color="#999999" @tap="close" /> </view> </view> <!-- 第二部分:可滚动表格 --> <scroll-view class="table-container" scroll-y> <view v-for="(item, index) in tableData" :key="index" class="table-row"> <!-- 左侧文本 --> <view class="text-group"> <text class="text-primary">{{ item.userName }}</text> <text class="text-secondary">{{ item.orgName + '/' + item.areaName }}</text> </view> <!-- 复选框 --> <view class="checkbox-group"> {{isClique}} <CrmSelectTag v-model="item.rulse.modelValue" :disabled="item.rulse.disabled" :disabledItems="item.rulse.disabledItems" :showCreateOpportunity="isClique" :show-create-opportunity="showOpportunity" :show-marketing-visit="showVisit" @change="crmSelectTagChangeFn($event, item.userId)" /> </view> <!-- 删除按钮 --> <uni-icons class="delete-btn" type="danchuangshanchu" size="13" color="#999999" @click="removeRow(item.userId)" /> </view> <!-- 继续添加 --> </scroll-view> <view class="add-more-btn" @click="$emit('addMore')"> <uni-icons class="add-more-btn-icon" type="tianjia" size="13" color="#0F56D5" /> <text>继续添加</text> </view> <view class="add-more-btn-bottom"> </view> <view class="item-wrap no-border"> <view class="item-left-wrap"> 共享原因 <uni-icons v-if="isMast()" color="#ff3141" class="star-img" :size="7" type="xinghao" /> </view> <view class="item-right-wrap" @touchstart="handleSound" @touchend="handleSoundEnd" @touchcancel="handleSoundEnd" ><uni-icons color="#0f56d5" :size="15" type="yuyin" /></view> </view> <view class="item-bottom-wrap"> <uni-easyinput v-model="channelTypeExplain" :maxlength="100" type="textarea" placeholder="请输入" :clearable="false" :inputBorder="false" trim :autoHeight="true" :disabled-default-padding="true" placeholderStyle="font-size: 15px; color: #ccc;" :cursorSpacing="30" @input="channelInput" > </uni-easyinput> <view class="text-count"> <view class="text-count-num"> {{ (channelTypeExplain && channelTypeExplain.length) || 0 }}/100 </view> </view> </view> <!-- 吸底按钮 --> <zy-sticky-bottom :z-index="'98'" :buttons="buttons" @click="onButtonClick" needBorder /> <!-- 语音组件 --> <!-- <zy-text-transspeech :soundTop="soundTop" :visible="showSoundPopup" @end="handleSoundEndSuccess"></zy-text-transspeech> --> </view> <zy-dialog ref="dialogRef" showPrimary :primaryText="dialogPrimaryText" :cancelText="dialogCancelText" :title="dialogTitle" @primary="dialogConfirm" @cancel="dialogCancel" /> </view> </template> <script setup> import { array } from '@/uni_modules/uview-plus/libs/function/test' import { ref } from 'vue' import CrmSelectTag from './CrmSelectTag.vue' const buttons = reactive([ { text: '取消', type: 'cancel' }, { text: '完成创建', type: 'confirm', loading: false // ✅ 提前定义 } ]) const onButtonClick = button => { if (button.text == '完成创建') { handleAddCustomer() } if (button.text == '取消') { handleOverlayClick() } } const crmSelectTagChangeFn = (selectedValue, userId) => { emit('change', userId, selectedValue) } const isMast = () => { const result = props.tableData.some(item => { // 先安全获取 rulse 对象 const disabled = item.rulse.disabled const values = item.rulse.modelValue const hasOne = item.rulse.disabledItems.includes(1) return !disabled && !hasOne && (values == 1 || values == 3) }) return result } const handleAddCustomer = () => { const result = props.tableData.some(item => { const values = item.rulse?.modelValue return values == 0 }) const allFalse = props.tableData.every(item => !item.rulse.isChange) let name = '' // 查找第一个 rulse.isChange 为 false 的项 const target = props.tableData.find(item => item.rulse?.isChange === false&&item.rulse?.modelValue != 3) if (target) { // 如果找到了任意一个 isChange 为 false 的项 name = target.userName } else { // 否则:找第一个 modelValue === 0 的项 const target = props.tableData.find(item => item.rulse?.modelValue === 0) if (target) { name = target.userName } else { name = '' // 或默认 } } if (result || allFalse) { return uni.showToast({ title: `${name}未配置被共享权限`, icon: 'none' }) } if (isMast()) { if (!channelTypeExplain.value) { return uni.showToast({ title: '被共享权限包含创建商机时必须填写共享原因', icon: 'none' }) } else if (channelTypeExplain.value && channelTypeExplain.value.length > 100) { return uni.showToast({ title: '共享原因最可填写100字符', icon: 'none' }) } } const hasItem = props.tableData.some(item => item.rulse.disabled === false && item.rulse.isChange === false) if (hasItem) return dialogTitleFn() emit('handleAddCustomer') } const dialogTitleFn = () => { dialogTitle.value = '存在没有勾选的权限的人,是否确认提交?' dialogRef.value.show() dialogConfirm.value = async () => { emit('handleAddCustomer') } } const dialogRef = ref() const dialogPrimaryText = ref('确定') const dialogCancelText = ref('取消') const dialogTitle = ref('') const dialogConfirm = ref(() => {}) const dialogCancel = ref(() => {}) const showOpportunity = ref(true) const showVisit = ref(true) // 语言组件高度 const soundTop = ref(0) const toggleShowOpportunity = e => (showOpportunity.value = e.detail.value) const toggleShowVisit = e => (showVisit.value = e.detail.value) // Props const props = defineProps({ title: { type: String, default: '添加被共享人' }, tableData: { type: Array, default: [] }, otherCloseFn: { type: Boolean, default: false }, isClique: { type: Boolean, default: false } }) const channelTypeExplain = ref('') /** 输入其他渠道类型 */ const channelInput = val => { channelTypeExplain.value = val emit('channelInput', channelTypeExplain.value) } const showSoundPopup = ref(false) const timeStap = ref(0) const handleSound = e => { // 直接从事件中取 Y 坐标 const clientY = e.touches[0]?.clientY || e.changedTouches[0]?.clientY timeStap.value = Date.now() soundTop.value = clientY // 直接赋 showSoundPopup.value = true } /** 结束录音 */ const handleSoundEnd = () => { if (Date.now() - timeStap.value < 1000 && timeStap.value > 0) { uni.showToast({ title: '说话时间太短', icon: 'error' }) } setTimeout(() => { showSoundPopup.value = false }, 100) } /** 转换成功 */ const handleSoundEndSuccess = val => { if (val) { channelTypeExplain.value = val emit('channelInput', channelTypeExplain.value) } else { uni.showToast({ title: '说话时间太短', icon: 'error' }) } } // Emits const emit = defineEmits([ 'addMore', 'submit', 'inputChange', 'open', 'close', 'change', 'channelInput', 'handleAddCustomer' ]) // 内部状态 const visible = ref(false) const inputValue = ref('') // 👇 暴露给外部的方法 defineExpose({ open, close }) // 打开方法 function open() { visible.value = true emit('open') } // 关闭方法 function close(val) { if (val) return visible.value = false visible.value = false emit('close') } // 点击遮罩关闭 function handleOverlayClick() { if(props.otherCloseFn) return emit('close') close() } // 删除某一行 function removeRow(index) { emit('removeRow', index) } </script> <style lang="scss" scoped> /* 样式同前,保持不变 */ .modal-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: flex-start; z-index: 9; } .modal-content { background-color: #fff; border-radius: 6px; width: 100%; max-height: 85vh; overflow: hidden; /* margin-bottom: 0; */ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); display: block; flex-direction: column; position: fixed; bottom: 0; padding-bottom: calc(80px + env(safe-area-inset-bottom)); } .modal-header { display: flex; justify-content: center; align-items: center; padding: 12px; font-size: 17px; color: #333333; text-align: center; font-weight: 500; } .title { font-size: 16px; font-weight: bold; color: #333; width: 100%; } .close-btn { float: right; } .table-container { flex: 1; max-height: 35vh; min-height: 5vh; padding: 0 8px 0 16px; padding-right: 0px; box-sizing: border-box; margin-bottom: 10px; } .table-row { display: flex; align-items: center; border-bottom: 1px dashed #eee; height: 67.5px; padding-right: 8px; } .text-group { flex: 1; display: flex; flex-direction: column; } .text-primary { font-size: 15px; color: #333333; letter-spacing: 0; line-height: 14px; margin-bottom: 6px; } .text-secondary { font-size: 13px; color: #999999; letter-spacing: 0; line-height: 14px; } .checkbox-group { display: flex; flex-direction: column; margin-right: 10px; } .delete-btn { width: 30px; display: flex; justify-content: center; align-items: center; } .add-more-btn { padding: 10px; text-align: center; border-radius: 4px; // margin: 10px 0; height: 48px; box-sizing: border-box; } .add-more-btn text { color: #0f56d5; font-size: 14px; } .add-more-btn-icon { margin-right: 6px; } .input-section { padding: 10px 16px; position: relative; } .input-box { width: 100%; height: 60px; border: 1px solid #ddd; border-radius: 4px; padding: 10px; font-size: 14px; color: #333; box-sizing: border-box; } .char-count { position: absolute; right: 20px; bottom: 15px; font-size: 12px; color: #999; } .sticky-footer { padding: 10px 16px 20px; background-color: #fff; border-top: 1px solid #eee; } .bottom-button { width: 100%; background-color: #0f56d5; color: #fff; font-size: 15px; border-radius: 4px; padding: 10px 0; } .add-more-btn-bottom { width: 100%; height: 10px; background-color: #f5f5f5; } .item-wrap { height: 48px; display: flex; align-items: center; margin-left: 16px; padding-right: 16px; border-bottom: 0.5px solid #eeeeee; .item-left-wrap { flex: 1; display: flex; align-items: center; font-size: 15px; color: #666666; font-weight: 400; .star-img { margin-left: 4px; display: flex; align-items: center; } .tips-wrap { position: relative; .tips-img { margin-left: 8px; display: flex; align-items: center; } .tips-box { .top { position: absolute; left: 5px; bottom: -12px; width: 18px; height: 10px; z-index: 99; } .tips-content { position: absolute; top: 26px; left: -20px; min-width: 202px; padding: 16px; box-sizing: border-box; background: #ffffff; box-shadow: 0 2px 5px 0 rgba(51, 51, 51, 0.2); border-radius: 10px; font-size: 13px; color: #333333; font-weight: 400; z-index: 98; } } } } &:last-child { border-bottom: 0; } .item-right-wrap { display: flex; align-items: center; justify-content: flex-end; text { padding-right: 8px; font-size: 15px; color: #cccccc; text-align: right; font-weight: 400; } .tit-cur { color: #333333; } .block { display: flex; align-items: center; justify-content: center; padding: 8px 18px; margin-right: 8px; background: #f5f5f5; border-radius: 4px; font-size: 14px; color: #333333; line-height: 14px; font-weight: 400; &:last-child { margin-right: 0; } } .checked { background: #e3efff; } .custInput { width: calc(100% - 14px); color: #333; text-align: right; :deep(.content-clear-icon) { padding: 0; } } .custName-box { display: flex; align-items: center; justify-content: flex-end; width: calc(100% - 14px); height: 100%; top: 0; left: 0; color: #333; padding-left: 14px; .custName { margin-right: 0px; padding-right: 0px; } .custName-cur { color: #333333; } } .item-right-wrap-cur { display: flex; align-items: center; .img { margin-right: 5px; margin-bottom: 2px; } } .btn { margin-right: 0px; font-size: 14px; color: #0f56d5; text-align: right; font-weight: 400; } .select { padding-right: 0; } .add { margin-right: 5px; padding-right: 0; } } } .item-wrap-cur { height: 42px; } .no-border { border-bottom: 0; } .item-bottom-wrap { padding: 0 16px; :deep(.uni-easyinput__content-textarea) { height: 47px; min-height: 47px; margin-top: 0; margin-right: 0; } .text-count { height: 25px; padding-bottom: 13px; border-bottom: 0.5px solid #eeeeee !important; .text-count-num { font-size: 15px; color: #999999; text-align: right; font-weight: 400; } } } .item-bottom-wrap-cur { margin-left: 16px; padding: 0; padding-right: 16px; padding-bottom: 19px; border-bottom: 0.5px solid #eeeeee; .custInput { height: 20px; } } </style> <script lang="ts" setup> import isTablet from '@/utils/device' const props = defineProps({ disabled: { type: Boolean, default: false }, disabledItems: { type: Array, default: [] }, showCreateOpportunity: { type: Boolean, default: false }, showMarketingVisit: { type: Boolean, default: false }, modelValue: { type: Number, default: 0 } }) const emit = defineEmits<{ (e: 'update:modelValue', value: string | number): void (e: 'change', value: 0 | 1 | 2 | 3): void }>() const isIpad = isTablet() // 构建 list 数据 const list = computed(() => { const result = [] if (props.showCreateOpportunity) { console.log("🚀 ~ list ~ props.showCreateOpportunity:", props.showCreateOpportunity) result.push({ label: '创建商机', value: 1 }) } result.push({ label: '营销拜访', value: 2 }) return result }) // 当前选中状态(内部使用数组管理) const selectedValues = computed({ get: (): number[] => { const val = Number(props.modelValue) const arr: number[] = [] if (val & 1) arr.push(1) // 营销拜访 if (val & 2) arr.push(2) // 创建商机 return arr }, set: (vals: number[]) => { let numVal = 0 if (vals.includes(1)) numVal |= 1 if (vals.includes(2)) numVal |= 2 const strVal = String(numVal) emit('update:modelValue', strVal) emit('change', numVal as 0 | 1 | 2 | 3) } }) // 判断某项是否激活 const getActive = (itemValue: number) => { return selectedValues.value.includes(itemValue) } // 点击处理 const handleItem = (value: 1 | 2) => { if (props.disabled || props.disabledItems.includes(value)) return const current = selectedValues.value const index = current.indexOf(value) if (index > -1) { // 已选中 → 移除 selectedValues.value = current.filter(v => v !== value) } else { // 未选中 → 添加 selectedValues.value = [...current, value] } } </script> <template> <view class="CrmSelectTag-container" :class="{ 'CrmSelectTag-container-iPad': isIpad }" > <view v-for="item in list" :key="item.value" class="item" :class="{ active: getActive(item.value), disabled: disabled || disabledItems.includes(item.value) }" @click="handleItem(item.value)" > <text class="item-text">{{ item.label }}</text> <view class="active-icon" v-show="getActive(item.value)"> <uni-icons class="icon" type="check-outline" color="#fff" size="9" /> </view> </view> </view> </template> <style lang="scss" scoped> .CrmSelectTag-container { display: flex; flex-wrap: wrap; gap: 12px; .item { width: 90px; box-sizing: border-box; border: 1px solid transparent; background-color: $uni-separator-bg; border-radius: $uni-border-radius-sm; font-size: $uni-font-size-15; color: $uni-text-color; font-weight: 400; position: relative; } .item-text { display: block; width: 100%; text-align: center; padding: $uni-spacing-col-xs 4px; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; word-break: break-all; } .active { background-color: $uni-text-color-primary-light; color: $uni-primary; border-color: $uni-primary; } .disabled { // color: $uni-text-color-extra; opacity: 0.6; pointer-events: none; } .active-icon { position: absolute; bottom: -1px; right: -1px; width: 0; height: 0; border: 8px solid transparent; border-bottom-color: $uni-primary; border-right-color: $uni-primary; border-bottom-right-radius: $uni-border-radius-sm; .icon { position: absolute; right: -8px; bottom: -12px; } } } // iPad 适配 .CrmSelectTag-container-iPad { .item { width: 109px; // 固定宽度 } } </style> console.log("🚀 ~ list ~ props.showCreateOpportunity:", props.showCreateOpportunity)入的是false,打印的是true
最新发布
11-07
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值