价格显示问题

This commit is contained in:
liuyuan
2025-04-18 17:56:33 +08:00
parent 648a993d14
commit 380ed58d17
179 changed files with 17322 additions and 2750 deletions

View File

@@ -2,8 +2,8 @@
"name" : "心灵空间",
"appid" : "__UNI__BBBDFD2",
"description" : "心灵空间",
"versionName" : "1.0.11",
"versionCode" : 1011,
"versionName" : "1.0.22",
"versionCode" : 1022,
"transformPx" : false,
/* 5+App */
"app-plus" : {
@@ -30,7 +30,7 @@
"prompt" : "template",
"template" : {
"title" : "用户协议和隐私政策",
"message" : "请你务必审慎阅读、充分理解“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href='https://soulspace.taihumed.com/agreement.html'>《用户协议》</a>和<a href='https://soulspace.taihumed.com/privacy.html'>《隐私协议》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"message" : "请你务必审慎阅读、充分理解“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>你可阅读<a href='https://soulspace.taihumed.com/agreement.html'>《用户协议》</a>和<a href='https://soulspace.taihumed.com/privacy.html'>《隐私协议》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept" : "同意",
"buttonRefuse" : "暂不同意"
}
@@ -56,7 +56,8 @@
],
"minSdkVersion" : 23,
"targetSdkVersion" : 35,
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
"schemes" : "soulspace"
},
/* ios */
"ios" : {
@@ -70,13 +71,14 @@
]
}
},
"idfa" : false,
"plistcmds" : [ "Delete :NSUserTrackingUsageDescription" ],
"idfa" : true,
"privacyDescription" : {
"NSPhotoLibraryUsageDescription" : "保障您在此app中的修改头像、申诉反馈上传图片功能的正常使用",
"NSPhotoLibraryAddUsageDescription" : "保障您在此app中的修改头像、申诉反馈上传图片功能的正常使用",
"NSCameraUsageDescription" : "保障您在此app中的修改头像、申诉反馈上传图片功能的正常使用"
}
"NSCameraUsageDescription" : "保障您在此app中的修改头像、申诉反馈上传图片功能的正常使用",
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提示浏览体验"
},
"urltypes" : "soulspace"
},
/* SDK */
"sdkConfigs" : {

View File

@@ -412,6 +412,21 @@
"popGesture": "none"
}
}
},
{
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"app-plus": {
"animationDuration": 200,
"animationType": "fade-in",
"background": "transparent",
"backgroundColorTop": "transparent",
"popGesture": "none",
"scrollIndicator": false,
"titleNView": false
},
"disableScroll": true
}
}
],
"tabBar": {

View File

@@ -14,15 +14,15 @@
<view :class="`common_radius_box goods_box popup_content`">
<view class="title title_box">
<!-- <view class="title_price" v-if="selectGoodsData.isVipPrice==1&&selectGoodsData.vipPrice!=null&&selectGoodsData.vipPrice!=0">
<view class="title_price" v-if="selectGoodsData.isVipPrice==1&&selectGoodsData.vipPrice!=null&&selectGoodsData.vipPrice!=0">
{{selectGoodsData.vipPrice}}
<text style=" font-weight: normal; padding-left: 15rpx; font-size: 26rpx;">VIP到手价</text>
</view>
<view class="title_price" v-else-if="selectGoodsData.activityPrice&&selectGoodsData.activityPrice>0">
{{selectGoodsData.activityPrice}}
<text style=" font-weight: normal; padding-left: 15rpx; font-size: 26rpx; color: #999;">活动价</text>
</view> -->
<text class="title_price">
</view>
<text class="title_price" v-else>
{{selectGoodsData.price}}
</text>
<u-icon
@@ -68,20 +68,11 @@
<view :class="`goods_info `">
<view class="name">{{ slotProps.row.productName }}</view>
<view class="price">
<!-- <view class="goods_price" v-if="slotProps.row.isVipPrice==1&&slotProps.row.vipPrice!=null&&slotProps.row.vipPrice!=0">
{{slotProps.row.vipPrice}}
<text class="line_text">{{slotProps.row.price}}</text>
</view>
<view class="goods_price" v-else-if="slotProps.row.activityPrice&&slotProps.row.activityPrice>0">
{{slotProps.row.activityPrice}}
<text class="line_text">{{slotProps.row.price}}</text>
</view> -->
<text>
{{slotProps.row.price}}
</text>
<!-- <text class="goods_text" v-if="slotProps.row.isVipPrice==1&&slotProps.row.vipPrice!=null&&slotProps.row.vipPrice!=0">
<text v-html="getPrice(slotProps)"></text>
<text class="goods_text" v-if="slotProps.row.isVipPrice==1&&slotProps.row.vipPrice!=null&&slotProps.row.vipPrice!=0">
VIP优惠
</text> -->
</text>
</view>
</view>
</view>
@@ -129,6 +120,38 @@ export default {
...mapState(["userInfo"]),
},
methods: {
getPrice(slotProps) {
if (
Number(slotProps.row.isVipPrice) === 1 &&
Number(slotProps.row.vipPrice) > 0
) {
return `
<text style="color: #e97512; font-size: 28rpx; font-weight: bold;">
${Number(slotProps.row.vipPrice).toFixed(2)}
</text>
<text
style="color: #8a8a8a; font-size: 22rpx; margin-left: 4px; font-weight: bold; text-decoration: line-through;"
>
${Number(slotProps.row.price).toFixed(2)}
</text>
`;
} else if (Number(slotProps.row.activityPrice) > 0) {
return `
<text style="color: #e97512; font-size: 28rpx; font-weight: bold;">
${Number(slotProps.row.activityPrice).toFixed(2)}
</text>
<text
style="color: #8a8a8a; font-size: 22rpx; margin-left: 4px; font-weight: bold; text-decoration: line-through;"
>
${Number(slotProps.row.price).toFixed(2)}
</text>
`;
} else {
return `<span style="color: #e97512;font-size: 28rpx;font-weight: bold;">
${Number(slotProps.row.price).toFixed(2)}</span>
`;
}
},
// 放大图片
previewImage(url) {
console.log(url);

View File

@@ -9,8 +9,7 @@
</view>
</z-nav-bar>
<view class="cateList">
<view class="labels_block" v-if="id!=75&&labelsList.length>0">
<!-- <view class="labels_block" v-if="labelsList.length>0" :style="`top: ${45 + statusBarHeight}px;`"> -->
<view class="labels_block" v-if="labelsList.length>0" :style="`top: ${45 + statusBarHeight}px;`">
<view class="labels_item"
:style="{ width: labelsList.length > 0 ? `${100 / labelsList.length}%` : '100%' }"
v-for="(tabData,tabIndex) in labelsList" :key="tabIndex"
@@ -54,7 +53,7 @@
<text v-if="statusNull" class="text_null">暂无数据</text>
</view>
<view class="xlcp_block" :class="XL_id==78?'xlcp_block_2':''" v-if="statusXLCP" style=" margin-top: 0;">
<view class="xlcp_block" :class="XL_id==78?'xlcp_block_2':''" v-if="statusXLCP">
<view class="form_block" v-if="XL_id==77">
<view class="form_item" v-for="(item,index) in formList" :key="index">
<text class="form_title">{{item.title}}</text>

View File

@@ -153,7 +153,7 @@
</span>
<span>¥</span>{{ orderContet.shippingMoney }}
</view>
<!-- <view class="orderReal" v-if="orderContet.orderType == 'order'&&orderContet.vipDiscountAmount&&orderContet.vipDiscountAmount>0">
<view class="orderReal" v-if="orderContet.orderType == 'order'&&orderContet.vipDiscountAmount&&orderContet.vipDiscountAmount>0">
<span style="color: red; margin-right: 10rpx; float: left;">VIP专享立减 :
</span>
<span style=" color: red;">-¥{{orderContet.vipDiscountAmount}}</span>
@@ -162,18 +162,76 @@
<span style="color: red; margin-right: 10rpx; float: left;">活动立减 :
</span>
<span style=" color: red;">-¥{{orderContet.districtMoney}}</span>
</view> -->
<view class="orderReal" v-if="orderContet.orderType == 'order'">
<span style="color: #666; margin-right: 10rpx; float: left">积分 :
</view>
<view class="orderReal" v-if="orderContet.jfDeduction > 0">
<span style="color: #666; margin-right: 10rpx; float: left"
>积分抵扣:
</span>
<span v-if="orderContet.jfDeduction > 0">-</span> <span>¥</span>{{ orderContet.jfDeduction }}
<span style="color: #fa2d12" v-if="orderContet.jfDeduction > 0"
>-&nbsp;&nbsp;</span
>
<span style="color: #fa2d12"> {{ orderContet.jfDeduction }}</span>
</view>
<view
class="orderReal"
v-if="orderContet.paymentMethod == 4 && orderContet.realMoney > 0"
>
<span style="color: #666; margin-right: 10rpx; float: left"
>天医币抵扣:
</span>
<span style="color: #fa2d12">-&nbsp;&nbsp;</span>
<span style="color: #fa2d12">¥{{ orderContet.realMoney }}</span>
</view>
<view class="orderReal" v-if="orderContet.orderStatus != 0">
<span style="color: #666; margin-right: 10rpx; float: left">
实付款:
</span>
<b style="color: red;"><span>¥</span>{{ orderContet.realMoney }}</b>
<b v-if="orderContet.orderType == 'point'" style="color: #dd3c0c"
>¥ {{ orderContet.bookBuyConfigEntity.realMoney }}</b
>
<b style="color: #dd3c0c" v-else>
<template v-if="orderContet.realMoney > 0&&orderContet.jfDeduction>0">
¥{{ orderContet.realMoney }}
<text style="font-size: 24rpx;margin-left: 10rpx;">
<span
style=""
v-if="orderContet.paymentMethod == 1"
>微信支付</span
>
<span
style=""
v-if="orderContet.paymentMethod == 2"
>支付宝支付</span
>
<span
style=""
v-if="orderContet.paymentMethod == 3"
>苹果支付</span
>
<span
style=""
v-if="orderContet.paymentMethod == 4"
>
天医币支付
</span>
</text>
</template>
<template v-if="orderContet.realMoney == 0&&orderContet.jfDeduction==0">¥0</template>
<text
style="margin: 0 4rpx"
v-if="
orderContet.realMoney > 0 && orderContet.jfDeduction > 0
"
>
+
</text>
<text v-if="orderContet.jfDeduction > 0"
>{{ orderContet.jfDeduction }} 积分</text
>
</b>
</view>
<view class="orderReal" v-if="orderContet.remark">
<span style="color: #666; margin-right: 10rpx; float: left">

View File

@@ -13,7 +13,7 @@
<view class="goods_box">
<view class="curriulum_title_box">
<view v-if="this.options.isMiaosha == 1" class="miaosha_box">
<!-- <view class="price_box"
<view class="price_box"
v-if="curriculumData.isVipPrice==1&&curriculumData.vipPrice!=null&&curriculumData.vipPrice!=0
||curriculumData.activityPrice && curriculumData.activityPrice > 0">
<view class="price_left">
@@ -44,8 +44,8 @@
</text>
<text class="xianshimaiosha" v-else-if="curriculumData.activityPrice && curriculumData.activityPrice > 0">活动立减</text>
</view>
</view> -->
<view class="putong">
</view>
<view class="putong" v-else>
<template>
<text class="aui-text-danger price">
¥{{ curriculumData.price }}

View File

@@ -27,14 +27,14 @@
<view class="order_top common_radius_box color_shandow goods_box">
<view class="curriulum_title_box goods_item" style=" position: relative;" v-for="(v, i) in goodsDataList">
<!-- <text class="goods_item_vip" v-if="v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">VIP优惠</text> -->
<text class="goods_item_vip" v-if="v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">VIP优惠</text>
<image class="goods_item_img" :src="v.productImages" mode="aspectFit">
</image>
<view class="normal_box">
<view class="normal_box_top">
<view class="curriulum_title">{{ v.productName }} </view>
<view class="price_box">
<!-- <view class="vip_price" v-if="v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">
<view class="vip_price" v-if="v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">
<text class="vip_price_jg">{{ v.vipPrice }}</text>
<text class="vip_price_text">VIP到手价</text>
<text class="vip_price_line">{{ v.price }}</text>
@@ -43,8 +43,8 @@
<text class="vip_price_jg">{{ v.activityPrice }}</text>
<text class="vip_price_text">活动价</text>
<text class="vip_price_line">{{ v.price }}</text>
</text> -->
<text>{{ v.price }}</text>
</text>
<text v-else>{{ v.price }}</text>
<view>
x 1
@@ -92,13 +92,13 @@
<text style="color: #aaa; margin-left: 10rpx">
(全部积分{{ initData.user.jf }})</text>
</template>
<!-- <template v-else-if="v.type == 5">
<template v-else-if="v.type == 5">
<text class="type_text_red">活动立减</text>
</template>
<template v-else-if="v.type == 6">
<text class="type_text_red">VIP专享立减</text>
</template> -->
</template>
<text v-else>{{ v.text }}</text>
</view>
<view class="right">
@@ -110,12 +110,12 @@
<template v-if="v.type == 3">
<text style="color: #fe6035"> -{{ jfNumberShow }}</text>
</template>
<!-- <template v-if="v.type == 5">
<template v-if="v.type == 5">
<text class="type_text_red">-{{districtAmount}}</text>
</template>
<template v-if="v.type == 6">
<text class="type_text_red">-{{vipPrice}}</text>
</template> -->
</template>
</view>
</view>
@@ -149,7 +149,7 @@
<text v-else> {{ v.text }}</text>
</view>
<template v-if="isDefaultCurrency">
<radio :value="v.value" color="#7dc1f0"
<radio :value="v.value" color="#7dc1f0" v-if="i == 2"
:checked="selectPayIndex == i ? true : false" @click="radioChange(i)" size="10" />
</template>
<template v-else>
@@ -213,10 +213,19 @@
<common-goods-nav :iconList="[]" :customButton="customButton" @submit="goBuyJie">
<!-- leftSlot -->
<template slot="leftSlot" slot-scope="slotProps">
<view class="price_box order_bottom_box"><text class="number">{{ number }}</text>
<text class="price">合计
<view class="price_box order_bottom_box">
<text class="price">合计:
<text class="total">{{ actualPayment }}</text>
<text class="total">
<template v-if="actualPayment>0">
{{ Number(actualPayment).toFixed(2) }}
</template>
<text style="margin: 0 4rpx;" v-if="actualPayment>0&&jfNumber>0">
+
</text>
<text v-if="jfNumber>0">{{ jfNumber }} 积分</text>
</text>
</text>
</view>
</template>
@@ -282,7 +291,18 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
this.selectPayIndex = 0;
}
}
this.payType = this.payList[this.selectPayIndex].type;
if (this.actualPayment == 0) {
var that = this;
this.payList.forEach((e, i) => {
if (e.type == 4) {
that.selectPayIndex = i;
that.payType = 4;
}
});
this.$forceUpdate();
}
this.$forceUpdate();
},
},
@@ -370,7 +390,7 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
freightNum: "book/buyOrder/calculateTransportPrice", //运费
},
customButton: [{
width: "340rpx",
width: "300rpx",
text: "立即支付",
backgroundColor: "linear-gradient(90deg, #294a97 0%, #7dc1f0 80%)",
color: "#fff",
@@ -558,10 +578,19 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
this.jfNumberShow = this.jfNumber.toFixed(2);
}
this.actualPayment = this.totalPrice - this.jfNumber + this.freightNum;
this.actualPayment = this.totalPrice - this.jfNumber - this.vipPrice - this.districtAmount + this.freightNum;
// 如果实际支付金额为0设置支付方式为默认货币
if (this.actualPayment == 0) {
console.log("this.actualPayment at line 1097:", this.actualPayment);
this.isDefaultCurrency = true;
this.payList.forEach((e, i) => {
if (e.type == 4) {
this.selectPayIndex = i;
this.payType = 4;
}
});
this.$forceUpdate();
} else {
this.isDefaultCurrency = false;
}
@@ -597,17 +626,17 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
},
];
//如果是活动
// if(this.goodsDataList[0].activityPrice&&this.goodsDataList[0].activityPrice>0&&!this.goodsDataList[0].vipPrice>0){
// this.priceBreakdownList.push({
// type: 5,
// })
// }
// //如果是vip
// if(this.goodsDataList[0].isVipPrice==1&&this.goodsDataList[0].vipPrice!=null&&this.goodsDataList[0].vipPrice!=0){
// this.priceBreakdownList.push({
// type: 6,
// })
// }
if(this.goodsDataList[0].activityPrice&&this.goodsDataList[0].activityPrice>0&&!this.goodsDataList[0].vipPrice>0){
this.priceBreakdownList.push({
type: 5,
})
}
//如果是vip
if(this.goodsDataList[0].isVipPrice==1&&this.goodsDataList[0].vipPrice!=null&&this.goodsDataList[0].vipPrice!=0){
this.priceBreakdownList.push({
type: 6,
})
}
var data = {
uid: this.userInfo.id,
@@ -753,8 +782,8 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
if (!this.isShowAddress) {
if (this.initData.user.jf >= that.totalPrice) {
this.jfNumber = that.totalPrice;
this.jfNumberMax = that.totalPrice;
this.jfNumber = that.totalPrice - that.vipPrice - that.districtAmount;
this.jfNumberMax = that.totalPrice - that.vipPrice - that.districtAmount;
} else {
this.jfNumber = this.initData.user.jf;
this.jfNumberMax = this.initData.user.jf;
@@ -766,23 +795,13 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
that.jfNumberShow = that.jfNumber.toFixed(2);
that.actualPayment = that.totalPrice - that.jfNumber;
}
that.actualPayment = that.actualPayment + that.freightNum;
that.actualPayment = that.actualPayment + that.freightNum - that.vipPrice- that.districtAmount;
} else {
that.actualPayment = that.totalPrice + that.freightNum;
that.actualPayment = that.totalPrice + that.freightNum - that.vipPrice - that.districtAmount;
}
if (this.actualPayment == 0 && !this.isShowAddress) {
this.isDefaultCurrency = true;
this.payList = [
{
text: "天医币",
imgUrl: require("@/static/icon/pay_3.png"),
type: 4,
value: "2",
},
];
this.selectPayIndex = 0;
this.payType = 4;
} else {
this.isDefaultCurrency = false;
}
@@ -1212,9 +1231,8 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
.price {
width: auto;
font-size: 30rpx;
font-size: 28rpx;
font-weight: 600;
float: right;
margin-right: 10rpx;
color: #333;
}
@@ -1378,7 +1396,6 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
.number {
font-size: 26rpx;
margin-right: 15rpx;
margin-top: 5rpx;
}
.price {
@@ -1395,7 +1412,6 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
width: calc(100% - 220rpx);
height: auto;
position: relative;
// overflow: hidden;
}
}
@@ -1409,6 +1425,7 @@ import parse from "../../uni_modules/uview-ui/libs/config/props/parse";
.order_bottom_box {
display: flex;
align-items: center;
justify-content: center;
}
.pay_box {

View File

@@ -37,13 +37,22 @@
</view>
<view class="cate_box">
<scroll-view scroll-x="true">
<view class="cate_list" v-if="cateList.length>0">
<view class="cate_list" v-if="$platform!='android'&&cateList.length>0">
<view class="cate_item_box" v-if="item.id!=75" v-for="(item, index) in cateList" @click="handleClickCate(item)">
<view class="cate_item_border">
<image :src="item.icon"></image>
</view>
<view class="cate_item_name" v-if="index==2">心身健康科学</view>
<view class="cate_item_name" v-else>{{ item.title }}</view>
</view>
</view>
<view class="cate_list" v-if="$platform=='android'&&cateList.length>0">
<view class="cate_item_box" v-for="(item, index) in cateList" @click="handleClickCate(item)">
<view class="cate_item_border">
<image :src="item.icon"></image>
</view>
<view class="cate_item_name" v-if="$platform!='android'&&index==2">心身健康科学</view>
<view class="cate_item_name" v-else>{{ item.title }}</view>
<view class="cate_item_name">{{ item.title }}</view>
</view>
</view>
</scroll-view>
@@ -66,7 +75,7 @@
<view class="miaosha_list">
<view class="miaosha_item" @click="goGoodsDetail(v)"
v-for="(v, i) in seckillLst">
<!-- <text class="miaosha_youhui" v-if="$platform=='android'&&v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">VIP优惠</text> -->
<text class="miaosha_youhui" v-if="v.isVipPrice==1&&v.vipPrice!=null&&v.vipPrice!=0">VIP优惠</text>
<image class="book_image" :src="v.productImages" mode="aspectFit"></image>
<view class="book_name">{{ v.productName }}</view>
</view>

View File

@@ -23,7 +23,7 @@
</view>
<view class="userInfoBox">
<view class="name">{{ userMes.nickname ? userMes.nickname : "未设置" }}</view>
<!-- <view class="phone" v-if="userMes.tel">手机号({{ userMes.tel }})</view> -->
<view class="phone" v-if="userMes.tel&&$platform=='android'">手机号({{ userMes.tel }})</view>
<view class="vip_type" v-if="$platform=='android'&&textList.length>0">
<view class="vip_type_item" v-for="(item,index) in textList" :key="index">
{{item}}<image src="@/static/icon/chao_vip.png"></image>

View File

@@ -115,7 +115,8 @@
font-weight: 700;
">
<text style="font-size: 20rpx"></text>
{{item2.product.price ? item2.product.price : ""}}
<text v-if="item2.product.price>=0">{{item2.product.price}}</text>
</view>
</view>
<view class="btns flexbox" style="margin-top: 10rpx">
@@ -138,66 +139,63 @@
</view>
<view style="margin-top: 10rpx; overflow: hidden">
<view class="btns flexbox" style="float: right; width: auto !important">
<view class="right flexbox opbtns" style="
<view
class="btns flexbox"
style="float: right; width: auto !important"
>
<view
class="right flexbox opbtns"
style="
width: auto;
line-height: 44rpx;
letter-spacing: 1rpx;
display: flex;
align-items: center;
">
<view style="
"
>
<view
style="
line-height: 46rpx;
color: #000;
font-size: 28rpx;
font-weight: 500;
margin-right: 10rpx;
">实付款</view>
<view style="
"
>实付款</view
>
<view
style="
line-height: 46rpx;
color: #333;
font-size: 30rpx;
font-weight: 700;
">
<text style="font-size: 20rpx"></text>
{{ slotProps.row.realMoney || slotProps.row.realMoney == 0 ? slotProps.row.realMoney: "" }}
</view>
</view>
</view>
<view class="btns flexbox" style="
margin-top: 0rpx;
float: right;
width: auto;
margin-left: 20rpx;"
"
>
<text v-if="slotProps.row.orderType == 'point'">
{{ slotProps.row.bookBuyConfigEntity.realMoney }}
</text>
<text v-if="slotProps.row.orderType != 'point'">
<text v-if="slotProps.row.realMoney && slotProps.row.realMoney > 0">
{{ slotProps.row.realMoney }}
</text>
<text v-if="slotProps.row.realMoney==0&&slotProps.row.jfDeduction==0">
0
</text>
<text
style="margin: 0 4rpx"
v-if="
(slotProps.row.addressId == 0 ||
slotProps.row.addressId == null) &&
slotProps.row.orderType == 'order' &&
slotProps.row.realMoney > 0 &&
slotProps.row.jfDeduction > 0
">
<view class="right flexbox opbtns" style="
line-height: 44rpx;
letter-spacing: 1rpx;
display: flex;
align-items: center;
"><text style="color: #9b9b9b"> </text>
<view style="
line-height: 46rpx;
color: #9b9b9b;
font-size: 24rpx;
font-weight: 500;
margin-right: 10rpx;
">积分抵扣</view>
<view style="
line-height: 46rpx;
color: #9b9b9b;
font-size: 26rpx;
font-weight: 700;
">
<text style="font-size: 20rpx">¥</text>
{{ slotProps.row.jfDeduction }}
"
>
+
</text>
</text>
<text v-if="slotProps.row.jfDeduction > 0">{{ slotProps.row.jfDeduction }} 积分</text>
</view>
</view>
</view><text style="color: #9b9b9b"></text>
</view>
</view>

View File

@@ -42,7 +42,7 @@
</view>
</view>
<view class="right">
<u-icon @click="toLogin()" class="editIcon" name="shopping-cart-fill"
<u-icon class="editIcon" name="shopping-cart-fill"
color="#FF2B57" size="30"
style="display: inline-block; margin-left: 10rpx"></u-icon>
</view>
@@ -56,8 +56,7 @@
<view class="shiting_content">
<view class="catalogueList">
<view class="chapter_content">
<courseDescription :isCondition="true" :dataList="slotProps.dataList"
@hancleClick="toLogin()" label="title">
<courseDescription :isCondition="true" :dataList="slotProps.dataList" label="title">
<template slot="labelSlot" slot-scope="slotProps">
<view :style="`${
slotProps.row.isAudition==1
@@ -218,21 +217,6 @@
});
return list;
},
//登录
toLogin(){
uni.showModal({
title: '提示',
content: '为了方便您后期用不同的设备查阅订购的记录,请先完成登录或注册。',
confirmText: '确认',
success(res){
if (res.confirm) {
uni.navigateTo({
url: '/pages/user/login'
})
}
}
})
}
}
};
</script>

View File

@@ -5,7 +5,7 @@
<view class="cate_box">
<scroll-view scroll-x="true">
<view class="cate_list" v-if="cateList.length>0">
<view class="cate_item_box" v-for="(item, index) in cateList" @click="handleClickCate(item)">
<view class="cate_item_box" v-if="item.id!=75" v-for="(item, index) in cateList" @click="handleClickCate(item)">
<view class="cate_item_border">
<image :src="item.icon"></image>
</view>
@@ -258,7 +258,7 @@ export default {
align-items: center;
.cate_item_box {
width: 20%;
width: 25%;
padding: 40rpx 0 30rpx;
text-align: center;

View File

@@ -12,7 +12,7 @@
<view class="list_item_title">{{item.title}}</view>
<view class="list_item_content" v-html="item.content"></view>
<view class="list_item_bt">
<text class="list_item_study" @click="toLogin()">了解课程</text>
<text class="list_item_study">了解课程</text>
</view>
</view>
</view>
@@ -20,7 +20,7 @@
</view>
<view class="xlcp_block" v-if="statusXLCP">
<view class="level_block" @click="toLogin()">
<view class="level_block">
<view class="level_item">自恋型人格障碍诊断标准</view>
</view>
</view>
@@ -44,13 +44,13 @@ export default {
},
onLoad(options) {
this.title = options.title;
this.id = options.id;
//如果是心理评测
if(this.id==75){
this.statusXLCP = true;
}else{
this.getData();
//针对身心医学的判断
if(options.title=='心身医学'){
this.title = '心身健康科学'
}else{
this.title = options.title;
}
},
methods: {
@@ -74,20 +74,6 @@ export default {
this.statusNull = true;
}
});
},
toLogin(){
uni.showModal({
title: '提示',
content: '为了方便您后期用不同的设备查阅订购的记录,请先完成登录或注册。',
confirmText: '确认',
success(res){
if (res.confirm) {
uni.navigateTo({
url: '/pages/user/login'
})
}
}
})
}
}
};

View File

@@ -1,9 +1,5 @@
<template>
<view class="commonPage commonPageBox">
<view class="bg_top">
<u-icon @click="goSetting()" class="setIcon" labelColor="#294a97" labelPos="bottom" label="设置" name="setting"
:style="`top:${(20 + statusBarHeight) * 2}rpx`" color="#294a97" size="22"></u-icon>
</view>
<view class="mine_box" :style="`top:${(80 + statusBarHeight) * 2}rpx`">
<view class="per_mes">

View File

