初始化

This commit is contained in:
zlw
2025-12-19 11:20:59 +08:00
commit ee272e2b8c
213 changed files with 16106 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
const FATAL_REBUILD_TOLERANCE = 10
const SETDATA_SCROLL_TO_BOTTOM = {
scrollTop: 100000,
scrollWithAnimation: true,
}
Component({
properties: {
envId: String,
collection: String,
groupId: String,
groupName: String,
userInfo: Object,
onGetUserInfo: {
type: Function,
},
getOpenID: {
type: Function,
},
},
data: {
chats: [],
textInputValue: '',
openId: '',
scrollTop: 0,
scrollToMessage: '',
hasKeyboard: false,
},
methods: {
onGetUserInfo(e) {
this.properties.onGetUserInfo(e)
},
getOpenID() {
return this.properties.getOpenID()
},
mergeCommonCriteria(criteria) {
return {
groupId: this.data.groupId,
...criteria,
}
},
async initRoom() {
this.try(async () => {
await this.initOpenID()
const { envId, collection } = this.properties
const db = this.db = wx.cloud.database({
env: envId,
})
const _ = db.command
const { data: initList } = await db.collection(collection).where(this.mergeCommonCriteria()).orderBy('sendTimeTS', 'desc').get()
console.log('init query chats', initList)
this.setData({
chats: initList.reverse(),
scrollTop: 10000,
})
this.initWatch(initList.length ? {
sendTimeTS: _.gt(initList[initList.length - 1].sendTimeTS),
} : {})
}, '初始化失败')
},
async initOpenID() {
return this.try(async () => {
const openId = await this.getOpenID()
this.setData({
openId,
})
}, '初始化 openId 失败')
},
async initWatch(criteria) {
this.try(() => {
const { collection } = this.properties
const db = this.db
const _ = db.command
console.warn(`开始监听`, criteria)
this.messageListener = db.collection(collection).where(this.mergeCommonCriteria(criteria)).watch({
onChange: this.onRealtimeMessageSnapshot.bind(this),
onError: e => {
if (!this.inited || this.fatalRebuildCount >= FATAL_REBUILD_TOLERANCE) {
this.showError(this.inited ? '监听错误,已断开' : '初始化监听失败', e, '重连', () => {
this.initWatch(this.data.chats.length ? {
sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
} : {})
})
} else {
this.initWatch(this.data.chats.length ? {
sendTimeTS: _.gt(this.data.chats[this.data.chats.length - 1].sendTimeTS),
} : {})
}
},
})
}, '初始化监听失败')
},
onRealtimeMessageSnapshot(snapshot) {
console.warn(`收到消息`, snapshot)
if (snapshot.type === 'init') {
this.setData({
chats: [
...this.data.chats,
...[...snapshot.docs].sort((x, y) => x.sendTimeTS - y.sendTimeTS),
],
})
this.scrollToBottom()
this.inited = true
} else {
let hasNewMessage = false
let hasOthersMessage = false
const chats = [...this.data.chats]
for (const docChange of snapshot.docChanges) {
switch (docChange.queueType) {
case 'enqueue': {
hasOthersMessage = docChange.doc._openid !== this.data.openId
const ind = chats.findIndex(chat => chat._id === docChange.doc._id)
if (ind > -1) {
if (chats[ind].msgType === 'image' && chats[ind].tempFilePath) {
chats.splice(ind, 1, {
...docChange.doc,
tempFilePath: chats[ind].tempFilePath,
})
} else chats.splice(ind, 1, docChange.doc)
} else {
hasNewMessage = true
chats.push(docChange.doc)
}
break
}
}
}
this.setData({
chats: chats.sort((x, y) => x.sendTimeTS - y.sendTimeTS),
})
if (hasOthersMessage || hasNewMessage) {
this.scrollToBottom()
}
}
},
async onConfirmSendText(e) {
this.try(async () => {
if (!e.detail.value) {
return
}
const { collection } = this.properties
const db = this.db
const _ = db.command
const doc = {
_id: `${Math.random()}_${Date.now()}`,
groupId: this.data.groupId,
avatar: this.data.userInfo.avatarUrl,
nickName: this.data.userInfo.nickName,
msgType: 'text',
textContent: e.detail.value,
sendTime: new Date(),
sendTimeTS: Date.now(), // fallback
}
this.setData({
textInputValue: '',
chats: [
...this.data.chats,
{
...doc,
_openid: this.data.openId,
writeStatus: 'pending',
},
],
})
this.scrollToBottom(true)
await db.collection(collection).add({
data: doc,
})
this.setData({
chats: this.data.chats.map(chat => {
if (chat._id === doc._id) {
return {
...chat,
writeStatus: 'written',
}
} else return chat
}),
})
}, '发送文字失败')
},
async onChooseImage(e) {
wx.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
success: async res => {
const { envId, collection } = this.properties
const doc = {
_id: `${Math.random()}_${Date.now()}`,
groupId: this.data.groupId,
avatar: this.data.userInfo.avatarUrl,
nickName: this.data.userInfo.nickName,
msgType: 'image',
sendTime: new Date(),
sendTimeTS: Date.now(), // fallback
}
this.setData({
chats: [
...this.data.chats,
{
...doc,
_openid: this.data.openId,
tempFilePath: res.tempFilePaths[0],
writeStatus: 0,
},
]
})
this.scrollToBottom(true)
const uploadTask = wx.cloud.uploadFile({
cloudPath: `${this.data.openId}/${Math.random()}_${Date.now()}.${res.tempFilePaths[0].match(/\.(\w+)$/)[1]}`,
filePath: res.tempFilePaths[0],
config: {
env: envId,
},
success: res => {
this.try(async () => {
await this.db.collection(collection).add({
data: {
...doc,
imgFileID: res.fileID,
},
})
}, '发送图片失败')
},
fail: e => {
this.showError('发送图片失败', e)
},
})
uploadTask.onProgressUpdate(({ progress }) => {
this.setData({
chats: this.data.chats.map(chat => {
if (chat._id === doc._id) {
return {
...chat,
writeStatus: progress,
}
} else return chat
})
})
})
},
})
},
onMessageImageTap(e) {
wx.previewImage({
urls: [e.target.dataset.fileid],
})
},
scrollToBottom(force) {
if (force) {
console.log('force scroll to bottom')
this.setData(SETDATA_SCROLL_TO_BOTTOM)
return
}
this.createSelectorQuery().select('.body').boundingClientRect(bodyRect => {
this.createSelectorQuery().select(`.body`).scrollOffset(scroll => {
if (scroll.scrollTop + bodyRect.height * 3 > scroll.scrollHeight) {
console.log('should scroll to bottom')
this.setData(SETDATA_SCROLL_TO_BOTTOM)
}
}).exec()
}).exec()
},
async onScrollToUpper() {
if (this.db && this.data.chats.length) {
const { collection } = this.properties
const _ = this.db.command
const { data } = await this.db.collection(collection).where(this.mergeCommonCriteria({
sendTimeTS: _.lt(this.data.chats[0].sendTimeTS),
})).orderBy('sendTimeTS', 'desc').get()
this.data.chats.unshift(...data.reverse())
this.setData({
chats: this.data.chats,
scrollToMessage: `item-${data.length}`,
scrollWithAnimation: false,
})
}
},
async try(fn, title) {
try {
await fn()
} catch (e) {
this.showError(title, e)
}
},
showError(title, content, confirmText, confirmCallback) {
console.error(title, content)
wx.showModal({
title,
content: content.toString(),
showCancel: confirmText ? true : false,
confirmText,
success: res => {
res.confirm && confirmCallback()
},
})
},
},
ready() {
global.chatroom = this
this.initRoom()
this.fatalRebuildCount = 0
},
})

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,85 @@
<view class="chatroom">
<view class="header">
<!-- display number of people in the room -->
<view class="left"></view>
<!-- room name -->
<view class="middle">{{groupName}}</view>
<!-- reserved -->
<view class="right"></view>
</view>
<!-- chats -->
<scroll-view
class="body"
scroll-y
scroll-with-animation="{{scrollWithAnimation}}"
scroll-top="{{scrollTop}}"
scroll-into-view="{{scrollToMessage}}"
bindscrolltoupper="onScrollToUpper"
>
<view
wx:for="{{chats}}"
wx:key="{{item._id}}"
id="item-{{index}}"
class="message {{openId == item._openid ? 'message__self' : ''}}"
>
<image
class="avatar"
src="{{item.avatar}}"
mode="scaleToFill"
></image>
<view class="main">
<view class="nickname">{{item.nickName}}</view>
<block wx:if="{{item.msgType === 'image'}}">
<view class="image-wrapper">
<view class="loading" wx:if="{{item.writeStatus > -1}}">{{item.writeStatus}}%</view>
<image
src="{{item.tempFilePath || item.imgFileID}}"
data-fileid="{{item.tempFilePath || item.imgFileID}}"
class="image-content"
style="{{item.imgStyle}}"
mode="scallToFill"
bindtap="onMessageImageTap"></image>
</view>
</block>
<block wx:else>
<view class="text-wrapper">
<view class="loading" wx:if="{{item.writeStatus === 'pending'}}">···</view>
<view class="text-content">{{item.textContent}}</view>
</view>
</block>
</view>
</view>
</scroll-view>
<!-- message sender -->
<view class="footer">
<view class="message-sender" wx:if="{{userInfo}}">
<input
class="text-input"
type="text"
confirm-type="send"
bindconfirm="onConfirmSendText"
cursor-spacing="20"
value="{{textInputValue}}"
></input>
<image
src="./photo.png"
class="btn-send-image"
mode="scaleToFill"
bindtap="onChooseImage"
></image>
</view>
<view class="message-sender" wx:if="{{!userInfo}}">
<button
open-type="getUserInfo"
bindgetuserinfo="onGetUserInfo"
class="userinfo"
>请先登录后参与聊天</button>
</view>
</view>
</view>

