支持动态展示修改数据,支持导出图片
WXML
<wxs src="/wxs/cdn.wxs" module="cdn" />
<nav-bar id="nav-bar" isInvert="{{true}}" defShowTitle="{{false}}" showBack="{{true}}" pageTitle='作息统计'></nav-bar>
<view class="schedule-container" style="padding-top:{{navigationBarHeight}}px;">
<!-- 头部 -->
<view class="header">
<auth-btn>
<view wx:if="{{userInfo.nickname}}" class="user-name">你好 {{userInfo.nickname}}</view>
<view wx:else class="user-name" style="text-decoration-line: underline;">点击登录</view>
<view class="header-desc">这是你的打卡回顾</view>
</auth-btn>
<view>
<image class="user-avatar" mode="aspectFit" src="{{userInfo.avatar?userInfo.avatar:cdn.image('mine_header_default.png')}}"></image>
</view>
</view>
<!-- 内容 -->
<view class="content">
<view class="statistics-box">
<view class="flex-col wake-days-box">
<view class=" flex-row-spbt-cen">
<view class="flex-col statistics-title">
<view>累计早打卡</view>
<view class="flex-row-start-cen" style="margin-top: 16rpx;">
<text class="statistics-desc">{{userInfo.wake_total_days || 0}}</text>天
</view>
</view>
<image class="mine-cloud-icon" mode="aspectFit" src="{{cdn.image('mine_rise_icon.png')}}"></image>
</view>
<view class="continuous-number">
已连续打卡{{userInfo.wake_days || 0}}天
</view>
</view>
<view class="flex-col sleep-days-box">
<view class=" flex-row-spbt-cen">
<view class="flex-col statistics-title">
<view>累计晚打卡</view>
<view class="flex-row-start-cen" style="margin-top: 16rpx;">
<text class="statistics-desc">{{userInfo.sleep_total_days || 0}}</text>天
</view>
</view>
<image class="mine-cloud-icon" mode="aspectFit" src="{{cdn.image('mine_sleep_icon.png')}}"></image>
</view>
<view class="continuous-number">
已连续打卡{{userInfo.sleep_days || 0}}天
</view>
</view>
</view>
<view class="tab-box">
<view style="line-height: 45rpx;">作息统计报告</view>
<view class="flex-row-start-cen mt24">
<view bind:tap="tabChange" data-index="1" class="{{tabIndex==1?'active-tab':'default-tab'}}">近7天</view>
<view bind:tap="tabChange" data-index="2" class="{{tabIndex==2?'active-tab':'default-tab'}}" style="margin-left: 16rpx;">近30天</view>
</view>
</view>
<view wx:if="{{!allEmpty}}" class="charts-content">
<!-- 平均睡眠时长 -->
<view class="charts-box">
<view class="flex-row-spbt-cen">
<view class="charts-title">睡眠时长报告</view>
<view wx:if="{{sleepBarData.xData.length}}" class="charts-download" data-type="1" bind:tap="downLoadImage">保存
<image class="charts-download-icon" src="{{cdn.image('rest_schedule_download_icon.png')}}" mode="aspectFit" />
</view>
</view>
<view wx:if="{{sleepBarData.xData.length}}" class="average-duration">
近{{tabIndex==1?'7':'30'}}天平均睡眠时长{{averageDuration}}小时
</view>
<view class="echart-view" wx:if="{{sleepBarData.xData.length}}">
<ec-canvas id="time-range-chart" canvas-id="time-range-chart" ec="{{ timeRangeEc }}"></ec-canvas>
</view>
<view wx:else class="empty-box">
<view class="empty-text">当日晚打卡和次日早打后,才可计算睡眠时长哦</view>
<van-button bind:click="routeToHome" custom-class="empty-button">去打卡</van-button>
</view>
</view>
<!-- 早打卡趋势 -->
<view class="charts-box">
<view class="flex-row-spbt-cen">
<view class="charts-title">早起打卡趋势报告</view>
<view wx:if="{{riseLineData.xData.length}}" class="charts-download" data-type="2" bind:tap="downLoadImage">保存
<image class="charts-download-icon" src="{{cdn.image('rest_schedule_download_icon.png')}}" mode="aspectFit" />
</view>
</view>
<view class="echart-view" wx:if="{{riseLineData.xData.length}}">
<ec-canvas id="rise-chart" canvas-id="rise-chart" ec="{{ riseEc }}"></ec-canvas>
</view>
<view class="empty-box" wx:else>
<view class="empty-text-tip">
暂无数据,记得按时打卡哦~
</view>
</view>
</view>
<!-- 晚打卡趋势 -->
<view class="charts-box">
<view class="flex-row-spbt-cen">
<view class="charts-title">早睡打卡趋势报告</view>
<view wx:if="{{sleepLineData.xData.length}}" class="charts-download" data-type="3" bind:tap="downLoadImage">保存
<image class="charts-download-icon" src="{{cdn.image('rest_schedule_download_icon.png')}}" mode="aspectFit" />
</view>
</view>
<view class="echart-view" wx:if="{{sleepLineData.xData.length}}">
<ec-canvas id="sleep-chart" canvas-id="sleep-chart" ec="{{ sleepEc }}"></ec-canvas>
</view>
<view class="empty-box" wx:else>
<view class="empty-text-tip">
暂无数据,记得按时打卡哦~
</view>
</view>
</view>
</view>
<view wx:else class="empty-box" style="margin-top: 64rpx;">
<view class="empty-text" style="font-weight: 600;">当日晚打卡和次日早打后,才可计算睡眠时长哦</view>
<van-button bind:click="routeToHome" custom-class="empty-button">去打卡</van-button>
</view>
</view>
</view>
<!--分享弹窗-->
<van-overlay lock-scroll="{{true}}" z-index="9999" show="{{ shareShow }}">
<view style="height:100%;width: 100%;" class="flex-col-cen-cen">
<image show-menu-by-longpress="true" src="{{shareImagePath}}" style="width: 630rpx;height: 800rpx;" mode="aspectFit" />
<van-button bind:click="clickToSave" custom-class="poster-save-btn">
保存到相册
</van-button>
<image class="close-btn" src="{{cdn.image('advert_close.png')}}" catchtap="closeShare"></image>
</view>
</van-overlay>
<!-- 海报画板 -->
<view style="position:fixed;left: -2000rpx;top: 0;" wx:if="{{showCanvas}}">
<wxml-to-canvas height="{{cvsHeight}}" width="{{cvsWidth}}" class="widget"></wxml-to-canvas>
</view>
<!-- 隐私接口协议 -->
<privacy id="privacy-timeImage"></privacy>
WXSS
.schedule-container {
background: #6145F0;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 48rpx 0rpx 32rpx;
height: 166rpx;
}
.user-name {
color: #FFF;
margin-top: 24rpx;
font-family: PingFang SC;
font-size: 40rpx;
font-style: normal;
font-weight: 600;
line-height: 56rpx;
}
.user-avatar {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
margin-top: 17rpx;
}
.header-desc {
color: #FFF;
margin-top: 8rpx;
font-family: PingFang TC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 39rpx;
}
/*内容*/
.content {
border-radius: 64rpx 64rpx 0 0;
background-color: #fff;
width: 100%;
box-sizing: border-box;
padding-bottom: env(safe-area-inset-bottom);
}
.statistics-box {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 40rpx 32rpx;
}
.box-title {
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 600;
color: #333333;
line-height: 45rpx;
}
.wake-days-box {
border-radius: 16rpx;
background: rgba(255, 249, 229, 0.40);
padding: 16rpx 16rpx 16rpx 32rpx;
width: 331rpx;
box-sizing: border-box;
}
.sleep-days-box {
border-radius: 16rpx;
background: rgba(240, 229, 255, 0.40);
padding: 16rpx 16rpx 16rpx 32rpx;
width: 331rpx;
box-sizing: border-box;
}
.mine-cloud-icon {
height: 96rpx;
width: 96rpx;
margin-top: 2rpx;
}
.statistics-title {
color: #333;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 39rpx
}
.statistics-desc {
color: #697AF6;
font-family: PingFang SC;
font-size: 32rpx;
font-style: normal;
font-weight: 600;
line-height: 45rpx;
}
.continuous-number {
color: #333;
margin-top: 11rpx;
font-family: PingFang SC;
font-size: 20rpx;
font-style: normal;
font-weight: 400;
line-height: 28rpx;
}
.tab-box {
padding: 0 32rpx;
color: #000;
font-family: PingFang SC;
font-size: 32rpx;
font-style: normal;
font-weight: 600;
}
.active-tab {
border-radius: 32px;
background: #6145F0;
color: #FFF;
padding: 12rpx 48rpx;
text-align: center;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 600;
line-height: 40rpx;
}
.default-tab {
border-radius: 32px;
background: #ECECFF;
color: #333;
padding: 12rpx 48rpx;
text-align: center;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 40rpx;
}
/*图表盒子*/
.charts-content {
padding: 8rpx 32rpx 60rpx;
}
.charts-box {
margin-top: 24rpx;
padding: 24rpx;
border-radius: 16rpx;
background: #F9F9FA;
}
.charts-title {
color: #333;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 600;
line-height: 39rpx
}
.charts-download {
color: #697AF6;
font-family: PingFang SC;
font-size: 24rpx;
font-style: normal;
font-weight: 600;
line-height: 34rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.charts-download-icon {
margin-left: 9rpx;
width: 26rpx;
height: 26rpx;
}
.average-duration {
border-radius: 16rpx;
background: rgba(240, 229, 255, 0.40);
color: #333;
padding: 16rpx;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 39rpx;
margin-top: 24rpx;
display: inline-block;
}
.echart-view {
background: #F9F9FA;
width: 638rpx;
height: 365rpx;
margin-top: 24rpx;
}
/*----------空状态---------*/
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
}
.empty-text {
color: #333;
margin-top: 48rpx;
text-align: center;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 39rpx;
}
.empty-button {
margin-top: 40rpx;
margin-bottom: 32rpx;
width: 534rpx !important;
height: 91rpx !important;
border-radius: 100rpx !important;
background: linear-gradient(180deg, #FFF189 1.97%, #FEBA40 100%) !important;
box-shadow: 1rpx -1rpx 3rpx 0rpx rgba(255, 255, 255, 0.80) inset !important;
border: transparent !important;
color: #333 !important;
font-size: 32rpx !important;
font-weight: 600 !important;
line-height: 48rpx !important;
}
.empty-text-tip{
color: #999;
margin-top: 64rpx;
margin-bottom: 40rpx;
font-family: PingFang SC;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 39rpx;
}
/*弹窗*/
.poster-save-btn{
width: 371rpx !important;
height: 88rpx !important;
border-radius: 50rpx!important;
color: #333!important;
text-align: center !important;
font-size: 32rpx !important;
font-weight: 600!important;
line-height: 88px !important;
background: #FCD34F !important;
border: transparent !important;
margin-top: 50rpx;
}
.close-btn{
margin-top: 40rpx;
width: 52rpx;
height: 52rpx;
}
JSON
{
"usingComponents": {
"ec-canvas": "../components/ec-canvas/ec-canvas",
"wxml-to-canvas": "../../miniprogram/miniprogram_npm/wxml-to-canvas/index",
"nav-bar":"../../components/navBar/navBar",
"van-button": "../../miniprogram/miniprogram_npm/@vant/weapp/button/index",
"auth-btn": "../../components/authBtn/authBtn",
"van-overlay": "../../miniprogram/miniprogram_npm/@vant/weapp/overlay/index",
"privacy": "../../components/privacy/privacy"
},
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
js
import * as echarts from '../components/ec-canvas/echarts';
import echartsOption from './echartsOption.js';
import {
CDN_PREFIX
} from '../../utils/key'
import {
QRURL
} from '../../utils/key'
import local from '../../utils/local';
import {
apiGetStatisticsChart,
apiGetQrCode
} from '../../http/api'
import {
getNavbarInfo
} from '../../utils/navigationBar'
const app = getApp()
const {
events,
} = app.globalData
const {
wxml,
style
} = require('./image.js')
const BASE_TIME_MAX = "2023-10-02";
const BASE_TIME_MIN = "2023-10-01";
Page({
/**
* 页面的初始数据
*/
data: {
navigationBarHeight: '',
tabIndex: 1, //1:7天 2:30天
averageDuration: 0,
ratio: 0.5,
allEmpty: false,
shareImagePath: '',
tempFilePath: '',
showCanvas: false,
shareShow: false,
cvsHeight: '',
cvsWeight: '',
userInfo: {},
sleepLineData: {
xData: [],
yData: []
},
riseLineData: {
xData: [],
yData: []
},
sleepBarData: {
xData: [],
yData: []
},
sleepEc: {
lazyLoad: true // 懒加载
},
riseEc: {
lazyLoad: true // 懒加载
},
timeRangeEc: {
lazyLoad: true // 懒加载
},
qrImg: '',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let navigationBarHeight = getApp().globalNavbarInfo.navigationBarHeight
let ratio = wx.getSystemInfoSync().windowWidth / 750
this.setData({
navigationBarHeight,
ratio: ratio
})
// 监听微信登录成功
events.on('on-launch-executed', this, async res => {
this.initialData()
})
this.initialData()
},
initialData() {
let userInfo = getApp().globalData.userInfo
this.setData({
userInfo
})
this.getChartsData()
},
async getChartsData() {
wx.showLoading({
title: '加载中...',
mask:true
})
const res = await apiGetStatisticsChart({
type: this.data.tabIndex
})
if (res.code == 200) {
let average = res.data.average
let sleep = res.data.sleep
let wake = res.data.wake
let averageIsEmpty = this.isAllZerosOrEmpty(average)
let sleepIsEmpty = this.isAllZerosOrEmpty(sleep)
let wakeIsEmpty = this.isAllZerosOrEmpty(wake)
if (averageIsEmpty && sleepIsEmpty && wakeIsEmpty) {
this.setData({
allEmpty: true
})
wx.hideLoading()
return
} else {
this.setData({
allEmpty: false
})
}
if (!averageIsEmpty) {
this.getTimeRangeData(average)
} else {
this.setData({
['sleepBarData.xData']: [],
['sleepBarData.yData']: [],
})
}
if (!sleepIsEmpty) {
this.getSleepData(sleep)
} else {
this.setData({
['sleepLineData.xData']: [],
['sleepLineData.yData']: [],
})
}
if (!wakeIsEmpty) {
this.getRiseData(wake)
} else {
this.setData({
['riseLineData.xData']: [],
['riseLineData.yData']: [],
})
}
} else {
this.setData({
allEmpty: true
})
}
wx.hideLoading()
},
//判断对象是否为空
isAllZerosOrEmpty(obj) {
for (let value of Object.values(obj)) {
if (value !== 0 && value !== '' && value !== null) {
if (typeof value === 'object') {
// 如果值是对象,递归判断
if (!this.isAllZerosOrEmpty(value)) {
return false;
}
} else {
// 值不为0、''、null或对象
return false;
}
}
}
return true;
},
//切换时间
tabChange(e) {
let tabIndex = e.currentTarget.dataset.index
this.setData({
tabIndex
}, () => {
this.getChartsData()
})
},
getTimeRangeData(average) {
const keys = Object.keys(average);
const values = Object.values(average);
let averageArr = []
let formatData = values.map(res => {
if (res) {
let num = (res / (60 * 60)).toFixed(1)
averageArr.push(Number(num))
return num
} else {
return ''
}
})
let averageDuration = averageArr.reduce((a, b) => a + b) / averageArr.length;
const sleepBarData = this.data.sleepBarData
sleepBarData.xData = keys
sleepBarData.yData = formatData
this.setData({
sleepBarData,
averageDuration: averageDuration.toFixed(1)
})
this.initTimeRangeChart(sleepBarData)
},
initTimeRangeChart(sleepBarData) {
let tabIndex = this.data.tabIndex
let ratio = this.data.ratio
// 绑定组件
this.barComponent = this.selectComponent("#time-range-chart");
// 初始化柱状图
this.barComponent.init((canvas, width, height, dpr) => {
// 初始化图表
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr // 解决模糊显示问题
})
// 开发中根据从后端获取sleepLineData数据,动态更新图表
chart.setOption(echartsOption.sleepBarOption(sleepBarData, tabIndex, ratio, echarts))
return chart
})
},
getSleepData(sleep) {
const keys = Object.keys(sleep);
const values = Object.values(sleep);
let formatData = values.map(res => {
if (res) {
let timeStr = res.time.slice(10, 19)
return {
value: res.type == 1 ? BASE_TIME_MIN + timeStr : BASE_TIME_MAX + timeStr,
realTime: res.time,
}
} else {
return {
value: '',
realTime: ''
}
}
})
const sleepLineData = this.data.sleepLineData
sleepLineData.xData = keys
sleepLineData.yData = formatData
this.setData({
sleepLineData
})
this.initSleepChart(sleepLineData)
},
initSleepChart(sleepLineData) {
let tabIndex = this.data.tabIndex
let ratio = this.data.ratio
// 绑定组件
this.barComponent = this.selectComponent("#sleep-chart");
// 初始化柱状图
this.barComponent.init((canvas, width, height, dpr) => {
// 初始化图表
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr // 解决模糊显示问题
})
// 开发中根据从后端获取sleepLineData数据,动态更新图表
chart.setOption(echartsOption.sleepLineOption(sleepLineData, BASE_TIME_MIN, BASE_TIME_MAX, tabIndex, ratio, echarts))
return chart
})
},
getRiseData(wake) {
const keys = Object.keys(wake);
const values = Object.values(wake);
let formatData = values.map(res => {
if (res) {
let timeStr = res.time.slice(10, 19)
return {
value: BASE_TIME_MIN + timeStr,
realTime: res.time,
}
} else {
return {
value: '',
realTime: ''
}
}
})
const riseLineData = this.data.riseLineData
riseLineData.xData = keys
riseLineData.yData = formatData
this.setData({
riseLineData
})
this.initRiseChart(riseLineData)
},
initRiseChart(riseLineData) {
let tabIndex = this.data.tabIndex
let ratio = this.data.ratio
// 绑定组件
this.barComponent = this.selectComponent("#rise-chart");
// 初始化柱状图
this.barComponent.init((canvas, width, height, dpr) => {
// 初始化图表
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr // 解决模糊显示问题
})
// 开发中根据从后端获取sleepLineData数据,动态更新图表
chart.setOption(echartsOption.riseLineOption(riseLineData, BASE_TIME_MIN, BASE_TIME_MAX, tabIndex, ratio, echarts))
return chart
})
},
routeToHome() {
wx.switchTab({
url: '/pages/index/index',
})
},
// 获取二维码
async getGetQrCode() {
var url = local.getSync(QRURL) || ''
if (!url) {
let res = await apiGetQrCode()
if (!res.data) {
console.log('请求二维码失败');
return
}
url = res.data.url
}
this.setData({
qrImg: url
})
},
async downLoadImage(e) {
wx.showLoading({
title: '生成中...',
mask:true
})
let type = e.currentTarget.dataset.type
//画板准备
let cvsHeight = 848
let cvsWidth = 630
this.setData({
cvsHeight: cvsHeight,
cvsWidth: cvsWidth,
showCanvas: true,
}, () => {
this.widget = this.selectComponent('.widget')
this.widget.setData({
top: 0,
left: 0,
height: this.data.cvsHeight,
width: this.data.cvsWidth,
});
})
//生成echart图片
await this.getGetQrCode()
let tempFilePath = ''
let title = ''
if (type == 1) {
tempFilePath = await this.drawImage1()
title = '睡眠时长报告'
} else if (type == 2) {
tempFilePath = await this.drawImage2()
title = '早起打卡趋势报告'
} else if (type == 3) {
tempFilePath = await this.drawImage3()
title = '早睡打卡趋势报告'
}
this.setData({
tempFilePath
}, () => {
this.renderToCanvas(title)
})
},
/**
* 绘图
*/
drawImage1() {
return new Promise((resolve, reject) => {
let ecComonnets = this.selectComponent('#time-range-chart')
ecComonnets.canvasToTempFilePath({
success: res => {
console.log("tempFilePath", res.tempFilePath)
resolve(res.tempFilePath)
},
fail: (err) => {
resolve('')
}
})
})
},
drawImage2() {
return new Promise((resolve, reject) => {
let ecComonnets = this.selectComponent('#rise-chart')
ecComonnets.canvasToTempFilePath({
success: res => {
console.log("tempFilePath", res.tempFilePath)
resolve(res.tempFilePath)
},
fail: (err) => {
resolve('')
}
})
})
},
drawImage3() {
return new Promise((resolve, reject) => {
let ecComonnets = this.selectComponent('#sleep-chart')
ecComonnets.canvasToTempFilePath({
success: res => {
console.log("tempFilePath", res.tempFilePath)
resolve(res.tempFilePath)
},
fail: (err) => {
resolve('')
}
})
})
},
//绘画
async renderToCanvas(title) {
let cardInfo = {
title: title,
nickname: this.data.userInfo.nickname || '',
avatar: this.data.userInfo.avatar || '',
averageDuration: this.data.averageDuration || 0,
tempFilePath: this.data.tempFilePath || '',
wake_total_days: this.data.userInfo.wake_total_days || 0,
sleep_total_days: this.data.userInfo.sleep_total_days || 0,
qrImg: this.data.qrImg || '',
maskImage: `${CDN_PREFIX}chart_share_mask_gu.png`,
tabName: this.data.tabIndex == 1 ? '7' : '30'
}
let wakeFontWight = cardInfo.wake_total_days.toString().length
let sleepFontWight = cardInfo.sleep_total_days.toString().length
const _wxml = wxml(cardInfo)
const _style = style(wakeFontWight,sleepFontWight)
const p1 = this.widget.renderToCanvas({
wxml: _wxml,
style:_style
})
p1.then((res) => {
this.extraImage()
}).catch(function () {
wx.hideLoading()
});
},
// 绘制图片
extraImage() {
const p2 = this.widget.canvasToTempFilePath()
p2.then(res => {
console.log('绘制完成src:', res.tempFilePath)
wx.hideLoading()
this.setData({
shareImagePath: res.tempFilePath,
showCanvas: false
}, () => {
this.setData({
shareShow: true
})
})
}).catch(function () {
wx.hideLoading()
});
},
closeShare() {
this.setData({
shareShow: false
})
},
// 下载图片
clickToSave() {
let imageUrl = this.data.shareImagePath
wx.getSetting({
success: (res) => {
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
// 用户已经同意小程序使用相册功能
this.saveImg(imageUrl);
},
fail: (e) => {
wx.showModal({
title: '保存失败',
content: '请检查是否授权了相册权限',
success: ({
confirm
}) => {
if (confirm) {
wx.openSetting({
success: ({
authSetting
}) => {
console.log(authSetting);
if (authSetting['scope.writePhotosAlbum']) {
this.saveImg(imageUrl);
}
}
})
} else {
// 用户取消授权,不进行任何操作
}
}
});
}
});
} else {
this.saveImg(imageUrl);
}
}
});
},
saveImg(imageUrl) {
wx.saveImageToPhotosAlbum({
filePath: imageUrl,
success: () => {
wx.showToast({
title: '保存成功',
})
}
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
const caniuse = wx.canIUse('onNeedPrivacyAuthorization')
caniuse && wx.onNeedPrivacyAuthorization(resolve => {
// 需要用户同意隐私授权时
// 弹出开发者自定义的隐私授权弹窗
const privacy = this.selectComponent('#privacy-timeImage')
privacy.show({
resolvePrivacy: resolve
})
})
},
/**
* 监听滚动事件
*/
onPageScroll(e) { //nvue暂不支持滚动监听,可用bindingx代替
let scrollTop = e.scrollTop
let navBar = this.selectComponent('#nav-bar')
let navInfo = getNavbarInfo()
navBar.setBgColor(scrollTop)
if (e.scrollTop < navInfo.navigationBarHeight) {
wx.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: '#ff0000',
animation: {
duration: 1,
timingFunc: 'easeIn'
}
})
} else {
wx.setNavigationBarColor({
frontColor: '#000000',
backgroundColor: '#ff0000',
animation: {
duration: 1,
timingFunc: 'easeIn'
}
})
}
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
events.remove('on-launch-executed', this)
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
OptionJS
function sleepBarOption(data, tabIndex,ratio,echarts,) {
let barData = data;
var option = {
backgroundColor: '#f9f9fa',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line' //选中区域使用线条
},
formatter: function (params, ticket, callback) {
let showHtm = params[0].name + '\n';
let seriesName = params[0].seriesName+":"
let marker =params[0].marker ;
let value = params[0].value? params[0].value+'小时':'-'
showHtm += marker+seriesName+value
return showHtm;
},
textStyle: {
//去除安卓手机错误阴影
textShadowBlur: 10,
textShadowColor: "transparent"
},
confine: true //是否将 tooltip 框限制在图表的区域内
},
xAxis: {
type: 'category',
boundaryGap:true,
data: barData.xData,
axisLine: {
lineStyle: {
color: '#999',
width:2*ratio
},
},
axisPointer: {
type: 'line'
},
axisTick: {
show: true,
alignWithLabel: true
},
axisLabel: {
fontSize:28*ratio,
color:'#333333',
interval:tabIndex==1?1:6,
formatter: function (value) {
let time = echarts.format.formatTime('MM-dd', value)
// 格式化时间
return time
},
},
},
yAxis: {
splitLine: {
show: false
},
axisLabel: {
align: 'right',
fontSize:28*ratio,
color:'#333333'
},
},
grid: {
left:60*ratio,
width:537*ratio,
height:275*ratio,
top:40*ratio
},
series: [
{
name: '睡眠时长',
type: 'bar',
barWidth: tabIndex==1?10:5,
itemStyle: {
borderRadius: [5,5,0,0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#D9BBF7' },
{ offset: 1, color: '#674FF5' }
])
},
data: barData.yData
},
]
};
return option;
}
function sleepLineOption(data, BASE_TIME_MIN, BASE_TIME_MAX,tabIndex,ratio, echarts) {
let lineData = data;
var option = {
backgroundColor: '#f9f9fa',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line' //选中区域使用线条
},
textStyle: {
//去除安卓手机错误阴影
textShadowBlur: 10,
textShadowColor: "transparent",
},
formatter: function (params, ticket, callback) {
let showHtm = params[0].name + '\n';
let seriesName = params[0].seriesName
let realTime = params[0].data.realTime
if(realTime){
realTime = realTime.slice(5,16)
}else{
realTime = '-'
}
let marker =params[0].marker ;
showHtm += marker+seriesName+' : ' + realTime
return showHtm;
},
confine: true //是否将 tooltip 框限制在图表的区域内
},
xAxis: {
type: 'category',
boundaryGap:true,
data: lineData.xData,
axisLine: {
lineStyle: {
color: '#999',
width:2*ratio
}
},
axisPointer: {
type: 'line'
},
axisTick: {
show: true,
alignWithLabel: true
},
axisLabel: {
formatter: function (value) {
let time = echarts.format.formatTime('MM-dd', value)
// 格式化时间
return time
},
fontSize:28*ratio,
color:'#333333',
align: 'center',
interval:tabIndex==1?1:6,
},
},
yAxis: {
type: 'time',
min: `${BASE_TIME_MIN} 20:00:00`,
max: `${BASE_TIME_MAX} 04:00:00`,
minInterval: 3600 * 1000 * 3,
axisLabel: {
align: 'right',
fontSize:28*ratio,
color:'#333333',
formatter: function (value) {
let time = echarts.format.formatTime('hh:mm', value)
// 格式化时间
return time
}
},
splitLine: {
show: false
},
},
grid: {
left:90*ratio,
width:517*ratio,
height:275*ratio,
top:40*ratio
},
series: [{
name: '晚打卡时间',
type: 'line',
smooth: true,
showAllSymbol: true,
connectNulls: true,
symbol: 'circle',
symbolSize: tabIndex==1?10:5,
lineStyle:{
color:'#bdc4ff',
width:tabIndex==1?3:1,
},
itemStyle:{
color :'#654FF5'
},
data: lineData.yData,
},
{
name: '晚打卡时间',
type: 'bar',
barGap: '-100%',
barWidth: tabIndex==1?10:5,
symbol: 'rect',
itemStyle: {
borderRadius: [5,5,0,0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(219,191,247,0.8)' },
{ offset: 0.2, color: 'rgba(219,191,247,0.5)' },
{ offset: 1, color: 'rgba(219,191,247,0)' }
])
},
data: lineData.yData
},
]
};
return option;
}
function riseLineOption(data, BASE_TIME_MIN, BASE_TIME_MAX,tabIndex,ratio, echarts) {
let lineData = data;
var option = {
backgroundColor: '#f9f9fa',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line' //选中区域使用线条
},
textStyle: {
//去除安卓手机错误阴影
textShadowBlur: 10,
textShadowColor: "transparent"
},
formatter: function (params, ticket, callback) {
let showHtm = params[0].name + '\n';
let seriesName = params[0].seriesName
let realTime = params[0].data.realTime
if(realTime){
realTime = realTime.slice(5,16)
}else{
realTime = '-'
}
let marker =params[0].marker ;
showHtm += marker+seriesName+' : ' + realTime
return showHtm;
},
confine: true //是否将 tooltip 框限制在图表的区域内
},
xAxis: {
type: 'category',
boundaryGap:true,
data: lineData.xData,
axisLine: {
lineStyle: {
color: '#999',
width:2*ratio
}
},
axisPointer: {
type: 'line'
},
axisTick: {
show: true,
alignWithLabel: true
},
axisLabel: {
formatter: function (value) {
let time = echarts.format.formatTime('MM-dd', value)
// 格式化时间
return time
},
fontSize:28*ratio,
color:'#333333',
align: 'center',
interval:tabIndex==1?1:6,
},
},
yAxis: {
type: 'time',
min: `${BASE_TIME_MIN} 04:00:00`,
max: `${BASE_TIME_MIN} 13:00:00`,
minInterval: 3600 * 1000 * 3,
axisLabel: {
align: 'right',
fontSize:28*ratio,
color:'#333333',
formatter: function (value) {
let time = echarts.format.formatTime('hh:mm', value)
// 格式化时间
return time
}
},
splitLine: {
show: false
},
},
grid: {
left:90*ratio,
width:517*ratio,
height:275*ratio,
top:40*ratio
},
series: [{
name: '早打卡时间',
type: 'line',
smooth: true,
showAllSymbol: true,
connectNulls: true,
symbol: 'circle',
symbolSize: tabIndex==1?10:5,
lineStyle:{
color:'#bdc4ff',
width:tabIndex==1?3:1,
},
itemStyle:{
color :'#654FF5'
},
data: lineData.yData,
},
{
name: '早打卡时间',
type: 'bar',
barGap: '-100%',
barWidth: tabIndex==1?10:5,
symbol: 'rect',
itemStyle: {
borderRadius:[5,5,0,0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(219,191,247,0.8)' },
{ offset: 0.2, color: 'rgba(219,191,247,0.5)' },
{ offset: 1, color: 'rgba(219,191,247,0)' }
])
},
data: lineData.yData
},
]
};
return option;
}
module.exports = {
sleepLineOption,
riseLineOption,
sleepBarOption
}
绘图JS
const wxml = (cardInfo) => {
//计算天数显示长度
let html = `
<view class="posterBg">
<image class="maskImage" src="` + cardInfo.maskImage + `" mode="aspectFit" />
<view class="flexRow">
<image class="avatar" src="` + cardInfo.avatar + `" mode="aspectFit" />
<view class="flexColumn">
<text class="nickname">` + cardInfo.nickname + `</text>
<text class="title">` + cardInfo.title + `</text>
</view>
</view>
<view class="chartCard">`
if (cardInfo.title == '睡眠时长报告') {
html += `<view class="avatarBox">
<text class="avatarText">近` + cardInfo.tabName + `天平均睡眠时长` + cardInfo.averageDuration + `小时</text>
</view>`
} else {
html += `<view class="nullBox">
<text class="nullBoxText">健康生活,早睡早起</text>
</view>`
}
html += `<image class="chartImage" src="` + cardInfo.tempFilePath + `" mode="aspectFit" />
</view>
<view class="infoCard flexRow">
<view class="flexColumnCen">
<text class="totalTitle">
累计晚打卡
</text>
<view class="totalNumberBox">
<text class="wakeTotalNumber">` + cardInfo.wake_total_days + `</text>
<text class="totalUnit">天</text>
</view>
</view>
<view class="flexColumnCen ml36">
<text class="totalTitle">
累计早打卡
</text>
<view class="totalNumberBox">
<text class="sleepTotalNumber">` + cardInfo.sleep_total_days + `</text>
<text class="totalUnit">天</text>
</view>
</view>
<view class="flexRow">
<image class="qrcodeImage" src="` + cardInfo.qrImg + `" mode="aspectFit" />
<text class="qrcodeText">扫码说早安</text>
<view>
</view>
</view>
`
return html;
}
const style = (wakeFontWight,sleepFontWight) => {
return {
posterBg: {
width: 630,
height: 800,
borderRadius: 32,
flexDirection: 'column',
backgroundColor: '#6145F0',
position: 'relative'
},
flexRow: {
flexDirection: 'row',
alignItems: 'center'
},
flexColumn: {
flexDirection: 'column',
justifyContent: 'center'
},
flexColumnCen:{
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
avatar: {
width: 85,
height: 85,
borderRadius: 43,
marginLeft: 24,
marginTop: 40
},
nickname: {
width: 353,
height: 45,
fontSize: 32,
lineHeight: 45,
textAlign: 'left',
color: '#FFFFFF',
fontWeight: 'bold',
marginLeft: 16,
marginTop: 40
},
title: {
width: 353,
height: 40,
fontSize: 28,
lineHeight: 40,
textAlign: 'left',
color: '#FFFFFF',
marginLeft: 16,
marginTop: 1
},
maskImage: {
width: 254,
height: 149,
position: 'absolute',
top: 0,
right: 0
},
chartCard: {
marginTop: 24,
marginLeft: 24,
marginRight: 24,
paddingTop: 16,
paddingBottom: 16,
borderRadius: 16,
backgroundColor: '#F9F9FA',
},
nullBox: {
padding: 16,
marginLeft: 24,
borderRadius: 16,
backgroundColor: 'rgba(240, 229, 255, 0.40)',
width: 302,
},
nullBoxText: {
width: 260,
height: 40,
fontSize: 28,
lineHeight: 40,
textAlign: 'center',
color: '#333333',
},
avatarBox: {
padding: 16,
marginLeft: 24,
borderRadius: 16,
backgroundColor: 'rgba(240, 229, 255, 0.40)',
width: 402,
},
avatarText: {
width: 370,
height: 40,
fontSize: 28,
lineHeight: 40,
textAlign: 'center',
color: '#333333',
},
chartImage: {
width: 534,
height: 336,
marginLeft: 24,
},
infoCard: {
marginTop: 32,
marginLeft: 24,
marginRight: 24,
paddingTop: 24,
paddingLeft: 24,
paddingRight: 24,
paddingBottom: 18,
borderRadius: 16,
backgroundColor: '#F9F9FA'
},
totalTitle: {
width: 140,
height: 36,
fontSize: 26,
lineHeight: 36,
color: '#333333',
marginLeft: 24,
},
totalNumberBox: {
marginTop: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 24,
},
wakeTotalNumber: {
width: 26 * wakeFontWight,
height: 56,
fontSize: 40,
lineHeight: 56,
textAlign: 'right',
color: '#333333',
fontWeight: 'bold',
},
sleepTotalNumber: {
width: 26 * sleepFontWight,
height: 56,
fontSize: 40,
lineHeight: 56,
textAlign: 'right',
color: '#333333',
fontWeight: 'bold',
},
totalUnit: {
width: 24,
height: 31,
fontSize: 22,
lineHeight: 31,
textAlign: 'left',
color: '#333333',
marginLeft: 8,
marginTop: 4,
},
ml36: {
marginLeft: 36
},
qrcodeImage: {
marginLeft: 67,
width: 84,
height: 84
},
qrcodeText: {
width: 22,
height: 100,
fontSize: 20,
lineHeight: 20,
textAlign: 'left',
color: '#333333',
marginLeft:4,
}
}
}
module.exports = {
wxml,
style
}