厨房秤功能

This commit is contained in:
qcl_123 2026-03-23 16:23:40 +08:00
parent 64952db2ad
commit 3400c47db8
504 changed files with 52977 additions and 969 deletions

392
Food/count/everyDay.vue Normal file
View File

@ -0,0 +1,392 @@
<template>
<view class="content">
<view class="content_box">
<view class="date"></view>
<!-- 早午晚餐 -->
<view class="everyDay">
<view class="title">
<view><text class="quan"></text>卡路里分析</view>
</view>
<div class="chart-wrap">
<qiun-data-charts type="ring" :opts="opts" :chartData="chartData" :cHeight="320" :cWidth="320" />
</div>
<view class="foodtools">
<view class="type" v-for="(item,index) in foodInfo.list">
<view class="name">
<text :style="{background:item.color}"></text>
<view>{{item.name}}</view>
</view>
<view>{{item.val||0}}{{item.unit}}<text>|</text>{{item.kcal_proportion}}%</view>
</view>
</view>
</view>
<!--营养元素分析 -->
<view class="everyDay">
<view class="title">
<view><text class="quan"></text>营养元素能量占比</view>
</view>
<div class="chart-wrap">
<qiun-data-charts type="ring" :opts="opts2" :chartData="chartData2" :cHeight="320" :cWidth="320" />
</div>
<view class="foodtools">
<view class="top">
<view>营养分类</view>
<view>摄入</view>
</view>
<view class="type">
<view class="name">
<text :style="{background:details.carbohydrate.color}"></text>
<image :src="details.carbohydrate.icon"></image>
<view>{{details.carbohydrate.name}}</view>
</view>
<view>
{{details.carbohydrate.val}}{{details.carbohydrate.unit}}<text>|</text>{{details.carbohydrate.proportion}}%
</view>
</view>
<view class="type">
<view class="name">
<text :style="{background:details.fat.color}"></text>
<image :src="details.fat.icon"></image>
<view>{{details.fat.name}}</view>
</view>
<view>{{details.fat.val}}{{details.fat.unit}}<text>|</text>{{details.fat.proportion}}%</view>
</view>
<view class="type">
<view class="name">
<text :style="{background:details.protein.color}"></text>
<image :src="details.protein.icon"></image>
<view>{{details.protein.name}}</view>
</view>
<view>
{{details.protein.val}}{{details.protein.unit}}<text>|</text>{{details.protein.proportion}}%
</view>
</view>
</view>
</view>
<!--营养元素排行榜 -->
<view class="everyDay">
<view class="title">
<view><text class="quan"></text>营养元素排行榜</view>
</view>
<view class="foodtools rank_list">
<view class="topname">{{details.carbohydrate.name}}</view>
<view class="type" v-for="(item,index) in details.carbohydrate.rank_list">
<view class="name">
<image :src="item.icon"></image>
<image :src="item.pic_url" class="pic" v-if="item.pic_url"></image>
<view>{{item.name?item.name:'--'}}</view>
</view>
<view>{{item.weight||0}}g</view>
</view>
<view class="topname">{{details.fat.name}}</view>
<view class="type" v-for="(item,index) in details.fat.rank_list">
<view class="name">
<image :src="item.icon"></image>
<image :src="item.pic_url" class="pic" v-if="item.pic_url"></image>
<view>{{item.name?item.name:'--'}}</view>
</view>
<view>{{item.weight||0}}g</view>
</view>
<view class="topname">{{details.protein.name}}</view>
<view class="type" v-for="(item,index) in details.protein.rank_list">
<view class="name">
<image :src="item.icon"></image>
<image :src="item.pic_url" class="pic" v-if="item.pic_url"></image>
<view>{{item.name?item.name:'--'}}</view>
</view>
<view>{{item.weight||0}}g</view>
</view>
</view>
</view>
<!-- 营养元素 -->
<view class="jishiqi">
<view class="right">
<view class="item" v-for="(ite,ind) in foodInfo.nutrients_four">
<view class="left-icon">
<view class="name">
<image :src="ite.icon"></image>
<text class="name">{{ite.name}}</text>
</view>
<view>
<text class="weight">{{ite.today_intake||0}}{{ite.unit}}</text> / {{ite.suggestion||0}}g
</view>
</view>
<view class="right-info">
<view class="right-info-bottom">
<view class="val" :style="{ width: ite.proportion + '%',background:ite.color}"></view>
</view>
</view>
</view>
<view class="item" v-for="(ite,ind) in foodInfo.trace_elements_all_day">
<view class="left-icon">
<view class="name">
<text class="name">{{ite.name_ch}}</text>
</view>
<view>
{{ite.value||0}}{{ite.unit}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts.vue';
export default {
data() {
return {
opts: {
color: [],
title: {
name: "",
}
},
opts2: {
color: [],
subtitle: {
name: "",
}
},
chartData: {},
chartData2: {},
foodInfo: {},
details: {}
}
},
components: {
qiunDataCharts
},
computed: {
...mapState(["user", "countFoodInfo"]),
},
onLoad(options) {
let that = this
that.handleList()
},
methods: {
handleList() {
let that = this
let chart_data = []
let chart_data2 = []
that.opts.color = []
that.opts2.color = []
that.foodInfo = that.countFoodInfo
that.details = that.countFoodInfo.details
for (let i = 0; i < that.foodInfo.list.length; ++i) {
that.opts.color.push(that.foodInfo.list[i].color)
chart_data.push({
name: that.foodInfo.list[i].name,
value: Number(that.foodInfo.list[i].kcal_proportion),
})
}
that.opts.title.name = that.foodInfo.nutrients_four[0].today_intake
that.chartData = JSON.parse(JSON.stringify({
series: [{
data: chart_data
}]
}));
for (let key in that.details) {
if (that.details.hasOwnProperty(key)) {
that.opts2.color.push(that.details[key].color)
chart_data2.push({
name: that.details[key].name,
value: Number(that.details[key].proportion),
})
}
}
that.chartData2 = JSON.parse(JSON.stringify({
series: [{
data: chart_data2
}]
}));
},
}
}
</script>
<style scoped lang="scss">
.content {
display: flex;
flex-direction: column;
align-items: center;
background-color: #f7f7f7;
min-height: 100vh;
}
.content_box {
width: 100%;
}
.chart-wrap {
position: relative;
width: 320rpx;
height: 320rpx;
margin: -30rpx auto 0;
.uchart-val {
margin-top: 70rpx;
position: absolute;
left: 0;
top: 48rpx;
width: 320rpx;
font-size: 46rpx;
text-align: center;
z-index: 9;
}
}
.everyDay {
background: #fff;
padding: 10px 10px 0;
border-radius: 10px;
margin-top: 15px;
height: auto;
overflow: hidden;
.title {
display: flex;
justify-content: space-between;
font-weight: bold;
font-size: 15px;
.quan {
width: 60rpx;
height: 40rpx;
position: relative;
margin-right: 70rpx;
}
}
.foodtools {
text {
margin: 0 15rpx;
color: #ccc;
}
.type {
display: flex;
line-height: 45px;
justify-content: space-between;
border-bottom: 1px solid #f7f7f7;
.name {
width: 80%;
display: flex;
align-items: center;
.pic {
border: 1px solid #f7f7f7;
border-radius: 10px;
}
text {
width: 7px;
height: 7px;
margin-right: 10px;
border-radius: 15px;
}
}
image {
width: 20px;
height: 20px;
margin-right: 10px;
}
}
}
.rank_list {
.topname {
position: relative;
padding-left: 15px;
margin-top: 5px;
}
.topname::after {
width: 5px;
height: 5px;
content: "";
position: absolute;
left: 0;
top: 7px;
background-color: #666;
border-radius: 50%;
}
.type {
border-bottom: none;
line-height: 40px;
}
}
.top {
display: flex;
justify-content: space-between;
}
}
.jishiqi {
margin: 0 10px;
.right {
width: 100%;
margin-left: 0;
height: auto;
margin-bottom: 20px;
}
.item {
width: auto;
background: #fff;
padding: 2px 15px;
border-radius: 10px;
display: flex;
flex-wrap: wrap;
font-size: 28rpx;
margin-bottom: 10px;
.left-icon {
width: 100%;
float: left;
height: 80rpx;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: inherit;
image {
margin-right: 5px;
width: 40rpx;
height: 40rpx;
}
.name {
display: flex;
align-items: center;
}
.weight {
margin-right: 5px;
color: $maincolor;
}
}
.right-info {
width: 100%;
height: 8px;
margin-bottom: 10px;
background-color: #f3f7f5;
border-radius: 5px;
position: relative;
}
}
}
</style>

467
Food/count/everyMeal.vue Normal file
View File

@ -0,0 +1,467 @@
<template>
<view class="content">
<view class="content_box">
<view class="box">
<!-- 类型 -->
<view class="top">
<image :src="bgimage" mode="aspectFill">
<view class="name">{{info.name}}</view>
<view class="time">{{time}}</view>
</image>
</view>
<!-- 成分统计 -->
<view class="everyDay">
<view class="title">
<view><text class="quan"></text>成分统计</view>
</view>
<view class="progress">
<div class="chart-wrap">
<qiun-data-charts type="ring" :opts="opts" canvasId="foodCharts" :chartData="chartData"
:cHeight="280" :cWidth="280" :canvas2d="true" />
</div>
<view class="info" v-if="info.nutrients_four">
<view class="info-item" v-for="(item,index) in info.nutrients_four.slice(1)" :key="index">
<view>
<view class="color" :style="{'background-color':`${item.color}`}"></view>
<view>{{item.name}}</view>
</view>
<view>
<view>{{item.value}}{{item.unit}}<text>|</text>{{item.proportion}}%</view>
</view>
</view>
</view>
</view>
</view>
<!-- 早午晚餐 -->
<view class="foodtools">
<view class="type">
<view class="title">
<view><text class="quan"></text>食物类型</view>
</view>
<view class="list" v-if="info.list.length">
<uni-swipe-action>
<uni-swipe-action-item v-for="(ite,ind) in info.list" :key="ind"
:right-options="actionOptions" @click="delAcitionItem(ite)">
<view class="item" @click="showFoodDetail(ite)">
<image :src="ite.pic_url" mode="aspectFill"></image>
<view class="weight">
<view>{{ite.name}}</view>
<view class="size12 c999">{{ite.weight}}<text>|</text>{{ite.val}}kcal</view>
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
<view v-else class="nolist">
<image src="/static/none.png"></image>
<view>暂无食物</view>
</view>
</view>
</view>
<!-- 添加食物 -->
<view class="add" @click="handleAddFood()">
<text>+</text>添加食物
</view>
</view>
</view>
<!-- 营养含量分析 -->
<view v-if="drawerVisible" class="drawerVisible">
<view class="bgVisible" @click="handleDrawerClose"></view>
<view class="infoVisible">
<scroll-view style="height: 100%;" scroll-y="true">
<view class="foodDetail">
<view class="foodInfo">
<image :src="activeFoodDetail.pic_url" mode="aspectFill"></image>
<view class="info">
<view class="name">{{activeFoodDetail.name}}</view>
<view class="kcal">{{activeFoodDetail.val}}千卡</view>
</view>
</view>
<view class="foodContent">
<view class="title">热量和营养</view>
<view class="progress">
<div class="chart-wrap">
<qiun-data-charts v-if="chartVisible" type="ring" :opts="opts2"
:chartData="chartData2" :cHeight="280" :cWidth="280" />
</div>
<view class="info" v-if="activeFoodDetail.nutrients_four">
<view class="info-item"
v-for="(item,index) in activeFoodDetail.nutrients_four.slice(1)" :key="index">
<view class="color" :style="{'background-color':`${item.color}`}">
</view>
<view>{{item.name}}{{item.proportion}}%</view>
</view>
</view>
</view>
<view class="tips">
<text>营养素</text>
<text>{{activeFoodDetail.weight}}含量</text>
</view>
<view class="foodDetailList">
<view class="foodDetailItem" v-for="(item,index) in activeFoodDetail.nutrients_list"
:key="index">
<view class="name">{{item.name_ch}}</view>
<view class="value">{{item.value}}{{item.unit}}
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
let next = 0
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts.vue';
export default {
data() {
return {
opts: {
color: [],
title: {
name: "",
}
},
opts2: {
color: [],
title: {
name: "",
}
},
chartData: {},
chartData2: {},
activeFoodDetail: {},
drawerVisible: false,
chartVisible: false, //
actionOptions: [{
text: '删除',
style: {
backgroundColor: '#dd524d',
borderRadius: '10rpx'
}
}],
time: "",
bgimage: "",
index: "",
info: {
date: "",
list: [],
nutrients_four: [],
}
}
},
components: {
qiunDataCharts
},
computed: {
...mapState(["user", "countFoodInfo", "configInfo"]),
foodItem() {
return this.configInfo.meal_list
},
},
onLoad(options) {
let that = this
that.index = options.index
that.bgimage = that.foodItem[options.index].icon_bg
that.handleInfo()
},
watch: {
user() {
this.handleInfo()
},
countFoodInfo() {
this.handleInfo()
}
},
methods: {
handleInfo() {
let that = this
that.time = that.countFoodInfo.date
that.info = that.countFoodInfo.list[that.index]
let chart_data = []
that.opts.color = []
for (let i = 1; i < that.info.nutrients_four.length; ++i) {
this.opts.color.push(that.info.nutrients_four[i].color)
chart_data.push({
name: that.info.nutrients_four[i].name,
value: Number(that.info.nutrients_four[i].proportion),
})
}
this.opts.title.name = that.info.val
this.chartData = JSON.parse(JSON.stringify({
series: [{
data: chart_data
}]
}));
},
//
showFoodDetail(item) {
console.log("item", item)
let that = this
let chart_data = []
this.activeFoodDetail = item
this.drawerVisible = true;
this.opts2.color = []
for (let i = 1; i < item.nutrients_four.length; ++i) {
this.opts2.color.push(item.nutrients_four[i].color)
chart_data.push({
name: item.nutrients_four[i].name,
value: Number(item.nutrients_four[i].proportion),
})
}
this.opts2.title.name = that.activeFoodDetail.val
this.chartData2 = JSON.parse(JSON.stringify({
series: [{
data: chart_data
}]
}));
that.$nextTick(() => {
//
setTimeout(() => {
that.chartVisible = true
}, 100)
})
},
handleDrawerClose() {
//
this.chartVisible = false
this.drawerVisible = false;
},
//
handleAddFood() {
uni.navigateTo({
url: "/Food/count/search?name=" + this.info.name + "&time=" + this.time
})
},
//
delAcitionItem(item) {
let that = this
uni.showModal({
content: `是否删除[${item.name}]`,
success: (res) => {
if (res.confirm) {
this.$model.delCEatAction({
aud_id: that.user.aud_id,
eat_log_id: item.id
}).then(res => {
//
that.$store.dispatch("getCountFoodInfo", {
aud_id: that.user.aud_id,
time: that.time
})
})
}
}
})
}
}
}
</script>
<style scoped lang="scss">
.content {
min-height: 100vh;
background: #f7f7f7;
padding: 0 20rpx;
}
.content_box {
width: 100%;
}
.quan {
margin-right: 70rpx;
}
.top {
width: 100%;
height: 280rpx;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
background: #dfdfdf;
border-radius: 10px;
margin-top: 15px;
.name {
width: 100%;
text-align: center;
font-size: 25px;
font-weight: bold;
}
view {
z-index: 11;
}
image {
position: absolute;
width: 100%;
height: 100%;
z-index: 9;
border-radius: 10px;
}
}
.everyDay {
background: #fff;
padding: 10px;
border-radius: 10px;
margin: 15px 0 0;
height: auto;
overflow: hidden;
.progress {
display: flex;
align-items: center;
.chart-wrap {
position: relative;
width: 280rpx;
height: 280rpx;
margin-top: -30rpx;
margin-left: -15px;
}
.info {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 24rpx;
width: calc(100% - 270rpx);
height: 200rpx;
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
view {
display: flex;
align-items: center;
}
text {
color: #ccc;
margin: 0px 5px;
}
.color {
width: 10rpx;
height: 10rpx;
margin-right: 10rpx;
border-radius: 3rpx;
}
}
}
}
}
.foodtools {
margin-top: 15px;
.type {
background: #fff;
margin-bottom: 10px;
border-radius: 10px;
padding: 10px 10px 0;
.title {
display: flex;
align-items: center;
justify-content: space-between;
image {
width: 30px;
height: 30px;
}
.text {
width: 50%;
display: flex;
align-items: center;
view {
font-size: 32rpx;
font-weight: bold;
margin: 0 10px;
}
}
.detail {
color: #999;
display: flex;
font-size: 24rpx;
align-items: center;
}
}
.list {
width: 100%;
margin-top: 15px;
.item {
display: flex;
padding: 10px 0;
border-bottom: 1px solid #f7f7f7;
image {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
border: 1px solid #f7f7f7;
}
.weight {
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 10px;
text {
margin: 0 10px;
color: #dfdfdf;
}
}
}
}
}
}
.nolist {
image {
width: 120rpx;
height: 120rpx;
}
view {
margin-bottom: 15px;
text-align: center;
width: 100%;
margin-top: 10px;
color: #999;
}
}
.add {
width: 100%;
text-align: center;
font-weight: bold;
margin: 30rpx 0;
}
.no-scroll {
height: 80vh !important;
overflow: hidden !important;
}
</style>

1503
Food/count/search.vue Normal file

File diff suppressed because it is too large Load Diff

285
Food/count/setting.vue Normal file
View File

@ -0,0 +1,285 @@
<template>
<view class="content">
<!-- -->
<view class="kcal">
<view class="set">
<input type="digit" v-model="weight" placeholder="请输入" :focus="focus" @blur="handleBlur">
<uni-icons v-if="weight!=''" type="close" size="24" class="uni-iocns" color="#999"
@click="handleclear"></uni-icons>
<view class="num">
{{kcal.suggestion_kcal_unit}}
</view>
</view>
<view class="desc">
{{kcal.suggestion_kcal_range_val}}
</view>
</view>
<!-- -->
<view class="kcal">
<view class="text">
营养占比
</view>
<view class="slider">
<llt-slider-range :model-value="rangeValue" @change="handleChange" />
</view>
<view class="list">
<view class="item" v-for="(ite,ind) in nutrition.list">
<icon class="iconfont" :class="ite.icon"></icon>
<text>{{ite.name}}</text>
<text>{{ite.proportion}}%</text>
<view class="val">{{ite.val}}{{ite.unit}}</view>
</view>
</view>
</view>
<view class="num">
<view class="item" v-for="(ite,ind) in nutrition.describe">
<text>{{ite}}</text>
</view>
</view>
<view class="subbtn" @click="handleEditKcal">保存</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import lltSliderRange from '@/uni_modules/llt-slider-range/components/llt-slider-range/llt-slider-range.vue';
export default {
data() {
return {
weight: "",
kcal: {},
nutrition: {},
focus: false,
carbohydrate_v: 0,
protein_v: 0,
fat_v: 0,
carbohydrate_p: 0,
protein_p: 0,
fat_p: 0,
rangeValue: [0, 0]
}
},
computed: {
...mapState(["user"]),
info() {
return this.user
}
},
onLoad() {
this.handleList()
},
components: {
lltSliderRange
},
methods: {
handleList() {
let that = this
that.$model.getCountSetKcal({
aud_id: that.user.aud_id
}).then(res => {
if (res.code == 0) {
that.kcal = res.data.kcal
that.nutrition = res.data.nutrition
that.weight = res.data.kcal.suggestion_kcal_val
that.rangeValue[0] = Number(that.nutrition.list[0].proportion)
that.rangeValue[1] = Number(that.nutrition.list[0].proportion) + Number(that.nutrition
.list[1].proportion)
console.log("that.rangeValue", that.rangeValue)
that.handleProportion()
}
})
},
handleChange(val) {
let that = this
that.rangeValue = val
that.weight = that.weight ? that.weight : Number(that.kcal.suggestion_kcal_val)
that.handleProportion()
},
handleProportion() {
let that = this
that.nutrition.list.forEach(ite => {
if (ite.key_v == "carbohydrate") {
ite.proportion = that.rangeValue[0]
ite.val = Number(that.weight * ite.proportion / 100 / 4).toFixed(2)
that.carbohydrate_v = ite.val
that.carbohydrate_p = ite.proportion
}
if (ite.key_v == "protein") {
ite.proportion = that.rangeValue[1] - that.rangeValue[0]
ite.val = Number(that.weight * ite.proportion / 100 / 4).toFixed(2)
that.protein_v = ite.val
that.protein_p = ite.proportion
}
if (ite.key_v == "fat") {
ite.proportion = 100 - that.rangeValue[1]
ite.val = Number(that.weight * ite.proportion / 100 / 9).toFixed(2)
that.fat_v = ite.val
that.fat_p = ite.proportion
}
})
},
handleBlur() {
let that = this
that.weight = that.weight ? that.weight : Number(that.kcal.suggestion_kcal_val)
that.handleProportion()
},
handleEditKcal() {
let that = this
if (that.weight == '' || Number(that.weight) <= 0) {
that.$tools.msg("请输入卡路里")
return
}
that.$model.getCountSetUserKcal({
aud_id: that.user.aud_id,
set_kcal: that.weight,
carbohydrate_v: that.carbohydrate_v,
protein_v: that.protein_v,
fat_v: that.fat_v,
carbohydrate_p: that.carbohydrate_p,
protein_p: that.protein_p,
fat_p: that.fat_p,
}).then(res => {
if (res.code == 0) {
that.$tools.msg("设置成功")
that.$store.dispatch('getUserInfo', {
aud_id: that.user.aud_id
})
setTimeout(function() {
uni.navigateBack()
}, 1000)
}
})
},
handleclear() {
this.focus = true
this.weight = ""
}
}
}
</script>
<style scoped lang="scss">
.content {
padding: 30rpx;
background: #f7f7f7;
}
.kcal {
width: calc(100% - 40rpx);
background: #fff;
margin-bottom: 30rpx;
padding: 20rpx;
border-radius: 10px;
.set {
width: calc(100% - 40rpx);
background: #f7f7f7;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
position: relative;
height: 45px;
input {
width: 100%;
font-size: 16px;
}
.num {
position: absolute;
right: 10px;
}
.uni-iocns {
position: absolute;
right: 50px;
z-index: 99;
}
}
.desc {
font-size: 24rpx;
color: #999;
line-height: 40rpx;
margin-top: 10px;
}
}
.slider {
background: #f7f7f7;
margin-top: 15px;
border-radius: 10px;
padding: -10px 0;
}
.list {
display: flex;
justify-content: space-between;
margin-top: 15px;
.item {
display: flex;
flex-wrap: wrap;
width: 30%;
background-color: #f7f7f7;
justify-content: center;
border-radius: 10px;
icon {
font-size: 40px;
color: $uni-color-warning;
border-radius: 30%;
padding: 5px;
}
text {
font-size: 14px;
font-weight: 500;
color: #666;
display: inline-block;
width: 100%;
text-align: center;
line-height: 20px;
border-bottom: none;
}
.val {
width: 100%;
background: #f7f7f7;
border-radius: 10px;
margin: auto;
text-align: center;
font-weight: bold;
padding: 2px 0;
}
}
}
.num {
display: flex;
font-size: 24rpx;
flex-direction: column;
.item {
width: 100%;
line-height: 24px;
}
}
.subbtn {
color: #fff;
width: 100%;
text-align: center;
border-radius: 20rpx;
height: 35px;
line-height: 35px;
margin: 15px 0;
background-color: #f0ae43;
}
</style>

720
Food/me/menudetail.vue Normal file
View File

@ -0,0 +1,720 @@
<template>
<view class="content addFood" :class="[isBle?'maxheight':'']">
<!-- 封面 -->
<view class="topimg">
<image :src="info.cover_pic_url" mode="aspectFill"></image>
</view>
<!-- 信息 -->
<view class="title">
<view class="table">{{info.title}}</view>
<view class="user">
<view class="left">
<image :src="info.create_user_head_pic"></image>
<text>{{info.create_user_nickname}}</text>
</view>
<view class="right">
<uni-icons :type="info.collect_status=='yes'?'heart-filled':'heart'" size="20"
:color="info.collect_status=='yes'?'red':'#999'"></uni-icons>
<text class="ml-5">{{info.likes_num}}</text>
</view>
</view>
</view>
<view class="title title2">
<view class="">菜谱类型</view>
<view>{{menu[cookIndex].name}}</view>
</view>
<!-- 食材 -->
<view class="food">
<view class="desc">
{{info.description}}
</view>
<view class="h4">
<view class="tags">
<view class="tags-item" :class="[index ==ind?'active':'']" v-for="(item,ind) in info.tags"
:key="ind" @click="handleToggle(ind)">{{item.title}}</view>
</view>
<view class="close" @click="handleWeight">
<image src="../../static/lianjie.png"></image>
连接测量
</view>
</view>
<view class="foodlist">
<view class="item" v-for="(ite,ind) in info.tags[index].list" :key="ind"
v-if="info.tags&&info.tags[index].list.length">
<view class="name" style="flex: 1;">{{ite.name}}</view>
<view class="weight">
{{ite.weight}}{{ite.unit}}
</view>
</view>
</view>
</view>
<!-- 步骤 -->
<view class="step">
<view class="stepList" v-for="(ite,ind) in info.step_list" :key="ind"
v-if="info.step_list&&info.step_list.length">
<view class="top">
<text>{{ite.step_num}}</text>
</view>
<view class="right">
<view class="desc">
{{ite.description}}
</view>
<view class="image" v-for="(it,id) in ite.pic_url_list">
<image :src="it" mode="aspectFill" class="mt-10"></image>
</view>
</view>
</view>
</view>
<!-- 底部操作 -->
<view class="foot">
<view class="item" @click="handleCang()">
<uni-icons :type="info.collect_status=='yes'?'heart-filled':'heart'" size="20"
:color="info.collect_status=='yes'?'red':'#999'"></uni-icons>
<text>收藏</text>
</view>
<!-- <view class="item" v-if="type=='我的菜谱'" @click="handleEdit()">
<icon class="iconfont icon-bianji"></icon>
<text>编辑</text>
</view>
<view class="item" v-if="type=='我的菜谱'" @click="handledel()">
<icon class="iconfont icon-ashbin"></icon>
<text>删除</text>
</view> -->
</view>
<!--蓝牙连接区 -->
<view class="wrapper" v-if="isBle">
<view class="bg" @click="isBle = false">
<view class="box weightBox">
<icon class="iconfont icon-error" @click='isBle = false'></icon>
<view class="foodlist" @click.stop>
<view class="text">
<text style="width: 30%;">食材</text>
<text style="width: 18%;">建议</text>
<view class="kcal">
<text>重量</text>
<text>热量</text>
<text>重秤</text>
</view>
</view>
<view class="item" v-for="(ite,ind) in info.tags[0].list" :key="ind"
v-if="info.tags&&info.tags[0].list.length"
:class="[activeType.id&&activeType.id ==ite.id&&!ite.newweight?'active2':'']">
<view class="name">{{ite.name}}</view>
<view class="num" style="width: 18%;">
{{ite.weight}}{{ite.unit}}
</view>
<view class="kcal" v-if="activeType.id&&activeType.id ==ite.id&&!ite.newweight">
正在测量...
</view>
<view class="kcal" v-else>
<view class="num" v-if="ite.newweight">
{{ite.newweight}}{{ite.newunit}}
</view>
<view class="num" v-if="ite.newkcal">
{{ite.newkcal}}
</view>
<view class="refreshempty
" @click="handlechongzhi(ite,ind)" v-if="ite.newweight">
<uni-icons type="refreshempty" size="22"></uni-icons>
</view>
</view>
</view>
</view>
<view class="blue-tooth" :style="{display: (!isWeightType&&iSWeightSub) ? '' : 'none'}" @click.stop>
<blue-tooth ref="blueTooth" @handleDetailNext="handleDetailNext"
@handleDetailSub="handleDetailSub" :weightKcal="weightKcal" :name="activeType.name"
:isLast="isLast" />
</view>
</view>
</view>
</view>
<view class="saveFood" v-if="showSaveFood">
<view class="saveFoodInner">
<view class="title">是否保存至计食为今日饮食</view>
<view class="types">
<view class="type-item" :class="{'active':selectSaveType == index}" v-for="(item,index) in foodItem"
:key="index" @click="selectSaveType=index">{{item.name}}</view>
</view>
<view class="btn-wrap">
<view class="confirm" @click="confirmSaveFood">确定</view>
<view class="cancel" @click="showSaveFood=false">取消</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import blueTooth from "@/components/foodIndex/bluetooth.vue"
export default {
data() {
return {
type: "",
info: {},
id: null,
index: 0,
isLast: false,
weightKcal: null,
weightType: 0,
activeType: {},
isBle: false,
isWeightType: true,
iSWeightSub: true,
listInd: 0,
cookIndex: null,
showSaveFood: false,
selectSaveType: 0
}
},
computed: {
...mapState(["user", "configInfo", "bleValue"]),
menu() {
return this.configInfo.cookbook_label
},
foodItem() {
return this.configInfo.meal_list
},
endDate() {
return this.$tools.getDate("start")
},
},
components: {
blueTooth
},
onLoad(options) {
let that = this
let info = {}
if (options && options.info) {
info = JSON.parse(options.info)
that.type = info.pageName
that.info = info
} else {
that.type = options.title
}
that.id = options.id
that.handleHomeInfo(options.id)
},
methods: {
handleToggle(ind) {
this.index = ind
},
handleHomeInfo(id) {
let that = this
that.$model.getCookListDetails({
cookbook_id: id,
aud_id: that.user.aud_id
}).then(res => {
if (res.code != 0) return
that.info = res.data
console.log("11111111",that.menu,res.data.cook_label)
that.cookIndex = that.menu.findIndex(ite => ite.id == res.data.cook_label)
if (that.bleValue.serviceId != '') {
that.handleWeight()
}
})
},
//
handleWeight() {
let that = this
if (that.isBle) {
return
}
that.listInd = 0
that.isBle = true
that.isLast = false
that.iSWeightSub = true
that.isWeightType = false
that.activeType = that.info.tags[0].list[0]
that.weightKcal = Number(Number(that.activeType.kcal) / 100).toFixed(2)
if (that.info.tags[0].list.length == 1) {
that.isLast = true
}
},
//
handleDetailNext(weight, dw, kcal) {
let that = this
let ind = that.info.tags[0].list.findIndex(ite => ite.id == that.activeType.id)
that.info.tags[0].list[ind].newweight = weight
that.info.tags[0].list[ind].newunit = dw
that.info.tags[0].list[ind].newkcal = kcal
that.listInd = that.listInd + 1
that.activeType = that.info.tags[0].list[ind + 1]
that.weightKcal = Number(Number(that.activeType.kcal) / 100).toFixed(2)
that.info.tags[0].list[that.listInd].newweight = ""
that.info.tags[0].list[that.listInd].newunit = ""
that.info.tags[0].list[that.listInd].newkcal = ""
if (that.listInd == that.info.tags[0].list.length - 1 || that.listInd == that.info.tags[0].list.length) {
that.isLast = true
console.log('已经测量完成')
}
},
//
handleDetailSub(weight, dw, kcal) {
let that = this
let ind = that.info.tags[0].list.findIndex(ite => ite.id == that.activeType.id)
that.info.tags[0].list[ind].newweight = weight
that.info.tags[0].list[ind].newunit = dw
that.info.tags[0].list[ind].newkcal = kcal
that.showSaveFood = true
},
//
handlechongzhi(ite, ind) {
let that = this
let weight = that.info.tags[0].list[ind].newweight
that.listInd = ind
that.isLast = false
that.activeType = ite
that.weightKcal = Number(Number(ite.kcal) / 100).toFixed(2)
that.info.tags[0].list[ind].newweight = ""
that.info.tags[0].list[ind].newunit = ""
that.info.tags[0].list[ind].newkcal = ""
if (that.listInd == that.info.tags[0].list.length - 1 || that.listInd == that.info.tags[0].list.length) {
that.isLast = true
}
},
//
confirmSaveFood() {
let that = this
let newFoodList = []
for (let i = 0; i < that.info.tags[0].list.length; ++i) {
if (that.info.tags[0].list[i].newweight) {
newFoodList.push({
meals_type: that.foodItem[that.selectSaveType].name,
id: that.info.tags[0].list[i].id,
weight: that.info.tags[0].list[i].newweight,
unit: that.info.tags[0].list[i].newunit
})
}
}
console.log("newFoodList", that.info.tags[0].list, newFoodList)
if (newFoodList.length > 0) {
that.$model.getAddIntakeFood({
aud_id: that.user.aud_id,
food_list: newFoodList,
time: that.$tools.getDate("start")
}).then(res => {
if (res.code != 0) return
that.$store.dispatch("getUserInfoFood")
uni.showToast({
title: '保存成功',
icon: 'success'
})
// uni.switchTab({
// url: "/pages/count/count"
// })
})
}
that.showSaveFood = false
that.iSWeightSub = false
that.isBle = false
},
handleCang() {
let that = this
that.$model.getCookLike({
cookbook_id: that.id,
aud_id: that.user.aud_id
}).then(res => {
if (res.code != 0) return
that.info.likes_num = res.data.likes_num
that.info.collect_status = res.data.collect_status
})
},
handleEdit() {
uni.navigateTo({
url: "/Food/me/menuEdit?info=" + JSON.stringify(this.info)
})
},
handledel() {
let that = this
uni.showModal({
title: '友情提示',
content: '是否删除当前菜谱?',
success: function(res) {
if (res.confirm) {
that.$model.getMyCookbookDel({
aud_id: that.user.aud_id,
cookbook_id: that.id,
}).then((res) => {
if (res.code != 0) {
that.$tools.msg(res.message)
return
}
that.$tools.msg("删除成功")
uni.navigateBack()
})
} else if (res.cancel) {
that.$tools.msg("您已取消操作!");
}
},
})
}
}
}
</script>
<style lang="scss" scoped>
.content {
padding: 0 30rpx;
}
.maxheight {
max-height: 90vh !important;
overflow: hidden;
}
.topimg {
width: 100%;
height: 340rpx;
background: #fff;
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
margin: 20rpx 0;
overflow: hidden;
position: relative;
.iconfont {
font-size: 60rpx;
color: $maincolor;
}
text {
display: inline-block;
width: 100%;
text-align: center;
color: #999;
}
.text {
font-size: 16px;
color: #666;
margin-bottom: 3px;
}
image {
width: 100%;
height: inherit;
}
}
.step {
.image {
height: 340rpx;
margin: auto;
background: #f7f7f7;
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
overflow: hidden;
image {
width: 100%;
height: inherit;
display: inline-table;
}
icon {
font-size: 60rpx;
color: #ff4c4f;
margin-bottom: 5px;
}
}
}
.title {
padding: 20rpx;
.table {
font-size: 16px;
font-weight: bold;
}
.user {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 30rpx;
.left {
display: flex;
align-items: center;
image {
width: 50rpx;
height: 50rpx;
margin-right: 5px;
border-radius: 50%;
}
}
.right {
display: flex;
}
}
}
.desc {
width: 100%;
line-height: 50rpx;
margin-bottom: 20rpx;
}
.h4 {
margin: 20rpx 0;
padding-top: 20rpx;
border-top: 1px solid #f7f7f7;
.tags {
flex: 1;
display: flex;
justify-content: space-around;
.tags-item {
border-bottom: 2px solid transparent;
}
.active {
border-bottom: 2px solid #ff4c4f;
}
}
.close {
color: #fff;
width: 100px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20rpx;
background-color: $maincolor;
image {
width: 50rpx;
height: 50rpx;
}
}
}
.step {
margin-bottom: 85px;
}
.foodlist {
border-radius: 20rpx;
background: #fff;
.item {
margin-top: 0 !important;
border-radius: 0px !important;
border-bottom: 1px solid #f7f7f7;
}
.name {
border-right: none !important;
}
}
.foot {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
display: flex;
justify-content: space-between;
padding: 5px 0px 40rpx;
border-radius: 20rpx 20rpx 0 0;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
.item {
width: 25%;
display: flex;
flex-wrap: wrap;
justify-content: center;
icon,
image {
width: 22px;
height: 22px;
font-size: 22px;
}
text {
display: inline-block;
text-align: center;
width: 100%;
}
}
}
//
.weightBox {
top: 40px;
background: #dfdfdf;
.icon-error {
position: absolute;
right: 20rpx;
top: -40rpx;
background: #fff;
font-size: 80rpx;
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.foodlist {
border-radius: 0;
height: 40%;
overflow: scroll;
padding: 10px;
border-radius: 10px;
margin-top: 30rpx;
font-size: 14px;
.text {
width: 100%;
font-weight: bold;
display: flex;
height: 80rpx;
line-height: 80rpx;
justify-content: space-between;
}
.item {
display: flex;
align-items: center;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
justify-content: space-between;
}
.name {
width: calc(30% - 10px);
text-align: left !important;
white-space: nowrap;
overflow-x: auto;
font-weight: inherit;
font-size: 28rpx;
margin-right: 10px;
}
.kcal {
width: 52% !important;
display: flex;
justify-content: space-between;
}
}
.blue-tooth {
background: #fff;
border-radius: 10px;
margin-top: 15px;
position: relative;
height: 50%;
}
.groupbtn {
position: absolute;
left: 30rpx;
right: 30rpx;
width: auto;
bottom: 100rpx;
}
}
.saveFood {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 99999;
.saveFoodInner {
display: flex;
flex-direction: column;
align-items: center;
width: 80%;
padding: 30rpx 20rpx;
padding-bottom: 0;
background-color: #fff;
border-radius: 20rpx;
.title {
font-size: 36rpx;
text-align: center;
}
.types {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 30rpx;
width: 100%;
.type-item {
width: 22%;
height: 50rpx;
line-height: 50rpx;
text-align: center;
font-size: 28rpx;
border: 1px solid #f1f1f1;
border-radius: 10rpx;
margin-bottom: 30rpx;
&.active {
border-color: #ff4c4f;
}
}
}
.btn-wrap {
display: flex;
width: 100%;
margin-top: 30rpx;
border-top: 1px solid #f1f1f1;
view {
width: 50%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-size: 32rpx;
}
view:first-child {
border-right: 1px solid #f1f1f1;
}
}
}
}
.active2 {
color: #8284f0;
font-weight: bold;
background: #ecedff;
}
.title2 {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>

147
Food/me/mymenu.vue Normal file
View File

@ -0,0 +1,147 @@
<template>
<view class="content">
<!-- 搜索 -->
<search @handleSearch="handleSearch"></search>
<!-- 食谱 -->
<view class="footlist footbox" v-if="menuList.length">
<view class="list" v-for="(it,id) in menuList" :key="it" @click="handleDetail(it.id)">
<view class="topimg">
<image :src="it.cover_url" class="img" mode="aspectFill"></image>
</view>
<view class="item">
<view class="title">{{it.title}}</view>
<view class="name">
<image :src="it.create_user_head_pic"></image>
<text class="overflow">{{it.create_user_nickname}}</text>
</view>
<view class="zan" @click="handleZan(it)">
<image class="mr-5 xin" :src="it.is_me_like_it=='yes'?'/static/xin2.png':'/static/xin.png'">
</image>
<text>{{it.likes_num}}</text>
</view>
</view>
</view>
</view>
<view class="endtext" v-if="(!lastPage || page >= lastPage)&&menuList.length"> 到底了看看别的吧 </view>
<view v-if="!menuList.length" class="nolist">
<icon class="iconfont icon-wancan"></icon>
<text>还没有记录哦</text>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import search from "@/components/foodIndex/search.vue"
export default {
data() {
return {
type: "",
name: "",
page: 1,
menuList: [],
lastPage: '',
}
},
components: {
search
},
computed: {
...mapState(["user"]),
info() {
return this.user
}
},
onLoad(option) {
let that = this
that.type = option.pageName
uni.setNavigationBarTitle({
title: option.pageName
});
},
onShow() {
let that = this
that.name = ""
that.page = 1
that.menuList = []
that.handleCooklist()
},
onReachBottom() {
let that = this
if (!this.lastPage || this.page >= this.lastPage) {
uni.showToast({
title: '没有更多数据!',
icon: 'none'
})
return
}
this.page++
this.handleCooklist(this.page)
},
methods: {
handleCooklist() {
let that = this
that.$model.getUserCollectList({
page: that.page,
aud_id: that.user.aud_id,
search_data: that.name
}).then(res => {
if (res.code != 0) return
that.menuList = res.data.content_list
that.lastPage = res.data.page_total
})
},
//
handleDetail(id) {
uni.navigateTo({
url: "/Food/me/menudetail?id=" + id + '&title=' + this.type
})
},
//
handleSearch(ite) {
let that = this
that.page = 1
that.name = ite
that.menuList = []
that.lastPage = ""
that.handleCooklist()
}
}
}
</script>
<style lang="scss" scoped>
.content {
background: #fff;
}
.xin {
width: 22px;
height: 22px;
}
/deep/.serachBox {
padding-top: 20rpx !important;
}
.footlist {
position: relative;
margin-top: 68px;
padding: 30rpx;
width: calc(100% - 60rpx);
background: #fff;
border-radius: 30rpx 30rpx 0 0;
.item {
background: #f7f7f7 !important;
border-top: 1px solid #f7f7f7;
}
}
.nolist {
margin-top: 50%;
}
</style>

156
Food/me/record.vue Normal file
View File

@ -0,0 +1,156 @@
<template>
<view class="content">
<!-- 列表 -->
<view class="box">
<view class="list" v-for="(item,ind) in infoList" :key="ind" @click="handleDetail(item)">
<view class="time">{{item.time}}</view>
<view class="kcal">
<view>摄入卡路里<text>{{item.val}}</text>{{item.unit}}</view>
<view class="status">
<text class="quan0" :style="'background:'+item.color"></text>
{{item.describe}}
</view>
<uni-icons type="forward" size="20" color="#666"></uni-icons>
</view>
</view>
<view class="endtext" v-if="!lastPage || page >= lastPage"> 到底了看看别的吧 </view>
<view v-if="!infoList.length" class="nolist">
<icon class="iconfont icon-wancan"></icon>
<text>还没有记录哦</text>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
page: 1,
lastPage: '',
infoList: []
}
},
computed: {
...mapState(["user"]),
end() {
return this.$tools.getTime()
},
endDate() {
return this.$tools.getDate("start")
},
},
onLoad() {
let that = this
that.page = 1
that.handleList()
},
onReachBottom() {
let that = this
if (!this.lastPage || this.page >= this.lastPage) {
uni.showToast({
title: '没有更多数据!',
icon: 'none'
})
return
}
this.page++
this.handleList(this.page)
},
methods: {
handleList() {
let that = this
that.$model.getMyLogList({
aud_id: that.user.aud_id,
page: that.page,
}).then(res => {
if (res.code == 0) {
that.infoList = that.infoList.concat(res.data.content_list)
that.lastPage = res.data.page_total
}
})
},
handleDetail(item) {
let that = this
that.$store.dispatch("getCountFoodInfo", {
aud_id: that.user.aud_id,
time: item.time
})
uni.navigateTo({
url: '/Food/me/recordetail'
});
}
}
}
</script>
<style scoped="scoped" lang="scss">
.content {
padding: 0 30rpx;
}
.calendar {
text-align: center;
background: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20;
height: 80rpx;
line-height: 80rpx;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
}
.box {
margin-top: 30rpx;
width: 100%;
}
.list {
color: #999;
width: calc(100% - 60rpx);
background: #fff;
margin-bottom: 30rpx;
border-radius: 30rpx;
padding: 20rpx 30rpx;
.time {
width: 100%;
height: 60rpx;
line-height: 60rpx;
}
.kcal {
display: flex;
justify-content: space-between;
align-items: center;
text {
font-size: 16px;
font-weight: bold;
margin: 0 3px;
color: #000;
}
.status {
width: 85px;
}
.quan0 {
width: 24rpx;
height: 24rpx;
background: $uni-color-warning;
display: inline-block;
border-radius: 50%;
margin-right: 5px;
}
}
}
</style>

35
Food/me/recordetail.vue Normal file
View File

@ -0,0 +1,35 @@
<template>
<view>
<food></food>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import food from "@/components/foodIndex/foodHome.vue"
export default {
data() {
return {}
},
components: {
food,
},
computed: {
...mapState(["user", "countFoodInfo"]),
foodInfo() {
return this.countFoodInfo
}
},
onLoad(options) {
let that = this
},
methods: {
}
}
</script>
<style scoped lang="scss">
</style>

131
Food/search/list.vue Normal file
View File

@ -0,0 +1,131 @@
<template>
<view class="content">
<search :name="search_value"></search>
<view class="footbox footlist" v-if="food_search_list.length">
<view class="list" v-for="(it,ind) in food_search_list" :key="ind" @click="handleDetail(it.id)">
<view class="topimg">
<image :src="it.cover" class="img" mode="aspectFill"></image>
</view>
<view class="item">
<view class="title">{{it.title}}</view>
<view class="name">
<image :src="it.create_user_head_pic"></image>
<text class="overflow">{{it.create_user_nickname}}</text>
</view>
<view class="zan">
<uni-icons :type="it.is_me_like_it=='yes'?'heart-filled':'heart'" size="20" :color="it.is_me_like_it=='yes'?'red':'#999'"></uni-icons>
<text>{{it.likes_num}}</text>
</view>
</view>
</view>
</view>
<view class="endtext" v-if="!lastPage || Page >= lastPage"> 到底了看看别的吧 </view>
<view v-if="!food_search_list.length" class="nolist">
<icon class="iconfont icon-wancan"></icon>
<text>还没有记录哦</text>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import search from '@/components/foodIndex/search2.vue';
export default {
name: "list",
data() {
return {
Page: 1,
lastPage: 1,
search_value: "",
food_search_list: [],
};
},
props: {
title: {
type: String,
default: ''
},
},
components: {
search
},
computed: {
...mapState(["user"]),
},
onLoad(options) {
let that = this
that.search_value = options.name
that.handleSearchColumn()
},
onReachBottom() {
let that = this
console.log("onReachBottom", this.lastPage)
if (!this.lastPage || this.Page >= this.lastPage) {
uni.showToast({
title: '没有更多数据!',
icon: 'none'
})
return
}
this.Page++
this.handleSearchColumn()
},
methods: {
handleSearchColumn() {
let that = this
that.$model.getMenuSearchColumn({
page: that.Page,
aud_id: that.user.aud_id,
search_data: that.search_value
}).then(res => {
if (res.code != 0 || res.data instanceof Array) return
that.food_search_list = that.food_search_list.concat(res.data.content_list)
that.lastPage = res.data.page_total
})
},
//
handleDetail(id) {
let that = this
if (that.user.aud_id == '') {
that.$tools.msg("完善资料后查看更多")
return
}
uni.navigateTo({
url: "/Food/me/menudetail?id=" + id
})
},
}
}
</script>
<style scoped lang="scss">
.content {
background: #fff;
min-height: calc(100vh - 40rpx);
}
.footlist {
position: relative;
margin-top: 68px;
padding: 30rpx;
width: calc(100% - 60rpx);
background: #fff;
border-radius: 30rpx 30rpx 0 0;
}
.list {
border: 1px solid #f7f7f7;
.item {
background: #f7f7f7;
border-top: 1px solid #f7f7f7;
}
}
.topimg {
width: 100%;
}
</style>

392
Food/search/search.vue Normal file
View File

@ -0,0 +1,392 @@
<template>
<view class="content">
<view class="search">
<input type="text" v-model="search_value" placeholder="输入关键字匹配食谱" />
<icon v-if="search_value" class="iconfont icon-error" @click="handlecolse"></icon>
<image src="/static/28.png" @click="handleSearchHistory(search_value)"></image>
</view>
<view class="content-box">
<!-- 历史搜索 -->
<view v-if="history_food.length" class="search-history">
<view class="title">
<view class="quan mr-5"></view>历史搜索
</view>
<view class="button-container" @click="showAll =! showAll" v-if="history_food.length>8">
<image :src="showAll?'/static/arrow-up.png':'/static/arrow-down.png'"></image>
</view>
<view class="history-list">
<view class="history-list-item"
v-for="(item,index) in showAll?history_food:history_food.slice(0, 8)"
@click="handleSearchHistory(item.keyword)">
{{item.keyword}}
</view>
</view>
</view>
<!-- 猜你想搜 -->
<view class="popular-container">
<view class="title">
<view class="quan mr-5"></view>猜你想搜
</view>
<view class="popular-food-item" v-for="(ite,index) in popular_food" :key="index">
<view class="food-title">{{ite.title}}</view>
<view class="popular-food-inner">
<text class="popular-food-subitem" v-for="(sub_item,sub_index) in ite.list"
@click="handleSearchHistory(sub_item.name)" :key="sub_index">{{sub_item.name}}
</text>
</view>
</view>
</view>
</view>
<!-- 语音 -->
<view class="footBtn">
<view class="mic-icon" @touchstart="onVoiceTouchStart" @touchend="onVoiceTouchEnd"
@touchcancel="cancelRecording">
<uni-icons type="mic-filled" size="20" color="#fff"></uni-icons>
语音搜索
</view>
</view>
<!-- 语音弹框 -->
<view class="wrapper" v-if="showAutoSearchDlg">
<view class="auto-search-dialog">
<view class="auto-search-inner">
<view class="voice-wave">
<view class="wave-bar"></view>
<view class="wave-bar"></view>
<view class="wave-bar"></view>
<view class="wave-bar"></view>
<view class="wave-bar"></view>
<view class="wave-bar"></view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
const plugin = requirePlugin("WechatSI")
export default {
data() {
return {
index: 0,
Page: 1,
showAll: false,
search_value: '',
showAutoSearchDlg: false,
voiceManager: null,
popular_food: [],
history_food: []
};
},
computed: {
...mapState(["user"]),
// popular_food() {
// return this.configInfo.search_guess.cookbook
// },
// history_food() {
// return this.configInfo.search_history.cookbook
// }
},
onShow() {
this.handleList()
},
mounted() {
let that = this
that.voiceManager = plugin.getRecordRecognitionManager()
that.voiceManager.onStop = function(res) {
that.handleSearchHistory(res.result.replace('。', ''))
}
that.voiceManager.onError = function(res) {
console.error("error msg", res.retcode)
}
that.voiceManager.stop()
},
methods: {
//
handleList() {
let that = this
that.$model.getCountSearchmsg({
aud_id: that.user.aud_id
}).then(res => {
if (res.code == 0) {
that.history_food = res.data.search_history.cookbook
that.popular_food = res.data.search_guess.cookbook
}
})
},
// /
toggleShowAll() {
this.showAll = !this.showAll
},
onVoiceTouchStart() {
let that = this
that.showAutoSearchDlg = true
that.voiceManager.start({
duration: 60000,
lang: "zh_CN"
})
},
onVoiceTouchEnd() {
let that = this
that.showAutoSearchDlg = false
that.voiceManager.stop()
},
//
cancelRecording() {
// #ifdef MP-WEIXIN
if (this.voiceManager) {
this.voiceManager.stop()
this.showAutoSearchDlg = false
}
// #endif
},
handlecolse() {
console.log("取消搜索")
this.search_value = ""
this.search_list = []
},
//
handleSearchHistory(text) {
let that = this
if (text == "") {
that.$tools.msg("输入关键字后搜索")
return
}
uni.navigateTo({
url: "/Food/search/list?name=" + text
})
}
}
}
</script>
<style lang="scss" scoped>
.content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow-y: auto;
box-sizing: border-box;
background: #fff;
-webkit-overflow-scrolling: touch;
.search {
width: 100%;
position: relative;
padding-bottom: 35px;
padding-top: 20rpx;
background-color: $maincolor;
input {
width: calc(100% - 80rpx);
background: #fff;
height: 39px;
line-height: 38px;
border-radius: 20rpx;
padding: 0 20rpx;
margin: 0 20rpx;
}
.input:hover {
box-shadow: 0 1rpx 20rpx #ccc;
}
image {
width: 50rpx;
height: 50rpx;
position: absolute;
right: 40rpx;
top: 18px;
z-index: 99;
}
}
.content-box {
background: #fff;
border-radius: 20rpx 20rpx 0 0;
position: relative;
z-index: 99;
width: 100%;
padding-top: -13px;
margin: -40rpx 0 70px;
}
.search-history {
width: 100%;
height: auto;
overflow: hidden;
uni-icons {
color: #333333;
font-size: 60rpx;
position: absolute;
top: 13px;
right: 30rpx;
}
}
.history-list {
width: calc(100% - 40rpx);
margin: 20rpx 20rpx 0;
height: auto;
display: flex;
flex-wrap: wrap;
.history-list-item {
border: 1px solid #dfdfdf;
padding: 3px 24rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
margin-right: 20rpx;
}
}
.title {
width: 90%;
font-size: 30rpx;
font-weight: bold;
color: #000;
margin-top: 30rpx;
margin-left: 30rpx;
display: flex;
align-items: center;
}
.popular-container {
width: 100%;
.popular-food-item {
display: flex;
flex-direction: column;
align-items: center;
margin: 20rpx;
padding: 20rpx;
box-sizing: border-box;
border-radius: 20rpx;
background: linear-gradient(#EDFFF4, #ffffff 80%);
.food-title {
font-size: 34rpx;
font-weight: 700;
}
.popular-food-inner {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
width: 100%;
margin-top: 20rpx;
.popular-food-subitem {
display: flex;
align-items: center;
margin-bottom: 20rpx;
padding: 10rpx 20rpx;
background-color: #fff;
margin-right: 20rpx;
border-radius: 20rpx;
border: 1px solid #f7f7f7;
}
}
}
}
.search_list {
display: flex;
padding: 20rpx;
flex-wrap: wrap;
margin-bottom: 90px;
margin-top: 30rpx;
justify-content: space-between;
.search_list_item {
width: 30%;
margin-top: 30rpx;
text-align: center;
image {
width: 220rpx;
height: 220rpx;
}
}
}
.auto-search-dialog {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
left: 0;
top: 0;
width: 100%;
bottom: 60px;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
.auto-search-inner {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
width: 50%;
padding: 60rpx 0;
background-color: #fff;
border-radius: 20rpx;
box-shadow: 0 0 20rpx #ccc;
}
}
.footBtn {
position: fixed;
width: 100%;
bottom: 0;
padding-top: 30rpx;
background: #fff;
display: flex;
z-index: 99;
justify-content: space-around;
view {
color: #fff;
width: 80%;
padding: 8px 40rpx;
background: $maincolor;
margin-bottom: 30rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20rpx;
}
}
}
.button-container {
position: absolute;
top: 20rpx;
right: 30rpx;
font-size: 40rpx;
image {
width: 50rpx;
height: 50rpx;
}
}
.icon-error {
color: #888484;
position: absolute;
right: 120rpx;
top: 18px;
z-index: 999;
font-size: 24px;
}
</style>

242
body/curve/curve.vue Normal file
View File

@ -0,0 +1,242 @@
<template>
<view class="content">
<!-- tabbar -->
<!-- 曲线 -->
<view class="charts">
<view class="TrendPage">
<!-- 时间选择 -->
<view class="boxTime">
<picker mode="date" :end="endDate" @change="handStartTimeH" :fields="fields"
:value="startTime?startTime:startDate">
<view class="uni-input mr-10">{{startTime?startTime:startDate}}
<icon class="iconfont icon-arrow-down-bold"></icon>
</view>
</picker>
<view class="center">~</view>
<picker mode="date" :end="endDate" @change="handEndTimeH" :fields="fields"
:value="endTime?endTime:endDate">
<view class="uni-input mr-10">{{endTime?endTime:endDate}}
<icon class="iconfont icon-arrow-down-bold"></icon>
</view>
</picker>
</view>
<!-- 曲线图 -->
<view class="box" v-if="weightList.length">
<view class="listC">
<view :class="{active2:index==active1}" class="name" v-for="(item,index) in weightList"
:key="index" @click="showbox(index)">
{{item.title}}
</view>
</view>
<view class="blockC">
<view v-if="handTrue&&lineData.categories&&lineData.categories.length">
<qiunDataCharts type="area" :chartData="lineData" :canvas2d="true" canvasId="charts09"
:animation="false" :Width="340" :Height="250"
:opts="{enableScroll:true,xAxis:{scrollShow:false,itemCount:3}}" :ontouch="true" />
</view>
<view class="box" v-else>
<view class="nolist">
<image src="../../static/none.png"></image>
<text>暂无数据</text>
</view>
</view>
</view>
</view>
<view class="box" v-else>
<view class="nolist">
<image src="../../static/none.png"></image>
<text>暂无数据</text>
</view>
</view>
</view>
<!-- 目标-->
<view class="gridList">
<view class="data">
<view class="item" @click="handleClick(1)">
<view class="val">
{{target_weight}}<text>kg</text>
</view>
<view class="name">目标体重<uni-icons class="iconfont icon-bianji" color="#FEC407"></uni-icons>
</view>
</view>
<view class="item">
<view class="val">
{{Math.abs(calculate_val)}}<text>kg</text>
</view>
<view class="name" v-if="Number(calculate_val)>0">累计增重</view>
<view class="name" v-else>累计减重</view>
</view>
<view class="item" @click="handleClick(2)">
<view class="val">
{{initial_weight}}<text>kg</text>
</view>
<view class="name">初始体重<uni-icons class="iconfont icon-bianji" color="#FEC407"></uni-icons>
</view>
</view>
<view class="item">
<view class="val">
{{calculate_time}}<text></text>
</view>
<view class="name">减重天数</view>
</view>
</view>
</view>
<!-- 初始 -->
<firstweight @handleLabelList="handleLabelList"></firstweight>
<!-- 目标 -->
<targetWeight @handleLabelList="handleLabelList"></targetWeight>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import firstweight from '@/components/bodyIndex/firstweight.vue';
import targetWeight from '@/components/bodyIndex/targetWeight.vue'
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
export default {
data() {
return {
fields: "",
active1: 0,
startTime: "",
endTime: "",
lineData: {},
weightList: [],
handTrue: true,
target_weight: 0, //
initial_weight: 0, //
calculate_val: 0, //
calculate_time: 0, //
}
},
computed: {
...mapState(['user']),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
components: {
firstweight,
targetWeight,
qiunDataCharts,
},
onLoad() {
let that = this
that.handleLabelList(that.endDate, that.startDate)
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
//
onPullDownRefresh() {
let that = this
that.handleLabelList(that.endDate, that.startDate)
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000);
},
methods: {
handleLabelList(startTime, endDate) {
let that = this
that.$model.getTrendList({
aud_id: that.user.aud_id,
s_time: endDate,
e_time: startTime
}).then(res => {
console.log("趋势", res)
if (res.code == 0) {
that.weightList = res.data.curve_list
that.target_weight = res.data.target_weight
that.calculate_val = res.data.calculate_val
that.initial_weight = res.data.initial_weight
that.calculate_time = res.data.calculate_time
that.showbox(0)
}
})
},
showbox(index) {
let that = this
that.handTrue = false
that.$nextTick(function() {
that.handTrue = true
that.lineData = that.weightList.length ? that.weightList[index].line : {},
console.log("11111", that.lineData)
})
that.active1 = index
},
//
handStartTimeH(e) {
let that = this
if (that.endTime) {
if (Date.parse(e.detail.value) > Date.parse(that.endTime)) {
that.$tools.msg("请选择正确的时间")
return
}
} else {
if (Date.parse(e.detail.value) > Date.parse(that.endDate)) {
that.$tools.msg("请选择正确的时间")
return
}
}
that.startTime = e.detail.value
let endtime = that.endTime ? that.endTime : that.endDate
that.handleLabelList(endtime, that.startTime)
that.showbox(0)
},
//
handEndTimeH(e) {
let that = this
if (that.startTime) {
if (Date.parse(e.detail.value) < Date.parse(that.startTime)) {
that.$tools.msg("请选择正确的时间")
return
}
} else {
if (Date.parse(e.detail.value) < Date.parse(that.startDate)) {
that.$tools.msg("请选择正确的时间")
return
}
}
that.endTime = e.detail.value
let startTime = that.startTime ? that.startTime : that.startDate
that.handleLabelList(that.endTime, startTime)
that.showbox(0)
},
handleClick(ind) {
if (ind == 1) {
this.$store.commit("changeTarget", true);
} else {
this.$store.commit("changeFirst", true);
}
},
}
}
</script>
<style scoped lang="scss">
@import "@/scss/body.scss";
.content {
padding: 15px;
font-size: 32rpx;
width: calc(100% - 30px);
background-color: #F5F6FA;
min-height: 100vh;
}
.listC {
margin: 0;
width: 100%;
}
</style>

207
body/history/history.vue Normal file
View File

@ -0,0 +1,207 @@
<template>
<view class="common">
<view class="history" v-if="ranklist.length">
<view class="list" v-for="(item, index) in ranklist" :key="index" @click="clickItemMethod(item)">
<uni-swipe-action>
<uni-swipe-action-item :right-options="item.options" @click="swipeClick($event, index)">
<view class="time">
<icon class="t-icon t-icon-shijian-mianxing-0"></icon>
<text>{{item.record_time}}</text>
</view>
<view class="item">
<view>{{item.v1}}<text>{{item.v1_name}}</text></view>
<view v-if="item.v2">{{item.v2}}<text>{{item.v2_name}}</text></view>
<view v-if="item.v3">{{item.v3}}<text>{{item.v3_name}}</text></view>
<uni-icons type="right" v-if="acd_id!=6"></uni-icons>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
<view class="endtext" v-if="!lastPage || page >= lastPage"> 到底了看看别的吧 </view>
</view>
<view class="nolist" v-if="!ranklist.length||!lastPage">
<image src="../../static/none.png"></image>
<text>暂无数据</text>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
computed: {
...mapState(['user']),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
data() {
return {
ranklist: [],
page: 1,
isDelete: false,
lastPage: '',
isActive: null,
}
},
onLoad(options) {
let that = this
that.getList()
},
onReachBottom() {
let that = this
console.log("onReachBottom", this.lastPage)
if (!this.lastPage || this.page >= this.lastPage) {
uni.showToast({
title: '没有更多数据!',
icon: 'none'
})
return
}
this.page++
this.getList(this.page)
},
methods: {
swipeClick(e, index) {
let that = this
let id = that.ranklist[index].id
uni.showModal({
title: '友情提示',
content: '是否删除当前测量记录?',
success: function(res) {
if (res.confirm) {
that.$model.gethistorydelete({
id: id,
}).then((res) => {
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.ranklist.splice(index, 1)
if (that.user.measure_model == "1") {
that.$store.dispatch("getUserInfo", {
aud_id: uni.getStorageSync('userid')
})
}
if (that.acd_id == 2) {
that.$store.dispatch("getResult", {
aud_id: uni.getStorageSync('userid')
})
} else {
that.isDelete = true
}
that.$tools.msg("删除成功")
})
} else if (res.cancel) {
that.$tools.msg("您已取消操作!");
}
},
})
},
clickItemMethod(item) {
let that = this
uni.navigateTo({
url: "/body/history/historyDetail?id=" + item.id
})
},
getList(page) {
let that = this
that.$model.gethistory({
aud_id: that.user.aud_id,
page: that.page,
}).then((res) => {
console.log("历史记录", res)
if (res.code != 0) return
let options = [{
text: '删除',
style: {
backgroundColor: '#dd524d'
}
}]
res.data.rows.forEach(item => {
item.options = options
})
this.ranklist = this.ranklist.concat(res.data.rows)
this.lastPage = res.data.totalpage
})
},
handleEdit(id) {
let that = this
that.isActive = that.isActive == id ? null : id
},
}
}
</script>
<style scoped="scoped" lang="scss">
.common {
width: 100%;
min-height: 100.5vh; //
overflow-y: scroll;
background-color: #f7f7f7;
}
.history {
width: calc(100% - 30px);
height: auto;
margin: 15px 15px 0;
padding-bottom: 40px;
.list {
width: 100%;
margin-top: 12px;
position: relative;
.item {
width: calc(100% - 20px);
height: auto;
background: #fff;
padding: 6px 10px;
display: flex;
justify-content: space-between;
border-radius: 10px;
align-items: center;
font-weight: 700;
line-height: 50rpx;
font-size: 36rpx !important;
text {
display: block;
color: #666;
text-align: center;
font-weight: 500;
font-size: 28rpx;
}
}
.time {
font-size: 28rpx;
color: #666;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin-bottom: 5px;
icon {
width: 40rpx;
height: 40rpx;
margin-right: 5px;
}
text {
font-size: 32rpx;
margin-top: 3px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<view>
<view class="box">
<view class="form lanBox">
<view class="lan border-bottom" v-for="(item,index) in ranklist">
<view class="left">
<view class="view">
<view class="t-icon" :class="'t-icon-'+item.key_name" v-if="item.key_name!='score'&&item.key_name!='head_circumference'"></view>
<view class="score" v-if="item.key_name=='score'">A</view>
<image src="../../static/head.png" v-if="item.key_name=='head_circumference'"></image>
</view>
{{item.name}}
</view>
<view class="right">{{item.value?item.value:"0"}}{{item.unit}}</view>
</view>
</view>
</view>
</view>
</template>
<script>
// import {
// mapState
// } from "vuex";
export default {
data() {
return {
id: "",
type: "",
ranklist: []
};
},
// computed: {
// ...mapState([])
// },
onLoad(options) {
let that = this
that.id = options.id
that.getList()
},
methods: {
getList() {
let that = this
that.$model.gethistorydetail({
id: that.id,
}).then((res) => {
console.log("历史记录详情", res)
this.ranklist = res.data
})
},
}
};
</script>
<style scoped="scoped" lang="scss">
.box {
min-height: 100vh;
background-color: #fff;
}
.lanBox {
padding: 0px 15px 0;
}
.lan {
display: flex;
align-items: center;
font-size: 32rpx;
height: 50px;
line-height: 50px;
justify-content: space-between;
border-bottom: 1px solid #f7f7f7;
.left {
display: flex;
align-items: center;
text-align: left;
font-size: 32rpx;
padding-left: 30px;
.view {
width: 18px;
height: 18px;
position: absolute;
left: 15px;
padding: 2px;
background-color: #c7c7c7;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.t-icon,
image {
width: 16px;
height: 16px;
}
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
width: 50%;
min-height: 38px;
box-sizing: border-box;
line-height: 36px;
}
}
</style>

252
body/home/body.vue Normal file
View File

@ -0,0 +1,252 @@
<template>
<view class="content indexCarList">
<view class="tagList">
<scroll-view class="scroll-view_H" scroll-x="true" :scroll-into-view="toView">
<text class="scroll-view-item_H" v-for="(ite,ind) in tagList" @click="handlePageScrollTo(ite.key,ind)"
:class="[ind == index?'active':'']" :id="'Body_'+ite.key">
{{ite.name}}
</text>
</scroll-view>
</view>
<!-- 身体数据 -->
<view class="report">
<view class="box" v-if="list.length" v-for="(item,index) in list" :id="item.key_name">
<view class="data data2">
<view class="left">
<view class="name bold">
<image :src="item.icon"></image>
{{item.name}}
</view>
<view class="val" v-if="item.key_name=='body_level'||item.key_name=='body_type'"> - </view>
<view class="val" v-else>{{item.value?item.value:'0'}}{{item.unit}}</view>
<view class="level">
<view class="btnf"
:style="{backgroundColor:(item.standard=='异常'?'#FFF':item.standard_color)}"
:class="[item.standard=='异常'?'btnC':'']">
{{item.standard=='异常'?'-':item.standard}}
</view>
</view>
</view>
</view>
<view class="desc">
<view v-if="item.description" class="ming size12">{{item.description}}</view>
<view :class="[item.standard_list.length?'statuevue':'']" v-if="item.standard_list">
<view class="bi" v-if="item.title!='基础代谢'">
<view :style="'left:'+item.offset+'%'" class="peobox">
<view class="xx"></view>
</view>
<view class="item" v-for="(ite , ind) in item.standard_list" :key="ind"
:style="{backgroundColor:ite.color}">
<view class="span1">{{ite.text}}</view>
<view class="span" v-if="ind<item.standard_list.length-1">{{ite.max_val}}
</view>
</view>
</view>
<view v-else>
<view class="kcalClass" v-if="item.standard_list&&item.standard_list.length">
标准值:{{item.standard_list[0].max_val}}kcal
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
to: "",
toView: "",
index: 0,
list: [],
tagList: [],
}
},
computed: {
...mapState(['user', "configInfo"]),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
onLoad(options) {
this.to = options.to
console.log("to", this.to)
this.handleResultDetail()
},
methods: {
handlePageScrollTo(to, ind) {
this.index = ind
uni.createSelectorQuery().select('#' + to).boundingClientRect(function(rect) {
uni.pageScrollTo({
scrollTop: rect.top - 50,
duration: 500
})
}).exec();
},
handleResultDetail() {
let list = []
let that = this
that.$model.getResultDetail({
aud_id: that.user.aud_id
}).then(res => {
if (res.code == 0) {
that.list = res.data
res.data.forEach(ite => {
list.push({
key: ite.key_name,
name: ite.name
})
})
that.tagList = list
const index = res.data.findIndex(ite => ite.key_name == that.to)
setTimeout(() => {
that.handlePageScrollTo(that.to, index)
that.toView = 'Body_' + res.data[index].key_name
}, 500);
}
})
},
}
}
</script>
<style scoped lang="scss">
@import "@/scss/body.scss";
.content {
font-size: 32rpx;
width: 100%;
padding-bottom: 15px;
background-color: #F5F6FA;
min-height: 100vh;
}
.active {
color: #000 !important;
font-weight: bold;
font-size: 16px;
}
.scroll-view_H {
white-space: nowrap;
width: 100%;
.scroll-view-item_H {
display: inline-block;
height: 45rpx;
line-height: 45rpx;
text-align: center;
}
}
.tagList {
width: 100%;
height: 45px;
line-height: 45px;
background: #fff;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
position: fixed;
top: 0;
z-index: 999;
text {
color: #666;
padding: 0 15px;
}
}
.title {
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 10px;
margin-bottom: 15px;
position: relative;
}
.title:before {
content: "";
position: absolute;
width: 8px;
height: 20px;
left: -5px;
border-radius: 5px;
background-color: #fea606;
}
.bleTips {
height: 35px;
line-height: 35px;
margin: -10px 10px 10px;
background: #fff;
border-radius: 10px;
text-align: center;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
}
.bleTips2 {
background: #fea606;
color: #fff;
}
.report {
margin: 60px 0 15px;
width: calc(100% - 30px);
}
.left {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.name {
display: flex;
align-items: center;
width: 33%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
image {
width: 22px;
height: 22px;
margin-right: 5px;
}
}
.tools {
display: flex;
padding: 0 15px;
margin-top: 15px;
justify-content: space-between;
view {
display: flex;
background: #fff;
padding: 10px;
border-radius: 10px;
width: 40%;
align-items: center;
justify-content: center;
}
image {
width: 40px;
height: 40px;
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,311 @@
<template>
<view class="content">
<!-- <view class="bg"></view> -->
<view class="login">
<view class="editem">
<view class="item">
<view class="text">手机号/邮箱</view>
<view class="input">
<input v-model="phone" type="text"/>
</view>
</view>
<view class="item ">
<view class="text">验证码</view>
<view class="input yanzhengma">
<input class="uni-input" v-model="code" />
<button class="code" type="none" @click="handleCode" v-model="code"
:disabled="disabled">{{second<60 ? second+'S后重发':'验证码'}}
</button>
</view>
</view>
<view class="item">
<view class="text">密码</view>
<view class="input">
<input class="uni-input" v-model="password" />
</view>
</view>
<view class="item">
<view class="text">确认密码</view>
<view class="input">
<input class="uni-input" v-model="password2" />
</view>
</view>
</view>
<!-- <view class="xieyi">
<checkbox-group @change="checkboxChange" class="group">
<label>
<checkbox :value="1" style="transform:scale(0.7)" />{{$t("login.agreement")}}
<text @click="handlexieyi" @click.stop>{{$t("login.agreementContnt")}}</text>
</label>
</checkbox-group>
</view> -->
<view class="btnlogin" @click="handleTelLogin">确认</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
phone: "",
code: "",
password: "",
password2: "",
disabled: false,
second: 60,
value: 1,
type: ""
}
},
onLoad(options) {
this.type = options.type //1
},
methods: {
checkboxChange(e) {
this.value = e.detail.value.length ? e.detail.value[0] : "0"
},
//
handleTelLogin() {
let that = this
let phoneType = that.phone.indexOf("@") !== -1
if (that.value == 0) {
that.$tools.msg("请先确认勾选协议")
return
}
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
if (phoneType && !(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
if (!that.code) {
that.$tools.msg("请填写验证码")
return
}
if (!that.password) {
that.$tools.msg("请填写密码")
return
}
if (!that.password2) {
that.$tools.msg("请确认密码")
return
}
if (that.password2 != that.password) {
that.$tools.msg("请确认两次密码填写一致")
return
}
let account = {
data: that.phone,
password: that.password,
c_password: that.password2,
code: that.code
}
let https = that.type == 'register' ? that.$model.getregister(account) : that.$model.getResetPassword(
account)
return https.then(res => {
console.log("注册", res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('aan_id', res.data.aan_id)
that.$tools.msg("设置成功,进入程序中")
setTimeout(function() {
uni.reLaunch({
url: "/pages/home/home?type=1"
})
}, 1000)
}).catch(err => {})
},
//
handleCode() {
let that = this
let phoneType = that.phone.indexOf("@") !== -1
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
if (phoneType && !(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
//
that.$model.getSendCode({
data: that.phone,
type: that.type
}).then(res => {
console.log(res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.disabled = true
let interval = setInterval(() => {
--that.second
}, 1000)
setTimeout(() => {
clearInterval(interval)
that.disabled = false
that.second = 60
}, 60000)
}).catch(err => {})
},
handlexieyi() {}
}
}
</script>
<style scoped lang="scss">
.content {
width: 100%;
height: 100vh;
// display: flex;
// flex-direction: column;
// align-items: center;
// justify-content: center;
}
.bg {
position: absolute;
top: 0;
width: 100%;
height: 50vh;
z-index: 9;
// background: $maincolor;
}
.login {
width: calc(100% - 30px);
height: auto;
background: #fff;
border-radius: 10px;
padding: 15px;
background-color: #fff;
z-index: 99;
// position: relative;
// margin-left: calc(10% - 40px);
// box-shadow: 0px 1px 5px 2px #dfe2e1fc;
.title {
text-align: left;
color: #333;
font-size: 40rpx;
font-weight: bold;
margin-bottom: 15px;
}
.editem {
position: relative;
display: flex;
align-items: center;
font-size: 32rpx;
justify-content: space-between;
flex-wrap: wrap;
.item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.text {
width: 240rpx;
height: 40px;
line-height: 40px;
font-size: 32rpx;
}
.input {
width: calc(100% - 240rpx);
height: 35px;
line-height: 35px;
display: flex;
position: relative;
border: #dfdfdf 1px solid;
border-radius: 5px;
padding: 0 10px;
background-color: #f7f7f7;
}
input {
height: 40px;
line-height: 40px;
position: absolute;
left: 10px;
right: 0px;
z-index: 88;
font-size:28rpx;
}
.yanzhengma {
input {
right: 220rpx;
font-size:28rpx;
}
}
}
.code {
width: 220rpx;
background: #dfdfdf;
font-size: 28rpx;
margin: 0;
line-height: 40px;
border-radius: 5px;
text-align: center;
position: absolute;
right: 0px;
top: 0px;
bottom: 0;
z-index: 99;
}
.forget {
width: 80px;
background: #fff;
color: $textcolor;
}
}
.btngroup {
width: 100%;
height: 35px;
line-height: 35px;
display: flex;
justify-content: center;
text {
display: block;
color: $textcolor;
}
}
.btnlogin {
width: 100%;
margin: 15px 0;
height: 42px;
line-height: 42px;
background: $btncolor;
font-weight: 700;
border-radius: 15px;
text-align: center;
color: #fff !important;
}
}
.xieyi {
font-size: 32rpx;
color: $textcolor;
text {
border-bottom: 1px solid $textcolor;
}
}
</style>

309
body/login/login.vue Normal file
View File

@ -0,0 +1,309 @@
<template>
<view class="content">
<view class="bg"></view>
<view class="top">
<image src="../../static/logo2.png"></image>
<text>Reedaw</text>
</view>
<view class="login box_shadow">
<view class="title">登录</view>
<view class="toggle cblue" @click="handleToggle">
切换登录
</view>
<view class="editem">
<view class="item">
<view class="text">手机号/邮箱</view>
<view class="input">
<input v-model="phone" />
</view>
</view>
<!-- 验证码登录 -->
<view class="item " v-if="isCode">
<view class="text">验证码</view>
<view class="input yanzhengma">
<input class="uni-input" v-model="code" />
<button class="code" type="none" @click="handleCode" v-model="code"
:disabled="disabled">{{second<60 ? second+'S后重发':'获取验证码'}}
</button>
</view>
</view>
<!-- 密码登录 -->
<view class="item " v-else>
<view class="text">密码</view>
<view class="input">
<input class="uni-input" v-model="password" />
</view>
</view>
<view class="forget " v-if="!isCode">
<text @click="handlePassword('forgetPassword')">忘记密码?</text>
</view>
</view>
<view class="xieyi">
<checkbox-group @change="checkboxChange" class="group">
<label>
<checkbox :value="1" style="transform:scale(0.7)" />阅读并同意
<!-- <text @click.stop @click="handleUserXieyi" class="blue">用户协议</text> -->
<text @click.stop @click="handlexieyi" class="blue">隐私协议</text>
</label>
</checkbox-group>
</view>
<view class="btnlogin" @click="handleTelLogin">登录</view>
<view class="btngroup" @click="handlePassword('register')">
<text>注册</text>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="wxbtn">
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber" v-if="value==1">
<view>
<image src="../../static/phone.png"></image>
</view>
<text>手机号快捷登录</text>
</button>
<button v-else @click="handleIsTel">
<view>
<image src="../../static/phone.png"></image>
</view>
<text>手机号快捷登录</text>
</button>
</view>
<!-- #endif -->
</view>
</template>
<script>
export default {
data() {
return {
phone: "",
code: "",
password: "",
disabled: false,
second: 60,
value: 0,
isCode: true,
loginCode: ""
}
},
onLoad() {
// #ifdef MP-WEIXIN
this.login()
// #endif
},
methods: {
//
checkboxChange(e) {
this.value = e.detail.value.length ? e.detail.value[0] : "0"
},
//
handleTelLogin() {
let that = this
let phoneType = that.phone.indexOf("@") !== -1
if (that.value == 0) {
that.$tools.msg("请先确认勾选协议")
return
}
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
if (phoneType && !(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
if (that.isCode && !that.code) {
that.$tools.msg("请输入验证码")
return
}
if (!that.isCode && !that.password) {
that.$tools.msg('请输入正确密码')
return
}
this.$model.getonlogin({
data: that.phone,
validate_data: that.isCode ? that.code : that.password,
validate_type: that.isCode ? 'code' : 'password'
}).then(res => {
console.log("data", res.data)
that.$tools.msg(res.msg)
if (res.code != 0) return
that.$tools.msg("登录成功")
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('aan_id', res.data.aan_id)
that.$store.dispatch('getFamilyList', {
type: 2
})
setTimeout(function() {
uni.reLaunch({
url: "/pages/home/home"
})
}, 1000)
}).catch(err => {})
},
//
handleCode() {
let that = this
let phoneType = that.phone.indexOf("@") !== -1
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
if (phoneType && !(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
//
that.$model.getSendCode({
data: that.phone,
type: "login"
}).then(res => {
console.log(res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.disabled = true
let interval = setInterval(() => {
--that.second
}, 1000)
setTimeout(() => {
clearInterval(interval)
that.disabled = false
that.second = 60
}, 60000)
}).catch(err => {})
},
//
handleIsTel() {
if (this.value == 0) {
this.$tools.msg("请先确认勾选协议")
return
}
},
// code
login() {
let that = this
uni.login({
success(res) {
if (res.code) {
if (res.errMsg = "login:ok") {
that.loginCode = res.code
}
}
}
})
},
//
getPhoneNumber(res) {
const that = this
if (res.detail.errMsg == 'getPhoneNumber:ok') {
this.$model.getRegisterPhone({
code: that.loginCode,
encryptedData: res.detail.encryptedData,
iv: res.detail.iv,
}).then(res => {
if (res.code != 0) return
that.value = 1
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('aan_id', res.data.aan_id)
setTimeout(function() {
uni.reLaunch({
url: "/pages/index/index"
})
}, 1000)
})
}
},
handleToggle() {
this.phone = ""
this.isCode = !this.isCode
},
handlePassword(text) {
uni.navigateTo({
url: "/pageTwo/login/forgetPassword?type=" + text
})
},
handlexieyi() {
let that = this
uni.navigateTo({
url: "/pageTwo/webview/webview?url=http://tc.pcxbc.com/technology/privacy_index"
})
},
handleUserXieyi() {
let that = this
}
}
}
</script>
<style scoped lang="scss">
@import "@/scss/login.scss";
.content {
width: 100%;
height: 100vh;
}
.xieyi {
font-size: 28rpx;
color: #999;
margin-left: 10px;
text {
color: $maincolor;
}
}
.href {
width: auto;
}
.wxbtn {
width: 100%;
position: absolute;
margin-top: 30px;
top: 80%;
icon {
font-size: 25px;
color: #28c445;
}
text {
display: block;
// width: 100%;
margin-top: 5px;
font-size: 12px;
// color: #666;
text-align: center;
border-bottom: 1px solid blue;
color: blue;
}
button {
line-height: initial;
background: #fff;
display: flex;
flex-wrap: wrap;
padding: 0;
justify-content: center;
}
button::after {
display: none;
}
view {
width: 100%;
}
image {
width: 30px;
height: 30px;
border-radius: 50%;
}
}
</style>

195
body/my/about.vue Normal file
View File

@ -0,0 +1,195 @@
<template>
<view class="content">
<view class="info">
<view class="logo">
<image src="../../static/logo2.png"></image>
<view>Reedaw</view>
<text>V{{phoneInfo.info.version}}</text>
</view>
<view class="list">
<view class="item" @click="handleVersion">
<view class="left">版本更新</view>
<view class="right">
<text class="new" v-if="version==-1">新版本{{phoneInfo.versionUrl.version}}</text>
<uni-icons type="right"></uni-icons>
</view>
</view>
<view class="item">
<a href="http://tc.pcxbc.com/technology/privacy_index" class="href">
<text class="left">隐私协议</text>
<uni-icons type="right"></uni-icons>
</a>
</view>
</view>
</view>
<!-- <view class="xieyi">
<a>隐私协议</a>
</view> -->
</view>
</template>
<script>
import {
mapState
} from "vuex";
let dtask;
export default {
data() {
return {
// 01-1
version: 0,
}
},
computed: {
...mapState(["phoneInfo"]),
},
onLoad() {
let that = this
if (!that.phoneInfo.versionUrl) {
console.log("111")
that.handleoginversion()
} else {
console.log("222")
that.version = that.$tools.compareVersions(that.phoneInfo.info.version, that.phoneInfo.versionUrl.version)
}
},
onBackPress() {
console.log("dtask", dtask)
if (dtask != undefined) {
dtask.onProgressUpdate((res) => {
if (res.progress != 100) {
dtask.abort();
}
});
}
},
methods: {
handleVersion() {
let that = this
if (that.version == -1) {
if (that.phoneInfo.platform === 'android') {
uni.setStorageSync('VERSION', that.phoneInfo.versionUrl.version)
let showLoading = plus.nativeUI.showWaiting('正在下载')
dtask = uni.downloadFile({
url: that.phoneInfo.versionUrl.url,
success: (downloadRes) => {
if (downloadRes.statusCode === 200) {
plus.nativeUI.closeWaiting()
plus.runtime.install(
downloadRes.tempFilePath, {
force: false
},
function() {
console.log('install success...');
plus.runtime.restart();
},
function(e) {
console.error('install fail...');
});
}
},
fail: () => {
uni.showToast({
title: '升级失败',
icon: 'none'
});
}
});
dtask.onProgressUpdate((res) => {
showLoading.setTitle(" 正在下载" + res.progress + "% ");
// console.log('' + res.progress + '%');
});
} else {
plus.runtime.launchApplication({
action: `itms-apps://itunes.apple.com/cn/app/id6654906497?mt=8`
})
}
} else {
that.$tools.msg("已经是最新版本了!")
}
},
//
handleoginversion() {
let that = this
that.$model.getloginversion({}).then(res => {
that.version = that.$tools.compareVersions(that.phoneInfo.info.version, res.data.version)
that.$store.commit('changePhoneInfo', {
versionUrl: res.data
})
})
},
}
}
</script>
<style scoped lang="scss">
.content {
min-height: calc(100vh - 30px);
padding: 15px;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-between;
background-color: #f7f7f7;
}
.info {
width: 100%;
}
.logo {
width: 100%;
text-align: center;
font-size: 40rpx;
line-height: 35px;
image {
width: 140rpx;
height: 140rpx;
border-radius: 5px;
}
text {
display: block;
font-size: 32rpx;
color: #999;
}
}
.list {
width: calc(100% - 20px);
margin: 50px 0;
.item {
width: 100%;
height: 50px;
line-height: 50px;
margin-bottom: 15px;
display: flex;
background-color: #fff;
border-radius: 10px;
padding: 0 10px;
justify-content: space-between;
.new {
color: #fff;
background-color: red;
font-size: 32rpx;
border-radius: 10px;
padding: 3px 5px;
}
.href {
display: flex;
width: 100%;
color: #000;
text-decoration: none;
justify-content: space-between;
}
}
}
.xieyi {
color: $textcolor;
}
</style>

212
body/my/manage.vue Normal file
View File

@ -0,0 +1,212 @@
<template>
<view class="common">
<view class="add" @click="handleAddUser">
<icon class="iconfont icon-tianjia"></icon>添加成员
</view>
<view class="box" v-if="familayList.lenght!=0">
<view class="list">
<uni-swipe-action>
<uni-swipe-action-item v-for="(item ,index) in familayList" :key="index"
:right-options="item.options" @click="handleDeldet($event, index)">
<view class="item">
<view class="left">
<image :src="item.head_pic" class="image1" />
<view class="name">
<view class="title">
{{item.nickname}}
</view>
<view class="title2">
<text>{{item.gender==1?'男':'女'}}</text>
<text>{{item.age}}</text>
</view>
</view>
</view>
<view class="right" @click.stop>
<view class="blueBtn" @click="editorInfo(item)">编辑</view>
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
</view>
</view>
<view v-else>
没有数据了
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {}
},
computed: {
...mapState(["familayList"])
},
onPullDownRefresh() {
let that = this
that.handleUserList()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000);
},
methods: {
//
handleDeldet(e, ind) {
let that = this
let id = that.familayList[ind].id
uni.showModal({
title: '友情提示',
content: '确定删除该成员吗',
success: function(res) {
if (res.confirm) {
that.$model.getDelUser({
aud_id: id,
}).then(res => {
if (res.code != 0) return
that.$tools.msg("删除成功!");
that.familayList.splice(ind, 1)
that.handleUserList()
})
} else if (res.cancel) {
that.$tools.msg("您已取消删除!");
}
}
});
},
handleUserList() {
let that = this
that.$model.getUserList({
type: 2
}).then(res => {
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.$store.commit('changeFamilay', res.data.user_list)
if (res.data.length) {
uni.setStorageSync('userid', res.data.user_list[0].id)
that.$store.dispatch('getUserInfo', {
aud_id: res.data.user_list[0].id
})
that.handlePublicRecord(res.data.user_list[0].id)
}
}).catch(err => {})
},
//
handlePublicRecord(id) {
let that = this
that.$model.getPublicRecord({
aud_id: id
}).then(res => {
console.log("公共手动记录", res)
if (res.code == 0) {
that.$store.commit('changePublicRecord', res.data)
}
})
},
//
editorInfo(item) {
uni.navigateTo({
url: "/body/my/userInfo?info=" + JSON.stringify(item)
})
},
//
handleAddUser() {
uni.navigateTo({
url: "/body/my/userInfo"
})
},
}
}
</script>
<style scoped="scoped" lang="scss">
.common {
padding: 15px;
background-color: #f7f7f7;
min-height: calc(100vh - 30px);
}
.add {
width: 100%;
height: 40px;
line-height: 40px;
font-size: 32rpx;
margin-bottom: 10px;
color: #fff;
border-radius: 15px;
display: flex;
justify-content: center;
background: $btncolor;
}
.box {
width: 100%;
height: auto;
margin: 15px 0;
padding-bottom: 40px;
}
.list {
width: 100%;
font-size: 36rpx;
.item {
background: #fff;
padding: 10px 15px;
width: calc(100% - 30px);
border-radius: 10px;
margin-top: 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
.left {
width: 75%;
display: flex;
align-items: center;
.image1 {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
margin-right: 15px;
display: block;
}
.name {
width: calc(100% - 70px);
.title {
font-size: 34rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 5px;
}
.title2 {
font-size: 32rpx;
color: #999;
margin-top: 15px;
text {
margin-right: 10px;
}
}
}
}
.blueBtn {
width: auto;
font-size: 32rpx;
text-align: center;
}
}
</style>

352
body/my/userInfo.vue Normal file
View File

@ -0,0 +1,352 @@
<template>
<view class="box">
<view class="lanBox">
<view class="headbox">
<view class="touxiang">
<image v-if="headimg" :src="headimg" class="headimage" />
<icon v-else class="iconfont icon-user-filling headimage"></icon>
</view>
</view>
<view class="lan border-bottom">
<view class="left">姓名</view>
<view class="right">
<input name="name" type="text" v-model="memInfo.nickname" placeholder="请输入姓名" />
<uni-icons type="clear" color="#999" v-if="memInfo.nickname" @click="memInfo.nickname=''"
size="20"></uni-icons>
</view>
</view>
<view class="lan border-bottom">
<view class="left">性别</view>
<view class="right">
<view class="radio">
<uni-icons :type="memInfo.gender==1?'checkbox-filled':'circle'" @click="memInfo.gender=1"
size="24" :color="memInfo.gender==1?'#fea606':'#dfdfdf'"></uni-icons>
</view>
<view class="radio ml-15">
<uni-icons :type="memInfo.gender==2?'checkbox-filled':'circle'" @click="memInfo.gender=2"
size="24" :color="memInfo.gender==2?'#fea606':'#dfdfdf'"></uni-icons>
</view>
</view>
</view>
<view class="lan border-bottom">
<view class="left">身高</view>
<view class="right">
<input name="name" class="mr-5" type="digit" v-model="memInfo.height" placeholder="请输入身高" />CM
<uni-icons type="clear" color="#999" v-if="memInfo.height" @click="memInfo.height=''"
size="20"></uni-icons>
</view>
</view>
<view class="lan border-bottom">
<view class="left">体重</view>
<view class="right">
<input name="name" type="digit" class="mr-5" v-model="memInfo.weight" placeholder="请输入体重" />KG
<uni-icons type="clear" color="#999" v-if="memInfo.weight" @click="memInfo.weight=''"
size="20"></uni-icons>
</view>
</view>
<view class="lan border-bottom">
<view class="left">出生日期</view>
<view class="right">
<picker mode="date" :end="endDate" @change="maskClick"
:value="memInfo.birthday?memInfo.birthday:endDate" :fields="fields">
<view class="uni-input">{{memInfo.birthday?memInfo.birthday:'请选择'}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<view class="lan border-bottom">
<view class="left">活动系数</view>
<view class="right">
<picker mode="selector" @change="changeClickType" :range="activityLevel" range-key="name"
:value="levelInd">
<view>
{{memInfo.activity_level?activityLevel[levelInd].name:'请选择'}}
<icon class="iconfont icon-arrow-down-bold"></icon>
</view>
</picker>
</view>
</view>
</view>
<view class="btn" @click="confirmInfo">提交</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
sexItem: [
"男",
"女"
],
memInfo: {
birthday: "",
height: "",
weight: "",
gender: 0,
nickname: "",
activity_level: "",
},
fields: "",
levelInd: 0,
isEdit: false,
};
},
computed: {
...mapState(["user", "familayList", "configInfo"]),
endDate() {
return this.$tools.getDate("start")
},
activityLevel() {
return this.configInfo.activity_level
}
},
onLoad(options) {
let that = this
if (options.info) { //
let info = JSON.parse(options.info)
that.memInfo = info
that.isEdit = true
console.log("that.memInfo ", that.memInfo)
}
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
watch: {
familayList() {
let that = this
let userid = ""
let found = that.familayList.find(e => e.id == that.userId);
userid = found !== undefined ? that.userId : that.familayList[0].id
uni.setStorageSync('userid', found !== undefined ? found.id : userid)
uni.setStorageSync('gender', found !== undefined ? found.gender : that.familayList[0].gender)
that.$store.commit('changeUser', found !== undefined ? found : that.familayList[0])
that.$store.dispatch("getResult", {
aud_id: userid
})
if (!that.isEdit && that.familayList.length == 1) {
uni.switchTab({
url: "/pages/home/home"
})
} else {
uni.navigateBack()
}
}
},
methods: {
//
confirmInfo() {
let that = this
if (!that.memInfo.nickname) {
that.$tools.msg("请输入姓名")
return;
}
if (!that.memInfo.height) {
that.$tools.msg("请选择身高")
return;
}
if (!that.memInfo.weight) {
that.$tools.msg("请选择体重")
return;
}
if (!that.memInfo.birthday) {
that.$tools.msg("请选择出生日期")
return;
}
if (!that.memInfo.activity_level) {
that.$tools.msg("请选择活动系数")
return;
}
let https = that.isEdit ? that.$model.getEditUser(that.memInfo) : that.$model.getAddUser(that.memInfo)
return https.then(res => {
console.log("成功", res)
if (res.code == 0) {
that.$tools.msg("提交成功");
that.userId = res.data.aud_id
that.$store.dispatch('getFamilyList', {
type: 2
})
} else {
that.$tools.msg(res.msg);
}
});
},
//
handlePublicRecord(id) {
let that = this
that.$model.getPublicRecord({
aud_id: id
}).then(res => {
console.log("公共手动记录", res)
if (res.code == 0) {
that.$store.commit('changePublicRecord', res.data)
}
})
},
//
maskClick(e) {
console.log("出生日期", e.detail.value)
this.memInfo.birthday = e.detail.value
},
//
onsexArr(e) {
this.memInfo.gender = this.sexItem[e.target.value] == "男" ? 1 : 2
},
changeClickType(e) {
this.levelInd = e.target.value
this.memInfo.activity_level = this.activityLevel[e.target.value].val
}
},
};
</script>
<style scoped="scoped" lang="scss">
.box {
height: 100vh;
background-color: #fff;
}
input {
border: none;
background: inherit;
}
.headbox {
height: 180rpx;
padding-top: 10px;
border-radius: 0 0 5px 5px;
background: $maincolor;
}
.headimage {
display: block;
padding-top: 10px;
width: 70px;
height: 70px;
border-radius: 50%;
font-size: 140rpx;
margin: auto;
color: #fff;
}
.lan {
display: flex;
align-items: center;
font-size: 32rpx;
padding: 5px 0;
margin: 5px 15px;
border-bottom: 1px solid #f7f7f7;
}
.left {
width: 24%;
text-align: left;
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
width: 72%;
height: 40px;
line-height: 40px;
box-sizing: border-box;
position: relative;
text-align: right;
.radio {
display: flex;
align-items: center;
}
picker {
width: 100%;
text-align: right;
border: none;
margin-right: 15px;
font-size: 32rpx;
color: #333333;
}
/deep/input {
height: 35px;
padding-top: 1px;
font-size: 32rpx;
color: #333333;
margin-right: 15px;
}
.iconfont {
color: #333333;
font-size: 32rpx;
position: absolute;
right: -10px;
top: 0;
}
}
.btn {
width: auto;
margin: 40px 15px 0;
background: $btncolor !important;
}
.visible {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background-color: rgba(0, 0, 0, 0.6);
.groupBtn {
height: 50px;
line-height: 50px;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 15px;
border-bottom: 1px solid #dfdfdf;
position: absolute;
left: 0;
right: 0;
bottom: 45%;
z-index: 999;
view {
height: 30px;
line-height: 30px;
padding: 0 10px;
background-color: #dfdfdf;
border-radius: 5px;
margin-top: 10px;
}
.sure {
background-color: $textcolor;
color: #fff !important;
}
}
.picker-view {
width: 100%;
height: 45%;
bottom: 0;
position: absolute;
background-color: #fff;
}
.item {
line-height: 45px;
text-align: center;
}
}
</style>

800
body/report/report.vue Normal file
View File

@ -0,0 +1,800 @@
<template>
<view class="content">
<view class="date">{{depthInfo.user_data.record_time}}</view>
<view class="header">
<view class="top">
<view class="left">
<image :src="depthInfo.user_data.head_pic"></image>
<view>
<text class="bold size14 name">{{depthInfo.user_data.nickname}}</text>
<text>性别:{{depthInfo.user_data.gender==2?'女':'男'}}</text>
</view>
</view>
<view class="right">
<view class="rightChart">
<view class="charts-box">
<qiun-data-charts type="arcbar" :opts="opts2" canvasId="foodCharts107"
:chartData="chartData2" :canvas2d="true" :cHeight="300" :cWidth="300" />
</view>
<view class="score">
<text>{{depthInfo.user_data.score}}</text>
健康评分
</view>
</view>
</view>
</view>
<view class="info">
<view class="text_l">
<text>年龄</text>
<text class="size20 bold">{{depthInfo.user_data.age}}</text>
</view>
<view class="line text_c">
<text>身高</text>
<text class="size20 bold">{{depthInfo.user_data.height}}</text>
</view>
<view class="text_r">
<text class="text_c">体重</text>
<text class="size20 bold">{{depthInfo.user_data.weight}}</text>
</view>
</view>
</view>
<!-- 心脏健康 -->
<view v-if="depthInfo.heart_rate&&depthInfo.heart_rate.value">
<view class="title">
<image :src="depthInfo.heart_rate.icon"></image>
{{depthInfo.heart_rate.title_name}}
</view>
<view class="heartRate bgfff ">
<view class="stand">
<view><text>{{depthInfo.heart_rate.value}}</text>{{depthInfo.heart_rate.unit}}</view>
<view class="bold size16" :style="{'color':depthInfo.heart_rate.standard_color}">
{{depthInfo.heart_rate.standard}}
</view>
</view>
<image src="/static/31.png" mode="widthFix"></image>
<view class="report" style="margin-top: -45px;">
<view class="Xitem myinfoPage">
<view class="box">
<view class="desc desc2">
<view class="statuevue">
<view class="bi">
<view :style="'left:'+depthInfo.heart_rate.offset+'%'" class="peobox">
<view class="xx"></view>
</view>
<view class="item" v-for="(ite,ind) in depthInfo.heart_rate.standard_list"
:key="ind" :style="{backgroundColor:ite.color}">
<view class="span1">{{ite.text}}</view>
<view class="span" v-if="ind<depthInfo.heart_rate.standard_list.length-1">
{{ite.max_val}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 身体成分分析 -->
<view v-if="depthInfo.body_data&&depthInfo.body_data.title_name">
<view class="title">
<image :src="depthInfo.body_data.icon"></image>
{{depthInfo.body_data.title_name}}
</view>
<view class="info bgfff">
<view class="infoTitle">
<text class="size16 bold">{{depthInfo.body_data.title}}</text>
<text class="size12 c999">{{depthInfo.body_data.description}}</text>
</view>
<view class="Qlist">
<view class="QItem" v-for="(ite,ind) in depthInfo.body_data.list">
<view class="left">
<view class="yuan" :style="{'backgroundColor':ite.color}"></view>
<view class="name">
<text class="bold">{{ite.name}}</text>
<text class="size12 c999">{{ite.scope}}</text>
</view>
</view>
<view class="size16 bold value">{{ite.value}}</view>
</view>
</view>
<view class="huan">
<qiun-data-charts type="ring" :opts="opts" canvasId="foodCharts209" :chartData="chartData"
:cHeight="320" :cWidth="320" :canvas2d="true" />
</view>
</view>
</view>
<!-- 肌肉脂肪分析 -->
<view v-if="depthInfo.muscle_fat&&depthInfo.muscle_fat.list">
<view class="title">
<image :src="depthInfo.muscle_fat.icon"></image>
{{depthInfo.muscle_fat.title_name}}
</view>
<view class="table bgfff report">
<view class="Xitem myinfoPage">
<view class="box" v-for="(item,index) in depthInfo.muscle_fat.list">
<view class="data data2">
<view class="name bold">
{{item.name}}
</view>
<view class="val">{{item.value?item.value:'0'}}{{item.unit}}</view>
<view class="level">
<view class="btnf"
:style="{backgroundColor:(item.standard=='异常'?'#FFF':item.standard_color)}"
:class="[item.standard=='异常'?'btnC':'']">
{{item.standard=='异常'?'-':item.standard}}
</view>
</view>
</view>
<view class="desc">
<view v-if="item.description" class="ming size12">{{item.description}}</view>
<view :class="[item.list.length?'statuevue':'']" v-if="item.list">
<view class="bi">
<view :style="'left:'+item.offset+'%'" class="peobox">
<view class="xx"></view>
</view>
<view class="item" v-for="(ite , ind) in item.list" :key="ind"
:style="{backgroundColor:ite.color}">
<view class="span1">{{ite.text}}</view>
<view class="span" v-if="ind<item.list.length-1">{{ite.max_val}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 节段脂肪分析 -->
<view v-if="depthInfo.segment_fat&&depthInfo.segment_fat.list" class="w100">
<view class="title">
<image :src="depthInfo.segment_fat.icon"></image>
{{depthInfo.segment_fat.title_name}}
</view>
<view class="bodyimg bgfff">
<image :src="depthInfo.segment_fat.bg_img"></image>
<view class="L1" v-for="(ite,ind) in depthInfo.segment_fat.list">
{{ite.name}}{{ite.value}}{{ite.unit}}
</view>
</view>
</view>
<!--肌肉平衡 -->
<view v-if="depthInfo.segment_muscle&&depthInfo.segment_muscle.list" class="w100">
<view class="title">
<image :src="depthInfo.segment_muscle.icon"></image>
{{depthInfo.segment_muscle.title_name}}
</view>
<view class="bodyimg bgfff">
<image :src="depthInfo.segment_muscle.bg_img"></image>
<view class="L1" v-for="(ite,ind) in depthInfo.segment_muscle.list">
{{ite.name}}{{ite.value}}{{ite.unit}}
</view>
</view>
</view>
<!-- 肥胖分析 -->
<view v-if="depthInfo.fat_analysis">
<view class="title">
<image :src="depthInfo.fat_analysis.icon"></image>
{{depthInfo.fat_analysis.title_name}}
</view>
<view class="table bgfff report">
<view class="Xitem myinfoPage">
<view class="box" v-for="(item,index) in depthInfo.fat_analysis.list">
<view class="data data2">
<view class="name bold">
{{item.name}}
</view>
<view class="val">{{item.value?item.value:'0'}}{{item.unit}}</view>
<view class="level">
<view class="btnf"
:style="{backgroundColor:(item.standard=='异常'?'#FFF':item.standard_color)}"
:class="[item.standard=='异常'?'btnC':'']">
{{item.standard=='异常'?'-':item.standard}}
</view>
</view>
</view>
<view class="desc">
<view v-if="item.description" class="ming size12">{{item.description}}</view>
<view :class="[item.list.length?'statuevue':'']" v-if="item.list">
<view class="bi">
<view :style="'left:'+item.offset+'%'" class="peobox">
<view class="xx"></view>
</view>
<view class="item" v-for="(ite , ind) in item.list" :key="ind"
:style="{backgroundColor:ite.color}">
<view class="span1">{{ite.text}}</view>
<view class="span" v-if="ind<item.list.length-1">{{ite.max_val}}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 体重控制 -->
<view v-if="depthInfo.weight_controller" class="w100">
<view class="title">
<image :src="depthInfo.weight_controller.icon"></image>
{{depthInfo.weight_controller.title_name}}
</view>
<view class="table2 bgfff">
<view class="Xlist" v-for="(ite,ind) in depthInfo.weight_controller.list">
<view class="name">{{ite.name}}</view>
<view>{{ite.value}}</view>
</view>
</view>
</view>
<!-- 身体类型 -->
<view v-if="depthInfo.body_type" class="w100">
<view class="title">
<image :src="depthInfo.body_type.icon"></image>
{{depthInfo.body_type.title_name}}
</view>
<view class="type">
<view class="typeItem">
<view class="bold">{{depthInfo.body_type.l_line_color.name}} </view>
<view class="Tlist">
<view class="line line1"
:style="{ backgroundImage: 'linear-gradient(to bottom, '+depthInfo.body_type.l_line_color.standard_list[2].color+','+depthInfo.body_type.l_line_color.standard_list[2].color+','+depthInfo.body_type.l_line_color.standard_list[0].color+')' }">
</view>
<view class="ge">
<view v-for="(ite,ind) in depthInfo.body_type.box_list"
:class="[depthInfo.body_type.value==ite?'active':'']">{{ite}}</view>
</view>
<view class="line line2"
:style="{ backgroundImage: 'linear-gradient(to right, '+depthInfo.body_type.r_line_color.standard_list[0].color+','+depthInfo.body_type.r_line_color.standard_list[1].color+','+depthInfo.body_type.r_line_color.standard_list[2].color+')' }">
</view>
</view>
<view class="bold text_r">{{depthInfo.body_type.r_line_color.name}} </view>
</view>
<view class="Tinfo">
<view class="bold">{{depthInfo.body_type.r_line_color.name}}:</view>
<view v-for="(ite,ind) in depthInfo.body_type.r_line_color.standard_list" class="TinfoList">
<text :style="{'backgroundColor':ite.color}"></text>{{ite.name}}
</view>
</view>
<view class="Tinfo">
<view class="bold">{{depthInfo.body_type.l_line_color.name}}:</view>
<view v-for="(ite,ind) in depthInfo.body_type.l_line_color.standard_list" class="TinfoList">
<text :style="{'backgroundColor':ite.color}"></text>{{ite.name}}
</view>
</view>
</view>
</view>
<!-- 其他指标 -->
<view v-if="depthInfo.other_data" class="w100">
<view class="title">
<image :src="depthInfo.other_data.icon"></image>
{{depthInfo.other_data.title_name}}
</view>
<view class="table2 bgfff">
<view class="Xlist" v-for="(ite,ind) in depthInfo.other_data.list">
<view class="name">{{ite.name}}</view>
<view>{{ite.value}}</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts.vue';
export default {
data() {
return {
opts2: {
color: ["#54d87c"],
title: {
name: "",
},
extra: {
arcbar: {
type: "default",
width: 12,
backgroundColor: "#d0ffde",
startAngle: 0.95,
endAngle: 0.05,
gap: 2,
linearType: "none"
}
}
},
opts: {
color: [],
title: {
name: "",
},
subtitle: {
name: "体重(kg)"
}
},
chartData: {},
chartData2: {},
depthInfo: {
body_data: {},
body_type: {},
fat_analysis: {},
muscle_fat: {},
other_data: {},
segment_fat: {},
segment_muscle: {},
user_data: {},
weight_controller: {}
}
}
},
components: {
qiunDataCharts,
},
computed: {
...mapState(["user"]),
info() {
return this.user
},
},
onLoad() {
this.handleLabelList()
},
methods: {
handleLabelList() {
let that = this
that.$model.getResultDepth({
aud_id: that.user.aud_id
}).then(res => {
console.log("深度报告", res)
if (res.code == 0) {
that.depthInfo = res.data
let chart_data = []
let list = that.depthInfo.body_data.list
that.opts.color = []
for (let i = 0; i < list.length; ++i) {
this.opts.color.push(list[i].color)
chart_data.push({
name: list[i].name,
value: Number(list[i].offset),
})
}
this.opts.title.name = that.depthInfo.body_data.value
this.chartData = JSON.parse(JSON.stringify({
series: [{
data: chart_data
}]
}));
this.chartData2 = JSON.parse(JSON.stringify({
series: [{
name: "健康评分",
data: Number(that.depthInfo.user_data.score) / 100
}]
}));
}
})
},
}
}
</script>
<style scoped lang="scss">
@import "@/scss/body.scss";
.content {
padding: 0 10px 20px;
}
.size12 {
font-size: 12px !important;
}
.w100 {
width: 100%;
}
.desc2 {
width: 100% !important;
padding: 0 !important;
background: inherit !important;
}
.date {
width: 100%;
font-weight: bold;
font-size: 16px;
margin: 15px 0;
}
.title {
display: flex;
width: 100%;
margin: 10px 0;
font-size: 16px;
font-weight: bold;
align-items: center;
image {
width: 20px;
height: 30px;
margin-right: 10px;
}
}
.header {
background: #fff;
padding: 10px;
width: calc(100% - 20px);
border-radius: 10px;
position: relative;
.top {
display: flex;
height: 55px;
.left {
width: calc(100% - 180px);
display: flex;
image {
width: 50px;
height: 50px;
margin-right: 10px;
}
view {
width: calc(100% - 60px);
display: flex;
flex-direction: column;
justify-content: space-around;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.right {
width: 160px;
position: absolute;
right: 5px;
top: -35px;
height: 100px;
overflow: hidden;
.rightChart {
width: 140px;
height: 140px;
background: #fff;
border-radius: 50%;
padding: 10px;
}
.charts-box {
width: 140px;
height: 140px;
margin-top: -15px;
}
.score {
position: absolute;
width: 140px;
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
top: 0;
text {
color: $maincolor;
font-size: 20px;
font-weight: bold;
}
}
}
}
.info {
display: flex;
text-align: center;
justify-content: space-between;
margin-top: 10px;
view {
width: 28%;
display: flex;
flex-direction: column;
}
.line {
width: 40%;
position: relative;
}
.line::after,
.line::before {
content: "";
position: absolute;
width: 3px;
height: 30px;
background: #b6fdcc;
border-radius: 10px;
top: 10px;
}
.line::before {
left: 0;
}
.line::after {
right: 0px;
}
}
}
.heartRate {
width: calc(100% - 20px);
padding: 10px;
border-radius: 10px;
.stand {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
text {
font-size: 18px;
font-weight: bold;
margin: 0 5px;
color: $maincolor;
}
}
}
.info {
padding: 10px;
display: flex;
flex-wrap: wrap;
border-radius: 10px;
width: calc(100% - 20px);
justify-content: space-between;
.infoTitle {
width: 100%;
display: flex;
flex-direction: column;
}
.Qlist {
width: calc(100% - 140px);
margin-top: 10px;
.QItem {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
border-radius: 10px;
padding-left: 5px;
margin-bottom: 5px;
.left {
width: calc(100% - 60px);
display: flex;
align-items: center;
.yuan {
background-color: #3E7AF6;
width: 16px;
height: 16px;
border-radius: 50%;
}
.name {
display: flex;
flex-direction: column;
line-height: 15px;
margin-left: 10px;
}
}
.value {
width: 60px;
text-align: left;
}
}
.QItem:nth-child(even) {
background-color: #f7f7f7;
}
}
.huan {
width: 160px;
position: absolute;
right: 0px;
margin-top: 35px;
}
}
.bodyimg {
padding: 20px 30px;
width: calc(100% - 60px);
text-align: center;
border-radius: 10px;
position: relative;
image {
width: 13.75rem;
height: 13.2rem;
}
view {
font-size: 15px;
font-weight: bold;
position: absolute;
}
.L1:nth-child(2) {
left: 26px;
top: 35px;
}
.L1:nth-child(3) {
right: 25px;
top: 35px;
}
.L1:nth-child(4) {
left: 25px;
top: 95px;
}
.L1:nth-child(5) {
left: 25px;
bottom: 50px;
}
.L1:nth-child(6) {
right: 25px;
bottom: 50px;
}
}
.report {
padding: 0;
margin: 0;
}
.table,
.table2 {
width: calc(100% - 20px);
padding: 10px;
.Xlist {
display: flex;
line-height: 40px;
view {
width: 50%;
text-align: center;
position: relative;
}
.name::after {
content: "";
position: absolute;
width: 1px;
height: 40px;
background: #f7f7f7;
top: 0px;
right: 0;
}
}
.Xlist:nth-child(odd) {
background-color: #dfdfdf;
}
.Xlist:nth-child(1) {
font-size: 16px;
color: #fff;
border-radius: 10px 10px 0 0;
background-color: $maincolor !important;
}
}
.table2 {
width: 100%;
padding: 0;
}
.type {
width: calc(100% - 20px);
padding: 10px;
background: #fff;
border-radius: 10px;
.Tlist {
display: flex;
justify-content: space-between;
margin-top: 10px;
margin-bottom: 5px;
width: 100%;
position: relative;
height: 235px;
.line {
position: absolute;
width: 5px;
height: 92%;
background: #dfdfdf;
border-radius: 10px;
}
.line1 {
top: 0;
left: 5px;
}
.line2 {
width: 92%;
height: 5px;
bottom: 0;
right: 0;
}
.ge {
width: calc(100% - 25px);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
position: absolute;
left: 25px;
view {
width: 30%;
height: 60px;
line-height: 60px;
text-align: center;
background: #f7f7f7;
border: 1px solid $maincolor;
border-radius: 10px;
margin-bottom: 15px;
}
.active {
color: #fff;
background: $maincolor;
}
}
}
.Tinfo {
display: flex;
padding: 5px 0;
.TinfoList {
color: #666;
display: flex;
align-items: center;
width: 15%;
margin-left: 15px;
}
text {
width: 7px;
height: 7px;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
}
}
}
</style>

209
body/setting/email.vue Normal file
View File

@ -0,0 +1,209 @@
<template>
<view class="content">
<view class="login">
<view class="editem">
<view class="item">
<view class="text">邮箱</view>
<view class="input">
<input v-model="phone" placeholder="请输入邮箱"/>
</view>
</view>
<view class="item ">
<view class="text">验证码</view>
<view class="input yanzhengma">
<input class="uni-input" v-model="code" />
<button class="code" type="none" @click="handleCode" v-model="code"
:disabled="disabled">{{second<60 ? second+'S后重发':$t("login.sendcode")}}
</button>
</view>
</view>
</view>
<view class="btnlogin" @click="handleTelLogin">确认</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
phone: "",
code: "",
disabled: false,
second: 60,
}
},
methods: {
//
handleTelLogin() {
let that = this
if (!(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
if (!that.code) {
that.$tools.msg("请填写验证码")
return
}
that.$model.getAccountMsg({
data: that.phone,
code: that.code,
}).then(res => {
if (res.code != 0) {
that.$tools.msg(res.msg)
return
} else {
that.$tools.msg("设置成功!")
that.$store.commit('changeAccountNumber', {
my_email: that.phone
})
uni.redirectTo({
url: "/pageTwo/setting/setting"
})
}
}).catch(err => {})
},
//
handleCode() {
let that = this
if (!that.phone) {
that.$tools.msg("请输入邮箱")
return
}
if (!(/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(that.phone))) {
that.$tools.msg("请输入正确的邮箱")
return
}
//
that.$model.getSendCode({
data: that.phone,
// type: that.type
}).then(res => {
console.log(res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.disabled = true
let interval = setInterval(() => {
--that.second
}, 1000)
setTimeout(() => {
clearInterval(interval)
that.disabled = false
that.second = 60
}, 60000)
}).catch(err => {})
},
}
}
</script>
<style scoped lang="scss">
.content {
width: 100%;
height: 100vh;
}
.login {
width: calc(100% - 30px);
height: auto;
background: #fff;
border-radius: 10px;
padding: 15px;
background-color: #fff;
z-index: 99;
.title {
text-align: left;
color: #333;
font-size: 40rpx;
font-weight: bold;
margin-bottom: 15px;
}
.editem {
position: relative;
display: flex;
align-items: center;
font-size: 32rpx;
justify-content: space-between;
flex-wrap: wrap;
.item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.text {
width: 80px;
height: 40px;
line-height: 40px;
font-size: 32rpx;
}
.input {
width: calc(100% - 100px);
height: 35px;
line-height: 35px;
display: flex;
position: relative;
border: #dfdfdf 1px solid;
border-radius: 5px;
padding: 0 10px;
background-color: #f7f7f7;
}
input {
height: 40px;
line-height: 40px;
position: absolute;
left: 10px;
right: 0px;
z-index: 88;
font-size: 32rpx;
}
.yanzhengma {
input {
right: 120px;
font-size: 32rpx;
}
}
}
.code {
width: 110px;
background: #dfdfdf;
font-size: 32rpx;
margin: 0;
line-height: 40px;
border-radius: 5px;
text-align: center;
position: absolute;
right: 0px;
top: 0px;
bottom: 0;
z-index: 99;
}
}
.btnlogin {
width: 100%;
margin: 15px 0;
height: 42px;
line-height: 42px;
background: $btncolor;
font-weight: 700;
border-radius: 15px;
text-align: center;
color: #fff !important;
}
}
</style>

139
body/setting/password.vue Normal file
View File

@ -0,0 +1,139 @@
<template>
<view class="content">
<view class="login">
<view class="editem">
<view class="item">
<view class="text">密码</view>
<view class="input">
<input class="uni-input" v-model="password" placeholder="请输入密码"/>
</view>
</view>
<view class="item">
<view class="text">确认密码</view>
<view class="input">
<input class="uni-input" v-model="password2" placeholder="请输入确认密码"/>
</view>
</view>
</view>
<view class="btnlogin" @click="handleTelLogin">确认</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
password: "",
password2: "",
}
},
onLoad() {},
methods: {
//
handleTelLogin() {
let that = this
if (!that.password) {
that.$tools.msg("请填写密码")
return
}
if (!that.password2) {
that.$tools.msg("请确认密码")
return
}
if (that.password2 != that.password) {
that.$tools.msg("请确认两次密码填写一致")
return
}
that.$model.getAccountPassword({
password: that.password,
c_password: that.password2,
}).then(res => {
console.log("注册", res)
if (res.code != 0) {
that.$tools.msg(res.msg)
} else {
that.$tools.msg("密码设置成功")
uni.navigateBack()
}
}).catch(err => {})
},
}
}
</script>
<style scoped lang="scss">
.content {
width: 100%;
height: 100vh;
}
.login {
width: calc(100% - 30px);
height: auto;
background: #fff;
border-radius: 10px;
padding: 15px;
background-color: #fff;
z-index: 99;
.editem {
position: relative;
display: flex;
align-items: center;
font-size:28rpx;
justify-content: space-between;
flex-wrap: wrap;
.item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.text {
width: 80px;
height: 40px;
line-height: 40px;
font-size: 32rpx;
}
.input {
width: calc(100% - 100px);
height: 35px;
line-height: 35px;
display: flex;
position: relative;
border: #dfdfdf 1px solid;
border-radius: 5px;
padding: 0 10px;
background-color: #f7f7f7;
}
input {
height: 40px;
line-height: 40px;
position: absolute;
left: 10px;
right: 0px;
z-index: 88;
font-size: 28rpx;
}
}
}
.btnlogin {
width: 100%;
margin: 15px 0;
height: 42px;
line-height: 42px;
background: $btncolor;
font-weight: 700;
border-radius: 15px;
text-align: center;
color: #fff !important;
}
}
</style>

209
body/setting/phone.vue Normal file
View File

@ -0,0 +1,209 @@
<template>
<view class="content">
<view class="login">
<view class="editem">
<view class="item">
<view class="text">手机号</view>
<view class="input">
<input v-model="phone" placeholder="请输入手机号" />
</view>
</view>
<view class="item ">
<view class="text">验证码</view>
<view class="input yanzhengma">
<input class="uni-input" v-model="code" />
<button class="code" type="none" @click="handleCode" v-model="code"
:disabled="disabled">{{second<60 ? second+'S后重发':$t("login.sendcode")}}
</button>
</view>
</view>
</view>
<view class="btnlogin" @click="handleTelLogin">确认</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
phone: "",
code: "",
disabled: false,
second: 60,
}
},
methods: {
//
handleTelLogin() {
let that = this
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
if (!that.code) {
that.$tools.msg("请填写验证码")
return
}
that.$model.getAccountMsg({
data: that.phone,
code: that.code,
}).then(res => {
if (res.code != 0) {
that.$tools.msg(res.msg)
return
} else {
that.$tools.msg("设置成功!")
that.$store.commit('changeAccountNumber', {
my_tel: that.phone
})
uni.redirectTo({
url: "/pageTwo/setting/setting"
})
}
}).catch(err => {})
},
//
handleCode() {
let that = this
if (!that.phone) {
that.$tools.msg("请输入手机号")
return
}
if (!phoneType && !(/^1[3456789]\d{9}$/.test(that.phone))) {
that.$tools.msg("请输入正确的手机号")
return
}
//
that.$model.getSendCode({
data: that.phone,
// type: that.type
}).then(res => {
console.log(res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.disabled = true
let interval = setInterval(() => {
--that.second
}, 1000)
setTimeout(() => {
clearInterval(interval)
that.disabled = false
that.second = 60
}, 60000)
}).catch(err => {})
},
}
}
</script>
<style scoped lang="scss">
.content {
width: 100%;
height: 100vh;
}
.login {
width: calc(100% - 30px);
height: auto;
background: #fff;
border-radius: 10px;
padding: 15px;
background-color: #fff;
z-index: 99;
.title {
text-align: left;
color: #333;
font-size:40rpx;
font-weight: bold;
margin-bottom: 15px;
}
.editem {
position: relative;
display: flex;
align-items: center;
font-size:28rpx;
justify-content: space-between;
flex-wrap: wrap;
.item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.text {
width: 80px;
height: 40px;
line-height: 40px;
font-size:28rpx;
}
.input {
width: calc(100% - 100px);
height: 35px;
line-height: 35px;
display: flex;
position: relative;
border: #dfdfdf 1px solid;
border-radius: 5px;
padding: 0 10px;
background-color: #f7f7f7;
}
input {
height: 40px;
line-height: 40px;
position: absolute;
left: 10px;
right: 0px;
z-index: 88;
font-size:28rpx;
}
.yanzhengma {
input {
right: 120px;
font-size: 32rpx;
}
}
}
.code {
width: 110px;
background: #dfdfdf;
font-size: 32rpx;
margin: 0;
line-height: 40px;
border-radius: 5px;
text-align: center;
position: absolute;
right: 0px;
top: 0px;
bottom: 0;
z-index: 99;
}
}
.btnlogin {
width: 100%;
margin: 15px 0;
height: 42px;
line-height: 42px;
background: $btncolor;
font-weight: 700;
border-radius: 15px;
text-align: center;
color: #fff !important;
}
}
</style>

161
body/setting/setting.vue Normal file
View File

@ -0,0 +1,161 @@
<template>
<view class="content">
<view class="caritem">
<view class="text">头像</view>
<image :src="user.head_pic" class="image"></image>
</view>
<view class="caritem">
<view class="text">昵称</view>
<view class="text_r">
<text v-if="!isEdit">{{user.nickname}}</text>
<input v-else type="text" v-model='nickname' @blur="handleBlur" />
<uni-icons type="compose" color="#FEC407" @click="isEdit=true" class="ml-10" size="22"></uni-icons>
</view>
</view>
<view class="caritem" @click="navTo('/pageTwo/setting/phone')">
<view class="text">手机号</view>
<view class="text_r">
<text>{{user.my_tel}}</text>
<uni-icons type="right"></uni-icons>
</view>
</view>
<view class="caritem" @click="navTo('/pageTwo/setting/email')">
<view class="text">邮箱</view>
<view class="text_r">
<text>{{user.my_email}}</text>
<uni-icons type="right"></uni-icons>
</view>
</view>
<view class="caritem" @click="navTo('/pageTwo/setting/password')">
<view class="text">设置密码</view>
<uni-icons type="right"></uni-icons>
</view>
<view class="btn mb-15" @click="handleOutLogin">删除账号</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
isEdit: false,
headimg: null,
nickname: ""
};
},
computed: {
...mapState(["accountNumber"]),
user() {
return this.accountNumber
}
},
methods: {
handleOutLogin() {
let that = this
uni.showModal({
title: '友情提示',
confirmText: '删除',
content: '删除成功后,该账号的所有关联信息将被清空且无法找回,是否删除?',
success: function(res) {
if (res.confirm) {
that.$model.getdeleteAccount({}).then((res) => {
if (res.code != 0) return
that.$tools.msg("删除成功!");
uni.setStorageSync('token', null)
uni.setStorageSync('aan_id', null)
uni.clearStorageSync()
setTimeout(() => {
uni.reLaunch({
url: "/pageTwo/login/login"
})
}, 3000);
})
} else if (res.cancel) {
that.$tools.msg("您已取消操作!");
}
},
})
},
//
handleBlur() {
let that = this
return that.$model.getAccountNickname({
nickname: that.nickname,
}).then(res => {
if (res.code != 0) return
that.user.nickname = that.nickname
that.$store.commit('changeAccountNumber', {
nickname: that.nickname
})
that.isEdit = false
})
},
navTo(url) {
uni.navigateTo({
url
})
},
}
}
</script>
<style lang="scss" scoped>
.content {
background-color: #F3F4F6;
padding: 15px;
min-height: 100vh;
font-size: 36rpx;
}
.image {
width: 40px;
height: 40px;
border-radius: 50%;
}
.text_r {
width: 70%;
display: flex;
align-items: center;
justify-content: flex-end;
text {
width: calc(100% - 30px);
display: inline-block;
}
input {
width: 80%;
border-bottom: 1px solid #dfdfdf;
}
}
.btn {
width: 100%;
background: #999;
margin: 50px 15px 0 15px;
}
.caritem {
width: calc(100% - 20px);
height: 50px;
line-height: 50px;
background-color: #fff;
border-radius: 10px;
padding: 0 10px;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: space-between;
.uni-icons {
width: 30px;
text-align: right;
}
}
</style>

21
body/webview/webview.vue Normal file
View File

@ -0,0 +1,21 @@
<template>
<web-view :src="webviewUrl"></web-view>
</template>
<script>
export default {
data() {
return {
webviewUrl: ''
};
},
// URL
onLoad(options) {
let that = this
let token = uni.getStorageSync('token')
let url = options.url + '?token=' + token + '&id=' + options.id
that.webviewUrl = decodeURIComponent(url);
console.log("11111", options, url, this.webviewUrl)
}
}
</script>

View File

@ -0,0 +1,223 @@
<template>
<view class="body">
<view class="top">
<view class="info box_shadow" v-for="(ite,ind) in Measure.top_list"
:style="{'backgroundColor':ite.bk_color}" @click="handelCurveDetailed">
<view class="title">
<view class="bold">{{ite.name}}</view>
<view class="c999">
{{ite.time}}
<uni-icons type="right" class="ml-10" color="#999"></uni-icons>
</view>
</view>
<view class="weight">
<view class="left">
<view class="number"><text class="size26 bold">{{ite.value}}</text>{{ite.unit}}</view>
<view class="standard bold size16" :style="{'color':ite.standard_color}">{{ite.standard}}</view>
</view>
<view class="charts">
<qiun-data-charts type="area" :chartData="ite.curve_list.line" :canvas2d="true" :opts="opts"
:canvasId="ite.key_name+'charts09'" :Width="140" :Height="85" />
</view>
</view>
<view class="Ideal c999" v-if="ite.ideal_weight">
<text>理想{{ite.name}}</text>
<text>{{ite.ideal_weight}}</text>
</view>
</view>
</view>
<!-- -->
<view class="function mt-15">
<view @click="handlerRecord">
<image src="/static/shoudong.png" mode="widthFix"></image>
</view>
<view @click="handReport">
<image src="/static/shendu.png" mode="widthFix"></image>
</view>
</view>
<!-- -->
<view class="tools">
<view class="item box_shadow" v-for="(ite,ind) in Measure.card_list" @click="handleTools(ite.key_name)">
<view class="top">
<image :src="ite.icon"></image>{{ite.name}}
</view>
<view class="number">
<text class="bold"
:class="[ite.key_name=='body_level'||ite.key_name=='body_type'?'size14':'size20']">{{ite.value}}</text>{{ite.unit}}
</view>
<view class="bold size14" :style="{'color':ite.standard_color}">{{ite.standard}}</view>
</view>
</view>
<!-- 手动记录 -->
<record></record>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import record from '@/components/bodyIndex/record.vue';
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts.vue'
export default {
name: "body",
components: {
record,
qiunDataCharts,
},
data() {
return {
opts: {
padding: [0, 0, 0, 0],
dataLabel: false,
dataPointShape: false,
enableScroll: false,
xAxis: {
disabled: true,
axisLine: false,
disableGrid: true,
},
yAxis: {
gridType: "dash",
dashLength: 2,
disabled: true,
axisLine: false,
disableGrid: true,
},
}
};
},
computed: {
...mapState(["user", "MeasureResult"]),
info() {
return this.user
},
Measure() {
return this.MeasureResult
}
},
methods: {
//
handleTools(name) {
uni.navigateTo({
url: "/body/home/body?to=" + name
})
},
//
handReport() {
uni.navigateTo({
url: "/body/report/report"
})
},
//
handelCurveDetailed(){
uni.navigateTo({
url: "/body/curve/curve"
})
},
//
handlerRecord() {
this.$store.commit('changeRecord', true)
},
}
}
</script>
<style scoped lang="scss">
.body {
background: #f7f7f7;
}
.info {
margin: 15px 10px;
padding: 10px;
height: auto;
overflow: hidden;
border-radius: 10px;
display: flex;
flex-wrap: wrap;
.title {
width: 100%;
display: flex;
justify-content: space-between;
}
.weight {
width: 100%;
padding-top: 10px;
display: flex;
justify-content: space-between;
.left {
width: 40%;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
}
}
.target {
color: #999;
width: 100%;
display: flex;
justify-content: space-between;
}
}
.charts {
width: 50%;
height: 70px;
}
.function {
display: flex;
margin: 0 5px;
width: calc(100% - 10px);
justify-content: space-between;
view {
width: 50%;
}
image {
width: 100%;
}
}
.tools {
display: flex;
flex-wrap: wrap;
padding: 0 0 10px;
padding-left: 3%;
width: 97%;
background: #f7f7f7;
box-shadow: none;
justify-content: space-between;
.item {
width: 40%;
background: #fff;
margin-bottom: 15px;
border-radius: 10px;
padding: 10px 3%;
margin-right: 3%;
}
.top {
display: flex;
align-items: center;
image {
width: 22px;
height: 22px;
margin-right: 5px;
}
}
.number {
margin: 5px 0;
}
}
</style>

View File

@ -0,0 +1,133 @@
<template>
<view class="wrapper" v-if="isDrawe">
<view class="bg" @click="clear"></view>
<view class="wrapper_box">
<view class="top">
<image class="headimage mt-10" v-if="userinfo.head_pic" :src="userinfo.head_pic"></image>
<view class="overflow">{{userinfo.nickname}}</view>
</view>
<view class="drawerList">
<view class="drawerList_item" v-for="(item, index) in List" :key="index" @click="toggle(item)"
v-if="List.length">
<image v-if="item.head_pic" :src="item.head_pic" class="image1"></image>
<view class="right">
<view class="name">
<view class="overflow">
{{item.nickname}}
</view>
<view class="dangqian" v-if="item.id == userinfo.id">当前</view>
</view>
<view class="info">
<view>{{item.gender==1?'男':'女'}}</view>
<view>{{item.age}}</view>
</view>
</view>
</view>
<view class="add" @click="addInfo()">
+
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {};
},
computed: {
...mapState(["user", "familayList", "isDrawe"]),
userinfo() {
return this.user
},
List() {
return this.familayList
},
},
methods: {
//
toggle(val) {
let that = this
uni.setStorageSync('userid', val.id)
uni.setStorageSync('gender', val.gender)
that.$store.commit('changeUser', val)
that.$store.dispatch("getResult", {
aud_id: val.id
})
that.$store.commit("changeDrawe", false);
// that.handlePublicRecord(val.id)
},
//
handlePublicRecord(id) {
let that = this
that.$model.getPublicRecord({
aud_id: id
}).then(res => {
console.log("公共手动记录", res)
if (res.code == 0) {
that.$store.commit('changePublicRecord', res.data)
}
})
},
//
addInfo() {
let that = this
uni.navigateTo({
url: "/pageTwo/my/userInfo"
})
that.$store.commit("changeDrawe", false);
},
clear() {
this.$store.commit("changeDrawe", false);
},
}
}
</script>
<style lang="scss" scoped>
.wrapper {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 999;
.bg {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 99;
}
}
.headimage {
width: 50px;
height: 50px;
margin: auto;
border-radius: 50%;
margin-bottom: 16rpx;
}
.dangqian {
font-size: 24upx !important;
}
@keyframes wrapper_box {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0%);
}
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<view v-if="isFirst" class="wrapper wrapperbg">
<view class="bg" @click="onTap">
<view class="edit" @click.stop>
<view class="title">初始体重</view>
<view class="editem">
<view class="name">日期</view>
<view class="right">
<picker mode="date" :end="endDate" @change="changeLog" :fields="fields">
<view class="uni-input">{{regTime?regTime:'请选择'}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<view class="editem">
<view class="name">体重</view>
<view class="right">
<input v-model="weight" type="digit" placeholder="请输入" />kg
</view>
</view>
<view class="btn close" @click="onTap()">取消</view>
<view class="btn" @click="handleTarget">确定</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
regTime: "",
weight: "",
fields: "",
}
},
computed: {
...mapState(["user", "isFirst"]),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
mounted() {
let that = this
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
methods: {
//
handleTarget() {
let that = this
if (!that.regTime) {
that.$tools.msg("请选择测量日期")
return
}
if (!that.weight) {
that.$tools.msg("请输入测量体重")
return
}
that.$model.getfirstweight({
aud_id: that.user.aud_id,
time: that.regTime,
weight: that.weight,
}).then(res => {
that.$tools.msg(res.msg)
if (res.code == 0) {
that.$store.commit("changeFirst", false);
that.$emit("handleLabelList", that.endDate, that.startDate)
// that.$store.dispatch('getUserInfo', {
// aud_id: that.user.aud_id
// })
that.regTime = ""
that.weight = ""
}
})
},
//
changeLog(e) {
this.regTime = e.detail.value
},
onTap() {
this.weight = ""
this.regTime = ""
this.$store.commit("changeFirst", false);
},
hideKeyboard() {
uni.hideKeyboard()
},
}
}
</script>
<style scoped lang="scss">
.btn {
width: 40%;
float: right;
margin-top: 15px;
background: $maincolor !important;
}
.edit {
top: 20%
}
.close {
background: #fff !important;
float: left;
color: #333;
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<view class="wrapper" v-if="isRecord">
<view class="bg" @click="onTap">
<view class="edit" @click.stop>
<view class="title">手动记录</view>
<view class="editem">
<view class="left">日期</view>
<view class="right">
<picker mode="date" :end="endDate" @change="changeLog" :fields="fields">
<view class="uni-input">{{regTime?regTime:'请选择'}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<!-- 身体 -->
<view>
<view class="editem" v-if="userInfo&&userInfo.height">
<view class="name">身高</view>
<view class="right">
<input type="digit" v-model="height" placeholder="请输入身高" />cm
</view>
</view>
<view class="editem" v-if="userInfo&&userInfo.weight">
<view class="name">体重</view>
<view class="right">
<input type="digit" v-model="weight" placeholder="请输入体重">kg
</view>
</view>
<view class="editem" v-if="userInfo.stage=='婴儿'">
<view class="left">头围</view>
<view class="right">
<input v-model="head" type="digit" placeholder="请输入" />cm
</view>
</view>
</view>
<view class="btn close" @click="onTap()">取消</view>
<view class="btn" @click="handleinsertmeasure()">确定</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
props: {
rtype: null,
},
data() {
return {
regTime: '',
weight: "",
height: '',
fields: "",
head: ""
}
},
computed: {
...mapState(["user", "isRecord"]),
userInfo() {
return this.user
},
endDate() {
return this.$tools.getDate("start")
},
},
mounted() {
let that = this
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
methods: {
//
handleinsertmeasure() {
let that = this
if (!that.regTime) {
that.$tools.msg("请选择测量日期")
return
}
if (!that.height) {
that.$tools.msg("请输入测量身高")
return
}
if (!that.weight) {
that.$tools.msg("请输入测量体重")
return
}
that.$model.getinsertmeasure({
aud_id: that.user.aud_id,
time: that.regTime,
weight: that.weight,
height: that.height,
head_data: that.head ? that.head : 0
}).then(res => {
if (res.code != 0) return
that.$tools.msg(res.msg)
that.$store.dispatch("getResult", {
aud_id: that.user.aud_id
})
that.$store.dispatch("getUserInfo", {
aud_id: that.user.aud_id
})
that.onTap()
})
},
changeLog(e) {
this.regTime = e.detail.value
},
onTap() {
let that = this
that.weight = ""
that.height = ""
that.regTime = ""
that.$store.commit("changeRecord", false);
},
}
}
</script>
<style scoped lang="scss">
.btn {
width: 40%;
float: right;
margin-top: 15px;
background: $maincolor !important;
}
.close {
background: #dfdfdf !important;
float: left;
}
</style>

View File

@ -0,0 +1,213 @@
<template>
<view v-if="isTarget" class="wrapper">
<view class="bg" @click="onTap">
<view class="edit" @click.stop>
<view class="title">目标体重</view>
<view class="editem">
<view class="left">目标体重</view>
<view class="right">
<input class="text" type="digit" placeholder="请输入" v-model="inputvalue" />kg
</view>
</view>
<view class="btn close" @click="onTap()">取消</view>
<view class="btn" @click="handleWeight">确定</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
inputvalue: "",
}
},
computed: {
...mapState(["user", "isTarget"]),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
methods: {
//
handleWeight() {
let that = this
if (!that.inputvalue) {
that.$tools.msg("请输入目标体重")
return
}
that.$model.getTargetweight({
aud_id: that.user.aud_id,
weight: that.inputvalue,
}).then(res => {
if (res.code != 0) return
that.$tools.msg(res.msg)
that.$store.commit("changeTarget", false);
that.$emit("handleLabelList", that.endDate, that.startDate)
// that.$store.dispatch('getUserInfo', {
// aud_id: that.user.aud_id
// })
that.inputvalue = ""
})
},
onTap() {
this.inputvalue = ""
this.$store.commit("changeTarget", false);
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 999;
.bg {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
z-index: 99;
}
.edit {
width: 15rem;
height: auto;
background: #fff;
border-radius: 10px;
padding: 15px;
position: relative;
top: 15%;
margin: auto;
z-index: 99999;
overflow: hidden;
.title {
text-align: center;
width: 100%;
color: #333;
font-size: 36rpx;
font-weight: bold;
}
.editem {
position: relative;
display: flex;
font-size: 32rpx;
border-radius: 10px;
margin-top: 15px;
height: 40px;
justify-content: space-between;
background: #eee;
padding: 0px 10px;
align-items: center;
.radioimg {
font-size: 44rpx;
color: $btncolor;
}
.radio {
width: 50%;
text-align: right;
display: flex;
justify-content: center;
align-items: center;
}
.name {
width: 4rem;
color: #333;
}
.right {
width: 60%;
height: 40px;
line-height: 40px;
text-align: right;
display: flex;
justify-content: flex-end;
input {
margin-right: 10px;
height: 40px;
line-height: 40px;
text-align: left;
}
picker {
width: 100%;
text-align: left;
}
.uni-input {
position: absolute;
right: 30px;
top: 0;
left: 0;
height: 40px;
line-height: 40px;
text-align: right;
z-index: 9999;
}
}
.value {
position: relative;
z-index: 999;
width: 3rem;
text-align: center;
float: left;
margin-right: 10px;
}
.iconfont {
margin-left: 10px;
float: right;
display: flex;
}
}
.tips {
font-size: 28rpx;
color: #999;
text-align: center;
margin-top: 15px;
margin-bottom: 20px;
display: flex;
}
}
}
.btn {
width: 40%;
float: right;
margin-top: 15px;
background: $maincolor !important;
}
.edit {
top: 20%
}
.close {
background: #dfdfdf !important;
float: left;
}
</style>

View File

@ -0,0 +1,160 @@
<template>
<view class="cardContent">
<view class="cardList">
<view class="caritem" @click="handleNavTol('/pageTwo/charts/charts')">
<image src="/static/q5.png"></image>
<view>曲线</view>
</view>
<view class="caritem" @click="handleNavTol('/pageTwo/compk/contrast')">
<image src="/static/q3.png"></image>
<view>数据对比</view>
</view>
<view class="caritem" @click="handlerRecord">
<image src="/static/q7.png"></image>
<view>添加记录</view>
</view>
<view class="caritem" @click="handleNavTol('/pageTwo/history/history')">
<image src="/static/q8.png"></image>
<view>历史记录</view>
</view>
</view>
<view class="cardListchosen">
<view class="item" v-for="(ite,ind) in cardList.chosen_fixed" @click="handleNavTol(ite.page_jump)">
<view class="info">
<image :src="ite.icon"></image>
<view>{{ite.name}}</view>
</view>
</view>
<view class="item" v-for="(ite,ind) in cardList.chosen_yes" @click="handleNavTol(ite.page_jump)">
<view class="info">
<image :src="ite.icon"></image>
<view>{{ite.name}}</view>
</view>
</view>
</view>
<view class="setCard" @click="handleSetCard">
工具设置
</view>
<!-- 手动记录 -->
<record></record>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import record from '@/components/cardIndex/record.vue';
export default {
data() {
return {
}
},
components: {
record,
},
computed: {
...mapState(['user', "CardList"]),
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
cardList() {
return this.CardList
}
},
methods: {
//
handlerRecord() {
this.$store.commit('changeTsPublicRecord', true)
},
handleNavTol(url) {
uni.navigateTo({
url: url
})
},
handleSetCard() {
uni.navigateTo({
url: "/pageTwo/cardList/card"
})
}
}
}
</script>
<style scoped lang="scss">
.cardContent {
background: #F7F7F7;
padding: 5px 0 15PX;
}
.cardList {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
background: #fff;
margin: 10px;
padding: 10px;
border-radius: 10px;
.caritem {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 40px;
height: 40px;
}
}
}
.cardListchosen {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin: 15px 10px;
.item {
width: 47%;
background-color: #fff;
margin-bottom: 15px;
position: relative;
height: 60px;
line-height: 60px;
border-radius: 5px;
.info {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
image {
width: 30px;
height: 30px;
background-color: #f2f2f2;
margin-right: 10px;
border-radius: 50%;
}
}
}
}
.setCard {
color: $maincolor;
margin: 15px;
text-align: center;
border: 1px solid $maincolor;
border-radius: 10px;
height: 40px;
line-height: 40px;
background: #fff;
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<view class="wrapper" v-if="isPublicRecord">
<view class="bg" @click="onTap">
<view class="edit" @click.stop>
<view class="title">添加记录</view>
<view class="editem">
<view class="left">选择项目</view>
<view class="right">
<picker mode="selector" @change="bindPickerChange" :value="index" :range="publicRecordInfo"
range-key="name">
<view class="uni-input">{{index!=-1?publicRecordInfo[index].name:'请选择'}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<!-- 项目内容 -->
<view class="editem" v-for="(ite,ind) in RecordList">
<view class="left">{{ite.name}}</view>
<!-- 日期选择器 -->
<view class="right" v-if="ite.type == 'date'">
<picker mode="date" :end="endDate" @change="changeLog($event,ind)" :fields="fields"
:value="RecordList[ind].value">
<view class="uni-input">
{{RecordList[ind].value?RecordList[ind].value:'请选择'}}
</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
<!-- 分秒选择器 -->
<view class="right" v-if="ite.type == 'multiSelector'">
<picker mode="multiSelector" :value="timesTndex" :range="timeList"
@change="bindTimeChange($event,ind)">
<view class="size16">
{{RecordList[ind].value?RecordList[ind].value:'请选择'}}{{ite.unit}}
</view>
</picker>
<uni-icons type="bottom" class="ml-15 c666"></uni-icons>
</view>
<!-- 数字输入框 -->
<view class="right" v-if="ite.type == 'number'">
<input type="number" v-model="ite.value" placeholder="请输入">{{ite.unit}}
</view>
<!-- 小数输入框 -->
<view class="right" v-if="ite.type == 'digit'">
<input type="digit" v-model="ite.value" placeholder="请输入">{{ite.unit}}
</view>
<!-- 文本输入框 -->
<view class="right" v-if="ite.type == 'text'">
<input type="text" v-model="ite.value" placeholder="请输入">{{ite.unit}}
</view>
</view>
<view class="btn close" @click="onTap()">取消</view>
<view class="btn" @click="handlepublicmeasure">确定</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
//
fields: "",
index: 0,
timeList: [],
RecordList: [],
timesTndex: [1, 0],
}
},
computed: {
...mapState(["user", "isPublicRecord", "PublicRecord"]),
userInfo() {
return this.user
},
publicRecordInfo() {
let that = this
return that.PublicRecord
},
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
mounted() {
let that = this
that.timeList = that.$tools.gethms()
that.RecordList = that.PublicRecord[that.index].list
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
methods: {
bindPickerChange(e) {
let that = this
for (let i = 0; i < that.RecordList.length; i++) {
that.RecordList[i].value = ""
}
that.index = e.detail.value
that.RecordList = that.publicRecordInfo[that.index].list
},
changeLog(e, ind) {
let that = this
that.RecordList[ind].value = e.detail.value
},
bindTimeChange(e, ind) {
let that = this
let m = e.target.value[0]
let s = e.target.value[1]
that.timesTndex = e.target.value
that.RecordList[ind].value = that.timeList[0][m].substring(0, 2) + ':' + that.timeList[1][s].substring(0,
2)
},
handlepublicmeasure() {
let that = this
if (that.index == -1) {
that.$tools.msg("请选择项目")
return
}
for (let i = 0; i < that.RecordList.length; i++) {
if (that.RecordList[i].value == "") {
that.$tools.msg("请输入正确信息")
break;
}
break;
}
that.$model.getpublicmeasure({
aud_id: that.user.aud_id,
type: that.publicRecordInfo[that.index].key_word,
data: that.RecordList
}).then(res => {
if (res.code != 0) return
that.$tools.msg(res.msg)
that.onTap()
})
},
onTap() {
let that = this
that.index = 0
that.RecordList = that.publicRecordInfo[that.index].list
for (let i = 0; i < that.RecordList.length; i++) {
that.RecordList[i].value = ""
}
that.$store.commit("changeTsPublicRecord", false);
},
}
}
</script>
<style scoped lang="scss">
.btn {
width: 40%;
float: right;
margin-top: 15px;
background: $maincolor !important;
}
.close {
background: #dfdfdf !important;
float: left;
}
picker {
font-size: 32rpx !important;
}
</style>

View File

@ -0,0 +1,458 @@
<template>
<view class="weightPages">
<view class="table">
<view class="text">
<image src="/static/zhong.png"></image>
<text @click="openBluetoothAdapter">{{bleTipsText}}</text>
</view>
<view class="duan" @click="handleBack" v-if="isShow&&isConnection == 2">
断开连接
</view>
</view>
<view class="name">{{name}}</view>
<!-- 蓝牙称重 -->
<view class="weight-wrap">
<view class="weight" @click="inputDialogToggle">
<text class="val">{{weight == '' ? '0.0':weight}}</text>
<text class="unit">{{unitConversion(unit)}}</text>
</view>
<view class="weight">
<text class="val">{{kcal?kcal:0}}</text>
<text class="unit">kcal</text>
</view>
</view>
<view class="groupbtn">
<view class="btn danwei">
<view class="lan border-bottom">
<view class="right">
<picker mode="selector" :range="unitA" range-key="name" @change="changleUnits"
:value="unitListIndex">
<view class="uni-input">
单位
</view>
</picker>
</view>
</view>
</view>
<view class="btn" @click="handleDetailSub" :style="{'width':!stopblue?'20%':'45%'}">保存</view>
<view class="btn" @click="handleDetailNext" v-if="!stopblue">下一味</view>
<view class="btn qingling" @click="handleqingling">清零</view>
</view>
<view>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog mode="input" title="重量" placeholder="请输入食物重量" @close="close"
@confirm="confirm"></uni-popup-dialog>
</uni-popup>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
kcal: "",
weightALL: "",
bleTipsText: "",
unit: 'g',
unitA: [],
stopblue: false,
unitListIndex: 0,
}
},
props: {
weightKcal: {
type: Number,
default: 0 //gkcal
},
isLast: {
type: Boolean,
default: false
},
name: {
type: String,
default: ''
}
},
computed: {
...mapState(["user", "isBluetoothTyle", "bleValue"]),
weight() {
let weightNew = this.convertToGrams(this.bleValue.countWeight, this.bleValue.unit)
this.kcal = (Number(this.weightKcal) * weightNew).toFixed(1)
this.unit = this.bleValue.unit
return this.bleValue.countWeight
},
isConnection() {
this.bleTipsText = this.bleValue.bleTipsText
return this.bleValue.isConnectStatus
},
isShow() {
return this.bleValue.serviceId != '' ? true : false
},
},
mounted() {
let that = this
uni.onBluetoothAdapterStateChange(function(res) {
// that.$store.commit("changeBluetooth", res.available);
})
},
watch: {
isBluetoothTyle: function() {
let that = this
if (!that.isBluetoothTyle) {
that.handleBack()
}
},
isLast: {
handler(newVal, oldVal) {
this.stopblue = newVal
},
// deep: true,
immediate: true
},
bleValue: {
handler(newVal, oldVal) {
this.unitA = newVal.unitList
this.unitListIndex = newVal.unitList.findIndex(ite => ite.unit == newVal.unit)
},
deep: true,
immediate: true
},
},
methods: {
//
openBluetoothAdapter() {
let that = this
if (that.isConnection == 0) return
that.kcal = ""
that.$store.commit('changeBluetoothValue', {
deviceId: "",
serviceId: "",
notify: '',
write: '',
unit: "g",
type: 1,
unitList: that.$json.unitMinus,
countWeight: "",
bleTipsText: "蓝牙搜索中",
isConnectStatus: 0,
})
that.$ble.openBluetoothAdapter()
},
changleUnits(e) {
let that = this
let name = that.unitA[e.detail.value].name
let val = that.unitA[e.detail.value].unit
if (that.isShow && that.unit != name) {
that.handletoggleUnit(that.unitConversion2(val))
}
if (that.bleValue.serviceId == '') {
that.unitListIndex = [e.detail.value]
that.$store.commit('changeBluetoothValue', {
unit: that.unitA[e.detail.value].unit,
})
}
},
convertToGrams(value, fromUnit) {
const conversionFactors = {
'lb': 453.59237, // 1 = 453.59237
'oz': 28.349523125, // 1 = 28.349523125
'kg': 1000, // 1 = 1000
'g': 1,
'ml': 1,
'斤': 500,
'Waterml': 1,
'milkml': 1.03
};
if (!conversionFactors.hasOwnProperty(fromUnit)) {
return ''
}
return value * conversionFactors[fromUnit];
},
unitConversion2(unit) {
if (unit == 'kg') {
return 0x00
} else if (unit == '斤') {
return 0x01
} else if (unit == 'st:lb') {
return 0x02
} else if (unit == 'lb') {
return 0x03
} else if (unit == 'g') {
return 0x04
} else if (unit == 'ml') {
return 0x05
} else if (unit == 'Waterml') {
return 0x06
} else if (unit == 'milkml') {
return 0x07
} else if (unit == 'oz') {
return 0x08
} else if (unit == 'floz') {
return 0x09
} else if (unit == 'lboz') {
return 0x0A
}
return unit
},
//
handletoggleUnit(unit) {
let that = this
let checksum = 0;
const bytes = [0xC5, 0x03, 0x05, 0x11]
bytes[4] = unit
for (let i = 0; i < bytes.length; i++) {
checksum ^= bytes[i];
}
bytes[5] = checksum
that.sendData(new Uint8Array(bytes).buffer)
},
handleqingling() {
let that = this
let str = "C503071100D0"
let buf = new Uint8Array(str.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
}))
that.sendData(buf.buffer)
},
sendData(buffer) {
let that = this
uni.writeBLECharacteristicValue({
deviceId: that.bleValue.deviceId,
serviceId: that.bleValue.serviceId,
characteristicId: that.bleValue.write,
value: buffer,
success: res => {
console.log('下发指令成功', res.errMsg)
},
fail: res => {
console.log("下发指令失败", res);
},
})
},
handleBack() {
let that = this
that.$store.commit("changeBluetoothValue", {
bleTipsText: "连接失败,点击重新连接",
unitList: that.$json.unitMinus,
isConnectStatus: 1,
})
that.$ble.stopBluetoothDevicesDiscovery() //
that.$ble.closeBLEConnection(that.bleValue.deviceId)
that.$ble.closeBluetoothAdapter()
},
unitConversion(unit) {
if (unit == 'kg') {
return '千克'
} else if (unit == '斤') {
return '斤'
} else if (unit == 'stlb') {
return 'stlb'
} else if (unit == 'lb') {
return '磅'
} else if (unit == 'g') {
return '克'
} else if (unit == 'ml') {
return '毫升'
} else if (unit == 'Waterml') {
return 'Waterml'
} else if (unit == 'milkml') {
return 'milkml'
} else if (unit == 'oz') {
return '盎司'
} else if (unit == 'floz') {
return 'floz'
} else if (unit == 'lboz') {
return 'lboz'
}
return unit
},
//
handleDetailSub() {
let that = this
if (Number(that.weight) > 0) {
that.$emit("handleDetailSub", that.weight, that.unit, that.kcal)
} else {
that.$tools.msg("数据异常,请清零后重新测量!")
}
},
//
handleDetailNext() {
let that = this
if (Number(that.weight) > 0) {
that.$emit("handleDetailNext", that.weight, that.unit, that.kcal)
} else {
that.$tools.msg("数据异常,请清零后重新测量!")
}
},
confirm(value) {
console.log(value)
let that = this
this.$store.commit("changeBluetoothValue", {
countWeight: value,
unit: this.unitA[this.unitListIndex].unit
})
this.$refs.popup.close()
},
close() {
this.$refs.popup.close()
},
inputDialogToggle() {
this.$refs.popup.open()
},
},
}
</script>
<style scoped lang="scss">
.table {
width: 100%;
font-size: 14px;
align-items: center;
padding: 5px 0;
border-radius: 5px;
display: flex;
justify-content: space-between;
.text {
color: #8284f0;
display: flex;
image {
width: 22px;
height: 22px;
margin-right: 5px;
}
}
}
.weightPages {
position: absolute;
left: 15px;
right: 15px;
bottom: 30rpx;
top: 15px;
display: flex;
flex-direction: column;
justify-content: space-between;
.weight {
background: #fff;
color: #666;
font-size: 16px;
flex-wrap: wrap;
text-align: center;
view {
width: 60%;
height: 100rpx;
display: flex;
margin-left: 25%;
align-items: flex-end;
margin-bottom: 30rpx;
text {
width: 80px;
display: inline-block;
border-bottom: 1px solid #dfdfdf;
margin: 0 20rpx;
font-size: 18px;
font-weight: bold;
color: #f0ae43;
}
}
}
.tips {
font-size: 24rpx;
text-align: center;
}
.groupbtn {
margin-top: 0;
display: flex;
align-items: center;
justify-content: space-between;
.btn {
width: 20%;
color: $maincolor;
text-align: center;
height: 40px;
line-height: 40px;
border-radius: 10px;
border: 1px solid $maincolor;
background: #fff;
margin: 0;
view {
border: none !important;
width: auto !important;
height: 40px !important;
line-height: 40px !important;
}
}
}
.weight-wrap {
display: flex;
justify-content: space-around;
align-items: center;
color: #666;
font-size: 16px;
text-align: center;
height: 60px;
border-radius: 10px;
.weight {
display: flex;
justify-content: center;
align-items: center;
width: 48%;
padding: 30rpx 0;
border-radius: 20rpx;
background-color: #F8F8F8;
.val {
font-size: 54rpx;
color: #F0AE43;
margin: 0 !important;
}
.unit {
padding: 10rpx;
margin-left: 20rpx;
font-size: 28rpx;
color: #fff;
border-radius: 8rpx;
background-color: #F0AE43;
}
}
}
.duan {
width: fit-content;
background: linear-gradient(-90deg, #d4f5c4, #a7d5e4 80%, );
border-radius: 5px;
text-align: center;
padding: 3px 10px;
font-size: 12px;
}
.name {
width: 100%;
text-align: center;
font-size: 16px;
font-weight: bold;
}
}
</style>

View File

@ -0,0 +1,451 @@
<template>
<view class="weightPages">
<view class="table">
<view class="text">
<image src="/static/zhong.png"></image>
<text @click="openBluetoothAdapter">{{bleTipsText}}</text>
</view>
<view class="duan" @click="handleBack" v-if="isShow&&isConnection == 2">
断开连接
</view>
</view>
<!-- -->
<view class="weight-wrap">
<!-- 蓝牙称重 -->
<view class="weight" @click="inputDialogToggle">
<text class="val">{{weight == '' ? '0.0':weight}}</text>
<text class="unit">{{unitConversion(unit)}}</text>
</view>
</view>
<!-- -->
<view class="groupbtn">
<view class="btn danwei">
<view class="lan border-bottom">
<view class="right">
<picker mode="selector" :range="unitA" range-key="name" @change="changleUnits"
:value="unitListIndex">
<view class="uni-input">
单位
</view>
</picker>
</view>
</view>
</view>
<view class="btn addbtn size14" @click="handlesub" v-if="btnType==2">保存</view>
<view class="btn addbtn" @click="handleAddFood" v-if="btnType==1">+</view>
<view class="btn qingling" @click="handleqingling">清零</view>
</view>
<!-- -->
<view>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog mode="input" title="重量" placeholder="请输入食物重量" @close="close"
@confirm="confirm"></uni-popup-dialog>
</uni-popup>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
let myTime
let nextCnt = 0
export default {
data() {
return {
kcal: 0,
unit: '',
inputDialog: false,
unitA: [],
unitListIndex: 0,
}
},
props: {
weightKcal: {
type: Number,
default: 0 //100gkcal
},
btnType: {
type: Number,
default: 1 //12
}
},
computed: {
...mapState(["bleValue", "isBluetoothTyle", "countFoodInfo"]),
weight() {
let weightNew = this.convertToGrams(this.bleValue.countWeight, this.bleValue.unit)
this.kcal = (Number(this.weightKcal) / 100 * weightNew).toFixed(1)
this.unit = this.bleValue.unit
return this.bleValue.countWeight
},
isConnection() {
return this.bleValue.isConnectStatus
},
isShow() {
return this.bleValue.serviceId != '' ? true : false
},
bleTipsText() {
return this.bleValue.bleTipsText
}
},
mounted() {
let that = this
uni.onBluetoothAdapterStateChange(function(res) {
// that.$store.commit("changeBluetooth", res.available);
})
},
destroyed() {
// this.isConnection = 1
// this.closeBLEConnection()
// this.closeBluetoothAdapter()
},
watch: {
isBluetoothTyle: function() {
let that = this
if (!that.isBluetoothTyle) {
that.handleBack()
}
},
bleValue: {
handler(newVal, oldVal) {
this.unitA = newVal.unitList
this.unitListIndex = newVal.unitList.findIndex(ite => ite.unit == newVal.unit)
},
deep: true,
immediate: true
},
},
methods: {
//
openBluetoothAdapter() {
let that = this
if (that.isConnection == 2) return
that.kcal = ""
that.$store.commit('changeBluetoothValue', {
deviceId: "",
serviceId: "",
notify: '',
write: '',
unit: "g",
type: 1,
unitList: that.$json.unitMinus,
countWeight: "",
bleTipsText: "蓝牙搜索中",
isConnectStatus: 0,
})
that.$ble.openBluetoothAdapter()
},
changleUnits(e) {
let that = this
let name = that.unitA[e.detail.value].name
let val = that.unitA[e.detail.value].unit
if (that.isShow && that.unit != name) {
that.handletoggleUnit(that.unitConversion2(val))
}
if (that.bleValue.serviceId == '') {
that.unitListIndex = [e.detail.value]
that.$store.commit('changeBluetoothValue', {
unit: that.unitA[e.detail.value].unit,
})
}
},
convertToGrams(value, fromUnit) {
const conversionFactors = {
'lb': 453.59237, // 1 = 453.59237
'oz': 28.349523125, // 1 = 28.349523125
'kg': 1000, // 1 = 1000
'g': 1,
'ml': 1,
'斤': 500,
'Waterml': 1,
'milkml': 1.03
// "stlb": "floz": "lboz":
};
if (!conversionFactors.hasOwnProperty(fromUnit)) {
return ''
}
return value * conversionFactors[fromUnit];
},
unitConversion2(unit) {
if (unit == 'kg') {
return 0x00
} else if (unit == '斤') {
return 0x01
} else if (unit == 'st:lb') {
return 0x02
} else if (unit == 'lb') {
return 0x03
} else if (unit == 'g') {
return 0x04
} else if (unit == 'ml') {
return 0x05
} else if (unit == 'Waterml') {
return 0x06
} else if (unit == 'milkml') {
return 0x07
} else if (unit == 'oz') {
return 0x08
} else if (unit == 'floz') {
return 0x09
} else if (unit == 'lboz') {
return 0x0A
}
return unit
},
//
handletoggleUnit(unit) {
let that = this
let checksum = 0;
const bytes = [0xC5, 0x03, 0x05, 0x11]
bytes[4] = unit
for (let i = 0; i < bytes.length; i++) {
checksum ^= bytes[i];
}
bytes[5] = checksum
that.sendData(new Uint8Array(bytes).buffer)
},
//
handleqingling() {
let that = this
let str = "C503071100D0"
let buf = new Uint8Array(str.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
}))
that.sendData(buf.buffer)
},
sendData(buffer) {
let that = this
uni.writeBLECharacteristicValue({
deviceId: that.bleValue.deviceId,
serviceId: that.bleValue.serviceId,
characteristicId: that.bleValue.write,
value: buffer,
success: res => {
console.log('下发指令成功', res.errMsg)
},
fail: res => {
console.log("下发指令失败", res);
},
})
},
//
handlesub() {
let that = this
console.log("测量保存", that.weight, that.unit, that.kcal)
if (Number(that.weight) > 0) {
that.$emit("handleBle", that.weight, that.unit, that.kcal)
} else {
that.$tools.msg("数据异常,请清零后重新测量!")
}
},
handleBack() {
let that = this
that.$store.commit("changeBluetoothValue", {
bleTipsText: "连接失败,点击重新连接",
unitList: that.$json.unitMinus,
isConnectStatus: 1,
})
that.$ble.stopBluetoothDevicesDiscovery() //
that.$ble.closeBLEConnection(that.bleValue.deviceId)
that.$ble.closeBluetoothAdapter()
},
unitConversion(unit) {
if (unit == 'kg') {
return '千克'
} else if (unit == '斤') {
return '斤'
} else if (unit == 'stlb') {
return 'stlb'
} else if (unit == 'lb') {
return '磅'
} else if (unit == 'g') {
return '克'
} else if (unit == 'ml') {
return '毫升'
} else if (unit == 'Waterml') {
return 'Waterml'
} else if (unit == 'milkml') {
return 'milkml'
} else if (unit == 'oz') {
return '盎司'
} else if (unit == 'floz') {
return 'floz'
} else if (unit == 'lboz') {
return 'lboz'
}
return unit
},
//
handleAddFood() {
uni.navigateTo({
url: "/Food/count/search?name=早餐&time=" + this.countFoodInfo.date
})
},
inputDialogToggle() {
this.$refs.popup.open()
},
//
confirm(value) {
console.log(value)
let that = this
this.$store.commit("changeBluetoothValue", {
countWeight: value,
unit: this.unitA[this.unitListIndex].unit
})
this.$refs.popup.close()
},
close() {
this.$refs.popup.close()
},
},
}
</script>
<style scoped lang="scss">
image {
width: 22px;
height: 22px;
}
.more {
padding: 6rpx 10rpx;
border-radius: 12rpx;
color: #fff;
background-color: #f0ae43;
}
.weightPages {
display: flex;
flex-wrap: wrap;
flex-direction: column;
position: relative;
background: #fff;
border-radius: 20rpx;
margin: 10px;
justify-content: space-around;
.weight-wrap {
display: flex;
justify-content: center;
align-items: center;
background: #fff;
color: #666;
font-size: 16px;
text-align: center;
height: 60px;
margin: 10px 0;
.weight,
.kcal {
display: flex;
justify-content: center;
align-items: center;
width: 70%;
padding: 30rpx 0;
border-radius: 20rpx;
background-color: #F8F8F8;
}
.weight {
.val {
font-size: 54rpx;
color: #F0AE43;
margin: 0 !important;
}
.unit {
padding: 10rpx;
margin-left: 30rpx;
font-size: 28rpx;
color: #fff;
border-radius: 8rpx;
background-color: #F0AE43;
}
}
}
.tips {
font-size: 24rpx;
text-align: center;
}
.groupbtn {
margin-top: 15px;
display: flex;
align-items: center;
justify-content: space-between;
.btn {
width: 30%;
color: $maincolor;
text-align: center;
height: 40px;
line-height: 40px;
border-radius: 10px;
border: 1px solid $maincolor;
background: #fff;
margin: 0;
box-sizing: border-box;
}
.addbtn {
width: 30%;
color: #fff;
font-size: 45px;
line-height: 38px;
background: $maincolor;
}
}
.table {
width: 100%;
font-size: 14px;
align-items: center;
padding: 5px 0;
border-radius: 5px;
display: flex;
justify-content: space-between;
.text {
color: #8284f0;
display: flex;
}
image {
width: 22px;
height: 22px;
margin-right: 5px;
}
}
.image {
width: 1120rpx;
height: 1120rpx;
margin: auto;
image {
width: 100%;
height: 100%;
}
}
.duan {
width: fit-content;
background: linear-gradient(-90deg, #d4f5c4, #a7d5e4 80%, );
border-radius: 5px;
text-align: center;
padding: 3px 10px;
font-size: 12px;
}
.tips {
margin-top: 30rpx;
margin-left: 30rpx;
display: flex;
color: #999;
}
}
</style>

View File

@ -0,0 +1,470 @@
<template>
<view class="content-box">
<!-- 日期选择 -->
<view class="data">
<picker mode="date" :end="endDate" @change="changeClickDate" :value="data?data:endDate">
<view>
{{data?data:"请选择"}}
<image src="/static/qie.png"></image>
</view>
</picker>
</view>
<!-- 称重 -->
<view class="blue-tooth" v-if="user.aud_id!=''">
<blue-tooth :btnType="'1'"></blue-tooth>
</view>
<!-- 能量摄入 -->
<view class="everyDay">
<view class="title">
<view><text class="quan"></text>热量摄入</view>
<view class="icon" @click="handleSet">
<uni-icons type="gear" size="26"></uni-icons>
</view>
</view>
<view class="jishiqi">
<view class="left">
<view class="chart-wrap">
<qiun-data-charts type="arcbar" :chartData="chartData" :cHeight="280" :cWidth="280"
:canvas2d="true" canvasId="countCharts" v-if="handTrue" />
<view class="center">
摄入
<text>{{foodInfo.nutrients_four[0].today_intake}}</text>
<view class="unit">Kcal</view>
</view>
</view>
<view class="mubiao">
目标<text>{{foodInfo.nutrients_four[0].suggestion}}</text>kcal
</view>
</view>
<view class="right">
<view class="item" v-for="(ite,ind) in foodInfo.nutrients_four.slice(1)">
<view class="left-icon">
<image :src="ite.icon"></image>
<view class="val" :style="{color:ite.color}">{{ite.proportion_fp||0}}%</view>
</view>
<view class="right-info">
<view class="right-info-top">
<text class="name">{{ite.name}}</text>
<text class="">
{{ite.today_intake||0}}/{{ite.suggestion||0}}g
</text>
</view>
<view class="right-info-bottom">
<view class="val" :style="{ width: ite.proportion + '%',background:ite.color}">
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 早午晚餐 -->
<view class="foodtools">
<view class="type" v-for="(item,index) in foodInfo.list">
<view class="title" @click="handledetail(index)">
<view class="text">
<image :src="item.icon"></image>
<view>{{item.name}}</view>
<text>{{item.val}}{{item.unit}}</text>
</view>
<view class="detail">
餐食详情
<uni-icons type="forward" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="list">
<uni-swipe-action>
<uni-swipe-action-item v-for="(ite,ind) in item.list" :key="ind" :right-options="actionOptions"
@click="delAcitionItem(ite)">
<view class="item" @click="showFoodDetail(ite)">
<image :src="ite.pic_url" mode="aspectFill"></image>
<view class="weight">
<view>{{ite.name}}</view>
<view class="size12 c999">{{ite.weight}}<text>|</text>{{ite.val}}kcal</view>
</view>
</view>
</uni-swipe-action-item>
</uni-swipe-action>
<view class="add" @click="handleAddFood(item.name)">
<text>+</text>添加食物
</view>
</view>
</view>
</view>
<view class="fenxi" @click="navTo('/Food/count/everyDay?page=count')">
<image src="/static/fenxi.png"></image>
营养分析
</view>
<!-- 搜索 -->
<view class="serachBox">
<view class="title">
<view class="quan mr-5"></view>教你做
</view>
<view class="searchInput">
<div class="search-wrap" @click="navTo('/Food/search/search')">
<text>输入食材快速搜索菜谱</text>
<image src="/static/28.png"></image>
</div>
</view>
</view>
<!-- 营养含量分析 -->
<view v-if="drawerVisible" class="drawerVisible">
<view class="bgVisible" @click="handleDrawerClose"></view>
<view class="infoVisible">
<scroll-view style="height: 100%;" scroll-y="true">
<view class="foodDetail">
<view class="foodInfo">
<image :src="activeFoodDetail.pic_url" mode="aspectFill"></image>
<view class="info">
<view class="name">{{activeFoodDetail.name}}</view>
<view class="kcal">{{activeFoodDetail.val}}千卡</view>
</view>
</view>
<view class="foodContent">
<view class="title">热量和营养</view>
<view class="progress">
<div class="chart-wrap">
<qiun-data-charts v-if="chartVisible" type="ring" :opts="opts"
:chartData="chartData2" :cHeight="280" :cWidth="280" />
</div>
<view class="info" v-if="activeFoodDetail.nutrients_four">
<view class="info-item"
v-for="(item,index) in activeFoodDetail.nutrients_four.slice(1)" :key="index">
<view class="color" :style="{'background-color':`${item.color}`}">
</view>
<view>{{item.name}}{{item.proportion}}%</view>
</view>
</view>
</view>
<view class="tips">
<text>营养素</text>
<text>{{activeFoodDetail.weight}}含量</text>
</view>
<view class="foodDetailList">
<view class="foodDetailItem" v-for="(item,index) in activeFoodDetail.nutrients_list"
:key="index">
<view class="name">{{item.name_ch}}</view>
<view class="value">{{item.value}}{{item.unit}}
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import blueTooth from "@/components/foodIndex/bluetooth_food.vue"
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts.vue';
export default {
name: "food",
data() {
return {
token: "",
index: 0,
opts: {
background: "transparent",
title: {
name: "",
},
},
chartData: {
series: [{
data: 0,
color: "#3CB383"
}]
},
data: '',
isBle: true,
handTrue: false,
chartData2: {},
activeFoodDetail: {},
drawerVisible: false,
chartVisible: false, //
actionOptions: [{
text: '删除',
style: {
backgroundColor: '#dd524d',
borderRadius: '10rpx'
}
}]
};
},
components: {
blueTooth,
qiunDataCharts
},
computed: {
...mapState(["configInfo", "user", "countFoodInfo", "bleValue"]),
info() {
return this.user
},
kcalVal() {
return this.configInfo.kcal_data
},
endDate() {
return this.$tools.getDate("start")
},
foodInfo() {
let that = this
this.handTrue = false
that.$nextTick(() => {
that.handTrue = true
that.chartData.series[0].data = that.user.aud_id ? Number(that.countFoodInfo.nutrients_four[0]
.proportion) / 100 : 0
})
that.data = that.user.aud_id ? that.countFoodInfo.date : that.$tools.getDate("start")
return that.user.aud_id ? that.countFoodInfo : that.configInfo.default_count_foot
}
},
methods: {
//
showFoodDetail(item) {
console.log("item", item)
let that = this
let chart_data = []
that.drawerVisible = true;
that.opts.color = []
that.activeFoodDetail = item
for (let i = 1; i < item.nutrients_four.length; ++i) {
that.opts.color.push(item.nutrients_four[i].color)
chart_data.push({
name: item.nutrients_four[i].name,
value: Number(item.nutrients_four[i].proportion),
})
}
that.opts.title.name = that.activeFoodDetail.val
that.chartData2 = JSON.parse(JSON.stringify({
series: [{
data: chart_data
}]
}));
that.$nextTick(() => {
//
setTimeout(() => {
that.chartVisible = true
}, 100)
})
},
//
handledetail(index) {
uni.navigateTo({
url: "/Food/count/everyMeal?index=" + index
})
},
//
handleSet() {
uni.navigateTo({
url: "/Food/count/setting"
})
},
changeClickDate(e) {
this.data = e.detail.value
this.$store.dispatch("getCountFoodInfo", {
aud_id: this.info.aud_id,
time: e.detail.value
})
},
//
handleAddFood(name) {
this.isShow = false
uni.navigateTo({
url: "/Food/count/search?name=" + name + "&time=" + this.date
})
},
//
navTo(url) {
uni.navigateTo({
url
})
},
handleDrawerClose() {
//
this.chartVisible = false
this.drawerVisible = false;
},
//
delAcitionItem(item) {
let that = this
uni.showModal({
content: `是否删除[${item.name}]`,
success: (res) => {
if (res.confirm) {
this.$model.delCEatAction({
aud_id: that.info.aud_id,
eat_log_id: item.id
}).then(res => {
//
if (that.foodInfo.date == that.info.food_count.date) {
that.$store.dispatch("getUserInfoFood")
} else {
that.$store.dispatch("getCountFoodInfo", {
aud_id: that.info.aud_id,
time: that.foodInfo.date
})
}
})
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.content-box {
padding-top: 15px;
padding-bottom: 15px;
background: #f7f7f7;
}
.serachBox {
position: initial;
margin-bottom: 10px;
height: auto;
overflow: hidden;
.searchInput {
background: #fff;
padding: 10px;
width: calc(100% - 20px);
position: initial;
margin: 10px 0;
border-radius: 10px;
}
}
.data {
width: 100%;
background: #fff;
height: 90rpx;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
margin: 10px;
width: calc(100% - 20px);
border-radius: 10px;
margin-top: 0;
picker {
display: flex;
width: 100%;
height: 90rpx;
position: relative;
align-items: center;
justify-content: center;
font-weight: bold;
view {
width: 100%;
position: absolute;
left: 0;
top: 0;
display: flex;
bottom: 0;
align-items: center;
justify-content: center;
}
image {
width: 30rpx;
height: 30rpx;
margin-left: 10px;
}
}
}
.blue-tooth {
padding: 10px;
background: #fff;
border-radius: 10px;
margin: 0 10px;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
}
.foodtools {
margin-top: 30rpx;
.type {
background: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin: 0 20rpx 20rpx;
.title {
display: flex;
align-items: center;
justify-content: space-between;
image {
width: 60rpx;
height: 60rpx;
}
.text {
width: 50%;
display: flex;
align-items: center;
view {
font-size: 32rpx;
font-weight: bold;
margin: 0 20rpx;
}
}
.detail {
color: #999;
display: flex;
font-size: 24rpx;
align-items: center;
}
}
.list {
width: 100%;
margin-top: 30rpx;
.item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #f7f7f7;
image {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
border: 1px solid #f7f7f7;
}
.weight {
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 20rpx;
text {
margin: 0 20rpx;
color: #dfdfdf;
}
}
}
}
.add {
width: 100%;
margin-top: 20rpx;
text-align: center;
}
}
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<!-- 搜索 -->
<view class="serachBox">
<view class="serach-box">
<view class="searchInput">
<input placeholder="请输入..." class="city-serach-input" v-model="name" />
<icon v-if="name" class="iconfont icon-error" @click="handlecolse"></icon>
<view class="voice" v-if="voice && !name">
<uni-icons class="mic" type="mic-filled" size="26" @click="onMic"></uni-icons>
<uni-icons class="camera" type="camera-filled" size="26" @click="onCamera"></uni-icons>
</view>
</view>
<view class="searchBtn">
<view @click="handleSerach">搜索</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "search",
props: {
voice: {
type: Boolean,
default: false
}
},
data() {
return {
name: ""
};
},
methods: {
//
handleSerach() {
this.$emit("handleSearch", this.name)
},
//
handlecolse() {
this.name = ""
this.$emit("handleSearch", "")
},
onMic() {
this.$emit("mic")
},
onCamera() {
this.$emit("camera")
}
}
}
</script>
<style scoped lang="scss">
.serachBox {
height: 80rpx;
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0 30rpx 20rpx;
z-index: 99;
background-color: #f7f7f7;
.serach-box {
height: 80rpx;
border-radius: 20rpx;
position: relative;
background-color: #fff;
}
.searchInput {
position: absolute;
left: 0;
right: 120rpx;
height: 80rpx;
icon {
position: absolute;
right: 20rpx;
top: 20rpx;
display: flex;
z-index: 99999;
}
.voice {
display: flex;
justify-content: flex-end;
align-items: center;
position: absolute;
right: 20rpx;
top: 0;
bottom: 0;
width: 150rpx;
display: flex;
z-index: 99999;
.mic {
margin-right: 10rpx;
}
}
}
.searchBtn {
position: absolute;
width: 120rpx;
right: 0px;
height: 80rpx;
line-height: 80rpx;
background: $maincolor;
border-radius: 0 20rpx 20rpx 0;
text-align: center;
color: #fff;
}
input {
height: 80rpx;
padding: 0 5px;
text-align: center;
position: absolute;
left: 0px;
right: 0px;
border-radius: 20rpx;
}
.icon {
width: 100rpx;
height: 80rpx;
position: absolute;
right: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<!-- 搜索 -->
<view class="search" @click="handleSearch">
<input type="text" :placeholder="name" />
<icon v-if="name" class="iconfont icon-error" @click="handleSearch"></icon>
<image src="/static/28.png"></image>
</view>
</template>
<script>
export default {
name: "search",
data() {
return {};
},
props: {
name: {
type: String,
default: ''
}
},
methods: {
//
handleSearch() {
uni.redirectTo({
url: '/Food/search/search'
})
},
}
}
</script>
<style scoped lang="scss">
.search {
width: 100%;
position: fixed;
left: 0;
top: 0;
padding-bottom: 35px;
padding-top: 20rpx;
background-color: $maincolor;
input {
width: calc(100% - 80rpx);
background: #fff;
height: 39px;
line-height: 38px;
border-radius: 20rpx;
padding: 0 20rpx;
margin: 0 20rpx;
}
.input:hover {
box-shadow: 0 1rpx 20rpx #ccc;
}
image {
width: 50rpx;
height: 50rpx;
position: absolute;
right: 40rpx;
top: 16px;
z-index: 99;
}
}
.icon-error {
color: #888484;
position: absolute;
right: 120rpx;
top: 15px;
font-size: 24px;
}
</style>

80
content.json Normal file
View File

@ -0,0 +1,80 @@
{
"addfoodList": [{
"name": "早加餐",
"id": 3
},
{
"name": "午加餐",
"id": 4
}, {
"name": "晚加餐",
"id": 5
}
],
"unitArray": [{
"name": "千克",
"unit": "kg"
},
{
"name": "斤",
"unit": "斤"
},
{
"name": "stlb",
"unit": "stlb"
},
{
"name": "磅",
"unit": "lb"
}, {
"name": "克",
"unit": "g"
}, {
"name": "毫升",
"unit": "ml"
},
{
"name": "Waterml",
"unit": "Waterml"
},
{
"name": "milkml",
"unit": "milkml"
},
{
"name": "盎司",
"unit": "oz"
},
{
"name": "floz",
"unit": "floz"
},
{
"name": "lboz",
"unit": "lboz"
}
],
"unitMinus": [{
"name": "千克",
"unit": "kg"
},
{
"name": "磅",
"unit": "lb"
}, {
"name": "克",
"unit": "g"
}, {
"name": "毫升",
"unit": "ml"
},
{
"name": "盎司",
"unit": "oz"
}
],
"units": ["kg", "斤", "stlb", "lb", "g", "ml", "Waterml", "milkml", "oz", "floz", "lboz"]
}

View File

@ -0,0 +1,252 @@
<template>
<view id="select-ruler" class="select-ruler">
<!-- 遮罩层 -->
<view class="tap-mask" v-if="disable"></view>
<!-- 线 -->
<view class="line"></view>
<view class="row-line"></view>
<!-- 左右渐变遮罩 -->
<view class="mask mask-left"></view>
<view class="mask mask-right"></view>
<!-- 刻度尺 -->
<scroll-view :scroll-x="true" enhanced :show-scrollbar="false" @scroll="handleScroll" @scrolltolower="scrolltolower" @scrolltoupper="scrolltoupper" :scroll-left="scroll_left">
<span :style="{'margin-left': index == 0 ? `${el_width+10}px`:'','margin-right':index==max?`${el_width-20}px`:''}"
class="ruler-item span-item" v-for="(item, index) in (max - min + 1)">
<span>{{((index + min)/multiple).toFixed(point)}}</span>
</span>
</scroll-view>
</view>
</template>
<script>
export default {
name: "select-ruler",
data() {
return {
number: 0,
scroll_left: 1,
last_scroll_left: 0,
el_width: 0,
left: 0,
right: 0,
scroll: {
detail: {},
},
};
},
props: {
//
min: {
type: Number,
default: 0
},
//
max: {
type: Number,
default: 500
},
//
multiple: {
type: Number,
default: 1
},
//
defaultValue: {
type: Number,
default: 0
},
//
disable: {
type: Boolean,
default: false,
},
//
point: {
type: Number,
default: 0,
}
},
mounted() {
//
this.setDefault(this.defaultValue);
},
methods: {
//
handleScroll(e) {
this.scroll = e;
console.log(e.detail.scrollLeft)
this.number = Math.round(e.detail.scrollLeft / 10)
if (this.number > this.max) {
this.number = this.max
} else {
}
this.$emit('change', ((this.number + this.min) / this.multiple).toFixed(this.point));
},
scrolltoupper() {
this.number = 0
this.$emit('change', this.number);
},
scrolltolower() {
this.number = this.max
this.$emit('change', this.number);
},
//
initScroll() {
this.scroll_left = this.number * 10
this.last_scroll_left = this.scroll_left
},
//
setDefault(number) {
const query = wx.createSelectorQuery().in(this)
query.select('#select-ruler').boundingClientRect(rect => {
if (rect) {
this.el_width = rect.width / 2
if (number < this.min || number > this.max) {
uni.showToast({
title: `数值超出范围(${this.min/this.multiple}-${this.max/this.multiple})`,
icon: 'none'
});
}
if (number < this.min) number = this.min;
if (number > this.max) number = this.max;
this.number = number - this.min;
this.initScroll();
}
}).exec()
},
//
plusValue(step) {
this.setDefault(this.number + this.min + Math.floor(step));
},
//
reduceValue(step) {
this.setDefault(this.number + this.min - Math.floor(step));
},
}
}
</script>
<style scoped lang="scss">
.select-ruler {
width: 100%;
height: 150rpx;
margin-top: 20rpx;
position: relative;
.tap-mask {
width: 100%;
height: 100%;
position: absolute;
z-index: 10;
top: 0;
left: 0;
}
.line {
width: 10px;
position: absolute;
left: 366rpx;
top: 40rpx;
text-align: center;
&:before {
content: '';
width: 6rpx;
height: 80rpx;
background: #00BC00;
display: inline-block;
vertical-align: text-top;
}
}
.row-line {
width: 100%;
height: 1px;
background: rgba(#3A414B, .07);
top: 40rpx;
left: 0;
position: absolute;
}
.mask {
width: 150rpx;
height: 120rpx;
position: absolute;
top: 0;
z-index: 2;
pointer-events: none;
&.mask-left {
left: 0;
background-image: linear-gradient(to right, #fff, rgba(#fff, 0));
}
&.mask-right {
right: 0;
background-image: linear-gradient(to right, rgba(#fff, 0), #fff);
}
}
scroll-view {
width: 100%;
height: 100%;
white-space: nowrap;
.slot {
display: inline-block;
}
.ruler-item {
width: 10px;
text-align: center;
display: inline-block;
position: relative;
padding-top: 40rpx;
// &:first-child {
// margin-left: 370rpx;
// }
// &:last-child {
// margin-right: 370rpx;
// }
&:before {
content: '';
width: 1px;
height: 30px;
background: rgba(#3A414B, .15);
display: inline-block;
vertical-align: text-top;
}
span {
position: absolute;
top: -10rpx;
left: 50%;
transform: translateX(-50%);
color: #3D3D3D;
font-size: 18px;
display: none;
}
&:nth-child(10n+1) {
&::before {
width: 1px;
height: 40px;
}
span {
display: block;
}
}
}
}
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
</style>

334
pageTwo/bmi/bmi.vue Normal file
View File

@ -0,0 +1,334 @@
<template>
<view class="content">
<view class="editem">
<view class="name">身高</view>
<view class="right">
<input type="digit" v-model="PostData.height" placeholder="请输入身高"
placeholder-style="font-size:13px;color:#666" />cm
</view>
</view>
<view class="editem">
<view class="name">体重</view>
<view class="right">
<input type="digit" v-model="PostData.weight" placeholder="请输入体重"
placeholder-style="font-size:13px;color:#666" />kg
</view>
</view>
<view class="editem">
<view class="name">性别</view>
<view class="right radio2">
<view class="radio" @click="PostData.sex = 1">
<uni-icons :type="PostData.sex==1?'checkbox-filled':'circle'" size="24"
:color="PostData.sex==1?'#fea606':'#dfdfdf'"></uni-icons>
</view>
<view class="radio ml-15" @click="PostData.sex = 2">
<uni-icons :type="PostData.sex==2?'checkbox-filled':'circle'" size="24"
:color="PostData.sex==2?'#fea606':'#dfdfdf'"></uni-icons>
</view>
</view>
</view>
<view class="editem">
<view class="name">出生日期</view>
<view class="right">
<picker mode="date" :value="PostData.birthday" :end="endDate" @change="bindDateChange" :fields="fields">
<view class="text">{{PostData.birthday?PostData.birthday:"请选择出生日期"}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<view class="box_con mb-15" v-if="iscalced">
<view class="BMIlist">
<view class="block">
<view class="name">
BMI
</view>
<view class="val">
{{resdata.bmi?resdata.bmi:'0'}}
</view>
<view class="btnf" :style="{backgroundColor:resdata.bmilevelcolor}">
{{resdata.bmilevel}}
</view>
</view>
<view class="desc">
<view class="statuevue">
<view class="bi">
<view :style="'left:'+resdata.offset+'%'" class="peobox">
<view class="xx"></view>
</view>
<view class="item" v-for="(ite,ind) in resdata.bmilevellist" :key="ind"
:style="{backgroundColor:ite.color}">
<view class="span1">{{ite.text}}</view>
<view class="span" v-if="ind<resdata.bmilevellist.length-1">{{ite.max_val}}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="tip">
BMI是身体质量指数,是目前国际上常用的衡量人体胖瘦程度以及是否健康的一个标准.
<view class="text" v-for="(ite,ind) in configInfo.literature.bmi_evaluation"
v-if="configInfo.literature.bmi_evaluation.length">
{{ite}}
</view>
</view>
<view class="btn mt-20" @click="submit"> 立即计算</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
//
PostData: {
weight: '',
height: '',
sex: 0,
birthday: null
},
resdata: {
bmi: 0,
bmilevel: '',
bmilevelcolor: '',
bmilevellist: [],
offset: ""
},
leftval: 0,
iscalced: false,
fields: "",
}
},
computed: {
...mapState(["configInfo"]),
endDate() {
return this.$tools.getDate("start")
},
},
onLoad() {
let that = this
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
methods: {
//
bindDateChange(e) {
this.PostData.birthday = e.target.value
},
//bmi
submit() {
var that = this;
if (!that.PostData.weight) {
this.$tools.msg("请输入体重")
return;
}
if (!that.PostData.height) {
this.$tools.msg("请输入身高")
return;
}
if (!that.PostData.sex) {
this.$tools.msg("请选择性别")
return;
}
if (!that.PostData.birthday) {
this.$tools.msg("请选择年龄")
return;
}
that.$model.calcbmi(that.PostData).then(res => {
if (res.code == 0) {
this.resdata = res.data;
this.iscalced = true;
}
});
},
}
}
</script>
<style scoped lang="scss">
.content {
padding: 15px;
background-color: #fff;
}
.tip {
font-size: 24rpx;
color: #999;
line-height: 22px
}
.editem {
width: 100%;
position: relative;
display: flex;
align-items: center;
font-size: 28rpx;
border-radius: 20px;
margin-bottom: 15px;
justify-content: space-between;
background: #eee;
padding-left: 15px;
.name {
width: 30%;
float: left;
height: 40px;
line-height: 40px;
}
.right {
width: 58%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 15px;
height: 38px;
line-height: 38px;
.radio2 {
justify-content: space-between;
}
.radio {
width: 100%;
display: flex;
height: 40px;
line-height: 40px;
}
.text,
/deep/input {
position: absolute;
right: 15px;
top: 0;
left: 40%;
z-index: 9999;
color: #666;
text-align: left;
}
/deep/input {
padding-left: 0px;
margin-top: 0px;
font-size: 30rpx !important;
height: 38px;
line-height: 38px;
}
}
}
.btn {
width: 100%;
margin-top: 20px;
background-color: $maincolor;
}
.box_con {
margin: 15px 0;
padding: 10px 15px 15px;
border-radius: 10px;
display: flex;
flex-direction: column;
box-shadow: 0px 0px 5px 0px #c3c3c3;
font-size: 26rpx;
width: calc(100% - 30px);
}
.desc {
line-height: 20px;
text-align: left;
width: calc(100%-20px);
height: auto;
border-radius: 5px;
font-size: 30rpx;
color: #999;
padding: 10px;
background: #f7f7f7;
.statuevue {
height: 35px;
position: relative;
width: 100% !important;
margin: 20px auto 10px;
.bi {
display: flex;
justify-content: space-between;
width: auto;
padding-top: 10px;
.peobox {
position: absolute;
right: 0;
top: -1px;
z-index: 999;
.xx {
width: 5px;
height: 5px;
border-radius: 50%;
background: #fff;
position: absolute;
z-index: 9;
border: 2px solid #1b2086;
top: 9px;
}
}
}
.item {
position: relative;
margin: 0;
flex: 1;
height: 5px;
color: #666;
font-size: 30rpx;
.span1 {
width: 100%;
text-align: center;
position: absolute;
top: -23px;
}
.span {
margin-top: 8px;
position: absolute;
right: -8px;
}
}
}
}
.block {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
align-items: center;
}
.btnf {
background-color: #ff5656;
padding: 3px 8px;
color: #fff;
border-radius: 5px;
}
.yuanxing {
display: inline-block;
background: #f19601;
width: 8px;
height: 12px;
margin-right: 5px;
font-size: 36rpx;
}
</style>

163
pageTwo/charts/charts.vue Normal file
View File

@ -0,0 +1,163 @@
<template>
<view class="content">
<view class="tagList">
<scroll-view class="scroll-view_H" scroll-x="true">
<text class="scroll-view-item_H" v-for="(ite,ind) in publicRecordInfo"
@click="handlePageScrollTo(ite.id,ind)" :class="[ind == index?'active':'']">
{{ite.name}}
</text>
</scroll-view>
</view>
<view class="TrendPage">
<view class="listC">
<view @click="handleActive(1)" :class="[active==1?'active':'']">当天</view>
<view @click="handleActive(2)" :class="[active==2?'active':'']">月度</view>
<view @click="handleActive(3)" :class="[active==3?'active':'']">年度</view>
</view>
<view class="box">
<!-- 时间选择 -->
<view class="boxTime">
<picker mode="date" class="f-l" :value="startDate" @change="handStartTimeH"
:fields="active==1?'day':active==2?'month':'year'">
<view class="uni-input">{{time}}<uni-icons type="bottom"></uni-icons></view>
</picker>
</view>
<!-- 曲线图 -->
<view class="boxLine">
<view class="line" v-for="(item,index) in weightList">
<view v-if="item.line.categories.length">
<qiunDataCharts type="column" :chartData="item.line" :canvas2d="true" :canvasId="item.id"
:Width="340" :Height="250" :animation="false"
:opts="{enableScroll:true,xAxis:{scrollShow:false,itemCount:3}}" :ontouch="true" />
</view>
<view class="line" v-else>
<view class="nolist mb-15">
<image src="../../static/none.png"></image>
<text>暂无数据请手动添加~</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
export default {
data() {
return {
weightList: [],
handTrue: true,
active: 1,
index: 0,
time: "",
}
},
components: {
qiunDataCharts,
},
computed: {
...mapState(["user", "PublicRecord"]),
userInfo() {
return this.user
},
publicRecordInfo() {
let that = this
return that.PublicRecord
},
startDate() {
return this.$tools.getDate("start")
}
},
onLoad() {
let that = this
that.active = 1
that.index = 0
that.time = that.startDate
that.getList()
},
methods: {
handlePageScrollTo(id, ind) {
let that = this
that.index = ind
that.active = 1
that.time = that.startDate
that.getList()
},
getList() {
let list = []
let that = this
that.$model.getPublicTrendList({
aud_id: that.user.aud_id,
time: that.time,
type: that.publicRecordInfo[that.index].key_word,
}).then(res => {
if (res.code == 0) {
that.weightList = res.data
}
})
},
handleActive(ite) {
let that = this
that.handTrue = false
that.time = ite == 1 ? this.startDate : ite == 2 ? this.$tools.getDate("month") : this.$tools.getDate(
"year")
that.getList()
that.$nextTick(function() {
that.handTrue = true
})
that.active = ite
},
handStartTimeH(e) {
let that = this
that.time = e.target.value
that.getList()
},
}
}
</script>
<style scoped lang="scss">
.tagList {
width: 100%;
height: 45px;
line-height: 45px;
background: #fff;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
position: fixed;
top: 0;
z-index: 999;
.active {
color: $maincolor;
font-weight: bold;
font-size: 16px;
}
.scroll-view_H {
white-space: nowrap;
width: 100%;
.scroll-view-item_H {
display: inline-block;
height: 45rpx;
line-height: 45rpx;
text-align: center;
}
}
text {
color: #666;
padding: 0 15px;
}
}
.TrendPage{
margin-top: 50px;
width: calc(1005 - 30px);
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<view class="weightPages">
<view class="content ">
<view class="status">{{UseBlueConfig.BleConnectMsg}}</view>
<view class="status">{{bleTipsText}}</view>
<view class="text">{{text}}</view>
<view class="image">
<image src="/pageTwo/static/HC.png" class="image3"></image>
@ -54,35 +54,39 @@
}
},
computed: {
...mapState(["user", "UseBlueConfig"]),
...mapState(["user", "bleValue"]),
userInfo() {
return this.user
}
},
onUnload: function() {
let that = this
that.useBluetooth.clearBluetoothStatus()
console.log("页面返回onUnload")
bleTipsText() {
return this.bleValue.bleTipsText
}
},
onLoad(options) {
let that = this
that.weight = that.userInfo.weight
that.deviceId = options.deviceId
that.useBluetooth.createBLEConnection("FFF0")
that.useBluetooth.onBLEConnectionStateChange()
that.useBluetooth.onBluetoothAdapterStateChange()
that.$ble.createBLEConnection("FFF0")
that.$ble.onBLEConnectionStateChange()
that.$ble.onBluetoothAdapterStateChange()
},
watch: {
'UseBlueConfig.isUseConnect'() {
'bleValue.isBluetoothTyle'() {
let that = this
if (!that.UseBlueConfig.isUseConnect) {
if (!that.bleValue.isBluetoothTyle) {
that.$tools.showModal('蓝牙连接已断开,请重新连接后测量')
setTimeout(function() {
uni.switchTab({
url: "/pages/index/index"
})
}, 1000)
}
},
'UseBlueConfig.serviceId'() {
'bleValue.serviceId'() {
let that = this
if (that.UseBlueConfig.serviceId) {
that.getBLEDeviceCharacteristics(that.UseBlueConfig.serviceId)
if (that.bleValue.serviceId) {
console.log("serviceId", that.bleValue.serviceId)
that.getBLEDeviceCharacteristics(that.bleValue.serviceId)
}
},
},
@ -105,9 +109,11 @@
that.write = item.uuid
}
}
that.$store.commit("getIsUseBluetooth", {
BleConnectMsg: "蓝牙连接成功,请开始测量",
});
that.$store.commit("changeBluetoothValue", {
type: 1,
isConnectStatus: 2,
bleTipsText: "蓝牙连接成功,请上秤测量",
})
uni.notifyBLECharacteristicValueChange({
deviceId: that.deviceId,
serviceId: serviceId,
@ -196,21 +202,25 @@
adc: 0,
weight: that.weight,
height: height,
aud_id: that.userInfo.id
aud_id: that.user.aud_id,
lefthand: 0,
righthand: 0,
leftfoot: 0,
rightfoot: 0,
heart_rate: 0,
}).then(res => {
that.isHeight = false
console.log("报告", res)
if (res.code == 0) {
// that.$store.dispatch('getUserInfo', {
// aud_id: uni.getStorageSync('userid')
// })
that.$store.dispatch('getUserInfo', {
aud_id: that.user.aud_id
})
that.$tools.msg("测量成功")
} else {
that.$tools.msg("测量失败")
}
uni.switchTab({
url: "/pages/body/body"
url: "/pages/index/index"
})
})
},

View File

@ -106,7 +106,7 @@
that.handleAudioStart()
that.$Bluetooth.onBLEConnectionStateChange()
uni.onBluetoothAdapterStateChange(function(res) {
that.$store.commit("changeBluetooth", res.available);
// that.$store.commit("changeBluetooth", res.available);
})
},
onUnload: function() {

173
pageTwo/devices/PCX01.vue Normal file
View File

@ -0,0 +1,173 @@
<template>
<view class="weightPages">
<view class="content ">
<view class="title">连接成功开始测量</view>
<view class="text">{{text}}</view>
<view class="image">
<image src="/pageTwo/static/PCL.gif" class="image3"></image>
</view>
<view class="tips">
<view>提示</view>
<text>1.请确定设备已开机</text>
<text>2.请确定手机蓝牙及位置信息已打开</text>
</view>
</view>
<view class="btnGroup" v-if="!stopblue">
<view class="Blue-btn Blue-close" @click="handleBack">返回上一页</view>
<view class="Blue-btn" @click="handleGetMeasure">结束测量</view>
</view>
<!-- 手动记录 -->
<!-- <view class="wrapper" v-if="isHeight">
<view class="bg"></view>
<view class="Blue">
<view class="h4">测量结果提示</view>
<view class="Blue-box">
本次测量体重为<text>{{weight}}{{unit}}</text>
</view>
<view class="Blue-box">
上次测量身高为<input v-model="height" type="digit" placeholder="请输入身高" />cm
</view>
<view class="Blue-btn Blue-close" @click="handleBack">取消</view>
<view class="Blue-btn" @click="handleGetMeasure">保存测量结果</view>
</view>
</view> -->
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
text: "",
imp: "",
weight: "",
height: "",
deviceId: "",
unit: "kg",
Unload: false,
isHeight: false,
stopblue: true,
HeartRateList: []
}
},
computed: {
...mapState(["user", "isUseBluetooth"]),
info() {
return this.user
},
},
onUnload: function() {
let that = this
if (!that.Unload) {
that.useBluetooth.stopSearchBluetooth()
}
},
onLoad(options) {
let that = this
that.text = ""
that.deviceId = options.deviceId
that.height = that.user.height
uni.onBluetoothDeviceFound(handleDeviceFound)
},
watch: {
isUseBluetooth: function() {
let that = this
if (!that.isUseBluetooth) {
that.$tools.showModal('蓝牙连接已断开,请重新连接后测量')
}
},
},
methods: {
/**
* 发现外围设备
*/
handleDeviceFound(res) {
var that = this;
res.devices.forEach(device => {
device.advertisData = device.advertisData ? device.advertisData : ''
if (device.name.toLowerCase().indexOf('pc-x01') != -1 && device.deviceId.indexOf(
that.deviceId) != -1) {
let value = that.$tools.ab2hex(device.advertisData, "")
let data = parseInt(value.substring(2, 6), 16) / 100
let HeartRate = parseInt(value.substring(10, 12), 16)
let type = value.substring(0, 2) //d0,e0
console.log("1111111111", value, data, HeartRate)
that.text = "您的实时体重是:" + data + that.unit
if (type == "e0") {
that.text = "您的稳定体重是:" + data + that.unit
that.imp = parseInt(value.substring(6, 10), 16)
that.weight = data
if (HeartRate != 0) {
that.HeartRateList.push(HeartRate)
}
that.stopblue = false
return
}
return;
}
})
},
//
handleGetMeasure() {
let that = this
that.$tools.msg("测量结束")
that.handleBack()
console.log("结束", that.weight, that.HeartRateList)
return
if (!that.height) {
this.$tools.msg("请输入身高")
return
}
that.$model.getmeasurefunit({
adc: that.imp,
weight: that.weight + that.unit,
height: that.height,
aud_id: that.info.id
}).then(res => {
that.isHeight = false
console.log("res", res, that.imp)
if (res.code == 0) {
that.$store.dispatch("getResult", {
aud_id: uni.getStorageSync('userid')
})
that.$tools.msg("测量成功")
} else {
that.$tools.msg("测量失败")
}
setTimeout(function() {
uni.switchTab({
url: "/pages/body/body"
})
}, 200)
})
},
//
handleBack() {
let that = this
that.text = ""
that.$store.commit("getIsUseBluetooth", {
isBleLink: true,
BleBodyMsg: "开始测量",
isUseConnect: false,
});
that.useBluetooth.stopSearchBluetooth() //
uni.switchTab({
url: "/pages/body/body"
})
},
},
}
</script>
<style scoped lang="scss">
.image3 {
width: 200px !important;
height: 340px !important;
}
</style>

View File

@ -0,0 +1,284 @@
<template>
<view class="content">
<view class="top">
<text>成年身高预测</text>
为保证数据准确定请认真填写真实信息
</view>
<view class="table">
<view class="tr">
<view class="text">性别</view>
<view class="td">
<view class="radio">
<uni-icons :type="sex==1?'checkbox-filled':'circle'" @click="sex=1" size="24"
:color="sex==1?'#fea606':'#dfdfdf'"></uni-icons>
</view>
<view class="radio ml-10">
<uni-icons :type="sex==2?'checkbox-filled':'circle'" @click="sex=2" size="24"
:color="sex==2?'#fea606':'#dfdfdf'"></uni-icons>
</view>
</view>
</view>
<view class="tr">
<view class="text">出生日期</view>
<view class="td">
<picker mode="date" :end="endDate" @change="maskClick" :value="birthday?birthday:endDate"
:fields="fields">
<view class="uni-input">
{{birthday?birthday:'请选择'}}
<icon class="iconfont icon-arrow-down-bold"></icon>
</view>
</picker>
</view>
</view>
<view class="tr">
<view class="text">爸爸身高</view>
<view class="td">
<input class="input" type="digit" v-model="dadheight " placeholder="请输入">cm
</view>
</view>
<view class="tr">
<view class="text">妈妈身高</view>
<view class="td">
<input class="input" type="digit" v-model="momheight" placeholder="请输入">cm
</view>
</view>
</view>
<view class="table table2">
<view class="text">当前标准身高</view>
<view class="text">成年身高预测</view>
<view class="td">
<text>{{geneticheight}}</text>cm
</view>
<view class="td">
<text>{{adultheight}}</text>
<text class="text2" v-if="errorvalue">±{{errorvalue}}</text> cm
</view>
</view>
<view class="btn mb-15" @click="handleClick">立即计算</view>
<view class="title mt-20">
<view class="h5">如果当前实际身高当前标准身高</view>
<view class="con">孩子后天生长环境不利长高请从饮食睡眠运动情绪等方面排查加强后天因素管理让孩子处于最佳长高状态</view>
<view class="h5">如果当前实际身高当前标准身高</view>
<view class="con">孩子后天生长环境有利长高请继续保持加强后天因素管理孩子成年可比标准高10cm以上</view>
<view class="con" v-for="(ite,ind) in configInfo.literature.height_prediction"
v-if="configInfo.literature.height_prediction.length">
{{ite}}
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
sex: 1,
errorvalue: 0,
adultheight: 0,
geneticheight: 0,
dadheight: "",
momheight: "",
birthday: "",
fields: "",
}
},
computed: {
...mapState(["user", "configInfo"]),
endDate() {
return this.$tools.getDate("start")
},
},
onLoad() {
let that = this
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
methods: {
handleClick() {
let that = this
if (!that.dadheight) {
that.$tools.msg("请输入爸爸身高!");
return
}
if (!that.momheight) {
that.$tools.msg("请输入妈妈身高!");
return
}
uni.showLoading({
title: '计算中'
});
that.$model.GetPredictheight({
dadHeight: that.dadheight,
momHeight: that.momheight,
birthday: that.birthday,
sex: that.sex,
}).then(res => {
if (res.code != 0) return
setTimeout(function() {
uni.hideLoading();
}, 1000);
that.adultheight = res.data.adultheight
that.geneticheight = res.data.geneticheight
that.errorvalue = res.data.errorvalue
})
},
//
maskClick(e) {
this.birthday = e.detail.value
},
}
}
</script>
<style scoped lang="scss">
.content {
padding: 15px 10px;
background: #fff;
min-height: calc(100vh - 30px);
.radio {
display: flex;
height: 34px;
line-height: 34px;
}
picker {
width: 100%;
text-align: right;
border: none;
color: #333333;
}
.top {
width: 100%;
line-height: 30px;
font-size: 24rpx;
text-align: center;
margin-bottom: 30rpx;
color: #999;
text {
width: 100%;
display: block;
text-align: center;
font-size: 40rpx;
color: #333;
font-weight: 600;
}
}
.h5 {
font-size: 30rpx;
color: #666;
}
.con {
margin-top: 5px;
color: #999;
line-height: 22px;
font-size: 28rpx;
}
.table,
.table2 {
width: 100%;
border: 1px solid #d69231;
border-bottom: none;
box-sizing: border-box;
border-spacing: inherit;
font-size: 28rpx;
height: auto;
overflow: hidden;
border-right: none;
.tr {
width: 50%;
float: left;
.text {
width: 40%;
}
.td {
width: 60%;
}
}
.text {
float: left;
height: 35px;
line-height: 35px;
background: #ffcf85;
box-sizing: border-box;
border-bottom: 1px solid #d69231;
border-right: 1px solid #d69231;
text-align: center;
}
.td {
box-sizing: border-box;
background: #e4cdac21;
display: flex;
float: left;
height: 35px;
line-height: 35px;
padding-right: 5px;
padding-left: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-bottom: 1px solid #d69231;
border-right: 1px solid #d69231;
text-align: center;
/deep/input {
width: 95%;
height: 35px;
line-height: 35px;
font-size: 30rpx !important;
}
}
}
.table2 {
margin: 50rpx auto;
border-right: none;
text-align: center;
.text {
width: 50%;
border-right: 1px solid #d69231;
}
.td {
height: 50px;
width: 50%;
line-height: 50px;
display: inherit;
border-right: 1px solid #d69231;
.text2 {
font-size: 36rpx;
font-weight: bold
}
}
}
}
.btn {
width: 100%;
margin-top: 20px;
margin-bottom: 15px;
background-color: $maincolor;
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<view class="content">
<!-- -->
<view class="skiptop">
<!-- <view class="status" @click="$Bluetooth.handleDevicesMac(isdevice,acd_id)"><text>设备连接</text></view> -->
<view class="item">
<view class="item-ite">平均吸气肺活量<text>{{info?info.average:'--'}}</text></view>
<view class="item-ite">吸气速度<text>--</text></view>
<view class="item-ite">是否达标<text>{{info?info.level:'--'}}</text></view>
</view>
</view>
<!--自由训练 -->
<view class="box1">
<view class="time">{{info?info.time:''}}</view>
<!-- <view class="LiuS">吸气速度 <text class="cgreen ml-5">{{info?info.flow_val:'--'}}L/min</text></view> -->
<view class="item">
<view class="image">
<image src="../../static/bae.png" mode="widthFix"></image>
</view>
<view class="center mt-15">
<view class="level"></view>
<view class="level level-bg" :style="{'top':info?Number(100 - info.offset)+'%':'100%'}"></view>
<view class="level-item" v-if="info">
<view class="ite" v-for="(ite,ind) in info.list" :style="{'height':100/info.list.length+'%'}">
{{ite.text}}
</view>
</view>
</view>
</view>
<view class="val" v-if="info">
<text>{{info.average}}</text>
吸气肺活量平均值
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
LiuS: 0,
isdevice: true,
}
},
computed: {
...mapState(["MeasureLung", "user"]),
info() {
return this.MeasureLung
}
},
onLoad(options) {
let that = this
that.$store.dispatch("getLungResult", {
aud_id: that.user.aud_id
})
},
onPullDownRefresh() {
let that = this
that.$store.dispatch("getLungResult", {
aud_id: that.user.aud_id
})
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000);
},
methods: {
//
reload() {
let that = this
console.log('重新加载');
this.$nextTick(() => {
that.$store.dispatch("getLungResult", {
aud_id: that.user.aud_id
})
})
},
navTo(url) {
uni.navigateTo({
url: url
})
},
}
}
</script>
<style lang="scss" scoped>
@import "@/scss/lunging.scss";
.skiptop {
margin-top: 15px;
width: calc(100% - 40px);
}
</style>

593
pageTwo/score/score.vue Normal file
View File

@ -0,0 +1,593 @@
<template>
<view class="content">
<view class="lan box_shadow" @click="visible = true">
<view class="left">地区</view>
<view class="right">{{region?region:'请选择'}}<uni-icons type="bottom"></uni-icons></view>
</view>
<view class="lan box_shadow">
<view class="left">性别</view>
<view class="right">
<picker mode="selector" :range="sexItem" @change="onsexArr">
<view class="uni-input">{{gender==1?'男':'女'}}</view>
<icon class="iconfont icon-arrow-down-bold"></icon>
</picker>
</view>
</view>
<!-- 估分 -->
<view class="box">
<view class="title bold">本次估分成绩为</view>
<view class="charts mt-15">
<qiun-data-charts type="arcbar" :chartData="chartData" :Height="140" :Width="140" :canvas2d="true" />
<view class="name">{{score?score:'--'}}</view>
</view>
<view class="title bold Max_score" v-if="Max_score">该地区体育总成绩为:{{Max_score}}</view>
</view>
<!-- -->
<view v-if="selectllist.length" style="width: 100%;">
<view v-for="(item,index) in selectllist">
<view class="titleName bold mt-15 ml-15 size16">{{item.name}}</view>
<view class="indexCarList">
<view class="card" v-for="(ite,ind) in item.list">
<view class="title">
<view class="name ">{{ite.name}}</view>
</view>
<!-- 列表 -->
<view class="item3" v-for="(it,ik) in ite.list" :key="ik" v-if="!isresult">
<!-- 单选 -->
<uni-icons :type="it.choice_state==1?'circle-filled':'circle'"
:color="it.choice_state==1?'#FEC407':'#dfdfdf'" size="22"
v-if="Number(ite.is_choice)>=1" @click="handleActiveState(ite,it,ik)"></uni-icons>
<view class="name" @click="handleTips(it.describe)">
<text>{{it.name}}</text>
<uni-icons type="info" color="#f0ad4e" size="20" v-if="it.describe"></uni-icons>
</view>
<view class="weight">
<view class="input">
<!-- 整数类型 -->
<input type="number" v-model="it.value" confirm-type="done" placeholder="请输入"
v-if="it.type ==1" :style="{'color':it.inputStyle}"
@input="changeDisabled(it)" />
<!-- 小数类型 -->
<input type="digit" v-model="it.value" confirm-type="done" placeholder="请输入"
maxlength="4" v-else-if="it.type ==2||it.type ==3"
:style="{'color':it.inputStyle}" @input="changeDisabled(it)" />
<!-- 分秒类型 -->
<picker mode="multiSelector" :range="timeList" @change="bindTimeChange($event,it)"
v-else-if="it.type ==4" :style="{'color':it.inputStyle}"
@input="changeDisabled(it)">
<view>{{it.value?it.value:'请选择'}}</view>
<uni-icons type="down" color="#999" size="20" class="down"></uni-icons>
</picker>
<uni-icons type="clear" color="#999" v-if="it.value &&it.type !='4'"
@click="handleValue(it)" size="24" class="uni-icons"></uni-icons>
</view>
<text :style="{'color':it.inputStyle}" @input="changeDisabled(it)">{{it.unit}}</text>
</view>
</view>
<!-- 结果展示 -->
<view class="result item3" v-for="(ita,idx) in ite.list" :key="idx"
v-if="isresult&&ite.list.length>1">
<view class="name">{{ita.name}}</view>
<view class="weight">
<view style="width: 40%;">{{ita.value?ita.value:'-'}}</view>
<view class="cblue bold">{{ita.proportion_value?ita.proportion_value:'-'}}</view>
<view class="cblue bold">{{ita.total_score?ita.total_score:'-'}}</view>
</view>
</view>
</view>
</view>
</view>
<view class="gfbtn" v-if="isSports" @click="handleNewScore()">重新估分</view>
<view class="gfbtn" @click="handlescore()" v-else>开始估分</view>
</view>
<!-- -->
<view class="nolist" v-else>
<image src="../../static/none.png"></image>
<text>{{msg}}</text>
</view>
<!-- 占比 -->
<uslider></uslider>
<!-- 弹框 -->
<view class="wrapper" v-if="isDrawe">
<view class="bg" @click="onTap"></view>
<!-- 单选 -->
<view class="edit" @click.stop>
<view class="title">请选择<text class="choice">({{List.length}}{{choice}})</text></view>
<view class="item" v-for="(item, index) in List" :key="index" @click="toggle(item)">
<!-- d单选 -->
<uni-icons :type="isActive.name==item.name?'circle-filled':'circle'" size="22"
:color="isActive.name==item.name?'#FEC407':'#dfdfdf'" v-if="choice==1"></uni-icons>
<!-- 多选 -->
<uni-icons :type="isActiveNameList.indexOf(item.name)!=-1?'checkbox-filled':'circle'" size="22"
:color="isActiveNameList.indexOf(item.name)!=-1?'#FEC407':'#dfdfdf'"
v-if="choice>1"></uni-icons>
<view class="name">
<view class="overflow">
{{item.name}}
</view>
</view>
</view>
<view class="btn close" @click="onTap">取消</view>
<view class="btn" @click="handleTarget">确定</view>
</view>
</view>
<!-- -->
<view v-if="visible" class="visible" @click="visible=false">
<view @click.stop class="item">
<view class="groupBtn">
<view @click="visible=false">取消</view>
<view @click="handlesure()" class="sure">确定</view>
</view>
<picker-view @change="bindChange" :value="value" class="picker-view" :indicator-style="indicatorStyle">
<picker-view-column>
<view class="item" v-for="(item,index) in province" :key="index">{{item.name}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in city" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</view>
<!-- #ifndef APP-PLUS -->
<view class="uni-stat-tooltip" v-if="isTips">
{{tips}}
</view>
<!-- #endif -->
</view>
</template>
<script>
import uslider from "@/element/slider-fraction.vue";
import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue';
import {
mapState
} from "vuex";
export default {
data() {
return {
tips: "",
isTips: false,
chartData: {
series: [{
name: "正确率",
color: "#4687F9",
data: 0.8
}]
},
sexItem: [
"男",
"女"
],
value: [1, 0],
visible: false,
region: "",
province: [], //
city: [], //
area: [], //
indicatorStyle: `height: 45px;`,
gender: 1,
sportsList: [],
List: [],
score: 0,
Max_score: 0,
isDrawe: false,
selectllist: [],
isActive: {},
isActiveList: [],
isActiveNameList: [],
region_list: {},
timeList: [],
timesTndex: [0, 0],
isSports: false,
msg: "暂无信息",
isresult: false,
choice: 0,
isRefresh: false,
}
},
computed: {
...mapState(["user", "configInfo"]),
userId() {
return this.user.aud_id
},
},
components: {
uslider,
qiunDataCharts
},
onLoad() {
let that = this
that.province = that.configInfo.area_list
that.city = that.province[1].list
let defaultRegion = [that.province[that.value[0]].name, that.city[that.value[1]]]
that.region = defaultRegion.join(",");
that.score = 0
that.tips = ""
that.isTips = false
that.selectllist = []
that.sportsList = []
that.timeList = that.$tools.gethms()
that.getList(0)
console.log("onLoad")
},
onPullDownRefresh() {
let that = this
that.tips = ""
that.isTips = false
that.score = 0
that.choice = 0
that.Max_score = 0
that.selectllist = []
that.sportsList = []
that.isRefresh = true
that.timeList = that.$tools.gethms()
that.getList(0)
setTimeout(() => {
that.isRefresh = false
uni.stopPullDownRefresh()
}, 200);
console.log("刷新")
},
onShow() {},
watch: {
isTips() {
let that = this
if (that.isTips) {
setTimeout(() => {
that.isTips = false
}, 4000);
}
}
},
methods: {
bindChange: function(e) {
let that = this
if (e.detail.value[0] != that.value[0]) {
e.detail.value[1] = 0
}
that.value = e.detail.value
that.city = that.province[that.value[0]].list
},
handlesure() {
let that = this
let defaultRegion = [that.province[that.value[0]].name, that.city[that.value[1]]]
that.region = defaultRegion.join(",");
that.visible = false
that.score = 0
that.isresult = false
that.selectllist = []
that.sportsList = []
that.isActiveList = []
that.isActiveNameList = []
that.getList(0)
},
//
handleCityList() {
let that = this
that.value = [2, 0]
that.city = that.configInfo.area_list[2].list
},
//
getList(ind) {
let that = this
that.isresult = false
that.isSports = false
that.$model.getSportsListAll({
gender: that.gender,
parameter_data: that.region,
}).then((res) => {
console.log("全部项目", res)
if (res.code != 0) {
that.msg = res.msg
return
}
res.data.forEach(item => {
item.list.forEach(list => {
list.list.forEach(val => {
val.inputStyle = "#999"
if (list.is_choice == 2 && val.choice_state == 1) {
that.isActiveList.push(val.name)
}
})
})
})
that.score = 0
that.Max_score = 0
that.selectllist = res.data
that.chartData.series[0].data = 0.8
that.tips = ""
that.isTips = false
})
},
//
handleNewScore() {
let that = this
that.isActive = {}
that.selectllist = []
that.sportsList = []
that.isActiveList = []
that.isActiveNameList = []
that.getList(0)
},
changeDisabled(item) {
item.inputStyle = '#333'
},
//
onsexArr(e) {
this.gender = this.sexItem[e.target.value] == "男" ? 1 : 2
},
//
handleActiveState(item, ite, ind) {
let that = this
//
if (Number(item.is_choice) == 1) {
item.list.forEach(list => {
list.choice_state = 0
})
ite.choice_state = ite.choice_state == 1 ? 0 : 1
}
//
let isActive = []
if (Number(item.is_choice) >= 2) {
if (that.isActiveList.indexOf(ite.name) == -1) {
that.isActiveList.push(ite.name);
} else {
that.isActiveList.splice(that.isActiveList.indexOf(ite.name), 1);
}
if (that.isActiveList.length > 2) {
that.isActiveList.splice(0, 1)
}
item.list.forEach(list => {
list.choice_state = 0
that.isActiveList.forEach(it => {
if (list.name == it) {
list.choice_state = 1
}
})
})
}
},
bindTimeChange(e, it) {
let that = this
let minute = e.target.value[0]
let second = e.target.value[1]
it.value = that.timeList[0][minute].substring(0, 2) + ':' + that.timeList[1][second].substring(
0, 2)
},
handleTips(text) {
if (text == null || text == '') return
// #ifndef APP-PLUS
this.tips = text
this.isTips = true
// #endif
// #ifdef APP-PLUS
uni.showToast({
title: text,
duration: 4000,
icon: 'none'
})
// #endif
},
handleValue(item) {
item.value = ""
},
//
handlescore() {
let that = this
console.log("selectllist", that.selectllist)
that.selectllist.forEach(item => {
item.list.forEach(ite => {
ite.list.forEach(it => {
it.value = it.value == "" ? "0" : it.value
})
})
})
that.$model.getSportsData({
gender: that.gender,
aud_id: that.user.aud_id,
parameter_data: that.region,
result_data: that.selectllist,
}).then((res) => {
console.log("开始估分", res)
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.isSports = true
that.isresult = true
that.selectllist = res.data.list
that.score = res.data.total_score
that.Max_score = res.data.max_score
that.chartData.series[0].data = Number(res.data.total_score) / res.data.max_score
})
},
//
handleHistory(item) {
let that = this
that.List = []
that.region_list = item
that.sportsList.forEach(ite => {
if (ite.key == item.key) {
that.List = ite.list
that.choice = item.is_choice
}
})
console.log("添加项目", item)
that.isDrawe = true
},
//
toggle(item) {
let that = this
//
if (that.choice == 1) {
that.isActive = that.isActive.name == item.name ? {} : item
return
}
//
if (that.isActiveNameList.indexOf(item.name) == -1) {
that.isActiveNameList.push(item.name)
that.isActiveList.push(item)
} else {
for (var n = 0; n < that.isActiveNameList.length; n++) {
if (item.name == that.isActiveNameList[n]) {
if (that.isActiveNameList.indexOf(item.name) == -1) {
that.isActiveNameList.push(item.name)
that.isActiveList.push(item);
}
that.isActiveNameList.splice(n, 1)
that.isActiveList.splice(n, 1);
}
}
}
if (that.isActiveList.length > that.choice) {
that.isActiveList.splice(0, 1)
that.isActiveNameList.splice(0, 1)
}
console.log("isActiveList", item.name, that.isActiveList, that.region_list.list)
},
//
handleTarget() {
let that = this
that.selectllist.forEach(item => {
item.list.forEach(it => {
if (it.key == that.region_list.key) {
it.list = []
}
})
})
if (that.choice == 1) {
if (Object.keys(that.isActive).length != 0) {
that.region_list.list.push(that.isActive)
}
} else {
that.region_list.list = that.isActiveList
}
that.isDrawe = false
},
//
onTap() {
let that = this
//
if (that.choice == 1) {
if (Object.keys(that.isActive).length == 0) {
that.region_list.list = []
that.selectllist.forEach(item => {
item.list.forEach(it => {
if (it.key == that.region_list.key) {
it.list = []
}
})
})
}
}
that.isDrawe = false
console.log("取消", Object.keys(that.isActive).length, this.isActive)
},
navTo(url) {
uni.navigateTo({
url: url
})
},
}
}
</script>
<style scoped lang="scss">
@import "@/scss/score.scss";
.lan {
width: calc(100% - 40px);
display: flex;
padding: 0 10px;
margin: 15px 10px 0;
border-radius: 10px;
background: #fff;
justify-content: space-between;
align-items: center;
.left {
width: 50%;
text-align: left;
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
width: 70%;
height: 40px;
line-height: 40px;
box-sizing: border-box;
position: relative;
text-align: right;
picker {
width: 100%;
text-align: right;
border: none;
margin-right: 15px;
font-size: 32rpx;
color: #333333;
}
.iconfont {
color: #333333;
font-size: 32rpx;
position: absolute;
right: 0px;
top: 0;
}
}
}
.content {
min-height: 100vh;
padding-bottom: 15px;
background-color: #f7f7f7;
}
.Max_score {
width: 100%;
text-align: center;
}
.Lastdata {
height: 35px;
line-height: 35px;
margin-top: 15px;
font-size: 28rpx;
padding: 0 10px;
background: #FEC407 !important;
border-radius: 10px;
color: #fff;
width: 30%;
text-align: center;
}
.uni-stat-tooltip {
width: 80%;
height: auto;
overflow: scroll;
word-break: break-word;
position: fixed;
margin: auto;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
z-index: 999;
padding: 10px;
border-radius: 10px;
top: 33%;
left: 10%;
}
</style>

701
pageTwo/skiping/skip.vue Normal file
View File

@ -0,0 +1,701 @@
<template>
<view class="content skipping">
<!-- -->
<view class="skiptop">
<view class="status" @click="handleisSdevice()">
<text>{{textLink}}</text>
<image v-if="islink!=1" :class="[islink==-1?'':'icon_link']" src="../../static/zhuan.png"></image>
<image v-if="islink==1" src="../../static/dui.png"></image>
</view>
<view class="item">
<view class="item-ite">今日个数<text>{{info?info.today_jump_num:'--'}}</text></view>
<view class="item-ite">今日时长<text>{{info?info.today_jump_time:'--'}}</text></view>
<view class="item-ite">卡路里/kcal<text>{{info?info.today_jump_kcal:'--'}}</text></view>
</view>
</view>
<!-- -->
<view class="tabbar">
<view @click="active=1" :class="[active==1?'active':'']">自由跳</view>
<view @click="active=3" :class="[active==3?'active':'']">倒计数</view>
<view @click="active=2" :class="[active==2?'active':'']">倒计时</view>
</view>
<!--自由训练 -->
<view class="box1 box" v-if="active==1">
<view class="item">
<text class="item-set item-set0">自由</text>
<text class="tips">无限制</text>
</view>
<view :class="[islink==1?'start':'start Nstart']" @click="handleStart(1)">开始</view>
</view>
<!--定时训练 -->
<view class="box1 box" v-if="active==2">
<view class="item">
<view class="item-set">
<text @click="handleTimeEdit('减')"></text>
<text>{{time_m}}:{{time_s}}</text>
<text @click="handleTimeEdit('加')"></text>
</view>
<view class="tips">
<picker mode="multiSelector" :range="timeList" :value="timesTndex" @change="bindTimeChange">
<view>调整目标</view>
</picker>
</view>
</view>
<view :class="[isConnection?'start':'start Nstart']" @click="handleStart(2)">开始</view>
</view>
<!--定数训练 -->
<view class="box1 box" v-if="active==3">
<view class="item">
<view class="item-set">
<text @click="handleWeightEdit('减')"></text>
<text v-if="!disabled">{{weight>50?weight:50}}</text>
<input type="number" v-model="weight" focus="true" v-else @blur="disabled=false">
<text @click="handleWeightEdit('加')"></text>
</view>
<text class="tips" @click="disabled=true">调整目标</text>
</view>
<view :class="[isConnection?'start':'start Nstart']" @click="handleStart(3)">开始</view>
</view>
<!-- -->
</view>
</template>
<script>
let myTime;
import {
mapState
} from "vuex";
export default {
data() {
return {
active: 1,
acd_id: 6,
weight: 50,
disabled: false,
isDevice: 0,
isConnection: false,
time_m: "",
time_s: "",
timeList: [],
devicesList: [],
timesTndex: [1, 0],
deviceId: "",
serviceId: "",
write: "",
notify: "",
islink: 0, //01-1
textLink: ""
}
},
computed: {
...mapState(['user', "MeasureSkip", "isConnected", "isBluetoothTyle"]),
info() {
return this.MeasureSkip
},
},
mounted() {
let that = this
that.timeList = that.$tools.gethms()
that.time_m = that.timeList[0][1].substring(0, 2)
that.time_s = that.timeList[1][0].substring(0, 2)
},
onLoad(options) {
let that = this
//
if (options && options.deviceId) {
that.deviceId = options.deviceId
setTimeout(function() {
that.createBLEConnection()
}, 500)
}
if (options && options.acd_id) {
that.$Bluetooth.stopBluetoothDevicesDiscovery()
setTimeout(function() {
that.handleisSdevice()
}, 500)
}
that.$store.dispatch("getSkipResult", {
aud_id: that.user.aud_id
})
that.isConnection = that.isConnected
that.onBLEConnectionStateChange()
uni.onBluetoothAdapterStateChange(function(res) {
that.$store.commit("changeBluetooth", res.available);
})
},
onPullDownRefresh() {
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000);
},
onShow() {
let that = this
//
uni.$on('updateData', function(info) {
let data = JSON.parse(info)
that.acd_id = data.acd_id
that.isDevice = data.device
that.active = data.active
that.deviceId = data.deviceId
that.serviceId = data.serviceId
that.write = data.write
that.notify = data.notify
that.islink = !that.isConnected ? -1 : 1
that.isConnection = that.isConnected
that.notifyBLECharacteristicValue()
if (data.isSuccessful) {
that.$store.dispatch("getSkipResult", {
aud_id: that.user.aud_id
})
}
that.onBLEConnectionStateChange()
uni.onBluetoothAdapterStateChange(function(res) {
that.$store.commit("changeBluetooth", res.available);
})
console.log('监听到事件来自 updateData', data);
})
},
onUnload: function() {
let that = this
clearTimeout(myTime);
that.islink = -1
that.isConnection = false
that.closeBLEConnection()
that.closeBluetoothAdapter()
uni.$off("updateData")
setTimeout(() => {
uni.switchTab({
url: '/pages/home/home'
})
}, 300)
console.log("返回首页onUnload")
},
watch: {
isConnected() {
let that = this
if (!that.isConnected) {
that.islink = -1
that.textLink = "重新连接"
}
that.isConnection = that.isConnected
console.log("蓝牙是否连接", that.isConnected)
},
isBluetoothTyle() {
let that = this
if (!that.isBluetoothTyle) {
that.isConnection = false
that.textLink = "请打开手机蓝牙"
that.islink = -1
}
console.log("蓝牙是否打开", that.isBluetoothTyle)
}
},
methods: {
//
handleisSdevice() {
let that = this
if (that.isConnected) return
that.devicesList = []
that.$Bluetooth.stopBluetoothDevicesDiscovery()
uni.openBluetoothAdapter({
success: e => {
that.islink = 0
that.handleMyTime()
that.textLink = "蓝牙搜索中"
that.startBluetoothDeviceDiscovery()
that.$store.commit("changeBluetooth", true)
console.log('初始化蓝牙成功:' + e.errMsg);
},
fail: e => {
that.islink = -1
that.textLink = that.$tools.getBluetoothAdapter(e)
return
}
});
},
//
startBluetoothDeviceDiscovery() {
let that = this
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
interval: 200, //
services: [],
success: res => {
that.onBluetoothDeviceFound();
},
fail: res => {}
});
},
/**
* 发现外围设备
*/
onBluetoothDeviceFound() {
var that = this;
uni.onBluetoothDeviceFound(res => {
res.devices.forEach(device => {
if (!device.name && !device.localName) {
return
}
if (device.name.indexOf('YPC') != -1) {
device.deviceId = device.deviceId
that.deviceId = device.deviceId
that.$Bluetooth.stopBluetoothDevicesDiscovery()
that.handleDevice(device)
return
}
})
});
that.handleMyTime()
},
handleDevice(device) {
let that = this
const foundDevices = that.devicesList
const idx = that.$tools.inArray(foundDevices, "deviceId", device.deviceId)
if (idx === -1) {
that.devicesList.push(device);
that.createBLEConnection()
}
},
//
createBLEConnection() {
let that = this;
uni.createBLEConnection({
deviceId: that.deviceId,
success: res => {
that.textLink = "蓝牙连接中"
setTimeout(function() {
that.islink = 1
that.getBLEDeviceServices()
}, 1000)
},
fail: res => {
that.textLink = "重新连接"
console.log("设备连接失败,请重新连接", res, that.deviceId);
}
});
},
/**
* 获取设备的UUID
*/
getBLEDeviceServices() {
let serviceList = [];
let that = this;
uni.getBLEDeviceServices({
deviceId: that.deviceId,
success: res => {
console.log("获取设备的UUID成功", res)
serviceList = res.services;
for (let i = 0; i < serviceList.length; i++) {
let service = serviceList[i];
if (service.uuid.indexOf("FFE0") != -1) {
that.serviceId = service.uuid;
that.isConnection = true
that.textLink = "连接成功"
that.getBLEDeviceCharacteristics();
console.log("设备的FFE0的serviceId " + that.serviceId);
break;
}
}
},
fail: res => {
console.log('获取设备的UUID失败:', res)
that.islink = -1
that.textLink = "重新连接"
clearTimeout(myTime);
that.isConnection = false
}
});
},
/**
* 获取指定服务的特征值
*/
getBLEDeviceCharacteristics() {
let characteristicsList = [];
let that = this;
uni.getBLEDeviceCharacteristics({
deviceId: that.deviceId,
serviceId: that.serviceId,
success: res => {
console.log("服务的特征值成功", res)
// * read: true, //,write: true, //,notify: true
for (let i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i];
if (item.uuid.indexOf('0000FF12') != -1) {
that.write = item.uuid
} else if (item.uuid.indexOf('0000FFE4') != -1) {
that.notify = item.uuid
}
}
setTimeout(function() {
let j = Number(165 + 10 + 1 + 8 + 8 + 8 + 8 + 8 + 8).toString(16)
let str = "A50A01080808080808" + j.substr(j.length - 2, 2)
that.SendData(str)
}, 300)
//
setTimeout(function() {
that.handleStart(4)
}, 600)
uni.notifyBLECharacteristicValueChange({
deviceId: that.deviceId,
serviceId: that.serviceId,
characteristicId: that.notify,
state: true,
})
uni.notifyBLECharacteristicValueChange({
deviceId: that.deviceId,
serviceId: that.serviceId,
characteristicId: that.write,
state: true,
})
that.notifyBLECharacteristicValue()
},
fail: res => {
console.log('获取特征值失败:', JSON.stringify(res))
}
})
},
notifyBLECharacteristicValue() {
let that = this;
uni.notifyBLECharacteristicValueChange({
state: true, // notify
deviceId: that.deviceId,
serviceId: that.serviceId,
characteristicId: that.notify,
success(res) {
uni.onBLECharacteristicValueChange(function(res) {
let value = that.$tools.ab2hex(res.value, "");
if (value == '5a05090169') { //
let info = {
active: that.active,
deviceId: that.deviceId,
serviceId: that.serviceId,
notify: that.notify,
write: that.write,
acd_id: that.acd_id,
isDevice: that.isDevice,
isSuccessful: false
}
setTimeout(function() {
uni.$off("updateData")
uni.navigateTo({
url: "/pageTwo/devices/PCT01?info=" + JSON
.stringify(info)
})
}, 200)
}
console.log("value", value, that.active)
})
}
})
},
//
SendData(str) {
let that = this
let buf = new Uint8Array(str.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
}))
console.log("xiafa", str)
uni.writeBLECharacteristicValue({
deviceId: that.deviceId,
serviceId: that.serviceId,
characteristicId: that.write,
value: buf.buffer,
success: res => {
console.log('下发指令成功', res.errMsg)
},
fail: res => {
console.log("下发指令失败", res);
},
})
},
//
handleStart(ind) {
let that = this
let m = null
let send = null
if (!that.isConnected) {
that.$tools.msg("请先连接设备!")
return
}
if (ind == 1) { // 1
m = Number(165 + 8 + 9).toString(16)
send = "A5080900000000" + m.substr(m.length - 2, 2)
}
if (ind == 2) { //2
let time = Number(that.time_m) * 60 + Number(that.time_s)
m = Number(165 + 8 + 9 + time).toString(16)
send = "A508090000" + that.$tools.toHex(time, 4) + m.substr(m.length - 2, 2)
}
if (ind == 3) { //3
let weight = that.$tools.toHex(that.weight, 4)
m = Number(165 + 8 + 9 + that.weight).toString(16)
send = "A50809" + weight + "0000" + m.substr(m.length - 2, 2)
}
if (ind == 4) { //
let weight = Number(that.user.weight) * 2
let num = parseInt(weight).toString();
m = Number(165 + 5 + 8 + Number(num)).toString(16)
send = "A50508" + Number(num).toString(16) + m.substr(m.length - 2, 2)
}
that.SendData(send)
that.$Bluetooth.stopBluetoothDevicesDiscovery()
},
//
reload() {
let that = this
this.$nextTick(() => {
that.$store.dispatch("getSkipResult", {
aud_id: that.user.aud_id
})
})
},
//
//
bindTimeChange(e) {
let that = this
let m = e.target.value[0]
let s = e.target.value[1]
that.timesTndex = e.target.value
let time_m = Number(that.timeList[0][m].substring(0, 2)) * 60
let time_s = Number(that.timeList[1][s].substring(0, 2))
if (Number(time_m + time_s) < 30) {
that.time_m = '00'
that.time_s = '30'
} else {
that.time_m = that.timeList[0][m].substring(0, 2)
that.time_s = that.timeList[1][s].substring(0, 2)
}
},
handleTimeEdit(text) {
let that = this
let time_m = Number(that.time_m) * 60
let time_s = Number(that.time_s)
let minutes = null
let seconds = null
if (Number(time_m + time_s) >= 3570 && text == '加') {
that.time_m = '59'
that.time_s = '59'
return
}
if (Number(time_m + time_s) <= 30 && text == '减') {
that.time_m = '00'
that.time_s = '30'
return
}
if (text == '减') {
minutes = Math.floor((Number(time_m + time_s - 30) % 3600) / 60)
seconds = Number(time_m + time_s - 30) % 60
}
if (text == '加') {
minutes = Math.floor((Number(time_m + time_s + 30) % 3600) / 60)
seconds = Number(time_m + time_s + 30) % 60
}
that.time_m = minutes > 9 ? minutes : '0' + minutes;
that.time_s = seconds > 9 ? seconds : '0' + seconds;
that.timesTndex = [Number(that.time_m), Number(that.time_s)]
},
handleWeightEdit(text) {
let that = this
if (text == '减') {
that.weight = Number(that.weight) - 50 > 50 ? Number(that.weight) - 50 : 50
}
if (text == '加') {
that.weight = Number(that.weight) + 50
}
},
//
handleMyTime() {
let that = this
myTime = setTimeout(function() {
if (!that.devicesList.length) {
that.islink = -1
that.textLink = "重新搜索"
that.$tools.showModal("没有查找到设备")
}
clearTimeout(myTime)
that.$Bluetooth.stopBluetoothDevicesDiscovery()
}, 20000);
},
//
onBLEConnectionStateChange() {
let that = this
uni.onBLEConnectionStateChange(function(res) {
console.log("监听蓝牙连接状态", res.connected)
that.$store.commit("changeConnected", res.connected);
})
},
/**
* 断开蓝牙模块
*/
closeBluetoothAdapter() {
let that = this;
uni.closeBluetoothAdapter({
success: res => {
console.log('蓝牙模块关闭成功');
}
})
},
/**
* 断开蓝牙连接
*/
closeBLEConnection() {
var that = this;
uni.closeBLEConnection({
deviceId: that.deviceId,
success: res => {
console.log('断开蓝牙连接成功');
that.$store.commit("changeConnected", false);
}
});
},
}
}
</script>
<style lang="scss" scoped>
.content {
width: 100%;
min-height: 100vh;
background-color: #F5F6FA;
}
.skiptop {
width: calc(100% - 40px);
margin-top: 15px;
}
.tabbar {
width: 100%;
margin-top: 20px;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: space-around;
view {
position: relative;
}
.active:after {
content: "";
height: 3px;
width: 100%;
position: absolute;
bottom: -8px;
left: 0;
border-radius: 5px;
background: $maincolor;
}
}
.box1 {
background-color: #fff;
padding: 10px !important;
color: #999;
margin: 15px;
border-radius: 10px;
width: calc(100% - 40px);
.time {
width: 100%;
font-size: 32rpx;
margin-bottom: 20px;
}
.item {
width: 100%;
color: #333;
font-size: 44rpx;
text-align: center;
margin-bottom: 15px;
text {
display: block;
}
.item-ite {
font-size: 36rpx;
margin-bottom: 15px;
width: 100%;
}
.item-set {
width: calc(100% - 30px);
display: flex;
align-items: center;
height: 55px;
line-height: 55px;
font-size: 60rpx;
margin-bottom: 5px;
background-color: #f7f7f7;
padding: 0 15px;
justify-content: space-between;
margin: auto;
position: relative;
/deep/input {
font-size: 60rpx;
height: 55px;
line-height: 55px;
}
}
.item-set0 {
background-color: #fff;
justify-content: center
}
.tips {
font-size: 32rpx;
color: #999;
margin-top: 10px;
}
}
.start {
color: #fff;
width: 100px;
height: 100px;
font-size: 36rpx;
font-weight: bold;
line-height: 100px;
text-align: center;
border-radius: 50%;
background: $textcolor;
margin: 15px auto;
}
.Nstart {
opacity: 0.5 !important;
}
}
.status {
background-color: #fff;
image {
width: 18px;
height: 18px;
}
.icon_link {
animation: rotation 0.6s infinite linear;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
</style>

View File

@ -260,6 +260,64 @@
"style": {
"navigationBarTitleText": "记录详情"
}
},
{
"path": "devices/search",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/G02",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/PCL",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
"path": "devices/PCV02",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/PCT01",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/B20",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/PCL22",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "devices/PCL22S",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
]
}

314
pages/index/index.vue Normal file
View File

@ -0,0 +1,314 @@
<template>
<view class="box">
<!-- 头部 -->
<view class="header" v-if="token">
<view class="top" v-if="userList.length">
<image :src="info.head_pic" class="headimg" @click="handleAddUser(1)"></image>
<view class="info" @click="handleAddUser(1)">
<view class="size18 bold">{{info.nickname}}</view>
<view class="mt-5">
<text class="mr-10">性别{{info.gender==1?'男':info.gender==2?'女':'未知'}}</text>
<text class="ml-10">年龄{{info.age}}</text>
</view>
</view>
<view class="t-icon t-icon-qiehuan1" @click="$store.commit('changeDrawe', true)"></view>
</view>
<view class="top" v-else @click="handleAddUser(2)">
<view class="info">
暂无成员请先添加~
</view>
<view class="add">
+
</view>
</view>
<!-- 蓝牙 -->
<view class="bluetooth" v-if="userList.length">
<view class="bleTips" @click="openBluetoothAdapter">
{{bleTipsText}}
</view>
</view>
</view>
<view class="header" v-else @click="handleLogin">
<view class="top top2">
登录后查看更多
</view>
</view>
<!-- 数据 -->
<view class="toggle">
<view :class="{'active':toolsIndex==0}" class="toolsItem" @click="toolsIndex=0">身体数据</view>
<view :class="{'active':toolsIndex==1}" class="toolsItem" @click="toolsIndex=1">饮食数据</view>
<view :class="{'active':toolsIndex==2}" class="toolsItem" @click="toolsIndex=2">健康工具</view>
</view>
<view v-if="token">
<body v-if="toolsIndex==0"></body>
<food v-if="toolsIndex==1"></food>
<card v-if="toolsIndex==2"></card>
</view>
<view class="nolist" v-else>
<image src="/static/none.png"></image>
<text>暂无数据</text>
</view>
<!-- -->
<drawer></drawer>
</view>
</template>
<script>
import {
mapState
} from "vuex";
import body from "@/components/bodyIndex/bodyHome.vue"
import food from "@/components/foodIndex/foodHome.vue"
import card from "@/components/cardIndex/cardHome.vue"
import drawer from "@/components/bodyIndex/drawer.vue"
export default {
data() {
return {
token: "",
toolsIndex: 0
}
},
components: {
food,
body,
card,
drawer,
},
computed: {
...mapState(["user", "familayList", 'MeasureResult', "configInfo", "bleValue"]),
info() {
return this.user
},
userList() {
return this.familayList
},
endDate() {
return this.$tools.getDate("start")
},
isConnection() {
return this.bleValue.isConnectStatus
},
bleTipsText() {
return this.bleValue.bleTipsText
}
},
onLoad() {
let that = this
that.token = uni.getStorageSync("token")
that.$store.dispatch("getHomeConfig")
that.handleUserList()
that.$ble.onBLEConnectionStateChange()
uni.onBluetoothAdapterStateChange(function(res) {
that.$store.commit("changeBluetoothValue", {
isBluetoothTyle: res.available
});
})
},
onPullDownRefresh() {
let that = this
that.handleUserList()
that.$ble.onBLEConnectionStateChange()
uni.stopPullDownRefresh()
},
watch: {
isBluetoothTyle: function() {
let that = this
if (!that.isBluetoothTyle) {
that.handleBack()
}
},
},
methods: {
//
handleUserList() {
let that = this
that.$model.getUserList({
type: 2
}).then(res => {
that.isShow = true
if (res.code != 0) {
that.$tools.msg(res.msg)
return
}
that.$store.commit('changeFamilay', res.data.user_list)
if (res.data.user_list.length) {
let userid = ""
if (uni.getStorageSync('userid')) {
let found = res.data.user_list.find(e => e.id == uni.getStorageSync('userid'));
if (found !== undefined) {
userid = found.id
} else {
userid = res.data.user_list[0].id
uni.setStorageSync('userid', res.data.user_list[0].id)
}
} else {
userid = res.data.user_list[0].id
uni.setStorageSync('userid', res.data.user_list[0].id)
}
//
that.$store.dispatch('getUserInfo', {
aud_id: userid
})
//
that.$store.dispatch("getCountFoodInfo", {
aud_id: userid,
time: that.$tools.getDate("start")
})
//
that.$store.dispatch("getCardAllList", {
aud_id: userid
})
//
that.$store.dispatch("getResult", {
aud_id: userid
})
that.handlePublicRecord(userid)
that.$ble.openBluetoothAdapter()
}
}).catch(err => {})
},
//
handlePublicRecord(id) {
let that = this
that.$model.getPublicRecord({
aud_id: id
}).then(res => {
console.log("公共手动记录", res)
if (res.code == 0) {
that.$store.commit('changePublicRecord', res.data)
}
})
},
//
openBluetoothAdapter() {
let that = this
if (that.isConnection == 2) return
that.$store.commit('changeBluetoothValue', {
deviceId: "",
serviceId: "",
// notify: '',
// write: '',
// unit: "g",
// type: 1,
// unitList: that.$json.unitMinus,
// countWeight: "",
isBleLink: false, //
bleTipsText: "蓝牙搜索中",
isConnectStatus: 0,
})
that.$ble.openBluetoothAdapter()
},
handleBack() {
let that = this
that.$store.commit("changeBluetoothValue", {
bleTipsText: "连接失败,点击重新连接",
isConnectStatus: 1,
})
that.$ble.stopBluetoothDevicesDiscovery() //
that.$ble.closeBLEConnection(that.bleValue.deviceId)
that.$ble.closeBluetoothAdapter()
},
//
handleAddUser(ind) {
let that = this
if (uni.getStorageSync('token')) {
uni.navigateTo({
url: ind == 1 ? "/body/my/userInfo?info=" + JSON.stringify(this.user) : "/body/my/userInfo"
})
} else {
that.$tools("登录后查看更多")
}
},
handleLogin() {
uni.redirectTo({
url: "/pageTwo/login/login"
})
},
}
}
</script>
<style lang="scss" scoped>
.header {
width: 100%;
height: 240rpx;
background: $maincolor;
.top {
display: flex;
align-items: center;
}
.top2 {
color: #fff;
font-size: 16px;
font-weight: bold;
width: 100%;
justify-content: center;
padding-top: 15px;
}
.headimg {
width: 55px;
height: 55px;
border-radius: 50%;
margin: 0 10px;
float: left;
}
.info {
width: calc(100% - 120px);
float: left;
}
.t-icon-qiehuan1 {
float: right;
}
}
.bluetooth {
width: calc(100% - 20px);
background: #fff;
padding: 8px 0;
margin-top: 15px;
margin-left: 10px;
border-radius: 10px;
text-align: center;
}
.toggle {
display: flex;
width: calc(100% - 20px);
height: 40px;
line-height: 40px;
font-size: 16px;
font-weight: bold;
margin-bottom: 0;
margin-top: 8px;
.toolsItem {
color: #999;
margin: 0 10px;
}
.active {
color: #333;
position: relative
}
.active:before {
content: "";
position: absolute;
bottom: 0;
width: 20px;
height: 4px;
background: #54d87c;
border-radius: 5px;
left: calc(50% - 10px);
}
}
</style>

146
pages/target/target.vue Normal file
View File

@ -0,0 +1,146 @@
<template>
<view class="content ">
</view>
</template>
<script>
import {
mapState
} from "vuex";
export default {
data() {
return {
fields: "",
active: 1,
acd_id: 2,
ind: 0,
active1: 0,
startTime: "",
endTime: "",
lineData: {},
handTrue: true,
}
},
computed: {
...mapState(['user', "MeasureResult", "Trend"]),
weightList() {
let that = this
that.showbox(0)
return that.Trend
},
userInfo() {
return this.MeasureResult
},
endDate() {
return this.$tools.getDate("start")
},
startDate() {
return this.$tools.GetDateStr(-90);
},
},
components: {
},
onLoad(options) {
let that = this
that.acd_id = options.acd_id
that.$store.dispatch("GetBodyTrendList", {
aud_id: uni.getStorageSync('userid'),
s_time: that.startDate,
e_time: that.endDate
})
// #ifdef APP-PLUS
that.fields = "time"
// #endif
// #ifndef APP-PLUS
that.fields = "day"
// #endif
},
//
onPullDownRefresh() {
let that = this
that.$store.dispatch("GetBodyTrendList", {
aud_id: uni.getStorageSync('userid'),
s_time: that.startDate,
e_time: that.endDate
})
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000);
},
methods: {
showbox(index) {
let that = this
that.handTrue = false
that.$nextTick(function() {
that.handTrue = true
that.lineData = that.weightList.length ? that.weightList[index].line : {}
})
that.active1 = index
},
//
handStartTimeH(e) {
let that = this
if (that.endTime) {
if (Date.parse(e.detail.value) > Date.parse(that.endTime)) {
that.$tools.msg("请选择正确的时间")
return
}
} else {
if (Date.parse(e.detail.value) > Date.parse(that.endDate)) {
that.$tools.msg("请选择正确的时间")
return
}
}
that.startTime = e.detail.value
let endtime = that.endTime ? that.endTime : that.endDate
that.$store.dispatch("GetBodyTrendList", {
aud_id: that.user.id,
s_time: that.startTime,
e_time: that.endTime ? that.endTime : that.endDate
})
that.showbox(0)
},
//
handEndTimeH(e) {
let that = this
if (that.startTime) {
if (Date.parse(e.detail.value) < Date.parse(that.startTime)) {
that.$tools.msg("请选择正确的时间")
return
}
} else {
if (Date.parse(e.detail.value) < Date.parse(that.startDate)) {
that.$tools.msg("请选择正确的时间")
return
}
}
that.endTime = e.detail.value
let startTime = that.startTime ? that.startTime : that.startDate
that.$store.dispatch("GetBodyTrendList", {
aud_id: uni.getStorageSync('userid'),
s_time: startTime,
e_time: that.endTime
})
that.showbox(0)
},
handleClick(ind) {
let that = this
if (!uni.getStorageSync('token')) {
that.$tools.msg("登录后查看更多!")
return
}
this.ind = ind
this.$store.commit("changeFirst", true);
},
}
}
</script>
<style scoped lang="scss">
@import "@/scss/body.scss";
.listC {
margin: 0;
width: 100%;
}
</style>

View File

@ -1118,3 +1118,145 @@
}
}
}
// 测量也
.weightPages {
.text {
width: 100%;
text-align: center;
font-size: 36rpx;
margin-top: 20px;
color: $textcolor;
}
.title {
width: 100%;
text-align: center;
height: 45px;
line-height: 45px;
font-size: 36rpx;
font-weight: bold;
}
.image {
text-align: center;
image {
width: 200px;
height: 200px;
margin: auto;
margin-top: 20px;
}
}
.tips {
width: auto;
margin-top: 25px;
padding-left: 20px;
line-height: 30px;
font-size:28rpx;
color: #999;
text {
display: block;
}
}
.wrapper {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.4);
.Blue {
width: 75%;
padding: 15px;
background: #fff;
z-index: 999;
border-radius: 5px;
text-align: center;
line-height: 30px;
.h4 {
font-size: 36rpx;
font-weight: 700;
margin-bottom: 10px;
}
.Blue-box {
display: flex;
align-items: center;
text-align: left;
height: 40px;
line-height: 40px;
margin-bottom: 15px;
text {
font-size: 36rpx;
font-weight: 700;
margin: 0 5px;
color: $textcolor;
}
}
input {
width: 85px;
background: #f7f7f7;
padding: 7px 5px;
margin-right: 10px;
border-radius: 5px;
}
}
.Blue-btn {
width: 45%;
background: $textcolor;
border-radius: 5px;
font-size: 32rpx;
margin-top: 10px;
margin-bottom: 5px;
height: 35px;
line-height: 35px;
float: right;
color: #fff;
}
.Blue-close {
background: #dfdfdf !important;
float: left !important;
color: #333;
}
}
.btnGroup {
width: 100%;
display: flex;
.btnClose,
.baocun {
width: 150px;
background-color: $textcolor;
border: 1px solid #f7f7f7;
color: #fff;
text-align: center;
padding: 7px;
border-radius: 10px;
margin: 15px auto;
}
.btnClose {
background-color: #dfdfdf;
color: #fff;
}
}
}

776
scss/food.scss Normal file
View File

@ -0,0 +1,776 @@
.everyDay {
background: #fff;
padding: 20rpx 20rpx 30rpx;
border-radius: 20rpx;
margin: 30rpx 20rpx 20rpx;
height: auto;
overflow: hidden;
.title {
display: flex;
justify-content: space-between;
font-weight: bold;
font-size: 30rpx;
.quan {
margin-right: 70rpx;
}
}
}
.jishiqi {
height: auto;
overflow: hidden;
margin-top: 15px;
.top {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.date {
font-size: 14px;
font-weight: bold;
}
.detail {
color: #3CB383;
width: auto;
padding: 3px 10px;
background: #fff;
border-radius: 8px;
border: 1px solid #3CB383;
display: flex;
align-items: center;
image {
width: 44rpx;
height: 44rpx;
margin-right: 5px;
}
}
.left {
float: left;
width: 270rpx;
height: 320rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.chart-wrap {
position: relative;
width: 250rpx;
height: 250rpx;
margin-top: -30rpx;
margin-left: -5px;
display: flex;
flex-wrap: wrap;
.center {
border: none;
width: 280rpx;
height: 210rpx;
position: absolute;
top: 64rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.mubiao {
width: 100%;
margin-top: 32rpx;
text-align: center;
font-size: 26rpx;
}
}
.right {
width: calc(100% - 290rpx);
display: flex;
flex-direction: column;
float: left;
height: 300rpx;
margin-left: 20rpx;
justify-content: space-between;
.item {
width: 100%;
font-size: 26rpx;
image {
width: 40rpx;
height: 40rpx;
}
.left-icon {
width: 90rpx;
float: left;
height: 80rpx;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-between;
}
.right-info {
width: calc(100% - 100rpx);
float: left;
height: 74rpx;
margin-left: 5px;
display: flex;
flex-direction: column;
justify-content: space-between;
.right-info-top {
width: 100%;
display: flex;
justify-content: space-between;
}
.right-info-bottom {
width: 100%;
height: 8px;
background-color: #f3f7f5;
border-radius: 5px;
position: relative;
.val {
width: 45%;
position: absolute;
left: 0;
top: 0;
z-index: 9;
height: 16rpx;
border-radius: 5px;
}
}
}
}
}
}
// 添加食谱
.addFood {
.title,
.textarea {
width: calc(100% - 40rpx);
margin-bottom: 20rpx;
background: #fff;
padding: 0 20rpx;
border-radius: 20rpx;
}
.food,
.step {
width: calc(100% - 40rpx);
margin-bottom: 20rpx;
background: #fff;
padding: 20rpx;
border-radius: 20rpx;
.h4 {
height: 60rpx;
line-height: 60rpx;
font-size: 14px;
display: flex;
font-weight: bold;
justify-content: space-between;
text {
font-size: 14px;
border: 1px solid #dfdfdf;
border-radius: 30rpx;
padding: 0 40rpx;
}
}
.foodlist {
column-count: 1;
.item {
display: flex;
justify-content: space-between;
width: calc(100% - 40rpx);
background: #f7f7f7;
border-radius: 20rpx;
padding: 8px 20rpx;
margin-top: 20rpx;
}
.name {
width: 30%;
border-right: 1px solid #999;
margin-right: 30rpx;
}
.input {
width: 30%;
display: flex;
align-items: center;
}
}
.edit {
width: 30%;
display: flex;
align-items: center;
justify-content: center;
icon {
display: flex;
margin-right: 5px;
color: $uni-color-warning;
}
image {
width: 18px;
height: 18px;
margin: 0 5px;
}
.shang {
transform: rotate(180deg);
}
}
.add {
width: 100%;
text-align: center;
height: 35px;
line-height: 35px;
background: $maincolor;
margin-top: 30rpx;
border-radius: 20rpx;
color: #fff;
}
}
.step {
.top {
display: flex;
justify-content: space-between;
margin: 20rpx 0;
font-size: 14px;
font-weight: bold;
}
.textarea {
margin-top: 20rpx;
background-color: #f7f7f7;
}
.add {
color: #000;
background-color: #fff;
border: 1px solid #f0ad4e;
}
}
.groupbtn {
width: 100%;
margin-top: 5px;
display: flex;
justify-content: space-between;
align-items: center;
view {
width: 45%;
background-color: #fff;
border: 1px solid #f0ad4e;
text-align: center;
height: 35px;
line-height: 35px;
border-radius: 20rpx;
margin-bottom: 30rpx;
}
.subbtn {
color: #fff;
border-color: $maincolor;
background-color: $maincolor;
}
}
}
.foodDetail {
background-color: #F7F7F7;
padding: 20rpx;
box-sizing: border-box;
.foodInfo {
display: flex;
width: 100%;
padding: 30rpx;
border-radius: 20rpx;
box-sizing: border-box;
background-color: #fff;
box-sizing: 0 0 20rpx #f1f1f1;
image {
width: 90rpx;
height: 90rpx;
border-radius: 15rpx;
}
.info {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 30rpx;
.name {
font-size: 28rpx;
font-weight: 700;
margin-bottom: 10rpx;
}
.kcal {
width: 100% !important;
font-size: 26rpx;
color: #666;
padding: 0 !important;
margin: 0 !important;
}
}
}
.foodContent {
width: 100%;
padding: 30rpx;
margin-top: 16rpx;
box-sizing: border-box;
border-radius: 20rpx;
box-sizing: border-box;
background-color: #fff;
box-sizing: 0 0 20rpx #f1f1f1;
.title {
font-size: 28rpx;
font-weight: 600;
}
.progress {
display: flex;
align-items: center;
.chart-wrap {
position: relative;
width: 280rpx;
height: 280rpx;
margin-top: -30rpx;
margin-left: -20px;
// .uchart-kcal {
// position: absolute;
// left: 60rpx;
// top: 120rpx;
// width: 130rpx;
// font-size: 40rpx;
// text-align: center;
// z-index: 9;
// }
}
.info {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 26rpx;
height: 200rpx;
.info-item {
display: flex;
align-items: center;
margin-top: 20rpx;
.color {
width: 6rpx;
height: 20rpx;
margin-right: 10rpx;
border-radius: 3rpx;
}
}
}
}
.tips {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #f1f1f1;
padding: 16rpx 0;
font-size: 26rpx;
margin-top: 10rpx;
}
.foodDetailList {
margin-top: 10rpx;
.foodDetailItem {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
box-sizing: border-box;
.name {
font-size: 26rpx;
color: #777;
}
.val {
font-size: 26rpx;
font-weight: 700;
color: #333;
}
}
}
}
}
.set {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
font-size: 32rpx;
font-weight: bold;
.icon {
background: #d1f2ed;
border-radius: 50%;
font-size: 56rpx;
color: #66cccc;
text-align: center;
padding: 10rpx;
}
}
.tools {
width: 100%;
background: #fff;
border-radius: 20rpx;
padding: 20rpx 0;
display: flex;
margin-bottom: 30rpx;
justify-content: space-between;
box-shadow: 0px 1px 5px 2px #dfe2e1fc;
.type {
width: 20%;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
image {
width: 90rpx;
height: 90rpx;
border-radius: 50%;
border: 1px solid #f7f7f7;
}
.text {
width: 100%;
text-align: center;
display: flex;
justify-content: center;
font-weight: bold;
icon {
font-size: 28rpx;
}
}
}
}
.list2 {
margin-top: 45%;
.btn {
color: #fff;
height: 64rpx;
line-height: 64rpx;
}
}
.fenxi {
color: $maincolor;
background: #fff;
border-radius: 10px;
margin: 0 20rpx 20rpx;
height: 45px;
display: flex;
justify-content: center;
align-items: center;
image {
width: 44rpx;
height: 44rpx;
margin-right: 5px;
}
}
.serachBox {
height: 80rpx;
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0 30rpx 20rpx;
z-index: 99;
background-color: #f7f7f7;
.title {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: bold;
}
.searchInput {
position: absolute;
left: 0;
right: 120rpx;
height: 80rpx;
}
.search-wrap {
height: 80rpx;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid $maincolor;
border-radius: 30rpx;
padding: 0 20rpx;
background: #fff;
image {
width: 50rpx;
height: 50rpx;
}
}
}
.f_banner {
width: 100% !important;
height: 450rpx;
margin: 30rpx auto;
/deep/swiper {
height: 450rpx;
}
image {
width: 100%;
height: 100%;
background-size: 100%;
position: relative;
}
}
.quan {
width: 60rpx;
height: 40rpx;
position: relative;
margin-right: 70rpx;
}
.quan::before {
content: "";
position: absolute;
width: 35rpx;
height: 35rpx;
left: 0px;
z-index: 22;
background: #3CB383;
border-radius: 50%;
}
.quan::after {
content: "";
position: absolute;
width: 35rpx;
height: 35rpx;
left: 17rpx;
z-index: 11;
background: #9CDCBF;
border-radius: 50%;
}
.drawerVisible {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
z-index: 999;
.bgVisible {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.4);
transition: opacity 0.3s;
}
.infoVisible {
display: block;
position: absolute;
top: 0;
right: 0;
width: 300px;
bottom: 0;
background-color: #ffffff;
transition: -webkit-transform 0.3s ease;
transition: transform 0.3s ease;
transition: transform 0.3s ease, -webkit-transform 0.3s ease;
}
}
// .列表样式
.footlist {
margin: 30rpx 0;
width: 100%;
height: auto;
overflow: hidden;
column-gap: 20rpx;
column-count:2;
// display: flex;
// flex-wrap: wrap;
// justify-content: space-between;
.list {
margin-bottom: 20rpx;
// height: auto;
overflow: auto;
break-inside: avoid;
border-radius: 15rpx;
overflow: hidden;
-webkit-column-break-inside: avoid;
.item {
color: #666;
width: calc(100% - 20rpx);
position: initial;
background: #fff;
border-radius: 0 0 5px 5px;
font-size: 14px;
height: auto;
overflow: hidden;
}
.topimg{
height:320rpx;
overflow: hidden;
position: relative;
}
.img {
width: 100%;
height: 100%;
display: block;
border-radius: 5px 5px 0 0;
}
.zan {
.iconfont {
font-size: 32rpx;
position: inherit !important;
}
}
}
}
.footbox {
// width: calc(100% - 60rpx);
// margin-top: 80rpx;
position: relative;
.item {
position: absolute;
bottom: 0px;
color: #fff;
left: 5px;
right: 5px;
background: #403f3f5c;
padding: 5px;
font-size: 30rpx;
z-index: 999999;
border-radius:0 0 20rpx 20rpx;
.title {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.name {
display: flex;
align-items: center;
font-size: 26rpx;
float: left;
width: 70%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 60rpx;
line-height: 60rpx;
image {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
margin-right: 5px;
}
text{
width: calc(100% - 60rpx);
display: inline-block;
}
}
.zan {
width: 30%;
float: left;
font-size: 26rpx;
display: flex;
align-items: center;
height: auto;
overflow: hidden;
height: 60rpx;
line-height: 60rpx;
justify-content: flex-end;
.iconfont {
display: flex;
align-items: center;
position: absolute;
right: 30rpx;
bottom: 20rpx;
text-align: right;
z-index: 99;
margin-right: 5px;
}
}
.icon-icon3 {
color: $maincolor;
}
}
}

BIN
static/28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
static/31.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
static/arrow-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
static/arrow-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/fenxi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
static/pan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
static/shendu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
static/shoudong.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
static/tou.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
static/xia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/xin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
static/xin2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
static/zhong.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -47,16 +47,19 @@ export default new Vuex.Store({
bleValue: {
deviceId: "",
serviceId: "",
notify: "",
write: "",
unit: "g",
type: 1,
unitList: json.unitArray,
isSendVal: false,
oldCountWeight: 0,
countWeight: 100,
bleTipsText: "",
isConnectStatus: 0,
isBleLink: false,
bleTipsText: "", //蓝牙提示语
isConnectStatus: 0, //蓝牙连接状态 0搜索中1失败2成功
isBluetoothTyle: false, //蓝牙状态
// notify: "",
// write: "",
// unit: "g",
// type: 1,
// unitList: json.unitArray,
// isSendVal: false,
// oldCountWeight: 0,
// countWeight: 100,
},
// 计食器
countFoodInfo: {
@ -104,6 +107,7 @@ export default new Vuex.Store({
MeasureLung: null,
familayList: [],
PublicRecord: [],
LungLevel: [], //肺活量标准
CardList: {
chosen_fixed: [],
chosen_yes: [],
@ -114,15 +118,6 @@ export default new Vuex.Store({
isFirst: false, //初始体重
isRecord: false, //手动记录
isPublicRecord: false, //公共手动记录弹框
UseBlueConfig: {
isBleLink: false, //是否可以点击
BleBodyMsg: "", //body页面蓝牙状态提示
BleConnectMsg: "", //测量页蓝牙状态提示
serviceId: "",
isUseConnect: false, //蓝牙是否连接
},
LungLevel: [], //肺活量标准
},
mutations: {
// 账户信息
@ -209,8 +204,6 @@ export default new Vuex.Store({
state.isPublicRecord = newData
},
// 计时
changeConfigInfo(state, newData) {
state.configInfo = newData
@ -219,18 +212,10 @@ export default new Vuex.Store({
changeCountFoodInfo(state, newData) {
state.countFoodInfo = newData
},
// 蓝牙起否连接
getIsUseBluetooth(state, newData) {
Object.assign(state.UseBlueConfig, newData)
},
// 蓝牙信息
changeBluetoothValue(state, newData) {
Object.assign(state.bleValue, newData)
},
//蓝牙状态
changeBluetooth(state, newData) {
state.isBluetoothTyle = newData
},
},
// 模块化vuex
modules: {},

View File

@ -3,11 +3,9 @@ import useBluetooth from '@/toolJs/Bluetooth.js'
import $tools from '@/toolJs/tools.js'
import $data from '@/content.json'
// // 蓝牙连接
let unitList = []
let deviceId = ""
let devicesList = []
let searchTimer = null
let UNIT_MAP = $data.units
export default {
openBluetoothAdapter,
startBluetoothDeviceDiscovery,
@ -25,14 +23,15 @@ export default {
// // 初始化蓝牙
function openBluetoothAdapter() {
devicesList = []
unitList = []
clearTimeout(searchTimer);
uni.openBluetoothAdapter({
success: e => {
$store.commit("changeBluetoothValue", {
bleTipsText: "蓝牙搜索中",
isConnectStatus: 0,
unitList: $data.unitMinus,
isBleLink: false,
deviceId: "",
serviceId: "",
})
startBluetoothDeviceDiscovery()
},
@ -40,7 +39,9 @@ function openBluetoothAdapter() {
$store.commit("changeBluetoothValue", {
bleTipsText: "连接超时,点击重新连接",
isConnectStatus: 1,
unitList: $data.unitMinus,
isBleLink: false,
deviceId: "",
serviceId: "",
})
}
});
@ -94,13 +95,14 @@ function onBluetoothDeviceFound() {
if (device.name.toLowerCase().indexOf('pc-c06pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c02pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c09pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c10pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c07pro') != -1 ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c07pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c10pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c06pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c02pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c09pro') != -1)) {
clearTimeout(searchTimer);
const bytes = new Uint8Array(device.advertisData);
const macBytes = bytes.slice(6, 12);
device.macAddr = $tools.ab2hex(macBytes, ':').toUpperCase()
stopBluetoothDevicesDiscovery()
Bluetoothfilter(device)
return
@ -113,9 +115,6 @@ function onBluetoothDeviceFound() {
device.name.toLowerCase().indexOf('pcf01') != -1 ||
device.name.toLowerCase().indexOf('Yihejia_Lung') != -1) {
clearTimeout(searchTimer);
const bytes = new Uint8Array(device.advertisData);
const macBytes = bytes.slice(6, 12);
device.macAddr = $tools.ab2hex(macBytes, ':').toUpperCase()
stopBluetoothDevicesDiscovery()
Bluetoothfilter(device)
return
@ -129,6 +128,7 @@ function Bluetoothfilter(device) {
const idx = $tools.inArray(foundDevices, "deviceId", device.deviceId)
if (idx === -1) {
devicesList.push(device);
deviceId = device.deviceId
// 体脂秤
if (device.name.toLowerCase().indexOf("pcl") != -1) {
uni.navigateTo({
@ -181,7 +181,14 @@ function Bluetoothfilter(device) {
//厨房秤
if (device.name.toLowerCase().indexOf('pc-c06pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c02pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c09pro') != -1) {
device.name.toLowerCase().indexOf('pc-c09pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c10pro') != -1 ||
device.name.toLowerCase().indexOf('pc-c07pro') != -1 ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c07pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c10pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c06pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c02pro') != -1) ||
(device.localName && device.localName.toLowerCase().indexOf('pc-c09pro') != -1)) {
handleDevType(device)
return
}
@ -207,12 +214,12 @@ function handleDevType(device) {
})
}
//连接设备
function createBLEConnection(device_id) {
function createBLEConnection(serviceId) {
uni.createBLEConnection({
deviceId: device_id,
deviceId: deviceId,
success: res => {
setTimeout(function() {
getBLEDeviceServices(device_id)
getBLEDeviceServices(serviceId)
}, 200)
},
fail: res => {
@ -227,18 +234,24 @@ function createBLEConnection(device_id) {
/**
* 获取设备的UUID
*/
function getBLEDeviceServices(device_id) {
function getBLEDeviceServices(serviceId) {
let serviceList = [];
uni.getBLEDeviceServices({
deviceId: device_id,
deviceId: deviceId,
success: res => {
console.log("获取设备的UUID成功", res)
stopBluetoothDevicesDiscovery();
serviceList = res.services;
for (let i = 0; i < serviceList.length; i++) {
let service = serviceList[i];
if (service.uuid.indexOf("FFF0") != -1) {
getBLEDeviceCharacteristics(device_id, service.uuid);
if (service.uuid.indexOf(serviceId) != -1) {
$store.commit("changeBluetoothValue", {
type: 1,
isConnectStatus: 0,
deviceId: deviceId,
serviceId: service.uuid,
bleTipsText: "蓝牙链接中",
})
break;
}
}
@ -450,7 +463,9 @@ function onBLEConnectionStateChange() {
closeBLEConnection()
closeBluetoothAdapter()
}
$store.commit("changeBluetooth", res.connected);
$store.commit("changeBluetoothValue", {
isBluetoothTyle: res.connected
});
})
}
/**

View File

@ -0,0 +1,30 @@
## 1.0.72025-03-17
修复vue3 bug
## 1.0.62025-01-08
更新文档
## 1.0.52024-12-26
优化显示
## 1.0.42024-12-26
优化显示
## 1.0.32024-12-25
- 更新依赖
## 1.0.22024-12-25
- 优化滑块相近时tip显示
## 1.0.12024-12-25
- 优化逻辑
## 1.0.02024-12-25
新增:
- 双滑块范围选择器基础功能
- 支持设置最小值(min)和最大值(max)
- 支持自定义步长(step)设置
- 支持禁用状态(disabled)
- 自定义样式功能:
- 滑动条背景色(backgroundColor)
- 选中范围颜色(activeColor)
- 滑块大小(blockSize)
- 滑块颜色(blockColor)
- 值格式化功能(format)
- 支持移动端触摸操作
- v-model双向绑定支持

View File

@ -0,0 +1,467 @@
<template>
<!-- 滑块范围选择器容器 -->
<view class="slider-range" :class="{disabled}" :style="sliderStyle">
<view class="slider-range-inner">
<!-- 滑块条 -->
<view class="slider-bar">
<!-- 背景条 -->
<view class="slider-bar-bg" :style="{backgroundColor}" />
<!-- 选中区域条 -->
<view class="slider-bar-inner" :style="barInnerStyle" />
</view>
<!-- 左右两个滑块按钮 -->
<view
v-for="block in ['lowerBlock', 'higherBlock']"
:key="block"
class="slider-handle-block"
:style="block === 'lowerBlock' ? leftHandleStyle : rightHandleStyle"
:data-tag="block"
@touchstart="handleDragStart"
@touchmove="handleDragMove"
@touchend="onBlockTouchEnd"
@mousedown="onMouseDown"
/>
<!-- 滑块值提示 -->
<!-- <view class="range-tip" :style="leftTipStyle">{{ formatValue(selectedRange[0]) }}</view>
<view class="range-tip" :style="rightTipStyle">{{ formatValue(selectedRange[1]) }}</view> -->
<!-- 刻度线 -->
<!-- <view
v-for="n in scaleCount + 1"
:key="n"
class="slider-scale"
:style="{left: `${(n / scaleCount) * 100}%`}"
/> -->
<!-- 最小最大值显示 -->
<!-- <view class="slider-value" style="left: 0">{{ min }}</view>
<view class="slider-value" style="right: 0">{{ max }}</view> -->
</view>
</view>
</template>
<script>
import throttle from './throttle'
//
const DEFAULT_SCALE_COUNT = 24
// (rpx)
const DEFAULT_BLOCK_SIZE = 48
/**
* 滑块范围选择器
* @description 一个可以选择数值范围的滑块组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=21575
* @property {Array} modelValue 双向绑定的值默认[0, 100]
* @property {Number} min 最小值默认0
* @property {Number} max 最大值默认100
* @property {Number} step 步长默认1
* @property {Function} format 格式化显示的值的函数
* @property {Boolean} disabled 是否禁用默认false
* @property {String} backgroundColor 背景颜色默认#F6F6F6
* @property {String} activeColor 激活颜色默认#4DB8F6
* @property {Number} blockSize 滑块大小默认48
* @property {String} blockColor 滑块颜色默认#fff
* @event {Function} update:modelValue 值变化时触发
*/
export default {
name: 'llt-slider-range',
// v-model
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
modelValue: {
type: Array,
default: () => [0, 100]
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
format: {
type: Function,
default: val => val
},
disabled: {
type: Boolean,
default: false
},
backgroundColor: {
type: String,
default: '#f0ae43'
},
activeColor: {
type: String,
default: '#3CB383'
},
blockSize: {
type: Number,
default: DEFAULT_BLOCK_SIZE
},
blockColor: {
type: String,
default: '#fff'
}
},
emits: ['update:modelValue', 'change'],
data() {
return {
selectedRange: this.modelValue, //
dragStartPosition: 0, //
dragStartValue: 0, //
activeBlock: '', //
scaleCount: DEFAULT_SCALE_COUNT, //
isDragging: false //
}
},
computed: {
//
leftHandlePosition() {
return this.calculateHandlePosition(this.selectedRange[0])
},
//
rightHandlePosition() {
return this.calculateHandlePosition(this.selectedRange[1])
},
//
leftHandleStyle() {
return this.generateHandleStyle('lowerBlock')
},
//
rightHandleStyle() {
return this.generateHandleStyle('higherBlock')
},
//
leftTipStyle() {
return this.generateTipStyle('lowerBlock')
},
//
rightTipStyle() {
return this.generateTipStyle('higherBlock')
},
//
sliderStyle() {
const padding = this.blockSize / 2
return `padding-left: ${padding}rpx;padding-right: ${padding}rpx`
},
//
barInnerStyle() {
const width = ((this.selectedRange[1] - this.selectedRange[0]) / (this.max - this.min)) * 100
return `width: ${width}%;left: ${this.leftHandlePosition}%;background-color: ${this.activeColor}`
}
},
watch: {
// modelValue
modelValue: {
deep: true,
immediate: true,
handler(val) {
if (!this.valuesEqual(val)) {
this.updateValues(val)
}
}
}
},
methods: {
//
formatValue(val) {
if (typeof this.format === 'function') {
return this.format(val)
}
return val
},
//
calculateHandlePosition(value) {
return ((value - this.min) / (this.max - this.min)) * 100
},
//
generateHandleStyle(block) {
const position = block === 'lowerBlock' ? this.leftHandlePosition : this.rightHandlePosition
let zIndex = this.activeBlock === block ? 20 : 12
if ((position < 1 && block === 'lowerBlock') || (position > 99 && block === 'higherBlock')) {
zIndex = 11
}
return `background-color: ${this.blockColor};width: ${this.blockSize}rpx;height: ${this.blockSize}rpx;left: ${position}%;z-index:${zIndex}`
},
//
generateTipStyle(type) {
const position = type === 'lowerBlock' ? this.leftHandlePosition : this.rightHandlePosition
// ,8
const maxDistance = String(this.selectedRange[1]).length * 8
// ,
const distance = maxDistance - (this.rightHandlePosition - this.leftHandlePosition)
// 0,,
if (distance > 0) {
// ,,
const diff = type === 'lowerBlock' ? -distance : distance
return `left: calc(${position}% + ${diff}rpx)`
}
return position < 90
? `left: ${position}%`
: `right: ${100 - position}%; transform: translate(50%, -100%)`
},
//
updateValues(newVal) {
if (this.step >= this.max - this.min) {
throw new RangeError('Invalid slider step or slider range')
}
if (!this.isValidValues(newVal)) {
this.selectedRange = []
this.$emit('update:modelValue', [], 'update')
this.$emit('change', [])
return
}
const newValues = this.calculateNewValues(newVal)
if (this.valuesEqual(newValues)) return
this.selectedRange = this.validateValues(newValues)
this.$emit('update:modelValue', [...this.selectedRange], 'update')
this.$emit('change', [...this.selectedRange])
},
//
calculateNewValues(val) {
return [
Math.round((val[0] - this.min) / this.step) * this.step + this.min,
Math.round((val[1] - this.min) / this.step) * this.step + this.min
]
},
//
validateValues(values) {
let [lower, higher] = values
lower = Math.max(lower, this.min)
higher = Math.min(higher, this.max)
if (lower >= higher) {
if (lower === this.selectedRange[0]) {
higher = lower + this.step
} else {
lower = higher - this.step
}
}
return [lower, higher]
},
//
valuesEqual(newValues) {
return Array.isArray(newValues) &&
Array.isArray(this.selectedRange) &&
newValues.length === this.selectedRange.length &&
newValues.every((val, index) => val === this.selectedRange[index])
},
//
handleDragStart(event) {
if (this.disabled) return
const tag = event.target.dataset.tag
this.activeBlock = tag
const { pageX } = event.changedTouches?.[0] || event
this.dragStartPosition = pageX
this.dragStartValue = tag === 'lowerBlock' ? this.selectedRange[0] : this.selectedRange[1]
this.isDragging = true
},
//
handleDragMove(event) {
if (!this.isDragging || this.disabled) return
throttle(this.processDrag(event), 500)
},
//
onBlockTouchEnd() {
this.isDragging = false
},
//
processDrag(event) {
const view = uni.createSelectorQuery().in(this).select('.slider-range-inner')
view.boundingClientRect(data => {
const sliderWidth = data.width
const { pageX } = event.changedTouches?.[0] || event
const diff = ((pageX - this.dragStartPosition) / sliderWidth) * (this.max - this.min)
const nextVal = this.dragStartValue + diff
const values = this.activeBlock === 'lowerBlock'
? [nextVal, this.selectedRange[1]]
: [this.selectedRange[0], nextVal]
this.updateValues(values)
}).exec()
},
//
isValidValues(values) {
return Array.isArray(values) && values.length === 2
},
//
onMouseDown(event) {
if (this.disabled) return
const tag = event.target.dataset.tag
this.activeBlock = tag
this.dragStartPosition = event.pageX
this.dragStartValue = tag === 'lowerBlock' ? this.selectedRange[0] : this.selectedRange[1]
this.isDragging = true
//
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
},
//
onMouseMove(event) {
if (!this.isDragging || this.disabled) return
event.preventDefault() //
throttle(this.handleMouseDrag(event), 500)
},
//
onMouseUp() {
this.isDragging = false
//
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
},
//
handleMouseDrag(event) {
const view = uni.createSelectorQuery().in(this).select('.slider-range-inner')
view.boundingClientRect(data => {
const sliderWidth = data.width
const diff = ((event.pageX - this.dragStartPosition) / sliderWidth) * (this.max - this.min)
const nextVal = this.dragStartValue + diff
const values = this.activeBlock === 'lowerBlock'
? [nextVal, this.selectedRange[1]]
: [this.selectedRange[0], nextVal]
this.updateValues(values)
}).exec()
}
}
}
</script>
<style lang="scss" scoped>
.slider-range {
position: relative;
padding-top: 40rpx;
&-inner {
position: relative;
width: 100%;
height: 100rpx;
}
&.disabled {
.slider-bar-inner {
opacity: 0.35;
}
.slider-handle-block {
cursor: not-allowed;
}
}
}
.slider-bar {
position: absolute;
top: 30%;
left: 0;
right: 0;
height: 15rpx;
transform: translateY(-30%);
&-inner,
&-bg {
position: absolute;
width: 100%;
height: 100%;
border-radius: 10000px;
}
&-inner {
z-index: 11;
}
&-bg {
z-index: 10;
}
}
.slider-handle-block {
position: absolute;
top: 30%;
transform: translate(-50%, -50%);
border-radius: 50%;
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(91, 91, 91, 0.2);
z-index: 12;
cursor: pointer;
user-select: none;
}
.range-tip {
position: absolute;
top: 0;
font-family: Source Han Sans CN;
font-weight: 400;
font-size: 26rpx;
color: #666666;
transform: translate(-30%, -100%);
}
.slider-scale {
position: absolute;
bottom: 30rpx;
width: 1rpx;
height: 14rpx;
background: #e2e2e2;
}
.slider-value {
position: absolute;
bottom: 0;
font-family: Source Han Sans CN;
font-weight: 400;
font-size: 21rpx;
color: #bbbbbb;
}
</style>

View File

@ -0,0 +1,30 @@
let timer; let
flag
/**
* 节流原理在一定时间内只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle

View File

@ -0,0 +1,86 @@
{
"id": "llt-slider-range",
"displayName": "slider range双滑块范围选择器",
"version": "1.0.7",
"description": "一个功能强大的双滑块范围选择器组件,可用于价格区间、数值范围等场景的选择。支持自定义样式、步长设置,具有良好的移动端适配性和交互体验。",
"keywords": [
"slider",
"range",
"slider-range",
"区间滑块"
],
"repository": "",
"engines": {
"HBuilderX": "^3.8.6"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n",
"app-uvue": "n",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,153 @@
# SliderRange 双滑块范围选择器
> 一个轻量级但功能强大的双滑块范围选择器组件完美适配移动端和PC端为您的项目提供直观的范围选择体验。
## 🌟 特性
- ✨ 灵活的范围设置 - 支持自定义最大最小值
- 📏 精确的步长控制 - 可设置数值变化的最小单位
- 🎨 丰富的样式定制 - 支持自定义滑块、轨道样式
- 📱 完美的跨端适配 - 支持H5/App/小程序多端使用
- 🔄 双向绑定支持 - 支持v-model语法
- 🎯 刻度线显示 - 直观的数值参考
- ⌨️ 键盘操作支持 - 提升无障碍体验
- 🚫 禁用状态 - 支持只读模式
## 📦 安装
### 下载使用
`llt-slider-range` 组件复制到你的项目中即可。
## 🚀 快速开始
### H5/App使用示例
```vue
<template>
<llt-slider-range v-model="rangeValue" />
</template>
<script>
export default {
data() {
return {
rangeValue: [30, 50] // 设置初始范围值
}
}
}
</script>
```
### 微信小程序使用示例
```vue
<template>
<llt-slider-range
:model-value="rangeValue"
@change="handleChange"
/>
</template>
<script>
export default {
data() {
return {
rangeValue: [30, 50]
}
},
methods: {
handleChange(val) {
this.rangeValue = val
}
}
}
</script>
```
## 📝 API文档
### Props 属性
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| modelValue/v-model | Array | [0, 100] | 当前选中的范围值 |
| min | Number | 0 | 最小可选值 |
| max | Number | 100 | 最大可选值 |
| step | Number | 1 | 步长必须大于0 |
| disabled | Boolean | false | 是否禁用 |
| backgroundColor | String | '#F6F6F6' | 滑动条背景色 |
| activeColor | String | '#4DB8F6' | 选中范围的颜色 |
| blockSize | Number | 48 | 滑块大小(rpx) |
| blockColor | String | '#fff' | 滑块颜色 |
| format | Function | val => val | 数值格式化函数 |
### Events 事件
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| update:modelValue | 值更新时触发 | (value: number[]) |
| change | 值变化时触发 | (value: number[]) |
## 💡 高级用法
### 价格范围选择器
```vue
<template>
<llt-slider-range
v-model="priceRange"
:min="0"
:max="10000"
:step="100"
:format="formatPrice"
active-color="#FF6B6B"
/>
</template>
<script>
export default {
data() {
return {
priceRange: [1000, 5000]
}
},
methods: {
formatPrice(val) {
return `¥${val}`
}
}
}
</script>
```
### 温度选择器
```vue
<template>
<llt-slider-range
v-model="tempRange"
:min="-20"
:max="40"
:format="val => `${val}°C`"
active-color="#2196F3"
/>
</template>
```
## ⚠️ 注意事项
1. 确保传入的范围值在 min 和 max 之间
2. step 值必须为正数
3. 移动端使用时建议适当增大 blockSize 以提升触控体验
4. 小程序端需使用 model-value + change 事件方式实现双向绑定
5. 建议在父容器设置合适的宽度,以获得最佳显示效果
## 🔗 相关链接
- [GitHub 仓库](https://github.com/llt3677/slider-range)
- [插件市场](https://ext.dcloud.net.cn/plugin?id=21575)
- [问题反馈](https://github.com/llt3677/slider-range/issues)
## 📄 License
[MIT](https://opensource.org/licenses/MIT)
---
如果这个组件对你有帮助,欢迎 star ⭐️ 支持一下!

View File

@ -0,0 +1,549 @@
<template>
<view class="calendar_all">
<view class="date" @click="handleisCalen">
{{startDay?startDay:startdate}}
</view>
<view class="wrapper Calen" v-if="isCalen">
<view class="bg" @click='isCalen=false'>
<view class="addfood" @click.stop>
<!-- -->
<view class="calendar-wrapper">
<view class="header">
<view class="pre" @click="changeMonth('pre')">
<uni-icons type="back" size="20"></uni-icons>
</view>
<view>{{y+'年'+formatNum(m)+'月'}}</view>
<view class="next" @click="changeMonth('next')">
<uni-icons type="forward" size="20"></uni-icons>
</view>
</view>
<view class="week">
<view class="week-day" v-for="(item, index) in weekDay" :key="index">{{ item }}</view>
</view>
<view :class="{ hide: !monthOpen }" class="content0" :style="{ height: height }">
<view :style="{ top: positionTop + 'rpx' }" class="days">
<view class="item" v-for="(item, index) in dates" :key="index">
<view class="day" @click="selectOne(item, $event)" :class="{
choose: choose == `${item.year}-${item.month}-${item.date}`&&item.isCurM,
nolm: !item.isCurM,
today: isToday(item.year, item.month, item.date),
isWorkDay: isWorkDay(item.year, item.month, item.date)
}">
{{ Number(item.date) }}
</view>
<view class="markDay error"
v-if="isMarkDay(item.year, item.month, item.date,'error')&&item.isCurM">
</view>
<view class="markDay success"
v-if="isMarkDay(item.year, item.month, item.date,'success')&&item.isCurM">
</view>
<view class="markDay warning"
v-if="isMarkDay(item.year, item.month, item.date,'warning')&&item.isCurM">
</view>
</view>
</view>
</view>
<view class="level">
<view><text></text>超标</view>
<view><text></text>达标</view>
<view><text></text>未达标</view>
</view>
</view>
<!-- -->
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ren-calendar',
props: {
// (0)
weekstart: {
type: Number,
default: 0
},
//
// markDays: {
// type: Array,
// default: () => {
// return [];
// }
// },
markDays: {
type: Object,
default: () => {
return {};
}
},
//
headerBar: {
type: Boolean,
default: true
},
//
open: {
type: Boolean,
default: true
},
//
collapsible: {
type: Boolean,
default: true
},
//
disabledAfter: {
type: Boolean,
default: true
},
//
startDay: {
type: String,
default: ""
}
},
data() {
return {
weektext: ['日', '一', '二', '三', '四', '五', '六'],
y: new Date().getFullYear(), //
m: new Date().getMonth() + 1, //
dates: [], //
positionTop: 0,
monthOpen: true,
choose: '',
month: null,
isCalen: false,
startdate: ""
};
},
created() {
this.startdate = this.$tools.getDate("start")
this.dates = this.monthDay(this.y, this.m);
!this.open && this.toggle();
},
mounted() {
this.y = new Date().getFullYear()
this.m = new Date().getMonth() + 1
this.month = this.$tools.getMonth(this.$tools.getTime(), 0)
this.choose = this.getToday().date;
},
computed: {
//
weekDay() {
return this.weektext.slice(this.weekstart).concat(this.weektext.slice(0, this.weekstart));
},
height() {
return (this.dates.length / 7) * 80 + 'rpx';
},
},
methods: {
handleisCalen() {
console.log("店家")
this.isCalen = !this.isCalen
},
formatNum(num) {
let res = Number(num);
return res < 10 ? '0' + res : res;
},
getToday() {
let date = new Date();
let y = date.getFullYear();
let m = date.getMonth();
let d = date.getDate();
let week = new Date().getDay();
let weekText = ['日', '一', '二', '三', '四', '五', '六'];
let formatWeek = '星期' + weekText[week];
let today = {
date: y + '-' + this.formatNum(m + 1) + '-' + this.formatNum(d),
week: formatWeek
};
return today;
},
//
monthDay(y, month) {
let dates = [];
let m = Number(month);
let firstDayOfMonth = new Date(y, m - 1, 1).getDay(); //
let lastDateOfMonth = new Date(y, m, 0).getDate(); //
let lastDayOfLastMonth = new Date(y, m - 1, 0).getDate(); //
let weekstart = this.weekstart == 7 ? 0 : this.weekstart;
let startDay = (() => {
//
if (firstDayOfMonth == weekstart) {
return 0;
} else if (firstDayOfMonth > weekstart) {
return firstDayOfMonth - weekstart;
} else {
return 7 - weekstart + firstDayOfMonth;
}
})();
let endDay = 7 - ((startDay + lastDateOfMonth) % 7); //
if (endDay == 7) {
endDay = 0;
}
for (let i = 1; i <= startDay; i++) {
dates.push({
date: this.formatNum(lastDayOfLastMonth - startDay + i),
day: weekstart + i - 1 || 7,
month: m - 1 >= 0 ? this.formatNum(m - 1) : 12,
year: m - 1 >= 0 ? y : y - 1
});
}
for (let j = 1; j <= lastDateOfMonth; j++) {
dates.push({
date: this.formatNum(j),
day: (j % 7) + firstDayOfMonth - 1 || 7,
month: this.formatNum(m),
year: y,
isCurM: true //
});
}
for (let k = 1; k <= endDay; k++) {
dates.push({
date: this.formatNum(k),
day: (lastDateOfMonth + startDay + weekstart + k - 1) % 7 || 7,
month: m + 1 <= 11 ? this.formatNum(m + 1) : 0,
year: m + 1 <= 11 ? y : y + 1
});
}
return dates;
},
isWorkDay(y, m, d) {
//
let ymd = `${y}-${m}-${d}`;
let formatDY = new Date(ymd.replace(/-/g, '/'));
let week = formatDY.getDay();
if (week == 0 || week == 6) {
return false;
} else {
return true;
}
},
isFutureDay(y, m, d) {
//
let ymd = `${y}-${m}-${d}`;
let formatDY = new Date(ymd.replace(/-/g, '/'));
let showTime = formatDY.getTime();
let curTime = new Date().getTime();
if (showTime > curTime) {
return true;
} else {
return false;
}
},
//
isMarkDay(y, m, d, type) {
let that = this
let flag = false;
let markDays = that.markDays[type]
for (let i = 0; i < markDays.length; i++) {
let dy = `${y}-${m}-${d}`;
if (markDays[i] == dy) {
flag = true;
break;
}
}
// console.log("isMarkDay", this.markDays, type, markDays)
return flag;
},
isToday(y, m, d) {
let checkD = y + '-' + m + '-' + d;
let today = this.getToday().date;
if (checkD == today) {
return true;
} else {
return false;
}
},
//
toggle() {
this.monthOpen = !this.monthOpen;
if (this.monthOpen) {
this.positionTop = 0;
} else {
let index = -1;
this.dates.forEach((i, x) => {
this.isToday(i.year, i.month, i.date) && (index = x);
});
this.positionTop = -((Math.ceil((index + 1) / 7) || 1) - 1) * 80;
}
},
//
selectOne(i, event) {
let date = `${i.year}-${i.month}-${i.date}`;
let selectD = new Date(date).getTime();
let curTime = new Date().getTime();
let week = new Date(date).getDay();
let weekText = ['日', '一', '二', '三', '四', '五', '六'];
let formatWeek = '星期' + weekText[week];
let response = {
date: date,
week: formatWeek
};
if (!i.isCurM) {
// console.log('');
return false;
}
if (selectD > curTime) {
if (this.disabledAfter) {
console.log('未来日期不可选');
return false;
} else {
this.choose = date;
this.$emit('maskClick', response);
}
} else {
this.choose = date;
this.$emit('maskClick', response);
}
this.startdate = response.date
this.isCalen = false
},
//
changYearMonth(y, m) {
this.dates = this.monthDay(y, m);
this.y = y;
this.m = m;
},
changeMonth(type) {
let that = this
// if (!uni.getStorageSync('token')) {
// this.$store.commit("changeUserLogin", true);
// return
// }
console.log("type", type)
if (type == 'pre') {
if (that.m + 1 == 2) {
that.m = 12;
that.y = that.y - 1;
} else {
that.m = that.m - 1;
}
that.month = this.$tools.getMonth(that.month, -1)
that.$emit('onMonthClickPre', that.month)
} else {
if (this.m + 1 == 13) {
this.m = 1;
this.y = this.y + 1;
} else {
this.m = this.m + 1;
}
that.month = this.$tools.getMonth(that.month, +1)
that.$emit('onMonthClickPre', that.month)
}
this.dates = this.monthDay(this.y, this.m);
}
}
};
</script>
<style lang="scss" scoped>
.date {
font-size: 16px;
font-weight: bold;
}
.Calen {
top: 50px;
.bg {
top: 50px;
}
.addfood {
top: 0;
bottom: auto;
border-radius: 0;
}
}
.calendar-wrapper {
color: #bbb7b7;
// border-radius: 10px;
font-size: 28rpx;
text-align: center;
background-color: #fff;
padding-bottom: 10rpx;
.header {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
color: #42464A;
font-size: 32rpx;
font-weight: bold;
justify-content: space-around;
.pre,
.next {
color: $maincolor;
font-size: 28rpx;
}
}
.week {
display: flex;
align-items: center;
height: 70rpx;
line-height: 70rpx;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
view {
flex: 1;
}
}
.content0 {
position: relative;
overflow: hidden;
transition: height 0.4s ease;
.days {
transition: top 0.3s;
display: flex;
align-items: center;
flex-wrap: wrap;
position: relative;
.item {
position: relative;
display: block;
height: 70rpx;
line-height: 70rpx;
width: calc(100% / 7);
.day {
font-style: normal;
display: inline-block;
vertical-align: middle;
width: 50rpx;
height: 50rpx;
line-height: 50rpx;
overflow: hidden;
border-radius: 60rpx;
&.choose {
background-color: $maincolor;
color: #fff;
}
&.nolm {
color: #fff;
opacity: 0.3;
}
}
.isWorkDay {
color: #42464a;
}
.notSigned {
font-style: normal;
width: 8rpx;
height: 8rpx;
background: #fa7268;
border-radius: 10rpx;
position: absolute;
left: 50%;
bottom: 0;
pointer-events: none;
}
.today {
color: #fff;
background-color: #a8c0ff;
}
.workDay {
font-style: normal;
width: 8rpx;
height: 8rpx;
background: #4d7df9;
border-radius: 10rpx;
position: absolute;
left: 50%;
bottom: 0;
pointer-events: none;
}
.markDay {
font-style: normal;
width: 10px;
height: 10px;
background: #fa7268;
border-radius: 10rpx;
position: absolute;
left: 42%;
bottom: -8px;
pointer-events: none;
}
.error {
background-color: $uni-color-error;
}
.warning {
background-color: $uni-color-warning;
}
.success {
background-color: $uni-color-success;
}
}
}
}
.hide {
height: 70rpx !important;
}
.weektoggle {
width: 85rpx;
height: 32rpx;
position: relative;
bottom: -42rpx;
&.down {
transform: rotate(180deg);
bottom: 0;
}
}
}
//
.level {
display: flex;
font-size: 12px;
margin-left: 10px;
margin-top: -20px;
view {
width: 25%;
text-align: left;
height: 28px;
line-height: 28px;
text {
width: 10px;
height: 10px;
background: red;
display: inline-block;
border-radius: 50%;
margin-right: 5px;
}
}
:nth-child(2) text {
background: $uni-color-success
}
:nth-child(3) text {
background: $uni-color-warning
}
}
</style>

View File

@ -0,0 +1,13 @@
## 1.2.12021-11-22
- 修复 vue3中个别scss变量无法找到的问题
## 1.2.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-drawer](https://uniapp.dcloud.io/component/uniui/uni-drawer)
## 1.1.12021-07-30
- 优化 vue3下事件警告的问题
## 1.1.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.72021-05-12
- 新增 组件示例地址
## 1.0.62021-02-04
- 调整为uni_modules目录规范

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

View File

@ -0,0 +1,183 @@
<template>
<view :style="{'display':visibleSync?'block':'none'}" :class="{ 'uni-drawer--visible': showDrawer }" class="uni-drawer" @touchmove.stop.prevent="clear">
<view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close('mask')" />
<view class="uni-drawer__content" :class="{'uni-drawer--right': rightMode,'uni-drawer--left': !rightMode, 'uni-drawer__content--visible': showDrawer}" :style="{width:drawerWidth+'px'}">
<slot />
</view>
<!-- #ifdef H5 -->
<keypress @esc="close('mask')" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
/**
* Drawer 抽屉
* @description 抽屉侧滑菜单
* @tutorial https://ext.dcloud.net.cn/plugin?id=26
* @property {Boolean} mask = [true | false] 是否显示遮罩
* @property {Boolean} maskClick = [true | false] 点击遮罩是否关闭
* @property {Boolean} mode = [left | right] Drawer 滑出位置
* @value left 从左侧滑出
* @value right 从右侧侧滑出
* @property {Number} width 抽屉的宽度 vue 页面生效
* @event {Function} close 组件关闭时触发事件
*/
export default {
name: 'UniDrawer',
components: {
// #ifdef H5
keypress
// #endif
},
emits:['change'],
props: {
/**
* 显示模式只在初始化生效
*/
mode: {
type: String,
default: ''
},
/**
* 蒙层显示状态
*/
mask: {
type: Boolean,
default: true
},
/**
* 遮罩是否可点击关闭
*/
maskClick:{
type: Boolean,
default: true
},
/**
* 抽屉宽度
*/
width: {
type: Number,
default: 220
}
},
data() {
return {
visibleSync: false,
showDrawer: false,
rightMode: false,
watchTimer: null,
drawerWidth: 220
}
},
created() {
// #ifndef APP-NVUE
this.drawerWidth = this.width
// #endif
this.rightMode = this.mode === 'right'
},
methods: {
clear(){},
close(type) {
// fixed by mehaotian
if((type === 'mask' && !this.maskClick) || !this.visibleSync) return
this._change('showDrawer', 'visibleSync', false)
},
open() {
// fixed by mehaotian
if(this.visibleSync) return
this._change('visibleSync', 'showDrawer', true)
},
_change(param1, param2, status) {
this[param1] = status
if (this.watchTimer) {
clearTimeout(this.watchTimer)
}
this.watchTimer = setTimeout(() => {
this[param2] = status
this.$emit('change',status)
}, status ? 50 : 300)
}
}
}
</script>
<style lang="scss" scoped>
$uni-mask: rgba($color: #000000, $alpha: 0.4) ;
//
$drawer-width: 220px;
.uni-drawer {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
z-index: 999;
}
.uni-drawer__content {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: absolute;
top: 0;
width: $drawer-width;
bottom: 0;
background-color: $uni-bg-color;
transition: transform 0.3s ease;
}
.uni-drawer--left {
left: 0;
/* #ifdef APP-NVUE */
transform: translateX(-$drawer-width);
/* #endif */
/* #ifndef APP-NVUE */
transform: translateX(-100%);
/* #endif */
}
.uni-drawer--right {
right: 0;
/* #ifdef APP-NVUE */
transform: translateX($drawer-width);
/* #endif */
/* #ifndef APP-NVUE */
transform: translateX(100%);
/* #endif */
}
.uni-drawer__content--visible {
transform: translateX(0px);
}
.uni-drawer__mask {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
opacity: 0;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: $uni-mask;
transition: opacity 0.3s;
}
.uni-drawer__mask--visible {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
opacity: 1;
}
</style>

View File

@ -0,0 +1,87 @@
{
"id": "uni-drawer",
"displayName": "uni-drawer 抽屉",
"version": "1.2.1",
"description": "抽屉式导航,用于展示侧滑菜单,侧滑导航。",
"keywords": [
"uni-ui",
"uniui",
"drawer",
"抽屉",
"侧滑导航"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,10 @@
## Drawer 抽屉
> **组件名uni-drawer**
> 代码块: `uDrawer`
抽屉侧滑菜单。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-drawer)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@ -0,0 +1,67 @@
## 1.0.42023-03-29
- 修复 手动上传删除一个文件后不能再上传的bug
## 1.0.32022-12-19
- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
## 1.0.22022-07-04
- 修复 在uni-forms下样式不生效的bug
## 1.0.12021-11-23
- 修复 参数为对象的情况下url在某些情况显示错误的bug
## 1.0.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
## 0.2.162021-11-08
- 修复 传入空对象 显示错误的Bug
## 0.2.152021-08-30
- 修复 return-type="object" 时且存在v-model时无法删除文件的Bug
## 0.2.142021-08-23
- 新增 参数中返回 fileID 字段
## 0.2.132021-08-23
- 修复 腾讯云传入fileID 不能回显的bug
- 修复 选择图片后,不能放大的问题
## 0.2.122021-08-17
- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
## 0.2.112021-08-16
- 新增 clearFiles(index) 方法,可以手动删除指定文件
- 修复 v-model 值设为 null 报错的Bug
## 0.2.102021-08-13
- 修复 return-type="object" 时无法删除文件的Bug
## 0.2.92021-08-03
- 修复 auto-upload 属性失效的Bug
## 0.2.82021-07-31
- 修复 fileExtname属性不指定值报错的Bug
## 0.2.72021-07-31
- 修复 在某种场景下图片不回显的Bug
## 0.2.62021-07-30
- 修复 return-type为object下返回值不正确的Bug
## 0.2.52021-07-30
- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
## 0.2.32021-07-28
- 优化 调整示例代码
## 0.2.22021-07-27
- 修复 vue3 下赋值错误的Bug
- 优化 h5平台下上传文件导致页面卡死的问题
## 0.2.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.12021-07-02
- 修复 sourceType 缺少默认值导致 ios 无法选择文件
## 0.1.02021-06-30
- 优化 解耦与uniCloud的强绑定关系 如不绑定服务空间默认autoUpload为false且不可更改
## 0.0.112021-06-30
- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
## 0.0.102021-06-29
- 优化 文件上传后进度条消失时机
## 0.0.92021-06-29
- 修复 在uni-forms 中,删除文件 获取的值不对的Bug
## 0.0.82021-06-15
- 修复 删除文件时无法触发 v-model 的Bug
## 0.0.72021-05-12
- 新增 组件示例地址
## 0.0.62021-04-09
- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
## 0.0.52021-04-09
- 优化 更新组件示例
## 0.0.42021-04-09
- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
## 0.0.32021-02-05
- 调整为uni_modules目录规范
- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug

View File

@ -0,0 +1,224 @@
'use strict';
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const {
count,
sizeType = ['original', 'compressed'],
sourceType,
extension
} = opts
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseVideo(opts) {
const {
camera,
compressed,
maxDuration,
sourceType,
extension
} = opts;
return new Promise((resolve, reject) => {
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const {
tempFilePath,
duration,
size,
height,
width
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [
{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
}, ],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseAll(opts) {
const {
count,
extension
} = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
}
function uploadFiles(choosePromise, {
onChooseFile,
onUploadProgress
}) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return res
})
}
function chooseAndUploadFile(opts = {
type: 'all'
}) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
}
else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
export {
chooseAndUploadFile,
uploadCloudFiles
};

View File

@ -0,0 +1,673 @@
<template>
<view class="uni-file-picker">
<view v-if="title" class="uni-file-picker__header">
<text class="file-title">{{ title }}</text>
<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
</view>
<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly"
:image-styles="imageStyles" :files-list="filesList" :limit="limitLength" :disablePreview="disablePreview"
:delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot>
<view class="is-add">
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</view>
</slot>
</upload-image>
<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
:list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
@uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
<slot><button type="primary" size="mini">选择文件</button></slot>
</upload-file>
</view>
</template>
<script>
import {
chooseAndUploadFile,
uploadCloudFiles
} from './choose-and-upload-file.js'
import {
get_file_ext,
get_extname,
get_files_and_is_max,
get_file_info,
get_file_data
} from './utils.js'
import uploadImage from './upload-image.vue'
import uploadFile from './upload-file.vue'
let fileInput = null
/**
* FilePicker 文件选择上传
* @description 文件选择上传组件可以选择图片视频等任意文件并上传到当前绑定的服务空间
* @tutorial https://ext.dcloud.net.cn/plugin?id=4079
* @property {Object|Array} value 组件数据通常用来回显 ,类型由return-type属性决定
* @property {Boolean} disabled = [true|false] 组件禁用
* @value true 禁用
* @value false 取消禁用
* @property {Boolean} readonly = [true|false] 组件只读不可选择不显示进度不显示删除按钮
* @value true 只读
* @value false 取消只读
* @property {String} return-type = [array|object] 限制 value 格式当为 object 组件只能单选且会覆盖
* @value array 规定 value 属性的类型为数组
* @value object 规定 value 属性的类型为对象
* @property {Boolean} disable-preview = [true|false] 禁用图片预览 mode:grid 时生效
* @value true 禁用图片预览
* @value false 取消禁用图片预览
* @property {Boolean} del-icon = [true|false] 是否显示删除按钮
* @value true 显示删除按钮
* @value false 不显示删除按钮
* @property {Boolean} auto-upload = [true|false] 是否自动上传值为true则只触发@select,可自行上传
* @value true 自动上传
* @value false 取消自动上传
* @property {Number|String} limit 最大选择个数 h5 会自动忽略多选的部分
* @property {String} title 组件标题右侧显示上传计数
* @property {String} mode = [list|grid] 选择文件后的文件列表样式
* @value list 列表显示
* @value grid 宫格显示
* @property {String} file-mediatype = [image|video|all] 选择文件类型
* @value image 只选择图片
* @value video 只选择视频
* @value all 选择所有文件
* @property {Array} file-extname 选择文件后缀根据 file-mediatype 属性而不同
* @property {Object} list-style mode:list 时的样式
* @property {Object} image-styles 选择文件后缀根据 file-mediatype 属性而不同
* @event {Function} select 选择文件后触发
* @event {Function} progress 文件上传时触发
* @event {Function} success 上传成功触发
* @event {Function} fail 上传失败触发
* @event {Function} delete 文件从列表移除时触发
*/
export default {
name: 'uniFilePicker',
components: {
uploadImage,
uploadFile
},
options: {
virtualHost: true
},
emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
props: {
// #ifdef VUE3
modelValue: {
type: [Array, Object],
default () {
return []
}
},
// #endif
// #ifndef VUE3
value: {
type: [Array, Object],
default () {
return []
}
},
// #endif
disabled: {
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
delIcon: {
type: Boolean,
default: true
},
//
autoUpload: {
type: Boolean,
default: true
},
//
index: {
type: Number,
default: 0
},
// h5
limit: {
type: [Number, String],
default: 9
},
// grid | list | list-card
mode: {
type: String,
default: 'grid'
},
// image/video/all
fileMediatype: {
type: String,
default: 'image'
},
//
fileExtname: {
type: [Array, String],
default () {
return []
}
},
title: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: false,
// 线
dividline: false,
// 线
borderStyle: {}
}
}
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto'
}
}
},
readonly: {
type: Boolean,
default: false
},
returnType: {
type: String,
default: 'array'
},
sizeType: {
type: Array,
default () {
return ['original', 'compressed']
}
},
sourceType: {
type: Array,
default () {
return ['album', 'camera']
}
}
},
data() {
return {
files: [],
localValue: []
}
},
watch: {
// #ifndef VUE3
value: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal, oldVal) {
this.setValue(newVal, oldVal)
},
immediate: true
},
// #endif
},
computed: {
filesList() {
let files = []
this.files.forEach(v => {
files.push(v)
})
return files
},
showType() {
if (this.fileMediatype === 'image') {
return this.mode
}
return 'list'
},
limitLength() {
if (this.returnType === 'object') {
return 1
}
if (!this.limit) {
return 1
}
if (this.limit >= 9) {
return 9
}
return this.limit
}
},
created() {
// TODO
if (!(uniCloud.config && uniCloud.config.provider)) {
this.noSpace = true
uniCloud.chooseAndUploadFile = chooseAndUploadFile
}
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.form && this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
methods: {
/**
* 公开用户使用清空文件
* @param {Object} index
*/
clearFiles(index) {
if (index !== 0 && !index) {
this.files = []
this.$nextTick(() => {
this.setEmit()
})
} else {
this.files.splice(index, 1)
}
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 公开用户使用继续上传
*/
upload() {
let files = []
this.files.forEach((v, index) => {
if (v.status === 'ready' || v.status === 'error') {
files.push(Object.assign({}, v))
}
})
return this.uploadFiles(files)
},
async setValue(newVal, oldVal) {
const newData = async (v) => {
const reg = /cloud:\/\/([\w.]+\/?)\S*/
let url = ''
if (v.fileID) {
url = v.fileID
} else {
url = v.url
}
if (reg.test(url)) {
v.fileID = url
v.url = await this.getTempFileURL(url)
}
if (v.url) v.path = v.url
return v
}
if (this.returnType === 'object') {
if (newVal) {
await newData(newVal)
} else {
newVal = {}
}
} else {
if (!newVal) newVal = []
for (let i = 0; i < newVal.length; i++) {
let v = newVal[i]
await newData(v)
}
}
this.localValue = newVal
if (this.form && this.formItem && !this.is_reset) {
this.is_reset = false
this.formItem.setValue(this.localValue)
}
let filesData = Object.keys(newVal).length > 0 ? newVal : [];
this.files = [].concat(filesData)
},
/**
* 选择文件
*/
choose() {
if (this.disabled) return
if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
'array') {
uni.showToast({
title: `您最多选择 ${this.limitLength} 个文件`,
icon: 'none'
})
return
}
this.chooseFiles()
},
/**
* 选择文件并上传
*/
chooseFiles() {
const _extname = get_extname(this.fileExtname)
//
uniCloud
.chooseAndUploadFile({
type: this.fileMediatype,
compressed: false,
sizeType: this.sizeType,
sourceType: this.sourceType,
// TODO video
extension: _extname.length > 0 ? _extname : undefined,
count: this.limitLength - this.files.length, //9
onChooseFile: this.chooseFileCallback,
onUploadProgress: progressEvent => {
this.setProgress(progressEvent, progressEvent.index)
}
})
.then(result => {
this.setSuccessAndError(result.tempFiles)
})
.catch(err => {
console.log('选择失败', err)
})
},
/**
* 选择文件回调
* @param {Object} res
*/
async chooseFileCallback(res) {
const _extname = get_extname(this.fileExtname)
const is_one = (Number(this.limitLength) === 1 &&
this.disablePreview &&
!this.disabled) ||
this.returnType === 'object'
//
if (is_one) {
this.files = []
}
let {
filePaths,
files
} = get_files_and_is_max(res, _extname)
if (!(_extname && _extname.length > 0)) {
filePaths = res.tempFilePaths
files = res.tempFiles
}
let currentData = []
for (let i = 0; i < files.length; i++) {
if (this.limitLength - this.files.length <= 0) break
files[i].uuid = Date.now()
let filedata = await get_file_data(files[i], this.fileMediatype)
filedata.progress = 0
filedata.status = 'ready'
this.files.push(filedata)
currentData.push({
...filedata,
file: files[i]
})
}
this.$emit('select', {
tempFiles: currentData,
tempFilePaths: filePaths,
index: this.index
})
res.tempFiles = files
//
if (!this.autoUpload || this.noSpace) {
res.tempFiles = []
}
},
/**
* 批传
* @param {Object} e
*/
uploadFiles(files) {
files = [].concat(files)
return uploadCloudFiles.call(this, files, 5, res => {
this.setProgress(res, res.index, true)
})
.then(result => {
this.setSuccessAndError(result)
return result;
})
.catch(err => {
console.log(err)
})
},
/**
* 成功或失败
*/
async setSuccessAndError(res, fn) {
let successData = []
let errorData = []
let tempFilePath = []
let errorTempFilePath = []
for (let i = 0; i < res.length; i++) {
const item = res[i]
const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
if (index === -1 || !this.files) break
if (item.errMsg === 'request:fail') {
this.files[index].url = item.path
this.files[index].status = 'error'
this.files[index].errMsg = item.errMsg
// this.files[index].progress = -1
errorData.push(this.files[index])
errorTempFilePath.push(this.files[index].url)
} else {
this.files[index].errMsg = ''
this.files[index].fileID = item.url
const reg = /cloud:\/\/([\w.]+\/?)\S*/
if (reg.test(item.url)) {
this.files[index].url = await this.getTempFileURL(item.url)
} else {
this.files[index].url = item.url
}
this.files[index].status = 'success'
this.files[index].progress += 1
successData.push(this.files[index])
tempFilePath.push(this.files[index].fileID)
}
}
if (successData.length > 0) {
this.setEmit()
//
this.$emit('success', {
tempFiles: this.backObject(successData),
tempFilePaths: tempFilePath
})
}
if (errorData.length > 0) {
this.$emit('fail', {
tempFiles: this.backObject(errorData),
tempFilePaths: errorTempFilePath
})
}
},
/**
* 获取进度
* @param {Object} progressEvent
* @param {Object} index
* @param {Object} type
*/
setProgress(progressEvent, index, type) {
const fileLenth = this.files.length
const percentNum = (index / fileLenth) * 100
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
let idx = index
if (!type) {
idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
}
if (idx === -1 || !this.files[idx]) return
// fix by mehaotian 100 -1
this.files[idx].progress = percentCompleted - 1
//
this.$emit('progress', {
index: idx,
progress: parseInt(percentCompleted),
tempFile: this.files[idx]
})
},
/**
* 删除文件
* @param {Object} index
*/
delFile(index) {
this.$emit('delete', {
tempFile: this.files[index],
tempFilePath: this.files[index].url
})
this.files.splice(index, 1)
this.$nextTick(() => {
this.setEmit()
})
},
/**
* 获取文件名和后缀
* @param {Object} name
*/
getFileExt(name) {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
},
/**
* 处理返回事件
*/
setEmit() {
let data = []
if (this.returnType === 'object') {
data = this.backObject(this.files)[0]
this.localValue = data ? data : null
} else {
data = this.backObject(this.files)
if (!this.localValue) {
this.localValue = []
}
this.localValue = [...data]
}
// #ifdef VUE3
this.$emit('update:modelValue', this.localValue)
// #endif
// #ifndef VUE3
this.$emit('input', this.localValue)
// #endif
},
/**
* 处理返回参数
* @param {Object} files
*/
backObject(files) {
let newFilesData = []
files.forEach(v => {
newFilesData.push({
extname: v.extname,
fileType: v.fileType,
image: v.image,
name: v.name,
path: v.path,
size: v.size,
fileID: v.fileID,
url: v.url,
// bug, #694
uuid: v.uuid,
status: v.status,
cloudPath: v.cloudPath
})
})
return newFilesData
},
async getTempFileURL(fileList) {
fileList = {
fileList: [].concat(fileList)
}
const urls = await uniCloud.getTempFileURL(fileList)
return urls.fileList[0].tempFileURL || ''
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
}
}
}
</script>
<style>
.uni-file-picker {
/* #ifndef APP-NVUE */
box-sizing: border-box;
overflow: hidden;
width: 100%;
/* #endif */
flex: 1;
}
.uni-file-picker__header {
padding-top: 5px;
padding-bottom: 10px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: space-between;
}
.file-title {
font-size: 14px;
color: #333;
}
.file-count {
font-size: 14px;
color: #999;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
</style>

View File

@ -0,0 +1,326 @@
<template>
<view class="uni-file-picker__files">
<view v-if="!readonly" class="files-button" @click="choose">
<slot></slot>
</view>
<!-- :class="{'is-text-box':showType === 'list'}" -->
<view v-if="list.length > 0" class="uni-file-picker__lists is-text-box" :style="borderStyle">
<!-- ,'is-list-card':showType === 'list-card' -->
<view class="uni-file-picker__lists-box" v-for="(item ,index) in list" :key="index" :class="{
'files-border':index !== 0 && styles.dividline}"
:style="index !== 0 && styles.dividline &&borderLineStyle">
<view class="uni-file-picker__item">
<!-- :class="{'is-text-image':showType === 'list'}" -->
<!-- <view class="files__image is-text-image">
<image class="header-image" :src="item.logo" mode="aspectFit"></image>
</view> -->
<view class="files__name">{{item.name}}</view>
<view v-if="delIcon&&!readonly" class="icon-del-box icon-files" @click="delFile(index)">
<view class="icon-del icon-files"></view>
<view class="icon-del rotate"></view>
</view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.status === 'error'" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadFile",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
delIcon: {
type: Boolean,
default: true
},
limit: {
type: [Number, String],
default: 9
},
showType: {
type: String,
default: ''
},
listStyles: {
type: Object,
default () {
return {
//
border: true,
// 线
dividline: true,
// 线
borderStyle: {}
}
}
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
list() {
let files = []
this.filesList.forEach(v => {
files.push(v)
})
return files
},
styles() {
let styles = {
border: true,
dividline: true,
'border-style': {}
}
return Object.assign(styles, this.listStyles)
},
borderStyle() {
let {
borderStyle,
border
} = this.styles
let obj = {}
if (!border) {
obj.border = 'none'
} else {
let width = (borderStyle && borderStyle.width) || 1
width = this.value2px(width)
let radius = (borderStyle && borderStyle.radius) || 5
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (borderStyle && borderStyle.style) || 'solid',
'border-color': (borderStyle && borderStyle.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
},
borderLineStyle() {
let obj = {}
let {
borderStyle
} = this.styles
if (borderStyle && borderStyle.color) {
obj['border-color'] = borderStyle.color
}
if (borderStyle && borderStyle.width) {
let width = borderStyle && borderStyle.width || 1
let style = borderStyle && borderStyle.style || 0
if (typeof width === 'number') {
width += 'px'
} else {
width = width.indexOf('px') ? width : width + 'px'
}
obj['border-width'] = width
if (typeof style === 'number') {
style += 'px'
} else {
style = style.indexOf('px') ? style : style + 'px'
}
obj['border-top-style'] = style
}
let classles = ''
for (let i in obj) {
classles += `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
console.log("1111", item, index)
this.$emit("uploadFiles", {
item,
index
})
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__files {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: flex-start;
}
.files-button {
// border: 1px red solid;
}
.uni-file-picker__lists {
position: relative;
margin-top: 5px;
overflow: hidden;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 14px;
background-color: rgba(0, 0, 0, 0.4);
}
.uni-file-picker__lists-box {
position: relative;
}
.uni-file-picker__item {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding: 8px 10px;
padding-right: 5px;
padding-left: 10px;
}
.files-border {
border-top: 1px #eee solid;
}
.files__name {
flex: 1;
font-size: 14px;
color: #666;
margin-right: 25px;
/* #ifndef APP-NVUE */
word-break: break-all;
word-wrap: break-word;
/* #endif */
}
.icon-files {
/* #ifndef APP-NVUE */
position: static;
background-color: initial;
/* #endif */
}
// .icon-files .icon-del {
// background-color: #333;
// width: 12px;
// height: 1px;
// }
.is-list-card {
border: 1px #eee solid;
margin-bottom: 5px;
border-radius: 5px;
box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.1);
padding: 5px;
}
.files__image {
width: 40px;
height: 40px;
margin-right: 10px;
}
.header-image {
width: 100%;
height: 100%;
}
.is-text-box {
border: 1px #eee solid;
border-radius: 5px;
}
.is-text-image {
width: 25px;
height: 25px;
margin-left: 5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
margin: auto 0;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 0px;
bottom: 0;
right: 5px;
height: 26px;
width: 26px;
// border-radius: 50%;
// background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 1px;
background-color: #333;
// border-radius: 1px;
}
/* #ifdef H5 */
@media all and (min-width: 768px) {
.uni-file-picker__files {
max-width: 375px;
}
}
/* #endif */
</style>

View File

@ -0,0 +1,292 @@
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<!-- <view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view> -->
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
const widthDefaultValue = 1
const radiusDefaultValue = 3
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || widthDefaultValue
width = this.value2px(width)
let radius = (border && border.radius) || radiusDefaultValue
radius = this.value2px(radius)
obj = {
'border-width': width,
// 'border-style': (border && border.style) || 'solid',
// 'border-color': (border && border.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.url)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
// margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
// margin: 5px;
// border: 1px #eee solid;
border-radius: 10px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 3px;
right: 3px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>

View File

@ -0,0 +1,109 @@
/**
* 获取文件名和后缀
* @param {String} name
*/
export const get_file_ext = (name) => {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
}
/**
* 获取扩展名
* @param {Array} fileExtname
*/
export const get_extname = (fileExtname) => {
if (!Array.isArray(fileExtname)) {
let extname = fileExtname.replace(/(\[|\])/g, '')
return extname.split(',')
} else {
return fileExtname
}
return []
}
/**
* 获取文件和检测是否可选
*/
export const get_files_and_is_max = (res, _extname) => {
let filePaths = []
let files = []
if(!_extname || _extname.length === 0){
return {
filePaths,
files
}
}
res.tempFiles.forEach(v => {
let fileFullName = get_file_ext(v.name)
const extname = fileFullName.ext.toLowerCase()
if (_extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
return {
filePaths,
files
}
}
/**
* 获取图片信息
* @param {Object} filepath
*/
export const get_file_info = (filepath) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
/**
* 获取封装数据
*/
export const get_file_data = async (files, type = 'image') => {
// 最终需要上传数据库的数据
let fileFullName = get_file_ext(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await get_file_info(files.path)
delete filedata.video
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
} else {
delete filedata.image
}
return filedata
}

View File

@ -0,0 +1,83 @@
{
"id": "uni-file-picker",
"displayName": "uni-file-picker 文件选择上传",
"version": "1.0.4",
"description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
"keywords": [
"uni-ui",
"uniui",
"图片上传",
"文件上传"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@ -0,0 +1,11 @@
## FilePicker 文件选择上传
> **组件名uni-file-picker**
> 代码块: `uFilePicker`
文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,102 @@
## 1.9.112025-08-20
- 修复 uni-popup-dialog组件设置 borderRadius 不生效的 Bug
## 1.9.102025-07-18
- 修复 nvue 下弹窗样式错乱的问题 ,更新依赖 uni-transition 组件
- 更新 示例取消 borderRadius 属性 ,如需内容圆角,用户应该直接在内容插槽中实现
## 1.9.92025-06-11
- 修复 uni-popup-dialog 中 setVal 方法报错的问题
- 修复 uni-popup-dialog 数据双向绑定问题。
## 1.9.82025-04-16
- 修复 更新组件示例 ,解决更新数据或保存项目导致弹窗消失的问题
## 1.9.72025-04-14
- 修复 uni-popup-dialog 弹出框在vue3中双向绑定问题
## 1.9.62025-01-08
- 修复 示例中过期图片地址
## 1.9.52024-10-15
- 修复 微信小程序中的getSystemInfo警告
## 1.9.22024-09-21
- 修复 uni-popup在android上的重复点击弹出位置不正确的bug
## 1.9.12024-04-02
- 修复 uni-popup-dialog vue3下使用value无法进行绑定的bug(双向绑定兼容旧写法)
## 1.9.02024-03-28
- 修复 uni-popup-dialog 双向绑定时初始化逻辑修正
## 1.8.92024-03-20
- 修复 uni-popup-dialog 数据输入时修正为双向绑定
## 1.8.82024-02-20
- 修复 uni-popup 在微信小程序下出现文字向上闪动的bug
## 1.8.72024-02-02
- 新增 uni-popup-dialog 新增属性focusinput模式下是否自动自动聚焦
## 1.8.62024-01-30
- 新增 uni-popup-dialog 新增属性maxLength:限制输入框字数
## 1.8.52024-01-26
- 新增 uni-popup-dialog 新增属性showClose:控制关闭按钮的显示
## 1.8.42023-11-15
- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close`
## 1.8.32023-04-17
- 修复 uni-popup 重复打开时的 bug
## 1.8.22023-02-02
- uni-popup-dialog 组件新增 inputType 属性
## 1.8.12022-12-01
- 修复 nvue 下 v-show 报错
## 1.8.02022-11-29
- 优化 主题样式
## 1.7.92022-04-02
- 修复 弹出层内部无法滚动的bug
## 1.7.82022-03-28
- 修复 小程序中高度错误的bug
## 1.7.72022-03-17
- 修复 快速调用open出现问题的Bug
## 1.7.62022-02-14
- 修复 safeArea 属性不能设置为false的bug
## 1.7.52022-01-19
- 修复 isMaskClick 失效的bug
## 1.7.42022-01-19
- 新增 cancelText \ confirmText 属性 ,可自定义文本
- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
## 1.7.32022-01-13
- 修复 设置 safeArea 属性不生效的bug
## 1.7.22021-11-26
- 优化 组件示例
## 1.7.12021-11-26
- 修复 vuedoc 文字错误
## 1.7.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
## 1.6.22021-08-24
- 新增 支持国际化
## 1.6.12021-07-30
- 优化 vue3下事件警告的问题
## 1.6.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.5.02021-06-23
- 新增 mask-click 遮罩层点击事件
## 1.4.52021-06-22
- 修复 nvue 平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.42021-06-18
- 修复 H5平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.32021-06-08
- 修复 错误的 watch 字段
- 修复 safeArea 属性不生效的问题
- 修复 点击内容再点击遮罩无法关闭的Bug
## 1.4.22021-05-12
- 新增 组件示例地址
## 1.4.12021-04-29
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
## 1.4.0 2021-04-29
- 新增 type 属性的 left\right 值,支持左右弹出
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
- 新增 safeArea 属性,是否适配底部安全区
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
- 修复 App 端弹出等待的Bug
- 优化 提升低配设备性能,优化动画卡顿问题
- 优化 更简单的组件自定义方式
## 1.2.92021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.2.82021-02-05
- 调整为uni_modules目录规范
## 1.2.72021-02-05
- 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

View File

@ -0,0 +1,330 @@
<template>
<view class="uni-popup-dialog" :style="{ borderRadius }">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" :maxlength="maxlength" v-model="val" :type="inputType"
:placeholder="placeholderText" :focus="focus">
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" v-if="showClose" @click="closeDialog">
<text class="uni-dialog-button-text">{{closeText}}</text>
</view>
<view class="uni-dialog-button" :class="showClose?'uni-border-left':''" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const {
t
} = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {Boolean} focus input模式下是否自动聚焦默认为true
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式
* @value base 基础对话框
* @value input 可输入对话框
* @showClose {Boolean} 是否显示关闭按钮
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @property {Number} maxlength 输入
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits: ['confirm', 'close', 'update:modelValue', 'input'],
props: {
inputType: {
type: String,
default: 'text'
},
showClose: {
type: Boolean,
default: true
},
// #ifdef VUE2
value: {
type: [String, Number],
default: ''
},
// #endif
// #ifdef VUE3
modelValue: {
type: [Number, String],
default: ''
},
// #endif
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
},
cancelText: {
type: String,
default: ''
},
confirmText: {
type: String,
default: ''
},
maxlength: {
type: Number,
default: -1,
},
focus: {
type: Boolean,
default: true,
},
borderRadius: {
type: String,
default: '11px',
}
},
data() {
return {
dialogType: 'error',
val: ""
}
},
computed: {
okText() {
return this.confirmText || t("uni-popup.ok")
},
closeText() {
return this.cancelText || t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
this.setVal(val)
},
// #ifdef VUE3
modelValue(val) {
this.setVal(val)
},
// #endif
val(val) {
// #ifdef VUE2
// TODO vue2
this.$emit('input', val);
// #endif
// #ifdef VUE3
// TODO  vue3
this.$emit('update:modelValue', val);
// #endif
}
},
created() {
//
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value;
// #ifdef VUE3
this.val = this.modelValue;
// #endif
} else {
this.dialogType = this.type
}
},
methods: {
/**
* 给val属性赋值
*/
setVal(val) {
if (this.maxlength != -1 && this.mode === 'input') {
this.val = val.slice(0, this.maxlength);
} else {
this.val = val
}
},
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input') {
this.$emit('confirm', this.val)
} else {
this.$emit('confirm')
}
if (this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if (this.beforeClose) return
this.popup.close()
},
close() {
this.popup.close()
}
}
}
</script>
<style lang="scss">
.uni-popup-dialog {
width: 300px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: #007aff;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: #4cd964;
}
.uni-popup__warn {
color: #f0ad4e;
}
.uni-popup__error {
color: #dd524d;
}
.uni-popup__info {
color: #909399;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" >
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>

View File

@ -0,0 +1,188 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
// TODO
bottomData: [{
text: '微信',
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
name: 'ali'
},
{
text: 'QQ',
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/unicloudlogo.png',
name: 'sina'
},
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "pleace enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}

View File

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

View File

@ -0,0 +1,26 @@
export default {
data() {
return {
}
},
created(){
this.popup = this.getParent()
},
methods:{
/**
* 获取父元素实例
*/
getParent(name = 'uniPopup') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
}
}

View File

@ -0,0 +1,90 @@
<template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
<view @click.stop>
<slot></slot>
</view>
</view>
</template>
<script>
type CloseCallBack = ()=> void;
let closeCallBack:CloseCallBack = () :void => {};
export default {
emits:["close","clickMask"],
data() {
return {
isShow:false,
isOpen:false
}
},
props: {
maskClick: {
type: Boolean,
default: true
},
},
watch: {
// 设置show = true 时,如果没有 open 需要设置为 open
isShow:{
handler(isShow) {
// console.log("isShow",isShow)
if(isShow && this.isOpen == false){
this.isOpen = true
}
},
immediate:true
},
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
isOpen:{
handler(isOpen) {
// console.log("isOpen",isOpen)
if(isOpen && this.isShow == false){
this.isShow = true
}
},
immediate:true
}
},
methods:{
open(){
// ...funs : CloseCallBack[]
// if(funs.length > 0){
// closeCallBack = funs[0]
// }
this.isOpen = true;
},
clickMask(){
if(this.maskClick == true){
this.$emit('clickMask')
this.close()
}
},
close(): void{
this.isOpen = false;
this.$emit('close')
closeCallBack()
},
hiden(){
this.isShow = false
},
show(){
this.isShow = true
}
}
}
</script>
<style>
.popup-root {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 100%;
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
z-index: 99;
}
</style>

Some files were not shown because too many files have changed in this diff Show More