Compare commits
81 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a1c08ebcc4 | |
|
|
9f0318bd9e | |
|
|
a6b8858927 | |
|
|
0860028c87 | |
|
|
9dfc035915 | |
|
|
9b29f5e7fb | |
|
|
87015949a4 | |
|
|
5ed27eef4f | |
|
|
835a054a8c | |
|
|
4ac3178b04 | |
|
|
11feb967cc | |
|
|
a7df117a65 | |
|
|
1b8112880d | |
|
|
55c4f454cf | |
|
|
c69c033703 | |
|
|
b7705ae680 | |
|
|
a5718c6ea0 | |
|
|
84dadc658a | |
|
|
93edf160e2 | |
|
|
e679b97a68 | |
|
|
551e5bcbd8 | |
|
|
ecad105f36 | |
|
|
81d08dbd56 | |
|
|
2c053e9f06 | |
|
|
bdae6b8390 | |
|
|
866a91e3cd | |
|
|
5c8ef01682 | |
|
|
55da5697d5 | |
|
|
c3b30deadb | |
|
|
adc5a2e41f | |
|
|
42311198bf | |
|
|
100e916332 | |
|
|
0ecae4ec9e | |
|
|
1732901bf9 | |
|
|
442ba49107 | |
|
|
f2eb371de4 | |
|
|
58f7b62a21 | |
|
|
91c75b6700 | |
|
|
d7941db749 | |
|
|
fe134407d3 | |
|
|
a885f458bc | |
|
|
375221af16 | |
|
|
551a21c7c2 | |
|
|
9aa917022d | |
|
|
8a6ef40cda | |
|
|
b4cd127ff2 | |
|
|
6980614cbc | |
|
|
4ee91d99b0 | |
|
|
8920d6117b | |
|
|
098156f558 | |
|
|
b532cc9a93 | |
|
|
0847b9490e | |
|
|
4154ebe70e | |
|
|
fdec89c4cd | |
|
|
c62ad0803c | |
|
|
015ce8616f | |
|
|
649b7482d0 | |
|
|
ad2d4a867a | |
|
|
04081fbc87 | |
|
|
7df093928c | |
|
|
a1f0644119 | |
|
|
acf70622f2 | |
|
|
384112c81f | |
|
|
b88ebaeeee | |
|
|
077ee218b5 | |
|
|
a174606e2b | |
|
|
982a5738df | |
|
|
38043f074d | |
|
|
a8d60b5829 | |
|
|
4457402aa4 | |
|
|
d299d24fde | |
|
|
82266e0b56 | |
|
|
6c0983c7c4 | |
|
|
3a157c1e07 | |
|
|
b5d2c3c768 | |
|
|
af58a8dd29 | |
|
|
9353c6442f | |
|
|
efcd82f4ee | |
|
|
7f3f9475bb | |
|
|
4a1a5ef2fc | |
|
|
114f47d126 |
46
App.vue
|
|
@ -3,10 +3,6 @@ import config from './config'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import mqttUtil from '@/utils/mqtt'
|
import mqttUtil from '@/utils/mqtt'
|
||||||
import {startMqttOnlinePing, stopMqttOnlinePing} from "./utils/mqtt";
|
import {startMqttOnlinePing, stopMqttOnlinePing} from "./utils/mqtt";
|
||||||
import {batchSubscribe} from "./api/system/mqtt";
|
|
||||||
import store from "store";
|
|
||||||
import {listAgri} from "./api/system/assets/agri";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
subscribeList:[],
|
subscribeList:[],
|
||||||
globalData: {
|
globalData: {
|
||||||
|
|
@ -60,7 +56,6 @@ export default {
|
||||||
console.log('小程序切前台/首次显示')
|
console.log('小程序切前台/首次显示')
|
||||||
const token = getToken() || this.globalData.mqtt.token
|
const token = getToken() || this.globalData.mqtt.token
|
||||||
if (token) {
|
if (token) {
|
||||||
this.$tab.reLaunch('/pages/control/index')
|
|
||||||
// 兜底检查:如果globalData里没有列表,但缓存里有,补充恢复
|
// 兜底检查:如果globalData里没有列表,但缓存里有,补充恢复
|
||||||
if (this.globalData.mqtt.subscribeList.length === 0) {
|
if (this.globalData.mqtt.subscribeList.length === 0) {
|
||||||
const savedSubscribeList = uni.getStorageSync('mqtt_subscribe_list')
|
const savedSubscribeList = uni.getStorageSync('mqtt_subscribe_list')
|
||||||
|
|
@ -124,27 +119,7 @@ export default {
|
||||||
console.error('MQTT连接失败')
|
console.error('MQTT连接失败')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var clientId = mqttUtil.getMqttState().clientId;
|
mqttUtil.updateSubscribeTopic();
|
||||||
this.getSubscribeImei(clientId).then(res=>{
|
|
||||||
console.info("subscribeList",res)
|
|
||||||
const subscribeList = res;
|
|
||||||
this.globalData.mqtt.subscribeList = subscribeList || []
|
|
||||||
batchSubscribe({clientId: clientId}).then((result) => {
|
|
||||||
if (result.code === 200) {
|
|
||||||
console.info(`设备列表订阅成功:${subscribeList}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// ========== 新增:登录成功时同步到localStorage ==========
|
|
||||||
uni.setStorageSync('mqtt_subscribe_list', subscribeList || [])
|
|
||||||
|
|
||||||
if (subscribeList.length > 0) {
|
|
||||||
mqttUtil.updateSubscribeList(subscribeList)
|
|
||||||
console.log('恢复MQTT订阅列表:', subscribeList)
|
|
||||||
// ========== 新增:恢复订阅后同步到localStorage ==========
|
|
||||||
uni.setStorageSync('mqtt_subscribe_list', subscribeList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
loginSuccess(token) {
|
loginSuccess(token) {
|
||||||
this.globalData.mqtt.hasLogin = true
|
this.globalData.mqtt.hasLogin = true
|
||||||
|
|
@ -164,21 +139,6 @@ export default {
|
||||||
uni.removeStorageSync('mqtt_subscribe_list')
|
uni.removeStorageSync('mqtt_subscribe_list')
|
||||||
console.log('登出成功,MQTT已断开')
|
console.log('登出成功,MQTT已断开')
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubscribeImei(clientId) {
|
|
||||||
return listAgri().then(response => {
|
|
||||||
const subscribeList = [];
|
|
||||||
if (response.code === 200) {
|
|
||||||
response.rows.forEach(item =>
|
|
||||||
subscribeList.push(`frontend/${clientId}/dtu/${item.imei}/+`)
|
|
||||||
);
|
|
||||||
if (store.getters && store.getters.name === 'admin') {
|
|
||||||
subscribeList.push(`frontend/${clientId}/dtu/862538065276061/+`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return subscribeList;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// token过期处理逻辑(核心)
|
// token过期处理逻辑(核心)
|
||||||
handleTokenExpired() {
|
handleTokenExpired() {
|
||||||
|
|
||||||
|
|
@ -257,5 +217,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '@/static/scss/index.scss'
|
// 加上图鸟会全变白
|
||||||
|
@import './tuniao-ui/iconfont.css';
|
||||||
|
@import '@/static/scss/index.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询卷膜运行条件列表
|
||||||
|
export function listAutoTerm(query) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询卷膜运行条件详细
|
||||||
|
export function getAutoTerm(id) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增卷膜运行条件
|
||||||
|
export function addAutoTerm(data) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改卷膜运行条件
|
||||||
|
export function updateAutoTerm(data) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除卷膜运行条件
|
||||||
|
export function delAutoTerm(id) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAgriTerm(imei) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm/getAgriTerm/' + imei,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAgriTerm(data) {
|
||||||
|
return request({
|
||||||
|
url: '/control/autoTerm/saveAgriTerm',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询卷膜参数配置列表
|
||||||
|
export function listRollerParam(query) {
|
||||||
|
return request({
|
||||||
|
url: '/control/rollerParam/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询卷膜参数配置详细
|
||||||
|
export function getRollerParam(id) {
|
||||||
|
return request({
|
||||||
|
url: '/control/rollerParam/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增卷膜参数配置
|
||||||
|
export function addRollerParam(data) {
|
||||||
|
return request({
|
||||||
|
url: '/control/rollerParam',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改卷膜参数配置
|
||||||
|
export function updateRollerParam(data) {
|
||||||
|
return request({
|
||||||
|
url: '/control/rollerParam',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除卷膜参数配置
|
||||||
|
export function delRollerParam(id) {
|
||||||
|
return request({
|
||||||
|
url: '/control/rollerParam/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,13 @@ export function getAgri(id) {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
export function getAgriInfo(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/agri/findAgriInfoByUser',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 新增大棚管理
|
// 新增大棚管理
|
||||||
export function addAgri(data) {
|
export function addAgri(data) {
|
||||||
|
|
@ -42,3 +49,23 @@ export function delAgri(id) {
|
||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动端添加大棚
|
||||||
|
* @param data
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export function addAgriMobile(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/agri/addAgriFromMobile',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function switchAgriMode(imei, code) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/agri/switchAgriMode/' + imei,
|
||||||
|
method: 'post',
|
||||||
|
params: code
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询大棚功能执行时间限位列表
|
||||||
|
export function listLimit_bak(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询大棚功能执行时间限位详细
|
||||||
|
export function getLimit_bak(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增大棚功能执行时间限位
|
||||||
|
export function addLimit_bak(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改大棚功能执行时间限位
|
||||||
|
export function updateLimit_bak(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除大棚功能执行时间限位
|
||||||
|
export function delLimit_bak(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function getAgriLimitByImei(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/getAgriLimitByImei/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAgriLimit(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/saveAgriLimit',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询dtu设备备注列表
|
||||||
|
export function listRemark_bak(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询dtu设备备注详细
|
||||||
|
export function getRemark_bak(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增dtu设备备注
|
||||||
|
export function addRemark_bak(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改dtu设备备注
|
||||||
|
export function updateRemark_bak(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除dtu设备备注
|
||||||
|
export function delRemark_bak(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDtuRemarkByImei(imei) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/remark_bak/getDtuRemarkByImei',
|
||||||
|
method: 'get',
|
||||||
|
params: imei
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAgriRemark(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/limit_bak/saveAgriRemark',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询自动化卷膜风口大小设置列表
|
||||||
|
export function listAir(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/air/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询自动化卷膜风口大小设置详细
|
||||||
|
export function getAir(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/air/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增自动化卷膜风口大小设置
|
||||||
|
export function addAir(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/air',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改自动化卷膜风口大小设置
|
||||||
|
export function updateAir(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/air',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除自动化卷膜风口大小设置
|
||||||
|
export function delAir(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/air/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询大棚信息(用户-设备关联)列表
|
||||||
|
export function listUserAgri(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询大棚信息(用户-设备关联)详细
|
||||||
|
export function getUserAgri(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function removeAgri(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/removeAgri',
|
||||||
|
method: 'delete',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 新增大棚信息(用户-设备关联)
|
||||||
|
export function addUserAgri(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改大棚信息(用户-设备关联)
|
||||||
|
export function updateUserAgri(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除大棚信息(用户-设备关联)
|
||||||
|
export function delUserAgri(id) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findAgriUser(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/findAgriUser',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findAllUser(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/findAllUser',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function batchAssociaUser(data) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/userAgri/batchAssociaUser',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -47,3 +47,12 @@ export function status() {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getAgriStatus(data) {
|
||||||
|
return request({
|
||||||
|
url: '/api/mqtt/getAgriStatus',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
Animation 微动画
|
||||||
|
基于ColorUI-GA组建库的动画模块
|
||||||
|
*/
|
||||||
|
.gif-black {
|
||||||
|
mix-blend-mode: screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gif-white {
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation css */
|
||||||
|
[class*='animation-'] {
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-fade {
|
||||||
|
animation-name: fade;
|
||||||
|
animation-duration: 0.8s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-scale-up {
|
||||||
|
animation-name: scale-up;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-scale-down {
|
||||||
|
animation-name: scale-down;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-slide-top {
|
||||||
|
animation-name: slide-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-slide-bottom {
|
||||||
|
animation-name: slide-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-slide-left {
|
||||||
|
animation-name: slide-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-slide-right {
|
||||||
|
animation-name: slide-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-shake {
|
||||||
|
animation-name: shake;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-reverse {
|
||||||
|
animation-direction: reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-up {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-down {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-top {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-bottom {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
10% {
|
||||||
|
transform: translateX(-9px);
|
||||||
|
}
|
||||||
|
|
||||||
|
20% {
|
||||||
|
transform: translateX(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: translateX(-7px);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateX(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: translateX(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
90% {
|
||||||
|
transform: translateX(-1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-left {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-right {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,507 @@
|
||||||
|
<template>
|
||||||
|
<!-- colorui/components/calendar/calendar.wxml -->
|
||||||
|
<view class="calendar">
|
||||||
|
<view class="title flex">
|
||||||
|
<view class="flex">
|
||||||
|
<picker :value="selectDay.year + '-' + selectDay.month" @change="editMonth" mode="date" fields="month" class="year-month">
|
||||||
|
{{ selectDay.year }}.{{ selectDay.month > 9 ? selectDay.month : '0' + selectDay.month }}
|
||||||
|
</picker>
|
||||||
|
<view class="icon" @tap="lastMonth" style="transform: rotate(180deg)">
|
||||||
|
<view class="iconfont icon-playfill"></view>
|
||||||
|
</view>
|
||||||
|
<view class="icon" @tap="nextMonth">
|
||||||
|
<view class="iconfont icon-playfill"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view @tap.stop.prevent="openChange" class="flex open">
|
||||||
|
<view>{{ open ? '收起' : '展开' }}</view>
|
||||||
|
<view style="margin-left: 6rpx; font-size: 20rpx" :class="'iconfont icon-' + (open ? 'fold' : 'unfold')"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历头部 -->
|
||||||
|
<view class="flex-around calendar-week">
|
||||||
|
<view class="view">日</view>
|
||||||
|
<view class="view">一</view>
|
||||||
|
<view class="view">二</view>
|
||||||
|
<view class="view">三</view>
|
||||||
|
<view class="view">四</view>
|
||||||
|
<view class="view">五</view>
|
||||||
|
<view class="view">六</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历主体 -->
|
||||||
|
<view class="flex-start flex-wrap calendar-main" :style="'height:' + (dateList.length / 7) * 92 + 'rpx'">
|
||||||
|
<view class="day" v-for="(item, index) in dateList" :key="index">
|
||||||
|
<view
|
||||||
|
:class="'bg ' + (item.year === selectDay.year && item.month === selectDay.month ? (item.day === selectDay.day ? 'select' : '') : 'other-month')"
|
||||||
|
@tap.stop.prevent="selectChange"
|
||||||
|
:data-day="item.day"
|
||||||
|
:data-year="item.year"
|
||||||
|
:data-month="item.month"
|
||||||
|
:data-date-string="item.dateString"
|
||||||
|
>
|
||||||
|
{{ item.day }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="spot" v-if="item.spot"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// colorui/components/calendar/calendar.js
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dateList: [],
|
||||||
|
|
||||||
|
//日历主体渲染数组
|
||||||
|
//选中时间
|
||||||
|
selectDay: {
|
||||||
|
year: '',
|
||||||
|
month: '',
|
||||||
|
day: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
open: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
spot: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
defaultTime: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {
|
||||||
|
attached() {
|
||||||
|
if (this.defaultTime) {
|
||||||
|
let now = new Date(this.defaultTime);
|
||||||
|
} else {
|
||||||
|
let now = new Date();
|
||||||
|
}
|
||||||
|
let selectDay = {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: now.getMonth() + 1,
|
||||||
|
day: now.getDate(),
|
||||||
|
dateString: this.formatTime(now, 'Y-M-D')
|
||||||
|
};
|
||||||
|
this.setMonth(selectDay.year, selectDay.month, selectDay.day);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间戳转化为年 月 日 时 分 秒
|
||||||
|
* time: 需要被格式化的时间,可以被new Date()解析即可
|
||||||
|
* format:格式化之后返回的格式,年月日时分秒分别为Y, M, D, h, m, s,这个参数不填的话则显示多久前
|
||||||
|
*/
|
||||||
|
formatTime(time, format) {
|
||||||
|
function formatNumber(n) {
|
||||||
|
n = n.toString();
|
||||||
|
return n[1] ? n : '0' + n;
|
||||||
|
}
|
||||||
|
function getDate(time, format) {
|
||||||
|
const formateArr = ['Y', 'M', 'D', 'h', 'm', 's'];
|
||||||
|
const returnArr = [];
|
||||||
|
const date = new Date(time);
|
||||||
|
returnArr.push(date.getFullYear());
|
||||||
|
returnArr.push(formatNumber(date.getMonth() + 1));
|
||||||
|
returnArr.push(formatNumber(date.getDate()));
|
||||||
|
returnArr.push(formatNumber(date.getHours()));
|
||||||
|
returnArr.push(formatNumber(date.getMinutes()));
|
||||||
|
returnArr.push(formatNumber(date.getSeconds()));
|
||||||
|
for (const i in returnArr) {
|
||||||
|
format = format.replace(formateArr[i], returnArr[i]);
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
function getDateDiff(time) {
|
||||||
|
let r = '';
|
||||||
|
const ft = new Date(time);
|
||||||
|
const nt = new Date();
|
||||||
|
const nd = new Date(nt);
|
||||||
|
nd.setHours(23);
|
||||||
|
nd.setMinutes(59);
|
||||||
|
nd.setSeconds(59);
|
||||||
|
nd.setMilliseconds(999);
|
||||||
|
const d = parseInt((nd - ft) / 86400000);
|
||||||
|
switch (true) {
|
||||||
|
case d === 0:
|
||||||
|
const t = parseInt(nt / 1000) - parseInt(ft / 1000);
|
||||||
|
switch (true) {
|
||||||
|
case t < 60:
|
||||||
|
r = '刚刚';
|
||||||
|
break;
|
||||||
|
case t < 3600:
|
||||||
|
r = parseInt(t / 60) + '分钟前';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = parseInt(t / 3600) + '小时前';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case d === 1:
|
||||||
|
r = '昨天';
|
||||||
|
break;
|
||||||
|
case d === 2:
|
||||||
|
r = '前天';
|
||||||
|
break;
|
||||||
|
case d > 2 && d < 30:
|
||||||
|
r = d + '天前';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = getDate(time, 'Y-M-D');
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (!format) {
|
||||||
|
return getDateDiff(time);
|
||||||
|
} else {
|
||||||
|
return getDate(time, format);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//picker设置月份
|
||||||
|
editMonth(e) {
|
||||||
|
const arr = e.detail.value.split('-');
|
||||||
|
const year = parseInt(arr[0]);
|
||||||
|
const month = parseInt(arr[1]);
|
||||||
|
this.setMonth(year, month);
|
||||||
|
},
|
||||||
|
|
||||||
|
//上月切换按钮点击
|
||||||
|
lastMonth() {
|
||||||
|
const lastMonth = new Date(this.selectDay.year, this.selectDay.month - 2);
|
||||||
|
const year = lastMonth.getFullYear();
|
||||||
|
const month = lastMonth.getMonth() + 1;
|
||||||
|
this.setMonth(year, month);
|
||||||
|
},
|
||||||
|
|
||||||
|
//下月切换按钮点击
|
||||||
|
nextMonth() {
|
||||||
|
const nextMonth = new Date(this.selectDay.year, this.selectDay.month);
|
||||||
|
const year = nextMonth.getFullYear();
|
||||||
|
const month = nextMonth.getMonth() + 1;
|
||||||
|
this.setMonth(year, month);
|
||||||
|
},
|
||||||
|
|
||||||
|
//设置月份
|
||||||
|
setMonth(setYear, setMonth, setDay) {
|
||||||
|
if (this.selectDay.year !== setYear || this.selectDay.month !== setMonth) {
|
||||||
|
const day = Math.min(new Date(setYear, setMonth, 0).getDate(), this.selectDay.day);
|
||||||
|
const time = new Date(setYear, setMonth - 1, setDay ? setDay : day);
|
||||||
|
const data = {
|
||||||
|
selectDay: {
|
||||||
|
year: setYear,
|
||||||
|
month: setMonth,
|
||||||
|
day: setDay ? setDay : day,
|
||||||
|
dateString: this.formatTime(time, 'Y-M-D')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!setDay) {
|
||||||
|
data.open = true;
|
||||||
|
}
|
||||||
|
this.setData(data);
|
||||||
|
this.dateInit(setYear, setMonth);
|
||||||
|
this.setSpot();
|
||||||
|
this.$emit('change', {
|
||||||
|
detail: this.selectDay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//展开收起
|
||||||
|
openChange() {
|
||||||
|
this.setData({
|
||||||
|
open: !this.open
|
||||||
|
});
|
||||||
|
this.$emit('aaa', {
|
||||||
|
detail: {
|
||||||
|
a: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.dateInit();
|
||||||
|
this.setSpot();
|
||||||
|
},
|
||||||
|
|
||||||
|
//设置日历底下是否展示小圆点
|
||||||
|
setSpot() {
|
||||||
|
const timeArr = this.spot.map((item) => {
|
||||||
|
return this.formatTime(item, 'Y-M-D');
|
||||||
|
});
|
||||||
|
this.dateList.forEach((item) => {
|
||||||
|
if (timeArr.indexOf(item.dateString) !== -1) {
|
||||||
|
item.spot = true;
|
||||||
|
} else {
|
||||||
|
item.spot = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setData({
|
||||||
|
dateList: this.dateList
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//日历主体的渲染方法
|
||||||
|
dateInit(setYear = this.selectDay.year, setMonth = this.selectDay.month) {
|
||||||
|
let dateList = []; //需要遍历的日历数组数据
|
||||||
|
let now = new Date(setYear, setMonth - 1); //当前月份的1号
|
||||||
|
let startWeek = now.getDay(); //目标月1号对应的星期
|
||||||
|
let dayNum = new Date(setYear, setMonth, 0).getDate(); //当前月有多少天
|
||||||
|
let forNum = Math.ceil((startWeek + dayNum) / 7) * 7; //当前月跨越的周数
|
||||||
|
if (this.open) {
|
||||||
|
//展开状态,需要渲染完整的月份
|
||||||
|
for (let i = 0; i < forNum; i++) {
|
||||||
|
const now2 = new Date(now);
|
||||||
|
now2.setDate(i - startWeek + 1);
|
||||||
|
let obj = {};
|
||||||
|
obj = {
|
||||||
|
day: now2.getDate(),
|
||||||
|
month: now2.getMonth() + 1,
|
||||||
|
year: now2.getFullYear(),
|
||||||
|
dateString: this.formatTime(now2, 'Y-M-D')
|
||||||
|
};
|
||||||
|
dateList[i] = obj;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//非展开状态,只需要渲染当前周
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const now2 = new Date(now);
|
||||||
|
//当前周的7天
|
||||||
|
now2.setDate(Math.ceil((this.selectDay.day + startWeek) / 7) * 7 - 6 - startWeek + i);
|
||||||
|
let obj = {};
|
||||||
|
obj = {
|
||||||
|
day: now2.getDate(),
|
||||||
|
month: now2.getMonth() + 1,
|
||||||
|
year: now2.getFullYear(),
|
||||||
|
dateString: this.formatTime(now2, 'Y-M-D')
|
||||||
|
};
|
||||||
|
dateList[i] = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setData({
|
||||||
|
dateList: dateList
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//一天被点击时
|
||||||
|
selectChange(e) {
|
||||||
|
const year = e.currentTarget.dataset.year;
|
||||||
|
const month = e.currentTarget.dataset.month;
|
||||||
|
const day = e.currentTarget.dataset.day;
|
||||||
|
const dateString = e.currentTarget.dataset.dateString;
|
||||||
|
const selectDay = {
|
||||||
|
year: year,
|
||||||
|
month: month,
|
||||||
|
day: day,
|
||||||
|
dateString: dateString
|
||||||
|
};
|
||||||
|
if (this.selectDay.year !== year || this.selectDay.month !== month) {
|
||||||
|
this.setMonth(year, month, day);
|
||||||
|
} else if (this.selectDay.day !== day) {
|
||||||
|
this.setData({
|
||||||
|
selectDay: selectDay
|
||||||
|
});
|
||||||
|
this.$emit('change', {
|
||||||
|
detail: this.selectDay
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
// 处理小程序 attached 生命周期
|
||||||
|
this.attached();
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
spot: function (spot) {
|
||||||
|
this.setSpot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function () {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* colorui/components/calendar/calendar.wxss */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'iconfont';
|
||||||
|
/* IE9 */
|
||||||
|
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAL8AAsAAAAABxQAAAKvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqBVIFEATYCJAMQCwoABCAFhG0HQhswBsgekiRFQgQU8AMAmmCIh3/HXu9L8hFVWwXoyALpqqoqwAEfD6g66abEPBux/13TPwBUeZ0lAldI75LS0ctlzLY8NwVk3NQm5Co3lqhaWPE+z+X0pi+Q1bez3MZatCYtPuoFGAcU4F7UiyIroBPkltpFHBi88q89JtA0p1jEYe/wNMQryKpAPHbYMMT3CooStetCdeZgEd9U1NPX9ADgQf8+/kFlxJNUMrLx5LpHhPaf+GUsavP/JpxJQpHgHq8gYx0oxO1s41yUiMQoTWOibQN1dYmf+Dn0MhYO2zgfttC/PEKSiSrS3QabXjPzE3ON4GdII/FrrEsGhPjr/dYGcOyzM56gg6PM1BXvcbZIxY7zzRyS4znJs/m2VmjuRNq9edF1kcx9FbW85/nRgbXX6s5q/AX+vsGh/kBhUUHAYszwFx4acaAA6l7k9bgjo8Vbue4acDcbCqFqcBru+htf4l/I4EnzzQAa8lT+kNoAyG/TJuTdyDcsG+NB4r05rfm73irgx/jRrw4XMB+gyga24dQGHCqlg8RKyZ3cWuWLEWhFQlMTmOwwFLrCPwH3E+omB/I1sy2yunmysOuoaNlEVd0Omtb0HW8ZYaJEaWDVnoPQd4ek6zuyvkeysE+omPpCVT+i0HQaWRe2LIYjsYsRzImIrEvIIagKZQmeaHoniW1Owqzr0NIsYbqjjCpKy4ftPqIQdsUWfd5WyTlFlKky6nWeI5KkIo2pTiLwUjvnWmNZGZ37UKmgypDoAENgHCFCrJYgDgKVgsrxXOLo+5MImzkSjK3oFuazCEbnmB6pUKocQPZ5FZDuV97RzbOpxHEUQjEqGdLrzCMkEhWizc9zIgRcKfuBmqZRGR1Fod7S5/3yH56AJnJDiRQ5SlRU3yhUlcRoMStek/ASdUgSAAA=')
|
||||||
|
format('woff2');
|
||||||
|
/* iOS 4.1- */
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family: 'iconfont' !important;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-unfold:before {
|
||||||
|
content: '\e661';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-fold:before {
|
||||||
|
content: '\e6de';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-playfill:before {
|
||||||
|
content: '\e74f';
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-start {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-end {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-around {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-stretch {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333;
|
||||||
|
padding: 30rpx;
|
||||||
|
line-height: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .title .year-month {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .title .icon {
|
||||||
|
padding: 0 16rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .title .open {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
color: #999;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
padding: 0 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-week {
|
||||||
|
line-height: 40rpx;
|
||||||
|
padding: 0 25rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-week .view {
|
||||||
|
width: 100rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main {
|
||||||
|
padding: 30rpx 25rpx;
|
||||||
|
transition: height 0.3s;
|
||||||
|
align-content: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main .day {
|
||||||
|
position: relative;
|
||||||
|
width: 100rpx;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
height: 66rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main .day .bg {
|
||||||
|
height: 56rpx;
|
||||||
|
line-height: 56rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main .day .select {
|
||||||
|
width: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(-60deg, #0fdac5, #1bc7b0);
|
||||||
|
box-shadow: 0px 5px 16px 0px #c6f3ed;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main .day .other-month {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar .calendar-main .day .spot {
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background-color: #1dcdb8;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 6rpx auto 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
<template>
|
||||||
|
<view style="height: 100%">
|
||||||
|
<!-- colorui/components/canvas2d-ring/canvas2d-ring.wxml -->
|
||||||
|
<canvas :style="'width:' + canvasWidth + 'px;height:' + canvasWidth + 'px; position:relative'" type="2d" id="myCanvas">
|
||||||
|
<view class="circle-bar" :style="'height:' + canvasWidth + 'px;'">
|
||||||
|
<view class="title_name">
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="title_val"
|
||||||
|
:style="'color: ' + valueColor + '; font-weight:' + f_weight + '; margin-top:' + (show_tip ? '10' : '0') + 'rpx;font-size:' + f_size + 'px'"
|
||||||
|
>
|
||||||
|
{{ value }} {{ suffix }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</canvas>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// colorui/components/canvas2d-ring/canvas2d-ring.js
|
||||||
|
var windWidth = uni.getSystemInfoSync().windowWidth;
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
canvasWidth: ' windWidth * 0.4',
|
||||||
|
show_tip: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
//画布的宽度 默认占屏幕宽度的0.4倍
|
||||||
|
canvasWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: windWidth * 0.4
|
||||||
|
},
|
||||||
|
//线条宽度 默认10
|
||||||
|
lineWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
//线条颜色
|
||||||
|
lineColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#3696FA'
|
||||||
|
},
|
||||||
|
//标题 默认“完成率”
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '完成率'
|
||||||
|
},
|
||||||
|
//当前的值 默认45
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 45
|
||||||
|
},
|
||||||
|
//值的颜色 默认""
|
||||||
|
valueColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#333'
|
||||||
|
},
|
||||||
|
//值的字体的大小颜色 默认28rpx
|
||||||
|
f_size: {
|
||||||
|
type: Number,
|
||||||
|
default: 14
|
||||||
|
},
|
||||||
|
f_weight: {
|
||||||
|
type: String,
|
||||||
|
default: '500'
|
||||||
|
},
|
||||||
|
//最大值 默认100
|
||||||
|
maxValue: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
//最小值 默认0
|
||||||
|
minValue: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
//当前值的后缀名
|
||||||
|
suffix: {
|
||||||
|
type: null,
|
||||||
|
default: '%'
|
||||||
|
},
|
||||||
|
//从什么角度开始 0~360之间 (12点方向为0,18点方向为180,0点方向为360)
|
||||||
|
startDegree: {
|
||||||
|
type: Number,
|
||||||
|
default: 180
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {
|
||||||
|
showCanvasRing() {
|
||||||
|
//没标题的时候去掉margin-top的值
|
||||||
|
if (this.title.replace(/(^\s*)|(\s*$)/g, '').length == 0) {
|
||||||
|
this.setData({
|
||||||
|
show_tip: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//canvas 2d
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query
|
||||||
|
.select('#myCanvas')
|
||||||
|
.fields({
|
||||||
|
node: true,
|
||||||
|
size: true
|
||||||
|
})
|
||||||
|
.exec(this.init.bind(this));
|
||||||
|
},
|
||||||
|
init(res) {
|
||||||
|
const canvas = res[0].node;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||||
|
canvas.width = res[0].width * dpr;
|
||||||
|
canvas.height = res[0].height * dpr;
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
// 大小值的计算
|
||||||
|
var circle_r = this.canvasWidth / 2; //画布的一半,用来找中心点和半径
|
||||||
|
var startDegree = this.startDegree; //从什么角度开始
|
||||||
|
var maxValue = this.maxValue; //最大值
|
||||||
|
var minValue = this.minValue; //最小值
|
||||||
|
var value = this.value; //当前的值
|
||||||
|
var lineColor = this.lineColor; //线条颜色
|
||||||
|
var lineWidth = this.lineWidth; //线条宽度
|
||||||
|
var percent = 360 * ((value - minValue) / (maxValue - minValue)); //计算结果
|
||||||
|
|
||||||
|
//定义起始点
|
||||||
|
ctx.translate(circle_r, circle_r);
|
||||||
|
//灰色圆弧
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = '#ebebeb';
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.arc(0, 0, circle_r - 10, 0, 2 * Math.PI, true);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
//有色彩的圆弧
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = lineColor;
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.arc(0, 0, circle_r - 10, (startDegree * Math.PI) / 180 - 0.5 * Math.PI, (percent * Math.PI) / 180 + (startDegree * Math.PI) / 180 - 0.5 * Math.PI, false);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* colorui/components/canvas2d-ring/canvas2d-ring.wxss */
|
||||||
|
.circle-bar {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-bar .title_name {
|
||||||
|
max-height: 62rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
.circle-bar .title_val {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
|
||||||
|
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
|
||||||
|
<view class="action" @tap="BackPage" v-if="isBack">
|
||||||
|
<text class="cuIcon-back"></text>
|
||||||
|
<slot name="backText"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="content" :style="[{top:StatusBar + 'px'}]">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
StatusBar: this.StatusBar,
|
||||||
|
CustomBar: this.CustomBar
|
||||||
|
};
|
||||||
|
},
|
||||||
|
name: 'cu-custom',
|
||||||
|
computed: {
|
||||||
|
style() {
|
||||||
|
var StatusBar= this.StatusBar;
|
||||||
|
var CustomBar= this.CustomBar;
|
||||||
|
var bgImage = this.bgImage;
|
||||||
|
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
|
||||||
|
if (this.bgImage) {
|
||||||
|
style = `${style}background-image:url(${bgImage});`;
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isBack: {
|
||||||
|
type: [Boolean, String],
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
bgImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
BackPage() {
|
||||||
|
if (getCurrentPages().length < 2 && 'undefined' !== typeof __wxConfig) {
|
||||||
|
let url = '/' + __wxConfig.pages[0]
|
||||||
|
return uni.redirectTo({url})
|
||||||
|
}
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<template>
|
||||||
|
<!-- colorui/components/cu-custom.wxml -->
|
||||||
|
<view :class="'cu-custom ' + (isLucency == true ? 'lucency' : '')" :style="'height:' + (isLucency == true ? 0 : CustomBar) + 'px'">
|
||||||
|
<view
|
||||||
|
:class="'cu-bar ' + (noFixed ? '' : 'fixed') + ' ' + (bgImage != '' ? 'none-bg text-white bg-img' : '') + ' ' + bgColor"
|
||||||
|
:style="'height:' + CustomBar + 'px;padding-top:' + StatusBar + 'px;' + (bgImage ? 'background-image:url( + bgImage+)' : '')"
|
||||||
|
>
|
||||||
|
<view class="action" @tap="BackPage" v-if="isBack && mode != 'singlePage'">
|
||||||
|
<text class="cuIcon-back"></text>
|
||||||
|
<slot name="backText"></slot>
|
||||||
|
</view>
|
||||||
|
<view class="action" @tap="toHome" :data-url="homePage" v-if="isBack && mode == 'singlePage'">
|
||||||
|
<text class="cuIcon-home padding-left-sm"></text>
|
||||||
|
<slot name="homeText"></slot>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="action border-custom"
|
||||||
|
v-if="isCustom"
|
||||||
|
:style="'width:' + Custom.width + 'px;height:' + Custom.height + 'px;margin-left:calc(750rpx - ' + Custom.right + 'px)'"
|
||||||
|
>
|
||||||
|
<text class="cuIcon-back" @tap="BackPage"></text>
|
||||||
|
<text class="cuIcon-homefill" @tap="toHome" :data-url="homePage"></text>
|
||||||
|
</view>
|
||||||
|
<view class="content" :style="'top:' + StatusBar + 'px'">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</view>
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// colorui/components/cu-custom.js
|
||||||
|
const app = getApp();
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
StatusBar: app.globalData.StatusBar,
|
||||||
|
CustomBar: app.globalData.CustomBar,
|
||||||
|
Custom: app.globalData.Custom,
|
||||||
|
mode: 'default'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的一些选项
|
||||||
|
*/
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true,
|
||||||
|
multipleSlots: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isCustom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isBack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
bgImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
isLucency: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
noFixed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
homePage: {
|
||||||
|
type: String,
|
||||||
|
default: '/pages/index/index'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {
|
||||||
|
handlePageShow() {
|
||||||
|
this.getInfo();
|
||||||
|
},
|
||||||
|
|
||||||
|
BackPage() {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
toHome(e) {
|
||||||
|
if (e.currentTarget.dataset.url != '') {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: e.currentTarget.dataset.url
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInfo() {
|
||||||
|
var that = this;
|
||||||
|
if (getCurrentPages().length === 1) {
|
||||||
|
that.setData({
|
||||||
|
mode: 'singlePage'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* colorui/components/cu-custom.wxss */
|
||||||
|
.lucency {
|
||||||
|
/* position: absolute;
|
||||||
|
z-index: 1; */
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
<template>
|
||||||
|
<view style="height: 100%">
|
||||||
|
<!-- colorui/components/skeleton/skeleton.wxml -->
|
||||||
|
<view v-if="loading" class="box padding">
|
||||||
|
<view v-if="avatar" class="bg avatar"></view>
|
||||||
|
<view class="column">
|
||||||
|
<view
|
||||||
|
:class="(active ? 'active' : '') + ' bg list'"
|
||||||
|
:style="'width: ' + columnWidth[index] + ';height:' + (index + 1 == column && endHeight ? endHeight : '')"
|
||||||
|
v-for="(item, index) in column"
|
||||||
|
:key="index"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<slot v-else />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// colorui/components/skeleton/skeleton.js
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
addGlobalClass: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
columnWidth: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
endHeight: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
avatar: Boolean
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {},
|
||||||
|
created: function () {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/* colorui/components/skeleton/skeleton.wxss */
|
||||||
|
.box {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column > view {
|
||||||
|
height: 36rpx;
|
||||||
|
margin-bottom: 15rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
|
||||||
|
background-size: 400% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
animation: loading 1.4s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 0 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 扩展头像功能 */
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
float: left;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* 暗黑模式下应用的样式 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
page {
|
||||||
|
/* 暗黑模式背景色 */
|
||||||
|
--darkBlack: #111111;
|
||||||
|
--lightGray: #222222;
|
||||||
|
--darkGray: #333333;
|
||||||
|
/* 暗黑模式按钮颜色 */
|
||||||
|
--btnGray: #444444;
|
||||||
|
/* 暗黑模式字体颜色 */
|
||||||
|
--darkTitle: rgba(255, 255, 255, 0.85);
|
||||||
|
--darkPrimaryText: rgba(255, 255, 255, 0.65);
|
||||||
|
--darkSecondaryText: rgba(255, 255, 255, 0.45);
|
||||||
|
--darkDisable: rgba(255, 255, 255, 0.1);
|
||||||
|
/* 暗黑模式渐变色 */
|
||||||
|
--darkGradualRed: linear-gradient(45deg, #f43f3b, #ec008c);
|
||||||
|
--darkGradualOrange: linear-gradient(45deg, #ff9700, #ed1c24);
|
||||||
|
--darkGradualGreen: linear-gradient(45deg, #39b54a, #8dc63f);
|
||||||
|
--darkGradualPurple: linear-gradient(45deg, #9000ff, #5e00ff);
|
||||||
|
--darkGradualPink: linear-gradient(45deg, #ec008c, #6739b6);
|
||||||
|
--darkGradualBlue: linear-gradient(45deg, #112a45, #1765ad);
|
||||||
|
/* 阴影透明色 */
|
||||||
|
--ShadowSize: 6rpx 6rpx 8rpx;
|
||||||
|
--redShadow: rgba(204, 69, 59, 0.8);
|
||||||
|
--orangeShadow: rgba(217, 109, 26, 0.8);
|
||||||
|
--yellowShadow: rgba(224, 170, 7, 0.8);
|
||||||
|
--oliveShadow: rgba(124, 173, 55, 0.8);
|
||||||
|
--greenShadow: rgba(48, 156, 63, 0.8);
|
||||||
|
--cyanShadow: rgba(28, 187, 180, 0.8);
|
||||||
|
--blueShadow: rgba(0, 102, 204, 0.8);
|
||||||
|
--purpleShadow: rgba(88, 48, 156, 0.8);
|
||||||
|
--mauveShadow: rgba(133, 33, 150, 0.8);
|
||||||
|
--pinkShadow: rgba(199, 50, 134, 0.8);
|
||||||
|
--brownShadow: rgba(140, 88, 53, 0.8);
|
||||||
|
--greyShadow: rgba(114, 130, 138, 0.8);
|
||||||
|
--grayShadow: rgba(114, 130, 138, 0.8);
|
||||||
|
--blackShadow: rgba(26, 26, 26, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
page {
|
||||||
|
background-color: var(--darkBlack);
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gradual-blue {
|
||||||
|
background-image: var(--darkGradualBlue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-white {
|
||||||
|
color: var(--gray);
|
||||||
|
background-color: var(--darkGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-list.menu > .cu-item {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-black,
|
||||||
|
.line-black,
|
||||||
|
.lines-black {
|
||||||
|
color: var(--darkTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solids::after {
|
||||||
|
border: 8rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solids-top::after {
|
||||||
|
border-top: 8rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solids-right::after {
|
||||||
|
border-right: 8rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solids-bottom::after {
|
||||||
|
border-bottom: 8rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
|
||||||
|
.solids-left::after {
|
||||||
|
border-left: 8rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-bar .search-form {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-list.grid {
|
||||||
|
background-color: var(--darkGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-btn:not([class*='bg-']) {
|
||||||
|
background-color: var(--btnGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
radio.radio[checked]::after {
|
||||||
|
border: 8px solid var(--darkDisable) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-progress {
|
||||||
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-progress view {
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-load.load-modal {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-load.load-modal::after {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-bar {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-bar,
|
||||||
|
.cu-bar.input {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-chat .cu-item > .main .content:not([class*='bg-']) {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-chat .cu-info {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
color: var(--darkPrimaryText);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-list.menu-avatar > .cu-item {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-card.dynamic > .cu-item {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-card > .cu-item {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-timeline {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-timeline > .cu-item::before {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-timeline > .cu-item[class*='cuIcon-']::before {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-form-group {
|
||||||
|
background-color: var(--lightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cu-form-group + .cu-form-group {
|
||||||
|
border-top: 1rpx solid var(--darkDisable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<uni-popup ref="addForm" mode="center" >
|
||||||
|
<!-- 新增:修改运行时间的弹窗 -->
|
||||||
|
<view class="modal-container">
|
||||||
|
<view class="modal-title">{{ `添加大棚` }}</view>
|
||||||
|
<uni-forms ref="form" :model="formData" :rules="rules" label-width="180rpx" validateTrigger="blur">
|
||||||
|
<uni-forms-item label="大棚名称:" name="agriName" required>
|
||||||
|
<uni-easyinput type="text" v-model="formData.agriName" placeholder="请输入大棚名称" />
|
||||||
|
</uni-forms-item>
|
||||||
|
<uni-forms-item label="设备imei:" name="imei" required>
|
||||||
|
<uni-easyinput v-model="formData.imei" placeholder="在此输入设备imei号" >
|
||||||
|
<template #right>
|
||||||
|
<yt-scanCode @getScanCode="getScanCode"></yt-scanCode>
|
||||||
|
</template>
|
||||||
|
</uni-easyinput>
|
||||||
|
</uni-forms-item>
|
||||||
|
</uni-forms>
|
||||||
|
|
||||||
|
<view class="modal-input-wrap" >
|
||||||
|
<text class="modal-label"></text>
|
||||||
|
<uni-icons type="info" size="15" color="#666"/>
|
||||||
|
<text class="modal-text">可直接点击右侧扫描设备二维码;<br>
|
||||||
|
如若失效,亦可手动输入设备imei号。</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-btn-wrap">
|
||||||
|
<button class="modal-btn cancel" @click="close">取消</button>
|
||||||
|
<button class="modal-btn confirm" @click="confirm">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import UniPopup from "../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
|
||||||
|
import {addAgriMobile} from "../../api/system/assets/agri";
|
||||||
|
import {updateSubscribeTopic} from "../../utils/mqtt";
|
||||||
|
import {batchUnsubscribe} from "../../api/system/mqtt";
|
||||||
|
import * as mqttUtil from "../../utils/mqtt";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "addAgri",
|
||||||
|
components: {UniPopup},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
agriName: null,
|
||||||
|
imei: null,
|
||||||
|
sourceCode:0
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
agriName: {
|
||||||
|
rules:[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
errorMessage: '请输入温室名称!',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 对name字段进行必填验证
|
||||||
|
imei: {
|
||||||
|
// name 字段的校验规则
|
||||||
|
rules:[
|
||||||
|
// 校验 name 不能为空
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
errorMessage: '请输入设备imei!',
|
||||||
|
},
|
||||||
|
// 对name字段进行长度验证
|
||||||
|
{
|
||||||
|
minLength: 15,
|
||||||
|
maxLength: 15,
|
||||||
|
errorMessage: '{label}不合法!需为 {maxLength} 位!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: '^\\d{15}$', // 核心正则
|
||||||
|
errorMessage: '请输入15位纯数字编号'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 当前表单域的字段中文名,可不填写
|
||||||
|
label:'设备imei'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
close() {
|
||||||
|
this.$refs.addForm.close();
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
this.$refs.form.validate().then(res => {
|
||||||
|
addAgriMobile(this.formData).then((response) => {
|
||||||
|
if (response.code===200) {
|
||||||
|
uni.showModal({
|
||||||
|
title: `${response.data.code===1?"✅":"⚠️"}操作提示`,
|
||||||
|
content: `${response.data.msg}`,
|
||||||
|
showCancal: false,
|
||||||
|
confirmText: '确定',
|
||||||
|
success: (res) => {
|
||||||
|
// 4. 只有用户点击弹窗的“确定”后,才执行后续操作
|
||||||
|
if (res.confirm) {
|
||||||
|
this.$emit("reload"); // 向父组件传值触发刷新
|
||||||
|
batchUnsubscribe({clientId: mqttUtil.getMqttState().clientId}).then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
updateSubscribeTopic();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.close(); // 关闭添加大棚的弹窗(移到这里)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
return
|
||||||
|
})
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
this.formData = {
|
||||||
|
agriName: null,
|
||||||
|
imei: null,
|
||||||
|
sourceCode:0
|
||||||
|
};
|
||||||
|
this.$refs.addForm.open();
|
||||||
|
},
|
||||||
|
getScanCode(res){
|
||||||
|
this.formData.imei = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped >
|
||||||
|
|
||||||
|
/* 新增:弹窗样式 */
|
||||||
|
.modal-container {
|
||||||
|
width: 600rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx 20rpx;
|
||||||
|
}
|
||||||
|
.modal-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.modal-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
.modal-input-wrap:nth-child(2) {
|
||||||
|
margin-bottom: 35rpx;
|
||||||
|
}
|
||||||
|
.modal-label {
|
||||||
|
width: 140rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
.modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 70rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.modal-btn.cancel {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.modal-btn.confirm {
|
||||||
|
background: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.modal-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #6C6C6C;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
/deep/ .uni-forms-item__label {
|
||||||
|
padding: 0!important;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
width: 154rpx!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .uni-forms-item:nth-child(2) {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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.$tn.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.$tn.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.$tn.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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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-7">
|
||||||
|
<view class="tn-icon-home-vertical-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/index'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 跳转回首页
|
||||||
|
navIndex() {
|
||||||
|
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
if (pages && pages.length > 0) {
|
||||||
|
const indexPath = this.indexPath || '/pages/index/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>
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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/index')) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/index/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.$tn.updateCustomBar()
|
||||||
|
customBarHeight = navBarInfo.customBarHeight
|
||||||
|
statusBarHeight = navBarInfo.statusBarHeight
|
||||||
|
} catch(e) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateCustomBarInfo()
|
||||||
|
}, 10)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新vuex中的导航栏信息
|
||||||
|
this.$tn.vuex('vuex_status_bar_height', statusBarHeight)
|
||||||
|
this.$tn.vuex('vuex_custom_bar_height', customBarHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
main.js
|
|
@ -6,16 +6,21 @@ import plugins from './plugins' // plugins
|
||||||
import './permission' // permission
|
import './permission' // permission
|
||||||
import { getDicts } from "@/api/system/dict/data"
|
import { getDicts } from "@/api/system/dict/data"
|
||||||
import "./utils/uni.css";
|
import "./utils/uni.css";
|
||||||
|
// 引入全局TuniaoUI
|
||||||
|
import TuniaoUI from 'tuniao-ui'
|
||||||
|
// 引入TuniaoUI提供的vuex简写方法
|
||||||
|
let vuexStore = require('@/store/$tn.mixin.js')
|
||||||
Vue.use(plugins)
|
Vue.use(plugins)
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
Vue.prototype.$store = store
|
Vue.prototype.$store = store
|
||||||
Vue.prototype.getDicts = getDicts
|
Vue.prototype.getDicts = getDicts
|
||||||
|
Vue.use(TuniaoUI)
|
||||||
|
Vue.mixin(vuexStore)
|
||||||
App.mpType = 'app'
|
App.mpType = 'app'
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
|
store,
|
||||||
...App
|
...App
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"versionName" : "1.2.0",
|
"versionName" : "1.2.0",
|
||||||
"versionCode" : "100",
|
"versionCode" : "100",
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
|
"sassImplementationName" : "node-sass",
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"nvueCompiler" : "uni-app",
|
"nvueCompiler" : "uni-app",
|
||||||
|
|
|
||||||
227
pages.json
|
|
@ -1,108 +1,191 @@
|
||||||
{
|
{
|
||||||
"pages": [{
|
"easycom": {
|
||||||
|
"^tn-(.*)": "@/tuniao-ui/components/tn-$1/tn-$1.vue"
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
"path": "pages/login",
|
"path": "pages/login",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "登录"
|
"navigationBarTitleText": "登录"
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"path": "pages/register",
|
"path": "pages/register",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "注册"
|
"navigationBarTitleText": "注册"
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"path": "pages/index",
|
"path": "pages/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "智能农业移动端框架",
|
"navigationBarTitleText": "首页"
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
"path": "pages/work/index",
|
{
|
||||||
|
"path": "pages/data/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "工作台"
|
"navigationBarTitleText": "数据中心"
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/news/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "消息中心"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
"path": "pages/mine/index",
|
"path": "pages/mine/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "我的"
|
"navigationBarTitleText": "我的"
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
"path": "pages/control/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "控制中心"
|
|
||||||
}
|
}
|
||||||
},{
|
],
|
||||||
"path": "pages/mine/avatar/index",
|
"subPackages": [
|
||||||
"style": {
|
{
|
||||||
"navigationBarTitleText": "修改头像"
|
"root": "pages/common",
|
||||||
}
|
"pages": [
|
||||||
}, {
|
{
|
||||||
"path": "pages/mine/info/index",
|
"path": "webview/index",
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "个人信息"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/info/edit",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "编辑资料"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/pwd/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "修改密码"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/setting/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "应用设置"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/help/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "常见问题"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/mqtt/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "mqtt工具"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/require/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "需求清单"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/mine/about/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "关于我们"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
"path": "pages/common/webview/index",
|
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "浏览网页"
|
"navigationBarTitleText": "浏览网页"
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
"path": "pages/common/textview/index",
|
{
|
||||||
|
"path": "textview/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "浏览文本"
|
"navigationBarTitleText": "浏览文本"
|
||||||
}
|
}
|
||||||
}],
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"root": "pages/home",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "control/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "控制中心"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "path": "control/automatic",
|
||||||
|
// "style": {
|
||||||
|
// "navigationBarTitleText": "自动控制"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "path": "control/manual",
|
||||||
|
// "style": {
|
||||||
|
// "navigationBarTitleText": "手动控制"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
"root": "pages/data",
|
||||||
|
"pages": [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"root": "pages/news",
|
||||||
|
"pages": [
|
||||||
|
]
|
||||||
|
},*/
|
||||||
|
{
|
||||||
|
"root": "pages/work",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工作台"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"root": "pages/mine/subpages",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "avatar/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改头像"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "info/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人信息"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "info/edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pwd/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "setting/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "应用设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "help/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "常见问题"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "mqtt/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "mqtt工具"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "require/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "需求清单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "about/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关于我们"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"color": "#000000",
|
"color": "#000000",
|
||||||
"selectedColor": "#000000",
|
"selectedColor": "#406ee9",
|
||||||
"borderStyle": "white",
|
"borderStyle": "white",
|
||||||
"backgroundColor": "#ffffff",
|
"backgroundColor": "#ffffff",
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"pagePath": "pages/control/index",
|
"pagePath": "pages/index",
|
||||||
"iconPath": "static/images/tabbar/work.png",
|
"iconPath": "static/images/tabbar/home.png",
|
||||||
"selectedIconPath": "static/images/tabbar/work_.png",
|
"selectedIconPath": "static/images/tabbar/home_.png",
|
||||||
"text": "控制中心"
|
"text": "首页"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/work/index",
|
"pagePath": "pages/data/index",
|
||||||
"iconPath": "static/images/tabbar/work.png",
|
"iconPath": "static/images/tabbar/data.png",
|
||||||
"selectedIconPath": "static/images/tabbar/work_.png",
|
"selectedIconPath": "static/images/tabbar/data_.png",
|
||||||
"text": "工作台"
|
"text": "历史数据"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/news/index",
|
||||||
|
"iconPath": "static/images/tabbar/news.png",
|
||||||
|
"selectedIconPath": "static/images/tabbar/news_.png",
|
||||||
|
"text": "消息中心"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/mine/index",
|
"pagePath": "pages/mine/index",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<view class="charts-box">
|
||||||
|
<qiun-data-charts
|
||||||
|
type="line"
|
||||||
|
:opts="opts"
|
||||||
|
background="#fff"
|
||||||
|
:chartData="chartData"
|
||||||
|
:canvas2d="true"
|
||||||
|
canvasId="nMPkeQGNEosMwoWKKNRBZBIEhguMoMWp"
|
||||||
|
:ontouch="true"
|
||||||
|
:onmovetip="true"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {},
|
||||||
|
//这里的 opts 是图表类型 type="line" 的全部配置参数,您可以将此配置复制到 config-ucharts.js 文件中下标为 ['line'] 的节点中来覆盖全局默认参数。实际应用过程中 opts 只需传入与全局默认参数中不一致的【某一个属性】即可实现同类型的图表显示不同的样式,达到页面简洁的需求。
|
||||||
|
opts: {
|
||||||
|
yAxis: {
|
||||||
|
gridType: "dash",
|
||||||
|
dashLength: 2,
|
||||||
|
disabled: false,
|
||||||
|
disableGrid: false,
|
||||||
|
splitNumber: 5,
|
||||||
|
gridColor: "#CCCCCC",
|
||||||
|
padding: 10,
|
||||||
|
showTitle: true,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
position: "left",
|
||||||
|
disabled: false,
|
||||||
|
axisLine: true,
|
||||||
|
axisLineColor: "#CCCCCC",
|
||||||
|
calibration: true,
|
||||||
|
fontColor: "#666666",
|
||||||
|
fontSize: 13,
|
||||||
|
textAlign: "left",
|
||||||
|
title: "温度(℃)",
|
||||||
|
titleFontSize: 14,
|
||||||
|
titleOffsetY: -10,
|
||||||
|
titleOffsetX: 5,
|
||||||
|
titleFontColor: "#333333",
|
||||||
|
min: 0,
|
||||||
|
max: 50,
|
||||||
|
tofix: 1,
|
||||||
|
unit: "",
|
||||||
|
format: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
position: "right",
|
||||||
|
disabled: false,
|
||||||
|
axisLine: true,
|
||||||
|
axisLineColor: "#CCCCCC",
|
||||||
|
calibration: true,
|
||||||
|
fontColor: "#666666",
|
||||||
|
fontSize: 13,
|
||||||
|
textAlign: "right",
|
||||||
|
title: "湿度(%RH)",
|
||||||
|
titleFontSize: 14,
|
||||||
|
titleOffsetY: -10,
|
||||||
|
titleOffsetX: 0,
|
||||||
|
titleFontColor: "#333333",
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
tofix: 1,
|
||||||
|
unit: "",
|
||||||
|
format: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
this.getServerData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getServerData() {
|
||||||
|
//模拟从服务器获取数据时的延时
|
||||||
|
setTimeout(() => {
|
||||||
|
//模拟服务器返回数据,如果数据格式和标准格式不同,需自行按下面的格式拼接
|
||||||
|
let res = {
|
||||||
|
categories: ["00:00","00:05","00:10","00:15","00:20","00:25","00:30","00:35","00:40","00:45","00:50","00:55","01:00","01:05","01:10","01:15","01:20","01:25","01:30","01:35","01:40","01:45","01:50","01:55","02:00","02:05","02:10","02:15","02:20","02:25","02:30","02:35","02:40","02:45","02:50","02:55","03:00","03:05","03:10","03:15","03:20","03:25","03:30","03:35","03:40","03:45","03:50","03:55","04:00","04:05","04:10","04:15","04:20","04:25","04:30","04:35","04:40","04:45","04:50","04:55","05:00","05:05","05:10","05:15","05:20","05:25","05:30","05:35","05:40","05:45","05:50","05:55","06:00","06:05","06:10","06:15","06:20","06:25","06:30","06:35","06:40","06:45","06:50","06:55","07:00","07:05","07:10","07:15","07:20","07:25","07:30","07:35","07:40","07:45","07:50","07:55","08:00","08:05","08:10","08:15","08:20","08:25","08:30","08:35","08:40","08:45","08:50","08:55","09:00","09:05","09:10","09:15","09:20","09:25","09:30","09:35","09:40","09:45","09:50","09:55","10:00","10:05","10:10","10:15","10:20","10:25","10:30","10:35","10:40","10:45","10:50","10:55","11:00","11:05","11:10","11:15","11:20","11:25","11:30","11:35","11:40","11:45","11:50","11:55","12:00","12:05","12:10","12:15","12:20","12:25","12:30","12:35","12:40","12:45","12:50","12:55","13:00","13:05","13:10","13:15","13:20","13:25","13:30","13:35","13:40","13:45","13:50","13:55","14:00","14:05","14:10","14:15","14:20","14:25","14:30","14:35","14:40","14:45","14:50","14:55","15:00","15:05","15:10","15:15","15:20","15:25","15:30","15:35","15:40","15:45","15:50","15:55","16:00","16:05","16:10","16:15","16:20","16:25","16:30","16:35","16:40","16:45","16:50","16:55","17:00","17:05","17:10","17:15","17:20","17:25","17:30","17:35","17:40","17:45","17:50","17:55","18:00","18:05","18:10","18:15","18:20","18:25","18:30","18:35","18:40","18:45","18:50","18:55","19:00","19:05","19:10","19:15","19:20","19:25","19:30","19:35","19:40","19:45","19:50","19:55","20:00","20:05","20:10","20:15","20:20","20:25","20:30","20:35","20:40","20:45","20:50","20:55","21:00","21:05","21:10","21:15","21:20","21:25","21:30","21:35","21:40","21:45","21:50","21:55","22:00","22:05","22:10","22:15","22:20","22:25","22:30","22:35","22:40","22:45","22:50","22:55","23:00","23:05","23:10","23:15","23:20","23:25","23:30","23:35","23:40","23:45","23:50","23:55"],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: "温度(℃)",
|
||||||
|
index: 0,
|
||||||
|
data: [18.2,17.9,17.5,17.1,16.8,16.5,16.3,16.1,15.9,15.8,15.7,15.6,15.5,15.4,15.3,15.2,15.1,15,14.9,14.8,14.7,14.6,14.5,14.4,14.3,14.2,14.1,14,13.9,13.8,13.7,13.6,13.5,13.4,13.3,13.2,13.1,13,12.9,12.8,12.7,12.6,12.5,12.4,12.3,12.2,12.1,12,12.1,12.2,12.3,12.4,12.5,12.6,12.7,12.8,12.9,13,13.2,13.4,13.6,13.8,14,14.3,14.6,14.9,15.2,15.5,15.8,16.1,16.4,16.7,17,17.3,17.6,17.9,18.2,18.5,18.8,19.1,19.4,19.7,20,20.3,20.6,20.9,21.2,21.5,21.8,22.1,22.4,22.7,23,23.2,23.4,23.6,23.8,24,24.2,24.4,24.6,24.8,25,25.1,25.2,25.3,25.4,25.5,25.6,25.7,25.8,25.9,26,26.1,26.2,26.3,26.4,26.5,26.6,26.7,26.8,26.9,27,27.1,27.2,27.3,27.4,27.5,27.6,27.7,27.8,27.9,28,28.1,28.2,28.3,28.4,28.5,28.6,28.7,28.8,28.9,29,29.1,29.2,29.3,29.4,29.5,29.6,29.7,29.8,29.9,30,29.8,29.6,29.4,29.2,29,28.8,28.6,28.4,28.2,28,27.8,27.6,27.4,27.2,27,26.8,26.6,26.4,26.2,26,25.8,25.6,25.4,25.2,25,24.8,24.6,24.4,24.2,24,23.8,23.6,23.4,23.2,23,22.8,22.6,22.4,22.2,22,21.8,21.6,21.4,21.2,21,20.8,20.6,20.4,20.2,20,19.8,19.6,19.4,19.2,19,18.8,18.6,18.4,18.2,18,17.8,17.6,17.4,17.2,17,16.8,16.6,16.4,16.2,16,15.8,15.6,15.4,15.2,15,14.8,14.6,14.4,14.2,14,13.8,13.6,13.4,13.2,13,12.8,12.6,12.4,12.2,12,11.8,11.6,11.4,11.2,11,10.8,10.6,10.4,10.2,10,10.2,10.4,10.6,10.8,11,11.2,11.4,11.6,11.8,12,12.2,12.4,12.6,12.8,13,13.2,13.4,13.6,13.8,14,14.2,14.4,14.6,14.8,15,15.2,15.4,15.6,15.8,16,16.2,16.4,16.6,16.8,17]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "湿度(%)",
|
||||||
|
index: 1,
|
||||||
|
data: [78.5,79.1,79.8,80.3,80.9,81.4,81.9,82.3,82.7,83,83.3,83.5,83.7,83.9,84,84.1,84.2,84.2,84.2,84.1,84,83.9,83.7,83.5,83.2,82.9,82.6,82.2,81.8,81.3,80.8,80.2,79.6,78.9,78.2,77.4,76.5,75.6,74.6,73.5,72.4,71.2,69.9,68.6,67.2,65.7,64.2,62.6,61,59.3,57.6,55.8,54,52.1,50.2,48.3,46.3,44.3,42.3,40.3,38.3,36.3,34.3,32.3,30.4,28.5,26.6,24.8,23,21.3,19.6,18,16.4,15,13.6,12.3,11.1,10,9,8.1,7.3,6.6,6,5.5,5.1,4.8,4.6,4.5,4.5,4.6,4.8,5.1,5.5,6,6.6,7.3,8.1,9,10,11.1,12.3,13.6,15,16.4,18,19.6,21.3,23,24.8,26.6,28.5,30.4,32.3,34.3,36.3,38.3,40.3,42.3,44.3,46.3,48.3,50.2,52.1,54,55.8,57.6,59.3,61,62.6,64.2,65.7,67.2,68.6,69.9,71.2,72.4,73.5,74.6,75.6,76.5,77.4,78.2,78.9,79.6,80.2,80.8,81.3,81.8,82.2,82.6,82.9,83.2,83.5,83.8,84,84.2,84.3,84.4,84.4,84.4,84.3,84.2,84,83.8,83.5,83.2,82.8,82.4,81.9,81.4,80.8,80.2,79.5,78.8,78,77.2,76.3,75.4,74.4,73.3,72.2,71,69.8,68.5,67.2,65.8,64.3,62.8,61.3,59.7,58,56.3,54.6,52.8,51,49.2,47.3,45.4,43.5,41.5,39.5,37.5,35.5,33.5,31.5,29.5,27.5,25.6,23.7,21.9,20.1,18.4,16.8,15.2,13.7,12.3,11,9.8,8.7,7.7,6.8,6,5.3,4.7,4.2,3.8,3.5,3.3,3.2,3.2,3.3,3.5,3.8,4.2,4.7,5.3,6,6.8,7.7,8.7,9.8,11,12.3,13.7,15.2,16.8,18.4,20.1,21.9,23.7,25.6,27.5,29.5,31.5,33.5,35.5,37.5,39.5,41.5,43.5,45.4,47.3,49.2,51,52.8,54.6,56.3,58,59.7,61.3,62.8,64.3,65.8,67.2,68.5,69.8,71,72.2,73.3,74.4,75.4,76.3,77.2,78,78.8,79.5,80.2,80.8]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
this.chartData = JSON.parse(JSON.stringify(res));
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
|
||||||
|
.charts-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,577 @@
|
||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<z-paging ref="paging" refresher-only bg-color="var(--gradualCyanLight)" :show-empty="false" :show-footer="false" @onRefresh="refresh">
|
||||||
|
|
||||||
|
<template #refresher="{refresherStatus}">
|
||||||
|
<!-- 此处的custom-refresh为demo中自定义的组件,非z-paging的内置组件,请在实际项目中自行创建。这里插入什么view,下拉刷新就显示什么view -->
|
||||||
|
<custom-refresher :status="refresherStatus" />
|
||||||
|
</template>
|
||||||
|
<view class="card shadow shadow-lg bg-white ">
|
||||||
|
<uni-section :title="`当前大棚:【${selectedText}】`" :subTitle="imei" titleFontSize="16px" type="line" >
|
||||||
|
<template v-slot:right>
|
||||||
|
<view class="switch-row" v-if="value!== 1 && !['862538065276939','A','B','C'].includes(value)">
|
||||||
|
<text class="modal-text">手动</text>
|
||||||
|
<tn-switch
|
||||||
|
v-model="currentMode"
|
||||||
|
:size="60"
|
||||||
|
@change="confirmSwitch"
|
||||||
|
/>
|
||||||
|
<text class="modal-text">自动</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<uni-divider margin="10rpx 0"></uni-divider>
|
||||||
|
|
||||||
|
<!-- 把解析好的完整数据传给子组件 -->
|
||||||
|
<auto-page
|
||||||
|
ref="autoPage"
|
||||||
|
v-if="currentMode === true"
|
||||||
|
:show="show"
|
||||||
|
:status="status"
|
||||||
|
:limitTimes="limitTimes"
|
||||||
|
:dtu_remark="dtu_remark"
|
||||||
|
:selectedText="selectedText"
|
||||||
|
:value="value"
|
||||||
|
:ventTotalLen="ventTotalLen"
|
||||||
|
:agriId="agriId"
|
||||||
|
:quiltNum="quiltNum"
|
||||||
|
:filmNum="filmNum"
|
||||||
|
:blindNum="blindNum"
|
||||||
|
@publicMsg="publishMessage"
|
||||||
|
@sendSettingMsg="sendSettingMsg"
|
||||||
|
@getAgriRemark="getRemarkByImei"
|
||||||
|
@getAgriLimit="getAgriByImei"
|
||||||
|
/>
|
||||||
|
<manual-page
|
||||||
|
ref="manualPage"
|
||||||
|
v-else-if="currentMode === false"
|
||||||
|
:liveData="liveData"
|
||||||
|
:show="show"
|
||||||
|
:status="status"
|
||||||
|
:fontStyle="fontStyle"
|
||||||
|
:limitTimes="limitTimes"
|
||||||
|
:dtu_remark="dtu_remark"
|
||||||
|
:selectedText="selectedText"
|
||||||
|
:value="value"
|
||||||
|
:agriId="agriId"
|
||||||
|
:quiltNum="quiltNum"
|
||||||
|
:filmNum="filmNum"
|
||||||
|
:blindNum="blindNum"
|
||||||
|
@publicMsg="publishMessage"
|
||||||
|
@getAgriRemark="getRemarkByImei"
|
||||||
|
@getAgriLimit="getAgriByImei"
|
||||||
|
/>
|
||||||
|
</uni-section>
|
||||||
|
</view>
|
||||||
|
</z-paging>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ManualPage from "./manual.vue"
|
||||||
|
import AutoPage from "./automatic.vue"
|
||||||
|
// 优化:抽离魔法值常量
|
||||||
|
const SENSOR_MAP = {
|
||||||
|
temp1: "201", temp2: "202", temp3: "203", temp4: "204",
|
||||||
|
humi1: "101", humi2: "102", humi3: "103", humi4: "104"
|
||||||
|
};
|
||||||
|
const MQTT_TOPIC_SUFFIX = { UP: "/+", DOWN: "/control" };
|
||||||
|
|
||||||
|
import mqttUtil from '@/utils/mqtt';
|
||||||
|
import {listAgri, switchAgriMode} from "../../../api/system/assets/agri";
|
||||||
|
import {getNewSpecialData} from "../../../api/data/specialData";
|
||||||
|
import store from "../../../store";
|
||||||
|
import {getRemarkByImei} from "../../../api/system/assets/remark";
|
||||||
|
import CustomRefresher from "../../../components/custom-refresher/custom-refresher.vue";
|
||||||
|
import ZPaging from "../../../uni_modules/z-paging/components/z-paging/z-paging.vue";
|
||||||
|
import {findDtuDataByInfo} from "../../../api/system/data";
|
||||||
|
import {getAgriByImei} from "../../../api/system/assets/limit";
|
||||||
|
import {getDtuRemarkByImei} from "../../../api/system/assets/remark_bak";
|
||||||
|
import {getAgriLimitByImei} from "../../../api/system/assets/limit_bak";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
|
dicts: ['sys_data_map'],
|
||||||
|
components: {
|
||||||
|
ZPaging,
|
||||||
|
CustomRefresher,
|
||||||
|
ManualPage,
|
||||||
|
AutoPage,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentMode:false,
|
||||||
|
mqttConfig: {
|
||||||
|
subscribeTopic:'/listener',
|
||||||
|
},
|
||||||
|
value: 1,
|
||||||
|
selectedText: '',
|
||||||
|
// range: [],
|
||||||
|
agriId:'',
|
||||||
|
imei:'',
|
||||||
|
publishTopic: '/control',
|
||||||
|
// 优化:声明响应式变量 connected
|
||||||
|
connected: false,
|
||||||
|
dtu_remark:{},
|
||||||
|
fontStyle: '',
|
||||||
|
liveData: {
|
||||||
|
temp1: '数据加载中...',
|
||||||
|
temp2: '数据加载中...',
|
||||||
|
temp3: '数据加载中...',
|
||||||
|
temp4: '数据加载中...',
|
||||||
|
humi1: '数据加载中...',
|
||||||
|
humi2: '数据加载中...',
|
||||||
|
humi3: '数据加载中...',
|
||||||
|
humi4: '数据加载中...',
|
||||||
|
temp: "正在加载中..."
|
||||||
|
},
|
||||||
|
// 卡片状态
|
||||||
|
show: {},
|
||||||
|
status: {
|
||||||
|
deviceTime:"正在加载中..."
|
||||||
|
},
|
||||||
|
// 新增:限位时间配置
|
||||||
|
limitTimes: {},
|
||||||
|
testMsg:'由于线上为真实数据。任何操作均可影响线上功能,故仅作演示',
|
||||||
|
ventTotalLen:0,
|
||||||
|
quiltNum:0,
|
||||||
|
filmNum:0,
|
||||||
|
blindNum:0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad(option) {
|
||||||
|
if (option.agriInfo) {
|
||||||
|
const decodedStr = decodeURIComponent(option.agriInfo);
|
||||||
|
const agriInfo = JSON.parse(decodedStr); // 反序列化为原对象
|
||||||
|
this.value = agriInfo.imei;
|
||||||
|
this.selectedText = agriInfo.agriName;
|
||||||
|
this.agriId = agriInfo.agriId;
|
||||||
|
this.currentMode = (agriInfo.workMode === 1);
|
||||||
|
this.quiltNum = agriInfo.quiltNum;
|
||||||
|
this.filmNum = agriInfo.filmNum;
|
||||||
|
this.blindNum = agriInfo.blindNum;
|
||||||
|
this.change(this.value)
|
||||||
|
}
|
||||||
|
// 定义所有互斥的键对:[k键, g键]
|
||||||
|
const mutexPairs = [
|
||||||
|
['jbk', 'jbg'],
|
||||||
|
['jlk', 'jlg'],
|
||||||
|
['jm1k', 'jm1g'],
|
||||||
|
['jm2k', 'jm2g'],
|
||||||
|
['jm3k', 'jm3g']
|
||||||
|
];
|
||||||
|
|
||||||
|
// 遍历处理每一组互斥规则(k=1则g=0,g=1则k=0)
|
||||||
|
mutexPairs.forEach(([kKey, gKey]) => {
|
||||||
|
if (this.status[kKey] === 1) this.status[gKey] = 0;
|
||||||
|
if (this.status[gKey] === 1) this.status[kKey] = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
// this.getAgriList();
|
||||||
|
// 注册MQTT消息回调(接收设备消息)
|
||||||
|
mqttUtil.setOnMessageCallback(this.ackMessage);
|
||||||
|
// 更新连接状态
|
||||||
|
this.connected = mqttUtil.getMqttState().isConnected;
|
||||||
|
},
|
||||||
|
onUnload() {
|
||||||
|
// 移除MQTT消息回调(避免内存泄漏)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
// this.getAgriList()
|
||||||
|
this.change(this.imei)
|
||||||
|
mqttUtil.setOnMessageCallback(this.ackMessage);
|
||||||
|
if (this.currentMode) {
|
||||||
|
this.$refs.autoPage.refresh();
|
||||||
|
}
|
||||||
|
this.$refs.paging.complete();
|
||||||
|
},
|
||||||
|
getNewSpecialData() {
|
||||||
|
getNewSpecialData().then(response => {
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
this.makeSpecialData(response.data,false);
|
||||||
|
this.liveData.temp = "最后更新时间:"+response.data.time;
|
||||||
|
this.fontStyle = 'font-size:16px;'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.imei = e;
|
||||||
|
var clientId = mqttUtil.getMqttState().clientId;
|
||||||
|
this.connected = mqttUtil.getMqttState().isConnected;
|
||||||
|
if ((e === 'A' || e==='B' || e==='C')
|
||||||
|
&& store.getters && store.getters.name === 'admin'
|
||||||
|
&& this.currentMode===false) {
|
||||||
|
if (!this.currentMode) {
|
||||||
|
this.getNewSpecialData(e);
|
||||||
|
}
|
||||||
|
this.mqttConfig.subscribeTopic = `frontend/${clientId}/dtu/862538065276061`;
|
||||||
|
}
|
||||||
|
if (e !== 'A' && e!=='B' && e!=='C') {
|
||||||
|
// 优化:使用常量拼接MQTT主题
|
||||||
|
this.publishTopic = `frontend/${clientId}${MQTT_TOPIC_SUFFIX.DOWN}/${this.imei}`;
|
||||||
|
this.mqttConfig.subscribeTopic = `frontend/${clientId}/dtu/${this.imei}`;
|
||||||
|
var queryParams = {
|
||||||
|
imei: this.imei
|
||||||
|
}
|
||||||
|
if (!this.currentMode) {
|
||||||
|
// 最新温湿度数据
|
||||||
|
findDtuDataByInfo(queryParams).then(response => {
|
||||||
|
Object.keys(response.data).forEach(key => {
|
||||||
|
this.liveData[key] = response.data[key] || '已离线..';
|
||||||
|
});
|
||||||
|
this.liveData.temp = "最后更新时间:"+response.data.time;
|
||||||
|
this.fontStyle = 'font-size:16px;'
|
||||||
|
console.info(this.liveData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.getAgriByImei();
|
||||||
|
// 备注
|
||||||
|
this.getRemarkByImei();
|
||||||
|
if (e!=="862538065276939"){
|
||||||
|
const message = JSON.stringify({jbk: 0,read:true})
|
||||||
|
this.publishMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
this.style="";
|
||||||
|
},
|
||||||
|
getRemarkByImei() {
|
||||||
|
getDtuRemarkByImei({imei:this.imei}).then(response => {
|
||||||
|
if (response.code===200 && (response.data)) {
|
||||||
|
this.dtu_remark={...response.data.remarkMaps}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getAgriByImei() {
|
||||||
|
getAgriLimitByImei(this.imei).then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
if (!response.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.limitTimes = response.data.limitMaps;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAgriList() {
|
||||||
|
listAgri().then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.range = response.rows.map(item => ({
|
||||||
|
agriId: item.id,
|
||||||
|
text: item.agriName,
|
||||||
|
value: item.imei // 提取并改名
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (store.getters && store.getters.name === 'admin') {
|
||||||
|
this.range.push(
|
||||||
|
{
|
||||||
|
text:"八方南棚",
|
||||||
|
value:"A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text:"九方春棚",
|
||||||
|
value:"B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text:"十二方棚",
|
||||||
|
value:"C"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
testFunction(content) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '操作提示:',
|
||||||
|
content: content,
|
||||||
|
cancelText: '确定',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
makeSpecialData(msgData, tag) {
|
||||||
|
const div10 = (v) => (v == null ? null : Math.round((Number(v)/10)*10)/10);
|
||||||
|
|
||||||
|
// 1. 提取B/C的tag对应键名(精简重复三元)
|
||||||
|
const B_KEYS = {
|
||||||
|
true: {temp1:'205',humi1:'105',temp2:'206',humi2:'106',temp3:'207',humi3:'107',temp4:'208',humi4:'108'},
|
||||||
|
false: {temp1:'temp5',humi1:'humi5',temp2:'temp6',humi2:'humi6',temp3:'temp7',humi3:'humi7',temp4:'temp8',humi4:'humi8'}
|
||||||
|
};
|
||||||
|
const C_KEYS = {
|
||||||
|
true: {temp1:'209',humi1:'109',temp2:'210',humi2:'110',temp3:'211',humi3:'111',temp4:'212',humi4:'112'},
|
||||||
|
false: {temp1:'temp9',humi1:'humi9',temp2:'temp10',humi2:'humi10',temp3:'temp11',humi3:'humi11',temp4:'temp12',humi4:'humi12'}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. 简化传感器键名映射表
|
||||||
|
const IMEI_SENSOR_MAP = {
|
||||||
|
A: SENSOR_MAP,
|
||||||
|
B: B_KEYS[tag], // 直接取tag对应的键名,替代三元
|
||||||
|
C: C_KEYS[tag]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. 确定传感器键名(精简变量名)
|
||||||
|
const isAdmin = store.getters?.name === 'admin';
|
||||||
|
const sk = (isAdmin && ['A','B','C'].includes(this.imei)) ? IMEI_SENSOR_MAP[this.imei] : IMEI_SENSOR_MAP.A;
|
||||||
|
// 4. 简化liveData的嵌套三元(核心优化)
|
||||||
|
this.liveData = Object.fromEntries(
|
||||||
|
Object.entries(sk).map(([k, skVal]) => [
|
||||||
|
k,
|
||||||
|
(tag
|
||||||
|
? div10(msgData[skVal])
|
||||||
|
: (this.imei === 'A' ? msgData[k] : msgData[skVal]))
|
||||||
|
|| "已离线..."
|
||||||
|
])
|
||||||
|
);
|
||||||
|
this.liveData.temp = "最后更新时间:" + this.getCurrentTime();
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
Object.keys(this.show).forEach(key => {
|
||||||
|
this.show[key] = "暂停";
|
||||||
|
});
|
||||||
|
Object.keys(this.status).forEach(key => {
|
||||||
|
if (key === "deviceTime") {
|
||||||
|
this.status[key] = '正在加载中...';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.status[key] = 0;
|
||||||
|
});
|
||||||
|
Object.keys(this.liveData).forEach(key => {
|
||||||
|
if (key==="temp") {
|
||||||
|
this.liveData[key] = '正在加载中...';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.liveData[key] = '数据加载中...';
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
// 测试专用
|
||||||
|
testAuto(type) {
|
||||||
|
this.$set(this.status, type, this.status[type] === 0 ? 1 : 0);
|
||||||
|
this.$set(this.show, type, this.status[type] === 0 ? "暂停" : "运行");
|
||||||
|
},
|
||||||
|
publishMessage(message) {
|
||||||
|
if (!this.connected || !this.publishTopic || !message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 调用全局MQTT工具类发布消息
|
||||||
|
const publishSuccess = mqttUtil.publishMqtt(this.publishTopic, message);
|
||||||
|
if (publishSuccess) {
|
||||||
|
this.addMessage(`【指令已发送】imei: ${this.publishTopic},指令: ${message}`);
|
||||||
|
} else {
|
||||||
|
this.addMessage(`发布失败:设备:[${this.publishTopic}]`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendSettingMsg(message) {
|
||||||
|
if (!this.connected || !message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const clientId = mqttUtil.getMqttState().clientId;
|
||||||
|
const controlTopic = `frontend/${clientId}/${this.imei}/config`;
|
||||||
|
// 调用全局MQTT工具类发布消息
|
||||||
|
const publishSuccess = mqttUtil.publishMqtt(controlTopic, message);
|
||||||
|
if (publishSuccess) {
|
||||||
|
this.addMessage(`【指令已发送】imei: ${controlTopic},指令: ${message}`);
|
||||||
|
} else {
|
||||||
|
this.addMessage(`发布失败:设备:[${controlTopic}]`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 消息回调逻辑完全保留(仅依赖全局工具类转发消息)
|
||||||
|
ackMessage(topic, payload) {
|
||||||
|
// 1. 先判断是否是目标订阅主题(如frontend/\\w+/control/\\w+")
|
||||||
|
if ((topic !== this.mqttConfig.subscribeTopic+"/ack")
|
||||||
|
&& (topic !== this.mqttConfig.subscribeTopic+"/listener")
|
||||||
|
&& topic !== this.mqttConfig.subscribeTopic+"/config") return;
|
||||||
|
const regexWithGroup = /^frontend\/[^/]+\/dtu\/\d+\/(.+)$/;
|
||||||
|
let msgData = {};
|
||||||
|
// 优化:捕获JSON解析异常
|
||||||
|
try {
|
||||||
|
msgData = JSON.parse(payload);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("MQTT消息解析失败:", e, payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchResult = topic.match(regexWithGroup);
|
||||||
|
const tag = matchResult[1]; // 提取结果:'1234567890'
|
||||||
|
|
||||||
|
// 3. 区分“回执”和“其他内容”
|
||||||
|
if (tag==='ack' && msgData.prop && "suc" in msgData) {
|
||||||
|
this.handleCommandAck(msgData);
|
||||||
|
} else if (tag==='listener' && "msg" in msgData && "clientId" in msgData) {
|
||||||
|
if (mqttUtil.getMqttState().clientId === msgData.clientId) {
|
||||||
|
this.$modal.msg(
|
||||||
|
`${msgData.msg}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.handleOtherContent(msgData,tag)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addMessage(content) {
|
||||||
|
console.info("提示消息:" + content)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理指令回执的函数(核心修改:添加定时器+快照)
|
||||||
|
handleCommandAck(ackData) {
|
||||||
|
// 拿到指令字段(如jm2k)和执行状态(suc)
|
||||||
|
const commandField = Object.keys(ackData.prop)[0]; // 这里是"jm2k"
|
||||||
|
const commandValue = ackData.prop[commandField]; // 这里是0/1
|
||||||
|
const isSuccess = ackData.suc; // 这里是true
|
||||||
|
|
||||||
|
const type = commandField.substring(0,commandField.length -1);
|
||||||
|
if (isSuccess) {
|
||||||
|
// 优化:使用$set确保响应式更新
|
||||||
|
this.$set(this.status, type, commandValue);
|
||||||
|
this.$set(this.show, type, commandValue === 0 ? "暂停" : "运行");
|
||||||
|
console.info(`收到回执,更新功能码成功:{"${type}":${commandValue}}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========== 修改:自动停止指令不弹窗 ==========
|
||||||
|
if (ackData.clientId && (ackData.clientId===mqttUtil.getMqttState().clientId)) {
|
||||||
|
console.info("用户提示成功!")
|
||||||
|
this.$modal[isSuccess ? 'msgSuccess' : 'msgError'](
|
||||||
|
`设备操作${isSuccess ? "成功" : "失败"}!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(`指令[${commandField}=${commandValue}]执行${isSuccess ? "成功" : "失败"}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOtherContent(msgData,tag) {
|
||||||
|
// 业务逻辑:处理传感器数据、设备状态等
|
||||||
|
// 设备状态展示
|
||||||
|
if (this.value === 1) return;
|
||||||
|
if (tag === 'listener') {
|
||||||
|
var arr = ['jbk', "jbg", "jm1k", "jm1g", "jm2k", "jm2g", "jm3k", "jm3g","jlk","jlg"]
|
||||||
|
const allKeysNumeric = Object.keys(msgData).some(key => arr.includes(key));
|
||||||
|
if (allKeysNumeric) {
|
||||||
|
//todo
|
||||||
|
this.status = {...msgData}
|
||||||
|
Object.keys(msgData).forEach(key => {
|
||||||
|
const value = msgData[key];
|
||||||
|
this.show[key] = value === 0 ? '暂停' : '运行';
|
||||||
|
});
|
||||||
|
this.status.deviceTime = '最后更新时间:' + this.getCurrentTime();
|
||||||
|
}
|
||||||
|
if (this.currentMode) return;
|
||||||
|
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
||||||
|
if (Object.keys(msgData).length > 0 && allKeysNumeric2) {
|
||||||
|
this.fontStyle = 'font-size:16px;'
|
||||||
|
this.makeSpecialData(msgData, true);
|
||||||
|
}
|
||||||
|
} else if (tag === 'config') {
|
||||||
|
if ("ventTotalLen" in msgData) {
|
||||||
|
console.info("参数设置:自动校准风口:",msgData.ventTotalLen)
|
||||||
|
this.ventTotalLen = msgData.ventTotalLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取格式化后的当前时间
|
||||||
|
* @returns {string} 格式为 YYYY-MM-DD HH:mm:ss 的当前时间
|
||||||
|
*/
|
||||||
|
getCurrentTime() {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
// 获取年、月、日(补零确保两位数)
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
// 获取时、分、秒(补零确保两位数)
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||||
|
|
||||||
|
// 拼接成标准格式
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
},
|
||||||
|
confirmSwitch(e) {
|
||||||
|
const mode = e;
|
||||||
|
this.currentMode = !mode
|
||||||
|
var showTip = mode ? '自动模式':'手动模式';
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: '操作提示',
|
||||||
|
content: `确定将【${this.selectedText}】切换为${showTip}?`,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
switchAgriMode(this.imei, {code:(mode?1:0)}).then(response => {
|
||||||
|
if (response.code===200) {
|
||||||
|
this.currentMode = mode
|
||||||
|
if (!mode) {
|
||||||
|
this.change(this.imei)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
mqttUtil.removeOnMessageCallback();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
mqttUtil.removeOnMessageCallback();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<!-- 仅保留这个无 scoped 的 style 块引入样式 -->
|
||||||
|
<style lang="scss">
|
||||||
|
@import '@/tuniao-ui/index.scss';
|
||||||
|
@import '@/colorui/main.css';
|
||||||
|
@import '@/colorui/icon.css';
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
/deep/ .z-paging-content-fixed {
|
||||||
|
padding: 20rpx !important;
|
||||||
|
/* 可选:防止margin塌陷,加overflow */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/deep/ .uni-section-header__slot-right {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: 15rpx
|
||||||
|
}
|
||||||
|
.uni-stat-tooltip {
|
||||||
|
width: 300rpx;
|
||||||
|
}
|
||||||
|
/deep/ .is-input-border {
|
||||||
|
width: 340rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.switch-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* 垂直居中 */
|
||||||
|
gap: 8px; /* 元素之间的间距,可根据需要调整 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-text {
|
||||||
|
/* 确保文字和开关在视觉上对齐 */
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,751 @@
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
|
||||||
|
<uni-section title="实时温湿度" titleFontSize="16px" type="line" v-if="value!== '1'">
|
||||||
|
<template v-slot:right>
|
||||||
|
{{ liveData.temp }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<!-- 优化:温湿度卡片循环渲染 -->
|
||||||
|
<view class="uni-flex_control uni-row" v-for="data in dataType" :key="data.name">
|
||||||
|
<view
|
||||||
|
:class="['text', `uni-flex_control_${data.name}`,'uni-view']"
|
||||||
|
v-for="item in sensorCards[data.name]"
|
||||||
|
:key="item.key"
|
||||||
|
@click="openDataModal(item)"
|
||||||
|
>
|
||||||
|
<text class="data" :style="fontStyle">
|
||||||
|
{{ liveData[item.key] }}
|
||||||
|
<text v-if="isEffectiveValue(liveData[item.key])" class="dataStyle">{{data.unit}}</text>
|
||||||
|
</text>
|
||||||
|
<text class="data" v-if="dtu_remark[item.key]">{{ dtu_remark[item.key] }}</text>
|
||||||
|
<text class="data" v-else>{{ item.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-section>
|
||||||
|
|
||||||
|
<uni-section title="设备控制" titleFontSize="16px" type="line"
|
||||||
|
v-if="value!== 1 && !['862538065276939','A','B','C'].includes(value)">
|
||||||
|
<template v-slot:right>
|
||||||
|
{{ status.deviceTime }}
|
||||||
|
</template>
|
||||||
|
<!-- 优化:设备卡片循环渲染 -->
|
||||||
|
<view class="card-grid">
|
||||||
|
<view
|
||||||
|
class="control-card"
|
||||||
|
v-for="card in deviceCards"
|
||||||
|
:key="card.type"
|
||||||
|
@click="openTimeModal(card)"
|
||||||
|
>
|
||||||
|
<view class="card-text">
|
||||||
|
<text class="card-main" v-if="dtu_remark[card.type]">{{ dtu_remark[card.type] }}</text>
|
||||||
|
<text class="card-main" v-else>{{ card.name }}</text>
|
||||||
|
<view class="card-sub-wrapper">
|
||||||
|
<text class="card-sub" v-if="showStatusText">{{ show[card.type] || '未知' }}</text>
|
||||||
|
<text class="limit-time">
|
||||||
|
运行时间:{{
|
||||||
|
limitTimes[`${card.type}`] && limitTimes[`${card.type}`] !== '0' ? `${limitTimes[`${card.type}`]} s` : '- -'
|
||||||
|
}}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="showFlag" class="card-icon" :class="{ active: status[card.type] === 1 }"
|
||||||
|
@click.stop="handleCardClick(1 - status[card.type], card.type)">
|
||||||
|
<!-- 加@click.stop防止冒泡触发卡片点击的弹窗事件 -->
|
||||||
|
<uni-icons
|
||||||
|
:type="status[card.type] === 1 ? 'circle' : 'circle-filled'"
|
||||||
|
size="24"
|
||||||
|
color="#fff"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-section>
|
||||||
|
|
||||||
|
<uni-popup ref="inputNamelog" mode="center">
|
||||||
|
<!-- 新增:修改运行时间的弹窗 -->
|
||||||
|
<view class="modal-container">
|
||||||
|
<view class="modal-title">{{ `【${selectedText} - 实时温湿度】别名设置` }}</view>
|
||||||
|
<view class="modal-input-wrap">
|
||||||
|
<text class="modal-label">别名设置:</text>
|
||||||
|
<uni-easyinput style="width: 100px" v-model="remark" placeholder="不填展示默认备注"/>
|
||||||
|
</view>
|
||||||
|
<view class="modal-btn-wrap">
|
||||||
|
<button class="modal-btn cancel" @click="closeModalDataName">取消</button>
|
||||||
|
<button class="modal-btn confirm" @click="confirmModifyName">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
|
||||||
|
<uni-popup ref="inputDialog" mode="center">
|
||||||
|
<!-- 新增:修改运行时间的弹窗 -->
|
||||||
|
<view class="modal-container">
|
||||||
|
<view class="modal-title">{{ `【${selectedText} - ${currentCard.name}】设置` }}</view>
|
||||||
|
<view class="modal-input-wrap">
|
||||||
|
<text class="modal-label">当前时间:</text>
|
||||||
|
<text class="modal-current">{{ currentCardTime > 0 ? `${currentCardTime} 秒` : '未设置' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="modal-input-wrap">
|
||||||
|
<text class="modal-label">修改后时间:</text>
|
||||||
|
<input
|
||||||
|
class="modal-input"
|
||||||
|
type="number"
|
||||||
|
v-model.number="newLimitTime"
|
||||||
|
/>
|
||||||
|
<!-- <uni-number-box v-model="newLimitTime" />-->
|
||||||
|
<text class="modal-unit">秒</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="modal-input-wrap">
|
||||||
|
<text class="modal-label">别名设置:</text>
|
||||||
|
<uni-easyinput style="width: 100px" v-model="remark" placeholder="不填展示默认备注"/>
|
||||||
|
</view>
|
||||||
|
<view class="modal-btn-wrap">
|
||||||
|
<button class="modal-btn cancel" @click="close">取消</button>
|
||||||
|
<button class="modal-btn confirm" @click="confirmModifyTime">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 优化:抽离魔法值常量
|
||||||
|
import UniPopup from "../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue"; // 引入弹窗组件
|
||||||
|
import mqttUtil from '@/utils/mqtt';
|
||||||
|
import store from "../../../store";
|
||||||
|
import {addRemark, updateRemark} from "../../../api/system/assets/remark";
|
||||||
|
import {saveAgriLimit} from "../../../api/system/assets/limit_bak";
|
||||||
|
import {saveAgriRemark} from "../../../api/system/assets/remark_bak";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
options: {
|
||||||
|
styleIsolation: 'shared'
|
||||||
|
},
|
||||||
|
dicts: ['sys_data_map'],
|
||||||
|
name: "manual",
|
||||||
|
props: {
|
||||||
|
// 尺寸
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: "1"
|
||||||
|
},
|
||||||
|
// 打开时的背景颜色
|
||||||
|
activeColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
dtu_remark: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
liveData: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limitTimes: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
type: Object,
|
||||||
|
default() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedText: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
agriId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
fontStyle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
quiltNum: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
filmNum: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
blindNum: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
deep: true, // 监听对象内部属性变化(防止数据是对象时监听不到)
|
||||||
|
immediate: true, // 关键:进入组件就执行,不用等数据变化
|
||||||
|
handler(newVal) {
|
||||||
|
// 确保数据有值后执行方法
|
||||||
|
if (newVal) {
|
||||||
|
this.imei = this.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
UniPopup // 注册弹窗组件
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sensorCards: {
|
||||||
|
temp: [
|
||||||
|
{label: '温度1', key: 'temp1'},
|
||||||
|
{label: '温度2', key: 'temp2'},
|
||||||
|
{label: '温度3', key: 'temp3'},
|
||||||
|
{label: '温度4', key: 'temp4'}
|
||||||
|
],
|
||||||
|
humi: [
|
||||||
|
{label: '湿度1', key: 'humi1'},
|
||||||
|
{label: '湿度2', key: 'humi2'},
|
||||||
|
{label: '湿度3', key: 'humi3'},
|
||||||
|
{label: '湿度4', key: 'humi4'}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
deviceCards: [
|
||||||
|
{type: 'jbk', name: '卷被开'},
|
||||||
|
{type: 'jbg', name: '卷被关'},
|
||||||
|
{type: 'jm1k', name: '卷膜1开'},
|
||||||
|
{type: 'jm1g', name: '卷膜1关'},
|
||||||
|
{type: 'jm2k', name: '卷膜2开'},
|
||||||
|
{type: 'jm2g', name: '卷膜2关'},
|
||||||
|
{type: 'jm3k', name: '卷膜3开'},
|
||||||
|
{type: 'jm3g', name: '卷膜3关'},
|
||||||
|
{type: 'jlk', name: '卷帘开'},
|
||||||
|
{type: 'jlg', name: '卷帘关'},
|
||||||
|
],
|
||||||
|
// 优化:语义化变量名(替换原 hide: false)
|
||||||
|
showStatusText: false,
|
||||||
|
showFlag: true,
|
||||||
|
remark: '',
|
||||||
|
// 新增:弹窗相关变量
|
||||||
|
currentCard: {}, // 当前点击的卡片信息
|
||||||
|
currentCardTime: '', // 当前卡片的运行时间
|
||||||
|
newLimitTime: 0, // 新的运行时间
|
||||||
|
message: {},
|
||||||
|
// 优化:声明响应式变量 connected
|
||||||
|
connected: false,
|
||||||
|
imei: '',
|
||||||
|
// 优化:温湿度卡片配置(固定顺序:温度1→2→3→4,湿度1→2→3→4)
|
||||||
|
sensorCard:{},
|
||||||
|
dataType: [{name:"temp", unit: "℃"}, {name:"humi", unit: "%RH"}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
refresh() {
|
||||||
|
this.showFlag = !((store.getters && store.getters.name !== 'admin') && this.$auth.hasRole("test"))
|
||||||
|
},
|
||||||
|
openDataModal(sensorCard) {
|
||||||
|
if ((store.getters && store.getters.name !== 'admin')
|
||||||
|
&& this.$auth.hasRole("test")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sensorCard = sensorCard;
|
||||||
|
this.remark = this.dtu_remark[sensorCard.key] || sensorCard.label;
|
||||||
|
this.$refs.inputNamelog.open()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 优化:封装温湿度单位判断函数
|
||||||
|
isEffectiveValue(value) {
|
||||||
|
return this.testNumber(value);
|
||||||
|
},
|
||||||
|
testNumber(data) {
|
||||||
|
const reg = /^-?\d+(\.\d+)?$/;
|
||||||
|
return reg.test(String(data).trim());
|
||||||
|
},
|
||||||
|
// 新增:打开修改运行时间的弹窗
|
||||||
|
openTimeModal(card) {
|
||||||
|
if ((store.getters && store.getters.name !== 'admin')
|
||||||
|
&& this.$auth.hasRole("test")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentCard = card; // 记录当前卡片信息
|
||||||
|
this.currentCardTime = this.limitTimes[`${card.type}`]; // 记录当前时间
|
||||||
|
this.newLimitTime = this.currentCardTime; // 默认填充当前时间
|
||||||
|
this.remark = this.dtu_remark[card.type] || card.name;
|
||||||
|
this.$refs.inputDialog.open()
|
||||||
|
},
|
||||||
|
// 卡片点击事件(实际项目中调用接口修改状态) 功能标识
|
||||||
|
handleCardClick(status, type) {
|
||||||
|
const funcMsg = "该功能用来开启或暂停设备,按钮亮为开启,按钮暗为暂停设备"
|
||||||
|
if ((store.getters && store.getters.name !== 'admin')
|
||||||
|
&& this.$auth.hasRole("test")) {
|
||||||
|
// this.testFunction(`${this.testMsg}\n${funcMsg}`)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验
|
||||||
|
// 定义类型与提示文案的映射关系,减少重复代码
|
||||||
|
const tipMap = {
|
||||||
|
'jbk': {opposite: 'jbg', name: '卷被关', op: '卷被开'},
|
||||||
|
'jbg': {opposite: 'jbk', name: '卷被开', op: '卷被关'},
|
||||||
|
'jlk': {opposite: 'jlg', name: '卷帘关', op: '卷帘开'},
|
||||||
|
'jlg': {opposite: 'jlk', name: '卷帘开', op: '卷帘关'},
|
||||||
|
'jm1k': {opposite: 'jm1g', name: '卷膜1关', op: '卷膜1开'},
|
||||||
|
'jm1g': {opposite: 'jm1k', name: '卷膜1开', op: '卷膜1关'},
|
||||||
|
'jm2k': {opposite: 'jm2g', name: '卷膜2关', op: '卷膜2开'},
|
||||||
|
'jm2g': {opposite: 'jm2k', name: '卷膜2开', op: '卷膜2关'},
|
||||||
|
'jm3k': {opposite: 'jm3g', name: '卷膜3关', op: '卷膜3开'},
|
||||||
|
'jm3g': {opposite: 'jm3k', name: '卷膜3开', op: '卷膜3关'}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 先判断类型是否在映射表中,避免无效case
|
||||||
|
if (!tipMap[type]) return;
|
||||||
|
|
||||||
|
const {opposite, name, op} = tipMap[type];
|
||||||
|
// 核心校验逻辑(只写一次,无需重复)
|
||||||
|
if (status === 1 && this.status[opposite] === 1) {
|
||||||
|
this.$modal.msgError(`【${this.selectedText}】${name}在运行状态,不能运行${op}操作!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 修改:从全局工具类获取连接状态 ==========
|
||||||
|
this.connected = mqttUtil.getMqttState().isConnected;
|
||||||
|
if (!this.connected) {
|
||||||
|
this.$modal.msgError("设备连接异常");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.value === 1) {
|
||||||
|
this.$modal.msgError("设备控制失败!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showModal({
|
||||||
|
title: '操作提示:',
|
||||||
|
content: `确定 ${status === 1 ? "运行" : "暂停"} 【${this.selectedText} - ${op}】设备?`,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// 组装消息
|
||||||
|
this.message = JSON.stringify({[`${type}1`]: status})
|
||||||
|
// 控制设备
|
||||||
|
this.$emit("publicMsg", this.message)
|
||||||
|
//todo
|
||||||
|
// this.testAuto(type);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
closeModalDataName() {
|
||||||
|
this.$refs.inputNamelog.close()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$refs.inputDialog.close()
|
||||||
|
},
|
||||||
|
// 新增:确认修改运行时间
|
||||||
|
// 确认修改运行时间
|
||||||
|
confirmModifyTime: function () {
|
||||||
|
const funcMsg = "该功能用来设置设备运行时间及设备别名,设备运行时间即开启设备后到达运行时间自行暂停设备;设备别名不填则展示默认设备名称"
|
||||||
|
if ((store.getters && store.getters.name !== 'admin')
|
||||||
|
&& this.$auth.hasRole("test")) {
|
||||||
|
// this.testFunction(`${this.testMsg}\n${funcMsg}`)
|
||||||
|
this.$refs?.inputDialog?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1. 解构赋值:简化频繁的this.xxx调用,提升可读性
|
||||||
|
let {
|
||||||
|
newLimitTime, currentCardTime, selectedText, currentCard,
|
||||||
|
limitTimes, dtu_remark, imei, agriId, remark
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
// 2. 恢复并优化数字校验(如需启用,取消注释即可)
|
||||||
|
/*if (!newLimitTime || newLimitTime < 1 || newLimitTime > 60) {
|
||||||
|
uni.showToast({ title: '请输入1-60的有效数字', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// 3. 定义核心判定变量(语义化命名)
|
||||||
|
const isLimitTimeModified = newLimitTime !== currentCardTime; // 运行时间是否修改
|
||||||
|
remark = remark || currentCard.name;
|
||||||
|
const isRemarkModified = (remark !== (dtu_remark[currentCard.type] || currentCard.name)); // 别名是否修改
|
||||||
|
|
||||||
|
// 4. 无修改则直接返回,避免无效弹窗
|
||||||
|
if (!isLimitTimeModified && !isRemarkModified) {
|
||||||
|
// 关闭弹窗(无论确认/取消都关闭)
|
||||||
|
this.$refs?.inputDialog?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 简化提示文案拼接(减少嵌套判断)
|
||||||
|
let confirmContent = `确定修改${selectedText}-${currentCard.name}:`;
|
||||||
|
const tips = [];
|
||||||
|
if (isLimitTimeModified) tips.push(`运行时间为:${newLimitTime} 秒`);
|
||||||
|
if (isRemarkModified) {
|
||||||
|
tips.push(`别名设置为:${remark}`)
|
||||||
|
}
|
||||||
|
confirmContent += `\n${tips.map((tip, idx) => `${idx + 1}. ${tip}?`).join('\n')}`;
|
||||||
|
|
||||||
|
// 6. 确认弹窗(async/await处理异步请求)
|
||||||
|
uni.showModal({
|
||||||
|
title: '温馨提示:',
|
||||||
|
content: confirmContent,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: async (res) => {
|
||||||
|
// 关闭弹窗(无论确认/取消都关闭)
|
||||||
|
this.$refs?.inputDialog?.close();
|
||||||
|
|
||||||
|
if (!res.confirm) return; // 取消操作则直接返回
|
||||||
|
|
||||||
|
let isAllSuccess = false; // 统一判定所有请求是否成功
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 7. 处理运行时间修改
|
||||||
|
if (isLimitTimeModified) {
|
||||||
|
this.$set(limitTimes, `${currentCard.type}`, newLimitTime || 0);
|
||||||
|
// 补全必要字段(合并重复逻辑)
|
||||||
|
const agriLimitInfo = {
|
||||||
|
agriId:agriId,
|
||||||
|
agriName:selectedText,
|
||||||
|
imei:imei,
|
||||||
|
roller:currentCard.type,
|
||||||
|
limit:newLimitTime
|
||||||
|
}
|
||||||
|
saveAgriLimit(agriLimitInfo).then(response => {
|
||||||
|
if (response.code===200) {
|
||||||
|
isAllSuccess = true;
|
||||||
|
this.$emit("getAgriLimit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 处理别名修改
|
||||||
|
if (isRemarkModified) {
|
||||||
|
this.$set(dtu_remark, currentCard.type, remark);
|
||||||
|
const agriRemarkInfo = {
|
||||||
|
imei:imei,
|
||||||
|
roller:currentCard.type,
|
||||||
|
remarkShort:remark
|
||||||
|
}
|
||||||
|
saveAgriRemark(agriRemarkInfo).then(response => {
|
||||||
|
if (response.code===200) {
|
||||||
|
this.$emit("getAgriRemark")
|
||||||
|
isAllSuccess = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 正确的提示逻辑(修复原本文案反写的问题)
|
||||||
|
this.$modal[isAllSuccess ? 'msgSuccess' : 'msgError'](
|
||||||
|
isAllSuccess ? '修改成功' : '修改失败'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// 捕获网络异常/接口报错,避免页面卡死
|
||||||
|
console.error('修改失败:', error);
|
||||||
|
this.$modal.msgError('修改失败,请重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
confirmModifyName() {
|
||||||
|
const funcMsg = "该功能用来设置温湿度卡片别名,如若不填则展示默认备注"
|
||||||
|
if ((store.getters && store.getters.name !== 'admin')
|
||||||
|
&& this.$auth.hasRole("test")) {
|
||||||
|
// this.testFunction(`${this.testMsg}\n${funcMsg}`)
|
||||||
|
this.$refs?.inputNamelog?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 1. 解构赋值优化:直接拆分sensorCard的key/label,减少冗余
|
||||||
|
let {
|
||||||
|
selectedText,
|
||||||
|
dtu_remark,
|
||||||
|
imei,
|
||||||
|
sensorCard: {key: sensorKey, label: sensorLabel},
|
||||||
|
remark
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
remark = remark || sensorLabel;
|
||||||
|
// 2. 语义化判定:别名是否修改
|
||||||
|
const isRemarkModified = remark !== (dtu_remark[sensorKey] || sensorLabel);
|
||||||
|
if (!isRemarkModified) {
|
||||||
|
// 统一关闭弹窗方式
|
||||||
|
this.$refs?.inputNamelog?.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 确认弹窗(async/await处理异步请求)
|
||||||
|
uni.showModal({
|
||||||
|
title: '温馨提示:',
|
||||||
|
content: `确定将${selectedText}-${sensorLabel}别名设置为:${remark}?`, // 修复文案冗余冒号,补全remark变量
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: async (res) => {
|
||||||
|
// 关闭弹窗(无论确认/取消都关闭)
|
||||||
|
this.$refs?.inputNamelog?.close();
|
||||||
|
|
||||||
|
if (!res.confirm) return; // 取消操作直接返回
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 4. 设置loading(可选,防止重复点击)
|
||||||
|
// uni.showLoading({ title: '处理中...' });
|
||||||
|
|
||||||
|
// 5. 更新响应式数据
|
||||||
|
this.$set(dtu_remark, sensorKey, remark); // sensorKey本身是字符串,无需拼接
|
||||||
|
// 补全imei字段(确保非空)
|
||||||
|
const agriRemarkInfo = {
|
||||||
|
imei:imei,
|
||||||
|
roller:sensorKey,
|
||||||
|
remarkShort:remark
|
||||||
|
}
|
||||||
|
saveAgriRemark(agriRemarkInfo).then(response => {
|
||||||
|
if (response.code===200) {
|
||||||
|
this.$emit("getAgriRemark")
|
||||||
|
this.$modal.msgSuccess('修改成功');
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError('修改失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// 捕获网络异常/接口报错
|
||||||
|
console.error('别名修改失败:', error);
|
||||||
|
this.$modal.msgError('修改失败,请重试');
|
||||||
|
} finally {
|
||||||
|
// 无论成功/失败,关闭loading
|
||||||
|
// uni.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 2列栅格布局 */
|
||||||
|
.card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片样式 */
|
||||||
|
.control-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx 20rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 2rpx 8rpx #bfbec1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片文字区域 */
|
||||||
|
.card-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-main {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 保留原有样式,仅修改/新增以下部分 */
|
||||||
|
.card-sub-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10rpx;
|
||||||
|
/* 新增:给容器加最小宽度,确保所有卡片一致 */
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
/* 可选:加固定左边距,确保和暂停的间距统一 */
|
||||||
|
margin-left: 0rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片图标容器 */
|
||||||
|
.card-icon {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 激活状态(运行) */
|
||||||
|
.card-icon.active {
|
||||||
|
background-color: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-px-5 {
|
||||||
|
padding-left: 20rpx;
|
||||||
|
padding-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-pb-5 {
|
||||||
|
padding-bottom: 15rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
width: 50rpx;
|
||||||
|
margin: 10rpx 10rpx 8rpx 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 70rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx #bfbec1
|
||||||
|
}
|
||||||
|
|
||||||
|
.dataStyle {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:first-child {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-view {
|
||||||
|
-webkit-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
height: 150rpx;
|
||||||
|
-webkit-justify-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 23px;
|
||||||
|
color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data:nth-child(even) {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #9c9c9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .uni-section-header__slot-right {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新增:弹窗样式 */
|
||||||
|
.modal-container {
|
||||||
|
width: 600rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 35rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-label {
|
||||||
|
width: 160rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-current {
|
||||||
|
margin-left: 14rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-input {
|
||||||
|
height: 60rpx;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 15rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
width: 80rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-unit {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-wrap {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 70rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.cancel {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn.confirm {
|
||||||
|
background: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: 15rpx
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .is-input-border {
|
||||||
|
width: 340rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
782
pages/index.vue
|
|
@ -1,36 +1,774 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="content">
|
<view class="page-container">
|
||||||
<image class="logo" src="@/static/logo200.png"></image>
|
<z-paging ref="home" refresher-only bg-color="radial-gradient(circle at top left, #E8F4F8 40%, #F8FCFE 100%)"
|
||||||
<view class="text-area">
|
:show-empty="false" :show-footer="false" @onRefresh="refresh">
|
||||||
<text class="title">Hello Agri</text>
|
<template #refresher="{refresherStatus}">
|
||||||
|
<!-- 此处的custom-refresh为demo中自定义的组件,非z-paging的内置组件,请在实际项目中自行创建。这里插入什么view,下拉刷新就显示什么view -->
|
||||||
|
<custom-refresher :status="refresherStatus" />
|
||||||
|
</template>
|
||||||
|
<template #top>
|
||||||
|
<uni-row class="demo-uni-row" >
|
||||||
|
<uni-col :span="7">
|
||||||
|
<uni-data-select align="left" :clear="false" v-model="value" @change="change" :localdata="range" ></uni-data-select>
|
||||||
|
</uni-col>
|
||||||
|
<uni-col :span="17">
|
||||||
|
<!-- confirm:confirm; input:change; cancel:点击取消;
|
||||||
|
clear:点击清除;focus:获取焦点;blur:失去焦点 -->
|
||||||
|
<uni-search-bar bgColor="#fbfdfe"
|
||||||
|
v-model="searchValue"
|
||||||
|
cancelButton="always"
|
||||||
|
@input="input"
|
||||||
|
clearButton="always"
|
||||||
|
@cancel="cancel"
|
||||||
|
@clear="cancel"
|
||||||
|
placeholder="请输入搜索内容">
|
||||||
|
<template v-slot:searchIcon>
|
||||||
|
<yt-scanCode @getScanCode="getScanCode"></yt-scanCode>
|
||||||
|
</template>
|
||||||
|
</uni-search-bar>
|
||||||
|
</uni-col>
|
||||||
|
</uni-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<uni-section class="mb-10" title="温室列表" style="background:transparent;" type="line">
|
||||||
|
<template v-slot:right>
|
||||||
|
<view>
|
||||||
|
<text style="color: #f64040">{{ dataDetails.online }} 在线 </text>
|
||||||
|
<text style="color: #2d56d8;margin-left: 10rpx">{{ dataDetails.offline }} 离线</text>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="list-scroll"
|
||||||
|
@scrolltolower="loadMore"
|
||||||
|
lower-threshold="50"
|
||||||
|
scroll-with-animation
|
||||||
|
>
|
||||||
|
<uni-swipe-action>
|
||||||
|
<uni-swipe-action-item
|
||||||
|
v-for="(item, index) in listData"
|
||||||
|
:key="index"
|
||||||
|
@click="onDeleteItem($event, item)"
|
||||||
|
:class="['list-item', { 'list-item-disabled': item.online === '离线' }]"
|
||||||
|
:right-options="options">
|
||||||
|
<view class="item-card"
|
||||||
|
@click="onItemTap(item)">
|
||||||
|
<view class="item-title">
|
||||||
|
<view class="title-text">{{ item.agriName }}</view>
|
||||||
|
<text :class="['tag','tag-status',{'tag-manual':item.workModeDesc==='手动模式'}]" v-if="item.workModeDesc">{{ item.workModeDesc }}</text>
|
||||||
|
<text :class="['tag','tag-online',{'tag-offline':item.online==='离线'}]" v-if="item.online">{{ item.online }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="item-subtitle">
|
||||||
|
设备编号:{{ item.imei }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="item-tags">
|
||||||
|
<text class="tag tag-temp1">温度1: {{ item.temp1 }}℃</text>
|
||||||
|
<text class="tag tag-temp2">温度2: {{ item.temp2 }}℃</text>
|
||||||
|
<text class="tag tag-temp3">温度3: {{ item.temp3 }}℃</text>
|
||||||
|
<text class="tag tag-temp4">温度4: {{ item.temp4 }}℃</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-swipe-action-item>
|
||||||
|
|
||||||
|
<view v-if="isLoading" class="loading-more">
|
||||||
|
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="noMore" class="no-more">没有更多数据了</view>
|
||||||
|
<!-- v-if="store.getters.name !== 'admin'"-->
|
||||||
|
<view class="add-agri" @tap="handleAddAgri" v-if="!isLoading">
|
||||||
|
<image :src="imageSrc" :style="'width:'+80+'rpx;height:'+80+'rpx'"></image>
|
||||||
|
<text class="text">点击添加大棚</text>
|
||||||
|
</view>
|
||||||
|
</uni-swipe-action>
|
||||||
|
</scroll-view>
|
||||||
|
</uni-section>
|
||||||
|
<!-- <uni-fab ref="fab" :pattern="pattern" :content="content" :horizontal="horizontal" :vertical="vertical"-->
|
||||||
|
<!-- :direction="direction" @trigger="trigger" @fabClick="fabClick" />-->
|
||||||
|
|
||||||
|
</z-paging>
|
||||||
|
<add-agri ref="addAgri" @reload="getListData"/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ZPaging from "../uni_modules/z-paging/components/z-paging/z-paging.vue";
|
||||||
|
import CustomRefresher from "../components/custom-refresher/custom-refresher.vue";
|
||||||
|
import { getAgriInfo} from "../api/system/assets/agri";
|
||||||
|
import store from "../store";
|
||||||
|
import TimeUtil from "../utils/TimeUtil";
|
||||||
|
import {getNewSpecialData} from "../api/data/specialData";
|
||||||
|
import AddAgri from "../components/addAgri/addAgri.vue";
|
||||||
|
import {removeAgri} from "../api/system/assets/userAgri";
|
||||||
|
import * as mqttUtil from "../utils/mqtt";
|
||||||
|
import {getAgriStatus} from "../api/system/mqtt";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
store() {
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {AddAgri, CustomRefresher, ZPaging},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
listData: [],
|
||||||
|
datas: [],
|
||||||
|
pageSize: 6,
|
||||||
|
isLoading: false,
|
||||||
|
noMore: false,
|
||||||
|
total: 20, // 模拟总条数
|
||||||
|
value: 0,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: '删除',
|
||||||
|
icon: 'delete',
|
||||||
|
style: {
|
||||||
|
backgroundColor: '#E83A30'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
range: [{
|
||||||
|
"value": 0,
|
||||||
|
"text": "大棚名称",
|
||||||
|
}, {
|
||||||
|
"value": 1,
|
||||||
|
"text": "设备编号",
|
||||||
|
}],
|
||||||
|
imageSrc:'/static/agri.png',
|
||||||
|
scrollHeight: 0,
|
||||||
|
// horizontal: 'right',
|
||||||
|
// vertical: 'bottom',
|
||||||
|
// direction: 'horizontal',
|
||||||
|
searchValue: null,
|
||||||
|
imeiToDeviceMap: new Map(),
|
||||||
|
dataDetails: {
|
||||||
|
tip: null,
|
||||||
|
online: 0,
|
||||||
|
offline: 0
|
||||||
|
}
|
||||||
|
/* pattern: {
|
||||||
|
color: '#7A7E83',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
selectedColor: '#007AFF',
|
||||||
|
buttonColor: '#007AFF',
|
||||||
|
iconColor: '#fff'
|
||||||
|
},
|
||||||
|
content: [{
|
||||||
|
iconPath: '/static/image.png',
|
||||||
|
selectedIconPath: '/static/image-active.png',
|
||||||
|
text: '添加大棚',
|
||||||
|
active: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconPath: '/static/home.png',
|
||||||
|
selectedIconPath: '/static/home-active.png',
|
||||||
|
text: '',
|
||||||
|
active: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconPath: '/static/star.png',
|
||||||
|
selectedIconPath: '/static/star-active.png',
|
||||||
|
text: '收藏',
|
||||||
|
active: false
|
||||||
|
}
|
||||||
|
]*/
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
onBackPress() {
|
||||||
|
/* if (this.$refs.fab.isShow) {
|
||||||
|
this.$refs.fab.close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false*/
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.info("首页在线状态注册回调")
|
||||||
|
this.$modal.loading("数据加载中,请耐心等待...")
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
console.info("首页在线状态注销回调")
|
||||||
|
mqttUtil.removeOnMessageCallback();
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getScanCode(res){
|
||||||
|
this.searchValue = res;
|
||||||
|
this.value = 1;
|
||||||
|
if (this.searchValue) {
|
||||||
|
this.change(this.searchValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleAddAgri() {
|
||||||
|
this.$refs.addAgri.open();
|
||||||
|
// 这里可以添加跳转到添加大棚页面的逻辑
|
||||||
|
// uni.navigateTo({ url: '/pages/add-agri/add-agri' })
|
||||||
|
},
|
||||||
|
change(e) {
|
||||||
|
this.input(this.searchValue)
|
||||||
|
},
|
||||||
|
input(res) {
|
||||||
|
if (!res) {
|
||||||
|
this.cancel(res)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listData = this.datas.filter(item =>
|
||||||
|
item[this.value === 0 ? 'agriName' : 'imei'].includes(res));
|
||||||
|
},
|
||||||
|
cancel(res) {
|
||||||
|
this.listData = [...this.datas]
|
||||||
|
},
|
||||||
|
/*trigger(e) {
|
||||||
|
console.log(e)
|
||||||
|
this.content[e.index].active = !e.item.active
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: `您${this.content[e.index].active ? '选中了' : '取消了'}${e.item.text}`,
|
||||||
|
success: function(res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
console.log('用户点击确定')
|
||||||
|
} else if (res.cancel) {
|
||||||
|
console.log('用户点击取消')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fabClick() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '点击了悬浮按钮',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},*/
|
||||||
|
|
||||||
|
// 模拟获取列表数据
|
||||||
|
getListData() {
|
||||||
|
this.noMore = false;
|
||||||
|
this.listData = [];
|
||||||
|
this.isLoading = true
|
||||||
|
|
||||||
|
getAgriInfo().then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
// 先清空旧索引
|
||||||
|
this.imeiToDeviceMap.clear();
|
||||||
|
// 1. 用 map 处理数据,返回新数组,避免 forEach 返回 undefined
|
||||||
|
// 2. 区分刷新/加载更多,更新 listData
|
||||||
|
this.listData = response.data.map(item => {
|
||||||
|
const deviceItem = {
|
||||||
|
...item,
|
||||||
|
deviceStatus: TimeUtil.isLessThanSpecifiedSeconds(item.time, 90) ? '在线' : '离线',
|
||||||
|
online: TimeUtil.isLessThanSpecifiedSeconds(item.time, 90) ? '在线' : '离线',
|
||||||
|
agriName: item.agriName || '未知大棚',
|
||||||
|
imei: `${item.imei || '未知'}`,
|
||||||
|
workModeDesc: item.workMode === 0 ? '手动模式':'自动模式',
|
||||||
|
workMode: item.workMode,
|
||||||
|
temp1: item.temp1,
|
||||||
|
temp2: item.temp2,
|
||||||
|
temp3: item.temp3,
|
||||||
|
temp4: item.temp4
|
||||||
|
};
|
||||||
|
// 存入索引Map
|
||||||
|
this.imeiToDeviceMap.set(deviceItem.imei, deviceItem);
|
||||||
|
return deviceItem;
|
||||||
|
});
|
||||||
|
// 5. 判断是否还有更多数据
|
||||||
|
this.noMore = this.listData.length >= this.total;
|
||||||
|
if (store.getters && store.getters.name === 'admin') {
|
||||||
|
this.getNewSpecialData();
|
||||||
|
}
|
||||||
|
// 左上角详情
|
||||||
|
this.getDataDetails();
|
||||||
|
this.datas = [...this.listData]
|
||||||
|
this.getAgriStatus();
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("获取大棚信息失败:", err);
|
||||||
|
}).finally(() => {
|
||||||
|
// 4. 无论成功失败,都结束加载状态
|
||||||
|
this.isLoading = false;
|
||||||
|
this.$modal.closeLoading()
|
||||||
|
this.$refs.home.complete();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 加载更多(防抖:避免快速上拉重复触发)
|
||||||
|
loadMore() {
|
||||||
|
if (!this.isLoading && !this.noMore) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refresh() {
|
||||||
|
mqttUtil.setOnMessageCallback(this.ackMessage);
|
||||||
|
this.getListData();
|
||||||
|
},
|
||||||
|
onDeleteItem(item, agri) {
|
||||||
|
if (item.content.text==='删除') {
|
||||||
|
uni.showModal({
|
||||||
|
title: '操作提示:',
|
||||||
|
content: `确定删除大棚【${agri.agriName}】?`,
|
||||||
|
cancelText: '取消',
|
||||||
|
confirmText: '确定',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
removeAgri(agri).then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.$modal.msgSuccess("删除成功!")
|
||||||
|
this.getListData();
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError("删除失败!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 点击列表项
|
||||||
|
onItemTap(item) {
|
||||||
|
|
||||||
|
var agri = JSON.stringify(
|
||||||
|
{
|
||||||
|
imei:item.imei,
|
||||||
|
agriName:item.agriName,
|
||||||
|
agriId:item.id,
|
||||||
|
workMode: item.workMode,
|
||||||
|
quiltNum:item.quiltNum,
|
||||||
|
filmNum:item.filmNum,
|
||||||
|
blindNum:item.blindNum
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.$tab.navigateTo('/pages/home/control/index?agriInfo='+encodeURIComponent(agri))
|
||||||
|
},
|
||||||
|
getNewSpecialData() {
|
||||||
|
getNewSpecialData().then(response => {
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
this.makeSpecialData(response.data);
|
||||||
|
}
|
||||||
|
this.datas = [...this.listData]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
makeSpecialData: function (msgData){
|
||||||
|
const online = TimeUtil.isLessThanSpecifiedSeconds(msgData.ts,90)?'在线':'离线';
|
||||||
|
const deviceConfigs = [
|
||||||
|
{ agriName: "八方南棚", imei: "A", temps: [1,2,3,4] },
|
||||||
|
{ agriName: "九方春棚", imei: "B", temps: [5,6,7,8] },
|
||||||
|
{ agriName: "十二方棚", imei: "C", temps: [9,10,11,12] }
|
||||||
|
];
|
||||||
|
|
||||||
|
const devices = deviceConfigs.map(({agriName, imei, temps}) => ({
|
||||||
|
agriName,
|
||||||
|
imei,
|
||||||
|
temp1: msgData[`temp${temps[0]}`],
|
||||||
|
temp2: msgData[`temp${temps[1]}`],
|
||||||
|
temp3: msgData[`temp${temps[2]}`],
|
||||||
|
temp4: msgData[`temp${temps[3]}`],
|
||||||
|
online
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.listData.push(...devices);
|
||||||
|
// 状态和温度不需要监听不需要搞
|
||||||
|
// devices.forEach(d => this.imeiToDeviceMap.set(d.imei, d));
|
||||||
|
},
|
||||||
|
ackMessage(topic, payload) {
|
||||||
|
const regex = /^device\/\d+\/status$/;
|
||||||
|
const regex1 = /^frontend\/.+\/dtu\/\d+\/listener$/;
|
||||||
|
|
||||||
|
if (!regex.test(topic) && !regex1.test(topic)) return;
|
||||||
|
|
||||||
|
let msgData = {};
|
||||||
|
// 优化:捕获JSON解析异常
|
||||||
|
try {
|
||||||
|
msgData = JSON.parse(payload);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("MQTT消息解析失败:", e, payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 设备在线状态
|
||||||
|
if (regex.test(topic) && msgData.online && "time" in msgData && "imei" in msgData) {
|
||||||
|
this.updateDeviceStatusFast(msgData.imei, msgData.online)
|
||||||
|
}
|
||||||
|
// 温度
|
||||||
|
const allKeysNumeric2 = Object.keys(msgData).every(key => /^\d+$/.test(key));
|
||||||
|
if (regex1.test(topic) && Object.keys(msgData).length > 0 && allKeysNumeric2) {
|
||||||
|
this.updateTempFast(msgData, topic)
|
||||||
|
}
|
||||||
|
this.getDataDetails();
|
||||||
|
},
|
||||||
|
// 离线状态查询 是先获取大棚表,无线表不存在大棚表里
|
||||||
|
updateDeviceStatusFast(targetImei, isOnline) {
|
||||||
|
console.info("监听到状态",targetImei, isOnline)
|
||||||
|
// 直接从Map里取设备项,无需遍历数组
|
||||||
|
const targetDevice = this.imeiToDeviceMap.get(targetImei);
|
||||||
|
if (!targetDevice) return;
|
||||||
|
// 直接修改对象属性,响应式正常生效
|
||||||
|
targetDevice.deviceStatus = isOnline;
|
||||||
|
targetDevice.online = isOnline;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTempFast: function (msgData,topic) {
|
||||||
|
const regexWithGroup = /^frontend\/(.+)\/dtu\/(\d+)\/listener$/;
|
||||||
|
const matchResult = topic.match(regexWithGroup);
|
||||||
|
const imei = matchResult[2]; // 提取结果:'1234567890'
|
||||||
|
// 直接从Map里取设备项,无需遍历数组
|
||||||
|
const targetDevice = this.imeiToDeviceMap.get(imei);
|
||||||
|
if (!targetDevice) return;
|
||||||
|
const div10 = (v) => (v == null ? null : Math.round((Number(v) / 10) * 10) / 10);
|
||||||
|
// 直接修改对象属性,响应式正常生效
|
||||||
|
|
||||||
|
targetDevice.temp1 = div10(msgData['201']);
|
||||||
|
targetDevice.temp2 = div10(msgData['202']);
|
||||||
|
targetDevice.temp3 = div10(msgData['203']);
|
||||||
|
targetDevice.temp4 = div10(msgData['204']);
|
||||||
|
|
||||||
|
// if (imei === '862538065276061') {
|
||||||
|
// const deviceConfigs = [
|
||||||
|
// { agriName: "八方南棚", imei: "A", temps: [1,2,3,4] },
|
||||||
|
// { agriName: "九方春棚", imei: "B", temps: [5,6,7,8] },
|
||||||
|
// { agriName: "十二方棚", imei: "C", temps: [9,10,11,12] }
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
// deviceConfigs.forEach(({agriName, imei, temps}) => {
|
||||||
|
// var target = this.imeiToDeviceMap.get(imei);
|
||||||
|
// target.temp1 = div10(msgData[`20${temps[0]}`]);
|
||||||
|
// target.temp2 = div10(msgData[`20${temps[1]}`]);
|
||||||
|
// target.temp3 = div10(msgData[`20${temps[2]}`]);
|
||||||
|
// target.temp4 = div10(msgData[`20${temps[3]}`]);
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
getDataDetails() {
|
||||||
|
this.dataDetails = {
|
||||||
|
tip: null,
|
||||||
|
online: 0,
|
||||||
|
offline: 0
|
||||||
|
}
|
||||||
|
var online = 0;
|
||||||
|
var offline = 0;
|
||||||
|
this.listData.forEach(item => {
|
||||||
|
if (item.online === '离线') {
|
||||||
|
offline += 1;
|
||||||
|
} else {
|
||||||
|
online += 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.dataDetails = {
|
||||||
|
tip: null,
|
||||||
|
online: online,
|
||||||
|
offline: offline,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAgriStatus() {
|
||||||
|
if (!this.listData || this.listData.length === 0) return []; // 空列表处理
|
||||||
|
const allImei = this.listData.map(item => item.imei).filter(imei => imei);
|
||||||
|
getAgriStatus([...new Set(allImei)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.content {
|
/* 适配小程序/H5的盒模型,避免padding导致宽度溢出 */
|
||||||
|
page {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.page-container {
|
||||||
|
padding: 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-scroll {
|
||||||
|
/* 把背景色改为透明,才能透出背景渐变 */
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
/* 新增:H5端避免滚动条样式不一致 */
|
||||||
|
scrollbar-width: thin;
|
||||||
|
/* 去掉固定高度,改为自适应(避免小程序滚动异常) */
|
||||||
|
height: calc(100vh - 180rpx);
|
||||||
|
/* #ifdef MP-WEIXIN */
|
||||||
|
height: calc(100vh - var(--status-bar-height) - 180rpx);
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
/* H5端滚动条样式优化(可选) */
|
||||||
|
.list-scroll::-webkit-scrollbar {
|
||||||
|
width: 4rpx;
|
||||||
|
}
|
||||||
|
.list-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
}
|
||||||
|
.item-card {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-shadow: 8rpx 5rpx 10rpx #999;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
// #ifndef H5
|
||||||
|
//margin-right: 10rpx;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/deep/ .uni-swipe {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
height: 214rpx !important;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/deep/ .uni-swipe_button {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
height: 198rpx !important;
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
height: 192rpx !important;
|
||||||
|
// #endif
|
||||||
|
margin-right: 10rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-shadow: 8rpx 5rpx 10rpx #999;
|
||||||
|
}
|
||||||
|
.list-item,/deep/.tn-swipe-action-item {
|
||||||
|
height: 190rpx !important;
|
||||||
|
/* 移除border-bottom,改为margin-top,并且把列表项背景设为半透明白色 */
|
||||||
|
// #ifdef H5
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
/* 新增:点击态样式,双端统一 */
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 8rpx 5rpx 10rpx #999;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
.list-item:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
.item-card:active {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
transform: scale(0.98);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 52rpx;
|
||||||
|
font-weight: bolder;
|
||||||
|
color: #333;
|
||||||
|
/* 核心:flex布局,让文字和标签在同一行 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 14rpx 25rpx 0 25rpx;
|
||||||
|
}
|
||||||
|
.title-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
/* 在线标签样式 */
|
||||||
|
.tag-online {
|
||||||
|
font-size: 20rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
background-color: #f0f9eb;
|
||||||
|
color: #67c23a;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 30rpx;
|
||||||
|
/* 兜底:如果flex失效,用margin-left:auto强制居右 */
|
||||||
|
margin-left: auto;
|
||||||
|
display: inline-block; /* 确保样式生效 */
|
||||||
|
}
|
||||||
|
.tag-status.tag-manual {
|
||||||
|
background-color: #dde3f6;
|
||||||
|
color: #7491ef;
|
||||||
|
}
|
||||||
|
/* 离线:深灰色系 */
|
||||||
|
.tag-offline {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
color: #c5c3c3;
|
||||||
|
}
|
||||||
|
.item-subtitle {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 52rpx;
|
||||||
|
display: block;
|
||||||
|
padding: 0 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
flex-wrap: wrap; /* 新增:标签过多时换行,避免溢出 */
|
||||||
|
padding: 14rpx 25rpx 28rpx 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
font-size: 22rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
display: inline-block; /* 修复H5端padding不生效问题 */
|
||||||
|
}
|
||||||
|
.tag-status {
|
||||||
|
background-color: #f8f1f1;
|
||||||
|
color: #e66060;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
font-weight: lighter;
|
||||||
|
line-height: 30rpx;
|
||||||
|
padding: 4rpx 6rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-temp1 {
|
||||||
|
background-color: #e8f4ff;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-temp2 {
|
||||||
|
background-color: #fdf4e4;
|
||||||
|
color: #f5961d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-temp3 {
|
||||||
|
background-color: #f4f4f5;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-temp4 {
|
||||||
|
background-color: #e7faf4;
|
||||||
|
color: #1bd1a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-more {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
padding: 30rpx;
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
gap: 10rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.no-more {
|
||||||
height: 200rpx;
|
/* 核心:flex布局实现居中 */
|
||||||
width: 200rpx;
|
|
||||||
margin-top: 200rpx;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 50rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-area {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
/* 占满滚动容器剩余高度,保证垂直居中 */
|
||||||
|
min-height: 200rpx; /* 最小高度,避免内容太少时不居中 */
|
||||||
|
/* 文字样式保留 */
|
||||||
|
color: #999;
|
||||||
|
font-size: 26rpx;
|
||||||
|
/* 可选:增加上下内边距,视觉更舒适 */
|
||||||
|
padding: 30rpx 0;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新增:禁用状态样式 */
|
||||||
|
.list-item-disabled {
|
||||||
|
/* 文字置灰 */
|
||||||
|
color: #999;
|
||||||
|
/* 禁用光标 */
|
||||||
|
cursor: not-allowed;
|
||||||
|
/* 背景色变浅,视觉区分 */
|
||||||
|
background-color: #fafafa;
|
||||||
|
/* 可选:添加透明度 */
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 禁用状态下的子元素也置灰 */
|
||||||
|
.list-item-disabled .tag {
|
||||||
|
color: #ccc;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .uni-searchbar {
|
||||||
|
padding: 20rpx 0;
|
||||||
|
}
|
||||||
|
/deep/ .uni-searchbar__box, /deep/ .uni-select {
|
||||||
|
box-shadow: 8rpx 5rpx 10rpx #999;
|
||||||
|
background: #fbfdfe;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
height: 70rpx !important;
|
||||||
|
}
|
||||||
|
/deep/ .uni-searchbar__box {
|
||||||
|
border-radius: 0 10rpx 10rpx 0 !important;
|
||||||
|
}
|
||||||
|
.uni-stat__select {
|
||||||
|
padding: 20rpx 0;
|
||||||
|
}
|
||||||
|
/deep/ .uni-select {
|
||||||
|
border: 0;
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
margin-top: 20rpx !important;
|
||||||
|
// #endif
|
||||||
|
border-radius: 10rpx 0 0 10rpx !important;
|
||||||
|
}
|
||||||
|
/deep/ .uni-section {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
/* 强制隐藏搜索图标容器 */
|
||||||
|
/*/deep/ .uni-searchbar__box-icon-search {
|
||||||
|
display: none !important;
|
||||||
|
}*/
|
||||||
|
/deep/ .uni-section .uni-section-header {
|
||||||
|
padding: 12rpx 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加大棚容器样式 */
|
||||||
|
.add-agri {
|
||||||
|
/* 核心:flex布局实现一行显示 + 垂直居中 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 水平居中(可选,根据需求调整) */
|
||||||
|
justify-content: center;
|
||||||
|
/* 内边距,增加点击区域和视觉间距 */
|
||||||
|
padding: 30rpx 0;
|
||||||
|
/* 可选:添加背景和圆角,和列表项风格统一 */
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
border-radius: 12rpx;
|
||||||
|
/* 点击态效果,和列表项保持一致 */
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.add-agri:active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片和文字的间距 */
|
||||||
|
.add-agri image {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
/* 可选:图片垂直对齐,兜底兼容 */
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文字样式优化 */
|
||||||
|
.add-agri .text {
|
||||||
|
/* 文字大小,和标签风格统一 */
|
||||||
|
font-size: 26rpx;
|
||||||
|
/* 文字颜色(保留原有绿色) */
|
||||||
|
color: #1AAD19;
|
||||||
|
/* 可选:加粗,突出按钮感 */
|
||||||
|
font-weight: 500;
|
||||||
|
/* 兜底:垂直对齐 */
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .z-paging-content-fixed {
|
||||||
|
padding: 20rpx !important;
|
||||||
|
/* 可选:防止margin塌陷,加overflow */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #8f8f94;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -31,7 +31,17 @@
|
||||||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="login-page" v-if="false">
|
||||||
|
<!-- 授权登录按钮 -->
|
||||||
|
<view class="submit-btn" @tap.stop="openAuthorizationModal">
|
||||||
|
授权登录
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<wx-user-info-modal
|
||||||
|
v-model="showAuthorizationModal"
|
||||||
|
@updated="updatedUserInfoEvent"
|
||||||
|
></wx-user-info-modal>
|
||||||
|
</view>
|
||||||
<!-- ========== 新增:uni-data-checkbox 记住密码(可正常勾选) ========== -->
|
<!-- ========== 新增:uni-data-checkbox 记住密码(可正常勾选) ========== -->
|
||||||
<view style="margin: 10px 0 0 10px; text-align: left;">
|
<view style="margin: 10px 0 0 10px; text-align: left;">
|
||||||
<uni-data-checkbox
|
<uni-data-checkbox
|
||||||
|
|
@ -76,7 +86,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getCodeImg } from '@/api/login'
|
import WxUserInfoModal from '@/uni_modules/tuniaoui-wx-user-info/components/tuniaoui-wx-user-info/tuniaoui-wx-user-info.vue'
|
||||||
|
|
||||||
|
import { getCodeImg } from '@/api/login'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import UniIcons from "../uni_modules/uni-icons/components/uni-icons/uni-icons.vue";
|
import UniIcons from "../uni_modules/uni-icons/components/uni-icons/uni-icons.vue";
|
||||||
// ========== 新增:引入uni-data-checkbox ==========
|
// ========== 新增:引入uni-data-checkbox ==========
|
||||||
|
|
@ -85,11 +97,12 @@
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// ========== 新增:注册组件 ==========
|
// ========== 新增:注册组件 ==========
|
||||||
components: {UniIcons, UniDataCheckbox},
|
components: {UniIcons, UniDataCheckbox,WxUserInfoModal},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isShowPwd: false,
|
isShowPwd: false,
|
||||||
codeUrl: "",
|
codeUrl: "",
|
||||||
|
showAuthorizationModal: false,
|
||||||
captchaEnabled: true,
|
captchaEnabled: true,
|
||||||
// 用户注册开关
|
// 用户注册开关
|
||||||
register: true,
|
register: true,
|
||||||
|
|
@ -111,11 +124,9 @@
|
||||||
this.getCode()
|
this.getCode()
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
//#ifdef H5
|
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
this.$tab.reLaunch('/pages/control/index')
|
this.$tab.reLaunch('/pages/index')
|
||||||
}
|
}
|
||||||
//#endif
|
|
||||||
// ========== 新增:页面加载时自动回填密码 ==========
|
// ========== 新增:页面加载时自动回填密码 ==========
|
||||||
this.loadSavedPwd()
|
this.loadSavedPwd()
|
||||||
},
|
},
|
||||||
|
|
@ -127,6 +138,15 @@
|
||||||
this.remember = true ;
|
this.remember = true ;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 打开获取用户信息弹框
|
||||||
|
openAuthorizationModal() {
|
||||||
|
this.showAuthorizationModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取到的用户信息
|
||||||
|
updatedUserInfoEvent(info) {
|
||||||
|
console.log('获取到的用户信息', info)
|
||||||
|
},
|
||||||
// ========== 新增:密码加密存储核心方法 ==========
|
// ========== 新增:密码加密存储核心方法 ==========
|
||||||
// ========== 【小程序修复版】生成加密密钥(无崩溃,兼容微信环境) ==========
|
// ========== 【小程序修复版】生成加密密钥(无崩溃,兼容微信环境) ==========
|
||||||
getEncryptKey() {
|
getEncryptKey() {
|
||||||
|
|
@ -282,7 +302,7 @@
|
||||||
app.loginSuccess(token)
|
app.loginSuccess(token)
|
||||||
|
|
||||||
// ========== 原有逻辑保留 ==========
|
// ========== 原有逻辑保留 ==========
|
||||||
this.$tab.reLaunch('/pages/control/index')
|
this.$tab.reLaunch('/pages/index')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,5 +402,26 @@
|
||||||
|
|
||||||
.icp { position: fixed; bottom: 0; width: 100%; background: #fff; padding: 12px 0; box-shadow: 0 -2px 4px rgba(0,0,0,0.1); }
|
.icp { position: fixed; bottom: 0; width: 100%; background: #fff; padding: 12px 0; box-shadow: 0 -2px 4px rgba(0,0,0,0.1); }
|
||||||
.icp-one { max-width: 800px; margin: 0 auto; text-align: center; font-size: 12px; color: #666; }
|
.icp-one { max-width: 800px; margin: 0 auto; text-align: center; font-size: 12px; color: #666; }
|
||||||
|
.login-page {
|
||||||
|
width: 300rpx;
|
||||||
|
height: 100rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 授权按钮 */
|
||||||
|
.submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #05C160;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-top: 60rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 25rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 30rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@
|
||||||
<view class="iconfont icon-friendfill text-pink icon"></view>
|
<view class="iconfont icon-friendfill text-pink icon"></view>
|
||||||
<text class="text">交流群</text>
|
<text class="text">交流群</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="action-item" @click="handleBuilding">
|
<button class="action-item contact-btn" open-type="contact">
|
||||||
<view class="iconfont icon-service text-blue icon"></view>
|
<view class="iconfont icon-service text-blue icon"></view>
|
||||||
<text class="text">在线客服</text>
|
<text class="text">在线客服</text>
|
||||||
</view>
|
</button>
|
||||||
<view class="action-item" @click="handleBuilding">
|
<view class="action-item" @click="handleBuilding">
|
||||||
<view class="iconfont icon-community text-mauve icon"></view>
|
<view class="iconfont icon-community text-mauve icon"></view>
|
||||||
<text class="text">反馈社区</text>
|
<text class="text">反馈社区</text>
|
||||||
|
|
@ -110,31 +110,31 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
handleToInfo() {
|
handleToInfo() {
|
||||||
this.$tab.navigateTo('/pages/mine/info/index')
|
this.$tab.navigateTo('/pages/mine/subpages/info/index')
|
||||||
},
|
},
|
||||||
handleToEditInfo() {
|
handleToEditInfo() {
|
||||||
this.$tab.navigateTo('/pages/mine/info/edit')
|
this.$tab.navigateTo('/pages/mine/subpages/info/edit')
|
||||||
},
|
},
|
||||||
handleToSetting() {
|
handleToSetting() {
|
||||||
this.$tab.navigateTo('/pages/mine/setting/index')
|
this.$tab.navigateTo('/pages/mine/subpages/setting/index')
|
||||||
},
|
},
|
||||||
handleToLogin() {
|
handleToLogin() {
|
||||||
this.$tab.reLaunch('/pages/login')
|
this.$tab.reLaunch('/pages/login')
|
||||||
},
|
},
|
||||||
handleToAvatar() {
|
handleToAvatar() {
|
||||||
this.$tab.navigateTo('/pages/mine/avatar/index')
|
this.$tab.navigateTo('/pages/mine/subpages/avatar/index')
|
||||||
},
|
},
|
||||||
handleHelp() {
|
handleHelp() {
|
||||||
this.$tab.navigateTo('/pages/mine/help/index')
|
this.$tab.navigateTo('/pages/mine/subpages/help/index')
|
||||||
},
|
},
|
||||||
handleMqtt() {
|
handleMqtt() {
|
||||||
this.$tab.navigateTo('/pages/mine/mqtt/index')
|
this.$tab.navigateTo('/pages/mine/subpages/mqtt/index')
|
||||||
},
|
},
|
||||||
handleRequire() {
|
handleRequire() {
|
||||||
this.$tab.navigateTo('/pages/mine/require/index')
|
this.$tab.navigateTo('/pages/mine/subpages/require/index')
|
||||||
},
|
},
|
||||||
handleAbout() {
|
handleAbout() {
|
||||||
this.$tab.navigateTo('/pages/mine/about/index')
|
this.$tab.navigateTo('/pages/mine/subpages/about/index')
|
||||||
},
|
},
|
||||||
handleJiaoLiuQun() {
|
handleJiaoLiuQun() {
|
||||||
this.$modal.showToast('QQ群:①133713780(满)、②146013835(满)、③189091635')
|
this.$modal.showToast('QQ群:①133713780(满)、②146013835(满)、③189091635')
|
||||||
|
|
@ -205,7 +205,19 @@
|
||||||
margin: 8px 0px;
|
margin: 8px 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.contact-btn {
|
||||||
|
background: transparent;
|
||||||
|
line-height: 40rpx;
|
||||||
|
margin-top: 10rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::v-deep .action-item.contact-btn::after {
|
||||||
|
border: none !important;
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
/* 或者写成 border: 0 !important; */
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -216,16 +216,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import UniTh from "../../../uni_modules/uni-table/components/uni-th/uni-th.vue";
|
import UniTh from "../../../../uni_modules/uni-table/components/uni-th/uni-th.vue";
|
||||||
import UniTr from "../../../uni_modules/uni-table/components/uni-tr/uni-tr.vue";
|
import UniTr from "../../../../uni_modules/uni-table/components/uni-tr/uni-tr.vue";
|
||||||
import UniTd from "../../../uni_modules/uni-table/components/uni-td/uni-td.vue";
|
import UniTd from "../../../../uni_modules/uni-table/components/uni-td/uni-td.vue";
|
||||||
import {addRequire, delRequire, delRequires, listRequire, updateRequire} from "../../../api/system/require";
|
import {addRequire, delRequire, delRequires, listRequire, updateRequire} from "../../../../api/system/require";
|
||||||
import UniTable from "../../../uni_modules/uni-table/components/uni-table/uni-table.vue";
|
import UniTable from "../../../../uni_modules/uni-table/components/uni-table/uni-table.vue";
|
||||||
import UniPopup from "../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
|
import UniPopup from "../../../../uni_modules/uni-popup/components/uni-popup/uni-popup.vue";
|
||||||
import UniForms from "../../../uni_modules/uni-forms/components/uni-forms/uni-forms.vue";
|
import UniForms from "../../../../uni_modules/uni-forms/components/uni-forms/uni-forms.vue";
|
||||||
import UniFormsItem from "../../../uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue";
|
import UniFormsItem from "../../../../uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue";
|
||||||
import UniEasyinput from "../../../uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue";
|
import UniEasyinput from "../../../../uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue";
|
||||||
import UniDataSelect from "../../../uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue";
|
import UniDataSelect from "../../../../uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -467,7 +467,8 @@ export default {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 页面基础样式重置 */
|
/* 页面基础样式重置 */
|
||||||
page {
|
page {
|
||||||
padding-top: 0 !important;
|
padding-top: 20rpx !important;
|
||||||
|
padding-bottom: 20rpx !important;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleToPwd() {
|
handleToPwd() {
|
||||||
this.$tab.navigateTo('/pages/mine/pwd/index')
|
this.$tab.navigateTo('/pages/mine/subpages/pwd/index')
|
||||||
},
|
},
|
||||||
handleToUpgrade() {
|
handleToUpgrade() {
|
||||||
this.$modal.showToast('模块建设中~')
|
this.$modal.showToast('模块建设中~')
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
// 2. 调用App.vue的logout方法(断开MQTT+清空订阅列表)
|
// 2. 调用App.vue的logout方法(断开MQTT+清空订阅列表)
|
||||||
app.logout()
|
app.logout()
|
||||||
}).finally(()=>{
|
}).finally(()=>{
|
||||||
this.$tab.reLaunch('/pages/control/index')
|
this.$tab.reLaunch('/pages/index')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,299 @@
|
||||||
|
<template>
|
||||||
|
<view >
|
||||||
|
<!-- <view class="nav-list">
|
||||||
|
<view class="nav-item" v-for="(item, index) in navItems" :key="index" @tap="switchTab(index)">
|
||||||
|
<image :src="item.icon" mode="aspectFit" class="nav-icon"></image>
|
||||||
|
<text :class="['nav-text', { 'active': currentTab === index }]">{{ item.title }}</text>
|
||||||
|
<view v-if="item.count > 0" class="nav-badge">{{ item.count }}</view>
|
||||||
|
</view>
|
||||||
|
</view>-->
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="message-list" @scrolltolower="loadMore">
|
||||||
|
<view v-for="(msg, index) in filteredMessages" :key="index" class="message-item" @tap="onMessageTap(msg)">
|
||||||
|
<image :src="msg.avatar" class="avatar" mode="aspectFill"></image>
|
||||||
|
<view class="message-content">
|
||||||
|
<view class="message-header">
|
||||||
|
<text class="sender">{{ msg.sender }}</text>
|
||||||
|
<text class="time">{{ msg.time }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="preview" :class="{ 'unread': msg.unread }">{{ msg.preview }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="msg.unread" class="unread-badge">
|
||||||
|
<text>{{ msg.unreadCount }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="isLoading" class="loading">
|
||||||
|
<uni-icons type="spinner-cycle" size="24" color="#007AFF"></uni-icons>
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
|
||||||
|
<view v-if="filteredMessages.length === 0" class="empty-state">
|
||||||
|
<uni-icons type="inbox" size="64" color="#999"></uni-icons>
|
||||||
|
<text>暂无消息</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: 0,
|
||||||
|
navItems: [{
|
||||||
|
title: '评论',
|
||||||
|
icon: 'https://www.zenkuai.com/attachment/demo/1.png',
|
||||||
|
count: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '粉丝',
|
||||||
|
icon: 'https://www.zenkuai.com/attachment/demo/2.png',
|
||||||
|
count: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '点赞',
|
||||||
|
icon: 'https://www.zenkuai.com/attachment/demo/2.png',
|
||||||
|
count: 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
searchText: '',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
sender: '系统通知',
|
||||||
|
avatar: '/static/news.png',
|
||||||
|
preview: '您有一条新的系统通知,请查看',
|
||||||
|
time: '10:30',
|
||||||
|
unread: true,
|
||||||
|
unreadCount: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
sender: '告警中心',
|
||||||
|
avatar: '/static/alarm.png',
|
||||||
|
preview: '您有一条新告警->',
|
||||||
|
time: '昨天',
|
||||||
|
unread: false,
|
||||||
|
unreadCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
sender: '客服小美',
|
||||||
|
avatar: '/static/cs.png',
|
||||||
|
preview: '您好,请问有什么可以帮到您的吗?',
|
||||||
|
time: '昨天',
|
||||||
|
unread: false,
|
||||||
|
unreadCount: 0
|
||||||
|
},
|
||||||
|
// 可以添加更多消息数据
|
||||||
|
],
|
||||||
|
isLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredMessages() {
|
||||||
|
return this.messages.filter(msg =>
|
||||||
|
msg.sender.toLowerCase().includes(this.searchText.toLowerCase()) ||
|
||||||
|
msg.preview.toLowerCase().includes(this.searchText.toLowerCase())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
switchTab(index) {
|
||||||
|
this.currentTab = index;
|
||||||
|
// 这里可以根据选中的标签加载相应的消息数据
|
||||||
|
console.log('Switched to tab:', this.navItems[index].title);
|
||||||
|
},
|
||||||
|
onMessageTap(msg) {
|
||||||
|
console.log('Message tapped:', msg)
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/message-detail/message-detail?id=${msg.id}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadMore() {
|
||||||
|
if (this.isLoading) return
|
||||||
|
this.isLoading = true
|
||||||
|
// 模拟加载更多消息
|
||||||
|
setTimeout(() => {
|
||||||
|
// 这里应该是从服务器加载更多消息的逻辑
|
||||||
|
this.messages.push(...[{
|
||||||
|
id: this.messages.length + 1,
|
||||||
|
sender: '新消息',
|
||||||
|
avatar: 'https://www.zenkuai.com/attachment/demo/1.png',
|
||||||
|
preview: '这是一条新加载的消息',
|
||||||
|
time: '刚刚',
|
||||||
|
unread: true,
|
||||||
|
unreadCount: 1
|
||||||
|
}])
|
||||||
|
this.isLoading = false
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.msg-center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 40rpx 20rpx 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nav-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin: 20rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-text.active {
|
||||||
|
color: #007AFF;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -10rpx;
|
||||||
|
right: -10rpx;
|
||||||
|
background-color: #FF3B30;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list {
|
||||||
|
flex: 1;
|
||||||
|
margin: 30rpx 20rpx;
|
||||||
|
}
|
||||||
|
.message-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 94%;
|
||||||
|
height: 150rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 90rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview.unread {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-badge {
|
||||||
|
background-color: #007AFF;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state text {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading text {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #007AFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -20,6 +20,19 @@ export default {
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 自定义提示窗
|
||||||
|
* @param content
|
||||||
|
* @param tag
|
||||||
|
*/
|
||||||
|
customMsg(content,tag) {
|
||||||
|
uni.showToast({
|
||||||
|
title: content,
|
||||||
|
icon: tag,
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 隐藏消息
|
// 隐藏消息
|
||||||
hideMsg(content) {
|
hideMsg(content) {
|
||||||
uni.hideToast()
|
uni.hideToast()
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,193 @@
|
||||||
|
/* 顶部 start */
|
||||||
|
.header {
|
||||||
|
padding: 80rpx 60rpx 40rpx 60rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: $tn-font-color;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $tn-content-color;
|
||||||
|
padding-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-title {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $tn-font-sub-color;
|
||||||
|
padding-top: 5rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 顶部 end */
|
||||||
|
|
||||||
|
/* 展示内容容器 start */
|
||||||
|
.show-content-container {
|
||||||
|
|
||||||
|
/* 标题容器 start */
|
||||||
|
.title-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 100rpx;
|
||||||
|
|
||||||
|
// 标题样式
|
||||||
|
.title {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 20rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
// 标题前面小点
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
background-color: $tn-main-color;
|
||||||
|
width: 15rpx;
|
||||||
|
height: 15rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-right: 18rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 标题容器 end */
|
||||||
|
|
||||||
|
/* 内容 start */
|
||||||
|
.content {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
/* 内容 end */
|
||||||
|
|
||||||
|
}
|
||||||
|
/* 展示内容容器 end */
|
||||||
|
|
||||||
|
/* 内容容器 start */
|
||||||
|
.demo-content-container {
|
||||||
|
border: 1rpx dashed $tn-main-color;
|
||||||
|
margin: 20rpx;
|
||||||
|
margin-top: 0rpx;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
width: 95%;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
transition: all 0.15s ease-out;
|
||||||
|
|
||||||
|
&.top {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-fixed {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题容器 start */
|
||||||
|
.title-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100rpx;
|
||||||
|
|
||||||
|
// 标题样式
|
||||||
|
.title {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 30rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 标题容器 end */
|
||||||
|
|
||||||
|
/* 内容 start */
|
||||||
|
.content {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
/* 内容 end */
|
||||||
|
|
||||||
|
}
|
||||||
|
/* 内容容器 end */
|
||||||
|
|
||||||
|
/* 可选项内容容器 start */
|
||||||
|
.demo-section-container {
|
||||||
|
margin: 20rpx;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* 标题容器 start */
|
||||||
|
.title-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
|
||||||
|
// 标题样式
|
||||||
|
.title {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 30rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: " ";
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 90%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-bottom: 1rpx solid $tn-border-solid-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 标题容器 end */
|
||||||
|
|
||||||
|
/* 参数内容 start*/
|
||||||
|
.content {
|
||||||
|
padding: 0 20rpx 10rpx 20rpx;
|
||||||
|
|
||||||
|
// 标题样式
|
||||||
|
.title {
|
||||||
|
padding-left: 20rpx;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 4rpx;
|
||||||
|
height: 90%;
|
||||||
|
background-color: $tn-main-color;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数样式
|
||||||
|
.section {
|
||||||
|
margin-top: 15rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 参数内容 end*/
|
||||||
|
|
||||||
|
}
|
||||||
|
/* 可选项内容容器 end */
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/icp.png
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 619 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 781 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 781 B |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
static/logo.png
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,310 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
|
||||||
<title>用户服务条款</title>
|
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
background-color: #f6f8f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
background-color: #ffffff;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 1600px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding: 24px 40px;
|
|
||||||
padding-bottom: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 90px 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
-webkit-margin-before: 0;
|
|
||||||
-webkit-margin-after: 0;
|
|
||||||
line-height: 24px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
li{
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 22px;
|
|
||||||
padding-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-title {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-list-item {
|
|
||||||
text-indent: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orderly-list {
|
|
||||||
/* margin-left: 2em; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.orderly-list-item {
|
|
||||||
margin-left: 2em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orderly-list-item .index {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.orderly-list-item-desc {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
left: 1.2em;
|
|
||||||
padding-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: right;
|
|
||||||
padding-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-inner {
|
|
||||||
position: relative;
|
|
||||||
margin-left: 2em;
|
|
||||||
padding-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-inner {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item-desc-inner {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content .desc {
|
|
||||||
text-indent: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.informal {
|
|
||||||
padding-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
white-space: normal;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border-left: 1px dashed #778899;
|
|
||||||
border-top: 1px dashed #778899;
|
|
||||||
position: relative;
|
|
||||||
white-space: normal;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-column {
|
|
||||||
text-align: left;
|
|
||||||
font-size: 17px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 15px 15px;
|
|
||||||
border-right: 1px dashed #778899;
|
|
||||||
border-bottom: 1px dashed #778899;
|
|
||||||
position: relative;
|
|
||||||
width: 670px;
|
|
||||||
background: 0 0;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row.left .table-column {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row.right {
|
|
||||||
left: 700px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gaizhang {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qianming {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner-span {
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
.msg{
|
|
||||||
background-color: #fcf8e3;
|
|
||||||
border-color: #faebcc;
|
|
||||||
color: #8a6d3b;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 760px) {
|
|
||||||
.container{
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.main{
|
|
||||||
padding: 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.orderly-list-item{
|
|
||||||
margin-left: 1.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="main">
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="title">用户服务条款</h1>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="content-title">服务条款确认与接纳</h2>
|
|
||||||
<p class="desc">
|
|
||||||
北京小策技术有限公司(以下简称小策技术) 拥有智能农业小程序其涉及到的产品、相关软件的所有权和运作权, 小策技术
|
|
||||||
享有对其产品的上一切活动的监督、提示、检查、纠正及处罚等权利。用户通过注册程序阅读本服务条款并点击"注册"按钮完成注册,即表示用户与 小策技术
|
|
||||||
已达成协议,自愿接受本服务条款的所有内容。如果用户不同意服务条款的条件,则不能获得使用 小策技术 服务以及注册成为 小策技术 用户的权利。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="content-title">使用规则</h2>
|
|
||||||
<div class="orderly-list">
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">1.</span>
|
|
||||||
<p class="orderly-list-item-desc">用户注册成功后,小策技术
|
|
||||||
将给予每个用户一个用户账号及相应的密码,该用户账号和密码由用户负责保管;用户应当对以其用户账号进行的所有活动和事件负法律责任。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">2.</span>
|
|
||||||
<p class="orderly-list-item-desc">用户须对在 小策技术
|
|
||||||
的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息;不得恶意使用注册帐户导致其他用户误认;否则 小策技术
|
|
||||||
有权立即停止提供服务,收回其账号并由用户独自承担由此而产生的一切法律责任。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">3.</span>
|
|
||||||
<p class="orderly-list-item-desc">用户不得使用 小策技术 服务发送或传播敏感信息和违反国家法律制度的信息,包括但不限于下列信息:</p>
|
|
||||||
<div>
|
|
||||||
<ul type="circle">
|
|
||||||
<li>反对宪法所确定的基本原则的;</li>
|
|
||||||
<li>危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;</li>
|
|
||||||
<li>损害国家荣誉和利益的;</li>
|
|
||||||
<li>煽动民族仇恨、民族歧视,破坏民族团结的;</li>
|
|
||||||
<li>破坏国家宗教政策,宣扬邪教和封建迷信的;</li>
|
|
||||||
<li>散布谣言,扰乱社会秩序,破坏社会稳定的;</li>
|
|
||||||
<li>散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;</li>
|
|
||||||
<li>侮辱或者诽谤他人,侵害他人合法权益的;</li>
|
|
||||||
<li>含有法律、行政法规禁止的其他内容的。</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">4.</span>
|
|
||||||
<p class="orderly-list-item-desc">小策技术 有权对用户使用 小策技术 产品的情况进行审查和监督,如用户在使用 小策技术
|
|
||||||
产品时违反任何上述规定,小策技术 或其授权的人有权要求用户改正或直接采取一切必要的措施以减轻用户不当行为造成的影响。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">5.</span>
|
|
||||||
<p class="orderly-list-item-desc">盗取他人用户账号或利用网络通讯骚扰他人,均属于非法行为。用户不得采用测试、欺骗等任何非法手段,盗取其他用户的账号和对他人进行骚扰。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="content-title">免责声明</h2>
|
|
||||||
<div class="orderly-list">
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">1.</span>
|
|
||||||
<p class="orderly-list-item-desc">小策技术 不能对用户在本社区回答问题的答案或评论的准确性及合理性进行保证。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">2.</span>
|
|
||||||
<p class="orderly-list-item-desc">若 小策技术
|
|
||||||
产品已经明示其网络服务提供方式发生变更并提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">3.</span>
|
|
||||||
<p class="orderly-list-item-desc">用户明确同意其使用 小策技术 产品网络服务所存在的风险将完全由其自己承担;因其使用 小策技术
|
|
||||||
服务而产生的一切后果也由其自己承担,小策技术 对用户不承担任何责任。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">4.</span>
|
|
||||||
<p class="orderly-list-item-desc">小策技术
|
|
||||||
不保证网络服务一定能满足用户的要求,也不保证网络服务不会中断,对网络服务的及时性、安全性、准确性也都不作保证。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">5.</span>
|
|
||||||
<p class="orderly-list-item-desc">对于因不可抗力或 小策技术 不能控制的原因造成的网络服务中断或其它缺陷,小策技术
|
|
||||||
不承担任何责任,但将尽力减少因此而给用户造成的损失和影响。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">5.</span>
|
|
||||||
<p class="orderly-list-item-desc">用户同意保障和维护 小策技术 及其他用户的利益,用户在 小策技术
|
|
||||||
发表的内容仅表明其个人的立场和观点,并不代表 小策技术 的立场或观点。由于用户发表内容违法、不真实、不正当、侵犯第三方合法权益,或用户违反本协议项下的任何条款而给
|
|
||||||
小策技术 或任何其他第三人造成损失,用户同意承担由此造成的损害赔偿责任。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="content-title">服务条款的修改</h2>
|
|
||||||
<p class="desc">
|
|
||||||
小策技术 会在必要时修改服务条款,服务条款一旦发生变动,小策技术
|
|
||||||
将会在用户进入下一步使用前的页面提示修改内容。如果您同意改动,请再一次激活"我同意"按钮。如果您不接受,请及时取消您的帐户。 用户要继续使用 小策技术 各项服务需要两方面的确认:
|
|
||||||
</p>
|
|
||||||
<div class="orderly-list">
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">1.</span>
|
|
||||||
<p class="orderly-list-item-desc">首先确认 小策技术 服务条款及其变动。</p>
|
|
||||||
</div>
|
|
||||||
<div class="orderly-list-item">
|
|
||||||
<span class="index">2.</span>
|
|
||||||
<p class="orderly-list-item-desc">同意接受所有的服务条款限制。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="content-title">隐私政策</h2>
|
|
||||||
<p class="desc">
|
|
||||||
小策技术 非常重视对用户隐私权的保护,承诺不会在未获得用户许可的情况下擅自将用户的个人资料信息出租或出售给任何第三方,但以下情况除外:
|
|
||||||
</p>
|
|
||||||
<ul type="circle">
|
|
||||||
<li>您同意让第三方共享资料;</li>
|
|
||||||
<li>您同意公开你的个人资料,享受为您提供的产品和服务;</li>
|
|
||||||
<li>本站需要听从法庭传票、法律命令或遵循法律程序;</li>
|
|
||||||
<li>本站发现您违反了本站服务条款或本站其它使用规定。</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="msg">如果您对此条款有任何疑问或建议,欢迎通过邮件联系我们:346039442@qq.com</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
|
// 尝试将用户在根目录中的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.$tn.vuex('user.name', '图鸟小菜')
|
||||||
|
// 修改vuexde state中的version变量为1.0.1 => this.$tn.vuex('version', 1.0.1)
|
||||||
|
this.$tn.vuex = (name, value) => {
|
||||||
|
this.$store.commit('$tStore', {
|
||||||
|
name, value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 将vuex的state中的变量结构到全局混入mixin中
|
||||||
|
...mapState($tStoreKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,77 @@ import getters from './getters'
|
||||||
|
|
||||||
Vue.use(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({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
user
|
user
|
||||||
},
|
},
|
||||||
getters
|
getters,
|
||||||
|
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
|
export default store
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
TuniaoUi for uniApp v1.0.0 | by 图鸟 2021-09-01
|
||||||
|
仅供开发,如作它用所承受的法律责任一概与作者无关
|
||||||
|
|
||||||
|
*使用TuniaoUi开发扩展与插件时,请注明基于tuniao字眼
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
<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 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 ? '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.$tn.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>
|
||||||
|
|
||||||
|
.border-solid-bottom{
|
||||||
|
border-bottom: 1rpx solid #F8F7F8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: #F8F7F8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<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"
|
||||||
|
: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>
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="tn-avatar-class tn-avatar"
|
||||||
|
:class="[backgroundColorClass,fontColorClass,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.fontColorStyle) {
|
||||||
|
style.color = this.fontColorStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.fontSizeStyle) {
|
||||||
|
style.fontSize = this.fontSizeStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
@ -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.$tn.string.getLengthUnitValue(this.top)
|
||||||
|
}
|
||||||
|
if (this.right) {
|
||||||
|
style.right = this.$tn.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>
|
||||||
|
|
@ -0,0 +1,348 @@
|
||||||
|
<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'
|
||||||
|
import {
|
||||||
|
debounceFun,
|
||||||
|
throttleFun
|
||||||
|
} from '../../libs/function/applyEven.js'
|
||||||
|
let spanTime = 200;
|
||||||
|
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
|
||||||
|
},
|
||||||
|
//场景:(如果开启blockRepeatClick,这里无效)none : 不开启防抖节流模式,debounce :防抖模式 throttle:节流模式
|
||||||
|
scene:{
|
||||||
|
type: String,
|
||||||
|
default: 'none'
|
||||||
|
},
|
||||||
|
// 防抖节流间隔时间(毫秒)
|
||||||
|
blockTime:{
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
//支持动态修改时间,但是这里是没有做撤销上一次的方法,毕竟这种场景非常少
|
||||||
|
//这里只是防止用户使用时复用了组件,有场景时长要求二次变动,而做的优化
|
||||||
|
blockTime:{
|
||||||
|
handler(newVal,oldVal){
|
||||||
|
this.initScene();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initScene()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initScene(){
|
||||||
|
// 动态传入blockTime,需要重新初始化,参数
|
||||||
|
//防抖模式
|
||||||
|
this.debounceClick=debounceFun(function() {
|
||||||
|
this.emitClick();
|
||||||
|
}, this.blockTime);
|
||||||
|
//节流模式
|
||||||
|
this.throttleClick=throttleFun(function() {
|
||||||
|
this.emitClick();
|
||||||
|
}, this.blockTime);
|
||||||
|
},
|
||||||
|
//防抖模式
|
||||||
|
debounceClick:debounceFun(function() {
|
||||||
|
this.emitClick();
|
||||||
|
}, spanTime),
|
||||||
|
//节流模式
|
||||||
|
throttleClick:throttleFun(function() {
|
||||||
|
this.emitClick();
|
||||||
|
}, spanTime),
|
||||||
|
emitClick() {
|
||||||
|
//触发事件
|
||||||
|
this.$emit('click', {
|
||||||
|
index: Number(this.index)
|
||||||
|
})
|
||||||
|
// 兼容tap事件
|
||||||
|
this.$emit('tap', {
|
||||||
|
index: Number(this.index)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 按钮点击事件
|
||||||
|
handleClick() {
|
||||||
|
if (this.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//兼容旧的
|
||||||
|
if (this.blockRepeatClick) {
|
||||||
|
this.throttleClick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//普通模式,触发多少次就回调多少次
|
||||||
|
if(this.scene === 'none'){
|
||||||
|
this.emitClick();
|
||||||
|
}else if(this.scene == 'debounce'){
|
||||||
|
//防抖模式
|
||||||
|
this.debounceClick();
|
||||||
|
}else{
|
||||||
|
//节流模式
|
||||||
|
this.throttleClick();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
|
@ -0,0 +1,711 @@
|
||||||
|
<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.$tn.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
|
||||||
|
let date = `${this.year}-${this.month}-${item}`
|
||||||
|
if (
|
||||||
|
(this.mode === 'date' && date == this.activeDate) ||
|
||||||
|
(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);
|
||||||
|
//传递事件
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
|
@ -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.$tn.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.$tn.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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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.$tn.$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.$tn.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>
|
||||||
|
|
@ -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.$tn.uuid()形式动态生成id值,否则会报错
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
elBgId: 'tCircleProgressBgId',
|
||||||
|
elId: 'tCircleProgressElId',
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
elBgId: this.$tn.uuid(),
|
||||||
|
elId: this.$tn.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>
|
||||||
|
|
@ -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.$tn.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.$tn.$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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -0,0 +1,318 @@
|
||||||
|
<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
|
||||||
|
class="tn-countdown__separator"
|
||||||
|
:style="{
|
||||||
|
fontSize: separatorSize + 'rpx',
|
||||||
|
color: separatorColor,
|
||||||
|
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ separator === 'en' ? (showHours || showMinutes || showSeconds ? ':' : '') : '天'}}
|
||||||
|
</view>
|
||||||
|
</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
|
||||||
|
v-if="showMinutes"
|
||||||
|
class="tn-countdown__separator"
|
||||||
|
:style="{
|
||||||
|
fontSize: separatorSize + 'rpx',
|
||||||
|
color: separatorColor,
|
||||||
|
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ separator === 'en' ? ':' : '时'}}
|
||||||
|
</view>
|
||||||
|
</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
|
||||||
|
v-if="showSeconds"
|
||||||
|
class="tn-countdown__separator"
|
||||||
|
:style="{
|
||||||
|
fontSize: separatorSize + 'rpx',
|
||||||
|
color: separatorColor,
|
||||||
|
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ separator === 'en' ? ':' : '分'}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="tn-countdown__item"
|
||||||
|
:class="[backgroundColorClass]"
|
||||||
|
:style="[itemStyle]"
|
||||||
|
v-if="showSeconds"
|
||||||
|
>
|
||||||
|
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||||
|
{{ s }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="tn-countdown__separator"
|
||||||
|
:style="{
|
||||||
|
fontSize: separatorSize + 'rpx',
|
||||||
|
color: separatorColor,
|
||||||
|
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ separator === 'en' ? '' : '秒'}}
|
||||||
|
</view>
|
||||||
|
</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.$tn.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.$tn.number.formatNumberAddZero(showHour)
|
||||||
|
minute = this.$tn.number.formatNumberAddZero(minute)
|
||||||
|
second = this.$tn.number.formatNumberAddZero(second)
|
||||||
|
day = this.$tn.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>
|
||||||
|
|
@ -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: $tn.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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
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 propChange(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.width
|
||||||
|
cropper.imgHeight = +dataset.height
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
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)'
|
||||||
|
})
|
||||||
|
} catch(e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片重置
|
||||||
|
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,
|
||||||
|
propChange: propChange
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,574 @@
|
||||||
|
<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="prop"
|
||||||
|
:change:prop="wxs.propChange"
|
||||||
|
: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: {},
|
||||||
|
// 传递的参数
|
||||||
|
prop: '',
|
||||||
|
// 标记是否发生改变
|
||||||
|
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.prop = `3,${this.angleChange}`
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.imgComputeSize()
|
||||||
|
this.angleChange++
|
||||||
|
this.prop = `3,${this.angleChange}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
isRound(val) {
|
||||||
|
if (val) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.imageReset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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.prop = '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.prop = `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.prop = `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.prop = `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.prop = `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-color: rgba(0, 0, 0, 0.7);
|
||||||
|
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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||