@ -0,0 +1,58 @@ | |||||
.container { | |||||
display: flex; | |||||
flex-direction: column; | |||||
height: 100vh; | |||||
width: 100%; | |||||
background-color: #f5f5f5; | |||||
/* position: relative; */ | |||||
} | |||||
/* 头部 */ | |||||
.header { | |||||
height: 15%; | |||||
width: 100%; | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: center; | |||||
padding: 1rem; | |||||
background-color: #044f7a; | |||||
} | |||||
.header_info{ | |||||
width: 100%; | |||||
height: 100%; | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: center; | |||||
} | |||||
.header_info_icons{ | |||||
display: flex; | |||||
align-items: center; | |||||
margin: 0 0.5rem 0 0.5rem; | |||||
} | |||||
.header_info_icon{ | |||||
display: flex; | |||||
height: 100%; | |||||
width: 10%; | |||||
width: 5%; | |||||
align-items: center; | |||||
justify-content: center; | |||||
} | |||||
.header_text{ | |||||
color: #bdd1dc; | |||||
font-size: 1rem; | |||||
left : 25%; | |||||
} | |||||
.header_texts{ | |||||
color: #bdd1dc; | |||||
font-size: 1rem; | |||||
} | |||||
.header_change{ | |||||
display: flex; | |||||
flex-direction: row; | |||||
align-items: center; | |||||
font-size: 1rem; | |||||
color: #bdd1dc; | |||||
} |
@ -0,0 +1,406 @@ | |||||
<template> | |||||
<view class="containers"> | |||||
<view class="header"> | |||||
<view class="header_info"> | |||||
<uni-icons class="header_info_icon" type="left" size="30" color="#c2d4de" @click.native.stop.prevent="toBack"></uni-icons> | |||||
<text class="header_text">付款信息</text> | |||||
</view> | |||||
</view> | |||||
<view class="idCard-box"> | |||||
<view class="reverse"> | |||||
<image :src="upLoadPositiveImg == ''?positiveImg:upLoadPositiveImg" @tap.prevent="uploadPositive"> | |||||
</image> | |||||
</view> | |||||
<view class="reverse"> | |||||
<image :src="upLoadReverseImg == ''?reverseImg:upLoadReverseImg" @tap.prevent="uploadReverse"> | |||||
</image> | |||||
</view> | |||||
<view class="reverse"> | |||||
<image :src="upLoadCarImg == ''?carImg:upLoadCarImg" @tap.prevent="uploadReverse"> | |||||
</image> | |||||
</view> | |||||
</view> | |||||
<!-- 客户基本信息 --> | |||||
<view class="section"> | |||||
<view class="con_size"> | |||||
<image src="/static/image/矩形 5315.png" class='con_size_img' ></image> | |||||
客户基本信息 | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">产品名称</text> | |||||
<text class="value">{{ selectedProduct }}</text> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">经销商</text> | |||||
<text class="value">{{ selectedStore }}</text> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">客户姓名</text> | |||||
<input class="input" v-model="clientInfo.name" placeholder="请输入客户姓名" /> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">居住地址</text> | |||||
<input class="input" v-model="clientInfo.address" placeholder="请输入居住地址" /> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">身份证号码</text> | |||||
<input class="input" v-model="clientInfo.idNumber" placeholder="请输入身份证号码" /> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">联系方式</text> | |||||
<input class="input" v-model="clientInfo.contact" placeholder="请输入联系方式" /> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">销售部门</text> | |||||
<l-radio-group class="radio-group" @change="handleDepartmentChange"> | |||||
<label class="radio-label" v-for="item in departments" :key="item"> | |||||
<l-radio :value="item" :checked="clientInfo.department === item" > | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:1.2rem; height:1.2rem;" src="/static/image/xuanzhong.png"></image> | |||||
<image v-show="!checked" style="width:1.2rem; height:1.2rem" src="/static/image/weixuanzhong.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<text style="margin-left:0.2rem;">{{ item }}</text> | |||||
</label> | |||||
</l-radio-group> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">促销销售顾问</text> | |||||
<input class="input" v-model="clientInfo.salesAdvisor" placeholder="请输入店铺销售顾问" /> | |||||
</view> | |||||
</view> | |||||
<!-- 车辆信息 --> | |||||
<view class="section"> | |||||
<view class="con_size"> | |||||
<image src="/static/image/矩形 5315.png" class='con_size_img' ></image>车辆信息</view> | |||||
<view class="form-item"> | |||||
<text class="label">车牌车系</text> | |||||
<picker class="picker" @change="bindCarBrandChange" :value="carBrandIndex" :range="carBrands"> | |||||
<view class="picker-text">{{ carBrands[carBrandIndex] || '请选择车辆品牌 >' }}</view> | |||||
</picker> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">车牌号</text> | |||||
<input class="input" v-model="vehicleInfo.plateNumber" placeholder="456123351" /> | |||||
</view> | |||||
</view> | |||||
<!-- 产品描述 --> | |||||
<view class="section"> | |||||
<view class="con_size"> | |||||
<image src="/static/image/矩形 5315.png" class='con_size_img' ></image>产品描述</view> | |||||
<view class="form-item"> | |||||
<text class="label">产品名称</text> | |||||
<text class="value">{{ selectedService }}</text> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">服务年龄</text> | |||||
<picker class="picker" @change="bindServiceAgeChange" :value="serviceAgeIndex" :range="serviceAges"> | |||||
<view class="picker-text">{{ serviceAges[serviceAgeIndex] || '请选择服务年龄 >' }}</view> | |||||
</picker> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">销售金额</text> | |||||
<input class="input" v-model="productInfo.salesAmount" placeholder="请输入销售金额" /> | |||||
</view> | |||||
</view> | |||||
<!-- 付款信息 --> | |||||
<view class="section"> | |||||
<view class="con_size"> | |||||
<image src="/static/image/矩形 5315.png" class='con_size_img' ></image>付款信息</view> | |||||
<view class="form-item"> | |||||
<text class="label">收款方</text> | |||||
<picker class="picker" @change="bindPayeeChange" :value="payeeIndex" :range="payees"> | |||||
<view class="picker-text">{{ payees[payeeIndex] || '请选择收款方 >' }}</view> | |||||
</picker> | |||||
</view> | |||||
<view class="form-item"> | |||||
<text class="label">付款时间</text> | |||||
<uni-datetime-picker class="timePiker" type="datetime" v-model="paymentInfo.paymentTime" :clear-icon="false" :border="false"> | |||||
</uni-datetime-picker> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
selectedProduct: "自动填写所选商品服务分类", | |||||
selectedStore: "自动填写所选择的门店", | |||||
selectedService: "自动填写所选择的服务分类", | |||||
clientInfo: { | |||||
name: '', | |||||
address: '', | |||||
idNumber: '', | |||||
contact: '', | |||||
department: '', | |||||
salesAdvisor: '' | |||||
}, | |||||
vehicleInfo: { | |||||
plateNumber: '456123351' | |||||
}, | |||||
productInfo: { | |||||
salesAmount: '' | |||||
}, | |||||
paymentInfo: { | |||||
paymentTime: '' | |||||
}, | |||||
departments: ['售前', '售后', '二网车'], | |||||
carBrands: ['品牌A', '品牌B', '品牌C'], | |||||
carBrandIndex: -1, | |||||
serviceAges: ['1年', '2年', '3年'], | |||||
serviceAgeIndex: -1, | |||||
payees: ['收款方A', '收款方B', '收款方C'], | |||||
payeeIndex: -1, | |||||
// 扫描 | |||||
positiveImg: '/static/image/组件 4 – 1.png',//自己图片路径 | |||||
upLoadPositiveImg: '', | |||||
// 反面身份证 | |||||
reverseImg: '/static/image/组 71663.png', //自己图片路径 | |||||
upLoadReverseImg: '', | |||||
// 行车驾驶证 | |||||
carImg: '/static/image/组件 2 – 1.png', //自己图片路径 | |||||
upLoadCarImg: '', | |||||
baidu_token:' '//百度token | |||||
} | |||||
}, | |||||
methods: { | |||||
toBack(){ | |||||
let canNavBack = getCurrentPages() | |||||
if( canNavBack && canNavBack.length>1) { | |||||
uni.navigateBack() | |||||
} else { | |||||
history.back(); | |||||
} | |||||
}, | |||||
handleDepartmentChange(e) { | |||||
this.clientInfo.department = e.detail.value | |||||
}, | |||||
bindCarBrandChange(e) { | |||||
this.carBrandIndex = e.detail.value | |||||
}, | |||||
bindServiceAgeChange(e) { | |||||
this.serviceAgeIndex = e.detail.value | |||||
}, | |||||
bindPayeeChange(e) { | |||||
this.payeeIndex = e.detail.value | |||||
}, | |||||
// file文件转base64 | |||||
getImageBase64(blob) { | |||||
return new Promise((resolve, reject) => { | |||||
const reader = new FileReader(); | |||||
reader.readAsDataURL(blob); | |||||
reader.onload = () => { | |||||
const base64 = reader.result; | |||||
resolve(base64); | |||||
} | |||||
reader.onerror = error => reject(error); | |||||
}); | |||||
}, | |||||
// 身份证正面上传 | |||||
uploadPositive() { | |||||
uni.chooseImage({ | |||||
count: 1, | |||||
sizeType: ['original', 'compressed'], | |||||
sourceType: ['album', 'camera'], | |||||
success: (res) => { | |||||
this.upLoadPositiveImg = res.tempFilePaths[0] | |||||
this.getImageBase64(res.tempFiles[0]).then(res => { | |||||
this.uploadIdentify(res) | |||||
}) | |||||
} | |||||
}) | |||||
}, | |||||
// 身份证反面上传 | |||||
uploadReverse() { | |||||
uni.chooseImage({ | |||||
count: 1, | |||||
sizeType: ['original', 'compressed'], | |||||
sourceType: ['album', 'camera'], | |||||
success: (res) => { | |||||
this.upLoadReverseImg = res.tempFilePaths[0] | |||||
this.getImageBase64(res.tempFiles[0]).then(res => { | |||||
this.uploadIdentify(res) | |||||
}) | |||||
} | |||||
}) | |||||
}, | |||||
// 获取百度token | |||||
getACSS_TOKEN() { | |||||
let that = this | |||||
uni.request({ | |||||
// url: '/baiduApi/oauth/2.0/token', | |||||
url: 'https://aip.baidubce.com/oauth/2.0/token', | |||||
method: 'POST', | |||||
data: { | |||||
grant_type: 'client_credentials', | |||||
client_id: '你的', | |||||
client_secret: '你的', | |||||
}, | |||||
header: { | |||||
'Content-Type': 'application/x-www-form-urlencoded' | |||||
}, | |||||
success: res => { | |||||
this.baidu_token = res.data.access_token | |||||
} | |||||
}); | |||||
}, | |||||
// 上传识别 | |||||
uploadIdentify(res) { | |||||
uni.request({ | |||||
url: '/baiduApi/rest/2.0/ocr/v1/idcard?access_token=' + this.baidu_token, | |||||
method: 'POST', | |||||
data: { | |||||
image: res, | |||||
id_card_side: 'back' // 身份证 正反面 front:身份证含照片的一面 back:身份证带国徽的一面 | |||||
}, | |||||
header: { | |||||
'Content-Type': 'application/x-www-form-urlencoded' | |||||
}, | |||||
success: res => { | |||||
console.log(res.data) | |||||
} | |||||
}) | |||||
}, | |||||
} | |||||
} | |||||
</script> | |||||
<style scoped lang="scss"> | |||||
.containers { | |||||
height: 100%; | |||||
width: 100%; | |||||
background-color: #fff; | |||||
overflow-y:scroll; | |||||
} | |||||
.container::-webkit-scrollbar { | |||||
display: none; | |||||
} | |||||
.section { | |||||
// margin-bottom: 30rpx; | |||||
border-bottom: 1rpx solid #eee; | |||||
margin: 0 1rem 0 1rem; | |||||
} | |||||
.form-item { | |||||
display: flex; | |||||
flex-direction: row; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
padding: 0.5rem 0; | |||||
border-bottom: 0.1rem solid #f2f2f2; | |||||
color: #767676; | |||||
} | |||||
.label { | |||||
font-size: 28rpx; | |||||
color: #666; | |||||
width: 200rpx; | |||||
} | |||||
.input { | |||||
flex: 1; | |||||
text-align: right; | |||||
font-size: 28rpx; | |||||
color: #737373; | |||||
} | |||||
.picker { | |||||
flex: 1; | |||||
} | |||||
.picker-text { | |||||
text-align: right; | |||||
color: #737373; | |||||
font-size: 0.8rem; | |||||
} | |||||
.radio-group { | |||||
display: flex; | |||||
flex: 1; | |||||
justify-content: flex-end; | |||||
flex-direction: row; | |||||
} | |||||
.radio-label { | |||||
padding: 0; | |||||
margin-left: 10rpx; | |||||
display: flex; | |||||
flex-direction: row; | |||||
font-size: 1rem; | |||||
transform: scale(0.7); | |||||
} | |||||
.value { | |||||
flex: 1; | |||||
text-align: right; | |||||
color: #737373; | |||||
} | |||||
.timePiker{ | |||||
display: flex; | |||||
justify-content: center; | |||||
/* align-items: center; */ | |||||
color: #3f3f3f; | |||||
} | |||||
.idCard-box { | |||||
margin-top: 10%; | |||||
width: 100%; | |||||
height: 50%; | |||||
display: flex; | |||||
flex-direction: row; | |||||
/* background-color: red; */ | |||||
flex-wrap: wrap; | |||||
.reverse { | |||||
height: 30%; | |||||
width: 40%; | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: center; | |||||
// background-color: blue; | |||||
margin: 0 5% 0 5%; | |||||
image { | |||||
width: 100%; | |||||
height: 100%; | |||||
} | |||||
} | |||||
} | |||||
/* 表单分区样式 */ | |||||
.con_size { | |||||
font-size: 1rem; | |||||
font-weight: bold; | |||||
margin: 1rem 0; | |||||
color: #000000; | |||||
display: flex; | |||||
flex-direction: row; | |||||
} | |||||
.con_size_img{ | |||||
height: 100%; | |||||
width: 2%; | |||||
margin-right: 2%; | |||||
} | |||||
</style> |
@ -0,0 +1,27 @@ | |||||
<template> | |||||
<view class="container"> | |||||
<view class="header"> | |||||
<view class="header_info"> | |||||
<uni-icons class="header_info_icon" type="left" size="30" color="#c2d4de" :size="1" @click="toBack"></uni-icons> | |||||
<text class="header_text">订单列表</text> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
} | |||||
}, | |||||
methods: { | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
</style> |
@ -1,88 +0,0 @@ | |||||
<template> | |||||
<view class="container"> | |||||
<view class="form"> | |||||
<input class="input" type="text" placeholder="请输入您的账号" v-model="username" /> | |||||
<input class="input" type="password" placeholder="请输入您的密码" v-model="password" /> | |||||
<view class="agreement"> | |||||
<checkbox-group @change="handleAgreementChange"> | |||||
<label> | |||||
<checkbox value="agree" /> 阅读并同意《用户协议》和《隐私政策》 | |||||
</label> | |||||
</checkbox-group> | |||||
</view> | |||||
<button class="button" @click="handleLogin">登录</button> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
username: '', | |||||
password: '', | |||||
agreed: false | |||||
}; | |||||
}, | |||||
methods: { | |||||
handleAgreementChange(e) { | |||||
this.agreed = e.detail.value.includes('agree'); | |||||
}, | |||||
handleLogin() { | |||||
if (!this.agreed) { | |||||
uni.showToast({ | |||||
title: '请先同意用户协议和隐私政策', | |||||
icon: 'none' | |||||
}); | |||||
return; | |||||
} | |||||
// 这里可以添加登录逻辑 | |||||
uni.showToast({ | |||||
title: '登录成功', | |||||
icon: 'success' | |||||
}); | |||||
} | |||||
} | |||||
}; | |||||
</script> | |||||
<style> | |||||
.container { | |||||
display: flex; | |||||
justify-content: center; | |||||
align-items: center; | |||||
height: 100vh; | |||||
background-color: #f5f5f5; | |||||
} | |||||
.form { | |||||
width: 80%; | |||||
background-color: #fff; | |||||
padding: 20px; | |||||
border-radius: 8px; | |||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | |||||
} | |||||
.input { | |||||
width: 100%; | |||||
height: 40px; | |||||
margin-bottom: 15px; | |||||
padding: 10px; | |||||
border: 1px solid #ccc; | |||||
border-radius: 4px; | |||||
} | |||||
.agreement { | |||||
margin-bottom: 15px; | |||||
} | |||||
.button { | |||||
width: 100%; | |||||
height: 40px; | |||||
background-color: #007aff; | |||||
color: #fff; | |||||
border: none; | |||||
border-radius: 4px; | |||||
font-size: 16px; | |||||
} | |||||
</style> |
@ -1,22 +0,0 @@ | |||||
<template> | |||||
<view> | |||||
你好 | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
} | |||||
}, | |||||
methods: { | |||||
} | |||||
} | |||||
</script> | |||||
<style> | |||||
</style> |
@ -0,0 +1,14 @@ | |||||
## 0.0.7(2025-02-09) | |||||
- fix: 修复uniappx app端自定义颜色的问题 | |||||
## 0.0.6(2024-12-16) | |||||
- fix: 修复vue2 微信小程序自定义颜色问题 | |||||
## 0.0.5(2024-12-16) | |||||
- feat: 兼容vue2 | |||||
## 0.0.4(2024-12-16) | |||||
- chore: 更新文档 | |||||
## 0.0.3(2024-12-16) | |||||
- chore: 更新文档 | |||||
## 0.0.2(2024-12-16) | |||||
- feat: 增加fontsize | |||||
## 0.0.1(2024-12-11) | |||||
- init |
@ -0,0 +1,48 @@ | |||||
<template> | |||||
<view class="l-radio-group" :class="'l-radio-group--'+ direction"> | |||||
<slot /> | |||||
</view> | |||||
</template> | |||||
<script lang="uts" setup> | |||||
import { RadioGroupProps } from './type'; | |||||
const emit = defineEmits(['change', 'update:modelValue']) | |||||
const props = withDefaults(defineProps<RadioGroupProps>(), { | |||||
allowUncheck: false, | |||||
disabled: false, | |||||
direction: 'horizontal' | |||||
// size: 'medium' | |||||
}) | |||||
const innerValue = ref<any|null>(null); | |||||
const groupValue = computed({ | |||||
set(value: any|null) { | |||||
innerValue.value = value | |||||
emit('update:modelValue', value) | |||||
emit('change', value) | |||||
}, | |||||
get():any|null { | |||||
return props.value ?? props.modelValue ?? props.defaultValue ?? innerValue.value | |||||
} | |||||
} as WritableComputedOptions<any|null>) | |||||
const handleRadioChange = (val: any) => { | |||||
if (props.allowUncheck && val == groupValue.value) { | |||||
groupValue.value = '' | |||||
} else { | |||||
groupValue.value = val | |||||
} | |||||
} | |||||
provide('limeRadioGroupProps', props) | |||||
provide('limeRadioGroupValue', groupValue) | |||||
provide('limeRadioGroupChange', handleRadioChange) | |||||
</script> | |||||
<style lang="scss"> | |||||
.l-radio-group { | |||||
flex-direction: row; | |||||
} | |||||
.l-radio-group--vertical { | |||||
flex-direction: column; | |||||
} | |||||
</style> |
@ -0,0 +1,70 @@ | |||||
<template> | |||||
<view class="l-radio-group" :class="'l-radio-group--'+ direction"> | |||||
<slot /> | |||||
</view> | |||||
</template> | |||||
<script lang="ts"> | |||||
// @ts-nocheck | |||||
import { defineComponent, provide, ref, computed } from '@/uni_modules/lime-shared/vue' | |||||
import radioGroupProps from './props' | |||||
const name = 'l-radio-group' | |||||
export default defineComponent({ | |||||
name, | |||||
props: radioGroupProps, | |||||
emits: ['update:value', 'update:modelValue', 'change', 'input'], | |||||
setup(props, { emit }) { | |||||
const innerValue = ref<any|null>(null); | |||||
const groupValue = computed({ | |||||
set(value: any|null) { | |||||
innerValue.value = value; | |||||
emit('update:modelValue', value) | |||||
emit('change', value) | |||||
// #ifdef VUE2 | |||||
emit('input', value) | |||||
// #endif | |||||
}, | |||||
get():any|null { | |||||
return props.value || props.name || props.modelValue || props.defaultValue || innerValue.value | |||||
} | |||||
} as WritableComputedOptions<any|null>) | |||||
const handleRadioChange = (val: any) => { | |||||
if (props.allowUncheck && val == groupValue.value) { | |||||
groupValue.value = '' | |||||
} else { | |||||
groupValue.value = val | |||||
} | |||||
} | |||||
provide('limeRadioGroupProps', props) | |||||
provide('limeRadioGroupValue', groupValue) | |||||
provide('limeRadioGroupChange', handleRadioChange) | |||||
} | |||||
}) | |||||
</script> | |||||
<style lang="scss"> | |||||
// .l-radio-group { | |||||
// display: flex; | |||||
// flex-direction: row; | |||||
// } | |||||
// .l-radio-group--vertical { | |||||
// flex-direction: column; | |||||
// } | |||||
.l-radio-group { | |||||
// background-color: antiquewhite; | |||||
} | |||||
.l-radio-group--vertical { | |||||
:deep(.l-radio) { | |||||
display: flex; | |||||
// line-height: 64rpx; | |||||
} | |||||
:deep(l-radio) { | |||||
display: flex; | |||||
// line-height: 64rpx; | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,69 @@ | |||||
// @ts-nocheck | |||||
export default { | |||||
/** 是否允许取消选中 */ | |||||
allowUncheck: Boolean, | |||||
/** 是否禁用全部子单选框。默认为 false。RadioGroup.disabled 优先级低于 Radio.disabled */ | |||||
disabled: Boolean, | |||||
/** HTML 元素原生属性 */ | |||||
name: { | |||||
type: String, | |||||
default: null, | |||||
}, | |||||
/** 选中的值 */ | |||||
value: { | |||||
type: [String, Number, Boolean], //as PropType<LRadioGroupProps['value']>, | |||||
default: null, | |||||
}, | |||||
modelValue: { | |||||
type: [String, Number, Boolean], //as PropType<LRadioGroupProps['value']>, | |||||
default: null, | |||||
}, | |||||
/** 选中的值,非受控属性 */ | |||||
defaultValue: { | |||||
type: [String, Number, Boolean] //as PropType<LRadioGroupProps['defaultValue']>, | |||||
}, | |||||
checkedColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconBgColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconBorderColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconDisabledColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconDisabledBgColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
icon: { | |||||
type: String, | |||||
default: 'circle' | |||||
}, //?: 'circle' | 'line' | 'dot'; | |||||
size: { | |||||
type: String, | |||||
default: 'medium' | |||||
}, //?: 'small' | 'medium' | 'large'; | |||||
iconSize: { | |||||
type: String, | |||||
defalut: null | |||||
}, | |||||
fontSize: { | |||||
type: String, | |||||
defalut: null | |||||
}, | |||||
direction: { | |||||
type: String, | |||||
default: 'horizontal' | |||||
}, //'horizontal' | 'vertical' | |||||
gap: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
} |
@ -0,0 +1,41 @@ | |||||
// @ts-nocheck | |||||
export type RadioValue = any//string | number | boolean; | |||||
export interface RadioGroupProps { | |||||
/** | |||||
* 是否允许取消选中 | |||||
*/ | |||||
allowUncheck: boolean; | |||||
/** | |||||
* 是否禁用全部子单选框。默认为 false。RadioGroup.disabled 优先级低于 Radio.disabled | |||||
*/ | |||||
disabled: boolean; | |||||
/** | |||||
* HTML 元素原生属性 | |||||
*/ | |||||
name ?: string; | |||||
/** | |||||
* 选中的值 | |||||
*/ | |||||
value ?: RadioValue; | |||||
/** | |||||
* 选中的值,非受控属性 | |||||
*/ | |||||
defaultValue ?: RadioValue; | |||||
/** | |||||
* 选中的值 | |||||
*/ | |||||
modelValue ?: RadioValue; | |||||
checkedColor?: string; | |||||
iconBgColor?: string; | |||||
iconBorderColor?: string; | |||||
iconDisabledColor?: string; | |||||
iconDisabledBgColor?: string; | |||||
icon ?: 'circle' | 'line' | 'dot'; | |||||
size ?: 'small' | 'medium' | 'large'; | |||||
iconSize ?: string; | |||||
fontSize ?: string; | |||||
gap ?: string; | |||||
direction: 'horizontal' | 'vertical' | |||||
} | |||||
@ -0,0 +1,173 @@ | |||||
@import '~@/uni_modules/lime-style/index.scss'; | |||||
@import '~@/uni_modules/lime-style/functions.scss'; | |||||
$radio: #{$prefix}-radio; | |||||
$icon: #{$radio}__icon; | |||||
$radio-icon-size: create-var(radio-icon-size, 40rpx); | |||||
$radio-font-size: create-var(radio-font-size, 32rpx); | |||||
$radio-small-icon-size: create-var(radio-small-icon-size, 28rpx); | |||||
$radio-small-font-size: create-var(radio-small-font-size, 30rpx); | |||||
$radio-large-icon-size: create-var(radio-large-icon-size, 44rpx); | |||||
$radio-large-font-size: create-var(radio-large-font-size, 36rpx); | |||||
$radio-icon-border-width: create-var(radio-icon-border-width, 1rpx); | |||||
// $radio-icon-border-color: var(--l-radio-border-color, $border-color); | |||||
$radio-icon-border-radius: create-var(radio-icon-border-radius, 999rpx); | |||||
$radio-icon-bg-color: create-var(radio-icon-bg-color, white); | |||||
$radio-icon-border-color: create-var(radio-border-icon-color, $gray-5); | |||||
$radio-icon-disabled-color: create-var(radio-icon-disabled-color, $gray-5); | |||||
$radio-icon-disabled-bg-color: create-var(radio-icon-disabled-bg-color, $gray-1); | |||||
$radio-icon-checked-color: create-var(radio-icon-checked-color, $primary-color); | |||||
.#{$radio} { | |||||
/* #ifndef UNI-APP-X */ | |||||
display: inline-flex; | |||||
/* #endif */ | |||||
flex-direction: row; | |||||
align-items: center; | |||||
&__icon { | |||||
position: relative; | |||||
box-sizing: border-box; | |||||
width: $radio-icon-size; | |||||
height: $radio-icon-size; | |||||
align-self: center; | |||||
transition-property: all; | |||||
transition-duration: 200ms; | |||||
transition-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
box-sizing: border-box; | |||||
position: absolute; | |||||
top: 50%; | |||||
left: 50%; | |||||
transform: translate(-50%,-50%); | |||||
opacity: 0; | |||||
content: ""; | |||||
transition-property: all; | |||||
transition-duration: 200ms; | |||||
transition-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); | |||||
} | |||||
/* #endif */ | |||||
&--dot,&--circle { | |||||
background-color: $radio-icon-bg-color; | |||||
border: $radio-icon-border-width solid $radio-icon-border-color; | |||||
border-radius: $radio-icon-border-radius; | |||||
} | |||||
&--circle { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
top: 48%; | |||||
left: 24%; | |||||
display: table; | |||||
width: divide(100%, 20) * 7; | |||||
height: divide(100%, 20) * 12; | |||||
border: calc(#{$radio-icon-size} / 7) solid transparent; | |||||
border-top: 0; | |||||
border-inline-start: 0; | |||||
transform: rotate(45deg) scale(0) translate(-50%,-50%); | |||||
content: ""; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--circle#{&}--checked { | |||||
background-color: $radio-icon-checked-color; | |||||
border-color: $radio-icon-checked-color; | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
opacity: 1; | |||||
transform: rotate(45deg) scale(1) translate(-50%,-50%); | |||||
border-color: white; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--dot { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
background-color: white; | |||||
border-radius: $radio-icon-border-radius; | |||||
transform: scale(0) translate(-50%,-50%); | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--dot#{&}--checked{ | |||||
background-color: $radio-icon-checked-color; | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
border-color: $radio-icon-checked-color; | |||||
&:after{ | |||||
opacity: 1; | |||||
width: 44%; | |||||
height: 44%; | |||||
transform: scale(1) translate(-50%,-50%); | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--line { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
top: 46%; | |||||
left: 0%; | |||||
inset-inline-start: 10%; | |||||
display: table; | |||||
width: divide(100%, 14) * 7; | |||||
height: divide(100%, 14) * 12; | |||||
border: calc(#{$radio-icon-size} / 7) solid transparent; | |||||
border-top: 0; | |||||
border-inline-start: 0; | |||||
transform: rotate(45deg) scale(0) translate(-50%,-50%); | |||||
content: ""; | |||||
transition-timing-function: cubic-bezier(0.78, 0.14, 0.15, 0.86); | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--line#{&}--checked{ | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after{ | |||||
opacity: 1; | |||||
transform: rotate(45deg) scale(1) translate(-50%,-50%); | |||||
border-color: $radio-icon-checked-color; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--circle#{&}--disabled, &--dot#{&}--disabled { | |||||
border-color: $radio-icon-disabled-color; | |||||
background-color: $radio-icon-disabled-bg-color; | |||||
} | |||||
&--circle#{&}--disabled#{&}--checked, &--dot#{&}--disabled#{&}--checked { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
border-color: $radio-icon-disabled-color; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--dot#{&}--disabled#{&}--checked { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
background-color: $radio-icon-disabled-color; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
&--line#{&}--disabled#{&}--checked { | |||||
/* #ifndef APP-ANDROID || APP-IOS */ | |||||
&:after { | |||||
border-color: $radio-icon-disabled-color; | |||||
} | |||||
/* #endif */ | |||||
} | |||||
} | |||||
&__label { | |||||
padding-left: $spacer-xs; | |||||
// padding-right: $spacer-xs; | |||||
font-size: $radio-font-size; | |||||
color: $text-color-1; | |||||
&--disabled { | |||||
color: $radio-icon-disabled-color; | |||||
} | |||||
} | |||||
} | |||||
@ -0,0 +1,216 @@ | |||||
<template> | |||||
<view class="l-radio" :class="classes" :style="[styles]" @click="onClick"> | |||||
<slot name="radio" :checked="radioChecked" :disabled="isDisabled"> | |||||
<slot name="icon" :checked="radioChecked" :disabled="isDisabled"> | |||||
<view class="l-radio__icon" ref="iconRef" :class="iconClasses" :style="[iconStyle]"></view> | |||||
</slot> | |||||
<text class="l-radio__label" :class="labelClass" v-if="label != null || $slots['default'] != null"> | |||||
<slot>{{label}}</slot> | |||||
</text> | |||||
</slot> | |||||
</view> | |||||
</template> | |||||
<script lang="uts" setup> | |||||
import { RadioProps } from './type'; | |||||
// #ifndef APP | |||||
import type { ComputedRef } from 'vue'; | |||||
type ComputedRefImpl<T> = ComputedRef<T> | |||||
// #endif | |||||
const emit = defineEmits(['change', 'update:checked']) | |||||
const props = withDefaults(defineProps<RadioProps>(), { | |||||
allowUncheck: false, | |||||
checked: false, | |||||
disabled: false, | |||||
icon: 'circle', | |||||
size: 'medium' | |||||
}) | |||||
defineSlots<{ | |||||
radio(props : { checked : boolean, disabled: boolean}) : any, | |||||
icon(props : { checked : boolean, disabled: boolean}) : any, | |||||
default(props : { checked : boolean, disabled: boolean}) : any, | |||||
}>() | |||||
const name = 'l-radio' const radioGroupProps = inject<LRadioGroupComponentPublicInstance|null>('limeRadioGroupProps', null); const radioGroupValue = inject<ComputedRefImpl<any>|null>('limeRadioGroupValue', null); const radioGroupChange = inject<((value: any|null) => void)|null>('limeRadioGroupChange', null); | |||||
const modelValue = defineModel({type: Boolean, default: false}); | |||||
const innerChecked = computed({ | |||||
set(value: boolean){ | |||||
modelValue.value = value; | |||||
emit('change', value) | |||||
}, | |||||
get(): boolean{ | |||||
return props.checked || modelValue.value | |||||
}, | |||||
} as WritableComputedOptions<boolean>) | |||||
const isDisabled = computed(():boolean => (props.disabled || (radioGroupProps?.disabled ?? false))) | |||||
const groupDisabled = computed(():boolean|null => radioGroupProps?.disabled); | |||||
const radioChecked = computed(():boolean => innerChecked.value || (props.name != null && props.name == radioGroupValue?.value) || (props.value != null && props.value == radioGroupValue?.value)); | |||||
const finalAllowUncheck = computed(():boolean => props.allowUncheck || (radioGroupProps?.allowUncheck ?? false)); | |||||
const innerIcon = computed(():string => radioGroupProps?.icon ?? props.icon) | |||||
const innerSize = computed(():string => radioGroupProps?.size ?? props.size) | |||||
const innerIconSize = computed(():string|null => radioGroupProps?.iconSize ?? props.iconSize) | |||||
const innerFontSize = computed(():string|null => radioGroupProps?.fontSize ?? props.fontSize) | |||||
const innerCheckedColor = computed(():string|null => radioGroupProps?.checkedColor ?? props.checkedColor) | |||||
const innerIconBgColor = computed(():string => props.iconBgColor ?? radioGroupProps?.iconBgColor ?? 'white') | |||||
const innerIconBorderColor = computed(():string => props.iconBorderColor ?? radioGroupProps?.iconBorderColor ?? '#c5c5c5') | |||||
const innerIconDisabledColor = computed(():string => props.iconDisabledColor ?? radioGroupProps?.iconDisabledColor ?? '#c5c5c5') | |||||
const innerIconDisabledBgColor = computed(():string => props.iconDisabledBgColor ?? radioGroupProps?.iconDisabledBgColor ?? '#f3f3f3') | |||||
const classes = computed(():Map<string, boolean>=>{ | |||||
const cls = new Map<string, boolean>(); | |||||
cls.set(`${name}--disabled`, isDisabled.value) | |||||
return cls | |||||
}) | |||||
const iconClasses = computed(():Map<string, boolean>=>{ | |||||
const cls = new Map<string, boolean>(); | |||||
// #ifdef APP | |||||
cls.set(`${name}__icon--checked`, radioChecked.value && innerCheckedColor.value == null) | |||||
cls.set(`${name}__icon--${innerIcon.value}`, true) | |||||
cls.set(`${name}__icon--disabled`, isDisabled.value) | |||||
// #endif | |||||
// #ifndef APP | |||||
cls.set(`${name}__icon--checked`, radioChecked.value) | |||||
cls.set(`${name}__icon--${innerIcon.value}`, true) | |||||
cls.set(`${name}__icon--disabled`, isDisabled.value) | |||||
// #endif | |||||
return cls | |||||
}) | |||||
const labelClass = computed(():Map<string, boolean>=>{ | |||||
const cls = new Map<string, boolean>(); | |||||
cls.set(`${name}__label--disabled`, isDisabled.value) | |||||
return cls | |||||
}) | |||||
const styles = computed(():Map<string, any>=>{ | |||||
const style = new Map<string, any>(); | |||||
if(radioGroupProps != null && radioGroupProps.gap != null) { | |||||
style.set(radioGroupProps.direction == 'horizontal' ? 'margin-right' : 'margin-bottom', radioGroupProps.gap!) | |||||
} | |||||
// #ifndef APP | |||||
if(innerCheckedColor.value != null) { | |||||
style.set('--l-radio-icon-checked-color', innerCheckedColor.value!) | |||||
} | |||||
if(innerIconBorderColor.value != null) { | |||||
style.set('--l-radio-icon-border-color', innerIconBorderColor.value!) | |||||
} | |||||
if(innerIconDisabledColor.value != null) { | |||||
style.set('--l-radio-icon-disabled-color', innerIconDisabledColor.value!) | |||||
} | |||||
if(innerIconDisabledBgColor.value != null) { | |||||
style.set('--l-radio-icon-disabled-bg-color', innerIconDisabledBgColor.value!) | |||||
} | |||||
if(innerFontSize.value != null) { | |||||
style.set('--l-radio-font-size', innerFontSize.value!) | |||||
} | |||||
// #endif | |||||
return style | |||||
}) | |||||
const iconStyle = computed(():Map<string, any>=>{ | |||||
const style = new Map<string, any>(); | |||||
if(innerIconSize.value != null) { | |||||
style.set('width', innerIconSize.value!) | |||||
style.set('height', innerIconSize.value!) | |||||
// #ifndef APP | |||||
style.set('--l-radio-icon-size', innerIconSize.value!) | |||||
// #endif | |||||
} | |||||
if(innerCheckedColor.value != null) { | |||||
// #ifndef APP | |||||
style.set('--l-radio-icon-checked-color', innerCheckedColor.value!) | |||||
// #endif | |||||
// #ifdef APP | |||||
if(radioChecked.value && ['dot', 'circle'].includes(innerIcon.value)) { | |||||
style.set('background', innerCheckedColor.value!) | |||||
style.set('border-color', innerCheckedColor.value!) | |||||
} | |||||
if(!isDisabled.value && !radioChecked.value && ['dot', 'circle'].includes(innerIcon.value)) { | |||||
style.set('background', innerIconBgColor.value) | |||||
style.set('border-color', innerIconBorderColor.value) | |||||
} | |||||
if(isDisabled.value && ['dot', 'circle'].includes(innerIcon.value)) { | |||||
style.set('background', innerIconDisabledBgColor.value) | |||||
style.set('border-color', innerIconDisabledColor.value) | |||||
} | |||||
// #endif | |||||
} | |||||
return style | |||||
}) | |||||
const labelStyle = computed(():Map<string, any>=>{ | |||||
const style = new Map<string, any>(); | |||||
const fontSize = props.fontSize ?? radioGroupProps?.fontSize | |||||
if(fontSize != null) { | |||||
style.set('font-size', fontSize) | |||||
} | |||||
return style | |||||
}) | |||||
const onClick = (e: UniPointerEvent) => { | |||||
if(isDisabled.value) return; | |||||
const _name = props.value ?? props.name | |||||
if(radioGroupChange != null && _name != null) { | |||||
const value = finalAllowUncheck.value && radioChecked.value ? null : _name; | |||||
radioGroupChange(value); | |||||
} else { | |||||
const value = finalAllowUncheck.value ? !radioChecked.value : true; | |||||
innerChecked.value = value | |||||
} | |||||
} | |||||
// #ifdef APP | |||||
const iconRef = ref<UniElement|null>(null) | |||||
const update = () => { | |||||
if(iconRef.value == null) return | |||||
const ctx = iconRef.value!.getDrawableContext()!; | |||||
const rect = iconRef.value!.getBoundingClientRect(); | |||||
const x = rect.width / 2; | |||||
const y = rect.height / 2; | |||||
ctx.reset(); | |||||
if(innerIcon.value == 'circle') { | |||||
if(radioChecked.value) { | |||||
ctx.strokeStyle = isDisabled.value ? innerIconDisabledColor.value : 'white'; | |||||
ctx.lineWidth = rect.width * 0.12; | |||||
ctx.lineCap = 'round'; | |||||
ctx.moveTo(rect.width * 0.2967, rect.height * 0.53) | |||||
ctx.lineTo(rect.width * 0.4342, rect.height * 0.6675) | |||||
ctx.lineTo(rect.width * 0.7092, rect.height * 0.3925) | |||||
ctx.stroke() | |||||
} | |||||
} else if(innerIcon.value == 'line') { | |||||
if(radioChecked.value) { | |||||
ctx.strokeStyle = isDisabled.value ? innerIconDisabledColor.value : props.checkedColor ?? '#3283ff'; | |||||
ctx.lineWidth = rect.width * 0.16; | |||||
ctx.lineCap = 'round'; | |||||
ctx.moveTo(rect.width * 0.10, rect.height * 0.5466) | |||||
ctx.lineTo(rect.width * 0.3666, rect.height * 0.8134) | |||||
ctx.lineTo(rect.width * 0.90, rect.height * 0.28) | |||||
ctx.stroke() | |||||
} | |||||
} else if(innerIcon.value == 'dot'){ | |||||
if(radioChecked.value) { | |||||
ctx.fillStyle = isDisabled.value ? innerIconDisabledColor.value : 'white'; | |||||
ctx.arc(x, y, rect.width * 0.22, 0, Math.PI * 2) | |||||
ctx.fill() | |||||
} | |||||
} | |||||
ctx.update(); | |||||
} | |||||
watchEffect(update) | |||||
onMounted(()=>{ | |||||
nextTick(update) | |||||
}) | |||||
// #endif | |||||
</script> | |||||
<style lang="scss"> | |||||
@import './index-u'; | |||||
</style> |
@ -0,0 +1,128 @@ | |||||
<template> | |||||
<view class="l-radio" :class="{'l-radio--disabled': isDisabled}" :style="[styles]" @click="onClick"> | |||||
<slot name="radio" :checked="radioChecked" :disabled="isDisabled"> | |||||
<slot name="icon" :checked="radioChecked" :disabled="isDisabled"> | |||||
<view class="l-radio__icon" ref="iconRef" | |||||
:class="['l-radio__icon--' + innerIcon, { | |||||
'l-radio__icon--checked': radioChecked, | |||||
'l-radio__icon--disabled': isDisabled | |||||
}]" :style="[iconStyle]"></view> | |||||
</slot> | |||||
<view class="l-radio__label" :class="{'l-radio__label--disabled': isDisabled}" v-if="label || $slots['default']"> | |||||
<slot>{{label}}</slot> | |||||
</view> | |||||
</slot> | |||||
</view> | |||||
</template> | |||||
<script lang="ts"> | |||||
// @ts-nocheck | |||||
import { defineComponent, computed, inject, Ref , ref} from '@/uni_modules/lime-shared/vue'; | |||||
import { RadioValue, LRadioProps } from './type' | |||||
import radioProps from './props' | |||||
const name = 'l-radio' | |||||
export default defineComponent({ | |||||
name, | |||||
props: radioProps, | |||||
emits: ['update:checked', 'update:modelValue', 'change', 'input'], | |||||
setup(props, { emit }) { | |||||
const radioGroupProps = inject<LRadioGroupComponentPublicInstance|null>('limeRadioGroupProps', null); | |||||
const radioGroupValue = inject<ComputedRefImpl<any>|null>('limeRadioGroupValue', null); | |||||
const radioGroupChange = inject<((value: any|null) => void)|null>('limeRadioGroupChange', null); | |||||
const modelValue = ref(props.checked || props.modelValue) | |||||
const innerChecked = computed({ | |||||
set(value: boolean){ | |||||
modelValue.value = value; | |||||
emit('update:modelValue', value) | |||||
emit('change', value) | |||||
// #ifdef VUE2 | |||||
emit('input', value) | |||||
// #endif | |||||
}, | |||||
get(): boolean{ | |||||
return props.checked || props.modelValue || modelValue.value | |||||
}, | |||||
} as WritableComputedOptions<boolean>) | |||||
const isDisabled = computed(():boolean => (props.disabled || (radioGroupProps?.disabled))) | |||||
const radioChecked = computed(():boolean => innerChecked.value || (props.name && props.name == radioGroupValue?.value) || (props.value && props.value == radioGroupValue?.value)); | |||||
const finalAllowUncheck = computed(():boolean => props.allowUncheck || (radioGroupProps?.allowUncheck)); | |||||
const innerIcon = computed(():string => radioGroupProps?.icon || props.icon) | |||||
const innerSize = computed(():string => radioGroupProps?.size || props.size) | |||||
const innerIconSize = computed(():string|null => radioGroupProps?.iconSize || props.iconSize) | |||||
const innerFontSize = computed(():string|null => radioGroupProps?.fontSize || props.fontSize) | |||||
const innerCheckedColor = computed(():string|null => radioGroupProps?.checkedColor || props.checkedColor) | |||||
const innerIconBgColor = computed(():string => props.iconBgColor || radioGroupProps?.iconBgColor) | |||||
const innerIconBorderColor = computed(():string => props.iconBorderColor || radioGroupProps?.iconBorderColor ) | |||||
const innerIconDisabledColor = computed(():string => props.iconDisabledColor || radioGroupProps?.iconDisabledColor) | |||||
const innerIconDisabledBgColor = computed(():string => props.iconDisabledBgColor || radioGroupProps?.iconDisabledBgColor) | |||||
const styles = computed(()=>{ | |||||
const style:Record<string, any> = {}; | |||||
if(radioGroupProps&& radioGroupProps.gap) { | |||||
style[radioGroupProps.direction == 'horizontal' ? 'margin-right' : 'margin-bottom'] = radioGroupProps.gap! | |||||
} | |||||
if(innerCheckedColor.value) { | |||||
style['--l-radio-icon-checked-color'] = innerCheckedColor.value! | |||||
} | |||||
if(innerIconBorderColor.value) { | |||||
style['--l-radio-icon-border-color'] = innerIconBorderColor.value! | |||||
} | |||||
if(innerIconDisabledColor.value) { | |||||
style['--l-radio-icon-disabled-color'] = innerIconDisabledColor.value! | |||||
} | |||||
if(innerIconDisabledBgColor.value) { | |||||
style['--l-radio-icon-disabled-bg-color'] = innerIconDisabledBgColor.value! | |||||
} | |||||
if(innerFontSize.value) { | |||||
style['--l-radio-font-size'] = innerFontSize.value! | |||||
} | |||||
if(innerIconBgColor.value) { | |||||
style['--l-radio-icon-bg-color'] = innerIconBgColor.value! | |||||
} | |||||
return style | |||||
}) | |||||
const iconStyle = computed(()=>{ | |||||
const style:Record<string, any> = {} | |||||
if(innerIconSize.valuel) { | |||||
style['width'] = innerIconSize.value! | |||||
style['height'] = innerIconSize.value! | |||||
style['--l-radio-icon-size'] = innerIconSize.value! | |||||
} | |||||
return style | |||||
}) | |||||
const onClick = (e: UniPointerEvent) => { | |||||
if(isDisabled.value) return; | |||||
const _name = props.value || props.name | |||||
if(radioGroupChange && _name) { | |||||
const value = finalAllowUncheck.value && radioChecked.value ? null : _name; | |||||
radioGroupChange(value); | |||||
} else { | |||||
const value = finalAllowUncheck.value ? !radioChecked.value : true; | |||||
innerChecked.value = value | |||||
} | |||||
} | |||||
return { | |||||
styles, | |||||
innerChecked, | |||||
iconStyle, | |||||
innerIcon, | |||||
radioChecked, | |||||
isDisabled, | |||||
onClick, | |||||
} | |||||
} | |||||
}); | |||||
</script> | |||||
<style lang="scss"> | |||||
@import './index-u'; | |||||
</style> |
@ -0,0 +1,70 @@ | |||||
export default { | |||||
/** 是否允许取消选中 */ | |||||
allowUncheck: Boolean, | |||||
/** 是否选中 */ | |||||
checked: { | |||||
type: Boolean, | |||||
default: null, | |||||
}, | |||||
modelValue: { | |||||
type: Boolean, | |||||
default: null, | |||||
}, | |||||
/** 是否选中,非受控属性 */ | |||||
defaultChecked: Boolean, | |||||
/** 是否为禁用态。如果存在父组件 RadioGroup,默认值由 RadioGroup.disabled 控制。Radio.disabled 优先级高于 RadioGroup.disabled */ | |||||
disabled: { | |||||
type: Boolean, | |||||
default: false, | |||||
}, | |||||
/** 主文案 */ | |||||
label: { | |||||
type: String, | |||||
}, | |||||
/** 唯一名称 */ | |||||
name: { | |||||
type: [String, Number], | |||||
default: null, | |||||
}, | |||||
/** 单选按钮的值 */ | |||||
value: { | |||||
type: [String, Number, Boolean],//as PropType<TdRadioProps['value']>, | |||||
default: null, | |||||
}, | |||||
checkedColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconBgColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconBorderColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconDisabledColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
iconDisabledBgColor: { | |||||
type: String, | |||||
default: null | |||||
}, | |||||
icon: { | |||||
type: String, | |||||
default: 'circle' | |||||
}, //?: 'circle' | 'line' | 'dot'; | |||||
size: { | |||||
type: String, | |||||
default: 'medium' | |||||
}, //?: 'small' | 'medium' | 'large'; | |||||
iconSize: { | |||||
type: String, | |||||
defalut: null | |||||
}, | |||||
fontSize: { | |||||
type: String, | |||||
defalut: null | |||||
}, | |||||
} |
@ -0,0 +1,49 @@ | |||||
// @ts-nocheck | |||||
export type RadioValue = any//string | number | boolean; | |||||
export interface RadioProps { | |||||
/** | |||||
* 是否允许取消选中 | |||||
*/ | |||||
allowUncheck : boolean; | |||||
/** | |||||
* 是否选中 | |||||
*/ | |||||
checked : boolean; | |||||
/** | |||||
* 是否选中,非受控属性 | |||||
*/ | |||||
defaultChecked ?: boolean; | |||||
/** | |||||
* 是否选中 | |||||
*/ | |||||
// modelValue ?: boolean; | |||||
/** | |||||
* 是否为禁用态。如果存在父组件 RadioGroup,默认值由 RadioGroup.disabled 控制。Radio.disabled 优先级高于 RadioGroup.disabled | |||||
*/ | |||||
disabled : boolean; | |||||
/** | |||||
* 自定义选中图标和非选中图标。示例:[选中态图标地址,非选中态图标地址]。使用 String 时,值为 circle 表示填充型图标、值为 line 表示描边型图标、值为 dot 表示圆点图标 | |||||
*/ | |||||
icon: 'circle' | 'line' | 'dot' //| string[]; | |||||
// icon ?: 'circle' | 'line' | 'dot'; | |||||
/** | |||||
* 主文案 | |||||
*/ | |||||
label ?: string; | |||||
/** | |||||
* 唯一名称 | |||||
*/ | |||||
name ?: string; | |||||
value ?: RadioValue; | |||||
size: 'small' | 'medium' | 'large' | |||||
fontSize ?: string; | |||||
iconSize?: string; | |||||
checkedColor?: string; | |||||
iconBgColor?: string; | |||||
iconBorderColor?: string; | |||||
iconDisabledColor?: string; | |||||
iconDisabledBgColor?: string; | |||||
} | |||||
type ComputedRefImpl<T> = ComputedRef<T> |
@ -0,0 +1,181 @@ | |||||
<template> | |||||
<view class="demo-block"> | |||||
<text class="demo-block__title-text ultra">单选框</text> | |||||
<text class="demo-block__desc-text" style="display: flex;">在一组备选项中进行单选。</text> | |||||
<view class="demo-block__body"> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">基本用法</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio allowUncheck>单选框</l-radio> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">选项组</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Shanghai" label="上海" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">禁用</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" disabled> | |||||
<l-radio style="margin-bottom:20px;margin-right: 20px;" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px;margin-right: 20px;" value="Shanghai" label="上海" /> | |||||
<l-radio style="margin-bottom:20px;margin-right: 20px;" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px;margin-right: 20px;" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">样式</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" icon="dot"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
<l-radio-group @change="onChange" icon="line"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义颜色</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义大小</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" icon-size="44px" value="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" icon-size="34px" value="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" icon-size="24px" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义图标</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Beijing" label="北京"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio style="margin-bottom:20px; margin-right: 20px;" value="Guangzhou" label="广州"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio style="margin-bottom:20px" value="Shenzen" label="深圳"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script lang="uts" setup> | |||||
// const checked = ref(false) | |||||
// const disabled = ref(false) | |||||
const onChange = (e: any) => { | |||||
console.log('e', e) | |||||
} | |||||
</script> | |||||
<style lang="scss"> | |||||
.row { | |||||
flex-direction: row; | |||||
flex-wrap: wrap; | |||||
} | |||||
.custom-radio { | |||||
padding: 20rpx 30rpx; | |||||
border-radius: 5rpx; | |||||
border: 1rpx solid #ddd; | |||||
transition: background-color 0.3s; | |||||
color: black; | |||||
&.checked { | |||||
background-color: #007aff; | |||||
color: white; | |||||
border-color: #007aff; | |||||
} | |||||
} | |||||
.demo-block { | |||||
margin: 32px 10px 0; | |||||
// overflow: visible; | |||||
&.card { | |||||
background-color: white; | |||||
padding: 30rpx; | |||||
margin-bottom: 20rpx !important; | |||||
} | |||||
&__title { | |||||
margin: 0; | |||||
margin-top: 8px; | |||||
&-text { | |||||
color: rgba(0, 0, 0, 0.6); | |||||
font-weight: 400; | |||||
font-size: 14px; | |||||
line-height: 16px; | |||||
&.large { | |||||
color: rgba(0, 0, 0, 0.9); | |||||
font-size: 18px; | |||||
font-weight: 700; | |||||
line-height: 26px; | |||||
} | |||||
&.ultra { | |||||
color: rgba(0, 0, 0, 0.9); | |||||
font-size: 24px; | |||||
font-weight: 700; | |||||
line-height: 32px; | |||||
} | |||||
} | |||||
} | |||||
&__desc-text { | |||||
color: rgba(0, 0, 0, 0.6); | |||||
margin: 8px 16px 0 0; | |||||
font-size: 14px; | |||||
line-height: 22px; | |||||
} | |||||
&__body { | |||||
margin: 16px 0; | |||||
overflow: visible; | |||||
.demo-block { | |||||
// margin-top: 0px; | |||||
margin: 0; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,189 @@ | |||||
<template> | |||||
<view class="demo-block"> | |||||
<text class="demo-block__title-text ultra">单选框</text> | |||||
<text class="demo-block__desc-text" style="display: flex;">在一组备选项中进行单选。</text> | |||||
<view class="demo-block__body"> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">基本用法</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio allowUncheck>单选框</l-radio> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">选项组</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shanghai" label="上海" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">禁用</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" disabled> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shanghai" label="上海" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">样式</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" icon="dot"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
<l-radio-group @change="onChange" icon="line"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义颜色</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义大小</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" icon-size="44px" name="Beijing" label="北京" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" icon-size="34px" name="Guangzhou" label="广州" /> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" icon-size="24px" name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
<view class="demo-block card"> | |||||
<text class="demo-block__title-text">自定义图标</text> | |||||
<view class="demo-block__body"> | |||||
<l-radio-group @change="onChange" checked-color="#ee0a24"> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Beijing" label="北京"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Guangzhou" label="广州"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio style="margin-bottom:20px; margin-right:20px" name="Shenzen" label="深圳"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
</l-radio-group> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</view> | |||||
</template> | |||||
<script> | |||||
export default { | |||||
data() { | |||||
return { | |||||
} | |||||
}, | |||||
methods: { | |||||
onChange(v) { | |||||
console.log('?', v) | |||||
} | |||||
}, | |||||
mounted() { | |||||
} | |||||
} | |||||
</script> | |||||
<style lang="scss"> | |||||
.row { | |||||
flex-direction: row; | |||||
flex-wrap: wrap; | |||||
} | |||||
.custom-radio { | |||||
padding: 20rpx 30rpx; | |||||
border-radius: 5rpx; | |||||
border: 1rpx solid #ddd; | |||||
transition: background-color 0.3s; | |||||
color: black; | |||||
&.checked { | |||||
background-color: #007aff; | |||||
color: white; | |||||
border-color: #007aff; | |||||
} | |||||
} | |||||
.demo-block { | |||||
margin: 32px 10px 0; | |||||
// overflow: visible; | |||||
&.card { | |||||
background-color: white; | |||||
padding: 30rpx; | |||||
margin-bottom: 20rpx !important; | |||||
} | |||||
&__title { | |||||
margin: 0; | |||||
margin-top: 8px; | |||||
&-text { | |||||
color: rgba(0, 0, 0, 0.6); | |||||
font-weight: 400; | |||||
font-size: 14px; | |||||
line-height: 16px; | |||||
&.large { | |||||
color: rgba(0, 0, 0, 0.9); | |||||
font-size: 18px; | |||||
font-weight: 700; | |||||
line-height: 26px; | |||||
} | |||||
&.ultra { | |||||
color: rgba(0, 0, 0, 0.9); | |||||
font-size: 24px; | |||||
font-weight: 700; | |||||
line-height: 32px; | |||||
} | |||||
} | |||||
} | |||||
&__desc-text { | |||||
color: rgba(0, 0, 0, 0.6); | |||||
margin: 8px 16px 0 0; | |||||
font-size: 14px; | |||||
line-height: 22px; | |||||
} | |||||
&__body { | |||||
margin: 16px 0; | |||||
overflow: visible; | |||||
.demo-block { | |||||
// margin-top: 0px; | |||||
margin: 0; | |||||
} | |||||
} | |||||
} | |||||
</style> |
@ -0,0 +1,89 @@ | |||||
{ | |||||
"id": "lime-radio", | |||||
"displayName": "lime-radio 单选框", | |||||
"version": "0.0.7", | |||||
"description": "lime-radio 在一组备选项中进行单选,支持自定义大小,自定义图标等,兼容uniapp/uniappx", | |||||
"keywords": [ | |||||
"lime-radio", | |||||
"radio", | |||||
"单选框" | |||||
], | |||||
"repository": "", | |||||
"engines": { | |||||
"HBuilderX": "^4.34" | |||||
}, | |||||
"dcloudext": { | |||||
"type": "component-vue", | |||||
"sale": { | |||||
"regular": { | |||||
"price": "0.00" | |||||
}, | |||||
"sourcecode": { | |||||
"price": "0.00" | |||||
} | |||||
}, | |||||
"contact": { | |||||
"qq": "" | |||||
}, | |||||
"declaration": { | |||||
"ads": "无", | |||||
"data": "无", | |||||
"permissions": "无" | |||||
}, | |||||
"npmurl": "" | |||||
}, | |||||
"uni_modules": { | |||||
"dependencies": [ | |||||
"lime-style", | |||||
"lime-shared" | |||||
], | |||||
"encrypt": [], | |||||
"platforms": { | |||||
"cloud": { | |||||
"tcb": "y", | |||||
"aliyun": "y", | |||||
"alipay": "y" | |||||
}, | |||||
"client": { | |||||
"Vue": { | |||||
"vue2": "u", | |||||
"vue3": "y" | |||||
}, | |||||
"App": { | |||||
"app-vue": "y", | |||||
"app-nvue": "u", | |||||
"app-uvue": "y", | |||||
"app-harmony": "u" | |||||
}, | |||||
"H5-mobile": { | |||||
"Safari": "y", | |||||
"Android Browser": "y", | |||||
"微信浏览器(Android)": "y", | |||||
"QQ浏览器(Android)": "y" | |||||
}, | |||||
"H5-pc": { | |||||
"Chrome": "u", | |||||
"IE": "u", | |||||
"Edge": "u", | |||||
"Firefox": "u", | |||||
"Safari": "u" | |||||
}, | |||||
"小程序": { | |||||
"微信": "y", | |||||
"阿里": "u", | |||||
"百度": "u", | |||||
"字节跳动": "u", | |||||
"QQ": "u", | |||||
"钉钉": "u", | |||||
"快手": "u", | |||||
"飞书": "u", | |||||
"京东": "u" | |||||
}, | |||||
"快应用": { | |||||
"华为": "u", | |||||
"联盟": "u" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,199 @@ | |||||
# lime-radio 单选框 | |||||
- 在一组备选项中进行单选。 | |||||
## 安装 | |||||
在插件市场导入即可,首次导入可能需要重新编译 | |||||
## 代码演示 | |||||
### 基础演示 | |||||
通过`allowUncheck`可设置单个是否允许取消选中 | |||||
```html | |||||
<l-radio allowUncheck>单选框</l-radio> | |||||
``` | |||||
### 选项组 | |||||
通过 v-model 绑定值当前选中项的 `value`或`name`,vue2使用的`value`被占用,故可以使用`name`。 | |||||
```html | |||||
<l-radio-group v-model="checked" @change="onChange"> | |||||
<l-radio value="Beijing" label="北京" /> | |||||
<l-radio value="Shanghai" label="上海" /> | |||||
<l-radio value="Guangzhou" label="广州" /> | |||||
<l-radio value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
<!-- vue2使用的`value`被占用,故可以使用`name` --> | |||||
<l-radio-group v-model="checked" @change="onChange"> | |||||
<l-radio name="Beijing" label="北京" /> | |||||
<l-radio name="Shanghai" label="上海" /> | |||||
<l-radio name="Guangzhou" label="广州" /> | |||||
<l-radio name="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
``` | |||||
```js | |||||
const checked = ref('Beijing'); | |||||
``` | |||||
### 禁用 | |||||
通过 `disabled` 属性禁止选项切换,在 Radio 上设置 `disabled` 可以禁用单个选项。 | |||||
```html | |||||
<l-radio-group v-model="checked" disabled> | |||||
<l-radio value="Beijing" label="北京" /> | |||||
<l-radio value="Shanghai" label="上海" /> | |||||
<l-radio value="Guangzhou" label="广州" /> | |||||
<l-radio value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
``` | |||||
### 样式 | |||||
`icon` 属性可选值为 `line` 和 `dot`,单选框形状分别对应线勾和圆点形。 | |||||
```html | |||||
<l-radio-group icon="dot"> | |||||
<l-radio value="Beijing" label="北京" /> | |||||
<l-radio value="Guangzhou" label="广州" /> | |||||
<l-radio value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
<l-radio-group icon="line"> | |||||
<l-radio value="Beijing" label="北京" /> | |||||
<l-radio value="Guangzhou" label="广州" /> | |||||
<l-radio value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
``` | |||||
### 自定义颜色 | |||||
通过 `checked-color` 属性设置选中状态的图标颜色。 | |||||
```html | |||||
<l-radio-group checked-color="#ee0a24"> | |||||
<l-radio value="Beijing" label="北京" /> | |||||
<l-radio value="Guangzhou" label="广州" /> | |||||
<l-radio value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
``` | |||||
### 自定义大小 | |||||
通过 `icon-size` 属性可以自定义图标的大小。 | |||||
```html | |||||
<l-radio-group> | |||||
<l-radio icon-size="44px" value="Beijing" label="北京" /> | |||||
<l-radio icon-size="34px" value="Guangzhou" label="广州" /> | |||||
<l-radio icon-size="24px" value="Shenzen" label="深圳" /> | |||||
</l-radio-group> | |||||
``` | |||||
### 自定义图标 | |||||
通过 icon 插槽自定义图标,并通过 slotProps 判断是否为选中状态。 | |||||
```html | |||||
<l-radio-group> | |||||
<l-radio value="Beijing" label="北京"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio value="Guangzhou" label="广州"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
<l-radio value="Shenzen" label="深圳"> | |||||
<template #icon="{checked}"> | |||||
<image v-show="checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-active.png"></image> | |||||
<image v-show="!checked" style="width:20px; height:20px" src="https://fastly.jsdelivr.net/npm/@vant/assets/user-inactive.png"></image> | |||||
</template> | |||||
</l-radio> | |||||
</l-radio-group> | |||||
``` | |||||
### 插件标签 | |||||
- 默认 l-radio 为 component | |||||
- 默认 lime-radio 为 demo | |||||
### 关于vue2的使用方式 | |||||
- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置 | |||||
- 关键代码是: 在main.js中 在vue2部分加上这一段即可. | |||||
```js | |||||
// vue2 | |||||
import Vue from 'vue' | |||||
import VueCompositionAPI from '@vue/composition-api' | |||||
Vue.use(VueCompositionAPI) | |||||
``` | |||||
## API | |||||
### Radio Props | |||||
| 参数 | 说明 | 类型 | 默认值 | | |||||
| --- | --- | --- | --- | | |||||
| name | 标识符,通常为一个唯一的字符串或数字 | _string\|number_ | - | | |||||
| value | 单选按钮的值 | _any_ | - | | |||||
| allowUncheck | 是否允许取消选中 | _boolean_ | `false` | | |||||
| checked | 是否选中 | _boolean_ | `false` | | |||||
| disabled | 是否为禁用态 | _boolean_ | `false` | | |||||
| icon | 自定义选中图标和非选中图标可选值`'circle' | 'line' | 'dot'` | _string_ | `circle` | | |||||
| label | 主文案 | _string_ | `` | | |||||
| fontSize | 文本大小 | _string_ | `` | | |||||
| iconSize | 图标大小 | _string_ | `` | | |||||
| checkedColor | 选中状态颜色 | _string_ | `` | | |||||
| iconBgColor | 图标背景颜色 | _string_ | `` | | |||||
| iconBorderColor | 图标描边颜色 | _string_ | `` | | |||||
| iconDisabledColor | 图标禁用颜色 | _string_ | `` | | |||||
| iconDisabledBgColor | 图标禁用背景颜色 | _string_ | `` | | |||||
### RadioGroup Props | |||||
| 参数 | 说明 | 类型 | 默认值 | | |||||
| --- | --- | --- | --- | | |||||
| v-model | 标识符,通常为一个唯一的字符串或数字 | _string\|number_ | - | | |||||
| name | 标识符,通常为一个唯一的字符串或数字 | _string\|number_ | - | | |||||
| value | 单选按钮的值 | _any_ | - | | |||||
| allowUncheck | 是否允许取消选中 | _boolean_ | `false` | | |||||
| disabled | 是否为禁用态 | _boolean_ | `false` | | |||||
| direction | 排列方向,可选值为vertical | _string_ | `horizontal` | | |||||
| fontSize | 文本大小 | _string_ | `` | | |||||
| iconSize | 图标大小 | _string_ | `` | | |||||
| checkedColor | 选中状态颜色 | _string_ | `` | | |||||
| iconBgColor | 图标背景颜色 | _string_ | `` | | |||||
| iconBorderColor | 图标描边颜色 | _string_ | `` | | |||||
| iconDisabledColor | 图标禁用颜色 | _string_ | `` | | |||||
| iconDisabledBgColor | 图标禁用背景颜色 | _string_ | `` | | |||||
### Events | |||||
| 事件名 | 说明 | 回调参数 | | |||||
| ------ | ------------------------ | ---------------------- | | |||||
| change | 当绑定值变化时触发的事件 | _currentValue: any_ | | |||||
### Radio Slots | |||||
| 插槽名 | 说明 | 回调参数 | | |||||
| ------ | ------------------------ | ---------------------- | | |||||
| default | 自定义文本 | _-_ | | |||||
| radio | 整体 | _{ checked: boolean, disabled: boolean }_ | | |||||
| icon | 自定义图标 | _{ checked: boolean, disabled: boolean }_ | | |||||
## 主题定制 | |||||
### 样式变量 | |||||
组件提供了下列 CSS 变量,可用于自定义样式,uvue app无效。 | |||||
| 名称 | 默认值 | 描述 | | |||||
| ------------------------ | -------------------- | ---- | | |||||
| --l-radio-icon-size | _40rpx_ | - | | |||||
| --l-radio-font-size | _32rpx_ | - | | |||||
| --l-radio-icon-bg-color | _white_ | - | | |||||
| --l-radio-border-icon-color | _$gray-5_ | - | | |||||
| --l-radio-icon-disabled-color | _$gray-5_ | - | | |||||
| --l-radio-icon-disabled-bg-color | _$gray-1_ | - | | |||||
| --l-radio-icon-checked-color | _$primary-color_ | - | | |||||
## 打赏 | |||||
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。 | |||||
 | |||||
 |
@ -0,0 +1,42 @@ | |||||
// @ts-nocheck | |||||
import {isNumeric} from '../isNumeric' | |||||
import {isDef} from '../isDef' | |||||
/** | |||||
* 给一个值添加单位(像素 px) | |||||
* @param value 要添加单位的值,可以是字符串或数字 | |||||
* @returns 添加了单位的值,如果值为 null 则返回 null | |||||
*/ | |||||
// #ifndef UNI-APP-X && APP | |||||
export function addUnit(value?: string | number): string | null { | |||||
if (!isDef(value)) { | |||||
return null; | |||||
} | |||||
value = String(value); // 将值转换为字符串 | |||||
// 如果值是数字,则在后面添加单位 "px",否则保持原始值 | |||||
return isNumeric(value) ? `${value}px` : value; | |||||
} | |||||
// #endif | |||||
// #ifdef UNI-APP-X && APP | |||||
function addUnit(value: string): string | |||||
function addUnit(value: number): string | |||||
function addUnit(value: any|null): string|null { | |||||
if (!isDef(value)) { | |||||
return null; | |||||
} | |||||
value = `${value}` //value.toString(); // 将值转换为字符串 | |||||
// 如果值是数字,则在后面添加单位 "px",否则保持原始值 | |||||
return isNumeric(value) ? `${value}px` : value; | |||||
} | |||||
export {addUnit} | |||||
// #endif | |||||
// console.log(addUnit(100)); // 输出: "100px" | |||||
// console.log(addUnit("200")); // 输出: "200px" | |||||
// console.log(addUnit("300px")); // 输出: "300px"(已经包含单位) | |||||
// console.log(addUnit()); // 输出: undefined(值为 undefined) | |||||
// console.log(addUnit(null)); // 输出: undefined(值为 null) |
@ -0,0 +1,82 @@ | |||||
export function cubicBezier(p1x : number, p1y : number, p2x : number, p2y : number):(x: number)=> number { | |||||
const ZERO_LIMIT = 1e-6; | |||||
// Calculate the polynomial coefficients, | |||||
// implicit first and last control points are (0,0) and (1,1). | |||||
const ax = 3 * p1x - 3 * p2x + 1; | |||||
const bx = 3 * p2x - 6 * p1x; | |||||
const cx = 3 * p1x; | |||||
const ay = 3 * p1y - 3 * p2y + 1; | |||||
const by = 3 * p2y - 6 * p1y; | |||||
const cy = 3 * p1y; | |||||
function sampleCurveDerivativeX(t : number) : number { | |||||
// `ax t^3 + bx t^2 + cx t` expanded using Horner's rule | |||||
return (3 * ax * t + 2 * bx) * t + cx; | |||||
} | |||||
function sampleCurveX(t : number) : number { | |||||
return ((ax * t + bx) * t + cx) * t; | |||||
} | |||||
function sampleCurveY(t : number) : number { | |||||
return ((ay * t + by) * t + cy) * t; | |||||
} | |||||
// Given an x value, find a parametric value it came from. | |||||
function solveCurveX(x : number) : number { | |||||
let t2 = x; | |||||
let derivative : number; | |||||
let x2 : number; | |||||
// https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation | |||||
// first try a few iterations of Newton's method -- normally very fast. | |||||
// http://en.wikipedia.org/wikiNewton's_method | |||||
for (let i = 0; i < 8; i++) { | |||||
// f(t) - x = 0 | |||||
x2 = sampleCurveX(t2) - x; | |||||
if (Math.abs(x2) < ZERO_LIMIT) { | |||||
return t2; | |||||
} | |||||
derivative = sampleCurveDerivativeX(t2); | |||||
// == 0, failure | |||||
/* istanbul ignore if */ | |||||
if (Math.abs(derivative) < ZERO_LIMIT) { | |||||
break; | |||||
} | |||||
t2 -= x2 / derivative; | |||||
} | |||||
// Fall back to the bisection method for reliability. | |||||
// bisection | |||||
// http://en.wikipedia.org/wiki/Bisection_method | |||||
let t1 = 1; | |||||
/* istanbul ignore next */ | |||||
let t0 = 0; | |||||
/* istanbul ignore next */ | |||||
t2 = x; | |||||
/* istanbul ignore next */ | |||||
while (t1 > t0) { | |||||
x2 = sampleCurveX(t2) - x; | |||||
if (Math.abs(x2) < ZERO_LIMIT) { | |||||
return t2; | |||||
} | |||||
if (x2 > 0) { | |||||
t1 = t2; | |||||
} else { | |||||
t0 = t2; | |||||
} | |||||
t2 = (t1 + t0) / 2; | |||||
} | |||||
// Failure | |||||
return t2; | |||||
} | |||||
return function (x : number) : number { | |||||
return sampleCurveY(solveCurveX(x)); | |||||
} | |||||
// return solve; | |||||
} |
@ -0,0 +1,3 @@ | |||||
import {cubicBezier} from './bezier'; | |||||
export let ease = cubicBezier(0.25, 0.1, 0.25, 1); | |||||
export let linear = cubicBezier(0,0,1,1); |
@ -0,0 +1,10 @@ | |||||
// @ts-nocheck | |||||
// #ifdef UNI-APP-X && APP | |||||
export * from './uvue.uts' | |||||
// #endif | |||||
// #ifndef UNI-APP-X && APP | |||||
export * from './vue.ts' | |||||
// #endif |
@ -0,0 +1,103 @@ | |||||
// @ts-nocheck | |||||
import type { ComponentPublicInstance } from 'vue' | |||||
import { ease, linear } from './ease'; | |||||
import { Timeline, Animation } from './'; | |||||
export type UseTransitionOptions = { | |||||
duration ?: number | |||||
immediate ?: boolean | |||||
context ?: ComponentPublicInstance | |||||
} | |||||
// #ifndef UNI-APP-X && APP | |||||
import { ref, watch, type Ref } from '@/uni_modules/lime-shared/vue' | |||||
export function useTransition(percent : Ref<number>|(() => number), options : UseTransitionOptions) : Ref<number> { | |||||
const current = ref(0) | |||||
const { immediate, duration = 300 } = options | |||||
let tl:Timeline|null = null; | |||||
let timer = -1 | |||||
const isFunction = typeof percent === 'function' | |||||
watch(isFunction ? percent : () => percent.value, (v) => { | |||||
if(tl == null){ | |||||
tl = new Timeline() | |||||
} | |||||
tl.start(); | |||||
tl.add( | |||||
new Animation( | |||||
current.value, | |||||
v, | |||||
duration, | |||||
0, | |||||
ease, | |||||
nowValue => { | |||||
current.value = nowValue | |||||
clearTimeout(timer) | |||||
if(current.value == v){ | |||||
timer = setTimeout(()=>{ | |||||
tl?.pause(); | |||||
tl = null | |||||
}, duration) | |||||
} | |||||
} | |||||
) | |||||
); | |||||
}, { immediate }) | |||||
return current | |||||
} | |||||
// #endif | |||||
// #ifdef UNI-APP-X && APP | |||||
type UseTransitionReturnType = Ref<number> | |||||
export function useTransition(source : any, options : UseTransitionOptions) : UseTransitionReturnType { | |||||
const outputRef : Ref<number> = ref(0) | |||||
const immediate = options.immediate ?? false | |||||
const duration = options.duration ?? 300 | |||||
const context = options.context //as ComponentPublicInstance | null | |||||
let tl:Timeline|null = null; | |||||
let timer = -1 | |||||
const watchFunc = (v : number) => { | |||||
if(tl == null){ | |||||
tl = new Timeline() | |||||
} | |||||
tl!.start(); | |||||
tl!.add( | |||||
new Animation( | |||||
outputRef.value, | |||||
v, | |||||
duration, | |||||
0, | |||||
ease, | |||||
nowValue => { | |||||
outputRef.value = nowValue | |||||
clearTimeout(timer) | |||||
if(outputRef.value == v){ | |||||
timer = setTimeout(()=>{ | |||||
tl?.pause(); | |||||
tl = null | |||||
}, duration) | |||||
} | |||||
} | |||||
), | |||||
null | |||||
); | |||||
} | |||||
if (context != null && typeof source == 'string') { | |||||
context.$watch(source, watchFunc, { immediate } as WatchOptions) | |||||
} else if(typeof source == 'function'){ | |||||
watch(source, watchFunc, { immediate }) | |||||
} else if(isRef(source) && typeof source.value == 'number') { | |||||
watch(source as Ref<number>, watchFunc, { immediate }) | |||||
} | |||||
// else if(source instanceof Ref<number>){ | |||||
// watch(source as Ref<number>, watchFunc, { immediate }) | |||||
// } | |||||
const stop = ()=>{ | |||||
} | |||||
return outputRef //as UseTransitionReturnType | |||||
} | |||||
// #endif |
@ -0,0 +1,119 @@ | |||||
import { raf, cancelRaf} from '../raf' | |||||
// @ts-nocheck | |||||
export class Timeline { | |||||
state : string | |||||
animations : Set<Animation> = new Set<Animation>() | |||||
delAnimations : Animation[] = [] | |||||
startTimes : Map<Animation, number> = new Map<Animation, number>() | |||||
pauseTime : number = 0 | |||||
pauseStart : number = Date.now() | |||||
tickHandler : number = 0 | |||||
tickHandlers : number[] = [] | |||||
tick : (() => void) | null = null | |||||
constructor() { | |||||
this.state = 'Initiated'; | |||||
} | |||||
start() { | |||||
if (!(this.state == 'Initiated')) return; | |||||
this.state = 'Started'; | |||||
let startTime = Date.now(); | |||||
this.pauseTime = 0; | |||||
this.tick = () => { | |||||
let now = Date.now(); | |||||
this.animations.forEach((animation : Animation) => { | |||||
let t:number; | |||||
const ani = this.startTimes.get(animation) | |||||
if (ani == null) return | |||||
if (ani < startTime) { | |||||
t = now - startTime - animation.delay - this.pauseTime; | |||||
} else { | |||||
t = now - ani - animation.delay - this.pauseTime; | |||||
} | |||||
if (t > animation.duration) { | |||||
this.delAnimations.push(animation) | |||||
// 不能在 foreach 里面 对 集合进行删除操作 | |||||
// this.animations.delete(animation); | |||||
t = animation.duration; | |||||
} | |||||
if (t > 0) animation.run(t); | |||||
}) | |||||
// 不能在 foreach 里面 对 集合进行删除操作 | |||||
while (this.delAnimations.length > 0) { | |||||
const animation = this.delAnimations.pop(); | |||||
if (animation == null) return | |||||
this.animations.delete(animation); | |||||
} | |||||
// cancelAnimationFrame(this.tickHandler); | |||||
if (this.state != 'Started') return | |||||
this.tickHandler = raf(()=>{ | |||||
this.tick!() | |||||
}) | |||||
this.tickHandlers.push(this.tickHandler) | |||||
} | |||||
if(this.tick != null) { | |||||
this.tick!() | |||||
} | |||||
} | |||||
pause() { | |||||
if (!(this.state === 'Started')) return; | |||||
this.state = 'Paused'; | |||||
this.pauseStart = Date.now(); | |||||
cancelRaf(this.tickHandler); | |||||
// cancelRaf(this.tickHandler); | |||||
} | |||||
resume() { | |||||
if (!(this.state === 'Paused')) return; | |||||
this.state = 'Started'; | |||||
this.pauseTime += Date.now() - this.pauseStart; | |||||
this.tick!(); | |||||
} | |||||
reset() { | |||||
this.pause(); | |||||
this.state = 'Initiated'; | |||||
this.pauseTime = 0; | |||||
this.pauseStart = 0; | |||||
this.animations.clear() | |||||
this.delAnimations.clear() | |||||
this.startTimes.clear() | |||||
this.tickHandler = 0; | |||||
} | |||||
add(animation : Animation, startTime ?: number | null) { | |||||
if (startTime == null) startTime = Date.now(); | |||||
this.animations.add(animation); | |||||
this.startTimes.set(animation, startTime); | |||||
} | |||||
} | |||||
export class Animation { | |||||
startValue : number | |||||
endValue : number | |||||
duration : number | |||||
timingFunction : (t : number) => number | |||||
delay : number | |||||
template : (t : number) => void | |||||
constructor( | |||||
startValue : number, | |||||
endValue : number, | |||||
duration : number, | |||||
delay : number, | |||||
timingFunction : (t : number) => number, | |||||
template : (v : number) => void) { | |||||
this.startValue = startValue; | |||||
this.endValue = endValue; | |||||
this.duration = duration; | |||||
this.timingFunction = timingFunction; | |||||
this.delay = delay; | |||||
this.template = template; | |||||
} | |||||
run(time : number) { | |||||
let range = this.endValue - this.startValue; | |||||
let progress = time / this.duration | |||||
if(progress != 1) progress = this.timingFunction(progress) | |||||
this.template(this.startValue + range * progress) | |||||
} | |||||
} |
@ -0,0 +1,123 @@ | |||||
// @ts-nocheck | |||||
const TICK = Symbol('tick'); | |||||
const TICK_HANDLER = Symbol('tick-handler'); | |||||
const ANIMATIONS = Symbol('animations'); | |||||
const START_TIMES = Symbol('start-times'); | |||||
const PAUSE_START = Symbol('pause-start'); | |||||
const PAUSE_TIME = Symbol('pause-time'); | |||||
const _raf = typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame : function(cb: Function) {return setTimeout(cb, 1000/60)} | |||||
const _caf = typeof cancelAnimationFrame !== 'undefined' ? cancelAnimationFrame: function(id: any) {clearTimeout(id)} | |||||
// const TICK = 'tick'; | |||||
// const TICK_HANDLER = 'tick-handler'; | |||||
// const ANIMATIONS = 'animations'; | |||||
// const START_TIMES = 'start-times'; | |||||
// const PAUSE_START = 'pause-start'; | |||||
// const PAUSE_TIME = 'pause-time'; | |||||
// const _raf = function(callback):number|null {return setTimeout(callback, 1000/60)} | |||||
// const _caf = function(id: number):void {clearTimeout(id)} | |||||
export class Timeline { | |||||
state: string | |||||
constructor() { | |||||
this.state = 'Initiated'; | |||||
this[ANIMATIONS] = new Set(); | |||||
this[START_TIMES] = new Map(); | |||||
} | |||||
start() { | |||||
if (!(this.state === 'Initiated')) return; | |||||
this.state = 'Started'; | |||||
let startTime = Date.now(); | |||||
this[PAUSE_TIME] = 0; | |||||
this[TICK] = () => { | |||||
let now = Date.now(); | |||||
this[ANIMATIONS].forEach((animation) => { | |||||
let t: number; | |||||
if (this[START_TIMES].get(animation) < startTime) { | |||||
t = now - startTime - animation.delay - this[PAUSE_TIME]; | |||||
} else { | |||||
t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME]; | |||||
} | |||||
if (t > animation.duration) { | |||||
this[ANIMATIONS].delete(animation); | |||||
t = animation.duration; | |||||
} | |||||
if (t > 0) animation.run(t); | |||||
}) | |||||
// for (let animation of this[ANIMATIONS]) { | |||||
// let t: number; | |||||
// console.log('animation', animation) | |||||
// if (this[START_TIMES].get(animation) < startTime) { | |||||
// t = now - startTime - animation.delay - this[PAUSE_TIME]; | |||||
// } else { | |||||
// t = now - this[START_TIMES].get(animation) - animation.delay - this[PAUSE_TIME]; | |||||
// } | |||||
// if (t > animation.duration) { | |||||
// this[ANIMATIONS].delete(animation); | |||||
// t = animation.duration; | |||||
// } | |||||
// if (t > 0) animation.run(t); | |||||
// } | |||||
this[TICK_HANDLER] = _raf(this[TICK]); | |||||
}; | |||||
this[TICK](); | |||||
} | |||||
pause() { | |||||
if (!(this.state === 'Started')) return; | |||||
this.state = 'Paused'; | |||||
this[PAUSE_START] = Date.now(); | |||||
_caf(this[TICK_HANDLER]); | |||||
} | |||||
resume() { | |||||
if (!(this.state === 'Paused')) return; | |||||
this.state = 'Started'; | |||||
this[PAUSE_TIME] += Date.now() - this[PAUSE_START]; | |||||
this[TICK](); | |||||
} | |||||
reset() { | |||||
this.pause(); | |||||
this.state = 'Initiated'; | |||||
this[PAUSE_TIME] = 0; | |||||
this[PAUSE_START] = 0; | |||||
this[ANIMATIONS] = new Set(); | |||||
this[START_TIMES] = new Map(); | |||||
this[TICK_HANDLER] = null; | |||||
} | |||||
add(animation: any, startTime?: number) { | |||||
if (arguments.length < 2) startTime = Date.now(); | |||||
this[ANIMATIONS].add(animation); | |||||
this[START_TIMES].set(animation, startTime); | |||||
} | |||||
} | |||||
export class Animation { | |||||
startValue: number | |||||
endValue: number | |||||
duration: number | |||||
timingFunction: (t: number) => number | |||||
delay: number | |||||
template: (t: number) => void | |||||
constructor(startValue: number, endValue: number, duration: number, delay: number, timingFunction: (t: number) => number, template: (v: number) => void) { | |||||
timingFunction = timingFunction || (v => v); | |||||
template = template || (v => v); | |||||
this.startValue = startValue; | |||||
this.endValue = endValue; | |||||
this.duration = duration; | |||||
this.timingFunction = timingFunction; | |||||
this.delay = delay; | |||||
this.template = template; | |||||
} | |||||
run(time: number) { | |||||
let range = this.endValue - this.startValue; | |||||
let progress = time / this.duration | |||||
if(progress != 1) progress = this.timingFunction(progress) | |||||
this.template(this.startValue + range * progress) | |||||
} | |||||
} |
@ -0,0 +1,71 @@ | |||||
// @ts-nocheck | |||||
import _areaList from './city-china.json'; | |||||
export const areaList = _areaList | |||||
// #ifndef UNI-APP-X | |||||
type UTSJSONObject = Record<string, string> | |||||
// #endif | |||||
// #ifdef UNI-APP-X | |||||
type Object = UTSJSONObject | |||||
// #endif | |||||
type AreaList = { | |||||
province_list : Map<string, string>; | |||||
city_list : Map<string, string>; | |||||
county_list : Map<string, string>; | |||||
} | |||||
// type CascaderOption = { | |||||
// text : string; | |||||
// value : string; | |||||
// children ?: CascaderOption[]; | |||||
// }; | |||||
const makeOption = ( | |||||
label : string, | |||||
value : string, | |||||
children ?: UTSJSONObject[], | |||||
) : UTSJSONObject => ({ | |||||
label, | |||||
value, | |||||
children, | |||||
}); | |||||
export function useCascaderAreaData() : UTSJSONObject[] { | |||||
const city = areaList['city_list'] as UTSJSONObject | |||||
const county = areaList['county_list'] as UTSJSONObject | |||||
const province = areaList['province_list'] as UTSJSONObject | |||||
const provinceMap = new Map<string, UTSJSONObject>(); | |||||
Object.keys(province).forEach((code) => { | |||||
provinceMap.set(code.slice(0, 2), makeOption(`${province[code]}`, code, [])); | |||||
}); | |||||
const cityMap = new Map<string, UTSJSONObject>(); | |||||
Object.keys(city).forEach((code) => { | |||||
const option = makeOption(`${city[code]}`, code, []); | |||||
cityMap.set(code.slice(0, 4), option); | |||||
const _province = provinceMap.get(code.slice(0, 2)); | |||||
if (_province != null) { | |||||
(_province['children'] as UTSJSONObject[]).push(option) | |||||
} | |||||
}); | |||||
Object.keys(county).forEach((code) => { | |||||
const _city = cityMap.get(code.slice(0, 4)); | |||||
if (_city != null) { | |||||
(_city['children'] as UTSJSONObject[]).push(makeOption(`${county[code]}`, code, null)); | |||||
} | |||||
}); | |||||
// #ifndef APP-ANDROID || APP-IOS | |||||
return Array.from(provinceMap.values()); | |||||
// #endif | |||||
// #ifdef APP-ANDROID || APP-IOS | |||||
const obj : UTSJSONObject[] = [] | |||||
provinceMap.forEach((value, code) => { | |||||
obj.push(value) | |||||
}) | |||||
return obj | |||||
// #endif | |||||
} |
@ -0,0 +1,8 @@ | |||||
// @ts-nocheck | |||||
// #ifndef UNI-APP-X && APP | |||||
export * from './vue.ts' | |||||
// #endif | |||||
// #ifdef UNI-APP-X && APP | |||||
export * from './uvue.uts' | |||||
// #endif |
@ -0,0 +1,10 @@ | |||||
// @ts-nocheck | |||||
// import {platform} from '../platform' | |||||
/** | |||||
* buffer转路径 | |||||
* @param {Object} buffer | |||||
*/ | |||||
// @ts-nocheck | |||||
export function arrayBufferToFile(buffer: ArrayBuffer, name?: string, format?:string):Promise<(File|string)> { | |||||
console.error('[arrayBufferToFile] 当前环境不支持') | |||||
} |
@ -0,0 +1,63 @@ | |||||
// @ts-nocheck | |||||
import {platform} from '../platform' | |||||
/** | |||||
* buffer转路径 | |||||
* @param {Object} buffer | |||||
*/ | |||||
// @ts-nocheck | |||||
export function arrayBufferToFile(buffer: ArrayBuffer | Blob, name?: string, format?:string):Promise<(File|string)> { | |||||
return new Promise((resolve, reject) => { | |||||
// #ifdef MP | |||||
const fs = uni.getFileSystemManager() | |||||
//自定义文件名 | |||||
if (!name && !format) { | |||||
reject(new Error('ERROR_NAME_PARSE')) | |||||
} | |||||
const fileName = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`; | |||||
let pre = platform() | |||||
const filePath = `${pre.env.USER_DATA_PATH}/${fileName}` | |||||
fs.writeFile({ | |||||
filePath, | |||||
data: buffer, | |||||
success() { | |||||
resolve(filePath) | |||||
}, | |||||
fail(err) { | |||||
console.error(err) | |||||
reject(err) | |||||
} | |||||
}) | |||||
// #endif | |||||
// #ifdef H5 | |||||
const file = new File([buffer], name, { | |||||
type: format, | |||||
}); | |||||
resolve(file) | |||||
// #endif | |||||
// #ifdef APP-PLUS | |||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) | |||||
const base64 = uni.arrayBufferToBase64(buffer) | |||||
bitmap.loadBase64Data(base64, () => { | |||||
if (!name && !format) { | |||||
reject(new Error('ERROR_NAME_PARSE')) | |||||
} | |||||
const fileNmae = `${name || new Date().getTime()}.${format.replace(/(.+)?\//,'')}`; | |||||
const filePath = `_doc/uniapp_temp/${fileNmae}` | |||||
bitmap.save(filePath, {}, | |||||
() => { | |||||
bitmap.clear() | |||||
resolve(filePath) | |||||
}, | |||||
(error) => { | |||||
bitmap.clear() | |||||
reject(error) | |||||
}) | |||||
}, (error) => { | |||||
bitmap.clear() | |||||
reject(error) | |||||
}) | |||||
// #endif | |||||
}) | |||||
} |
@ -0,0 +1,13 @@ | |||||
// @ts-nocheck | |||||
// 未完成 | |||||
export function base64ToArrayBuffer(base64 : string) { | |||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || []; | |||||
if (!format) { | |||||
new Error('ERROR_BASE64SRC_PARSE') | |||||
} | |||||
if(uni.base64ToArrayBuffer) { | |||||
return uni.base64ToArrayBuffer(bodyData) | |||||
} else { | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
// @ts-nocheck | |||||
// #ifndef UNI-APP-X && APP | |||||
export * from './vue.ts' | |||||
// #endif | |||||
// #ifdef UNI-APP-X && APP | |||||
export * from './uvue.uts' | |||||
// #endif |
@ -0,0 +1,22 @@ | |||||
// @ts-nocheck | |||||
import { processFile, type ProcessFileOptions } from '@/uni_modules/lime-file-utils' | |||||
/** | |||||
* base64转路径 | |||||
* @param {Object} base64 | |||||
*/ | |||||
export function base64ToPath(base64: string, filename: string | null = null):Promise<string> { | |||||
return new Promise((resolve,reject) => { | |||||
processFile({ | |||||
type: 'toDataURL', | |||||
path: base64, | |||||
filename, | |||||
success(res: string){ | |||||
resolve(res) | |||||
}, | |||||
fail(err){ | |||||
reject(err) | |||||
} | |||||
} as ProcessFileOptions) | |||||
}) | |||||
} |