| @ -0,0 +1,60 @@ | |||
| .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; | |||
| height: 100%; | |||
| width: 100%; | |||
| 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,470 @@ | |||
| <template> | |||
| <view class="containers"> | |||
| <view class="header"> | |||
| <view class="header_info"> | |||
| <view class="header_info_icon" @click.native.stop.prevent="toBack" style="display: flex; justify-content: center; align-items: center;"> | |||
| <uni-icons type="left" size="30" color="#c2d4de" > </uni-icons> | |||
| </view> | |||
| <text class="header_text">付款信息</text> | |||
| </view> | |||
| </view> | |||
| <view class="section" style="margin-top: 5%;"> | |||
| <view class="form-item"> | |||
| <text class="label">产品名称</text> | |||
| <l-radio :checked="paymentMethod === '全款'" label="全款" fontSize="1rem" style="transform: scale(0.7);"> | |||
| <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> | |||
| </view> | |||
| <view class="form-item"> | |||
| <text class="label">经销商</text> | |||
| <l-radio-group v-model="checked" @change="onChange" class="radio-group" > | |||
| <l-radio value="Beijing" label="个人" fontSize="1rem" style="margin-right: 1rem; transform: scale(0.7);"> | |||
| <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> | |||
| <l-radio value="Shanghai" label="公司" fontSize="1rem" style="transform: scale(0.7);"> | |||
| <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> | |||
| </l-radio-group> | |||
| </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 class="con_size"> | |||
| <text class="label">上传凭证</text> | |||
| <wht-img-upload v-model="imageList" /> | |||
| </view> | |||
| </view> | |||
| <view class="section"> | |||
| <view class="form-button"> | |||
| <button class=" button" @click="get_to" style="width: 60%;">提交去录单</button> | |||
| </view> | |||
| </view> | |||
| </view> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| data() { | |||
| return { | |||
| selectedProduct: "自动填写所选商品服务分类", | |||
| selectedStore: "自动填写所选择的门店", | |||
| selectedService: "自动填写所选择的服务分类", | |||
| paymentMethod: '全款', // 默认选中全款 | |||
| customerType: '个人' , // 默认选中个人 | |||
| clientInfo: { | |||
| name: '', | |||
| address: '', | |||
| idNumber: '', | |||
| contact: '', | |||
| department: '', | |||
| salesAdvisor: '' | |||
| }, | |||
| imageList:[], | |||
| 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: { | |||
| get_to(){ | |||
| console.log(111); | |||
| }, | |||
| 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%; | |||
| } | |||
| .form-button{ | |||
| width: 100%; | |||
| height:100%; | |||
| // background-color: #000000; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .button{ | |||
| height: 100%; | |||
| width: 100%; | |||
| background-color: #044f7a; | |||
| color: #ffffff; | |||
| border-radius: 1rem; | |||
| } | |||
| </style> | |||
| @ -1,22 +1,213 @@ | |||
| <template> | |||
| <view> | |||
| </view> | |||
| <view class="container"> | |||
| <!-- 搜索栏 --> | |||
| <view class="search-box"> | |||
| <input | |||
| v-model="searchKey" | |||
| placeholder="请输入客户姓名/客户手机号" | |||
| class="search-input" | |||
| /> | |||
| <button @tap="handleSearch" class="search-btn">搜索</button> | |||
| </view> | |||
| <!-- 订单状态筛选 --> | |||
| <view class="filter-tabs"> | |||
| <text | |||
| v-for="tab in tabs" | |||
| :key="tab" | |||
| :class="['tab-item', activeTab === tab ? 'active' : '']" | |||
| @tap="activeTab = tab" | |||
| > | |||
| {{ tab }} | |||
| </text> | |||
| </view> | |||
| <!-- 订单列表 --> | |||
| <scroll-view scroll-y class="order-list"> | |||
| <view | |||
| v-for="(order, index) in filteredOrders" | |||
| :key="index" | |||
| class="order-item" | |||
| > | |||
| <view class="order-header"> | |||
| <text class="order-no">{{ order.orderNo }}</text> | |||
| <text class="copy-btn" @tap="copyOrderNo(order.orderNo)">复制</text> | |||
| </view> | |||
| <view class="order-info"> | |||
| <text>客户姓名:{{ order.customerName }}</text> | |||
| <text>联系方式:{{ order.phone }}</text> | |||
| <text>服务名称:{{ order.serviceName }}</text> | |||
| <text>订单时间:{{ order.orderTime }}</text> | |||
| <text>销售人员:{{ order.salesman }}</text> | |||
| <text>门店名称:{{ order.store }}</text> | |||
| </view> | |||
| <button class="download-btn" @tap="downloadPDF(order)">PDF下载</button> | |||
| </view> | |||
| </scroll-view> | |||
| </view> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| data() { | |||
| return { | |||
| } | |||
| }, | |||
| methods: { | |||
| } | |||
| } | |||
| export default { | |||
| data() { | |||
| return { | |||
| searchKey: '', // 搜索关键词 | |||
| activeTab: '全部', // 当前激活的标签 | |||
| tabs: ['全部', '已生效', '已失效'], | |||
| orders: [/* 从接口获取的数据 */] | |||
| } | |||
| }, | |||
| computed: { | |||
| // 过滤后的订单列表 | |||
| filteredOrders() { | |||
| return this.orders.filter(order => { | |||
| const matchStatus = this.activeTab === '全部' || | |||
| order.status === this.activeTab | |||
| const matchSearch = order.customerName.includes(this.searchKey) || | |||
| order.phone.includes(this.searchKey) | |||
| return matchStatus && matchSearch | |||
| }) | |||
| } | |||
| }, | |||
| methods: { | |||
| // 搜索处理 | |||
| handleSearch() { | |||
| // 实际调用接口获取数据 | |||
| console.log('搜索关键词:', this.searchKey) | |||
| }, | |||
| // 复制订单号 | |||
| copyOrderNo(orderNo) { | |||
| uni.setClipboardData({ | |||
| data: orderNo, | |||
| success: () => { | |||
| uni.showToast({ title: '复制成功' }) | |||
| } | |||
| }) | |||
| }, | |||
| // PDF下载 | |||
| async downloadPDF(order) { | |||
| uni.showLoading({ title: '下载中...' }) | |||
| try { | |||
| // 1. 调用下载接口 | |||
| const { tempFilePath } = await uni.downloadFile({ | |||
| url: 'https://your-api.com/download', | |||
| header: { 'order-id': order.id } | |||
| }) | |||
| // 2. 保存到本地 | |||
| await uni.saveFile({ | |||
| tempFilePath, | |||
| success: (res) => { | |||
| uni.showToast({ title: '下载成功' }) | |||
| console.log('文件路径:', res.savedFilePath) | |||
| } | |||
| }) | |||
| // 3. 打开文档(可选) | |||
| uni.openDocument({ | |||
| filePath: tempFilePath, | |||
| showMenu: true | |||
| }) | |||
| } catch (err) { | |||
| uni.showToast({ title: '下载失败', icon: 'none' }) | |||
| } finally { | |||
| uni.hideLoading() | |||
| } | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| <style lang="scss" scoped> | |||
| .container { | |||
| padding: 20rpx; | |||
| background-color: #f5f5f5; | |||
| } | |||
| .search-box { | |||
| display: flex; | |||
| margin-bottom: 30rpx; | |||
| .search-input { | |||
| flex: 1; | |||
| background: #fff; | |||
| padding: 20rpx; | |||
| border-radius: 8rpx; | |||
| } | |||
| .search-btn { | |||
| width: 140rpx; | |||
| margin-left: 20rpx; | |||
| background: #007AFF; | |||
| color: white; | |||
| } | |||
| } | |||
| .filter-tabs { | |||
| display: flex; | |||
| margin-bottom: 30rpx; | |||
| .tab-item { | |||
| flex: 1; | |||
| text-align: center; | |||
| padding: 20rpx; | |||
| color: #666; | |||
| border-bottom: 4rpx solid transparent; | |||
| &.active { | |||
| color: #007AFF; | |||
| border-bottom-color: #007AFF; | |||
| } | |||
| } | |||
| } | |||
| .order-list { | |||
| width: 100%; | |||
| height: 40%; | |||
| height: calc(100vh - 300rpx); | |||
| } | |||
| .order-item { | |||
| background: white; | |||
| border-radius: 12rpx; | |||
| margin-bottom: 20rpx; | |||
| padding: 20rpx; | |||
| .order-header { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| padding-bottom: 20rpx; | |||
| border-bottom: 1rpx solid #eee; | |||
| .order-no { | |||
| color: #333; | |||
| font-size: 28rpx; | |||
| } | |||
| .copy-btn { | |||
| color: #007AFF; | |||
| font-size: 24rpx; | |||
| } | |||
| } | |||
| .order-info { | |||
| padding: 20rpx 0; | |||
| text { | |||
| display: block; | |||
| color: #666; | |||
| font-size: 24rpx; | |||
| line-height: 1.8; | |||
| } | |||
| } | |||
| .download-btn { | |||
| width: 100%; | |||
| height: 100%; | |||
| background-color: red; | |||
| color: #333; | |||
| font-size: 26rpx; | |||
| margin-top: 20rpx; | |||
| } | |||
| } | |||
| </style> | |||
| @ -1,151 +0,0 @@ | |||
| <template> | |||
| <view class="idCard-box"> | |||
| <view class="positive"> | |||
| <image :src="upLoadPositiveImg == ''?positiveImg:upLoadPositiveImg" @tap.prevent="uploadPositive"> | |||
| </image> | |||
| </view> | |||
| <view class="reverse"> | |||
| <image :src="upLoadReverseImg == ''?reverseImg:upLoadReverseImg" @tap.prevent="uploadReverse"> | |||
| </image> | |||
| </view> | |||
| </view> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| data() { | |||
| return { | |||
| // 正面身份证 | |||
| positiveImg: '',//自己图片路径 | |||
| upLoadPositiveImg: '', | |||
| // 反面身份证 | |||
| reverseImg: '', //自己图片路径 | |||
| upLoadReverseImg: '', | |||
| baidu_token:' '//百度token | |||
| } | |||
| }, | |||
| onLoad() { | |||
| // this.getACSS_TOKEN() | |||
| }, | |||
| methods: { | |||
| // 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 lang="scss" scoped> | |||
| .idCard-box { | |||
| margin-top: 100px; | |||
| width: 100%; | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: row; | |||
| background-color: #fff; | |||
| .positive { | |||
| flex: 1; | |||
| height: 30%; | |||
| width: 30%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background-color: red; | |||
| image { | |||
| width: 80%; | |||
| height: 100%; | |||
| } | |||
| } | |||
| .reverse { | |||
| flex: 1; | |||
| height: 30%; | |||
| width: 30%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background-color: blue; | |||
| image { | |||
| width: 80%; | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| </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) | |||
| }) | |||
| } | |||
| @ -0,0 +1,75 @@ | |||
| // @ts-nocheck | |||
| import {platform} from '../platform' | |||
| /** | |||
| * base64转路径 | |||
| * @param {Object} base64 | |||
| */ | |||
| export function base64ToPath(base64: string, filename?: string):Promise<string> { | |||
| const [, format] = /^data:image\/(\w+);base64,/.exec(base64) || []; | |||
| return new Promise((resolve, reject) => { | |||
| // #ifdef MP | |||
| const fs = uni.getFileSystemManager() | |||
| //自定义文件名 | |||
| if (!filename && !format) { | |||
| reject(new Error('ERROR_BASE64SRC_PARSE')) | |||
| } | |||
| // const time = new Date().getTime(); | |||
| const name = filename || `${new Date().getTime()}.${format}`; | |||
| let pre = platform() | |||
| const filePath = `${pre.env.USER_DATA_PATH}/${name}` | |||
| fs.writeFile({ | |||
| filePath, | |||
| data: base64.split(',')[1], | |||
| encoding: 'base64', | |||
| success() { | |||
| resolve(filePath) | |||
| }, | |||
| fail(err) { | |||
| console.error(err) | |||
| reject(err) | |||
| } | |||
| }) | |||
| // #endif | |||
| // #ifdef H5 | |||
| // mime类型 | |||
| let mimeString = base64.split(',')[0].split(':')[1].split(';')[0]; | |||
| //base64 解码 | |||
| let byteString = atob(base64.split(',')[1]); | |||
| //创建缓冲数组 | |||
| let arrayBuffer = new ArrayBuffer(byteString.length); | |||
| //创建视图 | |||
| let intArray = new Uint8Array(arrayBuffer); | |||
| for (let i = 0; i < byteString.length; i++) { | |||
| intArray[i] = byteString.charCodeAt(i); | |||
| } | |||
| resolve(URL.createObjectURL(new Blob([intArray], { | |||
| type: mimeString | |||
| }))) | |||
| // #endif | |||
| // #ifdef APP-PLUS | |||
| const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) | |||
| bitmap.loadBase64Data(base64, () => { | |||
| if (!filename && !format) { | |||
| reject(new Error('ERROR_BASE64SRC_PARSE')) | |||
| } | |||
| // const time = new Date().getTime(); | |||
| const name = filename || `${new Date().getTime()}.${format}`; | |||
| const filePath = `_doc/uniapp_temp/${name}` | |||
| bitmap.save(filePath, {}, | |||
| () => { | |||
| bitmap.clear() | |||
| resolve(filePath) | |||
| }, | |||
| (error) => { | |||
| bitmap.clear() | |||
| reject(error) | |||
| }) | |||
| }, (error) => { | |||
| bitmap.clear() | |||
| reject(error) | |||
| }) | |||
| // #endif | |||
| }) | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| /** | |||
| * 将字符串转换为 camelCase 或 PascalCase 风格的命名约定 | |||
| * @param str 要转换的字符串 | |||
| * @param isPascalCase 指示是否转换为 PascalCase 的布尔值,默认为 false | |||
| * @returns 转换后的字符串 | |||
| */ | |||
| export function camelCase(str: string, isPascalCase: boolean = false): string { | |||
| // 将字符串分割成单词数组 | |||
| let words: string[] = str.split(/[\s_-]+/); | |||
| // 将数组中的每个单词首字母大写(除了第一个单词) | |||
| let camelCased: string[] = words.map((word, index):string => { | |||
| if (index == 0 && !isPascalCase) { | |||
| return word.toLowerCase(); // 第一个单词全小写 | |||
| } | |||
| return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); | |||
| }); | |||
| // 将数组中的单词拼接成一个字符串 | |||
| return camelCased.join(''); | |||
| }; | |||
| @ -0,0 +1,67 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X && APP | |||
| // #ifdef MP-ALIPAY | |||
| interface My { | |||
| SDKVersion: string | |||
| } | |||
| declare var my: My | |||
| // #endif | |||
| function compareVersion(v1:string, v2:string) { | |||
| let a1 = v1.split('.'); | |||
| let a2 = v2.split('.'); | |||
| const len = Math.max(a1.length, a2.length); | |||
| while (a1.length < len) { | |||
| a1.push('0'); | |||
| } | |||
| while (a2.length < len) { | |||
| a2.push('0'); | |||
| } | |||
| for (let i = 0; i < len; i++) { | |||
| const num1 = parseInt(a1[i], 10); | |||
| const num2 = parseInt(a2[i], 10); | |||
| if (num1 > num2) { | |||
| return 1; | |||
| } | |||
| if (num1 < num2) { | |||
| return -1; | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| function gte(version: string) { | |||
| let {SDKVersion} = uni.getSystemInfoSync(); | |||
| // #ifdef MP-ALIPAY | |||
| SDKVersion = my.SDKVersion | |||
| // #endif | |||
| return compareVersion(SDKVersion, version) >= 0; | |||
| } | |||
| // #endif | |||
| /** 环境是否支持canvas 2d */ | |||
| export function canIUseCanvas2d(): boolean { | |||
| // #ifdef MP-WEIXIN | |||
| return gte('2.9.0'); | |||
| // #endif | |||
| // #ifdef MP-ALIPAY | |||
| return gte('2.7.0'); | |||
| // #endif | |||
| // #ifdef MP-TOUTIAO | |||
| return gte('1.78.0'); | |||
| // #endif | |||
| // #ifdef UNI-APP-X && WEB || UNI-APP-X && APP | |||
| return true; | |||
| // #endif | |||
| // #ifndef MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO | |||
| return false | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,111 @@ | |||
| // @ts-nocheck | |||
| import { isString } from "../isString"; | |||
| import { isNumber } from "../isNumber"; | |||
| /** | |||
| * 将金额转换为中文大写形式 | |||
| * @param {number | string} amount - 需要转换的金额,可以是数字或字符串 | |||
| * @returns {string} 转换后的中文大写金额 | |||
| */ | |||
| function capitalizedAmount(amount : number) : string | |||
| function capitalizedAmount(amount : string) : string | |||
| function capitalizedAmount(amount : any | null) : string { | |||
| try { | |||
| let _amountStr :string; | |||
| let _amountNum :number = 0; | |||
| // 如果输入是字符串,先将其转换为数字,并去除逗号 | |||
| if (typeof amount == 'string') { | |||
| _amountNum = parseFloat((amount as string).replace(/,/g, '')); | |||
| } | |||
| if(isNumber(amount)) { | |||
| _amountNum = amount as number | |||
| } | |||
| // 判断输入是否为有效的金额 || isNaN(amount) | |||
| if (amount == null) throw new Error('不是有效的金额!'); | |||
| let result = ''; | |||
| // 处理负数情况 | |||
| if (_amountNum < 0) { | |||
| result = '欠'; | |||
| _amountNum = Math.abs(_amountNum); | |||
| } | |||
| // 金额不能超过千亿以上 | |||
| if (_amountNum >= 10e11) throw new Error('计算金额过大!'); | |||
| // 保留两位小数并转换为字符串 | |||
| _amountStr = _amountNum.toFixed(2); | |||
| // 定义数字、单位和小数单位的映射 | |||
| const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; | |||
| const units = ['', '拾', '佰', '仟']; | |||
| const bigUnits = ['', '万', '亿']; | |||
| const decimalUnits = ['角', '分']; | |||
| // 分离整数部分和小数部分 | |||
| const amountArray = _amountStr.split('.'); | |||
| let integerPart = amountArray[0]; // string| number[] | |||
| const decimalPart = amountArray[1]; | |||
| // 处理整数部分 | |||
| if (integerPart != '0') { | |||
| let _integerPart = integerPart.split('').map((item):number => parseInt(item)); | |||
| // 将整数部分按四位一级进行分组 | |||
| const levels = _integerPart.reverse().reduce((prev:string[][], item, index):string[][] => { | |||
| // const level = prev?.[0]?.length < 4 ? prev[0] : []; | |||
| const level = prev.length > 0 && prev[0].length < 4 ? prev[0]: [] | |||
| const value = item == 0 ? digits[item] : digits[item] + units[index % 4]; | |||
| level.unshift(value); | |||
| if (level.length == 1) { | |||
| prev.unshift(level); | |||
| } else { | |||
| prev[0] = level; | |||
| } | |||
| return prev; | |||
| }, [] as string[][]); | |||
| // 将分组后的整数部分转换为中文大写形式 | |||
| result += levels.reduce((prev, item, index):string => { | |||
| let _level = bigUnits[levels.length - index - 1]; | |||
| let _item = item.join('').replace(/(零)\1+/g, '$1'); | |||
| if (_item == '零') { | |||
| _level = ''; | |||
| _item = ''; | |||
| } else if (_item.endsWith('零')) { | |||
| _item = _item.slice(0, _item.length - 1); | |||
| } | |||
| return prev + _item + _level; | |||
| }, ''); | |||
| } else { | |||
| result += '零'; | |||
| } | |||
| // 添加元 | |||
| result += '元'; | |||
| // 处理小数部分 | |||
| if (decimalPart != '00') { | |||
| if (result == '零元') result = ''; | |||
| for (let i = 0; i < decimalPart.length; i++) { | |||
| const digit = parseInt(decimalPart.charAt(i)); | |||
| if (digit != 0) { | |||
| result += digits[digit] + decimalUnits[i]; | |||
| } | |||
| } | |||
| } else { | |||
| result += '整'; | |||
| } | |||
| return result; | |||
| } catch (error : Error) { | |||
| return error.message; | |||
| } | |||
| }; | |||
| @ -0,0 +1,63 @@ | |||
| ## 0.2.9(2025-02-19) | |||
| - chore: 更新文档 | |||
| ## 0.2.8(2025-02-11) | |||
| - chore: 更新文档 | |||
| ## 0.2.7(2025-01-17) | |||
| - fix: 针对canvas 平台判断优化 | |||
| ## 0.2.6(2025-01-09) | |||
| - feat: 增加`areaData`中国省市区数据 | |||
| ## 0.2.5(2025-01-07) | |||
| - fix: animation在app上类型问题 | |||
| ## 0.2.4(2025-01-04) | |||
| - feat: getRect类型问题 | |||
| ## 0.2.3(2025-01-01) | |||
| - chore: unitConvert使用uni.rpx2px | |||
| ## 0.2.2(2024-12-11) | |||
| - chore: 动画使用`requestAnimationFrame` | |||
| ## 0.2.1(2024-11-20) | |||
| - feat: 增加`characterLimit` | |||
| ## 0.2.0(2024-11-14) | |||
| - fix: vue2的类型问题 | |||
| ## 0.1.9(2024-11-14) | |||
| - feat: 增加`shuffle` | |||
| ## 0.1.8(2024-10-08) | |||
| - fix: vue2 条件编译 // #ifdef APP-IOS || APP-ANDROID 会生效 | |||
| ## 0.1.7(2024-09-23) | |||
| - fix: raf 类型跟随版本变更 | |||
| ## 0.1.6(2024-07-24) | |||
| - fix: vue2 app ts需要明确的后缀,所有补全 | |||
| - chore: 减少依赖 | |||
| ## 0.1.5(2024-07-21) | |||
| - feat: 删除 Hooks | |||
| - feat: 兼容uniappx | |||
| ## 0.1.4(2023-09-05) | |||
| - feat: 增加 Hooks `useIntersectionObserver` | |||
| - feat: 增加 `floatAdd` | |||
| - feat: 因为本人插件兼容 vue2 需要使用 `composition-api`,故增加vue文件代码插件的条件编译 | |||
| ## 0.1.3(2023-08-13) | |||
| - feat: 增加 `camelCase` | |||
| ## 0.1.2(2023-07-17) | |||
| - feat: 增加 `getClassStr` | |||
| ## 0.1.1(2023-07-06) | |||
| - feat: 增加 `isNumeric`, 区别于 `isNumber` | |||
| ## 0.1.0(2023-06-30) | |||
| - fix: `clamp`忘记导出了 | |||
| ## 0.0.9(2023-06-27) | |||
| - feat: 增加`arrayBufferToFile` | |||
| ## 0.0.8(2023-06-19) | |||
| - feat: 增加`createAnimation`、`clamp` | |||
| ## 0.0.7(2023-06-08) | |||
| - chore: 更新注释 | |||
| ## 0.0.6(2023-06-08) | |||
| - chore: 增加`createImage`为`lime-watermark`和`lime-qrcode`提供依赖 | |||
| ## 0.0.5(2023-06-03) | |||
| - chore: 更新注释 | |||
| ## 0.0.4(2023-05-22) | |||
| - feat: 增加`range`,`exif`,`selectComponent` | |||
| ## 0.0.3(2023-05-08) | |||
| - feat: 增加`fillZero`,`debounce`,`throttle`,`random` | |||
| ## 0.0.2(2023-05-05) | |||
| - chore: 更新文档 | |||
| ## 0.0.1(2023-05-05) | |||
| - 无 | |||
| @ -0,0 +1,63 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 计算字符串字符的长度并可以截取字符串。 | |||
| * @param char 传入字符串(maxcharacter条件下,一个汉字表示两个字符) | |||
| * @param max 规定最大字符串长度 | |||
| * @returns 当没有传入maxCharacter/maxLength 时返回字符串字符长度,当传入maxCharacter/maxLength时返回截取之后的字符串和长度。 | |||
| */ | |||
| export type CharacterLengthResult = { | |||
| length : number; | |||
| characters : string; | |||
| } | |||
| // #ifdef APP-ANDROID | |||
| type ChartType = any | |||
| // #endif | |||
| // #ifndef APP-ANDROID | |||
| type ChartType = string | number | |||
| // #endif | |||
| export function characterLimit(type : string, char : ChartType, max : number) : CharacterLengthResult { | |||
| const str = `${char}`; | |||
| if (str.length == 0) { | |||
| return { | |||
| length: 0, | |||
| characters: '', | |||
| } as CharacterLengthResult | |||
| } | |||
| if (type == 'maxcharacter') { | |||
| let len = 0; | |||
| for (let i = 0; i < str.length; i += 1) { | |||
| let currentStringLength : number// = 0; | |||
| const code = str.charCodeAt(i)! | |||
| if (code > 127 || code == 94) { | |||
| currentStringLength = 2; | |||
| } else { | |||
| currentStringLength = 1; | |||
| } | |||
| if (len + currentStringLength > max) { | |||
| return { | |||
| length: len, | |||
| characters: str.slice(0, i), | |||
| } as CharacterLengthResult | |||
| } | |||
| len += currentStringLength; | |||
| } | |||
| return { | |||
| length: len, | |||
| characters: str, | |||
| } as CharacterLengthResult | |||
| } else if (type == 'maxlength') { | |||
| const length = str.length > max ? max : str.length; | |||
| return { | |||
| length: length, | |||
| characters: str.slice(0, length), | |||
| } as CharacterLengthResult | |||
| } | |||
| return { | |||
| length: str.length, | |||
| characters: str, | |||
| } as CharacterLengthResult | |||
| }; | |||
| @ -0,0 +1,16 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 将一个值限制在指定的范围内 | |||
| * @param val 要限制的值 | |||
| * @param min 最小值 | |||
| * @param max 最大值 | |||
| * @returns 限制后的值 | |||
| */ | |||
| export function clamp(val: number, min: number, max: number): number { | |||
| return Math.max(min, Math.min(max, val)); | |||
| } | |||
| // console.log(clamp(5 ,0, 10)); // 输出: 5(在范围内,不做更改) | |||
| // console.log(clamp(-5 ,0, 10)); // 输出: 0(小于最小值,被限制为最小值) | |||
| // console.log(clamp(15 ,0, 10)); // 输出: 10(大于最大值,被限制为最大值) | |||
| @ -0,0 +1,10 @@ | |||
| // @ts-nocheck | |||
| // #ifdef UNI-APP-X && APP | |||
| export * from './uvue.ts' | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| export * from './vue.ts' | |||
| // #endif | |||
| @ -0,0 +1,17 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 深度克隆一个对象或数组 | |||
| * @param obj 要克隆的对象或数组 | |||
| * @returns 克隆后的对象或数组 | |||
| */ | |||
| export function cloneDeep<T>(obj: any): T { | |||
| // 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回 | |||
| // if(['number', 'string'].includes(typeof obj) || Array.isArray(obj)){ | |||
| // return obj as T | |||
| // } | |||
| if(typeof obj == 'object'){ | |||
| return JSON.parse(JSON.stringify(obj as T)) as T; | |||
| } | |||
| return obj as T | |||
| } | |||
| @ -0,0 +1,103 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 深度克隆一个对象或数组 | |||
| * @param obj 要克隆的对象或数组 | |||
| * @returns 克隆后的对象或数组 | |||
| */ | |||
| export function cloneDeep<T>(obj: any): T { | |||
| // 如果传入的对象为空,返回空 | |||
| if (obj === null) { | |||
| return null as unknown as T; | |||
| } | |||
| // 如果传入的对象是 Set 类型,则将其转换为数组,并通过新的 Set 构造函数创建一个新的 Set 对象 | |||
| if (obj instanceof Set) { | |||
| return new Set([...obj]) as unknown as T; | |||
| } | |||
| // 如果传入的对象是 Map 类型,则将其转换为数组,并通过新的 Map 构造函数创建一个新的 Map 对象 | |||
| if (obj instanceof Map) { | |||
| return new Map([...obj]) as unknown as T; | |||
| } | |||
| // 如果传入的对象是 WeakMap 类型,则直接用传入的 WeakMap 对象进行赋值 | |||
| if (obj instanceof WeakMap) { | |||
| let weakMap = new WeakMap(); | |||
| weakMap = obj; | |||
| return weakMap as unknown as T; | |||
| } | |||
| // 如果传入的对象是 WeakSet 类型,则直接用传入的 WeakSet 对象进行赋值 | |||
| if (obj instanceof WeakSet) { | |||
| let weakSet = new WeakSet(); | |||
| weakSet = obj; | |||
| return weakSet as unknown as T; | |||
| } | |||
| // 如果传入的对象是 RegExp 类型,则通过新的 RegExp 构造函数创建一个新的 RegExp 对象 | |||
| if (obj instanceof RegExp) { | |||
| return new RegExp(obj) as unknown as T; | |||
| } | |||
| // 如果传入的对象是 undefined 类型,则返回 undefined | |||
| if (typeof obj === 'undefined') { | |||
| return undefined as unknown as T; | |||
| } | |||
| // 如果传入的对象是数组,则递归调用 cloneDeep 函数对数组中的每个元素进行克隆 | |||
| if (Array.isArray(obj)) { | |||
| return obj.map(cloneDeep) as unknown as T; | |||
| } | |||
| // 如果传入的对象是 Date 类型,则通过新的 Date 构造函数创建一个新的 Date 对象 | |||
| if (obj instanceof Date) { | |||
| return new Date(obj.getTime()) as unknown as T; | |||
| } | |||
| // 如果传入的对象是普通对象,则使用递归调用 cloneDeep 函数对对象的每个属性进行克隆 | |||
| if (typeof obj === 'object') { | |||
| const newObj: any = {}; | |||
| for (const [key, value] of Object.entries(obj)) { | |||
| newObj[key] = cloneDeep(value); | |||
| } | |||
| const symbolKeys = Object.getOwnPropertySymbols(obj); | |||
| for (const key of symbolKeys) { | |||
| newObj[key] = cloneDeep(obj[key]); | |||
| } | |||
| return newObj; | |||
| } | |||
| // 如果传入的对象是基本数据类型(如字符串、数字等),则直接返回 | |||
| return obj; | |||
| } | |||
| // 示例使用 | |||
| // // 克隆一个对象 | |||
| // const obj = { name: 'John', age: 30 }; | |||
| // const clonedObj = cloneDeep(obj); | |||
| // console.log(clonedObj); // 输出: { name: 'John', age: 30 } | |||
| // console.log(clonedObj === obj); // 输出: false (副本与原对象是独立的) | |||
| // // 克隆一个数组 | |||
| // const arr = [1, 2, 3]; | |||
| // const clonedArr = cloneDeep(arr); | |||
| // console.log(clonedArr); // 输出: [1, 2, 3] | |||
| // console.log(clonedArr === arr); // 输出: false (副本与原数组是独立的) | |||
| // // 克隆一个包含嵌套对象的对象 | |||
| // const person = { | |||
| // name: 'Alice', | |||
| // age: 25, | |||
| // address: { | |||
| // city: 'New York', | |||
| // country: 'USA', | |||
| // }, | |||
| // }; | |||
| // const clonedPerson = cloneDeep(person); | |||
| // console.log(clonedPerson); // 输出: { name: 'Alice', age: 25, address: { city: 'New York', country: 'USA' } } | |||
| // console.log(clonedPerson === person); // 输出: false (副本与原对象是独立的) | |||
| // console.log(clonedPerson.address === person.address); // 输出: false (嵌套对象的副本也是独立的) | |||
| @ -0,0 +1,22 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 在给定数组中找到最接近目标数字的元素。 | |||
| * @param arr 要搜索的数字数组。 | |||
| * @param target 目标数字。 | |||
| * @returns 最接近目标数字的数组元素。 | |||
| */ | |||
| export function closest(arr: number[], target: number):number { | |||
| return arr.reduce((pre: number, cur: number):number => | |||
| Math.abs(pre - target) < Math.abs(cur - target) ? pre : cur | |||
| ); | |||
| } | |||
| // 示例 | |||
| // // 定义一个数字数组 | |||
| // const numbers = [1, 3, 5, 7, 9]; | |||
| // // 在数组中找到最接近目标数字 6 的元素 | |||
| // const closestNumber = closest(numbers, 6); | |||
| // console.log(closestNumber); // 输出结果: 5 | |||
| @ -0,0 +1,156 @@ | |||
| <template> | |||
| <view id="shared" style="height: 500px; width: 300px; background-color: aqua;"> | |||
| </view> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { getRect, getAllRect } from '@/uni_modules/lime-shared/getRect' | |||
| import { camelCase } from '@/uni_modules/lime-shared/camelCase' | |||
| import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d' | |||
| import { clamp } from '@/uni_modules/lime-shared/clamp' | |||
| import { cloneDeep } from '@/uni_modules/lime-shared/cloneDeep' | |||
| import { closest } from '@/uni_modules/lime-shared/closest' | |||
| import { debounce } from '@/uni_modules/lime-shared/debounce' | |||
| import { fillZero } from '@/uni_modules/lime-shared/fillZero' | |||
| import { floatAdd } from '@/uni_modules/lime-shared/floatAdd' | |||
| import { floatMul } from '@/uni_modules/lime-shared/floatMul' | |||
| import { floatDiv } from '@/uni_modules/lime-shared/floatDiv' | |||
| import { floatSub } from '@/uni_modules/lime-shared/floatSub' | |||
| import { getClassStr } from '@/uni_modules/lime-shared/getClassStr' | |||
| import { getCurrentPage } from '@/uni_modules/lime-shared/getCurrentPage' | |||
| import { getStyleStr } from '@/uni_modules/lime-shared/getStyleStr' | |||
| import { hasOwn } from '@/uni_modules/lime-shared/hasOwn' | |||
| import { isBase64 } from '@/uni_modules/lime-shared/isBase64' | |||
| import { isBrowser } from '@/uni_modules/lime-shared/isBrowser' | |||
| import { isDef } from '@/uni_modules/lime-shared/isDef' | |||
| import { isEmpty } from '@/uni_modules/lime-shared/isEmpty' | |||
| import { isFunction } from '@/uni_modules/lime-shared/isFunction' | |||
| import { isNumber } from '@/uni_modules/lime-shared/isNumber' | |||
| import { isNumeric } from '@/uni_modules/lime-shared/isNumeric' | |||
| import { isObject } from '@/uni_modules/lime-shared/isObject' | |||
| import { isPromise } from '@/uni_modules/lime-shared/isPromise' | |||
| import { isString } from '@/uni_modules/lime-shared/isString' | |||
| import { kebabCase } from '@/uni_modules/lime-shared/kebabCase' | |||
| import { raf, doubleRaf } from '@/uni_modules/lime-shared/raf' | |||
| import { random } from '@/uni_modules/lime-shared/random' | |||
| import { range } from '@/uni_modules/lime-shared/range' | |||
| import { sleep } from '@/uni_modules/lime-shared/sleep' | |||
| import { throttle } from '@/uni_modules/lime-shared/throttle' | |||
| import { toArray } from '@/uni_modules/lime-shared/toArray' | |||
| import { toBoolean } from '@/uni_modules/lime-shared/toBoolean' | |||
| import { toNumber } from '@/uni_modules/lime-shared/toNumber' | |||
| import { unitConvert } from '@/uni_modules/lime-shared/unitConvert' | |||
| import { getCurrentInstance } from '@/uni_modules/lime-shared/vue' | |||
| import { capitalizedAmount } from '@/uni_modules/lime-shared/capitalizedAmount' | |||
| // #ifdef VUE2 | |||
| type UTSJSONObject = any | |||
| // #endif | |||
| const context = getCurrentInstance() | |||
| // getRect('#shared', context!).then(res =>{ | |||
| // console.log('res', res.bottom) | |||
| // }) | |||
| // getAllRect('#shared', context).then(res =>{ | |||
| // console.log('res', res) | |||
| // }) | |||
| // console.log('camelCase::', camelCase("hello world")); | |||
| // console.log('camelCase::', camelCase("my_name_is_john", true)); | |||
| // console.log('canIUseCanvas2d::', canIUseCanvas2d()); | |||
| // console.log('clamp::', clamp(5 ,0, 10)); | |||
| // console.log('cloneDeep::', cloneDeep<UTSJSONObject>({a:5})); | |||
| // console.log('closest::', closest([1, 3, 5, 7, 9], 6)); | |||
| // const saveData = (data: any) => { | |||
| // // 模拟保存数据的操作 | |||
| // console.log(`Saving data: ${data}`); | |||
| // } | |||
| // const debouncedSaveData = debounce(saveData, 500); | |||
| // debouncedSaveData('Data 1'); | |||
| // debouncedSaveData('Data 2'); | |||
| // console.log('fillZero', fillZero(1)) | |||
| // console.log('floatAdd', floatAdd(0.1, 0.2), floatAdd(1.05, 0.05), floatAdd(0.1, 0.7), floatAdd(0.0001, 0.0002), floatAdd(123.456 , 789.012)) | |||
| // console.log('floatMul', floatMul(0.1, 0.02), floatMul(1.0255, 100)) | |||
| // console.log('floatDiv', floatDiv(10.44, 100), floatDiv(1.0255, 100), floatDiv(5.419909340994699, 0.2)) | |||
| // console.log('floatSub', floatSub(0.4, 0.1), floatSub(1.0255, 100)) | |||
| const now = () : number => System.nanoTime() / 1_000_000.0 | |||
| console.log('capitalizedAmount', capitalizedAmount(0.4)) | |||
| console.log('capitalizedAmount', capitalizedAmount(100)) | |||
| console.log('capitalizedAmount', capitalizedAmount(100000000)) | |||
| console.log('capitalizedAmount', capitalizedAmount('2023.04')) | |||
| console.log('capitalizedAmount', capitalizedAmount(-1024)) | |||
| console.log('now', now(), Date.now()) | |||
| // console.log('getClassStr', getClassStr({hover: true})) | |||
| // console.log('getStyleStr', getStyleStr({ color: 'red', fontSize: '16px', backgroundColor: '', border: null })) | |||
| // console.log('hasOwn', hasOwn({a: true}, 'key')) | |||
| // console.log('isBase64::', isBase64("SGVsbG8sIFdvcmxkIQ==")); | |||
| // console.log('isBrowser::', isBrowser); | |||
| // console.log('isDef::', isDef('6')); | |||
| // console.log('isEmpty::', isEmpty({a: true})); | |||
| // const b = () =>{} | |||
| // console.log('isFunction::', isFunction(b)); | |||
| // console.log('isNumber::', isNumber('6')); | |||
| // console.log('isNumeric::', isNumeric('6')); | |||
| // console.log('isObject::', isObject({})); | |||
| // const promise = ():Promise<boolean> => { | |||
| // return new Promise((resolve) => { | |||
| // resolve(true) | |||
| // }) | |||
| // } | |||
| // const a = promise() | |||
| // console.log('isPromise::', isPromise(a)); | |||
| // console.log('isString::', isString('null')); | |||
| // console.log('kebabCase::', kebabCase('my love')); | |||
| // console.log('raf::', raf(()=>{ | |||
| // console.log('raf:::1') | |||
| // })); | |||
| // console.log('doubleRaf::', doubleRaf(()=>{ | |||
| // console.log('doubleRaf:::1') | |||
| // })); | |||
| // console.log('random', random(0, 10)) | |||
| // console.log('random', random(0, 1, 2)) | |||
| // console.log('range', range(0, 10, 2)) | |||
| // console.log('sleep', sleep(300).then(res => { | |||
| // console.log('log') | |||
| // })) | |||
| // const handleScroll = (a: string) => { | |||
| // console.log("Scroll event handled!", a); | |||
| // } | |||
| // // // 使用节流函数对 handleScroll 进行节流,间隔时间为 500 毫秒 | |||
| // const throttledScroll = throttle(handleScroll, 500); | |||
| // throttledScroll('5'); | |||
| // const page = getCurrentPage() | |||
| // console.log('getCurrentPage::', page) | |||
| // console.log('toArray', toArray<number>(5)) | |||
| // console.log('toBoolean', toBoolean(5)) | |||
| // console.log('toNumber', toNumber('5')) | |||
| // console.log('unitConvert', unitConvert('5')) | |||
| // uni.getImageInfo({ | |||
| // src: '/static/logo.png', | |||
| // success(res) { | |||
| // console.log('res', res) | |||
| // } | |||
| // }) | |||
| export default { | |||
| } | |||
| </script> | |||
| <style> | |||
| </style> | |||
| @ -0,0 +1,9 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X | |||
| export * from './type.ts' | |||
| export * from './vue.ts' | |||
| // #endif | |||
| // #ifdef UNI-APP-X | |||
| export * from './uvue.ts' | |||
| // #endif | |||
| @ -0,0 +1,25 @@ | |||
| export type CreateAnimationOptions = { | |||
| /** | |||
| * 动画持续时间,单位ms | |||
| */ | |||
| duration ?: number; | |||
| /** | |||
| * 定义动画的效果 | |||
| * - linear: 动画从头到尾的速度是相同的 | |||
| * - ease: 动画以低速开始,然后加快,在结束前变慢 | |||
| * - ease-in: 动画以低速开始 | |||
| * - ease-in-out: 动画以低速开始和结束 | |||
| * - ease-out: 动画以低速结束 | |||
| * - step-start: 动画第一帧就跳至结束状态直到结束 | |||
| * - step-end: 动画一直保持开始状态,最后一帧跳到结束状态 | |||
| */ | |||
| timingFunction ?: string //'linear' | 'ease' | 'ease-in' | 'ease-in-out' | 'ease-out' | 'step-start' | 'step-end'; | |||
| /** | |||
| * 动画延迟时间,单位 ms | |||
| */ | |||
| delay ?: number; | |||
| /** | |||
| * 设置transform-origin | |||
| */ | |||
| transformOrigin ?: string; | |||
| } | |||
| @ -0,0 +1,5 @@ | |||
| // @ts-nocheck | |||
| // export * from '@/uni_modules/lime-animateIt' | |||
| export function createAnimation() { | |||
| console.error('当前环境不支持,请使用:lime-animateIt') | |||
| } | |||
| @ -0,0 +1,148 @@ | |||
| // @ts-nocheck | |||
| // nvue 需要在节点上设置ref或在export里传入 | |||
| // const animation = createAnimation({ | |||
| // ref: this.$refs['xxx'], | |||
| // duration: 0, | |||
| // timingFunction: 'linear' | |||
| // }) | |||
| // animation.opacity(1).translate(x, y).step({duration}) | |||
| // animation.export(ref) | |||
| // 抹平nvue 与 uni.createAnimation的使用差距 | |||
| // 但是nvue动画太慢 | |||
| import { type CreateAnimationOptions } from './type' | |||
| // #ifdef APP-NVUE | |||
| const nvueAnimation = uni.requireNativePlugin('animation') | |||
| type AnimationTypes = 'matrix' | 'matrix3d' | 'rotate' | 'rotate3d' | 'rotateX' | 'rotateY' | 'rotateZ' | 'scale' | 'scale3d' | 'scaleX' | 'scaleY' | 'scaleZ' | 'skew' | 'skewX' | 'skewY' | 'translate' | 'translate3d' | 'translateX' | 'translateY' | 'translateZ' | |||
| | 'opacity' | 'backgroundColor' | 'width' | 'height' | 'left' | 'right' | 'top' | 'bottom' | |||
| interface Styles { | |||
| [key : string] : any | |||
| } | |||
| interface StepConfig { | |||
| duration?: number | |||
| timingFunction?: string | |||
| delay?: number | |||
| needLayout?: boolean | |||
| transformOrigin?: string | |||
| } | |||
| interface StepAnimate { | |||
| styles?: Styles | |||
| config?: StepConfig | |||
| } | |||
| interface StepAnimates { | |||
| [key: number]: StepAnimate | |||
| } | |||
| // export interface CreateAnimationOptions extends UniApp.CreateAnimationOptions { | |||
| // ref?: string | |||
| // } | |||
| type Callback = (time: number) => void | |||
| const animateTypes1 : AnimationTypes[] = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d', | |||
| 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY', | |||
| 'translateZ' | |||
| ] | |||
| const animateTypes2 : AnimationTypes[] = ['opacity', 'backgroundColor'] | |||
| const animateTypes3 : AnimationTypes[] = ['width', 'height', 'left', 'right', 'top', 'bottom'] | |||
| class LimeAnimation { | |||
| ref : any | |||
| context : any | |||
| options : UniApp.CreateAnimationOptions | |||
| // stack : any[] = [] | |||
| next : number = 0 | |||
| currentStepAnimates : StepAnimates = {} | |||
| duration : number = 0 | |||
| constructor(options : CreateAnimationOptions) { | |||
| const {ref} = options | |||
| this.ref = ref | |||
| this.options = options | |||
| } | |||
| addAnimate(type : AnimationTypes, args: (string | number)[]) { | |||
| let aniObj = this.currentStepAnimates[this.next] | |||
| let stepAnimate:StepAnimate = {} | |||
| if (!aniObj) { | |||
| stepAnimate = {styles: {}, config: {}} | |||
| } else { | |||
| stepAnimate = aniObj | |||
| } | |||
| if (animateTypes1.includes(type)) { | |||
| if (!stepAnimate.styles.transform) { | |||
| stepAnimate.styles.transform = '' | |||
| } | |||
| let unit = '' | |||
| if (type === 'rotate') { | |||
| unit = 'deg' | |||
| } | |||
| stepAnimate.styles.transform += `${type}(${args.map((v: number) => v + unit).join(',')}) ` | |||
| } else { | |||
| stepAnimate.styles[type] = `${args.join(',')}` | |||
| } | |||
| this.currentStepAnimates[this.next] = stepAnimate | |||
| } | |||
| animateRun(styles: Styles = {}, config:StepConfig = {}, ref: any) { | |||
| const el = ref || this.ref | |||
| if (!el) return | |||
| return new Promise((resolve) => { | |||
| const time = +new Date() | |||
| nvueAnimation.transition(el, { | |||
| styles, | |||
| ...config | |||
| }, () => { | |||
| resolve(+new Date() - time) | |||
| }) | |||
| }) | |||
| } | |||
| nextAnimate(animates: StepAnimates, step: number = 0, ref: any, cb: Callback) { | |||
| let obj = animates[step] | |||
| if (obj) { | |||
| let { styles, config } = obj | |||
| // this.duration += config.duration | |||
| this.animateRun(styles, config, ref).then((time: number) => { | |||
| step += 1 | |||
| this.duration += time | |||
| this.nextAnimate(animates, step, ref, cb) | |||
| }) | |||
| } else { | |||
| this.currentStepAnimates = {} | |||
| cb && cb(this.duration) | |||
| } | |||
| } | |||
| step(config:StepConfig = {}) { | |||
| this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config) | |||
| this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin | |||
| this.next++ | |||
| return this | |||
| } | |||
| export(ref: any, cb?: Callback) { | |||
| ref = ref || this.ref | |||
| if(!ref) return | |||
| this.duration = 0 | |||
| this.next = 0 | |||
| this.nextAnimate(this.currentStepAnimates, 0, ref, cb) | |||
| return null | |||
| } | |||
| } | |||
| animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => { | |||
| LimeAnimation.prototype[type] = function(...args: (string | number)[]) { | |||
| this.addAnimate(type, args) | |||
| return this | |||
| } | |||
| }) | |||
| // #endif | |||
| export function createAnimation(options : CreateAnimationOptions) { | |||
| // #ifndef APP-NVUE | |||
| return uni.createAnimation({ ...options }) | |||
| // #endif | |||
| // #ifdef APP-NVUE | |||
| return new LimeAnimation(options) | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,73 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X && APP | |||
| import type { ComponentInternalInstance } from '@/uni_modules/lime-shared/vue' | |||
| import { getRect } from '@/uni_modules/lime-shared/getRect' | |||
| import { canIUseCanvas2d } from '@/uni_modules/lime-shared/canIUseCanvas2d' | |||
| export const isCanvas2d = canIUseCanvas2d() | |||
| // #endif | |||
| export function createCanvas(canvasId : string, component : ComponentInternalInstance) { | |||
| // #ifdef UNI-APP-X | |||
| uni.createCanvasContextAsync({ | |||
| canvasId, | |||
| component, | |||
| success(context : CanvasContext) { | |||
| }, | |||
| fail(error : UniError) { | |||
| } | |||
| }) | |||
| // #endif | |||
| // #ifndef UNI-APP-X | |||
| const isCanvas2d = canIUseCanvas2d() | |||
| getRect('#' + canvasId, context, isCanvas2d).then(res => { | |||
| if (res.node) { | |||
| res.node.width = res.width | |||
| res.node.height = res.height | |||
| return res.node | |||
| } else { | |||
| const ctx = uni.createCanvasContext(canvasId, context) | |||
| if (!ctx._drawImage) { | |||
| ctx._drawImage = ctx.drawImage | |||
| ctx.drawImage = function (...args) { | |||
| const { path } = args.shift() | |||
| ctx._drawImage(path, ...args) | |||
| } | |||
| } | |||
| if (!ctx.getImageData) { | |||
| ctx.getImageData = function () { | |||
| return new Promise((resolve, reject) => { | |||
| uni.canvasGetImageData({ | |||
| canvasId, | |||
| x: parseInt(arguments[0]), | |||
| y: parseInt(arguments[1]), | |||
| width: parseInt(arguments[2]), | |||
| height: parseInt(arguments[3]), | |||
| success(res) { | |||
| resolve(res) | |||
| }, | |||
| fail(err) { | |||
| reject(err) | |||
| } | |||
| }, context) | |||
| }) | |||
| } | |||
| return { | |||
| getContext(type: string) { | |||
| if(type == '2d') { | |||
| return ctx | |||
| } | |||
| }, | |||
| width: res.width, | |||
| height: res.height, | |||
| createImage | |||
| } | |||
| } | |||
| } | |||
| }) | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,71 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X && APP | |||
| import {isBrowser} from '../isBrowser' | |||
| class Image { | |||
| currentSrc: string | null = null | |||
| naturalHeight: number = 0 | |||
| naturalWidth: number = 0 | |||
| width: number = 0 | |||
| height: number = 0 | |||
| tagName: string = 'IMG' | |||
| path: string = '' | |||
| crossOrigin: string = '' | |||
| referrerPolicy: string = '' | |||
| onload: () => void = () => {} | |||
| onerror: () => void = () => {} | |||
| complete: boolean = false | |||
| constructor() {} | |||
| set src(src: string) { | |||
| console.log('src', src) | |||
| if(!src) { | |||
| return this.onerror() | |||
| } | |||
| src = src.replace(/^@\//,'/') | |||
| this.currentSrc = src | |||
| uni.getImageInfo({ | |||
| src, | |||
| success: (res) => { | |||
| const localReg = /^\.|^\/(?=[^\/])/; | |||
| // #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO | |||
| res.path = localReg.test(src) ? `/${res.path}` : res.path; | |||
| // #endif | |||
| this.complete = true | |||
| this.path = res.path | |||
| this.naturalWidth = this.width = res.width | |||
| this.naturalHeight = this.height = res.height | |||
| this.onload() | |||
| }, | |||
| fail: () => { | |||
| this.onerror() | |||
| } | |||
| }) | |||
| } | |||
| get src() { | |||
| return this.currentSrc | |||
| } | |||
| } | |||
| interface UniImage extends WechatMiniprogram.Image { | |||
| complete?: boolean | |||
| naturalHeight?: number | |||
| naturalWidth?: number | |||
| } | |||
| /** 创建用于 canvas 的 img */ | |||
| export function createImage(canvas?: any): HTMLImageElement | UniImage { | |||
| if(canvas && canvas.createImage) { | |||
| return (canvas as WechatMiniprogram.Canvas).createImage() | |||
| } else if(this && this['tagName'] == 'canvas' && !('toBlob' in this) || canvas && !('toBlob' in canvas)){ | |||
| return new Image() | |||
| } else if(isBrowser) { | |||
| return new window.Image() | |||
| } | |||
| return new Image() | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| export function createImage():Image{ | |||
| // console.error('当前环境不支持') | |||
| return new Image() | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,10 @@ | |||
| // @ts-nocheck | |||
| // #ifdef UNI-APP-X && APP | |||
| export * from './uvue.ts' | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| export * from './vue.ts' | |||
| // #endif | |||
| @ -0,0 +1,36 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 防抖函数,通过延迟一定时间来限制函数的执行频率。 | |||
| * @param fn 要防抖的函数。 | |||
| * @param wait 触发防抖的等待时间,单位为毫秒。 | |||
| * @returns 防抖函数。 | |||
| */ | |||
| export function debounce<A extends any>(fn : (args: A)=> void, wait = 300): (args: A)=> void { | |||
| let timer = -1 | |||
| return (args: A) => { | |||
| if (timer >-1) {clearTimeout(timer)}; | |||
| timer = setTimeout(()=>{ | |||
| fn(args) | |||
| }, wait) | |||
| } | |||
| }; | |||
| // 示例 | |||
| // 定义一个函数 | |||
| // function saveData(data: string) { | |||
| // // 模拟保存数据的操作 | |||
| // console.log(`Saving data: ${data}`); | |||
| // } | |||
| // // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数 | |||
| // const debouncedSaveData = debounce(saveData, 500); | |||
| // // 连续调用防抖函数 | |||
| // debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数 | |||
| // debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数 | |||
| // 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2" | |||
| @ -0,0 +1,40 @@ | |||
| // @ts-nocheck | |||
| type Timeout = ReturnType<typeof setTimeout> | null; | |||
| /** | |||
| * 防抖函数,通过延迟一定时间来限制函数的执行频率。 | |||
| * @param fn 要防抖的函数。 | |||
| * @param wait 触发防抖的等待时间,单位为毫秒。 | |||
| * @returns 防抖函数。 | |||
| */ | |||
| export function debounce<A extends any, R>( | |||
| fn : (...args : A) => R, | |||
| wait : number = 300) : (...args : A) => void { | |||
| let timer : Timeout = null; | |||
| return function (...args : A) { | |||
| if (timer) clearTimeout(timer); // 如果上一个 setTimeout 存在,则清除它 | |||
| // 设置一个新的 setTimeout,在指定的等待时间后调用防抖函数 | |||
| timer = setTimeout(() => { | |||
| fn.apply(this, args); // 使用提供的参数调用原始函数 | |||
| }, wait); | |||
| }; | |||
| }; | |||
| // 示例 | |||
| // 定义一个函数 | |||
| // function saveData(data: string) { | |||
| // // 模拟保存数据的操作 | |||
| // console.log(`Saving data: ${data}`); | |||
| // } | |||
| // // 创建一个防抖函数,延迟 500 毫秒后调用 saveData 函数 | |||
| // const debouncedSaveData = debounce(saveData, 500); | |||
| // // 连续调用防抖函数 | |||
| // debouncedSaveData('Data 1'); // 不会立即调用 saveData 函数 | |||
| // debouncedSaveData('Data 2'); // 不会立即调用 saveData 函数 | |||
| // 在 500 毫秒后,只会调用一次 saveData 函数,输出结果为 "Saving data: Data 2" | |||
| @ -0,0 +1,9 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X && APP | |||
| export * from './vue.ts' | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| export * from './uvue.ts' | |||
| // #endif | |||
| @ -0,0 +1,7 @@ | |||
| class EXIF { | |||
| constructor(){ | |||
| console.error('当前环境不支持') | |||
| } | |||
| } | |||
| export const exif = new EXIF() | |||
| @ -0,0 +1,11 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 在数字前填充零,返回字符串形式的结果 | |||
| * @param number 要填充零的数字 | |||
| * @param length 填充零后的字符串长度,默认为2 | |||
| * @returns 填充零后的字符串 | |||
| */ | |||
| export function fillZero(number: number, length: number = 2): string { | |||
| // 将数字转换为字符串,然后使用 padStart 方法填充零到指定长度 | |||
| return `${number}`.padStart(length, '0'); | |||
| } | |||
| @ -0,0 +1,36 @@ | |||
| import { isNumber } from '../isNumber' | |||
| /** | |||
| * 返回两个浮点数相加的结果 | |||
| * @param num1 第一个浮点数 | |||
| * @param num2 第二个浮点数 | |||
| * @returns 两个浮点数的相加结果 | |||
| */ | |||
| export function floatAdd(num1 : number, num2 : number) : number { | |||
| // 检查 num1 和 num2 是否为数字类型 | |||
| if (!(isNumber(num1) || isNumber(num2))) { | |||
| console.warn('Please pass in the number type'); | |||
| return NaN; | |||
| } | |||
| let r1 : number, r2 : number, m : number; | |||
| try { | |||
| // 获取 num1 小数点后的位数 | |||
| r1 = num1.toString().split('.')[1].length; | |||
| } catch (error) { | |||
| r1 = 0; | |||
| } | |||
| try { | |||
| // 获取 num2 小数点后的位数 | |||
| r2 = num2.toString().split('.')[1].length; | |||
| } catch (error) { | |||
| r2 = 0; | |||
| } | |||
| // 计算需要扩大的倍数 | |||
| m = Math.pow(10, Math.max(r1, r2)); | |||
| // 返回相加结果 | |||
| return (num1 * m + num2 * m) / m; | |||
| } | |||
| @ -0,0 +1,45 @@ | |||
| import { floatMul } from '../floatMul'; | |||
| import { isNumber } from '../isNumber'; | |||
| /** | |||
| * 除法函数,用于处理浮点数除法并保持精度。 | |||
| * @param {number} num1 - 被除数。 | |||
| * @param {number} num2 - 除数。 | |||
| * @returns {number} 除法运算的结果,保留正确的精度。 | |||
| */ | |||
| export function floatDiv(num1:number, num2:number):number { | |||
| // 如果传入的不是数字类型,则打印警告并返回NaN | |||
| if (!isNumber(num1) || !isNumber(num2)) { | |||
| console.warn('请传入数字类型'); | |||
| return NaN; | |||
| } | |||
| let m1 = 0, // 被除数小数点后的位数 | |||
| m2 = 0, // 除数小数点后的位数 | |||
| s1 = num1.toString(), // 将被除数转换为字符串 | |||
| s2 = num2.toString(); // 将除数转换为字符串 | |||
| // 计算被除数小数点后的位数 | |||
| try { | |||
| m1 += s1.split('.')[1].length; | |||
| } catch (error) {} | |||
| // 计算除数小数点后的位数 | |||
| try { | |||
| m2 += s2.split('.')[1].length; | |||
| } catch (error) {} | |||
| // 进行除法运算并处理小数点后的位数,使用之前定义的乘法函数保持精度 | |||
| // #ifdef APP-ANDROID | |||
| return floatMul( | |||
| parseFloat(s1.replace('.', '')) / parseFloat(s2.replace('.', '')), | |||
| Math.pow(10, m2 - m1), | |||
| ); | |||
| // #endif | |||
| // #ifndef APP-ANDROID | |||
| return floatMul( | |||
| Number(s1.replace('.', '')) / Number(s2.replace('.', '')), | |||
| Math.pow(10, m2 - m1), | |||
| ); | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,44 @@ | |||
| // @ts-nocheck | |||
| import {isNumber} from '../isNumber'; | |||
| // #ifdef APP-ANDROID | |||
| import BigDecimal from 'java.math.BigDecimal' | |||
| // import BigDecimal from 'java.math.BigDecimal' | |||
| // import StringBuilder from 'java.lang.StringBuilder' | |||
| // import java.math.BigDecimal; | |||
| // #endif | |||
| /** | |||
| * 乘法函数,用于处理浮点数乘法并保持精度。 | |||
| * @param {number} num1 - 第一个乘数。 | |||
| * @param {number} num2 - 第二个乘数。 | |||
| * @returns {number} 乘法运算的结果,保留正确的精度。 | |||
| */ | |||
| export function floatMul(num1 : number, num2 : number) : number { | |||
| if (!(isNumber(num1) || isNumber(num2))) { | |||
| console.warn('Please pass in the number type'); | |||
| return NaN; | |||
| } | |||
| let m = 0; | |||
| // #ifdef APP-ANDROID | |||
| let s1 = BigDecimal.valueOf(num1.toDouble()).toPlainString(); //new UTSNumber(num1).toString() // //`${num1.toFloat()}`// num1.toString(), | |||
| let s2 = BigDecimal.valueOf(num2.toDouble()).toPlainString(); //new UTSNumber(num2).toString() //`${num2.toFloat()}`//.toString(); | |||
| // #endif | |||
| // #ifndef APP-ANDROID | |||
| let s1:string = `${num1}`// num1.toString(), | |||
| let s2:string = `${num2}`//.toString(); | |||
| // #endif | |||
| try { | |||
| m += s1.split('.')[1].length; | |||
| } catch (error) { } | |||
| try { | |||
| m += s2.split('.')[1].length; | |||
| } catch (error) { } | |||
| // #ifdef APP-ANDROID | |||
| return parseFloat(s1.replace('.', '')) * parseFloat(s2.replace('.', '')) / Math.pow(10, m); | |||
| // #endif | |||
| // #ifndef APP-ANDROID | |||
| return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m); | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| import { isNumber } from '../isNumber'; | |||
| /** | |||
| * 减法函数,用于处理浮点数减法并保持精度。 | |||
| * @param {number} num1 - 被减数。 | |||
| * @param {number} num2 - 减数。 | |||
| * @returns {number} 减法运算的结果,保留正确的精度。 | |||
| */ | |||
| export function floatSub(num1 : number, num2 : number) : number { | |||
| if (!(isNumber(num1) || isNumber(num2))) { | |||
| console.warn('Please pass in the number type'); | |||
| return NaN; | |||
| } | |||
| let r1:number, r2:number, m:number, n:number; | |||
| try { | |||
| r1 = num1.toString().split('.')[1].length; | |||
| } catch (error) { | |||
| r1 = 0; | |||
| } | |||
| try { | |||
| r2 = num2.toString().split('.')[1].length; | |||
| } catch (error) { | |||
| r2 = 0; | |||
| } | |||
| m = Math.pow(10, Math.max(r1, r2)); | |||
| n = r1 >= r2 ? r1 : r2; | |||
| // #ifndef APP-ANDROID | |||
| return Number(((num1 * m - num2 * m) / m).toFixed(n)); | |||
| // #endif | |||
| // #ifdef APP-ANDROID | |||
| return parseFloat(((num1 * m - num2 * m) / m).toFixed(n)); | |||
| // #endif | |||
| } | |||
| @ -0,0 +1,53 @@ | |||
| // @ts-nocheck | |||
| // #ifdef UNI-APP-X && APP | |||
| import { isNumber } from '../isNumber' | |||
| import { isString } from '../isString' | |||
| import { isDef } from '../isDef' | |||
| // #endif | |||
| /** | |||
| * 获取对象的类名字符串 | |||
| * @param obj - 需要处理的对象 | |||
| * @returns 由对象属性作为类名组成的字符串 | |||
| */ | |||
| export function getClassStr<T>(obj : T) : string { | |||
| let classNames : string[] = []; | |||
| // #ifdef UNI-APP-X && APP | |||
| if (obj instanceof UTSJSONObject) { | |||
| (obj as UTSJSONObject).toMap().forEach((value, key) => { | |||
| if (isDef(value)) { | |||
| if (isNumber(value)) { | |||
| classNames.push(key); | |||
| } | |||
| if (isString(value) && value !== '') { | |||
| classNames.push(key); | |||
| } | |||
| if (typeof value == 'boolean' && (value as boolean)) { | |||
| classNames.push(key); | |||
| } | |||
| } | |||
| }) | |||
| } | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| // 遍历对象的属性 | |||
| for (let key in obj) { | |||
| // 检查属性确实属于对象自身且其值为true | |||
| if ((obj as any).hasOwnProperty(key) && obj[key]) { | |||
| // 将属性名添加到类名数组中 | |||
| classNames.push(key); | |||
| } | |||
| } | |||
| // #endif | |||
| // 将类名数组用空格连接成字符串并返回 | |||
| return classNames.join(' '); | |||
| } | |||
| // 示例 | |||
| // const obj = { foo: true, bar: false, baz: true }; | |||
| // const classNameStr = getClassStr(obj); | |||
| // console.log(classNameStr); // 输出: "foo baz" | |||
| @ -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,5 @@ | |||
| // @ts-nocheck | |||
| export const getCurrentPage = ():Page => { | |||
| const pages = getCurrentPages(); | |||
| return pages[pages.length - 1] | |||
| }; | |||
| @ -0,0 +1,6 @@ | |||
| // @ts-nocheck | |||
| /** 获取当前页 */ | |||
| export const getCurrentPage = () => { | |||
| const pages = getCurrentPages(); | |||
| return pages[pages.length - 1] //as T & WechatMiniprogram.Page.TrivialInstance; | |||
| }; | |||
| @ -0,0 +1,62 @@ | |||
| // @ts-nocheck | |||
| // #ifdef APP-NVUE || APP-VUE | |||
| export const getLocalFilePath = (path : string) => { | |||
| if (typeof plus == 'undefined') return path | |||
| if (/^(_www|_doc|_documents|_downloads|file:\/\/|\/storage\/emulated\/0\/)/.test(path)) return path | |||
| if (/^\//.test(path)) { | |||
| const localFilePath = plus.io.convertAbsoluteFileSystem(path) | |||
| if (localFilePath !== path) { | |||
| return localFilePath | |||
| } else { | |||
| path = path.slice(1) | |||
| } | |||
| } | |||
| return '_www/' + path | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| export { getResourcePath as getLocalFilePath } from '@/uni_modules/lime-file-utils' | |||
| // export const getLocalFilePath = (path : string) : string => { | |||
| // let uri = path | |||
| // if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) { | |||
| // return uri | |||
| // } | |||
| // if (uri.startsWith("file://")) { | |||
| // uri = uri.substring("file://".length) | |||
| // } else if (uri.startsWith("unifile://")) { | |||
| // uri = UTSAndroid.convert2AbsFullPath(uri) | |||
| // } else { | |||
| // uri = UTSAndroid.convert2AbsFullPath(uri) | |||
| // if (uri.startsWith("/android_asset/")) { | |||
| // uri = uri.replace("/android_asset/", "") | |||
| // } | |||
| // } | |||
| // if (new File(uri).exists()) { | |||
| // return uri | |||
| // } else { | |||
| // return null | |||
| // } | |||
| // // return UTSAndroid.convert2AbsFullPath(path) | |||
| // } | |||
| // #endif | |||
| // #ifdef APP-IOS | |||
| // export const getLocalFilePath = (path : string) : string => { | |||
| // try { | |||
| // let uri = path | |||
| // if (uri.startsWith("http") || uri.startsWith("<svg") || uri.startsWith("data:image/svg+xml")) { | |||
| // return uri | |||
| // } | |||
| // if (uri.startsWith("file://")) { | |||
| // return uri.substring("file://".length) | |||
| // } else if (path.startsWith("/var/")) { | |||
| // return path | |||
| // } | |||
| // return UTSiOS.getResourcePath(path) | |||
| // } catch (e) { | |||
| // return null | |||
| // } | |||
| // // return UTSiOS.getResourcePath(path) | |||
| // } | |||
| // #endif | |||
| @ -0,0 +1,9 @@ | |||
| // @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,16 @@ | |||
| // @ts-nocheck | |||
| export function getRect(selector : string, context: ComponentPublicInstance):Promise<NodeInfo> { | |||
| return new Promise((resolve)=>{ | |||
| uni.createSelectorQuery().in(context).select(selector).boundingClientRect(res =>{ | |||
| resolve(res as NodeInfo) | |||
| }).exec(); | |||
| }) | |||
| } | |||
| export function getAllRect(selector : string, context: ComponentPublicInstance):Promise<NodeInfo[]> { | |||
| return new Promise((resolve)=>{ | |||
| uni.createSelectorQuery().in(context).selectAll(selector).boundingClientRect(res =>{ | |||
| resolve(res as NodeInfo[]) | |||
| }).exec(); | |||
| }) | |||
| } | |||
| @ -0,0 +1,117 @@ | |||
| // @ts-nocheck | |||
| // #ifdef APP-NVUE | |||
| // 当编译环境是 APP-NVUE 时,引入 uni.requireNativePlugin('dom'),具体插件用途未知 | |||
| const dom = uni.requireNativePlugin('dom') | |||
| // #endif | |||
| /** | |||
| * 获取节点信息 | |||
| * @param selector 选择器字符串 | |||
| * @param context ComponentInternalInstance 对象 | |||
| * @param node 是否获取node | |||
| * @returns 包含节点信息的 Promise 对象 | |||
| */ | |||
| export function getRect(selector : string, context : ComponentInternalInstance|ComponentPublicInstance, node: boolean = false) { | |||
| // 之前是个对象,现在改成实例,防止旧版会报错 | |||
| if(context== null) { | |||
| return Promise.reject('context is null') | |||
| } | |||
| if(context.context){ | |||
| context = context.context | |||
| } | |||
| // #ifdef MP || VUE2 | |||
| if (context.proxy) context = context.proxy | |||
| // #endif | |||
| return new Promise<UniNamespace.NodeInfo>((resolve, reject) => { | |||
| // #ifndef APP-NVUE | |||
| const dom = uni.createSelectorQuery().in(context).select(selector); | |||
| const result = (rect: UniNamespace.NodeInfo) => { | |||
| if (rect) { | |||
| resolve(rect) | |||
| } else { | |||
| reject('no rect') | |||
| } | |||
| } | |||
| if (!node) { | |||
| dom.boundingClientRect(result).exec() | |||
| } else { | |||
| dom.fields({ | |||
| node: true, | |||
| size: true, | |||
| rect: true | |||
| }, result).exec() | |||
| } | |||
| // #endif | |||
| // #ifdef APP-NVUE | |||
| let { context } = options | |||
| if (/#|\./.test(selector) && context.refs) { | |||
| selector = selector.replace(/#|\./, '') | |||
| if (context.refs[selector]) { | |||
| selector = context.refs[selector] | |||
| if(Array.isArray(selector)) { | |||
| selector = selector[0] | |||
| } | |||
| } | |||
| } | |||
| dom.getComponentRect(selector, (res) => { | |||
| if (res.size) { | |||
| resolve(res.size) | |||
| } else { | |||
| reject('no rect') | |||
| } | |||
| }) | |||
| // #endif | |||
| }); | |||
| }; | |||
| export function getAllRect(selector : string, context: ComponentInternalInstance|ComponentPublicInstance, node:boolean = false) { | |||
| if(context== null) { | |||
| return Promise.reject('context is null') | |||
| } | |||
| // #ifdef MP || VUE2 | |||
| if (context.proxy) context = context.proxy | |||
| // #endif | |||
| return new Promise<UniNamespace.NodeInfo>((resolve, reject) => { | |||
| // #ifndef APP-NVUE | |||
| const dom = uni.createSelectorQuery().in(context).selectAll(selector); | |||
| const result = (rect: UniNamespace.NodeInfo[]) => { | |||
| if (rect) { | |||
| resolve(rect) | |||
| } else { | |||
| reject('no rect') | |||
| } | |||
| } | |||
| if (!node) { | |||
| dom.boundingClientRect(result).exec() | |||
| } else { | |||
| dom.fields({ | |||
| node: true, | |||
| size: true, | |||
| rect: true | |||
| }, result).exec() | |||
| } | |||
| // #endif | |||
| // #ifdef APP-NVUE | |||
| let { context } = options | |||
| if (/#|\./.test(selector) && context.refs) { | |||
| selector = selector.replace(/#|\./, '') | |||
| if (context.refs[selector]) { | |||
| selector = context.refs[selector] | |||
| if(Array.isArray(selector)) { | |||
| selector = selector[0] | |||
| } | |||
| } | |||
| } | |||
| dom.getComponentRect(selector, (res) => { | |||
| if (res.size) { | |||
| resolve([res.size]) | |||
| } else { | |||
| reject('no rect') | |||
| } | |||
| }) | |||
| // #endif | |||
| }); | |||
| }; | |||
| @ -0,0 +1,54 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X && APP | |||
| interface CSSProperties { | |||
| [key : string] : string | number | null | |||
| } | |||
| // #endif | |||
| // #ifdef VUE3 | |||
| // #ifdef UNI-APP-X && APP | |||
| type CSSProperties = UTSJSONObject | |||
| // #endif | |||
| // #endif | |||
| /** | |||
| * 将字符串转换为带有连字符分隔的小写形式 | |||
| * @param key - 要转换的字符串 | |||
| * @returns 转换后的字符串 | |||
| */ | |||
| export function toLowercaseSeparator(key : string):string { | |||
| return key.replace(/([A-Z])/g, '-$1').toLowerCase(); | |||
| } | |||
| /** | |||
| * 获取样式对象对应的样式字符串 | |||
| * @param style - CSS样式对象 | |||
| * @returns 由非空有效样式属性键值对组成的字符串 | |||
| */ | |||
| export function getStyleStr(style : CSSProperties) : string { | |||
| // #ifdef UNI-APP-X && APP | |||
| let styleStr = ''; | |||
| style.toMap().forEach((value, key) => { | |||
| if(value !== null && value != '') { | |||
| styleStr += `${toLowercaseSeparator(key as string)}: ${value};` | |||
| } | |||
| }) | |||
| return styleStr | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| return Object.keys(style) | |||
| .filter( | |||
| (key) => | |||
| style[key] !== undefined && | |||
| style[key] !== null && | |||
| style[key] !== '') | |||
| .map((key : string) => `${toLowercaseSeparator(key)}: ${style[key]};`) | |||
| .join(' '); | |||
| // #endif | |||
| } | |||
| // 示例 | |||
| // const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null }; | |||
| // const styleStr = getStyleStr(style); | |||
| // console.log(styleStr); | |||
| // 输出: "color: red; font-size: 16px;" | |||
| @ -0,0 +1,39 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X | |||
| interface CSSProperties { | |||
| [key : string] : string | number | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X | |||
| type CSSProperties = UTSJSONObject | |||
| // #endif | |||
| /** | |||
| * 将字符串转换为带有连字符分隔的小写形式 | |||
| * @param key - 要转换的字符串 | |||
| * @returns 转换后的字符串 | |||
| */ | |||
| export function toLowercaseSeparator(key : string) : string { | |||
| return key.replace(/([A-Z])/g, '-$1').toLowerCase(); | |||
| } | |||
| /** | |||
| * 获取样式对象对应的样式字符串 | |||
| * @param style - CSS样式对象 | |||
| * @returns 由非空有效样式属性键值对组成的字符串 | |||
| */ | |||
| export function getStyleStr(style : CSSProperties) : string { | |||
| let styleStr = ''; | |||
| style.toMap().forEach((value, key) => { | |||
| if(value !== null && value != '') { | |||
| styleStr += `${toLowercaseSeparator(key as string)}: ${value};` | |||
| } | |||
| }) | |||
| return styleStr | |||
| } | |||
| // 示例 | |||
| // const style = { color: 'red', fontSize: '16px', backgroundColor: '', border: null }; | |||
| // const styleStr = getStyleStr(style); | |||
| // console.log(styleStr); | |||
| // 输出: "color: red; font-size: 16px;" | |||
| @ -0,0 +1,9 @@ | |||
| // @ts-nocheck | |||
| // #ifdef UNI-APP-X && APP | |||
| export * from './uvue.ts' | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| export * from './vue.ts' | |||
| // #endif | |||
| @ -0,0 +1,39 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查对象或数组是否具有指定的属性或键 | |||
| * @param obj 要检查的对象或数组 | |||
| * @param key 指定的属性或键 | |||
| * @returns 如果对象或数组具有指定的属性或键,则返回true;否则返回false | |||
| */ | |||
| function hasOwn(obj: UTSJSONObject, key: string): boolean | |||
| function hasOwn(obj: Map<string, unknown>, key: string): boolean | |||
| function hasOwn(obj: any, key: string): boolean { | |||
| if(obj instanceof UTSJSONObject){ | |||
| return obj[key] != null | |||
| } | |||
| if(obj instanceof Map<string, unknown>){ | |||
| return (obj as Map<string, unknown>).has(key) | |||
| } | |||
| return false | |||
| } | |||
| export { | |||
| hasOwn | |||
| } | |||
| // 示例 | |||
| // const obj = { name: 'John', age: 30 }; | |||
| // if (hasOwn(obj, 'name')) { | |||
| // console.log("对象具有 'name' 属性"); | |||
| // } else { | |||
| // console.log("对象不具有 'name' 属性"); | |||
| // } | |||
| // // 输出: 对象具有 'name' 属性 | |||
| // const arr = [1, 2, 3]; | |||
| // if (hasOwn(arr, 'length')) { | |||
| // console.log("数组具有 'length' 属性"); | |||
| // } else { | |||
| // console.log("数组不具有 'length' 属性"); | |||
| // } | |||
| // 输出: 数组具有 'length' 属性 | |||
| @ -0,0 +1,30 @@ | |||
| // @ts-nocheck | |||
| const hasOwnProperty = Object.prototype.hasOwnProperty | |||
| /** | |||
| * 检查对象或数组是否具有指定的属性或键 | |||
| * @param obj 要检查的对象或数组 | |||
| * @param key 指定的属性或键 | |||
| * @returns 如果对象或数组具有指定的属性或键,则返回true;否则返回false | |||
| */ | |||
| export function hasOwn(obj: Object | Array<any>, key: string): boolean { | |||
| return hasOwnProperty.call(obj, key); | |||
| } | |||
| // 示例 | |||
| // const obj = { name: 'John', age: 30 }; | |||
| // if (hasOwn(obj, 'name')) { | |||
| // console.log("对象具有 'name' 属性"); | |||
| // } else { | |||
| // console.log("对象不具有 'name' 属性"); | |||
| // } | |||
| // // 输出: 对象具有 'name' 属性 | |||
| // const arr = [1, 2, 3]; | |||
| // if (hasOwn(arr, 'length')) { | |||
| // console.log("数组具有 'length' 属性"); | |||
| // } else { | |||
| // console.log("数组不具有 'length' 属性"); | |||
| // } | |||
| // 输出: 数组具有 'length' 属性 | |||
| @ -0,0 +1,43 @@ | |||
| // @ts-nocheck | |||
| // validator | |||
| // export * from './isString' | |||
| // export * from './isNumber' | |||
| // export * from './isNumeric' | |||
| // export * from './isDef' | |||
| // export * from './isFunction' | |||
| // export * from './isObject' | |||
| // export * from './isPromise' | |||
| // export * from './isBase64' | |||
| // export * from './hasOwn' | |||
| // // 单位转换 | |||
| // export * from './addUnit' | |||
| // export * from './unitConvert' | |||
| // export * from './toNumber' | |||
| // export * from './random' | |||
| // export * from './range' | |||
| // export * from './fillZero' | |||
| // // image | |||
| // export * from './base64ToPath' | |||
| // export * from './pathToBase64' | |||
| // export * from './exif' | |||
| // // canvas | |||
| // export * from './canIUseCanvas2d' | |||
| // // page | |||
| // export * from './getCurrentPage' | |||
| // // dom | |||
| // export * from './getRect' | |||
| // export * from './selectComponent' | |||
| // export * from './createAnimation' | |||
| // // delay | |||
| // export * from './sleep' | |||
| // export * from './debounce' | |||
| // export * from './throttle' | |||
| @ -0,0 +1,23 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 判断一个字符串是否为Base64编码。 | |||
| * Base64编码的字符串只包含A-Z、a-z、0-9、+、/ 和 = 这些字符。 | |||
| * @param {string} str - 要检查的字符串。 | |||
| * @returns {boolean} 如果字符串是Base64编码,返回true,否则返回false。 | |||
| */ | |||
| export function isBase64(str: string): boolean { | |||
| const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; | |||
| return base64Regex.test(str); | |||
| } | |||
| /** | |||
| * 判断一个字符串是否为Base64编码的data URI。 | |||
| * Base64编码的data URI通常以"data:"开头,后面跟着MIME类型和编码信息,然后是Base64编码的数据。 | |||
| * @param {string} str - 要检查的字符串。 | |||
| * @returns {boolean} 如果字符串是Base64编码的data URI,返回true,否则返回false。 | |||
| */ | |||
| export function isBase64DataUri(str: string): boolean { | |||
| const dataUriRegex = /^data:([a-zA-Z]+\/[a-zA-Z0-9-+.]+)(;base64)?,([a-zA-Z0-9+/]+={0,2})$/; | |||
| return dataUriRegex.test(str); | |||
| } | |||
| @ -0,0 +1,8 @@ | |||
| // @ts-nocheck | |||
| // #ifdef WEB | |||
| export const isBrowser = typeof window !== 'undefined'; | |||
| // #endif | |||
| // #ifndef WEB | |||
| export const isBrowser = false; | |||
| // #endif | |||
| @ -0,0 +1,23 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否已定义(不为 undefined)且不为 null | |||
| * @param value 要检查的值 | |||
| * @returns 如果值已定义且不为 null,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef UNI-APP-X | |||
| export function isDef(value: unknown): boolean { | |||
| return value !== undefined && value !== null; | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X | |||
| export function isDef(value : any|null) : boolean { | |||
| // #ifdef UNI-APP-X && APP | |||
| return value != null; | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| return value != null && value != undefined; | |||
| // #endif | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,83 @@ | |||
| // @ts-nocheck | |||
| import {isDef} from '../isDef' | |||
| import {isString} from '../isString' | |||
| import {isNumber} from '../isNumber' | |||
| /** | |||
| * 判断一个值是否为空。 | |||
| * | |||
| * 对于字符串,去除首尾空格后判断长度是否为0。 | |||
| * 对于数组,判断长度是否为0。 | |||
| * 对于对象,判断键的数量是否为0。 | |||
| * 对于null或undefined,直接返回true。 | |||
| * 其他类型(如数字、布尔值等)默认不为空。 | |||
| * | |||
| * @param {any} value - 要检查的值。 | |||
| * @returns {boolean} 如果值为空,返回true,否则返回false。 | |||
| */ | |||
| // #ifdef UNI-APP-X && APP | |||
| export function isEmpty(value : any | null) : boolean { | |||
| // 为null | |||
| if(!isDef(value)){ | |||
| return true | |||
| } | |||
| // 为空字符 | |||
| if(isString(value)){ | |||
| return value.toString().trim().length == 0 | |||
| } | |||
| // 为数值 | |||
| if(isNumber(value)){ | |||
| return false | |||
| } | |||
| if(typeof value == 'object'){ | |||
| // 数组 | |||
| if(Array.isArray(value)){ | |||
| return (value as Array<unknown>).length == 0 | |||
| } | |||
| // Map | |||
| if(value instanceof Map<unknown, unknown>) { | |||
| return value.size == 0 | |||
| } | |||
| // Set | |||
| if(value instanceof Set<unknown>) { | |||
| return value.size == 0 | |||
| } | |||
| if(value instanceof UTSJSONObject) { | |||
| return value.toMap().size == 0 | |||
| } | |||
| return JSON.stringify(value) == '{}' | |||
| } | |||
| return true | |||
| } | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| export function isEmpty(value: any): boolean { | |||
| // 检查是否为null或undefined | |||
| if (value == null) { | |||
| return true; | |||
| } | |||
| // 检查字符串是否为空 | |||
| if (typeof value === 'string') { | |||
| return value.trim().length === 0; | |||
| } | |||
| // 检查数组是否为空 | |||
| if (Array.isArray(value)) { | |||
| return value.length === 0; | |||
| } | |||
| // 检查对象是否为空 | |||
| if (typeof value === 'object') { | |||
| return Object.keys(value).length === 0; | |||
| } | |||
| // 其他类型(如数字、布尔值等)不为空 | |||
| return false; | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,16 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否为函数类型 | |||
| * @param val 要检查的值 | |||
| * @returns 如果值的类型是函数类型,则返回 true;否则返回 false | |||
| */ | |||
| // #ifdef UNI-APP-X && APP | |||
| export const isFunction = (val: any):boolean => typeof val == 'function'; | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| export const isFunction = (val: unknown): val is Function => | |||
| typeof val === 'function'; | |||
| // #endif | |||
| @ -0,0 +1,26 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否为数字类型 | |||
| * @param value 要检查的值,可以是 number 类型或 string 类型的数字 | |||
| * @returns 如果值是数字类型且不是 NaN,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef UNI-APP-X | |||
| export function isNumber(value: number | string | null): boolean { | |||
| return typeof value === 'number' && !isNaN(value); | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X | |||
| export function isNumber(value: any|null): boolean { | |||
| // #ifdef APP-ANDROID | |||
| return ['Byte', 'UByte','Short','UShort','Int','UInt','Long','ULong','Float','Double','number'].includes(typeof value) | |||
| // #endif | |||
| // #ifdef APP-IOS | |||
| return ['Int8', 'UInt8','Int16','UInt16','Int32','UInt32','Int64','UInt64','Int','UInt','Float','Float16','Float32','Float64','Double', 'number'].includes(typeof value) | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| return typeof value === 'number' && !isNaN(value); | |||
| // #endif | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,33 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否为数字类型或表示数字的字符串 | |||
| * @param value 要检查的值,可以是 string 类型或 number 类型 | |||
| * @returns 如果值是数字类型或表示数字的字符串,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef UNI-APP-X && APP | |||
| export function isNumeric(value: string | number | undefined | null): boolean { | |||
| return /^(-)?\d+(\.\d+)?$/.test(value); | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| import {isNumber} from '../isNumber'; | |||
| import {isString} from '../isString'; | |||
| export function isNumeric(value : any|null) : boolean { | |||
| if(value == null) { | |||
| return false | |||
| } | |||
| if(isNumber(value)) { | |||
| return true | |||
| } else if(isString(value)) { | |||
| // const regex = "-?\\d+(\\.\\d+)?".toRegex() | |||
| const regex = new RegExp("^(-)?\\d+(\\.\\d+)?$") | |||
| return regex.test(value as string) //regex.matches(value as string) | |||
| } | |||
| return false | |||
| // return /^(-)?\d+(\.\d+)?$/.test(value); | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,19 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否为对象类型 | |||
| * @param val 要检查的值 | |||
| * @returns 如果值的类型是对象类型,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef UNI-APP-X && APP | |||
| export const isObject = (val : unknown) : val is Record<any, any> => | |||
| val !== null && typeof val === 'object'; | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| export const isObject = (val : any | null) : boolean =>{ | |||
| return val !== null && typeof val === 'object'; | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,22 @@ | |||
| // @ts-nocheck | |||
| import {isFunction} from '../isFunction' | |||
| import {isObject} from '../isObject' | |||
| /** | |||
| * 检查一个值是否为 Promise 类型 | |||
| * @param val 要检查的值 | |||
| * @returns 如果值的类型是 Promise 类型,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef APP-ANDROID | |||
| export const isPromise = <T = any>(val: unknown): val is Promise<T> => { | |||
| // 使用 isObject 函数判断值是否为对象类型 | |||
| // 使用 isFunction 函数判断值是否具有 then 方法和 catch 方法 | |||
| return isObject(val) && isFunction(val.then) && isFunction(val.catch); | |||
| }; | |||
| // #endif | |||
| // #ifdef APP-ANDROID | |||
| export const isPromise = (val: any): boolean => { | |||
| return val instanceof Promise<unknown> | |||
| }; | |||
| // #endif | |||
| @ -0,0 +1,19 @@ | |||
| // @ts-nocheck | |||
| /** | |||
| * 检查一个值是否为字符串类型 | |||
| * @param str 要检查的值 | |||
| * @returns 如果值的类型是字符串类型,则返回 true;否则返回 false | |||
| */ | |||
| // #ifndef UNI-APP-X && APP | |||
| // export const isString = (str: unknown): str is string => typeof str === 'string'; | |||
| export function isString (str: unknown): str is string { | |||
| return typeof str == 'string' | |||
| } | |||
| // #endif | |||
| // #ifdef UNI-APP-X && APP | |||
| export function isString (str: any|null): boolean { | |||
| return typeof str == 'string' | |||
| } | |||
| // #endif | |||
| @ -0,0 +1,24 @@ | |||
| // @ts-nocheck | |||
| // export function toLowercaseSeparator(key: string) { | |||
| // return key.replace(/([A-Z])/g, '-$1').toLowerCase(); | |||
| // } | |||
| /** | |||
| * 将字符串转换为指定连接符的命名约定 | |||
| * @param str 要转换的字符串 | |||
| * @param separator 指定的连接符,默认为 "-" | |||
| * @returns 转换后的字符串 | |||
| */ | |||
| export function kebabCase(str : string, separator : string = "-") : string { | |||
| return str | |||
| // #ifdef UNI-APP-X && APP | |||
| .replace(/[A-Z]/g, (match : string, _ : number, _ : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母 | |||
| // #endif | |||
| // #ifndef UNI-APP-X && APP | |||
| .replace(/[A-Z]/g, (match : string) : string => `${separator}${match.toLowerCase()}`) // 将大写字母替换为连接符加小写字母 | |||
| // #endif | |||
| .replace(/[\s_-]+/g, separator) // 将空格、下划线和短横线替换为指定连接符 | |||
| .replace(new RegExp(`^${separator}|${separator}$`, "g"), "") // 删除开头和结尾的连接符 | |||
| .toLowerCase(); // 将结果转换为全小写 | |||
| } | |||
| @ -0,0 +1,11 @@ | |||
| // @ts-nocheck | |||
| // #ifndef UNI-APP-X | |||
| type UTSJSONObject = Record<string, any> | |||
| // #endif | |||
| export function obj2url(data: UTSJSONObject, isPrefix: boolean = false): string { | |||
| const prefix = isPrefix ? '?' : ''; | |||
| const _result = []; | |||
| } | |||
| @ -0,0 +1,87 @@ | |||
| { | |||
| "id": "lime-shared", | |||
| "displayName": "lime-shared", | |||
| "version": "0.2.9", | |||
| "description": "本人插件的几个公共函数,获取当前页,图片的base64转临时路径,图片的exif信息等", | |||
| "keywords": [ | |||
| "lime-shared", | |||
| "exif" | |||
| ], | |||
| "repository": "", | |||
| "engines": { | |||
| "HBuilderX": "^3.1.0" | |||
| }, | |||
| "dcloudext": { | |||
| "type": "sdk-js", | |||
| "sale": { | |||
| "regular": { | |||
| "price": "0.00" | |||
| }, | |||
| "sourcecode": { | |||
| "price": "0.00" | |||
| } | |||
| }, | |||
| "contact": { | |||
| "qq": "" | |||
| }, | |||
| "declaration": { | |||
| "ads": "无", | |||
| "data": "无", | |||
| "permissions": "无" | |||
| }, | |||
| "npmurl": "" | |||
| }, | |||
| "uni_modules": { | |||
| "dependencies": [ | |||
| ], | |||
| "encrypt": [], | |||
| "platforms": { | |||
| "cloud": { | |||
| "tcb": "y", | |||
| "aliyun": "y", | |||
| "alipay": "n" | |||
| }, | |||
| "client": { | |||
| "Vue": { | |||
| "vue2": "y", | |||
| "vue3": "y" | |||
| }, | |||
| "App": { | |||
| "app-vue": "y", | |||
| "app-uvue": "y", | |||
| "app-nvue": "y", | |||
| "app-harmony": "u" | |||
| }, | |||
| "H5-mobile": { | |||
| "Safari": "y", | |||
| "Android Browser": "y", | |||
| "微信浏览器(Android)": "y", | |||
| "QQ浏览器(Android)": "y" | |||
| }, | |||
| "H5-pc": { | |||
| "Chrome": "y", | |||
| "IE": "u", | |||
| "Edge": "u", | |||
| "Firefox": "u", | |||
| "Safari": "u" | |||
| }, | |||
| "小程序": { | |||
| "微信": "y", | |||
| "阿里": "y", | |||
| "百度": "y", | |||
| "字节跳动": "y", | |||
| "QQ": "y", | |||
| "钉钉": "y", | |||
| "快手": "y", | |||
| "飞书": "y", | |||
| "京东": "u" | |||
| }, | |||
| "快应用": { | |||
| "华为": "u", | |||
| "联盟": "u" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -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,17 @@ | |||
| // @ts-nocheck | |||
| // import { processFile, ProcessFileOptions } from '@/uni_modules/lime-file-utils' | |||
| export function pathToBase64(path : string) : Promise<string> { | |||
| console.error('pathToBase64: 当前环境不支持,请使用 【lime-file-utils】') | |||
| // return new Promise((resolve, reject) => { | |||
| // processFile({ | |||
| // type: 'toDataURL', | |||
| // path, | |||
| // success(res : string) { | |||
| // resolve(res) | |||
| // }, | |||
| // fail(err: any){ | |||
| // reject(err) | |||
| // } | |||
| // } as ProcessFileOptions) | |||
| // }) | |||
| } | |||