Merge branch 'test' of https://e.coding.net/g-cphe0354/duanju/video_app into gaohao
This commit is contained in:
72
App.vue
72
App.vue
@@ -1,6 +1,6 @@
|
||||
|
||||
<script>
|
||||
import config from '@/common/config.js'
|
||||
import store from './store/index.js'
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
let that = this
|
||||
@@ -276,7 +276,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// setInterval(d => { //定时器,定时去调取聊天未读消息
|
||||
@@ -300,8 +300,8 @@
|
||||
// });
|
||||
// }
|
||||
// }, 3000);
|
||||
|
||||
|
||||
|
||||
|
||||
//#ifdef H5
|
||||
|
||||
this.$u.get('/app/common/type/108').then(res => { //// 是否开启公众号自动登陆 108
|
||||
@@ -369,13 +369,13 @@
|
||||
plus.runtime.restart();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
//获取当前系统版本信息
|
||||
plus.runtime.getProperty(plus.runtime.appid, widgetInfo => {
|
||||
//请求后台接口 解析数据 对比版本
|
||||
that.$Request.getT('/app/user/selectNewApp').then(res => {
|
||||
res = res.data[0];
|
||||
|
||||
|
||||
if (widgetInfo.version < res.version) {
|
||||
let downloadLink = '';
|
||||
let androidLink = res.androidWgtUrl;
|
||||
@@ -391,7 +391,7 @@
|
||||
console.log('发现下载地址');
|
||||
// 安卓:创建下载任务
|
||||
if (androidLink.match(RegExp(/.wgt/))) {
|
||||
console.log('确认wgt热更新包');
|
||||
console.log('确认wgt热更新包');
|
||||
downloadLink = androidLink;
|
||||
ready = true;
|
||||
} else {
|
||||
@@ -401,8 +401,8 @@
|
||||
console.log('下载地址是空的,无法继续');
|
||||
}
|
||||
} else {
|
||||
console.log('苹果系统');
|
||||
if (iosLink && iosLink !== '#') {
|
||||
console.log('苹果系统');
|
||||
if (iosLink && iosLink !== '#') {
|
||||
// 我这里默认#也是没有地址,请根据业务自行修改
|
||||
console.log('发现下载地址');
|
||||
// 苹果(A):进行热更新(如果iosLink是wgt更新包的下载地址)判断文件名中是否含有.wgt
|
||||
@@ -460,7 +460,7 @@
|
||||
}
|
||||
} else {
|
||||
//不是热更新是在线更新 校验是否强制升级
|
||||
|
||||
|
||||
if (res.method == "true") {
|
||||
uni.showModal({
|
||||
showCancel: false,
|
||||
@@ -469,8 +469,9 @@
|
||||
content: res.des,
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
plus.runtime.openURL(config.APIHOST2+'/pages/login/appEq')
|
||||
return
|
||||
plus.runtime.openURL(config.APIHOST2 +
|
||||
'/pages/login/appEq')
|
||||
return
|
||||
uni.showLoading({
|
||||
title: '下载中...',
|
||||
mask: true
|
||||
@@ -480,10 +481,18 @@
|
||||
uni.downloadFile({
|
||||
url: androidLink,
|
||||
success: downloadResult => {
|
||||
console.log(downloadResult)
|
||||
if (downloadResult.statusCode === 200) {
|
||||
console.log(
|
||||
downloadResult
|
||||
)
|
||||
if (downloadResult
|
||||
.statusCode ===
|
||||
200) {
|
||||
plus.runtime
|
||||
.install(downloadResult.tempFilePath, { force: false },
|
||||
.install(
|
||||
downloadResult
|
||||
.tempFilePath, {
|
||||
force: false
|
||||
},
|
||||
d => {
|
||||
console
|
||||
.log(
|
||||
@@ -493,7 +502,10 @@
|
||||
.restart();
|
||||
},
|
||||
e => {
|
||||
console.log(e)
|
||||
console
|
||||
.log(
|
||||
e
|
||||
)
|
||||
console
|
||||
.error(
|
||||
'install fail...'
|
||||
@@ -504,11 +516,11 @@
|
||||
// entry.getParent(_oldFile=>{
|
||||
// entry.moveTo(_oldFile,'.apk',newFilePath=>{
|
||||
// console.log('newFilePath',newFilePath.fullPath)
|
||||
|
||||
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -523,7 +535,7 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '发现新版本',
|
||||
confirmText: '立即更新',
|
||||
@@ -531,8 +543,9 @@
|
||||
content: res.des,
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
plus.runtime.openURL(config.APIHOST2+'/pages/login/appEq')
|
||||
return
|
||||
plus.runtime.openURL(config.APIHOST2 +
|
||||
'/pages/login/appEq')
|
||||
return
|
||||
uni.showLoading({
|
||||
title: '下载中...',
|
||||
mask: true
|
||||
@@ -542,9 +555,15 @@
|
||||
uni.downloadFile({
|
||||
url: androidLink,
|
||||
success: downloadResult => {
|
||||
if (downloadResult.statusCode === 200) {
|
||||
if (downloadResult
|
||||
.statusCode ===
|
||||
200) {
|
||||
plus.runtime
|
||||
.install(downloadResult.tempFilePath, { force: false },
|
||||
.install(
|
||||
downloadResult
|
||||
.tempFilePath, {
|
||||
force: false
|
||||
},
|
||||
d => {
|
||||
console
|
||||
.log(
|
||||
@@ -554,7 +573,10 @@
|
||||
.restart();
|
||||
},
|
||||
e => {
|
||||
console.log(e)
|
||||
console
|
||||
.log(
|
||||
e
|
||||
)
|
||||
console
|
||||
.error(
|
||||
'install fail...'
|
||||
@@ -840,4 +862,6 @@
|
||||
@import 'components/colorui/main.css';
|
||||
@import 'components/colorui/icon.css';
|
||||
@import '@/common/style/common.scss';
|
||||
@import './tuniao-ui/index.scss';
|
||||
@import './tuniao-ui/iconfont.css';
|
||||
</style>
|
||||
8
common/config/config.js
Normal file
8
common/config/config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
baseUrl: 'http://127.0.0.1/',
|
||||
baseApi: 'http://127.0.0.1:7001/',
|
||||
color:{
|
||||
main:'#FFC428',
|
||||
red:'#fa3534'
|
||||
}
|
||||
}
|
||||
458
components/other-xuafu.vue
Normal file
458
components/other-xuafu.vue
Normal file
@@ -0,0 +1,458 @@
|
||||
<template>
|
||||
<button @click="navThanks" v-if="show">
|
||||
<view class="dong">
|
||||
<view class="monster">
|
||||
<view class="monster__face">
|
||||
<view class="monster__eye avatar-eye avatar-eye--green eye--left">
|
||||
<view class="avatar-eye-pupil pupil--green"><span class="avatar-eye-pupil-blackThing"><span
|
||||
class="avatar-eye-pupil-lightReflection"></span></span></view>
|
||||
</view>
|
||||
<view class="monster__eye avatar-eye avatar-eye--violet eye--right">
|
||||
<view class="avatar-eye-pupil pupil--pink"><span class="avatar-eye-pupil-blackThing"><span
|
||||
class="avatar-eye-pupil-lightReflection"></span></span></view>
|
||||
</view>
|
||||
<view class="monster__noses">
|
||||
<view class="monster__nose"></view>
|
||||
<view class="monster__nose"></view>
|
||||
</view>
|
||||
<view class="monster__mouth">
|
||||
<view class="monster__top"></view>
|
||||
<view class="monster__bottom"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {returnIsShenhe} from '@/utils/api.js'
|
||||
import {isIos} from '@/utils/app.js'
|
||||
export default {
|
||||
name: "other-xuanu",
|
||||
data() {
|
||||
return {
|
||||
show:false
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
navThanks(){
|
||||
uni.navigateTo({
|
||||
url:'/other/index/index'
|
||||
})
|
||||
},
|
||||
async init(){
|
||||
const isShehe=await returnIsShenhe()
|
||||
this.show=isShehe
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef APP
|
||||
if(isIos()){
|
||||
// this.init()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* 大嘴鸟*/
|
||||
.dong {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: -80px;
|
||||
transform: scale(0.24);
|
||||
-webkit-transform: scale(0.24);
|
||||
-moz-transform: scale(0.24);
|
||||
|
||||
}
|
||||
|
||||
.monster {
|
||||
transform: rotate(-50deg);
|
||||
-ms-transform: rotate(-50deg);
|
||||
/* IE 9 */
|
||||
-moz-transform: rotate(-50deg);
|
||||
/* Firefox */
|
||||
-webkit-transform: rotate(-50deg);
|
||||
/* Safari 和 Chrome */
|
||||
-o-transform: rotate(-50deg);
|
||||
/* Opera */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 170px;
|
||||
height: 400px;
|
||||
border-top-left-radius: 200px;
|
||||
border-top-right-radius: 200px;
|
||||
background-color: rgb(255, 117, 129);
|
||||
box-shadow: 20px 20px 60px rgba(255, 117, 129,.7);
|
||||
}
|
||||
|
||||
.monster__face {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 14%;
|
||||
width: 75%;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.monster__noses {
|
||||
top: 50%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 28%;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.monster__nose {
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
border-radius: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 4px 8px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.monster__mouth {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
overflow: hidden;
|
||||
border: 25px solid #FFC333;
|
||||
border-radius: 100px;
|
||||
background-color: #810332;
|
||||
animation: mouth 1.75s infinite;
|
||||
box-shadow: 4px 8px 5px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monster__mouth::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 80px;
|
||||
border-radius: 100px;
|
||||
background-color: #400018;
|
||||
}
|
||||
|
||||
.monster__mouth::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -80px;
|
||||
width: 160px;
|
||||
height: 80px;
|
||||
border-top-left-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
background-color: #DC1B50;
|
||||
animation: tongue 1.75s infinite;
|
||||
}
|
||||
|
||||
.monster__top {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
width: 170px;
|
||||
height: 30px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
z-index: 100;
|
||||
animation: t 1.75s infinite;
|
||||
}
|
||||
|
||||
.monster__bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
background-color: #ffffff;
|
||||
z-index: 100;
|
||||
animation: b 1.75s infinite;
|
||||
}
|
||||
|
||||
|
||||
.avatar-eye {
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
background: linear-gradient(105deg, white, #cb87f4);
|
||||
border-radius: 100%;
|
||||
box-shadow: 4px 8px 5px rgba(0, 0, 0, 0.2);
|
||||
margin: 3px;
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
|
||||
.avatar-eye--green {
|
||||
background: linear-gradient(to bottom, #fdfdfd, #c3efea);
|
||||
}
|
||||
|
||||
.avatar-eye--violet {
|
||||
background: linear-gradient(to bottom, #fdfdfd, #e6d6f6);
|
||||
}
|
||||
|
||||
|
||||
.eye--left {
|
||||
left: 10%;
|
||||
}
|
||||
|
||||
.eye--right {
|
||||
left: 85%;
|
||||
}
|
||||
|
||||
.eye--center {
|
||||
left: 45%;
|
||||
top: 10%;
|
||||
}
|
||||
|
||||
.avatar-eye-pupil {
|
||||
position: absolute;
|
||||
width: 55%;
|
||||
height: 55%;
|
||||
left: 50%;
|
||||
top: 25%;
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
z-index: 100;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
|
||||
.pupil--green {
|
||||
background: linear-gradient(135deg, rgba(188, 248, 177, 0.7), #2fa38c 75%);
|
||||
}
|
||||
|
||||
.pupil--pink {
|
||||
background: linear-gradient(135deg, #f1a183, #8535cd);
|
||||
}
|
||||
|
||||
|
||||
.avatar-eye-pupil-blackThing {
|
||||
position: absolute;
|
||||
width: 55%;
|
||||
height: 55%;
|
||||
left: 50%;
|
||||
top: 25%;
|
||||
background: #2c2f32;
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
border-radius: 100%;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.avatar-eye-pupil-lightReflection {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
left: 25%;
|
||||
top: 10%;
|
||||
background: #ebebeb;
|
||||
-webkit-transform: translate(-50%);
|
||||
transform: translate(-50%);
|
||||
border-radius: 100%;
|
||||
box-shadow: 10px 10px 10px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
/*大嘴动起来*/
|
||||
@keyframes t {
|
||||
|
||||
0%,
|
||||
10%,
|
||||
80%,
|
||||
100% {
|
||||
top: -30px;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
30% {
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
40% {
|
||||
top: -0px;
|
||||
}
|
||||
|
||||
50% {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
70% {
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes b {
|
||||
|
||||
0%,
|
||||
10%,
|
||||
80%,
|
||||
100% {
|
||||
bottom: -30px;
|
||||
}
|
||||
|
||||
20% {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
30% {
|
||||
bottom: -20px;
|
||||
}
|
||||
|
||||
40% {
|
||||
bottom: -0px;
|
||||
}
|
||||
|
||||
50% {
|
||||
bottom: -25px;
|
||||
}
|
||||
|
||||
70% {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mouth {
|
||||
|
||||
0%,
|
||||
10%,
|
||||
100% {
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
15% {
|
||||
width: 90%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
20% {
|
||||
width: 50%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
25% {
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
30% {
|
||||
width: 80%;
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
35% {
|
||||
width: 60%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
40% {
|
||||
width: 55%;
|
||||
height: 75%;
|
||||
}
|
||||
|
||||
45% {
|
||||
width: 50%;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
50% {
|
||||
width: 40%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
55% {
|
||||
width: 70%;
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
60% {
|
||||
width: 40%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
65% {
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 100%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
75% {
|
||||
width: 90%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
80% {
|
||||
width: 50%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
85% {
|
||||
width: 90%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
85% {
|
||||
width: 40%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
90% {
|
||||
width: 90%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
95% {
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tongue {
|
||||
|
||||
0%,
|
||||
20%,
|
||||
100% {
|
||||
bottom: -80px;
|
||||
}
|
||||
|
||||
30%,
|
||||
90% {
|
||||
bottom: -40px;
|
||||
}
|
||||
|
||||
40% {
|
||||
bottom: -45px;
|
||||
}
|
||||
|
||||
50% {
|
||||
bottom: -50px;
|
||||
}
|
||||
|
||||
70% {
|
||||
bottom: -80px;
|
||||
}
|
||||
|
||||
90% {
|
||||
bottom: -40px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
94
libs/components/demo-title.vue
Normal file
94
libs/components/demo-title.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<view class="demo-title">
|
||||
<view>
|
||||
<view v-if="type === 'first'" class="main_title">
|
||||
<view v-if="leftIcon" class="main_title__icon main_title__icon--left" :class="[`tn-icon-${leftIcon}`]"></view>
|
||||
<view class="main_title__content">{{ title }}</view>
|
||||
<view v-if="rightIcon" class="main_title__icon main_title__icon--right" :class="[`tn-icon-${rightIcon}`]"></view>
|
||||
</view>
|
||||
<view v-if="type === 'second'" class="second_title">
|
||||
<view class="second_title__content">{{ title }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content" :class="[{
|
||||
'content--padding': contentPadding
|
||||
}]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'demo-title',
|
||||
options: {
|
||||
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
|
||||
virtualHost: true
|
||||
},
|
||||
props: {
|
||||
// 标题类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'first'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: 'star'
|
||||
},
|
||||
// 右图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: 'star'
|
||||
},
|
||||
// 内容容器是否有两边边距
|
||||
contentPadding: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main_title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 50rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&__content {
|
||||
padding: 0 18rpx;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.second_title {
|
||||
margin: 24rpx 0;
|
||||
margin-left: 30rpx;
|
||||
|
||||
&__content {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 30rpx;
|
||||
|
||||
&--padding {
|
||||
margin-left: 30rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
689
libs/components/dynamic-demo-template.vue
Normal file
689
libs/components/dynamic-demo-template.vue
Normal file
@@ -0,0 +1,689 @@
|
||||
<template>
|
||||
<view class="dynamic-demo">
|
||||
|
||||
<!-- 效果预览窗口 -->
|
||||
<view v-if="!noDemo" class="demo-container" :class="{'demo-container--full': full}">
|
||||
<view class="demo">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<!-- 提示信息 -->
|
||||
<view v-if="haveTips">
|
||||
<view class="demo__tips__icon" @click="demoTipsClick">
|
||||
<view class="icon tn-icon-help"></view>
|
||||
</view>
|
||||
<view class="demo__tips__content"
|
||||
:class="[showContentTips ? 'demo__tips__content--show' : 'demo__tips__content--hide']">
|
||||
<view v-for="(item,index) in tipsData" :key="index" class="demo__tips__content--item">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模式切换 -->
|
||||
<view v-if="multiMode" class="mode-switch">
|
||||
<view class="mode-switch__container">
|
||||
<view v-for="(item, index) in sectionModeListInfos" :key="index" class="mode-switch__item"
|
||||
:class="[`mode-switch-item-${index}`,{'mode-switch__item--active': modeIndex === index}]"
|
||||
@click="switchMode(index)">{{ item.name }}</view>
|
||||
|
||||
<!-- 滑块样式 -->
|
||||
<view class="mode-switch__slider" :style="[modeSwitchSliderStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 组件对应可选项容器 -->
|
||||
<view class="section-container">
|
||||
<scroll-view
|
||||
class="section__scroll-view"
|
||||
:class="{'section__scroll-view--auto': sectionScrollViewStyle.height === 'auto'}"
|
||||
:style="[sectionScrollViewStyle]"
|
||||
:scroll-y="sectionScrollViewStyle.height !== 'auto'"
|
||||
>
|
||||
<block v-for="(item,index) in btnsList" :key="index">
|
||||
<view class="section__content" :class="{'section__content--visible': item.show}">
|
||||
<view class="section__content__title">
|
||||
<view class="section__content__title__left-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
|
||||
<view class="section__content__title--text tn-text-ellipsis" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]">{{ item.title }}</view>
|
||||
<view class="section__content__title__right-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
|
||||
</view>
|
||||
<view class="section__content__btns">
|
||||
<view v-for="(section_btn,section_index) in item.optional" :key="section_index"
|
||||
class="section__content__btns__item" :class="[`tn-main-gradient-${tuniaoColorList[index]}--light`]" @click="sectionBtnClick(index, section_index)">
|
||||
<view class="section__content__btns__item__bg"
|
||||
:class="[`tn-main-gradient-${tuniaoColorList[index]}`, {'section__content__btns__item__bg--active':sectionIndex[modeIndex][index]['value'] === section_index}]"></view>
|
||||
<view class="section__content__btns__item--text tn-text-ellipsis"
|
||||
:class="[sectionIndex[modeIndex][index]['value'] === section_index ? 'section__content__btns__item--text--active' : `tn-color-${tuniaoColorList[index]}`]">{{ section_btn }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'dynamic-demo-template',
|
||||
props: {
|
||||
// 可选项列表数据
|
||||
sectionList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 提示信息
|
||||
tips: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
// 演示框的内容是否为铺满
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否使用了自定义顶部导航栏
|
||||
customBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否全屏滚动
|
||||
fullWindowsScroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 没有演示内容
|
||||
noDemo: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tipsData() {
|
||||
if (typeof this.tips === 'string') {
|
||||
return [this.tips]
|
||||
}
|
||||
return this.tips
|
||||
},
|
||||
haveTips() {
|
||||
return this.tips && this.tips.length > 0
|
||||
},
|
||||
multiMode() {
|
||||
return this.sectionList.length > 1
|
||||
},
|
||||
sectionModeList() {
|
||||
return this.sectionList.map((item) => {
|
||||
return item.name
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图鸟颜色列表
|
||||
tuniaoColorList: this.$t.color.getTuniaoColorList(),
|
||||
// 保存选项列表信息(由于prop中的数据时不能被修改的)
|
||||
_sectionList: [],
|
||||
// 模式列表信息
|
||||
sectionModeListInfos: [],
|
||||
// 所选模式的序号
|
||||
modeIndex: 0,
|
||||
// 模式选择滑块样式
|
||||
modeSwitchSliderStyle: {
|
||||
width: 0,
|
||||
left: 0
|
||||
},
|
||||
// 显示组件相关提示信息
|
||||
showContentTips: false,
|
||||
// 可选项滚动容器样式
|
||||
sectionScrollViewStyle: {
|
||||
height: 0
|
||||
},
|
||||
// 按钮列表信息
|
||||
btnsList: [],
|
||||
// 标记当前所选按钮
|
||||
sectionIndex: [],
|
||||
// 标记选项按钮是否可以滑动(使用scroll-view进行包裹)
|
||||
sectionScrollFlag: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
sectionList: {
|
||||
handler(value) {
|
||||
// 如果sectionList发生改变,重新初始化选项列表信息
|
||||
this.initSectionBtns()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
sectionScrollFlag(value) {
|
||||
if (!value) {
|
||||
this.sectionScrollViewStyle.height = 'auto'
|
||||
}
|
||||
},
|
||||
fullWindowsScroll: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.sectionScrollViewStyle.height = 'auto'
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化可选项模式列表
|
||||
this.sectionModeListInfos = this.sectionModeList.map((item) => {
|
||||
return {
|
||||
name: item
|
||||
}
|
||||
})
|
||||
// 初始化选项按钮默认信息
|
||||
this.initSectionBtns()
|
||||
},
|
||||
mounted() {
|
||||
// 等待加载组件完成
|
||||
// setTimeout(() => {
|
||||
// // 计算出底部scroll-view的高度
|
||||
// this.initSectionScrollView()
|
||||
|
||||
// if (this.multiMode) {
|
||||
// // 获取模式切换标签的信息
|
||||
// this.getModeTabsInfo()
|
||||
// }
|
||||
// }, 10)
|
||||
this.$nextTick(() => {
|
||||
// 计算出底部scroll-view的高度
|
||||
this.initSectionScrollView()
|
||||
|
||||
if (this.multiMode) {
|
||||
// 获取模式切换标签的信息
|
||||
this.getModeTabsInfo()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化选项滑动窗口的高度
|
||||
initSectionScrollView() {
|
||||
// 全屏滚动时不进行任何的操作
|
||||
if (this.fullWindowsScroll) {
|
||||
return
|
||||
}
|
||||
// 获取屏幕的高度
|
||||
uni.getSystemInfo({
|
||||
success: (systemInfo) => {
|
||||
// 通过当前屏幕的安全高度减去上一个元素的底部和距离上一个元素的外边距,然后减获取到的值减去标题栏的高度即可
|
||||
const navBarHeight = this.customBar ? 0 : this.vuex_custom_bar_height
|
||||
if (this.multiMode) {
|
||||
uni.createSelectorQuery().in(this).select('.mode-switch').boundingClientRect(data => {
|
||||
if (data.bottom >= systemInfo.safeArea.height) {
|
||||
this.sectionScrollFlag = false
|
||||
} else {
|
||||
this.sectionScrollFlag = true
|
||||
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
|
||||
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
|
||||
}
|
||||
}).exec()
|
||||
} else {
|
||||
if (!this.noDemo) {
|
||||
uni.createSelectorQuery().in(this).select('.demo-container').boundingClientRect(data => {
|
||||
if (data.bottom >= systemInfo.safeArea.height) {
|
||||
this.sectionScrollFlag = false
|
||||
} else {
|
||||
this.sectionScrollFlag = true
|
||||
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
|
||||
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
|
||||
}
|
||||
}).exec()
|
||||
} else {
|
||||
this.sectionScrollFlag = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
// 更新选项滑动容器的高度
|
||||
updateSectionScrollView() {
|
||||
this.$nextTick(() => {
|
||||
this.initSectionScrollView()
|
||||
})
|
||||
},
|
||||
// 获取各个模式tab的节点信息
|
||||
getModeTabsInfo() {
|
||||
let view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.sectionModeListInfos.length; i++) {
|
||||
view.select('.mode-switch-item-' + i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
// 如果没有获取到,则重新获取
|
||||
if (!res.length) {
|
||||
setTimeout(() => {
|
||||
this.getModeTabsInfo()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
// 将每个模式的宽度放入list中
|
||||
res.map((item, index) => {
|
||||
this.sectionModeListInfos[index].width = item.width
|
||||
})
|
||||
// 初始化滑块的宽度
|
||||
this.modeSwitchSliderStyle.width = this.sectionModeListInfos[0].width + 'px'
|
||||
|
||||
// 初始化滑块的位置
|
||||
this.modeSliderPosition()
|
||||
})
|
||||
},
|
||||
// 设置模式滑块的位置
|
||||
modeSliderPosition() {
|
||||
let left = 0
|
||||
// 计算当前所选模式选项到组件左边的距离
|
||||
this.sectionModeListInfos.map((item, index) => {
|
||||
if (index < this.modeIndex) left += item.width
|
||||
})
|
||||
|
||||
this.modeSwitchSliderStyle.left = left + 'px'
|
||||
},
|
||||
// 切换模式
|
||||
switchMode(index) {
|
||||
// 不允许点击当前激活的选项
|
||||
if (index === this.modeIndex) return
|
||||
this.modeIndex = index
|
||||
this.modeSliderPosition()
|
||||
this.updateSectionBtns()
|
||||
this.$emit('modeClick', {
|
||||
index: index
|
||||
})
|
||||
},
|
||||
// 点击内容提示信息
|
||||
demoTipsClick() {
|
||||
this.showContentTips = !this.showContentTips
|
||||
},
|
||||
// 初始化被选中选项按钮
|
||||
initSectionBtns() {
|
||||
this.sectionIndex = []
|
||||
this.sectionIndex = this.sectionList.map((item) => {
|
||||
if (item.hasOwnProperty('section') && item.section.length > 0) {
|
||||
return Array(item.section.length).fill({
|
||||
value: 0,
|
||||
change: false
|
||||
})
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
this._sectionList = this.$t.deepClone(this.sectionList)
|
||||
// 给本地选项按钮列表给默认show属性
|
||||
this._sectionList.map((item) => {
|
||||
const section = item.section.map((section_item) => {
|
||||
if (!section_item.hasOwnProperty('show')) {
|
||||
section_item.show = true
|
||||
}
|
||||
return section_item
|
||||
})
|
||||
item.section = section
|
||||
return item
|
||||
})
|
||||
|
||||
// 更新按钮信息
|
||||
this.updateSectionBtns()
|
||||
},
|
||||
// 跟新选项按钮信息
|
||||
updateSectionBtns(sectionIndex = -1, showState = true) {
|
||||
let sectionOptional = this._sectionList[this.modeIndex]['section']
|
||||
this.btnsList = sectionOptional.map((item, index) => {
|
||||
// 判断是否已经修改了对应的值
|
||||
let changeValue = this.sectionIndex[this.modeIndex][index]['change'] || false
|
||||
let currentSectionIndexValue = this.sectionIndex[this.modeIndex][index]['value'] || 0
|
||||
// 取出默认值(如果是已经修改过的选项,则使用之前的选项信息)
|
||||
let indexValue = changeValue ? currentSectionIndexValue : item.hasOwnProperty('current') ? item.current : 0
|
||||
// 取出是否显示当前选项
|
||||
let show = (sectionIndex !== -1 && sectionIndex === index) ? showState : item.hasOwnProperty('show') ? item.show : true
|
||||
// 处理最大最小值
|
||||
if (indexValue < 0) {
|
||||
indexValue = 0
|
||||
}
|
||||
if (indexValue >= item.optional.length) {
|
||||
indexValue = item.optional.length
|
||||
}
|
||||
// this.sectionIndex[this.modeIndex][index]['value'] = indexValue
|
||||
this.$set(this.sectionIndex[this.modeIndex], index, {value: indexValue, change: changeValue})
|
||||
item.show = show
|
||||
return item
|
||||
})
|
||||
},
|
||||
// 更新选项按钮状态信息
|
||||
updateSectionBtnsState(sectionIndex = -1, showState = true) {
|
||||
// 判断sectionIndex是否为数组
|
||||
if (this.$t.array.isArray(sectionIndex)) {
|
||||
if (sectionIndex.length === 0) {
|
||||
return
|
||||
}
|
||||
sectionIndex = sectionIndex.filter((item) => item >= 0 && item < this.sectionList[this.modeIndex]['section'].length)
|
||||
sectionIndex.map((item) => {
|
||||
this.btnsList[item]['show'] = showState
|
||||
this._sectionList[this.modeIndex]['section'][item]['show'] = showState
|
||||
})
|
||||
} else {
|
||||
if (sectionIndex < 0 || sectionIndex >= this.sectionList[this.modeIndex]['section'].length) {
|
||||
return
|
||||
}
|
||||
// 将按键的对应显示状态设置为对应的状态
|
||||
this.btnsList[sectionIndex]['show'] = showState
|
||||
this._sectionList[this.modeIndex]['section'][sectionIndex]['show'] = showState
|
||||
}
|
||||
|
||||
},
|
||||
// 更新选项按钮选中信息
|
||||
updateSectionBtnsValue(modeIndex = 0, sectionIndex = -1, value = 0) {
|
||||
if (sectionIndex < 0 || sectionIndex >= this.sectionList[modeIndex]['section'].length) {
|
||||
return
|
||||
}
|
||||
// 如果showState为false则移除对应的选项按钮,否则往对应的位置添加上对应的选项按钮
|
||||
this.sectionIndex[modeIndex][sectionIndex] = {
|
||||
value,
|
||||
change: true
|
||||
}
|
||||
},
|
||||
// 选项按钮点击事件
|
||||
sectionBtnClick(index, sectionIndex) {
|
||||
// if (this.sectionIndex[this.modeIndex][index] === sectionIndex) {
|
||||
// return
|
||||
// }
|
||||
this.$set(this.sectionIndex[this.modeIndex], index, {value: sectionIndex, change: true})
|
||||
this.$emit('click', {
|
||||
methods: this.btnsList[index]['methods'],
|
||||
index: sectionIndex,
|
||||
name: this.btnsList[index]['optional'][sectionIndex]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dynamic-demo {
|
||||
padding-top: 78rpx;
|
||||
|
||||
/* 顶部模式切换start */
|
||||
.mode-switch {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 75rpx;
|
||||
padding: 0 30rpx;
|
||||
|
||||
&__container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 476rpx;
|
||||
height: 62rpx;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||||
border-radius: 31rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
height: 62rpx;
|
||||
width: 100%;
|
||||
line-height: 62rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-sub-color;
|
||||
z-index: 2;
|
||||
transition: all 0.3s;
|
||||
|
||||
&--active {
|
||||
color: #FFFFFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__slider {
|
||||
position: absolute;
|
||||
height: 62rpx;
|
||||
border-radius: 31rpx;
|
||||
// background-image: linear-gradient(-86deg, #FF8359 0%, #FFDF40 100%);
|
||||
background-image: linear-gradient(-86deg, #00C3FF 0%, #58FFF5 100%);
|
||||
box-shadow: 1rpx 10rpx 24rpx 0rpx #00C3FF77;
|
||||
z-index: 1;
|
||||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部模式切换end */
|
||||
|
||||
/* 演示内容展示start */
|
||||
.demo-container {
|
||||
min-height: 327rpx;
|
||||
width: calc(100% - 60rpx);
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||||
margin: 0 30rpx 5rpx 30rpx;
|
||||
border-radius: 20rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&--full {
|
||||
display: inline-block;
|
||||
padding-bottom: 20rpx;
|
||||
min-height: 0rpx;
|
||||
padding: 10rpx 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.demo {
|
||||
padding-top: 70rpx;
|
||||
|
||||
&__tips {
|
||||
&__icon {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 16rpx;
|
||||
width: 39rpx;
|
||||
height: 39rpx;
|
||||
line-height: 39rpx;
|
||||
font-size: 39rpx;
|
||||
|
||||
.icon {
|
||||
background: linear-gradient(-45deg, #FF8359 0%, #FFDF40 100%);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
text-shadow: 0rpx 10rpx 10rpx rgba(255, 156, 82, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
top: 65rpx;
|
||||
right: 16rpx;
|
||||
font-size: 20rpx;
|
||||
margin-left: 20rpx;
|
||||
word-wrap: normal;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #E6E6E6;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1);
|
||||
transform-origin: 0 0;
|
||||
z-index: 999999;
|
||||
|
||||
&--hide {
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
&--show {
|
||||
transform: scaleY(100%);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent rgba(149, 149, 149, 0.1) transparent;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 演示内容展示end */
|
||||
|
||||
/* 可选项start */
|
||||
.section-container {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: 70rpx;
|
||||
|
||||
.section {
|
||||
&__content {
|
||||
margin-top: 70rpx;
|
||||
display: none;
|
||||
|
||||
&--visible {
|
||||
display: block;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: calc(70rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-top: 0rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 30rpx;
|
||||
text-align: center;
|
||||
|
||||
&__left-line,
|
||||
&__right-line {
|
||||
|
||||
width: 100rpx;
|
||||
height: 2rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__left-line {
|
||||
&::after {
|
||||
content: '';
|
||||
background: inherit;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
position: absolute;
|
||||
top: -12rpx;
|
||||
right: 0rpx;
|
||||
border-radius: 50%;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__right-line {
|
||||
&::after {
|
||||
content: '';
|
||||
background: inherit;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
position: absolute;
|
||||
top: -12rpx;
|
||||
left: 0rpx;
|
||||
border-radius: 50%;
|
||||
transform: translateY(50%);
|
||||
}
|
||||
}
|
||||
|
||||
&--text {
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
min-width: 124rpx;
|
||||
height: 30rpx;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
margin: 0 35rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__btns {
|
||||
width: calc(100% - 60rpx);
|
||||
margin: 0 30rpx;
|
||||
margin-top: 29rpx;
|
||||
padding: 50rpx 30rpx 0rpx 0rpx;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__item {
|
||||
max-width: 30%;
|
||||
padding: 17rpx 36rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 40rpx;
|
||||
margin-left: 40rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
// &::before {
|
||||
// content: " ";
|
||||
// position: absolute;
|
||||
// top: 10rpx;
|
||||
// left: 1rpx;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background: inherit;
|
||||
// filter: blur(24rpx);
|
||||
// opacity: 1;
|
||||
// z-index: -1;
|
||||
// }
|
||||
|
||||
&__bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
|
||||
&--active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&--text {
|
||||
font-size: 24rpx;
|
||||
line-height: 1.2em;
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
|
||||
&--active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 可选项end */
|
||||
}
|
||||
</style>
|
||||
147
libs/components/multiple-options-demo.vue
Normal file
147
libs/components/multiple-options-demo.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<view class="multiple-options">
|
||||
<view class="list">
|
||||
<block v-for="(item, index) in listData" :key="index">
|
||||
<view
|
||||
class="list__item"
|
||||
:class="[`tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}--light`]"
|
||||
@tap="navOptionsPage(item.url)"
|
||||
>
|
||||
<view class="list__content">
|
||||
<view class="list__content__title">{{ item.title }}</view>
|
||||
<view class="list__content__desc">{{ item.desc }}</view>
|
||||
</view>
|
||||
<view class="list__icon">
|
||||
<view class="list__icon__main" :class="[`tn-icon-${item.mainIcon}`, `tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}`]"></view>
|
||||
<view class="list__icon__sub" :class="[`tn-icon-${item.subIcon}`, `tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}`]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'multiple-options-demo',
|
||||
props: {
|
||||
// 显示的列表数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图鸟颜色列表
|
||||
tuniaoColorList: [
|
||||
'red',
|
||||
'purplered',
|
||||
'purple',
|
||||
'bluepurple',
|
||||
'aquablue',
|
||||
'blue',
|
||||
'indigo',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'orange',
|
||||
'orangered'
|
||||
],
|
||||
listData: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(val) {
|
||||
this.initList()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initList()
|
||||
},
|
||||
methods: {
|
||||
// 初始化列表数据
|
||||
initList() {
|
||||
// 给列表添加背景颜色数据
|
||||
this.listData = this.list.map((item, index) => {
|
||||
item.bgColorIndex = this.getBgNum()
|
||||
item.mainIcon = item?.mainIcon || 'computer-fill'
|
||||
item.subIcon = item?.subIcon || 'share'
|
||||
return item
|
||||
})
|
||||
},
|
||||
// 跳转到对应的选项页面
|
||||
navOptionsPage(url) {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
},
|
||||
// 获取酷炫背景随机数
|
||||
getBgNum() {
|
||||
return Math.floor((Math.random() * this.tuniaoColorList.length))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.list {
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: calc(100% - 60rpx);
|
||||
margin: 108rpx 30rpx 0rpx 30rpx;
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
// color: $tn-font-color;
|
||||
margin: 34rpx 0rpx 27rpx 37rpx;
|
||||
|
||||
&__title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
&__desc {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
flex: 1;
|
||||
margin-right: 26rpx;
|
||||
position: relative;
|
||||
|
||||
&__main, &__sub {
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
&__main {
|
||||
font-size: 200rpx;
|
||||
width: 190rpx;
|
||||
line-height: 200rpx;
|
||||
top: 0;
|
||||
right: 0rpx;
|
||||
transform: translateY(-60%);
|
||||
}
|
||||
&__sub {
|
||||
font-size: 70rpx;
|
||||
top: 0;
|
||||
right: 175rpx;
|
||||
transform: translateY(-5rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
libs/components/nav-index-button.vue
Normal file
169
libs/components/nav-index-button.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<view class="nav-index-button" :style="{bottom: `${bottom}rpx`, right: `${right}rpx`}" @tap.stop="navIndex">
|
||||
<view class="nav-index-button__content">
|
||||
<view class="nav-index-button__content--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-cool-bg-color-5">
|
||||
<view class="tn-icon-home-fill"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="nav-index-button__meteor">
|
||||
<view class="nav-index-button__meteor__wrapper">
|
||||
<view v-for="(item,index) in 6" :key="index" class="nav-index-button__meteor__item" :style="{transform: `rotateX(${-60 + (30 * index)}deg) rotateZ(${-60 + (30 * index)}deg)`}">
|
||||
<view class="nav-index-button__meteor__item--pic"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'nav-index-button',
|
||||
props: {
|
||||
// 距离底部的距离
|
||||
bottom: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
// 距离右边的距离
|
||||
right: {
|
||||
type: [Number, String],
|
||||
default: 75
|
||||
},
|
||||
// 首页地址
|
||||
indexPath: {
|
||||
type: String,
|
||||
default: '/pages/index'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 跳转回首页
|
||||
navIndex() {
|
||||
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
|
||||
const pages = getCurrentPages()
|
||||
if (pages && pages.length > 0) {
|
||||
const indexPath = this.indexPath || '/pages/index'
|
||||
const firstPage = pages[0]
|
||||
if (pages.length == 1 && (!firstPage.route || firstPage.route != indexPath.substring(1, indexPath.length))) {
|
||||
uni.reLaunch({
|
||||
url: indexPath
|
||||
})
|
||||
} else {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: indexPath
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.nav-index-button {
|
||||
position: fixed;
|
||||
animation: suspension 3s ease-in-out infinite;
|
||||
z-index: 999999;
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
&--icon {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
font-size: 60rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 18rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
transform: scale(0.85);
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
opacity: 1;
|
||||
transform: scale(1, 1);
|
||||
background-size: 100% 100%;
|
||||
background-image: url(https://resource.tuniaokj.com/images/cool_bg_image/icon_bg6.png);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__meteor {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
transform-style: preserve-3d;
|
||||
transform: translate(-50%, -50%) rotateY(75deg) rotateZ(10deg);
|
||||
|
||||
&__wrapper {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
transform-style: preserve-3d;
|
||||
animation: spin 20s linear infinite;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 1000rpx;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
&--pic {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url(https://resource.tuniaokj.com/images/cool_bg_image/arc3.png) no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
animation: arc 4s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes suspension {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-0.8rem);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotateX(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes arc {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
libs/mixin/dynamic_demo_mixin.js
Normal file
52
libs/mixin/dynamic_demo_mixin.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 动态参数演示mixin
|
||||
*/
|
||||
module.exports = {
|
||||
data() {
|
||||
return {
|
||||
// 效果显示框top的值
|
||||
contentContainerTop: '0px',
|
||||
contentContainerIsTop: false,
|
||||
|
||||
// 参数显示框top的值
|
||||
sectionContainerTop: '0px'
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.updateSectionContainerTop()
|
||||
},
|
||||
methods: {
|
||||
// 处理演示效果框的位置
|
||||
async _handleContentConatinerPosition() {
|
||||
// 获取效果演示框的节点信息
|
||||
const contentContainer = await this._tGetRect('#content_container')
|
||||
// 获取参数框的节点信息
|
||||
this._tGetRect('#section_container').then((res) => {
|
||||
// 判断参数框是否在移动,如果是则更新效果框的位置
|
||||
// 如果效果框的顶部已经触控到顶部导航栏就停止跟随
|
||||
if (res.top - contentContainer.bottom != 15) {
|
||||
const newTop = res.top - (contentContainer.height + uni.upx2px(20))
|
||||
const minTop = this.vuex_custom_bar_height + 1
|
||||
if (newTop < minTop) {
|
||||
this.contentContainerTop = minTop + 'px'
|
||||
this.contentContainerIsTop = true
|
||||
} else {
|
||||
this.contentContainerTop = newTop + 'px'
|
||||
this.contentContainerIsTop = false
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 更新状态切换栏位置信息
|
||||
updateSectionContainerTop() {
|
||||
this._tGetRect('#content_container').then((res) => {
|
||||
this.contentContainerTop = (this.vuex_custom_bar_height + 148) + 'px'
|
||||
this.sectionContainerTop = (res.height + 20) + 'px'
|
||||
})
|
||||
}
|
||||
},
|
||||
// 监听页面滚动
|
||||
onPageScroll() {
|
||||
this._handleContentConatinerPosition()
|
||||
}
|
||||
}
|
||||
60
libs/mixin/template_page_mixin.js
Normal file
60
libs/mixin/template_page_mixin.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 演示页面mixin
|
||||
*/
|
||||
module.exports = {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 更新顶部导航栏信息
|
||||
this.updateCustomBarInfo()
|
||||
},
|
||||
methods: {
|
||||
// 点击左上角返回按钮时触发事件
|
||||
goBack() {
|
||||
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
|
||||
const pages = getCurrentPages()
|
||||
if (pages && pages.length > 0) {
|
||||
const firstPage = pages[0]
|
||||
if (pages.length == 1 && (!firstPage.route || firstPage.route != 'pages/index')) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
} else {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index'
|
||||
})
|
||||
}
|
||||
},
|
||||
// 更新顶部导航栏信息
|
||||
async updateCustomBarInfo() {
|
||||
// 获取vuex中的自定义顶栏的高度
|
||||
let customBarHeight = this.vuex_custom_bar_height
|
||||
let statusBarHeight = this.vuex_status_bar_height
|
||||
// 如果获取失败则重新获取
|
||||
if (!customBarHeight) {
|
||||
try {
|
||||
const navBarInfo = await this.$t.updateCustomBar()
|
||||
customBarHeight = navBarInfo.customBarHeight
|
||||
statusBarHeight = navBarInfo.statusBarHeight
|
||||
} catch(e) {
|
||||
setTimeout(() => {
|
||||
this.updateCustomBarInfo()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 更新vuex中的导航栏信息
|
||||
this.$t.vuex('vuex_status_bar_height', statusBarHeight)
|
||||
this.$t.vuex('vuex_custom_bar_height', customBarHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
330
libs/navigation/navigation.js
Normal file
330
libs/navigation/navigation.js
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* 页面展示列表数据
|
||||
*/
|
||||
export default {
|
||||
data: [{
|
||||
title: '图鸟首页',
|
||||
backgroundColor: 'tn-cool-bg-color-1',
|
||||
list: [{
|
||||
icon: 'code',
|
||||
title: '关于我们',
|
||||
url: '/homePages/about',
|
||||
author: '图鸟北北'
|
||||
},{
|
||||
icon: 'code',
|
||||
title: '全局搜索',
|
||||
url: '/homePages/search',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '今日热榜',
|
||||
url: '/homePages/hot',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '前端业务',
|
||||
url: '/homePages/profession',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '加载效果',
|
||||
url: '/homePages/loading',
|
||||
author: '图鸟北北'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '酷炫圈子',
|
||||
backgroundColor: 'tn-cool-bg-color-1',
|
||||
list: [{
|
||||
icon: 'code',
|
||||
title: '博主_Me',
|
||||
url: '/circlePages/blogger',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '博主_Ta',
|
||||
url: '/circlePages/blogger_other',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '编辑发布',
|
||||
url: '/circlePages/edit',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '广告页',
|
||||
url: '/circlePages/advertise',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '资讯详情',
|
||||
url: '/circlePages/news',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '名片王者',
|
||||
url: '/circlePages/king',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '智能名片',
|
||||
url: '/circlePages/business',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '精选圈子',
|
||||
url: '/circlePages/group',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '积分排行',
|
||||
url: '/circlePages/ranking',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '圈子详情',
|
||||
url: '/circlePages/details',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '预约接龙',
|
||||
url: '/circlePages/reserve',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '活动创建',
|
||||
url: '/circlePages/create',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '打造圈子',
|
||||
url: '/circlePages/build',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '一起群聊',
|
||||
url: '/circlePages/chat',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '对话聊天',
|
||||
url: '/circlePages/chatting',
|
||||
author: '图鸟北北'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '活动广场',
|
||||
backgroundColor: 'tn-cool-bg-color-1',
|
||||
list: [{
|
||||
icon: 'code',
|
||||
title: '地图打卡',
|
||||
url: '/activityPages/map',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '快速答题',
|
||||
url: '/activityPages/topic',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '课程学习',
|
||||
url: '/activityPages/study',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '开源项目',
|
||||
url: '/activityPages/project',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '活动星球',
|
||||
url: '/activityPages/planet',
|
||||
author: '图鸟北北'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '商品优选',
|
||||
backgroundColor: 'tn-cool-bg-color-1',
|
||||
list: [{
|
||||
icon: 'code',
|
||||
title: '优质商家',
|
||||
url: '/preferredPages/shop',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '商品详情',
|
||||
url: '/preferredPages/product',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '历史订单',
|
||||
url: '/preferredPages/order',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '商品分类',
|
||||
url: '/preferredPages/classify',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '商家相册',
|
||||
url: '/preferredPages/photo',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '品牌官网',
|
||||
url: '/preferredPages/website',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '积分兑换',
|
||||
url: '/preferredPages/redeem',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '免单活动',
|
||||
url: '/preferredPages/award',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '免单获取',
|
||||
url: '/preferredPages/awardget',
|
||||
author: '图鸟北北'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '关于我的',
|
||||
backgroundColor: 'tn-cool-bg-color-1',
|
||||
list: [{
|
||||
icon: 'code',
|
||||
title: '使用协议',
|
||||
url: '/minePages/protocol',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '授权登录',
|
||||
url: '/minePages/login',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '消息通知',
|
||||
url: '/minePages/message',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '全局设置',
|
||||
url: '/minePages/set',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '立即体验',
|
||||
url: '/minePages/start',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '感谢名单',
|
||||
url: '/minePages/thanks',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '版本更新',
|
||||
url: '/minePages/version',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '帮助中心',
|
||||
url: '/minePages/help',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '头像上传',
|
||||
url: '/minePages/avatar',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '积分明细',
|
||||
url: '/minePages/integral',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '积分签到',
|
||||
url: '/minePages/signed',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '好物收藏',
|
||||
url: '/minePages/collect',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '账号安全',
|
||||
url: '/minePages/safety',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '赞赏支持',
|
||||
url: '/minePages/reward',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '缺省页',
|
||||
url: '/minePages/default',
|
||||
author: '图鸟北北'
|
||||
},
|
||||
{
|
||||
icon: 'code',
|
||||
title: '富文本',
|
||||
url: '/minePages/content',
|
||||
author: '图鸟北北'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
main.js
13
main.js
@@ -1,10 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
|
||||
|
||||
import store from './store'
|
||||
import Vue from 'vue'
|
||||
import HttpRequest from './common/httpRequest'
|
||||
import HttpCache from './common/cache'
|
||||
import queue from './common/queue'
|
||||
import TuniaoUI from 'tuniao-ui'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$Request = HttpRequest;
|
||||
@@ -12,7 +12,6 @@ Vue.prototype.$queue = queue;
|
||||
|
||||
Vue.prototype.$Sysconf = HttpRequest.config;
|
||||
Vue.prototype.$SysCache = HttpCache;
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
// 引入全局uView
|
||||
@@ -21,13 +20,17 @@ Vue.use(uView);
|
||||
|
||||
|
||||
const app = new Vue({
|
||||
store,
|
||||
...App
|
||||
})
|
||||
|
||||
// http拦截器,将此部分放在new Vue()和app.$mount()之间,才能App.vue中正常使用
|
||||
import httpInterceptor from '@/common/http.interceptor.js'
|
||||
Vue.use(httpInterceptor, app)
|
||||
|
||||
Vue.use(TuniaoUI)
|
||||
// 引入TuniaoUI提供的vuex简写方法
|
||||
let vuexStore = require('@/store/$t.mixin.js')
|
||||
Vue.mixin(vuexStore)
|
||||
// http接口API集中管理引入部分
|
||||
import httpApi from '@/common/http.api.js'
|
||||
Vue.use(httpApi, app)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"name" : "斯耀短剧",
|
||||
"appid" : "__UNI__E0B05B1",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.7",
|
||||
"versionCode" : 107,
|
||||
"versionName" : "1.0.8",
|
||||
"versionCode" : 108,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
</text>
|
||||
</view>
|
||||
<view class="list">
|
||||
<scroll-view @scrolltolower="scrolltolower" :refresher-enabled="refresherTriggered"
|
||||
@scrolltoupper="scrolltoupper" scroll-y="true"
|
||||
<scroll-view @scrollToLower="scrollToLower" :refresher-enabled="refresherTriggered"
|
||||
@scrollToUpper="scrollToUpper" scroll-y="true"
|
||||
style="width: 100%;height: 100%;background-color: #ffffff;padding-bottom: 30rpx;">
|
||||
<view class="list-item flex align-center justify-center" v-for="(item,index) in list" :key="index">
|
||||
<view class="list-item-box flex align-center justify-between">
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
onShow() {
|
||||
this.getAmount()
|
||||
this.getList()
|
||||
this.getUserBalanceList()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.getAmount()
|
||||
@@ -93,7 +93,7 @@
|
||||
getAmount() {
|
||||
this.$Request.getT('app/moneyDetails/selectUserMoney').then(res => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.amount = res.data.amount || 0
|
||||
} else {
|
||||
this.amount = '0.00'
|
||||
@@ -106,7 +106,7 @@
|
||||
}
|
||||
this.$Request.getT('app/cash/withdraw', params).then(res => {
|
||||
console.log(res)
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
// this.amount = res.data.amount
|
||||
} else {
|
||||
// this.amount = '0.00'
|
||||
@@ -120,7 +120,7 @@
|
||||
/**
|
||||
* 获取余额明细
|
||||
*/
|
||||
getList() {
|
||||
getUserBalanceList() {
|
||||
let data = {
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
@@ -129,14 +129,14 @@
|
||||
setTimeout(() => {
|
||||
this.refresherTriggered = false
|
||||
}, 1500)
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.pages = res.data.pages
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loadmore'
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.list = res.data.records
|
||||
} else {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
@@ -150,17 +150,17 @@
|
||||
})
|
||||
},
|
||||
//上拉刷新
|
||||
scrolltoupper() {
|
||||
scrollToUpper() {
|
||||
this.page = 1
|
||||
this.refresherTriggered = true
|
||||
this.getList()
|
||||
this.getUserBalanceList()
|
||||
},
|
||||
//加载更多
|
||||
scrolltolower() {
|
||||
scrollToLower() {
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loading'
|
||||
this.page += 1
|
||||
this.getList()
|
||||
this.getUserBalanceList()
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 1 订单拉起抽奖
|
||||
// 2 周任务拉起抽奖
|
||||
// 3 月任务拉起抽奖
|
||||
source:1,
|
||||
background:{
|
||||
background:'transparent'
|
||||
},
|
||||
@@ -237,7 +241,7 @@
|
||||
// 注意这里返回的是一个 Promise
|
||||
// 大哥,这里只是模拟,别告诉我你不会对接自己的接口
|
||||
async requestApiGetPrizeList() {
|
||||
const res = await this.$Request.getT('/app/discSpinning/selectDiscSpinning')
|
||||
const res = await this.$Request.getT('/app/discSpinning/selectDiscSpinning',{source:this.source})
|
||||
if (res.code == 0) {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -434,7 +438,7 @@
|
||||
async remoteGetPrizeIndex() {
|
||||
this.result=''
|
||||
console.warn('###当前处于模拟的请求接口,并返回了中奖信息###')
|
||||
const res = await this.$Request.getT('app/discSpinning/draw')
|
||||
const res = await this.$Request.getT('app/discSpinning/draw',{source:this.source})
|
||||
this.freeNum--
|
||||
// this.getCount()
|
||||
console.log(res);
|
||||
@@ -539,7 +543,7 @@
|
||||
}
|
||||
},
|
||||
async getCount(){
|
||||
const res=await this.$Request.getT('app/discSpinning/drawCount')
|
||||
const res=await this.$Request.getT('app/discSpinning/drawCount',{source:this.source})
|
||||
if(res.code==0){
|
||||
this.freeNum=res.count||0
|
||||
this.freeNumDay=res.sum||0
|
||||
@@ -555,6 +559,9 @@
|
||||
},
|
||||
onLoad(opt) {
|
||||
this.option = opt
|
||||
if(opt.source){
|
||||
this.source=opt.source
|
||||
}
|
||||
this.prizeList = []
|
||||
this.getCount()
|
||||
this.getRedPack()
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
limit: this.limit,
|
||||
}
|
||||
this.$u.api.collectList(data).then(res => {
|
||||
if(res.code == 0) {
|
||||
if( this.page == 1) {
|
||||
if(res.code === 0) {
|
||||
if( this.page === 1) {
|
||||
this.collectList = res.data.records
|
||||
uni.stopPullDownRefresh();
|
||||
return
|
||||
|
||||
@@ -69,13 +69,13 @@
|
||||
limit: this.limit,
|
||||
}
|
||||
this.$u.api.selectCourse(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
res.data.list.forEach(ret => {
|
||||
if (ret.avatar) {
|
||||
ret.avatar = ret.avatar.split(',')
|
||||
}
|
||||
})
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.courseList = res.data.list
|
||||
uni.stopPullDownRefresh();
|
||||
return
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<swiper-item class="swipers-item" v-for="(item,index) in swiperList" :key="item.courseDetailsId">
|
||||
<view class="swipers-items" v-if="current == index">
|
||||
<!-- 视频 -->
|
||||
<video :show-fullscreen-btn="false" @controlstoggle="controlstoggles" object-fit="contain"
|
||||
<video :show-fullscreen-btn="false" @controlstoggle="controlStoggles" object-fit="contain"
|
||||
v-if="current === index && item.videoUrl" :play-strategy="2" :show-loading="true"
|
||||
codec="software" :muted="false" :show-center-play-btn="true" :loop="true" @ended="ended"
|
||||
@timeupdate="timeupdate" @play="videoPlay('myVideo'+item.courseDetailsId, item.courseDetailsId)"
|
||||
@@ -46,7 +46,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="swipers-items-right-item" v-if="isCollect">
|
||||
<view class="swipers-items-right-item-img" @click.stop="shoucang()">
|
||||
<view class="swipers-items-right-item-img" @click.stop="collectVideo()">
|
||||
<image class="swipers-items-right-item-imgs" src="../../static/images/me/shuqian_s.png"
|
||||
mode=""></image>
|
||||
</view>
|
||||
@@ -57,7 +57,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="swipers-items-right-item" v-else>
|
||||
<view class="swipers-items-right-item-img" @click.stop="shoucang()">
|
||||
<view class="swipers-items-right-item-img" @click.stop="collectVideo()">
|
||||
<image class="swipers-items-right-item-imgs" src="../../static/images/me/shuqian.png"
|
||||
mode=""></image>
|
||||
</view>
|
||||
@@ -188,7 +188,7 @@
|
||||
<text class="popuppay-title-l">
|
||||
购买后继续观看
|
||||
</text>
|
||||
<image @click="closePopusPay()" class="popuppay-title-r"
|
||||
<image @click="closePopupPay()" class="popuppay-title-r"
|
||||
src="../../static/images/me/closeIconss.png" mode=""></image>
|
||||
</view>
|
||||
<!-- 余额 -->
|
||||
@@ -390,7 +390,6 @@
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
console.log("走了iOS播放器。。。。。。。。。。。。。。。");
|
||||
//当应用从后台进入前台时自动播放
|
||||
if (this.videoContext) {
|
||||
this.videoContext.play()
|
||||
@@ -398,7 +397,7 @@
|
||||
this.isVips = uni.getStorageSync('isVips') ? uni.getStorageSync('isVips') : '否'
|
||||
let that = this
|
||||
uni.$on('back', (data) => {
|
||||
if (data.flag == true) {
|
||||
if (data.flag) {
|
||||
that.showPay = false
|
||||
that.getDataList(that.courseId, that.courseDetailsId, true);
|
||||
that.getMyLoveStatus()
|
||||
@@ -468,14 +467,14 @@
|
||||
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
this.closePopusPay()
|
||||
this.closePopupPay()
|
||||
})
|
||||
if (this.courseId) {
|
||||
this.getDataList(this.courseId, this.courseDetailsId);
|
||||
|
||||
}
|
||||
httpsRequest.getT('app/course/getRedEnvelopeTips').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.getRedEnvelopeTips = res.data
|
||||
}
|
||||
})
|
||||
@@ -494,7 +493,7 @@
|
||||
console.log('this bottom padding = ' + this.paddingBottom);
|
||||
|
||||
httpsRequest.getT("app/common/type/919", {}).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
let isIos = sysInfo.platform == 'ios'
|
||||
this.isShowMoneyPay = !(res.data.value == '1' && isIos)
|
||||
@@ -535,7 +534,7 @@
|
||||
*/
|
||||
getScale() {
|
||||
httpsRequest.getT("app/common/type/914", {}).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.scale = Number(res.data.value)
|
||||
}
|
||||
});
|
||||
@@ -574,7 +573,7 @@
|
||||
},
|
||||
//选集弹窗的回调
|
||||
changeXj(e) {
|
||||
if (e.show == false) {
|
||||
if (!e.show) {
|
||||
//关闭弹窗的时候重置scrollIntoViews,防止关闭后第二次点就不能自动滑动到当前集的位置
|
||||
this.scrollIntoViews = 'video0'
|
||||
}
|
||||
@@ -615,7 +614,7 @@
|
||||
});
|
||||
},
|
||||
isCheckPay(status, name, order) {
|
||||
if (status == 0) {
|
||||
if (status === 0) {
|
||||
this.setPayment(name, order);
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
@@ -625,7 +624,7 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
iphonepay() {
|
||||
iphonePay() {
|
||||
let that = this
|
||||
plus.payment.getChannels((res) => {
|
||||
let channel = res.find(i => i.id === 'appleiap')
|
||||
@@ -681,7 +680,7 @@
|
||||
httpsRequest.postT('/app/ios/isoPayApp?receipt=' + receipt + '&ordersId=' + that.iosPayId).then(
|
||||
res => {
|
||||
uni.hideLoading();
|
||||
if (res.status == 0) {
|
||||
if (res.status === 0) {
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
duration: 2000,
|
||||
@@ -693,7 +692,7 @@
|
||||
},
|
||||
// 充值
|
||||
pay() {
|
||||
if (this.checked == false) {
|
||||
if (!this.checked) {
|
||||
uni.showToast({
|
||||
title: '请阅读并同意《付费须知说明》',
|
||||
icon: 'none'
|
||||
@@ -704,7 +703,7 @@
|
||||
uni.showLoading({
|
||||
title: '支付中...'
|
||||
})
|
||||
if (this.openWay == 2) {
|
||||
if (this.openWay === 2) {
|
||||
// 微信APP支付 根据订单id获取支付信息
|
||||
httpsRequest.postT("/app/wxPay/payMoney", {
|
||||
classify: 1,
|
||||
@@ -712,7 +711,7 @@
|
||||
}).then(ret => {
|
||||
this.isCheckPay(ret.code, 'wxpay', JSON.stringify(ret.data));
|
||||
});
|
||||
} else if (this.openWay == 1) {
|
||||
} else if (this.openWay === 1) {
|
||||
let paytype = 'h5'
|
||||
// #ifdef APP
|
||||
paytype = 'app'
|
||||
@@ -725,7 +724,7 @@
|
||||
});
|
||||
// this.isCheckPay(ret.code, 'wxpay', JSON.stringify(ret.data));
|
||||
});
|
||||
} else if (this.openWay == 3) {
|
||||
} else if (this.openWay === 3) {
|
||||
let userId = uni.getStorageSync('userId');
|
||||
let data = {
|
||||
payClassifyId: this.wallet[this.wallCurr].payClassifyId,
|
||||
@@ -742,7 +741,7 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this.openWay == 4) {
|
||||
} else if (this.openWay === 4) {
|
||||
let userId = uni.getStorageSync('userId');
|
||||
let data = {
|
||||
// money: this.wallet[this.current].price
|
||||
@@ -772,7 +771,7 @@
|
||||
getMyMoney() {
|
||||
if (uni.getStorageSync('token')) {
|
||||
httpsRequest.getT('/app/moneyDetails/selectUserMoney').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.moneyNum = res.data.money
|
||||
}
|
||||
})
|
||||
@@ -826,7 +825,7 @@
|
||||
*/
|
||||
getMoneyList() {
|
||||
httpsRequest.getT('/app/payClassify/selectPayClassifyList').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
let priceObj = this.findMinMaxPricesWithIndexes(res.data)
|
||||
this.wallet = res.data
|
||||
this.wallet[priceObj.minPriceIndex].remarks = '特惠'
|
||||
@@ -843,7 +842,7 @@
|
||||
this.wallet = arr
|
||||
}
|
||||
// #ifdef APP
|
||||
this.iphonepay()
|
||||
this.iphonePay()
|
||||
// #endif
|
||||
} else {
|
||||
uni.showToast({
|
||||
@@ -854,11 +853,11 @@
|
||||
})
|
||||
},
|
||||
//关闭支付弹窗
|
||||
closePopusPay() {
|
||||
closePopupPay() {
|
||||
this.$refs.popuppay.close()
|
||||
},
|
||||
//打开支付弹窗
|
||||
openPopusPay() {
|
||||
openPopupPay() {
|
||||
this.$refs.popuppay.open('bottom')
|
||||
},
|
||||
// 获取收藏状态
|
||||
@@ -878,7 +877,7 @@
|
||||
})
|
||||
},
|
||||
//显示/隐藏适配控制器的回调
|
||||
controlstoggles(e) {
|
||||
controlStoggles(e) {
|
||||
this.showControls = e.detail.show
|
||||
this.showBack = this.showControls
|
||||
console.log(e.detail.show, '显示/隐藏控制栏')
|
||||
@@ -962,16 +961,16 @@
|
||||
|
||||
data.courseDetailsId = this.videoList[this.current].courseDetailsId
|
||||
httpsRequest.getT('/app/order/insertCourseOrders', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.ordersId = res.data.orders.ordersId //记录订单id
|
||||
this.payMoney = res.data.orders.payMoney //记录订单价格
|
||||
|
||||
if (type == 1) { //金币
|
||||
if (type === 1) { //金币
|
||||
this.payOrder(res.data.orders.ordersId, res.data.orders.payMoney)
|
||||
} else if (type == 2) { //支付宝
|
||||
} else if (type === 2) { //支付宝
|
||||
this.closePay() //关闭购买选择弹窗
|
||||
this.payPrice = res.data.orders.payMoney //需要支付的价格
|
||||
this.openPopusPay() //显示充值弹窗
|
||||
this.openPopupPay() //显示充值弹窗
|
||||
}
|
||||
|
||||
|
||||
@@ -989,13 +988,13 @@
|
||||
httpsRequest.postT("/app/order/payOrders", {
|
||||
orderId: orderId,
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '已成功解锁',
|
||||
icon: 'none'
|
||||
})
|
||||
this.closePopusPay()
|
||||
this.closePopupPay()
|
||||
this.closePay()
|
||||
that.showPay = false
|
||||
that.showMoney = false
|
||||
@@ -1037,7 +1036,7 @@
|
||||
this.videoList = [...this.videoList, ...this.meunList.slice(index + 1, index + 3)]
|
||||
}
|
||||
//只剩一条数据
|
||||
if (lengths == 1) {
|
||||
if (lengths === 1) {
|
||||
//把剩下的一条给放进去
|
||||
this.videoList = [
|
||||
//选中的那条
|
||||
@@ -1049,7 +1048,7 @@
|
||||
]
|
||||
}
|
||||
//选择的就是最后一条数据
|
||||
if (lengths == 0) {
|
||||
if (lengths === 0) {
|
||||
//后两条拿总数组的前两条就可以了
|
||||
this.videoList = [...this.videoList, ...this.meunList.slice(0, 2)]
|
||||
}
|
||||
@@ -1062,7 +1061,7 @@
|
||||
// const index = this.meunList.findIndex(menu => menu.courseDetailsId === item.courseDetailsId);
|
||||
// this.videoList = [this.meunList[index]]
|
||||
this.current = index
|
||||
if (this.videoList[this.current].videoUrl == '' && this.videoList[this.current].price <= 0 && !type) {
|
||||
if (this.videoList[this.current].videoUrl === '' && this.videoList[this.current].price <= 0 && !type) {
|
||||
this.getDataList(this.courseId, this.courseDetailsId, true, 'select')
|
||||
return;
|
||||
}
|
||||
@@ -1104,7 +1103,7 @@
|
||||
this.$refs.popup.close()
|
||||
},
|
||||
//收藏
|
||||
shoucang() {
|
||||
collectVideo() {
|
||||
if (uni.getStorageSync('token')) {
|
||||
let data = {
|
||||
courseId: this.courseId,
|
||||
@@ -1112,7 +1111,7 @@
|
||||
type: this.isCollect == false ? 1 : 0
|
||||
}
|
||||
httpsRequest.postJson('/app/courseCollect/insertCourseCollect', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.getMyLoveStatus()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
types:1
|
||||
}
|
||||
this.$u.api.help(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.helpClassifyList = res.data
|
||||
for (var i = 0; i < this.helpClassifyList.length; i++) {
|
||||
this.helpClassifyList[i].parentId = 1
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
methods: {
|
||||
getIntegral() {
|
||||
this.$u.api.integral().then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.integralNum = res.data.integralNum
|
||||
}else {
|
||||
uni.showToast({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +1,133 @@
|
||||
<template>
|
||||
<view style="text-align: left">
|
||||
<view v-if="list.length" v-for="(item, index) in list" :key="index" class="item">
|
||||
<view>
|
||||
<view style="margin-bottom: 8upx;text-align: right;">
|
||||
<text style="margin-bottom: 8upx;color: green" v-if="item.state==1"> 提现成功</text>
|
||||
<text style="margin-bottom: 8upx;color: green" v-if="item.state==0"> 提现中</text>
|
||||
<text style="margin-bottom: 8upx;color: #FD6416" v-if="item.state==-1"> 提现失败</text>
|
||||
</view>
|
||||
<view style="text-align: left">
|
||||
<view v-if="list.length" v-for="(item, index) in list" :key="index" class="item">
|
||||
<view>
|
||||
<view style="margin-bottom: 8upx;text-align: right;">
|
||||
<text style="margin-bottom: 8upx;color: green" v-if="item.state===1"> 提现成功</text>
|
||||
<text style="margin-bottom: 8upx;color: green" v-if="item.state===0"> 提现中</text>
|
||||
<text style="margin-bottom: 8upx;color: #FD6416" v-if="item.state===-1"> 提现失败</text>
|
||||
</view>
|
||||
|
||||
<view style="color: #999999;font-size: 28upx;">
|
||||
<view style="margin-bottom: 8upx"> 收款人账号:{{item.zhifubao}}</view>
|
||||
<view style="margin-bottom: 8upx"> 收款人姓名:{{item.zhifubaoName}}</view>
|
||||
<view style="margin-bottom: 8upx"> 发起时间:{{item.createAt}}</view>
|
||||
<view style="margin-bottom: 8upx" v-if="item.state==1">成功时间 {{item.outAt}}</view>
|
||||
<view style="margin-bottom: 8upx;color: #FD6416" v-if="item.state==-1">{{item.refund}}</view>
|
||||
<view style="color: #999999;font-size: 28upx;">
|
||||
<view style="margin-bottom: 8upx"> 收款人账号:{{ item.zhifubao }}</view>
|
||||
<view style="margin-bottom: 8upx"> 收款人姓名:{{ item.zhifubaoName }}</view>
|
||||
<view style="margin-bottom: 8upx"> 发起时间:{{ item.createAt }}</view>
|
||||
<view style="margin-bottom: 8upx" v-if="item.state===1">成功时间 {{ item.outAt }}</view>
|
||||
<view style="margin-bottom: 8upx;color: #FD6416" v-if="item.state===-1">{{ item.refund }}</view>
|
||||
|
||||
<view style="margin-bottom: 8upx;text-align: right;">
|
||||
<!-- 提现金额: -->
|
||||
<text style="color: #FD6416;font-size: 32upx;font-weight: 600"> ¥{{item.money}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="margin-bottom: 8upx;text-align: right;">
|
||||
<!-- 提现金额: -->
|
||||
<text style="color: #FD6416;font-size: 32upx;font-weight: 600"> ¥{{ item.money }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-box" v-if="!list.length">
|
||||
<view class="centre">
|
||||
<image src="../../static/images/learn/none.png" mode=""></image>
|
||||
<view class="tips">
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="page-box" v-if="!list.length">
|
||||
<view class="centre">
|
||||
<image src="../../static/images/learn/none.png" mode=""></image>
|
||||
<view class="tips">
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalCount: 0,
|
||||
}
|
||||
},
|
||||
onLoad: function(e) {
|
||||
this.getMoney();
|
||||
},
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
page: 1,
|
||||
limit: 10,
|
||||
totalCount: 0,
|
||||
}
|
||||
},
|
||||
onLoad: function (e) {
|
||||
this.getMoney();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getMoney() {
|
||||
let that = this;
|
||||
let token = uni.getStorageSync('token')
|
||||
methods: {
|
||||
getMoney() {
|
||||
let that = this;
|
||||
let token = uni.getStorageSync('token')
|
||||
|
||||
if (token) {
|
||||
//可以提现金额查询预估收入查询
|
||||
let data = {
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
}
|
||||
this.$Request.getT('app/cash/selectPayDetails', data).then(res => {
|
||||
this.totalCount = res.data.totalCount;
|
||||
if ( res.data.list.length > 0) {
|
||||
this.list = [...this.list,...res.data.list];
|
||||
}
|
||||
})
|
||||
}
|
||||
if (token) {
|
||||
//可以提现金额查询预估收入查询
|
||||
let data = {
|
||||
page: this.page,
|
||||
limit: this.limit
|
||||
}
|
||||
this.$Request.getT('app/cash/selectPayDetails', data).then(res => {
|
||||
this.totalCount = res.data.totalCount;
|
||||
if (res.data.list.length > 0) {
|
||||
this.list = [...this.list, ...res.data.list];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
onReachBottom: function() {
|
||||
if (this.page*this.limit < this.totalCount) {
|
||||
this.page = this.page + 1;
|
||||
this.getMoney();
|
||||
}
|
||||
},
|
||||
onPullDownRefresh: function() {
|
||||
this.page = 1;
|
||||
// that.list = []
|
||||
this.getMoney();
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
onReachBottom: function () {
|
||||
if (this.page * this.limit < this.totalCount) {
|
||||
this.page = this.page + 1;
|
||||
this.getMoney();
|
||||
}
|
||||
},
|
||||
onPullDownRefresh: function () {
|
||||
this.page = 1;
|
||||
// that.list = []
|
||||
this.getMoney();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
/* @import "../../static/css/index.css"; */
|
||||
/* @import "../../static/css/index.css"; */
|
||||
|
||||
page {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
page {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: white;
|
||||
padding: 32rpx;
|
||||
margin: 32rpx;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 7px 9px 34px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 16upx;
|
||||
}
|
||||
.item {
|
||||
background: white;
|
||||
padding: 32rpx;
|
||||
margin: 32rpx;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 7px 9px 34px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 16upx;
|
||||
}
|
||||
|
||||
.centre {
|
||||
text-align: center;
|
||||
padding: 200rpx 0;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
.centre {
|
||||
text-align: center;
|
||||
padding: 200rpx 0;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
image {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
// margin-bottom: 20rpx;
|
||||
margin: 0 auto 20rpx;
|
||||
// border: 1px dotted #000000;
|
||||
}
|
||||
image {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
// margin-bottom: 20rpx;
|
||||
margin: 0 auto 20rpx;
|
||||
// border: 1px dotted #000000;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 34rpx;
|
||||
color: #999999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.tips {
|
||||
font-size: 34rpx;
|
||||
color: #999999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 80rpx auto;
|
||||
width: 600rpx;
|
||||
border-radius: 32rpx;
|
||||
line-height: 90rpx;
|
||||
color: #ffffff;
|
||||
font-size: 34rpx;
|
||||
background: #ff7581;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
margin: 80rpx auto;
|
||||
width: 600rpx;
|
||||
border-radius: 32rpx;
|
||||
line-height: 90rpx;
|
||||
color: #ffffff;
|
||||
font-size: 34rpx;
|
||||
background: #ff7581;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,7 @@
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class=" invite-box u-relative">
|
||||
<view class=" inviteBox u-relative">
|
||||
<view class="top">
|
||||
<u-image src="/me/static/invite/bg1.png" alt="" width="422rpx" height="76rpx"></u-image>
|
||||
<view class="u-absolute font-bold">
|
||||
@@ -809,7 +809,7 @@
|
||||
background: #F3F4F8;
|
||||
}
|
||||
|
||||
.invite-box {
|
||||
.inviteBox {
|
||||
position: relative;
|
||||
margin-top: -240rpx;
|
||||
background-color: #fff;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<view class="" style="position: relative;overflow: hidden;width: 90px;">
|
||||
<view style="position: absolute;bottom:0;">
|
||||
<!-- <view class="flex"> -->
|
||||
<button @tap="getOut" class="cu-btn round">立即提现</button>
|
||||
<button @tap="getOut" class="cuBtn round">立即提现</button>
|
||||
<!-- </view> -->
|
||||
</view>
|
||||
</view>
|
||||
@@ -33,7 +33,7 @@
|
||||
style="width: 80rpx;height:80rpx;border-radius: 50rpx;"></image>
|
||||
<view class="margin-left-sm">
|
||||
<view class="userName_view">{{item.userName}}</view>
|
||||
<view class="text-cut" style="font-size: 24rpx;" v-if="item.userType && item.userType == 2">二级好友
|
||||
<view class="text-cut" style="font-size: 24rpx;" v-if="item.userType && item.userType === 2">二级好友
|
||||
</view>
|
||||
<view class="text-cut" style="font-size: 24rpx;" v-else>一级好友
|
||||
</view>
|
||||
@@ -45,7 +45,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<empty v-if="userList.length == 0" />
|
||||
<empty v-if="userList.length === 0" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -84,7 +84,7 @@
|
||||
limit: this.limit
|
||||
}
|
||||
this.$u.api.queryInviter(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.inviterRecord = res.data.inviteMoney.money
|
||||
} else {
|
||||
uni.showToast({
|
||||
@@ -102,8 +102,8 @@
|
||||
userType:1
|
||||
}
|
||||
this.$u.api.inviter(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (this.page == 1) {
|
||||
if (res.code === 0) {
|
||||
if (this.page === 1) {
|
||||
this.userList = res.data.list
|
||||
} else {
|
||||
this.userList = [...this.userList, ...res.data.list]
|
||||
@@ -128,7 +128,7 @@
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cu-btn {
|
||||
.cuBtn {
|
||||
background: rgba(255, 117, 129, 0.2);
|
||||
color: #ff7581;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<view style="margin-bottom: 8upx"> 时间: {{item.createTime}}</view>
|
||||
<view style="margin-bottom: 8upx;text-align: right;">
|
||||
<!-- 提现金额: -->
|
||||
<text v-if="item.type==2" style="color: #666;font-size: 32upx;font-weight: 600"> - ¥{{item.money}}</text>
|
||||
<text v-if="item.type==1" style="color: #FD6416;font-size: 32upx;font-weight: 600">+ ¥{{item.money}}</text>
|
||||
<text v-if="item.type===2" style="color: #666;font-size: 32upx;font-weight: 600"> - ¥{{item.money}}</text>
|
||||
<text v-if="item.type===1" style="color: #FD6416;font-size: 32upx;font-weight: 600">+ ¥{{item.money}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
if (e.moneyType) {
|
||||
this.moneyType = e.moneyType
|
||||
uni.setNavigationBarTitle({
|
||||
title: e.moneyType == 1 ? '红包明细' : '金币明细'
|
||||
title: e.moneyType === 1 ? '红包明细' : '金币明细'
|
||||
});
|
||||
}
|
||||
if (e.viewType) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view class="containerView">
|
||||
<list-cell title="收款人姓名" type="text" placeholder="请输入支付宝收款人姓名" v-model="zhiFuBaoName"></list-cell>
|
||||
|
||||
<list-cell title="支付宝账号" type="text" placeholder="请输入要绑定的支付宝手机号" v-model="zhiFuBao"></list-cell>
|
||||
@@ -10,7 +10,6 @@
|
||||
<view style="padding: 32upx 64upx;font-size: 24upx;color: #999999;">提示:请正确填写收款人的支付宝账户和真实的收款人姓名,否则将无法正常收款</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -31,7 +30,7 @@
|
||||
|
||||
if (userId) {
|
||||
this.$u.api.userinfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
if (res.data.zhiFuBao) {
|
||||
this.zhiFuBao = res.data.zhiFuBao;
|
||||
}
|
||||
@@ -79,7 +78,7 @@
|
||||
this.$u.post('app/user/updateUser?zhiFuBao=' + zhiFuBao + '&zhiFuBaoName=' + zhiFuBaoName).then(
|
||||
res => {
|
||||
console.log(res);
|
||||
if(res.code==0){
|
||||
if(res.code===0){
|
||||
uni.setStorageSync('zhiFuBao', zhiFuBao)
|
||||
uni.setStorageSync('zhiFuBaoName', zhiFuBaoName)
|
||||
uni.showToast({
|
||||
@@ -135,7 +134,7 @@
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.container {
|
||||
.containerView {
|
||||
padding-top: 32upx;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
integral: this.value
|
||||
}
|
||||
this.$Request.postT('/app/integral/creditsExchange', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '积分兑换成功'
|
||||
})
|
||||
@@ -97,7 +97,7 @@
|
||||
//获取积分兑换比例
|
||||
getBili() {
|
||||
this.$u.get('/app/common/type/104').then(res => { // 积分兑换比例 104
|
||||
if (res.code == 0 && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.bili = res.data.value
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,14 +112,14 @@
|
||||
setTimeout(() => {
|
||||
this.refresherTriggered = false
|
||||
}, 1500)
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.pages = res.data.pages
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loadmore'
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.list = res.data.records
|
||||
} else {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
@@ -131,7 +131,7 @@
|
||||
getNum() {
|
||||
this.$Request.getT('/app/integral/selectByUserId').then(res => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.num = res.data.integralNum
|
||||
} else {
|
||||
this.num = 0
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</view>
|
||||
<view class="list-box-r-new flex align-center justify-between">
|
||||
<view class="list-box-r-new-l">
|
||||
{{item.over==1?'完结':'更新'+item.courseDetailsCount+'集'}}
|
||||
{{item.over===1?'完结':'更新'+item.courseDetailsCount+'集'}}
|
||||
</view>
|
||||
<view class="list-box-r-new-r">
|
||||
继续观看
|
||||
@@ -27,7 +27,7 @@
|
||||
<view class="" style="margin: 20rpx;" v-if="list.length > 0">
|
||||
<u-loadmore :status="status" />
|
||||
</view>
|
||||
<empty v-if="list.length == 0" title="暂无记录" />
|
||||
<empty v-if="list.length === 0" title="暂无记录" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -47,17 +47,17 @@
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.getList()
|
||||
this.getDataList()
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.page = 1
|
||||
this.getList()
|
||||
this.getDataList()
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.page < this.pages) {
|
||||
this.page += 1
|
||||
this.status = 'loading'
|
||||
this.getList()
|
||||
this.getDataList()
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
@@ -70,7 +70,7 @@
|
||||
})
|
||||
},
|
||||
//获取观看记录
|
||||
getList() {
|
||||
getDataList() {
|
||||
let data = {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
@@ -78,14 +78,14 @@
|
||||
}
|
||||
this.$Request.getT('/app/courseCollect/selectByUserId', data).then(res => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.pages = res.data.pages
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loadmore'
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.list = res.data.records
|
||||
} else {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
@@ -79,14 +79,14 @@
|
||||
}
|
||||
this.$u.api.courseList(data).then(res => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.pages = res.data.totalPage
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loadmore'
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.jqList = res.data.list
|
||||
} else {
|
||||
this.jqList = [...this.jqList, ...res.data.list]
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
state: 5
|
||||
}
|
||||
this.$u.api.message(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (this.page == 1) {
|
||||
if (res.code === 0) {
|
||||
if (this.page === 1) {
|
||||
this.msgList = res.data.list
|
||||
uni.stopPullDownRefresh();
|
||||
return
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<button class="confirm-btn" @click="toLogin" :disabled="logining">立即换绑
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{formatNumber(moneyNum)}}
|
||||
</view>
|
||||
</view>
|
||||
<view @click="goNav('/me/wallet/mingxi')" class="money-bto flex align-center justify-between">
|
||||
<view @click="goNav('/me/wallet/wallet_detail')" class="money-bto flex align-center justify-between">
|
||||
金币明细
|
||||
<u-icon name="arrow-right" color="#ff7581" size="40"></u-icon>
|
||||
</view>
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
</view>
|
||||
<view class="list-box-time flex align-center justify-between">
|
||||
{{item.createTime}}
|
||||
<text>{{item.type==1?'+':'-'}}{{item.money}}</text>
|
||||
<text>{{item.type===1?'+':'-'}}{{item.money}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="list.length > 3" class="loadmore">
|
||||
<view v-if="list.length > 3" class="loadMore">
|
||||
<u-loadmore :status="status" />
|
||||
</view>
|
||||
|
||||
<empty v-if="list.length==0" title="暂无明细" />
|
||||
<empty v-if="list.length===0" title="暂无明细" />
|
||||
<!-- 抖音im客服 -->
|
||||
<ttMsg />
|
||||
</view>
|
||||
@@ -66,14 +66,14 @@
|
||||
}
|
||||
this.$Request.getT('/app/moneyDetails/queryUserMoneyDetails', data).then(res => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.pages = res.data.pages
|
||||
if (this.page < this.pages) {
|
||||
this.status = 'loadmore'
|
||||
} else {
|
||||
this.status = 'nomore'
|
||||
}
|
||||
if (this.page == 1) {
|
||||
if (this.page === 1) {
|
||||
this.list = res.data.records
|
||||
} else {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
@@ -123,7 +123,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.loadmore {
|
||||
.loadMore {
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
</style>
|
||||
1206
other/blogger/blogger.vue
Normal file
1206
other/blogger/blogger.vue
Normal file
File diff suppressed because it is too large
Load Diff
800
other/blogger/details.vue
Normal file
800
other/blogger/details.vue
Normal file
@@ -0,0 +1,800 @@
|
||||
<template>
|
||||
<view class="template-details tn-safe-area-inset-bottom">
|
||||
<!-- 顶部自定义导航 -->
|
||||
<tn-nav-bar fixed alpha customBack>
|
||||
<view slot="back" class='tn-custom-nav-bar__back'
|
||||
@click="goBack">
|
||||
<text class='icon tn-icon-left'></text>
|
||||
<text class='icon tn-icon-home-capsule-fill'></text>
|
||||
</view>
|
||||
</tn-nav-bar>
|
||||
|
||||
<view class="" :style="{paddingTop: vuex_custom_bar_height + 'px'}">
|
||||
<!-- 图文信息 -->
|
||||
<block v-for="(item,index) in content" :key="index">
|
||||
<view class="blogger__item">
|
||||
<view class="blogger__author tn-flex tn-flex-row-between tn-flex-col-center">
|
||||
<view class="justify__author__info" @click="tn('/circlePages/blogger_other')">
|
||||
<view class="tn-flex tn-flex-row-center">
|
||||
<view class="tn-flex tn-flex-row-center tn-flex-col-center">
|
||||
<view class="">
|
||||
<tn-avatar
|
||||
class=""
|
||||
shape="circle"
|
||||
:src="item.userAvatar"
|
||||
size="lg">
|
||||
</tn-avatar>
|
||||
</view>
|
||||
<view class="tn-padding-right tn-text-ellipsis">
|
||||
<view class="tn-padding-right tn-padding-left-sm tn-text-bold tn-text-lg">{{ item.userName }}</view>
|
||||
<view class="tn-padding-right tn-padding-left-sm tn-padding-top-xs tn-color-gray">{{ item.date }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="blogger__author__btn justify-content-item tn-flex-col-center tn-flex-row-center">
|
||||
<!-- 既然都点到详情里面了,加个关注按钮呗 -->
|
||||
<text class="tn-bg-brown--light tn-round tn-text-df tn-text-bold tn-color-brown" style="padding: 10rpx 24rpx;">+ 关注</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="blogger__desc tn-margin-top-sm tn-margin-bottom-sm tn-text-justify tn-flex-col-center tn-flex-row-left">
|
||||
<view v-for="(label_item,label_index) in item.label" :key="label_index" class="blogger__desc__label tn-float-left tn-margin-right tn-bg-gray--light tn-round tn-text-sm tn-text-bold">
|
||||
<text class="blogger__desc__label--prefix">#</text>
|
||||
<text class="tn-text-df">{{ label_item }}</text>
|
||||
</view>
|
||||
<!-- 不用限制长度了,因为发布的时候限制长度了-->
|
||||
<text v-if="!item.label || item.label.length < 4" class="blogger__desc__content tn-flex-1 tn-text-justify tn-text-df">{{ item.desc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 内容太多疲劳了-->
|
||||
<!-- <view
|
||||
v-if="item.content"
|
||||
class="blogger__content"
|
||||
:id="`blogger__content--${index}`"
|
||||
>
|
||||
<view
|
||||
class="blogger__content__data clamp-text-2">
|
||||
{{ item.content }}
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<block v-if="item.mainImage">
|
||||
<view v-if="[1,2,4].indexOf(item.mainImage.length) != -1" class="tn-padding-top-xs">
|
||||
<image v-for="(image_item,image_index) in item.mainImage" :key="image_index"
|
||||
class="blogger__main-image"
|
||||
:class="{
|
||||
'blogger__main-image--1 tn-margin-bottom-sm': item.mainImage.length === 1,
|
||||
'blogger__main-image--2 tn-margin-right-sm tn-margin-bottom-sm': item.mainImage.length === 2 || item.mainImage.length === 4
|
||||
}"
|
||||
:src="image_item"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</view>
|
||||
<view v-else class="tn-padding-top-xs">
|
||||
<tn-grid hoverClass="none" :col="3">
|
||||
<block v-for="(image_item,image_index) in item.mainImage" :key="image_index">
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<tn-grid-item style="width: 30%;margin: 10rpx;">
|
||||
<image
|
||||
class="blogger__main-image blogger__main-image--3"
|
||||
:src="image_item"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</tn-grid-item>
|
||||
<!-- #endif-->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<tn-grid-item style="width: 30%;margin: 10rpx;">
|
||||
<image
|
||||
class="blogger__main-image blogger__main-image--3"
|
||||
:src="image_item"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
</tn-grid-item>
|
||||
<!-- #endif-->
|
||||
</block>
|
||||
</tn-grid>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-xs">
|
||||
<view class="justify-content-item tn-flex tn-flex-col-center">
|
||||
<view style="margin-right: 10rpx;">
|
||||
<tn-avatar-group :lists="item.viewUser.latestUserAvatar" size="sm"></tn-avatar-group>
|
||||
</view>
|
||||
<text class="tn-color-gray">{{ item.viewUser.viewUserCount }}人</text>
|
||||
</view>
|
||||
<view class="justify-content-item tn-color-gray tn-text-center">
|
||||
<view class="">
|
||||
<text class="blogger__count-icon tn-icon-footprint"></text>
|
||||
<text class="tn-padding-right">{{ item.collectionCount }}</text>
|
||||
<text class="blogger__count-icon tn-icon-message"></text>
|
||||
<text class="tn-padding-right">{{ item.commentCount }}</text>
|
||||
<text class="blogger__count-icon tn-icon-like"></text>
|
||||
<text class="">{{ item.likeCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 边距间隔 -->
|
||||
<!-- <view class="tn-strip-bottom" v-if="index != content.length - 1"></view> -->
|
||||
</block>
|
||||
|
||||
<!-- 按钮-->
|
||||
<view class="tn-flex tn-flex-row-between" style="margin: 40rpx 0 60rpx 0;">
|
||||
<view class="tn-flex-1 justify-content-item tn-margin-xs tn-text-center">
|
||||
<tn-button backgroundColor="#00FFC6" padding="40rpx 0" width="90%" shadow fontBold>
|
||||
<text class="tn-icon-like-lack tn-padding-right-xs tn-color-black"></text>
|
||||
<text class="tn-color-black">点 赞</text>
|
||||
</tn-button>
|
||||
</view>
|
||||
<view class="tn-flex-1 justify-content-item tn-margin-xs tn-text-center">
|
||||
<tn-button backgroundColor="#FFF00D" padding="40rpx 0" width="90%" shadow fontBold open-type="share">
|
||||
<text class="tn-icon-share-triangle tn-padding-right-xs tn-color-black"></text>
|
||||
<text class="tn-color-black">分 享</text>
|
||||
</tn-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 评论 -->
|
||||
<view class="tn-margin" style="padding-bottom: 120rpx;">
|
||||
<!-- 图标logo/头像 -->
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-xl" @click="tn('/circlePages/blogger_other')">
|
||||
<view class="justify-content-item">
|
||||
<view class="tn-flex tn-flex-col-center tn-flex-row-left">
|
||||
<view class="logo-pic tn-shadow">
|
||||
<view class="logo-image">
|
||||
<view class="tn-shadow-blur" style="background-image:url('https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg');width: 60rpx;height: 60rpx;background-size: cover;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-padding-right tn-padding-left-sm">
|
||||
<view class="tn-padding-right tn-text-df tn-text-bold tn-color-black">
|
||||
抓住那只高产母猪
|
||||
</view>
|
||||
<view class="tn-padding-right tn-text-ellipsis tn-text-xs tn-color-gray" style="padding-top: 5rpx;">
|
||||
2024年5月26日
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="justify-content-item tn-flex-row-center tn-flex-col-center tn-color-gray">
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-icon-like-lack tn-padding-xs"></text>
|
||||
</view>
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-text-xs">26</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="" style="margin: 20rpx 30rpx 30rpx 80rpx;">
|
||||
求打卡地点
|
||||
</view>
|
||||
<view class="tn-bg-gray--light tn-padding-sm" style="margin: 20rpx 30rpx 30rpx 80rpx;border-radius: 10rpx;">
|
||||
<text class="tn-text-bold tn-padding-right-xs">博主回复: </text>
|
||||
<text style="line-height: 40rpx;">保密</text>
|
||||
<view class="tn-flex tn-flex-row-between tn-margin-top-xs">
|
||||
<view class="justify-content-item tn-text-xs tn-color-gray" style="padding-top: 5rpx;">
|
||||
2024年5月26日
|
||||
</view>
|
||||
<view class="justify-content-item tn-text-xs tn-color-gray">
|
||||
<text class="tn-padding-xs">16</text>
|
||||
<text class="tn-icon-like-lack"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评论2-->
|
||||
<!-- 图标logo/头像 -->
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-xl" @click="tn('/circlePages/blogger_other')">
|
||||
<view class="justify-content-item">
|
||||
<view class="tn-flex tn-flex-col-center tn-flex-row-left">
|
||||
<view class="logo-pic tn-shadow">
|
||||
<view class="logo-image">
|
||||
<view class="tn-shadow-blur" style="background-image:url('https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg');width: 60rpx;height: 60rpx;background-size: cover;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-padding-right tn-padding-left-sm">
|
||||
<view class="tn-padding-right tn-text-df tn-text-bold tn-color-black">
|
||||
北染陌人
|
||||
</view>
|
||||
<view class="tn-padding-right tn-text-ellipsis tn-text-xs tn-color-gray" style="padding-top: 5rpx;">
|
||||
2024年5月25日
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="justify-content-item tn-flex-row-center tn-flex-col-center tn-color-gray">
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-icon-like-lack tn-padding-xs"></text>
|
||||
</view>
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-text-xs">68</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="" style="margin: 20rpx 30rpx 30rpx 80rpx;">
|
||||
求摄影师微信,谢谢
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 评论3-->
|
||||
<!-- 图标logo/头像 -->
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin-top-xl" @click="tn('/circlePages/blogger_other')">
|
||||
<view class="justify-content-item">
|
||||
<view class="tn-flex tn-flex-col-center tn-flex-row-left">
|
||||
<view class="logo-pic tn-shadow">
|
||||
<view class="logo-image">
|
||||
<view class="tn-shadow-blur" style="background-image:url('https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg');width: 60rpx;height: 60rpx;background-size: cover;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-padding-right tn-padding-left-sm">
|
||||
<view class="tn-padding-right tn-text-df tn-text-bold tn-color-black">
|
||||
原来是吖释鸭
|
||||
</view>
|
||||
<view class="tn-padding-right tn-text-ellipsis tn-text-xs tn-color-gray">
|
||||
2024年5月25日
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="justify-content-item tn-flex-row-center tn-flex-col-center tn-color-gray">
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-icon-like-lack tn-padding-xs"></text>
|
||||
</view>
|
||||
<view class="tn-text-center">
|
||||
<text class="tn-text-xs">43</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="" style="margin: 20rpx 30rpx 30rpx 80rpx;">
|
||||
吃瓜群众到此一游,阿巴阿巴
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<view class="tabbar footerfixed dd-glass">
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
|
||||
<view class="justify-content-item tn-margin-top">
|
||||
<view class="tn-flex tn-flex-row-center tn-flex-col-center">
|
||||
|
||||
|
||||
<!-- <view class="tn-flex tn-flex-row-center tn-padding-right tn-padding-left">
|
||||
<text class="tn-icon-emoji-good tn-text-xxl"></text>
|
||||
</view> -->
|
||||
|
||||
<view class="tn-flex tn-flex-row-center tn-flex-col-center tn-padding-right tn-padding-left-sm">
|
||||
<view class="avatar-all">
|
||||
<view class="tn-shadow-blur" style="background-image:url('https://resource.tuniaokj.com/images/blogger/onepiece-1.jpg');width: 60rpx;height: 60rpx;background-size: cover;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="topic__info__item__input tn-flex tn-flex-direction-row tn-flex-nowrap tn-flex-col-center tn-flex-row-left">
|
||||
<view class="topic__info__item__input__left-icon">
|
||||
<view class="tn-icon-emoji-good"></view>
|
||||
</view>
|
||||
<view class="topic__info__item__input__content">
|
||||
<input maxlength="20" placeholder-class="input-placeholder" :cursor-spacing="18" placeholder="不说点啥子吗?" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="justify-content-item tn-flex-row-center tn-flex-col-center tn-margin-top tn-margin-right">
|
||||
<view class="topic__info__item__sure">
|
||||
<view class="tn-flex-1 justify-content-item tn-text-center">
|
||||
<tn-button shape="round" backgroundColor="tn-cool-bg-color-15--reverse" width="100%" shadow>
|
||||
<text class="tn-color-white" hover-class="tn-hover" :hover-stay-time="150">
|
||||
发 送
|
||||
</text>
|
||||
</tn-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import template_page_mixin from '@/libs/mixin/template_page_mixin.js'
|
||||
export default {
|
||||
name: 'TemplateDetails',
|
||||
mixins: [template_page_mixin],
|
||||
data(){
|
||||
return {
|
||||
content: [
|
||||
/* {
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: ['追剧达人','重生粉','UI框架'],
|
||||
desc: '追剧比追人要轻松多了,助你开发酷炫UI一臂之力',
|
||||
content: '基础常用的布局元素,酷炫完善的配色体系,统一可增的图标 icon ,简便调用的功能组件,酷炫的前端页面,吖,编不下去了',
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
],
|
||||
viewUserCount: 62
|
||||
},
|
||||
collectionCount: 439,
|
||||
commentCount: 46,
|
||||
likeCount: 83
|
||||
},
|
||||
{
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: ['追剧达人','重生粉','UI框架'],
|
||||
desc: '追剧比追人要轻松多了,助你开发酷炫UI一臂之力',
|
||||
content: '基础常用的布局元素,酷炫完善的配色体系,统一可增的图标 icon ,简便调用的功能组件,酷炫的前端页面,吖,编不下去了',
|
||||
mainImage:[
|
||||
'https://resource.tuniaokj.com/images/blogger/content_1.jpeg'
|
||||
],
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
],
|
||||
viewUserCount: 12
|
||||
},
|
||||
collectionCount: 902,
|
||||
commentCount: 64,
|
||||
likeCount: 83
|
||||
},
|
||||
{
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: [],
|
||||
desc: '',
|
||||
content: '',
|
||||
mainImage:[
|
||||
'https://resource.tuniaokj.com/images/shop/computer2.jpg',
|
||||
'https://resource.tuniaokj.com/images/shop/prototype2.jpg',
|
||||
],
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
],
|
||||
viewUserCount: 56
|
||||
},
|
||||
collectionCount: 431,
|
||||
commentCount: 26,
|
||||
likeCount: 84
|
||||
},
|
||||
{
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: ['追剧达人','重生粉'],
|
||||
desc: '追剧比追人要轻松多了',
|
||||
content: '基础常用的布局元素,酷炫完善的配色体系,统一可增的图标 icon ,简便调用的功能组件,酷炫的前端页面,吖,编不下去了 基础常用的布局元素,酷炫完善的配色体系,统一可增的图标 icon ,简便调用的功能组件,酷炫的前端页面,吖,编不下去了',
|
||||
mainImage:[
|
||||
'https://resource.tuniaokj.com/images/swiper/swiper2.jpg',
|
||||
'https://resource.tuniaokj.com/images/swiper/swiper3.jpg',
|
||||
'https://resource.tuniaokj.com/images/swiper/swiper4.jpg',
|
||||
],
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
],
|
||||
viewUserCount: 231
|
||||
},
|
||||
collectionCount: 780,
|
||||
commentCount: 89,
|
||||
likeCount: 82
|
||||
},
|
||||
{
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/content_1.jpeg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: ['追剧达人','链接'],
|
||||
desc: 'https://www.yuque.com/tuniao',
|
||||
mainImage:[
|
||||
'https://resource.tuniaokj.com/images/shop/watch1.jpg',
|
||||
'https://resource.tuniaokj.com/images/shop/watch2.jpg',
|
||||
'https://resource.tuniaokj.com/images/shop/pillow2.jpg',
|
||||
'https://resource.tuniaokj.com/images/shop/pillow.jpg',
|
||||
],
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
],
|
||||
viewUserCount: 28
|
||||
},
|
||||
collectionCount: 432,
|
||||
commentCount: 33,
|
||||
likeCount: 12
|
||||
}, */
|
||||
{
|
||||
userAvatar: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg',
|
||||
userName: '可我会像',
|
||||
date: '2024年5月20日',
|
||||
label: ['追剧达人','重生粉'],
|
||||
desc: '追剧比追人要轻松多了',
|
||||
mainImage:[
|
||||
'https://resource.tuniaokj.com/images/blogger/y11.jpg',
|
||||
'https://resource.tuniaokj.com/images/blogger/y33.jpg',
|
||||
'https://resource.tuniaokj.com/images/blogger/y22.jpg',
|
||||
'https://resource.tuniaokj.com/images/blogger/y44.jpg',
|
||||
'https://resource.tuniaokj.com/images/blogger/y55.jpg',
|
||||
],
|
||||
viewUser: {
|
||||
latestUserAvatar: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
],
|
||||
viewUserCount: 65
|
||||
},
|
||||
collectionCount: 265,
|
||||
commentCount: 22,
|
||||
likeCount: 102
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 跳转
|
||||
tn(e) {
|
||||
uni.navigateTo({
|
||||
url: e,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 胶囊*/
|
||||
.tn-custom-nav-bar__back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 1000rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||
color: #FFFFFF;
|
||||
font-size: 18px;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
width: 1rpx;
|
||||
height: 110%;
|
||||
position: absolute;
|
||||
top: 22.5%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
opacity: 0.7;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* 文章内容 start*/
|
||||
.blogger {
|
||||
&__item {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
&__author {
|
||||
&__btn {
|
||||
margin-right: -12rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__desc {
|
||||
line-height: 55rpx;
|
||||
|
||||
&__label {
|
||||
padding: 0 20rpx;
|
||||
margin: 0rpx 18rpx 0 0;
|
||||
|
||||
&--prefix {
|
||||
color: #00FFC8;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
}
|
||||
&__content {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-top: 18rpx;
|
||||
padding-right: 18rpx;
|
||||
|
||||
&__data {
|
||||
line-height: 46rpx;
|
||||
text-align: justify;
|
||||
overflow: hidden;
|
||||
transition: all 0.25s ease-in-out;
|
||||
|
||||
}
|
||||
|
||||
&__status {
|
||||
margin-top: 10rpx;
|
||||
font-size: 26rpx;
|
||||
color: #82B2FF;
|
||||
}
|
||||
}
|
||||
|
||||
&__main-image {
|
||||
border-radius: 16rpx;
|
||||
|
||||
&--1 {
|
||||
max-width: 80%;
|
||||
max-height: 300rpx;
|
||||
}
|
||||
|
||||
&--2 {
|
||||
max-width: 260rpx;
|
||||
max-height: 260rpx;
|
||||
}
|
||||
|
||||
&--3 {
|
||||
height: 212rpx;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__count-icon {
|
||||
font-size: 40rpx;
|
||||
padding-right: 5rpx;
|
||||
}
|
||||
|
||||
&__ad {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
transform: translate3d(0px, 0px, 0px) !important;
|
||||
|
||||
::v-deep .uni-swiper-slide-frame {
|
||||
transform: translate3d(0px, 0px, 0px) !important;
|
||||
}
|
||||
.uni-swiper-slide-frame {
|
||||
transform: translate3d(0px, 0px, 0px) !important;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: left center;
|
||||
transform: translate3d(100%, 0px, 0px) scale(1) !important;
|
||||
transition: transform 0.25s ease-in-out;
|
||||
z-index: 1;
|
||||
|
||||
&--0 {
|
||||
transform: translate3d(0%, 0px, 0px) scale(1) !important;
|
||||
z-index: 4;
|
||||
}
|
||||
&--1 {
|
||||
transform: translate3d(13%, 0px, 0px) scale(0.9) !important;
|
||||
z-index: 3;
|
||||
}
|
||||
&--2 {
|
||||
transform: translate3d(26%, 0px, 0px) scale(0.8) !important;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
border-radius: 40rpx;
|
||||
width: 640rpx;
|
||||
height: 500rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 文章内容 end*/
|
||||
|
||||
/* 间隔线 start*/
|
||||
.tn-strip-bottom {
|
||||
width: 100%;
|
||||
border-bottom: 20rpx solid rgba(241, 241, 241, 0.8);
|
||||
}
|
||||
/* 间隔线 end*/
|
||||
|
||||
/* 头像 start */
|
||||
.logo-image {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logo-pic {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
// background-attachment:fixed;
|
||||
background-position: top;
|
||||
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
// background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
/* 底部 start*/
|
||||
.footerfixed{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.tabbar {
|
||||
align-items: center;
|
||||
min-height: 130rpx;
|
||||
padding: 0;
|
||||
height: calc(130rpx + env(safe-area-inset-bottom) / 2);
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom) / 2);
|
||||
padding-left: 10rpx;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
|
||||
/* 毛玻璃*/
|
||||
.dd-glass {
|
||||
width: 100%;
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
/* 头像*/
|
||||
.avatar-all {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid rgba(255,255,255,0.05);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 内容*/
|
||||
.topic {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
margin-bottom: 120rpx;
|
||||
|
||||
|
||||
/* 表单信息 start */
|
||||
&__info {
|
||||
margin: 0 50rpx;
|
||||
margin-top: 105rpx;
|
||||
padding: 30rpx 51rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255,255,255,1);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||||
|
||||
&__item {
|
||||
|
||||
&__input {
|
||||
width: 400rpx;
|
||||
height: 60rpx;
|
||||
border: 1rpx solid #C6D1D8;
|
||||
border-radius: 39rpx;
|
||||
|
||||
&__left-icon {
|
||||
width: 10%;
|
||||
font-size: 44rpx;
|
||||
margin-left: 20rpx;
|
||||
margin-right: 5rpx;
|
||||
color: #C6D1D8;
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 80%;
|
||||
padding-left: 10rpx;
|
||||
|
||||
&--verify-code {
|
||||
width: 56%;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 30rpx;
|
||||
color: #78909C;
|
||||
// letter-spacing: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
width: 10%;
|
||||
font-size: 34rpx;
|
||||
margin-right: 20rpx;
|
||||
color: #78909C;
|
||||
}
|
||||
|
||||
&__right-verify-code {
|
||||
width: 34%;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
text-align: center;
|
||||
font-size: 31rpx;
|
||||
font-weight: bold;
|
||||
line-height: 77rpx;
|
||||
// text-indent: 1em;
|
||||
border-radius: 100rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
// border: 2rpx solid #FFFFFF;
|
||||
}
|
||||
|
||||
&__sure {
|
||||
height: 60rpx;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/* 表单信息 end */
|
||||
|
||||
/* 内容 end */
|
||||
|
||||
}
|
||||
|
||||
/deep/.input-placeholder {
|
||||
font-size: 30rpx;
|
||||
color: #C6D1D8;
|
||||
}
|
||||
|
||||
</style>
|
||||
1042
other/index/index.vue
Normal file
1042
other/index/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
163
other/slotMachine/components/pop-ling-qu.vue
Normal file
163
other/slotMachine/components/pop-ling-qu.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-popup v-model="show" mode="center" @close="close">
|
||||
<view class="bg">
|
||||
<view class="title">恭喜您获得</view>
|
||||
<view class="goods">
|
||||
<template v-if="result&&result.type==3">
|
||||
<view class="u-flex u-col-center u-row-center">
|
||||
<image style="height: 100px;" :src="result.img" mode="heightFix"></image>
|
||||
</view>
|
||||
<view class="u-flex u-row-center u-m-t-30">
|
||||
<view class="type">{{result.name}}</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="result&&result.type==2">
|
||||
<view class="u-flex color-money u-col-center u-row-center">
|
||||
<view class="money">{{result.number}}</view>
|
||||
<view class="font-bold " style="margin-top: 20rpx;font-size: 36rpx;">元</view>
|
||||
</view>
|
||||
<view class="u-flex u-m-t-24 u-row-center">
|
||||
<view class="type">现金红包</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="u-flex close u-row-center">
|
||||
<u-icon name="close-circle" :size="54" @click="close" color="#fff"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
result: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(data) {
|
||||
console.log(data);
|
||||
this.result = data
|
||||
this.show = true
|
||||
},
|
||||
close() {
|
||||
console.log('抽奖弹窗关闭');
|
||||
this.show = false
|
||||
// if(!this.result){
|
||||
// return
|
||||
// }
|
||||
// const {
|
||||
// orderId,
|
||||
// id
|
||||
// } = this.result
|
||||
// this.$Request.postJson('app/discSpinning/receive', this.result).then(res => {
|
||||
// this.result = ''
|
||||
// console.log(res)
|
||||
// if (res.code == 0) {
|
||||
// console.log('抽奖领取成功');
|
||||
// const key=res.data==0?'isBindAliPay':undefined
|
||||
// this.$emit('close',key)
|
||||
// if(key&&key=='isBindAliPay'){
|
||||
// uni.navigateTo({
|
||||
// url:'/me/invite/zhifubao'
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .u-mode-center-box {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.color-money {
|
||||
color: #E42F00;
|
||||
}
|
||||
|
||||
.money {
|
||||
font-weight: 700;
|
||||
font-size: 96rpx;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.bg {
|
||||
width: 628rpx;
|
||||
height: 770rpx;
|
||||
margin-right: 10rpx;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
background-image: url("~static/images/zhuanpan/gift.png");
|
||||
position: relative;
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 2),
|
||||
(min-device-pixel-ratio: 2) {
|
||||
background-image: url("~static/images/zhuanpan/gift@2x.png");
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 218rpx;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-weight: 700;
|
||||
font-size: 60rpx;
|
||||
color: #AF6920;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.goods {
|
||||
position: absolute;
|
||||
top: 336rpx;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.type {
|
||||
padding: 6rpx 28rpx;
|
||||
border-radius: 100rpx;
|
||||
background: #E25B41;
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close{
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.btn-box {
|
||||
position: absolute;
|
||||
top: 574rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.btn {
|
||||
padding: 10rpx 60rpx 10rpx 64rpx;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 44rpx;
|
||||
color: #AF6920;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +1,73 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
|
||||
<u-navbar title="抽奖" back-icon-color="#fff" :background="{background:'transparent'}" immersive :border-bottom="false"
|
||||
title-color="#fff"></u-navbar>
|
||||
|
||||
<u-navbar title="抽奖" back-icon-color="#fff" :background="{background:'transparent'}" immersive
|
||||
:border-bottom="false" title-color="#fff"></u-navbar>
|
||||
<view class="sm">
|
||||
<HM-slotMachine ref="HMslotMachine"></HM-slotMachine>
|
||||
</view>
|
||||
<view class="start" @tap="start">
|
||||
<text>开 始</text>
|
||||
</view>
|
||||
|
||||
<ling-qu ref="refLingqu"></ling-qu>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import lingQu from './components/pop-ling-qu.vue'
|
||||
|
||||
function isAllElementssame(array, key) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
if (array[index][key] != array[0][key]) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
lingQu
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prizeList:[
|
||||
{name:'iPhone13',value:'iPhone',img:require('@/other/static/1.png')},
|
||||
{name:'airPods3',value:'airPods',img:require('@/other/static/2.png')},
|
||||
{name:'行李箱',value:'luggage',img:require('@/other/static/3.png')},
|
||||
{name:'风筒',value:'dryer',img:require('@/other/static/4.png')},
|
||||
{name:'平行车',value:'balanceCar',img:require('@/other/static/5.png')},
|
||||
{name:'iPad5',value:'iPad',img:require('@/other/static/6.png')}
|
||||
prizeList: [{
|
||||
name: 'iPhone13',
|
||||
value: 'iPhone',
|
||||
img: require('@/other/static/1.png')
|
||||
},
|
||||
{
|
||||
name: 'airPods3',
|
||||
value: 'airPods',
|
||||
img: require('@/other/static/2.png')
|
||||
},
|
||||
{
|
||||
name: '行李箱',
|
||||
value: 'luggage',
|
||||
img: require('@/other/static/3.png')
|
||||
},
|
||||
{
|
||||
name: '风筒',
|
||||
value: 'dryer',
|
||||
img: require('@/other/static/4.png')
|
||||
},
|
||||
{
|
||||
name: '平行车',
|
||||
value: 'balanceCar',
|
||||
img: require('@/other/static/5.png')
|
||||
},
|
||||
{
|
||||
name: 'iPad5',
|
||||
value: 'iPad',
|
||||
img: require('@/other/static/6.png')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
||||
|
||||
},
|
||||
onReady() {
|
||||
// init(options)初始化抽奖组件
|
||||
@@ -37,30 +77,45 @@
|
||||
// duration 总抽奖时长 毫秒
|
||||
// direction 滚动方向 up|down
|
||||
this.$refs.HMslotMachine.init({
|
||||
prizeList:this.prizeList,
|
||||
defaultResults:['iPhone','iPhone','iPhone'],
|
||||
duration:4000,
|
||||
direction:'up'
|
||||
prizeList: this.prizeList,
|
||||
defaultResults: ['iPhone', 'iPhone', 'iPhone'],
|
||||
duration: 4000,
|
||||
direction: 'up'
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
start(){
|
||||
showLingPop(data) {
|
||||
this.$refs.refLingqu.open(data)
|
||||
},
|
||||
start() {
|
||||
// roll(options)开始摇奖
|
||||
// 参数说明
|
||||
// results 开奖结果,结构[value,value,value] value为奖品数据的value值
|
||||
// success 开奖回调 e={results} results为开奖结果数据
|
||||
const that = this;
|
||||
this.$refs.HMslotMachine.roll({
|
||||
results:this.getResults(),
|
||||
success:(e)=>{
|
||||
|
||||
console.log("success e: ",e);
|
||||
results: this.getResults(),
|
||||
success: (e) => {
|
||||
console.log("success e: ", e);
|
||||
const item = isAllElementssame(e.results, 'value') ? e.results[0] : undefined
|
||||
if (item) {
|
||||
that.showLingPop({...item,type:3})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '很遗憾,未中奖',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
getResults(){
|
||||
getResults() {
|
||||
// 生成随机的抽奖结果 实际应用应该ajax请求后台,让后台返回开奖结果
|
||||
let max = this.prizeList.length-1;
|
||||
let arr = [Math.floor(Math.random() * (max - 1 + 1) + 1),Math.floor(Math.random() * (max - 1 + 1) + 1),Math.floor(Math.random() * (max - 1 + 1) + 1)];
|
||||
let max = this.prizeList.length - 1;
|
||||
let arr = [Math.floor(Math.random() * (max - 1 + 1) + 1), Math.floor(Math.random() * (max - 1 + 1) + 1),
|
||||
Math.floor(Math.random() * (max - 1 + 1) + 1)
|
||||
];
|
||||
return [
|
||||
this.prizeList[arr[0]].value,
|
||||
this.prizeList[arr[1]].value,
|
||||
@@ -71,19 +126,22 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page{
|
||||
background-image: linear-gradient(to top,#8F1E70, #51279A);
|
||||
min-height: calc(100vh - var(--window-bottom) - var(--window-top));
|
||||
}
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-image: linear-gradient(to top, #8F1E70, #51279A);
|
||||
min-height: calc(100vh - var(--window-bottom) - var(--window-top));
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.sm{
|
||||
|
||||
.sm {
|
||||
margin-top: 200rpx;
|
||||
}
|
||||
.start{
|
||||
|
||||
.start {
|
||||
width: 70%;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
@@ -95,7 +153,8 @@ page{
|
||||
box-shadow: 0 1px 2px rgba($color: #51279A, $alpha: 1);
|
||||
border-bottom: solid 3px #8d5805;
|
||||
box-sizing: border-box;
|
||||
text{
|
||||
|
||||
text {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #b51c06;
|
||||
@@ -103,6 +162,4 @@ page{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</style>
|
||||
BIN
other/static/ad.png
Normal file
BIN
other/static/ad.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
BIN
other/static/avatar/5.png
Normal file
BIN
other/static/avatar/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
other/static/avatar/6.png
Normal file
BIN
other/static/avatar/6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
other/static/avatar/7.png
Normal file
BIN
other/static/avatar/7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
779
other/tools/tools.vue
Normal file
779
other/tools/tools.vue
Normal file
@@ -0,0 +1,779 @@
|
||||
<template>
|
||||
<view class="template-activity tn-safe-area-inset-bottom">
|
||||
<!-- 顶部自定义导航 -->
|
||||
<tn-nav-bar fixed alpha customBack>
|
||||
<view slot="back" class='tn-custom-nav-bar__back' @click="goBack">
|
||||
<text class='icon tn-icon-left'></text>
|
||||
<text class='icon tn-icon-home-capsule-fill'></text>
|
||||
</view>
|
||||
</tn-nav-bar>
|
||||
|
||||
|
||||
<!-- 方式4 start-->
|
||||
<!-- <view class="tn-flex">
|
||||
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="tn('/activityPages/planet')">
|
||||
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
|
||||
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
|
||||
<view class="tn-icon-discover-planet-fill tn-cool-color-icon4 tn-cool-bg-color-5"></view>
|
||||
</view>
|
||||
<view class="tn-color-gray--dark tn-text-center">
|
||||
<text class="tn-text-ellipsis">知识星球</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="tn('/activityPages/project')">
|
||||
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
|
||||
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
|
||||
<view class="tn-icon-trophy-fill tn-cool-color-icon4 tn-cool-bg-color-15"></view>
|
||||
</view>
|
||||
<view class="tn-color-gray--dark tn-text-center">
|
||||
<text class="tn-text-ellipsis">开源项目</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="tn('/activityPages/map')">
|
||||
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
|
||||
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
|
||||
<view class="tn-icon-map-fill tn-cool-color-icon4 tn-cool-bg-color-8"></view>
|
||||
</view>
|
||||
<view class="tn-color-gray--dark tn-text-center">
|
||||
<text class="tn-text-ellipsis">地图打卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-flex-1 tn-padding-sm tn-radius" @click="tn('/activityPages/study')">
|
||||
<view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
|
||||
<view class="icon4__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur">
|
||||
<view class="tn-icon-creative-fill tn-cool-color-icon4 tn-cool-bg-color-3"></view>
|
||||
</view>
|
||||
<view class="tn-color-gray--dark tn-text-center">
|
||||
<text class="tn-text-ellipsis">课程学习</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 方式4 end-->
|
||||
|
||||
<view class="tn-margin-top-lg">
|
||||
<view class="nav_title--wrap">
|
||||
<view class="nav_title tn-cool-bg-color-15">
|
||||
<text class="tn-icon-star tn-padding-right-sm"></text>
|
||||
工 / 具 / 集 / 合
|
||||
<text class="tn-icon-star tn-padding-left-sm"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class='nav-list tn-margin-bottom tn-margin-top'>
|
||||
|
||||
<block v-for="(item, index) in list1" :key="index">
|
||||
<view class="nav-list-item tn-shadow-blur tn-cool-bg-image"
|
||||
:class="['tn-main-gradient-' + item.color + '--light']">
|
||||
<view class="nav-link">
|
||||
<view class='title tn-text-bold' style="color: #080808;">{{ item.title }}</view>
|
||||
<view class='join tn-color-grey tn-text-sm'>{{ item.join }} 人参与</view>
|
||||
</view>
|
||||
<view class="icon tn-shadow-blur" :class="['tn-bg-' + item.color]">
|
||||
<view class="" :class="['tn-icon-' + item.icon]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="tn-margin-top-lg">
|
||||
<view class="nav_title--wrap">
|
||||
<view class="nav_title tn-cool-bg-color-15">
|
||||
<text class="tn-icon-star tn-padding-right-sm"></text>
|
||||
友 / 情 / 链 / 接
|
||||
<text class="tn-icon-star tn-padding-left-sm"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class='nav-list tn-margin-bottom tn-margin-top'>
|
||||
|
||||
<navigator class="nav-list-item tn-shadow-blur tn-cool-bg-image"
|
||||
:class="['tn-main-gradient-' + item.color + '--light']" target="miniProgram" :app-id="item.appid"
|
||||
:path="item.path" version="release" delta="1" hover-class="none" v-for="(item, index) in linksData"
|
||||
:key="index">
|
||||
<view class="nav-link">
|
||||
<view class='title tn-text-bold' style="color: #080808;">{{ item.title }}</view>
|
||||
<view class='join tn-color-grey tn-text-sm'>{{ item.join }} 人查看</view>
|
||||
</view>
|
||||
<view class="icon tn-shadow-blur" :class="['tn-bg-' + item.color]">
|
||||
<view class="" :class="['tn-icon-' + item.icon]"></view>
|
||||
</view>
|
||||
</navigator>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<view class='tn-tabbar-height'></view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Discovery',
|
||||
data() {
|
||||
return {
|
||||
cardCur: 0,
|
||||
swiperList: [{
|
||||
id: 0,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro1.jpg',
|
||||
}, {
|
||||
id: 1,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro2.jpg',
|
||||
}, {
|
||||
id: 2,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro3.jpg',
|
||||
}, {
|
||||
id: 3,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro4.jpg',
|
||||
}, {
|
||||
id: 4,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro5.jpg',
|
||||
}, {
|
||||
id: 5,
|
||||
type: 'image',
|
||||
url: 'https://resource.tuniaokj.com/images/index_bg/pro6.jpg',
|
||||
}],
|
||||
list1: [{
|
||||
icon: 'honor-fill',
|
||||
title: '称呼计算器',
|
||||
join: '629',
|
||||
color: 'blue'
|
||||
},
|
||||
{
|
||||
icon: 'count-fill',
|
||||
title: '支付宝语音生成',
|
||||
join: '268',
|
||||
color: 'purplered'
|
||||
},
|
||||
{
|
||||
icon: 'gloves-fill',
|
||||
title: '一周天气预报',
|
||||
join: '332',
|
||||
color: 'cyan'
|
||||
},
|
||||
{
|
||||
icon: 'trusty-fill',
|
||||
title: '今日星座运势',
|
||||
join: '106',
|
||||
color: 'orangeyellow'
|
||||
},
|
||||
{
|
||||
icon: 'hardware-fill',
|
||||
title: '来碗毒鸡汤',
|
||||
join: '98',
|
||||
color: 'indigo'
|
||||
},
|
||||
{
|
||||
icon: 'baby-fill',
|
||||
title: '垃圾分一分',
|
||||
join: '57',
|
||||
color: 'red'
|
||||
},
|
||||
{
|
||||
icon: 'safe-fill',
|
||||
title: '手持弹幕',
|
||||
join: '76',
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
icon: 'flag-fill',
|
||||
title: '孩子取名',
|
||||
join: '225',
|
||||
color: 'orange'
|
||||
},
|
||||
{
|
||||
icon: 'topics-fill',
|
||||
title: '午餐吃什么',
|
||||
join: '422',
|
||||
color: 'teal'
|
||||
},
|
||||
{
|
||||
icon: 'light-fill',
|
||||
title: '朋友圈文案',
|
||||
join: '983',
|
||||
color: 'orangered'
|
||||
}
|
||||
],
|
||||
linksData: [{
|
||||
icon: 'honor-fill',
|
||||
join: '629',
|
||||
color: 'purplered',
|
||||
url: '',
|
||||
title: '司命',
|
||||
appid: 'wx734d93edc5b48019',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'count-fill',
|
||||
join: '268',
|
||||
color: 'blue',
|
||||
url: '',
|
||||
title: '爱小睡眠',
|
||||
appid: 'wx65880bde88b32037',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'gloves-fill',
|
||||
join: '332',
|
||||
color: 'orangeyellow',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'trusty-fill',
|
||||
join: '106',
|
||||
color: 'cyan',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'hardware-fill',
|
||||
join: '98',
|
||||
color: 'red',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'baby-fill',
|
||||
join: '57',
|
||||
color: 'indigo',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'safe-fill',
|
||||
join: '76',
|
||||
color: 'orange',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'flag-fill',
|
||||
join: '225',
|
||||
color: 'green',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'topics-fill',
|
||||
join: '422',
|
||||
color: 'orangered',
|
||||
url: '',
|
||||
title: '群友作品',
|
||||
appid: '',
|
||||
path: 'pages/index/index'
|
||||
},
|
||||
{
|
||||
icon: 'light-fill',
|
||||
join: '983',
|
||||
color: 'teal',
|
||||
url: '',
|
||||
title: '表情包制作',
|
||||
appid: 'wx096589e82af2ffa5',
|
||||
path: 'pages/index/index'
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack(){
|
||||
uni.navigateBack()
|
||||
},
|
||||
// cardSwiper
|
||||
cardSwiper(e) {
|
||||
this.cardCur = e.detail.current
|
||||
},
|
||||
// 跳转
|
||||
tn(e) {
|
||||
uni.navigateTo({
|
||||
url: e,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.template-activity {
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.tn-tabbar-height {
|
||||
min-height: 120rpx;
|
||||
height: calc(140rpx + env(safe-area-inset-bottom) / 2);
|
||||
}
|
||||
|
||||
/* 胶囊*/
|
||||
.tn-custom-nav-bar__back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 1000rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||
color: #FFFFFF;
|
||||
font-size: 18px;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
width: 1rpx;
|
||||
height: 110%;
|
||||
position: absolute;
|
||||
top: 22.5%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
opacity: 0.7;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* .tnphone-white-min 细边框*/
|
||||
.tnphone-white-min {
|
||||
width: 380rpx;
|
||||
height: 800rpx;
|
||||
border-radius: 40rpx;
|
||||
background: #E9E5F3;
|
||||
padding: 7rpx;
|
||||
display: table;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 0, 0, 0.15);
|
||||
margin: 70rpx auto;
|
||||
cursor: default;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.tnphone-white-min .skin {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 40rpx;
|
||||
background: #E9E5F3;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.tnphone-white-min .screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 30rpx;
|
||||
background: #E9E5F3;
|
||||
position: relative;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.tnphone-white-min .head {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
padding: 45rpx 15rpx 10rpx 15rpx;
|
||||
}
|
||||
|
||||
.tnphone-white-min .peak {
|
||||
left: 22%;
|
||||
width: 56%;
|
||||
height: 27rpx;
|
||||
margin: -2rpx auto 0rpx;
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
background: #E9E5F3;
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.tnphone-white-min .sound {
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 15rpx;
|
||||
background: #555;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -24rpx;
|
||||
margin-top: -10rpx;
|
||||
box-shadow: 0rpx 4rpx 4rpx 0rpx #444 inset
|
||||
}
|
||||
|
||||
.tnphone-white-min .lens {
|
||||
width: 6rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 50%;
|
||||
background: #2c5487;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: 34rpx;
|
||||
margin-top: -10rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .talk {
|
||||
width: 50%;
|
||||
height: 6rpx;
|
||||
border-radius: 15rpx;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
left: 50%;
|
||||
margin-left: -25%
|
||||
}
|
||||
|
||||
.tnphone-white-min .area-l,
|
||||
.tnphone-white-min .area-r {
|
||||
width: 70rpx;
|
||||
height: 16rpx;
|
||||
position: absolute;
|
||||
top: 6rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .area-l {
|
||||
left: 0;
|
||||
text-align: center;
|
||||
font-size: 12rpx;
|
||||
line-height: 22rpx;
|
||||
text-indent: 10rpx;
|
||||
font-weight: 600;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.tnphone-white-min .area-r {
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 12rpx;
|
||||
line-height: 22rpx;
|
||||
text-indent: 10rpx;
|
||||
font-weight: 600;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.tnphone-white-min .fa-feed {
|
||||
float: left;
|
||||
font-size: 12rpx !important;
|
||||
transform: rotate(-45deg);
|
||||
margin-top: 4rpx;
|
||||
margin-right: 8rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .fa-battery-full {
|
||||
float: left;
|
||||
font-size: 12rpx !important;
|
||||
margin-top: 6rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .fa-chevron-left {
|
||||
float: left;
|
||||
margin-top: 4rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .fa-cog {
|
||||
float: right;
|
||||
margin-top: 4rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .btn01 {
|
||||
width: 3rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 3rpx 0 0 3rpx;
|
||||
background: #222;
|
||||
position: absolute;
|
||||
top: 105rpx;
|
||||
left: -3rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .btn02 {
|
||||
width: 3rpx;
|
||||
height: 54rpx;
|
||||
border-radius: 3rpx 0 0 3rpx;
|
||||
background: #222;
|
||||
position: absolute;
|
||||
top: 160rpx;
|
||||
left: -3rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .btn03 {
|
||||
width: 3rpx;
|
||||
height: 54rpx;
|
||||
border-radius: 3rpx 0 0 3rpx;
|
||||
background: #222;
|
||||
position: absolute;
|
||||
top: 230rpx;
|
||||
left: -3rpx
|
||||
}
|
||||
|
||||
.tnphone-white-min .btn04 {
|
||||
width: 3rpx;
|
||||
height: 86rpx;
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
background: #222;
|
||||
position: absolute;
|
||||
top: 180rpx;
|
||||
right: -3rpx
|
||||
}
|
||||
|
||||
|
||||
/* 顶部背景图 start */
|
||||
.top-backgroup {
|
||||
height: 450rpx;
|
||||
z-index: -1;
|
||||
|
||||
.backgroud-image {
|
||||
width: 100%;
|
||||
height: 667rpx;
|
||||
// z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部背景图 end */
|
||||
|
||||
/* 轮播样机样式 start*/
|
||||
.card-swiper {
|
||||
height: 830rpx !important;
|
||||
}
|
||||
|
||||
.card-swiper swiper-item {
|
||||
width: 260rpx !important;
|
||||
// left: 170rpx;
|
||||
// width: 380rpx !important;
|
||||
// left: 185rpx;
|
||||
box-sizing: border-box;
|
||||
padding: 0rpx 15rpx 90rpx 15rpx;
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.card-swiper swiper-item .swiper-item {
|
||||
display: block;
|
||||
transform: scale(0.45);
|
||||
transition: all 0.2s ease-in 0s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-swiper swiper-item.cur .swiper-item {
|
||||
transform: scale(0.65);
|
||||
transition: all 0.2s ease-in 0s;
|
||||
}
|
||||
|
||||
.image-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-banner image {
|
||||
width: 100%;
|
||||
height: 770rpx;
|
||||
// border: 1rpx solid red;
|
||||
}
|
||||
|
||||
/* 轮播指示点 start*/
|
||||
.indication {
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
height: 36rpx;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spot {
|
||||
background-color: #000;
|
||||
opacity: 0;
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
border-radius: 20rpx;
|
||||
margin: 0 8rpx !important;
|
||||
top: -80rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.spot.active {
|
||||
opacity: 0;
|
||||
width: 30rpx;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/* 图标容器4 start */
|
||||
.tn-cool-color-icon4 {
|
||||
// background-image: -webkit-linear-gradient(135deg, #ED1C24, #FECE12); 16
|
||||
// background-image: linear-gradient(135deg, #ED1C24, #FECE12);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.icon4 {
|
||||
&__item {
|
||||
width: 30%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 10rpx;
|
||||
padding: 30rpx;
|
||||
margin: 20rpx 10rpx;
|
||||
transform: scale(1);
|
||||
transition: transform 0.3s linear;
|
||||
transform-origin: center center;
|
||||
|
||||
&--icon {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
font-size: 55rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 18rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-shadow: 0px 10px 30px rgba(70, 23, 129, 0.12),
|
||||
0px -8px 40px rgba(255, 255, 255, 1),
|
||||
inset 0px -10px 10px rgba(70, 23, 129, 0.05),
|
||||
inset 0px 10px 20px rgba(255, 255, 255, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 标题 start */
|
||||
.nav_title {
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
|
||||
&--wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 120rpx;
|
||||
font-size: 42rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
background-image: url(https://resource.tuniaokj.com/images/title_bg/title44.png);
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
/* 标题 end */
|
||||
|
||||
/* 组件导航列表 start*/
|
||||
.nav-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0rpx 12rpx 0rpx;
|
||||
justify-content: space-between;
|
||||
|
||||
/* 列表元素 start */
|
||||
.nav-list-item {
|
||||
padding: 95rpx 30rpx 5rpx 30rpx;
|
||||
border-radius: 12rpx;
|
||||
width: 45%;
|
||||
margin: 0 18rpx 40rpx;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
|
||||
/* 元素标题 start */
|
||||
.nav-link {
|
||||
font-size: 32rpx;
|
||||
text-transform: capitalize;
|
||||
padding: 0 0 10rpx 0;
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
color: #FFFFFF;
|
||||
margin-top: 100rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.join {
|
||||
color: #FFFFFF;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 元素标题 end */
|
||||
|
||||
/* 元素图标 start */
|
||||
.icon {
|
||||
font-variant: small-caps;
|
||||
position: absolute;
|
||||
top: 60rpx;
|
||||
right: 50rpx;
|
||||
left: 37%;
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
white-space: nowrap;
|
||||
opacity: 0.9;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-size: cover;
|
||||
background-position: 50%;
|
||||
border-radius: 5000rpx;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
opacity: 1;
|
||||
transform: scale(1, 1);
|
||||
background-size: 100% 100%;
|
||||
background-image: url(https://resource.tuniaokj.com/images/cool_bg_image/icon_bg2.png);
|
||||
}
|
||||
}
|
||||
|
||||
/* 元素图标 end */
|
||||
}
|
||||
|
||||
/* 列表元素 end */
|
||||
}
|
||||
|
||||
/* 组件导航列表 end*/
|
||||
</style>
|
||||
570
other/topic/reserve.vue
Normal file
570
other/topic/reserve.vue
Normal file
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<view class="template-reserve tn-safe-area-inset-bottom">
|
||||
<!-- 顶部自定义导航 -->
|
||||
<tn-nav-bar fixed alpha customBack>
|
||||
<view slot="back" class='tn-custom-nav-bar__back'
|
||||
@click="goBack">
|
||||
<text class='icon tn-icon-left'></text>
|
||||
<text class='icon tn-icon-home-capsule-fill'></text>
|
||||
</view>
|
||||
</tn-nav-bar>
|
||||
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<view class="slideshow">
|
||||
<view class="slideshow-image" style="background-image: url('https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg')"></view>
|
||||
<view class="slideshow-image" style="background-image: url('https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg')"></view>
|
||||
<view class="slideshow-image" style="background-image: url('https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg')"></view>
|
||||
<view class="slideshow-image" style="background-image: url('https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg')"></view>
|
||||
</view>
|
||||
|
||||
<view class="reserve tn-safe-area-inset-bottom" :style="{paddingTop: vuex_custom_bar_height + 'px'}">
|
||||
<view class="reserve-content tn-padding tn-color-black tn-text-lg dd-glass2" style="margin:71vh 30rpx 20vh 30rpx">
|
||||
<view class="tn-text-center tn-text-bold tn-padding-top tn-padding-bottom">
|
||||
活动详情
|
||||
</view>
|
||||
<view class="">
|
||||
|
||||
<view class="blogger__desc tn-margin-top-sm tn-margin-bottom-sm tn-text-justify tn-flex-col-center tn-flex-row-left">
|
||||
<view class="blogger__desc__label tn-float-left tn-margin-right tn-bg-gray--light tn-round tn-text-sm tn-text-bold">
|
||||
<text class="blogger__desc__label--prefix">#</text>
|
||||
<text class="tn-text-df">常回家看看</text>
|
||||
</view>
|
||||
<!-- 不用限制长度了,因为发布的时候限制长度了-->
|
||||
<text class="blogger__desc__content tn-flex-1 tn-text-justify tn-text-df">
|
||||
可爱的校友,让我们相约华软,来一场说看就看的木棉雨;通知将于活动前一晚送达,请查收!!
|
||||
</text>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
<view class="tn-padding-top-lg">
|
||||
集合时间:2024年12月20日 12:00:00
|
||||
</view>
|
||||
<view class="">
|
||||
集合地点:喷泉广场
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- <view class="tn-margin-top tn-color-black tn-bg-white tn-radius" >
|
||||
<view class="tn-flex tn-flex-row-between tn-flex-col-center">
|
||||
<view class="justify-content-item">
|
||||
<view class="tn-flex tn-flex-col-center tn-flex-row-left">
|
||||
<view class="tn-padding tn-color-black">
|
||||
<view class="tn-padding-right-sm tn-text-lg tn-text-bold clamp-text-1">
|
||||
广东省广州市番禺祈福新村129号
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-flex justify-content-item tn-flex-row-center tn-padding-right-xs">
|
||||
<view class="tn-bg-gray--light tn-padding-xs tn-margin-sm tn-color-black tn-round">
|
||||
<text class="tn-icon-location-fill" style="font-size: 50rpx;"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="tn-text-center tn-text-bold tn-padding-top-xl">
|
||||
活动参与者
|
||||
</view>
|
||||
|
||||
<tn-read-more :closeBtn="true" openText="查看更多参与者" openIcon="more-circle" closeText="折叠起来" closeIcon="close" :showHeight="300">
|
||||
|
||||
<view class="tn-flex tn-flex-wrap tn-margin-top-xl">
|
||||
<block v-for="(item, index) in groupList" :key="index">
|
||||
<view class="tn-padding-bottom tn-padding-left" style="width: 20%;">
|
||||
<view class="tn-flex tn-flex-row-left tn-flex-col-center" style="">
|
||||
<view class="user-pic">
|
||||
<view class="user-image">
|
||||
<view class="tn-shadow-blur" :style="'background-image:url('+ item.src +');width: 70rpx;height: 70rpx;background-size: cover;'">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
</tn-read-more>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<view class="tn-footerfixed tn-flex tn-flex-row-between tn-flex-col-center tn-padding tn-safe-area-inset-bottom dd-glass">
|
||||
<view class="justify-content-item tn-padding-bottom">
|
||||
<view class="tn-flex tn-flex-col-center tn-flex-row-left">
|
||||
<view class="user-pic">
|
||||
<view class="user-image">
|
||||
<view class="tn-shadow-blur" style="background-image:url('https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg');width: 70rpx;height: 70rpx;background-size: cover;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 按钮-->
|
||||
<view class="justify-content-item tn-flex-col-center tn-flex-row-center tn-text-center tn-padding-bottom">
|
||||
<view class="tn-flex tn-flex-row-between">
|
||||
<view class="justify-content-item tn-margin-xs tn-text-center" style="width: 300rpx;">
|
||||
<tn-button shape="round" backgroundColor="#00FFC6" padding="40rpx 0" width="90%" shadow fontBold>
|
||||
<text class="tn-icon-topic tn-padding-right-xs tn-color-black"></text>
|
||||
<text class="tn-color-black">参与活动</text>
|
||||
</tn-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="justify-content-item tn-flex-col-center tn-flex-row-center tn-text-center tn-padding-bottom" @click="openLocation">
|
||||
<view class="tn-bg-gray--light tn-padding-xs tn-margin-sm tn-color-black tn-round">
|
||||
<text class="tn-icon-location-fill" style="font-size: 50rpx;"></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import template_page_mixin from '@/libs/mixin/template_page_mixin.js'
|
||||
export default {
|
||||
name: 'TemplateReserve',
|
||||
mixins: [template_page_mixin],
|
||||
data(){
|
||||
return {
|
||||
groupList: [
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/shop/phonecase1.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/shop/cup1.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/shop/watch1.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_2.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_4.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/blogger_beibei.jpg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_1.jpeg'},
|
||||
{src: 'https://resource.tuniaokj.com/images/blogger/avatar_3.jpeg'},
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openLocation() {
|
||||
uni.openLocation({
|
||||
longitude: 113.3298396012573,
|
||||
latitude: 22.961803525530176,
|
||||
name: '祈福新村',
|
||||
address: '祈福新村'
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.template-reserve {
|
||||
}
|
||||
/* 胶囊*/
|
||||
.tn-custom-nav-bar__back {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 1000rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||||
color: #FFFFFF;
|
||||
font-size: 18px;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
flex: 1;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
width: 1rpx;
|
||||
height: 110%;
|
||||
position: absolute;
|
||||
top: 22.5%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
opacity: 0.7;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容*/
|
||||
.reserve{
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.reserve-content{
|
||||
background-color: rgba(255,255,255,0.7);
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
|
||||
/* 标签 */
|
||||
.blogger {
|
||||
&__item {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
line-height: 55rpx;
|
||||
|
||||
&__label {
|
||||
padding: 0 20rpx;
|
||||
margin: 0rpx 18rpx 0 0;
|
||||
|
||||
&--prefix {
|
||||
color: #00FFC8;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* 文字截取*/
|
||||
.clamp-text-1 {
|
||||
-webkit-line-clamp: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clamp-text-2 {
|
||||
-webkit-line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 毛玻璃*/
|
||||
.dd-glass {
|
||||
width: 100%;
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
/* 毛玻璃*/
|
||||
.dd-glass2 {
|
||||
// width: 100%;
|
||||
backdrop-filter: blur(8rpx);
|
||||
-webkit-backdrop-filter: blur(8rpx);
|
||||
}
|
||||
|
||||
/* 用户头像 start */
|
||||
.user-image {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-pic {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
// background-attachment:fixed;
|
||||
background-position: top;
|
||||
border-radius: 100rpx;
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* 底部悬浮按钮 start*/
|
||||
.tn-tabbar-height {
|
||||
min-height: 120rpx;
|
||||
height: calc(140rpx + env(safe-area-inset-bottom) / 2);
|
||||
}
|
||||
.tn-footerfixed {
|
||||
position: fixed;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
transition: all 0.25s ease-out;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* 相册 start*/
|
||||
.slideshow {
|
||||
top: 0;
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.slideshow-image {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: no-repeat 50% 50%;
|
||||
background-size: cover;
|
||||
-webkit-animation-name: kenburns;
|
||||
animation-name: kenburns;
|
||||
-webkit-animation-timing-function: linear;
|
||||
animation-timing-function: linear;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
animation-iteration-count: infinite;
|
||||
-webkit-animation-duration: 16s;
|
||||
animation-duration: 16s;
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
.slideshow-image:nth-child(1) {
|
||||
-webkit-animation-name: kenburns-1;
|
||||
animation-name: kenburns-1;
|
||||
z-index: 3;
|
||||
}
|
||||
.slideshow-image:nth-child(2) {
|
||||
-webkit-animation-name: kenburns-2;
|
||||
animation-name: kenburns-2;
|
||||
z-index: 2;
|
||||
}
|
||||
.slideshow-image:nth-child(3) {
|
||||
-webkit-animation-name: kenburns-3;
|
||||
animation-name: kenburns-3;
|
||||
z-index: 1;
|
||||
}
|
||||
.slideshow-image:nth-child(4) {
|
||||
-webkit-animation-name: kenburns-4;
|
||||
animation-name: kenburns-4;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@-webkit-keyframes kenburns-1 {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
1.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
23.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
26.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
98.4375% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2117647059);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes kenburns-1 {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
1.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
23.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
26.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
98.4375% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2117647059);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes kenburns-2 {
|
||||
23.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
26.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
48.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
51.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
@keyframes kenburns-2 {
|
||||
23.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
26.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
48.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
51.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes kenburns-3 {
|
||||
48.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
51.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
73.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
76.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
@keyframes kenburns-3 {
|
||||
48.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
51.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
73.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
76.5625% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes kenburns-4 {
|
||||
73.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
76.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
98.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes kenburns-4 {
|
||||
73.4375% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
76.5625% {
|
||||
opacity: 1;
|
||||
}
|
||||
98.4375% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
/* 相册 end*/
|
||||
</style>
|
||||
1287
other/topic/topic.vue
Normal file
1287
other/topic/topic.vue
Normal file
File diff suppressed because it is too large
Load Diff
84
pages.json
84
pages.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"easycom": {
|
||||
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue",
|
||||
"^tn-(.*)": "@/tuniao-ui/components/tn-$1/tn-$1.vue"
|
||||
},
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
@@ -565,14 +566,14 @@
|
||||
}
|
||||
|
||||
}, {
|
||||
"path": "wallet/mingxi",
|
||||
"path": "wallet/wallet_detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "金币明细",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
|
||||
}, {
|
||||
"path": "jilu/jilu",
|
||||
"path": "jilu/record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "最近观看",
|
||||
"enablePullDownRefresh": true
|
||||
@@ -705,7 +706,13 @@
|
||||
},
|
||||
{
|
||||
"root": "other",
|
||||
"pages": [
|
||||
"pages": [{
|
||||
"path": "index/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "更多"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "about/about",
|
||||
"style": {
|
||||
@@ -713,33 +720,68 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "coup/coup",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "卡包"
|
||||
"path": "coup/coup",
|
||||
"style": {
|
||||
"navigationBarTitleText": "卡包"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "address/address",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "收货地址"
|
||||
"path": "address/address",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收货地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pay/pay",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "发红包"
|
||||
"path": "pay/pay",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发红包"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "slotMachine/slotMachine",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "抽奖",
|
||||
"path": "slotMachine/slotMachine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "抽奖",
|
||||
"navigationStyle": "custom"
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "blogger/blogger",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "blogger/details",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "topic/topic",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "话题"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "topic/reserve",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "tools/tools",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "",
|
||||
"navigationStyle": "custom"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<view class="itemTitle-box-l">
|
||||
最近观看
|
||||
</view>
|
||||
<view class="itemTitle-box-r" @click="goNav('/me/jilu/jilu')">
|
||||
<view class="itemTitle-box-r" @click="goNav('/me/jilu/record')">
|
||||
更多
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
<view class="head">
|
||||
<scroll-view scroll-x class="bg nav bg-white u-border-bottom">
|
||||
<view class="flex text-center">
|
||||
<view class="cu-item flex-sub text-bold" :class="item.name==TabCur?' cur ':'text-black'"
|
||||
<view class="cu-item flex-sub text-bold" :class="item.name===TabCur?' cur ':'text-black'"
|
||||
v-for="(item,index) in tabList" :key="index" :data-id="item.name" @tap="tabSelect">
|
||||
{{item.name}}
|
||||
<view v-if="item.name==TabCur"
|
||||
<view v-if="item.name===TabCur"
|
||||
style="width: 64rpx;height: 8rpx;;background: #5074FF;margin: -20rpx auto;border-radius: 10rpx;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="headlen">
|
||||
<view v-if="TabCur == tabList[0].name">
|
||||
<view class="headLen">
|
||||
<view v-if="TabCur === tabList[0].name">
|
||||
<view class="page-box" v-if="latelyCourseList.length && userId">
|
||||
<view class="order" v-for="(item, index) in latelyCourseList" :key="index"
|
||||
<view class="orderView" v-for="(item, index) in latelyCourseList" :key="index"
|
||||
@click="goCourseDet(item)">
|
||||
<view class="item">
|
||||
<view class="left">
|
||||
@@ -71,7 +71,7 @@
|
||||
</view>
|
||||
<view v-if="TabCur == tabList[2].name">
|
||||
<view class="page-box" v-if="collectList.length && userId">
|
||||
<view class="order" v-for="(item,index) in collectList" :key='index' @click="goCourseDet(item)">
|
||||
<view class="orderView" v-for="(item,index) in collectList" :key='index' @click="goCourseDet(item)">
|
||||
<view class="item">
|
||||
<view class="left">
|
||||
<image :src="item.titleImg" mode="aspectFill"
|
||||
@@ -444,7 +444,7 @@
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.headlen {
|
||||
.headLen {
|
||||
/* #ifdef H5 */
|
||||
margin-top: 80rpx;
|
||||
/* #endif */
|
||||
@@ -457,7 +457,7 @@
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.order {
|
||||
.orderView {
|
||||
width: 700rpx;
|
||||
background-color: #ffffff;
|
||||
margin: 20rpx auto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="containers">
|
||||
<view class="containersView">
|
||||
<div style="width: 100%;height: 85%;position: absolute;background: url('../../static/images/appeq_bg.png') no-repeat center bottom / cover;"></div>
|
||||
|
||||
<view style="text-align: center;position: relative;height: 100%;display: flex;flex-direction: column;align-items: center;justify-content: center;">
|
||||
@@ -57,7 +57,7 @@
|
||||
var u = navigator.userAgent;
|
||||
if (u.indexOf('Android') > -1 || u.indexOf('Adr') > -1) {
|
||||
this.$Request.get('/app/common/type/49').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
if (res.data && res.data.value) {
|
||||
if (this.openShare) {
|
||||
let ua = navigator.userAgent.toLowerCase();
|
||||
@@ -97,7 +97,7 @@
|
||||
} else {
|
||||
|
||||
this.$Request.get('/app/common/type/50').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
if (res.data && res.data.value) {
|
||||
if (this.openShares) {
|
||||
let ua = navigator.userAgent.toLowerCase();
|
||||
@@ -151,7 +151,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.containers {
|
||||
.containersView {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="container1">
|
||||
<view class="containerView">
|
||||
<view class="cu-form-group"
|
||||
style="margin: 30upx;border: 2upx solid whitesmoke;margin-bottom: 20px;border-radius: 30px">
|
||||
<view class="title">手机号</view>
|
||||
@@ -11,11 +11,10 @@
|
||||
<text class="title">验证码</text>
|
||||
<input type="number" :value="code" placeholder="请输入验证码" maxlength="6" data-key="code" @input="inputChange"
|
||||
@confirm="toLogin" />
|
||||
<button class="send-msg" @click="sendMsg" :disabled="sending">{{ sendTime }}</button>
|
||||
<button class="send-msg" @click="sendMsg" :disabled="sendIng">{{ sendTime }}</button>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn" @click="toLogin" :disabled="logining">立即绑定</button>
|
||||
</view>
|
||||
<button class="confirm-btn" @click="toLogin" :disabled="loginIng">立即绑定</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -29,8 +28,8 @@
|
||||
return {
|
||||
mobile: '',
|
||||
code: '',
|
||||
logining: false,
|
||||
sending: false,
|
||||
loginIng: false,
|
||||
sendIng: false,
|
||||
sendTime: '获取验证码',
|
||||
count: 60,
|
||||
type: '',
|
||||
@@ -55,11 +54,11 @@
|
||||
} = this;
|
||||
if (count === 1) {
|
||||
this.count = 60;
|
||||
this.sending = false;
|
||||
this.sendIng = false;
|
||||
this.sendTime = '获取验证码'
|
||||
} else {
|
||||
this.count = count - 1;
|
||||
this.sending = true;
|
||||
this.sendIng = true;
|
||||
this.sendTime = count - 1 + '秒后重新获取';
|
||||
setTimeout(this.countDown.bind(this), 1000);
|
||||
}
|
||||
@@ -76,7 +75,7 @@
|
||||
this.$queue.showLoading("正在发送验证码...");
|
||||
this.$Request.getT('/app/Login/sendMsg/' + mobile + '/gzg').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.sending = true;
|
||||
this.sendIng = true;
|
||||
this.$queue.showToast('验证码发送成功请注意查收');
|
||||
this.countDown();
|
||||
uni.hideLoading();
|
||||
@@ -162,7 +161,7 @@
|
||||
background: #557EFD;
|
||||
}
|
||||
|
||||
.container1 {
|
||||
.containerView {
|
||||
top: 0;
|
||||
padding-top: 32upx;
|
||||
position: relative;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<text class="title">验证码</text>
|
||||
<input type="number" :value="code" placeholder="请输入验证码" maxlength="6" data-key="code"
|
||||
@input="inputChange" @confirm="toLogin" />
|
||||
<button class="send-msg" @click="sendMsg" :disabled="sending">{{sendTime}}</button>
|
||||
<button class="send-msg" @click="sendMsg" :disabled="sendIng">{{sendTime}}</button>
|
||||
</view>
|
||||
<view class="cu-form-group"
|
||||
style="border: 2upx solid whitesmoke;margin-bottom: 20px;border-radius: 30px">
|
||||
@@ -34,10 +34,10 @@
|
||||
code: '',
|
||||
mobile: '',
|
||||
password: '',
|
||||
sending: false,
|
||||
sendIng: false,
|
||||
sendTime: '获取验证码',
|
||||
count: 60,
|
||||
logining: false
|
||||
loginIng: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
})
|
||||
this.$u.get('/app/Login/sendMsg/' + mobile + '/forget').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.sending = true;
|
||||
this.sendIng = true;
|
||||
uni.showToast({
|
||||
title: '验证码发送成功请注意查收',
|
||||
icon: 'none',
|
||||
@@ -89,12 +89,12 @@
|
||||
} = this;
|
||||
if (count === 1) {
|
||||
this.count = 60;
|
||||
this.sending = false;
|
||||
this.sendIng = false;
|
||||
this.sendTime = '获取验证码'
|
||||
} else {
|
||||
this.count = count - 1;
|
||||
this.sending = true;
|
||||
this.sendTime = count - 1 + '秒后重新获取';
|
||||
this.sendIng = true;
|
||||
this.sendTime = count - 1 + '秒后获取';
|
||||
setTimeout(this.countDown.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
@@ -140,7 +140,7 @@
|
||||
duration: 1000
|
||||
})
|
||||
} else {
|
||||
this.logining = true;
|
||||
this.loginIng = true;
|
||||
// this.$queue.showLoading("正在修改密码中...");
|
||||
uni.showLoading({
|
||||
title: '正在修改密码中...'
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<button class="confirm-btn" @click="toRegister">注册</button>
|
||||
<button class="confirm-btn" @click="toLogin">登录</button>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<view class="footerView">
|
||||
<text @tap="isShowAgree" class="cuIcon"
|
||||
:class="showAgree ? 'cuIcon-radiobox' : 'cuIcon-round'">注册即同意</text>
|
||||
<!-- 协议地址 -->
|
||||
@@ -89,7 +89,7 @@
|
||||
showMa() {
|
||||
//查询官方邀请码
|
||||
this.$Request.getT('/common/type/88').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.invitation = res.data.value;
|
||||
}
|
||||
});
|
||||
@@ -211,7 +211,7 @@
|
||||
})
|
||||
return
|
||||
}
|
||||
if (invitation.length == 0 && registerCode == '是') {
|
||||
if (invitation.length === 0 && registerCode === '是') {
|
||||
uni.showToast({
|
||||
title: '请输入邀请码',
|
||||
icon: 'none',
|
||||
@@ -356,7 +356,7 @@
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.footer {
|
||||
.footerView {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<button class="confirm-btn" @click="toRegister">注册</button>
|
||||
<button class="confirm-btn" @click="toLogin">登录</button>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<view class="footerView">
|
||||
<text @tap="isShowAgree" class="cuIcon"
|
||||
:class="showAgree ? 'cuIcon-radiobox' : 'cuIcon-round'">同意</text>
|
||||
<!-- 协议地址 -->
|
||||
@@ -362,7 +362,7 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.footer {
|
||||
.footerView {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
onLoad() {
|
||||
//pc展示用户端二维码 854
|
||||
this.$Request.getT("/app/common/type/854").then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
if (res.data && res.data.value) {
|
||||
this.erweima = res.data.value;
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
});
|
||||
//pc展示用户端文字 853
|
||||
this.$Request.getT("/app/common/type/853").then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
if (res.data && res.data.value) {
|
||||
this.content = res.data.value;
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
follow() {
|
||||
if (this.$queue.getData("openid")) {
|
||||
this.$Request.get("/tao/wx/follow/" + this.$queue.getData("openid")).then(res => {
|
||||
if (res === true) {
|
||||
if (res) {
|
||||
window.location.replace(this.$queue.publicYuMing());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<u-badge :offset="[0,20]" type="error" :count="numCount"></u-badge>
|
||||
</view>
|
||||
<view class="tool-box-content-item flex align-center justify-center flex-wrap"
|
||||
@click="goNav('/me/jilu/jilu')">
|
||||
@click="goNav('/me/jilu/record')">
|
||||
<view class="tool-box-content-item-img">
|
||||
<image src="../../static/images/me/vlishi.png" mode=""></image>
|
||||
</view>
|
||||
@@ -251,16 +251,21 @@
|
||||
</u-popup>
|
||||
<!-- 抖音im客服 -->
|
||||
<ttMsg />
|
||||
|
||||
|
||||
|
||||
<other-xuanfu></other-xuanfu>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ttMsg from '../../components/ttMsg/ttMsg.vue'
|
||||
import httpsRequest from '../../common/httpRequest.js'
|
||||
import otherXuanfu from '@/components/other-xuafu.vue'
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ttMsg
|
||||
ttMsg,otherXuanfu
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -308,9 +313,9 @@
|
||||
this.token = uni.getStorageSync('token')
|
||||
if (this.token) {
|
||||
this.getMyMoney()
|
||||
this.getJifen()
|
||||
this.getPoints()
|
||||
this.$u.api.userinfo().then(res => {
|
||||
if (res.code == 0 && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
uni.setStorageSync('zhiFuBao', res.data.zhiFuBao)
|
||||
uni.setStorageSync('zhiFuBaoName', res.data.zhiFuBaoName)
|
||||
uni.setStorageSync('userName', res.data.userName)
|
||||
@@ -327,7 +332,7 @@
|
||||
userId: uni.getStorageSync('userId')
|
||||
}
|
||||
this.$u.api.userVip(data).then(res => {
|
||||
if (res.code == 0 && res.data && res.data.isVip == 2) {
|
||||
if (res.code === 0 && res.data && res.data.isVip === 2) {
|
||||
this.isVIP = true;
|
||||
this.endTime = res.data.endTime
|
||||
uni.setStorageSync('isVIP', true)
|
||||
@@ -342,7 +347,7 @@
|
||||
this.userName = uni.getStorageSync('userName')
|
||||
this.isVIP = uni.getStorageSync('isVIP')
|
||||
this.getMyLoveNum()
|
||||
this.getMyZhuiNum()
|
||||
this.getMyFansNum()
|
||||
} else {
|
||||
this.isLogin = false
|
||||
this.isVIP = false
|
||||
@@ -359,10 +364,10 @@
|
||||
|
||||
httpsRequest.getT("app/common/type/919", {}).then(res => {
|
||||
console.log(res);
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
let isIos = sysInfo.platform == 'ios'
|
||||
this.isShowMoneyPay = !(res.data.value == '1' && isIos)
|
||||
let isIos = sysInfo.platform === 'ios'
|
||||
this.isShowMoneyPay = !(res.data.value === '1' && isIos)
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -380,7 +385,7 @@
|
||||
sdkContent: this.kami
|
||||
}
|
||||
this.$Request.postT('/app/sdkInfo/sdkExchange', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
uni.showToast({
|
||||
title: '兑换成功'
|
||||
})
|
||||
@@ -389,7 +394,7 @@
|
||||
userId: uni.getStorageSync('userId')
|
||||
}
|
||||
this.$u.api.userVip(datas).then(rest => {
|
||||
if (rest.code == 0 && rest.data && rest.data.isVip == 2) {
|
||||
if (rest.code === 0 && rest.data && rest.data.isVip === 2) {
|
||||
this.isVIP = true;
|
||||
this.endTime = rest.data.endTime
|
||||
uni.setStorageSync('isVIP', true)
|
||||
@@ -425,7 +430,7 @@
|
||||
classify: 2
|
||||
}
|
||||
this.$Request.getT('/app/courseCollect/selectByUserId', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.myLoveNum = res.data.total
|
||||
} else {
|
||||
this.myLoveNum = 0
|
||||
@@ -433,14 +438,14 @@
|
||||
})
|
||||
},
|
||||
//获取我追剧的数量
|
||||
getMyZhuiNum() {
|
||||
getMyFansNum() {
|
||||
let data = {
|
||||
page: 1,
|
||||
limit: 1,
|
||||
classify: 1
|
||||
}
|
||||
this.$Request.getT('/app/courseCollect/selectByUserId', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.myZhui = res.data.total
|
||||
} else {
|
||||
this.myZhui = 0
|
||||
@@ -450,9 +455,9 @@
|
||||
/**
|
||||
* 获取积分
|
||||
*/
|
||||
getJifen() {
|
||||
getPoints() {
|
||||
this.$Request.getT('/app/integral/selectByUserId').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.jifen = res.data.integralNum
|
||||
} else {
|
||||
this.jifen = 0
|
||||
@@ -476,7 +481,7 @@
|
||||
*/
|
||||
getMyMoney() {
|
||||
this.$Request.getT('/app/moneyDetails/selectUserMoney').then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.moneyNum = res.data.money
|
||||
this.userInfo = res.data
|
||||
// this.$Request.getT('/app/invite/selectInviteMoney').then(ret => {
|
||||
@@ -519,15 +524,15 @@
|
||||
goMsg() {
|
||||
let kefu = uni.getStorageSync('kefu'); // 用户端联系方式 1 手机号 2企业微信
|
||||
let kefuPhone = uni.getStorageSync('kefuPhone');
|
||||
if (kefu == 1) {
|
||||
if (kefu === 1) {
|
||||
uni.navigateTo({
|
||||
url: '/me/setting/kefu'
|
||||
})
|
||||
} else if (kefu == 3) {
|
||||
} else if (kefu === 3) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: kefuPhone //仅为示例
|
||||
});
|
||||
} else if (kefu == 2) {
|
||||
} else if (kefu === 2) {
|
||||
// #ifdef MP-WEIXIN
|
||||
let that = this
|
||||
try {
|
||||
@@ -571,7 +576,7 @@
|
||||
console.log(e)
|
||||
let token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
if (type == 'tabbar') {
|
||||
if (type === 'tabbar') {
|
||||
uni.switchTab({
|
||||
url: e
|
||||
})
|
||||
@@ -593,10 +598,10 @@
|
||||
this.$u.api.bannerList({
|
||||
classify: '3'
|
||||
}).then(res => {
|
||||
if (res.code == 0 && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
let arr = []
|
||||
res.data.forEach(ret => {
|
||||
if (ret.state == 1) {
|
||||
if (ret.state === 1) {
|
||||
arr.push(ret)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="usermain">
|
||||
<view class="usermain-item u-border-bottom">
|
||||
<view class="userMain">
|
||||
<view class="userMain-item u-border-bottom">
|
||||
<view>头像</view>
|
||||
<view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
@@ -16,7 +16,7 @@
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<view class="usermain-item item-padding u-border-bottom">
|
||||
<view class="userMain-item item-padding u-border-bottom">
|
||||
<view>用户名</view>
|
||||
<view>
|
||||
<view class="cu-form-group">
|
||||
@@ -24,14 +24,14 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="usermain-item item-padding">
|
||||
<!-- <view class="userMain-item item-padding">
|
||||
<view >姓名</view>
|
||||
<view class="cu-form-group">
|
||||
<input v-model="realName" placeholder="请填写您的真实姓名" />
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="usermain-item item-padding u-border-bottom">
|
||||
<view class="userMain-item item-padding u-border-bottom">
|
||||
<view>手机</view>
|
||||
<view>
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="usermain-item item-padding" @click="goMyAddress">
|
||||
<!-- <view class="userMain-item item-padding" @click="goMyAddress">
|
||||
|
||||
<view >地址管理</view>
|
||||
<view>
|
||||
@@ -63,7 +63,7 @@
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="footer-btn">
|
||||
<view class="usermain-btn" @click="messagebtn()">保存</view>
|
||||
<view class="userMain-btn" @click="messagebtn()">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -99,7 +99,7 @@
|
||||
code: code
|
||||
}
|
||||
this.$Request.postT('/app/Login/wxPhone', data).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.phone = res.data.phone_info.purePhoneNumber;
|
||||
} else {
|
||||
uni.showToast({
|
||||
@@ -294,7 +294,7 @@
|
||||
getUserInfo() {
|
||||
let userId = uni.getStorageSync('userId')
|
||||
this.$u.api.userinfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.code === 0) {
|
||||
this.userName = res.data.userName;
|
||||
this.phone = res.data.phone;
|
||||
this.phones = res.data.phone
|
||||
@@ -427,11 +427,11 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.usermain {
|
||||
.userMain {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.usermain-item {
|
||||
.userMain-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
@@ -440,7 +440,7 @@
|
||||
/* border-bottom: 2rpx solid #f2f2f2; */
|
||||
}
|
||||
|
||||
.usermain-item.item-padding {
|
||||
.userMain-item.item-padding {
|
||||
padding: 0 40rpx 0 0;
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@
|
||||
margin-top: 150rpx;
|
||||
}
|
||||
|
||||
.footer-btn .usermain-btn {
|
||||
.footer-btn .userMain-btn {
|
||||
color: #FFFFFF;
|
||||
background: #ff7581;
|
||||
text-align: center;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<image class="task_icon2 u-relative" src="../../static/images/me/task_icon2.png"></image>
|
||||
</view>
|
||||
|
||||
<view class="content signIn margin-lr padding bg-white u-relative" v-if="isShowMoneyPay" style="margin-bottom: 32rpx;">
|
||||
<view class="content signIn margin-lr padding bg-white u-relative" style="margin-bottom: 32rpx;">
|
||||
<view class="title flex justify-between">
|
||||
<view>已连续签到 <text class="num">{{signDays}}</text> 天</view>
|
||||
<!-- <view class="dk flex">
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="swipers-items-right-item" v-if="item.isCollect==null || item.isCollect == 0">
|
||||
<view class="swipers-items-right-item-img" @click.stop="shoucang(item)">
|
||||
<view class="swipers-items-right-item-img" @click.stop="collectVideo(item)">
|
||||
<image class="swipers-items-right-item-imgs" src="../../static/images/me/shuqian.png"
|
||||
mode=""></image>
|
||||
</view>
|
||||
@@ -54,7 +54,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="swipers-items-right-item" v-else>
|
||||
<view class="swipers-items-right-item-img" @click.stop="shoucang(item)">
|
||||
<view class="swipers-items-right-item-img" @click.stop="collectVideo(item)">
|
||||
<image class="swipers-items-right-item-imgs" src="../../static/images/me/shuqian_s.png"
|
||||
mode=""></image>
|
||||
</view>
|
||||
@@ -317,7 +317,7 @@
|
||||
})
|
||||
},
|
||||
//收藏
|
||||
shoucang(item) {
|
||||
collectVideo(item) {
|
||||
if (uni.getStorageSync('token')) {
|
||||
let data = {
|
||||
courseId: item.courseId,
|
||||
|
||||
28
store/$t.mixin.js
Normal file
28
store/$t.mixin.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { mapState } from 'vuex'
|
||||
import store from '@/store/index.js'
|
||||
|
||||
// 尝试将用户在根目录中的store/index.js的vuex的state变量加载到全局变量中
|
||||
let $tStoreKey = []
|
||||
try {
|
||||
$tStoreKey = store.state ? Object.keys(store.state) : []
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
beforeCreate() {
|
||||
// 将vuex方法挂在在$t中
|
||||
// 使用方法:
|
||||
// 修改vuex的state中的user.name变量为图鸟小菜 => this.$t.vuex('user.name', '图鸟小菜')
|
||||
// 修改vuexde state中的version变量为1.0.1 => this.$t.vuex('version', 1.0.1)
|
||||
this.$t.vuex = (name, value) => {
|
||||
this.$store.commit('$tStore', {
|
||||
name, value
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 将vuex的state中的变量结构到全局混入mixin中
|
||||
...mapState($tStoreKey)
|
||||
}
|
||||
}
|
||||
75
store/index.js
Normal file
75
store/index.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
let lifeData = {}
|
||||
|
||||
// 尝试获取本地是否存在lifeData变量,第一次启动时不存在
|
||||
try {
|
||||
lifeData = uni.getStorageSync('lifeData')
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
|
||||
// 标记需要永久存储的变量,在每次启动时取出,在state中的变量名
|
||||
let saveStateKeys = ['vuex_user']
|
||||
|
||||
// 保存变量到本地存储
|
||||
const saveLifeData = function(key, value) {
|
||||
// 判断变量是否在存储数组中
|
||||
if (saveStateKeys.indexOf(key) != -1) {
|
||||
// 获取本地存储的lifeData对象,将变量添加到对象中
|
||||
let tmpLifeData = uni.getStorageSync('lifeData')
|
||||
// 第一次启动时不存在,则放一个空对象
|
||||
tmpLifeData = tmpLifeData ? tmpLifeData : {},
|
||||
tmpLifeData[key] = value
|
||||
// 将变量再次放回本地存储中
|
||||
uni.setStorageSync('lifeData', tmpLifeData)
|
||||
}
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
|
||||
// 加上vuex_前缀,是防止变量名冲突,也让人一目了然
|
||||
vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '图鸟'},
|
||||
|
||||
// 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式
|
||||
// app版本
|
||||
vuex_version: "1.0.0",
|
||||
// 是否使用自定义导航栏
|
||||
vuex_custom_nav_bar: true,
|
||||
// 状态栏高度
|
||||
vuex_status_bar_height: 0,
|
||||
// 自定义导航栏的高度
|
||||
vuex_custom_bar_height: 0
|
||||
},
|
||||
mutations: {
|
||||
$tStore(state, payload) {
|
||||
// 判断是否多层调用,state中为对象存在的情况,例如user.info.score = 1
|
||||
let nameArr = payload.name.split('.')
|
||||
let saveKey = ''
|
||||
let len = nameArr.length
|
||||
if (len >= 2) {
|
||||
let obj = state[nameArr[0]]
|
||||
for (let i= 1; i < len - 1; i++) {
|
||||
obj = obj[nameArr[i]]
|
||||
}
|
||||
obj[nameArr[len - 1]] = payload.value
|
||||
saveKey = nameArr[0]
|
||||
} else {
|
||||
// 单层级变量
|
||||
state[payload.name] = payload.value
|
||||
saveKey = payload.name
|
||||
}
|
||||
|
||||
// 保存变量到本地中
|
||||
saveLifeData(saveKey, state[saveKey])
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
}
|
||||
})
|
||||
|
||||
export default store
|
||||
4
tuniao-ui/README.md
Normal file
4
tuniao-ui/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
TuniaoUi for uniApp v1.0.0 | by 图鸟 2021-09-01
|
||||
仅供开发,如作它用所承受的法律责任一概与作者无关
|
||||
|
||||
*使用TuniaoUi开发扩展与插件时,请注明基于tuniao字眼
|
||||
202
tuniao-ui/components/tn-action-sheet/tn-action-sheet.vue
Normal file
202
tuniao-ui/components/tn-action-sheet/tn-action-sheet.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-action-sheet-class tn-action-sheet">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
length="auto"
|
||||
:popup="false"
|
||||
:borderRadius="borderRadius"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="close"
|
||||
>
|
||||
<!-- 提示信息 -->
|
||||
<view
|
||||
v-if="tips.text"
|
||||
class="tn-action-sheet__tips tn-border-solid-bottom"
|
||||
:style="[tipsStyle]"
|
||||
>
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<!-- 按钮列表 -->
|
||||
<block v-for="(item, index) in list" :key="index">
|
||||
<view
|
||||
class="tn-action-sheet__item tn-text-ellipsis"
|
||||
:class="[ index < list.length - 1 ? 'tn-border-solid-bottom' : '']"
|
||||
:style="[itemStyle(index)]"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="itemClick(index)"
|
||||
@touchmove.stop.prevent
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text v-if="item.subText" class="tn-action-sheet__item__subtext tn-text-ellipsis">{{item.subText}}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<block v-if="cancelBtn">
|
||||
<view class="tn-action-sheet__cancel--gab"></view>
|
||||
<view
|
||||
class="tn-action-sheet__cancel tn-action-sheet__item"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="close"
|
||||
>{{cancelText}}</view>
|
||||
</block>
|
||||
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-action-sheet',
|
||||
props: {
|
||||
// 通过v-model控制弹出和收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按钮文字数组,可以自定义颜色和字体大小
|
||||
// return [{
|
||||
// text: '确定',
|
||||
// subText: '这是一个确定按钮',
|
||||
// color: '',
|
||||
// fontSize: '',
|
||||
// disabled: true
|
||||
// }]
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 顶部提示文字
|
||||
tips: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: 26
|
||||
}
|
||||
}
|
||||
},
|
||||
// 弹出的顶部圆角值
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 点击遮罩可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 开启底部安全区域
|
||||
// 在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// z-index值
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部提示样式
|
||||
tipsStyle() {
|
||||
let style = {}
|
||||
if (this.tips.color) style.color = this.tips.color
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this.list[index].color) style.color = this.list[index].color
|
||||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'
|
||||
|
||||
// 选项被禁用的样式
|
||||
if (this.list[index].disabled) style.color = '#AAAAAA'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
close() {
|
||||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
// 关闭弹窗
|
||||
popupClose() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 点击对应的item
|
||||
itemClick(index) {
|
||||
// 如果是禁用项则不进行操作
|
||||
if (this.list[index].disabled) return
|
||||
this.$emit('click', index)
|
||||
this.popupClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-action-sheet {
|
||||
&__tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $tn-content-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
|
||||
&__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $tn-content-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
color: $tn-font-color;
|
||||
|
||||
&--gab {
|
||||
height: 12rpx;
|
||||
background-color: #eaeaec;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
104
tuniao-ui/components/tn-avatar-group/tn-avatar-group.vue
Normal file
104
tuniao-ui/components/tn-avatar-group/tn-avatar-group.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<view class="tn-avatar-group-class tn-avatar-group">
|
||||
<view v-for="(item, index) in lists" :key="index" class="tn-avatar-group__item" :style="[itemStyle(index)]">
|
||||
<tn-avatar
|
||||
:src="item.src || ''"
|
||||
:text="item.text || ''"
|
||||
:icon="item.icon || ''"
|
||||
:size="size"
|
||||
:shape="shape"
|
||||
:imgMode="imgMode"
|
||||
:border="true"
|
||||
backgroundColor="rgba(255, 255, 255, 0.4)"
|
||||
:borderSize="4"
|
||||
></tn-avatar>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-avatar-group',
|
||||
props: {
|
||||
// 头像列表
|
||||
lists: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 头像之间的遮挡比例
|
||||
// 0.4 代表 40%
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 0.4
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this._checkSizeIsInline()) {
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.marginLeft = index != 0 ? `${-48 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'lg':
|
||||
style.marginLeft = index != 0 ? `${-96 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'xl':
|
||||
style.marginLeft = index != 0 ? `${-128 * this.gap}rpx` : ''
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const size = Number(this.size.replace(/(px|rpx)/g, '')) || 64
|
||||
style.marginLeft = index != 0 ? `-${size * this.gap}rpx` : ''
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/(xs|sm|md|lg|xl|xxl)/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-avatar-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
298
tuniao-ui/components/tn-avatar/tn-avatar.vue
Normal file
298
tuniao-ui/components/tn-avatar/tn-avatar.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-avatar-class tn-avatar"
|
||||
:class="[backgroundColorClass,avatarClass]"
|
||||
:style="[avatarStyle]"
|
||||
@tap="click"
|
||||
>
|
||||
<image
|
||||
v-if="showImg"
|
||||
class="tn-avatar__img"
|
||||
:class="[imgClass]"
|
||||
:src="src"
|
||||
:mode="imgMode || 'aspectFill'"
|
||||
@error="loadImageError"
|
||||
></image>
|
||||
<view v-else class="tn-avatar__text" >
|
||||
<view v-if="text">{{ text }}</view>
|
||||
<view v-else :class="[`tn-icon-${icon}`]"></view>
|
||||
</view>
|
||||
|
||||
<!-- 角标 -->
|
||||
<tn-badge
|
||||
v-if="badge && (badgeIcon || badgeText)"
|
||||
:radius="badgeSize"
|
||||
:backgroundColor="badgeBgColor"
|
||||
:fontColor="badgeColor"
|
||||
:fontSize="badgeSize - 8"
|
||||
:absolute="true"
|
||||
:top="badgePosition[0]"
|
||||
:right="badgePosition[1]"
|
||||
>
|
||||
<view v-if="badgeIcon && badgeText === ''">
|
||||
<view :class="[`tn-icon-${badgeIcon}`]"></view>
|
||||
</view>
|
||||
<view v-else>
|
||||
{{ badgeText }}
|
||||
</view>
|
||||
</tn-badge>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-avatar',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.1)'
|
||||
},
|
||||
// 边框大小, rpx
|
||||
borderSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 是否显示角标
|
||||
badge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 设置显示角标后,角标大小
|
||||
badgeSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 角标图标
|
||||
badgeIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标文字,优先级比icon高
|
||||
badgeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标坐标
|
||||
// [top, right]
|
||||
badgePosition: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图片显示是否发生错误
|
||||
imgLoadError: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showImg() {
|
||||
// 如果设置了图片地址,则为显示图片,否则为显示文本
|
||||
return this.text === '' && this.icon === ''
|
||||
},
|
||||
avatarClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar--${this.shape}`
|
||||
|
||||
if (this._checkSizeIsInline()) {
|
||||
clazz += ` tn-avatar--${this.size}`
|
||||
}
|
||||
|
||||
if (this.shadow) {
|
||||
clazz += ' tn-avatar--shadow'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
avatarStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.background = this.backgroundColorStyle
|
||||
} else if (this.shadow && this.showImg) {
|
||||
style.backgroundImage = `url(${this.src})`
|
||||
}
|
||||
|
||||
if (this.border) {
|
||||
style.border = `${this.borderSize}rpx solid ${this.borderColor}`
|
||||
}
|
||||
|
||||
if (!this._checkSizeIsInline()) {
|
||||
style.width = this.size
|
||||
style.height = this.size
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
imgClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar__img--${this.shape}`
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载图片失败
|
||||
loadImageError() {
|
||||
this.imgLoadError = true
|
||||
},
|
||||
// 点击事件
|
||||
click() {
|
||||
this.$emit("click", this.index)
|
||||
},
|
||||
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/^(xs|sm|md|lg|xl|xxl)$/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $tn-font-holder-color;
|
||||
// color: #FFFFFF;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
z-index: 1;
|
||||
|
||||
&--sm {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
&--lg {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
&--xl {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: inherit;
|
||||
filter: blur(10rpx);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
transform-origin: 0 0;
|
||||
border-radius: inherit;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
173
tuniao-ui/components/tn-badge/tn-badge.vue
Normal file
173
tuniao-ui/components/tn-badge/tn-badge.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-badge-class tn-badge"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
badgeClass
|
||||
]"
|
||||
:style="[badgeStyle]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot v-if="!dot"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-badge',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 徽章的大小 rpx
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为一个点
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否使用绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// top
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// right
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 居中 对齐右上角
|
||||
translateCenter: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
badgeClass() {
|
||||
let clazz = ''
|
||||
if (this.dot) {
|
||||
clazz += ' tn-badge--dot'
|
||||
}
|
||||
if (this.absolute) {
|
||||
clazz += ' tn-badge--absolute'
|
||||
|
||||
if (this.translateCenter) {
|
||||
clazz += ' tn-badge--center-position'
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
badgeStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.radius !== 0) {
|
||||
style.width = this.radius + 'rpx'
|
||||
style.height = this.radius + 'rpx'
|
||||
style.lineHeight = this.radius + 'rpx'
|
||||
|
||||
// style.borderRadius = (this.radius * 8) + 'rpx'
|
||||
}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.top) {
|
||||
style.top = this.$t.string.getLengthUnitValue(this.top)
|
||||
}
|
||||
if (this.right) {
|
||||
style.right = this.$t.string.getLengthUnitValue(this.right)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-badge {
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
font-size: 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
// color: #FFFFFF;
|
||||
border-radius: 100rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
line-height: initial;
|
||||
|
||||
&--dot {
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
}
|
||||
&--absolute {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
&--center-position {
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
302
tuniao-ui/components/tn-button/tn-button.vue
Normal file
302
tuniao-ui/components/tn-button/tn-button.vue
Normal file
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<button
|
||||
class="tn-btn-class tn-btn"
|
||||
:class="[
|
||||
buttonClass,
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[buttonStyle]"
|
||||
hover-class="tn-hover"
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
@getuserinfo="handleGetUserInfo"
|
||||
@getphonenumber="handleGetPhoneNumber"
|
||||
@contact="handleContact"
|
||||
@error="handleError"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: "tn-button",
|
||||
// 解决再微信小程序种,自定义按钮无法触发bindsubmit
|
||||
behaviors: ['wx://form-field-button'],
|
||||
props: {
|
||||
// 按钮索引,用于区分多个按钮
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 按钮形状 default 默认 round 圆角 icon 图标按钮
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 是否加阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 宽度 rpx或%
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 高度 rpx或%
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮的尺寸 sm lg
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体是否加粗
|
||||
fontBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0 30rpx'
|
||||
},
|
||||
// 外边距 与css的margin参数用法相同
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当plain=true时,是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当plain=true时,是否加粗显示边框
|
||||
borderBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示加载图标
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 触发form表单的事件类型
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 开放能力
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否阻止重复点击(默认间隔是200ms)
|
||||
blockRepeatClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 根据不同的参数动态生成class
|
||||
buttonClass() {
|
||||
let clazz = ''
|
||||
// 按钮形状
|
||||
switch (this.shape) {
|
||||
case 'icon':
|
||||
case 'round':
|
||||
clazz += ' tn-round'
|
||||
break
|
||||
}
|
||||
|
||||
// 阴影
|
||||
if (this.shadow) {
|
||||
if (this.backgroundColorClass !== '' && this.backgroundColorClass.indexOf('tn-bg') != -1) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-shadow-${color}`
|
||||
} else {
|
||||
clazz += ' tn-shadow-blur'
|
||||
}
|
||||
}
|
||||
|
||||
// 字体加粗
|
||||
if (this.fontBold) {
|
||||
clazz += ' tn-text-bold'
|
||||
}
|
||||
|
||||
// 设置为镂空并且设置镂空便可才进行设置
|
||||
if (this.plain) {
|
||||
clazz += ' tn-btn--plain'
|
||||
if (this.border) {
|
||||
clazz += ' tn-border-solid'
|
||||
if (this.borderBold) {
|
||||
clazz += ' tn-bold-border'
|
||||
}
|
||||
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-border-${color}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
// 按钮的样式
|
||||
buttonStyle() {
|
||||
let style = {}
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.padding = '0 20rpx'
|
||||
style.fontSize = '22rpx'
|
||||
style.height = this.height || '48rpx'
|
||||
break
|
||||
case 'lg':
|
||||
style.padding = '0 40rpx'
|
||||
style.fontSize = '32rpx'
|
||||
style.height = this.height || '80rpx'
|
||||
break
|
||||
default :
|
||||
style.padding = '0 30rpx'
|
||||
style.fontSize = '28rpx'
|
||||
style.height = this.height || '64rpx'
|
||||
}
|
||||
|
||||
// 是否手动设置了内边距
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
|
||||
// 是否手动设置外边距
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
|
||||
// 是否手动设置了字体大小
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
style.width = this.shape === 'icon' ? style.height : this.width
|
||||
style.padding = this.shape === 'icon' ? '0' : style.padding
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
if (!this.backgroundColorClass) {
|
||||
if (this.plain) {
|
||||
style.borderColor = this.backgroundColorStyle || '#080808'
|
||||
} else {
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
}
|
||||
}
|
||||
|
||||
// 设置阴影
|
||||
if (this.shadow && !this.backgroundColorClass) {
|
||||
if (this.backgroundColorStyle.indexOf('#') != -1) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || '#000000')}10`
|
||||
} else if (this.backgroundColorStyle.indexOf('rgb') != -1 || this.backgroundColorStyle.indexOf('rgba') != -1 || !this.backgroundColorStyle) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || 'rgba(0, 0, 0, 0.1)')}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 上次点击的时间
|
||||
clickTime: 0,
|
||||
// 两次点击防抖的间隔时间
|
||||
clickIntervalTime: 200
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击事件
|
||||
handleClick() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
if (this.blockRepeatClick) {
|
||||
const nowTime = new Date().getTime()
|
||||
if (nowTime - this.clickTime <= this.clickIntervalTime) {
|
||||
return
|
||||
}
|
||||
this.clickTime = nowTime
|
||||
setTimeout(() => {
|
||||
this.clickTime = 0
|
||||
}, this.clickIntervalTime)
|
||||
}
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
// 兼容tap事件
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
handleGetUserInfo({ detail = {} } = {}) {
|
||||
this.$emit('getuserinfo', detail);
|
||||
},
|
||||
handleContact({ detail = {} } = {}) {
|
||||
this.$emit('contact', detail);
|
||||
},
|
||||
handleGetPhoneNumber({ detail = {} } = {}) {
|
||||
this.$emit('getphonenumber', detail);
|
||||
},
|
||||
handleError({ detail = {} } = {}) {
|
||||
this.$emit('error', detail);
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-btn {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
overflow: visible;
|
||||
transform: translate(0rpx, 0rpx);
|
||||
// background-color: $tn-mai
|
||||
border-radius: 12rpx;
|
||||
// color: $tn-font-color;
|
||||
margin: 0;
|
||||
|
||||
&--plain {
|
||||
background-color: transparent !important;
|
||||
background-image: none;
|
||||
|
||||
&.tn-round {
|
||||
border-radius: 1000rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
707
tuniao-ui/components/tn-calendar/tn-calendar.vue
Normal file
707
tuniao-ui/components/tn-calendar/tn-calendar.vue
Normal file
@@ -0,0 +1,707 @@
|
||||
<template>
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:borderRadius="borderRadius"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:maskCloseable="maskCloseable"
|
||||
:closeBtn="closeBtn"
|
||||
:zIndex="elIndex"
|
||||
@close="close"
|
||||
>
|
||||
<view class="tn-calendar-class tn-calendar">
|
||||
<!-- 头部 -->
|
||||
<view class="tn-calendar__header">
|
||||
<view v-if="!$slots.tooltip || !$slots.$tooltip" class="tn-calendar__header__text">
|
||||
{{ toolTips }}
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot name="tooltip"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作提示信息 -->
|
||||
<view class="tn-calendar__action">
|
||||
<view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(false)">
|
||||
<view><text class="tn-icon-left"></text></view>
|
||||
</view>
|
||||
<view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(false)">
|
||||
<view><text class="tn-icon-left"></text></view>
|
||||
</view>
|
||||
<view class="tn-calendar__action__text">{{ dateTitle }}</view>
|
||||
<view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(true)">
|
||||
<view><text class="tn-icon-right"></text></view>
|
||||
</view>
|
||||
<view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(true)">
|
||||
<view><text class="tn-icon-right"></text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 星期中文标识 -->
|
||||
<view class="tn-calendar__week-day-zh">
|
||||
<view v-for="(item,index) in weekDayZh" :key="index" class="tn-calendar__week-day-zh__text">{{ item }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 日历主体 -->
|
||||
<view class="tn-calendar__content">
|
||||
<!-- 前置空白部分 -->
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="tn-calendar__content__item"></view>
|
||||
</block>
|
||||
<view
|
||||
v-for="(item, index) in daysArr"
|
||||
:key="index"
|
||||
class="tn-calendar__content__item"
|
||||
:class="{
|
||||
'tn-hover': disabledChoose(year, month, index + 1),
|
||||
'tn-calendar__content--start-date': (mode === 'range' && startDate == `${year}-${month}-${index+1}`) || mode === 'date',
|
||||
'tn-calendar__content--end-date': (mode === 'range' && endDate == `${year}-${month}-${index+1}`) || mode === 'date'
|
||||
}"
|
||||
:style="{
|
||||
backgroundColor: colorValue(index, 'bg')
|
||||
}"
|
||||
@tap.stop="dateClick(index)"
|
||||
>
|
||||
<view class="tn-calendar__content__item__text" :style="{color: colorValue(index, 'text')}">
|
||||
<view>{{ item.day }}</view>
|
||||
</view>
|
||||
<view class="tn-calendar__content__item__tips" :style="{color: item.color}">
|
||||
{{ item.bottomInfo }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-calendar__content__month--bg">{{ month }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部 -->
|
||||
<view class="tn-calendar__bottom">
|
||||
<view class="tn-calendar__bottom__choose">
|
||||
<text>{{ mode === 'date' ? activeDate : startDate }}</text>
|
||||
<text v-if="endDate">至{{ endDate }}</text>
|
||||
</view>
|
||||
<view class="tn-calendar__bottom__btn" :style="{backgroundColor: btnColor}" @click="handleBtnClick(false)">
|
||||
<view class="tn-calendar__bottom__btn--text">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from '../../libs/utils/calendar.js'
|
||||
|
||||
export default {
|
||||
name: 'tn-calendar',
|
||||
props: {
|
||||
// 双向绑定控制组件弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 模式
|
||||
// date -> 单日期 range -> 日期范围
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'date'
|
||||
},
|
||||
// 是否允许切换年份
|
||||
changeYear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许切换月份
|
||||
changeMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 可切换的最大年份
|
||||
maxYear: {
|
||||
type: [Number, String],
|
||||
default: 2100
|
||||
},
|
||||
// 可切换的最小年份
|
||||
minYear: {
|
||||
type: [Number, String],
|
||||
default: 1970
|
||||
},
|
||||
// 最小日期(不在范围被不允许选择)
|
||||
minDate: {
|
||||
type: String,
|
||||
default: '1970-01-01'
|
||||
},
|
||||
// 最大日期,如果为空则默认为今天
|
||||
maxDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 切换月份按钮的颜色
|
||||
monthArrowColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 切换年份按钮的颜色
|
||||
yearArrowColor: {
|
||||
type: String,
|
||||
default: '#C8C8C8'
|
||||
},
|
||||
// 默认字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 选中|起始结束日期背景颜色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 选中|起始结束日期文字颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 范围日期内的背景颜色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: '#E6E6E655'
|
||||
},
|
||||
// 范围日期内的文字颜色
|
||||
rangeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 起始日期显示的文字,mode=range时生效
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// 结束日期显示的文字,mode=range时生效
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
// 按钮背景颜色
|
||||
btnColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 农历文字的颜色
|
||||
lunarColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 选中日期是否有选中效果
|
||||
isActiveCurrent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 切换年月是否触发事件,mode=date时生效
|
||||
isChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示农历
|
||||
showLunar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 顶部提示文字
|
||||
toolTips: {
|
||||
type: String,
|
||||
default: '请选择日期'
|
||||
},
|
||||
// 显示圆角的大小
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dateChange() {
|
||||
return `${this.mode}-${this.minDate}-${this.maxDate}`
|
||||
},
|
||||
elIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
},
|
||||
colorValue() {
|
||||
return (index, type) => {
|
||||
let color = type === 'bg' ? '' : this.color
|
||||
let day = index + 1
|
||||
let date = `${this.year}-${this.month}-${day}`
|
||||
let timestamp = new Date(date.replace(/\-/g,'/')).getTime()
|
||||
let start = this.startDate.replace(/\-/g,'/')
|
||||
let end = this.endDate.replace(/\-/g,'/')
|
||||
if ((this.mode === 'date' && this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||
color = type === 'bg' ? this.activeBgColor : this.activeColor
|
||||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||
color = type === 'bg' ? this.rangeBgColor : this.rangeColor
|
||||
}
|
||||
return color
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 星期几,1-7
|
||||
weekday: 1,
|
||||
weekdayArr: [],
|
||||
// 星期对应的中文
|
||||
weekDayZh: ['日','一','二','三','四','五','六'],
|
||||
// 当前月有多少天
|
||||
days: 0,
|
||||
daysArr: [],
|
||||
year: 2021,
|
||||
month: 0,
|
||||
day: 0,
|
||||
startYear: 0,
|
||||
startMonth: 0,
|
||||
startDay: 0,
|
||||
endYear: 0,
|
||||
endMonth: 0,
|
||||
endDay: 0,
|
||||
today: '',
|
||||
activeDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
min: null,
|
||||
max: null,
|
||||
// 日期标题
|
||||
dateTitle: '',
|
||||
// 标记是否已经选择了开始日期
|
||||
chooseStart: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dateChange() {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
let now = new Date()
|
||||
this.year = now.getFullYear()
|
||||
this.month = now.getMonth() + 1
|
||||
this.day = now.getDate()
|
||||
this.today = `${this.year}-${this.month}-${this.day}`
|
||||
this.activeDate = this.today
|
||||
this.min = this.initDate(this.minDate)
|
||||
this.max = this.initDate(this.maxDate || this.today)
|
||||
this.startDate = ''
|
||||
this.startYear = 0
|
||||
this.startMonth = 0
|
||||
this.startDay = 0
|
||||
this.endDate = ''
|
||||
this.endYear = 0
|
||||
this.endMonth = 0
|
||||
this.endDay = 0
|
||||
this.chooseStart = false
|
||||
this.changeData()
|
||||
},
|
||||
// 切换月份
|
||||
changeMonthHandler(add) {
|
||||
if (add) {
|
||||
let month = this.month + 1
|
||||
let year = month > 12 ? this.year + 1 : this.year
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month > 12 ? 1 : month
|
||||
this.year = year
|
||||
this.changeData()
|
||||
}
|
||||
} else {
|
||||
let month = this.month - 1
|
||||
let year = month < 1 ? this.year - 1 : this.year
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month < 1 ? 12 : month
|
||||
this.year = year
|
||||
this.changeData()
|
||||
}
|
||||
}
|
||||
},
|
||||
// 切换年份
|
||||
changeYearHandler(add) {
|
||||
let year = add ? this.year + 1 : this.year - 1
|
||||
if (!this.checkRange(year)) {
|
||||
this.year = year
|
||||
this.changeData()
|
||||
}
|
||||
},
|
||||
// 日期点击事件
|
||||
dateClick(day) {
|
||||
day += 1
|
||||
if (!this.disabledChoose(this.year, this.month, day)) {
|
||||
this.day = day
|
||||
let date = `${this.year}-${this.month}-${day}`
|
||||
if (this.mode === 'date') {
|
||||
this.activeDate = date
|
||||
} else {
|
||||
let startTimeCompare = new Date(date.replace(/\-/g,'/')).getTime() < new Date(this.startDate.replace(/\-/g,'/')).getTime()
|
||||
if (!this.chooseStart || startTimeCompare) {
|
||||
this.startDate = date
|
||||
this.startYear = this.year
|
||||
this.startMonth = this.month
|
||||
this.startDay = this.day
|
||||
this.endYear = 0
|
||||
this.endMonth = 0
|
||||
this.endDay = 0
|
||||
this.endDate = ''
|
||||
this.activeDate = ''
|
||||
this.chooseStart = true
|
||||
} else {
|
||||
this.endDate = date
|
||||
this.endYear = this.year
|
||||
this.endMonth = this.month
|
||||
this.endDay = this.day
|
||||
this.chooseStart = false
|
||||
}
|
||||
}
|
||||
this.daysArr = this.handleDaysArr()
|
||||
}
|
||||
},
|
||||
// 修改日期数据
|
||||
changeData() {
|
||||
this.days = this.getMonthDay(this.year, this.month)
|
||||
this.daysArr = this.handleDaysArr()
|
||||
this.weekday = this.getMonthFirstWeekDay(this.year, this.month)
|
||||
this.weekdayArr = this.generateArray(1, this.weekday)
|
||||
this.dateTitle = `${this.year}年${this.month}月`
|
||||
if (this.isChange && this.mode === 'date') {
|
||||
this.handleBtnClick(true)
|
||||
}
|
||||
},
|
||||
// 处理按钮点击
|
||||
handleBtnClick(show) {
|
||||
if (!show) {
|
||||
this.close()
|
||||
}
|
||||
if (this.mode === 'date') {
|
||||
let arr = this.activeDate.split('-')
|
||||
let year = this.isChange ? this.year : Number(arr[0])
|
||||
let month = this.isChange ? this.month : Number(arr[1])
|
||||
let day = this.isChange ? this.day : Number(arr[2])
|
||||
let days = this.getMonthDay(year, month)
|
||||
let result = `${year}-${this.formatNumber(month)}-${this.formatNumber(day)}`
|
||||
let weekText = this.getWeekText(result)
|
||||
let isToday = false
|
||||
if (`${year}-${month}-${day}` === this.today) {
|
||||
isToday = true
|
||||
}
|
||||
this.$emit('change', {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
days,
|
||||
week: weekText,
|
||||
isToday,
|
||||
date: result,
|
||||
// 是否为切换年月操作
|
||||
switch: show
|
||||
})
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
|
||||
let startMonth = this.formatNumber(this.startMonth)
|
||||
let startDay = this.formatNumber(this.startDay)
|
||||
let startDate = `${this.startYear}-${startMonth}-${startDay}`
|
||||
let startWeek = this.getWeekText(startDate)
|
||||
|
||||
let endMonth = this.formatNumber(this.endMonth)
|
||||
let endDay = this.formatNumber(this.endDay)
|
||||
let endDate = `${this.endYear}-${endMonth}-${endDay}`
|
||||
let endWeek = this.getWeekText(endDate)
|
||||
|
||||
this.$emit('change', {
|
||||
startYear: this.startYear,
|
||||
startMonth: this.startMonth,
|
||||
startDay: this.startDay,
|
||||
startDate,
|
||||
startWeek,
|
||||
endYear: this.endYear,
|
||||
endMonth: this.endMonth,
|
||||
endDay: this.endDay,
|
||||
endDate,
|
||||
endWeek
|
||||
})
|
||||
}
|
||||
},
|
||||
// 判断是否允许选择
|
||||
disabledChoose(year, month, day) {
|
||||
let flag = true
|
||||
let date = `${year}/${month}/${day}`
|
||||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`
|
||||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`
|
||||
let timestamp = new Date(date).getTime()
|
||||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||
flag = false
|
||||
}
|
||||
return flag
|
||||
},
|
||||
// 检查是否在日期范围内
|
||||
checkRange(year) {
|
||||
let overstep = false
|
||||
if (year < this.minYear || year > this.maxYear) {
|
||||
uni.showToast({
|
||||
title: '所选日期超出范围',
|
||||
icon: 'none'
|
||||
})
|
||||
overstep = true
|
||||
}
|
||||
return overstep
|
||||
},
|
||||
// 处理日期
|
||||
initDate(date) {
|
||||
let fdate = date.split('-')
|
||||
return {
|
||||
year: Number(fdate[0] || 1970),
|
||||
month: Number(fdate[1] || 1),
|
||||
day: Number(fdate[2] || 1)
|
||||
}
|
||||
},
|
||||
// 处理日期数组
|
||||
handleDaysArr() {
|
||||
let days = this.generateArray(1, this.days)
|
||||
let daysArr = days.map((item) => {
|
||||
let bottomInfo = this.showLunar ? Calendar.solar2lunar(this.year, this.month, item).IDayCn : ''
|
||||
let color = this.showLunar ? this.lunarColor : this.activeColor
|
||||
if (
|
||||
(this.mode === 'date' && this.day == item) ||
|
||||
(this.mode === 'range' && (this.startDay == item || this.endDay == item))
|
||||
) {
|
||||
color = this.activeColor
|
||||
}
|
||||
if (this.mode === 'range') {
|
||||
if (this.startDay == item && this.startDay != this.endDay) {
|
||||
bottomInfo = this.startText
|
||||
}
|
||||
if (this.endDay == item) {
|
||||
bottomInfo = this.endText
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
day: item,
|
||||
color: color,
|
||||
bottomInfo: bottomInfo
|
||||
}
|
||||
})
|
||||
return daysArr
|
||||
},
|
||||
// 获取对应月有多少天
|
||||
getMonthDay(year, month) {
|
||||
return new Date(year, month, 0).getDate()
|
||||
},
|
||||
// 获取对应月的第一天时星期几
|
||||
getMonthFirstWeekDay(year, month) {
|
||||
return new Date(`${year}/${month}/01 00:00:00`).getDay()
|
||||
},
|
||||
// 获取对应星期的文本
|
||||
getWeekText(date) {
|
||||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`)
|
||||
let week = date.getDay()
|
||||
return '星期' + this.weekDayZh[week]
|
||||
},
|
||||
// 生成日期天数数组
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start)
|
||||
},
|
||||
// 格式化数字
|
||||
formatNumber(num) {
|
||||
return num < 10 ? '0' + num : num + ''
|
||||
},
|
||||
// 关闭窗口
|
||||
close() {
|
||||
this.$emit('input', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-calendar {
|
||||
color: $tn-font-color;
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
color: $tn-main-color;
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 30rpx;
|
||||
padding: 0 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 40rpx 0 40rpx 0;
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 16rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
font-size: 20rpx;
|
||||
// line-height: 32rpx;
|
||||
border-radius: 50%;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: 0 16rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__week-day-zh {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12rpx 0;
|
||||
overflow: hidden;
|
||||
box-shadow: 16rpx 6rpx 8rpx 0 #E6E6E6;
|
||||
margin-bottom: 2rpx;
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding: 12rpx 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #F7F7F7;
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 14.2857%;
|
||||
padding: 12rpx 0;
|
||||
margin: 6rpx 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
// box-shadow: inset 0rpx 0rpx 22rpx 4rpx rgba(255,255,255, 0.52);
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
font-size: 32rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__tips {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
line-height: 24rpx;
|
||||
left: 0;
|
||||
bottom: 8rpx;
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
transform-origin: center center;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&--start-date {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--end-date {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&__month {
|
||||
&--bg {
|
||||
position: absolute;
|
||||
font-size: 200rpx;
|
||||
line-height: 200rpx;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: $tn-font-holder-color;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background-color: #F7F7F7;
|
||||
padding: 0 40rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
color: $tn-font-sub-color;
|
||||
|
||||
&__choose {
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
border-radius: 40rpx;
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
320
tuniao-ui/components/tn-car-keyboard/tn-car-keyboard.vue
Normal file
320
tuniao-ui/components/tn-car-keyboard/tn-car-keyboard.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<view class="tn-car-keyboard-class tn-car-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="tn-car-keyboard__grids">
|
||||
|
||||
<view
|
||||
v-for="(data, index) in inputCarNumber ? endKeyBoardList : areaList"
|
||||
:key="index"
|
||||
class="tn-car-keyboard__grids__item"
|
||||
>
|
||||
<view
|
||||
v-for="(sub_data, sub_index) in data"
|
||||
:key="sub_index"
|
||||
class="tn-car-keyboard__grids__btn"
|
||||
:class="{'tn-car-keyboard__grids__btn--disabled': sub_data === 'I'}"
|
||||
:hover-class="sub_data !== 'I' ? 'tn-car-keyboard--hover' : ''"
|
||||
:hover-stay-time="100"
|
||||
@tap="click(index, sub_index)"
|
||||
>
|
||||
{{ sub_data }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__back"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-left-arrow tn-car-keyboard__back__icon"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__change"
|
||||
hover-class="tn-car-keyboard--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="changeMode"
|
||||
>
|
||||
<text class="tn-car-keyboard__mode--zh" :class="[`tn-car-keyboard__mode--${!inputCarNumber ? 'active' : 'inactive'}`]">中</text>
|
||||
/
|
||||
<text class="tn-car-keyboard__mode--en" :class="[`tn-car-keyboard__mode--${inputCarNumber ? 'active' : 'inactive'}`]">英</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-car-keyboard',
|
||||
props: {
|
||||
// 是否打乱键盘顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 切换中英文输入
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学',
|
||||
'临',
|
||||
'警'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 30)
|
||||
showData[3] = data.slice(30, 37)
|
||||
return showData
|
||||
},
|
||||
endKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 29)
|
||||
showData[3] = data.slice(29, 36)
|
||||
return showData
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 标记是否输入车牌号码
|
||||
inputCarNumber: false,
|
||||
// 长按多次删除事件监听
|
||||
longPressDeleteTimer: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
switchEnMode: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.inputCarNumber = true
|
||||
} else {
|
||||
this.inputCarNumber = false
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
click(i, j) {
|
||||
let value = ''
|
||||
// 根据不同模式获取不同数组的值
|
||||
if (this.inputCarNumber) value = this.endKeyBoardList[i][j]
|
||||
else value = this.areaList[i][j]
|
||||
|
||||
// 车牌里不包含I
|
||||
if (value === 'I') return
|
||||
|
||||
this.$emit('change', value)
|
||||
},
|
||||
// 修改输入模式
|
||||
// 中文/英文
|
||||
changeMode() {
|
||||
this.inputCarNumber = !this.inputCarNumber
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
this.clearTimer()
|
||||
this.longPressDeleteTimer = setInterval(() => {
|
||||
this.$emit('backspace')
|
||||
}, 250)
|
||||
},
|
||||
// 清空定时器
|
||||
clearTimer() {
|
||||
if (this.longPressDeleteTimer) {
|
||||
clearInterval(this.longPressDeleteTimer)
|
||||
this.longPressDeleteTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-car-keyboard {
|
||||
position: relative;
|
||||
padding: 24rpx 0;
|
||||
background-color: #E6E6E6;
|
||||
|
||||
&__grids {
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
flex: 0 0 64rpx;
|
||||
width: 62rpx;
|
||||
height: 80rpx;
|
||||
font-size: 38rpx;
|
||||
line-height: 80rpx;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background-color: #FFFFFF;
|
||||
margin: 8rpx 5rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__back {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
right: 22rpx;
|
||||
bottom: 32rpx;
|
||||
background-color: #E6E6E6;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__change {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
left: 22rpx;
|
||||
bottom: 32rpx;
|
||||
line-height: 1;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__mode {
|
||||
&--zh {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
&--en {
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: $tn-main-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--zh {
|
||||
transform: scale(0.85) translateY(-10rpx);
|
||||
}
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--en {
|
||||
transform: scale(0.85) translateY(10rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: #E6E6E6 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,654 @@
|
||||
<template>
|
||||
<view class="tn-cascade-selection tn-cascade-selection-class">
|
||||
<scroll-view
|
||||
class="selection__scroll-view"
|
||||
:class="[{'tn-border-solid-bottom': headerLine}]"
|
||||
:style="[scrollViewStyle]"
|
||||
scroll-x
|
||||
scroll-with-animation
|
||||
:scroll-into-view="scrollViewId"
|
||||
>
|
||||
<view class="selection__header" :class="[backgroundColorClass]" :style="[headerStyle]">
|
||||
<view
|
||||
v-for="(item, index) in selectedArr"
|
||||
:key="index"
|
||||
:id="`select__${index}`"
|
||||
class="selection__header__item"
|
||||
:class="[headerItemClass(index)]"
|
||||
:style="[headerItemStyle(index)]"
|
||||
@tap.stop="clickNav(index)"
|
||||
>
|
||||
{{ item.text }}
|
||||
<view
|
||||
v-if="index===currentTab && showActiveLine"
|
||||
class="selection__header__line"
|
||||
:style="{backgroundColor: activeLineColor}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<swiper
|
||||
class="selection__list"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[listStyle]"
|
||||
:current="currentTab"
|
||||
:duration="300"
|
||||
@change="switchTab"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="(item, index) in selectedArr"
|
||||
:key="index"
|
||||
>
|
||||
<scroll-view
|
||||
class="selection__list__item"
|
||||
:style="{height: selectionContainerHeight + 'rpx'}"
|
||||
scroll-y
|
||||
:scroll-into-view="item.scrollViewId"
|
||||
>
|
||||
<view class="selection__list__item--first"></view>
|
||||
<view
|
||||
v-for="(subItem, subIndex) in item.list"
|
||||
:key="subIndex"
|
||||
:id="`select__${subIndex}`"
|
||||
class="selection__list__item__cell"
|
||||
:style="[itemStyle]"
|
||||
@tap="change(index, subIndex, subItem)"
|
||||
>
|
||||
<view
|
||||
v-if="item.index === subIndex"
|
||||
class="selection__list__item__icon tn-icon-success"
|
||||
:style="[itemIconStyle]"
|
||||
></view>
|
||||
<image
|
||||
v-if="subItem.src"
|
||||
class="selection__list__item__image"
|
||||
:style="[itemImageStyle]"
|
||||
:src="subItem.src"
|
||||
></image>
|
||||
<view
|
||||
class="selection__list__item__title"
|
||||
:class="[{'tn-text-bold': item.index === subIndex && itemActiveBold}]"
|
||||
:style="[itemTitleStyle(index, subIndex)]"
|
||||
>
|
||||
{{ subItem.text }}
|
||||
</view>
|
||||
<view
|
||||
v-if="subItem.subText"
|
||||
class="selection__list__item__title--sub"
|
||||
:style="[itemSubTitleStyle]"
|
||||
>
|
||||
{{ subItem.subText }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-cascade-selection',
|
||||
mixins: [ componentsColorMixin ],
|
||||
props: {
|
||||
// 如果下一级是请求返回,则为第一级数据,否则为所有数据
|
||||
/* {
|
||||
text: '', // 标题
|
||||
subText: '', // 子标题
|
||||
src: '', // 图片地址
|
||||
value: 0, // 选中的值
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
subText: '',
|
||||
value: 0,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
} */
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 默认选中值
|
||||
// ['value1','value2','value3']
|
||||
defaultValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 子集数据通过请求来获取
|
||||
request: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// request为true时生效, 获取到的子集数据
|
||||
receiveData: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 显示header底部细线
|
||||
headerLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// header背景颜色
|
||||
headerBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 顶部标签栏高度,单位rpx
|
||||
tabsHeight: {
|
||||
type: Number,
|
||||
default: 88
|
||||
},
|
||||
// 默认显示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
// 选中的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 选中后加粗
|
||||
activeBold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 选中显示底部线条
|
||||
showActiveLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 线条颜色
|
||||
activeLineColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// icon大小,单位rpx
|
||||
activeIconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// icon颜色
|
||||
activeIconColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// item图片宽度, 单位rpx
|
||||
itemImgWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item图片高度, 单位rpx
|
||||
itemImgHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item图片圆角
|
||||
itemImgRadius: {
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
// item text颜色
|
||||
itemTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// item text选中颜色
|
||||
itemActiveTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// item text选中加粗
|
||||
itemActiveBold: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// item text文字大小, 单位rpx
|
||||
itemTextSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item subText颜色
|
||||
itemSubTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// item subText字体大小, 单位rpx
|
||||
itemSubTextSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// selection选项容器高度, 单位rpx
|
||||
selectionContainerHeight: {
|
||||
type: Number,
|
||||
default: 300
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scrollViewStyle() {
|
||||
let style = {}
|
||||
if (this.headerBgColor) {
|
||||
style.backgroundColor = this.headerBgColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
headerStyle() {
|
||||
let style = {}
|
||||
style.height = `${this.tabsHeight}rpx`
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
return style
|
||||
},
|
||||
headerItemClass() {
|
||||
return (index) => {
|
||||
let clazz = ''
|
||||
if (index !== this.currentTab) {
|
||||
clazz += ` ${this.fontColorClass}`
|
||||
} else {
|
||||
if (this.activeBold) {
|
||||
clazz += ' tn-text-bold'
|
||||
}
|
||||
}
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
headerItemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
style.color = index === this.currentTab ? this.activeColor : (this.fontColorStyle ? this.fontColorStyle : '')
|
||||
if (this.fontSizeStyle) {
|
||||
style.fontSize = this.fontSizeStyle
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
listStyle() {
|
||||
let style = {}
|
||||
style.height = `${this.selectionContainerHeight}rpx`
|
||||
if (this.backgroundColorStyle) {
|
||||
style.color = this.backgroundColorStyle
|
||||
}
|
||||
return style
|
||||
},
|
||||
itemIconStyle() {
|
||||
let style = {}
|
||||
if (this.activeIconColor) {
|
||||
style.color = this.activeIconColor
|
||||
}
|
||||
if (this.activeIconSize) {
|
||||
style.fontSize = this.activeIconSize + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
itemImageStyle() {
|
||||
let style = {}
|
||||
if (this.itemImgWidth) {
|
||||
style.width = this.itemImgWidth + 'rpx'
|
||||
}
|
||||
if (this.itemImgHeight) {
|
||||
style.height = this.itemImgHeight + 'rpx'
|
||||
}
|
||||
if (this.itemImgRadius) {
|
||||
style.borderRadius = this.itemImgRadius
|
||||
}
|
||||
return style
|
||||
},
|
||||
itemTitleStyle() {
|
||||
return (index, subIndex) => {
|
||||
let style = {}
|
||||
if (index === subIndex) {
|
||||
if (this.itemActiveTextColor) {
|
||||
style.color = this.itemActiveTextColor
|
||||
}
|
||||
} else {
|
||||
if (this.itemTextColor) {
|
||||
style.color = this.itemTextColor
|
||||
}
|
||||
}
|
||||
if (this.itemTextSize) {
|
||||
style.fontSize = this.itemTextSize + 'rpx'
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
itemSubTitleStyle() {
|
||||
let style = {}
|
||||
if (this.itemSubTextColor) {
|
||||
style.color = this.itemSubTextColor
|
||||
}
|
||||
if (this.itemSubTextSize) {
|
||||
style.fontSize = this.itemSubTextSize + 'rpx'
|
||||
}
|
||||
return {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(val) {
|
||||
this.initData(val, -1)
|
||||
},
|
||||
defaultValue(val) {
|
||||
this.setDefaultValue(val)
|
||||
},
|
||||
receiveData(val) {
|
||||
this.addSubData(val, this.currentTab)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前选中的子集
|
||||
currentTab: 0,
|
||||
// tabs栏scrollView滚动的位置
|
||||
scrollViewId: 'select__0',
|
||||
// 选项数组
|
||||
selectedArr: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setDefaultValue(this.defaultValue)
|
||||
},
|
||||
methods: {
|
||||
// 初始化数据
|
||||
initData(data, index) {
|
||||
if (!data || data.length === 0) return
|
||||
if (this.request) {
|
||||
// 第一级数据
|
||||
this.addSubData(data, index)
|
||||
} else {
|
||||
this.addSubData(this.getItemList(index, -1), index)
|
||||
}
|
||||
},
|
||||
// 重置数据
|
||||
reset() {
|
||||
this.initData(this.list, -1)
|
||||
},
|
||||
// 滚动切换
|
||||
switchTab(e) {
|
||||
this.currentTab = e.detail.current
|
||||
this.checkSelectPosition()
|
||||
},
|
||||
// 点击标题切换
|
||||
clickNav(index) {
|
||||
if (this.currentTab !== index) {
|
||||
this.currentTab = index
|
||||
}
|
||||
},
|
||||
// 列表数据发生改变
|
||||
change(index, subIndex, subItem) {
|
||||
let item = this.selectedArr[index]
|
||||
if (item.index === subIndex) return
|
||||
item.index = subIndex
|
||||
item.text = subItem.text
|
||||
item.subText = subItem.subText || ''
|
||||
item.value = subItem.value
|
||||
item.src = subItem.src || ''
|
||||
this.$emit('change', {
|
||||
index: index,
|
||||
subIndex: subIndex,
|
||||
...subItem
|
||||
})
|
||||
|
||||
// 如果不是异步加载,则取出对应的数据
|
||||
if (!this.request) {
|
||||
let data = this.getItemList(index, subIndex)
|
||||
this.addSubData(data, index)
|
||||
}
|
||||
},
|
||||
// 设置默认的数据
|
||||
setDefaultValue(val) {
|
||||
let defaultValues = val || []
|
||||
if (defaultValues.length > 0) {
|
||||
this.selectedArr = this.getItemListWithValues(JSON.parse(JSON.stringify(this.list)), defaultValues)
|
||||
if (!this.selectedArr) return
|
||||
this.currentTab = this.selectedArr.length - 1
|
||||
this.$nextTick(() => {
|
||||
this.checkSelectPosition()
|
||||
})
|
||||
// defaultItemList.map((item) => {
|
||||
// item.scrollViewId = `select__${item.index}`
|
||||
// })
|
||||
// this.selectedArr = defaultItemList
|
||||
// this.currentTab = defaultItemList.length - 1
|
||||
// this.$nextTick(() => {
|
||||
// this.checkSelectPosition()
|
||||
// })
|
||||
} else {
|
||||
this.initData(this.list, -1)
|
||||
}
|
||||
},
|
||||
// 获取对应选项的item数据
|
||||
getItemList(index, subIndex) {
|
||||
let list = []
|
||||
let arr = JSON.parse(JSON.stringify(this.list))
|
||||
// 初始化数据
|
||||
if (index === -1) {
|
||||
list = this.removeChildren(arr)
|
||||
} else {
|
||||
// 判断第一项是否已经选择
|
||||
let value = this.selectedArr[0].index
|
||||
value = value === -1 ? subIndex : value
|
||||
list = arr[value].children || []
|
||||
if (index > 0) {
|
||||
for (let i = 1; i < index + 1; i++) {
|
||||
// 获取当前数据选中的序号
|
||||
let val = index === i ? subIndex : this.selectedArr[i].index
|
||||
list = list[val].children || []
|
||||
if (list.length === 0) break
|
||||
}
|
||||
}
|
||||
list = this.removeChildren(list)
|
||||
}
|
||||
return list
|
||||
},
|
||||
// 根据数组中的值获取对应的item数据
|
||||
getItemListWithValues(data, values) {
|
||||
const defaultValues = JSON.parse(JSON.stringify(values))
|
||||
if (!defaultValues || defaultValues.length === 0) return
|
||||
// 取出第一个值所对应的item
|
||||
const itemIndex = data.findIndex((item) => {
|
||||
return item.value === defaultValues[0]
|
||||
})
|
||||
if (itemIndex === -1) return
|
||||
const item = data[itemIndex]
|
||||
item.index = itemIndex
|
||||
item.scrollViewId = `select__${itemIndex}`
|
||||
item.list = this.removeChildren(JSON.parse(JSON.stringify(data)))
|
||||
// 判断是否只有1个值
|
||||
if (defaultValues.length === 1 || (!item.hasOwnProperty('children') || item.children.length === 0)) {
|
||||
return this.removeChildren([item])
|
||||
} else {
|
||||
let selectItemList = []
|
||||
const children = item.children
|
||||
selectItemList.push(item)
|
||||
// 移除已经获取的值
|
||||
defaultValues.splice(0, 1)
|
||||
const childrenValue = this.getItemListWithValues(children, defaultValues)
|
||||
selectItemList = selectItemList.concat(childrenValue)
|
||||
|
||||
return this.removeChildren(selectItemList)
|
||||
}
|
||||
},
|
||||
// 删除子元素
|
||||
removeChildren(data) {
|
||||
let list = data.map((item) => {
|
||||
if (item.hasOwnProperty('children')) {
|
||||
delete item['children']
|
||||
}
|
||||
return item
|
||||
})
|
||||
return list
|
||||
},
|
||||
// 新增子集数据时处理
|
||||
addSubData(data, index) {
|
||||
// 判断是否已经完成选择数据或者为初始化数据
|
||||
if (!data || data.length === 0) {
|
||||
if (index == -1) return
|
||||
// 完成选择
|
||||
let arr = this.selectedArr
|
||||
// 如果当前选中项的序号比已选数据的长度小,则表示当前重新选择了数据
|
||||
if (index < arr.length - 1) {
|
||||
let newArr = arr.slice(0, index + 1)
|
||||
this.selectedArr = newArr
|
||||
}
|
||||
let result = JSON.parse(JSON.stringify(this.selectedArr))
|
||||
let lastItem = result[result.length - 1] || {}
|
||||
let text = ''
|
||||
result.map(item => {
|
||||
text += item.text
|
||||
delete item['list']
|
||||
delete item['scrollViewId']
|
||||
return item
|
||||
})
|
||||
this.$emit('complete', {
|
||||
result: result,
|
||||
value: lastItem.value,
|
||||
text: text,
|
||||
subText: lastItem.subText,
|
||||
src: lastItem.src
|
||||
})
|
||||
} else {
|
||||
// 重置数据
|
||||
let item = [{
|
||||
text: this.text,
|
||||
subText: '',
|
||||
value: '',
|
||||
src: '',
|
||||
index: -1,
|
||||
scrollViewId: 'select__0',
|
||||
list: data
|
||||
}]
|
||||
// 初始化数据
|
||||
if (index === -1) {
|
||||
this.selectedArr = item
|
||||
} else {
|
||||
// 拼接新旧数据并且判断是否为重新选择了数据(如果为重新选择了数据则重置之后的选项数据)
|
||||
let retainArr = this.selectedArr.slice(0, index + 1)
|
||||
this.selectedArr = retainArr.concat(item)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.currentTab = this.selectedArr.length - 1
|
||||
})
|
||||
}
|
||||
},
|
||||
// 检查当前选中项,并将选项设置位置信息
|
||||
checkSelectPosition() {
|
||||
let item = this.selectedArr[this.currentTab]
|
||||
item.scrollViewId = 'select__0'
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
// 设置当前数据滚动到的位置
|
||||
let val = item.index < 2 ? 0 : Number(item.index - 2)
|
||||
item.scrollViewId = `select__${val}`
|
||||
}, 10)
|
||||
})
|
||||
|
||||
// 设置选项滚动到所在的位置
|
||||
if (this.currentTab > 1) {
|
||||
this.scrollViewId = `select__${this.currentTab - 1}`
|
||||
} else {
|
||||
this.scrollViewId = `select__0`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-cascade-selection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selection {
|
||||
&__scroll-view {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
max-width: 240rpx;
|
||||
padding: 15rpx 30rpx;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__line {
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 4rpx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
background-color: #FFFFFF;
|
||||
&__item {
|
||||
&--first {
|
||||
width: 100%;
|
||||
height: 20rpx;
|
||||
}
|
||||
|
||||
&__cell {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 12rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
word-break: break-all;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
|
||||
&--sub {
|
||||
margin-left: 20rpx;
|
||||
word-break: break-all;
|
||||
color: $tn-font-sub-color;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
134
tuniao-ui/components/tn-checkbox-group/tn-checkbox-group.vue
Normal file
134
tuniao-ui/components/tn-checkbox-group/tn-checkbox-group.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<view class="tn-checkbox-group-class tn-checkbox-group">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [ Emitter ],
|
||||
name: 'tn-checkbox-group',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 可以选中多少个checkbox
|
||||
max: {
|
||||
type: Number,
|
||||
default: 999
|
||||
},
|
||||
// 表单提交时的标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 每个checkbox占的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件tn-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(tn-checkbox-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.value, this.disabled, this.disabledLabel, this.shape, this.activeColor, this.size, this.width, this.wrap, this.iconSize]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件中的子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-checkbox)如果有updateParentData方法的话,子组件重新从父组件拉取了最新的值
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
initValue(values) {
|
||||
this.$emit('input', values)
|
||||
},
|
||||
// 触发事件
|
||||
emitEvent() {
|
||||
let values = []
|
||||
this.children.map(child => {
|
||||
if (child.checkValue) values.push(child.name)
|
||||
})
|
||||
this.$emit('change', values)
|
||||
this.$emit('input', values)
|
||||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 tn-form-item 进行校验
|
||||
this.dispatch('tn-form-item', 'on-form-change', values)
|
||||
}, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
&::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
328
tuniao-ui/components/tn-checkbox/tn-checkbox.vue
Normal file
328
tuniao-ui/components/tn-checkbox/tn-checkbox.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<view class="tn-checkbox-class tn-checkbox" :style="[checkboxStyle]">
|
||||
<view
|
||||
class="tn-checkbox__icon-wrap"
|
||||
:class="[iconClass]"
|
||||
:style="[iconStyle]"
|
||||
@tap="toggle"
|
||||
>
|
||||
<view class="tn-checkbox__icon-wrap__icon" :class="[`tn-icon-${iconName}`]"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-checkbox__label"
|
||||
:class="[labelClass]"
|
||||
:style="{
|
||||
fontSize: labelSize ? labelSize + 'rpx' : ''
|
||||
}"
|
||||
@tap="onClickLabel"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-checkbox',
|
||||
props: {
|
||||
// checkbox名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否为选中状态
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标名称
|
||||
iconName: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的字体大小
|
||||
labelSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabled() {
|
||||
return this.disabled ? this.disabled : (this.parent ? this.parentData.disabled : false)
|
||||
},
|
||||
// 是否禁用点击label选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabledLabel() {
|
||||
return this.disabledLabel ? this.disabledLabel : (this.parent ? this.parentData.disabledLabel : false)
|
||||
},
|
||||
// 尺寸
|
||||
checkboxSize() {
|
||||
return this.size ? this.size : (this.parent ? this.parentData.size : 34)
|
||||
},
|
||||
// 激活时的颜色
|
||||
elAvtiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parent ? this.parentData.activeColor : '#01BEFF')
|
||||
},
|
||||
// 形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parent ? this.parentData.shape : 'square')
|
||||
},
|
||||
iconClass() {
|
||||
let clazz = ''
|
||||
clazz += (' tn-checkbox__icon-wrap--' + this.elShape)
|
||||
|
||||
if (this.checkValue) clazz += ' tn-checkbox__icon-wrap--checked'
|
||||
if (this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled'
|
||||
if (this.value && this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled--checked'
|
||||
|
||||
return clazz
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
// 判断是否用户手动禁用和传递的值
|
||||
if (this.elAvtiveColor && this.checkValue && !this.isDisabled) {
|
||||
style.borderColor = this.elAvtiveColor
|
||||
style.backgroundColor = this.elAvtiveColor
|
||||
}
|
||||
|
||||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
|
||||
style.color = this.checkValue ? '#FFFFFF' : 'transparent'
|
||||
|
||||
style.width = this.checkboxSize + 'rpx'
|
||||
style.height = style.width
|
||||
|
||||
style.fontSize = (this.iconSize ? this.iconSize : (this.parent ? this.parentData.iconSize : 20)) + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
checkboxStyle() {
|
||||
let style = {}
|
||||
if (this.parent && this.parentData.width) {
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parentData.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
labelClass() {
|
||||
let clazz = ''
|
||||
if (this.isDisabled) {
|
||||
clazz += ' tn-checkbox__label--disabled'
|
||||
}
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前checkbox的value值
|
||||
checkValue: false,
|
||||
parentData: {
|
||||
value: null,
|
||||
max: null,
|
||||
disabled: null,
|
||||
disabledLabel: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
wrap: null,
|
||||
iconSize: null
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.checkValue = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
// this.parent = this.$t.$parent.call(this, 'tn-checkbox-group')
|
||||
// // 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
|
||||
// this.parent && this.parent.children.push(this)
|
||||
// // 初始化父组件的value值
|
||||
// this.parent && this.parent.emitEvent()
|
||||
this.updateParentData()
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
methods: {
|
||||
updateCheckValue() {
|
||||
// 更新当前checkbox的选中状态
|
||||
this.checkValue = (this.parent && this.parentData.value.includes(this.name)) || this.value === true
|
||||
if (this.parent) {
|
||||
if (this.value && !this.parentData.value.includes(this.name)) {
|
||||
this.parentData.value.push(this.name)
|
||||
this.parent.initValue(this.parentData.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
updateParentData() {
|
||||
this.getParentData('tn-checkbox-group')
|
||||
this.updateCheckValue()
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.isDisabled && !this.isDisabledLabel) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', {
|
||||
name: this.name,
|
||||
value: !this.checkValue
|
||||
})
|
||||
if (this.parent) {
|
||||
this.checkValue = !this.checkValue
|
||||
// 执行父组件tn-checkbox-group的事件方法
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
setTimeout(() => {
|
||||
if(this.parent.emitEvent) this.parent.emitEvent();
|
||||
}, 80)
|
||||
}
|
||||
},
|
||||
// 设置input的值,通过v-modal绑定组件的值
|
||||
setValue() {
|
||||
// 判断是否为可选项组
|
||||
if (this.parent) {
|
||||
// 反转状态
|
||||
if (this.checkValue === true) {
|
||||
this.emitEvent()
|
||||
// this.$emit('input', !this.checkValue)
|
||||
} else {
|
||||
// 超出最大可选项,弹出提示
|
||||
if (this.parentData.value.length >= this.parentData.max) {
|
||||
return this.$t.message.toast(`最多可选${this.parent.max}项`)
|
||||
}
|
||||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
|
||||
this.emitEvent();
|
||||
// this.$emit('input', !this.checkValue);
|
||||
}
|
||||
} else {
|
||||
// 只有一个可选项
|
||||
this.emitEvent()
|
||||
this.$emit('input', !this.checkValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $tn-font-color;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
border: 1px solid $tn-font-sub-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
border-color: $tn-main-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $tn-font-holder-color;
|
||||
border-color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $tn-font-sub-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
223
tuniao-ui/components/tn-circle-progress/tn-circle-progress.vue
Normal file
223
tuniao-ui/components/tn-circle-progress/tn-circle-progress.vue
Normal file
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-circle-progress-class tn-circle-progress"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
>
|
||||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
||||
<!-- 默认圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas-bg"
|
||||
:canvas-id="elBgId"
|
||||
:id="elBgId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<!-- 进度圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas"
|
||||
:canvas-id="elId"
|
||||
:id="elId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<view class="tn-circle-progress__content">
|
||||
<slot v-if="$slots.default || $slots.$default"></slot>
|
||||
<view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-circle-progress',
|
||||
props: {
|
||||
// 进度(百分比)
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100
|
||||
}
|
||||
},
|
||||
// 圆环线宽
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
// 整体圆的宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否运动
|
||||
stripedActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 激活部分颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 非激活部分颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#f0f0f0'
|
||||
},
|
||||
// 是否显示进度条内部百分比值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆环执行动画的时间,ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1500
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错
|
||||
// #ifdef MP-WEIXIN
|
||||
elBgId: 'tCircleProgressBgId',
|
||||
elId: 'tCircleProgressElId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
elBgId: this.$t.uuid(),
|
||||
elId: this.$t.uuid(),
|
||||
// #endif
|
||||
// 活动圆上下文
|
||||
progressContext: null,
|
||||
// 转换成px为单位的背景宽度
|
||||
widthPx: uni.upx2px(this.width || 200),
|
||||
// 转换成px为单位的圆环宽度
|
||||
borderWidthPx: uni.upx2px(this.borderWidth || 14),
|
||||
// canvas画圆的起始角度,默认为-90度,顺时针
|
||||
startAngle: -90 * Math.PI / 180,
|
||||
// 动态修改进度值的时候,保存进度值的变化前后值
|
||||
newPercent: 0,
|
||||
oldPercent: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
percent(newVal, oldVal = 0) {
|
||||
if (newVal > 100) newVal = 100
|
||||
if (oldVal < 0) oldVal = 0
|
||||
|
||||
this.newPercent = newVal
|
||||
this.oldPercent = oldVal
|
||||
setTimeout(() => {
|
||||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
|
||||
// 将此值减少或者新增到新的百分比值
|
||||
this.drawCircleByProgress(oldVal)
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 赋值,用于加载后第一个画圆使用
|
||||
this.newPercent = this.percent;
|
||||
this.oldPercent = 0;
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.drawProgressBg()
|
||||
this.drawCircleByProgress(this.oldPercent)
|
||||
}, 50)
|
||||
},
|
||||
methods: {
|
||||
// 绘制进度条背景
|
||||
drawProgressBg() {
|
||||
let ctx = uni.createCanvasContext(this.elBgId, this)
|
||||
// 设置线宽
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
// 设置颜色
|
||||
ctx.setStrokeStyle(this.inactiveColor)
|
||||
ctx.beginPath()
|
||||
let radius = this.widthPx / 2
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
},
|
||||
// 绘制圆弧的进度
|
||||
drawCircleByProgress(progress) {
|
||||
// 如果已经存在则拿来使用
|
||||
let ctx = this.progressContext
|
||||
if (!ctx) {
|
||||
ctx =uni.createCanvasContext(this.elId, this)
|
||||
this.progressContext = ctx
|
||||
}
|
||||
ctx.setLineCap('round')
|
||||
// 设置线条宽度和颜色
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
ctx.setStrokeStyle(this.activeColor)
|
||||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
|
||||
let preSecondTime = Math.floor(this.duration / 100)
|
||||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
|
||||
let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle
|
||||
let radius = this.widthPx / 2
|
||||
ctx.beginPath()
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
|
||||
// 如果变更后新值大于旧值,意味着增大了百分比
|
||||
if (this.newPercent > this.oldPercent) {
|
||||
// 每次递增百分之一
|
||||
progress++
|
||||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
|
||||
if (progress > this.newPercent) return
|
||||
} else {
|
||||
progress--
|
||||
if (progress < this.newPercent) return
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
|
||||
this.drawCircleByProgress(progress)
|
||||
}, preSecondTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-circle-progress {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
|
||||
&__canvas {
|
||||
position: absolute;
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__percent {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
236
tuniao-ui/components/tn-collapse-item/tn-collapse-item.vue
Normal file
236
tuniao-ui/components/tn-collapse-item/tn-collapse-item.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<view class="tn-collapse-item-class tn-collapse-item" :style="[itemStyle]">
|
||||
<!-- 头部 -->
|
||||
<view
|
||||
class="tn-collapse-item__head"
|
||||
:style="[headStyle]"
|
||||
:hover-stay-time="200"
|
||||
:hover-class="hoverClass"
|
||||
@tap.stop="headClick"
|
||||
>
|
||||
<block v-if="!$slots['title-all'] || !$slots['$title-all']">
|
||||
<view
|
||||
v-if="!$slots.title || !$slots.$title"
|
||||
class="tn-collapse-item__head__title tn-text-ellipsis"
|
||||
:style="[
|
||||
{ textAlign: align ? align : 'left'},
|
||||
isShow && activeStyle && !arrow ? activeStyle : ''
|
||||
]"
|
||||
>{{ title }}</view>
|
||||
<view v-else>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
<view class="tn-collapse-item__head__icon__wrap">
|
||||
<view
|
||||
v-if="arrow"
|
||||
class="tn-icon-down tn-collapse-item__head__icon__arrow"
|
||||
:class="{'tn-collapse-item__head__icon__arrow--active': isShow}"
|
||||
:style="[arrowIconStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else>
|
||||
<slot name="title-all"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
class="tn-collapse-item__body"
|
||||
:style="[{
|
||||
height: isShow ? height + 'px' : '0'
|
||||
}]"
|
||||
>
|
||||
<view class="tn-collapse-item__body__content" :id="elId" :style="[bodyStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse-item',
|
||||
props: {
|
||||
// 展开
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 唯一标识
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击不收起
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 活动时样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标识
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
arrowIconStyle() {
|
||||
let style = {}
|
||||
if (this.arrowColor) {
|
||||
style.color = this.arrowColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
elId: this.$t.uuid(),
|
||||
// body高度
|
||||
height: 0,
|
||||
// 头部样式
|
||||
headStyle: {},
|
||||
// 主体样式
|
||||
bodyStyle: {},
|
||||
// item样式
|
||||
itemStyle: {},
|
||||
// 显示右边箭头
|
||||
arrow: true,
|
||||
// 箭头颜色
|
||||
arrowColor: '',
|
||||
// 点击头部时的效果样式
|
||||
hoverClass: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open(value) {
|
||||
this.isShow = value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.isShow = this.open
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容或者修改了内容时重新获取内容的信息
|
||||
init() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-collapse')
|
||||
if (this.parent) {
|
||||
this.nameSync = this.name ? this.name : this.parent.childrens.length
|
||||
// 不存在才添加对应实例
|
||||
!this.parent.childrens.includes(this) && this.parent.childrens.push(this)
|
||||
this.headStyle = this.parent.headStyle
|
||||
this.bodyStyle = this.parent.bodyStyle
|
||||
this.itemStyle = this.parent.itemStyle
|
||||
this.arrow = this.parent.arrow
|
||||
this.arrowColor = this.parent.arrowColor
|
||||
this.hoverClass = this.parent.hoverClass
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.queryRect()
|
||||
})
|
||||
},
|
||||
// 点击头部
|
||||
headClick() {
|
||||
if (this.disabled) return
|
||||
if (this.parent && this.parent.accordion) {
|
||||
this.parent.childrens.map(child => {
|
||||
// 如果是手风琴模式,将其他的item关闭
|
||||
if (this !== child) {
|
||||
child.isShow = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.isShow = !this.isShow
|
||||
// 触发修改事件
|
||||
this.$emit('change', {
|
||||
index: this.index,
|
||||
show: this.isShow
|
||||
})
|
||||
// 只有在打开时才触发父元素的change
|
||||
if (this.isShow) this.parent && this.parent.onChange()
|
||||
this.$forceUpdate()
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this.height = res.height
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-collapse-item {
|
||||
|
||||
&__head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
padding: 24rpx 0;
|
||||
padding-left: 24rpx;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&__arrow {
|
||||
transition: all 0.3s;
|
||||
margin-right: 20rpx;
|
||||
margin-left: 14rpx;
|
||||
font-size: inherit;
|
||||
|
||||
&--active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
tuniao-ui/components/tn-collapse/tn-collapse.vue
Normal file
98
tuniao-ui/components/tn-collapse/tn-collapse.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view class="tn-collapse-class tn-collapse">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse',
|
||||
props: {
|
||||
// 是否为手风琴
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主题样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 显示箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 点击标题栏时的按压样式
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parentData() {
|
||||
return [this.headStyle, this.bodyStyle, this.itemStyle, this.arrow, this.arrowColor, this.hoverClass]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
// 如果父组件的参数发生变化重新初始化子组件的信息
|
||||
if (this.childrens.length > 0) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化内部所有子元素计算高度,异步获取数据时重新渲染
|
||||
init() {
|
||||
this.childrens.forEach((child, index) => {
|
||||
child.init()
|
||||
})
|
||||
},
|
||||
// collapseItem被点击时由collapseItem调用父组件
|
||||
onChange() {
|
||||
let activeItem = []
|
||||
this.childrens.forEach((child, index) => {
|
||||
if (child.isShow) {
|
||||
activeItem.push(child.nameSync)
|
||||
}
|
||||
})
|
||||
// 如果时手风琴模式,只有一个匹配结果,即activeItem长度为1
|
||||
if (this.accordion) activeItem = activeItem.join(',')
|
||||
this.$emit('change', activeItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
318
tuniao-ui/components/tn-color-icon/tn-color-icon.vue
Normal file
318
tuniao-ui/components/tn-color-icon/tn-color-icon.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<text
|
||||
class="tn-color-icon-class tn-color-icon"
|
||||
:class="[
|
||||
'tn-color-icon-' + name
|
||||
]"
|
||||
:style="{
|
||||
fontSize: size + unit,
|
||||
margin: margin
|
||||
}"
|
||||
@tap="handleClick"
|
||||
></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-color-icon',
|
||||
props: {
|
||||
// 索引
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 图标名称
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小
|
||||
size: {
|
||||
type: Number,
|
||||
default:32
|
||||
},
|
||||
// 大小单位
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit("tap", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: "tuniaoColorFont"; /* Project id 2445412 */
|
||||
/* Color fonts */
|
||||
src: url('iconfont.woff2?t=1632654518618') format('woff2');
|
||||
}
|
||||
|
||||
.tn-color-icon {
|
||||
font-family: "tuniaoColorFont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-github:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-qq:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weixin:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-alipay:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weibo:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-dingtalk:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.tn-color-icon-safe:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.tn-color-icon-wifi:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.tn-color-icon-help:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.tn-color-icon-tag:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.tn-color-icon-play:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.tn-color-icon-stopwatch:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.tn-color-icon-home:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.tn-color-icon-map:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.tn-color-icon-book:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.tn-color-icon-qrcode:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.tn-color-icon-discover:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.tn-color-icon-visitor:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.tn-color-icon-menu:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.tn-color-icon-renew:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.tn-color-icon-business:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.tn-color-icon-telephone:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.tn-color-icon-medicine:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.tn-color-icon-chicken:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.tn-color-icon-clock:before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.tn-color-icon-download:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.tn-color-icon-lamp:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.tn-color-icon-hourglass:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.tn-color-icon-calendar:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.tn-color-icon-bluetooth:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.tn-color-icon-fish:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.tn-color-icon-seal:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.tn-color-icon-remind:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.tn-color-icon-music:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.tn-color-icon-email:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.tn-color-icon-medal:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.tn-color-icon-image:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.tn-color-icon-network:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.tn-color-icon-wallet:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.tn-color-icon-program:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.tn-color-icon-shrimp:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.tn-color-icon-collect:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.tn-color-icon-screw:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.tn-color-icon-set:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.tn-color-icon-userfavorite:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.tn-color-icon-useradd:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.tn-color-icon-honor:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.tn-color-icon-shop:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.tn-color-icon-usercard:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.tn-color-icon-school:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.tn-color-icon-user:before {
|
||||
content: "\e633";
|
||||
}
|
||||
|
||||
.tn-color-icon-internet:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.tn-color-icon-time:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.tn-color-icon-topic:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.tn-color-icon-phone:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.tn-color-icon-usertable:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.tn-color-icon-userset:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.tn-color-icon-game:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
</style>
|
||||
251
tuniao-ui/components/tn-column-notice/tn-column-notice.vue
Normal file
251
tuniao-ui/components/tn-column-notice/tn-column-notice.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-column-notice-class tn-column-notice"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[noticeStyle]"
|
||||
>
|
||||
<!-- 左图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="leftIcon"
|
||||
class="tn-column-notice__icon--left"
|
||||
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('leftIcon')]"
|
||||
@tap="clickLeftIcon"></view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动显示内容 -->
|
||||
<swiper class="tn-column-notice__swiper" :style="[swiperStyle]" :vertical="vertical" circular :autoplay="autoplay && playStatus === 'play'" :interval="duration" @change="change">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="tn-column-notice__swiper--item">
|
||||
<view
|
||||
class="tn-column-notice__swiper--content tn-text-ellipsis"
|
||||
:class="[fontColorClass]"
|
||||
:style="[fontStyle()]"
|
||||
@tap="click(index)"
|
||||
>{{ item }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 右图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="rightIcon"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('rightIcon')]"
|
||||
@tap="clickRightIcon"></view>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-close`,fontColorClass]"
|
||||
:style="[fontStyle('close')]"
|
||||
@tap="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-column-notice',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 滚动方向
|
||||
// horizontal -> 水平滚动 vertical -> 垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动周期
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
return (type) => {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : ''
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
|
||||
if (type === 'leftIcon' && this.leftIconSize) {
|
||||
style.fontSize = this.leftIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'rightIcon' && this.rightIconSize) {
|
||||
style.fontSize = this.rightIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'close') {
|
||||
style.fontSize = '24rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
noticeStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
|
||||
if (this.padding) style.padding = this.padding
|
||||
return style
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
style.height = this.fontSize ? this.fontSize + 6 + this.fontUnit : '32rpx'
|
||||
style.lineHeight = style.height
|
||||
|
||||
return style
|
||||
},
|
||||
// 标记是否为垂直
|
||||
vertical() {
|
||||
if (this.mode === 'horizontal') return false
|
||||
else return true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 点击了通知栏
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
},
|
||||
// 切换消息时间
|
||||
change(event) {
|
||||
let index = event.detail.current
|
||||
if (index === this.list.length - 1) {
|
||||
this.$emit('end')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-column-notice {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&__swiper {
|
||||
height: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 12rpx;
|
||||
|
||||
&--item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&--left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
314
tuniao-ui/components/tn-count-down/tn-count-down.vue
Normal file
314
tuniao-ui/components/tn-count-down/tn-count-down.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<view class="tn-countdown-class tn-countdown">
|
||||
<view
|
||||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ d }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showHours && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '天'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showHours"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '时'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ m }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '分'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds && separator === 'cn'"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
秒
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-down',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 倒计时时间,秒作为单位
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 数字框高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 分隔符类型
|
||||
// en -> 使用英文的冒号 cn -> 使用中文进行分割
|
||||
separator: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 分割符大小
|
||||
separatorSize: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 分隔符颜色
|
||||
separatorColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示边框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示秒
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示分
|
||||
showMinutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示时
|
||||
showHours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示天
|
||||
showDays: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 如果当天的部分为0时,是否隐藏不显示
|
||||
hideZeroDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 倒计时item的样式
|
||||
itemStyle() {
|
||||
let style = {}
|
||||
if (this.height) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
style.width = style.height
|
||||
}
|
||||
if (this.showBorder) {
|
||||
style.borderStyle = 'solid'
|
||||
style.borderColor = this.borderColor
|
||||
style.borderWidth = '1rpx'
|
||||
}
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
return style
|
||||
},
|
||||
// 倒计时数字样式
|
||||
letterStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.fontSizeStyle || '30rpx'
|
||||
style.color = this.fontColorStyle || '#080808'
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d: '00',
|
||||
h: '00',
|
||||
m: '00',
|
||||
s: '00',
|
||||
// 定时器
|
||||
timer: null,
|
||||
// 记录倒计过程中变化的秒数
|
||||
seconds: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听时间戳变化
|
||||
timestamp(value) {
|
||||
this.clearTimer()
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果时自动倒计时,加载完成开始计时
|
||||
this.autoplay && this.timestamp && this.start()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
methods: {
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 避免可能出现的倒计时重叠情况
|
||||
this.clearTimer()
|
||||
if (this.timestamp <= 0) return
|
||||
this.seconds = Number(this.timestamp)
|
||||
this.formatTime(this.seconds)
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--
|
||||
// 发出change事件
|
||||
this.$emit('change', this.seconds)
|
||||
if (this.seconds < 0) {
|
||||
return this.end()
|
||||
}
|
||||
this.formatTime(this.seconds)
|
||||
}, 1000)
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(seconds) {
|
||||
// 小于等于0的话,结束倒计时
|
||||
seconds <= 0 && this.end()
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||||
day = Math.floor(seconds / (60 * 60 * 24))
|
||||
// 如果不显示天,则将天对应的小时计入到小时中
|
||||
// 先把当前的hour计算出来供分和秒使用
|
||||
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||||
let showHour = null
|
||||
if (this.showDays) {
|
||||
showHour = hour
|
||||
} else {
|
||||
// 将天数对应的小时加入到时中进行显示
|
||||
showHour = Math.floor(seconds / (60 * 60))
|
||||
}
|
||||
minute = Math.floor(seconds / 60) - (hour * 60) - (day * 24 * 60)
|
||||
second = Math.floor(seconds) - (minute * 60) - (hour * 60 * 60) - (day * 24 * 60 * 60)
|
||||
// 如果小于0在前面进行补0操作
|
||||
showHour = this.$t.number.formatNumberAddZero(showHour)
|
||||
minute = this.$t.number.formatNumberAddZero(minute)
|
||||
second = this.$t.number.formatNumberAddZero(second)
|
||||
day = this.$t.number.formatNumberAddZero(day)
|
||||
|
||||
this.d = day
|
||||
this.h = showHour
|
||||
this.m = minute
|
||||
this.s = second
|
||||
},
|
||||
// 倒计时结束
|
||||
end() {
|
||||
this.clearTimer()
|
||||
this.$emit('end')
|
||||
},
|
||||
// 清除倒计时
|
||||
clearTimer() {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
|
||||
&__item {
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rpx;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
|
||||
&__time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
171
tuniao-ui/components/tn-count-scroll/tn-count-scroll.vue
Normal file
171
tuniao-ui/components/tn-count-scroll/tn-count-scroll.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<view class="tn-count-scroll-class tn-count-scroll">
|
||||
<view
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
class="tn-count-scroll__box"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: heightPxValue + 'px'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-count-scroll__column"
|
||||
:style="{
|
||||
transform: `translate3d(0, -${keys[index] * heightPxValue}px, 0)`,
|
||||
transitionDuration: `${duration}s`
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-for="(value, value_index) in item"
|
||||
:key="value_index"
|
||||
class="tn-count-scroll__column__item"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
height: heightPxValue + 'px',
|
||||
lineHeight: heightPxValue + 'px',
|
||||
fontSize: fontSizeStyle || '32rpx',
|
||||
fontWeight: bold ? 'bold': 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ value }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-scroll',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 行高
|
||||
height: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 单个字的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 持续时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
},
|
||||
// 十分位分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位分割符
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
heightPxValue() {
|
||||
return uni.upx2px(this.height || 0)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 每列的数据
|
||||
columns: [],
|
||||
// 每列对应值所在的滚动位置
|
||||
keys: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.initColumn(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 为了达到一进入就有滚动效果,延迟执行初始化
|
||||
this.initColumn()
|
||||
setTimeout(() => {
|
||||
this.initColumn(this.value)
|
||||
}, 20)
|
||||
},
|
||||
methods: {
|
||||
// 初始化每一列的数据
|
||||
initColumn(val) {
|
||||
val = val + ''
|
||||
let digit = val.length,
|
||||
columnArray = [],
|
||||
rows = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
for (let i = 0; i < digit; i++) {
|
||||
if (val[i] === this.decimalSeparator || val[i] === this.thousandthsSeparator) {
|
||||
columnArray.push(val[i])
|
||||
} else {
|
||||
columnArray.push(rows)
|
||||
}
|
||||
}
|
||||
this.columns = columnArray
|
||||
this.roll(val)
|
||||
},
|
||||
// 滚动处理
|
||||
roll(value) {
|
||||
let valueArray = value.toString().split(''),
|
||||
lengths = this.columns.length,
|
||||
indexs = [];
|
||||
|
||||
while (valueArray.length) {
|
||||
let figure = valueArray.pop()
|
||||
if (figure === this.decimalSeparator || figure === this.thousandthsSeparator) {
|
||||
indexs.unshift(0)
|
||||
} else {
|
||||
indexs.unshift(Number(figure))
|
||||
}
|
||||
}
|
||||
while(indexs.length < lengths) {
|
||||
indexs.unshift(0)
|
||||
}
|
||||
this.keys = indexs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-scroll {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__column {
|
||||
transform: translate3d(0, 0, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0, 1);
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
231
tuniao-ui/components/tn-count-to/tn-count-to.vue
Normal file
231
tuniao-ui/components/tn-count-to/tn-count-to.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-count-num-class tn-count-num"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
fontSize: fontSizeStyle || '50rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ displayValue }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-to',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 开始的数值,默认为0
|
||||
startVal: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 结束目标数值
|
||||
endVal: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动到目标值的持续时间,单位为毫秒
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// 是否在即将结束的时候使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 显示的小数位数
|
||||
decimals: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 十进制的分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位的分隔符
|
||||
// 类似金额的分割(¥23,321.05中的",")
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
localDuration: this.duration,
|
||||
// 显示的数值
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
// 打印的数值
|
||||
printValue: null,
|
||||
// 是否暂停
|
||||
paused: false,
|
||||
// 开始时间戳
|
||||
startTime: null,
|
||||
// 停留时间戳
|
||||
remainingTime: null,
|
||||
// 当前时间戳
|
||||
timestamp: null,
|
||||
// 上一次的时间戳
|
||||
lastTime: 0,
|
||||
rAF: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
methods: {
|
||||
// 开始滚动
|
||||
start() {
|
||||
this.localStartVal = this.startVal
|
||||
this.startTime = null
|
||||
this.localDuration = this.duration
|
||||
this.paused = false
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重新开始
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume()
|
||||
this.paused = false
|
||||
} else {
|
||||
this.stop()
|
||||
this.paused = true
|
||||
}
|
||||
},
|
||||
// 停止
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 恢复
|
||||
resume() {
|
||||
this.startTime = null
|
||||
this.localDuration = this.remainingTime
|
||||
this.localStartVal = this.printValue
|
||||
this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null
|
||||
this.cnacelAnimationFrame(this.rAF)
|
||||
this.displayValue = this.formatNumber(this.startVal)
|
||||
},
|
||||
// 销毁组件
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 累加时间
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp
|
||||
this.timestamp = timestamp
|
||||
const progress = timestamp - this.startTime
|
||||
this.remainingTime = this.localDuration - progress
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||||
} {
|
||||
this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
|
||||
} else {
|
||||
this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue
|
||||
} else {
|
||||
this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue
|
||||
}
|
||||
|
||||
this.displayValue = this.formatNumber(this.printValue)
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
} else {
|
||||
this.$emit('end')
|
||||
}
|
||||
},
|
||||
// 缓动时间计算
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||
},
|
||||
// 请求帧动画
|
||||
requestAnimationFrame(cb) {
|
||||
const currentTime = new Date().getTime()
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime))
|
||||
const timerId = setTimeout(() => {
|
||||
cb && cb(currentTime + timeToCall)
|
||||
}, timeToCall)
|
||||
this.lastTime = currentTime + timeToCall
|
||||
return timerId
|
||||
},
|
||||
// 清除帧动画
|
||||
clearAnimationFrame(timerId) {
|
||||
clearTimeout(timerId)
|
||||
},
|
||||
// 格式化数值
|
||||
formatNumber(number) {
|
||||
const reg = /(\d+)(\d{3})/
|
||||
number = Number(number)
|
||||
number = number.toFixed(Number(this.decimals))
|
||||
number += ''
|
||||
const numberArray = number.split('.')
|
||||
let num1 = numberArray[0]
|
||||
const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : ''
|
||||
|
||||
if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) {
|
||||
while(reg.test(num1)) {
|
||||
num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2')
|
||||
}
|
||||
}
|
||||
return num1 + num2
|
||||
},
|
||||
// 判断是否为数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
328
tuniao-ui/components/tn-cropper/index.wxs
Normal file
328
tuniao-ui/components/tn-cropper/index.wxs
Normal file
@@ -0,0 +1,328 @@
|
||||
var cropper = {
|
||||
// 画布x轴起点
|
||||
cutX: 0,
|
||||
// 画布y轴起点
|
||||
cutY: 0,
|
||||
// 触摸点信息(手指与图片中心点的相对位置)
|
||||
touchRelactive: [{
|
||||
x: 0,
|
||||
y: 0
|
||||
}],
|
||||
// 双指触摸时斜边的长度
|
||||
hypotenuseLength:0,
|
||||
// 是否结束触摸
|
||||
touchEndFlag: false,
|
||||
// 画布宽高
|
||||
canvasWidth: 0,
|
||||
canvasHeight: 0,
|
||||
// 图片宽高
|
||||
imgWidth: 0,
|
||||
imgHeight: 0,
|
||||
// 图片缩放比例
|
||||
scale: 1,
|
||||
// 图片旋转角度
|
||||
angle: 0,
|
||||
// 图片上边距
|
||||
imgTop: 0,
|
||||
// 图片左边距
|
||||
imgLeft: 0,
|
||||
// 窗口宽高
|
||||
windowWidth: 0,
|
||||
windowHeight: 0,
|
||||
init: true
|
||||
}
|
||||
|
||||
function bool(str) {
|
||||
return str === 'true' || str === true
|
||||
}
|
||||
|
||||
function propsChange(prop, oldProp, ownerInstance, instance) {
|
||||
if (prop && prop !== 'null') {
|
||||
var params = prop.split(',')
|
||||
var type = +params[0]
|
||||
var dataset = instance.getDataset()
|
||||
if (cropper.init || type == 4) {
|
||||
cropper.canvasWidth = +dataset.width
|
||||
cropper.canvasHeight = +dataset.height
|
||||
cropper.imgTop = +dataset.windowheight / 2
|
||||
cropper.imgLeft = +dataset.windowwidth / 2
|
||||
cropper.imgWidth = +dataset.imgwidth
|
||||
cropper.imgHeight = +dataset.imgheight
|
||||
cropper.windowHeight = +dataset.windowheight
|
||||
cropper.windowWidth = +dataset.windowwidth
|
||||
cropper.init = false
|
||||
} else if (type == 2 || type == 3) {
|
||||
cropper.imgWidth = +dataset.imgwidth
|
||||
cropper.imgHeight = +dataset.imgheight
|
||||
}
|
||||
cropper.angle = +dataset.angle
|
||||
if (type == 3) {
|
||||
imgTransform(ownerInstance)
|
||||
}
|
||||
switch(type) {
|
||||
case 1:
|
||||
setCutCenter(ownerInstance)
|
||||
// 设置裁剪框大小
|
||||
computeCutSize(ownerInstance)
|
||||
// 检查裁剪框是否在范围内
|
||||
cutDetectionPosition(ownerInstance)
|
||||
break
|
||||
case 2:
|
||||
setCutCenter(ownerInstance)
|
||||
break
|
||||
case 3:
|
||||
imgMarginDetectionScale(ownerInstance)
|
||||
break
|
||||
case 4:
|
||||
imageReset(ownerInstance)
|
||||
break
|
||||
case 5:
|
||||
setCutCenter(ownerInstance)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function touchStart(event, ownerInstance) {
|
||||
var touch = event.touches || event.changedTouches
|
||||
cropper.touchEndFlag = false
|
||||
if (touch.length === 1) {
|
||||
cropper.touchRelactive[0] = {
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
}
|
||||
} else {
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX)
|
||||
var height = Math.abs(touch[0].pageY - touch[1].pageY)
|
||||
cropper.touchRelactive = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
},{
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}]
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
|
||||
}
|
||||
}
|
||||
|
||||
function touchMove(event, ownerInstance) {
|
||||
var touch = event.touches || event.changedTouches
|
||||
if (cropper.touchEndFlag) return
|
||||
moveDuring(ownerInstance)
|
||||
if (event.touches.length === 1) {
|
||||
var left = touch[0].pageX - cropper.touchRelactive[0].x,
|
||||
top = touch[0].pageY - cropper.touchRelactive[0].y;
|
||||
cropper.imgLeft = left
|
||||
cropper.imgTop = top
|
||||
imgTransform(ownerInstance)
|
||||
imgMarginDetectionPosition(ownerInstance)
|
||||
} else {
|
||||
var dataset = event.instance.getDataset()
|
||||
var minScale = +dataset.minscale
|
||||
var maxScale = +dataset.maxscale
|
||||
var width = Math.abs(touch[0].pageX - touch[1].pageX),
|
||||
height = Math.abs(touch[0].pageY - touch[1].pageY),
|
||||
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
|
||||
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
|
||||
current_deg = 0;
|
||||
scale = scale <= minScale ? minScale : scale
|
||||
scale = scale >= maxScale ? maxScale : scale
|
||||
cropper.scale = scale
|
||||
imgMarginDetectionScale(ownerInstance, true)
|
||||
var touchRelative = [{
|
||||
x: touch[0].pageX - cropper.imgLeft,
|
||||
y: touch[0].pageY - cropper.imgTop
|
||||
}, {
|
||||
x: touch[1].pageX - cropper.imgLeft,
|
||||
y: touch[1].pageY - cropper.imgTop
|
||||
}]
|
||||
cropper.touchRelactive = touchRelative
|
||||
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
|
||||
// 更新视图
|
||||
cropper.angle = cropper.angle + current_deg
|
||||
imgTransform(ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
function touchEnd(event, ownerInstance) {
|
||||
cropper.touchEndFlag = true
|
||||
moveStop(ownerInstance)
|
||||
updateData(ownerInstance)
|
||||
}
|
||||
|
||||
function moveDuring(ownerInstance) {
|
||||
if (!ownerInstance) return
|
||||
ownerInstance.callMethod('moveDuring')
|
||||
}
|
||||
|
||||
function moveStop(ownerInstance) {
|
||||
if (!ownerInstance) return
|
||||
ownerInstance.callMethod('moveStop')
|
||||
}
|
||||
|
||||
function setCutCenter(ownerInstance) {
|
||||
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5
|
||||
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5
|
||||
|
||||
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY
|
||||
cropper.cutY = cutY
|
||||
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX
|
||||
cropper.cutX = cutX
|
||||
cutDetectionPosition(ownerInstance)
|
||||
imgTransform(ownerInstance)
|
||||
updateData(ownerInstance)
|
||||
}
|
||||
|
||||
// 检测剪裁框位置是否在允许的范围内(屏幕内)
|
||||
function cutDetectionPosition(ownerInstance) {
|
||||
var windowHeight = cropper.windowHeight,
|
||||
windowWidth = cropper.windowWidth;
|
||||
|
||||
// 检测上边距是否在范围内
|
||||
var cutDetectionPositionTop = function() {
|
||||
if (cropper.cutY < 0) {
|
||||
cropper.cutY = 0
|
||||
}
|
||||
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
|
||||
cropper.cutY = windowHeight - cropper.canvasHeight
|
||||
}
|
||||
}
|
||||
|
||||
// 检测左边距是否在范围内
|
||||
var cutDetectionPositionLeft = function() {
|
||||
if (cropper.cutX < 0) {
|
||||
cropper.cutX = 0
|
||||
}
|
||||
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
|
||||
cropper.cutX = windowWidth - cropper.canvasWidth
|
||||
}
|
||||
}
|
||||
|
||||
// 裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认为居中)
|
||||
if (cropper.cutX === null && cropper.cutY === null) {
|
||||
var cutX = (windowWidth - cropper.canvasWidth) * 0.5,
|
||||
cutY = (windowHeight - cropper.canvasHeight) * 0.5;
|
||||
cropper.cutX = cutX
|
||||
cropper.cutY = cutY
|
||||
} else if (cropper.cutX !== null && cropper.cutX !== null) {
|
||||
cutDetectionPositionTop()
|
||||
cutDetectionPositionLeft()
|
||||
} else if (cropper.cutX !== null && cropper.cutY === null) {
|
||||
cutDetectionPositionLeft()
|
||||
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2
|
||||
} else if (cropper.cutX === null && cropper.cutY !== null) {
|
||||
cutDetectionPositionTop()
|
||||
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2
|
||||
}
|
||||
}
|
||||
|
||||
// 图片边缘检测-缩放
|
||||
function imgMarginDetectionScale(ownerInstance, delay) {
|
||||
var scale = cropper.scale,
|
||||
imgWidth = cropper.imgWidth,
|
||||
imgHeight = cropper.imgHeight;
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight
|
||||
imgHeight = cropper.imgWidth
|
||||
}
|
||||
if (imgWidth * scale < cropper.canvasWidth) {
|
||||
scale = cropper.canvasWidth / imgWidth
|
||||
}
|
||||
if (imgHeight * scale < cropper.canvasHeight) {
|
||||
scale = Math.max(scale, cropper.canvasHeight / imgHeight)
|
||||
}
|
||||
imgMarginDetectionPosition(ownerInstance, scale, delay)
|
||||
}
|
||||
|
||||
// 图片边缘检测-位置
|
||||
function imgMarginDetectionPosition(ownerInstance, scale, delay) {
|
||||
var left = cropper.imgLeft,
|
||||
top = cropper.imgTop,
|
||||
imgWidth = cropper.imgWidth,
|
||||
imgHeight = cropper.imgHeight;
|
||||
scale = scale || cropper.scale
|
||||
if ((cropper.angle / 90) % 2) {
|
||||
imgWidth = cropper.imgHeight
|
||||
imgHeight = cropper.imgWidth
|
||||
}
|
||||
|
||||
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2
|
||||
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2
|
||||
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2
|
||||
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2
|
||||
|
||||
cropper.imgLeft = left
|
||||
cropper.imgTop = top
|
||||
cropper.scale = scale
|
||||
if (!delay || delay === 'null') {
|
||||
imgTransform(ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 改变截取值大小
|
||||
function computeCutSize(ownerInstance) {
|
||||
if (cropper.canvasWidth > cropper.windowWidth) {
|
||||
cropper.canvasWidth = cropper.windowWidth
|
||||
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
|
||||
cropper.cutX = cropper.windowWidth - cropper.cutX
|
||||
}
|
||||
if (cropper.canvasHeight > cropper.windowHeight) {
|
||||
cropper.canvasHeight = cropper.windowHeight
|
||||
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
|
||||
cropper.cutY = cropper.windowHeight - cropper.cutY
|
||||
}
|
||||
}
|
||||
|
||||
// 图片动画
|
||||
function imgTransform(ownerInstance) {
|
||||
var image = ownerInstance.selectComponent('.tn-cropper__image')
|
||||
if (!image) return
|
||||
var x = cropper.imgLeft - cropper.imgWidth / 2,
|
||||
y = cropper.imgTop - cropper.imgHeight / 2;
|
||||
image.setStyle({
|
||||
'transform': 'translate3d('+ x + 'px,' + y + 'px,0) scale(' + cropper.scale +') rotate(' + cropper.angle + 'deg)'
|
||||
})
|
||||
}
|
||||
|
||||
// 图片重置
|
||||
function imageReset(ownerInstance) {
|
||||
cropper.scale = 1
|
||||
cropper.angle = 0
|
||||
imgTransform(ownerInstance)
|
||||
}
|
||||
|
||||
// 高度变化
|
||||
function canvasHeight(ownerInstance) {
|
||||
if (!ownerInstance) return
|
||||
computeCutSize(ownerInstance)
|
||||
}
|
||||
|
||||
// 宽度变化
|
||||
function canvasWidth(ownerInstance) {
|
||||
if (!ownerInstance) return
|
||||
computeCutSize(ownerInstance)
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
function updateData(ownerInstance) {
|
||||
if (!ownerInstance) return
|
||||
ownerInstance.callMethod('change', {
|
||||
cutX: cropper.cutX,
|
||||
cutY: cropper.cutY,
|
||||
imgWidth: cropper.imgWidth,
|
||||
imgHeight: cropper.imgHeight,
|
||||
scale: cropper.scale,
|
||||
angle: cropper.angle,
|
||||
imgTop: cropper.imgTop,
|
||||
imgLeft: cropper.imgLeft
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd,
|
||||
propsChange: propsChange
|
||||
}
|
||||
570
tuniao-ui/components/tn-cropper/tn-cropper.vue
Normal file
570
tuniao-ui/components/tn-cropper/tn-cropper.vue
Normal file
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<view class="tn-cropper-class tn-cropper" @touchmove.stop.prevent="stop">
|
||||
<image
|
||||
v-if="imageUrl"
|
||||
:src="imageUrl"
|
||||
class="tn-cropper__image"
|
||||
:style="{
|
||||
width: (imgWidth ? imgWidth : width) + 'px',
|
||||
height: (imgHeight ? imgHeight : height) + 'px',
|
||||
transitionDuration: (animation ? 0.3 : 0) + 's'
|
||||
}"
|
||||
mode="widthFix"
|
||||
:data-minScale="minScale"
|
||||
:data-maxScale="maxScale"
|
||||
@load="imageLoad"
|
||||
@error="imageLoad"
|
||||
@touchstart="wxs.touchStart"
|
||||
@touchmove="wxs.touchMove"
|
||||
@touchend="wxs.touchEnd"
|
||||
></image>
|
||||
|
||||
<view
|
||||
class="tn-cropper__wrapper"
|
||||
:style="{
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
borderRadius: isRound ? '50%' : '0'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-cropper__border"
|
||||
:style="{
|
||||
border: borderStyle,
|
||||
borderRadius: isRound ? '50%' : '0',
|
||||
}"
|
||||
:prop="props"
|
||||
:change:prop="wxs.propsChange"
|
||||
:data-width="width"
|
||||
:data-height="height"
|
||||
:data-windowHeight="systemInfo.windowHeight || 600"
|
||||
:data-windowWidth="systemInfo.windowWidth || 400"
|
||||
:data-imgTop="imgTop"
|
||||
:data-imgWidth="imgWidth"
|
||||
:data-imgHeight="imgHeight"
|
||||
:data-angle="angle"
|
||||
></view>
|
||||
</view>
|
||||
|
||||
<canvas
|
||||
class="tn-cropper__canvas"
|
||||
:style="{
|
||||
width: width * scaleRatio + 'px',
|
||||
height: height * scaleRatio + 'px'
|
||||
}"
|
||||
:canvas-id="CANVAS_ID"
|
||||
:id="CANVAS_ID"
|
||||
:disable-scroll="true"
|
||||
></canvas>
|
||||
|
||||
<view
|
||||
v-if="!custom"
|
||||
class="tn-cropper__tabbar"
|
||||
>
|
||||
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__cancel" @tap.stop="back">取消</view>
|
||||
<view class="tn-cropper__tabbar__rotate" :class="[`tn-icon-${rotateIcon}`]" @tap.stop="setAngle"></view>
|
||||
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__confirm" @tap.stop="getCutImage">完成</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script src="./index.wxs" lang="wxs" module="wxs"></script>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-cropper',
|
||||
props: {
|
||||
// 图片路径
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 裁剪框高度 px
|
||||
height: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
// 裁剪框的宽度 px
|
||||
width: {
|
||||
type: Number,
|
||||
default: 280
|
||||
},
|
||||
// 是否为圆形裁剪框
|
||||
isRound: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 裁剪框边框样式
|
||||
borderStyle: {
|
||||
type: String,
|
||||
default: '1rpx solid #FFF'
|
||||
},
|
||||
// 生成的图片尺寸相对于裁剪框的比例
|
||||
scaleRatio: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 裁剪后的图片质量
|
||||
// 取值范围为:(0, 1]
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 0.8
|
||||
},
|
||||
// 是否返回base64(H5默认为base64)
|
||||
returnBase64: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图片旋转角度
|
||||
rotateAngle: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图片最小缩放比
|
||||
minScale: {
|
||||
type: Number,
|
||||
default: 0.5
|
||||
},
|
||||
// 图片最大缩放比
|
||||
maxScale: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 自定义操作栏(设置后会隐藏默认的底部操作栏)
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否在值发生改变的时候开始裁剪
|
||||
// custom为true时生效
|
||||
startCutting: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 裁剪时是否显示loading
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 旋转图片图标
|
||||
rotateIcon: {
|
||||
type: String,
|
||||
default: 'circle-arrow'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// canvas容器id
|
||||
CANVAS_ID: 'tn-cropper-canvas',
|
||||
// 移动裁剪超时时间定时器
|
||||
TIME_CUT_CENTER: null,
|
||||
// canvas容器
|
||||
ctx: null,
|
||||
// 画布x轴起点
|
||||
cutX: 0,
|
||||
// 画布y轴起点
|
||||
cutY: 0,
|
||||
// 图片宽度
|
||||
imgWidth: 0,
|
||||
// 图片高度
|
||||
imgHeight: 0,
|
||||
// 图片底部位置
|
||||
imgTop: 0,
|
||||
// 图片左边位置
|
||||
imgLeft: 0,
|
||||
// 图片缩放比
|
||||
scale: 1,
|
||||
// 图片旋转角度
|
||||
angle: 0,
|
||||
// 开启动画过渡效果
|
||||
animation: false,
|
||||
// 动画定时器
|
||||
animationTime: null,
|
||||
// 系统信息
|
||||
systemInfo: {},
|
||||
// 传递的参数
|
||||
props: '',
|
||||
// 标记是否发生改变
|
||||
sizeChange: 0,
|
||||
angleChange: 0,
|
||||
resetChange: 0,
|
||||
centerChange: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imageUrl(val) {
|
||||
this.imageReset()
|
||||
this.showLoading()
|
||||
uni.getImageInfo({
|
||||
src: val,
|
||||
success: (res) => {
|
||||
// 计算图片尺寸
|
||||
this.imgComputeSize(res.width, res.height)
|
||||
this.angleChange++
|
||||
this.props = `3,${this.angleChange}`
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
this.imgComputeSize()
|
||||
this.angleChange++
|
||||
this.props = `3,${this.angleChange}`
|
||||
}
|
||||
})
|
||||
},
|
||||
rotateAngle(val) {
|
||||
this.animation = true
|
||||
this.angle = val
|
||||
this.angleChanged(val)
|
||||
},
|
||||
animation(val) {
|
||||
clearTimeout(this.animationTime)
|
||||
if (val) {
|
||||
this.animationTime = setTimeout(() => {
|
||||
this.animation = false
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
startCutting(val) {
|
||||
if (this.custom && val) {
|
||||
this.getCutImage()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.systemInfo = uni.getSystemInfoSync()
|
||||
this.imgTop = this.systemInfo.windowHeight / 2
|
||||
this.imgLeft = this.systemInfo.windowWidth / 2
|
||||
this.ctx = uni.createCanvasContext(this.CANVAS_ID, this)
|
||||
// 初始化
|
||||
this.$nextTick(() => {
|
||||
this.props = '1,1'
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.$emit('ready', {})
|
||||
}, 200)
|
||||
},
|
||||
methods: {
|
||||
// 将网络图片转换为本地图片【同步执行】
|
||||
async getLocalImage(url) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: (res) => {
|
||||
resolve(res.tempFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 返回裁剪后的图片信息
|
||||
getCutImage() {
|
||||
if (!this.imageUrl) {
|
||||
uni.showToast({
|
||||
title: '请选择图片',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.loading && this.showLoading()
|
||||
const draw = async () => {
|
||||
// 图片实际大小
|
||||
let imgWidth = this.imgWidth * this.scale * this.scaleRatio
|
||||
let imgHeight = this.imgHeight * this.scale * this.scaleRatio
|
||||
// canvas和图片的相对距离
|
||||
let xpos = this.imgLeft - this.cutX
|
||||
let ypos = this.imgTop - this.cutY
|
||||
|
||||
|
||||
let imgUrl = this.imageUrl
|
||||
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
if (~this.imageUrl.indexOf('https:')) {
|
||||
imgUrl = await this.getLocalImage(this.imageUrl)
|
||||
}
|
||||
// #endif
|
||||
// 旋转画布
|
||||
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio)
|
||||
// 如果时圆形则截取圆形
|
||||
if (this.isRound) {
|
||||
const r = this.width > this.height ? Math.floor(this.height / 2) : Math.floor(this.width / 2)
|
||||
let translateX = Math.floor(this.width / 2)
|
||||
let translateY = Math.floor(this.height / 2)
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(translateX - (xpos * this.scaleRatio), translateY - (ypos * this.scaleRatio), r, 0, (360 * Math.PI) / 180)
|
||||
this.ctx.closePath()
|
||||
this.ctx.stroke()
|
||||
this.ctx.clip()
|
||||
}
|
||||
|
||||
this.ctx.rotate((this.angle * Math.PI) / 180)
|
||||
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight)
|
||||
|
||||
// 清空后再继续绘制
|
||||
this.ctx.draw(false, () => {
|
||||
let params = {
|
||||
width: this.width * this.scaleRatio,
|
||||
height: Math.round(this.height * this.scaleRatio),
|
||||
destWidth: this.width * this.scaleRatio,
|
||||
destHeight: Math.round(this.height) * this.scaleRatio,
|
||||
fileType: 'png',
|
||||
quality: this.quality
|
||||
}
|
||||
let data = {
|
||||
url: '',
|
||||
base64: '',
|
||||
width: this.width * this.scaleRatio,
|
||||
height: this.height * this.scaleRatio
|
||||
}
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
if (this.returnBase64) {
|
||||
this.ctx.toDataURL(params).then((urlData) => {
|
||||
data.base64 = urlData
|
||||
this.loading && uni.hideLoading()
|
||||
this.$emit('cropper', data)
|
||||
})
|
||||
} else {
|
||||
this.ctx.toTempFilePath({
|
||||
...params,
|
||||
success: (res) => {
|
||||
data.url = res.apFilePath
|
||||
this.loading && uni.hideLoading()
|
||||
this.$emit('cropper', data)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
let base64Flag = this.returnBase64
|
||||
// #ifndef MP-ALIPAY
|
||||
// #ifdef MP-BAIDU || MP-TOUTIAO || H5
|
||||
base64Flag = false
|
||||
// #endif
|
||||
|
||||
if (base64Flag) {
|
||||
uni.canvasGetImageData({
|
||||
canvasId: this.CANVAS_ID,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.width * this.scaleRatio,
|
||||
height: Math.round(this.height * this.scaleRatio),
|
||||
success: (res) => {
|
||||
const arrayBuffer = new Uint8Array(res.data)
|
||||
const base64 = uni.arrayBufferToBase64(arrayBuffer)
|
||||
data.base64 = base64
|
||||
this.loading && uni.hideLoading()
|
||||
this.$emit('cropper', data)
|
||||
}
|
||||
}, this)
|
||||
} else {
|
||||
uni.canvasToTempFilePath({
|
||||
...params,
|
||||
canvasId: this.CANVAS_ID,
|
||||
success: (res) => {
|
||||
data.url = res.tempFilePath
|
||||
// #ifdef H5
|
||||
data.base64 = res.tempFilePath
|
||||
// #endif
|
||||
this.loading && uni.hideLoading()
|
||||
this.$emit('cropper', data)
|
||||
}
|
||||
}, this)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
draw()
|
||||
},
|
||||
// 修改图片后触发的函数
|
||||
change(e) {
|
||||
this.cutX = e.cutX || 0
|
||||
this.cutY = e.cutY || 0
|
||||
this.imgWidth = e.imgWidth || this.imgWidth
|
||||
this.imgHeight = e.imgHeight || this.imgHeight
|
||||
this.scale = e.scale || 1
|
||||
this.angle = e.angle || 0
|
||||
this.imgTop = e.imgTop || 0
|
||||
this.imgLeft = e.imgLeft || 0
|
||||
},
|
||||
// 重置图片
|
||||
imageReset() {
|
||||
this.scale = 1
|
||||
this.angle = 0
|
||||
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
|
||||
this.imgTop = systemInfo.windowHeight / 2
|
||||
this.imgLeft = systemInfo.windowWidth / 2
|
||||
this.resetChange++
|
||||
this.props = `4,${this.resetChange}`
|
||||
// 初始旋转角度
|
||||
this.$emit('initAngle', {})
|
||||
},
|
||||
// 图片的生成的尺寸
|
||||
imgComputeSize(width, height) {
|
||||
// 默认按图片的最小边 = 对应的裁剪框尺寸
|
||||
let imgWidth = width,
|
||||
imgHeight = height;
|
||||
if (imgWidth && imgHeight) {
|
||||
if (imgWidth / imgHeight > this.width / this.height) {
|
||||
imgHeight = this.height
|
||||
imgWidth = (width / height) * imgHeight
|
||||
} else {
|
||||
imgWidth = this.width
|
||||
imgHeight = (height / width) * imgWidth
|
||||
}
|
||||
} else {
|
||||
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
|
||||
imgWidth = systemInfo.windowWidth
|
||||
imgHeight = 0
|
||||
}
|
||||
this.imgWidth = imgWidth
|
||||
this.imgHeight = imgHeight
|
||||
this.sizeChange++
|
||||
this.props = `2,${this.sizeChange}`
|
||||
},
|
||||
// 图片加载完毕
|
||||
imageLoad(e) {
|
||||
this.imageReset()
|
||||
uni.hideLoading()
|
||||
this.$emit('imageLoad', {})
|
||||
},
|
||||
// 移动结束
|
||||
moveStop() {
|
||||
clearTimeout(this.TIME_CUT_CENTER)
|
||||
this.TIME_CUT_CENTER = setTimeout(() => {
|
||||
this.centerChange++
|
||||
this.props = `5,${this.centerChange}`
|
||||
}, 688)
|
||||
},
|
||||
// 移动中
|
||||
moveDuring() {
|
||||
clearTimeout(this.TIME_CUT_CENTER)
|
||||
},
|
||||
// 显示加载框
|
||||
showLoading() {
|
||||
uni.showLoading({
|
||||
title: '请稍等......',
|
||||
mask: true
|
||||
})
|
||||
},
|
||||
// 停止
|
||||
stop() {},
|
||||
// 取消/返回
|
||||
back() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
// 角度改变
|
||||
angleChanged(val) {
|
||||
this.moveStop()
|
||||
if (val % 90) {
|
||||
this.angle = Math.round(val / 90) * 90
|
||||
}
|
||||
this.angleChange++
|
||||
this.props = `3,${this.angleChange}`
|
||||
},
|
||||
// 设置角度
|
||||
setAngle() {
|
||||
this.animation = true
|
||||
this.angle = this.angle + 90
|
||||
this.angleChanged(this.angle)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-cropper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(-120deg, #F15BB5, #9A5CE5, #01BEFF, #00F5D4);
|
||||
// background: linear-gradient(-120deg, #9A5CE5, #01BEFF, #00F5D4, #43e97b);
|
||||
// background: linear-gradient(-120deg,#c471f5, #ec008c, #ff4e50,#f9d423);
|
||||
// background: linear-gradient(-120deg, #0976ea, #c471f5, #f956b6, #ea7e0a);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
border-style: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
&__canvas {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
left: -2000px;
|
||||
top: -2000px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 3000px solid rgba(0, 0, 0, 0.55);
|
||||
pointer-events: none;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
&__border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__tabbar {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
padding: 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #FFFFFF;
|
||||
font-size: 32rpx;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__rotate {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
font-size: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
288
tuniao-ui/components/tn-custom-swiper-item/index.wxs
Normal file
288
tuniao-ui/components/tn-custom-swiper-item/index.wxs
Normal file
@@ -0,0 +1,288 @@
|
||||
|
||||
function setTimeout(instance, cb, time) {
|
||||
if (time > 0) {
|
||||
var s = getDate().getTime()
|
||||
var fn = function () {
|
||||
if (getDate().getTime() - s > time) {
|
||||
cb && cb()
|
||||
} else
|
||||
instance.requestAnimationFrame(fn)
|
||||
}
|
||||
fn()
|
||||
}
|
||||
else
|
||||
cb && cb()
|
||||
}
|
||||
|
||||
// 判断触摸的移动方向
|
||||
function decideSwiperDirection(startTouches, currentTouches, vertical) {
|
||||
// 震动偏移容差
|
||||
var toleranceShake = 150
|
||||
// 移动容差
|
||||
var toleranceTranslate = 10
|
||||
|
||||
if (!vertical) {
|
||||
// 水平方向移动
|
||||
if (Math.abs(currentTouches.y - startTouches.y) <= toleranceShake) {
|
||||
// console.log(currentTouches.x, startTouches.x);
|
||||
if (Math.abs(currentTouches.x - startTouches.x) > toleranceTranslate) {
|
||||
if (currentTouches.x - startTouches.x > 0) {
|
||||
return 'right'
|
||||
} else if (currentTouches.x - startTouches.x < 0) {
|
||||
return 'left'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 垂直方向移动
|
||||
if (Math.abs(currentTouches.x - startTouches.x) <= toleranceShake) {
|
||||
// console.log(currentTouches.x, startTouches.x);
|
||||
if (Math.abs(currentTouches.y - startTouches.y) > toleranceTranslate) {
|
||||
if (currentTouches.y - startTouches.y > 0) {
|
||||
return 'down'
|
||||
} else if (currentTouches.y - startTouches.y < 0) {
|
||||
return 'up'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// swiperItem参数数据更新
|
||||
var itemDataObserver = function(newVal, oldVal, ownerInstance, instance) {
|
||||
if (!newVal || newVal === 'undefined') return
|
||||
var state = ownerInstance.getState()
|
||||
state.itemData = newVal
|
||||
}
|
||||
|
||||
// swiperIndex数据更新
|
||||
var currentIndexObserver = function(newVal, oldVal, ownerInstance, instance) {
|
||||
if ((!newVal && newVal != 0) || newVal === 'undefined') return
|
||||
var state = ownerInstance.getState()
|
||||
state.currentIndex = newVal
|
||||
}
|
||||
|
||||
// containerData数据更新
|
||||
var containerDataObserver = function(newVal, oldVal, ownerInstance, instance) {
|
||||
if (!newVal || newVal === 'undefined') return
|
||||
var state = ownerInstance.getState()
|
||||
state.containerData = newVal
|
||||
}
|
||||
|
||||
// 开始触摸
|
||||
var touchStart = function(event, ownerInstance) {
|
||||
console.log('touchStart');
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
var itemData = state.itemData
|
||||
var containerData = state.containerData
|
||||
|
||||
// 由于当前SwiperIndex初始为0,可能会导致swiperIndex数据没有更新
|
||||
if (!state.currentIndex || state.currentIndex === 'undefined') {
|
||||
state.currentIndex = 0
|
||||
}
|
||||
|
||||
if (!containerData || containerData.circular === 'undefined') {
|
||||
containerData.circular = false
|
||||
}
|
||||
state.containerData = containerData
|
||||
|
||||
// 如果当前切换动画还没执行结束,再次触摸会重新加载对应的swiperContainer的信息
|
||||
// console.log(containerData.animationFinish);
|
||||
if (!containerData.animationFinish) {
|
||||
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
|
||||
status: 'reload'
|
||||
})
|
||||
}
|
||||
|
||||
// 判断是否为为当前显示的SwiperItem
|
||||
if (itemData.index != state.currentIndex) return
|
||||
|
||||
var touches = event.changedTouches[0]
|
||||
if (!touches) return
|
||||
|
||||
// 标记滑动开始时间
|
||||
state.touchStartTime = getDate().getTime()
|
||||
|
||||
// 记录当前滑动开始的x,y坐标
|
||||
state.touchRelactive = {
|
||||
x: touches.pageX,
|
||||
y: touches.pageY
|
||||
}
|
||||
// 记录触摸id,用于处理多指的情况
|
||||
state.touchId = touches.identifier
|
||||
|
||||
// 标记开始触摸
|
||||
state.touching = true
|
||||
ownerInstance.callMethod('updateTouchingStatus', {
|
||||
status: true
|
||||
})
|
||||
}
|
||||
|
||||
// 正在移动
|
||||
var touchMove = function(event, ownerInstance) {
|
||||
console.log('touchMove');
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
var itemData = state.itemData
|
||||
var containerData = state.containerData
|
||||
|
||||
// 判断是否为为当前显示的SwiperItem
|
||||
if (itemData.index != state.currentIndex) return
|
||||
|
||||
// 判断是否开始触摸
|
||||
if (!state.touching) return
|
||||
|
||||
var touches = event.changedTouches[0]
|
||||
if (!touches) return
|
||||
// 判断是否为同一个触摸点
|
||||
if (state.touchId != touches.identifier) return
|
||||
|
||||
var currentTouchRelactive = {
|
||||
x: touches.pageX,
|
||||
y: touches.pageY
|
||||
}
|
||||
|
||||
// 计算相对位移比例
|
||||
if (containerData.vertical) {
|
||||
var touchDistance = currentTouchRelactive.y - state.touchRelactive.y
|
||||
var itemHeight = itemData.itemHeight
|
||||
var distanceRate = touchDistance / itemHeight
|
||||
// console.log(currentTouchRelactive.y, touchDistance, itemHeight, distanceRate);
|
||||
|
||||
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
|
||||
if (!containerData.circular &&
|
||||
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果超出了距离则不进行操作
|
||||
if((Math.abs(touchDistance) > (itemData.itemTop + itemData.itemHeight))) {
|
||||
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
|
||||
value: distanceRate < 0 ? -1 : 1
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var touchDistance = currentTouchRelactive.x - state.touchRelactive.x
|
||||
var itemWidth = itemData.itemWidth
|
||||
var distanceRate = touchDistance / itemWidth
|
||||
// console.log(currentTouchRelactive.x, touchDistance, itemWidth, distanceRate);
|
||||
|
||||
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
|
||||
if (!containerData.circular &&
|
||||
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果超出了距离则不进行操作
|
||||
if((Math.abs(touchDistance) > (itemData.itemLeft + itemData.itemWidth))) {
|
||||
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
|
||||
value: distanceRate < 0 ? -1 : 1
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
|
||||
value: distanceRate
|
||||
})
|
||||
}
|
||||
|
||||
// 移动结束
|
||||
var touchEnd = function(event, ownerInstance) {
|
||||
console.log('touchEnd');
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
var itemData = state.itemData
|
||||
var containerData = state.containerData
|
||||
|
||||
// 判断是否为为当前显示的SwiperItem
|
||||
if (itemData.index != state.currentIndex) return
|
||||
|
||||
// 判断是否开始触摸
|
||||
if (!state.touching) return
|
||||
|
||||
var touches = event.changedTouches[0]
|
||||
if (!touches) return
|
||||
// 判断是否为同一个触摸点
|
||||
if (state.touchId != touches.identifier) return
|
||||
|
||||
|
||||
var currentTime = getDate().getTime()
|
||||
var currentTouchRelactive = {
|
||||
x: touches.pageX,
|
||||
y: touches.pageY
|
||||
}
|
||||
|
||||
if (containerData.vertical) {
|
||||
// 判断触摸移动方向
|
||||
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, true)
|
||||
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
|
||||
if (containerData.circular ||
|
||||
!((state.currentIndex === 0 && direction === 'down') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'up'))
|
||||
) {
|
||||
// 判断触摸的时间和移动的距离是否超过了当前itemHeight的一半,如果是则执行切换操作
|
||||
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.y - state.touchRelactive.y));
|
||||
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.y - state.touchRelactive.y) < itemData.itemHeight / 2) {
|
||||
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
|
||||
status: 'reset'
|
||||
})
|
||||
} else {
|
||||
// console.log(direction, state.touchRelactive.y, currentTouchRelactive.y);
|
||||
|
||||
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
|
||||
direction: direction
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 判断触摸移动方向
|
||||
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, false)
|
||||
// 判断是否为衔接轮播,如果不是衔接轮播,如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
|
||||
if (containerData.circular ||
|
||||
!((state.currentIndex === 0 && direction === 'right') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'left'))
|
||||
) {
|
||||
// 判断触摸的时间和移动的距离是否超过了当前itemWidth的一半,如果是则执行切换操作
|
||||
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.x - state.touchRelactive.x));
|
||||
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.x - state.touchRelactive.x) < itemData.itemWidth / 2) {
|
||||
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
|
||||
status: 'reset'
|
||||
})
|
||||
} else {
|
||||
// console.log(direction, state.touchRelactive.x, currentTouchRelactive.x);
|
||||
|
||||
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
|
||||
direction: direction
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清除标记
|
||||
state.touchId = null
|
||||
state.touchRelactive = null
|
||||
state.touchStartTime = 0
|
||||
|
||||
|
||||
// 标记停止触摸
|
||||
state.touching = true
|
||||
ownerInstance.callMethod('updateTouchingStatus', {
|
||||
status: false
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
itemDataObserver: itemDataObserver,
|
||||
currentIndexObserver: currentIndexObserver,
|
||||
containerDataObserver: containerDataObserver,
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view
|
||||
class="tn-c-swiper-item"
|
||||
:style="[swiperStyle]"
|
||||
:itemData="itemData"
|
||||
:currentIndex="currentIndex"
|
||||
:containerData="containerData"
|
||||
:change:itemData="wxs.itemDataObserver"
|
||||
:change:currentIndex="wxs.currentIndexObserver"
|
||||
:change:containerData="wxs.containerDataObserver"
|
||||
@touchstart="wxs.touchStart"
|
||||
:catch:touchmove="touching?wxs.touchMove:''"
|
||||
:catch:touchend="touching?wxs.touchEnd:''"
|
||||
>
|
||||
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view
|
||||
class="tn-c-swiper-item"
|
||||
:style="[swiperStyle]"
|
||||
:itemData="itemData"
|
||||
:currentIndex="currentIndex"
|
||||
:containerData="containerData"
|
||||
:change:itemData="wxs.itemDataObserver"
|
||||
:change:currentIndex="wxs.currentIndexObserver"
|
||||
:change:containerData="wxs.containerDataObserver"
|
||||
@touchstart="wxs.touchStart"
|
||||
@touchmove="wxs.touchMove"
|
||||
@touchend="wxs.touchEnd"
|
||||
>
|
||||
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script src="./index.wxs" lang="wxs" module="wxs"></script>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-custom-swiper-item',
|
||||
props: {
|
||||
|
||||
},
|
||||
computed: {
|
||||
// swiperItem公共数据
|
||||
itemData() {
|
||||
return {
|
||||
index: this.index,
|
||||
itemWidth: this.itemWidth,
|
||||
itemHeight: this.itemHeight,
|
||||
itemTop: this.itemTop,
|
||||
itemLeft: this.itemLeft
|
||||
}
|
||||
},
|
||||
currentIndex() {
|
||||
return this.parentData.currentIndex
|
||||
},
|
||||
containerData() {
|
||||
return {
|
||||
duration: this.parentData.duration,
|
||||
animationFinish: this.parentData.swiperContainerAnimationFinish,
|
||||
circular: this.parentData.circular,
|
||||
swiperItemLength: this.swiperItemLength,
|
||||
vertical: this.parentData.vertical
|
||||
}
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
style.transform = `translate3d(${this.translateX}%, ${this.translateY}%, 0px)`
|
||||
return style
|
||||
},
|
||||
containerStyle() {
|
||||
let style = {}
|
||||
if (this.parentData.customSwiperStyle && Object.keys(this.parentData.customSwiperStyle).length > 0) {
|
||||
style = this.parentData.customSwiperStyle
|
||||
}
|
||||
if ((this.currentIndex === 0 && this.index === this.swiperItemLength - 1) || (this.index === this.currentIndex - 1) &&
|
||||
(this.parentData.prevSwiperStyle && Object.keys(this.parentData.prevSwiperStyle).length > 0)
|
||||
) {
|
||||
// 前一个swiperItem
|
||||
const copyStyle = JSON.parse(JSON.stringify(style))
|
||||
style = Object.assign(copyStyle, this.parentData.prevSwiperStyle)
|
||||
}
|
||||
if ((this.currentIndex === this.swiperItemLength - 1 && this.index === 0) || (this.index === this.currentIndex + 1) &&
|
||||
(this.parentData.nextSwiperStyle && Object.keys(this.parentData.nextSwiperStyle).length > 0)
|
||||
) {
|
||||
// 后一个swiperItem
|
||||
const copyStyle = JSON.parse(JSON.stringify(style))
|
||||
style = Object.assign(copyStyle, this.parentData.nextSwiperStyle)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件参数
|
||||
parentData: {
|
||||
duration: 500,
|
||||
currentIndex: 0,
|
||||
swiperContainerAnimationFinish: false,
|
||||
circular: false,
|
||||
vertical: false,
|
||||
prevSwiperStyle: {},
|
||||
customSwiperStyle: {},
|
||||
nextSwiperStyle: {}
|
||||
},
|
||||
// 标记当前是否正在触摸
|
||||
touching: true,
|
||||
// 当前swiperItem的偏移位置
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
// 当前swiperItem的宽高
|
||||
itemWidth: 0,
|
||||
itemHeight: 0,
|
||||
// 当前swiperItem的位置信息
|
||||
itemTop: 0,
|
||||
itemLeft: 0,
|
||||
// 当前swiperItem的状态 prev current next
|
||||
status: 'current',
|
||||
// 当前swiperItem的index序号
|
||||
index: 0,
|
||||
// swiperItem的的数量
|
||||
swiperItemLength: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.updateParentData()
|
||||
// 获取当前父组件children的数量作为当前swiperItem的序号
|
||||
this.index = this.parent.children.length
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initSwiperItem()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化swiperItem
|
||||
initSwiperItem() {
|
||||
this.getSwiperItemRect(() => {
|
||||
this.parent.updateAllSwiperItemStyle()
|
||||
this.parentData.swiperContainerAnimationFinish = true
|
||||
})
|
||||
},
|
||||
// 获取swiperItem的信息
|
||||
async getSwiperItemRect(callback) {
|
||||
const swiperItemRes = await this._tGetRect('.tn-c-swiper-item')
|
||||
if (!swiperItemRes.height || !swiperItemRes.width) {
|
||||
setTimeout(() => {
|
||||
this.getSwiperItemRect()
|
||||
}, 30)
|
||||
return
|
||||
}
|
||||
|
||||
this.itemWidth = swiperItemRes.width
|
||||
this.itemHeight = swiperItemRes.height
|
||||
this.itemTop = swiperItemRes.top
|
||||
this.itemLeft = swiperItemRes.left
|
||||
callback && callback()
|
||||
},
|
||||
// 更新swiperItem样式
|
||||
updateSwiperItemStyle(swiperItemLength, currentIndex = undefined) {
|
||||
currentIndex = currentIndex != undefined ? currentIndex : this.parentData.currentIndex
|
||||
this.swiperItemLength = swiperItemLength
|
||||
// 根据当前swiperItem的序号设置偏移位置
|
||||
// 判断当前swiperItem是否为第一个,如果是则将最后的swiperItem移动到当前的前一个位置(即最前面)
|
||||
if (currentIndex === 0 && this.index === swiperItemLength - 1) {
|
||||
if (this.parentData.vertical) {
|
||||
this.translateX = 0
|
||||
this.translateY = -100
|
||||
} else {
|
||||
this.translateX = -100
|
||||
this.translateY = 0
|
||||
}
|
||||
}
|
||||
// 判断当前swiperItem是否为最后一个,如果是则将最前的swiperItem移动到当前的后一个位置(即最后面)
|
||||
else if (currentIndex === swiperItemLength - 1 && this.index === 0) {
|
||||
if (this.parentData.vertical) {
|
||||
this.translateX = 0
|
||||
this.translateY = swiperItemLength * 100
|
||||
} else {
|
||||
this.translateX = swiperItemLength * 100
|
||||
this.translateY = 0
|
||||
}
|
||||
}
|
||||
// 正常情况
|
||||
else {
|
||||
if (this.parentData.vertical) {
|
||||
this.translateX = 0
|
||||
this.translateY = this.index * 100
|
||||
} else {
|
||||
this.translateX = this.index * 100
|
||||
this.translateY = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
// 更新父组件的偏移位置信息
|
||||
updateParentSwiperContainerStyle(e) {
|
||||
this.parent.updateSwiperContainerStyleWithValue(e.value)
|
||||
},
|
||||
// 根据方向更新父组件的偏移位置信息
|
||||
updateParentSwiperContainerStyleWithDirection(e) {
|
||||
this.parent.updateSwiperContainerStyleWithDirection(e.direction)
|
||||
},
|
||||
// 修改父组件的偏移位置的状态
|
||||
changeParentSwiperContainerStyleStatus(e) {
|
||||
// reset -> 重置 reload -> 重载
|
||||
this.parent.updateSwiperContainerStyleWithDirection(e.status)
|
||||
},
|
||||
// 更新父组件信息
|
||||
updateParentData() {
|
||||
this.getParentData('tn-custom-swiper')
|
||||
},
|
||||
// 更新触摸状态
|
||||
updateTouchingStatus(e) {
|
||||
this.touching = e.status
|
||||
if (e.status) {
|
||||
this.parent.stopAutoPlay()
|
||||
} else {
|
||||
this.parent.startAutoPlay()
|
||||
}
|
||||
},
|
||||
// 提取对应用户自定义样式
|
||||
extractCustomStyle(customStyle) {
|
||||
let data = {
|
||||
transform: {},
|
||||
style: {}
|
||||
}
|
||||
if (!customStyle) return data
|
||||
// 允许设置的transform参数
|
||||
const allowTransformProps = ['scale','scaleX','scaleY','scaleZ','rotate','rotateX','rotateY','rotateZ']
|
||||
for (let prop in customStyle) {
|
||||
if (prop.startsWith('transformProp')) {
|
||||
// transform里面的样式
|
||||
let transformProp = prop.substring('transformProp'.length)
|
||||
const index = allowTransformProps.findIndex((item) => {
|
||||
return item.toLowerCase() === transformProp.toLowerCase()
|
||||
})
|
||||
if (index !== -1) {
|
||||
transformProp = allowTransformProps[index]
|
||||
data.transform[transformProp] = customStyle[prop]
|
||||
}
|
||||
} else {
|
||||
// 普通样式
|
||||
data.style[prop] = customStyle[prop]
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-c-swiper-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
display: block;
|
||||
will-change: transform;
|
||||
cursor: none;
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
|
||||
.item__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
535
tuniao-ui/components/tn-custom-swiper/tn-custom-swiper.vue
Normal file
535
tuniao-ui/components/tn-custom-swiper/tn-custom-swiper.vue
Normal file
@@ -0,0 +1,535 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-c-swiper-class tn-c-swiper"
|
||||
>
|
||||
<!-- 轮播item容器-->
|
||||
<view class="tn-swiper__container" :style="[swiperContainerStyle]" :animation="containerAnimation">
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 轮播指示器-->
|
||||
<view v-if="indicator" class="tn-swiper__indicator" :class="[`tn-swiper__indicator--${vertical ? 'vertical' : 'horizontal'}`]" :style="[indicatorStyle]">
|
||||
<!-- 方形 -->
|
||||
<block v-if="indicatorType === 'rect'">
|
||||
<view
|
||||
v-for="(item, index) in children.length"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__rect"
|
||||
:class="[
|
||||
`tn-swiper__indicator__rect--${vertical ? 'vertical' : 'horizontal'}`,
|
||||
currentIndex === index ? `tn-swiper__indicator__rect--active tn-swiper__indicator__rect--active--${vertical ? 'vertical' : 'horizontal'}` : ''
|
||||
]"
|
||||
:style="[indicatorPointStyle(index)]"
|
||||
></view>
|
||||
</block>
|
||||
<!-- 点 -->
|
||||
<block v-if="indicatorType === 'dot'">
|
||||
<view
|
||||
v-for="(item, index) in children.length"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__dot"
|
||||
:class="[
|
||||
`tn-swiper__indicator__dot--${vertical ? 'vertical' : 'horizontal'}`,
|
||||
currentIndex === index ? `tn-swiper__indicator__dot--active tn-swiper__indicator__dot--active--${vertical ? 'vertical' : 'horizontal'}` : ''
|
||||
]"
|
||||
:style="[indicatorPointStyle(index)]"
|
||||
></view>
|
||||
</block>
|
||||
<!-- 圆角方形 -->
|
||||
<block v-if="indicatorType === 'round'">
|
||||
<view
|
||||
v-for="(item, index) in children.length"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__round"
|
||||
:class="[
|
||||
`tn-swiper__indicator__round--${vertical ? 'vertical' : 'horizontal'}`,
|
||||
currentIndex === index ? `tn-swiper__indicator__round--active tn-swiper__indicator__round--active--${vertical ? 'vertical' : 'horizontal'}` : ''
|
||||
]"
|
||||
:style="[indicatorPointStyle(index)]"
|
||||
></view>
|
||||
</block>
|
||||
<!-- 序号 -->
|
||||
<block v-if="indicatorType === 'number' && !vertical">
|
||||
<view class="tn-swiper__indicator__number">{{ currentIndex + 1 }}/{{ children.length }}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-custom-swiper',
|
||||
props: {
|
||||
// 当前所在的轮播位置
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自动切换
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 自动切换时间间隔
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
// 滑动动画时长
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
// 是否采用衔接滑动
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滑动方向为纵向
|
||||
vertical: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示指示点
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 指示点类型
|
||||
// rect -> 方形 round -> 圆角方形 dot -> 点 number -> 轮播图下标
|
||||
indicatorType: {
|
||||
type: String,
|
||||
default: 'dot'
|
||||
},
|
||||
// 指示点的位置
|
||||
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
|
||||
indicatorPosition: {
|
||||
type: String,
|
||||
default: 'bottomCenter'
|
||||
},
|
||||
// 指示点激活时颜色
|
||||
indicatorActiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 指示点未激活时颜色
|
||||
indicatorInactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 前一个轮播的自定义样式
|
||||
prevSwiperStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 当前轮播的自定义样式
|
||||
customSwiperStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 后一个轮播的自定义样式
|
||||
nextSwiperStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parentData() {
|
||||
return [
|
||||
this.duration,
|
||||
this.currentIndex,
|
||||
this.swiperContainerAnimationFinish,
|
||||
this.circular,
|
||||
this.vertical,
|
||||
this.prevSwiperStyle,
|
||||
this.customSwiperStyle,
|
||||
this.nextSwiperStyle
|
||||
]
|
||||
},
|
||||
indicatorStyle() {
|
||||
let style = {}
|
||||
if (this.vertical) {
|
||||
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
|
||||
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
|
||||
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
|
||||
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
|
||||
if (this.vertical) {
|
||||
style.right = '12rpx'
|
||||
style.left = 'auto'
|
||||
} else {
|
||||
style.top = '12rpx'
|
||||
style.bottom = 'auto'
|
||||
}
|
||||
} else {
|
||||
if (this.vertical) {
|
||||
style.right = 'auto'
|
||||
style.left = '12rpx'
|
||||
} else {
|
||||
style.top = 'auto'
|
||||
style.bottom = '12rpx'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
|
||||
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
|
||||
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
|
||||
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
|
||||
style.top = '12rpx'
|
||||
style.bottom = 'auto'
|
||||
} else {
|
||||
style.top = 'auto'
|
||||
style.bottom = '12rpx'
|
||||
}
|
||||
}
|
||||
return style
|
||||
},
|
||||
indicatorPointStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (index === this.currentIndex && this.indicatorActiveColor !== '') {
|
||||
style.backgroundColor = this.indicatorActiveColor
|
||||
} else if (this.indicatorInactiveColor !== '') {
|
||||
style.backgroundColor = this.indicatorInactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.forEach((item) => {
|
||||
// 判断子组件如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(item.updateParentData) === 'function' && item.updateParentData()
|
||||
})
|
||||
}
|
||||
},
|
||||
current(nVal, oVal) {
|
||||
if (this.currentIndex === nVal) return
|
||||
this.currentIndex = nVal > this.children.length ? this.children.length - 1 : nVal
|
||||
this.swiperContainerAnimationFinish = false
|
||||
// 设置动画过渡时间
|
||||
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
|
||||
this.updateSwiperContainerItem(oVal)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 清除动画定时器
|
||||
clearAnimationTimer: null,
|
||||
// 前后衔接执行定时器
|
||||
convergeTimer: null,
|
||||
// 自动轮播Timer
|
||||
autoPlayTimer: null,
|
||||
// 当前选中的轮播
|
||||
currentIndex: this.current,
|
||||
// swiperContainer样式
|
||||
swiperContainerStyle: {
|
||||
transform: 'translate3d(0px, 0px, 0px)',
|
||||
transitionDuration: '0ms'
|
||||
},
|
||||
// swiperContainer动画
|
||||
containerAnimation: {},
|
||||
// 滑动动画结束标记
|
||||
swiperContainerAnimationFinish: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
const index = this.currentIndex > this.children.length ? this.children.length - 1 : this.currentIndex
|
||||
this.updateSwiperContainerStyle(index)
|
||||
this.startAutoPlay()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 更新全部swiperItem的样式
|
||||
updateAllSwiperItemStyle() {
|
||||
this.children.forEach((item, index) => {
|
||||
typeof(item.updateSwiperItemStyle) === 'function' && item.updateSwiperItemStyle(this.children.length)
|
||||
})
|
||||
|
||||
},
|
||||
// 根据swiperIndex更新swiperItemContainer的样式
|
||||
updateSwiperContainerStyle(index) {
|
||||
if (this.vertical) {
|
||||
this.swiperContainerStyle.transform = `translate3d(0px, ${-index * 100}%, 0px)`
|
||||
} else {
|
||||
this.swiperContainerStyle.transform = `translate3d(${-index * 100}%, 0px, 0px)`
|
||||
}
|
||||
},
|
||||
// 根据传递的值更新swiperItemContainer的位置
|
||||
updateSwiperContainerStyleWithValue(value) {
|
||||
if (this.vertical) {
|
||||
this.swiperContainerStyle.transform = `translate3d(0px, ${(-this.currentIndex * 100) + value * 100}%, 0px)`
|
||||
} else {
|
||||
this.swiperContainerStyle.transform = `translate3d(${(-this.currentIndex * 100) + value * 100}%, 0px, 0px)`
|
||||
}
|
||||
},
|
||||
// 根据传递的方向更新swiperItemContainer的位置
|
||||
updateSwiperContainerStyleWithDirection(direction) {
|
||||
const oldCurrent = this.currentIndex
|
||||
const childrenLength = this.children.length
|
||||
const lastSwiperItemIndex = childrenLength - 1
|
||||
this.swiperContainerAnimationFinish = false
|
||||
|
||||
|
||||
// 向后切换一个SwiperItem
|
||||
if (direction === 'reset') {
|
||||
// 设置动画过渡时间
|
||||
this.swiperContainerStyle.transitionDuration = `${this.duration}ms`
|
||||
this.updateSwiperContainerStyle(this.currentIndex)
|
||||
this.clearAnimationTimer = setTimeout(() => {
|
||||
this.clearSwiperContainerAnimation()
|
||||
}, this.duration)
|
||||
} else if (direction === 'reload') {
|
||||
this.clearConvergeSwiperItemTimer()
|
||||
this.clearSwiperContainerAnimation()
|
||||
this.updateSwiperItemStyle(0)
|
||||
this.updateSwiperItemStyle(lastSwiperItemIndex)
|
||||
} else {
|
||||
if (direction === 'left' || direction === 'up') {
|
||||
if (oldCurrent === childrenLength - 1 && !this.circular) {
|
||||
this.clearSwiperContainerAnimation()
|
||||
this.clearConvergeSwiperItemTimer()
|
||||
return
|
||||
}
|
||||
this.currentIndex = oldCurrent + 1 >= childrenLength ? 0 : oldCurrent + 1
|
||||
} else if (direction === 'right' || direction === 'down') {
|
||||
if (oldCurrent === 0 && !this.circular) {
|
||||
this.clearSwiperContainerAnimation()
|
||||
this.clearConvergeSwiperItemTimer()
|
||||
return
|
||||
}
|
||||
this.currentIndex = oldCurrent - 1 < 0 ? childrenLength - 1 : oldCurrent - 1
|
||||
}
|
||||
// 设置动画过渡时间
|
||||
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
|
||||
// this.updateSwiperItemContainerRect(this.currentIndex)
|
||||
}
|
||||
|
||||
// console.log(direction, oldCurrent, this.currentIndex);
|
||||
this.updateSwiperContainerItem(oldCurrent)
|
||||
|
||||
// 切换轮播时触发事件
|
||||
this.$emit('change', {
|
||||
current: this.currentIndex
|
||||
})
|
||||
},
|
||||
// 设置自动轮播
|
||||
startAutoPlay() {
|
||||
if (this.autoplay && !this.autoPlayTimer && this.circular) {
|
||||
this.autoPlayTimer = setInterval(() => {
|
||||
this.updateSwiperContainerStyleWithDirection('left')
|
||||
}, this.interval)
|
||||
}
|
||||
},
|
||||
// 停止自动轮播
|
||||
stopAutoPlay() {
|
||||
if (this.autoPlayTimer) {
|
||||
clearInterval(this.autoPlayTimer)
|
||||
this.autoPlayTimer = null
|
||||
}
|
||||
},
|
||||
// 更新swiperContainer和swiperItem相关联信息
|
||||
updateSwiperContainerItem(oldCurrent) {
|
||||
const childrenLength = this.children.length
|
||||
const lastSwiperItemIndex = childrenLength - 1
|
||||
// 判断当前是否为头尾,如果是更新对应的头尾SwiperItem样式
|
||||
// 更新swiperItemContainer的样式
|
||||
if (oldCurrent === 0 && this.currentIndex === lastSwiperItemIndex) {
|
||||
// 先移动到最左边然后再去除动画偏移到正常的位置
|
||||
// this.swiperContainerStyle.transform = `translate3d(100%, 0px, 0px)`
|
||||
this.updateSwiperContainerStyle(-1)
|
||||
this.clearSwiperContainerAnimationTimer()
|
||||
this.clearAnimationTimer = setTimeout(() => {
|
||||
this.convergeSwiperItem()
|
||||
}, this.duration)
|
||||
} else if (oldCurrent === lastSwiperItemIndex && this.currentIndex === 0) {
|
||||
// 先移动到最右边然后再去除动画偏移到正常的位置
|
||||
// this.swiperContainerStyle.transform = `translate3d(${-childrenLength * 100}%, 0px, 0px)`
|
||||
this.updateSwiperContainerStyle(childrenLength)
|
||||
this.clearSwiperContainerAnimationTimer()
|
||||
this.clearAnimationTimer = setTimeout(() => {
|
||||
this.convergeSwiperItem()
|
||||
}, this.duration)
|
||||
} else {
|
||||
this.updateSwiperContainerStyle(this.currentIndex)
|
||||
this.updateSwiperItemStyle(0)
|
||||
this.updateSwiperItemStyle(lastSwiperItemIndex)
|
||||
this.clearAnimationTimer = setTimeout(() => {
|
||||
this.clearSwiperContainerAnimation()
|
||||
}, this.duration)
|
||||
}
|
||||
},
|
||||
// 更新对应swiperItem的信息
|
||||
updateSwiperItemStyle(index) {
|
||||
const childrenLength = this.children.length
|
||||
if (index < 0) index = 0
|
||||
if (index > childrenLength - 1) index = childrenLength - 1
|
||||
|
||||
typeof(this.children[index].updateSwiperItemStyle) === 'function' && this.children[index].updateSwiperItemStyle(childrenLength, this.currentIndex)
|
||||
},
|
||||
// 更新对应swiperItem的容器信息
|
||||
updateSwiperItemContainerRect(index) {
|
||||
const childrenLength = this.children.length
|
||||
if (index < 0) index = 0
|
||||
if (index > childrenLength - 1) index = childrenLength - 1
|
||||
|
||||
typeof(this.children[index].getSwiperItemRect) === 'function' && this.children[index].getSwiperItemRect()
|
||||
},
|
||||
// 执行前后衔接
|
||||
convergeSwiperItem() {
|
||||
const lastSwiperItemIndex = this.children.length - 1
|
||||
this.clearSwiperContainerAnimation()
|
||||
this.clearConvergeSwiperItemTimer()
|
||||
this.convergeTimer = setTimeout(() => {
|
||||
this.updateSwiperItemStyle(0)
|
||||
this.updateSwiperItemStyle(lastSwiperItemIndex)
|
||||
this.updateSwiperContainerStyle(this.currentIndex)
|
||||
this.clearConvergeSwiperItemTimer()
|
||||
}, 30)
|
||||
},
|
||||
// 停止/清除切换动画
|
||||
clearSwiperContainerAnimation() {
|
||||
this.swiperContainerStyle.transitionDuration = `0ms`
|
||||
this.swiperContainerAnimationFinish = true
|
||||
this.clearSwiperContainerAnimationTimer()
|
||||
},
|
||||
// 停止/清除执行前后衔接定时器
|
||||
clearConvergeSwiperItemTimer() {
|
||||
if (this.convergeTimer) {
|
||||
clearTimeout(this.convergeTimer)
|
||||
this.convergeTimer = null
|
||||
}
|
||||
},
|
||||
// 停止/清除切换动画定时器
|
||||
clearSwiperContainerAnimationTimer() {
|
||||
if (this.clearAnimationTimer) {
|
||||
clearTimeout(this.clearAnimationTimer)
|
||||
this.clearAnimationTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-c-swiper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.tn-swiper {
|
||||
&__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
will-change: transform;
|
||||
transition-property: all;
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
|
||||
&--horizontal {
|
||||
padding: 0 24rpx;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
&--vertical {
|
||||
padding: 24rpx 0;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__rect {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--horizontal {
|
||||
width: 26rpx;
|
||||
height: 8rpx;
|
||||
}
|
||||
&--vertical {
|
||||
width: 8rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--horizontal {
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
&--vertical {
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__round {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--horizontal {
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
&--vertical {
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
&--horizontal {
|
||||
width: 34rpx;
|
||||
}
|
||||
&--vertical {
|
||||
height: 34rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__number {
|
||||
padding: 6rpx 16rpx;
|
||||
line-height: 1;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 100rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
265
tuniao-ui/components/tn-drag/index.wxs
Normal file
265
tuniao-ui/components/tn-drag/index.wxs
Normal file
@@ -0,0 +1,265 @@
|
||||
// 判断是否出界
|
||||
var isOutRange = function(x1, y1, x2, y2, x3, y3) {
|
||||
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
|
||||
}
|
||||
var edit = false
|
||||
|
||||
function bool(str) {
|
||||
return str === 'true' || str === true
|
||||
}
|
||||
/**
|
||||
* 排序核心
|
||||
* @param {Object} startKey 开始时位置
|
||||
* @param {Object} endKey 结束时位置
|
||||
* @param {Object} instance wxs内的局部变量快照
|
||||
*/
|
||||
var sortCore = function(startKey, endKey, state) {
|
||||
var basedata = state.basedata
|
||||
var excludeFix = function(sortKey, type) {
|
||||
// fixed 元素位置不会变化, 这里直接用 sortKey 获取,更加便捷
|
||||
if (state.list[sortKey].fixed) {
|
||||
var _sortKey = type ? --sortKey : ++sortKey
|
||||
return excludeFix(sortKey, type)
|
||||
}
|
||||
return sortKey
|
||||
}
|
||||
|
||||
// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
|
||||
var endRealKey = -1
|
||||
state.list.forEach(function(item) {
|
||||
if (item.sortKey === endKey) endRealKey = item.realKey
|
||||
})
|
||||
|
||||
return state.list.map(function(item) {
|
||||
if (item.fixed) return item
|
||||
var sortKey = item.sortKey
|
||||
var realKey = item.realKey
|
||||
|
||||
if (startKey < endKey) {
|
||||
// 正序拖动
|
||||
if (sortKey > startKey && sortKey <= endKey) {
|
||||
--realKey
|
||||
sortKey = excludeFix(--sortKey, true)
|
||||
} else if (sortKey === startKey) {
|
||||
realKey = endRealKey
|
||||
sortKey = endKey
|
||||
}
|
||||
} else if (startKey > endKey) {
|
||||
// 倒序拖动
|
||||
if (sortKey >= endKey && sortKey < startKey) {
|
||||
++realKey
|
||||
sortKey = excludeFix(++sortKey, false)
|
||||
} else if (sortKey === startKey) {
|
||||
realKey = endRealKey
|
||||
sortKey = endKey
|
||||
}
|
||||
}
|
||||
|
||||
if (item.sortKey != sortKey) {
|
||||
item.translateX = (sortKey % basedata.columns) * 100 + '%'
|
||||
item.translateY = Math.floor(sortKey / basedata.columns) * 100 + '%'
|
||||
item.sortKey = sortKey
|
||||
item.realKey = realKey
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
var triggerCustomEvent = function(list, type, instance) {
|
||||
if (!instance) return
|
||||
var _list = [],
|
||||
listData = [];
|
||||
|
||||
list.forEach(function(item) {
|
||||
_list[item.sortKey] = item
|
||||
})
|
||||
_list.forEach(function(item) {
|
||||
listData.push(item.data)
|
||||
})
|
||||
|
||||
// 编译到小程序 funcName作为参数传递导致事件不执行
|
||||
switch(type) {
|
||||
case 'change':
|
||||
instance.callMethod('change', {data: listData})
|
||||
break
|
||||
case 'sortEnd':
|
||||
instance.callMethod('sortEnd', {data: listData})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var listObserver = function(newVal, oldVal, ownerInstance, instance) {
|
||||
var state = ownerInstance.getState()
|
||||
state.itemsInstance = ownerInstance.selectAllComponents('.tn-drag__item')
|
||||
|
||||
state.list = newVal || []
|
||||
|
||||
state.list.forEach(function(item, index) {
|
||||
var itemInstance = state.itemsInstance[index]
|
||||
if (item && itemInstance) {
|
||||
itemInstance.setStyle({
|
||||
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
|
||||
})
|
||||
if (item.fixed) itemInstance.addClass('tn-drag__fixed')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var baseDataObserver = function(newVal, oldVal, ownerInstance, instance) {
|
||||
var state = ownerInstance.getState()
|
||||
state.basedata = newVal
|
||||
}
|
||||
|
||||
var longPress = function(event, ownerInstance) {
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
|
||||
edit = bool(dataset.edit)
|
||||
if (!edit) return
|
||||
if (!state.basedata || state.basedata === 'undefined') {
|
||||
state.basedata = JSON.parse(dataset.basedata)
|
||||
}
|
||||
var basedata = state.basedata
|
||||
var touches = event.changedTouches[0]
|
||||
if (!touches) return
|
||||
|
||||
state.current = +dataset.index
|
||||
|
||||
// 初始项是固定项则返回
|
||||
var item = state.list[state.current]
|
||||
if (item && item.fixed) return
|
||||
|
||||
// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
|
||||
if (state.dragging) return
|
||||
|
||||
ownerInstance.callMethod("drag", {
|
||||
dragging: true
|
||||
})
|
||||
|
||||
// 计算X, Y轴初始位移,使item中心移动到点击处,单列的时候X轴初始不做位移
|
||||
state.translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
|
||||
state.translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
|
||||
state.touchId = touches.identifier
|
||||
|
||||
instance.setStyle({
|
||||
'transform': 'translate3d(' + state.translateX + 'px,' + state.translateY +'px, 0)'
|
||||
})
|
||||
state.itemsInstance.forEach(function(item, index) {
|
||||
item.removeClass("tn-drag__transition").removeClass("tn-drag__current")
|
||||
item.addClass(index === state.current ? "tn-drag__current" : "tn-drag__transition")
|
||||
})
|
||||
|
||||
ownerInstance.callMethod("vibrate")
|
||||
state.dragging = true
|
||||
}
|
||||
|
||||
var touchStart = function(event, ownerInstance) {
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
edit = bool(dataset.edit)
|
||||
}
|
||||
|
||||
var touchMove = function(event, ownerInstance) {
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
var basedata = state.basedata
|
||||
|
||||
if (!state.dragging || !edit) return
|
||||
var touches = event.changedTouches[0]
|
||||
if (!touches) return
|
||||
|
||||
// 如果不是同一个触发点则返回
|
||||
if (state.touchId !== touches.identifier) return
|
||||
|
||||
// 计算X,Y轴位移, 单列时候X轴初始不做位移
|
||||
var translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
|
||||
var translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
|
||||
|
||||
// 到顶到低自动滑动
|
||||
if (touches.clientY > basedata.windowHeight - basedata.itemHeight - basedata.realBottomSize) {
|
||||
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
|
||||
ownerInstance.callMethod('pageScroll', {
|
||||
scrollTop: touches.pageY + basedata.itemHeight - (basedata.windowHeight - basedata.realBottomSize)
|
||||
})
|
||||
} else if (touches.clientY < basedata.itemHeight + basedata.realTopSize) {
|
||||
// 当前触摸点pageY - item高度 - 顶部固定区域高
|
||||
ownerInstance.callMethod('pageScroll', {
|
||||
scrollTop: touches.pageY - basedata.itemHeight - basedata.realTopSize
|
||||
})
|
||||
}
|
||||
|
||||
// 设置当前激活元素的偏移量
|
||||
instance.setStyle({
|
||||
'transform': 'translate3d('+ translateX + 'px,' + translateY + 'px, 0)'
|
||||
})
|
||||
|
||||
var startKey = state.list[state.current].sortKey
|
||||
var currentX = Math.round(translateX / basedata.itemWidth)
|
||||
var currentY = Math.round(translateY / basedata.itemHeight)
|
||||
var endKey = currentX + basedata.columns * currentY
|
||||
|
||||
// 目标项时固定项则返回
|
||||
var item = state.list[endKey]
|
||||
if (item && item.fixed) return
|
||||
|
||||
// X轴或者Y轴超出范围则返回
|
||||
if (isOutRange(currentX, basedata.columns, currentY, basedata.rows, endKey, state.list.length)) return
|
||||
|
||||
// 防止拖拽过程中发生乱序问题
|
||||
if (startKey === endKey || startKey === state.preStartKey) return
|
||||
state.preStartKey = startKey
|
||||
|
||||
var list = sortCore(startKey, endKey, state)
|
||||
state.itemsInstance.forEach(function(itemInstance, index) {
|
||||
var item = list[index]
|
||||
if (index !== state.current) {
|
||||
itemInstance.setStyle({
|
||||
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// ownerInstance.callMethod('vibrate')
|
||||
ownerInstance.callMethod('listDataChange', {
|
||||
data: list
|
||||
})
|
||||
triggerCustomEvent(list, "change", ownerInstance)
|
||||
}
|
||||
|
||||
var touchEnd = function(event, ownerInstance) {
|
||||
var instance = event.instance
|
||||
var dataset = instance.getDataset()
|
||||
var state = ownerInstance.getState()
|
||||
var basedata = state.basedata
|
||||
|
||||
if (!state.dragging || !edit) return
|
||||
triggerCustomEvent(state.list, "sortEnd", ownerInstance)
|
||||
|
||||
instance.addClass('tn-drag__transition')
|
||||
instance.setStyle({
|
||||
'transform': 'translate3d('+ state.list[state.current].translateX + ',' + state.list[state.current].translateY + ', 0)'
|
||||
})
|
||||
state.itemsInstance.forEach(function(item, index) {
|
||||
item.removeClass('tn-drag__transition')
|
||||
})
|
||||
|
||||
state.preStartKey = -1
|
||||
state.dragging = false
|
||||
ownerInstance.callMethod('drag', {
|
||||
dragging: false
|
||||
})
|
||||
state.current = -1
|
||||
state.translateX = 0
|
||||
state.translateY = 0
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
longPress: longPress,
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd,
|
||||
baseDataObserver: baseDataObserver,
|
||||
listObserver: listObserver
|
||||
}
|
||||
278
tuniao-ui/components/tn-drag/tn-drag.vue
Normal file
278
tuniao-ui/components/tn-drag/tn-drag.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-drag-class tn-drag"
|
||||
:style="{
|
||||
height: wrapHeight + 'rpx'
|
||||
}"
|
||||
:list="listData"
|
||||
:basedata="baseData"
|
||||
:change:list="wxs.listObserver"
|
||||
:change:basedata="wxs.baseDataObserver"
|
||||
>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view
|
||||
v-for="(item, index) in listData"
|
||||
:key="item.id"
|
||||
class="tn-drag__item"
|
||||
:style="{
|
||||
width: 100 / columns + '%',
|
||||
height: itemHeight + 'rpx'
|
||||
}"
|
||||
:data-index="index"
|
||||
:data-basedata="baseData"
|
||||
:data-edit="edit"
|
||||
@longpress="wxs.longPress"
|
||||
@touchstart="wxs.touchStart"
|
||||
:catch:touchmove="dragging?wxs.touchMove:''"
|
||||
:catch:touchend="dragging?wxs.touchEnd:''"
|
||||
>
|
||||
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view
|
||||
v-for="(item, index) in listData"
|
||||
:key="item.id"
|
||||
class="tn-drag__item"
|
||||
:style="{
|
||||
width: 100 / columns + '%',
|
||||
height: itemHeight + 'rpx'
|
||||
}"
|
||||
@longpress="wxs.longPress"
|
||||
:data-index="index"
|
||||
:data-basedata="baseData"
|
||||
:data-edit="edit"
|
||||
@touchstart="wxs.touchStart"
|
||||
@touchmove="wxs.touchMove"
|
||||
@touchend="wxs.touchEnd"
|
||||
>
|
||||
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
<script src="./index.wxs" lang="wxs" module="wxs"></script>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-drag',
|
||||
props: {
|
||||
// 数据源
|
||||
// 如果属性中包含fixed,则标识当前数据不允许拖动
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否允许拖动编辑
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 列数
|
||||
columns: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
// item元素高度, 单位rpx
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 当前父元素滚动的高度
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapHeight() {
|
||||
return this.rows * this.itemHeight
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 未渲染前节点数据
|
||||
baseData: {},
|
||||
// 拖动后的数据
|
||||
dragData: [],
|
||||
// 行数
|
||||
rows: 0,
|
||||
// 渲染数据
|
||||
listData: [],
|
||||
// 标记是否正在拖动
|
||||
dragging: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(val) {
|
||||
this.listData = []
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
columns(val) {
|
||||
this.listData = []
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.dragging = true
|
||||
const initDragItem = item => {
|
||||
const obj = {
|
||||
...item
|
||||
}
|
||||
const fixed = obj?.fixed || false
|
||||
delete obj["fixed"]
|
||||
return {
|
||||
id: this.unique(),
|
||||
fixed,
|
||||
data: {
|
||||
...obj
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0
|
||||
const listData = (this.list || []).map((item, index) => {
|
||||
let listItem = initDragItem(item)
|
||||
// 真实排序
|
||||
listItem.realKey = i++
|
||||
// 整体排序
|
||||
listItem.sortKey = index
|
||||
listItem.translateX = `${(listItem.sortKey % this.columns) * 100}%`
|
||||
listItem.translateY = `${Math.floor(listItem.sortKey / this.columns) * 100}%`
|
||||
return listItem
|
||||
})
|
||||
this.rows = Math.ceil(listData.length / this.columns)
|
||||
this.listData = listData
|
||||
this.dragData = listData
|
||||
|
||||
if (listData.length === 0) return
|
||||
// console.log(listData);
|
||||
|
||||
// 初始化dom元素
|
||||
this.$nextTick(() => {
|
||||
this.initRect()
|
||||
})
|
||||
},
|
||||
// 初始化dom元素
|
||||
initRect() {
|
||||
const {
|
||||
windowWidth,
|
||||
windowHeight
|
||||
} = uni.getSystemInfoSync()
|
||||
|
||||
let baseData = {}
|
||||
baseData.windowHeight = windowHeight
|
||||
baseData.realTopSize = 0
|
||||
baseData.realBottomSize = 0
|
||||
baseData.columns = this.columns
|
||||
baseData.rows = this.rows
|
||||
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
query.select('.tn-drag').boundingClientRect()
|
||||
query.select('.tn-drag__item').boundingClientRect()
|
||||
query.exec(res => {
|
||||
if (!res) {
|
||||
setTimeout(() => {
|
||||
this.initRect()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
|
||||
baseData.itemWidth = res[1].width
|
||||
baseData.itemHeight = res[1].height
|
||||
baseData.left = res[0].left
|
||||
baseData.top = res[0].top + this.scrollTop
|
||||
this.dragging = false
|
||||
this.baseData = baseData
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
// 触发震动
|
||||
vibrate() {
|
||||
uni.vibrateShort()
|
||||
},
|
||||
// 滚动到指定的位置
|
||||
pageScroll(e) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: e.scrollTop,
|
||||
duration: 0
|
||||
})
|
||||
},
|
||||
// 修改拖动状态
|
||||
drag(e) {
|
||||
this.dragging = e.dragging
|
||||
},
|
||||
// 拖拽数据发生改变
|
||||
listDataChange(e) {
|
||||
this.dragData = e.data
|
||||
},
|
||||
// item被点击
|
||||
itemClick(index) {
|
||||
const item = this.dragData[index]
|
||||
this.$emit('click', {
|
||||
key: item.realKey,
|
||||
data: item.data
|
||||
})
|
||||
},
|
||||
|
||||
// 拖拽结束事件
|
||||
sortEnd(e) {
|
||||
this.$emit('end', {
|
||||
data: e.data
|
||||
})
|
||||
},
|
||||
// 排序发生改变事件
|
||||
change(e) {
|
||||
this.$emit('change', {
|
||||
data: e.data
|
||||
})
|
||||
},
|
||||
|
||||
// 生成元素唯一id
|
||||
unique(n = 6) {
|
||||
let id = ''
|
||||
for (let i = 0; i < n; i++) id += Math.floor(Math.random() * 10)
|
||||
return 'tn_' + new Date().getTime() + id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-drag {
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&__transition {
|
||||
transition: transform 0.25s !important;
|
||||
}
|
||||
|
||||
&__current {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
&__fixed {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
190
tuniao-ui/components/tn-empty/tn-empty.vue
Normal file
190
tuniao-ui/components/tn-empty/tn-empty.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<view v-if="show" class="tn-empty-class tn-empty" :style="[emptyStyle]">
|
||||
<view
|
||||
v-if="!isImage"
|
||||
class="tn-empty__icon"
|
||||
:class="[icon ? `tn-icon-${icon}` : `tn-icon-empty-${mode}`]"
|
||||
:style="[iconStyle]"
|
||||
></view>
|
||||
<image
|
||||
v-else
|
||||
class="tn-empty__image"
|
||||
:style="[imageStyle]"
|
||||
:src="icon"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
|
||||
<view
|
||||
class="tn-empty__text"
|
||||
:style="[textStyle]"
|
||||
>{{ text ? text : icons[mode]}}</view>
|
||||
<view v-if="$slots.default || $slots.$default" class="tn-empty__slot">
|
||||
<slot/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-empty',
|
||||
props: {
|
||||
// 显示空白页
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 内置icon的名称
|
||||
// 图片路径,建议使用绝对路径
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 预置图标类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'data'
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字颜色
|
||||
textColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
textSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小,单位rpx
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图片宽度(当图标为图片时生效),单位rpx
|
||||
imgWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图片高度(当图标为图片时生效),单位rpx
|
||||
imgHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自定义组件样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emptyStyle() {
|
||||
let style = {}
|
||||
Object.assign(style, this.customStyle)
|
||||
return style
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
if (this.iconSize) {
|
||||
style.fontSize = this.iconSize + 'rpx'
|
||||
}
|
||||
if (this.iconColor) {
|
||||
style.color = this.iconColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
imageStyle() {
|
||||
let style = {}
|
||||
if (this.imgWidth) {
|
||||
style.width = this.imgWidth + 'rpx'
|
||||
}
|
||||
if (this.imgHeight) {
|
||||
style.height = this.imgHeight + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
textStyle() {
|
||||
let style = {}
|
||||
if (this.textColor) {
|
||||
style.color = this.textColor
|
||||
}
|
||||
if (this.textSize) {
|
||||
style.fontSize = this.textSize + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
// 判断传递的icon是否为图片
|
||||
isImage() {
|
||||
return this.icon.indexOf('/') >= 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: {
|
||||
cart: '购物车为空',
|
||||
page: '页面不存在',
|
||||
search: '搜索结果为空',
|
||||
address: '地址为空',
|
||||
network: '网络不通',
|
||||
order: '订单为空',
|
||||
coupon: '优惠券为空',
|
||||
favor: '暂无收藏',
|
||||
permission: '无权限',
|
||||
history: '历史记录为空',
|
||||
message: '暂无消息',
|
||||
list: '列表为空',
|
||||
data: '暂无数据',
|
||||
comment: '暂无评论'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__icon {
|
||||
margin-top: 14rpx;
|
||||
color: #AAAAAA;
|
||||
font-size: 90rpx;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
color: #AAAAAA;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
&__slot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
523
tuniao-ui/components/tn-fab/tn-fab.vue
Normal file
523
tuniao-ui/components/tn-fab/tn-fab.vue
Normal file
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<view class="tn-fab-class tn-fab" @touchmove.stop.prevent>
|
||||
<view
|
||||
class="tn-fab__box"
|
||||
:class="{'tn-fab--right': left === 'auto'}"
|
||||
:style="{
|
||||
left: $t.string.getLengthUnitValue(fabLeft || left),
|
||||
right: $t.string.getLengthUnitValue(fabRight || right),
|
||||
bottom: $t.string.getLengthUnitValue(fabBottom || bottom),
|
||||
zIndex: elZIndex
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-if="visibleSync"
|
||||
class="tn-fab__btns"
|
||||
:class="[`tn-fab__btns__animation--${animationType}`,
|
||||
showFab ? `tn-fab__btns--visible--${animationType}` : ''
|
||||
]"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in btnList"
|
||||
:key="index"
|
||||
class="tn-fab__item"
|
||||
:class="[
|
||||
`tn-fab__item__animation--${animationType}`,
|
||||
{'tn-fab__item--left': right === 'auto'}
|
||||
]"
|
||||
:style="[fabItemStyle(index)]"
|
||||
@tap.stop="handleClick(index)"
|
||||
>
|
||||
<!-- 带图标或者图片时显示的文字信息 -->
|
||||
<view
|
||||
v-if="animationType !== 'around' && (item.imgUrl || item.icon)"
|
||||
:class="[left === 'auto' ? 'tn-fab__item__text--right' : 'tn-fab__item__text--left']"
|
||||
:style="{
|
||||
color: item.textColor || '#FFF',
|
||||
fontSize: $t.string.getLengthUnitValue(item.textSize || 28)
|
||||
}"
|
||||
>{{ item.text || '' }}</view>
|
||||
|
||||
<!-- 带图片或者图标时的图片、图标信息 -->
|
||||
<view
|
||||
class="tn-fab__item__btn"
|
||||
:class="[!item.bgColor ? backgroundColorClass : '']"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: $t.string.getLengthUnitValue(height),
|
||||
lineHeight: $t.string.getLengthUnitValue(height),
|
||||
backgroundColor: item.bgColor || backgroundColorStyle || '#01BEFF',
|
||||
borderRadius: $t.string.getLengthUnitValue(radius)
|
||||
}"
|
||||
>
|
||||
<!-- 无图片和无图标时只显示文字 -->
|
||||
<view
|
||||
v-if="!item.imgUrl && !item.icon"
|
||||
class="tn-fab__item__btn__title"
|
||||
:style="{
|
||||
color: item.textColor || '#fff',
|
||||
fontSize: $t.string.getLengthUnitValue(item.textSize || 28)
|
||||
}"
|
||||
>{{ item.text || '' }}</view>
|
||||
<!-- 图标 -->
|
||||
<view
|
||||
v-if="item.icon"
|
||||
class="tn-fab__item__btn__icon"
|
||||
:class="[`tn-icon-${item.icon}`]"
|
||||
:style="{
|
||||
color: item.iconColor || '#fff',
|
||||
fontSize: $t.string.getLengthUnitValue(item.iconSize || iconSize || 64)
|
||||
}"
|
||||
></view>
|
||||
<!-- 图片 -->
|
||||
<image
|
||||
v-else-if="!item.icon && item.imgUrl"
|
||||
class="tn-fab__item__btn__image"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(item.imgWidth || 64),
|
||||
height: $t.string.getLengthUnitValue(item.imgHeight || 64),
|
||||
}"
|
||||
:src="item.imgUrl"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-fab__item__btn tn-fab__item__btn--fab"
|
||||
:class="[backgroundColorClass, fontColorClass, {'tn-fab__item__btn--active': showFab}]"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: $t.string.getLengthUnitValue(height),
|
||||
backgroundColor: backgroundColorStyle || !backgroundColorClass ? '#01BEFF' : '',
|
||||
color: fontColorStyle || '#fff',
|
||||
borderRadius: $t.string.getLengthUnitValue(radius),
|
||||
zIndex: elZIndex - 1
|
||||
}"
|
||||
@tap.stop="fabClick"
|
||||
>
|
||||
<slot>
|
||||
<view class="tn-fab__item__btn__icon" :class="[`tn-icon-${icon}`]" :style="{fontSize: $t.string.getLengthUnitValue(iconSize || 64)}"></view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="visibleSync && showMask" class="tn-fab__mask" :class="{'tn-fab__mask--visible': showFab}" :style="{zIndex: elZIndex - 3}" @tap="clickMask()"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-fab',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 按钮列表
|
||||
// {
|
||||
// // 背景颜色
|
||||
// bgColor: '#fff',
|
||||
// // 图片地址
|
||||
// imgUrl: '',
|
||||
// // 图片宽度
|
||||
// imgWidth: 60,
|
||||
// // 图片高度
|
||||
// imgHeight: 60,
|
||||
// // 图标名称
|
||||
// icon: '',
|
||||
// // 图标尺寸
|
||||
// iconSize: 60,
|
||||
// // 图标颜色
|
||||
// iconColor: '#fff',
|
||||
// // 提示文字
|
||||
// text: '',
|
||||
// // 文字大小
|
||||
// textSize: 30,
|
||||
// // 字体颜色
|
||||
// textColor: '#fff'
|
||||
// }
|
||||
btnList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 自定义悬浮按钮内容
|
||||
customBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 悬浮按钮的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 88
|
||||
},
|
||||
// 悬浮按钮的高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 88
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: 64
|
||||
},
|
||||
// 图标名称
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'open'
|
||||
},
|
||||
// 按钮圆角
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: '50%'
|
||||
},
|
||||
// 按钮距离左边的位置
|
||||
left: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 按钮距离右边的位置
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 按钮距离底部的位置
|
||||
bottom: {
|
||||
type: [String, Number],
|
||||
default: 100
|
||||
},
|
||||
// 展示动画类型 up 往上展示 around 环绕
|
||||
animationType: {
|
||||
type: String,
|
||||
default: 'up'
|
||||
},
|
||||
// 当动画为圆环时,每个弹出按钮之间的距离, 单位px
|
||||
aroundBtnDistance: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示遮罩
|
||||
showMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击遮罩是否可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showFab: false,
|
||||
visibleSync: false,
|
||||
timer: null,
|
||||
fabLeft: 0,
|
||||
fabRight: 0,
|
||||
fabBottom: 0,
|
||||
fabBtnInfo: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
},
|
||||
systemInfo: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
updateProps: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex || this.$t.zIndex.fab
|
||||
},
|
||||
propsData() {
|
||||
return [this.width, this.height, this.left, this.right, this.bottom]
|
||||
},
|
||||
fabItemStyle() {
|
||||
return (index) => {
|
||||
let style = {
|
||||
zIndex: this.elZIndex - 2
|
||||
}
|
||||
if (this.animationType === 'up' || !this.showFab) {
|
||||
return style
|
||||
}
|
||||
let base = 1
|
||||
if (this.left === 'auto') {
|
||||
base = 1
|
||||
} else if (this.right === 'auto') {
|
||||
base = -1
|
||||
}
|
||||
style.transform = `rotate(${base * index * 60}deg) translateX(${(this.aroundBtnDistance + this.fabBtnInfo.width) * (-(base))}px)`
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
propsData() {
|
||||
// 更新按钮信息
|
||||
this.updateProps = true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.getFabBtnRectInfo()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击事件
|
||||
handleClick(index) {
|
||||
this.close()
|
||||
this.$emit('click', {index: index})
|
||||
},
|
||||
// 点击悬浮按钮
|
||||
fabClick() {
|
||||
if (this.showFab) {
|
||||
this.close()
|
||||
} else {
|
||||
// console.log(this.visibleSync);
|
||||
if (this.visibleSync) {
|
||||
this.visibleSync = false
|
||||
return
|
||||
}
|
||||
this.open()
|
||||
}
|
||||
},
|
||||
// 点击遮罩
|
||||
clickMask() {
|
||||
if (!this.showMask || !this.maskCloseable) return
|
||||
this.close()
|
||||
},
|
||||
|
||||
open() {
|
||||
this.change('visibleSync', 'showFab', true)
|
||||
this.translateFabPosition()
|
||||
},
|
||||
close() {
|
||||
this.change('showFab', 'visibleSync', false)
|
||||
this.fabLeft = 0
|
||||
this.fabRight = 0
|
||||
this.fabBottom = 0
|
||||
},
|
||||
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||||
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||||
change(param1, param2, status) {
|
||||
this[param1] = status
|
||||
if (status) {
|
||||
// #ifdef H5 || MP
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 10)
|
||||
// #endif
|
||||
// #ifndef H5 || MP
|
||||
this.$nextTick(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 250)
|
||||
}
|
||||
},
|
||||
|
||||
/******************** 旋转动画相关函数 ********************/
|
||||
// 获取按钮的信息
|
||||
async getFabBtnRectInfo() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const btnRectInfo = await this._tGetRect('.tn-fab__item__btn--fab')
|
||||
if (!btnRectInfo) {
|
||||
setTimeout(() => {
|
||||
this.getFabBtnRectInfo()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
console.log(btnRectInfo);
|
||||
this.systemInfo = {
|
||||
width: systemInfo.windowWidth,
|
||||
height: systemInfo.windowHeight
|
||||
}
|
||||
this.fabBtnInfo.width = btnRectInfo.width
|
||||
this.fabBtnInfo.height = btnRectInfo.height
|
||||
this.fabBtnInfo.left = btnRectInfo.left
|
||||
this.fabBtnInfo.right = btnRectInfo.right
|
||||
this.fabBtnInfo.bottom = btnRectInfo.bottom
|
||||
},
|
||||
// 更新悬浮按钮的位置
|
||||
translateFabPosition() {
|
||||
if (this.updateProps) {
|
||||
this.getFabBtnRectInfo()
|
||||
this.updateProps = false
|
||||
}
|
||||
if (this.animationType === 'up') return
|
||||
// 按钮组的宽度
|
||||
const btnGroupWidth = this.fabBtnInfo.width + this.aroundBtnDistance + 10
|
||||
// 判断当前按钮是在左边还是右边
|
||||
if (this.left === 'auto' && btnGroupWidth > this.systemInfo.width - this.fabBtnInfo.right) {
|
||||
// 距离不够需要移动
|
||||
this.fabRight = btnGroupWidth + 'px'
|
||||
} else if (this.right === 'auto' && btnGroupWidth > this.fabBtnInfo.left) {
|
||||
this.fabLeft = btnGroupWidth + 'px'
|
||||
}
|
||||
|
||||
if (btnGroupWidth > this.systemInfo.height - this.fabBtnInfo.bottom) {
|
||||
this.fabBottom = btnGroupWidth + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-fab {
|
||||
&__box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
transition: all 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
&--right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
&__btns {
|
||||
transition: all 0.25s cubic-bezier(0,.13,0,1.43);
|
||||
transform-origin: 80% bottom;
|
||||
|
||||
&__animation--up {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
&__animation--around {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&--visible--up {
|
||||
// visibility: visible;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
&--visible--around {
|
||||
// visibility: visible;
|
||||
// opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
&__animation--around {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: transform 0.25s ease-in-out;
|
||||
transform-origin: 50% 50%;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&--left {
|
||||
flex-flow: row-reverse;
|
||||
}
|
||||
|
||||
&__text {
|
||||
&--left {
|
||||
padding-left: 14rpx;
|
||||
}
|
||||
&--right {
|
||||
padding-right: 14rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 5rpx 2rpx rgba(0, 0, 0, 0.07);
|
||||
transition: all 0.2s linear;
|
||||
|
||||
&--active {
|
||||
animation-name: fab-button-animation;
|
||||
animation-duration: 0.2s;
|
||||
animation-timing-function: cubic-bezier(0,.13,0,1.43);
|
||||
}
|
||||
|
||||
&__title {
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
text-align: center;
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
&__image {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: $tn-mask-bg-color;
|
||||
transition: all 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
&--visible {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fab-button-animation {
|
||||
0% {
|
||||
transform: scale(0.6);
|
||||
}
|
||||
// 20% {
|
||||
// transform: scale(1.8);
|
||||
// }
|
||||
// 40% {
|
||||
// transform: scale(0.4);
|
||||
// }
|
||||
// 50% {
|
||||
// transform: scale(1.4);
|
||||
// }
|
||||
// 80% {
|
||||
// transform: scale(0.8);
|
||||
// }
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
457
tuniao-ui/components/tn-form-item/tn-form-item.vue
Normal file
457
tuniao-ui/components/tn-form-item/tn-form-item.vue
Normal file
@@ -0,0 +1,457 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-form-item-class tn-form-item"
|
||||
:class="{
|
||||
'tn-border-solid-bottom': elBorderBottom,
|
||||
'tn-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-form-item__body"
|
||||
:style="{
|
||||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
|
||||
}"
|
||||
>
|
||||
<!-- 处理微信小程序中设置属性的问题,不设置值的时候会变成true -->
|
||||
<view
|
||||
class="tn-form-item--left"
|
||||
:style="{
|
||||
width: wLabelWidth,
|
||||
flex: `0 0 ${wLabelWidth}`,
|
||||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx'
|
||||
}"
|
||||
>
|
||||
<!-- 块对齐 -->
|
||||
<view v-if="required || leftIcon || label" class="tn-form-item--left__content"
|
||||
:style="[leftContentStyle]"
|
||||
>
|
||||
<!-- nvue不支持伪元素before -->
|
||||
<view v-if="leftIcon" class="tn-form-item--left__content__icon">
|
||||
<view :class="[`tn-icon-${leftIcon}`]" :style="leftIconStyle"></view>
|
||||
</view>
|
||||
<!-- <view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle, {
|
||||
'justify-content': elLabelAlign === 'left' ? 'flex-satrt' : elLabelAlign === 'center' ? 'center' : 'flex-end'
|
||||
}]"
|
||||
>
|
||||
{{label}}
|
||||
</view> -->
|
||||
<view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle]"
|
||||
>
|
||||
{{label}}
|
||||
</view>
|
||||
<text v-if="required" class="tn-form-item--left__content--required">*</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-form-item--right tn-flex">
|
||||
<view class="tn-form-item--right__content">
|
||||
<view class="tn-form-item--right__content__slot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view v-if="$slots.right || rightIcon" class="tn-form-item--right__content__icon tn-flex">
|
||||
<view v-if="rightIcon" :class="[`tn-icon-${rightIcon}`]" :style="rightIconStyle"></view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="validateState === 'error' && showError('message')"
|
||||
class="tn-form-item__message"
|
||||
:style="{
|
||||
paddingLeft: elLabelPosition === 'left' ? elLabelWidth + 'rpx' : '0'
|
||||
}"
|
||||
>
|
||||
{{validateMessage}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
import schema from '../../libs/utils/async-validator.js'
|
||||
// 去除警告信息
|
||||
schema.warning = function() {}
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-form-item',
|
||||
inject: {
|
||||
tnForm: {
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
// label提示语
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 绑定的值
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// top - 上对齐
|
||||
// right - 右对齐
|
||||
// bottom - 下对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标样式
|
||||
leftIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 右侧图标样式
|
||||
rightIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示必填项的*,不做校验用途
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 处理微信小程序label的宽度
|
||||
wLabelWidth() {
|
||||
// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
|
||||
return this.elLabelPosition === 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.elLabelWidth + 'rpx') : '100%'
|
||||
},
|
||||
// 是否显示错误提示
|
||||
showError() {
|
||||
return type => {
|
||||
if (this.errorType.indexOf('none') >= 0) return false
|
||||
else if (this.errorType.indexOf(type) >= 0) return true
|
||||
else return false
|
||||
}
|
||||
},
|
||||
// label的宽度(默认值为90)
|
||||
elLabelWidth() {
|
||||
return this.labelWidth != 0 ? this.labelWidth : (this.parentData.labelWidth != 0 ? this.parentData.labelWidth : 90)
|
||||
},
|
||||
// label的样式
|
||||
elLabelStyle() {
|
||||
return Object.keys(this.labelStyle).length ? this.labelStyle : (Object.keys(this.parentData.labelStyle).length ? this.parentData.labelStyle : {})
|
||||
},
|
||||
// label显示位置
|
||||
elLabelPosition() {
|
||||
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : 'left')
|
||||
},
|
||||
// label对齐方式
|
||||
elLabelAlign() {
|
||||
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left')
|
||||
},
|
||||
// label下划线
|
||||
elBorderBottom() {
|
||||
return this.borderBottom !== '' ? this.borderBottom : (this.parentData.borderBottom !== '' ? this.parentData.borderBottom : true)
|
||||
},
|
||||
leftContentStyle() {
|
||||
let style = {}
|
||||
if (this.elLabelPosition === 'left') {
|
||||
switch(this.elLabelAlign) {
|
||||
case 'left':
|
||||
style.justifyContent = 'flex-start'
|
||||
break
|
||||
case 'center':
|
||||
style.justifyContent = 'center'
|
||||
break
|
||||
default:
|
||||
style.justifyContent = 'flex-end'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
initialValue: '',
|
||||
// 是否校验成功
|
||||
validateState: '',
|
||||
// 校验失败提示信息
|
||||
validateMessage: '',
|
||||
// 错误的提示方式(参考form组件)
|
||||
errorType: ['message'],
|
||||
// 当前子组件输入的值
|
||||
fieldValue: '',
|
||||
// 父组件的参数
|
||||
// 由于再computed中无法得知this.parent的变化,所以放在data中
|
||||
parentData: {
|
||||
borderBottom: true,
|
||||
labelWidth: 90,
|
||||
labelPosition: 'left',
|
||||
labelAlign: 'left',
|
||||
labelStyle: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
validateState(val) {
|
||||
this.broadcastInputError()
|
||||
},
|
||||
"tnForm.errorType"(val) {
|
||||
this.errorType = val
|
||||
this.broadcastInputError()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件创建完成后,保存当前实例到form组件中
|
||||
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用\
|
||||
this.parent = this.$t.$parent.call(this, 'tn-form')
|
||||
if (this.parent) {
|
||||
// 遍历parentData属性,将parent中同名的属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key]
|
||||
})
|
||||
// 如果没有传入prop或者tnForm为空(单独使用form-item组件的时候),就不进行校验
|
||||
if (this.prop) {
|
||||
// 将本实例添加到父组件中
|
||||
this.parent.fields.push(this)
|
||||
this.errorType = this.parent.errorType
|
||||
// 设置初始值
|
||||
this.initialValue = this.fieldValue
|
||||
// 添加表单校验,这里必须要写在$nextTick中,因为tn-form的rules是通过ref手动传入的
|
||||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给tn-form,导致规则为空
|
||||
this.$nextTick(() => {
|
||||
this.setRules()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前,将实例从tn-form的缓存中移除
|
||||
// 如果当前没有prop的话表示当前不进行删除
|
||||
if (this.parent && this.prop) {
|
||||
this.parent.fields.map((item, index) => {
|
||||
if (item === this) this.parent.fields.splice(index, 1)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 向input组件发出错误事件
|
||||
broadcastInputError() {
|
||||
this.broadcast('tn-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'))
|
||||
},
|
||||
// 设置校验规则
|
||||
setRules() {
|
||||
let that = this
|
||||
// 从父组件tn-form拿到当前tn-form-item需要验证 的规则
|
||||
// let rules = this.getRules()
|
||||
// if (rules.length) {
|
||||
// this.isRequired = rules.some(rule => {
|
||||
// // 如果有必填项,就返回,没有的话,就是undefined
|
||||
// return rule.required
|
||||
// })
|
||||
// }
|
||||
|
||||
// blur事件
|
||||
this.$on('on-form-blur', that.onFieldBlur)
|
||||
// change事件
|
||||
this.$on('on-form-change', that.onFieldChange)
|
||||
},
|
||||
// 从form的rules属性中取出当前form-item的校验规则
|
||||
getRules() {
|
||||
let rules = this.parent.rules
|
||||
rules = rules ? rules[this.prop] : []
|
||||
|
||||
// 返回数值形式的值
|
||||
return [].concat(rules || [])
|
||||
},
|
||||
// blur事件时进行表单认证
|
||||
onFieldBlur() {
|
||||
this.validation('blur')
|
||||
},
|
||||
// change事件时进行表单认证
|
||||
onFieldChange() {
|
||||
this.validation('change')
|
||||
},
|
||||
// 过滤出符合要求的rule规则
|
||||
getFilterRule(triggerType = '') {
|
||||
let rules = this.getRules()
|
||||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
|
||||
if (!triggerType) return rules
|
||||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
|
||||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
|
||||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
|
||||
return rules.filter(rule => rule.trigger && rule.trigger.indexOf(triggerType) !== -1)
|
||||
},
|
||||
// 校验数据
|
||||
validation(trigger, callback = ()=>{}) {
|
||||
// 校验之前先获取需要校验的值
|
||||
this.fieldValue = this.parent.model[this.prop]
|
||||
// blur和change是否有当前方式的校验规则
|
||||
let rules = this.getFilterRule(trigger)
|
||||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件tn-form会因为
|
||||
// 对count变量的统计错误而无法进入上一层的回调
|
||||
if (!rules || rules.length === 0) {
|
||||
return callback('')
|
||||
}
|
||||
// 设置当前为校验中
|
||||
this.validateState = 'validating'
|
||||
// 调用async-validator的方法
|
||||
let validator = new schema({
|
||||
[this.prop]: rules
|
||||
})
|
||||
validator.validate({
|
||||
[this.prop]: this.fieldValue
|
||||
}, {
|
||||
firstFields: true
|
||||
}, (errors, fields) => {
|
||||
// 记录状态和报错信息
|
||||
this.validateState = !errors ? 'success' : 'error'
|
||||
this.validateMessage = errors ? errors[0].message : ''
|
||||
|
||||
callback(this.validateMessage)
|
||||
})
|
||||
},
|
||||
|
||||
// 清空当前item信息
|
||||
resetField() {
|
||||
this.parent.model[this.prop] = this.initialValue
|
||||
// 清空错误标记
|
||||
this.validateState = 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
box-sizing: border-box;
|
||||
line-height: $tn-form-item-height;
|
||||
|
||||
&__border-bottom--error:after {
|
||||
border-color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&--left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding-right: 18rpx;
|
||||
flex: 1;
|
||||
|
||||
&--required {
|
||||
position: relative;
|
||||
right: 0;
|
||||
vertical-align: middle;
|
||||
color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $tn-font-sub-color;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex: 1;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&__slot {
|
||||
flex: 1;
|
||||
/* #ifndef MP */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-left: 10rpx;
|
||||
color: $tn-font-sub-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: $tn-color-red;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
139
tuniao-ui/components/tn-form/tn-form.vue
Normal file
139
tuniao-ui/components/tn-form/tn-form.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<view class="tn-form-class tn-form">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-form',
|
||||
props: {
|
||||
// 表单数据对象(需要验证的表单数据)
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 发生错误时的提示方式
|
||||
// toast - 弹出toast框
|
||||
// message - 提示信息
|
||||
// border - 如果设置了边框,边框会变成红色
|
||||
// border-bottom - 下边框会呈现红色
|
||||
// none - 无提示
|
||||
errorType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['message', 'toast']
|
||||
}
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 90
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// center - 居中对齐
|
||||
// right - 右对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 向子孙传递数据
|
||||
provide() {
|
||||
return {
|
||||
tnForm: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 存储当前form下的所有form-item的实例
|
||||
// 不能定义再data中,否则小程序会循环引用而报错
|
||||
this.fields = []
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 设置规则
|
||||
*
|
||||
* @param {Object} rules
|
||||
*/
|
||||
setRules(rules) {
|
||||
this.rules = rules
|
||||
},
|
||||
/**
|
||||
* 清空form-item组件
|
||||
*/
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.resetField()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 校验数据
|
||||
* @param {Object} callback 校验回调方法
|
||||
*/
|
||||
validate(callback) {
|
||||
return new Promise(resolve => {
|
||||
// 标记校验是否通过
|
||||
let valid = true
|
||||
// 标记是否检查完毕
|
||||
let count = 0
|
||||
// 存放错误信息
|
||||
let errors = []
|
||||
|
||||
// 对所有form-item进行校验
|
||||
this.fields.map(field => {
|
||||
// 调用对应form-item实例的validation校验方法
|
||||
field.validation('', error => {
|
||||
// 如果有一个form-item校验不通过,则整个表单校验不通过
|
||||
if (error) {
|
||||
valid = false
|
||||
errors.push(error)
|
||||
}
|
||||
// 当遍历完所有的form-item的校验规则,返回信息
|
||||
if (++count === this.fields.length) {
|
||||
resolve(valid)
|
||||
// 判断是否设置了toast的提示方式,只提示表单域中最前面的一条错误信息
|
||||
if (this.errorType.indexOf('none') === -1 &&
|
||||
this.errorType.indexOf('toast') >= 0 &&
|
||||
errors.length > 0) {
|
||||
this.$t.message.toast(errors[0])
|
||||
}
|
||||
// 调用回调方法
|
||||
if (typeof callback == 'function') callback(valid)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
382
tuniao-ui/components/tn-goods-nav/tn-goods-nav.vue
Normal file
382
tuniao-ui/components/tn-goods-nav/tn-goods-nav.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-goods-nav-class tn-goods-nav"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
{
|
||||
'tn-goods-nav--fixed': fixed,
|
||||
'tn-safe-area-inset-bottom': safeAreaInsetBottom,
|
||||
'tn-goods-nav--shadow': shadow
|
||||
}
|
||||
]"
|
||||
:style="[backgroundColorStyle, navStyle]"
|
||||
>
|
||||
<view class="options">
|
||||
<view
|
||||
v-for="(item, index) in optionsData"
|
||||
:key="index"
|
||||
class="options__item"
|
||||
:class="[{'options__item--avatar': item.showAvatar}]"
|
||||
@tap="handleOptionClick(index)"
|
||||
>
|
||||
<block v-if="item.showAvatar">
|
||||
<tn-avatar
|
||||
:src="item.avatar"
|
||||
size="sm"
|
||||
:badge="item.showBadge"
|
||||
:badgeText="item.count"
|
||||
:badgeBgColor="item.countBackgroundColor"
|
||||
:badgeColor="item.countFontColor"
|
||||
:badgeSize="26"
|
||||
></tn-avatar>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view class="options__item__icon" :class="[`tn-icon-${item.icon}`]" :style="[optionStyle(index, 'icon')]">
|
||||
<tn-badge v-if="item.showBadge" :absolute="true" :backgroundColor="item.countBackgroundColor" :fontColor="item.countFontColor" :fontSize="16" padding="2rpx 5rpx">{{ item.count }}</tn-badge>
|
||||
</view>
|
||||
<view class="options__item__text" :style="[optionStyle(index, 'text')]">{{ item.text }}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<view class="buttons">
|
||||
<view
|
||||
v-for="(item, index) in buttonGroupsData"
|
||||
:key="index"
|
||||
class="buttons__item"
|
||||
:class="[buttonClass(index)]"
|
||||
:style="[buttonStyle(index)]"
|
||||
@tap="handleButtonClick(index)"
|
||||
>
|
||||
<view class="buttons__item__text">{{ item.text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-goods-nav',
|
||||
props: {
|
||||
// 选项信息
|
||||
// 建议不超过3个
|
||||
// {
|
||||
// icon: '', // 图标名称
|
||||
// text: '', // 显示的文本
|
||||
// count: '', // 角标的值
|
||||
// countBackgroundColor: '', // 角标背景颜色
|
||||
// countFontColor: '', // 角标字体颜色
|
||||
// iconColor: '', // 图标颜色
|
||||
// textColor: '', // 文本颜色
|
||||
// avatar: '', // 显示头像(此时将不显示图标和文本)
|
||||
// }
|
||||
options: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [{
|
||||
icon: 'shop',
|
||||
text: '店铺'
|
||||
},{
|
||||
icon: 'service',
|
||||
text: '客服'
|
||||
},{
|
||||
icon: 'star',
|
||||
text: '收藏'
|
||||
}]
|
||||
}
|
||||
},
|
||||
// 按钮组
|
||||
// 建议不超过2个
|
||||
// {
|
||||
// text: '', // 显示的文本
|
||||
// backgroundColor: '', // 按钮背景颜色
|
||||
// color: '' // 文本颜色
|
||||
// }
|
||||
buttonGroups: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [{
|
||||
text: '加入购物车',
|
||||
backgroundColor: '#FFA726',
|
||||
color: '#FFFFFF'
|
||||
},{
|
||||
text: '结算',
|
||||
backgroundColor: '#FF7043',
|
||||
color: '#FFFFFF'
|
||||
}]
|
||||
}
|
||||
},
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 导航的高度,单位rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 导航的层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 按钮类型
|
||||
// rect -> 方形 paddingRect -> 上下带边距方形 round -> 圆角
|
||||
buttonType: {
|
||||
type: String,
|
||||
default: 'rect'
|
||||
},
|
||||
// 是否固定在底部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
navStyle() {
|
||||
let style = {}
|
||||
if (this.height) {
|
||||
style.height = this.height + 'rpx'
|
||||
}
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.goodsNav
|
||||
return style
|
||||
},
|
||||
// 选项style
|
||||
optionStyle() {
|
||||
return (index, type) => {
|
||||
let style = {}
|
||||
const item = this.optionsData[index]
|
||||
if (type === 'icon' && item.iconColor) {
|
||||
style.color = item.iconColor
|
||||
} else if (type === 'text' && item.fontColor) {
|
||||
style.color = item.fontColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 按钮class
|
||||
buttonClass() {
|
||||
return (index) => {
|
||||
let clazz = ''
|
||||
const item = this.buttonGroupsData[index]
|
||||
if (item.backgroundColorClass) {
|
||||
clazz += ` ${item.backgroundColorClass}`
|
||||
}
|
||||
if (item.colorClass) {
|
||||
clazz += ` ${item.colorClass}`
|
||||
}
|
||||
|
||||
clazz += ` buttons__item--${this.$t.string.humpConvertChar(this.buttonType, '-')}`
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
// 按钮style
|
||||
buttonStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
const item = this.buttonGroupsData[index]
|
||||
if (item.backgroundColorStyle) {
|
||||
style.backgroundColor = item.backgroundColorStyle
|
||||
}
|
||||
if (item.colorStyle) {
|
||||
style.color = item.colorStyle
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options(val) {
|
||||
this.initData()
|
||||
},
|
||||
buttonGroups(val) {
|
||||
this.initData()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 保存选项数据
|
||||
optionsData: [],
|
||||
// 保存按钮组数据
|
||||
buttonGroupsData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
// 初始化选项和按钮数据
|
||||
initData() {
|
||||
this.handleOptionsData()
|
||||
this.handleButtonGroupsData()
|
||||
},
|
||||
// 选项点击事件
|
||||
handleOptionClick(index) {
|
||||
this.$emit('optionClick', {
|
||||
index: index
|
||||
})
|
||||
},
|
||||
// 按钮点击事件
|
||||
handleButtonClick(index) {
|
||||
this.$emit('buttonClick', {
|
||||
index: index
|
||||
})
|
||||
},
|
||||
// 处理选项组数据
|
||||
handleOptionsData() {
|
||||
this.optionsData = this.options.map((item) => {
|
||||
let option = {...item}
|
||||
option.showAvatar = item.hasOwnProperty('avatar')
|
||||
if (item.hasOwnProperty('count')) {
|
||||
const count = this.$t.number.formatNumberString(item.count, 2)
|
||||
option.showBadge = true
|
||||
option.count = typeof count === 'number' ? String(count) : count
|
||||
option.countBackgroundColor = item.countBackgroundColor ? item.countBackgroundColor : '#01BEFF'
|
||||
option.countFontColor = item.countFontColor ? item.countFontColor : '#FFFFFF'
|
||||
}
|
||||
|
||||
return option
|
||||
})
|
||||
},
|
||||
// 处理按钮组数据
|
||||
handleButtonGroupsData() {
|
||||
this.buttonGroupsData = this.buttonGroups.map((item) => {
|
||||
let button = {...item}
|
||||
if (item.hasOwnProperty('backgroundColor')) {
|
||||
button.backgroundColorClass = this.$t.color.getBackgroundColorInternalClass(item.backgroundColor)
|
||||
button.backgroundColorStyle = this.$t.color.getBackgroundColorStyle(item.backgroundColor)
|
||||
}
|
||||
if (item.hasOwnProperty('color')) {
|
||||
button.colorClass = this.$t.color.getBackgroundColorInternalClass(item.color)
|
||||
button.colorStyle = this.$t.color.getBackgroundColorStyle(item.color)
|
||||
}
|
||||
return button
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-goods-nav {
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
width: 100%;
|
||||
box-sizing: content-box;
|
||||
|
||||
&--shadow {
|
||||
box-shadow: 0rpx -10rpx 30rpx 0rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
background-color: transparent;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&--fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #AAAAAA;
|
||||
|
||||
&__item {
|
||||
padding: 0 26rpx;
|
||||
|
||||
&--avatar {
|
||||
padding: 0rpx 0rpx 0rpx 26rpx !important;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
font-size: 36rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
padding: 0 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--rect {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--padding-rect {
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
&--round {
|
||||
height: 75%;
|
||||
&:first-child {
|
||||
border-top-left-radius: 100rpx;
|
||||
border-bottom-left-radius: 100rpx;
|
||||
}
|
||||
&:last-child {
|
||||
border-top-right-radius: 100rpx;
|
||||
border-bottom-right-radius: 100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
114
tuniao-ui/components/tn-grid-item/tn-grid-item.vue
Normal file
114
tuniao-ui/components/tn-grid-item/tn-grid-item.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-item-class tn-grid-item"
|
||||
:class="[
|
||||
backgroundColorClass
|
||||
]"
|
||||
:hover-class="hoverClass"
|
||||
:hover-stay-time="150"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
width: gridWidth
|
||||
}"
|
||||
@tap="click"
|
||||
>
|
||||
<view
|
||||
class="tn-grid-item__box"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-grid-item',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件数据
|
||||
parentData: {
|
||||
// 按下去的样式
|
||||
hoverClass: '',
|
||||
col: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件实例
|
||||
this.updateParentData()
|
||||
this.parent.children.push(this)
|
||||
},
|
||||
computed: {
|
||||
// 计算每个宫格的宽度
|
||||
gridWidth() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return '100%'
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
return 100 / Number(this.parentData.col) + '%'
|
||||
// #endif
|
||||
},
|
||||
// 点击效果
|
||||
hoverClass() {
|
||||
return this.parentData.hoverClass !== 'none'
|
||||
? this.parentData.hoverClass + ' tn-grid-item--hover'
|
||||
: this.parentData.hoverClass
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取父组件参数
|
||||
updateParentData() {
|
||||
this.getParentData('tn-grid')
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index)
|
||||
this.parent && this.parent.click(this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-grid-item {
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
/* #ifdef MP */
|
||||
// float: left;
|
||||
/* #endif */
|
||||
|
||||
&__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background: $tn-space-color !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
111
tuniao-ui/components/tn-grid/tn-grid.vue
Normal file
111
tuniao-ui/components/tn-grid/tn-grid.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-class tn-grid"
|
||||
:style="{
|
||||
justifyContent: gridAlignStyle
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-grid',
|
||||
props: {
|
||||
// 分成几列
|
||||
col: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 宫格对齐方式
|
||||
// left 左对齐 center 居中对齐 right 右对齐
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击时的效果,none没有效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件和子组件需要共享参数变化,通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件是否有updateParentData方式,有才执行
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = []
|
||||
},
|
||||
computed: {
|
||||
// 计算父组件的值是否发生变化
|
||||
parentData() {
|
||||
return [this.hoverClass, this.col, this.border]
|
||||
},
|
||||
// 宫格对齐方式
|
||||
gridAlignStyle() {
|
||||
switch(this.align) {
|
||||
case 'left':
|
||||
return 'flex-start'
|
||||
case 'center':
|
||||
return 'center'
|
||||
case 'right':
|
||||
return 'flex-end'
|
||||
default:
|
||||
return 'flex-start'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
// 组件中兼容小程序的方式,不过不能使用对齐方式
|
||||
// .tn-grid {
|
||||
// width: 100%;
|
||||
// /* #ifdef MP */
|
||||
// position: relative;
|
||||
// box-sizing: border-box;
|
||||
// overflow: hidden;
|
||||
// /* #endif */
|
||||
|
||||
// /* #ifndef MP */
|
||||
// /* #ifndef APP-NVUE */
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// /* #endif */
|
||||
// flex-wrap: wrap;
|
||||
// align-items: center;
|
||||
// /* #endif */
|
||||
// }
|
||||
|
||||
// 在使用组件时兼容小程序
|
||||
.tn-grid {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
1011
tuniao-ui/components/tn-image-upload-drag/tn-image-upload-drag.vue
Normal file
1011
tuniao-ui/components/tn-image-upload-drag/tn-image-upload-drag.vue
Normal file
File diff suppressed because it is too large
Load Diff
645
tuniao-ui/components/tn-image-upload/tn-image-upload.vue
Normal file
645
tuniao-ui/components/tn-image-upload/tn-image-upload.vue
Normal file
@@ -0,0 +1,645 @@
|
||||
<template>
|
||||
<view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
|
||||
<block v-if="showUploadList">
|
||||
<view
|
||||
v-for="(item, index) in lists"
|
||||
:key="index"
|
||||
class="tn-image-upload__item tn-image-upload__item-preview"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: $t.string.getLengthUnitValue(height)
|
||||
}"
|
||||
>
|
||||
<!-- 删除按钮 -->
|
||||
<view
|
||||
v-if="deleteable"
|
||||
class="tn-image-upload__item-preview__delete"
|
||||
@tap.stop="deleteItem(index)"
|
||||
:style="{
|
||||
borderTopColor: deleteBackgroundColor
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-image-upload__item-preview__delete--icon"
|
||||
:class="[`tn-icon-${deleteIcon}`]"
|
||||
:style="{
|
||||
color: deleteColor
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
<!-- 进度条 -->
|
||||
<tn-line-progress
|
||||
v-if="showProgress && item.progress > 0 && !item.error"
|
||||
class="tn-image-upload__item-preview__progress"
|
||||
:percent="item.progress"
|
||||
:showPercent="false"
|
||||
:round="false"
|
||||
:height="8"
|
||||
></tn-line-progress>
|
||||
<!-- 重试按钮 -->
|
||||
<view v-if="item.error" class="tn-image-upload__item-preview__error-btn" @tap.stop="retry(index)">点击重试</view>
|
||||
<!-- 图片信息 -->
|
||||
<image
|
||||
class="tn-image-upload__item-preview__image"
|
||||
:src="item.url || item.path"
|
||||
:mode="imageMode"
|
||||
@tap.stop="doPreviewImage(item.url || item.path, index)"
|
||||
></image>
|
||||
</view>
|
||||
</block>
|
||||
<!-- <view v-if="$slots.file || $slots.$file" style="width: 100%;">
|
||||
|
||||
</view> -->
|
||||
<!-- 自定义图片展示列表 -->
|
||||
<slot name="file" :file="lists"></slot>
|
||||
|
||||
<!-- 添加按钮 -->
|
||||
<view v-if="maxCount > lists.length" class="tn-image-upload__add" :class="{'tn-image-upload__add--custom': customBtn}" @tap="selectFile">
|
||||
<!-- 添加按钮 -->
|
||||
<view
|
||||
v-if="!customBtn"
|
||||
class="tn-image-upload__item tn-image-upload__item-add"
|
||||
hover-class="tn-hover-class"
|
||||
hover-stay-time="150"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: $t.string.getLengthUnitValue(height)
|
||||
}"
|
||||
>
|
||||
<view class="tn-image-upload__item-add--icon tn-icon-add"></view>
|
||||
<view class="tn-image-upload__item-add__tips">{{ uploadText }}</view>
|
||||
</view>
|
||||
<!-- 自定义添加按钮 -->
|
||||
<view>
|
||||
<slot name="addBtn"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-image-upload',
|
||||
props: {
|
||||
// 已上传的文件列表
|
||||
fileList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 上传图片地址
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 上传文件的字段名称
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
},
|
||||
// 头部信息
|
||||
header: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 携带的参数
|
||||
formData: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动上传
|
||||
autoUpload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 最大上传数量
|
||||
maxCount: {
|
||||
type: Number,
|
||||
default: 9
|
||||
},
|
||||
// 是否显示组件自带的图片预览
|
||||
showUploadList: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 预览上传图片的裁剪模式
|
||||
imageMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 点击图片是否全屏预览
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示进度条
|
||||
showProgress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示删除按钮
|
||||
deleteable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 删除按钮图标
|
||||
deleteIcon: {
|
||||
type: String,
|
||||
default: 'close'
|
||||
},
|
||||
// 删除按钮的背景颜色
|
||||
deleteBackgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 删除按钮的颜色
|
||||
deleteColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 上传区域提示文字
|
||||
uploadText: {
|
||||
type: String,
|
||||
default: '选择图片'
|
||||
},
|
||||
// 显示toast提示
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义选择图标按钮
|
||||
customBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 预览图片和选择图片区域的宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 预览图片和选择图片区域的高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 选择图片的尺寸
|
||||
// 参考上传文档 https://uniapp.dcloud.io/api/media/image
|
||||
sizeType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['original', 'compressed']
|
||||
}
|
||||
},
|
||||
// 图片来源
|
||||
sourceType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['album', 'camera']
|
||||
}
|
||||
},
|
||||
// 是否可以多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 文件大小(byte)
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 10 * 1024 * 1024
|
||||
},
|
||||
// 允许上传的类型
|
||||
limitType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['png','jpg','jpeg','webp','gif','image']
|
||||
}
|
||||
},
|
||||
// 是否自定转换为json
|
||||
toJson: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 上传前钩子函数,每个文件上传前都会执行
|
||||
beforeUpload: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
// 删除文件前钩子函数
|
||||
beforeRemove: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
uploading: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fileList: {
|
||||
handler(val) {
|
||||
val.map(value => {
|
||||
// 首先检查内部是否已经添加过这张图片,因为外部绑定了一个对象给fileList的话(对象引用),进行修改外部fileList时,
|
||||
// 会触发watch,导致重新把原来的图片再次添加到this.lists
|
||||
// 数组的some方法意思是,只要数组元素有任意一个元素条件符合,就返回true,而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
|
||||
let tmp = this.lists.some(listVal => {
|
||||
return listVal.url === value.url
|
||||
})
|
||||
// 如果内部没有这张图片,则添加到内部
|
||||
!tmp && this.lists.push({ url: value.url, error: false, progress: 100 })
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
lists(val) {
|
||||
this.$emit('on-list-change', val, this.index)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 清除列表
|
||||
clear() {
|
||||
this.lists = []
|
||||
},
|
||||
// 重新上传队列中上传失败所有文件
|
||||
reUpload() {
|
||||
this.uploadFile()
|
||||
},
|
||||
// 选择图片
|
||||
selectFile() {
|
||||
if (this.disabled) return
|
||||
const {
|
||||
name = '',
|
||||
maxCount,
|
||||
multiple,
|
||||
maxSize,
|
||||
sizeType,
|
||||
lists,
|
||||
camera,
|
||||
compressed,
|
||||
sourceType
|
||||
} = this
|
||||
let chooseFile = null
|
||||
const newMaxCount = maxCount - lists.length
|
||||
// 只选择图片的时候使用 chooseImage 来实现
|
||||
chooseFile = new Promise((resolve, reject) => {
|
||||
uni.chooseImage({
|
||||
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
|
||||
sourceType,
|
||||
sizeType,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
chooseFile.then(res => {
|
||||
let file = null
|
||||
let listOldLength = lists.length
|
||||
res.tempFiles.map((val, index) => {
|
||||
if (!this.checkFileExt(val)) return
|
||||
|
||||
// 是否超出最大限制数量
|
||||
if (!multiple && index >= 1) return
|
||||
if (val.size > maxSize) {
|
||||
this.$emit('on-oversize', val, lists, this.index)
|
||||
this.showToast('超出可允许文件大小')
|
||||
} else {
|
||||
if (maxCount <= lists.length) {
|
||||
this.$emit('on-exceed', val, lists, this.index)
|
||||
this.showToast('超出最大允许的文件数')
|
||||
return
|
||||
}
|
||||
lists.push({
|
||||
url: val.path,
|
||||
progress: 0,
|
||||
error: false,
|
||||
file: val
|
||||
})
|
||||
}
|
||||
})
|
||||
this.$emit('on-choose-complete', this.lists, this.index)
|
||||
if (this.autoUpload) this.uploadFile(listOldLength)
|
||||
}).catch(err => {
|
||||
this.$emit('on-choose-fail', err)
|
||||
})
|
||||
},
|
||||
// 提示用户信息
|
||||
showToast(message, force = false) {
|
||||
if (this.showTips || force) {
|
||||
this.$t.message.toast(message)
|
||||
}
|
||||
},
|
||||
// 手动上传,通过ref进行调用
|
||||
upload() {
|
||||
this.uploadFile()
|
||||
},
|
||||
// 对失败图片进行再次上传
|
||||
retry(index) {
|
||||
this.lists[index].progress = 0
|
||||
this.lists[index].error = false
|
||||
this.lists[index].response = null
|
||||
this.$t.message.loading('重新上传')
|
||||
this.uploadFile(index)
|
||||
},
|
||||
// 上传文件
|
||||
async uploadFile(index = 0) {
|
||||
if (this.disabled) return
|
||||
if (this.uploading) return
|
||||
// 全部上传完成
|
||||
if (index >= this.lists.length) {
|
||||
this.$emit('on-uploaded', this.lists, this.index)
|
||||
return
|
||||
}
|
||||
// 检查是否已经全部上传或者上传中
|
||||
if (this.lists[index].progress === 100) {
|
||||
this.lists[index].uploadTask = null
|
||||
if (this.autoUpload) this.uploadFile(index + 1)
|
||||
return
|
||||
}
|
||||
// 执行before-upload钩子
|
||||
if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
|
||||
// 在微信,支付宝等环境(H5正常),会导致父组件定义的函数体中的this变成子组件的this
|
||||
// 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
|
||||
// 因为upload组件可能会被嵌套在其他组件内,比如tn-form,这时this.$parent其实为tn-form的this,
|
||||
// 非页面的this,所以这里需要往上历遍,一直寻找到最顶端的$parent,这里用了this.$u.$parent.call(this)
|
||||
let beforeResponse = this.beforeUpload.bind(this.$t.$parent.call(this))(index, this.lists)
|
||||
// 判断是否返回了Promise
|
||||
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
|
||||
await beforeResponse.then(res => {
|
||||
// promise返回成功,不进行操作继续
|
||||
}).catch(err => {
|
||||
// 进入catch回调的话,继续下一张
|
||||
return this.uploadFile(index + 1)
|
||||
})
|
||||
} else if (beforeResponse === false) {
|
||||
// 如果返回flase,继续下一张图片上传
|
||||
return this.uploadFile(index + 1)
|
||||
} else {
|
||||
// 为true的情况,不进行操作
|
||||
}
|
||||
}
|
||||
// 检查上传地址
|
||||
if (!this.action) {
|
||||
this.showToast('请配置上传地址', true)
|
||||
return
|
||||
}
|
||||
this.lists[index].error = false
|
||||
this.uploading = true
|
||||
// 创建上传对象
|
||||
const task = uni.uploadFile({
|
||||
url: this.action,
|
||||
filePath: this.lists[index].url,
|
||||
name: this.name,
|
||||
formData: this.formData,
|
||||
header: this.header,
|
||||
success: res => {
|
||||
// 判断啊是否为json字符串,将其转换为json格式
|
||||
let data = this.toJson && this.$t.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
|
||||
if (![200, 201, 204].includes(res.statusCode)) {
|
||||
this.uploadError(index, data)
|
||||
} else {
|
||||
this.lists[index].response = data
|
||||
this.lists[index].progress = 100
|
||||
this.lists[index].error = false
|
||||
this.$emit('on-success', data, index, this.lists, this.index)
|
||||
}
|
||||
},
|
||||
fail: err => {
|
||||
this.uploadError(index, err)
|
||||
},
|
||||
complete: res => {
|
||||
this.$t.message.closeLoading()
|
||||
this.uploading = false
|
||||
this.uploadFile(index + 1)
|
||||
this.$emit('on-change', res, index, this.lists, this.index)
|
||||
}
|
||||
})
|
||||
this.lists[index].uploadTask = task
|
||||
task.onProgressUpdate(res => {
|
||||
if (res.progress > 0) {
|
||||
this.lists[index].progress = res.progress
|
||||
this.$emit('on-progress', res, index, this.lists, this.index)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 上传失败
|
||||
uploadError(index, err) {
|
||||
this.lists[index].progress = 0
|
||||
this.lists[index].error = true
|
||||
this.lists[index].response = null
|
||||
this.showToast('上传失败,请重试')
|
||||
this.$emit('on-error', err, index, this.lists, this.index)
|
||||
},
|
||||
// 删除一个图片
|
||||
deleteItem(index) {
|
||||
if (!this.deleteable) return
|
||||
this.$t.message.modal(
|
||||
'提示',
|
||||
'您确定要删除吗?',
|
||||
async () => {
|
||||
// 先检查是否有定义before-remove移除前钩子
|
||||
// 执行before-remove钩子
|
||||
if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
|
||||
let beforeResponse = this.beforeRemove.bind(this.$t.$parent.call(this))(index, this.lists)
|
||||
// 判断是否返回promise
|
||||
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
|
||||
await beforeResponse.then(res => {
|
||||
// promise返回成功不进行操作
|
||||
this.handlerDeleteItem(index)
|
||||
}).catch(err => {
|
||||
this.showToast('删除操作被中断')
|
||||
})
|
||||
} else if (beforeResponse === false) {
|
||||
this.showToast('删除操作被中断')
|
||||
} else {
|
||||
this.handlerDeleteItem(index)
|
||||
}
|
||||
} else {
|
||||
this.handlerDeleteItem(index)
|
||||
}
|
||||
}, true)
|
||||
},
|
||||
// 移除文件操作
|
||||
handlerDeleteItem(index) {
|
||||
// 如果文件正在上传中,终止上传任务
|
||||
if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
|
||||
typeof this.lists[index].uploadTask !== 'undefined' && this.lists[index].uploadTask.abort()
|
||||
}
|
||||
this.lists.splice(index, 1)
|
||||
this.$forceUpdate()
|
||||
this.$emit('on-remove', index, this.lists, this.index)
|
||||
this.showToast('删除成功')
|
||||
},
|
||||
// 移除文件,通过ref手动形式进行调用
|
||||
remove(index) {
|
||||
if (!this.deleteable) return
|
||||
// 判断索引合法
|
||||
if (index >= 0 && index < this.lists.length) {
|
||||
this.lists.splice(index, 1)
|
||||
}
|
||||
},
|
||||
// 预览图片
|
||||
doPreviewImage(url, index) {
|
||||
if (!this.previewFullImage) return
|
||||
const images = this.lists.map(item => item.url || item.path)
|
||||
uni.previewImage({
|
||||
urls: images,
|
||||
current: url,
|
||||
success: () => {
|
||||
this.$emit('on-preview', url, this.lists, this.index)
|
||||
},
|
||||
fail: () => {
|
||||
this.showToast('预览图片失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
// 检查文件后缀是否合法
|
||||
checkFileExt(file) {
|
||||
// 是否为合法后缀
|
||||
let noArrowExt = false
|
||||
// 后缀名
|
||||
let fileExt = ''
|
||||
const reg = /.+\./
|
||||
|
||||
// #ifdef H5
|
||||
fileExt = file.name.replace(reg, '').toLowerCase()
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
fileExt = file.path.replace(reg, '').toLowerCase()
|
||||
// #endif
|
||||
noArrowExt = this.limitType.some(ext => {
|
||||
return ext.toLowerCase() === fileExt
|
||||
})
|
||||
if (!noArrowExt) this.showToast(`不支持${fileExt}格式的文件`)
|
||||
return noArrowExt
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-image-upload {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
&__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
overflow: hidden;
|
||||
margin: 12rpx;
|
||||
margin-left: 0;
|
||||
background-color: $tn-font-holder-color;
|
||||
position: relative;
|
||||
border-radius: 10rpx;
|
||||
|
||||
&-preview {
|
||||
border: 1rpx solid $tn-border-solid-color;
|
||||
|
||||
&__delete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
border-top: 60rpx solid;
|
||||
border-left: 60rpx solid transparent;
|
||||
border-top-color: $tn-color-red;
|
||||
width: 0rpx;
|
||||
height: 0rpx;
|
||||
|
||||
&--icon {
|
||||
position: absolute;
|
||||
top: -50rpx;
|
||||
right: 6rpx;
|
||||
color: #FFFFFF;
|
||||
font-size: 24rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__progress {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
right: 0rpx;
|
||||
z-index: 9;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__error-btn {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $tn-color-red;
|
||||
color: #FFFFFF;
|
||||
font-size: 20rpx;
|
||||
padding: 8rpx 0;
|
||||
text-align: center;
|
||||
z-index: 9;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-add {
|
||||
flex-direction: column;
|
||||
color: $tn-content-color;
|
||||
font-size: 26rpx;
|
||||
|
||||
&--icon {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
&__tips {
|
||||
margin-top: 20rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__add {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
|
||||
&--custom {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
tuniao-ui/components/tn-index-anchor/tn-index-anchor.vue
Normal file
90
tuniao-ui/components/tn-index-anchor/tn-index-anchor.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view :id="elId" class="tn-index-anchor__wrap" :style="[wrapperStyle]">
|
||||
<view class="tn-index-anchor" :class="[active ? 'tn-index-anchor--active' : '']" :style="[customAnchorStyle]">
|
||||
<view v-if="useSlot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<block v-else>
|
||||
<text>{{ index }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-index-anchor',
|
||||
props: {
|
||||
// 使用自定义内容
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 索引字符
|
||||
index: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customAnchorStyle() {
|
||||
return Object.assign(this.anchorStyle, this.customStyle)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elId: this.$t.uuid(),
|
||||
// 内容的高度
|
||||
height: 0,
|
||||
// 内容的top
|
||||
top: 0,
|
||||
// 是否被激活
|
||||
active: false,
|
||||
// 样式(父组件外部提供)
|
||||
wrapperStyle: {},
|
||||
anchorStyle: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
},
|
||||
mounted() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-index-list')
|
||||
if (this.parent) {
|
||||
this.parent.childrens.push(this)
|
||||
this.parent.updateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-anchor {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8rpx 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
background-color: rgb(245, 245, 245);
|
||||
|
||||
&--active {
|
||||
right: 0;
|
||||
left: 0;
|
||||
color: $tn-main-color;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
361
tuniao-ui/components/tn-index-list/tn-index-list.vue
Normal file
361
tuniao-ui/components/tn-index-list/tn-index-list.vue
Normal file
@@ -0,0 +1,361 @@
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view class="tn-index-list-class tn-index-list">
|
||||
<slot></slot>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<view
|
||||
v-if="showSidebar"
|
||||
class="tn-index-list__sidebar"
|
||||
@touchstart.stop.prevent="onTouchMove"
|
||||
@touchmove.stop.prevent="onTouchMove"
|
||||
@touchend.stop.prevent="onTouchStop"
|
||||
@touchcancel.stop.prevent="onTouchStop"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in indexList"
|
||||
:key="index"
|
||||
class="tn-index-list__sidebar__item"
|
||||
:style="{
|
||||
zIndex: zIndex + 1,
|
||||
color: activeAnchorIndex === index ? activeColor : ''
|
||||
}"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选中弹出框 -->
|
||||
<view
|
||||
v-if="touchMove && indexList[touchMoveIndex]"
|
||||
class="tn-index-list__alert"
|
||||
:style="{
|
||||
zIndex: selectAlertZIndex
|
||||
}"
|
||||
>
|
||||
<text>{{ indexList[touchMoveIndex] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 生成 A-Z的字母列表
|
||||
let indexList = function() {
|
||||
let indexList = []
|
||||
let charCodeOfA = 'A'.charCodeAt(0)
|
||||
for (var i = 0; i < 26; i++) {
|
||||
indexList.push(String.fromCharCode(charCodeOfA + i))
|
||||
}
|
||||
return indexList
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'tn-index-list',
|
||||
props: {
|
||||
// 索引列表
|
||||
indexList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return indexList()
|
||||
}
|
||||
},
|
||||
// 是否自动吸顶
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自动吸顶时距离顶部的距离,单位px
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自定义顶栏的高度,单位px
|
||||
customBarHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 当前滚动的高度
|
||||
// 由于自定义组件无法获取滚动高度,所以依赖传入
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 选中索引时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 吸顶时的z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 选中索引列表弹出提示框的z-index
|
||||
selectAlertZIndex() {
|
||||
return this.$t.zIndex.toast
|
||||
},
|
||||
// 吸顶的偏移高度
|
||||
stickyOffsetTop() {
|
||||
// #ifdef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 44
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 0
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前激活的列表锚点的序号
|
||||
activeAnchorIndex: 0,
|
||||
// 显示侧边索引栏
|
||||
showSidebar: true,
|
||||
// 标记是否开始触摸移动
|
||||
touchMove: false,
|
||||
// 当前触摸移动到对应索引的序号
|
||||
touchMoveIndex: 0,
|
||||
// 滚动到对应锚点的序号
|
||||
scrollToAnchorIndex: 0,
|
||||
// 侧边栏的信息
|
||||
sidebar: {
|
||||
height: 0,
|
||||
top: 0
|
||||
},
|
||||
// 内容区域高度
|
||||
height: 0,
|
||||
// 内容区域top
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop() {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 只能在created生命周期定义childrens,如果在data定义,会因为循环引用而报错
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 更新数据
|
||||
updateData() {
|
||||
this.timer && clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
this.showSidebar = !!this.childrens.length
|
||||
this.getRect().then(() => {
|
||||
this.onScroll()
|
||||
})
|
||||
}, 0)
|
||||
},
|
||||
// 获取对应的信息
|
||||
getRect() {
|
||||
return Promise.all([
|
||||
this.getAnchorRect(),
|
||||
this.getListRect(),
|
||||
this.getSidebarRect()
|
||||
])
|
||||
},
|
||||
// 获取列表内容子元素信息
|
||||
getAnchorRect() {
|
||||
return Promise.all(this.childrens.map((child, index) => {
|
||||
child._tGetRect('.tn-index-anchor__wrap').then((rect) => {
|
||||
Object.assign(child, {
|
||||
height: rect.height,
|
||||
top: rect.top - this.customBarHeight
|
||||
})
|
||||
})
|
||||
}))
|
||||
},
|
||||
// 获取列表信息
|
||||
getListRect() {
|
||||
return this._tGetRect('.tn-index-list').then(rect => {
|
||||
Object.assign(this, {
|
||||
height: rect.height,
|
||||
top: rect.top + this.scrollTop
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取侧边滚动栏信息
|
||||
getSidebarRect() {
|
||||
return this._tGetRect('.tn-index-list__sidebar').then(rect => {
|
||||
this.sidebar = {
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
}
|
||||
})
|
||||
},
|
||||
// 滚动事件
|
||||
onScroll() {
|
||||
const {
|
||||
childrens = []
|
||||
} = this
|
||||
if (!childrens.length) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
sticky,
|
||||
stickyOffsetTop,
|
||||
zIndex,
|
||||
scrollTop,
|
||||
activeColor
|
||||
} = this
|
||||
const active = this.getActiveAnchorIndex()
|
||||
this.activeAnchorIndex = active
|
||||
if (sticky) {
|
||||
let isActiveAnchorSticky = false
|
||||
if (active !== -1) {
|
||||
isActiveAnchorSticky = childrens[active].top <= 0
|
||||
}
|
||||
childrens.forEach((item, index) => {
|
||||
if (index === active) {
|
||||
let wrapperStyle = ''
|
||||
let anchorStyle = {
|
||||
color: `${activeColor}`
|
||||
}
|
||||
if (isActiveAnchorSticky) {
|
||||
wrapperStyle = {
|
||||
height: `${childrens[index].height}px`
|
||||
}
|
||||
anchorStyle = {
|
||||
position: 'fixed',
|
||||
top: `${stickyOffsetTop}px`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
}
|
||||
item.active = true
|
||||
item.wrapperStyle = wrapperStyle
|
||||
item.anchorStyle = anchorStyle
|
||||
} else if (index === active - 1) {
|
||||
const currentAnchor = childrens[index]
|
||||
const currentOffsetTop = currentAnchor.top
|
||||
const targetOffsetTop = index === childrens.length - 1 ? this.top : childrens[index + 1].top
|
||||
const parentOffsetHeight = targetOffsetTop - currentOffsetTop
|
||||
const translateY = parentOffsetHeight - currentAnchor.height
|
||||
const anchorStyle = {
|
||||
position: 'relative',
|
||||
transform: `translate3d(0, ${translateY}px, 0)`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
item.active = false
|
||||
item.anchorStyle = anchorStyle
|
||||
} else {
|
||||
item.active = false
|
||||
item.wrapperStyle = ''
|
||||
item.anchorStyle = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 触摸移动
|
||||
onTouchMove(event) {
|
||||
this.touchMove = true
|
||||
const sidebarLength = this.childrens.length
|
||||
const touch = event.touches[0]
|
||||
const itemHeight = this.sidebar.height / sidebarLength
|
||||
let clientY = touch.clientY
|
||||
let index = Math.floor((clientY - this.sidebar.top) / itemHeight)
|
||||
if (index < 0) {
|
||||
index = 0
|
||||
} else if (index > sidebarLength - 1) {
|
||||
index = sidebarLength - 1
|
||||
}
|
||||
this.touchMoveIndex = index
|
||||
this.scrollToAnchor(index)
|
||||
},
|
||||
// 触摸停止
|
||||
onTouchStop() {
|
||||
this.touchMove = false
|
||||
this.scrollToAnchorIndex = null
|
||||
},
|
||||
// 获取当前的锚点序号
|
||||
getActiveAnchorIndex() {
|
||||
const {
|
||||
childrens,
|
||||
sticky
|
||||
} = this
|
||||
for (let i = this.childrens.length - 1; i >= 0; i--) {
|
||||
const preAnchorHeight = i > 0 ? childrens[i - 1].height : 0
|
||||
const reachTop = sticky ? preAnchorHeight : 0
|
||||
if (reachTop >= childrens[i].top) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
},
|
||||
// 滚动到对应的锚点
|
||||
scrollToAnchor(index) {
|
||||
if (this.scrollToAnchorIndex === index) {
|
||||
return
|
||||
}
|
||||
this.scrollToAnchorIndex = index
|
||||
const anchor = this.childrens.find(item => item.index === this.indexList[index])
|
||||
if (anchor) {
|
||||
const scrollTop = anchor.top + this.scrollTop
|
||||
this.$emit('select', {
|
||||
index: anchor.index,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
uni.pageScrollTo({
|
||||
duration:0,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-list {
|
||||
position: relative;
|
||||
|
||||
&__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
z-index: 99;
|
||||
|
||||
&__item {
|
||||
font-weight: 500;
|
||||
padding: 8rpx 18rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__alert {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
top: 50%;
|
||||
right: 90rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -60rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-font-sub-color;
|
||||
padding: 0;
|
||||
z-index: 9999999;
|
||||
|
||||
text {
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
427
tuniao-ui/components/tn-input/tn-input.vue
Normal file
427
tuniao-ui/components/tn-input/tn-input.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-input-class tn-input"
|
||||
:class="{
|
||||
'tn-input--border': border,
|
||||
'tn-input--error': validateState
|
||||
}"
|
||||
:style="{
|
||||
padding: `0 ${border ? 20 : 0}rpx`,
|
||||
borderColor: borderColor,
|
||||
textAlign: inputAlign
|
||||
}"
|
||||
@tap.stop="inputClick"
|
||||
>
|
||||
<textarea
|
||||
v-if="type === 'textarea'"
|
||||
class="tn-input__input tn-input__textarea"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:fixed="fixed"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
class="tn-input__input"
|
||||
:type="type === 'password' ? 'text' : type"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:password="type === 'password' && !showPassword"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
|
||||
<!-- 右边的icon -->
|
||||
<view class="tn-input__right-icon tn-flex tn-flex-col-center">
|
||||
<!-- 清除按钮 -->
|
||||
<view
|
||||
v-if="clearable && value !== '' && focused"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="onClear"
|
||||
>
|
||||
<view class="icon tn-icon-close"></view>
|
||||
</view>
|
||||
<view
|
||||
v-else-if="type === 'text' && !focused && showRightIcon && rightIcon !== ''"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
>
|
||||
<view class="icon" :class="[`tn-icon-${rightIcon}`]"></view>
|
||||
</view>
|
||||
<!-- 显示密码按钮 -->
|
||||
<view
|
||||
v-if="passwordIcon && type === 'password'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="showPassword = !showPassword"
|
||||
>
|
||||
<view v-if="!showPassword" class="tn-icon-eye-hide"></view>
|
||||
<view v-else class="icon tn-icon-eye"></view>
|
||||
</view>
|
||||
<!-- 可选项箭头 -->
|
||||
<view
|
||||
v-if="type === 'select'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__select"
|
||||
:class="{
|
||||
'tn-input__right-icon__select--reverse': selectOpen
|
||||
}"
|
||||
>
|
||||
<view class="icon tn-icon-up-triangle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-input',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 输入框的类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
// 输入框文字对齐方式
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 文本框为空时显示的信息
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: 'color: #AAAAAA'
|
||||
},
|
||||
// 是否禁用输入框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 可输入文字的最大长度
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 255
|
||||
},
|
||||
// 输入框高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 根据内容自动调整高度
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 键盘右下角显示的文字,仅在text时生效
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 输入框自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否固定输入框
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当type为password时,是否显示右侧密码图标
|
||||
passwordIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当type为 input或者textarea时是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框的颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 当type为select时,旋转右侧图标,标记当时select是打开还是关闭
|
||||
selectOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 光标与键盘的距离
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// selectionStart和selectionEnd需要搭配使用,自动聚焦时生效
|
||||
// 光标起始位置
|
||||
selectionStart: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 光标结束位置
|
||||
selectionEnd: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 自动去除两端空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示键盘上方的完成按钮
|
||||
showConfirmBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否在输入框内最右边显示图标
|
||||
showRightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 最右边图标的名称
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 输入框样式
|
||||
inputStyle() {
|
||||
let style = {}
|
||||
// 如果没有设置高度,根据不同的类型设置一个默认值
|
||||
style.minHeight = this.height ? this.height + 'rpx' :
|
||||
this.type === 'textarea' ? this.textareaHeight + 'rpx' : this.inputHeight + 'rpx'
|
||||
|
||||
style = Object.assign(style, this.customStyle)
|
||||
|
||||
return style
|
||||
},
|
||||
// 光标起始位置
|
||||
elSelectionStart() {
|
||||
return String(this.selectionStart)
|
||||
},
|
||||
// 光标结束位置
|
||||
elSelectionEnd() {
|
||||
return String(this.selectionEnd)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
defaultValue: this.value,
|
||||
// 输入框高度
|
||||
inputHeight: 70,
|
||||
// textarea的高度
|
||||
textareaHeight: 100,
|
||||
// 标记验证的状态
|
||||
validateState: false,
|
||||
// 标记是否获取到焦点
|
||||
focused: false,
|
||||
// 是否预览密码
|
||||
showPassword: false,
|
||||
// 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input事件
|
||||
lastValue: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal, oldVal) {
|
||||
this.defaultValue = newVal
|
||||
// 当值发生变化时,并且type为select时,不会触发input事件
|
||||
// 模拟input事件
|
||||
if (newVal !== oldVal && this.type === 'select') {
|
||||
this.handleInput({
|
||||
detail: {
|
||||
value: newVal
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听form-item发出的错误事件,将输入框变成红色
|
||||
this.$on("on-form-item-error", this.onFormItemError)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* input事件
|
||||
*/
|
||||
handleInput(event) {
|
||||
let value = event.detail.value
|
||||
// 是否需要去掉空格
|
||||
if (this.trim) value = this.$t.string.trim(value)
|
||||
// 原生事件
|
||||
this.$emit('input', value)
|
||||
// model赋值
|
||||
this.defaultValue = value
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-change', value)
|
||||
}, 40)
|
||||
},
|
||||
/**
|
||||
* blur事件
|
||||
*/
|
||||
handleBlur(event) {
|
||||
let value = event.detail.value
|
||||
|
||||
// 由于点击清除图标也会触发blur事件,导致图标消失从而无法点击
|
||||
setTimeout(() => {
|
||||
this.focused = false
|
||||
}, 100)
|
||||
|
||||
// 原生事件
|
||||
this.$emit('blur', value)
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('blur')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-blur', value)
|
||||
}, 40)
|
||||
},
|
||||
// 处理校验错误
|
||||
onFormItemError(status) {
|
||||
this.validateState = status
|
||||
},
|
||||
// 聚焦事件
|
||||
onFocus(event) {
|
||||
this.focused = true
|
||||
this.$emit('focus')
|
||||
},
|
||||
// 点击确认按钮事件
|
||||
onConfirm(event) {
|
||||
this.$emit('confirm', event.detail.value)
|
||||
},
|
||||
// 清除事件
|
||||
onClear(event) {
|
||||
this.$emit('input', '')
|
||||
},
|
||||
// 点击事件
|
||||
inputClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
&__input {
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
padding: 10rpx 0;
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border: 2rpx solid $tn-border-solid-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: $tn-color-red !important;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
line-height: 1;
|
||||
.icon {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
&__clear {
|
||||
.icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__select {
|
||||
transition: transform .4s;
|
||||
|
||||
.icon {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
220
tuniao-ui/components/tn-keyboard/tn-keyboard.vue
Normal file
220
tuniao-ui/components/tn-keyboard/tn-keyboard.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-keyboard-class tn-keyboard">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:mask="mask"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="popupClose"
|
||||
>
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view v-if="tooltip" class="tn-keyboard__tooltip">
|
||||
<view
|
||||
v-if="cancelBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__cancel"
|
||||
hover-class="tn-keyboard__tooltip__cancel--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onCancel"
|
||||
>
|
||||
{{ cancelBtn ? cancelText : ''}}
|
||||
</view>
|
||||
<view v-if="showTips" class="tn-keyboard__tooltip__item tn-keyboard__tooltip__tips">
|
||||
{{ tips ? tips : mode === 'number' ? '数字键盘' : mode === 'card' ? '身份证键盘' : '车牌号码键盘'}}
|
||||
</view>
|
||||
<view
|
||||
v-if="confirmBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__confirm"
|
||||
hover-class="tn-keybord__tooltip__confirm--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onConfirm"
|
||||
>
|
||||
{{ confirmBtn ? confirmText : ''}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 键盘内容 -->
|
||||
<block v-if="mode === 'number' || mode === 'card'">
|
||||
<tn-number-keyboard :mode="mode" :dotEnabled="dotEnabled" :randomEnabled="randomEnabled" @change="change" @backspace="backspaceClick"></tn-number-keyboard>
|
||||
</block>
|
||||
<block v-if="mode === 'car'">
|
||||
<tn-car-keyboard :randomEnabled="randomEnabled" :switchEnMode="switchEnMode" @change="change" @backspace="backspaceClick"></tn-car-keyboard>
|
||||
</block>
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-keyboard',
|
||||
props: {
|
||||
// 控制键盘弹出收回
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 键盘类型
|
||||
// number - 数字键盘 card - 身份证键盘 car - 车牌号码
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 当mode = number时,是否显示'.'符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否打乱顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当mode = car,设置键盘中英文状态
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示顶部工具条
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示提示信息
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
confirmBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 取消按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(e) {
|
||||
this.$emit('change', e)
|
||||
},
|
||||
// 关闭键盘
|
||||
popupClose() {
|
||||
// 修改value的值
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 输入完成
|
||||
onConfirm() {
|
||||
this.popupClose()
|
||||
this.$emit('confirm')
|
||||
},
|
||||
// 输入取消
|
||||
onCancel() {
|
||||
this.popupClose()
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-keyboard {
|
||||
position: relative;
|
||||
|
||||
&__tooltip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
&__item {
|
||||
color: $tn-font-color;
|
||||
flex: 0 0 33.3333333333%;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-left: 40rpx;
|
||||
color: $tn-content-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-right: 40rpx;
|
||||
color: $tn-main-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
225
tuniao-ui/components/tn-landscape/tn-landscape.vue
Normal file
225
tuniao-ui/components/tn-landscape/tn-landscape.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="tn-landscape-class tn-landscape">
|
||||
<view v-if="showValue" class="tn-landscape__container" :style="[containerStyle]">
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-landscape__icon tn-icon-close-fill"
|
||||
:class="[{
|
||||
'tn-landscape__icon--left-top': closePosition === 'leftTop',
|
||||
'tn-landscape__icon--right-top': closePosition === 'rightTop',
|
||||
'tn-landscape__icon--bottom': closePosition === 'bottom'
|
||||
}]"
|
||||
:style="[closeBtnStyle]"
|
||||
@tap="close"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
v-if="mask"
|
||||
class="tn-landscape__mask"
|
||||
:class="[{'tn-landscape__mask--show': showValue}]"
|
||||
:style="[maskStyle]"
|
||||
@tap="closeMask"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-landscape',
|
||||
props: {
|
||||
// 显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示关闭图标
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 关闭图标颜色
|
||||
closeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 关闭图标大小,单位rpx
|
||||
closeSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标位置
|
||||
// leftTop -> 左上角 rightTop -> 右上角 bottom -> 底部
|
||||
closePosition: {
|
||||
type: String,
|
||||
default: 'rightTop'
|
||||
},
|
||||
// 关闭图标top值,单位rpx
|
||||
// 当关闭图标在leftTop或者rightTop时生效
|
||||
closeTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标right值,单位rpx
|
||||
// 当关闭图标在RightTop时生效
|
||||
closeRight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标bottom值,单位rpx
|
||||
// 当关闭图标在bottom时生效
|
||||
closeBottom: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标left值,单位rpx
|
||||
// 当关闭图标在leftTop时生效
|
||||
closeLeft: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击遮罩可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerStyle() {
|
||||
let style = {}
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.landsacpe
|
||||
return style
|
||||
},
|
||||
closeBtnStyle() {
|
||||
let style = {}
|
||||
if (this.closePosition === 'leftTop') {
|
||||
if (this.closeTop) {
|
||||
style.top = this.closeTop + 'rpx'
|
||||
}
|
||||
if (this.closeLeft) {
|
||||
style.left = this.closeLeft + 'rpx'
|
||||
}
|
||||
} else if (this.closePosition === 'rightTop') {
|
||||
if (this.closeTop) {
|
||||
style.top = this.closeTop + 'rpx'
|
||||
}
|
||||
if (this.closeRight) {
|
||||
style.right = this.closeRight + 'rpx'
|
||||
}
|
||||
} else if (this.closePosition === 'bottom') {
|
||||
if (this.closeBottom) {
|
||||
style.bottom = this.closeBottom + 'rpx'
|
||||
}
|
||||
}
|
||||
if (this.closeSize) {
|
||||
style.fontSize = this.closeSize + 'rpx'
|
||||
}
|
||||
if (this.closeColor) {
|
||||
style.color = this.closeColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
maskStyle() {
|
||||
let style = {}
|
||||
style.zIndex = this.zIndex ? this.zIndex - 1 : this.$t.zIndex.landsacpe - 1
|
||||
return style
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
this.showValue = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showValue: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 关闭压屏窗
|
||||
close() {
|
||||
this.showValue = false
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击遮罩关闭
|
||||
closeMask() {
|
||||
if (!this.maskCloseable) return
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-landscape {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__container {
|
||||
max-width: 100%;
|
||||
position: fixed;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
|
||||
&--left-top {
|
||||
top: -40rpx;
|
||||
left: 20rpx;
|
||||
}
|
||||
|
||||
&--right-top {
|
||||
top: -40rpx;
|
||||
right: 40rpx;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
left: 50%;
|
||||
bottom: -40rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $tn-mask-bg-color;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transform: scale3d(1, 1, 0);
|
||||
transition: all 0.25s ease-in;
|
||||
|
||||
&--show {
|
||||
opacity: 1 !important;
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user