This commit is contained in:
dev
2025-10-28 09:25:46 +08:00
commit 9431e325df
1089 changed files with 143917 additions and 0 deletions
+447
View File
@@ -0,0 +1,447 @@
<!-- 签到界面 -->
<template>
<s-layout title="签到有礼">
<s-empty v-if="state.loading" icon="/static/data-empty.png" text="签到活动还未开始" />
<view v-if="state.loading" />
<view class="sign-wrap" v-else-if="!state.loading">
<!-- 签到日历 -->
<view class="content-box calendar">
<view class="sign-everyday ss-flex ss-col-center ss-row-between ss-p-x-30">
<text class="sign-everyday-title">签到日历</text>
<view class="sign-num-box">
已连续签到 <text class="sign-num">{{ state.signInfo.continuousDay }}</text>
</view>
</view>
<view class="list acea-row row-between-wrapper" style="
padding: 0 30rpx;
height: 240rpx;
display: flex;
justify-content: space-between;
align-items: center;
">
<view class="item" v-for="(item, index) in state.signConfigList" :key="index">
<view :class="
(index === state.signConfigList.length ? 'reward' : '') +
' ' +
(state.signInfo.continuousDay >= item.day ? 'rewardTxt' : '')
">
{{ item.day }}
</view>
<view class="venus" :class="
(index + 1 === state.signConfigList.length ? 'reward' : '') +
' ' +
(state.signInfo.continuousDay >= item.day ? 'venusSelect' : '')
">
</view>
<view class="num" :class="state.signInfo.continuousDay >= item.day ? 'on' : ''">
+ {{ item.point }}
</view>
</view>
</view>
<!-- 签到按钮 -->
<view class="myDateTable">
<view class="ss-flex ss-col-center ss-row-center sign-box ss-m-y-40">
<button class="ss-reset-button sign-btn" v-if="!state.signInfo.todaySignIn" @tap="onSign">
签到
</button>
<button class="ss-reset-button already-btn" v-else disabled> 已签到 </button>
</view>
</view>
</view>
<!-- 签到说明 TODO @签到这里改成已累计签到改版接入 sheepjs -->
<view class="bg-white ss-m-t-16 ss-p-t-30 ss-p-b-60 ss-p-x-40">
<view class="activity-title ss-m-b-30">签到说明</view>
<view class="activity-des">1已累计签到{{state.signInfo.totalDay}}</view>
<view class="activity-des">
2据说连续签到第 {{ state.maxDay }} 天可获得超额积分一定要坚持签到哦~~~
</view>
</view>
</view>
<!-- 签到结果弹窗 -->
<su-popup :show="state.showModel" type="center" round="10" :isMaskClick="false">
<view class="model-box ss-flex-col">
<view class="ss-m-t-56 ss-flex-col ss-col-center">
<text class="cicon-check-round"></text>
<view class="score-title">
<text v-if="state.signResult.point">{{ state.signResult.point }} 积分 </text>
<text v-if="state.signResult.experience"> {{ state.signResult.experience }} 经验</text>
</view>
<view class="model-title ss-flex ss-col-center ss-m-t-22 ss-m-b-30">
已连续打卡 {{ state.signResult.day }}
</view>
</view>
<view class="model-bg ss-flex-col ss-col-center ss-row-right">
<view class="title ss-m-b-64">签到成功</view>
<view class="ss-m-b-40">
<button class="ss-reset-button confirm-btn" @tap="onConfirm">确认</button>
</view>
</view>
</view>
</su-popup>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import {
onReady
} from '@dcloudio/uni-app';
import {
reactive
} from 'vue';
import SignInApi from '@/sheep/api/member/signin';
const headerBg = sheep.$url.css('/static/img/shop/app/sign.png');
const state = reactive({
loading: true,
signInfo: {}, // 签到信息
signConfigList: [], // 签到配置列表
maxDay: 0, // 最大的签到天数
showModel: false, // 签到弹框
signResult: {}, // 签到结果
});
// 发起签到
async function onSign() {
const {
code,
data
} = await SignInApi.createSignInRecord();
if (code !== 0) {
return;
}
state.showModel = true;
state.signResult = data;
// 重新获得签到信息
await getSignInfo();
}
// 签到确认刷新页面
function onConfirm() {
state.showModel = false;
}
// 获得个人签到统计
async function getSignInfo() {
const {
code,
data
} = await SignInApi.getSignInRecordSummary();
if (code !== 0) {
return;
}
state.signInfo = data;
state.loading = false;
}
// 获取签到配置
async function getSignConfigList() {
const {
code,
data
} = await SignInApi.getSignInConfigList();
if (code !== 0) {
return;
}
state.signConfigList = data;
if (data.length > 0) {
state.maxDay = data[data.length - 1].day;
}
}
onReady(() => {
getSignInfo();
getSignConfigList();
});
// TODO 1)css 需要优化,例如说引入的图片;2)删除多余的样式
</script>
<style lang="scss" scoped>
.header-box {
border-top: 2rpx solid rgba(#dfdfdf, 0.5);
}
// 日历
.calendar {
background: #fff;
.sign-everyday {
height: 100rpx;
background: rgba(255, 255, 255, 1);
border: 2rpx solid rgba(223, 223, 223, 0.4);
.sign-everyday-title {
font-size: 32rpx;
color: rgba(51, 51, 51, 1);
font-weight: 500;
}
.sign-num-box {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
.sign-num {
font-size: 30rpx;
font-weight: 600;
color: #4A8EFC;
padding: 0 10rpx;
font-family: OPPOSANS;
}
}
}
// 年月日
.bar {
height: 100rpx;
.date {
font-size: 30rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #333333;
line-height: normal;
}
}
.cicon-back {
margin-top: 6rpx;
font-size: 30rpx;
color: #c4c4c4;
line-height: normal;
}
.cicon-forward {
margin-top: 6rpx;
font-size: 30rpx;
color: #c4c4c4;
line-height: normal;
}
// 星期
.week {
.week-item {
font-size: 24rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
flex: 1;
}
}
// 日历表
.myDateTable {
display: flex;
flex-wrap: wrap;
.dateCell {
width: calc(750rpx / 7);
height: 80rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
}
}
.is-sign {
width: 48rpx;
height: 48rpx;
position: relative;
.is-sign-num {
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
line-height: normal;
}
.is-sign-image {
position: absolute;
left: 0;
top: 0;
width: 48rpx;
height: 48rpx;
}
}
.cell-num {
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #333333;
line-height: normal;
}
.cicon-title {
position: absolute;
right: -10rpx;
top: -6rpx;
font-size: 20rpx;
color: red;
}
// 签到按钮
.sign-box {
height: 140rpx;
width: 100%;
.sign-btn {
width: 710rpx;
height: 80rpx;
border-radius: 35rpx;
font-size: 30rpx;
font-weight: 500;
box-shadow: 0 0.2em 0.5em rgba(#4A8EFC, 0.4);
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
color: #fff;
}
.already-btn {
width: 710rpx;
height: 80rpx;
border-radius: 35rpx;
font-size: 30rpx;
font-weight: 500;
}
}
.model-box {
width: 520rpx;
// height: 590rpx;
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
// background: linear-gradient(177deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 10rpx;
.cicon-check-round {
font-size: 70rpx;
color: #fff;
}
.score-title {
font-size: 34rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #fcff00;
}
.model-title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.model-bg {
width: 520rpx;
height: 344rpx;
background-size: 100% 100%;
background-image: v-bind(headerBg);
background-repeat: no-repeat;
border-radius: 0 0 10rpx 10rpx;
.title {
font-size: 34rpx;
font-weight: bold;
// color: var(--ui-BG-Main);
color: #4A8EFC;
}
.subtitle {
font-size: 26rpx;
font-weight: 500;
color: #999999;
}
.cancel-btn {
width: 220rpx;
height: 70rpx;
border: 2rpx solid #4A8EFC;
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #4A8EFC;
line-height: normal;
margin-right: 10rpx;
}
.confirm-btn {
width: 220rpx;
height: 70rpx;
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
box-shadow: 0 0.2em 0.5em rgba(#4A8EFC, 0.4);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
line-height: normal;
}
}
}
//签到说明
.activity-title {
font-size: 32rpx;
font-weight: 500;
color: #333333;
line-height: normal;
}
.activity-des {
font-size: 26rpx;
font-weight: 500;
color: #666666;
line-height: 40rpx;
}
.reward {
background-image: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/reward.png');
width: 75rpx;
height: 56rpx;
}
.rewardTxt {
width: 74rpx;
height: 32rpx;
background-color: #f4b409;
border-radius: 16rpx;
font-size: 20rpx;
color: #a57d3f;
line-height: 32rpx;
text-align: center;
}
.venus {
background-image: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/venus.png');
background-repeat: no-repeat;
background-size: 100% 100%;
width: 56rpx;
height: 56rpx;
margin: 10rpx 0;
}
.venusSelect {
background-image: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/venusSelect.png');
}
.num {
font-size: 36rpx;
font-family: 'Guildford Pro';
}
.item {
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eee;
height: 130rpx;
}
.on {
background-color: #999 !important;
}
</style>
+578
View File
@@ -0,0 +1,578 @@
<template>
<s-layout title="月度账单" navbar="normal" onShareAppMessage>
<view class="block">
<scroll-view scroll-y="true" style="height: 84vh;">
<view class="bottom"></view>
<view class="top">
<view class="timeSelect">
<view class="monthBtn" @click="changeMonth('sub')">上个月</view>
<picker mode="date" :value="date" @change="bindDateChange($event)" fields='month' class="datePicker">
<view class="uni-input">{{ formatDate(date)}}<u-icon name="arrow-down"></u-icon></view>
</picker>
<view class="monthBtn" @click="changeMonth('add')">下个月</view>
</view>
<view class="totalData">
<text class="label">本月为您节省()</text>
<text class="money">{{toFix(totalMonitorData.economizeMoney ,2)}}</text>
<text class="power">约等于电量:{{ toFix((totalMonitorData.economizeMoney / 0.90961475),2) }}</text>
<view class="dataInfo">
<view class="info">
<text class="number">{{ toFix(totalMonitorData.totalSpent,2)}}</text>
<text class="label">本月支出()</text>
</view>
<view class="info">
<text class="number">{{ toFix(totalMonitorData.totalPower,2)}}</text>
<text class="label">本月电量()</text>
</view>
<view class="info">
<text class="number">{{totalMonitorData.totalOrders}}</text>
<text class="label">总笔数()</text>
</view>
</view>
</view>
</view>
<view class="class">
<text class="title">支出分类</text>
<view class="charts-box">
<qiun-data-charts type="ring" :opts="expendDataOpts" :chartData="expendData" />
</view>
<view class="expendLegen">
<view class="item" v-for="(item,index) in expendLegend" :key="index">
<view class="left">
<text class="icon" :style="{background: item.color}"></text>
{{item.label}}
</view>
<text class="value">{{item.value}}</text>
</view>
</view>
</view>
<view class="class">
<text class="title">充电优惠分类</text>
<view class="charts-box">
<qiun-data-charts type="ring" :opts="powerDiscountOpts" :chartData="powerDiscountData" />
</view>
<view class="powerDiscountLegen">
<view class="item" v-for="(item,index) in powerDiscountLegend" :key="index">
<view class="left">
<text class="icon" :style="{background: item.color}"></text>
{{item.label}}({{item.percent}})
</view>
<text class="line"></text>
<text class="value">{{item.value}}</text>
</view>
</view>
</view>
</scroll-view>
<view class="btn">
<u-button type="primary" text="去充电" @click="goToStation"></u-button>
</view>
</view>
</s-layout>
</template>
<script>
import moment from 'dayjs';
import {
getOrderReport
} from "@/sheep/api/charge/order.js";
export default {
data() {
return {
date: new Date,
expendLegend: [{
color: "#01b3c3",
label: '充电支出',
value: 0
},
{
color: "#2bcbdb",
label: '超时占位费',
value: 0
},
{
color: "#73e4ee",
label: '省钱会员',
value: 0
},
{
color: "#a3f1f8",
label: '券包套餐',
value: 0
},
{
color: "#bcfaf8",
label: '零售支出',
value: 0
}
],
expendData: {
series: [{
data: [{
name: '充电支出',
value: 0
},
{
name: '超时占位费',
value: 0
},
{
name: '省钱会员',
value: 0
},
{
name: '券包套餐',
value: 0
},
{
name: '零售支出',
value: 0
},
]
}]
},
expendDataOpts: {
rotate: false,
rotateLock: false,
color: ["#01b3c3", "#2bcbdb", "#73e4ee", "#a3f1f8", "#bcfaf8"],
padding: [5, 5, 5, 5],
dataLabel: false,
enableScroll: false,
legend: {
show: false,
position: "bottom",
lineHeight: 25
},
title: {
name: "0.00",
fontSize: 25,
color: "#000"
},
subtitle: {
name: "本月支出(元)",
fontSize: 15,
color: "#737169"
},
extra: {
ring: {
ringWidth: 30,
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 15,
border: false,
borderWidth: 3,
borderColor: "#FFFFFF"
}
}
},
powerDiscountLegend: [{
color: "#fd4c03",
label: '会员优惠',
value: 0,
percent: '0%'
},
{
color: "#f97729",
label: '卡券优惠',
value: 0,
percent: '0%'
},
{
color: "#fc9a56",
label: '商家优惠',
value: 0,
percent: '0%'
},
{
color: "#fcba8f",
label: '红包优惠',
value: 0,
percent: '0%'
},
{
color: "#ffdebf",
label: '碳积分优惠',
value: 0,
percent: '0%'
}
],
powerDiscountOpts: {
rotate: false,
rotateLock: false,
color: ["#fd4c03", "#f97729", "#fc9a56", "#fcba8f", "#ffdebf"],
// padding: [5,5,5,5],
dataLabel: false,
enableScroll: false,
legend: {
show: false,
position: "bottom",
lineHeight: 25
},
title: {
name: "0.00",
fontSize: 25,
color: "#000"
},
subtitle: {
name: "充电节省(元)",
fontSize: 15,
color: "#737169"
},
extra: {
ring: {
ringWidth: 30,
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 15,
border: false,
borderWidth: 3,
borderColor: "#FFFFFF"
}
}
},
powerDiscountData: {
series: [{
data: [{
name: '会员优惠',
value: 0
},
{
name: '卡券优惠',
value: 0
},
{
name: '商家优惠',
value: 0
},
{
name: '红包优惠',
value: 0
},
{
name: '碳积分优惠',
value: 0
},
]
}]
},
expendParmasMap: new Map([
['chargingExpense', '充电支出'],
['timeoutExpense', '超时占位费'],
['membershipExpense', '省钱会员'],
['voucherPackageExpense', '券包套餐'],
['retailExpense', '零售支出']
]),
powerDiscountParmasMap: new Map([
['membershipCouponDiscount', '会员优惠'],
['cardCouponDiscount', '卡券优惠'],
['merchantCouponDiscount', '商家优惠'],
['redPacketCouponDiscount', '红包优惠'],
['carbonCreditCouponDiscount', '碳积分优惠']
]),
totalMonitorData: {
economizeMoney: 0,
totalSpent: 0,
totalPower: 0,
totalOrders: 0,
}
};
},
filters: {
},
methods: {
formatDate(date) {
return moment(date).format('YYYY年MM月')
},
toFix(data, num) {
if (!data) {
return '0.00'
}
return Number(data).toFixed(num);
},
goToStation() {
uni.reLaunch({
url: '/pages/home-page/home-page'
})
},
changeMonth(type) {
if (type == 'sub') {
this.date = moment(this.date).subtract(1, 'month');
} else {
this.date = moment(this.date).add(1, 'month');
}
this.getReportData();
},
bindDateChange(e) {
this.date = e.detail.value
this.getReportData();
},
async getReportData() {
try {
const startTs = moment(this.date).startOf('month').valueOf();
const endTs = moment(this.date).endOf('month').valueOf();
const {
data
} = await getOrderReport(startTs, endTs);
if (data) {
this.totalMonitorData = data;
for (const key in data) {
if (this.expendParmasMap.get(key)) {
const findLegend = this.expendLegend.find(k => k.label == this.expendParmasMap.get(key));
this.$set(findLegend, 'value', Number(data[key]).toFixed(2));
const findData = this.expendData.series[0].data.find(k => k.name == this.expendParmasMap.get(key));
this.$set(findData, 'value', Number(Number(data[key]).toFixed(2)));
}
if (this.powerDiscountParmasMap.get(key)) {
const findLegend = this.powerDiscountLegend.find(k => k.label == this.powerDiscountParmasMap.get(
key));
this.$set(findLegend, 'value', Number(data[key]).toFixed(2));
const findData = this.powerDiscountData.series[0].data.find(k => k.name == this.powerDiscountParmasMap
.get(key));
this.$set(findData, 'value', Number(Number(data[key]).toFixed(2)));
}
}
this.expendDataOpts.title.name = this.expendLegend.reduce((pre, item) => {
pre = Number(pre) + Number(item.value);
return (pre).toFixed(2);
}, 0)
this.powerDiscountOpts.title.name = this.powerDiscountLegend.reduce((pre, item) => {
pre = Number(pre) + Number(item.value);
return (pre).toFixed(2);
}, 0)
this.powerDiscountLegend = this.powerDiscountLegend.map((ele, index, arr) => {
const total = arr.reduce((sum, i) => {
sum += Number(i.value || 0);
return sum;
}, 0)
ele.percent = (ele.value / total * 100).toFixed(2) + '%';
return ele;
})
} else {
this.expendLegend.forEach(item => item.value = 0);
this.expendDataOpts.title.name = '0.00';
this.powerDiscountOpts.title.name = '0.00';
this.powerDiscountLegend = this.powerDiscountLegend.map(item => {
item.percent = 0 + '%';
item.value = 0;
return item;
});
this.powerDiscountData.series[0].data.forEach(item => item.value = 0);
this.expendData.series[0].data.forEach(item => item.value = 0);
this.totalMonitorData = {
economizeMoney: 0,
totalSpent: 0,
totalPower: 0,
totalOrders: 0,
}
}
// consoel.log(this.expendDataOpts, this.e)
} catch (e) {
//TODO handle the exception
}
}
},
onShow() {
this.getReportData();
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f5f5f5;
}
.block {
position: relative;
.bottom {
position: absolute;
z-index: -1;
top: -10px;
height: 23vh;
width: 750rpx;
background: linear-gradient(45deg, #00c3ffa6, #0b91ff);
}
.top,
.class {
width: 660rpx;
margin: 20rpx auto;
padding: 25rpx;
border-radius: 20rpx;
background-color: #fff;
}
.top {
.timeSelect {
display: flex;
background: #f5f5f5;
justify-content: space-between;
align-items: center;
width: 85%;
margin: auto;
border-radius: 40rpx;
padding: 8rpx;
.monthBtn {
background-color: #fff;
// border-radius: 25%;
padding: 10rpx 0;
display: flex;
justify-content: center;
align-items: center;
width: 25%;
border-radius: 25rpx;
}
.datePicker {
width: 50%;
.uni-input {
display: flex;
justify-content: center;
align-items: center;
}
}
}
.totalData {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 25rpx;
.label {
color: #bcb6b3;
}
.money {
font-size: 55rpx;
font-weight: 600;
color: #42a5f5;
display: inline-block;
margin: 20rpx 0rpx;
}
.power {
display: inline-block;
padding: 10rpx 15rpx;
font-size: 30rpx;
margin: 0rpx 0rpx 20rpx;
color: #42a5f5;
text-align: center;
width: 50%;
border-radius: 25rpx;
background-color: rgba(66, 165, 245, 0.3);
}
.dataInfo {
display: flex;
width: 100%;
justify-content: space-around;
border-radius: 15rpx;
padding: 20rpx 0;
background-color: rgba(66, 165, 245, 0.1);
.info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.number {
font-size: 35rpx;
font-weight: 600;
display: inline-block;
margin-bottom: 15rpx;
}
}
}
}
}
.class {
.title {
font-size: 30rpx;
font-weight: 600;
}
.charts-box {
width: 100%;
height: 30vh;
}
.expendLegen {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.item {
width: 48%;
display: flex;
margin-bottom: 15rpx;
justify-content: space-between;
align-items: center;
}
}
.powerDiscountLegen {
.item {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.line {
display: inline-block;
border-bottom: 2rpx dashed #bab9b5;
height: 2rpx;
width: 43%;
margin: 0 20rpx;
}
}
}
.left {
font-size: 30rpx;
}
.icon {
display: inline-block;
width: 25rpx;
height: 25rpx;
margin-right: 10rpx;
}
.value {
font-weight: 600;
}
}
.btn {
position: fixed;
bottom: 0;
padding: 30rpx 0;
width: 750rpx;
background-color: #fff;
.u-button {
margin: auto !important;
width: 700rpx !important;
border-radius: 50rpx !important;
height: 103rpx !important;
.u-button__text {
font-size: 37.5rpx !important;
}
}
}
}
</style>
+525
View File
@@ -0,0 +1,525 @@
<template>
<s-layout title="正在充电" navbar="normal" onShareAppMessage @clickLeft="clearTimer">
<view class="container">
<view class="charging-percentage">
<view class="percentage-container">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/147.png" class="circle"></image>
<view class="percentage">
<text class="value">{{ data.soc?data.soc:0 }}</text>
<text>%</text>
</view>
<view class="status">
充电中...
</view>
</view>
<view class="name">
<text>{{data.stationName}}</text>
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27574.png"></image>
</view>
</view>
<view class="detail-info">
<view class="rest-time">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27698 (2).png"></image>
<text>预计充满时间{{ estimatedFullTime }}</text>
</view>
<view class="cost-info">
<view class="item">
<view class="name">
已充电量
</view>
<view class="value">
{{data.chargedPower}}
</view>
</view>
<view class="line"></view>
<view class="item">
<view class="name">
充电时长
</view>
<view class="value">
{{chargingDuration}}
</view>
</view>
<view class="line"></view>
<view class="item">
<view class="name">
费用总计
</view>
<view class="value">
{{data.chargedAmount}}
</view>
</view>
</view>
<view class="cable-info">
<view class="item">
<text>实时电流</text>
<text class="value">{{data.outputCurrent}}</text>
</view>
<view class="item">
<text>实时电压</text>
<text class="value">{{data.outputVoltage}}</text>
</view>
<view class="item">
<text>实时功率</text>
<text class="value">{{ (data.outputCurrent * data.outputVoltage).toFixed(2) }}KW</text>
</view>
</view>
<view class="split-line"></view>
<view style="width:100%;">
<view class="header">
<view class="line"></view>
<view>
订单信息
</view>
</view>
</view>
<view style="width: 100%;">
<view class="list-item">
<text>订单编号</text>
<text class="value">{{data.seqNumber}}</text>
</view>
<view class="list-item">
<text>绑定车牌</text>
<text class="value">{{data.carNumber}}</text>
</view>
<view class="list-item">
<text>充电时间</text>
<text class="value">{{ formatTime }}</text>
</view>
<view class="list-item">
<text>充电桩编号</text>
<text class="value">{{data.equipmentConnectorCode}}</text>
</view>
</view>
</view>
<ljs-loading v-if="progress>0">{{progress}}%</ljs-loading>
</view>
<template v-slot:kxTabBar>
<kx-tabbar-wrapper>
<view class="tab-bar">
<button class="left item" @click="toPage('/pages/pay/recharge')">
续缴
</button>
<button class="right item" :disabled="interval>0" @click="endCharge()">
{{interval>0?`(${interval}秒)`:''}}结束
</button>
</view>
</kx-tabbar-wrapper>
</template>
</s-layout>
</template>
<script setup>
import {
ref,
onMounted,
onUnmounted,
computed
} from 'vue'
import {
getOrder as getOrderApi
} from '@/sheep/api/charge/operations';
import * as OrderApi from "@/sheep/api/charge/order";
const interval = ref(0) // 剩余秒数
const data = ref({})
let timer = null
let countdownTimer = null // 新增倒计时定时器
let durationTimer = null // 新增充电时长定时器
let hasInitializedTimers = false // 新增标志变量
const chargeStatus = ref([5, 6, 7, 8, 9])
const progress = ref(0)
let progressTimer = null
const toPage = (url) => {
uni.navigateTo({
url
});
};
// 清除所有定时器
const clearTimers = () => {
if (timer) {
clearTimeout(timer)
timer = null
}
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
if (durationTimer) {
clearInterval(durationTimer)
durationTimer = null
}
if (progressTimer) {
clearTimeout(progressTimer)
progressTimer = null
}
hasInitializedTimers = false // 重置标志
}
// 格式化时间戳
const formatTime = computed(() => {
if (!data.value.startTime) return '--'
const date = new Date(parseInt(data.value.startTime))
// 格式化为 YYYY-MM-DD HH:mm:ss
return `${date.getFullYear()}-${padZero(date.getMonth()+1)}-${padZero(date.getDate())} ${padZero(date.getHours())}:${padZero(date.getMinutes())}:${padZero(date.getSeconds())}`
})
// 计算预计充满时间(响应式)
const estimatedFullTime = computed(() => {
if (!data.value.soc) return '计算中...'
const remainingPercent = 100 - data.value.soc
const totalMinutes = Math.ceil(remainingPercent * 1.5) // 假设每1%需要1.5分钟
const hours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
if (hours > 0) {
return `${hours}小时${minutes}分钟`
} else {
return `${minutes}分钟`
}
})
const endCharge = async () => {
const response = await OrderApi.endCharge(data.value.seqNumber);
if (response.code === 0) {
clearTimers()
progress.value = 90
afterEndCharge()
}
};
const afterEndCharge = async () => {
progress.value--
const orderResponse = await OrderApi.getOrderInfo(data.value.seqNumber);
if (chargeStatus.value.includes(orderResponse.data.chargeStatus) || progress.value <= 0) {
uni.navigateTo({
url: `/pages/order-info/order-info?id=` + data.value.seqNumber
})
} else {
progressTimer = setTimeout(() => {
afterEndCharge()
}, 5000)
}
}
// 补零函数
const padZero = (num) => {
return num < 10 ? `0${num}` : num
}
// 格式化时间为 HH:mm:ss
const formatDuration = (seconds) => {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
return [
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
secs.toString().padStart(2, '0')
].join(':')
}
// 计算充电时长(响应式)
const chargingDuration = computed(() => {
if (!data.value.startTime) return '00:00:00'
const now = Date.now()
const startTimestamp = parseInt(data.value.startTime)
const diffInSeconds = Math.floor((now - startTimestamp) / 1000)
return formatDuration(diffInSeconds)
})
// 计算剩余秒数并开始倒计时
const startCountdown = (startTime) => {
const now = Date.now()
const startTimestamp = parseInt(startTime)
const diffInSeconds = Math.floor((now - startTimestamp) / 1000)
// 如果超过60秒,不显示倒计时
if (diffInSeconds >= 60) {
interval.value = 0
return
}
// 计算剩余秒数
interval.value = 60 - diffInSeconds
// 开始倒计时
countdownTimer = setInterval(() => {
interval.value -= 1
// 倒计时结束
if (interval.value <= 0) {
clearInterval(countdownTimer)
countdownTimer = null
}
}, 1000)
}
// 开始充电时长计时
const startDurationTimer = () => {
// 先清除已有的定时器
if (durationTimer) clearInterval(durationTimer)
// 每秒更新一次
durationTimer = setInterval(() => {
// computed属性会自动更新
}, 1000)
}
onLoad(({
seqNumber
}) => {
getOrder(seqNumber)
})
onHide(() => {
clearTimers()
})
onUnmounted(() => {
clearTimers()
})
onBackPress(() => {
clearTimers()
})
const getOrder = async seqNumber => {
const res = await getOrderApi(seqNumber)
data.value = res.data
// 修改后的计时器初始化逻辑
if (res.data.startTime && !hasInitializedTimers) {
startCountdown(res.data.startTime)
startDurationTimer()
hasInitializedTimers = true // 设置标志为true
}
if (res.data.orderStatus === 4) {
clearTimers()
uni.navigateTo({
url: `/pages/order-info/order-info?id=` + res.data.seqNumber
})
return
}
timer = setTimeout(() => {
getOrder(seqNumber)
}, 10000)
}
</script>
<style scoped lang="scss">
.container {
padding-top: 54rpx;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/box.png') 0 0/100% 812rpx no-repeat;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.charging-percentage {
display: flex;
flex-direction: column;
align-items: center;
.percentage-container {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/percentage.png') 0 0/100% 100% no-repeat;
width: 416rpx;
height: 416rpx;
justify-content: center;
}
.circle {
width: 347rpx;
height: 347rpx;
position: absolute;
transform-origin: center center;
animation: spin 1s linear infinite;
}
.percentage {
font-weight: bold;
font-size: 42rpx;
color: #000000;
}
.value {
font-size: 99rpx;
}
.status {
font-size: 33rpx;
color: #000000;
}
.name {
background: rgba(255, 255, 255, 0.19);
border-radius: 45rpx;
border: 1rpx solid #FFFFFF;
padding: 8rpx 38rpx;
margin: 38rpx 0 64rpx;
font-weight: bold;
font-size: 30rpx;
color: #FFFFFF;
image {
width: 26rpx;
height: 26rpx;
margin-left: 10rpx;
}
}
}
.detail-info {
display: flex;
flex-direction: column;
align-items: center;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 20rpx 1rpx rgba(53, 127, 251, 0.1);
border-radius: 36rpx 36rpx 0rpx 0rpx;
padding: 30rpx;
}
.rest-time {
font-weight: 800;
font-size: 32rpx;
color: #333333;
margin-bottom: 24rpx;
image {
width: 20rpx;
height: 32rpx;
margin-right: 10rpx;
}
}
.cost-info {
background: rgba(48, 114, 246, .1);
border-radius: 25rpx;
display: flex;
align-items: center;
justify-content: space-around;
width: 690rpx;
padding: 30rpx 0;
font-size: 28rpx;
color: #000000;
.line {
width: 1rpx;
height: 61rpx;
background: #DCDCDC;
}
.item {
text-align: center;
}
.value {
font-weight: bold;
font-size: 32rpx;
margin-top: 10rpx;
}
}
.cable-info {
display: flex;
justify-content: space-evenly;
margin: 30rpx 0;
font-size: 24rpx;
color: #000000;
width: 100%;
.value {
font-weight: bold;
margin-left: 10rpx;
}
}
.split-line {
width: 693rpx;
height: 0rpx;
border: 1rpx solid #E3E3E3;
margin-bottom: 24rpx;
}
.header {
font-weight: 800;
font-size: 32rpx;
color: #333333;
display: flex;
align-items: center;
margin-bottom: 20rpx;
.line {
width: 7rpx;
height: 32rpx;
margin-right: 16rpx;
background: #3072F6;
}
}
.list-item {
font-size: 28rpx;
color: #999999;
.value {
color: #333333;
margin-left: 20rpx;
}
}
.list-item+.list-item {
margin-top: 20rpx;
}
.tab-bar {
display: flex;
justify-content: space-evenly;
width: 100%;
.item {
width: 334rpx;
// padding: 24rpx 0;
font-weight: bold;
font-size: 30rpx;
color: #FFFFFF;
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
border-radius: 45rpx;
text-align: center;
}
.right {
background: linear-gradient(131deg, #FC754A 0%, #FD5050 100%);
}
}
</style>
+426
View File
@@ -0,0 +1,426 @@
<template>
<s-layout title="开始充电" navbar="normal" onShareAppMessage @clickLeft="clearTimer">
<kx-equipment-info :detail="detail"></kx-equipment-info>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="header">
<view class="line"></view>
<view>
价格信息
</view>
</view>
<view class="time">
价格时段:{{currentCostTemplatePrice.startTimeStr}}-{{currentCostTemplatePrice.endTimeStr}}
</view>
<view class="charge">
<view class="price">
<text>{{(currentCostTemplatePriceType.pricePower+currentCostTemplatePriceType.priceService).toFixed(2)}}</text>
/
</view>
<view class="vip">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16568@2x.png" mode="aspectFit"></image>
<text>
{{(currentCostTemplatePriceType.pricePower+currentCostTemplatePriceType.priceService).toFixed(2)}}/
</text>
</view>
</view>
<view class="time" style="margin-bottom: 0;">
电价{{currentCostTemplatePriceType.pricePower}}/服务费{{currentCostTemplatePriceType.priceService}}/
</view>
</uni-card>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="header">
<view class="line"></view>
<view>
停车场收费
</view>
</view>
<view class="remark">
{{detail.stationVo.openExplain}}
</view>
</uni-card>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="header">
<view class="line"></view>
<view>
占用收费
</view>
</view>
<view class="remark">
充电完成后超过30分钟不拔枪系统将按照0.17/分钟计算收取设备占用费费用上线为30元
</view>
</uni-card>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="header">
<view class="line"></view>
<view>
充电爱车
</view>
<view style="flex:1;">
<view class="right" @click="toPage('/pages/my-car/add-my-car/add-my-car')">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27672.png" style="width:28rpx;height:28rpx;margin-right: 10rpx;"></image>
<text>添加爱车</text>
</view>
</view>
</view>
<view class="car-info" @click="toPage(`/pages/my-car/my-car?connectorCode=${connectorCode}`)">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27673.png"></image>
<text>{{ plateNumber }}</text>
</view>
</uni-card>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="header">
<view class="line"></view>
<view>
付款方式
</view>
</view>
<view class="pay-info">
<view class="account">
<text>账户余额</text>
<text class="value">{{ balance }}</text>
</view>
<view class="recharge" @click="toPage(`/pages/pay/recharge?connectorCode=${connectorCode}`)">去充值 ></view>
</view>
</uni-card>
<ljs-loading v-if="progress>0">{{progress}}%</ljs-loading>
<template v-slot:kxTabBar>
<kx-tabbar-wrapper>
<view class="tab-bar" @click="startCharge">
开始充电
</view>
</kx-tabbar-wrapper>
</template>
</s-layout>
</template>
<script setup>
import PayWalletApi from '@/sheep/api/pay/wallet';
import * as MemberApi from "@/sheep/api/charge/member";
import * as OrderApi from "@/sheep/api/charge/order";
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import {
getConnectorInfo as getConnectorInfoApi
} from '@/sheep/api/charge/operations'
dayjs.extend(customParseFormat);
const balance = ref(0);
const plateNumber = ref('');
const orderSeqNumber = ref('');
const progress = ref(0);
const connectorCode = ref('');
const detail = ref({
equipmentModelRespVO: {},
stationVo: {},
workState: 3,
connectorCode: ''
})
let timer = null
let getDataTimer = null
const currentCostTemplatePrice = ref({})
const currentCostTemplatePriceType = ref({})
const toPage = (url) => {
uni.navigateTo({
url
});
};
const getConnectorInfo = async id => {
const res = await getConnectorInfoApi(id)
detail.value = res.data
getDataTimer = setTimeout(() => {
getConnectorInfo(id)
}, 5000)
}
const getPrice = () => {
const now = dayjs()
const {
costTemplatePriceRespVOS: costTemplatePrice,
costTemplatePriceTypeRespVOS: costTemplatePriceType
} = detail.value.stationVo.costTemplateRespVO
currentCostTemplatePrice.value = costTemplatePrice.filter(({
endTimeStr,
startTimeStr
}) => {
const start = dayjs(startTimeStr, 'HH:mm'),
end = dayjs(endTimeStr, 'HH:mm')
return start.isBefore(now) && end.isAfter(now)
})?.[0]
currentCostTemplatePriceType.value = costTemplatePriceType.filter(({
typeName
}) => currentCostTemplatePrice.value.priceTypeId === typeName)?.[0]
}
onLoad(async (options) => {
connectorCode.value = options.connectorCode;
if (options.carInfo) {
plateNumber.value = JSON.parse(options.carInfo).plateNumber
}
await getConnectorInfo(options.connectorCode)
getPrice()
});
// 将初始化逻辑提取到单独的函数
const initData = async () => {
const response = await PayWalletApi.getPayWallet();
if (response.code === 0) {
balance.value = (response.data.balance / 100).toFixed(2);
}
};
onMounted(async () => {
const responseCar = await MemberApi.getMyCar();
if (responseCar.code === 0) {
const cars = responseCar.data;
// 查找 isDefault = 1 的车辆
const defaultCar = cars.find(car => car.isDefault === 1);
if (plateNumber.value === '') {
if (defaultCar) {
plateNumber.value = defaultCar.plateNumber; // 有默认车辆,赋值
} else if (cars.length > 0) {
plateNumber.value = cars[0].plateNumber; // 没有默认车辆,取第一辆
} else {
plateNumber.value = ""; // 没有车辆,设为空
}
}
}
initData(); // 页面初次加载时调用
});
onShow(() => {
initData(); // 页面再次显示时调用
});
const startCharge = async () => {
const response = await OrderApi.startCharge(connectorCode.value, plateNumber.value, 1);
if (response.code === 0) {
orderSeqNumber.value = response.data;
startProgress();
}
};
onHide(() => {
progress.value = 0;
clearTimer()
})
onBackPress(() => {
progress.value = 0;
clearTimer()
})
onUnmounted(() => {
progress.value = 0;
clearTimer()
})
const clearTimer = () => {
if (timer) {
clearTimeout(timer)
timer = null
}
if (getDataTimer) {
clearTimeout(getDataTimer)
getDataTimer = null
}
}
const startProgress = () => {
progress.value = 0;
let count = 0;
const query = async () => {
progress.value += 3
count++
const orderResponse = await OrderApi.getOrderInfo(orderSeqNumber.value);
// TODO
if (count >= 1 || orderResponse.data.orderStatus === 2) {
progress.value = 100
clearTimer()
uni.navigateTo({
url: '/pages/charging/index?seqNumber=' + orderResponse.data.seqNumber
});
return
}
if (count >= 1 || orderResponse.data.orderStatus === 6) {
progress.value = 100
clearTimer()
uni.navigateTo({
url: `/pages/order-info/order-info?id=` + orderResponse.data.seqNumber
})
return
}
timer = setTimeout(() => {
query();
}, 1000)
}
query()
}
// const startProgress = () => {
// progress.value = 0;
// intervalId.value = setInterval(async () => {
// progress.value += 10;
// if (progress.value >= 3000) {
// clearInterval(intervalId.value);
// setTimeout(() => {
// uni.navigateTo({
// url: '/pages/charging/index'
// });
// }, 1000);
// return;
// }
// const orderResponse = await OrderApi.getOrderInfo(orderSeqNumber.value);
// if (orderResponse.data.orderStatus === 4) {
// clearInterval(intervalId.value);
// progress.value = 100;
// setTimeout(() => {
// uni.navigateTo({
// url: '/pages/charging/index'
// });
// }, 1000);
// }
// }, 1000);
// };
</script>
<style lang="scss" scoped>
.header {
font-weight: 800;
font-size: 32rpx;
color: #333333;
display: flex;
align-items: center;
margin-bottom: 20rpx;
.line {
width: 7rpx;
height: 32rpx;
margin-right: 16rpx;
background: #3072F6;
}
.right {
float: right;
font-size: 30rpx;
color: #337BFB;
}
}
.time {
font-size: 24rpx;
color: #333333;
margin: 20rpx 0;
}
.price {
font-size: 24rpx;
color: #333;
display: inline-block;
margin-right: 18rpx;
vertical-align: middle;
text {
font-size: 40rpx;
color: #EB5336;
}
}
.vip {
width: 230rpx;
height: 45rpx;
background: linear-gradient(180deg, #FADBB7 0%, #F6BA85 100%);
border-radius: 10rpx 10rpx 10rpx 10rpx;
display: inline-block;
image {
width: 82rpx;
height: 45rpx;
}
text {
font-size: 24rpx;
color: #3A3E5E;
}
}
.remark {
font-size: 28rpx;
color: #333333;
}
.car-info {
background: #F5F6FA;
border-radius: 20rpx;
font-size: 30rpx;
color: #323551;
text-align: center;
padding: 30rpx 0;
image {
width: 50rpx;
height: 40rpx;
margin-right: 10rpx;
}
}
.pay-info {
display: flex;
justify-content: space-between;
.account {
font-size: 24rpx;
color: #000000;
}
.value {
font-weight: bold;
font-size: 32rpx;
margin-left: 10rpx;
}
.recharge {
font-size: 26rpx;
color: #666666;
}
}
.tab-bar {
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
border-radius: 45rpx;
font-weight: bold;
font-size: 30rpx;
color: #FFF;
text-align: center;
padding: 24rpx 0;
width: 90%;
}
.progress-bar {
width: 100%;
height: 20rpx;
background-color: #e0e0e0;
border-radius: 10rpx;
overflow: hidden;
margin-top: 20rpx;
.progress {
height: 100%;
background-color: #4A8EFC;
width: v-bind(progress + '%');
transition: width 1s linear;
}
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<s-layout title="电站详情" navbar="normal" onShareAppMessage>
<kx-equipment-info></kx-equipment-info>
<kx-equipment-info></kx-equipment-info>
<template v-slot:kxTabBar>
<kx-tabbar></kx-tabbar>
</template>
</s-layout>
</template>
<script setup>
</script>
+330
View File
@@ -0,0 +1,330 @@
<!-- 首页支持店铺装修 -->
<template>
<s-layout title="首页" navbar="normal" tabbar="pages/index/index" onShareAppMessage>
<view class="container">
<kx-search-bar class="search-bar" scroll id="kxSearchBar"></kx-search-bar>
<view class="banner">
<uni-swiper-dot :info="carouselList"
:dots-styles="{backgroundColor: 'rgba(255,255,255, .4)',selectedBackgroundColor: 'rgba(255,255,255)'}"
:current="currentCarousel">
<swiper autoplay circular @change="onCarouselChange">
<swiper-item v-for="item in carouselList" :key="item.id">
<image :src="item.url" mode="aspectFit" style="width:100%;height:100%;"></image>
</swiper-item>
</swiper>
</uni-swiper-dot>
</view>
<view>
<view class="charging" v-for="item in chargingList" :key="item.id">
<view class="left">
<view class="icon">
<image :src="userStore?.userInfo?.avatar || 'https://kxcharge.oss-cn-beijing.aliyuncs.com/default-avatar.png'" mode="aspectFill">
</image>
</view>
<view class="info">
<view class="car-no">
{{item.plateNumber}}
</view>
<view class="progress">
<view class="icon">
</view>
<view>
正在充电中<span class="ellipsis">......</span>{{ item.endSoc }}%
</view>
</view>
</view>
</view>
<view class="right" @click="toPage('/pages/charging/index?seqNumber=' + item.seqNumber)">
查看详情
<uni-icons type="arrow-right" size="16" color="#3072F6" style="vertical-align: middle;"></uni-icons>
</view>
</view>
</view>
<view class="station-header">
附近充电站
</view>
<view class="station-tabs"
:style="{top:stationTabsTop+'px',backgroundColor:(scrollTop>stationTabsTop)?'#f5f9ff':'transparent'}">
<view class="item" v-for="(item,index) in tabs" :key="item.index" :class="{active:tabIndex===index}"
@tap="onTabTap(index)">
{{item.text}}
</view>
</view>
<view class="station-list">
<kx-station-info :station="item" v-for="item in stationList" :key="item.id"></kx-station-info>
<kx-tabbar-placeholder></kx-tabbar-placeholder>
</view>
</view>
</s-layout>
</template>
<script setup>
import {
getStationList,
getChargingPage
} from '@/sheep/api/charge/operations'
import {
getCarouselList
} from '@/sheep/api/charge/member'
// onPageScroll
const {
scrollTop
} = usePageScroll()
let longitude = null,
latitude = null
const userStore = computed(() => {
return sheep.$store('user').$state
});
const stationTabsTop = ref(0)
onMounted(() => {
uni.createSelectorQuery().in(getCurrentInstance().proxy).select("#kxSearchBar").boundingClientRect((data) => {
stationTabsTop.value = data.height + sheep.$platform.navbar
}).exec();
})
const stationList = ref([])
const chargingList = ref([])
const carouselList = ref([])
const currentCarousel = ref(0)
onShow(() => {
const actualLocation = sheep.$store('user').actualLocation
longitude = actualLocation.longitude
latitude = actualLocation.latitude
getStationList({
longitude,
latitude
}).then(res => {
stationList.value = res.data
})
getChargingPage().then(({
data
}) => {
chargingList.value = data.list
})
getCarouselList().then(res => {
carouselList.value = res.data
})
})
const onCarouselChange = (e) => {
currentCarousel.value = e.detail.current
}
// 隐藏原生tabBar
uni.hideTabBar();
const tabIndex = ref(0)
const tabs = [{
text: '距离最近'
}, {
text: '价格最低'
}, {
text: '快充'
}, {
text: '慢充'
}]
const onTabTap = (index) => {
tabIndex.value = index;
// 确定 rank 的值
let rank = '';
switch (index) {
case 0:
rank = 'distance'; // 距离最近
break;
case 1:
rank = 'price'; // 价格最低
break;
case 2:
rank = 'fast'; // 快充(如果需要)
break;
case 3:
rank = 'slow'; // 慢充(如果需要)
break;
default:
rank = '';
}
// 重新获取站点列表
getStationList({
longitude,
latitude,
rank
}).then(res => {
stationList.value = res.data;
});
}
const toPage = (url) => {
if (!url) return; // 如果没有设置路径,则不跳转
// 判断是否是 tabBar 页面
const tabBarPages = [
'/pages/index/index',
'/pages/map/index',
'/pages/order/index',
'/pages/my/index'
];
if (tabBarPages.includes(url)) {
// 使用 switchTab 跳转 tabBar 页面
uni.switchTab({
url
});
} else {
// 其他页面使用 navigateTo
uni.navigateTo({
url
});
}
}
</script>
<style lang="scss" scoped>
.ellipsis {
display: inline-block;
vertical-align: bottom;
overflow: hidden;
animation: ellipsis 3s infinite steps(4, jump-none);
}
@keyframes ellipsis {
from {
width: 18.1rpx;
}
to {
width: 36.5rpx;
}
}
.container {
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/1.png') 0 0/100% 424rpx no-repeat, #f5f9ff;
}
.banner {
width: 690rpx;
height: 300rpx;
margin: 20rpx auto 30rpx;
}
.grid-item-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
padding: 15px 0;
.text {
font-size: 26rpx;
color: #333333;
}
.img {
width: 90rpx;
height: 90rpx;
}
}
.charging {
width: 690rpx;
height: 140rpx;
background: linear-gradient(90deg, #FFFFFF 0%, #C8E7FF 100%);
box-shadow: inset 0rpx 3rpx 6rpx 1rpx #FFFFFF;
border-radius: 87rpx;
margin: 30rpx auto 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
.left {
display: flex;
}
.icon {
width: 94rpx;
height: 94rpx;
// background: #B7CFFB;
border-radius: 34rpx;
}
.info {
margin-left: 20rpx;
margin-top: 5rpx;
.icon {
width: 20rpx;
height: 20rpx;
background: #03E698;
margin-right: 10rpx;
}
}
.car-no {
font-weight: bold;
font-size: 30rpx;
color: #333;
margin-bottom: 15rpx;
}
.progress {
font-size: 26rpx;
color: #666;
display: flex;
align-items: center;
}
.right {
font-size: 24rpx;
color: #3072F6;
}
}
.station-header {
display: inline-block;
margin: 30rpx 0 25rpx 30rpx;
font-weight: 800;
font-size: 36rpx;
color: #333;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/5946@2x.png') 0 bottom/100% 12rpx no-repeat
}
.station-tabs {
display: flex;
padding: 0 0 20rpx 30rpx;
position: -webkit-sticky;
position: sticky;
z-index: 100;
.item {
font-weight: bold;
font-size: 28rpx;
color: #333;
padding: 5rpx 12rpx;
}
.item+.item {
margin-left: 60rpx;
}
.item.active {
background: rgba(48, 114, 246, 0.1);
border-radius: 25rpx 25rpx 25rpx 25rpx;
color: #3072F6;
}
}
</style>
+377
View File
@@ -0,0 +1,377 @@
<template>
<s-layout navbar="normal" onShareAppMessage>
<view class="block">
<view class="carInfo">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/add-car-image.png" mode="aspectFit"></image>
<view class="carType">
<text class="title">车型</text>
<text>新能源汽车</text>
</view>
<view class="plateNum" @click="inputPlate">
<text class="title">车牌号</text>
<view class="num">
<text class="numItem" v-for="(item,index) in blockArr" :key="index">{{item || ''}}</text>
</view>
</view>
<view class="isDefault" v-if="!isEdit">
<text class="title">是否为默认充电车辆</text>
<uv-switch v-model="defaultCar"></uv-switch>
</view>
<view class="carType" v-else>
<text class="title">车辆识别代码</text>
<text>未认证</text>
</view>
</view>
<uv-button type="primary" text="添加车辆" shape="circle" @click="addCar('add')" v-if="!isEdit"></uv-button>
<view v-else>
<uv-button type="primary" text="编辑车辆" shape="circle" @click="addCar('edit')"></uv-button>
<view class="delCar" @click="onDelCar">删除车辆</view>
</view>
<!-- <text @click="goBack" class="goBack">我再想想</text> -->
<uv-popup ref="popup" mode="bottom" @change="onPopupChange">
<view class="popup-content">
<view class="btn">
<text @click="()=>$refs.popup.close()">取消</text>
<text @click="submitPlate">确定</text>
</view>
<view>
<text class="label">车牌号</text>
<view class="num">
<text class="text-block" v-for="(item,index) in popBlockArr" :key="index">{{item || ''}}</text>
</view>
</view>
<uv-keyboard :autoChange="true" ref="uKeyboard" mode="car" :overlay="false" @change="clickKeyboard($event)"
:tooltip="false" @backspace="backspace"></uv-keyboard>
</view>
</uv-popup>
<uv-modal ref="modal" content='是否确认删除该车辆?' title='提示' :showCancelButton="true" class="modal"
@cancel="showModal = false" @confirm="submitDel"></uv-modal>
</view>
</s-layout>
</template>
<script>
import {
delCar,
createCar,
editCar
} from "@/sheep/api/charge/member"
export default {
data() {
return {
defaultCar: true,
showModal: false,
plateValue: '',
isFocus: false,
time1: 0,
time2: 0,
time3: 0,
blockArr: new Array(8),
popBlockArr: new Array(8),
isEdit: false,
carId: '',
};
},
methods: {
clickKeyboard(event) {
if (this.plateValue.length >= 8) {
return;
}
this.plateValue += event;
this.plateValue.split("").forEach((k, index) => {
this.popBlockArr[index] = k;
});
},
backspace() {
this.popBlockArr = this.popBlockArr.map((item, index, arr) => {
if (!arr[index + 1]) item = '';
return item;
});
this.plateValue = this.popBlockArr.join("")
// console.log('this.popBlockArr: ',this.popBlockArr, this.plateValue);
},
addCar(type) {
if (!this.plateValue) {
uni.showToast({
icon: 'none',
title: '请输入车牌号'
})
return;
}
const data = {
plateNumber: this.plateValue,
isDefault: Number(this.defaultCar),
plateType: 1
}
if (this.carId) {
data['id'] = this.carId;
}
try {
const method = type == "add" ? createCar : editCar;
const tip = type == "add" ? '添加' : '编辑'
method(data).then(res => {
console.log('res: ', res);
uni.showToast({
title: `${tip}成功!`,
icon: 'success'
})
// 使用 setTimeout 延迟跳转
setTimeout(() => {
uni.navigateTo({
url: `/pages/my-car/my-car`
});
}, 1500); // 2000 毫秒 = 2 秒
}).catch(err => {
uni.showToast({
title: `${tip}失败!`,
icon: 'error'
})
})
} catch (e) {
uni.showToast({
title: '添加失败!',
icon: 'error'
})
}
},
goBack() {
uni.navigateBack();
},
inputPlate() {
// if (this.isEdit) return;
this.$refs.popup.open()
this.$refs.uKeyboard.open()
},
onPopupChange(e) {
if (e) {
this.open()
} else {
this.close()
}
},
open() {
this.$refs.popup.open();
this.time2 = setTimeout(() => {
this.isFocus = true;
}, 1000)
},
close() {
this.isFocus = false;
this.plateValue = '';
},
submitPlate() {
if (!this.plateValue) {
uni.showToast({
icon: 'none',
title: '输入的车牌号是否为空'
})
return;
}
// const plateArr = this.plateValue.split("")
if (this.plateValue.length !== 8 && this.plateValue.length !== 7) {
uni.showToast({
icon: 'none',
title: '输入的车牌号有误,请重新输入'
})
return;
}
this.$refs.popup.close()
this.$refs.uKeyboard.close()
this.plateValue.split("").forEach((k, index) => {
this.blockArr[index] = k;
});
this.blockArr = this.blockArr.filter(k => k);
},
onDelCar() {
this.$refs.modal.open();
},
submitDel() {
try {
delCar(this.carId).then(res => {
this.$refs.modal.close();;
uni.showToast({
title: '删除成功!',
icon: 'success'
})
// 使用 setTimeout 延迟跳转
setTimeout(() => {
uni.navigateTo({
url: `/pages/my-car/my-car`
});
}, 1500); // 2000 毫秒 = 2 秒
}).catch(err => {
console.log('err: ', err);
uni.showToast({
title: '删除失败!',
icon: 'error'
})
})
} catch (e) {
//TODO handle the exception
uni.showToast({
title: '删除失败!',
icon: 'error'
})
}
}
},
beforeDestroy() {
clearTimeout(this.time1);
clearTimeout(this.time2);
clearTimeout(this.time3);
},
onLoad(options) {
if (options.carInfo) {
console.log('options.carInfo: ', options.carInfo);
const info = JSON.parse(options.carInfo);
this.defaultCar = info.isDefault
this.plateValue = info.plateNumber;
this.plateValue.split("").forEach((k, index) => {
this.blockArr[index] = k;
});
this.blockArr = this.blockArr.filter(k => k);
this.popBlockArr = this.blockArr;
this.isEdit = true;
this.carId = info.id;
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f0f3f8;
}
.block {
height: 100vh;
padding: 15rpx;
.carInfo {
background-color: #fff;
border-radius: 20rpx;
image {
width: 730rpx;
height: 365rpx;
}
.carType {
padding: 0 30rpx 30rpx;
display: flex;
justify-content: space-between;
font-size: 35rpx;
}
.title {
color: #999999;
margin-right: 25rpx;
}
.plateNum {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15rpx 30rpx;
.num {
display: flex;
}
.numItem {
display: inline-block;
margin-right: 10rpx;
width: 60rpx;
background-color: #f2f2f2;
height: 70rpx;
line-height: 50rpx;
text-align: center;
padding: 13rpx;
}
}
.isDefault {
padding: 0 25rpx 30rpx 15rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
}
.delCar {
margin-top: 25rpx;
display: flex;
justify-content: center;
align-items: center;
color: #797979;
font-size: 30rpx;
}
.uv-button {
margin-top: 40rpx;
width: 720rpx !important;
height: 110rpx !important;
.uv-button__text {
font-size: 35rpx !important;
}
}
.goBack {
display: flex;
justify-content: center;
align-items: center;
color: #797979;
font-size: 40rpx;
margin: 30rpx auto;
}
.popup-content {
padding: 35rpx 35rpx 450rpx;
position: relative;
.label {
font-size: 22rpx;
color: #797979;
}
.btn {
color: #1890ff;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 50rpx;
}
.num {
display: flex;
justify-content: space-around;
align-items: center;
}
.text-block {
display: inline-block;
margin-right: 10rpx;
width: 60rpx;
background-color: #f2f2f2;
height: 70rpx;
line-height: 80rpx;
text-align: center;
}
.uv-popup {
position: absolute;
bottom: 0;
.uv-popup__content {
padding: 0;
}
}
}
}
</style>
+242
View File
@@ -0,0 +1,242 @@
<template>
<s-layout title="我的车辆" navbar="normal" onShareAppMessage>
<view class="block">
<uv-loading-icon text="加载中" v-if="isShowPageLoading"></uv-loading-icon>
<scroll-view scroll-y="true" v-else class="scroll">
<view class="noCar" v-if="isShowNoCar">
<text class="title">您还没有添加车辆</text>
<text class="tip">添加车辆可以获得更贴心的服务</text>
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/car-default.png" mode="aspectFit"></image>
<uv-button type="primary" text="添加车辆" shape="circle" @click="goToAddCar"></uv-button>
</view>
<view v-else>
<view class="scrollItem" v-for="(item,index) in carList" :key="item.id" @click="goToInfo(item)">
<text class="plateNum">{{item.plateNumber}}</text>
<view class="info">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/car-icon.png" mode="aspectFit"></image>
<view class="detail">
<view class="item"><text class="title">汽车类型:</text>{{item.CarType}}</view>
<view class="item"><text class="title">上次充电地点:</text>{{item.latelyPlace || ''}}</view>
<view class="item"><text class="title">上次充电时间:</text>{{item.LatelyTime || ''}}</view>
<view class="item"><text class="title">充电次数:</text>{{item.chargeQuantity}}</view>
<view class="item"><text class="title">创建时间:</text>{{formatTime(item.createTime)}}</view>
</view>
</view>
<view class="default" v-if="item.isDefault">
默认车辆
</view>
<view class="default" v-else style="background-color: #1890ff;" @click.stop="setDefault(item.id)">
设为默认
</view>
</view>
<uv-button type="primary" text="添加车辆" shape="circle" @click="goToAddCar"></uv-button>
<uv-modal ref="modal" content='是否设置该车辆为默认车辆' title='提示' :showCancelButton="true" class="modal"
@cancel="showModal = false" @confirm="submitDefault"></uv-modal>
</view>
</scroll-view>
</view>
</s-layout>
</template>
<script>
import moment from "dayjs";
import {
getMyCar,
setDefaultCar
} from "@/sheep/api/charge/member";
let connectorCode = null
export default {
data() {
return {
carList: [],
isShowNoCar: true,
showModal: false,
carTypeMap: new Map([
[1, '新能源汽车']
]),
activeId: '',
isShowPageLoading: true
};
},
methods: {
formatTime(ts) {
return moment(ts).format("YYYY-MM-DD HH:mm");
},
getCarList() {
this.isShowPageLoading = true;
getMyCar().then(res => {
const {
data
} = res;
this.isShowNoCar = data.length === 0;
if (!this.isShowNoCar) {
data.forEach(item => {
item['CarType'] = this.carTypeMap.get(item.plateType);
item['LatelyTime'] = item.latelyTime ? moment(item.latelyTime).format("YYYY-MM-DD HH:mm:ss") :
'';
this.carList.push(item);
})
}
}).finally(() => {
this.isShowPageLoading = false
})
},
goToInfo(data) {
if (connectorCode) {
uni.navigateTo({
url: `/pages/equipment-detail/index?carInfo=${JSON.stringify(data)}&connectorCode=${connectorCode}`
})
} else {
uni.navigateTo({
url: `/pages/my-car/add-my-car/add-my-car?carInfo=${JSON.stringify(data)}`
})
}
},
goToAddCar() {
uni.navigateTo({
url: '/pages/my-car/add-my-car/add-my-car'
})
},
setDefault(id) {
this.activeId = id;
this.$refs.modal.open();
},
submitDefault() {
try {
setDefaultCar(this.activeId, 0).then(res => {
this.carList = [];
this.getCarList();
this.$refs.modal.close();;
uni.showToast({
icon: 'success',
title: '设置成功!'
})
}).catch(err => {
uni.showToast({
icon: 'fail',
title: '设置失败!'
})
console.log('err: ', err);
})
} catch (e) {
//TODO handle the exception
uni.showToast({
icon: 'fail',
title: '设置失败!'
})
}
}
},
onLoad(options) {
if (options.connectorCode) {
connectorCode = options.connectorCode
}
},
onShow() {
this.carList = [];
this.getCarList();
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f0f3f8;
}
.block {
// height: 100vh;
// background-color: #f2f2f2;
.noCar {
display: flex;
flex-direction: column;
image {
width: 650rpx;
height: 650rpx;
margin: auto;
}
.title {
font-size: 58rpx;
margin: 180rpx 40rpx 15rpx;
}
.tip {
color: #a7a7a7;
margin-left: 40rpx;
}
}
.scroll {
background-color: #fff;
padding: 15rpx;
}
.uv-button {
margin-top: 50rpx;
width: 375rpx !important;
height: 95rpx !important;
margin: 20rpx auto;
.uv-button__text {
font-size: 35rpx !important;
}
}
.scrollItem {
background-color: #fff;
margin: 35rpx 15rpx 0;
position: relative;
padding: 30rpx 20rpx 0;
border-radius: 20rpx;
.plateNum {
font-size: 35rpx;
display: inline-block;
margin-left: 10rpx;
}
.info {
display: flex;
font-size: 25rpx;
image {
width: 220rpx;
height: 220rpx;
margin-right: 30rpx;
}
.item {
margin-bottom: 25rpx;
}
.title {
width: 160rpx;
display: inline-block;
text-align: right;
margin-right: 20rpx;
}
}
.default {
position: absolute;
top: 20rpx;
right: 0rpx;
color: #fff;
background-color: #61ec0b;
border-top-left-radius: 25rpx;
border-bottom-left-radius: 25rpx;
padding: 8rpx 15rpx;
font-size: 25rpx;
}
}
}
</style>
+346
View File
@@ -0,0 +1,346 @@
<template>
<s-layout title="我的" navbar="normal" tabbar="pages/my/index" onShareAppMessage>
<view class="container">
<view class="info flex justify-around align-center">
<view class="avatar flex">
<view class="image">
<image :src="userStore?.userInfo?.avatar || 'https://kxcharge.oss-cn-beijing.aliyuncs.com/default-avatar.png'"
style="width:100%;height:100%;" mode="aspectFill">
</image>
</view>
<view class="name flex flex-column justify-center">
<view>
<text>
{{ userStore? userStore.userInfo.nickname : '未登录' }}
</text>
<image v-if="userStore?.userInfo?.level" src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16491.png"
style="width:100%;height:100%;"></image>
</view>
<view class="level">
{{ userStore ? 'KC充电用户' : '请先登录' }}
</view>
</view>
</view>
<view class="check">
</view>
</view>
<view class="account flex justify-around">
<view class="item flex align-center" @click="toPage('/pages/pay/recharge')">
<view class="title">
账户余额
</view>
<view class="value">
{{ userStore ? (userStore.userWallet.balance / 100).toFixed(2) : 0 }}
</view>
</view>
<view class="item flex align-center" @click="toPage('/pages/user/wallet/score')">
<view class="title">
积分
</view>
<view class="value">
{{ userStore ? userStore.userInfo.point : 0 }}
</view>
</view>
</view>
<view class="banner">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16490.png" mode="aspectFill" style="width:100%;height:100%;"></image>
</view>
<uni-card padding="40rpx 20rpx" margin="8rpx" spacing="20rpx">
<template v-slot:title>
<uni-row>
<uni-col :span="5">
<view class="text-bold">我的爱车</view>
</uni-col>
<uni-col :span="5" :push="14">
<view @click="toPage('/pages/my-car/my-car')">全部车辆></view>
</uni-col>
</uni-row>
</template>
<view class="flex align-center" @click="toPage('/pages/my-car/add-my-car/add-my-car')">
<image v-if="userStore?.userInfo?.plateNumber" src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16580.png"
style="width:60rpx;height:60rpx;margin-right: 10rpx;"></image>
<image v-else src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27672.png" style="width:28rpx;height:28rpx;margin-right: 10rpx;">
</image>
<text v-if="userStore?.userInfo?.plateNumber">{{ userStore ? userStore.userInfo.plateNumber : 0 }}</text>
<text v-else>添加我的爱车</text>
</view>
<view slot="actions" class="flex justify-around card-actions">
<view class="flex align-center">
<uni-icons type="heart" size="18" color="#999"></uni-icons>
<text>停车减免</text>
</view>
<view class="flex align-center">
<uni-icons type="heart" size="18" color="#999"></uni-icons>
<text>充电安全</text>
</view>
<view class="flex align-center">
<uni-icons type="heart" size="18" color="#999"></uni-icons>
<text>精准推荐</text>
</view>
</view>
</uni-card>
<view class="service">
<view class="more-header">
更多服务
</view>
<uni-grid :column="4" :highlight="true" :show-border="false" :square="false">
<uni-grid-item v-for="(item, index) in menus" :index="index" :key="index">
<view class="grid-item-box" @click="toPage(item.path)">
<image :src="item.icon" mode="aspectFit" class="img"></image>
<text class="text">{{item.text}}</text>
</view>
</uni-grid-item>
</uni-grid>
</view>
</view>
</s-layout>
</template>
<script setup>
import {
showAuthModal
} from '@/sheep/hooks/useModal';
import * as UserApi from '@/sheep/api/charge/member';
const userStore = computed(() => {
return sheep.$store('user').$state;
});
// // 获取最新的用户数据
// const getUserData = async () => {
// try {
// const {
// data
// } = await UserApi.getUserInfo();
// if (data) {
// userStore.userInfo = data;
// console.log('获取到新的用户数据:', data);
// }
// } catch (error) {
// console.error('获取用户信息失败:', error);
// }
// };
// 检查登录状态
const checkLoginStatus = () => {
const token = uni.getStorageSync('token');
if (!token) {
showAuthModal();
return;
}
const cachedUserStore = uni.getStorageSync('user-store');
if (cachedUserStore) {
try {
const parsedUserStore = JSON.parse(cachedUserStore);
if (parsedUserStore.isLogin) {
userStore.value = parsedUserStore;
// getUserData();
}
} catch (e) {
console.error('解析用户信息失败:', e);
}
}
};
// // 组件挂载时检查登录状态
// onMounted(() => {
// checkLoginStatus();
// });
// 添加页面显示时的处理
onShow(() => {
checkLoginStatus();
sheep.$store('user').getInfo()
sheep.$store('user').getWallet()
});
const menus = [{
icon: 'https://kxcharge.oss-cn-beijing.aliyuncs.com/16582.png',
text: '账单',
path: '/pages/user/wallet/money'
}, {
icon: 'https://kxcharge.oss-cn-beijing.aliyuncs.com/16588.png',
text: '订单',
path: '/pages/order/index'
}, {
icon: 'https://kxcharge.oss-cn-beijing.aliyuncs.com/16585.png',
text: '联系客服'
}, {
icon: 'https://kxcharge.oss-cn-beijing.aliyuncs.com/16587.png',
text: '设置',
path: '/pages/public/setting'
}]
onLaunch(() => {
// 隐藏原生导航栏 使用自定义底部导航
uni.hideTabBar();
});
const toLogin = () => {
const token = uni.getStorageSync('token');
if (!token) {
showAuthModal();
return;
}
}
const toPage = (url) => {
uni.navigateTo({
url
})
}
</script>
<style lang="scss" scoped>
.card-actions {
border-top: 2rpx solid #eee;
padding-top: 10rpx;
}
text {
vertical-align: middle;
}
.container {
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/1.png') 0 0/100% 424rpx no-repeat, #f5f9ff;
padding-top: 28rpx;
height: 100%;
flex: 1;
}
.avatar {
.image {
width: 114rpx;
height: 114rpx;
border-radius: 50%;
border: 8rpx solid #FFFFFF;
margin-right: 28rpx;
overflow: hidden;
image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.name {
font-weight: 800;
font-size: 30rpx;
color: #323551;
image {
width: 60rpx;
height: 38rpx;
}
}
.level {
margin-top: 26rpx;
font-size: 24rpx;
color: #666666;
font-weight: normal;
}
}
.check {
width: 130rpx;
height: 52rpx;
}
.account {
padding: 36rpx 50rpx;
.title {
font-size: 24rpx;
color: #000000;
margin-right: 10rpx;
}
.value {
font-weight: bold;
font-size: 32rpx;
color: #000000;
}
}
.banner {
margin: 0 auto;
width: 690rpx;
height: 138rpx;
}
.service {
// background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/6043.png') 0 0/100% 100% no-repeat;
background: #fff;
border-radius: 40rpx;
padding: 50rpx 40rpx;
}
.item-container {
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.icon {
width: 82rpx;
height: 82rpx;
}
.title {
font-size: 26rpx;
color: #333333;
margin-top: 10rpx;
}
}
.car-info {
background: #F5F6FA;
border-radius: 20rpx;
font-size: 30rpx;
padding: 30rpx;
margin: 30rpx 0;
.left {
color: #323551;
}
.right {
color: #337BFB;
}
}
.more-header {
display: inline-block;
font-weight: 800;
font-size: 36rpx;
color: #333;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/5946@2x.png') 0 bottom/100% 12rpx no-repeat
}
.grid-item-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.text {
font-size: 26rpx;
color: #333333;
margin-top: 12rpx;
}
.img {
width: 50rpx;
height: 50rpx;
margin-top: 30rpx;
}
}
</style>
+541
View File
@@ -0,0 +1,541 @@
<template>
<s-layout title="订单详情" navbar="normal" onShareAppMessage>
<view class="block">
<uv-loading-page :loading="isShowLoading"></uv-loading-page>
<scroll-view scroll-y="true" class="chargeInfo" style="height: 90vh;">
<view class="bg">
<!-- <image src="/static/image/personal/bg-user.png" mode="widthFix"></image> -->
<text class="tip">感谢您的光临!</text>
</view>
<view class="stationInfo">
<view class="stationName">
<text class="name">{{ orderInfo.stationName }}</text>
<view class="map" @click="toMap()">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/nav.png" mode="aspectFit"></image>
导航
</view>
</view>
<view class="address">
{{orderInfo.address || ''}}
</view>
</view>
<view class="orderInfo">
<view class="orderCode" style="margin-bottom: 20rpx;display: flex;align-items: center;">
<text class="label">订单编号</text>
<text class="seq" style="color: #000;">{{orderInfo.StartChargeSeq}}</text>
<text v-if="orderInfo.discountType == 2"
style="width: 60rpx;border:1px solid #ddd; padding: 10rpx; margin-left: 10px;border-radius: 5px; color: #f4ea2a;font-style: italic;text-align: center;">SVIP</text>
<text v-if="orderInfo.discountType == 1"
style="width: 60rpx;border:1px solid #ddd; padding: 10rpx; margin-left: 10px;border-radius: 5px; color: #f4ea2a;font-style: italic;text-align: center;">VIP</text>
</view>
<view class="chargeTime" style="margin-bottom: 20rpx;">
<text>订单状态</text>
<text>{{ orderInfo.status || '未知' }}</text>
</view>
<view class="chargeTime" style="margin-bottom: 20rpx;">
<text>车牌号</text>
<text>{{ orderInfo.carNumber || '未绑定车牌号' }}</text>
</view>
<view class="chargeTime" style="margin-bottom: 20rpx;">
<text>充电桩度数</text>
<text>{{ fixedNum(orderInfo.chargedPower,2)}}</text>
</view>
<view class="chargeTime" style="margin-bottom: 20rpx;">
<text>充电时间</text>
<text>{{ timePip(orderInfo.startTime,'YYYY-MM-DD HH:mm')}}{{ timePip(orderInfo.endTime,'HH:mm')}}</text>
</view>
<view class="orderMoney" style="margin-bottom: 20rpx;">
<view class="total">
<view class="money">
<text>订单费用</text>
<text style="color: #000;">{{ fixedNum(orderInfo.allAmount,2)}}</text>
</view>
<view class="isExpand" @click="isExpand = !isExpand" style="display: flex; align-items: center;">
{{ isExpand ? '收起' : '展开'}}
<uv-icon :name="isExpand ? 'arrow-up' : 'arrow-down'"></uv-icon>
</view>
</view>
<view class="table" style="margin-top: 20rpx; border-bottom: 1rpx solid #e2e2e2;">
<view v-if="isExpand">
<view style="display: flex;color: #000; width: 100%; height: 80rpx;">
<view style="width: 20%; text-align: center;">开始时段</view>
<view style="width: 20%; text-align: center;">结束时段</view>
<view style="width: 20%; text-align: center;">电费</view>
<view style="width: 20%; text-align: center;">服务费</view>
<view style="width: 20%; text-align: center;">总费用</view>
</view>
<view v-for="(item, index) in orderInfo.orderDetailsRespVOList" :key="index"
style="display: flex;color: #000; width: 100%; height: 80rpx;">
<view style="width: 20%; text-align: center;">{{ timePip(Number(item.startTime),'HH:mm') }}</view>
<view style="width: 20%; text-align: center;">
{{ timePip(Number(item.endTime),'HH:mm') }}
</view>
<view style="width: 20%; text-align: center;">{{ fixedNum(item.chargedPrice,2) }}</view>
<view style="width: 20%; text-align: center;">{{ fixedNum(item.servicePrice,2) }}</view>
<view style="width: 20%; text-align: center;">
{{ fixedNum((item.chargedPrice + item.servicePrice),2) }}
</view>
</view>
</view>
</view>
<view class="preferential">
<view class="item">
<text class="label">折扣优惠</text>
<text class="val">-{{ fixedNum(orderInfo.serviceDiscountAmount,2) }}</text>
</view>
<view class="item">
<text class="label">优惠券</text>
<text class="val">-{{ fixedNum(orderInfo.couponPrice,2) }}</text>
</view>
<view class="item">
<text class="label" style="font-weight: 600;">优惠减免合计</text>
<text class="val"
style="color: #f00;">-{{ fixedNum((orderInfo.serviceDiscountAmount + orderInfo.couponPrice),2) }}</text>
</view>
</view>
<view class="actualMoney">
实付<text style="color: #000; font-weight: 600;">-{{ fixedNum(orderInfo.inPay,2) }}</text>
</view>
</view>
</view>
<view class="payment">
<view class="item" style="margin-bottom: 20rpx;">
<text class="label">支付时间{{ timePip(orderInfo.payTime,'YYYY-MM-DD HH:mm') }}</text>
</view>
<view class="item" style="margin-bottom: 20rpx;">
<text class="label">支付方式<text style="color: #000;">{{paymentType(orderInfo.amountMethod)}}</text></text>
</view>
<view class="item" v-if="orderInfo.failReasonMsg">
<text class="label">充电结束原因{{orderInfo.failReasonMsg || ''}}</text>
</view>
</view>
<view class="code" v-if="stationInfo.codeImage">
<uv-image :showLoading="true" :src="stationInfo.codeImage" width="100px" height="100px"
:showMenuByLongpress="true"></uv-image>
<text class="tips"> 长按识别图片请添加会员群</text>
</view>
</scroll-view>
<kx-tabbar-wrapper v-slot:kxTabBar>
<view class="tab-bar" @click="toPay" v-if="orderInfo.pay === 0 && orderInfo.startChargeSeqStat === 4">
主动结算
</view>
<view class="tab-bar" @click="goToStation" v-else>
继续充电
</view>
</kx-tabbar-wrapper>
<uv-modal :show="showModal" content='是否结算该订单?' title='提示' :showCancelButton="true" class="modal"
@cancel="showModal = false" @confirm="submitPay"></uv-modal>
</view>
</s-layout>
</template>
<script>
import moment from "dayjs";
import {
convertSecondsToTime,
Tips
} from "@/sheep/util/utils";
import {
getOrderInfo,
proactiveBilling
} from "@/sheep/api/charge/order"
import {
getDictData,
getDictDataAlone
} from "@/sheep/api/charge/system";
import {
getStationDetail
} from "@/sheep/api/charge/operations";
const PayTypeMap = new Map([
[1, '先充后付支付'],
[2, '预充值支付'],
[3, '余额支付']
])
export default {
data() {
return {
id: '',
orderInfo: {},
statusMap: new Map([
[1, '启动中'],
[2, '进行中'],
[3, '停止中'],
[4, '已完成'],
[5, '未知'],
]),
orderStopReason: new Map(),
isShowLoading: true,
showOverlay: false,
isExpand: false,
showModal: false,
isPop: false,
seconds: 15,
time: 0,
stationInfo: {
codeImage: ''
}
};
},
methods: {
toPay() {
this.showModal = true;
},
paymentType(type) {
return PayTypeMap.get(type) || '余额支付';
},
submitPay() {
proactiveBilling(this.orderInfo.id).then(res => {
this.showModal = false;
if (res.statusCode === 200) {
Tips({
title: '结算成功!',
icon: 'success'
})
uni.navigateBack();
} else {
Tips({
title: res.data.detail,
icon: 'none'
})
}
}).catch(err => {
Tips({
title: err.error.detail,
icon: 'none'
})
this.showModal = false;
})
},
// 首页
goToStation() {
uni.reLaunch({
url: '/pages/index/index'
})
},
closePop() {
clearInterval(this.time);
this.isPop = false;
},
// goToShare() {
// uni.reLaunch({
// url: '/pages/invite-code/q-code/q-code'
// })
// },
// 获取订单详情
async getOrderInfo() {
this.isShowLoading = true;
try {
const {
data
} = await getOrderInfo(this.id);
this.orderInfo = data;
this.$set(this.orderInfo, 'status', this.statusMap.get(this.orderInfo.orderStatus));
this.$set(this.orderInfo, 'StartChargeSeq', this.orderInfo.seqNumber?.substring(9));
this.$set(this.orderInfo, 'failReasonMsg', this.orderStopReason.get(String(this.orderInfo.stopReason)));
this.isShowLoading = false;
const {
data: res
} = await getStationDetail({
id: data.stationId,
longitude: 0,
latitude: 0
});
this.stationInfo = res;
this.$set(this.stationInfo, 'codeImage', JSON.parse(res.codeUrl || '[]')?.[0]);
console.log('res: ', res);
} catch (e) {
console.log('e: ', e);
//TODO handle the exception
}
},
// 导航
toMap() {
uni.openLocation({
latitude: this.orderInfo.stationLat,
longitude: this.orderInfo.stationLng,
name: this.orderInfo.stationName,
address: this.orderInfo.address
});
},
async getChargeStopMsg() {
try {
const {
data
} = await getDictDataAlone('stop_charge_type');
data.forEach(item => {
this.orderStopReason.set(item.value, item.label)
})
} catch (e) {
console.log('e: ', e);
//TODO handle the exception
}
},
timePip(val, format) {
if (!val) {
return '';
}
return moment(val).format(format);
},
fixedNum(num, fix) {
if (!num) {
return '0.00'
}
return Number(num).toFixed(fix)
},
formatData(item) {
item['status'] = this.statusMap.get(item.orderStatus);
item['StartTime'] = moment(item.startTime).format("YYYY-MM-DD HH:mm:ss");
return item;
}
},
async onLoad(option) {
this.id = option.id;
await this.getChargeStopMsg();
this.getOrderInfo();
this.isPop = option.isPop || false;
if (this.isPop) {
this.time = setInterval(() => {
if (this.seconds < 1) {
this.isPop = false;
clearInterval(this.time);
} else {
this.seconds -= 1
}
}, 1000)
}
}
}
</script>
<style lang="scss" scoped>
.tab-bar {
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
border-radius: 45rpx;
font-weight: bold;
font-size: 30rpx;
color: #FFF;
text-align: center;
padding: 24rpx 0;
width: 90%;
}
page {
// background-color: rgb(191, 248, 250);
// background-image: url(/static/images/bg-user.jpg);
background-size: 100% 100%;
background-repeat: no-repeat;
}
.block {
height: 100vh;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/box.png') 0 0/100% 812rpx no-repeat;
// background-color: #f2f2f2;
// padding-bottom: 130rpx;
.chargeInfo {
.bg {
// image {
// width: 100%;
// height: 100%;
// position: absolute;
// top: 0;
// left: 0;
// }
width: 100%;
position: relative;
height: 17vh;
width: 750rpx;
// background: linear-gradient(
// 180deg,#3c9cff 5%,#ebebeb 40%);
.tip {
position: absolute;
bottom: 8vh;
left: 50rpx;
font-size: 38rpx;
font-weight: 600;
color: #fff;
}
}
.stationInfo {
width: 650rpx;
padding: 25rpx;
margin: auto;
background-color: #fff;
border-radius: 12rpx;
position: relative;
box-shadow: 0rpx 0rpx 20rpx 5rpx #ccc;
top: -50rpx;
.stationName {
display: flex;
font-size: 30rpx;
justify-content: space-between;
.name {
font-weight: 600;
}
.map {
color: #229bff;
image {
width: 25rpx;
margin-right: 15rpx;
height: 25rpx;
}
}
}
.address {
font-size: 25rpx;
color: #aaa;
margin-top: 20rpx;
}
}
.orderInfo {
width: 650rpx;
padding: 25rpx;
margin: -20rpx auto 0;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0rpx 0rpx 20rpx 5rpx #ccc;
color: #aaa;
font-size: 30rpx;
.total {
display: flex;
justify-content: space-between;
}
.preferential {
margin-top: 20rpx;
border-bottom: 1rpx solid #e2e2e2;
color: #000;
.item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
}
.actualMoney {
display: flex;
justify-content: flex-end;
margin-top: 25rpx;
}
}
.payment {
.item {
color: #aaa;
}
}
}
.btn {
position: fixed;
bottom: 0;
padding: 30rpx 0;
width: 750rpx;
.uv-button {
margin: auto !important;
width: 700rpx !important;
border-radius: 50rpx !important;
height: 103rpx !important;
.uv-button__text {
font-size: 37.5rpx !important;
}
}
}
.uv-fade-enter-active {
display: flex;
justify-content: center;
align-items: center;
.content {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
color: #229bff;
.header {
position: absolute;
z-index: 9999;
right: 0;
color: #fff !important;
display: flex;
justify-content: space-between;
align-items: center;
width: 99%;
padding: 10rpx 0;
.uv-icon__icon {
color: #fff !important;
}
}
.uv-button {
margin-top: 30rpx;
border-radius: 50rpx;
width: 270rpx;
}
}
}
.code,
.payment {
width: 650rpx;
padding: 25rpx;
margin: auto;
background-color: #fff;
border-radius: 12rpx;
box-shadow: 0rpx 0rpx 20rpx 5rpx #ccc;
font-size: 30rpx;
margin-top: 30rpx;
}
.code {
display: flex;
justify-content: flex-start;
align-items: center;
.tips {
display: inline-block;
margin-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
}
}
}
</style>
+633
View File
@@ -0,0 +1,633 @@
<template>
<s-layout title="订单" navbar="normal" tabbar="pages/order/index" onShareAppMessage>
<view class="block">
<view class="tabs">
<view class="item" v-for="(item,index) in tabList" :key="index" :class="{'activeItem': activeTab === item[0]}"
@click="getTrottle(item)">
<text class="label">{{item[1]}}</text>
</view>
</view>
<uv-loading-icon text="加载中" v-if="isShowPageLoading"></uv-loading-icon>
<scroll-view scroll-y="true" v-else @scrolltolower="nextPage" style="height:60vh;" :refresher-enabled="true"
:refresher-triggered="nowRefsh" @refresherrefresh="refresh">
<template v-if="orderList.length > 0">
<view class="orderItem" v-for="(item,index) in orderList" :key="item.id">
<view class="top">
<view class="code">
<!-- <text class="subCode" >订单编号{{item.StartChargeSeq}}</text> -->
<uni-tooltip :content="'订单编号:' +item.StartChargeSeq" placement="top" style="width: 80%;">
<uv-text class="subCode" :lines="1" :text="'订单编号:' +item.StartChargeSeq"
style="font-size: 30rpx; font-weight: 600;"></uv-text>
</uni-tooltip>
<text style="display: inline-block; width: 15%;">{{item.status}}</text>
</view>
<view class="time">{{item.StartTime}}</view>
</view>
<view class="bottom">
<view class="item">
<text class="label">实付金额</text>
<text class="val">{{fixedNum(item.inPay,2)}}</text>
</view>
<view class="item">
<text class="label">站点名称</text>
<text class="val">{{item.stationName || ''}}</text>
</view>
<view class="item">
<text class="label">车牌号</text>
<text class="val">{{item.plateNumber || '未绑定车牌号'}}</text>
</view>
</view>
<view class="opBtn">
<view class="btn errorBtn" @click="toPay(item)" v-if="item.pay === 0 && item.startChargeSeqStat === 4"
style="margin-right: 10rpx;">主动结算
</view>
<!-- <view class="btn" style="margin-right: 20rpx;" @click="goToTicket(item)">去开票</view> -->
<view class="btn" style="margin-right: 20rpx;" @click="goToInfo(item)">查看详情</view>
<view v-if="item.plateNumber == null" class="btn" style="background-color: #00b26a;" @click="open(item)">
绑定车牌</view>
</view>
</view>
<uv-loading-icon text="加载中" v-if="isShowLoading"></uv-loading-icon>
<view class="loadTip" v-else>
到底了...
</view>
<kx-tabbar-placeholder></kx-tabbar-placeholder>
</template>
<view class="noData" v-else>
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/noData.png" mode="aspectFit"></image>
<text class="label">暂无数据</text>
</view>
</scroll-view>
<uv-toast ref="uToast"></uv-toast>
<uv-modal :show="showModal" content='是否结算该订单?' title='提示' :showCancelButton="true" class="modal"
@cancel="showModal = false" @confirm="submitPay"></uv-modal>
<uv-popup :show="showPop" mode="bottom">
<view class="btn">
<text @click="showPop = false">取消</text>
<picker @change="selectPlateNum($event)" :range="columns" range-key="label">
<text @click="showPop = false;isSelectCar = true" style="color: #1890ff;">选择车辆</text>
</picker>
<text @click="submitPlate">确定</text>
</view>
<view>
<text class="label">车牌号</text>
<view class="num">
<text class="block" v-for="(item,index) in popBlockArr" :key="index">{{item || ''}}</text>
</view>
</view>
<uv-keyboard :autoChange="true" ref="uKeyboard" mode="car" :show="true" :overlay="false"
@change="clickKeyboard($event)" :tooltip="false" @backspace="backspace"></uv-keyboard>
</uv-popup>
<uv-modal :show="showbindCarModal" :content='modalContent' title='提示' :showCancelButton="true" class="modal"
@cancel="showbindCarModal = false" @confirm="saveCarPlate"></uv-modal>
</view>
</s-layout>
</template>
<script>
import {
proactiveBilling
} from "@/sheep/api/charge/order";
import {
convertSecondsToTime,
throttle,
Tips
} from "@/sheep/util/utils";
import moment from "dayjs";
import * as OrderApi from "@/sheep/api/charge/order";
import * as UserApi from "@/sheep/api/charge/member";
export default {
data() {
return {
tabMap: new Map([
['all', '全部'],
[2, '进行中'],
[4, '已完成'],
[5, '待处理'],
]),
statusMap: new Map([
[1, '启动中'],
[2, '进行中'],
[3, '停止中'],
[4, '已完成'],
[5, '未知'],
]),
tabList: [],
activeTab: 'all',
time: 0,
pagetion: {
page: 1,
pageSize: 10
},
orderList: [],
time2: 0,
isShowLoading: false,
isShowPageLoading: true,
time3: 0,
nowRefsh: false,
getTrottle: undefined,
time4: 0,
showModal: true,
payOrderId: '',
orderId: null,
showPop: false,
popBlockArr: new Array(8),
plateValue: '',
isSelectCar: false,
columns: [],
plateNum: '未设置',
showbindCarModal: false,
modalContent: '',
totalData: {
economizeMoney: 0,
totalMoney: 0,
totalPower: 0
},
};
},
created() {
this.getTrottle = throttle((data) => {
this.active(data)
}, 800)
},
methods: {
fixedNum(num, fix) {
if (!num) {
return '0.00'
}
return Number(num).toFixed(fix)
},
clickKeyboard(event) {
if (this.plateValue.length >= 8) {
return;
}
this.plateValue += event;
this.plateValue.split("").forEach((k, index) => {
this.popBlockArr[index] = k;
});
},
backspace() {
this.popBlockArr = this.popBlockArr.map((item, index, arr) => {
if (!arr[index + 1]) item = '';
return item;
});
},
selectPlateNum(e) {
const {
detail
} = e;
this.plateNum = this.columns[detail.value].label;
this.bindCarconfirm();
},
open(orderInfo) {
this.showPop = true;
this.orderId = orderInfo.id;
console.log(this.orderId)
},
getCarList() {
this.columns = []
if (!sheep.$store('user').userInfo.token) return
UserApi.getMyCar().then(res => {
const {
data
} = res
// if (statusCode === 200) {
const find = data.find(item => item.isDefault);
console.log('find', find)
this.plateNum = this.initPlate ? this.initPlate : (find?.plateNumber || '未设置')
data.forEach(item => {
this.columns.push({
label: item.plateNumber,
id: item.id
})
})
// }
}).catch(err => {
console.log('err: ', err);
})
},
submitPlate() {
if (!this.plateValue) {
Tips({
icon: 'none',
title: '输入的车牌号是否为空'
})
return;
}
// const plateArr = this.plateValue.split("")
if (this.plateValue.length !== 8 && this.plateValue.length !== 7) {
Tips({
icon: 'none',
title: '输入的车牌号有误,请重新输入'
})
return;
}
this.showPop = false;
this.bindCarconfirm();
},
bindCarconfirm() {
let plate = this.isSelectCar ? this.plateNum : this.plateValue;
this.modalContent = `是否为此订单绑定车牌【${plate}】?`;
this.showbindCarModal = true;
},
saveCarPlate() {
let plate = this.isSelectCar ? this.plateNum : this.plateValue;
OrderApi.licensePlateBinding(this.orderId, plate).then(res => {
this.orderId = null;
this.popBlockArr = new Array(8);
this.plateValue = '',
this.isSelectCar = false;
this.showbindCarModal = false;
Tips({
icon: 'none',
title: '绑定成功!'
})
this.refresh();
}).catch(err => {
console.log(err)
this.showbindCarModal = false;
})
},
active(data) {
this.isShowPageLoading = true;
this.activeTab = data[0];
this.orderList = [];
this.pagetion.page = 1;
clearInterval(this.time4);
this.getData();
if (this.activeTab === '2') {
this.time4 = setInterval(() => {
this.getData();
}, 5000)
}
},
toPay(data) {
this.payOrderId = data.id;
this.showModal = true;
},
submitPay() {
proactiveBilling(this.payOrderId).then(res => {
if (res.statusCode === 200) {
Tips({
title: res.data.msg,
icon: 'success'
})
this.orderList = [];
this.getData();
} else {
Tips({
title: res.data.detail,
icon: 'none'
})
}
this.showModal = false;
}).catch(err => {
Tips({
title: err.error.detail,
icon: 'none'
})
this.showModal = false;
})
},
nextPage() {
if (this.isShowLoading) {
clearTimeout(this.time3);
this.time3 = setTimeout(() => {
this.pagetion.page += 1;
this.getData();
}, 2000)
}
},
refresh() {
this.nowRefsh = true;
this.pagetion.page = 1;
this.getData();
},
goToInfo(item) {
if (item.status !== '进行中') {
uni.navigateTo({
url: `/pages/order-info/order-info?id=${item.seqNumber}`
})
} else {
uni.navigateTo({
url: `/pages/charging/index?seqNumber=${item.seqNumber}`
})
}
},
async getData() {
try {
const {
data
} = await OrderApi.getOrderList(this.pagetion, this.activeTab);
if (data) {
const {
list,
total
} = data;
if (this.nowRefsh || this.isShowPageLoading) {
this.orderList = [];
}
this.isShowLoading = this.hasNext(total);
if (this.activeTab === '2') {
this.orderList = list?.map(item => this.formatData(item))
} else {
list?.forEach(item => {
this.orderList.push(this.formatData(item));
})
}
this.nowRefsh = false;
this.isShowPageLoading = false;
}
const {
data: result
} = await OrderApi.getTotalOrder();
result && (this.totalData = result);
} catch (e) {
this.isShowPageLoading = false;
this.$nextTick(() => {
this.$refs.uToast.show({
message: '数据异常,稍后重试',
type: 'error',
duration: '3000'
})
})
}
},
hasNext(total) {
return this.pagetion.page * this.pagetion.pageSize < total
},
formatData(item) {
item['status'] = this.statusMap.get(item.orderStatus);
item['StartTime'] = moment(item.startTime).format("YYYY-MM-DD HH:mm:ss");
item['EndTime'] = item['endTime'] ? moment(item.endTime).format("YYYY-MM-DD HH:mm:ss") : '';
item['StartChargeSeq'] = item['seqNumber'].substring(9);
item['interval'] = item['endTime'] ? convertSecondsToTime(item.startTime, item.endTime) :
convertSecondsToTime(item.startTime, +new Date());
return item;
}
},
onLoad(options) {
this.tabList = Array.from(this.tabMap);
console.log(this.tabList)
if (options.type) {
this.activeTab = options.type
}
this.isShowPageLoading = true;
},
onShow() {
this.orderList = [];
this.getData();
this.getCarList();
if (this.activeTab === '2') {
this.time4 = setInterval(() => {
this.getData();
}, 5000)
}
},
onHide() {
clearTimeout(this.time);
clearTimeout(this.time2);
clearTimeout(this.time3);
clearTimeout(this.time4);
},
beforeDestroy() {
clearTimeout(this.time);
clearTimeout(this.time2);
clearTimeout(this.time3);
clearTimeout(this.time4);
}
}
</script>
<style lang="scss" scoped>
.block {
background-color: #f5f9ff;
.header {
width: 640rpx;
margin: auto;
padding: 30rpx;
border-top-right-radius: 25rpx;
border-top-left-radius: 25rpx;
background: linear-gradient(45deg, #00c3ffa6, #0b91ff);
font-size: 30rpx;
color: #fffc;
position: relative;
.saveMoney {
color: #fff;
margin: 20rpx 0;
.money {
font-size: 45rpx;
font-weight: 600;
}
}
.saveInfo {
display: flex;
.vlaueData {
color: #fff;
}
}
.billInfo {
position: absolute;
right: 20rpx;
top: 30rpx;
font-size: 25rpx;
border-radius: 25rpx;
color: #fff;
background-color: #a5d2f78c;
}
}
.tabs {
display: flex;
justify-content: space-between;
// background-color: #fff;
padding: 0 15rpx;
.item {
flex: 1;
display: flex;
padding-top: 15rpx;
display: flex;
flex-direction: column;
align-items: center;
&::after {
width: 25rpx;
height: 5rpx;
content: "";
display: inline-block;
margin-top: 15rpx;
}
}
.activeItem {
.label {
color: #229bff;
}
&::after {
background-color: #229bff;
}
}
}
.orderItem {
padding: 25rpx;
width: 650rpx;
background-color: #fff;
box-shadow: 0rpx 0rpx 8rpx 5rpx rgba(172, 172, 172, 0.3);
margin: 30rpx auto;
color: #aaaaaa;
font-size: 30rpx;
border-radius: 15rpx;
.top {
border-bottom: 1rpx solid #e2e2e2;
padding-bottom: 20rpx;
color: #aaaaaa;
.code {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
.uv-text__value {
font-weight: 600 !important;
font-size: 30rpx !important;
color: #000 !important;
}
}
}
.bottom {
.item {
color: #aaaaaa;
margin: 15rpx 0;
.label {
margin-right: 20rpx;
}
}
}
.opBtn {
display: flex;
justify-content: flex-end;
.btn {
color: #fff;
padding: 15rpx 25rpx;
border-radius: 50rpx;
background-color: #229bff;
}
.errorBtn {
background-color: #f56c6c;
}
}
}
.uv-loading-icon {
margin-top: 10rpx;
}
.loadTip {
display: flex;
justify-content: center;
margin-top: 10rpx;
color: #aaaaaa;
}
.noData {
position: relative;
image {
width: 750rpx;
height: 50vh;
}
.label {
color: #aaaaaa;
display: flex;
justify-content: center;
position: absolute;
width: 750rpx;
top: 35vh;
}
}
}
.uv-slide-up-enter-active {
.uv-popup__content {
padding: 35rpx 35rpx 450rpx;
position: relative;
.label {
font-size: 22rpx;
color: #797979;
}
.btn {
color: #1890ff;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 50rpx;
}
.num {
display: flex;
justify-content: space-around;
align-items: center;
}
.block {
display: inline-block;
margin-right: 10rpx;
width: 46rpx;
background-color: #f2f2f2;
height: 50rpx;
line-height: 50rpx;
text-align: center;
}
.uv-popup {
position: absolute;
bottom: 0;
.uv-popup__content {
padding: 0;
}
}
}
}
</style>
+310
View File
@@ -0,0 +1,310 @@
<!-- 收银台 -->
<template>
<s-layout title="收银台">
<view class="bg-white ss-modal-box ss-flex-col">
<!-- 订单信息 -->
<view class="modal-header ss-flex-col ss-col-center ss-row-center">
<view class="money-box ss-m-b-20">
<text class="money-text">{{ fen2yuan(state.orderInfo.price) }}</text>
</view>
<view class="time-text">
<text>{{ payDescText }}</text>
</view>
</view>
<!-- 支付方式 -->
<view class="modal-content ss-flex-1">
<view class="pay-title ss-p-l-30 ss-m-y-30">选择支付方式</view>
<radio-group @change="onTapPay">
<label class="pay-type-item" v-for="item in state.payMethods" :key="item.title">
<view
class="pay-item ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom"
:class="{ 'disabled-pay-item': item.disabled }"
>
<view class="ss-flex ss-col-center">
<image
class="pay-icon"
v-if="item.disabled"
:src="sheep.$url.static('/static/img/shop/pay/cod_disabled.png')"
mode="aspectFit"
/>
<image
class="pay-icon"
v-else
:src="sheep.$url.static(item.icon)"
mode="aspectFit"
/>
<text class="pay-title">{{ item.title }}</text>
</view>
<view class="check-box ss-flex ss-col-center ss-p-l-10">
<view class="userInfo-money ss-m-r-10" v-if="item.value === 'wallet'">
余额: {{ fen2yuan(userWallet.balance) }}
</view>
<radio
:value="item.value"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
:disabled="item.disabled"
:checked="state.payment === item.value"
/>
</view>
</view>
</label>
</radio-group>
</view>
<!-- 工具 -->
<view class="modal-footer ss-flex ss-row-center ss-col-center ss-m-t-80 ss-m-b-40">
<button v-if="state.payStatus === 0" class="ss-reset-button past-due-btn">
检测支付环境中
</button>
<button v-else-if="state.payStatus === -1" class="ss-reset-button past-due-btn" disabled>
支付已过期
</button>
<button
v-else
class="ss-reset-button save-btn"
@tap="onPay"
:disabled="state.payStatus !== 1"
:class="{ 'disabled-btn': state.payStatus !== 1 }"
>
立即支付
</button>
</view>
</view>
</s-layout>
</template>
<script setup>
import { computed, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods';
import PayOrderApi from '@/sheep/api/pay/order';
import PayChannelApi from '@/sheep/api/pay/channel';
import { getPayMethods, goPayResult } from '@/sheep/platform/pay';
const userWallet = computed(() => sheep.$store('user').userWallet);
// 检测支付环境
const state = reactive({
orderType: 'goods', // 订单类型; goods - 商品订单, recharge - 充值订单
orderInfo: {}, // 支付单信息
payStatus: 0, // 0=检测支付环境, -2=未查询到支付单信息, -1=支付已过期, 1=待支付,2=订单已支付
payMethods: [], // 可选的支付方式
payment: '', // 选中的支付方式
connectorCode: '', // 枪号
});
const onPay = () => {
if (state.payment === '') {
sheep.$helper.toast('请选择支付方式');
return;
}
if (state.payment === 'wallet') {
uni.showModal({
title: '提示',
content: '确定要支付吗?',
success: function (res) {
if (res.confirm) {
sheep.$platform.pay(state.payment, state.orderType, state.orderInfo.id,state.connectorCode);
}
},
});
} else {
sheep.$platform.pay(state.payment, state.orderType, state.orderInfo.id,state.connectorCode);
}
};
// 支付文案提示
const payDescText = computed(() => {
if (state.payStatus === 2) {
return '该订单已支付';
}
if (state.payStatus === 1) {
const time = useDurationTime(state.orderInfo.expireTime);
if (time.ms <= 0) {
state.payStatus = -1;
return '';
}
return `剩余支付时间 ${time.h}:${time.m}:${time.s} `;
}
if (state.payStatus === -2) {
return '未查询到支付单信息';
}
return '';
});
// 状态转换:payOrder.status => payStatus
function checkPayStatus() {
if (state.orderInfo.status === 10 || state.orderInfo.status === 20) {
// 支付成功
state.payStatus = 2;
// 跳转回支付成功页
uni.showModal({
title: '提示',
content: '订单已支付',
showCancel: false,
success: function () {
goPayResult(state.orderInfo.id, state.orderType);
},
});
return;
}
if (state.orderInfo.status === 30) {
// 支付关闭
state.payStatus = -1;
return;
}
state.payStatus = 1; // 待支付
}
// 切换支付方式
function onTapPay(e) {
state.payment = e.detail.value;
}
// 设置支付订单信息
async function setOrder(id) {
// 获得支付订单信息
const { data, code } = await PayOrderApi.getOrder(id, true);
if (code !== 0 || !data) {
state.payStatus = -2;
return;
}
state.orderInfo = data;
// 设置支付状态
checkPayStatus();
// 获得支付方式
await setPayMethods();
}
// 获得支付方式
async function setPayMethods() {
const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId);
if (code !== 0) {
return;
}
state.payMethods = getPayMethods(data);
state.payMethods.find((item) => {
if (item.value && !item.disabled) {
state.payment = item.value;
return true;
}
});
}
onLoad((options) => {
if (
sheep.$platform.name === 'WechatOfficialAccount' &&
sheep.$platform.os === 'ios' &&
!sheep.$platform.landingPage.includes('pages/pay/index')
) {
location.reload();
return;
}
// 获得支付订单信息
let id = options.id;
if (options.orderType) {
state.orderType = options.orderType;
}
// 枪号
if (options.connectorCode) {
state.connectorCode = options.connectorCode;
}
setOrder(id);
// 刷新钱包的缓存
sheep.$store('user').getWallet();
});
</script>
<style lang="scss" scoped>
.pay-icon {
width: 36rpx;
height: 36rpx;
margin-right: 26rpx;
}
.ss-modal-box {
// max-height: 1000rpx;
.modal-header {
position: relative;
padding: 60rpx 20rpx 40rpx;
.money-text {
color: $red;
font-size: 46rpx;
font-weight: bold;
font-family: OPPOSANS;
&::before {
content: '¥';
font-size: 30rpx;
}
}
.time-text {
font-size: 26rpx;
color: $gray-b;
}
.close-icon {
position: absolute;
top: 10rpx;
right: 20rpx;
font-size: 46rpx;
opacity: 0.2;
}
}
.modal-content {
overflow-y: auto;
.pay-title {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
.pay-tip {
font-size: 26rpx;
color: #bbbbbb;
}
.pay-item {
height: 86rpx;
}
.disabled-pay-item {
.pay-title {
color: #999999;
}
}
.userInfo-money {
font-size: 26rpx;
color: #bbbbbb;
line-height: normal;
}
}
.save-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
.disabled-btn {
background: #e5e5e5;
color: #999999;
}
.past-due-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background-color: #999;
color: #fff;
}
}
</style>
+167
View File
@@ -0,0 +1,167 @@
<!-- 充值记录 -->
<template>
<s-layout class="widthdraw-log-wrap" title="充值记录">
<!-- 记录卡片 -->
<view class="wallet-log-box ss-p-b-30">
<view class="log-list" v-for="item in state.pagination.list" :key="item">
<view class="head ss-flex ss-col-center ss-row-between">
<view class="title">充值金额</view>
<view class="num" :class="item.refundStatus === 10 ? 'danger-color' : 'success-color'">
{{ fen2yuan(item.payPrice) }}
<text v-if="item.bonusPrice > 0">赠送 {{ fen2yuan(item.bonusPrice) }} </text>
</view>
</view>
<view class="status-box item ss-flex ss-col-center ss-row-between">
<view class="item-title">支付状态</view>
<view
class="status-text"
:class="item.refundStatus === 10 ? 'danger-color' : 'success-color'"
>
{{ item.refundStatus === 10 ? '已退款' : '已支付' }}
</view>
</view>
<view class="time-box item ss-flex ss-col-center ss-row-between">
<text class="item-title">充值渠道</text>
<view class="time ss-ellipsis-1">{{ item.payChannelName }}</view>
</view>
<view class="time-box item ss-flex ss-col-center ss-row-between">
<text class="item-title">充值单号</text>
<view class="time"> {{ item.payOrderChannelOrderNo }} </view>
</view>
<view class="time-box item ss-flex ss-col-center ss-row-between">
<text class="item-title">充值时间</text>
<view class="time">
{{ sheep.$helper.timeFormat(item.payTime, 'yyyy-mm-dd hh:MM:ss') }}</view
>
</view>
</view>
</view>
<s-empty
v-if="state.pagination.total === 0"
icon="/static/comment-empty.png"
text="暂无充值记录"
/>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
</s-layout>
</template>
<script setup>
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import PayWalletApi from '@/sheep/api/pay/wallet';
import sheep from '@/sheep';
import { fen2yuan } from '../../sheep/hooks/useGoods';
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 5,
},
loadStatus: '',
});
async function getLogList(page = 1, list_rows = 5) {
const { code, data } = await PayWalletApi.getWalletRechargePage({
pageNo: page,
pageSize: list_rows,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 加载更多
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getLogList();
}
onLoad(() => {
getLogList();
});
onReachBottom(() => {
loadMore();
});
</script>
<style lang="scss" scoped>
// 记录卡片
.log-list {
min-height: 213rpx;
background: $white;
margin-bottom: 10rpx;
padding-bottom: 10rpx;
.head {
padding: 0 35rpx;
height: 80rpx;
border-bottom: 1rpx solid $gray-e;
margin-bottom: 20rpx;
.title {
font-size: 28rpx;
font-weight: 500;
color: $dark-3;
}
.num {
font-size: 28rpx;
font-weight: 500;
}
}
.item {
padding: 0 30rpx 10rpx;
.item-icon {
color: $gray-d;
font-size: 36rpx;
margin-right: 8rpx;
}
.item-title {
width: 180rpx;
font-size: 24rpx;
font-weight: 400;
color: #666666;
}
.status-text {
font-size: 24rpx;
font-weight: 500;
}
.time {
font-size: 24rpx;
font-weight: 400;
color: #c0c0c0;
}
}
}
.warning-color {
color: #faad14;
}
.danger-color {
color: #ff4d4f;
}
.success-color {
color: #67c23a;
}
</style>
+287
View File
@@ -0,0 +1,287 @@
<!-- 充值界面 -->
<template>
<s-layout title="充值" class="withdraw-wrap" navbar="inner">
<view class="wallet-num-box ss-flex ss-col-center ss-row-between" :style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 108) + 'rpx',
},
]">
<view class="">
<view class="num-title">当前余额</view>
<view class="wallet-num">{{ fen2yuan(userStore?userStore.userWallet.balance:0) }}</view>
</view>
<button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/pay/recharge-log')">
充值记录
</button>
</view>
<view class="recharge-box">
<view class="recharge-card-box">
<view class="input-label ss-m-b-50">充值金额</view>
<view class="input-box ss-flex border-bottom ss-p-b-20">
<view class="unit"></view>
<uni-easyinput v-model="state.recharge_money" type="digit" placeholder="请输入充值金额" :inputBorder="false" />
</view>
<view class="face-value-box ss-flex ss-flex-wrap ss-m-y-40">
<button class="ss-reset-button face-value-btn" v-for="item in state.packageList" :key="item.money"
:class="[{ 'btn-active': state.recharge_money === fen2yuan(item.payPrice) }]" @tap="onCard(item.payPrice)">
<text class="face-value-title">{{ fen2yuan(item.payPrice) }}</text>
<view v-if="item.bonusPrice" class="face-value-tag">
{{ fen2yuan(item.bonusPrice) }}
</view>
</button>
</view>
<button class="ss-reset-button save-btn ss-m-t-60" @tap="onConfirm">
确认充值
</button>
</view>
</view>
</s-layout>
</template>
<script setup>
import {
computed,
reactive
} from 'vue';
import sheep from '@/sheep';
import {
onLoad
} from '@dcloudio/uni-app';
import {
fen2yuan
} from '@/sheep/hooks/useGoods';
import PayWalletApi from '@/sheep/api/pay/wallet';
import {
WxaSubscribeTemplate
} from '@/sheep/util/const';
const connectorCode = ref(0)
const userStore = computed(() => {
return sheep.$store('user').$state
});
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
const state = reactive({
recharge_money: '', // 输入的充值金额
packageList: [],
});
// 点击卡片,选择充值金额
function onCard(e) {
state.recharge_money = fen2yuan(e);
}
// 获得钱包充值套餐列表
async function getRechargeTabs() {
const {
code,
data
} = await PayWalletApi.getWalletRechargePackageList();
if (code !== 0) {
return;
}
state.packageList = data;
}
// 发起支付
async function onConfirm() {
const {
code,
data
} = await PayWalletApi.createWalletRecharge({
packageId: state.packageList.find((item) => fen2yuan(item.payPrice) === state.recharge_money)?.id,
payPrice: state.recharge_money * 100,
});
if (code !== 0) {
return;
}
// #ifdef MP
sheep.$platform.useProvider('wechat').subscribeMessage(WxaSubscribeTemplate.PAY_WALLET_RECHARGER_SUCCESS);
// #endif
const connectorCodeStr = connectorCode.value || '';
sheep.$router.go('/pages/pay/index', {
id: data.payOrderId,
orderType: 'recharge',
connectorCode: connectorCodeStr // 传递枪号
});
}
onShow(() => {
sheep.$store('user').getWallet()
});
onLoad((options) => {
getRechargeTabs();
// 枪号
if (options.connectorCode) {
connectorCode.value = options.connectorCode;
}
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-input-input {
font-family: OPPOSANS !important;
}
}
.wallet-num-box {
padding: 0 40rpx 80rpx;
background: url('https://kxcharge.oss-cn-beijing.aliyuncs.com/mini/box.png') 0 0/100% 812rpx no-repeat;
border-radius: 0 0 5% 5%;
.num-title {
font-size: 26rpx;
font-weight: 500;
color: $white;
margin-bottom: 20rpx;
}
.wallet-num {
font-size: 60rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.log-btn {
width: 170rpx;
height: 60rpx;
line-height: 60rpx;
border: 1rpx solid $white;
border-radius: 30rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: $white;
}
}
.recharge-box {
position: relative;
padding: 0 30rpx;
margin-top: -60rpx;
}
.save-btn {
width: 620rpx;
height: 86rpx;
border-radius: 44rpx;
font-size: 30rpx;
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
color: #FFFFFF;
}
.recharge-card-box {
width: 690rpx;
background: var(--ui-BG);
border-radius: 20rpx;
padding: 30rpx;
box-sizing: border-box;
.input-label {
font-size: 30rpx;
font-weight: 500;
color: #333;
}
.unit {
display: flex;
align-items: center;
font-size: 48rpx;
font-weight: 500;
}
.uni-easyinput__placeholder-class {
font-size: 30rpx;
height: 60rpx;
display: flex;
align-items: center;
}
:deep(.uni-easyinput__content-input) {
font-size: 48rpx;
}
.face-value-btn {
width: 200rpx;
height: 144rpx;
border: 1px solid var(--ui-BG-Main);
border-radius: 10rpx;
position: relative;
z-index: 1;
margin-bottom: 15rpx;
margin-right: 15rpx;
&:nth-of-type(3n) {
margin-right: 0;
}
.face-value-title {
font-size: 36rpx;
font-weight: 500;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
&::after {
content: '元';
font-size: 24rpx;
margin-left: 6rpx;
}
}
.face-value-tag {
position: absolute;
z-index: 2;
height: 40rpx;
line-height: 40rpx;
background: var(--ui-BG-Main);
opacity: 0.8;
border-radius: 10rpx 0 20rpx 0;
top: 0;
left: -2rpx;
padding: 0 16rpx;
font-size: 22rpx;
color: $white;
font-family: OPPOSANS;
}
&::before {
position: absolute;
content: ' ';
width: 100%;
height: 100%;
background: var(--ui-BG-Main);
opacity: 0.1;
z-index: 0;
left: 0;
top: 0;
}
}
.btn-active {
z-index: 1;
&::before {
content: '';
background: var(--ui-BG-Main);
opacity: 1;
}
.face-value-title {
color: $white;
position: relative;
z-index: 1;
font-family: OPPOSANS;
}
.face-value-tag {
background: $white;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
}
}
}
</style>
+329
View File
@@ -0,0 +1,329 @@
<!-- 支付结果页面 -->
<template>
<s-layout title="支付结果" :bgStyle="{ color: '#FFF' }">
<view class="pay-result-box ss-flex-col ss-row-center ss-col-center">
<!-- 信息展示 -->
<view class="pay-waiting ss-m-b-30" v-if="payResult === 'waiting'" />
<image
class="pay-img ss-m-b-30"
v-if="payResult === 'success'"
:src="sheep.$url.static('/static/img/shop/order/order_pay_success.gif')"
/>
<image
class="pay-img ss-m-b-30"
v-if="['failed', 'closed'].includes(payResult)"
:src="sheep.$url.static('/static/img/shop/order/order_paty_fail.gif')"
/>
<view class="tip-text ss-m-b-30" v-if="payResult === 'success'">支付成功</view>
<view class="tip-text ss-m-b-30" v-if="payResult === 'failed'">支付失败</view>
<view class="tip-text ss-m-b-30" v-if="payResult === 'closed'">该订单已关闭</view>
<view class="tip-text ss-m-b-30" v-if="payResult === 'waiting'">检测支付结果...</view>
<view class="pay-total-num ss-flex" v-if="payResult === 'success'">
<view>{{ fen2yuan(state.orderInfo.price) }}</view>
</view>
<!-- 操作区 -->
<view class="btn-box ss-flex ss-row-center ss-m-t-50">
<button
class="back-btn ss-reset-button"
@tap="state.connectorCode ?
sheep.$router.redirect('/pages/equipment-detail/index', { connectorCode: state.connectorCode }) :
sheep.$router.go('/pages/index/index')"
>
{{ state.connectorCode ? '继续充电' : '返回首页' }}
</button>
<button
class="check-btn ss-reset-button"
v-if="payResult === 'failed'"
@tap="
sheep.$router.redirect('/pages/pay/index', { id: state.id, orderType: state.orderType })
"
>
重新支付
</button>
<button class="check-btn ss-reset-button" v-if="payResult === 'success'" @tap="onOrder">
查看订单
</button>
<button
class="check-btn ss-reset-button"
v-if="payResult === 'success' && state.tradeOrder.type === 3"
@tap="sheep.$router.redirect('/pages/activity/groupon/order')"
>
我的拼团
</button>
</view>
<!-- #ifdef MP -->
<view
class="subscribe-box ss-flex ss-m-t-44"
v-if="showSubscribeBtn && state.orderType === 'goods'"
>
<image class="subscribe-img" :src="sheep.$url.static('/static/img/shop/order/cargo.png')" />
<view class="subscribe-title ss-m-r-48 ss-m-l-16">获取实时发货信息与订单状态</view>
<view class="subscribe-start" @tap="subscribeMessage">立即订阅</view>
</view>
<!-- #endif -->
</view>
</s-layout>
</template>
<script setup>
import { onLoad, onHide, onShow } from '@dcloudio/uni-app';
import { reactive, computed, ref } from 'vue';
import { isEmpty } from 'lodash-es';
import sheep from '@/sheep';
import PayOrderApi from '@/sheep/api/pay/order';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import OrderApi from '@/sheep/api/trade/order';
import { WxaSubscribeTemplate } from '@/sheep/util/const';
const state = reactive({
id: 0, // 支付单号
orderType: 'goods', // 订单类型
result: 'unpaid', // 支付状态
orderInfo: {}, // 支付订单信息
tradeOrder: {}, // 商品订单信息,只有在 orderType 为 goods 才会请求。目的:【我的拼团】按钮的展示
counter: 0, // 获取结果次数
connectorCode: '', // 枪号
});
// 支付结果 result => payResult
const payResult = computed(() => {
if (state.result === 'unpaid') {
return 'waiting';
}
if (state.result === 'paid') {
return 'success';
}
if (state.result === 'failed') {
return 'failed';
}
if (state.result === 'closed') {
return 'closed';
}
});
// 获得订单信息
async function getOrderInfo(id) {
state.counter++;
// 1. 加载订单信息
const { data, code } = await PayOrderApi.getOrder(id);
if (code === 0) {
state.orderInfo = data;
if (!state.orderInfo || state.orderInfo.status === 30) {
// 支付关闭
state.result = 'closed';
return;
}
if (state.orderInfo.status !== 0) {
// 非待支付,可能是已支付,可能是已退款
state.result = 'paid';
// #ifdef MP
uni.showModal({
title: '支付结果',
showCancel: false, // 不要取消按钮
content: '支付成功',
success: () => {
// 订阅只能由用户主动触发,只能包一层 showModal 诱导用户点击
autoSubscribeMessage();
},
});
// #endif
// 特殊:获得商品订单信息
if (state.orderType === 'goods') {
const { data, code } = await OrderApi.getOrderDetail(
state.orderInfo.merchantOrderId,
true,
);
if (code === 0) {
state.tradeOrder = data;
}
}
return;
}
}
// 2.1 情况三一:未支付,且轮询次数小于三次,则继续轮询
if (state.counter < 3 && state.result === 'unpaid') {
setTimeout(() => {
getOrderInfo(id);
}, 1500);
}
// 2.2 情况二:超过三次检测才判断为支付失败
if (state.counter >= 3) {
state.result = 'failed';
}
}
function onOrder() {
if (state.orderType === 'recharge') {
sheep.$router.redirect('/pages/pay/recharge-log');
} else {
sheep.$router.redirect('/pages/order/list');
}
}
// #ifdef MP
const showSubscribeBtn = ref(false); // 默认隐藏
const SUBSCRIBE_BTN_STATUS_STORAGE_KEY = 'subscribe_btn_status';
function subscribeMessage() {
if (state.orderType !== 'goods') {
return;
}
const event = [WxaSubscribeTemplate.TRADE_ORDER_DELIVERY];
if (state.tradeOrder.type === 3) {
event.push(WxaSubscribeTemplate.PROMOTION_COMBINATION_SUCCESS);
}
sheep.$platform.useProvider('wechat').subscribeMessage(event, () => {
// 订阅后记录一下订阅状态
uni.removeStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY);
uni.setStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY, '已订阅');
// 隐藏订阅按钮
showSubscribeBtn.value = false;
});
}
async function autoSubscribeMessage() {
// 1. 校验是否手动订阅过
const subscribeBtnStatus = uni.getStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY);
if (!subscribeBtnStatus) {
showSubscribeBtn.value = true;
return;
}
// 2. 订阅消息
subscribeMessage();
}
// #endif
onLoad(async (options) => {
// 支付订单号
if (options.id) {
state.id = options.id;
}
// 订单类型
if (options.orderType) {
state.orderType = options.orderType;
}
// 枪号
if (options.connectorCode) {
state.connectorCode = options.connectorCode;
}
// 支付结果传值过来是失败,则直接显示失败界面
if (options.payState === 'fail') {
state.result = 'failed';
} else {
// 轮询三次检测订单支付结果
await getOrderInfo(state.id);
}
});
onShow(() => {
if (isEmpty(state.orderInfo)) {
return;
}
getOrderInfo(state.id);
});
onHide(() => {
state.result = 'unpaid';
state.counter = 0;
});
</script>
<style lang="scss" scoped>
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.score-img {
width: 36rpx;
height: 36rpx;
margin: 0 4rpx;
}
.pay-result-box {
padding: 60rpx 0;
.pay-waiting {
margin-top: 20rpx;
width: 60rpx;
height: 60rpx;
border: 10rpx solid rgb(233, 231, 231);
border-bottom-color: rgb(204, 204, 204);
border-radius: 50%;
display: inline-block;
// -webkit-animation: rotation 1s linear infinite;
animation: rotation 1s linear infinite;
}
.pay-img {
width: 130rpx;
height: 130rpx;
}
.tip-text {
font-size: 30rpx;
font-weight: bold;
color: #333333;
}
.pay-total-num {
font-size: 36rpx;
font-weight: 500;
color: #333333;
font-family: OPPOSANS;
}
.btn-box {
width: 100%;
.back-btn {
width: 190rpx;
height: 70rpx;
font-size: 28rpx;
border: 2rpx solid #dfdfdf;
border-radius: 35rpx;
font-weight: 400;
color: #595959;
}
.check-btn {
width: 190rpx;
height: 70rpx;
font-size: 28rpx;
border: 2rpx solid #dfdfdf;
border-radius: 35rpx;
font-weight: 400;
color: #595959;
margin-left: 32rpx;
}
}
.subscribe-box {
.subscribe-img {
width: 44rpx;
height: 44rpx;
}
.subscribe-title {
font-weight: 500;
font-size: 32rpx;
line-height: 36rpx;
color: #434343;
}
.subscribe-start {
color: var(--ui-BG-Main);
font-weight: 700;
font-size: 32rpx;
line-height: 36rpx;
}
}
}
</style>
+283
View File
@@ -0,0 +1,283 @@
<template>
<s-layout title="电价详情" navbar="normal" onShareAppMessage>
<view class="block">
<view class="header">
<text class="name title">时段</text>
<view class="formula">
<view class="item" style="margin-right: 5rpx;">
<text class="name">价格</text>
<text class="unit">(/)</text>
</view>
<view>=</view>
<view class="item">
<text class="name">电价</text>
<text class="unit">(/)</text>
</view>
<view style="margin-right: 8rpx;">+</view>
<view class="item">
<text class="name">服务费</text>
<text class="unit">(/)</text>
</view>
</view>
</view>
<scroll-view scroll-y="true" class="scroll">
<view class="item" :class="{nowItem: item.isNow}" v-for="(item,index) in intervalList" :key="index">
<view class="left">
<text class="now" v-if="item.isNow">当前时段</text>
<text class="time">{{item.interval}}</text>
</view>
<view class="info">
<view class="price">
<text style="width: 80rpx;">价格</text>
<text class="total">{{fixedNum(item.eleMoney + item.serviceMoney,2)}}</text>
<text>{{fixedNum(item.eleMoney,2)}}</text>
<text>{{fixedNum(item.serviceMoney,2)}}</text>
</view>
<view class="price vip">
<text style="width: 80rpx;">VIP</text>
<text class="total">{{fixedNum(discountPrice(item.eleMoney, item.interval),2)}}</text>
<text>{{fixedNum(item.eleMoney,2)}}</text>
<text>{{ fixedNum(countDiscountPrice(item.interval),2)}}</text>
</view>
<view class="price vip">
<text style="width: 80rpx;">SVIP</text>
<text class="total">{{fixedNum(vipPrice(item.eleMoney, item.interval),2)}}</text>
<text>{{fixedNum(item.eleMoney ,2)}}</text>
<text>{{fixedNum(countVipPrice(item.interval),2)}}</text>
</view>
</view>
</view>
<view class="tip">
充电费用仅供参考实际以充电桩上费用为准
</view>
</scroll-view>
</view>
</s-layout>
</template>
<script>
import moment from 'dayjs';
export default {
data() {
return {
electricityFee: {},
serviceFee: {},
intervalList: [],
priceServiceDiscount: [],
isShowVip: false
};
},
methods: {
fixedNum(num, fix) {
// console.log('num: ',num);
if (!num) {
return "0.00"
}
return Number(num).toFixed(fix)
},
discountPrice(eleMoney, time) {
const servicePrice = this.priceServiceDiscount.find(item => item.time == time)?.priceDiscount;
return +eleMoney + +servicePrice;
},
vipPrice(eleMoney, time) {
const servicePrice = this.priceServiceDiscount.find(item => item.time == time)?.vipDiscountServiceFee;
return +eleMoney + +servicePrice;
},
countDiscountPrice(time) {
return this.priceServiceDiscount.find(item => item.time == time)?.priceDiscount;
},
countVipPrice(time) {
return this.priceServiceDiscount.find(item => item.time == time)?.vipDiscountServiceFee;
},
handelData() {
console.log('this.electricityFee, this.serviceFee: ', this.electricityFee, this.serviceFee);
const list = []
for (const key in this.electricityFee) {
const now = +new Date();
const timeArr = key.split("-").map(t => moment(`${moment().format("YYYY-MM-DD")} ${t}`).valueOf());
const obj = {
interval: key,
eleMoney: this.electricityFee[key] || 0,
isNow: now > timeArr[0] && now <= timeArr[1]
};
if (this.serviceFee[key] || this.serviceFee[key] === 0) {
obj['serviceMoney'] = this.serviceFee[key] || 0
} else {
for (const k in this.serviceFee) {
const t = moment(`${moment().format("YYYY-MM-DD")} ${k.split("-")[1]}`).valueOf();
if (timeArr[1] < t) {
obj['serviceMoney'] = this.serviceFee[k] || 0;
break;
}
}
}
list.push(obj);
}
const index = list.findIndex(k => k.isNow);
if (index > -1) this.intervalList.push(list[index]);
const itemsToKeep = list.slice(0, index).concat(list.slice(index + 1));
itemsToKeep.sort((a, b) => {
const atime = moment(`${moment().format("YYYY-MM-DD")} ${a.interval.split("-")[1]}`).valueOf();
const btime = moment(`${moment().format("YYYY-MM-DD")} ${b.interval.split("-")[1]}`).valueOf();
return atime - btime
}).forEach(item => {
this.intervalList.push(item)
})
const deletedItems = list.splice(index, 1);
console.log('this.intervalList, list: ', this.intervalList, list);
}
},
onLoad(option) {
console.log(89, option)
// 解析 electricityFee 和 serviceFee
const parseFeeString = (feeString) => {
const result = {};
const items = feeString.split(";").filter(item => item); // 按分号分割并过滤空项
items.forEach((item) => {
// 找到最后一个冒号的位置
const lastColonIndex = item.lastIndexOf(":");
if (lastColonIndex === -1) return; // 如果没有冒号,跳过
const key = item.slice(0, lastColonIndex); // 时间区间(如 "00:00-01:00"
const value = item.slice(lastColonIndex + 1); // 数值(如 "1.0"
if (key && value) {
result[key] = parseFloat(value);
}
});
return result;
};
this.electricityFee = parseFeeString(option.electricityFee.slice(1, -1));
this.serviceFee = parseFeeString(option.serviceFee.slice(1, -1));
this.priceServiceDiscount = JSON.parse(option.priceServiceDiscount);
this.isShowVip = true;
this.handelData();
}
}
</script>
<style lang="scss" scoped>
.block {
background-color: #fff;
height: 100vh;
.header {
width: 680rpx;
margin: auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
.formula {
display: flex;
width: 60%;
justify-content: flex-end;
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 105rpx;
.unit {
font-size: 25rpx;
color: #aaaaaa;
}
}
}
.name {
font-size: 30rpx;
font-weight: 600;
}
.title {
display: inline-block;
width: 30%;
text-align: center;
}
}
.scroll {
padding-bottom: 25rpx;
.item {
width: 700rpx;
margin: auto;
border-radius: 12rpx;
box-shadow: 0px 0px 5px 5px #f1f1f1;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
color: #666;
font-size: 30rpx;
position: relative;
overflow: hidden;
.left {
width: 30%;
text-align: center;
padding: 50rpx 0;
.now {
position: absolute;
top: 0;
left: 0;
background-color: #1890FF;
padding: 5rpx 20rpx;
color: #fff;
font-size: 25rpx;
}
}
.time,
.total {
font-weight: 600;
}
.info {
width: 70%;
padding: 20rpx 0;
display: flex;
flex-direction: column;
justify-content: space-around;
color: #1890FF;
.price {
width: 95%;
padding: 10rpx 0;
display: flex;
justify-content: space-between;
color: #1890FF;
}
.vip {
color: #fe5d0f;
}
// padding-right: 10rpx;
}
}
.nowItem {
border: 1rpx solid #1890FF;
background: #1890FF4D;
}
}
.tip {
font-size: 25rpx;
color: #aaaaaa;
width: 700rpx;
margin: auto;
}
}
</style>
+15
View File
@@ -0,0 +1,15 @@
<!-- FAQ 常见问题 -->
<template>
<s-layout class="set-wrap" title="合作联系" :bgStyle="{ color: '#FFF' }">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/qywx.jpg" mode="widthFix" show-menu-by-longpress></image>
<template v-slot:kxTabBar>
<kx-tabbar-wrapper>
<view class="tab-bar">
扫码识别商业合作
</view>
</kx-tabbar-wrapper>
</template>
</s-layout>
</template>
<script setup></script>
+60
View File
@@ -0,0 +1,60 @@
<!-- 错误界面 -->
<template>
<view class="error-page">
<s-empty
v-if="errCode === 'NetworkError'"
icon="/static/internet-empty.png"
text="网络连接失败"
showAction
actionText="重新连接"
@clickAction="onReconnect"
buttonColor="#ff3000"
/>
<s-empty
v-else-if="errCode === 'TemplateError'"
icon="/static/internet-empty.png"
text="未找到模板"
showAction
actionText="重新加载"
@clickAction="onReconnect"
buttonColor="#ff3000"
/>
<s-empty
v-else-if="errCode !== ''"
icon="/static/internet-empty.png"
:text="errMsg"
showAction
actionText="重新加载"
@clickAction="onReconnect"
buttonColor="#ff3000"
/>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { ref } from 'vue';
import { ShoproInit } from '@/sheep';
const errCode = ref('');
const errMsg = ref('');
onLoad((options) => {
errCode.value = options.errCode;
errMsg.value = options.errMsg;
});
// 重新连接
async function onReconnect() {
uni.reLaunch({
url: '/pages/index/index',
});
await ShoproInit();
}
</script>
<style lang="scss" scoped>
.error-page {
width: 100%;
}
</style>
+118
View File
@@ -0,0 +1,118 @@
<!-- FAQ 常见问题 -->
<template>
<s-layout class="set-wrap" title="常见问题" :bgStyle="{ color: '#FFF' }">
<uni-collapse>
<uni-collapse-item v-for="(item, index) in state.list" :key="item">
<template v-slot:title>
<view class="ss-flex ss-col-center header">
<view class="ss-m-l-20 ss-m-r-20 icon">
<view class="rectangle">
<view class="num ss-flex ss-row-center ss-col-center">
{{ index + 1 < 10 ? '0' + (index + 1) : index + 1 }}
</view>
</view>
<view class="triangle"> </view>
</view>
<view class="title ss-m-t-36 ss-m-b-36">
{{ item.title }}
</view>
</view>
</template>
<view class="content ss-p-l-78 ss-p-r-40 ss-p-b-50 ss-p-t-20">
<text class="text">{{ item.content }}</text>
</view>
</uni-collapse-item>
</uni-collapse>
<s-empty
v-if="state.list.length === 0 && !state.loading"
text="暂无常见问题"
icon="/static/collect-empty.png"
/>
</s-layout>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import sheep from '@/sheep';
const state = reactive({
list: [],
loading: true,
});
async function getFaqList() {
const { error, data } = await sheep.$api.data.faq();
if (error === 0) {
state.list = data;
state.loading = false;
}
}
onLoad(() => {
// TODO 【文章】目前简单做,使用营销文章,作为 faq
if (true) {
sheep.$router.go('/pages/public/richtext', {
title: '常见问题',
})
return;
}
getFaqList();
});
</script>
<style lang="scss" scoped>
.header {
.title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: 30rpx;
max-width: 688rpx;
}
.icon {
position: relative;
width: 40rpx;
height: 40rpx;
.rectangle {
position: absolute;
left: 0;
top: 0;
width: 40rpx;
height: 36rpx;
background: var(--ui-BG-Main);
border-radius: 4px;
.num {
width: 100%;
height: 100%;
font-size: 24rpx;
font-weight: 500;
color: var(--ui-BG);
line-height: 32rpx;
}
}
.triangle {
width: 0;
height: 0;
border-left: 4rpx solid transparent;
border-right: 4rpx solid transparent;
border-top: 8rpx solid var(--ui-BG-Main);
position: absolute;
left: 16rpx;
bottom: -4rpx;
}
}
}
.content {
border-bottom: 1rpx solid #dfdfdf;
.text {
font-size: 26rpx;
color: #666666;
}
}
</style>
+54
View File
@@ -0,0 +1,54 @@
<!-- 文章展示 -->
<template>
<s-layout class="set-wrap" :title="state.title" :bgStyle="{ color: '#FFF' }">
<view class="ss-p-30">
<mp-html class="richtext" :content="state.content" />
</view>
</s-layout>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import ArticleApi from '@/sheep/api/promotion/article';
const state = reactive({
title: '',
content: '',
});
async function getRichTextContent(id, title) {
const { code, data } = await ArticleApi.getArticle(id, title);
if (code !== 0) {
return;
}
state.content = data.content;
// 标题不一致时,修改标题
if (state.title !== data.title) {
state.title = data.title;
uni.setNavigationBarTitle({
title: state.title,
});
}
}
onLoad((options) => {
if (options.title) {
state.title = options.title;
uni.setNavigationBarTitle({
title: state.title,
});
}
getRichTextContent(options.id, options.title);
});
</script>
<style lang="scss" scoped>
.set-title {
margin: 0 30rpx;
}
.richtext {
}
</style>
+201
View File
@@ -0,0 +1,201 @@
<template>
<s-layout class="set-wrap" title="系统设置" :bgStyle="{ color: '#fff' }">
<view class="header-box ss-flex-col ss-row-center ss-col-center">
<image class="logo-img ss-m-b-46" :src="sheep.$url.cdn(appInfo.logo)" mode="aspectFit"></image>
<view class="name ss-m-b-24">{{ appInfo.name }}</view>
</view>
<view class="container-list">
<uni-list :border="false">
<uni-list-item title="当前版本" :rightText="appInfo.version" showArrow clickable :border="false" class="list-border"
@tap="onCheckUpdate" />
<uni-list-item title="本地缓存" :rightText="storageSize" showArrow :border="false" class="list-border" />
<uni-list-item title="关于我们" showArrow clickable :border="false" class="list-border" @tap="
sheep.$router.go('/pages/public/richtext', {
title: '关于我们'
})
" />
<!-- 为了过审 只有 iOS-App 有注销账号功能 -->
<uni-list-item v-if="isLogin && sheep.$platform.os === 'ios' && sheep.$platform.name === 'App'" title="注销账号"
rightText="" showArrow clickable :border="false" class="list-border" @click="onLogoff" />
</uni-list>
</view>
<view class="set-footer ss-flex-col ss-row-center ss-col-center">
<view class="agreement-box ss-flex ss-col-center ss-m-b-40">
<view class="ss-flex ss-col-center ss-m-b-10">
<view class="tcp-text" @tap="
sheep.$router.go('/pages/public/richtext', {
title: '用户协议'
})
">
用户协议
</view>
<view class="agreement-text"></view>
<view class="tcp-text" @tap="
sheep.$router.go('/pages/public/richtext', {
title: '隐私协议'
})
">
隐私协议
</view>
</view>
</view>
<view class="copyright-text ss-m-b-10">{{ appInfo.copyright }}</view>
<view class="copyright-text">{{ appInfo.copytime }}</view>
</view>
<su-fixed bottom placeholder>
<view class="ss-p-x-20 ss-p-b-40">
<button class="loginout-btn ss-reset-button ui-BG-Main ui-Shadow-Main" @tap="onLogout" v-if="isLogin">
退出登录
</button>
</view>
</su-fixed>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import {
computed,
reactive
} from 'vue';
import AuthUtil from '@/sheep/api/member/auth';
const appInfo = computed(() => sheep.$store('app').info);
const isLogin = computed(() => sheep.$store('user').isLogin);
const storageSize = uni.getStorageInfoSync().currentSize + 'Kb';
const state = reactive({
showModal: false,
});
function onCheckUpdate() {
sheep.$platform.checkUpdate();
// 小程序初始化时已检查更新
// H5实时更新无需检查
// App 1.跳转应用市场更新 2.手动热更新 3.整包更新
}
// 注销账号
function onLogoff() {
uni.showModal({
title: '提示',
content: '确认注销账号?',
success: async function(res) {
if (!res.confirm) {
return;
}
const {
code
} = await AuthUtil.logout();
if (code !== 0) {
return;
}
sheep.$store('user').logout();
sheep.$router.go('/pages/index/user');
},
});
}
// 退出账号
function onLogout() {
uni.showModal({
title: '提示',
content: '确认退出账号?',
success: async function(res) {
if (!res.confirm) {
return;
}
const {
code
} = await AuthUtil.logout();
if (code !== 0) {
return;
}
sheep.$store('user').logout();
sheep.$router.go('/pages/my/index');
},
});
}
</script>
<style lang="scss" scoped>
.container-list {
width: 100%;
}
.set-title {
margin: 0 30rpx;
}
.header-box {
padding: 100rpx 0;
.logo-img {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
}
.name {
font-size: 42rpx;
font-weight: 400;
color: $dark-3;
}
.version {
font-size: 32rpx;
font-weight: 500;
line-height: 32rpx;
color: $gray-b;
}
}
.set-footer {
margin: 100rpx 0 0 0;
.copyright-text {
font-size: 22rpx;
font-weight: 500;
color: $gray-c;
line-height: 30rpx;
}
.agreement-box {
font-size: 26rpx;
font-weight: 500;
.tcp-text {
color: var(--ui-BG-Main);
}
.agreement-text {
color: $dark-9;
}
}
}
.loginout-btn {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
font-size: 30rpx;
}
.list-border {
font-size: 28rpx;
font-weight: 400;
color: #333333;
border-bottom: 2rpx solid #eeeeee;
}
:deep(.uni-list-item__content-title) {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
:deep(.uni-list-item__extra-text) {
color: #bbbbbb;
font-size: 28rpx;
}
</style>
+18
View File
@@ -0,0 +1,18 @@
<!-- 网页加载 -->
<template>
<view>
<web-view :src="url" />
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app';
import { ref } from 'vue';
const url = ref('');
onLoad((options) => {
url.value = decodeURIComponent(options.url);
});
</script>
<style lang="scss" scoped></style>
+27
View File
@@ -0,0 +1,27 @@
<template>
<s-layout navbar="normal" onShareAppMessage>
</s-layout>
</template>
<script setup>
import tenantId from '@/sheep/config/tenantId';
onLoad(async () => {
uni.showLoading({
title: '加载中...',
mask: true
})
Promise.all([tenantId.getTenantByWebsite(), sheep.$store('user').getLocation()])
.then(() => {
// 加载Shopro底层依赖
ShoproInit();
sheep.$store('dict').getDictData()
uni.hideLoading()
uni.switchTab({
url: '/pages/index/index'
})
}, err => {
uni.hideLoading()
console.log(err)
})
})
</script>
+614
View File
@@ -0,0 +1,614 @@
<template>
<s-layout :title="station?.stationName" navbar="normal" onShareAppMessage>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="info-container">
<view class="content">
<view class="header">
<view class="name">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16511.png"></image>
<text>{{station.stationName}}</text>
</view>
<view class="favorite" @click="toggleFavorite">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27697(1).png" v-if="station.favorite"></image>
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27697.png" v-else></image>
<text :class="{active:station.favorite}">收藏</text>
</view>
</view>
<view class="abstract">
<text>{{station.stationType}}</text>
<view class="line"></view>
<text>{{station.openExplain}}</text>
<view class="line"></view>
<text>{{station.remarks}}</text>
</view>
<view class="abstract">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16512.png"></image>
<text>营业时间{{station.openExplain}}</text>
</view>
<scroll-view scroll-x="true" class="detail">
<image :src="station.imgFlagPath"></image>
<image :src="station.imgFullView"></image>
<image :src="station.imgMainEntrance"></image>
<image :src="station.imgOther"></image>
<image :src="station.imgPilesFeature"></image>
</scroll-view>
</view>
<view class="tip">
<text>{{station.address}}</text>
<view class="distance">
<uni-icons type="paperplane-filled" color="#3072F6" :size="13"></uni-icons>
{{station?.distance?.toFixed(2)}}
</view>
</view>
</view>
</uni-card>
<view class="tabs">
<view class="tab-item" :class="{active:currentType===CONNECT_TYPE.FAST}" @click="onToggleType(CONNECT_TYPE.FAST)">
<view class="icon"></view>
<view class="text">{{station.leisureConnectorNumber}}/{{station.connectorNumber}}</view>
<view class="arrow">
<!-- <image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/jiantou1.png"></image> -->
</view>
</view>
<view class="tab-item slow" :class="{active:currentType===CONNECT_TYPE.SLOW}"
@click="onToggleType(CONNECT_TYPE.SLOW)">
<view class="icon"></view>
<view class="text">{{station.slowLeisureConnectorNumber}}/{{station.slowConnectorNumber}}</view>
<view class="arrow">
<!-- <image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/jiantou2.png"></image> -->
</view>
</view>
</view>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="price-container">
<view class="header">
<view class="title">
<view class="line"></view>
价格信息
</view>
<view class="detail" @click="goToPriceDetails()">价格详情<uni-icons type="right" size="14"></uni-icons></view>
</view>
<view class="time">价格时段08:30-24:00</view>
<view class="charge">
<view class="price">
<text>{{station.currentPrice}}</text>
/
</view>
<view class="vip">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16568@2x.png" mode="aspectFit"></image>
<text>
{{station.currentPrice}}/
</text>
</view>
</view>
<view class="tip">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16415@2x.png" mode="aspectFit"></image>
<text>停车说明{{station.parkExplain}}</text>
</view>
</view>
</uni-card>
<uni-card is-shadow spacing="0" padding="30rpx">
<view class="list-container">
<view class="header">
<view class="title">
<view class="line"></view>
终端列表
</view>
<view class="detail" @click="viewList">查看全部<uni-icons type="right" size="14"></uni-icons></view>
</view>
<view class="list">
<view class="list-item" v-for="item in list" :key="item.id"
@click="WORK_STATE.get(item.workState).clickable ? onChargeStart(item) : null">
<view class="status" :class="[WORK_STATE.get(item.workState).state,{plug:item.insertArmsState==='1'}]">
{{item.insertArmsState==='1'?'已插枪':WORK_STATE.get(item.workState).text}}
</view>
<view class="info">
<view class="name">{{item.connectorName}}</view>
<view class="remark">{{item.connectorCode}}</view>
</view>
</view>
</view>
</view>
</uni-card>
<template v-slot:kxTabBar>
<kx-tabbar @contact-service="contactService"></kx-tabbar>
</template>
</s-layout>
</template>
<script setup>
import {
createFavorite,
deleteFavorite,
getStationDetail
} from '@/sheep/api/charge/operations'
const CONNECT_TYPE = {
FAST: 1,
SLOW: 2
}
const WORK_STATE = new Map([
[0, { state: 'hitch', text: '故障', clickable: false }],
[1, { state: 'hitch', text: '故障', clickable: false }],
[2, { state: 'idle', text: '空闲', clickable: true }],
[3, { state: 'charging', text: '充电中', clickable: false }],
[4, { state: 'idle', text: '空闲', clickable: true }]
])
const station = ref({})
let refreshInterval = null // 存储定时器ID
let isRefreshing = false // 标记是否正在刷新中
const contactService = () => {
uni.makePhoneCall({
phoneNumber: station.value.serviceTel
});
}
// 获取站点详情
const fetchStationDetail = async () => {
try {
const { data } = await getStationDetail({
id,
longitude,
latitude
})
station.value = data
} catch (error) {
console.error('获取站点详情失败:', error)
} finally {
isRefreshing = false
}
}
// 启动定时刷新
const startRefresh = () => {
// 先清除已有的定时器
stopRefresh()
// 立即执行第一次刷新
fetchStationDetail()
// 设置定时器,每5秒刷新一次
// refreshInterval = setInterval(() => {
// if (!isRefreshing) {
// isRefreshing = true
// fetchStationDetail()
// }
// }, 5000)
}
// 停止定时刷新
const stopRefresh = () => {
if (refreshInterval) {
clearInterval(refreshInterval)
refreshInterval = null
}
isRefreshing = false
}
let id, longitude, latitude
onLoad((params) => {
id = params.id
longitude = params.longitude
latitude = params.latitude
// 开始定时刷新
startRefresh()
})
// 页面卸载时停止定时刷新
onUnload(() => {
stopRefresh()
})
const toggleFavorite = () => {
const {
stationLat: latitude,
stationLng: longitude,
id,
favorite
} = station.value
let api = createFavorite
if (favorite) {
api = deleteFavorite
}
api({
stationId: id
}).then(() => {
fetchStationDetail()
})
}
const currentType = ref(CONNECT_TYPE.FAST)
const onToggleType = (val) => {
currentType.value = val
}
const goToPriceDetails = () => {
const priceServiceDiscount = station.value.costTemplateRespVO.costTemplatePriceRespVOS.map(item => {
return {
time: `${item.startTimeStr}-${item.endTimeStr}`
}
})
uni.navigateTo({
url: `/pages/price-details/price-details?priceServiceDiscount=${JSON.stringify(priceServiceDiscount)}&electricityFee=${JSON.stringify(station.value.electricityFee)}&serviceFee=${JSON.stringify(station.value.serviceFee)}`
})
}
const list = computed(() => {
return station.value?.equipmentConnectorInfoVOS?.filter(el => el.equipmentModelRespVO.type === currentType
.value)
})
const viewList = () => {
uni.navigateTo({
url: '/pages/equipment-list/index'
})
}
const onChargeStart = (item) => {
uni.navigateTo({
url: `/pages/equipment-detail/index?connectorCode=${item.connectorCode}`
})
}
</script>
<style scoped lang="scss">
.info-container {
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.name {
font-weight: 800;
font-size: 34rpx;
color: #333;
image {
width: 36rpx;
height: 38rpx;
display: inline-block;
margin-right: 10rpx;
}
}
.favorite {
float: right;
image {
width: 30rpx;
height: 24rpx;
display: inline-block;
margin-right: 4rpx;
}
text {
font-weight: 500;
font-size: 24rpx;
color: #333333;
}
text.active {
color: #FC8229;
}
}
.abstract {
margin-top: 10rpx;
display: flex;
align-items: center;
image {
width: 24rpx;
height: 24rpx;
display: inline-block;
margin-right: 10rpx;
}
text {
font-weight: 500;
font-size: 24rpx;
color: #666;
}
.line {
width: 1rpx;
height: 21rpx;
border: 1rpx solid #ccc;
margin: 0 10rpx;
}
}
.detail {
margin: 10rpx 0;
white-space: nowrap;
image {
width: 200rpx;
height: 112rpx;
}
}
.tip {
padding-top: 20rpx;
border-top: 1rpx solid #E3E3E3;
text {
font-weight: 500;
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
}
.distance {
float: right;
background: rgba(48, 114, 246, .1);
border-radius: 25rpx;
font-weight: bold;
font-size: 24rpx;
color: #3072F6;
padding: 0 10rpx;
}
}
}
.tabs {
display: flex;
justify-content: space-evenly;
position: relative;
.tab-item {
color: #40C5A8;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 20rpx 1rpx rgba(53, 127, 251, 0.1);
border-radius: 20rpx 20rpx 20rpx 20rpx;
width: 334rpx;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
.icon {
font-weight: bold;
font-size: 26rpx;
color: #FFFFFF;
padding: 8rpx 10rpx;
margin-right: 10rpx;
background: #40C5A8;
border-radius: 50%;
}
.text {
font-size: 32rpx;
font-weight: bold;
}
.arrow {
position: absolute;
width: 30rpx;
height: 30rpx;
bottom: -2rpx;
display: none;
}
}
.tab-item.slow {
color: #FC8229;
.icon {
background: #FC8229;
}
}
.tab-item.active {
background: #40C5A8;
.icon {
color: #40C5A8;
background: #fff;
}
.text {
color: #fff;
}
.arrow {
display: block;
}
}
.tab-item.active.slow {
background: #FC8229;
.icon {
color: #FC8229;
}
}
}
.price-container {
.header {
display: flex;
justify-content: space-between;
}
.title {
font-weight: 800;
font-size: 32rpx;
color: #333333;
display: flex;
align-items: center;
.line {
width: 8rpx;
height: 32rpx;
background: #3072F6;
border-radius: 0rpx 0rpx 0rpx 0rpx;
margin-right: 16rpx;
}
}
.detail {
display: flex;
align-items: center;
font-size: 26rpx;
color: #666666;
}
.time {
font-size: 24rpx;
color: #333333;
margin: 26rpx 0;
}
.price {
font-size: 24rpx;
color: #333;
display: inline-block;
margin-right: 18rpx;
vertical-align: middle;
text {
font-size: 40rpx;
color: #EB5336;
}
}
.vip {
width: 194rpx;
height: 45rpx;
background: linear-gradient(180deg, #FADBB7 0%, #F6BA85 100%);
border-radius: 10rpx 10rpx 10rpx 10rpx;
display: inline-block;
image {
width: 82rpx;
height: 45rpx;
}
text {
font-size: 24rpx;
color: #3A3E5E;
}
}
.tip {
padding-top: 20rpx;
border-top: 1rpx solid #E3E3E3;
margin-top: 14rpx;
image {
width: 28rpx;
height: 28rpx;
}
text {
font-weight: 500;
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
}
}
}
.list-container {
.header {
display: flex;
justify-content: space-between;
}
.title {
font-weight: 800;
font-size: 32rpx;
color: #333333;
display: flex;
align-items: center;
.line {
width: 8rpx;
height: 32rpx;
background: #3072F6;
border-radius: 0rpx 0rpx 0rpx 0rpx;
margin-right: 16rpx;
}
}
.detail {
display: flex;
align-items: center;
font-size: 26rpx;
color: #666666;
}
.time {
font-size: 24rpx;
color: #333333;
margin: 26rpx 0;
}
}
.list-item {
display: flex;
padding: 30rpx 0;
color: #333333;
align-items: center;
&:not(:last-child) {
border-bottom: 1rpx solid #E3E3E3;
}
.status {
width: 98rpx;
height: 98rpx;
border-radius: 50%;
border-width: 6rpx;
border-style: solid;
border-color: rgba(48, 114, 246, 0.4);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
font-size: 28rpx;
color: #3072F6;
&.idle {
border-color: rgba(64, 197, 168, 0.4);
color: #40C5A8;
}
&.hitch {
border-color: rgba(184, 184, 184, 0.4);
color: red;
}
&.plug {
border-color: rgba(13, 192, 226, 0.4);
color: #0DC0E2;
}
&.charging {
border-color: rgba(13, 192, 226, 0.4);
color: #0d22e2;
}
}
.name {
font-weight: bold;
font-size: 28rpx;
margin-bottom: 16rpx;
}
.remark {
font-size: 24rpx;
}
}
</style>
+220
View File
@@ -0,0 +1,220 @@
<!-- 我的商品收藏 -->
<template>
<s-layout title="收藏站点">
<view class="cart-box ss-flex ss-flex-col ss-row-between">
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 个站点
</view>
<view class="header-right">
<button v-if="state.editMode && state.pagination.total" class="ss-reset-button" @tap="state.editMode = false">
取消
</button>
<button v-if="!state.editMode && state.pagination.total" class="ss-reset-button ui-TC-Main"
@tap="state.editMode = true">
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content">
<kx-station-info v-for="item in state.pagination.list" :station="item.stationInfoVO"
:key="item.id"></kx-station-info>
<!-- <view class="goods-box ss-r-10 ss-m-b-14" v-for="item in state.pagination.list" :key="item.id">
<view class="ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-l-10" v-if="state.editMode" @tap="onSelect(item.id)">
<radio :checked="state.selectedCollectList.includes(item.id)" color="var(--ui-BG-Main)"
style="transform: scale(0.8)" @tap.stop="onSelect(item.id)" />
</label>
<s-goods-item :title="item.spuName" :img="item.picUrl" :price="item.price" priceColor="#FF3000"
:titleWidth="400" @tap="
sheep.$router.go('/pages/goods/index', {
id: item.spuId,
})
" />
</view>
</view> -->
</view>
<!-- 底部 -->
<su-fixed bottom :val="0" placeholder v-show="state.editMode">
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio :checked="state.selectAll" color="var(--ui-BG-Main)" style="transform: scale(0.7)"
@tap.stop="onSelectAll" />
<view> 全选 </view>
</label>
</view>
<view class="footer-right">
<button class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main" @tap="onCancel">
取消收藏
</button>
</view>
</view>
</su-fixed>
</view>
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
contentdown: '上拉加载更多',
}" @tap="loadMore" />
<s-empty v-if="state.pagination.total === 0" text="暂无收藏" icon="/static/collect-empty.png" />
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import {
reactive
} from 'vue';
import {
onLoad,
onReachBottom
} from '@dcloudio/uni-app';
import _ from 'lodash-es';
import {
getFavoritePage,
deleteFavorite
} from '@/sheep/api/charge/operations';
import {
resetPagination
} from '@/sheep/util';
const sys_navBar = sheep.$platform.navbar;
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 6,
},
loadStatus: '',
editMode: false,
selectedCollectList: [], // 选中的 SPU 数组
selectAll: false,
});
async function getData() {
state.loadStatus = 'loading';
const res = await sheep.$store('user').getLocation()
const {
latitude,
longitude
} = res
const {
code,
data
} = await getFavoritePage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
latitude,
longitude
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 单选选中
const onSelect = (spuId) => {
if (!state.selectedCollectList.includes(spuId)) {
state.selectedCollectList.push(spuId);
} else {
state.selectedCollectList.splice(state.selectedCollectList.indexOf(spuId), 1);
}
state.selectAll = state.selectedCollectList.length === state.pagination.list.length;
};
// 全选
const onSelectAll = () => {
state.selectAll = !state.selectAll;
if (!state.selectAll) {
state.selectedCollectList = [];
} else {
state.selectedCollectList = state.pagination.list.map((item) => item.spuId);
}
};
async function onCancel() {
if (!state.selectedCollectList) {
return;
}
// 取消收藏
for (const id of state.selectedCollectList) {
await deleteFavorite(id);
}
// 清空选择 + 重新加载
state.editMode = false;
state.selectedCollectList = [];
state.selectAll = false;
resetPagination(state.pagination);
await getData();
}
// 加载更多
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getData();
}
onReachBottom(() => {
loadMore();
});
onLoad(() => {
getData();
});
</script>
<style lang="scss" scoped>
.cart-box {
.cart-header {
height: 70rpx;
// background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-footer {
height: 100rpx;
background-color: #fff;
.pay-btn {
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
padding: 0 40rpx;
min-width: 200rpx;
}
}
.cart-content {
width: 100%;
margin-top: 70rpx;
// padding: 0 20rpx;
box-sizing: border-box;
.goods-box {
background-color: #fff;
&:last-child {
margin-bottom: 40rpx;
}
}
}
}
</style>
+308
View File
@@ -0,0 +1,308 @@
<!-- 商品浏览记录 -->
<template>
<s-layout title="我的足迹" :bgStyle="{ color: '#f2f2f2' }">
<view class="cart-box ss-flex ss-flex-col ss-row-between">
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">
{{ state.pagination.total }}
</text>
件商品
</view>
<view class="header-right">
<button
v-if="state.editMode && state.pagination.total"
class="ss-reset-button"
@tap="state.editMode = false"
>
取消
</button>
<button
v-if="!state.editMode && state.pagination.total"
class="ss-reset-button ui-TC-Main"
@tap="state.editMode = true"
>
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content">
<view
class="goods-box ss-r-10 ss-m-b-14"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="ss-flex ss-col-center">
<label
class="check-box ss-flex ss-col-center ss-p-l-10"
v-if="state.editMode"
@tap="onSelect(item.spuId)"
>
<radio
:checked="state.selectedSpuIdList.includes(item.spuId)"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onSelect(item.spuId)"
/>
</label>
<s-goods-item
:title="item.spuName"
:img="item.picUrl"
:price="item.price"
:skuText="item.introduction"
priceColor="#FF3000"
:titleWidth="400"
@tap="
sheep.$router.go('/pages/goods/index', {
id: item.spuId,
})
"
>
</s-goods-item>
</view>
</view>
</view>
<!-- 底部 -->
<su-fixed bottom :val="0" placeholder v-show="state.editMode">
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio
:checked="state.selectAll"
color="var(--ui-BG-Main)"
style="transform: scale(0.7)"
@tap.stop="onSelectAll"
/>
<view>全选</view>
</label>
</view>
<view class="footer-right ss-flex">
<button
:class="[
'ss-reset-button pay-btn ss-font-28 ',
{
'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0,
'ui-Shadow-Main': state.selectedSpuIdList.length > 0,
},
]"
@tap="onDelete"
>
删除足迹
</button>
<button
class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main ml-2"
@tap="onClean"
>
清空
</button>
</view>
</view>
</su-fixed>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
<s-empty
v-if="state.pagination.total === 0"
text="暂无浏览记录"
icon="/static/collect-empty.png"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import SpuHistoryApi from '@/sheep/api/product/history';
import { cloneDeep } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
const pagination = {
list: [],
pageNo: 1,
total: 1,
pageSize: 10,
};
const state = reactive({
pagination: cloneDeep(pagination),
loadStatus: '',
editMode: false,
selectedSpuIdList: [],
selectAll: false,
});
async function getList() {
state.loadStatus = 'loading';
const { code, data } = await SpuHistoryApi.getBrowseHistoryPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 单选选中
const onSelect = (id) => {
if (!state.selectedSpuIdList.includes(id)) {
state.selectedSpuIdList.push(id);
} else {
state.selectedSpuIdList.splice(state.selectedSpuIdList.indexOf(id), 1);
}
state.selectAll = state.selectedSpuIdList.length === state.pagination.list.length;
};
// 全选
const onSelectAll = () => {
state.selectAll = !state.selectAll;
if (!state.selectAll) {
state.selectedSpuIdList = [];
} else {
state.pagination.list.forEach((item) => {
if (state.selectedSpuIdList.includes(item.spuId)) {
state.selectedSpuIdList.splice(state.selectedSpuIdList.indexOf(item.spuId), 1);
}
state.selectedSpuIdList.push(item.spuId);
});
}
};
// 删除足迹
async function onDelete() {
if (state.selectedSpuIdList.length <= 0) {
return;
}
const { code } = await SpuHistoryApi.deleteBrowseHistory(state.selectedSpuIdList);
if (code === 0) {
reload();
}
}
// 清空
async function onClean() {
const { code } = await SpuHistoryApi.cleanBrowseHistory();
if (code === 0) {
reload();
}
}
function reload() {
state.editMode = false;
state.selectedSpuIdList = [];
state.selectAll = false;
state.pagination = pagination;
getList();
}
// 加载更多
function loadMore() {
if (state.loadStatus !== 'noMore') {
state.pagination.pageNo += 1;
getList();
}
}
onReachBottom(() => {
loadMore();
});
onLoad(() => {
getList();
});
</script>
<style lang="scss" scoped>
.cart-box {
.cart-header {
height: 70rpx;
background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-footer {
height: 100rpx;
background-color: #fff;
.pay-btn {
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
padding: 0 40rpx;
min-width: 200rpx;
}
}
.cart-content {
width: 100%;
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 70rpx;
.goods-box {
background-color: #fff;
&:last-child {
margin-bottom: 40rpx;
}
}
}
}
.title-card {
padding: 36rpx 0 46rpx 20rpx;
.img-box {
width: 164rpx;
height: 164rpx;
.order-img {
width: 164rpx;
height: 164rpx;
}
}
.check-box {
height: 100%;
}
.title-text {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.params-box {
.params-title {
height: 38rpx;
background: #f4f4f4;
border-radius: 2rpx;
font-size: 24rpx;
font-weight: 400;
color: #666666;
}
}
.price-text {
color: $red;
font-family: OPPOSANS;
}
}
</style>
+282
View File
@@ -0,0 +1,282 @@
<template>
<s-layout title="选择自提门店" :bgStyle="{ color: '#FFF' }">
<view class="storeBox" ref="container">
<view
class="storeBox-box"
v-for="(item, index) in state.storeList"
:key="index"
@tap="checked(item)"
>
<view class="store-img">
<image :src="item.logo" class="img" />
</view>
<view class="store-cent-left">
<view class="store-name">{{ item.name }}</view>
<view class="store-address line1">
{{ item.areaName }}{{ ', ' + item.detailAddress }}
</view>
</view>
<view class="row-right ss-flex-col ss-col-center">
<view>
<!-- #ifdef H5 -->
<a class="store-phone" :href="'tel:' + item.phone">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</a>
<!-- #endif -->
<!-- #ifdef MP -->
<view class="store-phone" @click="call(item.phone)">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
<!-- #endif -->
</view>
<view class="store-distance ss-flex ss-row-center" @tap.stop="showMaoLocation(item)">
<text class="addressTxt" v-if="item.distance">
距离{{ item.distance.toFixed(2) }}千米
</text>
<text class="addressTxt" v-else>查看地图</text>
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
</view>
</view>
</view>
</s-layout>
</template>
<script setup>
import DeliveryApi from '@/sheep/api/trade/delivery';
import { onMounted, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
const LONGITUDE = 'user_longitude';
const LATITUDE = 'user_latitude';
const state = reactive({
loaded: false,
loading: false,
storeList: [],
system_store: {},
locationShow: false,
user_latitude: 0,
user_longitude: 0,
});
const call = (phone) => {
uni.makePhoneCall({
phoneNumber: phone,
});
};
const selfLocation = () => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.getLocation((res) => {
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
getList();
});
} else {
// #endif
uni.getLocation({
type: 'gcj02',
success: (res) => {
try {
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
} catch (e) {
console.error(e);
}
getList();
},
complete: () => {
getList();
},
});
// #ifdef H5
}
// #endif
};
const showMaoLocation = (e) => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`,
});
} else {
// #endif
uni.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`,
success: function () {
console.log('success');
},
});
// #ifdef H5
}
// #endif
};
/**
* 选中门店
*/
const checked = (addressInfo) => {
uni.$emit('SELECT_PICK_UP_INFO', {
addressInfo,
});
sheep.$router.back();
};
/**
* 获取门店列表数据
*/
const getList = async () => {
if (state.loading || state.loaded) {
return;
}
state.loading = true;
const { data, code } = await DeliveryApi.getDeliveryPickUpStoreList({
latitude: state.user_latitude,
longitude: state.user_longitude,
});
if (code !== 0) {
return;
}
state.loading = false;
state.storeList = data;
};
onMounted(() => {
if (state.user_latitude && state.user_longitude) {
getList();
} else {
selfLocation();
getList();
}
});
onLoad(() => {
try {
state.user_latitude = uni.getStorageSync(LATITUDE);
state.user_longitude = uni.getStorageSync(LONGITUDE);
} catch (e) {
console.error(e);
}
});
</script>
<style lang="scss" scoped>
.line1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.geoPage {
position: fixed;
width: 100%;
height: 100%;
top: 0;
z-index: 10000;
}
.storeBox {
width: 100%;
background-color: #fff;
padding: 0 30rpx;
}
.storeBox-box {
width: 100%;
height: auto;
display: flex;
align-items: center;
padding: 23rpx 0;
justify-content: space-between;
border-bottom: 1px solid #eee;
}
.store-cent {
display: flex;
align-items: center;
width: 80%;
}
.store-cent-left {
//width: 45%;
flex: 2;
}
.store-img {
flex: 1;
width: 120rpx;
height: 120rpx;
border-radius: 6rpx;
margin-right: 22rpx;
}
.store-img .img {
width: 100%;
height: 100%;
}
.store-name {
color: #282828;
font-size: 30rpx;
margin-bottom: 22rpx;
font-weight: 800;
}
.store-address {
color: #666666;
font-size: 24rpx;
}
.store-phone {
width: 50rpx;
height: 50rpx;
color: #fff;
border-radius: 50%;
display: block;
text-align: center;
line-height: 48rpx;
background-color: #e83323;
margin-bottom: 22rpx;
text-decoration: none;
}
.store-distance {
font-size: 22rpx;
color: #e83323;
}
.iconfont {
font-size: 20rpx;
}
.row-right {
flex: 2;
//display: flex;
//flex-direction: column;
//align-items: flex-end;
//width: 33.5%;
}
</style>
+470
View File
@@ -0,0 +1,470 @@
<!-- 用户信息 -->
<template>
<s-layout title="用户信息" class="set-userinfo-wrap">
<uni-forms
:model="state.model"
:rules="state.rules"
labelPosition="left"
border
class="form-box"
>
<!-- 头像 -->
<view class="ss-flex ss-row-center ss-col-center ss-p-t-60 ss-p-b-0 bg-white">
<view class="header-box-content">
<su-image
class="content-img"
isPreview
:current="0"
:src="state.model?.avatar"
:height="160"
:width="160"
:radius="80"
mode="scaleToFill"
/>
<view class="avatar-action">
<!-- #ifdef MP -->
<button
class="ss-reset-button avatar-action-btn"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
修改
</button>
<!-- #endif -->
<!-- #ifndef MP -->
<button class="ss-reset-button avatar-action-btn" @tap="onChangeAvatar">修改</button>
<!-- #endif -->
</view>
</view>
</view>
<view class="bg-white ss-p-x-30">
<!-- 昵称 + 性别 -->
<uni-forms-item name="nickname" label="昵称">
<uni-easyinput
v-model="state.model.nickname"
type="nickname"
placeholder="设置昵称"
:inputBorder="false"
:placeholderStyle="placeholderStyle"
/>
</uni-forms-item>
<uni-forms-item name="sex" label="性别">
<view class="ss-flex ss-col-center ss-h-100">
<radio-group @change="onChangeGender" class="ss-flex ss-col-center">
<label class="radio" v-for="item in sexRadioMap" :key="item.value">
<view class="ss-flex ss-col-center ss-m-r-32">
<radio
:value="item.value"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
:checked="parseInt(item.value) === state.model?.sex"
/>
<view class="gender-name">{{ item.name }}</view>
</view>
</label>
</radio-group>
</view>
</uni-forms-item>
<uni-forms-item name="mobile" label="手机号" @tap="onChangeMobile">
<uni-easyinput
v-model="userInfo.mobile"
placeholder="请绑定手机号"
:inputBorder="false"
disabled
:styles="{ disableColor: '#fff' }"
:placeholderStyle="placeholderStyle"
:clearable="false"
>
<template v-slot:right>
<view class="ss-flex ss-col-center">
<su-radio v-if="userInfo.verification?.mobile" :modelValue="true" />
<button v-else class="ss-reset-button ss-flex ss-col-center ss-row-center">
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx"></text>
</button>
</view>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="password" label="登录密码" @tap="onSetPassword">
<uni-easyinput
v-model="userInfo.password"
placeholder="点击修改登录密码"
:inputBorder="false"
:styles="{ disableColor: '#fff' }"
disabled
placeholderStyle="color:#BBBBBB;font-size:28rpx;line-height:normal"
:clearable="false"
>
<template v-slot:right>
<view class="ss-flex ss-col-center">
<su-radio
class="ss-flex"
v-if="userInfo.verification?.password"
:modelValue="true"
/>
<button v-else class="ss-reset-button ss-flex ss-col-center ss-row-center">
<text class="_icon-forward" style="color: #bbbbbb; font-size: 26rpx" />
</button>
</view>
</template>
</uni-easyinput>
</uni-forms-item>
</view>
<view class="bg-white ss-m-t-14">
<uni-list>
<uni-list-item
clickable
@tap="sheep.$router.go('/pages/user/address/list')"
title="地址管理"
showArrow
:border="false"
class="list-border"
/>
</uni-list>
</view>
</uni-forms>
<!-- 当前社交平台的绑定关系只处理 wechat 微信场景 -->
<view v-if="sheep.$platform.name !== 'H5'">
<view class="title-box ss-p-l-30">第三方账号绑定</view>
<view class="account-list ss-flex ss-row-between">
<view v-if="'WechatOfficialAccount' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/WechatOfficialAccount.png')"
/>
<text class="list-name">微信公众号</text>
</view>
<view v-if="'WechatMiniProgram' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/WechatMiniProgram.png')"
/>
<text class="list-name">微信小程序</text>
</view>
<view v-if="'App' === sheep.$platform.name" class="ss-flex ss-col-center">
<image
class="list-img"
:src="sheep.$url.static('/static/img/shop/platform/wechat.png')"
/>
<text class="list-name">微信开放平台</text>
</view>
<view class="ss-flex ss-col-center">
<view class="info ss-flex ss-col-center" v-if="state.thirdInfo">
<image class="avatar ss-m-r-20" :src="sheep.$url.cdn(state.thirdInfo.avatar)" />
<text class="name">{{ state.thirdInfo.nickname }}</text>
</view>
<view class="bind-box ss-m-l-20">
<button
v-if="state.thirdInfo.openid"
class="ss-reset-button relieve-btn"
@tap="unBindThirdOauth"
>
解绑
</button>
<button v-else class="ss-reset-button bind-btn" @tap="bindThirdOauth">绑定</button>
</view>
</view>
</view>
</view>
<su-fixed bottom placeholder bg="none">
<view class="footer-box ss-p-20">
<button class="ss-rest-button logout-btn ui-Shadow-Main" @tap="onSubmit">保存</button>
</view>
</su-fixed>
</s-layout>
</template>
<script setup>
import { computed, reactive, onBeforeMount } from 'vue';
import sheep from '@/sheep';
import { clone } from 'lodash-es';
import { showAuthModal } from '@/sheep/hooks/useModal';
import FileApi from '@/sheep/api/infra/file';
import UserApi from '@/sheep/api/member/user';
const state = reactive({
model: {}, // 个人信息
rules: {},
thirdInfo: {}, // 社交用户的信息
});
const placeholderStyle = 'color:#BBBBBB;font-size:28rpx;line-height:normal';
const sexRadioMap = [
{
name: '男',
value: '1',
},
{
name: '女',
value: '2',
},
];
const userInfo = computed(() => sheep.$store('user').userInfo);
// 选择性别
function onChangeGender(e) {
state.model.sex = e.detail.value;
}
// 修改手机号
const onChangeMobile = () => {
showAuthModal('changeMobile');
};
// 选择微信的头像,进行上传
function onChooseAvatar(e) {
const tempUrl = e.detail.avatarUrl || '';
uploadAvatar(tempUrl);
}
// 手动选择头像,进行上传
function onChangeAvatar() {
uni.chooseImage({
success: async (chooseImageRes) => {
const tempUrl = chooseImageRes.tempFilePaths[0];
await uploadAvatar(tempUrl);
},
});
}
// 上传头像文件
async function uploadAvatar(tempUrl) {
if (!tempUrl) {
return;
}
let { data } = await FileApi.uploadFile(tempUrl);
state.model.avatar = data;
}
// 修改密码
function onSetPassword() {
showAuthModal('changePassword');
}
// 绑定第三方账号
async function bindThirdOauth() {
let result = await sheep.$platform.useProvider('wechat').bind();
if (result) {
await getUserInfo();
}
}
// 解绑第三方账号
function unBindThirdOauth() {
uni.showModal({
title: '解绑提醒',
content: '解绑后您将无法通过微信登录此账号',
cancelText: '再想想',
confirmText: '确定',
success: async function (res) {
if (!res.confirm) {
return;
}
const result = await sheep.$platform.useProvider('wechat').unbind(state.thirdInfo.openid);
if (result) {
await getUserInfo();
}
},
});
}
// 保存信息
async function onSubmit() {
const { code } = await UserApi.updateUser({
avatar: state.model.avatar,
nickname: state.model.nickname,
sex: state.model.sex,
});
if (code === 0) {
await getUserInfo();
}
}
// 获得用户信息
const getUserInfo = async () => {
// 个人信息
const userInfo = await sheep.$store('user').getInfo();
state.model = clone(userInfo);
// 获得社交用户的信息
if (sheep.$platform.name !== 'H5') {
const result = await sheep.$platform.useProvider('wechat').getInfo();
state.thirdInfo = result || {};
}
};
onBeforeMount(() => {
getUserInfo();
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-file-picker {
border-radius: 50%;
}
.uni-file-picker__container {
margin: -14rpx -12rpx;
}
.file-picker__progress {
height: 0 !important;
}
.uni-list-item__content-title {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
.uni-icons {
font-size: 40rpx !important;
}
.is-disabled {
color: #333333;
}
}
:deep(.disabled) {
opacity: 1;
}
.gender-name {
font-size: 28rpx;
font-weight: 500;
line-height: normal;
color: #333333;
}
.title-box {
font-size: 28rpx;
font-weight: 500;
color: #666666;
line-height: 100rpx;
}
.logout-btn {
width: 710rpx;
height: 80rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: $white;
}
.radio-dark {
filter: grayscale(100%);
filter: gray;
opacity: 0.4;
}
.content-img {
border-radius: 50%;
}
.header-box-content {
position: relative;
width: 160rpx;
height: 160rpx;
overflow: hidden;
border-radius: 50%;
}
.avatar-action {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
z-index: 1;
width: 160rpx;
height: 46rpx;
background: rgba(#000000, 0.3);
.avatar-action-btn {
width: 160rpx;
height: 46rpx;
font-weight: 500;
font-size: 24rpx;
color: #ffffff;
}
}
// 绑定项
.account-list {
background-color: $white;
height: 100rpx;
padding: 0 20rpx;
.list-img {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.list-name {
font-size: 28rpx;
color: #333333;
}
.info {
.avatar {
width: 38rpx;
height: 38rpx;
border-radius: 50%;
overflow: hidden;
}
.name {
font-size: 28rpx;
font-weight: 400;
color: $dark-9;
}
}
.bind-box {
width: 100rpx;
height: 50rpx;
line-height: normal;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
.bind-btn {
width: 100%;
height: 100%;
border-radius: 25rpx;
background: #f4f4f4;
color: #999999;
}
.relieve-btn {
width: 100%;
height: 100%;
border-radius: 25rpx;
background: var(--ui-BG-Main-opacity-1);
color: var(--ui-BG-Main);
}
}
}
.list-border {
font-size: 28rpx;
font-weight: 400;
color: #333333;
border-bottom: 2rpx solid #eeeeee;
}
image {
width: 100%;
height: 100%;
}
</style>
+379
View File
@@ -0,0 +1,379 @@
<!-- 我的钱包 -->
<template>
<s-layout class="wallet-wrap" title="钱包">
<!-- 钱包卡片 -->
<view class="header-box ss-flex ss-row-center ss-col-center">
<view class="card-box ui-BG-Main ui-Shadow-Main">
<view class="card-head ss-flex ss-col-center">
<view class="card-title ss-m-r-10">钱包余额</view>
<view
@tap="state.showMoney = !state.showMoney"
class="ss-eye-icon"
:class="state.showMoney ? 'cicon-eye' : 'cicon-eye-off'"
/>
</view>
<view class="ss-flex ss-row-between ss-col-center ss-m-t-64">
<view class="money-num">{{
state.showMoney ? fen2yuan(userWallet.balance) : '*****'
}}</view>
<button class="ss-reset-button topup-btn" @tap="sheep.$router.go('/pages/pay/recharge')">
充值
</button>
</view>
</view>
</view>
<su-sticky>
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker
v-model="state.data"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
<view class="total-box">
<view class="ss-m-b-10">总收入{{ fen2yuan(state.summary.totalIncome) }}</view>
<view>总支出{{ fen2yuan(state.summary.totalExpense) }}</view>
</view>
</view>
<su-tabs
:list="tabMaps"
@change="onChange"
:scrollable="false"
:current="state.currentTab"
></su-tabs>
</su-sticky>
<s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" />
<!-- 钱包记录 -->
<view v-if="state.pagination.total > 0">
<view
class="wallet-list ss-flex border-bottom"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="list-content">
<view class="title-box ss-flex ss-row-between ss-m-b-20">
<text class="title ss-line-1">
{{ item.title }}
</text>
<view class="money">
<text v-if="item.price >= 0" class="add">+{{ fen2yuan(item.price) }}</text>
<text v-else class="minus">{{ fen2yuan(item.price) }}</text>
</view>
</view>
<text class="time">
{{ sheep.$helper.timeFormat(state.createTime, 'yyyy-mm-dd hh:MM:ss') }}
</text>
</view>
</view>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
/>
</s-layout>
</template>
<script setup>
import { computed, reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import dayjs from 'dayjs';
import _ from 'lodash-es';
import PayWalletApi from '@/sheep/api/pay/wallet';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import { resetPagination } from '@/sheep/util';
const headerBg = sheep.$url.css('/static/img/shop/user/wallet_card_bg.png');
// 数据
const state = reactive({
showMoney: false,
date: [], // 筛选的时间段
currentTab: 0,
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 8,
},
summary: {
totalIncome: 0,
totalExpense: 0,
},
loadStatus: '',
today: '',
});
const tabMaps = [
{
name: '全部',
value: '',
},
{
name: '收入',
value: '1',
},
{
name: '支出',
value: '2',
},
];
const userWallet = computed(() => sheep.$store('user').userWallet);
// 格式化时间段
const dateFilterText = computed(() => {
if (state.date[0] === state.date[1]) {
return state.date[0];
} else {
return state.date.join('~');
}
});
// 获得钱包记录分页
async function getLogList() {
state.loadStatus = 'loading';
const { data, code } = await PayWalletApi.getWalletTransactionPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
type: tabMaps[state.currentTab].value,
'createTime[0]': state.date[0] + ' 00:00:00',
'createTime[1]': state.date[1] + ' 23:59:59',
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
// 获得钱包统计
async function getSummary() {
const { data, code } = await PayWalletApi.getWalletTransactionSummary({
createTime: [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'],
});
if (code !== 0) {
return;
}
state.summary = data;
}
onLoad(() => {
state.today = dayjs().format('YYYY-MM-DD');
state.date = [state.today, state.today];
getLogList();
getSummary();
// 刷新钱包的缓存
sheep.$store('user').getWallet();
});
// 处理 tab 切换
function onChange(e) {
state.currentTab = e.index;
// 重新加载列表
resetPagination(state.pagination);
getLogList();
getSummary();
}
// 处理时间筛选
function onChangeTime(e) {
state.date[0] = e[0];
state.date[1] = e[e.length - 1];
// 重新加载列表
resetPagination(state.pagination);
getLogList();
getSummary();
}
onReachBottom(() => {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getLogList();
});
</script>
<style lang="scss" scoped>
// 钱包
.header-box {
background-color: $white;
padding: 30rpx;
.card-box {
width: 100%;
min-height: 300rpx;
padding: 40rpx;
background-size: 100% 100%;
border-radius: 30rpx;
overflow: hidden;
position: relative;
z-index: 1;
box-sizing: border-box;
&::after {
content: '';
display: block;
width: 100%;
height: 100%;
z-index: 2;
position: absolute;
top: 0;
left: 0;
background: v-bind(headerBg) no-repeat;
pointer-events: none;
}
.card-head {
color: $white;
font-size: 30rpx;
}
.ss-eye-icon {
font-size: 40rpx;
color: $white;
}
.money-num {
font-size: 70rpx;
line-height: 70rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.reduce-num {
font-size: 26rpx;
font-weight: 400;
color: $white;
}
.topup-btn {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30px;
font-size: 26rpx;
font-weight: 500;
background-color: $white;
color: var(--ui-BG-Main);
}
}
}
// 筛选
.filter-box {
height: 114rpx;
background-color: $bg-page;
.total-box {
font-size: 24rpx;
font-weight: 500;
color: $dark-9;
}
.date-btn {
background-color: $white;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: $dark-6;
.ss-seldate-icon {
font-size: 50rpx;
color: $dark-9;
}
}
}
.tabs-box {
background: $white;
border-bottom: 2rpx solid #eeeeee;
}
// tab
.wallet-tab-card {
.tab-item {
height: 80rpx;
position: relative;
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 2rpx;
background-color: var(--ui-BG-Main);
}
}
}
// 钱包记录
.wallet-list {
padding: 30rpx;
background-color: #ffff;
.head-img {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
background: $gray-c;
}
.list-content {
justify-content: space-between;
align-items: flex-start;
flex: 1;
.title {
font-size: 28rpx;
color: $dark-3;
width: 400rpx;
}
.time {
color: $gray-c;
font-size: 22rpx;
}
}
.money {
font-size: 28rpx;
font-weight: bold;
font-family: OPPOSANS;
.add {
color: var(--ui-BG-Main);
}
.minus {
color: $dark-3;
}
}
}
</style>
+277
View File
@@ -0,0 +1,277 @@
<!-- 我的积分 -->
<template>
<s-layout class="wallet-wrap" title="我的积分" navbar="inner">
<view
class="header-box ss-flex ss-flex-col ss-row-center ss-col-center"
:style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 88) + 'rpx',
},
]"
>
<view class="header-bg">
<view class="bg" />
</view>
<view class="score-box ss-flex-col ss-row-center ss-col-center">
<view class="ss-m-b-30">
<text class="all-title ss-m-r-8">当前积分</text>
</view>
<text class="all-num">{{ userInfo.point || 0 }}</text>
</view>
</view>
<!-- tab -->
<su-sticky :customNavHeight="sys_navBar">
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker
v-model="state.date"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
</view>
<su-tabs
:list="tabMaps"
@change="onChange"
:scrollable="false"
:current="state.currentTab"
></su-tabs>
</su-sticky>
<!-- list -->
<view class="list-box">
<view v-if="state.pagination.total > 0">
<view
class="list-item ss-flex ss-col-center ss-row-between"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="ss-flex-col">
<view class="name"
>{{ item.title }}{{ item.description ? ' - ' + item.description : '' }}</view
>
<view class="time">{{
sheep.$helper.timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss')
}}</view>
</view>
<view class="add" v-if="item.point > 0">+{{ item.point }}</view>
<view class="minus" v-else>{{ item.point }}</view>
</view>
</view>
<s-empty v-else text="暂无数据" icon="/static/data-empty.png" />
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="onLoadMore"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import _ from 'lodash-es';
import dayjs from 'dayjs';
import PointApi from '@/sheep/api/member/point';
import { resetPagination } from '@/sheep/util';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const userInfo = computed(() => sheep.$store('user').userInfo);
const sys_navBar = sheep.$platform.navbar;
const state = reactive({
currentTab: 0,
pagination: {
list: 0,
total: 0,
pageSize: 6,
pageNo: 1,
},
loadStatus: '',
date: [],
today: '',
});
const tabMaps = [
{
name: '全部',
value: 'all',
},
{
name: '收入',
value: 'true',
},
{
name: '支出',
value: 'false',
},
];
const dateFilterText = computed(() => {
if (state.date[0] === state.date[1]) {
return state.date[0];
} else {
return state.date.join('~');
}
});
async function getLogList() {
state.loadStatus = 'loading';
let { code, data } = await PointApi.getPointRecordPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
addStatus: state.currentTab > 0 ? tabMaps[state.currentTab].value : undefined,
'createTime[0]': state.date[0] + ' 00:00:00',
'createTime[1]': state.date[1] + ' 23:59:59',
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
onLoad(() => {
state.today = dayjs().format('YYYY-MM-DD');
state.date = [state.today, state.today];
getLogList();
});
function onChange(e) {
state.currentTab = e.index;
resetPagination(state.pagination);
getLogList();
}
function onChangeTime(e) {
state.date[0] = e[0];
state.date[1] = e[e.length - 1];
resetPagination(state.pagination);
getLogList();
}
function onLoadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getLogList();
}
onReachBottom(() => {
onLoadMore();
});
</script>
<style lang="scss" scoped>
.header-box {
width: 100%;
background: linear-gradient(180deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%)
no-repeat;
background-size: 750rpx 100%;
padding: 0 0 120rpx 0;
box-sizing: border-box;
.score-box {
height: 100%;
.all-num {
font-size: 50rpx;
font-weight: bold;
color: #fff;
font-family: OPPOSANS;
}
.all-title {
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.cicon-help-o {
color: #fff;
font-size: 28rpx;
}
}
}
// 筛选
.filter-box {
height: 114rpx;
background-color: $bg-page;
.total-box {
font-size: 24rpx;
font-weight: 500;
color: $dark-9;
}
.date-btn {
background-color: $white;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: $dark-6;
.ss-seldate-icon {
font-size: 50rpx;
color: $dark-9;
}
}
}
.list-box {
.list-item {
background: #fff;
border-bottom: 1rpx solid #dfdfdf;
padding: 30rpx;
.name {
font-size: 28rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
line-height: 28rpx;
margin-bottom: 20rpx;
}
.time {
font-size: 24rpx;
font-weight: 500;
color: rgba(196, 196, 196, 1);
line-height: 24px;
}
.add {
font-size: 30rpx;
font-weight: 500;
color: #e6b873;
}
.minus {
font-size: 30rpx;
font-weight: 500;
color: $dark-3;
}
}
}
</style>