@@ -313,7 +313,7 @@ export default {
quantity: 1,
manualFinishTransaction: true, // 3.5.1+ 支持,设置此参数后需要开发者主动关闭订单,参见下面的关闭订单方法 finishTransaction()
}
this.transaction = await this.requestPayment(orderInfo)
that.transaction = await that.requestPayment(orderInfo)
},
iapCheck(result) {
let that = this;
@@ -337,7 +337,7 @@ export default {
})
.then((res) => {
if (res.code == 0) {
this.transaction = null
that.transaction = null
uni.hideLoading()
uni.showToast({
title: '充值成功!',

Binary file not shown.

View File

@@ -0,0 +1,2 @@
## 1.0.02021-05-12
1. 支持uni_modules

View File

@@ -0,0 +1,185 @@
<template>
<view>
<view class="popupClick" @click="onPopupShow()"><slot></slot></view>
<z-popup v-model="currentValue">
<view class="multiple_choice_title">
<text @click="currentValue = false">取消</text>
<view>{{title}}</view>
<text @click="onConfirm">确定</text>
</view>
<scroll-view scroll-y="true" class="multiple_choice_scroll">
<view class="multiple_choice_box">
<view class="multiple_choice_content">
<view class="multiple_choice_item" v-for="(item,index) of rangeList" :key="index" @click="onSelect(index)">
<view class="select" :class="{active: item.select }"></view>
<view class="value">{{item[rangeKey]}}</view>
</view>
</view>
</view>
</scroll-view>
</z-popup>
</view>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
range: {
type: Array,
default: function(){
return []
}
},
rangeKey: {
type: String,
default: "name"
},
},
created() {
if (typeof this.value !== 'undefined') {
this.currentValue = this.value;
}
this.rangeList = this.range.map(item => {
item.select = false;
return item;
});
},
watch: {
value(val) {
this.currentValue = val;
},
currentValue(val) {
this.$emit(val ? 'on-show' : 'on-hide');
this.$emit('input', val);
},
range(val){
this.rangeList = val.map(item => {
item.select = false;
return item;
});
}
},
data() {
return {
currentValue: false,
rangeList: []
};
},
methods: {
onPopupShow(){
this.currentValue = true;
},
onSelect(index){
let item = this.rangeList[index];
item.select = !item.select;
this.$set(this.rangeList, index, item);
},
onConfirm(){
let resultList = this.rangeList.filter(item => {
if(item.select){
return true;
} else {
return false;
}
});
if(resultList.length > 0){
this.currentValue = false;
this.$emit("change", resultList);
} else {
uni.showToast({
title: "请选择",
icon: "none"
});
}
}
},
mounted() {}
};
</script>
<style lang="scss" scoped>
@import '@/style/mixin.scss';
.multiple_choice_title {
display: flex;
justify-content: space-between;
height: 88upx;
line-height: 88upx;
border-bottom: 2upx solid #ebebeb;
padding: 0 20upx;
background-color: #FFF;
}
.multiple_choice_title view {
font-size: 32upx;
}
.multiple_choice_title text {
width: 80upx;
flex-shrink: 0;
text-align: center;
}
.multiple_choice_title text {
font-size: 28upx;
color: #999;
}
.multiple_choice_title text:last-child {
color: $themeColor;
}
.multiple_choice_scroll {
background-color: #FFF;
max-height: 60vh;
min-height: 30vh;
}
.multiple_choice_box {
display: flex;
justify-content: center;
align-items: center;
min-height: 30vh;
}
.multiple_choice_content {
.multiple_choice_item {
height: 100rpx;
padding: 0 30rpx;
font-size: 30rpx;
display: flex;
align-items: center;
// &:nth-child(2n) {
// background-color: #f7f7f7;
// }
.select {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
// background-image: url(../../static/icon/ic_notselected.png);
// background-size: 100% 100%;
flex-shrink: 0;
border-radius: 50%;
border: 2rpx solid #ccc;
&.active {
border: 2rpx solid $themeColor;
background-color: $themeColor;
text-align: center;
line-height: 38rpx;
transform:rotate(15deg);
}
&.active::before {
content: "√";
color: #FFF;
}
}
.value {
width: calc(100% - 55rpx);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
}
}
</style>

View File

@@ -0,0 +1,77 @@
{
"id": "multiple-choice",
"displayName": "多选组件",
"version": "1.0.0",
"description": "多选组件",
"keywords": [
"多选",
"多选组件"
],
"repository": "https://github.com/zhouwei1994/uni-app-demo",
"engines": {
"HBuilderX": "^3.0.0"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "465081029"
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
# 多选组件
多选组件
| `QQ交流群(607391225)` | `微信交流群(加我好友备注"进群"` |
| ----------------------------|--------------------------- |
|![QQ交流群](http://qn.kemean.cn//upload/202004/14/15868301778472k7oubi6.png)|![微信交流群](https://qn.kemean.cn/upload/202010/13/weiXin_group_code.jpg)|
| QQ群号607391225 |微信号zhou0612wei|
### [点击跳转-5年的web前端开源的uni-app快速开发模板-下载看文档](https://ext.dcloud.net.cn/plugin?id=2009)
### 案例一
```
<multiple-choice v-model="true" title="选择性别" :range="[{name: '男'}, {name: '女'}]" rangeKey="name"></multiple-choice>
```
### 案例二
```
<multiple-choice title="选择性别" :range="[{name: '男'}, {name: '女'}]" rangeKey="name">
<button>打开</button>
</multiple-choice>
```
### 属性
| 名称 | 类型 | 默认值 | 描述 |
| ----------------------------|--------------- | ------------- | ---------------------------------------------------|
| value | Boolean | false | 控制弹窗是否打开 |
| title | String | | 弹窗标题|
| range | Array | [] | 可选内容 |
| rangeKey | String | name | 显示内容的key|

View File

@@ -0,0 +1,72 @@
## 2.2.52024-07-30
* 修复 当 checkRange=true 时,拖动四个伸缩角放大图片时还可能会超出或未到边界的问题
* 修复 当 checkRange=false 时,图片旋转时会放大图片适应裁剪尺寸的问题
* 修复 当 checkRange=true 时,图片旋转 90° 或 270° 进行缩放可能会无法拖动图片的问题
## 2.2.42024-06-21
* 新增 reverseRotatable 属性,是否支持逆向翻转
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
## 2.2.32024-06-21
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
## 2.2.22024-06-21
* 优化 组件实例 chooseImage 方法支持传参
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
## 2.2.12024-06-15
* 修复 H5平台不支持手势拖动图片的问题
## 2.2.02024-05-31
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
## 2.1.92024-05-29
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译导致运行了H5平台代码报错的问题
## 2.1.82024-05-29
* 新增 zIndex 属性,调整组件层级
* 新增 组件内容插槽
* 优化 微信小程序平台动态修改元素style时的多余内容
## 2.1.72024-05-28
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
* 优化 动态修改图片宽高但没有传入src时尺寸适应问题
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
## 2.1.62023-04-16
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
## 2.1.52023-04-15
* 新增 兼容APP平台
## 2.1.42023-03-13
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制1365*1365则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
* 优化 旋转图标指示方向与实际旋转方向不符
## 2.1.32023-02-06
* 优化 vue3支持
## 2.1.22023-02-03
* 新增 navigation 属性H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
* 修复 H5平台部分设备已知iPhone11以下机型拍照的图片缩放时会闪动的问题
## 2.1.12022-12-06
* 修复 横屏适配问题
## 2.1.02022-12-06
* 新增 兼容H5平台使用 renderjs 响应手势事件
## 2.0.02022-12-05
* 重构 插件,使用 WXS 响应手势事件
* 新增 图片翻转
* 新增 拉伸裁剪框放大图片
* 新增 监听PC鼠标滚轮触发缩放
* 新增 圆形、圆角矩形的图片裁剪
* 优化 图片缩放移动端以双指触摸中心点为缩放中心点PC端以鼠标所在点为缩放中心点
* 优化 裁剪框样式
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
* 优化 生成图片使用新版 Canvas 2D 接口

View File

@@ -0,0 +1,855 @@
/**
* 图片编辑器-手势监听
* 1. 支持编译到app-vueuni-app 2.5.5及以上版本、H5上
*/
/** 图片偏移量 */
var offset = { x: 0, y: 0 };
/** 图片缩放比例 */
var scale = 1;
/** 图片最小缩放比例 */
var minScale = 1;
/** 图片旋转角度 */
var rotate = 0;
/** 触摸点 */
var touches = [];
/** 图片布局信息 */
var img = {};
/** 系统信息 */
var sys = {};
/** 裁剪区域布局信息 */
var area = {};
/** 触摸行为类型 */
var touchType = '';
/** 操作角的位置 */
var activeAngle = 0;
/** 裁剪区域布局信息偏移量 */
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
/** 元素ID */
var elIds = {
'imageStyles': 'crop-image',
'maskStylesList': 'crop-mask-block',
'borderStyles': 'crop-border',
'circleBoxStyles': 'crop-circle-box',
'circleStyles': 'crop-circle',
'gridStylesList': 'crop-grid',
'angleStylesList': 'crop-angle',
}
/** 记录上次初始化时间戳排除APP重复更新 */
var timestamp = 0;
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
// #ifdef H5
var platform = 'H5';
// #endif
// #ifdef APP
var platform = 'APP';
// #endif
/** 容错值 */
var fault = 0.000001;
/**
* 获取a、b两数中的最小正数
* @param a
* @param b
*/
function minimum(a, b) {
if (a > 0 && b < 0) return a;
if (a < 0 && b > 0) return b;
if (a > 0 && b > 0) return Math.min(a, b);
return 0;
}
/**
* 在容错访问内获取n近似值
* @param n
*/
function num(n) {
var m = parseFloat((n).toFixed(6));
return m === fault || m === -fault ? 0 : m;
}
/**
* 比较a值在容错值范围内是否等于b值
* @param a
* @param b
*/
function equalsByFault(a, b) {
return Math.abs(a - b) <= fault;
}
/**
* 比较a值在容错值范围内是否小于b值
* @param a
* @param b
*/
function lessThanByFault(a, b) {
var c = a - b;
return c < 0 ? c < -fault : c < fault;
}
/**
* 验证并获取有效最大值
* @param v
* @param max
* @param isInclude
* @param x
* @param y
* @param rate
* @returns
*/
function validMax(v, max, isInclude, x, y, rate) {
if(typeof max === 'number') {
if(isInclude && equalsByFault(max, y)) { // 宽高不等时x轴用y轴值要做等比例转换
var n = num(max * rate);
if (n <= x) return n; // 转化后值在x轴最大值范围内
return x; // 转化后值超出x轴最大值范围则用最大值
}
return max;
}
return v;
}
/**
* 样式对象转字符串
* @param {Object} style 样式对象
*/
function styleToString(style) {
if(typeof style === 'string') return style;
var str = '';
for (let k in style) {
str += k + ':' + style[k] + ';';
}
return str;
}
/**
*
* @param {Object} instance 页面实例对象
* @param {Object} key 要修改样式的key
* @param {Object|Array} style 样式
*/
function setStyle(instance, key, style) {
// console.log('setStyle', instance, key, JSON.stringify(style))
// #ifdef APP-PLUS
if(platform === 'APP') {
if(Object.prototype.toString.call(style) === '[object Array]') {
for (var i = 0, len = style.length; i < len; i++) {
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
el && (el.style = styleToString(style[i]));
}
} else {
var el = window.document.getElementById(elIds[key]);
el && (el.style = styleToString(style));
}
}
// #endif
// #ifdef H5
if(platform === 'H5') instance[key] = style;
// #endif
}
/**
* 触发页面实例指定方法
* @param {Object} instance 页面实例对象
* @param {Object} name 方法名称
* @param {Object} obj 传递参数
*/
function callMethod(instance, name, obj) {
// #ifdef APP-PLUS
if(platform === 'APP') instance.callMethod(name, obj);
// #endif
// #ifdef H5
if(platform === 'H5') instance[name](obj);
// #endif
}
/**
* 计算两点间距
* @param {Object} touches 触摸点信息
*/
function getDistanceByTouches(touches) {
// 根据勾股定理求两点间距离
var a = touches[1].pageX - touches[0].pageX;
var b = touches[1].pageY - touches[0].pageY;
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
// 求两点间的中点坐标
// 1. a、b可能为负值
// 2. 在求a、b时如用touches[1]减touches[0]则求中点坐标也得用touches[1]减a/2、b/2
// 3. 同理在求a、b时也可用touches[0]减touches[1]则求中点坐标也得用touches[0]减a/2、b/2
var x = touches[1].pageX - a / 2;
var y = touches[1].pageY - b / 2;
return { c, x, y };
};
/**
* 修正取值
* @param {Object} a
* @param {Object} b
* @param {Object} c
* @param {Object} reverse 是否反向
*/
function correctValue(a, b, c, reverse) {
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
}
/**
* 旋转90°或270°时检查边界限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
* @param {Object} xReverse x是否反向
* @param {Object} yReverse y是否反向
*/
function checkRotateRange(e, xReverse, yReverse) {
var o = num((img.height - img.width) / 2); // 宽高差值一半
return {
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
};
}
/**
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
*/
function checkRange(e) {
var r = rotate / 90 % 2;
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
if (area.width === area.height) {
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
if (img.width < area.height || img.height < area.width) {
if (area.width < area.height && img.width < img.height) {
return isInclude
? checkRotateRange(e, area.width < area.height, area.width < area.height)
: checkRotateRange(e, false, true);
}
if (area.height < area.width && img.height < img.width) {
return isInclude
? checkRotateRange(e, area.height < area.width, area.height < area.width)
: checkRotateRange(e, true, false);
}
}
if (img.height >= area.width && img.width >= area.height) {
return checkRotateRange(e, false, false);
}
if (isInclude) {
return area.height < area.width
? checkRotateRange(e, true, true)
: checkRotateRange(e, area.width < area.height, area.width < area.height);
}
if (img.height < area.width && !img.width < area.height) {
return checkRotateRange(e, true, false);
}
if (!img.height < area.width && img.width < area.height) {
return checkRotateRange(e, false, true);
}
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
return {
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
}
};
/**
* 变更图片布局信息
* @param {Object} e 布局信息
*/
function changeImageRect(e) {
// console.log('changeImageRect', e)
offset.x += e.x || 0;
offset.y += e.y || 0;
if(e.check && area.checkRange) { // 检查边界
var point = checkRange(offset);
if(offset.x !== point.x || offset.y !== point.y) {
offset = point;
}
}
// 因频繁修改 width/height 会造成大量的内存消耗改为scale
// e.instance.imageStyles = {
// width: img.width + 'px',
// height: img.height + 'px',
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
// };
var ox = (img.width - img.oldWidth) / 2;
var oy = (img.height - img.oldHeight) / 2;
// e.instance.imageStyles = {
// width: img.oldWidth + 'px',
// height: img.oldHeight + 'px',
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
// };
setStyle(e.instance, 'imageStyles', {
width: img.oldWidth + 'px',
height: img.oldHeight + 'px',
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
});
callMethod(e.instance, 'dataChange', {
width: img.width,
height: img.height,
x: offset.x,
y: offset.y,
rotate: rotate
});
};
/**
* 变更裁剪区域布局信息
* @param {Object} e 布局信息
*/
function changeAreaRect(e) {
// console.log('changeAreaRect', e)
// 变更蒙版样式
setStyle(e.instance, 'maskStylesList', [
{
left: 0,
width: (area.left + areaOffset.left) + 'px',
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.right + areaOffset.right) + 'px',
right: 0,
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: 0,
height: (area.top + areaOffset.top) + 'px',
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: (area.bottom + areaOffset.bottom) + 'px',
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
bottom: 0,
'z-index': area.zIndex + 2
}
]);
// 变更边框样式
if(area.showBorder) {
setStyle(e.instance, 'borderStyles', {
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
});
}
// 变更参考线样式
if(area.showGrid) {
setStyle(e.instance, 'gridStylesList', [
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
}
]);
}
// 变更四个伸缩角样式
if(area.showAngle) {
setStyle(e.instance, 'angleStylesList', [
{
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
}
]);
}
// 变更圆角样式
if(area.radius > 0) {
var radius = area.radius;
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
radius = (area.width / 2);
} else { // 圆角矩形
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
radius = Math.min(area.width / 2, area.height / 2, radius);
}
}
setStyle(e.instance, 'circleBoxStyles', {
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 2
});
setStyle(e.instance, 'circleStyles', {
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
'border-radius': radius + 'px'
});
}
};
/**
* 缩放图片
* @param {Object} e 布局信息
*/
function scaleImage(e) {
// console.log('scaleImage', e)
var last = scale;
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
if(last !== scale) {
img.width = num(img.oldWidth * scale);
img.height = num(img.oldHeight * scale);
// 参考问题有一个长4000px、宽4000px的四方形ABCDA点的坐标固定在(-2000,-2000)
// 该四边形上有一个点E坐标为(-100,-300)将该四方形复制一份并缩小到90%后,
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
e.x = num((e.x - offset.x) * (1 - scale / last));
e.y = num((e.y - offset.y) * (1 - scale / last));
changeImageRect(e);
return true;
}
return false;
};
/**
* 获取触摸点在哪个角
* @param {number} x 触摸点x轴坐标
* @param {number} y 触摸点y轴坐标
* @return {number} 角的位置0=无1=左上2=右上3=左下4=右下;
*/
function getToucheAngle(x, y) {
// console.log('getToucheAngle', x, y, JSON.stringify(area))
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
var oy = sys.navigation ? 0 : sys.windowTop;
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 1; // 左上角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 2; // 右上角
}
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 3; // 左下角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 4; // 右下角
}
}
return 0; // 无触摸到角
};
/**
* 重置数据
*/
function resetData() {
offset = { x: 0, y: 0 };
scale = 1;
minScale = img.minScale;
rotate = 0;
};
function getTouchs(touches) {
var result = [];
var len = touches ? touches.length : 0
for (var i = 0; i < len; i++) {
result[i] = {
pageX: touches[i].pageX,
// h5无标题栏时窗口顶部距离仍为标题栏高度且触摸点y轴坐标还是有标题栏的值即减去标题栏高度的值
pageY: touches[i].pageY + sys.windowTop
};
}
return result;
};
var mouseEvent = false;
export default {
data() {
return {
imageStyles: {},
maskStylesList: [{}, {}, {}, {}],
borderStyles: {},
gridStylesList: [{}, {}, {}, {}],
angleStylesList: [{}, {}, {}, {}],
circleBoxStyles: {},
circleStyles: {}
}
},
created() {
// 监听 PC 端鼠标滚轮
// #ifdef H5
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
var touchs = getTouchs([e])
img.src && scaleImage({
instance: await this.getInstance(),
check: true,
// 鼠标向上滚动时deltaY 固定 -100鼠标向下滚动时deltaY 固定 100
scale: e.deltaY > 0 ? -0.05 : 0.05,
x: touchs[0].pageX,
y: touchs[0].pageY
});
});
// #endif
},
// #ifdef H5
mounted() {
platform === 'H5' && this.initH5Events();
},
// #endif
setPlatform(p) {
platform = p;
},
methods: {
// #ifdef H5
getTouchEvent(e) {
e.touches = [
{ pageX: e.pageX, pageY: e.pageY }
];
return e;
},
initH5Events() {
const preview = document.getElementById('pic-preview');
preview?.addEventListener('mousedown', (e, ev) => {
mouseEvent = true;
this.touchstart(this.getTouchEvent(e));
});
preview?.addEventListener('mousemove', (e) => {
if (!mouseEvent) return;
this.touchmove(this.getTouchEvent(e));
});
preview?.addEventListener('mouseup', (e) => {
mouseEvent = false;
this.touchend(this.getTouchEvent(e))
});
preview?.addEventListener('mouseleave', (e) => {
mouseEvent = false;
this.touchend(this.getTouchEvent(e))
});
},
// #endif
async getInstance() {
// #ifdef APP-PLUS
if(platform === 'APP')
return this.$ownerInstance
? Promise.resolve(this.$ownerInstance)
: new Promise((resolve) => {
setTimeout(async () => {
resolve(await this.getInstance());
});
});
// #endif
// #ifdef H5
if(platform === 'H5')
return Promise.resolve(this);
// #endif
},
/**
* 初始化:观察数据变更
* @param {Object} newVal 新数据
* @param {Object} oldVal 旧数据
* @param {Object} o 组件实例对象
*/
initObserver: async function(newVal, oldVal, o, i) {
// console.log('initObserver', newVal, oldVal, o, i)
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
timestamp = newVal.timestamp;
img = newVal.img;
sys = newVal.sys;
area = newVal.area;
minScale = img.minScale;
resetData();
const instance = await this.getInstance()
img.src && changeImageRect({
instance,
x: (sys.windowWidth - img.width) / 2,
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
});
changeAreaRect({
instance
});
}
},
/**
* 鼠标滚轮滚动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
mousewheel: function(e, o) {
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
},
/**
* 触摸开始
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchstart: function(e, o) {
if(!img.src) return;
touches = getTouchs(e.touches);
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
if(touches.length === 1 && activeAngle !== 0) {
touchType = 'stretch'; // 伸缩裁剪区域
} else {
touchType = '';
}
// console.log('touchstart', e, activeAngle)
},
/**
* 触摸移动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchmove: async function(e, o) {
if(!img.src) return;
// console.log('touchmove', e, o)
e.touches = getTouchs(e.touches);
if(touchType === 'stretch') { // 触摸四个角进行拉伸
var point = e.touches[0];
var start = touches[0];
var x = point.pageX - start.pageX;
var y = point.pageY - start.pageY;
if(x !== 0 || y !== 0) {
var maxX = num(area.width * (1 - area.minScale));
var maxY = num(area.height * (1 - area.minScale));
// console.log(x, y, maxX, maxY, offset, area)
touches[0] = point;
var r = rotate / 90 % 2;
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
var isInclude = xCompare && yCompare;
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
switch(activeAngle) {
case 1: // 左上角
x = num(x + areaOffset.left);
y = num(y + areaOffset.top);
if(x >= 0 && y >= 0) { // 有效滑动
var t = num(offset.y + m - area.top);
var l = num(offset.x - m - area.left);
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.top = y;
}
break;
case 2: // 右上角
x = num(x + areaOffset.right);
y = num(y + areaOffset.top);
if(x <= 0 && y >= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var t = num(offset.y + m - area.top);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((t >= 0) || (l >= 0))
? minimum(t, l)
: false;
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(-y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.top = y;
}
break;
case 3: // 左下角
x += num(x + areaOffset.left);
y += num(y + areaOffset.bottom);
if(x >= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.width : img.height);
var t = num(area.bottom - m - offset.y - w);
var l = num(offset.x - m - area.left);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(-y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.bottom = y;
}
break;
case 4: // 右下角
x = num(x + areaOffset.right);
y = num(y + areaOffset.bottom);
if(x <= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var h = (r === 1 ? img.width : img.height);
var t = num(area.bottom - offset.y - h - m);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.bottom = y;
}
break;
}
// console.log(x, y, JSON.stringify(areaOffset))
changeAreaRect({
instance: await this.getInstance(),
});
// this.draw();
}
} else if (e.touches.length == 2) { // 双点触摸缩放
var start = getDistanceByTouches(touches);
var end = getDistanceByTouches(e.touches);
scaleImage({
instance: await this.getInstance(),
check: !area.bounce,
scale: (end.c - start.c) / 100,
x: end.x,
y: end.y
});
touchType = 'scale';
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
touchType = 'move';
} else {
changeImageRect({
instance: await this.getInstance(),
check: !area.bounce,
x: e.touches[0].pageX - touches[0].pageX,
y: e.touches[0].pageY - touches[0].pageY
});
touchType = 'move';
}
touches = e.touches;
},
/**
* 触摸结束
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchend: async function(e, o) {
if(!img.src) return;
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
// 裁剪区域宽度被缩放到多少
var left = areaOffset.left;
var right = areaOffset.right;
var top = areaOffset.top;
var bottom = areaOffset.bottom;
var w = area.width + right - left;
var h = area.height + bottom - top;
// 图像放大倍数
var p = scale * (area.width / w) - scale;
// 复原裁剪区域
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
changeAreaRect({
instance: await this.getInstance(),
});
scaleImage({
instance: await this.getInstance(),
scale: p,
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
});
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
changeImageRect({
instance: await this.getInstance(),
check: true
});
}
},
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
rotateImage: async function(r) {
rotate = (rotate + (r || 90)) % 360;
if(img.minScale >= 1 && area.checkRange) {
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
minScale = 1;
if(img.width < area.height) {
minScale = area.height / img.oldWidth;
} else if(img.height < area.width) {
minScale = area.width / img.oldHeight;
}
if(minScale !== 1) {
scaleImage({
instance: await this.getInstance(),
scale: minScale - scale,
x: sys.windowWidth / 2,
y: (sys.windowHeight - sys.offsetBottom) / 2
});
}
}
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
changeImageRect({
instance: await this.getInstance(),
check: true,
x: -ox - oy,
y: -oy + ox
});
},
rotateImage90: function() {
this.rotateImage(90)
},
rotateImage270: function() {
this.rotateImage(270)
},
}
}

View File

@@ -0,0 +1,746 @@
<template>
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
<slot name="slotNamess" />
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
width: `${canvansWidth}px`,
height: `${canvansHeight}px`
}"></canvas>
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
width: `${canvansWidth}px`,
height: `${canvansHeight}px`
}"></canvas>
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
</view>
<block v-if="showGrid">
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
</block>
<block v-if="showAngle">
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
<view :style="[{
width: `${angleSize}px`,
height: `${angleSize}px`
}]"></view>
</view>
</block>
</view>
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
<!-- <view class="choose-btn" @click="cancleChoose">取消</view> -->
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
</view>
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
<block v-else-if="!!imgSrc">
<view class="rechoose" @click="chooseImage">重选</view>
<button class="button" size="mini" @click="cropClick">确定</button>
</block>
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
</view>
</view>
</template>
<!-- #ifdef APP-VUE -->
<script module="cropper" lang="renderjs">
import cropper from './qf-image-cropper.render.js';
// vue3 app renderjs中条件编译无效
cropper.setPlatform('APP');
export default {
mixins: [ cropper ]
}
</script>
<!-- #endif -->
<!-- #ifdef H5 -->
<script module="cropper" lang="renderjs">
import cropper from './qf-image-cropper.render.js';
export default {
mixins: [ cropper ]
}
</script>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-QQ -->
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
<!-- #endif -->
<script>
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
const AREA_SIZE = 75;
/** 图片默认宽高 */
const IMG_SIZE = 300;
export default {
name:"qf-image-cropper",
// #ifdef MP-WEIXIN
options: {
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
styleIsolation: "isolated"
},
// #endif
props: {
/** 图片资源地址 */
src: {
type: String,
default: ''
},
/** 裁剪宽度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
width: {
type: Number,
default: IMG_SIZE
},
/** 裁剪高度有些平台或设备对于canvas的尺寸有限制过大可能会导致无法正常绘制 */
height: {
type: Number,
default: IMG_SIZE
},
/** 是否绘制裁剪区域边框 */
showBorder: {
type: Boolean,
default: true
},
/** 是否绘制裁剪区域网格参考线 */
showGrid: {
type: Boolean,
default: true
},
/** 是否展示四个支持伸缩的角 */
showAngle: {
type: Boolean,
default: true
},
/** 裁剪区域最小缩放倍数 */
areaScale: {
type: Number,
default: 0.3
},
/** 图片最小缩放倍数 */
minScale: {
type: Number,
default: 1
},
/** 图片最大缩放倍数 */
maxScale: {
type: Number,
default: 5
},
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
checkRange: {
type: Boolean,
default: true
},
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
backgroundColor: {
type: String
},
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
bounce: {
type: Boolean,
default: true
},
/** 是否支持翻转 */
rotatable: {
type: Boolean,
default: true
},
/** 是否支持逆向翻转 */
reverseRotatable: {
type: Boolean,
default: false
},
/** 是否支持从本地选择素材 */
choosable: {
type: Boolean,
default: true
},
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
gpu: {
type: Boolean,
default: false
},
/** 四个角尺寸单位px */
angleSize: {
type: Number,
default: 20
},
/** 四个角边框宽度单位px */
angleBorderWidth: {
type: Number,
default: 2
},
zIndex: {
type: [Number, String]
},
/** 裁剪图片圆角半径单位px */
radius: {
type: Number,
default: 0
},
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
fileType: {
type: String,
default: 'png'
},
/**
* 图片从绘制到生成所需时间单位ms
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
*/
delay: {
type: Number,
default: 1000
},
// #ifdef H5
/**
* 页面是否是原生标题栏
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
* 注因H5平台的窗口高度是包含标题栏的而屏幕触摸点的坐标是不包含的
*/
navigation: {
type: Boolean,
default: true
}
// #endif
},
emits: ["crop"],
data() {
return {
// 用不同 id 使 v-for key 不重复
maskList: [
{ id: 'crop-mask-block-1' },
{ id: 'crop-mask-block-2' },
{ id: 'crop-mask-block-3' },
{ id: 'crop-mask-block-4' },
],
gridList: [
{ id: 'crop-grid-1' },
{ id: 'crop-grid-2' },
{ id: 'crop-grid-3' },
{ id: 'crop-grid-4' },
],
angleList: [
{ id: 'crop-angle-1' },
{ id: 'crop-angle-2' },
{ id: 'crop-angle-3' },
{ id: 'crop-angle-4' },
],
/** 本地缓存的图片路径 */
imgSrc: '',
/** 图片的裁剪宽度 */
imgWidth: IMG_SIZE,
/** 图片的裁剪高度 */
imgHeight: IMG_SIZE,
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
widthPercent: AREA_SIZE,
/** 裁剪区域最大高度所占屏幕宽度百分比 */
heightPercent: AREA_SIZE,
/** 裁剪区域布局信息 */
area: {},
/** 未被缩放过的图片宽 */
oldWidth: 0,
/** 未被缩放过的图片高 */
oldHeight: 0,
/** 系统信息 */
sys: uni.getSystemInfoSync(),
scaleWidth: 0,
scaleHeight: 0,
rotate: 0,
offsetX: 0,
offsetY: 0,
use2d: false,
canvansWidth: 0,
canvansHeight: 0,
// imageStyles: {},
// maskStylesList: [{}, {}, {}, {}],
// borderStyles: {},
// gridStylesList: [{}, {}, {}, {}],
// angleStylesList: [{}, {}, {}, {}],
// circleBoxStyles: {},
// circleStyles: {},
}
},
computed: {
initData() {
// console.log('initData')
return {
timestamp: new Date().getTime(),
area: {
...this.area,
bounce: this.bounce,
showBorder: this.showBorder,
showGrid: this.showGrid,
showAngle: this.showAngle,
angleSize: this.angleSize,
angleBorderWidth: this.angleBorderWidth,
minScale: this.areaScale,
widthPercent: this.widthPercent,
heightPercent: this.heightPercent,
radius: this.radius,
checkRange: this.checkRange,
zIndex: +this.zIndex || 0,
},
sys: this.sys,
img: {
minScale: this.minScale,
maxScale: this.maxScale,
src: this.imgSrc,
width: this.oldWidth,
height: this.oldHeight,
oldWidth: this.oldWidth,
oldHeight: this.oldHeight,
gpu: this.gpu,
}
}
},
imgProps() {
return {
width: this.width,
height: this.height,
src: this.src,
}
}
},
watch: {
imgProps: {
handler(val, oldVal) {
// 自定义裁剪尺,示例如下:
this.imgWidth = Number(val.width) || IMG_SIZE;
this.imgHeight = Number(val.height) || IMG_SIZE;
let use2d = true;
// #ifndef MP-WEIXIN
use2d = false;
// #endif
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
// use2d = false;
// }
let canvansWidth = this.imgWidth;
let canvansHeight = this.imgHeight;
let size = Math.max(canvansWidth, canvansHeight)
let scalc = 1;
if(size > 1365) {
scalc = 1365 / size;
}
this.canvansWidth = canvansWidth * scalc;
this.canvansHeight = canvansHeight * scalc;
this.use2d = use2d;
this.initArea();
const src = val.src || this.imgSrc;
src && this.initImage(src, oldVal === undefined);
},
immediate: true
},
},
methods: {
/** 提供给wxs调用用来接收图片变更数据 */
dataChange(e) {
// console.log('dataChange', e)
this.scaleWidth = e.width;
this.scaleHeight = e.height;
this.rotate = e.rotate;
this.offsetX = e.x;
this.offsetY = e.y;
},
/** 初始化裁剪区域布局信息 */
initArea() {
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
// #ifndef H5
this.sys.windowTop = 0;
this.sys.navigation = true;
// #endif
// #ifdef H5
// h5平台的窗口高度是包含标题栏的
this.sys.windowTop = this.sys.windowTop || 44;
this.sys.navigation = this.navigation;
// #endif
let wp = this.widthPercent;
let hp = this.heightPercent;
if (this.imgWidth > this.imgHeight) {
hp = hp * this.imgHeight / this.imgWidth;
} else if (this.imgWidth < this.imgHeight) {
wp = wp * this.imgWidth / this.imgHeight;
}
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
const width = size * wp / 100;
const height = size * hp / 100;
const left = (this.sys.windowWidth - width) / 2;
const right = left + width;
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
this.area = { width, height, left, right, top, bottom };
this.scaleWidth = width;
this.scaleHeight = height;
},
/** 从本地选取图片 */
chooseImage(options) {
// #ifdef MP-WEIXIN || MP-JD
if(uni.chooseMedia) {
uni.chooseMedia({
...options,
count: 1,
mediaType: ['image'],
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].tempFilePath);
}
});
return;
}
// #endif
uni.chooseImage({
...options,
count: 1,
success: (res) => {
this.resetData();
this.initImage(res.tempFiles[0].path);
}
});
},
/** 重置数据 */
resetData() {
this.imgSrc = '';
this.rotate = 0;
this.offsetX = 0;
this.offsetY = 0;
this.initArea();
},
/**
* 初始化图片信息
* @param {String} url 图片链接
*/
initImage(url, isFirst) {
uni.getImageInfo({
src: url,
success: async (res) => {
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
this.imgSrc = res.path;
let scale = res.width / res.height;
let areaScale = this.area.width / this.area.height;
if (scale > 1) { // 横向图片
if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
} else { // 否则宽固定、高自适应
this.scaleHeight = res.height * this.scaleWidth / res.width;
}
} else { // 纵向图片
if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
} else { // 否则高固定,宽自适应
this.scaleWidth = res.width * this.scaleHeight / res.height;
}
}
// 记录原始宽高,为缩放比列做限制
this.oldWidth = +this.scaleWidth.toFixed(2);
this.oldHeight = +this.scaleHeight.toFixed(2);
},
fail: (err) => {
console.error(err)
}
});
},
/**
* 剪切图片圆角
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} radius 圆角半径
* @param {Number} scale 生成图片的实际尺寸与截取区域比
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
*/
drawClipImage(ctx, radius, scale, drawImage) {
if(radius > 0) {
ctx.save();
ctx.beginPath();
const w = this.canvansWidth;
const h = this.canvansHeight;
if(w === h && radius >= w / 2) { // 圆形
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
} else { // 圆角矩形
if(w !== h) { // 限制圆角半径不能超过短边的一半
radius = Math.min(w / 2, h / 2, radius);
// radius = Math.min(Math.max(w, h) / 2, radius);
}
ctx.moveTo(radius, 0);
ctx.arcTo(w, 0, w, h, radius);
ctx.arcTo(w, h, 0, h, radius);
ctx.arcTo(0, h, 0, 0, radius);
ctx.arcTo(0, 0, w, 0, radius);
ctx.closePath();
}
ctx.clip();
drawImage && drawImage(true);
ctx.restore();
} else {
drawImage && drawImage(false);
}
},
/**
* 旋转图片
* @param {Object} ctx canvas 的绘图上下文对象
* @param {Number} rotate 旋转角度
* @param {Number} scale 生成图片的实际尺寸与截取区域比
*/
drawRotateImage(ctx, rotate, scale) {
if(rotate !== 0) {
// 1. 以图片中心点为旋转中心点
const x = this.scaleWidth * scale / 2;
const y = this.scaleHeight * scale / 2;
ctx.translate(x, y);
// 2. 旋转画布
ctx.rotate(rotate * Math.PI / 180);
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
ctx.translate(-x, -y);
}
},
drawImage(ctx, image, callback) {
// 生成图片的实际尺寸与截取区域比
const scale = this.canvansWidth / this.area.width;
if(this.backgroundColor) {
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
else ctx.fillStyle = this.backgroundColor;
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
}
this.drawClipImage(ctx, this.radius, scale, () => {
this.drawRotateImage(ctx, this.rotate, scale);
const r = this.rotate / 90;
ctx.drawImage(
image,
[
(this.offsetX - this.area.left),
(this.offsetY - this.area.top),
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top)
][r] * scale,
[
(this.offsetY - this.area.top),
-(this.offsetX - this.area.left),
-(this.offsetY - this.area.top),
(this.offsetX - this.area.left)
][r] * scale,
this.scaleWidth * scale,
this.scaleHeight * scale
);
});
},
/**
* 绘图
* @param {Object} canvas
* @param {Object} ctx canvas 的绘图上下文对象
* @param {String} src 图片路径
* @param {Function} callback 开始绘制时回调
*/
draw2DImage(canvas, ctx, src, callback) {
// console.log('draw2DImage', canvas, ctx, src, callback)
if(canvas) {
const image = canvas.createImage();
image.onload = () => {
this.drawImage(ctx, image);
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
callback && setTimeout(callback, this.delay);
};
image.onerror = (err) => {
console.error(err)
uni.hideLoading();
};
image.src = src;
} else {
this.drawImage(ctx, src);
setTimeout(() => {
ctx.draw(false, callback);
}, 200);
}
},
/**
* 画布转图片到本地缓存
* @param {Object} canvas
* @param {String} canvasId
*/
canvasToTempFilePath(canvas, canvasId) {
// console.log('canvasToTempFilePath', canvas, canvasId)
uni.canvasToTempFilePath({
canvas,
canvasId,
x: 0,
y: 0,
width: this.canvansWidth,
height: this.canvansHeight,
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
fileType: this.fileType, // 目标文件的类型默认png
success: (res) => {
// 生成的图片临时文件路径
this.handleImage(res.tempFilePath);
},
fail: (err) => {
uni.hideLoading();
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
}
}, this);
},
/** 确认裁剪 */
cropClick() {
uni.showLoading({ title: '裁剪中...', mask: true });
if(!this.use2d) {
const ctx = uni.createCanvasContext('imgCanvas', this);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(null, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(null, 'imgCanvas');
});
return;
}
// #ifdef MP-WEIXIN
const query = uni.createSelectorQuery().in(this);
query.select('#imgCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const dpr = uni.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
this.canvasToTempFilePath(canvas);
});
});
// #endif
},
handleImage(tempFilePath){
// 在H5平台下tempFilePath 为 base64
// console.log(tempFilePath)
uni.hideLoading();
this.$emit('crop', { tempFilePath });
}
}
}
</script>
<style lang="scss" scoped>
.image-cropper {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #000;
.img-canvas {
position: absolute !important;
transform: translateX(-100%);
}
.pic-preview {
width: 100%;
flex: 1;
position: relative;
.crop-mask-block {
background-color: rgba(51, 51, 51, 0.8);
z-index: 2;
position: fixed;
box-sizing: border-box;
pointer-events: none;
}
.crop-circle-box {
position: fixed;
box-sizing: border-box;
z-index: 2;
pointer-events: none;
overflow: hidden;
.crop-circle {
width: 100%;
height: 100%;
}
}
.crop-image {
padding: 0 !important;
margin: 0 !important;
border-radius: 0 !important;
display: block !important;
backface-visibility: hidden;
}
.crop-border {
position: fixed;
border: 1px solid #fff;
box-sizing: border-box;
z-index: 3;
pointer-events: none;
}
.crop-grid {
position: fixed;
z-index: 3;
border-style: dashed;
border-color: #fff;
pointer-events: none;
opacity: 0.5;
}
.crop-angle {
position: fixed;
z-index: 3;
border-style: solid;
border-color: #fff;
pointer-events: none;
}
}
.fixed-bottom {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
flex-direction: row;
background-color: $uni-bg-color-grey;
.action-bar {
position: absolute;
top: -90rpx;
left: 10rpx;
display: flex;
.rotate-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
background-size: 60% 60%;
background-repeat: no-repeat;
background-position: center;
width: 80rpx;
height: 80rpx;
&.is-reverse {
transform: rotateY(180deg);
}
}
}
.rechoose {
color: $uni-color-primary;
padding: 0 $uni-spacing-row-lg;
line-height: 100rpx;
}
.choose-btn {
color: $uni-color-primary;
text-align: center;
line-height: 100rpx;
flex: 1;
}
.button {
margin: auto $uni-spacing-row-lg auto auto;
background-color: $uni-color-primary;
color: #fff;
}
}
.safe-area-inset-bottom {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
}
}
</style>

