项目初始化

This commit is contained in:
jerry
2025-01-21 01:46:34 +08:00
parent 364021b042
commit 48153e7761
962 changed files with 172070 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
<!-- z-paging自定义的点击返回顶部view -->
<template>
<view class="back-to-top">
<view class="container">
<u-icon name="fanhuidingbu" color="#555666" size="50" custom-prefix="iconfont"></u-icon>
</view>
</view>
</template>
<script>
export default {
props: {
// 当前加载了几页
current: {
type: Number,
default: function() {
return 0;
},
},
// 一共有多少页可以加载
total: {
type: Number,
default: function() {
return 0;
},
},
},
data() {
return {
// 0 返回返回顶部图片 1显示current/total
type: 0,
// 当前定时器
timeout: null
};
},
methods: {
}
}
</script>
<style scoped>
.back-to-top {
/* 这里请设置100%填充满返回顶部按钮 */
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: white;
border-radius: 50%;
background-color: white;
box-shadow: 0 0 20rpx #cccccc;
background-color: white;
}
.container {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
.count-text {
font-size: 24rpx;
color: #555555;
font-weight: bold;
}
.line {
width: 55rpx;
height: 1rpx;
background-color: #dedede;
}
.back-img {
width: 50rpx;
height: 50rpx;
}
</style>

View File

@@ -0,0 +1,831 @@
<template>
<view class="htz-image-upload-list">
<view class="htz-image-upload-Item" v-for="(item,index) in uploadLists" :key="index">
<view class="htz-image-upload-Item-video" v-if="isVideo(item)">
<!-- #ifndef APP-PLUS -->
<video :disabled="false" :controls="false" :src="getFileUrl(item)">
<cover-view class="htz-image-upload-Item-video-fixed" @tap.stop="previewVideo(getFileUrl(item))">
</cover-view>
<cover-view class="htz-image-upload-Item-del-cover" v-if="remove && previewVideoSrc==''" @tap.stop="imgDel(index)">×</cover-view>
</video>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view class="htz-image-upload-Item-video-fixed" @tap.stop="previewVideo(getFileUrl(item))"></view>
<image v-if="dataType==1 && item.cover" class="htz-image-upload-Item-video-app-poster" mode="widthFix" :src="item.cover"></image>
<image v-else class="htz-image-upload-Item-video-app-poster" mode="widthFix" :src="appVideoPoster"></image>
<!-- #endif -->
</view>
<image v-else :src="getFileUrl(item)" @tap.stop="imgPreview(getFileUrl(item))"></image>
<!-- #ifdef APP-PLUS -->
<view class="htz-image-upload-Item-del" v-if="remove" @tap.stop="imgDel(index)">×</view>
<!-- #endif -->
</view>
<view class="htz-image-upload-Item htz-image-upload-Item-add" v-if="uploadLists.length<max && add" @click="chooseFile">
+
</view>
<view class="preview-full" v-if="previewVideoSrc!=''">
<video :autoplay="true" :src="previewVideoSrc" :show-fullscreen-btn="false">
<cover-view class="preview-full-close" @click="previewVideoClose"> ×
</cover-view>
</video>
</view>
<!-- -->
</view>
</template>
<style>
.ceshi {
width: 100%;
height: 100%;
position: relative;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: #FFFFFF;
color: #2C405A;
opacity: 0.5;
z-index: 100;
}
</style>
<script>
export default {
name: 'htz-image-upload',
props: {
max: { //展示图片最大值
type: Number,
default: 1,
},
chooseNum: { //选择图片数
type: Number,
default: 9,
},
name: { //发到后台的文件参数名
type: String,
default: 'file',
},
dataType: { //v-model的数据结构类型
type: Number,
default: 0, // 0: ['http://xxxx.jpg','http://xxxx.jpg'] 1:[{type:0,url:'http://xxxx.jpg'}] type 0 图片 1 视频 url 文件地址 此类型是为了给没有文件后缀的状况使用的
},
remove: { //是否展示删除按钮
type: Boolean,
default: true,
},
add: { //是否展示添加按钮
type: Boolean,
default: true,
},
disabled: { //是否禁用
type: Boolean,
default: false,
},
sourceType: { //选择照片来源 【psH5就别费劲了设置了也没用。不是我说的官方文档就这样
type: Array,
default: () => ['album', 'camera'],
},
action: { //上传地址 如需使用uniCloud服务设置为uniCloud即可
type: String,
default: '',
},
headers: { //上传的请求头部
type: Object,
default: () => {},
},
formData: { //HTTP 请求中其他额外的 form data
type: Object,
default: () => {},
},
compress: { //是否需要压缩
type: Boolean,
default: true,
},
quality: { //压缩质量范围0100
type: Number,
default: 80,
},
// #ifndef VUE3
value: { //受控图片列表
type: Array,
default: () => [],
},
// #endif
// #ifdef VUE3
modelValue: { //受控图片列表
type: Array,
default: () => [],
},
// #endif
uploadSuccess: {
default: (res) => {
return {
success: false,
url: ''
}
},
},
mediaType: { //文件类型 image/video/all
type: String,
default: 'image',
},
maxDuration: { //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。 (只针对拍摄视频有用)
type: Number,
default: 60,
},
camera: { //'front'、'back',默认'back'(只针对拍摄视频有用)
type: String,
default: 'back',
},
appVideoPoster: { //app端视频展示封面 只对app有效
type: String,
default: '/static/htz-image-upload/play.png',
},
},
data() {
return {
uploadLists: [],
mediaTypeData: ['image', 'video', 'all'],
previewVideoSrc: '',
}
},
mounted: function() {
this.$nextTick(function() {
// #ifndef VUE3
this.uploadLists = this.value;
// #endif
// #ifdef VUE3
this.uploadLists = this.modelValue;
// #endif
if (this.mediaTypeData.indexOf(this.mediaType) == -1) {
uni.showModal({
title: '提示',
content: 'mediaType参数不正确',
showCancel: false,
success: function(res) {
if (res.confirm) {
//console.log('用户点击确定');
} else if (res.cancel) {
//console.log('用户点击取消');
}
}
});
}
});
},
watch: {
// #ifndef VUE3
value(val, oldVal) {
//console.log('value',val, oldVal)
this.uploadLists = val;
},
// #endif
// #ifdef VUE3
modelValue(val, oldVal) {
//console.log('value',val, oldVal)
this.uploadLists = val;
},
// #endif
},
methods: {
isVideo(item) {
let isPass = false
if ((!/.(gif|jpg|jpeg|png|gif|jpg|png)$/i.test(item) && this.dataType == 0) || (this.dataType == 1 && item
.type == 1)) {
isPass = true
}
return isPass
},
getFileUrl(item) {
var url = item;
if (this.dataType == 1) {
url = item.url
}
//console.log('url', url)
return url
},
previewVideo(src) {
this.previewVideoSrc = src;
// this.previewVideoSrc =
// 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-fbd63a76-dc76-485c-b711-f79f2986daeb/ba804d82-860b-4d1a-a706-5a4c8ce137c3.mp4'
},
previewVideoClose() {
this.previewVideoSrc = ''
//console.log('previewVideoClose', this.previewVideoSrc)
},
imgDel(index) {
// this.uploadLists.splice(index, 1)
// this.$emit("input", this.uploadLists);
// this.$emit("imgDelete", this.uploadLists);
let delUrl = this.uploadLists[index]
this.uploadLists.splice(index, 1)
// #ifndef VUE3
this.$emit("input", this.uploadLists);
// #endif
// #ifdef VUE3
this.$emit("update:modelValue", this.uploadLists);
// #endif
this.$emit("imgDelete", {
del: delUrl,
tempFilePaths: this.uploadLists
});
},
imgPreview(index) {
var imgData = []
this.uploadLists.forEach(item => {
if (!this.isVideo(item)) {
imgData.push(this.getFileUrl(item))
}
})
//console.log('imgPreview', imgData)
uni.previewImage({
urls: imgData,
current: index,
loop: true,
});
},
chooseFile() {
if (this.disabled) {
return false;
}
switch (this.mediaTypeData.indexOf(this.mediaType)) {
case 1: //视频
this.videoAdd();
break;
case 2: //全部
uni.showActionSheet({
itemList: ['相册', '视频'],
success: (res) => {
if (res.tapIndex == 1) {
this.videoAdd();
} else if (res.tapIndex == 0) {
this.imgAdd();
}
},
fail: (res) => {
console.log(res.errMsg);
}
});
break;
default: //图片
this.imgAdd();
break;
}
//if(this.mediaType=='image'){
},
videoAdd() {
//console.log('videoAdd')
let nowNum = Math.abs(this.uploadLists.length - this.max);
let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
uni.chooseVideo({
compressed: this.compress,
sourceType: this.sourceType,
camera: this.camera,
maxDuration: this.maxDuration,
success: (res) => {
// console.log('videoAdd', res)
// console.log(res.tempFilePath)
this.chooseSuccessMethod([res.tempFilePath], 1)
//this.imgUpload([res.tempFilePath]);
//console.log('tempFiles', res)
// if (this.action == '') { //未配置上传路径
// this.$emit("chooseSuccess", res.tempFilePaths);
// } else {
// if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M进行压缩上传
// this.imgCompress(res.tempFilePaths);
// } else {
// this.imgUpload(res.tempFilePaths);
// }
// }
}
});
},
imgAdd() {
//console.log('imgAdd')
let nowNum = Math.abs(this.uploadLists.length - this.max);
let thisNum = (this.chooseNum > nowNum ? nowNum : this.chooseNum) //可选数量
//console.log('nowNum', nowNum)
//console.log('thisNum', thisNum)
// #ifdef APP-PLUS
if (this.sourceType.length > 1) {
uni.showActionSheet({
itemList: ['拍摄', '从手机相册选择'],
success: (res) => {
if (res.tapIndex == 1) {
this.appGallery(thisNum);
} else if (res.tapIndex == 0) {
this.appCamera();
}
},
fail: (res) => {
console.log(res.errMsg);
}
});
}
if (this.sourceType.length == 1 && this.sourceType.indexOf('album') > -1) {
this.appGallery(thisNum);
}
if (this.sourceType.length == 1 && this.sourceType.indexOf('camera') > -1) {
this.appCamera();
}
// #endif
//#ifndef APP-PLUS
uni.chooseImage({
count: thisNum,
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: this.sourceType,
success: (res) => {
this.chooseSuccessMethod(res.tempFilePaths, 0)
//console.log('tempFiles', res)
// if (this.action == '') { //未配置上传路径
// this.$emit("chooseSuccess", res.tempFilePaths);
// } else {
// if (this.compress && (res.tempFiles[0].size / 1024 > 1025)) { //设置了需要压缩 并且 文件大于1M进行压缩上传
// this.imgCompress(res.tempFilePaths);
// } else {
// this.imgUpload(res.tempFilePaths);
// }
// }
}
});
// #endif
},
appCamera() {
var cmr = plus.camera.getCamera();
var res = cmr.supportedImageResolutions[0];
var fmt = cmr.supportedImageFormats[0];
//console.log("Resolution: " + res + ", Format: " + fmt);
cmr.captureImage((path) => {
//alert("Capture image success: " + path);
this.chooseSuccessMethod([path], 0)
},
(error) => {
//alert("Capture image failed: " + error.message);
console.log("Capture image failed: " + error.message)
}, {
resolution: res,
format: fmt
}
);
},
appGallery(maxNum) {
plus.gallery.pick((res) => {
this.chooseSuccessMethod(res.files, 0)
}, function(e) {
//console.log("取消选择图片");
}, {
filter: "image",
multiple: true,
maximum: maxNum
});
},
chooseSuccessMethod(filePaths, type) {
if (this.action == '') { //未配置上传路径
this.$emit("chooseSuccess", filePaths, type); //filePaths 路径 type 0 为图片 1为视频
} else {
if (type == 1) {
this.imgUpload(filePaths, type);
} else {
if (this.compress) { //设置了需要压缩
this.imgCompress(filePaths, type);
} else {
this.imgUpload(filePaths, type);
}
}
}
},
imgCompress(tempFilePaths, type) { //type 0 为图片 1为视频
uni.showLoading({
title: '压缩中...'
});
let compressImgs = [];
let results = [];
tempFilePaths.forEach((item, index) => {
compressImgs.push(new Promise((resolve, reject) => {
// #ifndef H5
uni.compressImage({
src: item,
quality: this.quality,
success: res => {
//console.log('compressImage', res.tempFilePath)
results.push(res.tempFilePath);
resolve(res.tempFilePath);
},
fail: (err) => {
//console.log(err.errMsg);
reject(err);
},
complete: () => {
//uni.hideLoading();
}
})
// #endif
// #ifdef H5
this.canvasDataURL(item, {
quality: this.quality / 100
}, (base64Codes) => {
//this.imgUpload(base64Codes);
results.push(base64Codes);
resolve(base64Codes);
})
// #endif
}))
})
Promise.all(compressImgs) //执行所有需请求的接口
.then((results) => {
uni.hideLoading();
//console.log('imgCompress', type)
this.imgUpload(results, type);
})
.catch((res, object) => {
uni.hideLoading();
});
},
imgUpload(tempFilePaths, type) { //type 0 为图片 1为视频
// if (this.action == '') {
// uni.showToast({
// title: '未配置上传地址',
// icon: 'none',
// duration: 2000
// });
// return false;
// }
if (this.action == 'uniCloud') {
this.uniCloudUpload(tempFilePaths, type)
return
}
uni.showLoading({
title: '上传中'
});
//console.log('imgUpload', tempFilePaths)
let uploadImgs = [];
tempFilePaths.forEach((item, index) => {
uploadImgs.push(new Promise((resolve, reject) => {
//console.log(index, item)
const uploadTask = uni.uploadFile({
url: this.action, //仅为示例,非真实的接口地址
filePath: item,
name: this.name,
fileType: 'image',
formData: this.formData,
header: this.headers,
success: (uploadFileRes) => {
//uni.hideLoading();
//console.log(typeof this.uploadSuccess)
//console.log('')
uploadFileRes.fileType = type
if (typeof this.uploadSuccess == 'function') {
let thisUploadSuccess = this.uploadSuccess(
uploadFileRes)
if (thisUploadSuccess.success) {
let keyName = '';
// #ifndef VUE3
keyName = 'value'
// #endif
// #ifdef VUE3
keyName = 'modelValue'
// #endif
if (this.dataType == 0) {
this[keyName].push(thisUploadSuccess.url)
} else {
this[keyName].push({
type: type,
url: thisUploadSuccess.url,
...thisUploadSuccess
})
}
//this.$emit("input", this.uploadLists);
// #ifndef VUE3
this.$emit("input", this.uploadLists);
// #endif
// #ifdef VUE3
this.$emit("update:modelValue", this.uploadLists);
// #endif
}
}
resolve(uploadFileRes);
this.$emit("uploadSuccess", uploadFileRes);
},
fail: (err) => {
console.log(err);
//uni.hideLoading();
reject(err);
this.$emit("uploadFail", err);
},
complete: () => {
//uni.hideLoading();
}
});
}))
})
Promise.all(uploadImgs) //执行所有需请求的接口
.then((results) => {
uni.hideLoading();
})
.catch((res, object) => {
uni.hideLoading();
this.$emit("uploadFail", res);
});
// uploadTask.onProgressUpdate((res) => {
// //console.log('',)
// uni.showLoading({
// title: '上传中' + res.progress + '%'
// });
// if (res.progress == 100) {
// uni.hideLoading();
// }
// });
},
uniCloudUpload(tempFilePaths, type) {
uni.showLoading({
title: '上传中'
});
console.log('uniCloudUpload', tempFilePaths);
let uploadImgs = [];
tempFilePaths.forEach((item, index) => {
uploadImgs.push(new Promise((resolve, reject) => {
uniCloud.uploadFile({
filePath: item,
cloudPath: this.guid() + '.' + this.getFileType(item, type),
success(uploadFileRes) {
if (uploadFileRes.success) {
resolve(uploadFileRes.fileID);
}
},
fail(err) {
console.log(err);
reject(err);
},
complete() {}
});
}))
})
Promise.all(uploadImgs) //执行所有需请求的接口
.then((results) => {
uni.hideLoading();
// console.log('then', results)
uniCloud.getTempFileURL({
fileList: results,
success: (res) => {
//console.log('success',res.fileList)
res.fileList.forEach(item => {
//console.log(item.tempFileURL)
//this.value.push(item.tempFileURL)
// #ifndef VUE3
this.value.push(item.tempFileURL)
this.$emit("input", this.value);
// #endif
// #ifdef VUE3
this.modelValue.push(item.tempFileURL)
this.$emit("update:modelValue", this.modelValue);
// #endif
})
},
fail() {},
complete() {}
});
})
.catch((res, object) => {
uni.hideLoading();
});
},
getFileType(path, type) { //手机端默认图片为jpg 视频为mp4
// #ifdef H5
var result = type == 0 ? 'jpg' : 'mp4';
// #endif
// #ifndef H5
var result = path.split('.').pop().toLowerCase();
// #ifdef MP
if (this.compress) { //微信小程序压缩完没有后缀
result = type == 0 ? 'jpg' : 'mp4';
}
// #endif
// #endif
return result;
},
guid() {
return 'xxxxxxxx-date-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
}).replace(/date/g, function(c) {
return Date.parse(new Date());
});
},
canvasDataURL(path, obj, callback) {
var img = new Image();
img.src = path;
img.onload = function() {
var that = this;
// 默认按比例压缩
var w = that.width,
h = that.height,
scale = w / h;
w = obj.width || w;
h = obj.height || (w / scale);
var quality = 0.8; // 默认图片质量为0.8
//生成canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// 创建属性节点
var anw = document.createAttribute("width");
anw.nodeValue = w;
var anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(that, 0, 0, w, h);
// 图像质量
if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
quality = obj.quality;
}
// quality值越小所绘制出的图像越模糊
var base64 = canvas.toDataURL('image/jpeg', quality);
// 回调函数返回base64的值
callback(base64);
}
},
}
}
</script>
<style>
.preview-full {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 1002;
}
.preview-full video {
width: 100%;
height: 100%;
z-index: 1002;
}
.preview-full-close {
position: fixed;
right: 32rpx;
top: 25rpx;
width: 80rpx;
height: 80rpx;
line-height: 60rpx;
text-align: center;
z-index: 1003;
/* background-color: #808080; */
color: #fff;
font-size: 65rpx;
font-weight: bold;
text-shadow: 1px 2px 5px rgb(0 0 0);
}
/* .preview-full-close-before,
.preview-full-close-after {
position: absolute;
top: 50%;
left: 50%;
content: '';
height: 60rpx;
margin-top: -30rpx;
width: 6rpx;
margin-left: -3rpx;
background-color: #FFFFFF;
z-index: 20000;
}
.preview-full-close-before {
transform: rotate(45deg);
}
.preview-full-close-after {
transform: rotate(-45deg);
} */
.htz-image-upload-list {
display: flex;
flex-wrap: wrap;
}
.htz-image-upload-Item {
width: 160rpx;
height: 160rpx;
margin: 13rpx;
border-radius: 10rpx;
position: relative;
}
.htz-image-upload-Item image {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.htz-image-upload-Item-video {
width: 100%;
height: 100%;
border-radius: 10rpx;
position: relative;
}
.htz-image-upload-Item-video-fixed {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
border-radius: 10rpx;
z-index: 996;
}
.htz-image-upload-Item video {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.htz-image-upload-Item-add {
font-size: 105rpx;
/* line-height: 160rpx; */
text-align: center;
border: 1px dashed #d9d9d9;
color: #d9d9d9;
}
.htz-image-upload-Item-del {
background-color: var(--ui-BG-Main);
font-size: 24rpx;
position: absolute;
width: 35rpx;
height: 35rpx;
line-height: 35rpx;
text-align: center;
top: 0;
right: 0;
z-index: 997;
color: #fff;
border-top-right-radius: 10rpx;
}
.htz-image-upload-Item-del-cover {
background-color: var(--ui-BG-Main);
font-size: 24rpx;
position: absolute;
width: 35rpx;
height: 35rpx;
text-align: center;
top: 0;
right: 0;
color: #fff;
/* #ifdef APP-PLUS */
line-height: 25rpx;
/* #endif */
/* #ifndef APP-PLUS */
line-height: 35rpx;
/* #endif */
z-index: 997;
border-top-right-radius: 10rpx;
}
.htz-image-upload-Item-video-app-poster {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<su-popup
:show="modal.qrcode"
type="center"
backgroundColor="none"
round="0"
:showClose="false"
:isMaskClick="true"
@close="closeModal"
>
<view class="content">
<image class="img" :src="tradeConfig.qrcode" show-menu-by-longpress="true"></image>
<view class="tip">长按二维码 关注公众号</view>
<view class="desc">用于接收消息提醒</view>
<view class="close-btn" @click="getQrcode">点击验证</view>
</view>
</su-popup>
</template>
<script>
import sheep from '@/sheep';
import $store from '@/sheep/store';
import UserApi from '@/sheep/api/member/user';
export default {
components: {
},
data() {
return {
show: false,
};
},
computed: {
modal() {
return sheep.$store('modal');
},
tradeConfig() {
return sheep.$store('user').tradeConfig;
},
},
methods: {
open() {
this.show = true;
},
closeModal() {
$store('modal').$patch((state) => {
state.qrcode = false;
});
},
getQrcode() {
UserApi.getQrcode().then((res) => {
if (res.code === 0) {
if(res.data){
sheep.$helper.toast('验证通过');
this.closeModal();
}else{
sheep.$helper.toast('未关注公众号');
}
}
});
},
}
};
</script>
<style lang="scss" scoped>
.show {
background-color: rgba(0, 0, 0, 0.7);
position: fixed;
z-index: 999999;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.hide {
display: none;
}
.content {
padding: 40rpx;
background-color: #fff;
border-radius: 10rpx;
.img {
width: 500rpx;
height: 500rpx;
}
.tip {
display: flex;
justify-content: center;
padding-top: 40rpx;
padding-bottom: 20rpx;
font-size: 28rpx;
color: #666666;
}
.desc{
display: flex;
justify-content: center;
font-size: 28rpx;
font-weight: bold;
}
.close-btn {
display: flex;
justify-content: center;
background-color: var(--ui-BG-Main);
border-radius: 40px;
margin-top: 40rpx;
padding: 20rpx;
font-size: 24rpx;
color: #fff;
}
}
</style>

View File

@@ -0,0 +1,356 @@
<template>
<su-popup
:show="modal.search"
backgroundColor="#fff"
round="5"
:showClose="false"
:isMaskClick="true"
@close="closeModal"
>
<view class="s-form">
<view class="s-title">筛选</view>
<view class="s-box">
<view class="s-label">年龄</view>
<view class="age-box">
<view class="s-tag" @click="ageChange(aindex)" :class="ageIndex == aindex ? 'active' : ''" v-for="(age, aindex) in ageList" :key="aindex">{{age.text}}</view>
</view>
</view>
<view class="s-box">
<view class="s-label">性别</view>
<view class="age-box">
<view class="s-tag" @click="sexChange(index)" :class="sexIndex == index ? 'active' : ''" v-for="(sex, index) in sexList" :key="index">{{sex.text}}</view>
</view>
</view>
<view class="s-box">
<view class="s-label">是否同城会员特权</view>
<view class="age-box">
<view class="s-tag" @click="cityChange(cindex)" :class="cityIndex == cindex ? 'active' : ''" v-for="(city, cindex) in cityList" :key="cindex">{{city.text}}</view>
</view>
</view>
<view class="s-box">
<view class="s-label">城市会员特权</view>
<view class="age-box">
<view class="s-tag" @click="query.city = ''">全部</view>
<view class="s-tag" @click="cityShow = true" v-if="query.city">{{query.city}}</view>
<view class="s-tag" @click="cityShow = true" v-else>切换城市</view>
</view>
</view>
<!-- <view class="s-box">
<view class="s-label">距离</view>
<view class="age-box">
<view class="s-tag" @click="miChange(mindex)" :class="miIndex == mindex ? 'active' : ''" v-for="(mi, mindex) in miList" :key="mindex">{{mi.text}}</view>
</view>
</view> -->
<view class="btn-box" :class="(ageIndex < 0 && sexIndex < 0 && miIndex < 0) ? 'btn-disabled' : ''" @click="ok">确定</view>
</view>
</su-popup>
<su-region-picker :show="cityShow" @cancel="cityShow = false" @confirm="cityOk" />
</template>
<script>
import sheep from '@/sheep';
import $store from '@/sheep/store';
export default {
emits: ['query'],
components: {
},
data() {
return {
min: 18,
max: 18,
ageIndex: 0,
sexIndex: 0,
cityIndex: 0,
miIndex: -1,
ageList: [
{
text: '全部',
min: 0,
max: 100,
},
{
text: '18~23',
min: 18,
max: 23,
},
{
text: '23~28',
min: 23,
max: 28,
},
{
text: '28~33',
min: 28,
max: 33,
},
{
text: '33~38',
min: 33,
max: 38,
},
{
text: '38~43',
min: 38,
max: 43,
},
{
text: '43~48',
min: 43,
max: 48,
},
{
text: '48~53',
min: 48,
max: 53,
},
],
sexList: [
{
text: '全部',
value: '',
},
{
text: '男',
value: 2,
},
{
text: '女',
value: 1,
},
],
cityList: [
{
text: '否',
value: '1',
},
{
text: '是',
value: '0',
},
],
miList: [
{
text: '5KM',
value: 5000,
},
{
text: '10KM',
value: 10000,
},
{
text: '15KM',
value: 15000,
},
{
text: '20KM',
value: 20000,
},
{
text: '25KM',
value: 25000,
},
{
text: '30KM',
value: 30000,
},
{
text: '35KM',
value: 35000,
},
{
text: '40KM',
value: 40000,
},
],
search: false,
cityShow: false,
query: {
maxAge: 35,
minAge: 18,
sex: '',
isCity: 1,
city: '',
},
}
},
computed: {
modal() {
return sheep.$store('modal');
},
userInfo: {
get() {
return sheep.$store('user').userInfo;
},
},
},
methods: {
closeModal() {
$store('modal').$patch((state) => {
state.search = false;
});
},
ageChange(i) {
this.ageIndex = i;
},
sexChange(i) {
this.sexIndex = i;
},
cityChange(i) {
this.cityIndex = i;
},
miChange(i) {
this.miIndex = i;
},
ok() {
if(this.ageIndex < 0 && this.sexIndex < 0 && this.miIndex < 0){
return;
}
if(this.ageIndex > -1){
this.query.maxAge = this.ageList[this.ageIndex].max;
this.query.minAge = this.ageList[this.ageIndex].min;
}
if(this.sexIndex > -1){
this.query.sex = this.sexList[this.sexIndex].value;
}
if(this.cityIndex > -1){
this.query.isCity = this.cityList[this.cityIndex].value;
}
this.$emit('query', this.query);
this.closeModal();
},
handerChooseLocation () {
uni.chooseLocation({
success: (res) => {
this.updateLocation(res.latitude, res.longitude);
},
fail: function (err) {
console.log('取消按钮', err)
}
});
},
doCity() {
this.$u.route({
url: '/pages/index/user/city',
});
},
updateLocation(lat,lng) {
this.$u.put('/system/user/profile',{
lat: lat,
lon: lng,
}).then(res => {
this.doCity();
})
},
more() {
this.$u.route({
url: 'pages/index/user/menu',
});
},
open() {
this.search = true;
},
cancel() {
this.search = false;
},
minEnd() {
if(this.max < this.min){
this.max = this.min;
}
},
maxEnd() {
if(this.max < this.min){
this.min = this.max;
}
},
cityOk(e) {
this.query.city = e.city_name;
this.cityShow = false;
},
}
}
</script>
<style lang="scss" scoped>
.s-form {
padding: 20px;
}
.s-title {
font-size: 38rpx;
display: flex;
align-items: center;
font-weight: bold;
justify-content: center;
margin-bottom: 15px;
}
.s-label {
font-size: 30rpx;
margin-top: 10px;
margin-bottom: 10px;
color: black;
}
.s-age {
display: flex;
align-items: center;
margin: 30px 0;
}
.s-slider {
flex: 1;
}
.btn-box {
background-color: #283242;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
border-radius: 20px;
font-size: 30rpx;
color: #fff;
margin-top: 20px;
}
.sex-btn {
background-color: #2979ff;
border-radius: 20px;
padding: 3px 10px;
color: #fff;
display: flex;
justify-content: center;
margin-left: 10px;
}
.more {
margin-bottom: 15px;
background-color: #19be6b;
}
.s-box {
display: flex;
flex-direction: column;
}
.age-box {
display: flex;
flex-wrap: wrap;
}
.s-tag {
border: 1px solid #ddd;
border-radius: 40px;
display: flex;
font-size: 24rpx;
align-items: center;
justify-content: center;
margin-right: 10px;
height: 25px;
width: 60px;
margin-bottom: 15px;
}
.active {
background-color: #283242;
color: #fff;
border: 1px solid #283242;
}
.btn-disabled {
background-color: #ddd;
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<view @touchmove.stop.prevent>
<view class="tui-actionsheet" :class="{'tui-actionsheet-show':show,'tui-actionsheet-radius':radius}"
:style="{zIndex:getZIndex}">
<view class="tui-actionsheet-tips" :style="{fontSize:size+'rpx',color:color}" v-if="tips">
{{tips}}
</view>
<view :class="[isCancel?'tui-operate-box':'']">
<block v-for="(item,index) in itemList" :key="index">
<view class="tui-actionsheet-btn tui-actionsheet-divider"
:class="{'tui-btn-last':!isCancel && index==itemList.length-1}"
hover-class="tui-actionsheet-hover" :hover-stay-time="150" :data-index="index"
:style="{color:item.color || '#2B2B2B'}" @tap="handleClickItem">{{item.text}}</view>
</block>
</view>
<view class="tui-actionsheet-btn tui-actionsheet-cancel" hover-class="tui-actionsheet-hover"
:hover-stay-time="150" v-if="isCancel" @tap="handleClickCancel">取消</view>
</view>
<view class="tui-actionsheet-mask" :class="{'tui-mask-show':show}" :style="{background:maskColor,zIndex:zIndex}"
@tap="handleClickMask"></view>
</view>
</template>
<script>
export default {
name: "tuiActionsheet",
emits: ['click', 'cancel'],
props: {
//显示操作菜单
show: {
type: Boolean,
default: false
},
//菜单按钮数组,自定义文本颜色,红色参考色:#e53a37
itemList: {
type: Array,
default: function() {
return [{
text: "确定",
color: "#2B2B2B"
}]
}
},
//点击遮罩 是否可关闭
maskClosable: {
type: Boolean,
default: true
},
//v2.1.0
maskColor: {
type: String,
default: "rgba(0, 0, 0, 0.6)"
},
//提示文字
tips: {
type: String,
default: ""
},
//提示文字颜色
color: {
type: String,
default: "#808080"
},
//提示文字大小 rpx
size: {
type: Number,
default: 26
},
//是否需要圆角
radius: {
type: Boolean,
default: true
},
//是否需要取消按钮
isCancel: {
type: Boolean,
default: true
},
zIndex: {
type: [Number, String],
default: 998
}
},
computed: {
getZIndex() {
return Number(this.zIndex) + 2
}
},
methods: {
handleClickMask() {
if (!this.maskClosable) return;
this.handleClickCancel();
},
handleClickItem(e) {
if (!this.show) return;
const index = Number(e.currentTarget.dataset.index);
this.$emit('click', {
index: index,
...this.itemList[index]
});
},
handleClickCancel() {
this.$emit('cancel');
}
}
}
</script>
<style scoped>
.tui-actionsheet {
width: 100%;
position: fixed;
left: 0;
right: 0;
bottom: 0;
visibility: hidden;
transform: translate3d(0, 100%, 0);
transform-origin: center;
transition: all 0.25s ease-in-out;
background-color: #F7F7F7;
min-height: 100rpx;
}
.tui-actionsheet-radius {
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
overflow: hidden;
}
.tui-actionsheet-show {
transform: translate3d(0, 0, 0);
visibility: visible;
}
.tui-actionsheet-tips {
width: 100%;
padding: 40rpx 60rpx;
box-sizing: border-box;
text-align: center;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.tui-operate-box {
padding-bottom: 12rpx;
}
.tui-actionsheet-btn {
width: 100%;
height: 100rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 34rpx;
position: relative;
}
.tui-btn-last {
padding-bottom: env(safe-area-inset-bottom);
}
.tui-actionsheet-divider::before {
content: '';
width: 100%;
border-top: 1rpx solid #E7E7E7;
position: absolute;
top: 0;
left: 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
.tui-actionsheet-cancel {
color: #1a1a1a;
padding-bottom: env(safe-area-inset-bottom);
}
.tui-actionsheet-hover {
background-color: #f7f7f9;
}
.tui-actionsheet-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.tui-mask-show {
opacity: 1;
visibility: visible;
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<view :class="[dot ? 'tui-badge-dot' : 'tui-badge', !dot ? 'tui-badge-scale' : '']"
:style="{ top: top, right: right, position: absolute ? 'absolute' : 'static', transform: getStyle, margin: margin,background:getBackground,color:getColor }"
@tap="handleClick">
<slot></slot>
</view>
</template>
<script>
export default {
name: 'tuiBadge',
emits: ['click'],
props: {
//primary,warning,green,danger,whiteblackgray,white_red
type: {
type: String,
default: 'primary'
},
//是否是圆点
dot: {
type: Boolean,
default: false
},
margin: {
type: String,
default: '0'
},
//是否绝对定位
absolute: {
type: Boolean,
default: false
},
top: {
type: String,
default: '-8rpx'
},
right: {
type: String,
default: '0'
},
//缩放比例
scaleRatio: {
type: Number,
default: 1
},
//水平方向移动距离
translateX: {
type: String,
default: '0'
}
},
computed: {
getStyle() {
return `scale(${this.scaleRatio}) translateX(${this.translateX})`;
},
getBackground() {
const global = uni && uni.$tui && uni.$tui.color;
let color = {
'primary': (global && global.primary) || '#5677fc',
'green': (global && global.success) || '#07c160',
'warning': (global && global.warning) || '#ff7900',
'danger': (global && global.danger) || '#EB0909',
'white': '#fff',
'black': '#000',
'gray': '#ededed',
'red': (global && global.pink) || '#f74d54',
'pink': (global && global.pink) || '#f74d54',
'white_red': '#fff',
'white_primary': '#fff',
'white_green': '#fff',
'white_warning': '#fff',
'white_pink': '#fff'
} [this.type]
return color
},
getColor() {
const global = uni && uni.$tui && uni.$tui.color;
let color = {
'primary': '#fff',
'green': '#fff',
'warning': '#fff',
'danger': '#fff',
'white': '#333',
'black': '#fff',
'gray': '#999',
'red': '#fff',
'pink': (global && global.pink) || '#f74d54',
'white_red': (global && global.danger) || '#EB0909',
'white_primary': (global && global.primary) || '#5677fc',
'white_green': (global && global.success) || '#07c160',
'white_warning': (global && global.warning) || '#ff7900',
'white_pink': (global && global.pink) || '#f74d54',
} [this.type]
return color
}
},
methods: {
handleClick() {
this.$emit('click', {});
}
}
};
</script>
<style scoped>
.tui-badge-dot {
height: 8px;
width: 8px;
border-radius: 50%;
}
.tui-badge {
font-size: 24rpx;
line-height: 24rpx;
height: 36rpx;
min-width: 36rpx;
padding: 0 10rpx;
box-sizing: border-box;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.tui-badge-scale {
transform-origin: center center;
}
</style>

View File

@@ -0,0 +1,119 @@
<template>
<view @touchmove.stop.prevent>
<view class="tui-popup-class tui-bottom-popup"
:class="{ 'tui-popup-show': show, 'tui-popup-radius': radius,'tui-bp__safearea':isSafeArea }"
:style="{ background: backgroundColor, height: height ? height + 'rpx' : 'auto', zIndex: zIndex,transform:`translate3d(0, ${show?translateY:'100%'}, 0)`}">
<slot></slot>
</view>
<view class="tui-popup-mask" :class="[show ? 'tui-mask-show' : '']" :style="{ zIndex: maskZIndex }" v-if="mask"
@tap="handleClose"></view>
</view>
</template>
<script>
export default {
name: 'tuiBottomPopup',
emits: ['close'],
props: {
//是否需要mask
mask: {
type: Boolean,
default: true
},
//控制显示
show: {
type: Boolean,
default: false
},
//背景颜色
backgroundColor: {
type: String,
default: '#fff'
},
//高度 rpx
height: {
type: Number,
default: 0
},
//设置圆角
radius: {
type: Boolean,
default: true
},
zIndex: {
type: [Number, String],
default: 997
},
maskZIndex: {
type: [Number, String],
default: 996
},
//弹层显示时,垂直方向移动的距离
translateY: {
type: String,
default: '0'
},
//是否需要判断底部安全区域主要针对iphonex以上机型
isSafeArea: {
type: Boolean,
default: true
}
},
methods: {
handleClose() {
if (!this.show) {
return;
}
this.$emit('close', {});
}
}
};
</script>
<style scoped>
.tui-bottom-popup {
width: 100%;
position: fixed;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transform: translate3d(0, 100%, 0);
transform-origin: center;
transition: all 0.3s ease-in-out;
min-height: 20rpx;
}
.tui-bp__safearea {
padding-bottom: env(safe-area-inset-bottom);
}
.tui-popup-radius {
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
overflow: hidden;
}
.tui-popup-show {
opacity: 1;
/* transform: translate3d(0, 0, 0); */
}
.tui-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.tui-mask-show {
opacity: 1;
visibility: visible;
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<view :class="{ 'tui-flex-end': flexEnd }">
<view class="tui-popup-list" :class="{ 'tui-popup-show': show,'tui-z_index':show && position!='relative' }" :style="{ width: width, backgroundColor: backgroundColor, borderRadius: radius, color: color, position: position, left: left, right: right, bottom: bottom, top: top,transform:`translate(${translateX},${translateY})` }">
<view class="tui-triangle" :style="{
borderWidth: borderWidth,
borderColor: `transparent transparent ${backgroundColor} transparent`,
left: triangleLeft,
right: triangleRight,
top: triangleTop,
bottom: triangleBottom
}"
v-if="direction == 'top'"></view>
<view class="tui-triangle" :style="{
borderWidth: borderWidth,
borderColor: `${backgroundColor} transparent transparent transparent`,
left: triangleLeft,
right: triangleRight,
top: triangleTop,
bottom: triangleBottom
}"
v-if="direction == 'bottom'"></view>
<view class="tui-triangle" :style="{
borderWidth: borderWidth,
borderColor: `transparent ${backgroundColor} transparent transparent`,
left: triangleLeft,
right: triangleRight,
top: triangleTop,
bottom: triangleBottom
}"
v-if="direction == 'left'"></view>
<view class="tui-triangle" :style="{
borderWidth: borderWidth,
borderColor: `transparent transparent transparent ${backgroundColor}`,
left: triangleLeft,
right: triangleRight,
top: triangleTop,
bottom: triangleBottom
}"
v-if="direction == 'right'"></view>
<slot></slot>
</view>
<view @touchmove.stop.prevent="stop" class="tui-popup-mask" :class="{ 'tui-popup-show': show }" :style="{ backgroundColor: maskBgColor }"
v-if="mask" @tap="handleClose"></view>
</view>
</template>
<script>
export default {
name: 'tuiBubblePopup',
emits: ['close'],
props: {
//宽度
width: {
type: String,
default: '300rpx'
},
//popup圆角
radius: {
type: String,
default: '8rpx'
},
//popup 定位 left right top bottom值
left: {
type: String,
default: 'auto'
},
right: {
type: String,
default: 'auto'
},
top: {
type: String,
default: 'auto'
},
bottom: {
type: String,
default: 'auto'
},
translateX:{
type: String,
default: '0'
},
translateY:{
type: String,
default: '0'
},
//背景颜色
backgroundColor: {
type: String,
default: '#4c4c4c'
},
//字体颜色
color: {
type: String,
default: '#fff'
},
//三角border-width
borderWidth: {
type: String,
default: '12rpx'
},
//三角形方向 top left right bottom
direction: {
type: String,
default: 'top'
},
//定位 left right top bottom值
triangleLeft: {
type: String,
default: 'auto'
},
triangleRight: {
type: String,
default: 'auto'
},
triangleTop: {
type: String,
default: 'auto'
},
triangleBottom: {
type: String,
default: 'auto'
},
//定位 relative absolute fixed
position: {
type: String,
default: 'fixed'
},
//flex-end
flexEnd: {
type: Boolean,
default: false
},
//是否需要mask
mask: {
type: Boolean,
default: true
},
maskBgColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)'
},
//控制显示
show: {
type: Boolean,
default: false
}
},
methods: {
handleClose() {
if (!this.show) {
return;
}
this.$emit('close', {});
},
stop() {
return false;
}
}
};
</script>
<style scoped>
.tui-popup-list {
z-index: 1;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.tui-flex-end {
width: 100%;
display: flex;
justify-content: flex-end;
}
.tui-triangle {
position: absolute;
width: 0;
height: 0;
border-style: solid;
z-index: 997;
}
.tui-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 995;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.tui-popup-show {
opacity: 1;
visibility: visible;
}
.tui-z_index {
z-index: 996;
}
</style>

View File

@@ -0,0 +1,309 @@
<template>
<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
<!-- #ifndef MP-ALIPAY -->
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
:style="{ width: diam + 'px', height: (height || diam) + 'px' }"
v-if="defaultShow && defaultCanvasId"></canvas>
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
:style="{ width: diam + 'px', height: (height || diam) + 'px' }" v-if="progressCanvasId"></canvas>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }" v-if="defaultShow"></canvas>
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }"></canvas>
<!-- #endif -->
<slot></slot>
</view>
</template>
<script>
export default {
name: 'tuiCircularProgress',
emits: ['change', 'end'],
props: {
/*
传值需使用rpx进行转换保证各终端兼容
px = rpx / 750 * wx.getSystemInfoSync().windowWidth
圆形进度条(画布)宽度,直径 [px]
*/
diam: {
type: Number,
default: 60
},
//圆形进度条(画布)高度默认取diam值[当画半弧时传值height有值时则取height]
height: {
type: Number,
default: 0
},
//进度条线条宽度[px]
lineWidth: {
type: Number,
default: 4
},
/*
线条的端点样式
butt向线条的每个末端添加平直的边缘
round 向线条的每个末端添加圆形线帽
square 向线条的每个末端添加正方形线帽
*/
lineCap: {
type: String,
default: 'round'
},
//圆环进度字体大小 [px]
fontSize: {
type: Number,
default: 12
},
//圆环进度字体颜色
fontColor: {
type: String,
default: ''
},
//是否显示进度文字
fontShow: {
type: Boolean,
default: true
},
/*
自定义显示文字[默认为空显示百分比fontShow=true时生效]
可以使用 slot自定义显示内容
*/
percentText: {
type: String,
default: ''
},
//是否显示默认(背景)进度条
defaultShow: {
type: Boolean,
default: true
},
//默认进度条颜色
defaultColor: {
type: String,
default: '#CCCCCC'
},
//进度条颜色
progressColor: {
type: String,
default: ''
},
//进度条渐变颜色[结合progressColor使用默认为空]
gradualColor: {
type: String,
default: ''
},
//起始弧度,单位弧度
sAngle: {
type: Number,
default: -Math.PI / 2
},
//指定弧度的方向是逆时针还是顺时针。默认是false即顺时针
counterclockwise: {
type: Boolean,
default: false
},
//进度百分比 [10% 传值 10]
percentage: {
type: Number,
default: 0
},
//进度百分比缩放倍数[使用半弧为100%时则可传2]
multiple: {
type: Number,
default: 1
},
//动画执行时间[单位毫秒低于50无动画]
duration: {
type: Number,
default: 800
},
//backwards: 动画从头播forwards动画从上次结束点接着播
activeMode: {
type: String,
default: 'backwards'
}
},
watch: {
percentage(val) {
this.initDraw();
}
},
data() {
// #ifndef MP-WEIXIN || MP-QQ
let cid = `id01_${Math.ceil(Math.random() * 10e5).toString(36)}`
let did = `id02_${Math.ceil(Math.random() * 10e5).toString(36)}`
// #endif
return {
// #ifdef MP-WEIXIN || MP-QQ
progressCanvasId: 'progressCanvasId',
defaultCanvasId: 'defaultCanvasId',
// #endif
// #ifndef MP-WEIXIN || MP-QQ
progressCanvasId: cid,
defaultCanvasId: did,
// #endif
progressContext: null,
linearGradient: null,
//起始百分比
startPercentage: 0
// dpi
//pixelRatio: uni.getSystemInfoSync().pixelRatio
};
},
mounted() {
this.$nextTick(() => {
setTimeout(() => {
this.initDraw(true);
}, 50)
})
},
methods: {
//初始化绘制
initDraw(init) {
let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
start = start > this.percentage ? 0 : start;
if (this.defaultShow && init) {
this.drawDefaultCircular();
}
this.drawProgressCircular(start);
},
//默认(背景)圆环
drawDefaultCircular() {
let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
let lineWidth = Number(this.lineWidth)
// #ifdef MP-ALIPAY
lineWidth = lineWidth * 4
// #endif
ctx.setLineWidth(lineWidth);
ctx.setStrokeStyle(this.defaultColor);
//终止弧度
let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
this.drawArc(ctx, eAngle);
},
//进度圆环
drawProgressCircular(startPercentage) {
let ctx = this.progressContext;
let gradient = this.linearGradient;
if (!ctx) {
ctx = uni.createCanvasContext(this.progressCanvasId, this);
//创建一个线性的渐变颜色 CanvasGradient对象
let diam = Number(this.diam)
// #ifdef MP-ALIPAY
diam = diam * 4
// #endif
const progressColor = this.progressColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
gradient = ctx.createLinearGradient(0, 0, diam, 0);
gradient.addColorStop('0', progressColor);
if (this.gradualColor) {
gradient.addColorStop('1', this.gradualColor);
}
// #ifdef APP-PLUS || MP
const res = uni.getSystemInfoSync();
if (!this.gradualColor && res.platform.toLocaleLowerCase() == 'android') {
gradient.addColorStop('1', progressColor);
}
// #endif
this.progressContext = ctx;
this.linearGradient = gradient;
}
let lineWidth = Number(this.lineWidth)
// #ifdef MP-ALIPAY
lineWidth = lineWidth * 4
// #endif
ctx.setLineWidth(lineWidth);
ctx.setStrokeStyle(gradient);
let time = this.percentage == 0 || this.duration < 50 ? 0 : this.duration / this.percentage;
if (this.percentage > 0) {
startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
startPercentage++;
}
if (this.fontShow) {
let fontSize = Number(this.fontSize)
// #ifdef MP-ALIPAY
fontSize = fontSize * 4
// #endif
ctx.setFontSize(fontSize);
const fontColor = this.fontColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
ctx.setFillStyle(fontColor);
ctx.setTextAlign('center');
ctx.setTextBaseline('middle');
let percentage = this.percentText;
if (!percentage) {
percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this
.multiple;
percentage = `${percentage}%`;
}
let radius = this.diam / 2;
// #ifdef MP-ALIPAY
radius = radius * 4
// #endif
ctx.fillText(percentage, radius, radius);
}
if (this.percentage == 0 || (this.counterclockwise && startPercentage == 100)) {
ctx.draw();
} else {
let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
this.drawArc(ctx, eAngle);
}
setTimeout(() => {
this.startPercentage = startPercentage;
if (startPercentage >= this.percentage) {
this.$emit('end', {
canvasId: this.progressCanvasId,
percentage: startPercentage
});
} else {
this.drawProgressCircular(startPercentage);
}
this.$emit('change', {
percentage: startPercentage
});
}, time);
// #ifdef H5
// requestAnimationFrame(()=>{})
// #endif
},
//创建弧线
drawArc(ctx, eAngle) {
ctx.setLineCap(this.lineCap);
ctx.beginPath();
let radius = this.diam / 2; //x=y
let lineWidth = Number(this.lineWidth)
// #ifdef MP-ALIPAY
radius = radius * 4
lineWidth = lineWidth * 4
// #endif
ctx.arc(radius, radius, radius - lineWidth, this.sAngle, eAngle, this.counterclockwise);
ctx.stroke();
ctx.draw();
}
}
};
</script>
<style scoped>
.tui-circular-container,
.tui-circular-default {
position: relative;
}
/* #ifdef MP-ALIPAY */
.tui-circular-default,
.tui-circular-progress {
zoom: 0.25;
}
/* #endif */
.tui-circular-progress {
position: absolute !important;
left: 0;
top: 0;
z-index: 10;
}
</style>

View File

@@ -0,0 +1,3 @@
export default {
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,338 @@
<template>
<view class="tui-modal__container" :class="[show ? 'tui-modal-show' : '']" :style="{zIndex:zIndex}"
@touchmove.stop.prevent>
<view class="tui-modal-box"
:style="{ width: width, padding: padding, borderRadius: radius, backgroundColor: backgroundColor,zIndex:zIndex+1 }"
:class="[fadeIn || show ? 'tui-modal-normal' : 'tui-modal-scale', show ? 'tui-modal-show' : '']">
<view v-if="!custom">
<view class="tui-modal-title" v-if="title">{{ title }}</view>
<view class="tui-modal-content" :class="[title ? '' : 'tui-mtop']"
:style="{ color: color, fontSize: size + 'rpx' }">{{ content }}</view>
<view class="tui-modalBtn-box" :class="[button.length != 2 ? 'tui-flex-column' : '']">
<view v-for="(item, index) in button" :key="index" class="tui-modal-btn" :class="[
button.length != 2 ? 'tui-btn-width' : '',
button.length > 2 ? 'tui-mbtm' : '',
shape == 'circle' ? 'tui-circle-btn' : ''
]" :style="{background:getColor(item.type,true,item.plain),color:getTextColor(item.type,item.plain)}"
@tap="handleClick(index)">
{{ item.text || '确定' }}
<view class="tui-modal__border" :class="[shape == 'circle' ? 'tui-circle-border' : '']"
:style="{borderColor:getColor(item.type)}" v-if="item.plain"></view>
</view>
</view>
</view>
<view v-else>
<slot></slot>
</view>
</view>
<view v-if="isMask" class="tui-modal-mask" :class="[show ? 'tui-mask-show' : '']"
:style="{zIndex:maskZIndex,background:maskColor}" @tap="handleClickCancel"></view>
</view>
</template>
<script>
export default {
name: 'tuiModal',
emits: ['click', 'cancel'],
props: {
//是否显示
show: {
type: Boolean,
default: false
},
width: {
type: String,
default: '84%'
},
backgroundColor: {
type: String,
default: '#fff'
},
padding: {
type: String,
default: '40rpx 64rpx'
},
radius: {
type: String,
default: '24rpx'
},
//标题
title: {
type: String,
default: ''
},
//内容
content: {
type: String,
default: ''
},
//内容字体颜色
color: {
type: String,
default: '#999'
},
//内容字体大小 rpx
size: {
type: Number,
default: 28
},
//形状 circle, square
shape: {
type: String,
default: 'square'
},
button: {
type: Array,
default: function() {
return [{
text: '取消',
type: 'red',
plain: true //是否空心
},
{
text: '确定',
type: 'red',
plain: false
}
];
}
},
//点击遮罩 是否可关闭
maskClosable: {
type: Boolean,
default: true
},
//是否显示mask
isMask: {
type: Boolean,
default: true
},
maskColor: {
type: String,
default: 'rgba(0, 0, 0, 0.6)'
},
//淡入效果自定义弹框插入input输入框时传true
fadeIn: {
type: Boolean,
default: false
},
//自定义弹窗内容
custom: {
type: Boolean,
default: false
},
//容器z-index
zIndex: {
type: Number,
default: 9997
},
//mask z-index
maskZIndex: {
type: Number,
default: 9990
}
},
data() {
return {};
},
methods: {
getColor(type, isBg, plain) {
const global = uni && uni.$tui && uni.$tui.color;
let color = {
'primary': (global && global.primary) || '#5677fc',
'green': (global && global.success) || '#07c160',
'warning': (global && global.warning) || '#ff7900',
'danger': (global && global.danger) || '#EB0909',
'red': (global && global.danger) || '#EB0909',
'pink': (global && global.pink) || '#f74d54',
'white': isBg ? '#fff' : '#333',
'gray': isBg ? '#ededed' : '#999'
} [type || 'primary']
if (isBg && plain) {
color = 'transparent'
}
return color
},
getTextColor(type, plain) {
const global = uni && uni.$tui && uni.$tui.color;
let color = {
'primary': (global && global.primary) || '#5677fc',
'green': (global && global.success) || '#07c160',
'warning': (global && global.warning) || '#ff7900',
'danger': (global && global.danger) || '#EB0909',
'red': (global && global.danger) || '#EB0909',
'pink': (global && global.pink) || '#f74d54',
'white': '#333',
'gray': '#999'
} [type || 'primary']
if (!plain && type !== 'white' && type !== 'gray') {
color = '#fff'
}
return color
},
handleClick(index) {
if (!this.show) return;
this.$emit('click', {
index: Number(index)
});
},
handleClickCancel() {
if (!this.maskClosable) return;
this.$emit('cancel');
}
}
};
</script>
<style scoped>
.tui-modal__container {
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
}
.tui-modal-box {
position: relative;
opacity: 0;
visibility: hidden;
box-sizing: border-box;
transition: all 0.3s ease-in-out;
}
.tui-modal-scale {
transform: scale(0);
}
.tui-modal-normal {
transform: scale(1);
}
.tui-modal-show {
opacity: 1;
visibility: visible;
}
.tui-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
transition: all 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.tui-mask-show {
visibility: visible;
opacity: 1;
}
.tui-modal-title {
text-align: center;
font-size: 34rpx;
color: #333;
padding-top: 20rpx;
font-weight: bold;
}
.tui-modal-content {
text-align: center;
color: #999;
font-size: 28rpx;
padding-top: 20rpx;
padding-bottom: 60rpx;
}
.tui-mtop {
margin-top: 30rpx;
}
.tui-mbtm {
margin-bottom: 30rpx;
}
.tui-modalBtn-box {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.tui-flex-column {
flex-direction: column;
}
.tui-modal-btn {
width: 46%;
height: 68rpx;
line-height: 68rpx;
position: relative;
border-radius: 10rpx;
font-size: 26rpx;
overflow: visible;
margin-left: 0;
margin-right: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
border-width: 0;
}
.tui-modal-btn:active::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.15);
pointer-events: none;
}
.tui-modal-btn::after {
border-radius: 10rpx;
}
.tui-modal__border {
position: absolute;
width: 200%;
height: 200%;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
transform: scale(0.5, 0.5) translateZ(0);
border: 1px solid;
box-sizing: border-box;
left: 0;
top: 0;
z-index: 2;
border-radius: 20rpx;
pointer-events: none;
}
.tui-btn-width {
width: 80% !important;
}
.tui-circle-btn {
border-radius: 40rpx !important;
}
.tui-circle-btn::after {
border-radius: 40rpx !important;
}
.tui-circle-border {
border-radius: 80rpx !important;
}
</style>

View File

@@ -0,0 +1,258 @@
<template>
<view class="tui-navigation-bar"
:class="{ 'tui-bar-line': opacity > 0.85 && splitLine, 'tui-navbar-fixed': isFixed, 'tui-backdrop__filter': backdropFilter && dropDownOpacity > 0 }"
:style="{ height: height + 'px', background: isOpacity? `rgba(${background},${opacity})`:background, opacity: dropDownOpacity, zIndex: isFixed ? zIndex : 'auto' }">
<view class="tui-status-bar" :style="{ height: statusBarHeight + 'px' }" v-if="isImmersive"></view>
<view class="tui-navigation_bar-title"
:style="{ opacity: transparent || opacity >= maxOpacity ? 1 : opacity, color: color, paddingTop: top - statusBarHeight + 'px' }"
v-if="title && !isCustom">
{{ title }}
</view>
<slot></slot>
</view>
</template>
<script>
export default {
name: 'tuiNavigationBar',
emits: ['init', 'change'],
props: {
//NavigationBar标题
title: {
type: String,
default: ''
},
//NavigationBar标题颜色
color: {
type: String,
default: '#333'
},
//NavigationBar背景颜色,不支持rgb
backgroundColor: {
type: String,
default: '#fff'
},
//是否需要分割线
splitLine: {
type: Boolean,
default: false
},
//是否设置不透明度
isOpacity: {
type: Boolean,
default: true
},
//不透明度最大值 0-1
maxOpacity: {
type: [Number, String],
default: 1
},
//背景透明 【设置该属性则背景透明只出现内容isOpacity和maxOpacity失效】
transparent: {
type: Boolean,
default: false
},
//滚动条滚动距离
scrollTop: {
type: [Number, String],
default: 0
},
/*
isOpacity 为true时生效
opacity=scrollTop /windowWidth * scrollRatio
*/
scrollRatio: {
type: [Number, String],
default: 0.3
},
//是否自定义header内容
isCustom: {
type: Boolean,
default: false
},
//是否沉浸式
isImmersive: {
type: Boolean,
default: true
},
isFixed: {
type: Boolean,
default: true
},
//是否开启高斯模糊效果[仅在支持的浏览器有效果]
backdropFilter: {
type: Boolean,
default: false
},
//下拉隐藏NavigationBar主要针对有回弹效果ios端
dropDownHide: {
type: Boolean,
default: false
},
//z-index设置
zIndex: {
type: [Number, String],
default: 996
}
},
watch: {
scrollTop(newValue, oldValue) {
if (this.isOpacity && !this.transparent) {
this.opacityChange();
}
},
backgroundColor(val) {
if (val) {
if (this.isOpacity) {
this.background = this.hexToRgb(val);
} else {
this.background = this.transparent ? 'rgba(0, 0, 0, 0)' : val
}
}
}
},
data() {
return {
width: 375, //header宽度
left: 375, //小程序端 左侧距胶囊按钮距离
height: 44, //header高度
top: 0,
scrollH: 1, //滚动总高度,计算opacity
opacity: 1, //0-1
statusBarHeight: 0, //状态栏高度
background: '255,255,255', //header背景色
dropDownOpacity: 1
};
},
created() {
this.dropDownOpacity = this.backdropFilter && 0;
this.opacity = this.isOpacity || this.transparent ? 0 : this.maxOpacity;
if (this.isOpacity) {
this.background = this.hexToRgb(this.backgroundColor);
} else {
this.background = this.transparent ? 'rgba(0, 0, 0, 0)' : this.backgroundColor
}
let obj = {};
// #ifdef MP-WEIXIN
obj = wx.getMenuButtonBoundingClientRect();
// #endif
// #ifdef MP-BAIDU
obj = swan.getMenuButtonBoundingClientRect();
// #endif
// #ifdef MP-ALIPAY
my.hideAddToDesktopMenu();
// #endif
uni.getSystemInfo({
success: res => {
this.statusBarHeight = res.statusBarHeight;
this.width = res.windowWidth;
this.left = obj.left || res.windowWidth;
if (this.isImmersive) {
this.height = obj.top ? obj.top + obj.height + 8 : res.statusBarHeight + 44;
}
this.scrollH = res.windowWidth * this.scrollRatio;
this.top = obj.top ? obj.top + (obj.height - 32) / 2 : res.statusBarHeight + 6;
this.$emit('init', {
width: this.width,
height: this.height,
left: this.left,
top: this.top,
statusBarHeight: this.statusBarHeight,
opacity: this.opacity,
windowHeight: res.windowHeight
});
}
});
},
methods: {
hexToRgb(hex) {
let rgb = '255,255,255';
if (hex && ~hex.indexOf('#')) {
if (hex.length === 4) {
let text = hex.substring(1, 4);
hex = '#' + text + text;
}
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
rgb = `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}`;
}
}
return rgb;
},
opacityChange() {
if (this.dropDownHide) {
if (this.scrollTop < 0) {
if (this.dropDownOpacity > 0) {
this.dropDownOpacity = 1 - Math.abs(this.scrollTop) / 30;
}
} else {
this.dropDownOpacity = 1;
}
}
let scroll = this.scrollTop <= 1 ? 0 : this.scrollTop;
let opacity = scroll / this.scrollH;
if ((this.opacity >= this.maxOpacity && opacity >= this.maxOpacity) || (this.opacity == 0 && opacity ==
0)) {
return;
}
this.opacity = opacity > this.maxOpacity ? this.maxOpacity : opacity;
if (this.backdropFilter) {
this.dropDownOpacity = this.opacity >= this.maxOpacity ? 1 : this.opacity;
}
this.$emit('change', {
opacity: this.opacity
});
}
}
};
</script>
<style scoped>
.tui-navigation-bar {
width: 100%;
transition: opacity 0.4s;
}
.tui-backdrop__filter {
/* Safari for macOS & iOS */
-webkit-backdrop-filter: blur(15px);
/* Google Chrome */
backdrop-filter: blur(15px);
}
.tui-navbar-fixed {
position: fixed;
left: 0;
top: 0;
transform: translateZ(0);
}
.tui-status-bar {
width: 100%;
}
.tui-navigation_bar-title {
width: 100%;
font-size: 17px;
line-height: 17px;
/* #ifndef APP-PLUS */
font-weight: 500;
/* #endif */
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.tui-bar-line::after {
content: '';
position: absolute;
border-bottom: 1rpx solid #eaeef1;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
bottom: 0;
right: 0;
left: 0;
}
</style>

View File

@@ -0,0 +1,248 @@
<template>
<view class="tui-numberbox">
<view class="tui-num__icon__box" :style="{background:iconBgColor,borderRadius:radius}" @tap="reduce"
:class="[disabled || min >= inputValue ? 'tui-disabled' : '']">
<text class="tui-numbox-icon tui-num__icon-reduce"
:style="{ color: iconColor, fontSize: iconSize + 'rpx',lineHeight:iconSize + 'rpx' }"></text>
</view>
<input type="number" v-model="inputValue" :disabled="disabled" @blur="blur" class="tui-num-input"
:style="{ color: color, fontSize: size + 'rpx', background: backgroundColor, height: height + 'rpx', minHeight: height + 'rpx', width: width + 'rpx' }" />
<view class="tui-num__icon__box" :style="{background:iconBgColor,borderRadius:radius}" @tap="plus"
:class="[disabled || inputValue >= max ? 'tui-disabled' : '']">
<text class="tui-numbox-icon tui-num__icon-plus"
:style="{ color: iconColor, fontSize: iconSize + 'rpx',lineHeight:iconSize + 'rpx' }"></text>
</view>
</view>
</template>
<script>
export default {
name: 'tuiNumberbox',
emits: ['change'],
props: {
value: {
type: [Number, String],
default: 1
},
//最小值
min: {
type: Number,
default: 1
},
//最大值
max: {
type: Number,
default: 99
},
//迈步大小 1 1.1 10...
step: {
type: Number,
default: 1
},
//是否禁用操作
disabled: {
type: Boolean,
default: false
},
iconBgColor: {
type: String,
default: 'transparent'
},
radius:{
type: String,
default: '50%'
},
//加减图标大小 rpx
iconSize: {
type: Number,
default: 22
},
iconColor: {
type: String,
default: '#666666'
},
//input 高度
height: {
type: Number,
default: 42
},
//input 宽度
width: {
type: Number,
default: 80
},
size: {
type: Number,
default: 28
},
//input 背景颜色
backgroundColor: {
type: String,
default: '#F5F5F5'
},
//input 字体颜色
color: {
type: String,
default: '#333'
},
//索引值,列表中使用
index: {
type: [Number, String],
default: 0
},
//自定义参数
custom: {
type: [Number, String],
default: 0
}
},
created() {
this.inputValue = +this.value;
},
data() {
return {
inputValue: 0
};
},
watch: {
value(val) {
this.inputValue = +val;
}
},
methods: {
getScale(val, step) {
let scale = 1;
let scaleVal = 1;
//浮点型
if (!Number.isInteger(step)) {
scale = Math.pow(10, (step + '').split('.')[1].length);
}
//浮点型
if (!Number.isInteger(val)) {
scaleVal = Math.pow(10, (val + '').split('.')[1].length);
}
return Math.max(scale, scaleVal);
},
calcNum: function(type) {
if (this.disabled || (this.inputValue == this.min && type === 'reduce') || (this.inputValue == this
.max && type === 'plus')) {
return;
}
const scale = this.getScale(Number(this.inputValue), Number(this.step));
let num = Number(this.inputValue) * scale;
let step = this.step * scale;
if (type === 'reduce') {
num -= step;
} else if (type === 'plus') {
num += step;
}
let value = Number((num / scale).toFixed(String(scale).length - 1));
if (value < this.min) {
value = this.min;
} else if (value > this.max) {
value = this.max;
}
this.handleChange(value, type);
},
plus: function() {
this.calcNum('plus');
},
reduce: function() {
this.calcNum('reduce');
},
blur: function(e) {
let value = e.detail.value;
if (value) {
if (~value.indexOf('.') && Number.isInteger(this.step) && Number.isInteger(Number(value))) {
value = value.split('.')[0];
}
value = Number(value);
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min;
}
} else {
value = this.min;
}
if ((value == this.value && value != this.inputValue) || !e.detail.value) {
this.inputValue = value;
}
this.handleChange(value, 'blur');
},
handleChange(value, type) {
if (this.disabled) return;
this.$emit('change', {
value: Number(value),
type: type,
index: this.index,
custom: this.custom
});
}
}
};
</script>
<style scoped>
@font-face {
font-family: 'numberbox';
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASQAA0AAAAABtwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEdAAAABoAAAAciBpnRUdERUYAAARUAAAAHgAAAB4AKQALT1MvMgAAAZwAAABDAAAAVjxzSINjbWFwAAAB9AAAAEYAAAFK5zLpOGdhc3AAAARMAAAACAAAAAj//wADZ2x5ZgAAAkgAAACHAAAAnIfIEjxoZWFkAAABMAAAAC8AAAA2FZWEOWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAARAAAAEgwAAAFsb2NhAAACPAAAAAwAAAAMADAATm1heHAAAAF8AAAAHwAAACABEAAobmFtZQAAAtAAAAFJAAACiCnmEVVwb3N0AAAEHAAAAC0AAABV/+8iFXjaY2BkYGAA4gVmC5Tj+W2+MnCzMIDATWsFOQT9v5GFgbkeyOVgYAKJAgDrogf+AHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGWQYQDRDAxMQMwFhAwM/8F8BgALpAE5AHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs9Yn01kbvjfwBDD3MDQABRmBMkBAOXpDHEAeNpjYYAAFghmZGAAAACdAA4AAAB42mNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZiesT6b+P8/AwOElvwnWQxVDwSMbAxwDiMTkGBiQAWMDMMeAABRZwszAAAAAAAAAAAAAAAwAE542iWKQQrCMBBF5xNpd0pQ7EIoTEnahSCTUNqdWz2A9TrieXKeXCc1qcPn/zfzh0BYv2pVH7oQgbvqdG5Yt/DTrNlPYz+wHvuuqhFSME4sFshTgKUsKfhH5lg8BSul3i5bS3mQdd0RIh2IjnvUrkXDd8zuhuFt86tY9fonIsSYgsXpB+cCGosAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMwiWZmJQJRXVQoigTgjMd9QGIsgAFDsEBsAAAAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwAEAAEABAAAAAIAAAAAeNpjYGBgZACCq0vUOUD0TWsFORgNADPBBE4AAA==) format('woff');
font-weight: normal;
font-style: normal;
}
.tui-num__icon__box {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 12rpx;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.tui-numbox-icon {
font-family: 'numberbox' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.tui-num__icon-reduce:before {
content: '\e691';
}
.tui-num__icon-plus:before {
content: '\e605';
}
.tui-numberbox {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
}
.tui-num-input {
text-align: center;
margin: 0 6rpx;
font-weight: 400;
padding: 0;
border-width: 0;
}
/* #ifdef H5 */
::-webkit-inner-spin-button,
::-webkit-outer-spin-button{
-webkit-appearance: none;
margin: 0;
}
/* #endif */
.tui-disabled {
opacity: .5;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<view class="tui-sticky-class" :id="tui_Id">
<!--sticky 容器-->
<view :style="{height: stickyHeight,background:backgroundColor}" v-if="isFixed"></view>
<view :class="{'tui-sticky-fixed':isFixed}" :style="{top:isFixed?stickyTop+'px':'auto'}">
<slot name="header"></slot>
</view>
<!--sticky 容器-->
<!--内容-->
<slot name="content"></slot>
</view>
</template>
<script>
export default {
name: "tuiSticky",
emits: ['sticky', 'change'],
props: {
scrollTop: {
type: Number
},
//吸顶时与顶部的距离单位px
stickyTop: {
type: [Number, String]
// #ifndef H5
,
default: 0
// #endif
// #ifdef H5
,
default: 44
// #endif
},
//是否指定容器即内容放置插槽content内
container: {
type: Boolean,
default: false
},
//是否是原生自带header
isNativeHeader: {
type: Boolean,
default: true
},
//吸顶容器 高度 rpx
stickyHeight: {
type: String,
default: "auto"
},
//占位容器背景颜色
backgroundColor: {
type: String,
default: "transparent"
},
//是否重新计算[有异步加载时使用,设置大于0的数]
recalc: {
type: Number,
default: 0
},
//列表中的索引值
index: {
type: [Number, String],
default: 0
}
},
watch: {
scrollTop(newValue, oldValue) {
if (this.initialize != 0) {
this.updateScrollChange(() => {
this.updateStickyChange();
this.initialize = 0
});
} else {
this.updateStickyChange();
}
},
recalc(newValue, oldValue) {
this.updateScrollChange(() => {
this.updateStickyChange();
this.initialize = 0;
});
}
},
created() {
this.initialize = this.recalc
},
mounted() {
this.$nextTick(() => {
setTimeout(() => {
this.updateScrollChange();
}, 50)
})
},
data() {
const Id = `tui_${Math.ceil(Math.random() * 10e5).toString(36)}`
return {
timer: null,
top: 0,
height: 0,
isFixed: false,
initialize: 0, //重新初始化
tui_Id: Id
};
},
methods: {
updateStickyChange() {
const top = this.top;
const height = this.height;
const scrollTop = this.scrollTop
let stickyTop = this.stickyTop
// #ifdef H5
if (this.isNativeHeader) {
stickyTop = stickyTop - 44
stickyTop = stickyTop < 0 ? 0 : stickyTop
}
// #endif
if (this.container) {
this.isFixed = (scrollTop + stickyTop >= top && scrollTop + stickyTop < top + height) ? true : false
} else {
this.isFixed = scrollTop + stickyTop >= top ? true : false
}
//是否吸顶
this.$emit("sticky", {
isFixed: this.isFixed,
index: this.index
})
},
updateScrollChange(callback) {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.timer = setTimeout(() => {
const selectId = `#${this.tui_Id}`;
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select(selectId)
.fields({
size: true,
rect: true
}, res => {
if (res) {
this.top = res.top + (this.scrollTop || 0);
this.height = res.height;
callback && callback();
this.$emit("change", {
index: Number(this.index),
top: this.top
})
}
}).exec()
}, 0)
}
}
}
</script>
<style scoped>
.tui-sticky-fixed {
width: 100%;
position: fixed;
left: 0;
z-index: 888;
}
</style>

View File

@@ -0,0 +1,287 @@
<template>
<view class="tui-tabbar"
:class="{ 'tui-tabbar-fixed': isFixed, 'tui-unlined': unlined, 'tui-backdrop__filter': backdropFilter }"
:style="{ background: backgroundColor, zIndex: isFixed ? zIndex : 'auto' }">
<block v-for="(item, index) in tabBar" :key="index">
<view class="tui-tabbar-item" :class="{ 'tui-item-hump': item.hump }"
:style="{ backgroundColor: item.hump && !backdropFilter ? backgroundColor : 'none' }"
@tap="tabbarSwitch(index, item.hump, item.pagePath, item.verify)">
<view class="tui-icon-box" :class="{ 'tui-tabbar-hump': item.hump }">
<image :src="current == index ? item.selectedIconPath : item.iconPath"
:class="[item.hump ? '' : 'tui-tabbar-icon']" v-if="!item.name"></image>
<tui-icon :name="current===index?item.activeName:item.name" :customPrefix="item.customPrefix || ''"
:size="item.iconSize || iconSize" unit="rpx" :color="current == index?getActiveColor:color"
v-else></tui-icon>
<view :class="[item.isDot ? 'tui-badge-dot' : 'tui-badge']"
:style="{ color: badgeColor, backgroundColor: getBadgeBgColor }" v-if="item.num">
{{ item.isDot ? '' : item.num }}
</view>
</view>
<view class="tui-text-scale" :class="{ 'tui-text-hump': item.hump }"
:style="{ color: current == index ? getActiveColor : color }">{{ item.text }}</view>
</view>
</block>
<view :style="{ background: backgroundColor }" :class="{ 'tui-hump-box': hump }"
v-if="hump && !unlined && !backdropFilter"></view>
</view>
</template>
<script>
import tuiIcon from '@/components/thorui/tui-icon/tui-icon.vue'
export default {
name: 'tuiTabbar',
emits: ['click'],
components:{
tuiIcon
},
props: {
//当前索引
current: {
type: Number,
default: 0
},
//字体颜色
color: {
type: String,
default: '#666'
},
//字体选中颜色
selectedColor: {
type: String,
default: ''
},
//背景颜色
backgroundColor: {
type: String,
default: '#FFFFFF'
},
//是否需要中间凸起按钮
hump: {
type: Boolean,
default: false
},
iconSize: {
type: [Number, String],
default: 52
},
//固定在底部
isFixed: {
type: Boolean,
default: true
},
tabBar: {
type: Array,
default () {
return [];
}
},
//角标字体颜色
badgeColor: {
type: String,
default: '#fff'
},
//角标背景颜色
badgeBgColor: {
type: String,
default: ''
},
unlined: {
type: Boolean,
default: false
},
//是否开启高斯模糊效果[仅在支持的浏览器有效果]
backdropFilter: {
type: Boolean,
default: false
},
//z-index
zIndex: {
type: [Number, String],
default: 9999
}
},
data() {
return {};
},
computed:{
getActiveColor(){
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
},
getBadgeBgColor(){
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54';
}
},
methods: {
tabbarSwitch(index, hump, pagePath, verify) {
this.$emit('click', {
index: index,
hump: hump,
pagePath: pagePath,
verify: verify
});
}
}
};
</script>
<style scoped>
.tui-tabbar {
width: 100%;
height: 100rpx;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.tui-backdrop__filter {
/* Safari for macOS & iOS */
-webkit-backdrop-filter: blur(15px);
/* Google Chrome */
backdrop-filter: blur(15px);
}
.tui-tabbar-fixed {
position: fixed;
left: 0;
bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box !important;
}
.tui-tabbar::before {
content: ' ';
width: 100%;
border-top: 1px solid #b2b2b2;
position: absolute;
top: -1rpx;
left: 0;
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 0;
display: block;
z-index: 3;
}
.tui-tabbar-item {
height: 100%;
flex: 1;
display: flex;
text-align: center;
align-items: center;
flex-direction: column;
justify-content: space-between;
position: relative;
padding: 10rpx 0;
box-sizing: border-box;
z-index: 5;
}
.tui-icon-box {
position: relative;
}
.tui-item-hump {
height: 98rpx;
}
.tui-tabbar-icon {
width: 52rpx;
height: 52rpx;
display: block;
}
.tui-hump-box {
width: 120rpx;
height: 120rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -50rpx;
border-radius: 50%;
z-index: 4;
}
.tui-hump-box::after {
content: ' ';
height: 200%;
width: 200%;
border: 1px solid #b2b2b2;
position: absolute;
top: 0;
left: 0;
transform: scale(0.5) translateZ(0);
transform-origin: 0 0;
border-radius: 120rpx;
box-sizing: border-box;
display: block;
}
.tui-unlined::after {
height: 0 !important;
}
.tui-tabbar-hump {
width: 100rpx;
height: 100rpx;
position: absolute;
left: 50%;
-webkit-transform: translateX(-50%) rotate(0deg);
transform: translateX(-50%) rotate(0deg);
top: -40rpx;
-webkit-transition: all 0.2s linear;
transition: all 0.2s linear;
border-radius: 50%;
z-index: 5;
}
.tui-tabbar-hump image {
width: 100rpx;
height: 100rpx;
display: block;
}
.tui-hump-active {
-webkit-transform: translateX(-50%) rotate(135deg);
transform: translateX(-50%) rotate(135deg);
}
.tui-text-scale {
transform: scale(0.8);
font-size: 25rpx;
line-height: 28rpx;
transform-origin: center 100%;
}
.tui-text-hump {
position: absolute;
left: 50%;
bottom: 10rpx;
transform: scale(0.8) translateX(-50%);
transform-origin: 0 100%;
}
.tui-badge {
position: absolute;
font-size: 24rpx;
height: 32rpx;
min-width: 20rpx;
padding: 0 6rpx;
border-radius: 40rpx;
right: 0;
top: -5rpx;
transform: translateX(70%);
display: flex;
align-items: center;
justify-content: center;
}
.tui-badge-dot {
position: absolute;
height: 16rpx;
width: 16rpx;
border-radius: 50%;
right: -4rpx;
top: -4rpx;
}
</style>

View File

@@ -0,0 +1,346 @@
<template>
<view class="tui-tabs-view"
:class="[isFixed ? 'tui-tabs-fixed' : 'tui-tabs-relative', unlined ? 'tui-unlined' : '']" :style="{
width: tabsWidth + 'px',
height: height + 'rpx',
padding: `0 ${padding}rpx`,
background: backgroundColor,
top: isFixed ? top + 'px' : 'auto',
zIndex: isFixed ? zIndex : 'auto'
}" v-if="tabsWidth>0">
<view v-for="(item, index) in tabs" :key="index" class="tui-tabs-item"
:style="{ width: getItemWidth,height: height + 'rpx' }" @tap.stop="swichTabs(index)">
<view class="tui-tabs-title" :class="{'tui-tabs-disabled': item.disabled }" :style="{
color: currentTab == index ? getSelectedColor : color,
fontSize: size + 'rpx',
fontWeight: bold && currentTab == index ? 'bold' : 'normal',transform:`scale(${currentTab == index?scale:1})`
}">
{{ item[field] }} <text v-if="item[badgeField]">({{item[badgeField]}})</text>
<view :class="[item.isDot ? 'tui-badge__dot' : 'tui-tabs__badge']"
:style="{ color: badgeColor, backgroundColor: getBadgeBgColor }"
v-if="item.isDot">
{{ item.isDot ? '' : item[badgeField] }}
</view>
</view>
</view>
<view v-if="isSlider" class="tui-tabs-slider" :style="{
transform: 'translateX(' + scrollLeft + 'px)',
width: sliderWidth + 'rpx',
height: sliderHeight + 'rpx',
borderRadius: sliderRadius,
bottom: bottom,
background: getSliderBgColor,
marginBottom: bottom == '50%' ? '-' + sliderHeight / 2 + 'rpx' : 0
}"></view>
</view>
</template>
<script>
export default {
name: 'tuiTabs',
emits: ['change'],
props: {
//标签页
tabs: {
type: Array,
default () {
return [];
}
},
//显示文本字段名称
field: {
type: String,
default: 'name'
},
badgeField: {
type: String,
default: 'num'
},
//tabs宽度不传值则默认使用windowWidth单位px
width: {
type: [Number, String],
default: 0
},
//rpx
height: {
type: Number,
default: 80
},
//rpx 只对左右padding起作用上下为0
padding: {
type: Number,
default: 30
},
//背景色
backgroundColor: {
type: String,
default: '#FFFFFF'
},
//是否固定
isFixed: {
type: Boolean,
default: false
},
//px
top: {
type: Number,
// #ifndef H5
default: 0,
// #endif
// #ifdef H5
default: 44
// #endif
},
//是否去掉底部线条
unlined: {
type: Boolean,
default: false
},
//当前选项卡
currentTab: {
type: Number,
default: 0
},
isSlider: {
type: Boolean,
default: true
},
//滑块宽度
sliderWidth: {
type: Number,
default: 68
},
//滑块高度
sliderHeight: {
type: Number,
default: 6
},
//滑块背景颜色
sliderBgColor: {
type: String,
default: ''
},
sliderRadius: {
type: String,
default: '50rpx'
},
//滑块bottom
bottom: {
type: String,
default: '0'
},
//标签页宽度
itemWidth: {
type: String,
default: ''
},
//字体颜色
color: {
type: String,
default: '#666'
},
//选中后字体颜色
selectedColor: {
type: String,
default: ''
},
//字体大小
size: {
type: Number,
default: 28
},
//选中后 是否加粗 ,未选中则无效
bold: {
type: Boolean,
default: false
},
//2.3.0+
scale: {
type: [Number, String],
default: 1
},
//角标字体颜色
badgeColor: {
type: String,
default: '#fff'
},
//角标背景颜色
badgeBgColor: {
type: String,
default: ''
},
zIndex: {
type: [Number, String],
default: 996
}
},
watch: {
currentTab() {
this.checkCor();
},
tabs() {
this.checkCor();
},
width(val) {
this.tabsWidth = val;
this.checkCor();
}
},
computed: {
getItemWidth() {
let width = 100 / (this.tabs.length || 4) + '%'
return this.itemWidth ? this.itemWidth : width
},
getSliderBgColor() {
return this.sliderBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
},
getSelectedColor() {
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
},
getBadgeBgColor() {
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54';
}
},
created() {
// 高度自适应
setTimeout(() => {
uni.getSystemInfo({
success: res => {
this.winWidth = res.windowWidth;
this.tabsWidth = this.width == 0 ? this.winWidth : Number(this.width);
this.checkCor();
}
});
}, 0);
},
data() {
return {
winWidth: 0,
tabsWidth: 0,
scrollLeft: 0
};
},
methods: {
checkCor: function() {
let tabsNum = this.tabs.length;
let padding = uni.upx2px(Number(this.padding));
let width = this.tabsWidth - padding * 2;
let left = (width / tabsNum - uni.upx2px(Number(this.sliderWidth))) / 2 + padding;
let scrollLeft = left;
if (this.currentTab > 0) {
scrollLeft = scrollLeft + (width / tabsNum) * this.currentTab;
}
this.scrollLeft = scrollLeft;
},
// 点击标题切换当前页时改变样式
swichTabs: function(index) {
let item = this.tabs[index];
if (item && item.disabled) return;
if (this.currentTab == index) {
return false;
} else {
this.$emit('change', {
index: Number(index),
item: item
});
}
}
}
};
</script>
<style scoped>
.tui-tabs-view {
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
}
.tui-tabs-relative {
position: relative;
}
.tui-tabs-fixed {
position: fixed;
left: 0;
}
.tui-tabs-fixed::before,
.tui-tabs-relative::before {
content: '';
position: absolute;
border-bottom: 1rpx solid #eaeef1;
-webkit-transform: scaleY(0.5) translateZ(0);
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 100%;
bottom: 0;
right: 0;
left: 0;
}
.tui-unlined::before {
border-bottom: 0 !important;
}
.tui-tabs-item {
display: flex;
align-items: center;
justify-content: center;
overflow: visible;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.tui-tabs-disabled {
opacity: 0.6;
}
.tui-tabs-title {
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 3;
overflow: visible;
transition: all 0.15s ease-in-out;
}
.tui-tabs-slider {
position: absolute;
left: 0;
transition: all 0.3s ease-in-out;
z-index: 1;
transform-style: preserve-3d;
}
.tui-tabs__badge {
position: absolute;
font-size: 24rpx;
height: 32rpx;
min-width: 20rpx;
padding: 0 6rpx;
border-radius: 40rpx;
right: 0;
top: 0;
transform: translate(88%, -50%);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
z-index: 4;
font-weight: normal !important;
}
.tui-badge__dot {
position: absolute;
height: 16rpx;
width: 16rpx;
border-radius: 50%;
right: -10rpx;
top: -10rpx;
z-index: 4;
}
</style>