refactor: ♻️ 优化Radio和Checkbox组件

This commit is contained in:
xuqingkai 2023-07-10 23:11:58 +08:00
parent b7dad0fb01
commit f90fd3dcdc
9 changed files with 1020 additions and 77 deletions

View File

@ -370,6 +370,26 @@
},
"navigationBarTitleText": "Tab 标签页"
}
},
{
"path": "pages/radio/Index",
"name": "radio",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "Radio 单选框"
}
},
{
"path": "pages/checkbox/Index",
"name": "checkbox",
"style": {
"mp-alipay": {
"allowsBounceVertical": "NO"
},
"navigationBarTitleText": "Checkbox 复选框"
}
}
],
// "tabBar": {

View File

@ -1,7 +1,124 @@
<template>
<demo-block title="基本用法">
<wd-checkbox v-model:value="check1">京麦</wd-checkbox>
</demo-block>
<template>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" scoped>
</style>
<demo-block title="修改形状: square">
<wd-checkbox v-model:value="check2" shape="square">京麦</wd-checkbox>
</demo-block>
<demo-block title="修改形状: button">
<wd-checkbox v-model:value="check3" shape="button">京麦</wd-checkbox>
</demo-block>
<demo-block title="修改选中颜色">
<wd-checkbox v-model:value="check4" checked-color="rgb(52, 209, 157)">京麦</wd-checkbox>
</demo-block>
<demo-block title="禁用状态">
<view style="margin-bottom: 10px">
<wd-checkbox-group v-model="value1" disabled>
<wd-checkbox :value="1">京麦</wd-checkbox>
<wd-checkbox :value="2" disabled="{{false}}">商家后台</wd-checkbox>
<wd-checkbox :value="3" shape="square">京麦</wd-checkbox>
<wd-checkbox :value="4" shape="square">商家后台</wd-checkbox>
</wd-checkbox-group>
</view>
<wd-checkbox-group v-model="value2" disabled>
<wd-checkbox :value="1" shape="button">京麦</wd-checkbox>
<wd-checkbox :value="2" shape="button">商家后台</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block :title="`修改 true-value 和 false-value ${value3}`">
<wd-checkbox v-model:value="value3" true-value="京麦" false-value="商家后台" @change="handleChange1">复选框</wd-checkbox>
</demo-block>
<demo-block title="同行展示">
<wd-checkbox-group v-model="value4" inline>
<wd-checkbox :value="1">京麦</wd-checkbox>
<wd-checkbox :value="2">商家后台</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block title="复选框组">
<wd-checkbox-group v-model="value5">
<wd-checkbox :value="1">京麦</wd-checkbox>
<wd-checkbox :value="2">商家后台</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block title="表单模式---复选框组" transparent>
<wd-checkbox-group v-model="value6" cell>
<wd-checkbox :value="1">京麦</wd-checkbox>
<wd-checkbox :value="2">商家后台</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block title="表单模式---复选框按钮组" transparent>
<wd-checkbox-group v-model="value7" cell shape="button">
<wd-checkbox :value="1" disabled>选项一</wd-checkbox>
<wd-checkbox :value="2">选项二</wd-checkbox>
<wd-checkbox :value="3">选项三</wd-checkbox>
<wd-checkbox :value="4">选项四</wd-checkbox>
<wd-checkbox :value="5">选项五</wd-checkbox>
<wd-checkbox :value="6">选项六</wd-checkbox>
<wd-checkbox :value="7">选项七</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block title="设置最小选中数量和最大选中数量" transparent>
<wd-checkbox-group v-model="value8" :min="1" :max="3" cell>
<wd-checkbox :value="1">京东</wd-checkbox>
<wd-checkbox :value="2">京麦</wd-checkbox>
<wd-checkbox :value="3">商家后台</wd-checkbox>
<wd-checkbox :value="4">营销中心</wd-checkbox>
</wd-checkbox-group>
</demo-block>
<demo-block title="大尺寸">
<wd-checkbox-group v-model="value9" inline size="large">
<wd-checkbox value="jingmai">京麦</wd-checkbox>
<wd-checkbox value="shop">商家后台</wd-checkbox>
</wd-checkbox-group>
<wd-checkbox-group v-model="value10" size="large" class="group">
<wd-checkbox value="jingmai">京麦</wd-checkbox>
<wd-checkbox value="shop">商家后台</wd-checkbox>
</wd-checkbox-group>
</demo-block>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const check1 = ref<boolean>(true)
const check2 = ref<boolean>(true)
const check3 = ref<boolean>(true)
const check4 = ref<boolean>(true)
const check5 = ref<boolean>(true)
const check6 = ref<boolean>(true)
const value1 = ref<number[]>([1, 3])
const value2 = ref<number[]>([1])
const value3 = ref<string>('京麦')
const value4 = ref<number[]>([1])
const value5 = ref<number[]>([])
const value6 = ref<number[]>([1])
const value7 = ref<number[]>([1])
const value8 = ref<number[]>([1])
const value9 = ref<string[]>([])
const value10 = ref<string[]>([])
function handleChange1(e) {
console.log(e)
}
</script>
<style lang="scss" scoped>
.group {
display: block;
margin-top: 10px;
padding: 10px 0;
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
</style>

View File

@ -1,7 +1,138 @@
<template>
<demo-block title="基本用法">
<view>
1内容项在3项以内且有比较重要的信息备选如付款类型选择等可考虑采用圆形组件因为会跟圆形复选框容易混淆且会造成当前表单页页面结构不统一
<text style="color: #f0883a">一般情况不建议使用点状单选</text>
</view>
<view style="margin-bottom: 10px">
2单选框基本使用未对高度进行扩充
<text style="color: #f0883a">一般情况建议使用表单--单选组</text>
</view>
<wd-radio-group v-model="value0" @change="change">
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
</demo-block>
<template>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" scoped>
</style>
<demo-block title="修改形状--button">
<wd-radio-group shape="button" v-model="value1" @change="change">
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="修改形状--dot">
<wd-radio-group shape="dot" v-model="value2" @change="change">
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="表单---单选组" transparent>
<wd-radio-group cell v-model="value3" @change="change">
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="表单--单选按钮组" transparent>
<wd-radio-group v-model="value4" cell shape="button">
<wd-radio :value="1">选项一</wd-radio>
<wd-radio :value="2">选项二</wd-radio>
<wd-radio :value="3">选项三</wd-radio>
<wd-radio :value="4">选项四</wd-radio>
<wd-radio :value="5">选项五</wd-radio>
<wd-radio :value="6">选项六</wd-radio>
<wd-radio :value="7">选项七</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="同行展示">
<wd-radio-group v-model="value5" inline>
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
<view class="divider"></view>
<wd-radio-group v-model="value6" inline shape="dot">
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="修改选中颜色">
<wd-radio-group v-model="value7" @change="change">
<wd-radio :value="1" checked-color="#fa4350">京麦</wd-radio>
<wd-radio :value="2" checked-color="#fa4350">商家后台</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="禁用">
<wd-radio-group v-model="value1" disabled shape="dot">
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
<view class="divider"></view>
<wd-radio-group v-model="value1" disabled>
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
<view class="divider"></view>
<wd-radio-group v-model="value1" disabled shape="button">
<wd-radio :value="1">京麦</wd-radio>
<wd-radio :value="2">商家后台</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="大尺寸">
<wd-radio-group v-model="value8" size="large">
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
<view class="divider"></view>
<wd-radio-group v-model="value9" size="large" shape="dot">
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
<view class="divider"></view>
<wd-radio-group v-model="value10" size="large" inline custom-class="group">
<wd-radio :value="1">单选框1</wd-radio>
<wd-radio :value="2">单选框2</wd-radio>
</wd-radio-group>
</demo-block>
<demo-block title="radio的props比radioGroup的优先级高">
<wd-radio-group hape="button" disabled checked-color="#fa4350" v-model="value11" @change="change">
<wd-radio :value="1" checked-color="#000" :disabled="false">商家前端</wd-radio>
<wd-radio :value="2" :disabled="false">京麦</wd-radio>
<wd-radio :value="3">商家智能</wd-radio>
</wd-radio-group>
</demo-block>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const value = ref<number>(1)
const value0 = ref<number>(1)
const value1 = ref<number>(1)
const value2 = ref<number>(1)
const value3 = ref<number>(1)
const value4 = ref<number>(1)
const value5 = ref<number>(1)
const value6 = ref<number>(1)
const value7 = ref<number>(1)
const value8 = ref<number>(1)
const value9 = ref<number>(1)
const value10 = ref<number>(1)
const value11 = ref<number>(1)
function change(e) {
console.log(e)
}
</script>
<style lang="scss" scoped>
.divider {
margin-top: 10px;
margin-bottom: 10px;
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
</style>

View File

@ -6,7 +6,7 @@ VueComponent({
relations: {
'../checkbox/index': {
type: 'descendant',
linked (target) {
linked(target) {
this.children = this.children || []
this.children.push(target)
const index = this.children.indexOf(target)
@ -25,8 +25,8 @@ VueComponent({
prevChild && renderData(prevChild, { isLast: false })
}
},
unlinked (target) {
this.children = this.children.filter(child => child !== target)
unlinked(target) {
this.children = this.children.filter((child) => child !== target)
const index = this.children.indexOf(target)
if (this.children.length === 0) return
@ -49,16 +49,16 @@ VueComponent({
value: {
type: Array,
value: [],
observer (value, oldVal) {
observer(value, oldVal) {
// 传入的value数组中包括重复的元素这种情况非法。
if (new Set(value).size !== value.length) {
throw Error('checkboxGroup\'s bound value includes same value')
throw Error("checkboxGroup's bound value includes same value")
}
if (value.length < this.data.min) {
throw Error('checkboxGroup\'s bound value\'s length can\'t be less than min')
throw Error("checkboxGroup's bound value's length can't be less than min")
}
if (this.data.max !== 0 && value.length > this.data.max) {
throw Error('checkboxGroup\'s bound value\'s length can\'t be large than max')
throw Error("checkboxGroup's bound value's length can't be large than max")
}
// 每次value变化都会触发重新匹配选中项
this.children && this.children.length > 0 && this.resetChildren()
@ -68,37 +68,39 @@ VueComponent({
type: Boolean,
value: false,
// 以下内容用于解决父子组件样式隔离的问题 —— START
observer (value) {
this.children && this.children.forEach(child => {
child.setData({ cellBox: value })
})
observer(value) {
this.children &&
this.children.forEach((child) => {
child.setData({ cellBox: value })
})
}
// 以下内容用于解决父子组件样式隔离的问题 —— END
},
shape: {
type: String,
value: 'circle',
observer (value) {
observer(value) {
const type = ['circle', 'square', 'button']
if (type.indexOf(value) === -1) throw Error(`shape must be one of ${type.toString()}`)
this.updateAllChild({ shape: value })
// 以下内容用于解决父子组件样式隔离的问题 —— START
this.children && this.children.forEach(child => {
child.setData({ buttonBox: value === 'button' })
})
this.children &&
this.children.forEach((child) => {
child.setData({ buttonBox: value === 'button' })
})
// 以下内容用于解决父子组件样式隔离的问题 —— END
}
},
checkedColor: {
type: String,
observer (value) {
observer(value) {
this.updateAllChild({ checkedColor: value })
}
},
disabled: {
type: Boolean,
value: null,
observer () {
observer() {
// 当值修改时需要重新检测
this.resetChildren()
}
@ -106,7 +108,7 @@ VueComponent({
min: {
type: Number,
value: 0,
observer (value) {
observer(value) {
checkNumRange(value, 'min')
// 当值修改时需要重新检测
this.resetChildren()
@ -115,7 +117,7 @@ VueComponent({
max: {
type: Number,
value: 0,
observer (value) {
observer(value) {
checkNumRange(value, 'max')
// 当值修改时需要重新检测
this.resetChildren()
@ -124,13 +126,13 @@ VueComponent({
inline: {
type: Boolean,
value: false,
observer (value) {
observer(value) {
this.updateAllChild({ inline: value })
}
},
size: {
type: String,
observer (value) {
observer(value) {
this.updateAllChild({ size: value })
}
}
@ -140,27 +142,24 @@ VueComponent({
* @description 当和child建立relation后用checkboxGroup的props覆盖checkbox中props值为null的属性
* @param {Object} data 属性键值对
*/
updateAllChild (data) {
updateAllChild(data) {
const keys = Object.keys(data)
this.children && this.children.forEach(child => {
const will = {}
keys.forEach(key => {
if (
data[key] !== null &&
data[key] !== undefined &&
child.data[key] === null
) {
will[key] = data[key]
}
this.children &&
this.children.forEach((child) => {
const will = {}
keys.forEach((key) => {
if (data[key] !== null && data[key] !== undefined && child.data[key] === null) {
will[key] = data[key]
}
})
renderData(child, will)
})
renderData(child, will)
})
},
/**
* @description 子节点通知父节点修改子节点选中状态
* @param {any} value 子组件的标识符
*/
changeSelectState (value) {
changeSelectState(value) {
const temp = this.data.value
const index = temp.indexOf(value)
if (index > -1) {
@ -183,18 +182,19 @@ VueComponent({
* @description 修正子组件的 isChecked finalDisabled
* @param {array} values
*/
resetChildren (values) {
resetChildren(values) {
values = values || this.data.value
this.children && this.children.forEach(child => {
// value 对应的节点直接选中
const isChecked = values.indexOf(child.data.value) > -1
renderData(child, { isChecked })
child.checkDisabled()
})
this.children &&
this.children.forEach((child) => {
// value 对应的节点直接选中
const isChecked = values.indexOf(child.data.value) > -1
renderData(child, { isChecked })
child.checkDisabled()
})
}
},
beforeCreate () {
beforeCreate() {
// 设置防抖,避免修改 props(min, max, disabled) 触发多次
this.resetChildren = debounce(this.resetChildren, 50)
}
})
})

View File

@ -1,5 +1,176 @@
<template></template>
<template>
<view :class="`wd-checkbox-group ${shape === 'button' && cell ? 'is-button' : ''} ${customClass}`">
<slot />
</view>
</template>
<script></script>
<script lang="ts" setup>
import { getCurrentInstance, provide, watch } from 'vue'
import { checkNumRange, debounce, deepClone } from '../common/util'
<style lang="scss" scoped></style>
type checkShape = 'circle' | 'square' | 'button'
interface Props {
customClass?: string
modelValue: Array<string | number | boolean>
cell: boolean | null
shape: checkShape
checkedColor: string
disabled: boolean | null
min: number
max: number
inline: boolean | null
size: string
name: string
}
const props = withDefaults(defineProps<Props>(), {
customClass: '',
value: () => [],
shape: 'circle',
checkedColor: '#4D80F0',
disabled: false,
min: 0,
max: 0,
inline: false
})
const children: any[] = [] //
const { proxy } = getCurrentInstance() as any
/**
* @description 修正子组件的 isChecked finalDisabled
* @param {array} values
*/
const resetChildren = debounce(function (values) {
values = values || props.modelValue
children &&
children.forEach((child) => {
// value
const isChecked = values.indexOf(child.value) > -1
child.$.exposed.setChecked(isChecked)
})
}, 50)
watch(
() => props.modelValue,
(newValue) => {
// value
if (new Set(newValue).size !== newValue.length) {
// eslint-disable-next-line quotes
throw Error("checkboxGroup's bound value includes same value")
}
if (newValue.length < props.min) {
// eslint-disable-next-line quotes
throw Error("checkboxGroup's bound value's length can't be less than min")
}
if (props.max !== 0 && newValue.length > props.max) {
// eslint-disable-next-line quotes
throw Error("checkboxGroup's bound value's length can't be large than max")
}
// value
children && children.length > 0 && resetChildren()
},
{ deep: true, immediate: true }
)
watch(
() => props.shape,
(newValue) => {
const type = ['circle', 'square', 'button']
if (type.indexOf(newValue) === -1) throw Error(`shape must be one of ${type.toString()}`)
},
{ deep: true, immediate: true }
)
watch(
[() => props.disabled, () => props.inline, () => props.size],
() => {
resetChildren()
},
{ deep: true, immediate: true }
)
watch(
() => props.min,
(newValue) => {
checkNumRange(newValue, 'min')
resetChildren()
},
{ deep: true, immediate: true }
)
watch(
() => props.max,
(newValue) => {
checkNumRange(newValue, 'max')
resetChildren()
},
{ deep: true, immediate: true }
)
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child) {
const hasChild = children.findIndex((check) => {
return check.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
const index = children.findIndex((check) => {
return check.$.uid === child.$.uid
})
// isFirsttrue
if (index === 0) {
child.$.exposed.setFirst(true)
}
// isLasttrueisLast
if (index === children.length - 1) {
child.$.exposed.setLast(true)
const [prevChild] = children.slice(-2, -1)
prevChild && prevChild.$.exposed.setLast(false)
}
resetChildren()
}
/**
* @description 子节点通知父节点修改子节点选中状态
* @param {any} value 子组件的标识符
*/
function changeSelectState(value) {
const temp: (string | number | boolean)[] = deepClone(props.modelValue)
const index = temp.indexOf(value)
if (index > -1) {
// value
temp.splice(index, 1)
} else {
// value
temp.push(value)
}
emit('update:modelValue', temp)
// disabled
resetChildren(temp)
emit('change', {
value: temp
})
}
provide('checkGroup', proxy)
defineExpose({
setChild,
changeSelectState,
children
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -36,7 +36,7 @@
margin: 0;
opacity: 0;
}
@include e(btn-check) {
@include edeep(btn-check) {
display: inline-block;
font-size: $-checkbox-icon-size;
margin-right: 4px;
@ -58,7 +58,7 @@
font-size: $-checkbox-label-fs;
color: $-checkbox-label-color;
}
@include e(check) {
@include edeep(check) {
color: $-checkbox-check-color;
font-size: $-checkbox-icon-size;
opacity: 0;
@ -70,7 +70,7 @@
background: currentColor;
border-color: currentColor;
}
.wd-checkbox__check {
:deep(.wd-checkbox__check) {
opacity: 1;
position: absolute;
left: 0;
@ -195,7 +195,7 @@
.wd-checkbox__label {
font-size: $-checkbox-large-label-fs;
}
.wd-checkbox__check {
:deep(.wd-checkbox__check) {
top: 0;
left: 1px;
}

View File

@ -1,8 +1,251 @@
<template>
<view
:class="`wd-checkbox ${innerCell ? 'is-cell-box' : ''} ${innerShape === 'button' ? 'is-button-box' : ''} ${isChecked ? 'is-checked' : ''} ${
isFirst ? 'is-first-child' : ''
} ${isLast ? 'is-last-child' : ''} ${innerInline ? 'is-inline' : ''} ${innerShape === 'button' ? 'is-button' : ''} ${
innerDisabled ? 'is-disabled' : ''
} ${innerSize ? 'is-' + innerSize : ''} ${customClass}`"
@click="toggle"
>
<!--shape为button时移除wd-checkbox__shape只保留wd-checkbox__label-->
<view
v-if="innerShape !== 'button'"
:class="`wd-checkbox__shape ${innerShape === 'square' ? 'is-square' : ''} ${customShapeClass}`"
:style="isChecked && !innerDisabled && innerCheckedColor ? 'color :' + innerCheckedColor : ''"
>
<wd-icon custom-class="wd-checkbox__check" name="check-bold" size="14px" color="#ffffff" />
</view>
<!--shape为button时只保留wd-checkbox__label-->
<view
:class="`wd-checkbox__label ${customLabelClass}`"
:style="isChecked && innerShape === 'button' && !innerDisabled && innerCheckedColor ? 'color:' + innerCheckedColor : ''"
>
<!--button选中时展示的icon-->
<wd-icon v-if="innerShape === 'button' && isChecked" custom-class="wd-checkbox__btn-check" name="check-bold" size="14px" />
<!--文案-->
<view class="wd-checkbox__txt" :style="maxWidth ? 'max-width:' + maxWidth : ''">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
<script lang="ts">
export default {
options: {
virtualHost: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, getCurrentInstance, inject, onBeforeMount, onMounted, ref, watch } from 'vue'
type checkShape = 'circle' | 'square' | 'button'
interface Props {
customLabelClass?: string
customShapeClass?: string
customClass?: string
value: string | number | boolean
shape: checkShape
checkedColor: string
disabled: boolean | null
trueValue: string | number | boolean
falseValue: string | number | boolean
size: string
maxWidth: string
name: string
}
const props = withDefaults(defineProps<Props>(), {
customLabelClass: '',
customShapeClass: '',
customClass: '',
trueValue: true,
falseValue: false,
disabled: null
})
const isChecked = ref<boolean>(false) //
const inited = ref<boolean>(false)
// last-childfirst-child
const isFirst = ref<boolean>(false)
const isLast = ref<boolean>(false)
const parent = inject<any>('checkGroup')
const { proxy } = getCurrentInstance() as any
watch(
() => props.value,
(newValue) => {
if (newValue === null || newValue === undefined) {
// eslint-disable-next-line prettier/prettier
throw Error('checkbox\'s value can\'t be null or undefined')
}
if (!inited.value) return
// 使
if (parent && parent.$.exposed.resetChildren) {
checkName()
return parent.$.exposed.resetChildren()
}
isChecked.value = newValue === props.trueValue
}
)
watch(
() => props.shape,
(newValue) => {
const type = ['circle', 'square', 'button']
if (type.indexOf(newValue) === -1) throw Error(`shape must be one of ${type.toString()}`)
}
)
const innerShape = computed(() => {
if (!props.shape && parent && parent.shape) {
return parent.shape
} else {
return props.shape
}
})
const innerCheckedColor = computed(() => {
if (!props.checkedColor && parent && parent.checkedColor) {
return parent.checkedColor
} else {
return props.checkedColor
}
})
const innerDisabled = computed(() => {
let innerDisabled = props.disabled
if (parent) {
if (
// max group
(parent.max && parent.modelValue.length >= parent.max && !isChecked.value) ||
// min group
(parent.min && parent.modelValue.length <= parent.min && isChecked.value) ||
// disabled disabled
props.disabled === true ||
// disabled disabled
(parent.disabled && props.disabled === null)
) {
innerDisabled = true
}
}
return innerDisabled
})
const innerInline = computed(() => {
if (parent && parent.inline) {
return parent.inline
} else {
return false
}
})
const innerCell = computed(() => {
if (parent && parent.cell) {
return parent.cell
} else {
return false
}
})
const innerSize = computed(() => {
if (!props.size && parent && parent.size) {
return parent.size
} else {
return props.size
}
})
onBeforeMount(() => {
// eslint-disable-next-line quotes
if (props.value === null) throw Error("checkbox's value must be set")
inited.value = true
})
onMounted(() => {
// isChecked
if (!parent) {
isChecked.value = props.value === props.trueValue
isFirst.value = true
isLast.value = true
}
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
isChecked.value = props.value === parent.modelValue
}
})
const emit = defineEmits(['change', 'update:value'])
/**
* @description 检测checkbox绑定的value是否和其它checkbox的value冲突
* @param {Object} self 自身
* @param myName 自己的标识符
*/
function checkName() {
parent &&
parent.$.exposed.children &&
parent.$.exposed.children.forEach((child) => {
if (child.$.uid !== proxy.$.uid && child.value === props.value) {
throw Error(`The checkbox's bound value: ${props.value} has been used`)
}
})
}
/**
* @description 点击checkbox的Event handle
*/
function toggle() {
if (innerDisabled.value) return
// 使checkboxchange
if (parent) {
emit('change', {
value: !isChecked.value
})
parent.$.exposed.changeSelectState(props.value)
} else {
const newVal = props.value === props.trueValue ? props.falseValue : props.trueValue
emit('update:value', newVal)
emit('change', {
value: newVal
})
}
}
/**
* 设置是否为第一个
* @param first
*/
function setFirst(first: boolean) {
isFirst.value = first
}
/**
* 设置是否为最后一个
* @param first
*/
function setLast(last: boolean) {
isLast.value = last
}
/**
* 设置是否选中
* @param checked 是否选中
*/
function setChecked(checked: boolean) {
isChecked.value = checked
}
defineExpose({
setFirst,
setLast,
setChecked
})
</script>
<style lang="scss" scoped>
</style>
@import './index.scss';
</style>

View File

@ -1,6 +1,126 @@
<template>
</template>
<script>
</script>
<style lang="scss" scoped>
</style>
<view :class="`wd-radio-group ${customClass} ${cell && shape === 'button' ? 'is-button' : ''}`">
<slot />
</view>
</template>
<script lang="ts" setup>
import { getCurrentInstance, provide, watch } from 'vue'
type RadioShape = 'dot' | 'button' | 'check'
interface Props {
customClass?: string
modelValue: string | number | boolean
shape: RadioShape
checkedColor: string
disabled: boolean
cell: boolean
size: string
inline: boolean
}
const props = withDefaults(defineProps<Props>(), {
customClass: '',
shape: 'check',
size: '',
checkedColor: '#4D80F0',
disabled: false,
inline: false,
cell: false
})
const children: any[] = [] //
const { proxy } = getCurrentInstance() as any
watch(
() => props.modelValue,
(newValue, oldValue) => {
// (nullundefinedundefinedvoid (0)undefined)
if (newValue === null || newValue === undefined) {
// eslint-disable-next-line quotes
throw Error("value can't be null or undefined")
}
// propwatchrelationsready
if (oldValue !== null) {
// radioGroupvaluevalueradio
changeSelect(newValue)
}
},
{ deep: true, immediate: true }
)
watch(
() => props.shape,
(newValue) => {
// type: 'dot', 'button', 'check'
const type = ['check', 'dot', 'button']
if (type.indexOf(newValue) === -1) throw Error(`shape must be one of ${type.toString()}`)
},
{ deep: true, immediate: true }
)
const emit = defineEmits(['change', 'update:modelValue'])
/**
* 设置子项
* @param child
*/
function setChild(child) {
checkValue(child)
const hasChild = children.findIndex((radio) => {
return radio.$.uid === child.$.uid
})
if (hasChild <= -1) {
children.push(child)
} else {
children[hasChild] = child
}
}
/**
* @description 检测radio绑定的value是否已经被其他节点绑定
*/
function checkValue(child) {
children.forEach((radio) => {
const value = child.value
if (radio.$.uid !== child.$.uid && radio.value === value) {
throw Error(`The radio's bound value: ${value} has been used `)
}
})
}
/**
* 修改选中的radio
* @param value - radio绑定的value
* @param old - 老节点默认为已经被选中的节点
*/
function changeSelect(value) {
// radio
if (!children || children.length === 0 || value === null) {
return
}
children.forEach((child) => {
child.$.exposed.setChecked(child.value === value)
})
}
/**
* @description 处理radio子节点通知
*/
function handleClick(value) {
emit('update:modelValue', value)
emit('change', {
value
})
}
defineExpose({
setChild,
handleClick,
changeSelect,
checkValue,
children
})
provide('radioGroup', proxy)
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -1,7 +1,7 @@
<!--
* @Author: weisheng
* @Date: 2023-06-12 18:40:58
* @LastEditTime: 2023-07-01 18:13:00
* @LastEditTime: 2023-07-10 10:55:11
* @LastEditors: weisheng
* @Description:
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-radio\wd-radio.vue
@ -9,17 +9,23 @@
-->
<template>
<view
class="wd-radio {{cell ? 'is-cell-radio':''}} {{cell && shape == 'button' ? 'is-button-radio':''}} {{ size && ('is-' + size) }} {{ inline ? 'is-inline' : '' }} {{ isChecked ? 'is-checked' : '' }} {{shape !== 'check' ? 'is-'+shape : '' }} {{ disabled ? 'is-disabled' : ''}} custom-class"
bindtap="handleClick"
:class="`wd-radio ${innerCell ? 'is-cell-radio' : ''} ${innerCell && innerShape == 'button' ? 'is-button-radio' : ''} ${
innerSize ? 'is-' + innerSize : ''
} ${innerInline ? 'is-inline' : ''} ${isChecked ? 'is-checked' : ''} ${innerShape !== 'check' ? 'is-' + innerShape : ''} ${
innerDisabled ? 'is-disabled' : ''
} ${customClass}`"
@click="handleClick"
>
<view
class="wd-radio__label"
style="{{ maxWidth ? 'max-width:'+ maxWidth : '' }}; {{(isChecked && shape === 'button' && !disabled ) ? 'color :' + checkedColor : '' }} "
:style="`${maxWidth ? 'max-width:' + maxWidth : ''}; ${
isChecked && innerShape === 'button' && !innerDisabled ? 'color :' + innerCheckedColor : ''
}`"
>
<slot></slot>
</view>
<view class="wd-radio__shape" style="{{ (isChecked && !disabled) ? 'color: ' + checkedColor : '' }}">
<wd-icon v-if="{{shape === 'check'}}" style="{{ (isChecked && !disabled) ? 'color: ' + checkedColor : '' }}" name="check"></wd-icon>
<view class="wd-radio__shape" :style="isChecked && !disabled ? 'color: ' + innerCheckedColor : ''">
<wd-icon v-if="innerShape === 'check'" :style="isChecked && !disabled ? 'color: ' + innerCheckedColor : ''" name="check"></wd-icon>
</view>
</view>
</template>
@ -31,7 +37,142 @@ export default {
}
}
</script>
<script lang="ts" setup></script>
<script lang="ts" setup>
import { computed, getCurrentInstance, inject, onBeforeMount, ref, watch } from 'vue'
type RadioShape = 'dot' | 'button' | 'check'
interface Props {
customClass?: string
value: string | number | boolean
shape: RadioShape
checkedColor: string
disabled: boolean | null
cell: boolean | null
size: string
inline: boolean | null
maxWidth: string
}
const props = withDefaults(defineProps<Props>(), {
customClass: '',
disabled: null,
cell: null,
inline: null
})
const isChecked = ref<boolean>(false) //
const parent = inject<any>('radioGroup') || {}
const { proxy } = getCurrentInstance() as any
const innerShape = computed(() => {
if (!props.shape && parent && parent.shape) {
return parent.shape
} else {
return props.shape
}
})
const innerCheckedColor = computed(() => {
if (!props.checkedColor && parent && parent.checkedColor) {
return parent.checkedColor
} else {
return props.checkedColor
}
})
const innerDisabled = computed(() => {
if ((props.disabled === null || props.disabled === undefined) && parent && parent.disabled) {
return parent.disabled
} else {
return props.disabled
}
})
const innerInline = computed(() => {
if ((props.inline === null || props.inline === undefined) && parent && parent.inline) {
return parent.inline
} else {
return props.inline
}
})
const innerSize = computed(() => {
if (!props.size && parent && parent.size) {
return parent.size
} else {
return props.size
}
})
const innerCell = computed(() => {
if ((props.cell === null || props.cell === undefined) && parent && parent.cell) {
return parent.cell
} else {
return props.cell
}
})
watch(
() => props.value,
(newValue) => {
// (nullundefinedundefinedvoid (0)undefined)
if (newValue === null || newValue === undefined) {
// eslint-disable-next-line quotes
throw Error("value can't be null or undefined")
}
// relationsradiovalue,
if (!parent || newValue === null) return
// radio
parent.$.exposed.checkValue(proxy)
// valueradioGroupvalueradio
//
if (newValue === parent.modelValue) {
parent.$.exposed.changeSelect(newValue)
} else {
isChecked.value = false
}
}
)
watch(
() => props.shape,
(newValue) => {
// type: 'dot', 'button', 'check'
const type = ['check', 'dot', 'button']
if (type.indexOf(newValue) === -1) throw Error(`shape must be one of ${type.toString()}`)
}
)
onBeforeMount(() => {
if (parent) {
parent.$.exposed.setChild && parent.$.exposed.setChild(proxy)
isChecked.value = props.value === parent.modelValue
}
})
/**
* 点击子元素通知父元素触发change事件
*/
function handleClick() {
const { value } = props
if (!innerDisabled.value && parent && value !== null && value !== undefined) {
parent.$.exposed.handleClick(value)
// this.parent.setData({ value })
}
}
/**
* 设置选中状态
* @param checked 选中状态
*/
function setChecked(checked: boolean) {
isChecked.value = checked
}
defineExpose({
setChecked
})
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>