View File

@@ -0,0 +1,727 @@
/**
* 图片编辑器-手势监听
* 1. wxs 暂不支持 es6 语法
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上uni-app 2.2.5及以上版本)
*/
/** 图片偏移量 */
var offset = { x: 0, y: 0 };
/** 图片缩放比例 */
var scale = 1;
/** 图片最小缩放比例 */
var minScale = 1;
/** 图片旋转角度 */
var rotate = 0;
/** 触摸点 */
var touches = [];
/** 图片布局信息 */
var img = {};
/** 系统信息 */
var sys = {};
/** 裁剪区域布局信息 */
var area = {};
/** 触摸行为类型 */
var touchType = '';
/** 操作角的位置 */
var activeAngle = 0;
/** 裁剪区域布局信息偏移量 */
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
/** 容错值 */
var fault = 0.000001;
/**
* 获取a、b两数中的最小正数
* @param a
* @param b
*/
function minimum(a, b) {
if (a > 0 && b < 0) return a;
if (a < 0 && b > 0) return b;
if (a > 0 && b > 0) return Math.min(a, b);
return 0;
}
/**
* 在容错访问内获取n近似值
* @param n
*/
function num(n) {
var m = parseFloat((n).toFixed(6));
return m === fault || m === -fault ? 0 : m;
}
/**
* 比较a值在容错值范围内是否等于b值
* @param a
* @param b
*/
function equalsByFault(a, b) {
return Math.abs(a - b) <= fault;
}
/**
* 比较a值在容错值范围内是否小于b值
* @param a
* @param b
*/
function lessThanByFault(a, b) {
var c = a - b;
return c < 0 ? c < -fault : c < fault;
}
/**
* 验证并获取有效最大值
* @param v
* @param max
* @param isInclude
* @param x
* @param y
* @param rate
* @returns
*/
function validMax(v, max, isInclude, x, y, rate) {
if(typeof max === 'number') {
if(isInclude && equalsByFault(max, y)) { // 宽高不等时x轴用y轴值要做等比例转换
var n = num(max * rate);
if (n <= x) return n; // 转化后值在x轴最大值范围内
return x; // 转化后值超出x轴最大值范围则用最大值
}
return max;
}
return v;
}
/**
* 计算两点间距
* @param {Object} touches 触摸点信息
*/
function getDistanceByTouches(touches) {
// 根据勾股定理求两点间距离
var a = touches[1].pageX - touches[0].pageX;
var b = touches[1].pageY - touches[0].pageY;
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
// 求两点间的中点坐标
// 1. a、b可能为负值
// 2. 在求a、b时如用touches[1]减touches[0]则求中点坐标也得用touches[1]减a/2、b/2
// 3. 同理在求a、b时也可用touches[0]减touches[1]则求中点坐标也得用touches[0]减a/2、b/2
var x = touches[1].pageX - a / 2;
var y = touches[1].pageY - b / 2;
return { c, x, y };
};
/**
* 修正取值
* @param {Object} a
* @param {Object} b
* @param {Object} c
* @param {Object} reverse 是否反向
*/
function correctValue(a, b, c, reverse) {
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
}
/**
* 旋转90°或270°时检查边界限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
* @param {Object} xReverse x是否反向
* @param {Object} yReverse y是否反向
*/
function checkRotateRange(e, xReverse, yReverse) {
var o = num((img.height - img.width) / 2); // 宽高差值一半
return {
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
};
}
/**
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
* @param {Object} e 点坐标
*/
function checkRange(e) {
var r = rotate / 90 % 2;
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
if (area.width === area.height) {
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
if (img.width < area.height || img.height < area.width) {
if (area.width < area.height && img.width < img.height) {
return isInclude
? checkRotateRange(e, area.width < area.height, area.width < area.height)
: checkRotateRange(e, false, true);
}
if (area.height < area.width && img.height < img.width) {
return isInclude
? checkRotateRange(e, area.height < area.width, area.height < area.width)
: checkRotateRange(e, true, false);
}
}
if (img.height >= area.width && img.width >= area.height) {
return checkRotateRange(e, false, false);
}
if (isInclude) {
return area.height < area.width
? checkRotateRange(e, true, true)
: checkRotateRange(e, area.width < area.height, area.width < area.height);
}
if (img.height < area.width && !img.width < area.height) {
return checkRotateRange(e, true, false);
}
if (!img.height < area.width && img.width < area.height) {
return checkRotateRange(e, false, true);
}
return checkRotateRange(e, img.height < area.height, img.width < area.width);
}
return {
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
};
};
/**
* 变更图片布局信息
* @param {Object} e 布局信息
*/
function changeImageRect(e) {
offset.x += e.x || 0;
offset.y += e.y || 0;
var image = e.instance.selectComponent('.crop-image');
if(e.check && area.checkRange) { // 检查边界
var point = checkRange(offset);
if(offset.x !== point.x || offset.y !== point.y) {
offset = point;
}
}
// image.setStyle({
// width: img.width + 'px',
// height: img.height + 'px',
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
// });
var ox = (img.width - img.oldWidth) / 2;
var oy = (img.height - img.oldHeight) / 2;
image.setStyle({
width: img.oldWidth + 'px',
height: img.oldHeight + 'px',
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
});
e.instance.callMethod('dataChange', {
width: img.width,
height: img.height,
x: offset.x,
y: offset.y,
rotate: rotate
});
};
/**
* 变更裁剪区域布局信息
* @param {Object} e 布局信息
*/
function changeAreaRect(e) {
// 变更蒙版样式
var masks = e.instance.selectAllComponents('.crop-mask-block');
var maskStyles = [
{
left: 0,
width: (area.left + areaOffset.left) + 'px',
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.right + areaOffset.right) + 'px',
right: 0,
top: 0,
bottom: 0,
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: 0,
height: (area.top + areaOffset.top) + 'px',
'z-index': area.zIndex + 2
},
{
left: (area.left + areaOffset.left) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
top: (area.bottom + areaOffset.bottom) + 'px',
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
bottom: 0,
'z-index': area.zIndex + 2
}
];
var len = masks.length;
for (var i = 0; i < len; i++) {
masks[i].setStyle(maskStyles[i]);
}
// 变更边框样式
if(area.showBorder) {
var border = e.instance.selectComponent('.crop-border');
border.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
});
}
// 变更参考线样式
if(area.showGrid) {
var grids = e.instance.selectAllComponents('.crop-grid');
var gridStyles = [
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '1px 0 0 0',
left: (area.left + areaOffset.left) + 'px',
right: (area.right + areaOffset.right) + 'px',
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 1px 0 0',
top: (area.top + areaOffset.top) + 'px',
bottom: (area.bottom + areaOffset.bottom) + 'px',
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 3
}
];
var len = grids.length;
for (var i = 0; i < len; i++) {
grids[i].setStyle(gridStyles[i]);
}
}
// 变更四个伸缩角样式
if(area.showAngle) {
var angles = e.instance.selectAllComponents('.crop-angle');
var angleStyles = [
{
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
},
{
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
left: (area.right + areaOffset.right - area.angleSize) + 'px',
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
'z-index': area.zIndex + 3
}
];
var len = angles.length;
for (var i = 0; i < len; i++) {
angles[i].setStyle(angleStyles[i]);
}
}
// 变更圆角样式
if(area.radius > 0) {
var circleBox = e.instance.selectComponent('.crop-circle-box');
var circle = e.instance.selectComponent('.crop-circle');
var radius = area.radius;
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
radius = (area.width / 2);
} else { // 圆角矩形
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
radius = Math.min(area.width / 2, area.height / 2, radius);
}
}
circleBox.setStyle({
left: (area.left + areaOffset.left) + 'px',
top: (area.top + areaOffset.top) + 'px',
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
'z-index': area.zIndex + 2
});
circle.setStyle({
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
'border-radius': radius + 'px'
});
}
};
/**
* 缩放图片
* @param {Object} e 布局信息
*/
function scaleImage(e) {
var last = scale;
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
if(last !== scale) {
img.width = num(img.oldWidth * scale);
img.height = num(img.oldHeight * scale);
// 参考问题有一个长4000px、宽4000px的四方形ABCDA点的坐标固定在(-2000,-2000)
// 该四边形上有一个点E坐标为(-100,-300)将该四方形复制一份并缩小到90%后,
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
e.x = num((e.x - offset.x) * (1 - scale / last));
e.y = num((e.y - offset.y) * (1 - scale / last));
changeImageRect(e);
return true;
}
return false;
};
/**
* 获取触摸点在哪个角
* @param {number} x 触摸点x轴坐标
* @param {number} y 触摸点y轴坐标
* @return {number} 角的位置0=无1=左上2=右上3=左下4=右下;
*/
function getToucheAngle(x, y) {
// console.log('getToucheAngle', x, y, JSON.stringify(area))
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 1; // 左上角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 2; // 右上角
}
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
return 3; // 左下角
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
return 4; // 右下角
}
}
return 0; // 无触摸到角
};
/**
* 重置数据
*/
function resetData() {
offset = { x: 0, y: 0 };
scale = 1;
minScale = img.minScale;
rotate = 0;
};
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
function rotateImage(e, o, r) {
rotate = (rotate + r) % 360;
if(img.minScale >= 1 && area.checkRange) {
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
minScale = 1;
if(img.width < area.height) {
minScale = area.height / img.oldWidth;
} else if(img.height < area.width) {
minScale = area.width / img.oldHeight;
}
if(minScale !== 1) {
scaleImage({
instance: o,
scale: minScale - scale,
x: sys.windowWidth / 2,
y: (sys.windowHeight - sys.offsetBottom) / 2
});
}
}
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
changeImageRect({
instance: o,
check: true,
x: -ox - oy,
y: -oy + ox
});
};
module.exports = {
/**
* 初始化:观察数据变更
* @param {Object} newVal 新数据
* @param {Object} oldVal 旧数据
* @param {Object} o 组件实例对象
*/
initObserver: function(newVal, oldVal, o, i) {
if(newVal) {
img = newVal.img;
sys = newVal.sys;
area = newVal.area;
minScale = img.minScale;
resetData();
img.src && changeImageRect({
instance: o,
x: (sys.windowWidth - img.width) / 2,
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
});
changeAreaRect({
instance: o
});
// console.log('initRect', JSON.stringify(newVal))
}
},
/**
* 鼠标滚轮滚动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
mousewheel: function(e, o) {
if(!img.src) return;
scaleImage({
instance: o,
check: true,
// 鼠标向上滚动时deltaY 固定 -100鼠标向下滚动时deltaY 固定 100
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
x: e.touches[0].pageX,
y: e.touches[0].pageY
});
},
/**
* 触摸开始
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchstart: function(e, o) {
if(!img.src) return;
touches = e.touches;
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
if(touches.length === 1 && activeAngle !== 0) {
touchType = 'stretch'; // 伸缩裁剪区域
} else {
touchType = '';
}
// console.log('touchstart', JSON.stringify(e), activeAngle)
},
/**
* 触摸移动
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchmove: function(e, o) {
if(!img.src) return;
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
if(touchType === 'stretch') { // 触摸四个角进行拉伸
var point = e.touches[0];
var start = touches[0];
var x = point.pageX - start.pageX;
var y = point.pageY - start.pageY;
if(x !== 0 || y !== 0) {
var maxX = num(area.width * (1 - area.minScale));
var maxY = num(area.height * (1 - area.minScale));
// console.log(x, y, maxX, maxY, offset, area)
touches[0] = point;
var r = rotate / 90 % 2;
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
var isInclude = xCompare && yCompare;
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
switch(activeAngle) {
case 1: // 左上角
x = num(x + areaOffset.left);
y = num(y + areaOffset.top);
if(x >= 0 && y >= 0) { // 有效滑动
var t = num(offset.y + m - area.top);
var l = num(offset.x - m - area.left);
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.top = y;
}
break;
case 2: // 右上角
x = num(x + areaOffset.right);
y = num(y + areaOffset.top);
if(x <= 0 && y >= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var t = num(offset.y + m - area.top);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((t >= 0) || (l >= 0))
? minimum(t, l)
: false;
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
// : false;
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(y > maxY) y = maxY;
x = num(-y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.top = y;
}
break;
case 3: // 左下角
x += num(x + areaOffset.left);
y += num(y + areaOffset.bottom);
if(x >= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.width : img.height);
var t = num(area.bottom - m - offset.y - w);
var l = num(offset.x - m - area.left);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(x > maxX) x = maxX;
y = num(-x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(-y * area.width / area.height);
}
areaOffset.left = x;
areaOffset.bottom = y;
}
break;
case 4: // 右下角
x = num(x + areaOffset.right);
y = num(y + areaOffset.bottom);
if(x <= 0 && y <= 0) { // 有效滑动
var w = (r === 1 ? img.height : img.width);
var h = (r === 1 ? img.width : img.height);
var t = num(area.bottom - offset.y - h - m);
var l = num(area.right + m - offset.x - w);
var max = isIntersect && ((l >= 0) || (t >= 0))
? minimum(t, l)
: false;
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
if(-x > maxX) x = -maxX;
y = num(x * area.height / area.width);
} else { // 以y轴滑动距离为缩放基准
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
if(-y > maxY) y = -maxY;
x = num(y * area.width / area.height);
}
areaOffset.right = x;
areaOffset.bottom = y;
}
break;
}
// console.log(x, y, JSON.stringify(areaOffset))
changeAreaRect({
instance: o,
});
// this.draw();
}
} else if (e.touches.length == 2) { // 双点触摸缩放
var start = getDistanceByTouches(touches);
var end = getDistanceByTouches(e.touches);
scaleImage({
instance: o,
check: !area.bounce,
scale: (end.c - start.c) / 100,
x: end.x,
y: end.y
});
touchType = 'scale';
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
touchType = 'move';
} else {
changeImageRect({
instance: o,
check: !area.bounce,
x: e.touches[0].pageX - touches[0].pageX,
y: e.touches[0].pageY - touches[0].pageY
});
touchType = 'move';
}
touches = e.touches;
},
/**
* 触摸结束
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
touchend: function(e, o) {
if(!img.src) return;
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
// 裁剪区域宽度被缩放到多少
var left = areaOffset.left;
var right = areaOffset.right;
var top = areaOffset.top;
var bottom = areaOffset.bottom;
var w = area.width + right - left;
var h = area.height + bottom - top;
// 图像放大倍数
var p = scale * (area.width / w) - scale;
// 复原裁剪区域
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
changeAreaRect({
instance: o,
});
scaleImage({
instance: o,
scale: p,
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
});
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
changeImageRect({
instance: o,
check: true
});
}
},
/**
* 顺时针翻转图片90°
* @param {Object} e 事件对象
* @param {Object} o 组件实例对象
*/
rotateImage: function(e, o) {
rotateImage(e, o, 90);
},
rotateImage90: function(e, o) {
rotateImage(e, o, 90)
},
rotateImage270: function(e, o) {
rotateImage(e, o, 270)
},
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
imageStyles: '',
maskStylesList: ['', '', '', ''],
borderStyles: '',
gridStylesList: ['', '', '', ''],
angleStylesList: ['', '', '', ''],
circleBoxStyles: '',
circleStyles: '',
}

View File