View File

@@ -0,0 +1,161 @@
.chatroom {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.chatroom .header {
flex-basis: fit-content;
display: flex;
flex-direction: row;
border-bottom: 1px solid #ddd;
padding: 20rpx 0 30rpx;
font-size: 30rpx;
/* background: rgb(34, 187, 47);
color: rgba(255, 255, 255, 1) */
/* font-family: 'Microsoft YaHei' */
}
.chatroom .header .left {
flex: 1;
}
.chatroom .header .middle {
flex: 2;
text-align: center;
}
.chatroom .header .right {
flex: 1;
}
.chatroom .body {
flex: 2;
display: flex;
flex-direction: column;
background: rgb(237,237,237);
padding-bottom: 16rpx;
}
.body .message {
display: flex;
flex-direction: row;
position: relative;
margin: 12rpx 0;
}
.body .message.message__self {
flex-direction: row-reverse;
}
.body .message .avatar {
position: relative;
top: 5rpx;
width: 60rpx;
height: 60rpx;
border-radius: 5rpx;
margin: 15rpx;
}
.body .message .main {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
}
.body .message.message__self .main {
align-items: flex-end;
}
.body .message .nickname {
font-size: 24rpx;
color: #444;
}
.body .message .text-content {
border: 1px solid transparent;
border-radius: 3px;
background-color: #fff;
margin: 2px 0 0 0;
padding: 4px 10px;
font-size: 30rpx;
display: inline-block;
}
.body .message.message__self .text-content {
background-color: paleturquoise;
}
.body .message .text-wrapper {
display: flex;
flex-direction: row;
align-items: center;
max-width: 80%;
}
.body .message.message__self .text-wrapper .loading{
font-size: 16rpx;
margin-right: 18rpx;
}
.body .message .image-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.body .message .image-content {
max-width: 240rpx;
max-height: 240rpx;
}
.body .message.message__self .image-wrapper .loading {
font-size: 20rpx;
margin-right: 18rpx;
}
.chatroom .footer {
flex-basis: fit-content;
display: flex;
flex-direction: row;
border-top: 1px solid #ddd;
font-size: 10rpx;
padding: 20rpx 30rpx;
background: rgb(246,246,246);
}
.chatroom .footer .message-sender {
flex: 1;
display: flex;
flex-direction: row;
}
.message-sender .text-input {
flex: 1;
font-size: 16px;
border: 1px solid transparent;
border-radius: 5px;
padding: 3px 6px;
margin: 0 10px 0 5px;
background: #fff;
}
.message-sender .btn-send-image {
width: 50rpx;
height: 50rpx;
align-self: center;
}
button {
font-size: 30rpx;
}
button.userinfo {
background: darkturquoise;
color: aliceblue;
padding: 0 100rpx;
border: 1px solid #ddd;
border-radius: 20px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View File

@@ -0,0 +1,601 @@
Component({
/**
* 组件的属性列表
*/
properties: {
is_show: {
type: Boolean,
value: false,
observer:function(val){ //弹出动画
// console.log(this.data);
if(val){
let animation = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
let animationOpacity = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
setTimeout(() => {
animation.bottom(0).step();
animationOpacity.opacity(0.7).step();
this.setData({
animationOpacity: animationOpacity.export(),
animationData: animation.export()
})
}, 0);
}else{
let animation = wx.createAnimation({
duration: 100,
timingFunction: "ease"
});
let animationOpacity = wx.createAnimation({
duration: 500,
timingFunction: "ease"
});
animation.bottom(-320).step();
animationOpacity.opacity(0).step();
this.setData({
animationOpacity: animationOpacity.export(),
animationData: animation.export()
});
}
// 在picker滚动未停止前点确定会使startValue数组各项归零发生错误这里判断并重新初始化
// 微信新增了picker滚动的回调函数已进行兼容
if(this.data.startValue&&this.data.endValue){
let s = 0, e = 0;
let conf = this.data.config;
this.data.startValue.map(val => {
if (val == 0) {
s++
}
})
this.data.endValue.map(val => {
if (val == 0) {
e++;
}
});
let tmp={
hour:4,
minute:5,
second:6
}
let n = tmp[conf.column];
if (s>=n || e>=n) {
this.initPick();
this.setData({
startValue: this.data.startValue,
endValue: this.data.endValue,
});
}
}
}
},
config: Object
},
/**
* 组件的初始数据
*/
data: {
value: [],
currentTag:'start'
},
ready: function () {
this.readConfig();
this.initPick(this.data.config || null);
this.setData({
startValue: this.data.startValue,
endValue: this.data.endValue,
});
},
/**
* 组件的方法列表
*/
methods: {
// 关闭弹框
cancel() {
this.setData({
is_show: false
})
this.triggerEvent('cancel')
},
chooseTime(e){
console.log(e);
const {name}=e.currentTarget.dataset
this.setData({
currentTag:name
})
console.log(this.data.currentTag,'currentTag');
},
onConfirm: function() {
//滚动未结束时不能确认
if(this.data.isPicking){return}
let startTime = new Date(this.data.startPickTime.replace(/-/g, "/"));
let endTime = new Date(this.data.endPickTime.replace(/-/g, "/"));
console.log(startTime,'startTime');
console.log(endTime,'endTime');
if (startTime <= endTime || !this.data.endDate) {
this.setData({
startTime,
endTime
});
let startArr = formatTime(startTime).arr;
let endArr = formatTime(endTime).arr;
let format0 = function(num){
return num<10?'0'+num:num
}
let startTimeBack =
startArr[0] +
"-" +
format0(startArr[1]) +
"-" +
format0(startArr[2]) +
" " +
(this.data.hourColumn ? format0(startArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(startArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(startArr[5]) : "00");
let endTimeBack =
endArr[0] +
"-" +
format0(endArr[1]) +
"-" +
format0(endArr[2]) +
" " +
(this.data.hourColumn ? format0(endArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(endArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(endArr[5]) : "00");
const days=this.calculateTimeSpan(startTimeBack,endTimeBack)
let time = {
startTime: startTimeBack,
endTime: endTimeBack
};
if(days>90){
wx.showModal({
title: "提示",
content: "时间跨度不能超过90天请重新选择!",
showCancel:false
});
}else{
//触发自定义事件
this.triggerEvent("setPickerTime", time);
this.triggerEvent("hidePicker", {});
}
} else {
wx.showToast({
icon: "none",
title: "时间不合理"
});
}
},
calculateTimeSpan(startDate, endDate) {
const startTime = new Date(startDate);
const endTime = new Date(endDate);
const timeSpanMilliseconds = endTime - startTime;
const timeSpanDays = Math.ceil(timeSpanMilliseconds / (1000 * 60 * 60 * 24)) ;
return timeSpanDays;
},
bindChange: function (e) {
const val = e.detail.value
this.setData({
year: this.data.years[val[0]],
month: this.data.months[val[1]],
day: this.data.days[val[2]]
})
},
//读取配置项
readConfig() {
let limitEndTime = new Date().getTime();
// let limitStartTime = new Date().getTime() - 1000 * 60 * 60 * 24 * 30*3;
let limitStartTime = '';
if (this.data.config) {
let conf = this.data.config;
if (typeof conf.dateLimit == "number") {
limitStartTime =
new Date().getTime() - 1000 * 60 * 60 * 24 * conf.dateLimit;
}
if (conf.limitStartTime) {
limitStartTime = new Date(conf.limitStartTime.replace(/-/g, '/')).getTime();
}
if (conf.limitEndTime) {
limitEndTime = new Date(conf.limitEndTime.replace(/-/g, '/')).getTime();
}
this.setData({
yearStart: conf.yearStart || 2000,
yearEnd: conf.yearEnd || 2100,
endDate: conf.endDate || false,
dateLimit: conf.dateLimit || false,
hourColumn:
conf.column == "hour" ||
conf.column == "minute" ||
conf.column == "second",
minColumn: conf.column == "minute" || conf.column == "second",
// secColumn: conf.column == "second"
});
}
let limitStartTimeArr = formatTime(limitStartTime);
let limitEndTimeArr = formatTime(limitEndTime);
this.setData({
limitStartTime,
limitStartTimeArr,
limitEndTime,
limitEndTimeArr
});
},
//滚动开始
handlePickStart: function (e) {
this.setData({
isPicking: true
})
},
//滚动结束
handlePickEnd: function (e) {
this.setData({
isPicking: false
})
},
changeStartDateTime: function (e) {
let val = e.detail.value;
this.compareTime(val, "start");
},
changeEndDateTime: function (e) {
let val = e.detail.value;
this.compareTime(val, "end");
},
//比较时间是否在范围内
compareTime(val, type) {
let h = val[3] ? this.data.HourList[val[3]] : "00";
let m = val[4] ? this.data.MinuteList[val[4]] : "00";
let s = val[5] ? this.data.SecondList[val[5]] : "00";
let time =
this.data.YearList[val[0]] +
"-" +
this.data.MonthList[val[1]] +
"-" +
this.data.DayList[val[2]] +
" " +
h +
":" +
m +
":" +
s;
let start = this.data.limitStartTime;
let end = this.data.limitEndTime;
let timeNum = new Date(time.replace(/-/g, '/')).getTime();
let year, month, day, hour, min, sec, limitDate;
let tempArr = []
if (!this.data.dateLimit) {
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]]]
} else if (type == "start" && timeNum > new Date(this.data.endPickTime.replace(/-/g, '/')) && this.data.config.endDate) {
limitDate = formatTime(this.data.endPickTime).arr;
} else if (type == "end" && timeNum < new Date(this.data.startPickTime.replace(/-/g, '/'))) {
limitDate = formatTime(this.data.startPickTime).arr;
} else if (timeNum < start) {
limitDate = this.data.limitStartTimeArr.arr;
} else if (timeNum > end) {
limitDate = this.data.limitEndTimeArr.arr;
} else {
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]]
]
}
year = limitDate[0];
month = limitDate[1];
day = limitDate[2];
hour = limitDate[3];
min = limitDate[4];
sec = limitDate[5];
if (type == "start") {
this.setStartDate(year, month, day, hour, min, 0);
} else if (type == "end") {
this.setEndDate(year, month, day, hour, min, 0);
}
},
getDays: function (year, month) {
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (month === 2) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
? 29
: 28;
} else {
return daysInMonth[month - 1];
}
},
initPick: function (initData) {
const date = initData.initStartTime ? new Date(initData.initStartTime.replace(/-/g, '/')) : new Date();
const endDate = initData.initEndTime ? new Date(initData.initEndTime.replace(/-/g, '/')) : new Date();
// const startDate = new Date(date.getTime() - 1000 * 60 * 60 * 24);
const startDate = date;
const startYear = date.getFullYear();
const startMonth = date.getMonth() + 1;
const startDay = date.getDate();
const startHour = date.getHours();
const startMinute = date.getMinutes();
const startSecond = date.getSeconds();
const endYear = endDate.getFullYear();
const endMonth = endDate.getMonth() + 1;
const endDay = endDate.getDate();
const endHour = endDate.getHours();
const endMinute = endDate.getMinutes();
const endSecond = endDate.getSeconds();
let YearList = [];
let MonthList = [];
let DayList = [];
let HourList = [];
let MinuteList = [];
let SecondList = [];
//设置年份列表
for (let i = this.data.yearStart; i <= this.data.yearEnd; i++) {
YearList.push(i);
}
// 设置月份列表
for (let i = 1; i <= 12; i++) {
MonthList.push(i);
}
// 设置日期列表
for (let i = 1; i <= 31; i++) {
DayList.push(i);
}
// 设置时列表
for (let i = 0; i <= 23; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
HourList.push(i);
}
// 分|秒
for (let i = 0; i <= 59; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
MinuteList.push(i);
SecondList.push(i);
}
this.setData({
YearList,
MonthList,
DayList,
HourList,
MinuteList,
SecondList
});
this.setStartDate(startYear, startMonth, startDay, startHour, startMinute, 0);
this.setEndDate(endYear, endMonth, endDay, endHour, endMinute, 0);
//!!!
// setTimeout(() => {
// this.setStartDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// this.setEndDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// }, 0);
},
setPickerDateArr(type, year, month, day, hour, minute, second) {
let yearIdx = 0;
let monthIdx = 0;
let dayIdx = 0;
let hourIdx = 0;
let minuteIdx = 0;
let secondIdx = 0;
this.data.YearList.map((v, idx) => {
if (parseInt(v) === year) {
yearIdx = idx;
}
});
this.data.MonthList.map((v, idx) => {
if (parseInt(v) === month) {
monthIdx = idx;
}
});
// 重新设置日期列表
let DayList = [];
for (let i = 1; i <= this.getDays(year, month); i++) {
DayList.push(i);
}
DayList.map((v, idx) => {
if (parseInt(v) === day) {
dayIdx = idx;
}
});
if (type == "start") {
this.setData({ startDayList: DayList });
} else if (type == "end") {
this.setData({ endDayList: DayList });
}
this.data.HourList.map((v, idx) => {
if (parseInt(v) === parseInt(hour)) {
hourIdx = idx;
}
});
this.data.MinuteList.map((v, idx) => {
if (parseInt(v) === parseInt(minute)) {
minuteIdx = idx;
}
});
this.data.SecondList.map((v, idx) => {
if (parseInt(v) === parseInt(second)) {
secondIdx = idx;
}
});
return {
yearIdx,
monthIdx,
dayIdx,
hourIdx,
minuteIdx,
secondIdx
};
},
setStartDate: function (year, month, day, hour, minute, second) {
let pickerDateArr = this.setPickerDateArr(
"start",
year,
month,
day,
hour,
minute,
second
);
this.setData({
startYearList: this.data.YearList,
startMonthList: this.data.MonthList,
// startDayList: this.data.DayList,
startHourList: this.data.HourList,
startMinuteList: this.data.MinuteList,
startSecondList: this.data.SecondList,
startValue: [
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx
],
startPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx]
// startPickTime:
// this.data.YearList[pickerDateArr.yearIdx] +
// "-" +
// this.data.MonthList[pickerDateArr.monthIdx] +
// "-" +
// this.data.DayList[pickerDateArr.dayIdx] +
// " " +
// this.data.HourList[pickerDateArr.hourIdx] +
// ":" +
// this.data.MinuteList[pickerDateArr.minuteIdx] +
// ":" +
// this.data.SecondList[pickerDateArr.secondIdx]
});
},
setEndDate: function (year, month, day, hour, minute, second) {
let pickerDateArr = this.setPickerDateArr(
"end",
year,
month,
day,
hour,
minute,
second
);
this.setData({
endYearList: this.data.YearList,
endMonthList: this.data.MonthList,
// endDayList: this.data.DayList,
endHourList: this.data.HourList,
endMinuteList: this.data.MinuteList,
endSecondList: this.data.SecondList,
endValue: [
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx
],
endPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx]
// endPickTime:
// this.data.YearList[pickerDateArr.yearIdx] +
// "-" +
// this.data.MonthList[pickerDateArr.monthIdx] +
// "-" +
// this.data.DayList[pickerDateArr.dayIdx] +
// " " +
// this.data.HourList[pickerDateArr.hourIdx] +
// ":" +
// this.data.MinuteList[pickerDateArr.minuteIdx] +
// ":" +
// this.data.SecondList[pickerDateArr.secondIdx]
});
},
}
})
function formatTime(date) {
if (typeof date == 'string' || 'number') {
try {
date = date.replace(/-/g, '/')//兼容ios
} catch (error) {
}
date = new Date(date)
}
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return {
str: [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':'),
arr: [year, month, day, hour, minute, second]
}
}
function formatNumber(n) {
n = n.toString()
return n[1] ? n : '0' + n
}

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,69 @@
<view class="mask" hidden="{{!is_show}}" catchtouchmove="true"></view>
<view class="modal_info fixed" hidden="{{!is_show}}">
<!-- 头部 -->
<view class="header">
<view class="left">选择时间</view>
<view class="right" bind:tap="onConfirm">确定</view>
</view>
<!-- 日期显示 -->
<view class="body">
<view class="{{currentTag==='start'?'left':'currentTag'}}" bind:tap="chooseTime" data-name="start">{{startPickTime}}</view>
<view class="{{currentTag==='end'?'right':'currentTag'}}"bind:tap="chooseTime"data-name="end">{{endPickTime}}</view>
</view>
<view class="footer">
<view class="timeNav">
<view class="item">年</view>
<view class="item">月</view>
<view class="item">日</view>
<view class="item">时</view>
<view class="item">分</view>
</view>
<view wx:if="{{currentTag==='start'}}">
<picker-view class='sensorTypePicker'bindchange="changeStartDateTime"
indicator-class='indicator'
value="{{startValue}}" style="height: {{endDate?'120px':'250px'}};" bindpickstart="handlePickStart" bindpickend="handlePickEnd">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class="{{[startValue[0]===index?'picker-item-active':'picker-item']}}" wx:for="{{startYearList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="{{[startValue[1]===index?'picker-item-active':'picker-item']}}" wx:for="{{startMonthList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="{{[startValue[2]===index?'picker-item-active':'picker-item']}}" wx:for="{{startDayList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}">
<view class="{{[startValue[3]===index?'picker-item-active':'picker-item']}}" wx:for="{{startHourList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class="{{[startValue[4]===index?'picker-item-active':'picker-item']}}" wx:for="{{startMinuteList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class="{{[startValue[5]===index?'picker-item-active':'picker-item']}}" wx:for="{{startSecondList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
</picker-view>
</view>
<view wx:else>
<picker-view class='sensorTypePicker' indicator-style='height: 35px;' bindchange="changeEndDateTime" bindpickstart="handlePickStart" bindpickend="handlePickEnd"
value="{{endValue}}">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class="{{[endValue[0]===index?'picker-item-active':'picker-item']}}" wx:for="{{endYearList}}" wx:key='*this' style="min-width: 70px;">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="{{[endValue[1]===index?'picker-item-active':'picker-item']}}" wx:for="{{endMonthList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="{{[endValue[2]===index?'picker-item-active':'picker-item']}}" wx:for="{{endDayList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}" >
<view class="{{[endValue[3]===index?'picker-item-active':'picker-item']}}" wx:for="{{endHourList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class="{{[endValue[4]===index?'picker-item-active':'picker-item']}}" wx:for="{{endMinuteList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class="{{[endValue[5]===index?'picker-item-active':'picker-item']}}" wx:for="{{startSecondList}}" wx:key='*this'>{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</view>
</view>

View File

@@ -0,0 +1,193 @@
/* components/datetimePicker/datetimePicker.wxss */
/* 遮罩 */
.mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, .6);
-webkit-transition-duration: .3s;
transition-duration: .3s;
}
.fixed {
z-index: 1003;
position: fixed;
left: 0;
bottom: 0;
/* top: 50%;
left: 50%;
transform: translate(-50%, -50%); */
}
.modal_info {
width: 100%;
background: #FFFFFF;
border-radius: 20rpx 20rpx 0 0;
padding-top: 16rpx;
overflow: hidden;
font-size: 32rpx;
line-height: 49rpx;
color: #333333;
display: flex;
flex-direction: column;
align-items: center;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 22rpx 30rpx;
box-sizing: border-box;
}
.header .left {
color: #191b27;
font-size: 32rpx;
font-weight: 700;
}
.header .right {
display: flex;
width: 130rpx;
box-sizing: border-box;
justify-content: center;
align-items: center;
background: #367DF9;
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
}
.body {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 22rpx 30rpx;
box-sizing: border-box;
}
.body .left,
.right {
display: flex;
width: 320rpx;
height: 72rpx;
padding: 16rpx 0rpx;
justify-content: center;
align-items: center;
gap: 20rpx;
flex-shrink: 0;
border-radius: 8rpx;
border: 2rpx solid #367DF9;
background: #F5F8FE;
color: #367df9;
text-align: center;
font-size: 28rpx;
font-weight: 500;
line-height: normal;
box-sizing: border-box;
}
.body .currentTag {
display: flex;
width: 320rpx;
height: 72rpx;
padding: 16rpx 0rpx;
justify-content: center;
align-items: center;
gap: 20rpx;
flex-shrink: 0;
border-radius: 8rpx;
/* border: 2rpx solid #367DF9; */
background: #F5F8FE;
color: #8fb8ff;
text-align: center;
font-size: 28rpx;
font-weight: 500;
line-height: normal;
box-sizing: border-box;
}
.footer {
width: 100%;
}
.picker {
width: 100%;
height: 100%;
}
.picker .item {
width: 100%;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
background: #F5F8FE;
text-align: center;
font-size: 32rpx;
}
.sensorTypePicker {
width: 100%;
height: 120px;
/* padding: 45px 0; */
}
/* 至 */
.to{
width:100%;
display: flex;
justify-content: center;align-items: center;
color:rgb(138,138,138);
border-radius: 8rpx;
color: #367df9;
text-align: center;
font-size: 32rpx;
font-weight: 700;
background: #F5F8FE;
/* font-size:30rpx; */
}
.picker-item {
line-height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
/* overflow: hidden; */
}
.indicator {
border-radius: 8rpx;
text-align: center;
font-size: 32rpx;
font-weight: 700;
border: none !important;
}
.picker-item-active{
line-height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 17px;
color: #367df9;
font-weight: 700;
background: rgba(245, 248, 254, 1);
}
.timeNav{
display: flex;
justify-content: space-around;
align-items: center;
}
.timeNav .item{
display: flex;
justify-content: center;
align-items: center;
color: #808080;
text-align: center;
font-size: 28rpx;
font-weight: 500;
}

View File

@@ -0,0 +1,284 @@
import WxCanvas from './wx-canvas';
import * as echarts from './echarts';
let ctx;
function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i])
const num2 = parseInt(v2[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
Component({
properties: {
canvasId: {
type: String,
value: 'ec-canvas'
},
ec: {
type: Object
},
forceUseOldCanvas: {
type: Boolean,
value: false
}
},
data: {
isUseNewCanvas: false
},
ready: function () {
// Disable prograssive because drawImage doesn't support DOM as parameter
// See https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.drawImage.html
echarts.registerPreprocessor(option => {
if (option && option.series) {
if (option.series.length > 0) {
option.series.forEach(series => {
series.progressive = 0;
});
}
else if (typeof option.series === 'object') {
option.series.progressive = 0;
}
}
});
if (!this.data.ec) {
console.warn('组件需绑定 ec 变量,例:<ec-canvas id="mychart-dom-bar" '
+ 'canvas-id="mychart-bar" ec="{{ ec }}"></ec-canvas>');
return;
}
if (!this.data.ec.lazyLoad) {
this.init();
}
},
methods: {
init: function (callback) {
const version = wx.getSystemInfoSync().SDKVersion
const canUseNewCanvas = compareVersion(version, '2.9.0') >= 0;
const forceUseOldCanvas = this.data.forceUseOldCanvas;
const isUseNewCanvas = canUseNewCanvas && !forceUseOldCanvas;
this.setData({ isUseNewCanvas });
if (forceUseOldCanvas && canUseNewCanvas) {
console.warn('开发者强制使用旧canvas,建议关闭');
}
if (isUseNewCanvas) {
// console.log('微信基础库版本大于2.9.0,开始使用<canvas type="2d"/>');
// 2.9.0 可以使用 <canvas type="2d"></canvas>
this.initByNewWay(callback);
} else {
const isValid = compareVersion(version, '1.9.91') >= 0
if (!isValid) {
console.error('微信基础库版本过低,需大于等于 1.9.91。'
+ '参见https://github.com/ecomfe/echarts-for-weixin'
+ '#%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC%E8%A6%81%E6%B1%82');
return;
} else {
console.warn('建议将微信基础库调整大于等于2.9.0版本。升级后绘图将有更好性能');
this.initByOldWay(callback);
}
}
},
initByOldWay(callback) {
// 1.9.91 <= version < 2.9.0:原来的方式初始化
ctx = wx.createCanvasContext(this.data.canvasId, this);
const canvas = new WxCanvas(ctx, this.data.canvasId, false);
if (echarts.setPlatformAPI) {
echarts.setPlatformAPI({
createCanvas: () => canvas,
});
} else {
echarts.setCanvasCreator(() => canvas);
};
// const canvasDpr = wx.getSystemInfoSync().pixelRatio // 微信旧的canvas不能传入dpr
const canvasDpr = 1
var query = wx.createSelectorQuery().in(this);
query.select('.ec-canvas').boundingClientRect(res => {
if (typeof callback === 'function') {
this.chart = callback(canvas, res.width, res.height, canvasDpr);
}
else if (this.data.ec && typeof this.data.ec.onInit === 'function') {
this.chart = this.data.ec.onInit(canvas, res.width, res.height, canvasDpr);
}
else {
this.triggerEvent('init', {
canvas: canvas,
width: res.width,
height: res.height,
canvasDpr: canvasDpr // 增加了dpr可方便外面echarts.init
});
}
}).exec();
},
initByNewWay(callback) {
// version >= 2.9.0:使用新的方式初始化
const query = wx.createSelectorQuery().in(this)
query
.select('.ec-canvas')
.fields({ node: true, size: true })
.exec(res => {
const canvasNode = res[0].node
this.canvasNode = canvasNode
const canvasDpr = wx.getSystemInfoSync().pixelRatio
const canvasWidth = res[0].width
const canvasHeight = res[0].height
const ctx = canvasNode.getContext('2d')
const canvas = new WxCanvas(ctx, this.data.canvasId, true, canvasNode)
if (echarts.setPlatformAPI) {
echarts.setPlatformAPI({
createCanvas: () => canvas,
loadImage: (src, onload, onerror) => {
if (canvasNode.createImage) {
const image = canvasNode.createImage();
image.onload = onload;
image.onerror = onerror;
image.src = src;
return image;
}
console.error('加载图片依赖 `Canvas.createImage()` API要求小程序基础库版本在 2.7.0 及以上。');
// PENDING fallback?
}
})
} else {
echarts.setCanvasCreator(() => canvas)
}
if (typeof callback === 'function') {
this.chart = callback(canvas, canvasWidth, canvasHeight, canvasDpr)
} else if (this.data.ec && typeof this.data.ec.onInit === 'function') {
this.chart = this.data.ec.onInit(canvas, canvasWidth, canvasHeight, canvasDpr)
} else {
this.triggerEvent('init', {
canvas: canvas,
width: canvasWidth,
height: canvasHeight,
dpr: canvasDpr
})
}
})
},
canvasToTempFilePath(opt) {
if (this.data.isUseNewCanvas) {
// 新版
const query = wx.createSelectorQuery().in(this)
query
.select('.ec-canvas')
.fields({ node: true, size: true })
.exec(res => {
const canvasNode = res[0].node
opt.canvas = canvasNode
wx.canvasToTempFilePath(opt)
})
} else {
// 旧的
if (!opt.canvasId) {
opt.canvasId = this.data.canvasId;
}
ctx.draw(true, () => {
wx.canvasToTempFilePath(opt, this);
});
}
},
touchStart(e) {
if (this.chart && e.touches.length > 0) {
var touch = e.touches[0];
var handler = this.chart.getZr().handler;
handler.dispatch('mousedown', {
zrX: touch.x,
zrY: touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
handler.dispatch('mousemove', {
zrX: touch.x,
zrY: touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
handler.processGesture(wrapTouch(e), 'start');
}
},
touchMove(e) {
if (this.chart && e.touches.length > 0) {
var touch = e.touches[0];
var handler = this.chart.getZr().handler;
handler.dispatch('mousemove', {
zrX: touch.x,
zrY: touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
handler.processGesture(wrapTouch(e), 'change');
}
},
touchEnd(e) {
if (this.chart) {
const touch = e.changedTouches ? e.changedTouches[0] : {};
var handler = this.chart.getZr().handler;
handler.dispatch('mouseup', {
zrX: touch.x,
zrY: touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
handler.dispatch('click', {
zrX: touch.x,
zrY: touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
handler.processGesture(wrapTouch(e), 'end');
}
}
}
});
function wrapTouch(event) {
for (let i = 0; i < event.touches.length; ++i) {
const touch = event.touches[i];
touch.offsetX = touch.x;
touch.offsetY = touch.y;
}
return event;
}

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,4 @@
<!-- 新的接口对其了H5 -->
<canvas wx:if="{{isUseNewCanvas}}" type="2d" class="ec-canvas" canvas-id="{{ canvasId }}" bindinit="init" bindtouchstart="{{ ec.disableTouch ? '' : 'touchStart' }}" bindtouchmove="{{ ec.disableTouch ? '' : 'touchMove' }}" bindtouchend="{{ ec.disableTouch ? '' : 'touchEnd' }}"></canvas>
<!-- 旧的 -->
<canvas wx:else class="ec-canvas" canvas-id="{{ canvasId }}" bindinit="init" bindtouchstart="{{ ec.disableTouch ? '' : 'touchStart' }}" bindtouchmove="{{ ec.disableTouch ? '' : 'touchMove' }}" bindtouchend="{{ ec.disableTouch ? '' : 'touchEnd' }}"></canvas>

View File

@@ -0,0 +1,4 @@
.ec-canvas {
width: 100%;
height: 100%;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,111 @@
export default class WxCanvas {
constructor(ctx, canvasId, isNew, canvasNode) {
this.ctx = ctx;
this.canvasId = canvasId;
this.chart = null;
this.isNew = isNew
if (isNew) {
this.canvasNode = canvasNode;
}
else {
this._initStyle(ctx);
}
// this._initCanvas(zrender, ctx);
this._initEvent();
}
getContext(contextType) {
if (contextType === '2d') {
return this.ctx;
}
}
// canvasToTempFilePath(opt) {
// if (!opt.canvasId) {
// opt.canvasId = this.canvasId;
// }
// return wx.canvasToTempFilePath(opt, this);
// }
setChart(chart) {
this.chart = chart;
}
addEventListener() {
// noop
}
attachEvent() {
// noop
}
detachEvent() {
// noop
}
_initCanvas(zrender, ctx) {
zrender.util.getContext = function () {
return ctx;
};
zrender.util.$override('measureText', function (text, font) {
ctx.font = font || '12px sans-serif';
return ctx.measureText(text);
});
}
_initStyle(ctx) {
ctx.createRadialGradient = () => {
return ctx.createCircularGradient(arguments);
};
}
_initEvent() {
this.event = {};
const eventNames = [{
wxName: 'touchStart',
ecName: 'mousedown'
}, {
wxName: 'touchMove',
ecName: 'mousemove'
}, {
wxName: 'touchEnd',
ecName: 'mouseup'
}, {
wxName: 'touchEnd',
ecName: 'click'
}];
eventNames.forEach(name => {
this.event[name.wxName] = e => {
const touch = e.touches[0];
this.chart.getZr().handler.dispatch(name.ecName, {
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
zrY: name.wxName === 'tap' ? touch.clientY : touch.y,
preventDefault: () => {},
stopImmediatePropagation: () => {},
stopPropagation: () => {}
});
};
});
}
set width(w) {
if (this.canvasNode) this.canvasNode.width = w
}
set height(h) {
if (this.canvasNode) this.canvasNode.height = h
}
get width() {
if (this.canvasNode)
return this.canvasNode.width
return 0
}
get height() {
if (this.canvasNode)
return this.canvasNode.height
return 0
}
}

View File

@@ -0,0 +1,126 @@
// components/load-more/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
hasMore: {
type: Boolean,
value: false
},
// 加载中的显示文本
loadingText: {
type: String,
value: '加载中...'
},
// 加载失败的显示文本
failText: {
type: String,
value: '加载失败, 请点击重试!'
},
// 没有更多后的显示文本, 默认没有则隐藏加载更多控件
finishText: {
type: String,
value: '没有数据了...'
},
// 列表渲染延时, 默认为 500 ms, 我在开发工具中测试列表渲染速度时快时慢, 可根据实际使用中界面复杂度自行调整
// ps 如果能监听setData() 渲染结束的话则可以不需要延时
listRenderingDelay: {
type: Number,
value: 500
}
},
/**
* 组件的初始数据
*/
data: {
showThis: false,
text: '',
showIcon: false,
isLoading: false
},
/**
* 组件的方法列表
*/
methods: {
//加载更多的入口方法, 直接在page中使用时请在onReachBottom() 方法中调用这个方法, 并实现loadMoreListener方法去获取数据
loadMore: function () {
if (!this.properties.hasMore) {
console.log('load more finish')
return
}
if (this.data.isLoading) {
console.log('loading ...')
return
}
this.setData({
isLoading: true
})
this.triggerEvent('loadMoreListener')
},
//加载完成, 传入hasMore
loadMoreComplete: function (res) {
// var hasMore = data.curPage < data.pageCount && data.pageCount != 1
var hasMore = false;
if (res.length == 0) {
hasMore = false;
}
if (res.length > 0) {
hasMore = true;
}
if (res.length < 10) {
hasMore = false;
}
var text = '', showThis = false, showIcon = false
if (hasMore) {
showIcon = true
showThis = true
text = this.properties.loadingText
} else if (this.properties.finishText.length > 0) {
text = this.properties.finishText
showThis = true
}
this.setData({
hasMore: hasMore,
text: text,
showIcon: showIcon,
showThis: showThis
})
//界面渲染延迟, 避免列表还未渲染完成就再次触发 loadMore 方法
setTimeout(function () {
this.setData({
isLoading: false
})
}.bind(this), this.properties.listRenderingDelay)
},
// 加载失败
loadMoreFail: function () {
this.setData({
showIcon: false,
text: this.properties.failText
})
//界面渲染延迟, 避免列表还未渲染完成就再次触发 loadMore 方法
setTimeout(function () {
this.setData({
isLoading: false
})
}.bind(this), this.properties.listRenderingDelay)
},
//点击 loadmore 控件时触发, 只有加载失败时才会进入页面回调方法
clickLoadMore: function () {
if (this.data.text != this.properties.failText) return
this.setData({
showIcon: true,
text: this.properties.loadingText,
isLoading: true
})
this.triggerEvent('clickLoadMore')
}
}
})

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,5 @@
<!--components/load-more/index.wxml-->
<view class="loadmore" hidden='{{!showThis}}' bindtap='clickLoadMore'>
<image class="icon {{showIcon?'active':''}}" src='http://static.sxlyb.com/images/loading01.png' hidden='{{!showIcon}}'/>
<text>{{text}}</text>
</view>

View File

@@ -0,0 +1,36 @@
/* components/load-more/index.wxss */
.loadmore {
height: 35px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.loadmore text{
font-size: 13px;
color: #bfbfbf;
font-weight: bold;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.icon{
width: 25px;
height: 25px;
margin-right: 10px;
}
.active {
animation: weuiLoading 0.6s steps(12, end) infinite;
}
@keyframes weuiLoading {
0% {
transform: rotate3d(0, 0, 1, 0deg);
}
100% {
transform: rotate3d(0, 0, 1, 360deg);
}
}

View File

@@ -0,0 +1,36 @@
const app = getApp()
Component({
properties: {
navbarData: { //navbarData 由父页面传递的数据,变量名字自命名
type: Object,
value: {},
observer: function (newVal, oldVal) { }
}
},
data: {
height: '',
//默认值 默认显示左上角
navbarData: {
showCapsule: 1
}
},
attached: function () {
// 获取是否是通过分享进入的小程序
this.setData({
share: app.globalData.share
})
// 定义导航栏的高度 方便对齐
this.setData({
height: app.globalData.height
})
},
methods: {
// 返回上一页面
_navback() {
wx.navigateBack();
// wx.navigateTo({
// url: '../oilManage/oilManage'
// })
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,12 @@
<view class='nav-wrap' style='height: {{height*2 + 20}}px;'>
<view class='nav-title' style='line-height: {{height*2 + 44}}px;'>{{navbarData.title}}</view>
<view style='display: flex; justify-content: space-around;flex-direction: column'>
<view class='nav-capsule' style='height: {{height*2 + 44}}px;' wx:if='{{navbarData.showCapsule}}'>
<view bindtap='_navback' >
<image src='../../pages/images/you.png' mode='aspectFill' class='back-pre'></image>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,53 @@
/* 顶部要固定定位 标题要居中 自定义按钮和标题要和右边微信原生的胶囊上下对齐 */
.nav-wrap {
position: fixed;
width: 100%;
top: 0;
background: #fff;
color: #000;
z-index: 9999999;
}
/* 标题要居中 */
.nav-title {
position: absolute;
text-align: center;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 36rpx;
color: #2c2b2b;
font-weight: 600;
}
.nav-capsule {
display: flex;
align-items: center;
margin-left: 10px;
width: 90px;
justify-content: space-between;
height: 100%;
}
.back-pre, .back-home {
width: 10px;
height: 20px;
margin-top: 8px;
padding: 10px;
}
.nav-capsule .back-home {
width: 70px;
height: 28px;
margin-top: 3rpx;
}

View File

@@ -0,0 +1,43 @@
const app = getApp()
Component({
properties: {
navbarData: { //navbarData 由父页面传递的数据,变量名字自命名
type: Object,
value: {},
observer: function (newVal, oldVal) { }
}
},
data: {
height: '',
//默认值 默认显示左上角
navbarData: {
showCapsule: 1
}
},
attached: function () {
// 获取是否是通过分享进入的小程序
this.setData({
share: app.globalData.share
})
// 定义导航栏的高度 方便对齐
this.setData({
height: app.globalData.height
})
},
methods: {
// 返回上一页面
_navback() {
wx.navigateBack();
// wx.navigateTo({
// url: '../wodeOil/wodeOil'
// })
},
//返回到首页
_backhome() {
wx.switchTab({
url: '/pages/index/index',
})
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,12 @@
<view class='nav-wrap' style='height: {{height*2 + 20}}px;'>
<view class='nav-title' style='line-height: {{height*2 + 44}}px;'>{{navbarData.title}}</view>
<view style='display: flex; justify-content: space-around;flex-direction: column'>
<view class='nav-capsule' style='height: {{height*2 + 44}}px;' wx:if='{{navbarData.showCapsule}}'>
<view bindtap='_navback' >
<image src='../../pages/images/avatar.png' mode='aspectFill' class='back-pre'></image>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,56 @@
/* 顶部要固定定位 标题要居中 自定义按钮和标题要和右边微信原生的胶囊上下对齐 */
.nav-wrap {
position: fixed;
width: 100%;
top: 0;
background: #fff;
color: #000;
z-index: 9999999;
}
/* 标题要居中 */
.nav-title {
position: absolute;
text-align: center;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 36rpx;
color: #2c2b2b;
font-weight: 600;
}
.nav-capsule {
display: flex;
align-items: center;
margin-left: 10px;
width: 90px;
justify-content: space-between;
height: 100%;
}
.back-pre, .back-home {
width: 35px;
height: 35px;
margin-top: 8px;
padding: 10px;
z-index: 10;
position: absolute;
/* margin-left: 25px; */
}
.nav-capsule .back-home {
width: 70px;
height: 28px;
margin-top: 3rpx;
}

View File

@@ -0,0 +1,33 @@
const app = getApp()
Component({
properties: {
navbarData: { //navbarData 由父页面传递的数据,变量名字自命名
type: Object,
value: {},
observer: function (newVal, oldVal) { }
}
},
data: {
height: '',
//默认值 默认显示左上角
navbarData: {
showCapsule: 1
}
},
attached: function () {
// 获取是否是通过分享进入的小程序
this.setData({
share: app.globalData.share
})
// 定义导航栏的高度 方便对齐
this.setData({
height: app.globalData.height
})
},
methods: {
// 返回上一页面
_navback() {
wx.navigateBack()
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,12 @@
<view class='nav-wrap' style='height: {{height*2 + 20}}px;'>
<view class='nav-title' style='line-height: {{height*2 + 40}}px;'>{{navbarData.title}}</view>
<view style='display: flex; justify-content: space-around;flex-direction: column'>
<view class='nav-capsule' style='height: {{height*2 + 40}}px;' wx:if='{{navbarData.showCapsule}}'>
<view bindtap='_navback' >
<image src='../../pages/images/you.png' mode='aspectFill' class='back-pre'></image>
</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,53 @@
/* 顶部要固定定位 标题要居中 自定义按钮和标题要和右边微信原生的胶囊上下对齐 */
.nav-wrap {
position: fixed;
width: 100%;
top: 0;
background: #fff;
color: #000;
z-index: 9999999;
}
/* 标题要居中 */
.nav-title {
position: absolute;
text-align: center;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 36rpx;
color: #2c2b2b;
font-weight: 600;
}
.nav-capsule {
display: flex;
align-items: center;
margin-left: 10px;
width: 90px;
justify-content: space-between;
height: 100%;
}
.back-pre, .back-home {
width: 10px;
height: 20px;
margin-top: 8px;
padding: 10px;
}
.nav-capsule .back-home {
width: 70px;
height: 28px;
margin-top: 3rpx;
}

View File

@@ -0,0 +1,33 @@
const app = getApp()
Component({
properties: {
navbarData: { //navbarData 由父页面传递的数据,变量名字自命名
type: Object,
value: {},
observer: function (newVal, oldVal) { }
}
},
data: {
height: '',
//默认值 默认显示左上角
navbarData: {
showCapsule: 1
}
},
attached: function () {
// 获取是否是通过分享进入的小程序
this.setData({
share: app.globalData.share
})
// 定义导航栏的高度 方便对齐
this.setData({
height: app.globalData.height
})
},
methods: {
// 返回上一页面
_navback() {
wx.navigateBack()
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,12 @@
<view class='nav-wrap' style='height: {{height*2 + 20}}px;'>
<view class='nav-title' style='line-height: {{height*2 + 40}}px;'>{{navbarData.title}}</view>
<view style='display: flex; justify-content: space-around;flex-direction: column'>
<view class='nav-capsule' style='height: {{height*2 + 40}}px;' wx:if='{{navbarData.showCapsule}}'>
<!-- <view bindtap='_navback' >
<image src='../../pages/images/you.png' mode='aspectFill' class='back-pre'></image>
</view> -->
</view>
</view>
</view>

View File

@@ -0,0 +1,53 @@
/* 顶部要固定定位 标题要居中 自定义按钮和标题要和右边微信原生的胶囊上下对齐 */
.nav-wrap {
position: fixed;
width: 100%;
top: 0;
background: #fff;
color: #000;
z-index: 9999999;
}
/* 标题要居中 */
.nav-title {
position: absolute;
text-align: center;
max-width: 400rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
font-size: 36rpx;
color: #2c2b2b;
font-weight: 600;
}
.nav-capsule {
display: flex;
align-items: center;
margin-left: 10px;
width: 90px;
justify-content: space-between;
height: 100%;
}
.back-pre, .back-home {
width: 10px;
height: 20px;
margin-top: 8px;
padding: 10px;
}
.nav-capsule .back-home {
width: 70px;
height: 28px;
margin-top: 3rpx;
}

View File

@@ -0,0 +1,30 @@
// components/pay-list/pay-list.js
Component({
/**
* 组件的属性列表
*/
properties: {
item: {
type: Object,
value: {},
},
settlementType:{
type:Number,
value:1
}
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,41 @@
<wxs src="../../utils/filter.wxs" module="filter"></wxs>
<view class="box">
<view class="top">
<view class="left">
<block>
<!-- 钱包支付 -->
<image class="walletType" wx:if="{{item.walletType===613}}" src="../../pages/images/ic_list_pay_wallet.png" mode="widthFix" />
<!-- 电子油卡支付 -->
<image class="walletType" wx:if="{{item.walletType===611||item.walletType===405||item.walletType===621}}" src="../../pages/images/ic_list_pay_card.png" mode="widthFix" />
<!-- 司机优惠微信支付 -->
<image class="walletType" wx:if="{{item.walletType===610||item.walletType===612 || item.walletType===622}}" src="../../pages/images/ic_list_pay_discount.png" mode="widthFix" />
<!-- 信用油卡支付 -->
<image class="walletType" wx:if="{{item.walletType===1402}}" src="../../pages/images/ic_oil_pay_credit.png" mode="widthFix" />
<!-- 退款 -->
<image class="walletType" wx:if="{{item.walletType===6131||item.walletType===6121||item.walletType===6211||item.walletType===6111||item.walletType===6221}}" src="../../pages/images/ic_list_money_back.png" mode="widthFix" />
</block>
<view>{{item.description}}</view>
<view>-{{filter.extractLastFourDigits(item.userPhone)}}</view>
</view>
<view class="right">{{item.created}}</view>
</view>
<view class="bottom">
<view class="left">
<view class="money" wx:if="{{settlementType===2}}">
<view class="desc">加油量</view>
<view class="num">{{item.oilQuantity||0}}</view>
</view>
<view class="money">
<view class="desc">优惠</view>
<view class="num">{{item.payGift||0}}</view>
</view>
<view class="money">
<view class="desc">{{settlementType===1?'实收':'应收'}}</view>
<view class="num">{{(settlementType===1?item.actual:item.money)||0}}</view>
</view>
</view>
<view class="right {{item.type==1?'negativeGasReceipts':'positiveGasReceipts'}}">
{{item.type==1?'-':'+'}}¥{{settlementType===1?item.money:item.gasReceipts}}
</view>
</view>
</view>

View File

@@ -0,0 +1,79 @@
.box {
display: flex;
width: 682rpx;
padding: 24rpx 0;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16rpx;
border-radius: 16rpx;
background: #fff;
box-shadow: 0 0 8rpx 0 #f3f5f7;
box-sizing: border-box;
/* height: 162rpx; */
}
.top {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 11rpx;
}
.top .left {
display: flex;
align-items: center;
color: #363636;
font-family: "PingFang SC";
font-size: 26rpx;
font-style: normal;
font-weight: 500;
}
.walletType {
display: block;
width: 48rpx;
height: 48rpx;
margin-right: 8rpx;
}
.top .right {
color: #808080;
font-size: 26rpx;
font-weight: 500;
}
.bottom {
width: 100%;
display: flex;
justify-content: space-between;
}
.bottom .left {
display: flex;
color: #363636;
font-family: "PingFang SC";
font-size: 26rpx;
font-style: normal;
font-weight: 500;
}
.money {
margin-right: 60rpx;
}
.money .desc {
margin-right: 8rpx;
color: #808080;
font-size: 26rpx;
font-weight: 500;
}
.money .num {
color: #363636;
font-size: 28rpx;
font-weight: 700;
}
.bottom .right {
font-size: 36rpx;
font-weight: 700;
margin-top: 12rpx;
}
.bottom .negativeGasReceipts{
color: #FF7A7A;
}
.bottom .positiveGasReceipts{
color: #367df9;
}