项目初始化
This commit is contained in:
89
components/custom-back-to-top/custom-back-to-top.vue
Normal file
89
components/custom-back-to-top/custom-back-to-top.vue
Normal 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>
|
831
components/htz-image-upload/htz-image-upload.vue
Normal file
831
components/htz-image-upload/htz-image-upload.vue
Normal 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: { //选择照片来源 【ps:H5就别费劲了,设置了也没用。不是我说的,官方文档就这样!!!】
|
||||
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: { //压缩质量,范围0~100
|
||||
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>
|
121
components/qrcode-modal/qrcode-modal.vue
Normal file
121
components/qrcode-modal/qrcode-modal.vue
Normal 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>
|
356
components/search-modal/search-modal.vue
Normal file
356
components/search-modal/search-modal.vue
Normal 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>
|
201
components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
201
components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal 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>
|
129
components/thorui/tui-badge/tui-badge.vue
Normal file
129
components/thorui/tui-badge/tui-badge.vue
Normal 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,white,black,gray,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>
|
119
components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
119
components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal 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>
|
204
components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
204
components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal 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>
|
@@ -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>
|
3
components/thorui/tui-icon/tui-icon.js
Normal file
3
components/thorui/tui-icon/tui-icon.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
|
||||
}
|
82
components/thorui/tui-icon/tui-icon.vue
Normal file
82
components/thorui/tui-icon/tui-icon.vue
Normal file
File diff suppressed because one or more lines are too long
338
components/thorui/tui-modal/tui-modal.vue
Normal file
338
components/thorui/tui-modal/tui-modal.vue
Normal 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>
|
258
components/thorui/tui-navigation-bar/tui-navigation-bar.vue
Normal file
258
components/thorui/tui-navigation-bar/tui-navigation-bar.vue
Normal 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>
|
248
components/thorui/tui-numberbox/tui-numberbox.vue
Normal file
248
components/thorui/tui-numberbox/tui-numberbox.vue
Normal 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>
|
166
components/thorui/tui-sticky/tui-sticky.vue
Normal file
166
components/thorui/tui-sticky/tui-sticky.vue
Normal 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>
|
287
components/thorui/tui-tabbar/tui-tabbar.vue
Normal file
287
components/thorui/tui-tabbar/tui-tabbar.vue
Normal 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>
|
346
components/thorui/tui-tabs/tui-tabs.vue
Normal file
346
components/thorui/tui-tabs/tui-tabs.vue
Normal 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>
|
Reference in New Issue
Block a user