@@ -0,0 +1,81 @@
{
"id": "qf-image-cropper",
"displayName": "图片裁剪插件",
"version": "2.2.5",
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
"keywords": [
"qf-image-cropper",
"图片裁剪",
"图片编辑",
"头像裁剪",
"小程序"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "u",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

View File

@@ -0,0 +1,97 @@
# qf-image-cropper
## 图片裁剪插件
uniapp微信小程序图片裁剪插件支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
### 平台支持:
1. 支持微信小程序移动端、PC端、开发者工具
2. 支持H5平台2.1.0版本起)
3. 支持APP平台2.1.5版本起Android、IOS
4. 其他平台暂未测试兼容性未知
### 支持功能:
1. 自定义裁剪尺寸
2. 定点等比例缩放移动端以双指触摸中心点为缩放中心点PC端以鼠标所在点为缩放中心点
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
5. 裁剪生成新图片
6. 本地选择图片
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
8. 裁剪圆角图片:圆形、圆角矩形
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
|:---|:---|:---|:---|
| src | String | | 图片资源地址 |
| width | Number | 300 | 裁剪宽度 |
| height | Number | 300 | 裁剪高度 |
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
| minScale | Number | 1 | 图片最小缩放倍数 |
| maxScale | Number | 5 | 图片最大缩放倍数 |
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
| rotatable | Boolean | true | 是否支持翻转 |
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
| choosable | Boolean | true | 是否支持从本地选择素材 |
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
| angleSize | Number | 20 | 四个角尺寸单位px |
| angleBorderWidth | Number | 2 | 四个角边框宽度单位px |
| zIndex | Number/String | | 调整组件层级 |
| radius | Number | | 裁剪图片圆角半径单位px |
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
| delay | Number | 1000 | 图片从绘制到生成所需时间单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>注因H5平台的窗口高度是包含标题栏的而屏幕触摸点的坐标是不包含的 |
| @crop | EventHandle | | 剪裁完成后触发event = { tempFilePath }。在H5平台下tempFilePath 为 base64 |
### 基本用法
```
<template>
<div>
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
</div>
</template>
<script>
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
export default {
components: {
QfImageCropper
},
methods: {
handleCrop(e) {
uni.previewImage({
urls: [e.tempFilePath],
current: 0
});
}
}
}
</script>
```
通过ref组件实例可在进入页面后直接打开相册选择图片
```
mounted() {
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
}
```
### 使用说明
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
```
{
"enablePullDownRefresh": false,
"disableScroll": true
}
```
2.建议使用本插件不要设置过大宽高的目标图片尺寸建议1365x1365以内否则可能会导致如下问题
```
1.界面卡顿,内存占用过高
2.生成图片失真(模糊)
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
```
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
4.src属性设置网络图片时图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
5.如果组件无法正常渲染且使用了 `v-if` 时,可尝试将 `v-if` 替换为 `v-show`
6.如果App端导入组件后无法正常渲染请尝试重新运行

View File

@@ -1,17 +1,23 @@
## 1.4.102023-04-10
- 修复 某些情况 monthSwitch 未触发的Bug
## 1.4.92023-02-02
- 修复 某些情况切换月份错误的Bug
## 1.4.82023-01-30
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
## 1.4.72022-09-16
- 可以使用 uni-scss 控制主题色
- 优化 支持使用 uni-scss 控制主题色
## 1.4.62022-09-08
- fix: 表头年月切换导致改变当前日期为选择月1号且未触发change事件
- 修复 表头年月切换导致改变当前日期为选择月1号且未触发change事件的Bug
## 1.4.52022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.42022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.32021-09-22
- 修复 startDate、 endDate 属性失效的 bug
- 修复 startDate、 endDate 属性失效的Bug
## 1.4.22021-08-24
- 新增 支持国际化
## 1.4.12021-08-05
- 修复 弹出层被 tabbar 遮盖 bug
- 修复 弹出层被 tabbar 遮盖的Bug
## 1.4.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.3.162021-05-12

View File

@@ -51,11 +51,10 @@
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
export default {
emits:['change'],
props: {

View File

@@ -20,7 +20,7 @@
<view class="uni-calendar__header-btn-box" @click.stop="next">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<text class="uni-calendar__backtoday" @click="backtoday">{{todayText}}</text>
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
</view>
<view class="uni-calendar__box">
@@ -62,12 +62,12 @@
<script>
import Calendar from './util.js';
import calendarItem from './uni-calendar-item.vue'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
import CalendarItem from './uni-calendar-item.vue'
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
/**
* Calendar 日历
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
@@ -90,7 +90,7 @@
*/
export default {
components: {
calendarItem
CalendarItem
},
emits:['close','confirm','change','monthSwitch'],
props: {
@@ -199,26 +199,26 @@
}
},
created() {
// 获取日历方法实例
this.cale = new Calendar({
// date: new Date(),
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
})
// 选中某一天
// this.cale.setDate(this.date)
this.init(this.date)
// this.setDay
},
methods: {
// 取消穿透
clean() {},
bindDateChange(e) {
const value = e.detail.value + '-1'
console.log(this.cale.getDate(value));
this.setDate(value)
const { year,month } = this.cale.getDate(value)
this.$emit('monthSwitch', {
year,
month
})
},
/**
* 初始化日期显示
@@ -323,11 +323,16 @@
/**
* 回到今天
*/
backtoday() {
console.log(this.cale.getDate(new Date()).fullDate);
let date = this.cale.getDate(new Date()).fullDate
// this.cale.setDate(date)
this.init(date)
backToday() {
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
const date = this.cale.getDate(new Date())
const todayYearMonth = `${date.year}-${date.month}`
if(nowYearMonth !== todayYearMonth) {
this.monthSwitch()
}
this.init(date.fullDate)
this.change()
},
/**
@@ -446,7 +451,6 @@
.uni-calendar--fixed-width {
width: 50px;
// padding: 0 15px;
}
.uni-calendar__backtoday {

View File

@@ -76,10 +76,20 @@ class Calendar {
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31) {
if (dd.getDate() === 31 && AddDayCount>0) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
const preMonth = dd.getMonth()
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
const nextMonth = dd.getMonth()
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
}
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
}
}
break
case 'year':

View File

@@ -1,7 +1,7 @@
{
"id": "uni-calendar",
"displayName": "uni-calendar 日历",
"version": "1.4.7",
"version": "1.4.10",
"description": "日历组件",
"keywords": [
"uni-ui",

View File

@@ -77,7 +77,7 @@ export default {
### Calendar Props
| 属性名 | 类型 | 默认值| 说明 |
| | |
| - | - | - | - |
| date | String |- | 自定义当前时间,默认为今天 |
| lunar | Boolean | false | 显示农历 |
| startDate | String |- | 日期选择范围-开始日期 |
@@ -91,7 +91,7 @@ export default {
### Calendar Events
| 事件名 | 说明 |返回值|
| | | |
| - | - | - |
| open | 弹出日历组件,`insert :false` 时生效|- |

View File

@@ -1,3 +1,5 @@
## 1.4.42024-03-20
- 修复 titleBorder类型修正
## 1.4.32022-01-25
- 修复 初始化的时候 open 属性失效的bug
## 1.4.22022-01-21

View File

@@ -40,7 +40,7 @@
* @property {String} name 唯一标志符
* @property {Boolean} open = [true|false] 是否展开组件
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
* @property {Boolean} border = [true|false] 是否显示分隔线
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
* @property {Boolean} disabled = [true|false] 是否展开面板
* @property {Boolean} showAnimation = [true|false] 开启动画
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头

View File

@@ -1,7 +1,7 @@
{
"id": "uni-collapse",
"displayName": "uni-collapse 折叠面板",
"version": "1.4.3",
"version": "1.4.4",
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
"keywords": [
"uni-ui",
@@ -17,10 +17,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [

View File

@@ -5,9 +5,5 @@
"main": "index.js",
"keywords": [],
"author": "DCloud",
"license": "Apache-2.0",
"origin-plugin-dev-name": "uni-config-center",
"origin-plugin-version": "0.0.3",
"plugin-dev-name": "uni-config-center",
"plugin-version": "0.0.3"
"license": "Apache-2.0"
}

View File

@@ -1,3 +1,12 @@
## 1.1.22023-04-11
- 修复 更改 modelValue 报错的 bug
- 修复 v-for 未使用 key 值控制台 warning
## 1.1.12023-02-21
- 修复代码合并时引发 value 属性为空时不渲染数据的问题
## 1.1.02023-02-15
- 修复 localdata 不支持动态更新的bug
## 1.0.92023-02-15
- 修复 localdata 不支持动态更新的bug
## 1.0.82022-09-16
- 可以使用 uni-scss 控制主题色
## 1.0.72022-07-06

View File

@@ -16,8 +16,7 @@
</view>
</scroll-view>
<text v-else class="selected-area placeholder">{{placeholder}}</text>
<view v-if="clearIcon && !readonly && inputSelected.length" class="icon-clear"
@click.stop="clear">
<view v-if="clearIcon && !readonly && inputSelected.length" class="icon-clear" @click.stop="clear">
<uni-icons type="clear" color="#c0c4cc" size="24"></uni-icons>
</view>
<view class="arrow-area" v-if="(!clearIcon || !inputSelected.length) && !readonly ">
@@ -40,8 +39,8 @@
</view>
<data-picker-view class="picker-view" ref="pickerView" v-model="dataValue" :localdata="localdata"
:preload="preload" :collection="collection" :field="field" :orderby="orderby" :where="where"
:step-searh="stepSearh" :self-field="selfField" :parent-field="parentField" :managed-mode="true"
:map="map" :ellipsis="ellipsis" @change="onchange" @datachange="ondatachange" @nodeclick="onnodeclick">
:step-searh="stepSearh" :self-field="selfField" :parent-field="parentField" :managed-mode="true" :map="map"
:ellipsis="ellipsis" @change="onchange" @datachange="ondatachange" @nodeclick="onnodeclick">
</data-picker-view>
</view>
</view>
@@ -76,7 +75,7 @@
*/
export default {
name: 'UniDataPicker',
emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue'],
emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue','inputclick'],
mixins: [dataPicker],
components: {
DataPickerView
@@ -128,58 +127,49 @@
}
},
created() {
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
this.$nextTick(() => {
this.load()
this.load();
})
},
watch: {
localdata: {
handler() {
this.load()
},
deep: true
},
},
methods: {
clear() {
this.inputSelected.splice(0)
this._dispatchEvent([])
this._dispatchEvent([]);
},
onPropsChange() {
this._treeData = []
this.selectedIndex = 0
this.load()
this._treeData = [];
this.selectedIndex = 0;
this.load();
},
load() {
if (this.readonly) {
this._processReadonly(this.localdata, this.dataValue)
return
this._processReadonly(this.localdata, this.dataValue);
return;
}
if (this.isLocaldata) {
this.loadData()
this.inputSelected = this.selected.slice(0)
} else if (!this.parentField && !this.selfField && this.hasValue) {
this.getNodeData(() => {
this.inputSelected = this.selected.slice(0)
})
} else if (this.hasValue) {
this.getTreePath(() => {
this.inputSelected = this.selected.slice(0)
// 回显本地数据
if (this.isLocalData) {
this.loadData();
this.inputSelected = this.selected.slice(0);
} else if (this.isCloudDataList || this.isCloudDataTree) { // 回显 Cloud 数据
this.loading = true;
this.getCloudDataValue().then((res) => {
this.loading = false;
this.inputSelected = res;
}).catch((err) => {
this.loading = false;
this.errorMessage = err;
})
}
},
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
show() {
this.isOpened = true
setTimeout(() => {
@@ -197,6 +187,7 @@
},
handleInput() {
if (this.readonly) {
this.$emit('inputclick')
return
}
this.show()
@@ -413,7 +404,12 @@
.uni-data-tree-dialog {
position: fixed;
left: 0;
/* #ifndef APP-NVUE */
top: 20%;
/* #endif */
/* #ifdef APP-NVUE */
top: 200px;
/* #endif */
right: 0;
bottom: 0;
background-color: #FFFFFF;
@@ -436,7 +432,7 @@
display: flex;
/* #endif */
flex-direction: row;
/* border-bottom: 1px solid #f5f5f5; */
/* border-bottom: 1px solid #f0f0f0; */
}
.title-area {
@@ -505,7 +501,7 @@
max-height: 50vh;
background-color: #fff;
border: 1px solid #EBEEF5;
box-shadow: 0px 0px 3px 0px rgba(0, 82, 79, 0.65);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
overflow: unset;
}
@@ -550,5 +546,6 @@
border-top-width: 0;
border-bottom-color: #fff;
}
/* #endif */
</style>

View File

@@ -42,7 +42,7 @@ export default {
},
pageSize: {
type: Number,
default: 20
default: 500
},
getcount: {
type: [Boolean, String],
@@ -122,19 +122,22 @@ export default {
}
},
computed: {
isLocaldata() {
return !this.collection.length
isLocalData() {
return !this.collection.length;
},
postField() {
let fields = [this.field];
if (this.parentField) {
fields.push(`${this.parentField} as parent_value`);
}
return fields.join(',');
isCloudData() {
return this.collection.length > 0;
},
isCloudDataList() {
return (this.isCloudData && (!this.parentField && !this.selfField));
},
isCloudDataTree() {
return (this.isCloudData && this.parentField && this.selfField);
},
dataValue() {
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null || this.modelValue !== undefined)
return isModelValue ? this.modelValue : this.value
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null ||
this.modelValue !== undefined);
return isModelValue ? this.modelValue : this.value;
},
hasValue() {
if (typeof this.dataValue === 'number') {
@@ -183,8 +186,169 @@ export default {
},
methods: {
onPropsChange() {
this._treeData = []
this._treeData = [];
},
// 填充 pickview 数据
async loadData() {
if (this.isLocalData) {
this.loadLocalData();
} else if (this.isCloudDataList) {
this.loadCloudDataList();
} else if (this.isCloudDataTree) {
this.loadCloudDataTree();
}
},
// 加载本地数据
async loadLocalData() {
this._treeData = [];
this._extractTree(this.localdata, this._treeData);
let inputValue = this.dataValue;
if (inputValue === undefined) {
return;
}
if (Array.isArray(inputValue)) {
inputValue = inputValue[inputValue.length - 1];
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
inputValue = inputValue[this.map.value];
}
}
this.selected = this._findNodePath(inputValue, this.localdata);
},
// 加载 Cloud 数据 (单列)
async loadCloudDataList() {
if (this.loading) {
return;
}
this.loading = true;
try {
let response = await this.getCommand();
let responseData = response.result.data;
this._treeData = responseData;
this._updateBindData();
this._updateSelected();
this.onDataChange();
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 加载 Cloud 数据 (树形)
async loadCloudDataTree() {
if (this.loading) {
return;
}
this.loading = true;
try {
let commandOptions = {
field: this._cloudDataPostField(),
where: this._cloudDataTreeWhere()
};
if (this.gettree) {
commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`;
}
let response = await this.getCommand(commandOptions);
let responseData = response.result.data;
this._treeData = responseData;
this._updateBindData();
this._updateSelected();
this.onDataChange();
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 加载 Cloud 数据 (节点)
async loadCloudDataNode(callback) {
if (this.loading) {
return;
}
this.loading = true;
try {
let commandOptions = {
field: this._cloudDataPostField(),
where: this._cloudDataNodeWhere()
};
let response = await this.getCommand(commandOptions);
let responseData = response.result.data;
callback(responseData);
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 回显 Cloud 数据
getCloudDataValue() {
if (this.isCloudDataList) {
return this.getCloudDataListValue();
}
if (this.isCloudDataTree) {
return this.getCloudDataTreeValue();
}
},
// 回显 Cloud 数据 (单列)
getCloudDataListValue() {
// 根据 field's as value标识匹配 where 条件
let where = [];
let whereField = this._getForeignKeyByField();
if (whereField) {
where.push(`${whereField} == '${this.dataValue}'`)
}
where = where.join(' || ');
if (this.where) {
where = `(${this.where}) && (${where})`
}
return this.getCommand({
field: this._cloudDataPostField(),
where
}).then((res) => {
this.selected = res.result.data;
return res.result.data;
});
},
// 回显 Cloud 数据 (树形)
getCloudDataTreeValue() {
return this.getCommand({
field: this._cloudDataPostField(),
getTreePath: {
startWith: `${this.selfField}=='${this.dataValue}'`
}
}).then((res) => {
let treePath = [];
this._extractTreePath(res.result.data, treePath);
this.selected = treePath;
return treePath;
});
},
getCommand(options = {}) {
/* eslint-disable no-undef */
let db = uniCloud.database(this.spaceInfo)
@@ -229,125 +393,16 @@ export default {
return db
},
getNodeData(callback) {
if (this.loading) {
return
_cloudDataPostField() {
let fields = [this.field];
if (this.parentField) {
fields.push(`${this.parentField} as parent_value`);
}
this.loading = true
this.getCommand({
field: this.postField,
where: this._pathWhere()
}).then((res) => {
this.loading = false
this.selected = res.result.data
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
return fields.join(',');
},
getTreePath(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
getTreePath: {
startWith: `${this.selfField}=='${this.dataValue}'`
}
}).then((res) => {
this.loading = false
let treePath = []
this._extractTreePath(res.result.data, treePath)
this.selected = treePath
callback && callback()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
loadData() {
if (this.isLocaldata) {
this._processLocalData()
return
}
if (this.dataValue != null) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
this._updateSelected()
})
return
}
if (this.stepSearh) {
this._loadNodeData((data) => {
this._treeData = data
this._updateBindData()
})
} else {
this._loadAllData((data) => {
this._treeData = []
this._extractTree(data, this._treeData, null)
this._updateBindData()
})
}
},
_loadAllData(callback) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
gettree: true,
startwith: `${this.selfField}=='${this.dataValue}'`
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_loadNodeData(callback, pw) {
if (this.loading) {
return
}
this.loading = true
this.getCommand({
field: this.postField,
where: pw || this._postWhere(),
pageSize: 500
}).then((res) => {
this.loading = false
callback(res.result.data)
this.onDataChange()
}).catch((err) => {
this.loading = false
this.errorMessage = err
})
},
_pathWhere() {
let result = []
let where_field = this._getParentNameByField();
if (where_field) {
result.push(`${where_field} == '${this.dataValue}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_postWhere() {
_cloudDataTreeWhere() {
let result = []
let selected = this.selected
let parentField = this.parentField
@@ -364,17 +419,35 @@ export default {
if (this.where) {
where.push(`(${this.where})`)
}
if (result.length) {
where.push(`(${result.join(' || ')})`)
}
return where.join(' && ')
},
_nodeWhere() {
let result = []
let selected = this.selected
_cloudDataNodeWhere() {
let where = []
let selected = this.selected;
if (selected.length) {
result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`);
}
where = where.join(' || ');
if (this.where) {
return `(${this.where}) && (${where})`
}
return where
},
_getWhereByForeignKey() {
let result = []
let whereField = this._getForeignKeyByField();
if (whereField) {
result.push(`${whereField} == '${this.dataValue}'`)
}
if (this.where) {
@@ -383,41 +456,23 @@ export default {
return result.join(' || ')
},
_getParentNameByField() {
const fields = this.field.split(',');
let where_field = null;
_getForeignKeyByField() {
let fields = this.field.split(',');
let whereField = null;
for (let i = 0; i < fields.length; i++) {
const items = fields[i].split('as');
if (items.length < 2) {
continue;
}
if (items[1].trim() === 'value') {
where_field = items[0].trim();
whereField = items[0].trim();
break;
}
}
return where_field
},
_isTreeView() {
return (this.parentField && this.selfField)
},
_updateSelected() {
var dl = this.dataList
var sl = this.selected
let textField = this.map.text
let valueField = this.map.value
for (var i = 0; i < sl.length; i++) {
var value = sl[i].value
var dl2 = dl[i]
for (var j = 0; j < dl2.length; j++) {
var item2 = dl2[j]
if (item2[valueField] === value) {
sl[i].text = item2[textField]
break
}
}
}
return whereField;
},
_updateBindData(node) {
const {
dataList,
@@ -445,6 +500,25 @@ export default {
hasNodes
}
},
_updateSelected() {
let dl = this.dataList
let sl = this.selected
let textField = this.map.text
let valueField = this.map.value
for (let i = 0; i < sl.length; i++) {
let value = sl[i].value
let dl2 = dl[i]
for (let j = 0; j < dl2.length; j++) {
let item2 = dl2[j]
if (item2[valueField] === value) {
sl[i].text = item2[textField]
break
}
}
}
},
_filterData(data, paths) {
let dataList = []
let hasNodes = true
@@ -453,8 +527,8 @@ export default {
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
}))
for (let i = 0; i < paths.length; i++) {
var value = paths[i].value
var nodes = data.filter((item) => {
let value = paths[i].value
let nodes = data.filter((item) => {
return item.parent_value === value
})
@@ -470,6 +544,7 @@ export default {
hasNodes
}
},
_extractTree(nodes, result, parent_value) {
let list = result || []
let valueField = this.map.value
@@ -493,6 +568,7 @@ export default {
}
}
},
_extractTreePath(nodes, result) {
let list = result || []
for (let i = 0; i < nodes.length; i++) {
@@ -512,6 +588,7 @@ export default {
}
}
},
_findNodePath(key, nodes, path = []) {
let textField = this.map.text
let valueField = this.map.value
@@ -540,24 +617,6 @@ export default {
path.pop()
}
return []
},
_processLocalData() {
this._treeData = []
this._extractTree(this.localdata, this._treeData)
var inputValue = this.dataValue
if (inputValue === undefined) {
return
}
if (Array.isArray(inputValue)) {
inputValue = inputValue[inputValue.length - 1]
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
inputValue = inputValue[this.map.value]
}
}
this.selected = this._findNodePath(inputValue, this.localdata)
}
}
}

View File

@@ -1,26 +1,28 @@
<template>
<view class="uni-data-pickerview">
<scroll-view class="selected-area" scroll-x="true" scroll-y="false" :show-scrollbar="false">
<scroll-view v-if="!isCloudDataList" class="selected-area" scroll-x="true">
<view class="selected-list">
<template v-for="(item,index) in selected">
<view class="selected-item"
:class="{'selected-item-active':index==selectedIndex, 'selected-item-text-overflow': ellipsis}"
v-if="item.text" @click="handleSelect(index)">
<text class="">{{item.text}}</text>
<view
class="selected-item"
v-for="(item,index) in selected"
:key="index"
:class="{
'selected-item-active':index == selectedIndex
}"
@click="handleSelect(index)"
>
<text>{{item.text || ''}}</text>
</view>
</template>
</view>
</scroll-view>
<view class="tab-c">
<template v-for="(child, i) in dataList" >
<scroll-view class="list" :key="i" v-if="i==selectedIndex" :scroll-y="true">
<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in child"
@click="handleNodeClick(item, i, j)">
<text class="item-text item-text-overflow">{{item[map.text]}}</text>
<view class="check" v-if="selected.length > i && item[map.value] == selected[i].value"></view>
<scroll-view class="list" :scroll-y="true">
<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in dataList[selectedIndex]" :key="j"
@click="handleNodeClick(item, selectedIndex, j)">
<text class="item-text">{{item[map.text]}}</text>
<view class="check" v-if="selected.length > selectedIndex && item[map.value] == selected[selectedIndex].value"></view>
</view>
</scroll-view>
</template>
<view class="loading-cover" v-if="loading">
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
@@ -64,43 +66,33 @@
default: true
}
},
data() {
return {}
},
created() {
if (this.managedMode) {
return
}
if (!this.managedMode) {
this.$nextTick(() => {
this.load()
this.loadData();
})
}
},
methods: {
onPropsChange() {
this._treeData = []
this.selectedIndex = 0
this.load()
},
load() {
if (this.isLocaldata) {
this.loadData()
} else if (this.dataValue.length) {
this.getTreePath((res) => {
this.loadData()
this._treeData = [];
this.selectedIndex = 0;
this.$nextTick(() => {
this.loadData();
})
}
},
handleSelect(index) {
this.selectedIndex = index
this.selectedIndex = index;
},
handleNodeClick(item, i, j) {
if (item.disable) {
return
return;
}
const node = this.dataList[i][j]
const text = node[this.map.text]
const value = node[this.map.value]
const node = this.dataList[i][j];
const text = node[this.map.text];
const value = node[this.map.value];
if (i < this.selected.length - 1) {
this.selected.splice(i, this.selected.length - i)
this.selected.push({
@@ -124,18 +116,16 @@
hasNodes
} = this._updateBindData()
if (!this._isTreeView() && !hasNodes) {
// 本地数据
if (this.isLocalData) {
this.onSelectedChange(node, (!hasNodes || isleaf))
} else if (this.isCloudDataList) { // Cloud 数据 (单列)
this.onSelectedChange(node, true)
return
}
if (this.isLocaldata && (!hasNodes || isleaf)) {
this.onSelectedChange(node, true)
return
}
if (!isleaf && !hasNodes) {
this._loadNodeData((data) => {
} else if (this.isCloudDataTree) { // Cloud 数据 (树形)
if (isleaf) {
this.onSelectedChange(node, node.isleaf)
} else if (!hasNodes) { // 请求一次服务器以确定是否为叶子节点
this.loadCloudDataNode((data) => {
if (!data.length) {
node.isleaf = true
} else {
@@ -143,11 +133,9 @@
this._updateBindData(node)
}
this.onSelectedChange(node, node.isleaf)
}, this._nodeWhere())
return
})
}
}
this.onSelectedChange(node, false)
},
updateData(data) {
this._treeData = data.treeData
@@ -160,7 +148,7 @@
}
},
onDataChange() {
this.$emit('datachange')
this.$emit('datachange');
},
onSelectedChange(node, isleaf) {
if (isleaf) {
@@ -177,6 +165,7 @@
}
}
</script>
<style lang="scss">
$uni-primary: #007aff !default;
@@ -231,15 +220,14 @@
.selected-area {
width: 750rpx;
}
/* #endif */
.selected-list {
/* #ifndef APP-NVUE */
display: flex;
flex-wrap: nowrap;
/* #endif */
flex-direction: row;
flex-wrap: nowrap;
padding: 0 5px;
border-bottom: 1px solid #f8f8f8;
}
@@ -290,7 +278,7 @@
.item {
padding: 12px 15px;
/* border-bottom: 1px solid #f5f5f5; */
/* border-bottom: 1px solid #f0f0f0; */
/* #ifndef APP-NVUE */
display: flex;
/* #endif */

View File

@@ -1,7 +1,7 @@
{
"id": "uni-data-picker",
"displayName": "uni-data-picker 数据驱动的picker选择器",
"version": "1.0.8",
"version": "1.1.2",
"description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景",
"keywords": [
"uni-ui",

View File

@@ -1,3 +1,10 @@
## 1.0.62023-04-12
- 修复 微信小程序点击时会改变背景颜色的 bug
## 1.0.52023-02-03
- 修复 禁用时会显示清空按钮
## 1.0.42023-02-02
- 优化 查询条件短期内多次变更只查询最后一次变更后的结果
- 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue
## 1.0.32023-01-16
- 修复 不关联服务空间报错的问题
## 1.0.22023-01-14

View File

@@ -6,8 +6,12 @@
<view class="uni-select__input-box" @click="toggleSelector">
<view v-if="current" class="uni-select__input-text">{{current}}</view>
<view v-else class="uni-select__input-text uni-select__input-placeholder">{{typePlaceholder}}</view>
<uni-icons v-if="current && clear" type="clear" color="#c0c4cc" size="24" @click="clearVal" />
<uni-icons v-else :type="showSelector? 'top' : 'bottom'" size="14" color="#999" />
<view v-if="current && clear && !disabled" @click.stop="clearVal" >
<uni-icons type="clear" color="#c0c4cc" size="24"/>
</view>
<view v-else>
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" />
</view>
</view>
<view class="uni-select--mask" v-if="showSelector" @click="toggleSelector" />
<view class="uni-select__selector" v-if="showSelector">
@@ -43,17 +47,8 @@
*/
export default {
name: "uni-stat-select",
name: "uni-data-select",
mixins: [uniCloud.mixinDatacom || {}],
data() {
return {
showSelector: false,
current: '',
mixinDatacomResData: [],
apps: [],
channels: []
};
},
props: {
localdata: {
type: Array,
@@ -99,10 +94,22 @@
default: ''
},
},
data() {
return {
showSelector: false,
current: '',
mixinDatacomResData: [],
apps: [],
channels: [],
cacheKey: "uni-data-select-lastSelectedValue",
};
},
created() {
this.last = `${this.collection}_last_selected_option_value`
if (this.collection && !this.localdata.length) {
this.debounceGet = this.debounce(() => {
this.query();
}, 300);
if (this.collection && !this.localdata.length) {
this.debounceGet();
}
},
computed: {
@@ -117,6 +124,14 @@
return placeholder ?
common + placeholder :
common
},
valueCom(){
// #ifdef VUE3
return this.modelValue;
// #endif
// #ifndef VUE3
return this.value;
// #endif
}
},
watch: {
@@ -128,16 +143,9 @@
}
}
},
// #ifndef VUE3
value() {
valueCom(val, old) {
this.initDefVal()
},
// #endif
// #ifdef VUE3
modelValue() {
this.initDefVal()
},
// #endif
mixinDatacomResData: {
immediate: true,
handler(val) {
@@ -148,6 +156,15 @@
}
},
methods: {
debounce(fn, time = 100){
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, time)
}
},
// 执行数据库查询
query(){
this.mixinDatacomEasyGet();
@@ -155,19 +172,17 @@
// 监听查询条件变更事件
onMixinDatacomPropsChange(){
if (this.collection) {
this.query()
this.debounceGet();
}
},
initDefVal() {
let defValue = ''
if ((this.value || this.value === 0) && !this.isDisabled(this.value)) {
defValue = this.value
} else if ((this.modelValue || this.modelValue === 0) && !this.isDisabled(this.modelValue)) {
defValue = this.modelValue
if ((this.valueCom || this.valueCom === 0) && !this.isDisabled(this.valueCom)) {
defValue = this.valueCom
} else {
let strogeValue
if (this.collection) {
strogeValue = uni.getStorageSync(this.last)
strogeValue = this.getCache()
}
if (strogeValue || strogeValue === 0) {
defValue = strogeValue
@@ -205,7 +220,7 @@
clearVal() {
this.emit('')
if (this.collection) {
uni.removeStorageSync(this.last)
this.removeCache()
}
},
change(item) {
@@ -216,14 +231,13 @@
}
},
emit(val) {
this.$emit('change', val)
this.$emit('input', val)
this.$emit('update:modelValue', val)
this.$emit('change', val)
if (this.collection) {
uni.setStorageSync(this.last, val)
this.setCache(val);
}
},
toggleSelector() {
if (this.disabled) {
return
@@ -256,7 +270,32 @@
`未命名${channel_code}`
)
}
}
},
// 获取当前加载的数据
getLoadData(){
return this.mixinDatacomResData;
},
// 获取当前缓存key
getCurrentCacheKey(){
return this.collection;
},
// 获取缓存
getCache(name=this.getCurrentCacheKey()){
let cacheData = uni.getStorageSync(this.cacheKey) || {};
return cacheData[name];
},
// 设置缓存
setCache(value, name=this.getCurrentCacheKey()){
let cacheData = uni.getStorageSync(this.cacheKey) || {};
cacheData[name] = value;
uni.setStorageSync(this.cacheKey, cacheData);
},
// 删除缓存
removeCache(name=this.getCurrentCacheKey()){
let cacheData = uni.getStorageSync(this.cacheKey) || {};
delete cacheData[name];
uni.setStorageSync(this.cacheKey, cacheData);
},
}
}
</script>
@@ -280,7 +319,9 @@
display: flex;
align-items: center;
// padding: 15px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
width: 100%;
flex: 1;
box-sizing: border-box;
@@ -372,7 +413,7 @@
background-color: #FFFFFF;
border: 1px solid #EBEEF5;
border-radius: 6px;
box-shadow: 0px 0px 3px 0px rgba(0, 82, 79, 0.65);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 3;
padding: 4px 0;
}
@@ -384,6 +425,14 @@
/* #endif */
}
/* #ifdef H5 */
@media (min-width: 768px) {
.uni-select__selector-scroll {
max-height: 600px;
}
}
/* #endif */
.uni-select__selector-empty,
.uni-select__selector-item {
/* #ifndef APP-NVUE */
@@ -463,5 +512,6 @@
bottom: 0;
right: 0;
left: 0;
z-index: 2;
}
</style>

View File

@@ -1,7 +1,7 @@
{
"id": "uni-data-select",
"displayName": "uni-data-select 下拉框选择器",
"version": "1.0.3",
"version": "1.0.6",
"description": "通过数据驱动的下拉框选择器",
"keywords": [
"uni-ui",

View File

@@ -1,3 +1,17 @@
## 2.2.232023-05-02
- 修复 部分情况修改时间,开始时间未更新 [详情](https://github.com/dcloudio/uni-ui/issues/737)
- 修复 部分平台及设备第一次点击无法显示弹框
- 修复 ios 日期格式未补零显示及使用异常 [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.222023-03-30
- 修复 日历 picker 修改年月后自动选中当月1日 [详情](https://ask.dcloud.net.cn/question/165937)
- 修复 小程序端 低版本 ios NaN [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.212023-02-20
- 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362)
## 2.2.202023-02-17
- 优化 值为空依然选中当天问题
- 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间
- 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间
- 优化 字节小程序日期时间范围选择,底部日期换行问题
## 2.2.192023-02-09
- 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686)
## 2.2.182023-02-08

View File

@@ -16,7 +16,7 @@
<text v-if="selected && weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text>
</view>
<view :class="{'uni-calendar-item--isDay': weeks.isDay}"></view>
<view :class="{'uni-calendar-item--today': weeks.isToday}"></view>
</view>
</template>
@@ -41,10 +41,6 @@
return []
}
},
lunar: {
type: Boolean,
default: false
},
checkHover: {
type: Boolean,
default: false
@@ -83,11 +79,6 @@
color: darken($color: $uni-primary, $amount: 40%);
}
.uni-calendar-item__weeks-lunar-text {
font-size: 12px;
color: #333;
}
.uni-calendar-item__weeks-box-item {
position: relative;
/* #ifndef APP-NVUE */
@@ -123,7 +114,7 @@
color: #D1D1D1;
}
.uni-calendar-item--isDay {
.uni-calendar-item--today {
position: absolute;
top: 10px;
right: 17%;

View File

@@ -1,29 +1,37 @@
<template>
<view class="uni-calendar" @mouseleave="leaveCale">
<view v-if="!insert && show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}"
@click="clean();maskClick()"></view>
@click="maskClick"></view>
<view v-if="insert || show" class="uni-calendar__content"
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}">
<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}">
<view v-if="left" class="uni-calendar__header-btn-box" @click.stop="pre">
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('pre')">
<view class="uni-calendar__header-btn uni-calendar--left"></view>
</view>
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
<text
class="uni-calendar__header-text">{{ (nowDate.year||'') + yearText + ( nowDate.month||'') + monthText}}</text>
</picker>
<view v-if="right" class="uni-calendar__header-btn-box" @click.stop="next">
<view class="uni-calendar__header-btn-box" @click.stop="changeMonth('next')">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<view v-if="!insert" class="dialog-close" @click="clean">
<view v-if="!insert" class="dialog-close" @click="close">
<view class="dialog-close-plus" data-id="close"></view>
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
</view>
</view>
<view class="uni-calendar__box">
<view v-if="showMonth" class="uni-calendar__box-bg">
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
</view>
<view class="uni-calendar__weeks" style="padding-bottom: 7px;">
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
@@ -47,28 +55,30 @@
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
</view>
</view>
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar"
:selected="selected" :lunar="lunar" :checkHover="range" @change="choiceDate"
:selected="selected" :checkHover="range" @change="choiceDate"
@handleMouse="handleMouse">
</calendar-item>
</view>
</view>
</view>
<view v-if="!insert && !range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top"
<view v-if="!insert && !range && hasTime" class="uni-date-changed uni-calendar--fixed-top"
style="padding: 0 80px;">
<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>
<time-picker type="time" :start="reactStartTime" :end="reactEndTime" v-model="time"
<time-picker type="time" :start="timepickerStartTime" :end="timepickerEndTime" v-model="time"
:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style">
</time-picker>
</view>
<view v-if="!insert && range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top">
<view v-if="!insert && range && hasTime" class="uni-date-changed uni-calendar--fixed-top">
<view class="uni-date-changed--time-start">
<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}}
</view>
<time-picker type="time" :start="reactStartTime" v-model="timeRange.startTime" :border="false"
<time-picker type="time" :start="timepickerStartTime" v-model="timeRange.startTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style">
</time-picker>
</view>
@@ -77,11 +87,12 @@
</view>
<view class="uni-date-changed--time-end">
<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view>
<time-picker type="time" :end="reactEndTime" v-model="timeRange.endTime" :border="false"
<time-picker type="time" :end="timepickerEndTime" v-model="timeRange.endTime" :border="false"
:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style">
</time-picker>
</view>
</view>
<view v-if="!insert" class="uni-date-changed uni-date-btn--ok">
<view class="uni-datetime-picker--btn" @click="confirm">{{confirmText}}</view>
</view>
@@ -90,7 +101,7 @@
</template>
<script>
import Calendar from './util.js';
import { Calendar, getDate, getTime } from './util.js';
import calendarItem from './calendar-item.vue'
import timePicker from './time-picker.vue'
@@ -103,7 +114,6 @@
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
* @property {String} date 自定义当前时间,默认为今天
* @property {Boolean} lunar 显示农历
* @property {String} startDate 日期选择范围-开始日期
* @property {String} endDate 日期选择范围-结束日期
* @property {Boolean} range 范围选择
@@ -113,10 +123,11 @@
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
* @property {Boolean} showMonth 是否选择月份为背景
* @property {[String} defaultValue 选择器打开时默认显示的时间
* @event {Function} change 日期改变,`insert :ture` 时生效
* @event {Function} confirm 确认选择`insert :false` 时生效
* @event {Function} monthSwitch 切换月份时触发
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
* @example <uni-calendar :insert="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
*/
export default {
components: {
@@ -144,10 +155,6 @@
return []
}
},
lunar: {
type: Boolean,
default: false
},
startDate: {
type: String,
default: ''
@@ -168,7 +175,7 @@
type: Boolean,
default: false
},
typeHasTime: {
hasTime: {
type: Boolean,
default: false
},
@@ -184,14 +191,6 @@
type: Boolean,
default: true
},
left: {
type: Boolean,
default: true
},
right: {
type: Boolean,
default: true
},
checkHover: {
type: Boolean,
default: true
@@ -210,6 +209,10 @@
fulldate: ''
}
}
},
defaultValue: {
type: [String, Object, Array],
default: ''
}
},
data() {
@@ -217,7 +220,7 @@
show: false,
weeks: [],
calendar: {},
nowDate: '',
nowDate: {},
aniMaskShow: false,
firstEnter: true,
time: '',
@@ -229,7 +232,8 @@
tempRange: {
before: '',
after: ''
}
},
isPhone: false
}
},
watch: {
@@ -260,7 +264,7 @@
if(!this.cale){
return
}
this.cale.resetSatrtDate(val)
this.cale.setStartDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
@@ -269,7 +273,7 @@
if(!this.cale){
return
}
this.cale.resetEndDate(val)
this.cale.setEndDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
@@ -312,10 +316,10 @@
}
this.cale.setDefaultMultiple(before, after)
if (which === 'left') {
if (which === 'left' && before) {
this.setDate(before)
this.weeks = this.cale.weeks
} else {
} else if(after) {
this.setDate(after)
this.weeks = this.cale.weeks
}
@@ -326,15 +330,13 @@
}
},
computed: {
reactStartTime() {
timepickerStartTime() {
const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate
const res = activeDate === this.startDate ? this.selectableTimes.start : ''
return res
return activeDate === this.startDate ? this.selectableTimes.start : ''
},
reactEndTime() {
timepickerEndTime() {
const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate
const res = activeDate === this.endDate ? this.selectableTimes.end : ''
return res
return activeDate === this.endDate ? this.selectableTimes.end : ''
},
/**
* for i18n
@@ -393,6 +395,14 @@
// 选中某一天
this.init(this.date)
},
mounted(){
if(typeof navigator !== "undefined"){
this.isPhone = navigator.userAgent.toLowerCase().indexOf('mobile') !== -1
return
}
const { windowWidth } = uni.getSystemInfoSync()
this.isPhone = windowWidth <= 500
},
methods: {
leaveCale() {
this.firstEnter = true
@@ -420,14 +430,9 @@
const [yearB, monthB] = B.split('-')
return yearA === yearB && monthA === monthB
},
// 取消穿透
clean() {
this.close()
},
// 蒙版点击事件
maskClick() {
this.close()
this.$emit('maskClose')
},
@@ -446,21 +451,48 @@
this.tempSingleDate = ''
}
this.calendar.fullDate = ''
this.setDate()
this.setDate(new Date())
},
bindDateChange(e) {
const value = e.detail.value + '-1'
this.init(value)
this.setDate(value)
},
/**
* 初始化日期显示
* @param {Object} date
*/
init(date) {
this.cale.setDate(date)
// 字节小程序 watch 早于 created
if(!this.cale){
return
}
this.cale.setDate(date || new Date())
this.weeks = this.cale.weeks
this.nowDate = this.calendar = this.cale.getInfo(date)
this.nowDate = this.cale.getInfo(date)
this.calendar = {...this.nowDate}
if(!date){
// 优化date为空默认不选中今天
this.calendar.fullDate = ''
if(this.defaultValue && !this.range){
// 暂时只支持移动端非范围选择
const defaultDate = new Date(this.defaultValue)
const fullDate = getDate(defaultDate)
const year = defaultDate.getFullYear()
const month = defaultDate.getMonth()+1
const date = defaultDate.getDate()
const day = defaultDate.getDay()
this.calendar = {
fullDate,
year,
month,
date,
day
},
this.tempSingleDate = fullDate
this.time = getTime(defaultDate, this.hideSecond)
}
}
},
/**
* 打开日历弹窗
@@ -469,7 +501,6 @@
// 弹窗模式并且清理数据
if (this.clearDate && !this.insert) {
this.cale.cleanMultipleStatus()
// this.cale.setDate(this.date)
this.init(this.date)
}
this.show = true
@@ -523,12 +554,20 @@
* @param {Object} name
*/
setEmit(name) {
if(!this.range){
if(!this.calendar.fullDate){
this.calendar = this.cale.getInfo(new Date())
this.tempSingleDate = this.calendar.fullDate
}
if(this.hasTime && !this.time) {
this.time = getTime(new Date(), this.hideSecond)
}
}
let {
year,
month,
date,
fullDate,
lunar,
extraInfo
} = this.calendar
this.$emit(name, {
@@ -539,7 +578,6 @@
time: this.time,
timeRange: this.timeRange,
fulldate: fullDate,
lunar,
extraInfo: extraInfo || {}
})
},
@@ -557,7 +595,8 @@
this.tempSingleDate = this.calendar.fullDate
const beforeDate = new Date(this.cale.multipleStatus.before).getTime()
const afterDate = new Date(this.cale.multipleStatus.after).getTime()
if (beforeDate > afterDate && afterDate) {
// 这里返回的 before after 为什么要做替换导致PC端如果开始结束日期都是今天第一次选择开始日期早于今天开始日期不更新
if (beforeDate > afterDate && afterDate && !this.isPhone) {
this.tempRange.before = this.cale.multipleStatus.after
this.tempRange.after = this.cale.multipleStatus.before
} else {
@@ -566,44 +605,15 @@
}
this.change()
},
/**
* 回到今天
*/
backtoday() {
let date = this.cale.getDate(new Date()).fullDate
// this.cale.setDate(date)
this.init(date)
this.change()
},
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
changeMonth(type) {
let newDate
if(type === 'pre') {
newDate = this.cale.getPreMonthObj(this.nowDate.fullDate).fullDate
} else if(type === 'next') {
newDate = this.cale.getNextMonthObj(this.nowDate.fullDate).fullDate
}
},
/**
* 上个月
*/
pre() {
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
this.setDate(preDate)
this.monthSwitch()
},
/**
* 下个月
*/
next() {
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
this.setDate(nextDate)
this.setDate(newDate)
this.monthSwitch()
},
/**
@@ -863,6 +873,9 @@
.uni-date-changed--time-date {
color: #999;
line-height: 50px;
/* #ifdef MP-TOUTIAO */
font-size: 16px;
/* #endif */
margin-right: 5px;
// opacity: 0.6;
}

View File

@@ -1,7 +1,7 @@
{
"uni-datetime-picker.selectDate": "select date",
"uni-datetime-picker.selectTime": "select time",
"uni-datetime-picker.selectDateTime": "select datetime",
"uni-datetime-picker.selectDateTime": "select date and time",
"uni-datetime-picker.startDate": "start date",
"uni-datetime-picker.endDate": "end date",
"uni-datetime-picker.startTime": "start time",

View File

@@ -84,6 +84,7 @@
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
const { t } = initVueI18n(i18nMessages)
import { fixIosDateFormat } from './util'
/**
* DatetimePicker 时间选择器
@@ -176,7 +177,7 @@
value: {
handler(newVal) {
if (newVal) {
this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式
this.parseValue(fixIosDateFormat(newVal))
this.initTime(false)
} else {
this.time = ''
@@ -190,7 +191,7 @@
modelValue: {
handler(newVal) {
if (newVal) {
this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式
this.parseValue(fixIosDateFormat(newVal))
this.initTime(false)
} else {
this.time = ''
@@ -220,13 +221,13 @@
},
start: {
handler(newVal) {
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式
this.parseDatetimeRange(fixIosDateFormat(newVal), 'start')
},
immediate: true
},
end: {
handler(newVal) {
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式
this.parseDatetimeRange(fixIosDateFormat(newVal), 'end')
},
immediate: true
},
@@ -739,7 +740,7 @@
*/
initTimePicker() {
if (this.disabled) return
const value = this.fixIosDateFormat(this.time)
const value = fixIosDateFormat(this.time)
this.initPickerValue(value)
this.visible = !this.visible
},

View File

@@ -8,19 +8,16 @@
>
<view v-if="!isRange" class="uni-date-x uni-date-single">
<uni-icons class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons>
<input class="uni-date__x-input" type="text" v-model="displayValue"
:placeholder="singlePlaceholderText" :disabled="true" />
<view class="uni-date__x-input">{{ displayValue || singlePlaceholderText }}</view>
</view>
<view v-else class="uni-date-x uni-date-range">
<uni-icons class="icon-calendar" type="calendar" color="#c0c4cc" size="22"></uni-icons>
<input class="uni-date__x-input t-c" type="text" v-model="range.startDate"
:placeholder="startPlaceholderText" :disabled="true" />
<view class="uni-date__x-input text-center">{{ displayRangeValue.startDate || startPlaceholderText }}</view>
<view class="range-separator">{{rangeSeparator}}</view>
<input class="uni-date__x-input t-c" type="text" v-model="range.endDate"
:placeholder="endPlaceholderText" :disabled="true" />
<view class="uni-date__x-input text-center">{{ displayRangeValue.endDate || endPlaceholderText }}</view>
</view>
<view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear">
@@ -33,37 +30,38 @@
<view v-show="pickerVisible" class="uni-date-mask--pc" @click="close"></view>
<view v-if="!isPhone" v-show="pickerVisible" ref="datePicker" class="uni-date-picker__container">
<view v-if="!isRange" class="uni-date-single--x" :style="popover">
<view v-if="!isRange" class="uni-date-single--x" :style="pickerPositionStyle">
<view class="uni-popper__arrow"></view>
<view v-if="hasTime" class="uni-date-changed popup-x-header">
<input class="uni-date__input t-c" type="text" v-model="inputDate"
<input class="uni-date__input text-center" type="text" v-model="inputDate"
:placeholder="selectDateText" />
<time-picker type="time" v-model="pickerTime" :border="false" :disabled="!inputDate"
:start="reactStartTime" :end="reactEndTime" :hideSecond="hideSecond" style="width: 100%;">
<input class="uni-date__input t-c" type="text" v-model="pickerTime" :placeholder="selectTimeText"
:start="timepickerStartTime" :end="timepickerEndTime" :hideSecond="hideSecond" style="width: 100%;">
<input class="uni-date__input text-center" type="text" v-model="pickerTime" :placeholder="selectTimeText"
:disabled="!inputDate" />
</time-picker>
</view>
<calendar ref="pcSingle" :showMonth="false" :start-date="caleRange.startDate"
:end-date="caleRange.endDate" :date="calendarDate" @change="singleChange"
<Calendar ref="pcSingle" :showMonth="false" :start-date="calendarRange.startDate"
:end-date="calendarRange.endDate" :date="calendarDate" @change="singleChange"
:default-value="defaultValue"
style="padding: 0 8px;" />
<view v-if="hasTime" class="popup-x-footer">
<text class="confirm" @click="confirmSingleChange">{{okText}}</text>
<text class="confirm-text" @click="confirmSingleChange">{{okText}}</text>
</view>
</view>
<view v-else class="uni-date-range--x" :style="popover">
<view v-else class="uni-date-range--x" :style="pickerPositionStyle">
<view class="uni-popper__arrow"></view>
<view v-if="hasTime" class="popup-x-header uni-date-changed">
<view class="popup-x-header--datetime">
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.startDate"
:placeholder="startDateText" />
<time-picker type="time" v-model="tempRange.startTime" :start="reactStartTime" :border="false"
<time-picker type="time" v-model="tempRange.startTime" :start="timepickerStartTime" :border="false"
:disabled="!tempRange.startDate" :hideSecond="hideSecond">
<input class="uni-date__input uni-date-range__input" type="text"
v-model="tempRange.startTime" :placeholder="startTimeText"
@@ -77,7 +75,7 @@
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endDate"
:placeholder="endDateText" />
<time-picker type="time" v-model="tempRange.endTime" :end="reactEndTime" :border="false"
<time-picker type="time" v-model="tempRange.endTime" :end="timepickerEndTime" :border="false"
:disabled="!tempRange.endDate" :hideSecond="hideSecond">
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endTime"
:placeholder="endTimeText" :disabled="!tempRange.endDate" />
@@ -86,26 +84,27 @@
</view>
<view class="popup-x-body">
<calendar ref="left" :showMonth="false" :start-date="caleRange.startDate"
:end-date="caleRange.endDate" :range="true" @change="leftChange" :pleStatus="endMultipleStatus"
@firstEnterCale="updateRightCale" style="padding: 0 8px;" />
<calendar ref="right" :showMonth="false" :start-date="caleRange.startDate"
:end-date="caleRange.endDate" :range="true" @change="rightChange"
<Calendar ref="left" :showMonth="false" :start-date="calendarRange.startDate"
:end-date="calendarRange.endDate" :range="true" :pleStatus="endMultipleStatus"
@change="leftChange" @firstEnterCale="updateRightCale" style="padding: 0 8px;" />
<Calendar ref="right" :showMonth="false" :start-date="calendarRange.startDate"
:end-date="calendarRange.endDate" :range="true" @change="rightChange"
:pleStatus="startMultipleStatus" @firstEnterCale="updateLeftCale"
style="padding: 0 8px;border-left: 1px solid #F1F1F1;" />
</view>
<view v-if="hasTime" class="popup-x-footer">
<text class="" @click="clear">{{clearText}}</text>
<text class="confirm" @click="confirmRangeChange">{{okText}}</text>
<text @click="clear">{{clearText}}</text>
<text class="confirm-text" @click="confirmRangeChange">{{okText}}</text>
</view>
</view>
</view>
<calendar v-if="isPhone" ref="mobile" :clearDate="false" :date="calendarDate" :defTime="reactMobDefTime"
:start-date="caleRange.startDate" :end-date="caleRange.endDate" :selectableTimes="mobSelectableTime"
<Calendar v-if="isPhone" ref="mobile" :clearDate="false" :date="calendarDate" :defTime="mobileCalendarTime"
:start-date="calendarRange.startDate" :end-date="calendarRange.endDate" :selectableTimes="mobSelectableTime"
:startPlaceholder="startPlaceholder" :endPlaceholder="endPlaceholder"
:pleStatus="endMultipleStatus" :showMonth="false" :range="isRange" :typeHasTime="hasTime" :insert="false"
:default-value="defaultValue"
:pleStatus="endMultipleStatus" :showMonth="false" :range="isRange" :hasTime="hasTime" :insert="false"
:hideSecond="hideSecond" @confirm="mobileChange" @maskClose="close" />
</view>
</template>
@@ -125,16 +124,18 @@
* @property {Boolean} border = [true|false] 是否有边框
* @property {Boolean} disabled = [true|false] 是否禁用
* @property {Boolean} clearIcon = [true|false] 是否显示清除按钮仅PC端适用
* @property {[String} defaultValue 选择器打开时默认显示的时间
* @event {Function} change 确定日期时触发的事件
* @event {Function} maskClick 点击遮罩层触发的事件
* @event {Function} show 打开弹出层
* @event {Function} close 关闭弹出层
* @event {Function} clear 清除上次选中的状态和值
**/
import calendar from './calendar.vue'
import timePicker from './time-picker.vue'
import Calendar from './calendar.vue'
import TimePicker from './time-picker.vue'
import { initVueI18n } from '@dcloudio/uni-i18n'
import i18nMessages from './i18n/index.js'
import { getDateTime, getDate, getTime, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat } from './util'
export default {
name: 'UniDatetimePicker',
@@ -142,8 +143,8 @@
virtualHost: true
},
components: {
calendar,
timePicker
Calendar,
TimePicker
},
data() {
return {
@@ -153,14 +154,13 @@
inputDate: '',
calendarDate: '',
pickerTime: '',
// 范围选
caleRange: {
calendarRange: {
startDate: '',
startTime: '',
endDate: '',
endTime: ''
},
range: {
displayRangeValue: {
startDate: '',
endDate: '',
},
@@ -184,7 +184,7 @@
fulldate: ''
},
pickerVisible: false,
popover: null,
pickerPositionStyle: null,
isEmitValue: false,
isPhone: false,
isFirstShow: true,
@@ -247,6 +247,10 @@
hideSecond: {
type: [Boolean],
default: false
},
defaultValue: {
type: [String, Object, Array],
default: ''
}
},
watch: {
@@ -285,13 +289,9 @@
immediate: true,
handler(newVal) {
if (!newVal) return
const {
defDate,
defTime
} = this.parseDate(newVal)
this.caleRange.startDate = defDate
this.calendarRange.startDate = getDate(newVal)
if (this.hasTime) {
this.caleRange.startTime = defTime
this.calendarRange.startTime = getTime(newVal)
}
}
},
@@ -299,39 +299,33 @@
immediate: true,
handler(newVal) {
if (!newVal) return
const {
defDate,
defTime
} = this.parseDate(newVal)
this.caleRange.endDate = defDate
this.calendarRange.endDate = getDate(newVal)
if (this.hasTime) {
this.caleRange.endTime = defTime
this.calendarRange.endTime = getTime(newVal, this.hideSecond)
}
}
},
},
computed: {
reactStartTime() {
timepickerStartTime() {
const activeDate = this.isRange ? this.tempRange.startDate : this.inputDate
const res = activeDate === this.caleRange.startDate ? this.caleRange.startTime : ''
return res
return activeDate === this.calendarRange.startDate ? this.calendarRange.startTime : ''
},
reactEndTime() {
timepickerEndTime() {
const activeDate = this.isRange ? this.tempRange.endDate : this.inputDate
const res = activeDate === this.caleRange.endDate ? this.caleRange.endTime : ''
return res
return activeDate === this.calendarRange.endDate ? this.calendarRange.endTime : ''
},
reactMobDefTime() {
const times = {
mobileCalendarTime() {
const timeRange = {
start: this.tempRange.startTime,
end: this.tempRange.endTime
}
return this.isRange ? times : this.pickerTime
return this.isRange ? timeRange : this.pickerTime
},
mobSelectableTime() {
return {
start: this.caleRange.startTime,
end: this.caleRange.endTime
start: this.calendarRange.startTime,
end: this.calendarRange.endTime
}
},
datePopupWidth() {
@@ -379,7 +373,7 @@
return this.i18nT("uni-datetime-picker.clear")
},
showClearIcon() {
return this.clearIcon && !this.disabled && (this.displayValue || (this.range.startDate && this.range.endDate))
return this.clearIcon && !this.disabled && (this.displayValue || (this.displayRangeValue.startDate && this.displayRangeValue.endDate))
}
},
created() {
@@ -392,7 +386,7 @@
this.i18nT = vueI18n.t
},
initPicker(newVal) {
if (!newVal || Array.isArray(newVal) && !newVal.length) {
if ((!newVal && !this.defaultValue) || Array.isArray(newVal) && !newVal.length) {
this.$nextTick(() => {
this.clear(false)
})
@@ -400,36 +394,40 @@
}
if (!Array.isArray(newVal) && !this.isRange) {
const {
defDate,
defTime
} = this.parseDate(newVal)
this.displayValue = defDate
this.inputDate = defDate
this.calendarDate = defDate
if(newVal){
this.displayValue = this.inputDate = this.calendarDate = getDate(newVal)
if (this.hasTime) {
this.displayValue = defDate + ' ' + defTime
this.pickerTime = defTime
this.pickerTime = getTime(newVal, this.hideSecond)
this.displayValue = `${this.displayValue} ${this.pickerTime}`
}
}else if(this.defaultValue){
this.inputDate = this.calendarDate = getDate(this.defaultValue)
if(this.hasTime){
this.pickerTime = getTime(this.defaultValue, this.hideSecond)
}
}
} else {
const [before, after] = newVal
if (!before && !after) return
const defBefore = this.parseDate(before)
const defAfter = this.parseDate(after)
const startDate = defBefore.defDate
const endDate = defAfter.defDate
this.range.startDate = this.tempRange.startDate = startDate
this.range.endDate = this.tempRange.endDate = endDate
const beforeDate = getDate(before)
const beforeTime = getTime(before, this.hideSecond)
const afterDate = getDate(after)
const afterTime = getTime(after, this.hideSecond)
const startDate = beforeDate
const endDate = afterDate
this.displayRangeValue.startDate = this.tempRange.startDate = startDate
this.displayRangeValue.endDate = this.tempRange.endDate = endDate
if (this.hasTime) {
this.range.startDate = defBefore.defDate + ' ' + defBefore.defTime
this.range.endDate = defAfter.defDate + ' ' + defAfter.defTime
this.tempRange.startTime = defBefore.defTime
this.tempRange.endTime = defAfter.defTime
this.displayRangeValue.startDate = `${beforeDate} ${beforeTime}`
this.displayRangeValue.endDate = `${afterDate} ${afterTime}`
this.tempRange.startTime = beforeTime
this.tempRange.endTime = afterTime
}
const defaultRange = {
before: defBefore.defDate,
after: defAfter.defDate
before: beforeDate,
after: afterDate
}
this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, {
which: 'right'
@@ -452,6 +450,10 @@
right.setDate(this.$refs.right.nowDate.fullDate)
},
platform() {
if(typeof navigator !== "undefined"){
this.isPhone = navigator.userAgent.toLowerCase().indexOf('mobile') !== -1
return
}
const { windowWidth } = uni.getSystemInfoSync()
this.isPhone = windowWidth <= 500
this.windowWidth = windowWidth
@@ -462,16 +464,18 @@
}
this.platform()
if (this.isPhone) {
setTimeout(() => {
this.$refs.mobile.open()
}, 0);
return
}
this.popover = {
this.pickerPositionStyle = {
top: '10px'
}
const dateEditor = uni.createSelectorQuery().in(this).select(".uni-date-editor")
dateEditor.boundingClientRect(rect => {
if (this.windowWidth - rect.left < this.datePopupWidth) {
this.popover.right = 0
this.pickerPositionStyle.right = 0
}
}).exec()
setTimeout(() => {
@@ -481,13 +485,13 @@
const {
startDate,
endDate
} = this.range
} = this.calendarRange
if (startDate && endDate) {
if (this.diffDate(startDate, endDate) < 30) {
this.$refs.right.next()
this.$refs.right.changeMonth('pre')
}
} else {
this.$refs.right.next()
this.$refs.right.changeMonth('next')
this.$refs.right.cale.lastHover = false
}
}
@@ -525,13 +529,13 @@
}
}
this.$emit('change', value)
this.$emit('input', value)
this.$emit('update:modelValue', value)
this.$emit('input', value)
this.$emit('change', value)
this.isEmitValue = true
},
createTimestamp(date) {
date = this.fixIosDateFormat(date)
date = fixIosDateFormat(date)
return Date.parse(new Date(date))
},
singleChange(e) {
@@ -540,12 +544,10 @@
this.confirmSingleChange()
},
confirmSingleChange() {
if (!this.inputDate) {
this.pickerVisible = false
return
}
if(!this.checkDate(this.inputDate)){
this.inputDate = this.getDate(Date.now())
if(!checkDate(this.inputDate)){
const now = new Date()
this.calendarDate = this.inputDate = getDate(now)
this.pickerTime = getTime(now, this.hideSecond)
}
let startLaterInputDate = false
@@ -553,10 +555,10 @@
if(this.start) {
let startString = this.start
if(typeof this.start === 'number'){
startString = this.getDateTime(this.start)
startString = getDateTime(this.start, this.hideSecond)
}
[startDate, startTime] = startString.split(' ')
if(this.start && !this.dateCompare(startDate, this.inputDate)) {
if(this.start && !dateCompare(startDate, this.inputDate)) {
startLaterInputDate = true
this.inputDate = startDate
}
@@ -567,23 +569,23 @@
if(this.end) {
let endString = this.end
if(typeof this.end === 'number'){
endString = this.getDateTime(this.end)
endString = getDateTime(this.end, this.hideSecond)
}
[endDate, endTime] = endString.split(' ')
if(this.end && !this.dateCompare(this.inputDate, endDate)) {
if(this.end && !dateCompare(this.inputDate, endDate)) {
endEarlierInputDate = true
this.inputDate = endDate
}
}
if (this.hasTime) {
if(startLaterInputDate){
this.pickerTime = startTime || this.getDefaultSecond()
this.pickerTime = startTime || getDefaultSecond(this.hideSecond)
}
if(endEarlierInputDate){
this.pickerTime = endTime || this.getDefaultSecond()
this.pickerTime = endTime || getDefaultSecond(this.hideSecond)
}
if(!this.pickerTime){
this.pickerTime = this.getTime(Date.now())
this.pickerTime = getTime(Date.now(), this.hideSecond)
}
this.displayValue = `${this.inputDate} ${this.pickerTime}`
} else {
@@ -659,11 +661,11 @@
this.pickerVisible = false
return
}
if(!this.checkDate(this.tempRange.startDate)){
this.tempRange.startDate = this.getDate(Date.now())
if(!checkDate(this.tempRange.startDate)){
this.tempRange.startDate = getDate(Date.now())
}
if(!this.checkDate(this.tempRange.endDate)){
this.tempRange.endDate = this.getDate(Date.now())
if(!checkDate(this.tempRange.endDate)){
this.tempRange.endDate = getDate(Date.now())
}
let start, end
@@ -674,14 +676,14 @@
if(this.start) {
let startString = this.start
if(typeof this.start === 'number'){
startString = this.getDateTime(this.start)
startString = getDateTime(this.start, this.hideSecond)
}
[startDate,startTime] = startString.split(' ')
if(this.start && !this.dateCompare(this.start, this.tempRange.startDate)) {
if(this.start && !dateCompare(this.start, this.tempRange.startDate)) {
startDateLaterRangeStartDate = true
this.tempRange.startDate = startDate
}
if(this.start && !this.dateCompare(this.start, this.tempRange.endDate)) {
if(this.start && !dateCompare(this.start, this.tempRange.endDate)) {
startDateLaterRangeEndDate = true
this.tempRange.endDate = startDate
}
@@ -692,48 +694,48 @@
if(this.end) {
let endString = this.end
if(typeof this.end === 'number'){
endString = this.getDateTime(this.end)
endString = getDateTime(this.end, this.hideSecond)
}
[endDate,endTime] = endString.split(' ')
if(this.end && !this.dateCompare(this.tempRange.startDate, this.end)) {
if(this.end && !dateCompare(this.tempRange.startDate, this.end)) {
endDateEarlierRangeStartDate = true
this.tempRange.startDate = endDate
}
if(this.end && !this.dateCompare(this.tempRange.endDate, this.end)) {
if(this.end && !dateCompare(this.tempRange.endDate, this.end)) {
endDateEarlierRangeEndDate = true
this.tempRange.endDate = endDate
}
}
if (!this.hasTime) {
start = this.range.startDate = this.tempRange.startDate
end = this.range.endDate = this.tempRange.endDate
start = this.displayRangeValue.startDate = this.tempRange.startDate
end = this.displayRangeValue.endDate = this.tempRange.endDate
} else {
if(startDateLaterRangeStartDate){
this.tempRange.startTime = startTime || this.getDefaultSecond()
this.tempRange.startTime = startTime || getDefaultSecond(this.hideSecond)
}else if(endDateEarlierRangeStartDate){
this.tempRange.startTime = endTime || this.getDefaultSecond()
this.tempRange.startTime = endTime || getDefaultSecond(this.hideSecond)
}
if(!this.tempRange.startTime){
this.tempRange.startTime = this.getTime(Date.now())
this.tempRange.startTime = getTime(Date.now(), this.hideSecond)
}
if(startDateLaterRangeEndDate){
this.tempRange.endTime = startTime || this.getDefaultSecond()
this.tempRange.endTime = startTime || getDefaultSecond(this.hideSecond)
}else if(endDateEarlierRangeEndDate){
this.tempRange.endTime = endTime || this.getDefaultSecond()
this.tempRange.endTime = endTime || getDefaultSecond(this.hideSecond)
}
if(!this.tempRange.endTime){
this.tempRange.endTime = this.getTime(Date.now())
this.tempRange.endTime = getTime(Date.now(), this.hideSecond)
}
start = this.range.startDate = `${this.tempRange.startDate} ${this.tempRange.startTime}`
end = this.range.endDate = `${this.tempRange.endDate} ${this.tempRange.endTime}`
start = this.displayRangeValue.startDate = `${this.tempRange.startDate} ${this.tempRange.startTime}`
end = this.displayRangeValue.endDate = `${this.tempRange.endDate} ${this.tempRange.endTime}`
}
if(!this.dateCompare(start,end)){
if(!dateCompare(start,end)){
[start, end] = [end, start]
}
this.range.startDate = start
this.range.endDate = end
this.displayRangeValue.startDate = start
this.displayRangeValue.endDate = end
const displayRange = [start, end]
this.setEmit(displayRange)
this.pickerVisible = false
@@ -742,7 +744,7 @@
if (!(before && after)) return
const type = temp ? 'tempRange' : 'range'
const isStartEarlierEnd = this.dateCompare(before, after)
const isStartEarlierEnd = dateCompare(before, after)
this[type].startDate = isStartEarlierEnd ? before : after
this[type].endDate = isStartEarlierEnd ? after : before
},
@@ -785,8 +787,8 @@
this.$emit('update:modelValue', '')
}
} else {
this.range.startDate = ''
this.range.endDate = ''
this.displayRangeValue.startDate = ''
this.displayRangeValue.endDate = ''
this.tempRange.startDate = ''
this.tempRange.startTime = ''
this.tempRange.endDate = ''
@@ -796,7 +798,7 @@
} else {
this.$refs.left && this.$refs.left.clearCalender()
this.$refs.right && this.$refs.right.clearCalender()
this.$refs.right && this.$refs.right.next()
this.$refs.right && this.$refs.right.changeMonth('next')
}
if (needEmit) {
this.$emit('change', [])
@@ -804,65 +806,6 @@
this.$emit('update:modelValue', [])
}
}
},
parseDate(date) {
date = this.fixIosDateFormat(date)
const defVal = new Date(date)
const year = defVal.getFullYear()
const month = defVal.getMonth() + 1
const day = defVal.getDate()
const hour = defVal.getHours()
const minute = defVal.getMinutes()
const second = defVal.getSeconds()
const defDate = year + '-' + this.lessTen(month) + '-' + this.lessTen(day)
const defTime = this.lessTen(hour) + ':' + this.lessTen(minute) + (this.hideSecond ? '' : (':' + this
.lessTen(second)))
return {
defDate,
defTime
}
},
lessTen(item) {
return item < 10 ? '0' + item : item
},
//兼容 iOS、safari 日期格式
fixIosDateFormat(value) {
// #ifndef MP
if (typeof value === 'string') {
value = value.replace(/-/g, '/')
}
// #endif
return value
},
checkDate(date){
const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g
return date.match(dateReg)
},
getDateTime(date){
return `${this.getDate(date)} ${this.getTime(date)}`
},
getDate(date){
date = new Date(date)
const year = date.getFullYear()
const month = date.getMonth()+1
const day = date.getDate()
return `${year}-${this.addZero(month)}-${this.addZero(day)}`
},
getTime(date){
date = new Date(date)
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return this.hideSecond ? `${this.addZero(hour)}:${this.addZero(minute)}` : `${this.addZero(hour)}:${this.addZero(minute)}:${this.addZero(second)}`
},
addZero(num) {
if(num < 10){
num = `0${num}`
}
return num
},
getDefaultSecond() {
return this.hideSecond ? '00:00' : '00:00:00'
}
}
}
@@ -890,7 +833,11 @@
padding-left: 3px;
}
.range-separator{
height: 35px;
/* #ifndef MP */
padding: 0 2px;
/* #endif */
line-height: 35px;
}
}
@@ -917,15 +864,18 @@
.uni-date__x-input {
width: auto;
position: relative;
overflow: hidden;
flex: 1;
line-height: 1;
font-size: 14px;
height: 35px;
/* #ifndef MP */
padding-left: 5px;
/* #endif */
position: relative;
flex: 1;
line-height: 35px;
font-size: 14px;
overflow: hidden;
}
.t-c {
.text-center {
text-align: center;
}
@@ -962,7 +912,7 @@
top: 0;
z-index: 999;
border: 1px solid #EBEEF5;
box-shadow: 0px 0px 3px 0px rgba(0, 82, 79, 0.65);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
@@ -972,7 +922,7 @@
top: 0;
z-index: 999;
border: 1px solid #EBEEF5;
box-shadow: 0px 0px 3px 0px rgba(0, 82, 79, 0.65);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
@@ -1023,7 +973,7 @@
opacity: 0.8;
}
.popup-x-footer .confirm {
.popup-x-footer .confirm-text {
margin-left: 20px;
color: $uni-primary;
}

View File

@@ -1,27 +1,24 @@
class Calendar {
constructor({
date,
selected,
startDate,
endDate,
range,
// multipleStatus
} = {}) {
// 当前日期
this.date = this.getDate(new Date()) // 当前初入日期
this.date = this.getDateObj(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 范围开始
// 起始时间
this.startDate = startDate
// 范围结束
// 终止时间
this.endDate = endDate
// 是否范围选择
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
// this._getWeek(this.date.fullDate)
// this.multipleStatus = multipleStatus
this.lastHover = false
}
/**
@@ -29,8 +26,8 @@ class Calendar {
* @param {Object} date
*/
setDate(date) {
this.selectDate = this.getDate(date)
this._getWeek(this.selectDate.fullDate)
const selectDate = this.getDateObj(date)
this.getWeeks(selectDate.fullDate)
}
/**
@@ -44,103 +41,82 @@ class Calendar {
}
}
/**
* 重置开始日期
*/
resetSatrtDate(startDate) {
// 范围开始
setStartDate(startDate) {
this.startDate = startDate
}
/**
* 重置结束日期
*/
resetEndDate(endDate) {
// 范围结束
setEndDate(endDate) {
this.endDate = endDate
}
getPreMonthObj(date){
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth - 1)
const newMonth = date.getMonth()
if(oldMonth !== 0 && newMonth - oldMonth === 0){
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
getNextMonthObj(date){
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth + 1)
const newMonth = date.getMonth()
if(newMonth - oldMonth > 1){
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
/**
* 获取任意时间
* 获取指定格式Date对象
*/
getDate(date, AddDayCount = 0, str = 'day') {
if (!date) {
date = new Date()
}
if (typeof date !== 'object') {
date = date.replace(/-/g, '/')
}
const dd = new Date(date)
switch (str) {
case 'day':
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31 && AddDayCount>0) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
const preMonth = dd.getMonth()
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
const nextMonth = dd.getMonth()
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
}
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
}
}
break
case 'year':
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
break
}
const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号不足10补0
getDateObj(date) {
date = fixIosDateFormat(date)
date = new Date(date)
return {
fullDate: y + '-' + m + '-' + d,
year: y,
month: m,
date: d,
day: dd.getDay()
fullDate: getDate(date),
year: date.getFullYear(),
month: addZero(date.getMonth() + 1),
date: addZero(date.getDate()),
day: date.getDay()
}
}
/**
* 获取上月剩余天数
* 获取上一个月日期集合
*/
_getLastMonthDays(firstDay, full) {
let dateArr = []
for (let i = firstDay; i > 0; i--) {
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
dateArr.push({
date: beforeDate,
month: full.month - 1,
getPreMonthDays(amount, dateObj) {
const result = []
for (let i = amount - 1; i >= 0; i--) {
const month = dateObj.month - 1
result.push({
date: new Date(dateObj.year, month, -i).getDate(),
month,
disable: true
})
}
return dateArr
return result
}
/**
* 获取本月天数
* 获取本月日期集合
*/
_currentMonthDys(dateData, full) {
let dateArr = []
let fullDate = this.date.fullDate
for (let i = 1; i <= dateData; i++) {
let isinfo = false
let nowDate = full.year + '-' + (full.month < 10 ?
full.month : full.month) + '-' + (i < 10 ?
'0' + i : i)
// 是否今天
let isDay = fullDate === nowDate
getCurrentMonthDays(amount, dateObj) {
const result = []
const fullDate = this.date.fullDate
for (let i = 1; i <= amount; i++) {
const currentDate = `${dateObj.year}-${dateObj.month}-${addZero(i)}`
const isToday = fullDate === currentDate
// 获取打点信息
let info = this.selected && this.selected.find((item) => {
if (this.dateEqual(nowDate, item.date)) {
const info = this.selected && this.selected.find((item) => {
if (this.dateEqual(currentDate, item.date)) {
return item
}
})
@@ -149,62 +125,52 @@ class Calendar {
let disableBefore = true
let disableAfter = true
if (this.startDate) {
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
disableBefore = this.dateCompare(this.startDate, nowDate)
disableBefore = dateCompare(this.startDate, currentDate)
}
if (this.endDate) {
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
disableAfter = this.dateCompare(nowDate, this.endDate)
}
let multiples = this.multipleStatus.data
let checked = false
let multiplesStatus = -1
if (this.range) {
if (multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, nowDate)
})
}
if (multiplesStatus !== -1) {
checked = true
}
}
let data = {
fullDate: nowDate,
year: full.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after),
month: full.month,
disable: !(disableBefore && disableAfter),
isDay,
userChecked: false
}
if (info) {
data.extraInfo = info
disableAfter = dateCompare(currentDate, this.endDate)
}
dateArr.push(data)
let multiples = this.multipleStatus.data
let multiplesStatus = -1
if (this.range && multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, currentDate)
})
}
return dateArr
const checked = multiplesStatus !== -1
result.push({
fullDate: currentDate,
year: dateObj.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(currentDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(currentDate, this.multipleStatus.before, this.multipleStatus.after),
month: dateObj.month,
disable: (this.startDate && !dateCompare(this.startDate, currentDate)) || (this.endDate && !dateCompare(currentDate,this.endDate)),
isToday,
userChecked: false,
extraInfo: info
})
}
return result
}
/**
* 获取下月天数
* 获取下一个月日期集合
*/
_getNextMonthDays(surplus, full) {
let dateArr = []
for (let i = 1; i < surplus + 1; i++) {
dateArr.push({
_getNextMonthDays(amount, dateObj) {
const result = []
const month = dateObj.month + 1
for (let i = 1; i <= amount; i++) {
result.push({
date: i,
month: Number(full.month) + 1,
month,
disable: true
})
}
return dateArr
return result
}
/**
@@ -215,58 +181,37 @@ class Calendar {
if (!date) {
date = new Date()
}
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
return dateInfo
}
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
return this.calendar.find(item => item.fullDate === this.getDateObj(date).fullDate)
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
// 计算截止时间
before = new Date(before.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
after = new Date(after.replace('-', '/').replace('-', '/'))
if (before.getTime() - after.getTime() === 0) {
return true
} else {
return false
}
before = new Date(fixIosDateFormat(before))
after = new Date(fixIosDateFormat(after))
return before.valueOf() === after.valueOf()
}
/**
* 比较真实起始日期
*/
isLogicBefore(currentDay, before, after) {
isLogicBefore(currentDate, before, after) {
let logicBefore = before
if (before && after) {
logicBefore = this.dateCompare(before, after) ? before : after
logicBefore = dateCompare(before, after) ? before : after
}
return this.dateEqual(logicBefore, currentDay)
return this.dateEqual(logicBefore, currentDate)
}
isLogicAfter(currentDay, before, after) {
isLogicAfter(currentDate, before, after) {
let logicAfter = after
if (before && after) {
logicAfter = this.dateCompare(before, after) ? after : before
logicAfter = dateCompare(before, after) ? after : before
}
return this.dateEqual(logicAfter, currentDay)
return this.dateEqual(logicAfter, currentDate)
}
/**
@@ -286,7 +231,7 @@ class Calendar {
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
arr.push(this.getDateObj(new Date(parseInt(k))).fullDate)
}
return arr
}
@@ -295,11 +240,12 @@ class Calendar {
* 获取多选状态
*/
setMultiple(fullDate) {
if (!this.range) return
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (before && after) {
if (!this.lastHover) {
this.lastHover = true
@@ -316,7 +262,7 @@ class Calendar {
this.lastHover = false
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
.after);
} else {
@@ -326,32 +272,28 @@ class Calendar {
this.lastHover = true
}
}
this._getWeek(fullDate)
this.getWeeks(fullDate)
}
/**
* 鼠标 hover 更新多选状态
*/
setHoverMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range || this.lastHover) return
if (!this.range) return
if (this.lastHover) return
const { before } = this.multipleStatus
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
this._getWeek(fullDate)
this.getWeeks(fullDate)
}
/**
@@ -361,12 +303,12 @@ class Calendar {
this.multipleStatus.before = before
this.multipleStatus.after = after
if (before && after) {
if (this.dateCompare(before, after)) {
if (dateCompare(before, after)) {
this.multipleStatus.data = this.geDateAll(before, after);
this._getWeek(after)
this.getWeeks(after)
} else {
this.multipleStatus.data = this.geDateAll(after, before);
this._getWeek(before)
this.getWeeks(before)
}
}
}
@@ -375,46 +317,87 @@ class Calendar {
* 获取每周数据
* @param {Object} dateData
*/
_getWeek(dateData) {
getWeeks(dateData) {
const {
fullDate,
year,
month,
date,
day
} = this.getDate(dateData)
let firstDay = new Date(year, month - 1, 1).getDay()
let currentDay = new Date(year, month, 0).getDate()
let dates = {
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
nextMonthDays: [], // 下个月开始几天
weeks: []
} = this.getDateObj(dateData)
const preMonthDayAmount = new Date(year, month - 1, 1).getDay()
const preMonthDays = this.getPreMonthDays(preMonthDayAmount, this.getDateObj(dateData))
const currentMonthDayAmount = new Date(year, month, 0).getDate()
const currentMonthDays = this.getCurrentMonthDays(currentMonthDayAmount, this.getDateObj(dateData))
const nextMonthDayAmount = 42 - preMonthDayAmount - currentMonthDayAmount
const nextMonthDays = this._getNextMonthDays(nextMonthDayAmount, this.getDateObj(dateData))
const calendarDays = [...preMonthDays, ...currentMonthDays, ...nextMonthDays]
const weeks = new Array(6)
for (let i = 0; i < calendarDays.length; i++) {
const index = Math.floor(i / 7)
if(!weeks[index]){
weeks[index] = new Array(7)
}
let canlender = []
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
let weeks = {}
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
for (let i = 0; i < canlender.length; i++) {
if (i % 7 === 0) {
weeks[parseInt(i / 7)] = new Array(7)
weeks[index][i % 7] = calendarDays[i]
}
weeks[parseInt(i / 7)][i % 7] = canlender[i]
}
this.canlender = canlender
this.calendar = calendarDays
this.weeks = weeks
}
//静态方法
// static init(date) {
// if (!this.instance) {
// this.instance = new Calendar(date);
// }
// return this.instance;
// }
}
function getDateTime(date, hideSecond){
return `${getDate(date)} ${getTime(date, hideSecond)}`
}
export default Calendar
function getDate(date) {
date = fixIosDateFormat(date)
date = new Date(date)
const year = date.getFullYear()
const month = date.getMonth()+1
const day = date.getDate()
return `${year}-${addZero(month)}-${addZero(day)}`
}
function getTime(date, hideSecond){
date = fixIosDateFormat(date)
date = new Date(date)
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return hideSecond ? `${addZero(hour)}:${addZero(minute)}` : `${addZero(hour)}:${addZero(minute)}:${addZero(second)}`
}
function addZero(num) {
if(num < 10){
num = `0${num}`
}
return num
}
function getDefaultSecond(hideSecond) {
return hideSecond ? '00:00' : '00:00:00'
}
function dateCompare(startDate, endDate) {
startDate = new Date(fixIosDateFormat(startDate))
endDate = new Date(fixIosDateFormat(endDate))
return startDate <= endDate
}
function checkDate(date){
const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g
return date.match(dateReg)
}
const dateTimeReg = /^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])( [0-5]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])?$/
function fixIosDateFormat(value) {
if (typeof value === 'string' && dateTimeReg.test(value)) {
value = value.replace(/-/g, '/')
}
return value
}
export {Calendar, getDateTime, getDate, getTime, addZero, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat}

View File

@@ -1,7 +1,7 @@
{
"id": "uni-datetime-picker",
"displayName": "uni-datetime-picker 日期选择器",
"version": "2.2.19",
"version": "2.2.23",
"description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择",
"keywords": [
"uni-datetime-picker",

View File

@@ -1,53 +1,97 @@
## 1.1.32022-09-22
- 修复,引入 uni.scss 引入默认主题色报错的问题
## 1.1.22022-09-22
- 增加主题色 primaryColor 配置选项
## 1.1.12022-09-19
- 修复输入后回车change 事件触发两次,[详情](https://ask.dcloud.net.cn/question/152149)
## 1.1.92023-04-11
- 修复 vue3 下 keyboardheightchange 事件报错的bug
## 1.1.82023-03-29
- 优化 trim 属性默认值
## 1.1.72023-03-29
- 新增 cursor-spacing 属性
## 1.1.62023-01-28
- 新增 keyboardheightchange 事件,可监听键盘高度变化
## 1.1.52022-11-29
- 优化 主题样式
## 1.1.42022-10-27
- 修复 props 中背景颜色无默认值的bug
## 1.1.02022-06-30
- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
- 新增 clear 事件,点击右侧叉号图标触发
- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
-
## 1.0.52022-06-07
- 优化 clearable 显示策略
## 1.0.42022-06-07
- 优化 clearable 显示策略
## 1.0.32022-05-20
- 修复 关闭图标某些情况下无法取消的 bug
## 1.0.22022-04-12
- 修复 默认值不生效的 bug
## 1.0.12022-04-02
- 修复 value 不能为 0 的 bug
## 1.0.02021-11-19
- 优化 组件 UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
## 0.1.42021-08-20
- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
## 0.1.32021-08-11
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.1.22021-07-30
- 优化 vue3 下事件警告的问题
## 0.1.1
- 优化 errorMessage 属性支持 Boolean 类型
## 0.1.02021-07-13
- 组件兼容 vue3如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.162021-06-29
- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
## 0.0.152021-06-21
- 修复 passwordIcon 属性拼写错误的 bug
## 0.0.142021-06-18
- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
- 修复 confirmType 属性不生效的问题
## 0.0.132021-06-04
- 修复 disabled 状态可清出内容的 bug
## 0.0.122021-05-12
- 新增 组件示例地址
## 0.0.112021-05-07
- 修复 input-border 属性不生效的问题
## 0.0.102021-04-30
- 修复 ios 遮挡文字、显示一半的问题
## 0.0.92021-02-05
- 调整为 uni_modules 目录规范
- 优化 兼容 nvue 页面

View File

@@ -1,34 +1,74 @@
<template>
<view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="boxStyle">
<view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle">
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc"
@click="onClickIcon('prefix')" size="22"></uni-icons>
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea"
:class="{'input-padding':inputBorder}" :name="name" :value="val" :placeholder="placeholder"
:placeholderStyle="placeholderStyle" :disabled="disabled"
placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused"
:autoHeight="autoHeight" @input="onInput" @blur="_Blur" @focus="_Focus" @confirm="onConfirm"></textarea>
<input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input"
:style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'"
:placeholder="placeholder" :placeholderStyle="placeholderStyle"
placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength"
:focus="focused" :confirmType="confirmType" @focus="_Focus" @blur="_Blur" @input="onInput"
@confirm="onConfirm" />
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')" size="22"></uni-icons>
<textarea
v-if="type === 'textarea'"
class="uni-easyinput__content-textarea"
:class="{ 'input-padding': inputBorder }"
:name="name"
:value="val"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled"
placeholder-class="uni-easyinput__placeholder-class"
:maxlength="inputMaxlength"
:focus="focused"
:autoHeight="autoHeight"
:cursor-spacing="cursorSpacing"
@input="onInput"
@blur="_Blur"
@focus="_Focus"
@confirm="onConfirm"
@keyboardheightchange="onkeyboardheightchange"
></textarea>
<input
v-else
:type="type === 'password' ? 'text' : type"
class="uni-easyinput__content-input"
:style="inputStyle"
:name="name"
:value="val"
:password="!showPassword && type === 'password'"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
placeholder-class="uni-easyinput__placeholder-class"
:disabled="disabled"
:maxlength="inputMaxlength"
:focus="focused"
:confirmType="confirmType"
:cursor-spacing="cursorSpacing"
@focus="_Focus"
@blur="_Blur"
@input="onInput"
@confirm="onConfirm"
@keyboardheightchange="onkeyboardheightchange"
/>
<template v-if="type === 'password' && passwordIcon">
<!-- 开启密码时显示小眼睛 -->
<uni-icons v-if="isVal" class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}"
:type="showPassword?'eye-slash-filled':'eye-filled'" :size="22"
:color="focusShow ? primaryColor :'#c0c4cc'" @click="onEyes">
</uni-icons>
<uni-icons
v-if="isVal"
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
:type="showPassword ? 'eye-slash-filled' : 'eye-filled'"
:size="22"
:color="focusShow ? primaryColor : '#c0c4cc'"
@click="onEyes"
></uni-icons>
</template>
<template v-else-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc"
@click="onClickIcon('suffix')" size="22"></uni-icons>
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')" size="22"></uni-icons>
</template>
<template v-else>
<uni-icons v-if="clearable && isVal && !disabled && type !== 'textarea'" class="content-clear-icon"
:class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize"
:color="msg?'#dd524d':(focusShow? primaryColor :'#c0c4cc')" @click="onClear"></uni-icons>
<uni-icons
v-if="clearable && isVal && !disabled && type !== 'textarea'"
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
type="clear"
:size="clearSize"
:color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'"
@click="onClear"
></uni-icons>
</template>
<slot name="right"></slot>
</view>
@@ -61,6 +101,7 @@
* @property {String} suffixIcon 输入框尾部图标
* @property {String} primaryColor 设置主题色(默认#2979ff
* @property {Boolean} trim 是否自动去除两端的空格
* @property {Boolean} cursorSpacing 指定光标与键盘的距离,单位 px
* @value both 去除两端空格
* @value left 去除左侧空格
* @value right 去除右侧空格
@@ -79,27 +120,27 @@
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/
function obj2strClass(obj) {
let classess = ''
let classess = '';
for (let key in obj) {
const val = obj[key]
const val = obj[key];
if (val) {
classess += `${key} `
classess += `${key} `;
}
}
return classess
return classess;
}
function obj2strStyle(obj) {
let style = ''
let style = '';
for (let key in obj) {
const val = obj[key]
style += `${key}:${val};`
const val = obj[key];
style += `${key}:${val};`;
}
return style
return style;
}
export default {
name: 'uni-easyinput',
emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change'],
emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change', 'keyboardheightchange'],
model: {
prop: 'modelValue',
event: 'update:modelValue'
@@ -115,7 +156,7 @@
formItem: {
from: 'uniFormItem',
default: null
},
}
},
props: {
name: String,
@@ -172,7 +213,11 @@
},
trim: {
type: [Boolean, String],
default: true
default: false
},
cursorSpacing: {
type: Number,
default: 0
},
passwordIcon: {
type: Boolean,
@@ -187,9 +232,10 @@
default() {
return {
color: '#333',
backgroundColor: '#fff',
disableColor: '#F7F6F6',
borderColor: '#e5e5e5'
}
};
}
},
errorMessage: {
@@ -214,12 +260,12 @@
computed: {
// 输入框内是否有值
isVal() {
const val = this.val
const val = this.val;
// fixed by mehaotian 处理值为0的情况字符串0不在处理范围
if (val || val === 0) {
return true
return true;
}
return false
return false;
},
msg() {
@@ -228,7 +274,7 @@
// return this.errorMessage || this.formItem.errMsg;
// }
// TODO 处理头条 formItem 中 errMsg 不更新的问题
return this.localMsg || this.errorMessage
return this.localMsg || this.errorMessage;
},
// 因为uniapp的input组件的maxlength组件必须要数值这里转为数值用户可以传入字符串数值
inputMaxlength() {
@@ -237,7 +283,7 @@
// 处理外层样式的style
boxStyle() {
return `color:${this.inputBorder && this.msg?'#e43d33':this.styles.color};`
return `color:${this.inputBorder && this.msg ? '#e43d33' : this.styles.color};`;
},
// input 内容的类和样式处理
inputContentClass() {
@@ -245,54 +291,55 @@
'is-input-border': this.inputBorder,
'is-input-error-border': this.inputBorder && this.msg,
'is-textarea': this.type === 'textarea',
'is-disabled': this.disabled
})
'is-disabled': this.disabled,
'is-focused': this.focusShow
});
},
inputContentStyle() {
const focusColor = this.focusShow ? this.primaryColor : this.styles.borderColor
const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor
const focusColor = this.focusShow ? this.primaryColor : this.styles.borderColor;
const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor;
return obj2strStyle({
'border-color': borderColor || '#e5e5e5',
'background-color': this.disabled ? this.styles.disableColor : '#fff'
})
'background-color': this.disabled ? this.styles.disableColor : this.styles.backgroundColor
});
},
// input右侧样式
inputStyle() {
const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px'
const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px';
return obj2strStyle({
'padding-right': paddingRight,
'padding-left': this.prefixIcon ? '' : '10px'
})
});
}
},
watch: {
value(newVal) {
this.val = newVal
this.val = newVal;
},
modelValue(newVal) {
this.val = newVal
this.val = newVal;
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus
this.focusShow = this.focus
})
this.focused = this.focus;
this.focusShow = this.focus;
});
}
},
created() {
this.init()
this.init();
// TODO 处理头条vue3 computed 不监听 inject 更改的问题formItem.errMsg
if (this.form && this.formItem) {
this.$watch('formItem.errMsg', (newVal) => {
this.localMsg = newVal
})
this.$watch('formItem.errMsg', newVal => {
this.localMsg = newVal;
});
}
},
mounted() {
this.$nextTick(() => {
this.focused = this.focus
this.focusShow = this.focus
})
this.focused = this.focus;
this.focusShow = this.focus;
});
},
methods: {
/**
@@ -300,11 +347,11 @@
*/
init() {
if (this.value || this.value === 0) {
this.val = this.value
} else if (this.modelValue || this.modelValue === 0) {
this.val = this.modelValue
this.val = this.value;
} else if (this.modelValue || this.modelValue === 0 || this.modelValue === '') {
this.val = this.modelValue;
} else {
this.val = null
this.val = null;
}
},
@@ -313,15 +360,15 @@
* @param {Object} type
*/
onClickIcon(type) {
this.$emit('iconClick', type)
this.$emit('iconClick', type);
},
/**
* 显示隐藏内容,密码框时生效
*/
onEyes() {
this.showPassword = !this.showPassword
this.$emit('eyes', this.showPassword)
this.showPassword = !this.showPassword;
this.$emit('eyes', this.showPassword);
},
/**
@@ -332,19 +379,19 @@
let value = event.detail.value;
// 判断是否去除空格
if (this.trim) {
if (typeof(this.trim) === 'boolean' && this.trim) {
value = this.trimStr(value)
if (typeof this.trim === 'boolean' && this.trim) {
value = this.trimStr(value);
}
if (typeof(this.trim) === 'string') {
value = this.trimStr(value, this.trim)
if (typeof this.trim === 'string') {
value = this.trimStr(value, this.trim);
}
};
if (this.errMsg) this.errMsg = ''
this.val = value
}
if (this.errMsg) this.errMsg = '';
this.val = value;
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
this.$emit('update:modelValue', value);
},
/**
@@ -354,13 +401,13 @@
*/
onFocus() {
this.$nextTick(() => {
this.focused = true
})
this.focused = true;
});
this.$emit('focus', null);
},
_Focus(event) {
this.focusShow = true
this.focusShow = true;
this.$emit('focus', event);
},
@@ -370,24 +417,22 @@
* @param {Object} event
*/
onBlur() {
this.focused = false
this.focused = false;
this.$emit('focus', null);
},
_Blur(event) {
let value = event.detail.value;
this.focusShow = false
this.focusShow = false;
this.$emit('blur', event);
// 根据类型返回值在event中获取的值理论上讲都是string
if (this.isEnter === false) {
this.$emit('change', this.val)
this.$emit('change', this.val);
}
// 失去焦点时参与表单校验
if (this.form && this.formItem) {
const {
validateTrigger
} = this.form
const { validateTrigger } = this.form;
if (validateTrigger === 'blur') {
this.formItem.onFieldChange()
this.formItem.onFieldChange();
}
}
},
@@ -399,10 +444,10 @@
onConfirm(e) {
this.$emit('confirm', this.val);
this.isEnter = true;
this.$emit('change', this.val)
this.$emit('change', this.val);
this.$nextTick(() => {
this.isEnter = false
})
this.isEnter = false;
});
},
/**
@@ -415,9 +460,18 @@
this.$emit('input', '');
// TODO 兼容 vue2
// TODO 兼容 vue3
this.$emit('update:modelValue', '')
this.$emit('update:modelValue', '');
// 点击叉号触发
this.$emit('clear')
this.$emit('clear');
},
/**
* 键盘高度发生变化的时候触发此事件
* 兼容性微信小程序2.7.0+、App 3.1.0+
* @param {Object} event
*/
onkeyboardheightchange(event) {
this.$emit("keyboardheightchange",event);
},
/**
@@ -431,9 +485,9 @@
} else if (pos === 'right') {
return str.trimRight();
} else if (pos === 'start') {
return str.trimStart()
return str.trimStart();
} else if (pos === 'end') {
return str.trimEnd()
return str.trimEnd();
} else if (pos === 'all') {
return str.replace(/\s+/g, '');
} else if (pos === 'none') {
@@ -447,7 +501,7 @@
<style lang="scss">
$uni-error: #e43d33;
$uni-border-1: #DCDFE6 !default;
$uni-border-1: #dcdfe6 !default;
.uni-easyinput {
/* #ifndef APP-NVUE */
@@ -491,7 +545,7 @@
.uni-easyinput__placeholder-class {
color: #999;
font-size: 14px;
font-size: 12px;
// font-weight: 200;
}
@@ -568,11 +622,9 @@
.uni-easyinput__placeholder-class {
color: mix(#fff, $uni-error, 50%);
;
}
}
.uni-easyinput--border {
margin-bottom: 0;
padding: 10px 15px;
@@ -594,12 +646,12 @@
}
.is-disabled {
background-color: #F7F6F6;
color: #D5D5D5;
background-color: #f7f6f6;
color: #d5d5d5;
.uni-easyinput__placeholder-class {
color: #D5D5D5;
font-size: 14px;
color: #d5d5d5;
font-size: 12px;
}
}
</style>

View File

@@ -1,7 +1,7 @@
{
"id": "uni-easyinput",
"displayName": "uni-easyinput 增强输入框",
"version": "1.1.3",
"version": "1.1.9",
"description": "Easyinput 组件是对原生input组件的增强",
"keywords": [
"uni-ui",

View File

@@ -1,3 +1,5 @@
## 1.2.52023-03-29
- 新增 pattern.icon 属性,可自定义图标
## 1.2.42022-09-07
小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false)
## 1.2.32022-09-05

View File

@@ -35,7 +35,7 @@
'uni-fab__circle--rightTop': rightTop,
'uni-fab__content--other-platform': !isAndroidNvue
}" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor, 'bottom': nvueBottom }" @click="_onClick">
<uni-icons class="fab-circle-icon" type="plusempty" :color="styles.iconColor" size="32"
<uni-icons class="fab-circle-icon" :type="styles.icon" :color="styles.iconColor" size="32"
:class="{'uni-fab__plus--active': isShow && content.length > 0}"></uni-icons>
<!-- <view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view> -->
@@ -115,7 +115,8 @@
selectedColor: '#007AFF',
backgroundColor: '#fff',
buttonColor: '#007AFF',
iconColor: '#fff'
iconColor: '#fff',
icon: 'plusempty'
}
}
},
@@ -206,6 +207,9 @@
* 按钮点击事件
*/
_onItemClick(index, item) {
if (!this.isShow) {
return
}
this.$emit('trigger', {
index,
item

View File

@@ -1,7 +1,7 @@
{
"id": "uni-fab",
"displayName": "uni-fab 悬浮按钮",
"version": "1.2.4",
"version": "1.2.5",
"description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。",
"keywords": [
"uni-ui",

View File

@@ -1,4 +1,6 @@
## 1.0.32022-12-21
## 1.0.42023-03-29
- 修复 手动上传删除一个文件后不能再上传的bug
## 1.0.32022-12-19
- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
## 1.0.22022-07-04
- 修复 在uni-forms下样式不生效的bug

View File

@@ -583,7 +583,11 @@
path: v.path,
size: v.size,
fileID:v.fileID,
url: v.url
url: v.url,
// 修改删除一个文件后不能再上传的bug, #694
uuid: v.uuid,
status: v.status,
cloudPath: v.cloudPath
})
})
return newFilesData

View File

@@ -1,7 +1,7 @@
{
"id": "uni-file-picker",
"displayName": "uni-file-picker 文件选择上传",
"version": "1.0.3",
"version": "1.0.4",
"description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
"keywords": [
"uni-ui",
@@ -17,10 +17,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],

View File

@@ -1,3 +1,5 @@
## 1.4.92023-02-10
- 修复 required 参数无法动态绑定
## 1.4.82022-08-23
- 优化 根据 rules 自动添加 required 的问题
## 1.4.72022-08-22

View File

@@ -2,9 +2,9 @@
<view class="uni-forms-item"
:class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']">
<slot name="label">
<view class="uni-forms-item__label" :class="{'no-label':!label && !isRequired}"
<view class="uni-forms-item__label" :class="{'no-label':!label && !required}"
:style="{width:localLabelWidth,justifyContent: localLabelAlign}">
<text v-if="isRequired" class="is-required">*</text>
<text v-if="required" class="is-required">*</text>
<text>{{label}}</text>
</view>
</slot>
@@ -126,7 +126,6 @@
data() {
return {
errMsg: '',
isRequired: false,
userRules: null,
localLabelAlign: 'left',
localLabelWidth: '65px',
@@ -315,7 +314,6 @@
this.localLabelWidth = this._labelWidthUnit(labelWidth)
// 标签位置
this.localLabelPos = this._labelPosition()
this.isRequired = this.required
// 将需要校验的子组件加入form 队列
this.form && type && childrens.push(this)
@@ -351,8 +349,6 @@
this.validator = validator
// 默认值赋予
this.itemSetValue(_getDataValue(this.name, localData))
this.isRequired = this._isRequired()
},
unInit() {
if (this.form) {

View File

@@ -1,7 +1,7 @@
{
"id": "uni-forms",
"displayName": "uni-forms 表单",
"version": "1.4.8",
"version": "1.4.9",
"description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
"keywords": [
"uni-ui",

View File

@@ -1,3 +1,23 @@
## 2.0.102024-06-07
- 优化 uni-app x 中size 属性的类型
## 2.0.92024-01-12
fix: 修复图标大小默认值错误的问题
## 2.0.82023-12-14
- 修复 项目未使用 ts 情况下打包报错的bug
## 2.0.72023-12-14
- 修复 size 属性为 string 时不加单位导致尺寸异常的bug
## 2.0.62023-12-11
- 优化 兼容老版本icon类型如 top bottom 等
## 2.0.52023-12-11
- 优化 兼容老版本icon类型如 top bottom 等
## 2.0.42023-12-06
- 优化 uni-app x 下示例项目图标排序
## 2.0.32023-12-06
- 修复 nvue下引入组件报错的bug
## 2.0.22023-12-05
-优化 size 属性支持单位
## 2.0.12023-12-05
- 新增 uni-app x 支持定义图标
## 1.3.52022-01-24
- 优化 size 属性可以传入不带单位的字符串数值
## 1.3.42022-01-24

View File

@@ -0,0 +1,91 @@
<template>
<text class="uni-icons" :style="styleObj">
<slot>{{unicode}}</slot>
</text>
</template>
<script>
import { fontData, IconsDataItem } from './uniicons_file'
/**
* Icons 图标
* @description 用于展示 icon 图标
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
* @property {Number,String} size 图标大小
* @property {String} type 图标图案,参考示例
* @property {String} color 图标颜色
* @property {String} customPrefix 自定义图标
* @event {Function} click 点击 Icon 触发事件
*/
export default {
name: "uni-icons",
props: {
type: {
type: String,
default: ''
},
color: {
type: String,
default: '#333333'
},
size: {
type: [Number, String],
default: 16
},
fontFamily: {
type: String,
default: ''
}
},
data() {
return {};
},
computed: {
unicode() : string {
let codes = fontData.find((item : IconsDataItem) : boolean => { return item.font_class == this.type })
if (codes !== null) {
return codes.unicode
}
return ''
},
iconSize() : string {
const size = this.size
if (typeof size == 'string') {
const reg = /^[0-9]*$/g
return reg.test(size as string) ? '' + size + 'px' : '' + size;
// return '' + this.size
}
return this.getFontSize(size as number)
},
styleObj() : UTSJSONObject {
if (this.fontFamily !== '') {
return { color: this.color, fontSize: this.iconSize, fontFamily: this.fontFamily }
}
return { color: this.color, fontSize: this.iconSize }
}
},
created() { },
methods: {
/**
* 字体大小
*/
getFontSize(size : number) : string {
return size + 'px';
},
},
}
</script>
<style scoped>
@font-face {
font-family: UniIconsFontFamily;
src: url('./uniicons.ttf');
}
.uni-icons {
font-family: UniIconsFontFamily;
font-size: 18px;
font-style: normal;
color: #333;
}
</style>

View File

@@ -1,18 +1,22 @@
<template>
<!-- #ifdef APP-NVUE -->
<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" @click="_onClick">{{unicode}}</text>
<text :style="styleObj" class="uni-icons" @click="_onClick">{{unicode}}</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick"></text>
<text :style="styleObj" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick">
<slot></slot>
</text>
<!-- #endif -->
</template>
<script>
import icons from './icons.js';
import { fontData } from './uniicons_file_vue.js';
const getVal = (val) => {
const reg = /^[0-9]*$/g
return (typeof val === 'number' || reg.test(val) )? val + 'px' : val;
return (typeof val === 'number' || reg.test(val)) ? val + 'px' : val;
}
// #ifdef APP-NVUE
var domModule = weex.requireModule('dom');
import iconUrl from './uniicons.ttf'
@@ -51,23 +55,33 @@
customPrefix: {
type: String,
default: ''
},
fontFamily: {
type: String,
default: ''
}
},
data() {
return {
icons: icons.glyphs
icons: fontData
}
},
computed: {
unicode() {
let code = this.icons.find(v => v.font_class === this.type)
if (code) {
return unescape(`%u${code.unicode}`)
return code.unicode
}
return ''
},
iconSize() {
return getVal(this.size)
},
styleObj() {
if (this.fontFamily !== '') {
return `color: ${this.color}; font-size: ${this.iconSize}; font-family: ${this.fontFamily};`
}
return `color: ${this.color}; font-size: ${this.iconSize};`
}
},
methods: {
@@ -81,9 +95,10 @@
<style lang="scss">
/* #ifndef APP-NVUE */
@import './uniicons.css';
@font-face {
font-family: uniicons;
src: url('./uniicons.ttf') format('truetype');
src: url('./uniicons.ttf');
}
/* #endif */
@@ -92,5 +107,4 @@
text-decoration: none;
text-align: center;
}
</style>

View File

@@ -1,3 +1,12 @@
.uniui-cart-filled:before {
content: "\e6d0";
}
.uniui-gift-filled:before {
content: "\e6c4";
}
.uniui-color:before {
content: "\e6cf";
}
@@ -58,10 +67,6 @@
content: "\e6c3";
}
.uniui-gift-filled:before {
content: "\e6c4";
}
.uniui-fire-filled:before {
content: "\e6c5";
}
@@ -82,6 +87,18 @@
content: "\e698";
}
.uniui-arrowthinleft:before {
content: "\e6d2";
}
.uniui-arrowthinup:before {
content: "\e6d3";
}
.uniui-arrowthindown:before {
content: "\e6d4";
}
.uniui-back:before {
content: "\e6b9";
}
@@ -94,55 +111,43 @@
content: "\e6bb";
}
.uniui-arrowthinright:before {
content: "\e6bb";
}
.uniui-arrow-left:before {
content: "\e6bc";
}
.uniui-arrowthinleft:before {
content: "\e6bc";
}
.uniui-arrow-up:before {
content: "\e6bd";
}
.uniui-arrowthinup:before {
content: "\e6bd";
}
.uniui-arrow-down:before {
content: "\e6be";
}
.uniui-arrowthindown:before {
content: "\e6be";
.uniui-arrowthinright:before {
content: "\e6d1";
}
.uniui-down:before {
content: "\e6b8";
}
.uniui-bottom:before {
content: "\e6b8";
}
.uniui-arrowdown:before {
content: "\e6b8";
.uniui-arrowright:before {
content: "\e6d5";
}
.uniui-right:before {
content: "\e6b5";
}
.uniui-arrowright:before {
content: "\e6b5";
}
.uniui-top:before {
.uniui-up:before {
content: "\e6b6";
}
.uniui-arrowup:before {
.uniui-top:before {
content: "\e6b6";
}
@@ -150,8 +155,8 @@
content: "\e6b7";
}
.uniui-arrowleft:before {
content: "\e6b7";
.uniui-arrowup:before {
content: "\e6d6";
}
.uniui-eye:before {
@@ -638,10 +643,6 @@
content: "\e627";
}
.uniui-cart-filled:before {
content: "\e629";
}
.uniui-checkbox:before {
content: "\e62b";
}

View File

@@ -0,0 +1,664 @@
export type IconsData = {
id : string
name : string
font_family : string
css_prefix_text : string
description : string
glyphs : Array<IconsDataItem>
}
export type IconsDataItem = {
font_class : string
unicode : string
}
export const fontData = [
{
"font_class": "arrow-down",
"unicode": "\ue6be"
},
{
"font_class": "arrow-left",
"unicode": "\ue6bc"
},
{
"font_class": "arrow-right",
"unicode": "\ue6bb"
},
{
"font_class": "arrow-up",
"unicode": "\ue6bd"
},
{
"font_class": "auth",
"unicode": "\ue6ab"
},
{
"font_class": "auth-filled",
"unicode": "\ue6cc"
},
{
"font_class": "back",
"unicode": "\ue6b9"
},
{
"font_class": "bars",
"unicode": "\ue627"
},
{
"font_class": "calendar",
"unicode": "\ue6a0"
},
{
"font_class": "calendar-filled",
"unicode": "\ue6c0"
},
{
"font_class": "camera",
"unicode": "\ue65a"
},
{
"font_class": "camera-filled",
"unicode": "\ue658"
},
{
"font_class": "cart",
"unicode": "\ue631"
},
{
"font_class": "cart-filled",
"unicode": "\ue6d0"
},
{
"font_class": "chat",
"unicode": "\ue65d"
},
{
"font_class": "chat-filled",
"unicode": "\ue659"
},
{
"font_class": "chatboxes",
"unicode": "\ue696"
},
{
"font_class": "chatboxes-filled",
"unicode": "\ue692"
},
{
"font_class": "chatbubble",
"unicode": "\ue697"
},
{
"font_class": "chatbubble-filled",
"unicode": "\ue694"
},
{
"font_class": "checkbox",
"unicode": "\ue62b"
},
{
"font_class": "checkbox-filled",
"unicode": "\ue62c"
},
{
"font_class": "checkmarkempty",
"unicode": "\ue65c"
},
{
"font_class": "circle",
"unicode": "\ue65b"
},
{
"font_class": "circle-filled",
"unicode": "\ue65e"
},
{
"font_class": "clear",
"unicode": "\ue66d"
},
{
"font_class": "close",
"unicode": "\ue673"
},
{
"font_class": "closeempty",
"unicode": "\ue66c"
},
{
"font_class": "cloud-download",
"unicode": "\ue647"
},
{
"font_class": "cloud-download-filled",
"unicode": "\ue646"
},
{
"font_class": "cloud-upload",
"unicode": "\ue645"
},
{
"font_class": "cloud-upload-filled",
"unicode": "\ue648"
},
{
"font_class": "color",
"unicode": "\ue6cf"
},
{
"font_class": "color-filled",
"unicode": "\ue6c9"
},
{
"font_class": "compose",
"unicode": "\ue67f"
},
{
"font_class": "contact",
"unicode": "\ue693"
},
{
"font_class": "contact-filled",
"unicode": "\ue695"
},
{
"font_class": "down",
"unicode": "\ue6b8"
},
{
"font_class": "bottom",
"unicode": "\ue6b8"
},
{
"font_class": "download",
"unicode": "\ue68d"
},
{
"font_class": "download-filled",
"unicode": "\ue681"
},
{
"font_class": "email",
"unicode": "\ue69e"
},
{
"font_class": "email-filled",
"unicode": "\ue69a"
},
{
"font_class": "eye",
"unicode": "\ue651"
},
{
"font_class": "eye-filled",
"unicode": "\ue66a"
},
{
"font_class": "eye-slash",
"unicode": "\ue6b3"
},
{
"font_class": "eye-slash-filled",
"unicode": "\ue6b4"
},
{
"font_class": "fire",
"unicode": "\ue6a1"
},
{
"font_class": "fire-filled",
"unicode": "\ue6c5"
},
{
"font_class": "flag",
"unicode": "\ue65f"
},
{
"font_class": "flag-filled",
"unicode": "\ue660"
},
{
"font_class": "folder-add",
"unicode": "\ue6a9"
},
{
"font_class": "folder-add-filled",
"unicode": "\ue6c8"
},
{
"font_class": "font",
"unicode": "\ue6a3"
},
{
"font_class": "forward",
"unicode": "\ue6ba"
},
{
"font_class": "gear",
"unicode": "\ue664"
},
{
"font_class": "gear-filled",
"unicode": "\ue661"
},
{
"font_class": "gift",
"unicode": "\ue6a4"
},
{
"font_class": "gift-filled",
"unicode": "\ue6c4"
},
{
"font_class": "hand-down",
"unicode": "\ue63d"
},
{
"font_class": "hand-down-filled",
"unicode": "\ue63c"
},
{
"font_class": "hand-up",
"unicode": "\ue63f"
},
{
"font_class": "hand-up-filled",
"unicode": "\ue63e"
},
{
"font_class": "headphones",
"unicode": "\ue630"
},
{
"font_class": "heart",
"unicode": "\ue639"
},
{
"font_class": "heart-filled",
"unicode": "\ue641"
},
{
"font_class": "help",
"unicode": "\ue679"
},
{
"font_class": "help-filled",
"unicode": "\ue674"
},
{
"font_class": "home",
"unicode": "\ue662"
},
{
"font_class": "home-filled",
"unicode": "\ue663"
},
{
"font_class": "image",
"unicode": "\ue670"
},
{
"font_class": "image-filled",
"unicode": "\ue678"
},
{
"font_class": "images",
"unicode": "\ue650"
},
{
"font_class": "images-filled",
"unicode": "\ue64b"
},
{
"font_class": "info",
"unicode": "\ue669"
},
{
"font_class": "info-filled",
"unicode": "\ue649"
},
{
"font_class": "left",
"unicode": "\ue6b7"
},
{
"font_class": "link",
"unicode": "\ue6a5"
},
{
"font_class": "list",
"unicode": "\ue644"
},
{
"font_class": "location",
"unicode": "\ue6ae"
},
{
"font_class": "location-filled",
"unicode": "\ue6af"
},
{
"font_class": "locked",
"unicode": "\ue66b"
},
{
"font_class": "locked-filled",
"unicode": "\ue668"
},
{
"font_class": "loop",
"unicode": "\ue633"
},
{
"font_class": "mail-open",
"unicode": "\ue643"
},
{
"font_class": "mail-open-filled",
"unicode": "\ue63a"
},
{
"font_class": "map",
"unicode": "\ue667"
},
{
"font_class": "map-filled",
"unicode": "\ue666"
},
{
"font_class": "map-pin",
"unicode": "\ue6ad"
},
{
"font_class": "map-pin-ellipse",
"unicode": "\ue6ac"
},
{
"font_class": "medal",
"unicode": "\ue6a2"
},
{
"font_class": "medal-filled",
"unicode": "\ue6c3"
},
{
"font_class": "mic",
"unicode": "\ue671"
},
{
"font_class": "mic-filled",
"unicode": "\ue677"
},
{
"font_class": "micoff",
"unicode": "\ue67e"
},
{
"font_class": "micoff-filled",
"unicode": "\ue6b0"
},
{
"font_class": "minus",
"unicode": "\ue66f"
},
{
"font_class": "minus-filled",
"unicode": "\ue67d"
},
{
"font_class": "more",
"unicode": "\ue64d"
},
{
"font_class": "more-filled",
"unicode": "\ue64e"
},
{
"font_class": "navigate",
"unicode": "\ue66e"
},
{
"font_class": "navigate-filled",
"unicode": "\ue67a"
},
{
"font_class": "notification",
"unicode": "\ue6a6"
},
{
"font_class": "notification-filled",
"unicode": "\ue6c1"
},
{
"font_class": "paperclip",
"unicode": "\ue652"
},
{
"font_class": "paperplane",
"unicode": "\ue672"
},
{
"font_class": "paperplane-filled",
"unicode": "\ue675"
},
{
"font_class": "person",
"unicode": "\ue699"
},
{
"font_class": "person-filled",
"unicode": "\ue69d"
},
{
"font_class": "personadd",
"unicode": "\ue69f"
},
{
"font_class": "personadd-filled",
"unicode": "\ue698"
},
{
"font_class": "personadd-filled-copy",
"unicode": "\ue6d1"
},
{
"font_class": "phone",
"unicode": "\ue69c"
},
{
"font_class": "phone-filled",
"unicode": "\ue69b"
},
{
"font_class": "plus",
"unicode": "\ue676"
},
{
"font_class": "plus-filled",
"unicode": "\ue6c7"
},
{
"font_class": "plusempty",
"unicode": "\ue67b"
},
{
"font_class": "pulldown",
"unicode": "\ue632"
},
{
"font_class": "pyq",
"unicode": "\ue682"
},
{
"font_class": "qq",
"unicode": "\ue680"
},
{
"font_class": "redo",
"unicode": "\ue64a"
},
{
"font_class": "redo-filled",
"unicode": "\ue655"
},
{
"font_class": "refresh",
"unicode": "\ue657"
},
{
"font_class": "refresh-filled",
"unicode": "\ue656"
},
{
"font_class": "refreshempty",
"unicode": "\ue6bf"
},
{
"font_class": "reload",
"unicode": "\ue6b2"
},
{
"font_class": "right",
"unicode": "\ue6b5"
},
{
"font_class": "scan",
"unicode": "\ue62a"
},
{
"font_class": "search",
"unicode": "\ue654"
},
{
"font_class": "settings",
"unicode": "\ue653"
},
{
"font_class": "settings-filled",
"unicode": "\ue6ce"
},
{
"font_class": "shop",
"unicode": "\ue62f"
},
{
"font_class": "shop-filled",
"unicode": "\ue6cd"
},
{
"font_class": "smallcircle",
"unicode": "\ue67c"
},
{
"font_class": "smallcircle-filled",
"unicode": "\ue665"
},
{
"font_class": "sound",
"unicode": "\ue684"
},
{
"font_class": "sound-filled",
"unicode": "\ue686"
},
{
"font_class": "spinner-cycle",
"unicode": "\ue68a"
},
{
"font_class": "staff",
"unicode": "\ue6a7"
},
{
"font_class": "staff-filled",
"unicode": "\ue6cb"
},
{
"font_class": "star",
"unicode": "\ue688"
},
{
"font_class": "star-filled",
"unicode": "\ue68f"
},
{
"font_class": "starhalf",
"unicode": "\ue683"
},
{
"font_class": "trash",
"unicode": "\ue687"
},
{
"font_class": "trash-filled",
"unicode": "\ue685"
},
{
"font_class": "tune",
"unicode": "\ue6aa"
},
{
"font_class": "tune-filled",
"unicode": "\ue6ca"
},
{
"font_class": "undo",
"unicode": "\ue64f"
},
{
"font_class": "undo-filled",
"unicode": "\ue64c"
},
{
"font_class": "up",
"unicode": "\ue6b6"
},
{
"font_class": "top",
"unicode": "\ue6b6"
},
{
"font_class": "upload",
"unicode": "\ue690"
},
{
"font_class": "upload-filled",
"unicode": "\ue68e"
},
{
"font_class": "videocam",
"unicode": "\ue68c"
},
{
"font_class": "videocam-filled",
"unicode": "\ue689"
},
{
"font_class": "vip",
"unicode": "\ue6a8"
},
{
"font_class": "vip-filled",
"unicode": "\ue6c6"
},
{
"font_class": "wallet",
"unicode": "\ue6b1"
},
{
"font_class": "wallet-filled",
"unicode": "\ue6c2"
},
{
"font_class": "weibo",
"unicode": "\ue68b"
},
{
"font_class": "weixin",
"unicode": "\ue691"
}
] as IconsDataItem[]
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

View File

@@ -0,0 +1,649 @@
export const fontData = [
{
"font_class": "arrow-down",
"unicode": "\ue6be"
},
{
"font_class": "arrow-left",
"unicode": "\ue6bc"
},
{
"font_class": "arrow-right",
"unicode": "\ue6bb"
},
{
"font_class": "arrow-up",
"unicode": "\ue6bd"
},
{
"font_class": "auth",
"unicode": "\ue6ab"
},
{
"font_class": "auth-filled",
"unicode": "\ue6cc"
},
{
"font_class": "back",
"unicode": "\ue6b9"
},
{
"font_class": "bars",
"unicode": "\ue627"
},
{
"font_class": "calendar",
"unicode": "\ue6a0"
},
{
"font_class": "calendar-filled",
"unicode": "\ue6c0"
},
{
"font_class": "camera",
"unicode": "\ue65a"
},
{
"font_class": "camera-filled",
"unicode": "\ue658"
},
{
"font_class": "cart",
"unicode": "\ue631"
},
{
"font_class": "cart-filled",
"unicode": "\ue6d0"
},
{
"font_class": "chat",
"unicode": "\ue65d"
},
{
"font_class": "chat-filled",
"unicode": "\ue659"
},
{
"font_class": "chatboxes",
"unicode": "\ue696"
},
{
"font_class": "chatboxes-filled",
"unicode": "\ue692"
},
{
"font_class": "chatbubble",
"unicode": "\ue697"
},
{
"font_class": "chatbubble-filled",
"unicode": "\ue694"
},
{
"font_class": "checkbox",
"unicode": "\ue62b"
},
{
"font_class": "checkbox-filled",
"unicode": "\ue62c"
},
{
"font_class": "checkmarkempty",
"unicode": "\ue65c"
},
{
"font_class": "circle",
"unicode": "\ue65b"
},
{
"font_class": "circle-filled",
"unicode": "\ue65e"
},
{
"font_class": "clear",
"unicode": "\ue66d"
},
{
"font_class": "close",
"unicode": "\ue673"
},
{
"font_class": "closeempty",
"unicode": "\ue66c"
},
{
"font_class": "cloud-download",
"unicode": "\ue647"
},
{
"font_class": "cloud-download-filled",
"unicode": "\ue646"
},
{
"font_class": "cloud-upload",
"unicode": "\ue645"
},
{
"font_class": "cloud-upload-filled",
"unicode": "\ue648"
},
{
"font_class": "color",
"unicode": "\ue6cf"
},
{
"font_class": "color-filled",
"unicode": "\ue6c9"
},
{
"font_class": "compose",
"unicode": "\ue67f"
},
{
"font_class": "contact",
"unicode": "\ue693"
},
{
"font_class": "contact-filled",
"unicode": "\ue695"
},
{
"font_class": "down",
"unicode": "\ue6b8"
},
{
"font_class": "bottom",
"unicode": "\ue6b8"
},
{
"font_class": "download",
"unicode": "\ue68d"
},
{
"font_class": "download-filled",
"unicode": "\ue681"
},
{
"font_class": "email",
"unicode": "\ue69e"
},
{
"font_class": "email-filled",
"unicode": "\ue69a"
},
{
"font_class": "eye",
"unicode": "\ue651"
},
{
"font_class": "eye-filled",
"unicode": "\ue66a"
},
{
"font_class": "eye-slash",
"unicode": "\ue6b3"
},
{
"font_class": "eye-slash-filled",
"unicode": "\ue6b4"
},
{
"font_class": "fire",
"unicode": "\ue6a1"
},
{
"font_class": "fire-filled",
"unicode": "\ue6c5"
},
{
"font_class": "flag",
"unicode": "\ue65f"
},
{
"font_class": "flag-filled",
"unicode": "\ue660"
},
{
"font_class": "folder-add",
"unicode": "\ue6a9"
},
{
"font_class": "folder-add-filled",
"unicode": "\ue6c8"
},
{
"font_class": "font",
"unicode": "\ue6a3"
},
{
"font_class": "forward",
"unicode": "\ue6ba"
},
{
"font_class": "gear",
"unicode": "\ue664"
},
{
"font_class": "gear-filled",
"unicode": "\ue661"
},
{
"font_class": "gift",
"unicode": "\ue6a4"
},
{
"font_class": "gift-filled",
"unicode": "\ue6c4"
},
{
"font_class": "hand-down",
"unicode": "\ue63d"
},
{
"font_class": "hand-down-filled",
"unicode": "\ue63c"
},
{
"font_class": "hand-up",
"unicode": "\ue63f"
},
{
"font_class": "hand-up-filled",
"unicode": "\ue63e"
},
{
"font_class": "headphones",
"unicode": "\ue630"
},
{
"font_class": "heart",
"unicode": "\ue639"
},
{
"font_class": "heart-filled",
"unicode": "\ue641"
},
{
"font_class": "help",
"unicode": "\ue679"
},
{
"font_class": "help-filled",
"unicode": "\ue674"
},
{
"font_class": "home",
"unicode": "\ue662"
},
{
"font_class": "home-filled",
"unicode": "\ue663"
},
{
"font_class": "image",
"unicode": "\ue670"
},
{
"font_class": "image-filled",
"unicode": "\ue678"
},
{
"font_class": "images",
"unicode": "\ue650"
},
{
"font_class": "images-filled",
"unicode": "\ue64b"
},
{
"font_class": "info",
"unicode": "\ue669"
},
{
"font_class": "info-filled",
"unicode": "\ue649"
},
{
"font_class": "left",
"unicode": "\ue6b7"
},
{
"font_class": "link",
"unicode": "\ue6a5"
},
{
"font_class": "list",
"unicode": "\ue644"
},
{
"font_class": "location",
"unicode": "\ue6ae"
},
{
"font_class": "location-filled",
"unicode": "\ue6af"
},
{
"font_class": "locked",
"unicode": "\ue66b"
},
{
"font_class": "locked-filled",
"unicode": "\ue668"
},
{
"font_class": "loop",
"unicode": "\ue633"
},
{
"font_class": "mail-open",
"unicode": "\ue643"
},
{
"font_class": "mail-open-filled",
"unicode": "\ue63a"
},
{
"font_class": "map",
"unicode": "\ue667"
},
{
"font_class": "map-filled",
"unicode": "\ue666"
},
{
"font_class": "map-pin",
"unicode": "\ue6ad"
},
{
"font_class": "map-pin-ellipse",
"unicode": "\ue6ac"
},
{
"font_class": "medal",
"unicode": "\ue6a2"
},
{
"font_class": "medal-filled",
"unicode": "\ue6c3"
},
{
"font_class": "mic",
"unicode": "\ue671"
},
{
"font_class": "mic-filled",
"unicode": "\ue677"
},
{
"font_class": "micoff",
"unicode": "\ue67e"
},
{
"font_class": "micoff-filled",
"unicode": "\ue6b0"
},
{
"font_class": "minus",
"unicode": "\ue66f"
},
{
"font_class": "minus-filled",
"unicode": "\ue67d"
},
{
"font_class": "more",
"unicode": "\ue64d"
},
{
"font_class": "more-filled",
"unicode": "\ue64e"
},
{
"font_class": "navigate",
"unicode": "\ue66e"
},
{
"font_class": "navigate-filled",
"unicode": "\ue67a"
},
{
"font_class": "notification",
"unicode": "\ue6a6"
},
{
"font_class": "notification-filled",
"unicode": "\ue6c1"
},
{
"font_class": "paperclip",
"unicode": "\ue652"
},
{
"font_class": "paperplane",
"unicode": "\ue672"
},
{
"font_class": "paperplane-filled",
"unicode": "\ue675"
},
{
"font_class": "person",
"unicode": "\ue699"
},
{
"font_class": "person-filled",
"unicode": "\ue69d"
},
{
"font_class": "personadd",
"unicode": "\ue69f"
},
{
"font_class": "personadd-filled",
"unicode": "\ue698"
},
{
"font_class": "personadd-filled-copy",
"unicode": "\ue6d1"
},
{
"font_class": "phone",
"unicode": "\ue69c"
},
{
"font_class": "phone-filled",
"unicode": "\ue69b"
},
{
"font_class": "plus",
"unicode": "\ue676"
},
{
"font_class": "plus-filled",
"unicode": "\ue6c7"
},
{
"font_class": "plusempty",
"unicode": "\ue67b"
},
{
"font_class": "pulldown",
"unicode": "\ue632"
},
{
"font_class": "pyq",
"unicode": "\ue682"
},
{
"font_class": "qq",
"unicode": "\ue680"
},
{
"font_class": "redo",
"unicode": "\ue64a"
},
{
"font_class": "redo-filled",
"unicode": "\ue655"
},
{
"font_class": "refresh",
"unicode": "\ue657"
},
{
"font_class": "refresh-filled",
"unicode": "\ue656"
},
{
"font_class": "refreshempty",
"unicode": "\ue6bf"
},
{
"font_class": "reload",
"unicode": "\ue6b2"
},
{
"font_class": "right",
"unicode": "\ue6b5"
},
{
"font_class": "scan",
"unicode": "\ue62a"
},
{
"font_class": "search",
"unicode": "\ue654"
},
{
"font_class": "settings",
"unicode": "\ue653"
},
{
"font_class": "settings-filled",
"unicode": "\ue6ce"
},
{
"font_class": "shop",
"unicode": "\ue62f"
},
{
"font_class": "shop-filled",
"unicode": "\ue6cd"
},
{
"font_class": "smallcircle",
"unicode": "\ue67c"
},
{
"font_class": "smallcircle-filled",
"unicode": "\ue665"
},
{
"font_class": "sound",
"unicode": "\ue684"
},
{
"font_class": "sound-filled",
"unicode": "\ue686"
},
{
"font_class": "spinner-cycle",
"unicode": "\ue68a"
},
{
"font_class": "staff",
"unicode": "\ue6a7"
},
{
"font_class": "staff-filled",
"unicode": "\ue6cb"
},
{
"font_class": "star",
"unicode": "\ue688"
},
{
"font_class": "star-filled",
"unicode": "\ue68f"
},
{
"font_class": "starhalf",
"unicode": "\ue683"
},
{
"font_class": "trash",
"unicode": "\ue687"
},
{
"font_class": "trash-filled",
"unicode": "\ue685"
},
{
"font_class": "tune",
"unicode": "\ue6aa"
},
{
"font_class": "tune-filled",
"unicode": "\ue6ca"
},
{
"font_class": "undo",
"unicode": "\ue64f"
},
{
"font_class": "undo-filled",
"unicode": "\ue64c"
},
{
"font_class": "up",
"unicode": "\ue6b6"
},
{
"font_class": "top",
"unicode": "\ue6b6"
},
{
"font_class": "upload",
"unicode": "\ue690"
},
{
"font_class": "upload-filled",
"unicode": "\ue68e"
},
{
"font_class": "videocam",
"unicode": "\ue68c"
},
{
"font_class": "videocam-filled",
"unicode": "\ue689"
},
{
"font_class": "vip",
"unicode": "\ue6a8"
},
{
"font_class": "vip-filled",
"unicode": "\ue6c6"
},
{
"font_class": "wallet",
"unicode": "\ue6b1"
},
{
"font_class": "wallet-filled",
"unicode": "\ue6c2"
},
{
"font_class": "weibo",
"unicode": "\ue68b"
},
{
"font_class": "weixin",
"unicode": "\ue691"
}
]
// export const fontData = JSON.parse<IconsDataItem>(fontDataJson)

View File

@@ -1,7 +1,7 @@
{
"id": "uni-icons",
"displayName": "uni-icons 图标",
"version": "1.3.5",
"version": "2.0.10",
"description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
"keywords": [
"uni-ui",
@@ -17,10 +17,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
@@ -45,12 +42,14 @@
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
"app-nvue": "y",
"app-uvue": "y"
},
"H5-mobile": {
"Safari": "y",
@@ -70,11 +69,15 @@
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
"QQ": "y",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",

View File

@@ -1,7 +1,3 @@
## 1.0.182024-07-08
- checkToken时如果传入的token为空则返回uni-id-check-token-failed错误码以便uniIdRouter能正常跳转
## 1.0.172024-04-26
- 兼容uni-app-x对客户端uniPlatform的调整uni-app-x内uniPlatform区分app-android、app-ios
## 1.0.162023-04-25
- 新增maxTokenLength配置用于限制数据库用户记录token数组的最大长度
## 1.0.152023-04-06

View File

@@ -1,7 +1,7 @@
{
"id": "uni-id-common",
"displayName": "uni-id-common",
"version": "1.0.18",
"version": "1.0.16",
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
"keywords": [
"uni-id-common",
@@ -11,14 +11,15 @@
],
"repository": "https://gitcode.net/dcloud/uni-id-common",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": 0
"price": "0.00"
},
"sourcecode": {
"price": 0
"price": "0.00"
}
},
"contact": {
@@ -38,8 +39,7 @@
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
"aliyun": "y"
},
"client": {
"Vue": {

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +1,16 @@
{
"name": "uni-id-common",
"version": "1.0.18",
"version": "1.0.16",
"description": "uni-id token生成、校验、刷新",
"main": "index.js",
"homepage": "https:\/\/uniapp.dcloud.io\/uniCloud\/uni-id-common.html",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html",
"repository": {
"type": "git",
"url": "git+https:\/\/gitee.com\/dcloud\/uni-id-common.git"
"url": "git+https://gitee.com/dcloud/uni-id-common.git"
},
"author": "DCloud",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:..\/..\/..\/..\/..\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
},
"origin-plugin-dev-name": "uni-id-common",
"origin-plugin-version": "1.0.18",
"plugin-dev-name": "uni-id-common",
"plugin-version": "1.0.18"
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}

View File

@@ -1,92 +1,3 @@
## 3.3.292022-10-20
- 使用[uni-open-bridge-common](https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge.html#common)存储用户三方凭证,以便其他服务使用。
## 3.3.282022-07-27
- 修复 app端微信登录返回的accessToken过期时间expired不正确的Bug
## 3.3.272022-07-27
- 短信发送失败、微信登录失败等场景下输出原始错误方便排查错误
## 3.3.262022-07-08
- 兼容配置放在uni-id下的逻辑但是仍推荐使用uni-config-center
## 3.3.252022-06-30
- 修复config文件不合法时未抛出具体错误的Bug
## 3.3.242022-06-28
- 修复3.3.12引出的使用多应用配置时报错的Bug
## 3.3.232022-06-13
- 修复上版本引出的部分依赖未找到的Bug
## 3.3.222022-06-13
- 新增 preferedWebPlatform 配置用于解决HBuilderX 3.4.9版本起web端platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-web-platform)
## 3.3.212022-05-24
- 修复createInstance传入clientInfo无效的Bug
## 3.3.202022-05-19
- 调整以下错误码(账号已注册[uni-id-account-exists]、账号不存在[uni-id-account-not-exists]、账号已绑定[uni-id-account-bound]
## 3.3.192022-05-19
- 修复 addUser 部分情况下会创建出重复账号的Bug
## 3.3.182022-05-12
- 调整绑定、解绑邮箱手机号接口只要传递code参数就进行验证码校验即使传递的值为undefined
## 3.3.172022-05-09
- register_env内增加os_name字段用于区分注册时的客户端系统类型
## 3.3.162022-05-09
- 修复 addUser接口添加的用户无法使用密码登录的Bug [详情](https://ask.dcloud.net.cn/question/144670)
## 3.3.152022-05-08
- 修复config文件语法错误时报`this.t is not a function`的Bug 感谢@寒暄
## 3.3.142022-05-08
- 新增 getWeixinUserInfo接口 用于获取app平台微信登录用户的用户信息 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#get-weixin-user-info)
- 新增 addUser接口 用于手动添加用户 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#add-user)
- 新增 resetPwdBySms接口 用于使用短信验证码重置密码 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#reset-pwd-by-sms)
- 新增 refreshToken接口 用于主动刷新用户token [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#refresh-token)
- 调整 用户注册时记录用户注册环境到 register_env 字段 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#user-table)
- 调整 用户注册时将注册 ip 移至 register_env 内
## 3.3.132022-03-04
- createInstance方法支持传递clientInfo [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#create-instance)
- 修复`this.t is not a function`报错
## 3.3.122022-01-15
- 新增 preferedAppPlatform 配置用于解决uni-app vue2版本vue3版本获取platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-app-platform)
- 修复 checkToken 未返回自定义token内容的Bug
## 3.3.112022-01-11
- 修复用户名密码登录时多个应用出现重复用户名登录报错的Bug
## 3.3.102022-01-07
- 新增 自定义国际化语言支持 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=custom-i8n)
- 修复 一键登录时未校验重复手机号是否已验证的Bug
- 修复 Apple登录时用户邮箱为空时报错的Bug
- 修复 登录接口未传username时错误提示不正确的Bug
## 3.3.92021-11-09
- 去除重复的context.xxx未找到的提示语
## 3.3.82021-10-28
- 新增 用户账户封禁接口 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=ban-account)
- 新增 用户账户注销接口 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=close-account)
- 修复 未传appid时用户重复注册的Bug
## 3.3.72021-10-08
- 移除部分接口的废弃提示
## 3.3.62021-09-08
- 修复 邀请码可能重复的Bug
## 3.3.52021-08-10
- 修复版本号错误
## 3.3.42021-08-10
- 微信、QQ、支付宝登录新增type参数用于指定当前是登录还是注册
## 3.3.32021-08-04
- 修复使用数组形式的配置文件报错的Bug
## 3.3.22021-08-03
- 修复上3.3.0版本引出的createInstance接口传入配置不生效的Bug 感谢[hmh](https://gitee.com/hmh)
## 3.3.12021-07-30
- 修复 将设置用户允许登录的应用列表时传入空数组报错的Bug
## 3.3.02021-07-30
- 新增 不同端应用配置隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
- 新增 不同端用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
+ 此版本升级需要开发者处理一下用户数据,请参考 [补齐用户dcloud_appid字段](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=makeup-dcloud-appid)
- 新增 QQ登录、注册相关功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=qq)
- 调整 不再支持绑定手机、邮箱时不填验证码直接绑定
## 3.2.12021-07-09
- 撤销3.2.0版本所做的调整
## 3.2.02021-07-09
- 【重要】支持不同端(管理端、用户端等)用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
- 支持不同端(管理端、用户端等)配置文件隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
## 3.1.32021-07-08
- 移除插件内误传的node_modules
## 3.1.22021-07-08
- 修复 微信小程序绑定微信账号时报错的Bug
## 3.1.12021-07-01
- 使用新的错误码规范,兼容旧版 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=errcode)
- 修复微信登录、绑定时未返回用户accessToken的Bug
## 3.1.02021-04-19
- 增加对用户名、邮箱、密码字段的两端去空格
- 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive)

View File

@@ -1,7 +1,7 @@
{
"id": "uni-id",
"displayName": "uni-id",
"version": "3.3.29",
"version": "3.1.0",
"description": "简单、统一、可扩展的用户中心",
"keywords": [
"uniid",
@@ -15,6 +15,10 @@
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"云函数模板"
],
"sale": {
"regular": {
"price": "0.00"
@@ -31,11 +35,10 @@
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
"npmurl": ""
},
"uni_modules": {
"dependencies": ["uni-config-center", "uni-open-bridge-common"],
"dependencies": ["uni-config-center"],
"encrypt": [],
"platforms": {
"cloud": {
@@ -65,19 +68,11 @@
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "uni-id",
"version": "3.3.29",
"version": "3.1.0",
"description": "uni-id for uniCloud",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id",
@@ -11,7 +11,6 @@
"author": "",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-open-bridge-common": "file:../../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common"
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}

View File

@@ -1,3 +1,11 @@
## 1.8.32023-04-17
- 修复 uni-popup 重复打开时的 bug
## 1.8.22023-02-02
- uni-popup-dialog 组件新增 inputType 属性
## 1.8.12022-12-01
- 修复 nvue 下 v-show 报错
## 1.8.02022-11-29
- 优化 主题样式
## 1.7.92022-04-02
- 修复 弹出层内部无法滚动的bug
## 1.7.82022-03-28

View File

@@ -10,7 +10,7 @@
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholderText" :focus="focus" >
<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
</slot>
</view>
<view class="uni-dialog-button-group">
@@ -57,6 +57,10 @@
mixins: [popup],
emits:['confirm','close'],
props: {
inputType:{
type: String,
default: 'text'
},
value: {
type: [String, Number],
default: ''
@@ -229,7 +233,7 @@
}
.uni-border-left {
border-left-color: #f5f5f5;
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}

View File

@@ -40,25 +40,25 @@
data() {
return {
bottomData: [{
text: '微信',
text: '微信消息',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
text: '朋友圈',
icon: 'http://ehh-public-01.oss-cn-beijing.aliyuncs.com/image/20240626131115.png',
name: 'wx'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: 'QQ',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
// name: 'qq'
// },
// {
// text: '新浪',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
// name: 'sina'
// },
// {
// text: '百度',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',

View File

@@ -269,8 +269,7 @@
open(direction) {
// fix by mehaotian 处理快速打开关闭的情况
if (this.showPopup) {
clearTimeout(this.timer)
this.showPopup = false
return
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {

View File

@@ -1,7 +1,7 @@
{
"id": "uni-popup",
"displayName": "uni-popup 弹出层",
"version": "1.7.9",
"version": "1.8.3",
"description": " Popup 组件,提供常用的弹层",
"keywords": [
"uni-ui",
@@ -18,10 +18,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -38,7 +34,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [

View File

@@ -0,0 +1,95 @@
{
"name" : "",
"appid" : "",
"description": "应用描述",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App */
"app-plus": {
"usingComponents": true,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"OAuth": {},
"Payment": {},
"Push": {},
"Share": {},
"Speech": {},
"VideoPlayer": {}
},
/* */
"distribute": {
/* android */
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
]
},
/* ios */
"ios": {
"UIBackgroundModes": ["audio"]
},
/* SDK */
"sdkConfigs": {
"speech": {
"ifly": {}
}
},
"orientation": ["portrait-primary"]
}
},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"h5": {
"template": "template.h5.html",
"router": {
"mode": "history",
"base": "/h5/"
}
}
}

View File

@@ -1,3 +1,5 @@
## 1.2.42023-05-09
- 修复 i18n 国际化不正确的 Bug
## 1.2.32022-05-24
- 新增 readonly 属性,组件只读
## 1.2.22022-05-06

View File

@@ -1,4 +1,4 @@
{
"uni-search-bar.cancel": "cancel",
"uni-search-bar.cancel": "取消",
"uni-search-bar.placeholder": "请输入搜索内容"
}

View File

@@ -1,4 +1,4 @@
{
"uni-search-bar.cancel": "cancel",
"uni-search-bar.cancel": "取消",
"uni-search-bar.placeholder": "請輸入搜索內容"
}

View File

@@ -80,7 +80,7 @@
},
cancelText: {
type: String,
default: '取消'
default: ""
},
bgColor: {
type: String,

View File

@@ -1,7 +1,7 @@
{
"id": "uni-search-bar",
"displayName": "uni-search-bar 搜索栏",
"version": "1.2.3",
"version": "1.2.4",
"description": "搜索栏组件,通常用于搜索商品、文章等",
"keywords": [
"uni-ui",
@@ -17,10 +17,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [

View File

@@ -1,3 +1,7 @@
## 1.2.32023-03-28
- 修复 在vue3模式下可能会出现错误的问题
## 1.2.22022-11-29
- 优化 主题样式
## 1.2.12022-06-06
- 修复 微信小程序存在无使用组件的问题
## 1.2.02021-11-19

View File

@@ -3,9 +3,9 @@
<!-- #ifdef H5 -->
<table class="uni-table" border="0" cellpadding="0" cellspacing="0" :class="{ 'table--stripe': stripe }" :style="{ 'min-width': minWidth + 'px' }">
<slot></slot>
<view v-if="noData" class="uni-table-loading">
<view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
</view>
<tr v-if="noData" class="uni-table-loading">
<td class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</td>
</tr>
<view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
</table>
<!-- #endif -->
@@ -125,7 +125,7 @@ export default {
} else {
startIndex = theadChildren.rowspan - 1
}
let isHaveData = this.data && this.data.length.length > 0
let isHaveData = this.data && this.data.length > 0
theadChildren.checked = true
theadChildren.indeterminate = false
this.trChildren.forEach((item, index) => {

View File

@@ -78,7 +78,7 @@
padding: 8px 10px;
font-size: 14px;
border-bottom: 1px $border-color solid;
font-weight: 600;
font-weight: 400;
color: #606266;
line-height: 23px;
box-sizing: border-box;

View File

@@ -112,6 +112,12 @@
value: 'value'
}
}
},
filterDefaultValue: {
type: [Array,String],
default () {
return ""
}
}
},
computed: {
@@ -157,7 +163,7 @@
enabled: true,
isOpened: false,
dataList: [],
filterValue: '',
filterValue: this.filterDefaultValue,
checkedValues: [],
gtValue: '',
ltValue: '',
@@ -286,6 +292,8 @@
</script>
<style lang="scss">
$uni-primary: #1890ff !default;
.flex-r {
display: flex;
flex-direction: row;
@@ -315,8 +323,8 @@
}
.icon-select.active {
background-color: #1890ff;
border-top-color: #1890ff;
background-color: $uni-primary;
border-top-color: $uni-primary;
}
.icon-search {
@@ -343,11 +351,11 @@
}
.icon-search.active .icon-search-0 {
border-color: #1890ff;
border-color: $uni-primary;
}
.icon-search.active .icon-search-1 {
background-color: #1890ff;
background-color: $uni-primary;
}
.icon-calendar {
@@ -387,14 +395,14 @@
}
.icon-calendar.active {
color: #1890ff;
color: $uni-primary;
}
.icon-calendar.active .icon-calendar-0,
.icon-calendar.active .icon-calendar-1,
.icon-calendar.active .icon-calendar-0:before,
.icon-calendar.active .icon-calendar-0:after {
background-color: #1890ff;
background-color: $uni-primary;
}
.uni-filter-dropdown {
@@ -440,7 +448,7 @@
}
.list-item:hover {
background-color: #f5f5f5;
background-color: #f0f0f0;
}
.check {
@@ -453,7 +461,7 @@
.search-input {
font-size: 12px;
border: 1px solid #f5f5f5;
border: 1px solid #f0f0f0;
border-radius: 3px;
padding: 2px 5px;
min-width: 150px;
@@ -467,7 +475,7 @@
.input {
font-size: 12px;
border: 1px solid #f5f5f5;
border: 1px solid #f0f0f0;
border-radius: 3px;
margin: 10px;
padding: 2px 5px;
@@ -497,7 +505,7 @@
}
.btn-submit {
background-color: #1890ff;
background-color: $uni-primary;
color: #ffffff;
}
</style>

View File

@@ -9,7 +9,7 @@
<text class="arrow down" :class="{ active: descending }" @click.stop="descendingFn"></text>
</view>
</view>
<dropdown v-if="filterType || filterData.length" :filterData="filterData" :filterType="filterType" @change="ondropdown"></dropdown>
<dropdown v-if="filterType || filterData.length" :filterDefaultValue="filterDefaultValue" :filterData="filterData" :filterType="filterType" @change="ondropdown"></dropdown>
</view>
</th>
<!-- #endif -->
@@ -79,6 +79,12 @@ export default {
default () {
return []
}
},
filterDefaultValue: {
type: [Array,String],
default () {
return ""
}
}
},
data() {
@@ -200,6 +206,7 @@ export default {
<style lang="scss">
$border-color: #ebeef5;
$uni-primary: #007aff !default;
.uni-table-th {
padding: 12px 10px;
@@ -254,7 +261,7 @@ $border-color: #ebeef5;
}
&.active {
::after {
background-color: #007aff;
background-color: $uni-primary;
}
}
}
@@ -271,7 +278,7 @@ $border-color: #ebeef5;
}
&.active {
::after {
background-color: #007aff;
background-color: $uni-primary;
}
}
}

View File

@@ -77,7 +77,7 @@
</script>
<style lang="scss">
$checked-color: #007aff;
$uni-primary: #007aff !default;
$border-color: #DCDFE6;
$disable:0.4;
@@ -125,8 +125,8 @@
}
&.checkbox--indeterminate {
border-color: $checked-color;
background-color: $checked-color;
border-color: $uni-primary;
background-color: $uni-primary;
.checkbox__inner-icon {
position: absolute;
@@ -147,7 +147,7 @@
}
}
&:hover{
border-color: $checked-color;
border-color: $uni-primary;
}
// 禁用
&.is-disable {
@@ -160,8 +160,8 @@
// 选中
&.is-checked {
border-color: $checked-color;
background-color: $checked-color;
border-color: $uni-primary;
background-color: $uni-primary;
.checkbox__inner-icon {
opacity: 1;

View File

@@ -1,7 +1,7 @@
{
"id": "uni-table",
"displayName": "uni-table 表格",
"version": "1.2.1",
"version": "1.2.3",
"description": "表格组件,多用于展示多条结构类似的数据,如",
"keywords": [
"uni-ui",
@@ -17,10 +17,6 @@
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss","uni-datetime-picker"],

View File

@@ -1,3 +1,5 @@
## 1.3.22023-05-04
- 修复 NVUE 平台报错的问题
## 1.3.12021-11-23
- 修复 init 方法初始化问题
## 1.3.02021-11-19

View File

@@ -10,7 +10,10 @@ const nvueAnimation = uni.requireNativePlugin('animation')
class MPAnimation {
constructor(options, _this) {
this.options = options
this.animation = uni.createAnimation(options)
// 在iOS10+QQ小程序平台下传给原生的对象一定是个普通对象而不是Proxy对象否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
this.currentStepAnimates = {}
this.next = 0
this.$ = _this

Some files were not shown because too many files have changed in this diff Show More