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
+31
View File
@@ -0,0 +1,31 @@
# 版本号
SHOPRO_VERSION=v2.3.0
# 后端接口 - 正式环境(通过 process.env.NODE_ENV 非 development
SHOPRO_BASE_URL=https://kxcharge.kxmall.vip
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development
SHOPRO_DEV_BASE_URL=https://kxcharge.kxmall.vip
### SHOPRO_DEV_BASE_URL=http://localhost:48080
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
SHOPRO_UPLOAD_TYPE=server
# 后端接口前缀(一般不建议调整)
SHOPRO_API_PATH=/app-api
# 后端 websocket 接口前缀
SHOPRO_WEBSOCKET_PATH=/infra/ws
# 开发环境运行端口
SHOPRO_DEV_PORT=3000
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn
### SHOPRO_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
SHOPRO_MPLIVE_ON=0
# 租户ID 默认 1
SHOPRO_TENANT_ID=1
+11
View File
@@ -0,0 +1,11 @@
unpackage/*
node_modules/*
.idea/*
deploy.sh
.hbuilderx/
.vscode/
**/.DS_Store
yarn.lock
package-lock.json
*.keystore
pnpm-lock.yaml
+6
View File
@@ -0,0 +1,6 @@
/unpackage/*
/node_modules/**
/uni_modules/**
/public/*
**/*.svg
**/*.sh
+10
View File
@@ -0,0 +1,10 @@
{
"printWidth": 100,
"semi": true,
"vueIndentScriptAndStyle": true,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"htmlWhitespaceSensitivity": "strict",
"endOfLine": "auto"
}
+41
View File
@@ -0,0 +1,41 @@
<script setup>
import {
onLaunch,
onShow,
onError
} from '@dcloudio/uni-app';
import {
ShoproInit
} from './sheep';
onLaunch(() => {
// 隐藏原生导航栏 使用自定义底部导航
uni.hideTabBar();
});
onError((err) => {
console.log('AppOnError:', err);
});
onShow((options) => {
// #ifdef APP-PLUS
// 获取urlSchemes参数
const args = plus.runtime.arguments;
if (args) {}
// 获取剪贴板
uni.getClipboardData({
success: (res) => {},
});
// #endif
// #ifdef MP-WEIXIN
// 确认收货回调结果
// console.log(options, 'options');
// #endif
});
</script>
<style lang="scss">
@import '@/sheep/scss/index.scss';
</style>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 lidongtony
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+56
View File
@@ -0,0 +1,56 @@
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 🐶 新手必读
* 演示地址:<https://doc.iocoder.cn/mall-preview/1>
* 启动文档:<https://doc.iocoder.cn/quick-start/>
* 视频教程:<https://doc.iocoder.cn/video/>
## 🐯 商城简介
**芋道商城**,基于 [芋道开发平台](https://github.com/YunaiV/ruoyi-vue-pro) 构建,以开发者为中心,打造中国第一流的 Java 开源商城系统,全部开源,个人与企业可 100% 免费使用。
> 有任何问题,或者想要的功能,可以在 Issues 中提给艿艿。
>
> 😜 给项目点点 Star 吧,这对我们真的很重要!
![功能图](/.image/common/mall-feature.png)
* 基于 uni-app + Vue3 开发,支持微信小程序、微信公众号、H5 移动端,未来会支持支付宝小程序、抖音小程序等
* 支持 SaaS 多租户,可满足商品、订单、支付、会员、优惠券、秒杀、拼团、砍价、分销、积分等多种经营需求
## 🔥 后端架构
支持 Spring Boot、Spring Cloud 两种架构:
① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro>
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud>
![架构图](/.image/common/yudao-cloud-architecture.png)
## 🐱 移动端预览
![移动端预览](/.image/common/mall-preview.png)
## 🐶 管理端预览
![店铺装修](/.image/mall/店铺装修.png)
![会员详情](/.image/mall/会员详情.png)
![商品详情](/.image/mall/商品详情.png)
![订单详情](/.image/mall/订单详情.png)
![营销中心](/.image/mall/营销中心.png)
+3
View File
@@ -0,0 +1,3 @@
{
"prompt" : "template"
}
+17
View File
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
}
}
+19
View File
@@ -0,0 +1,19 @@
import App from './App';
import {
createSSRApp
} from 'vue';
import {
setupPinia
} from './sheep/store';
export function createApp() {
const app = createSSRApp(App);
setupPinia(app);
return {
app,
};
}
+264
View File
@@ -0,0 +1,264 @@
{
"name": "航兴充电桩",
"appid": "__UNI__4E88AD3",
"description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。",
"versionName": "2.1.0",
"versionCode": "183",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueCompiler": "uni-app",
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"nvueLaunchMode": "fast",
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"safearea": {
"bottom": {
"offset": "none"
}
},
"modules": {
"Payment": {},
"Share": {},
"VideoPlayer": {},
"OAuth": {},
"Maps": {}
},
"distribute": {
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
],
"minSdkVersion": 21,
"schemes": "shopro"
},
"ios": {
"urlschemewhitelist": [
"baidumap",
"iosamap"
],
"dSYMs": false,
"privacyDescription": {
"NSPhotoLibraryUsageDescription": "需要同意访问您的相册选取图片才能完善该条目",
"NSPhotoLibraryAddUsageDescription": "需要同意访问您的相册才能保存该图片",
"NSCameraUsageDescription": "需要同意访问您的摄像头拍摄照片才能完善该条目",
"NSUserTrackingUsageDescription": "开启追踪并不会获取您在其它站点的隐私信息,该行为仅用于标识设备,保障服务安全和提升浏览体验"
},
"urltypes": "shopro",
"capabilities": {
"entitlements": {
"com.apple.developer.associated-domains": [
"applinks:shopro.sheepjs.com"
]
}
},
"idfa": true
},
"sdkConfigs": {
"speech": {
"ifly": {}
},
"ad": {},
"oauth": {
"apple": {},
"weixin": {
"appid": "wxae7a0c156da9383b",
"UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
}
},
"payment": {
"weixin": {
"__platform__": [
"ios",
"android"
],
"appid": "wxae7a0c156da9383b",
"UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
},
"alipay": {
"__platform__": [
"ios",
"android"
]
}
},
"share": {
"weixin": {
"appid": "wxae7a0c156da9383b",
"UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
}
},
"geolocation": {
"system": {
"__platform__": [
"ios",
"android"
]
}
},
"maps": {
"tencent": {
"key": "MYKBZ-4HJ3C-7DP2I-AQZUL-RQRY3-NBFCU"
}
}
},
"orientation": [
"portrait-primary"
],
"splashscreen": {
"androidStyle": "common",
"iosStyle": "common",
"useOriginalMsgbox": true
},
"icons": {
"android": {
"hdpi": "unpackage/res/icons/72x72.png",
"xhdpi": "unpackage/res/icons/96x96.png",
"xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi": "unpackage/res/icons/192x192.png"
},
"ios": {
"appstore": "unpackage/res/icons/1024x1024.png",
"ipad": {
"app": "unpackage/res/icons/76x76.png",
"app@2x": "unpackage/res/icons/152x152.png",
"notification": "unpackage/res/icons/20x20.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x": "unpackage/res/icons/167x167.png",
"settings": "unpackage/res/icons/29x29.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x": "unpackage/res/icons/80x80.png"
},
"iphone": {
"app@2x": "unpackage/res/icons/120x120.png",
"app@3x": "unpackage/res/icons/180x180.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x": "unpackage/res/icons/120x120.png"
}
}
}
}
},
"quickapp": {},
"quickapp-native": {
"icon": "/static/logo.png",
"package": "com.example.demo",
"features": [
{
"name": "system.clipboard"
}
]
},
"quickapp-webview": {
"icon": "/static/logo.png",
"package": "com.example.demo",
"minPlatformVersion": 1070,
"versionName": "1.0.0",
"versionCode": 100
},
"mp-weixin": {
"appid": "wxe4fb0fff95f81d2e",
"setting": {
"urlCheck": false,
"minified": true,
"postcss": false,
"es6": false
},
"optimization": {
"subPackages": true
},
"plugins": {},
"lazyCodeLoading": "requiredComponents",
"usingComponents": {},
"permission": {
"scope.userLocation": {
"desc": "获取位置信息"
}
},
"requiredPrivateInfos": [
"chooseAddress",
"chooseLocation",
"getLocation"
]
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"mp-jd": {
"usingComponents": true
},
"h5": {
"template": "index.html",
"router": {
"mode": "history",
"base": "/"
},
"sdkConfigs": {
"maps": {
"qqmap": {
"key": "YNQBZ-TFA34-JLEUL-DWDYN-FWPOK-2ABYX"
}
}
},
"async": {
"timeout": 20000
},
"title": "航兴充电桩",
"optimization": {
"treeShaking": {
"enable": true
}
}
},
"vueVersion": "3",
"_spaceID": "192b4892-5452-4e1d-9f09-eee1ece40639",
"locale": "zh-Hans",
"fallbackLocale": "zh-Hans"
}
+105
View File
@@ -0,0 +1,105 @@
{
"id": "shopro",
"name": "shopro",
"displayName": "芋道商城",
"version": "2.3.0",
"description": "芋道商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能",
"scripts": {
"prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
},
"repository": "https://github.com/sheepjs/shop.git",
"keywords": [
"商城",
"B2C",
"商城模板"
],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/sheepjs/shop/issues"
},
"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
"dcloudext": {
"category": [
"前端页面模板",
"uni-app前端项目模板"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "u",
"vue3": "y"
}
}
}
},
"dependencies": {
"dayjs": "^1.11.7",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"luch-request": "^3.0.8",
"moment": "^2.30.1",
"pinia": "^2.0.33",
"pinia-plugin-persist-uni": "^1.2.0",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {
"prettier": "^2.8.7",
"unplugin-auto-import": "^0.18.3",
"vconsole": "^3.15.0"
}
}
+289
View File
@@ -0,0 +1,289 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^s-(.*)": "@/sheep/components/s-$1/s-$1.vue",
"^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue",
"^kx-(.*)": "@/sheep/components/kx-$1/index.vue"
}
},
"pages": [{
"path": "pages/redirect/index",
"style": {
"navigationBarTitleText": ""
}
}, {
"path": "pages/index/index",
"aliasPath": "/",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"sync": true,
"title": "首页"
}
},
{
"path": "pages/order/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/my/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/station-detail/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/equipment-list/index",
"style": {
"navigationBarTitleText": "电站详情"
}
},
{
"path": "pages/price-details/price-details",
"style": {
"navigationBarTitleText": "电价详情"
}
},
{
"path": "pages/equipment-detail/index",
"style": {
"navigationBarTitleText": "开始充电"
}
},
{
"path": "pages/charging/index",
"style": {
"navigationBarTitleText": "正在充电"
}
},
{
"path": "pages/my-car/my-car",
"style": {
"navigationBarTitleText": "我的车辆"
}
},
{
"path": "pages/my-car/add-my-car/add-my-car",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/order-info/order-info",
"style": {
"navigationBarTitleText": "订单详情"
}
}
],
"subPackages": [{
"root": "pages/public",
"pages": [{
"path": "setting",
"style": {
"navigationBarTitleText": "系统设置"
},
"meta": {
"sync": true,
"title": "系统设置",
"group": "通用"
}
},
{
"path": "richtext",
"style": {
"navigationBarTitleText": "富文本"
},
"meta": {
"sync": true,
"title": "富文本",
"group": "通用"
}
},
{
"path": "faq",
"style": {
"navigationBarTitleText": "常见问题"
},
"meta": {
"sync": true,
"title": "常见问题",
"group": "通用"
}
},
{
"path": "error",
"style": {
"navigationBarTitleText": "错误页面"
}
},
{
"path": "webview",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "association",
"style": {
"navigationBarTitleText": "合作联系"
},
"meta": {
"sync": true,
"title": "合作联系",
"group": "通用"
}
}
]
}, {
"root": "pages/user",
"pages": [{
"path": "info",
"style": {
"navigationBarTitleText": "我的信息"
},
"meta": {
"auth": true,
"sync": true,
"title": "用户信息",
"group": "用户中心"
}
},
{
"path": "goods-collect",
"style": {
"navigationBarTitleText": "我的收藏"
},
"meta": {
"auth": true,
"sync": true,
"title": "商品收藏",
"group": "用户中心"
}
},
{
"path": "goods-log",
"style": {
"navigationBarTitleText": "我的足迹"
},
"meta": {
"auth": true,
"sync": true,
"title": "浏览记录",
"group": "用户中心"
}
},
{
"path": "goods_details_store/index",
"style": {
"navigationBarTitleText": "自提门店"
},
"meta": {
"auth": true,
"sync": true,
"title": "地址管理",
"group": "用户中心"
}
},
{
"path": "wallet/money",
"style": {
"navigationBarTitleText": "我的余额"
},
"meta": {
"auth": true,
"sync": true,
"title": "用户余额",
"group": "用户中心"
}
},
{
"path": "wallet/score",
"style": {
"navigationBarTitleText": "我的积分"
},
"meta": {
"auth": true,
"sync": true,
"title": "用户积分",
"group": "用户中心"
}
}
]
}, {
"root": "pages/pay",
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "收银台"
}
},
{
"path": "result",
"style": {
"navigationBarTitleText": "支付结果"
}
},
{
"path": "recharge",
"style": {
"navigationBarTitleText": "充值余额"
},
"meta": {
"auth": true,
"sync": true,
"title": "充值余额",
"group": "支付"
}
},
{
"path": "recharge-log",
"style": {
"navigationBarTitleText": "充值记录"
},
"meta": {
"auth": true,
"sync": true,
"title": "充值记录",
"group": "支付"
}
}
]
}],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "航兴充电桩",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF",
"navigationStyle": "custom"
},
"tabBar": {
"color": "#444",
"selectedColor": "#037AFB",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/icons/tab-bar/16480@2x.png",
"selectedIconPath": "static/icons/tab-bar/16481@2x.png",
"text": "首页"
}, {
"pagePath": "pages/redirect/index",
"iconPath": "static/icons/tab-bar/16570@2x.png"
}, {
"pagePath": "pages/my/index",
"iconPath": "static/icons/tab-bar/16136@2x.png",
"selectedIconPath": "static/icons/tab-bar/16479@2x.png",
"text": "我的"
}]
}
}
+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>
+72
View File
@@ -0,0 +1,72 @@
import request from '@/sheep/request';
const BASE_URL = 'member/'
export function getCarouselList() {
return request({
url: BASE_URL + 'carousel/list'
})
}
export function getUserInfo() {
return request({
url: BASE_URL + 'user/get'
})
}
export function getMyCar() {
return request({
url: BASE_URL + 'plate/listAll'
})
}
export function setDefaultCar(id, isDefault) {
return request({
url: BASE_URL + 'plate/updateDefault',
method: 'put',
data: {
id,
isDefault
}
})
}
// 创建车辆
export function createCar(data) {
return request({
url: BASE_URL + 'plate/create',
method: 'post',
data
})
}
// 编辑车辆
export function editCar(data) {
return request({
url: BASE_URL + 'plate/update',
method: 'post',
data
})
}
// 删除车辆
export function delCar(id) {
return request({
url: BASE_URL + 'plate/delete?id=' + id,
method: 'delete'
})
}
export function createFeedback(data) {
return request({
url: BASE_URL + 'feedback/create',
method: 'post',
data
})
}
export function getFeedbackList() {
return request({
url: BASE_URL + 'feedback/listAll',
})
}
+68
View File
@@ -0,0 +1,68 @@
import request from '@/sheep/request';
const BASE_URL = 'operations/app/'
export function getStationList(params) {
return request({
url: BASE_URL + 'station/info',
params
})
}
export function getStationDetail({
id,
...params
}) {
return request({
url: BASE_URL + 'station/' + id,
params
})
}
export function createFavorite(data) {
return request({
url: BASE_URL + 'favorite/create',
data,
method: 'post'
})
}
export function deleteFavorite(data) {
return request({
url: BASE_URL + 'favorite/delete',
data,
method: 'delete'
})
}
export function getConnectorInfo(id) {
return request({
url: BASE_URL + 'station/connectorInfo/' + id,
custom: {
showLoading: false
}
})
}
export function getOrder(seqNumber) {
return request({
url: BASE_URL + 'order/' + seqNumber,
custom: {
showLoading: false
}
})
}
export function getFavoritePage(params) {
return request({
url: BASE_URL + 'favorite/page',
params
})
}
export function getChargingPage() {
return request({
url: BASE_URL + 'order/page?pageNo=1&pageSize=10&orderStatus=2'
})
}
+99
View File
@@ -0,0 +1,99 @@
import request from '@/sheep/request';
export function proactiveBilling(params) {
return request({
url: '/order-pile-service/chargeOrder/activeSettlement',
params
})
}
// 获取订单详情
export function getOrderInfo(id) {
return request({
url: `/operations/app/order/${id}`,
custom: {
showLoading: false
}
})
}
// 开启充电
export function startCharge(connectorCode, plateNumber, amountMethod) {
const params = {
connectorCode: connectorCode,
amountMethod: amountMethod
}
if (plateNumber !== '') {
params['plateNumber'] = plateNumber
}
return request({
url: `/operations/app/order/startCharge`,
params
})
}
// 获取订单列表
export function getOrderList(pageLink, orderStatus) {
const params = {
pageNo: pageLink.page,
pageSize: pageLink.pageSize,
}
if (orderStatus !== 'all') {
params['orderStatus'] = orderStatus
}
return request({
url: '/operations/app/order/page',
params
})
}
// 结束充电
export function endCharge(seqNumber) {
return request({
url: '/operations/app/order/endCharge',
params: {
seqNumber
}
})
}
// 订单绑定车牌
export function licensePlateBinding(id, plateNumber) {
const parmas = {
id
}
if (plateNumber != '未设置') {
parmas['plateNumber'] = plateNumber
}
return request({
url: `/operations/app/order/licensePlateBinding`,
params
})
}
// 查询信用分
export function orderaccredit(option) {
const data = {
channelCode: option.channelCode,
openid: option.openid
}
return request({
url: '/pay/order/accredit',
method: 'post',
data
})
}
// 查询总订单数据
export function getTotalOrder() {
return request({
url: '/operations/app/order/totalOrder'
})
}
// 查订单图表数据
export function getOrderReport(startTs, endTs) {
return request({
url: `/operations/app/order/report?startTime=${startTs}&endTime=${endTs}`
})
}
+23
View File
@@ -0,0 +1,23 @@
import request from '@/sheep/request';
const BASE_URL = 'system/'
export function getTenantByWebsite(params) {
return request({
url: BASE_URL + 'tenant/get-by-website',
params
})
}
export function getDictData(params) {
return request({
url: BASE_URL + 'dict-data/type',
params
})
}
export function getDictDataAlone(params) {
return request({
url: BASE_URL + 'dict-data/type?type='+params,
})
}
+66
View File
@@ -0,0 +1,66 @@
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
import request from '@/sheep/request';
const FileApi = {
// 上传文件
uploadFile: (file) => {
// TODO 访问令牌的接入;
const token = uni.getStorageSync('token');
uni.showLoading({
title: '上传中',
});
return new Promise((resolve, reject) => {
uni.uploadFile({
url: baseUrl + apiPath + '/infra/file/upload',
filePath: file,
name: 'file',
header: {
// Accept: 'text/json',
Accept: '*/*',
'tenant-id': tenantId,
// Authorization: 'Bearer test247',
},
success: (uploadFileRes) => {
let result = JSON.parse(uploadFileRes.data);
if (result.error === 1) {
uni.showToast({
icon: 'none',
title: result.msg,
});
} else {
return resolve(result);
}
},
fail: (error) => {
console.log('上传失败:', error);
return resolve(false);
},
complete: () => {
uni.hideLoading();
},
});
});
},
// 获取文件预签名地址
getFilePresignedUrl: (path) => {
return request({
url: '/infra/file/presigned-url',
method: 'GET',
params: {
path,
},
});
},
// 创建文件
createFile: (data) => {
return request({
url: '/infra/file/create', // 请求的 URL
method: 'POST', // 请求方法
data: data, // 要发送的数据
});
},
};
export default FileApi;
+132
View File
@@ -0,0 +1,132 @@
import request from '@/sheep/request';
const AuthUtil = {
// 使用手机 + 密码登录
login: (data) => {
return request({
url: '/member/auth/login',
method: 'POST',
data,
custom: {
showSuccess: true,
loadingMsg: '登录中',
successMsg: '登录成功',
},
});
},
// 使用手机 + 验证码登录
smsLogin: (data) => {
return request({
url: '/member/auth/sms-login',
method: 'POST',
data,
custom: {
showSuccess: true,
loadingMsg: '登录中',
successMsg: '登录成功',
},
});
},
// 发送手机验证码
sendSmsCode: (mobile, scene) => {
return request({
url: '/member/auth/send-sms-code',
method: 'POST',
data: {
mobile,
scene,
},
custom: {
loadingMsg: '发送中',
showSuccess: true,
successMsg: '发送成功',
},
});
},
// 登出系统
logout: () => {
return request({
url: '/member/auth/logout',
method: 'POST',
});
},
// 刷新令牌
refreshToken: (refreshToken) => {
return request({
url: '/member/auth/refresh-token',
method: 'POST',
params: {
refreshToken
},
custom: {
loading: false, // 不用加载中
showError: false, // 不展示错误提示
},
});
},
// 社交授权的跳转
socialAuthRedirect: (type, redirectUri) => {
return request({
url: '/member/auth/social-auth-redirect',
method: 'GET',
params: {
type,
redirectUri,
},
custom: {
showSuccess: true,
loadingMsg: '登陆中',
},
});
},
// 社交快捷登录
socialLogin: (type, code, state) => {
return request({
url: '/member/auth/social-login',
method: 'POST',
data: {
type,
code,
state,
},
custom: {
showSuccess: true,
loadingMsg: '登陆中',
},
});
},
// 微信小程序的一键登录
weixinMiniAppLogin: (phoneCode, loginCode, state) => {
return request({
url: '/member/auth/weixin-mini-app-login',
method: 'POST',
data: {
phoneCode,
loginCode,
state
},
custom: {
showSuccess: true,
loadingMsg: '登陆中',
successMsg: '登录成功',
},
});
},
// 创建微信 JS SDK 初始化所需的签名
createWeixinMpJsapiSignature: (url) => {
return request({
url: '/member/auth/create-weixin-jsapi-signature',
method: 'POST',
params: {
url
},
custom: {
showError: false,
showLoading: false,
},
})
},
//
};
export default AuthUtil;
+19
View File
@@ -0,0 +1,19 @@
import request from '@/sheep/request';
const PointApi = {
// 获得用户积分记录分页
getPointRecordPage: (params) => {
if (params.addStatus === undefined) {
delete params.addStatus
}
const queryString = Object.keys(params)
.map((key) => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/member/point/record/page?${queryString}`,
method: 'GET',
});
}
};
export default PointApi;
+37
View File
@@ -0,0 +1,37 @@
import request from '@/sheep/request';
const SignInApi = {
// 获得签到规则列表
getSignInConfigList: () => {
return request({
url: '/member/sign-in/config/list',
method: 'GET',
});
},
// 获得个人签到统计
getSignInRecordSummary: () => {
return request({
url: '/member/sign-in/record/get-summary',
method: 'GET',
});
},
// 签到
createSignInRecord: () => {
return request({
url: '/member/sign-in/record/create',
method: 'POST',
});
},
// 获得签到记录分页
getSignRecordPage: (params) => {
const queryString = Object.keys(params)
.map((key) => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/member/sign-in/record/page?${queryString}`,
method: 'GET',
});
},
};
export default SignInApi;
+76
View File
@@ -0,0 +1,76 @@
import request from '@/sheep/request';
const SocialApi = {
// 获得社交用户
getSocialUser: (type) => {
return request({
url: '/member/social-user/get',
method: 'GET',
params: {
type
},
custom: {
showLoading: false,
},
});
},
// 社交绑定
socialBind: (type, code, state) => {
return request({
url: '/member/social-user/bind',
method: 'POST',
data: {
type,
code,
state
},
custom: {
custom: {
showSuccess: true,
loadingMsg: '绑定中',
successMsg: '绑定成功',
},
},
});
},
// 社交绑定
socialUnbind: (type, openid) => {
return request({
url: '/member/social-user/unbind',
method: 'DELETE',
data: {
type,
openid
},
custom: {
showLoading: false,
loadingMsg: '解除绑定',
successMsg: '解绑成功',
},
});
},
// 获取订阅消息模板列表
getSubscribeTemplateList: () =>
request({
url: '/member/social-user/get-subscribe-template-list',
method: 'GET',
custom: {
showError: false,
showLoading: false,
},
}),
// 获取微信小程序码
getWxaQrcode: async (path, query) => {
return await request({
url: '/member/social-user/wxa-qrcode',
method: 'POST',
data: {
scene: query,
path,
checkPath: false, // TODO 开发环境暂不检查 path 是否存在
},
});
},
};
export default SocialApi;
+85
View File
@@ -0,0 +1,85 @@
import request from '@/sheep/request';
const UserApi = {
// 获得基本信息
getUserInfo: () => {
return request({
url: '/member/user/get',
method: 'GET',
custom: {
showLoading: false,
auth: true,
},
});
},
// 修改基本信息
updateUser: (data) => {
return request({
url: '/member/user/update',
method: 'PUT',
data,
custom: {
auth: true,
showSuccess: true,
successMsg: '更新成功'
},
});
},
// 修改用户手机
updateUserMobile: (data) => {
return request({
url: '/member/user/update-mobile',
method: 'PUT',
data,
custom: {
loadingMsg: '验证中',
showSuccess: true,
successMsg: '修改成功'
},
});
},
// 基于微信小程序的授权码,修改用户手机
updateUserMobileByWeixin: (code) => {
return request({
url: '/member/user/update-mobile-by-weixin',
method: 'PUT',
data: {
code
},
custom: {
showSuccess: true,
loadingMsg: '获取中',
successMsg: '修改成功'
},
});
},
// 修改密码
updateUserPassword: (data) => {
return request({
url: '/member/user/update-password',
method: 'PUT',
data,
custom: {
loadingMsg: '验证中',
showSuccess: true,
successMsg: '修改成功'
},
});
},
// 重置密码
resetUserPassword: (data) => {
return request({
url: '/member/user/reset-password',
method: 'PUT',
data,
custom: {
loadingMsg: '验证中',
showSuccess: true,
successMsg: '修改成功'
}
});
},
};
export default UserApi;
+21
View File
@@ -0,0 +1,21 @@
import request from '@/sheep/request';
// TODO 小程序直播还不支持
export default {
//小程序直播
mplive: {
getRoomList: (ids) =>
request({
url: 'app/mplive/getRoomList',
method: 'GET',
params: {
ids: ids.join(','),
}
}),
getMpLink: () =>
request({
url: 'app/mplive/getMpLink',
method: 'GET'
}),
},
};
+10
View File
@@ -0,0 +1,10 @@
const files = import.meta.glob('./*.js', { eager: true });
let api = {};
Object.keys(files).forEach((key) => {
api = {
...api,
[key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default,
};
});
export default api;
+18
View File
@@ -0,0 +1,18 @@
import request from '@/sheep/request';
export default {
// 苹果相关
apple: {
// 第三方登录
login: (data) =>
request({
url: 'third/apple/login',
method: 'POST',
data,
custom: {
showSuccess: true,
loadingMsg: '登陆中',
},
}),
},
};
+14
View File
@@ -0,0 +1,14 @@
import request from '@/sheep/request';
const PayChannelApi = {
// 获得指定应用的开启的支付渠道编码列表
getEnableChannelCodeList: (appId) => {
return request({
url: '/pay/channel/get-enable-code-list',
method: 'GET',
params: { appId }
});
},
};
export default PayChannelApi;
+22
View File
@@ -0,0 +1,22 @@
import request from '@/sheep/request';
const PayOrderApi = {
// 获得支付订单
getOrder: (id, sync) => {
return request({
url: '/pay/order/get',
method: 'GET',
params: { id, sync },
});
},
// 提交支付订单
submitOrder: (data) => {
return request({
url: '/pay/order/submit',
method: 'POST',
data,
});
},
};
export default PayOrderApi;
+68
View File
@@ -0,0 +1,68 @@
import request from '@/sheep/request';
const PayWalletApi = {
// 获取钱包
getPayWallet() {
return request({
url: '/pay/wallet/get',
method: 'GET',
custom: {
showLoading: false,
auth: true,
},
});
},
// 获得钱包流水分页
getWalletTransactionPage: (params) => {
const queryString = Object.keys(params)
.map((key) => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/pay/wallet-transaction/page?${queryString}`,
method: 'GET',
});
},
// 获得钱包流水统计
getWalletTransactionSummary: (params) => {
const queryString = `createTime=${params.createTime[0]}&createTime=${params.createTime[1]}`;
return request({
url: `/pay/wallet-transaction/get-summary?${queryString}`,
// url: `/pay/wallet-transaction/get-summary`,
method: 'GET',
// params: params
});
},
// 获得钱包充值套餐列表
getWalletRechargePackageList: () => {
return request({
url: '/pay/wallet-recharge-package/list',
method: 'GET',
custom: {
showError: false,
showLoading: false,
},
});
},
// 创建钱包充值记录(发起充值)
createWalletRecharge: (data) => {
return request({
url: '/pay/wallet-recharge/create',
method: 'POST',
data,
});
},
// 获得钱包充值记录分页
getWalletRechargePage: (params) => {
return request({
url: '/pay/wallet-recharge/page',
method: 'GET',
params,
custom: {
showError: false,
showLoading: false,
},
});
},
};
export default PayWalletApi;
+54
View File
@@ -0,0 +1,54 @@
import request from '@/sheep/request';
const FavoriteApi = {
// 获得商品收藏分页
getFavoritePage: (data) => {
return request({
url: '/product/favorite/page',
method: 'GET',
params: data,
});
},
// 检查是否收藏过商品
isFavoriteExists: (spuId) => {
return request({
url: '/product/favorite/exits',
method: 'GET',
params: {
spuId,
},
});
},
// 添加商品收藏
createFavorite: (spuId) => {
return request({
url: '/product/favorite/create',
method: 'POST',
data: {
spuId,
},
custom: {
auth: true,
showSuccess: true,
successMsg: '收藏成功',
},
});
},
// 取消商品收藏
deleteFavorite: (spuId) => {
return request({
url: '/product/favorite/delete',
method: 'DELETE',
data: {
spuId,
},
custom: {
auth: true,
showSuccess: true,
successMsg: '取消成功',
},
});
},
};
export default FavoriteApi;
+39
View File
@@ -0,0 +1,39 @@
import request from '@/sheep/request';
const SpuHistoryApi = {
// 删除商品浏览记录
deleteBrowseHistory: (spuIds) => {
return request({
url: '/product/browse-history/delete',
method: 'DELETE',
data: { spuIds },
custom: {
showSuccess: true,
successMsg: '删除成功',
},
});
},
// 清空商品浏览记录
cleanBrowseHistory: () => {
return request({
url: '/product/browse-history/clean',
method: 'DELETE',
custom: {
showSuccess: true,
successMsg: '清空成功',
},
});
},
// 获得商品浏览记录分页
getBrowseHistoryPage: (data) => {
return request({
url: '/product/browse-history/page',
method: 'GET',
data,
custom: {
showLoading: false
},
});
},
};
export default SpuHistoryApi;
+53
View File
@@ -0,0 +1,53 @@
import request from '@/sheep/request';
const SpuApi = {
// 获得商品 SPU 列表
getSpuListByIds: (ids) => {
return request({
url: '/product/spu/list-by-ids',
method: 'GET',
params: { ids },
custom: {
showLoading: false,
showError: false,
},
});
},
// 获得商品结算信息
getSettlementProduct: (spuIds) => {
return request({
url: '/trade/order/settlement-product',
method: 'GET',
params: { spuIds },
custom: {
showLoading: false,
showError: false,
},
});
},
// 获得商品 SPU 分页
getSpuPage: (params) => {
return request({
url: '/product/spu/page',
method: 'GET',
params,
custom: {
showLoading: false,
showError: false,
},
});
},
// 查询商品
getSpuDetail: (id) => {
return request({
url: '/product/spu/get-detail',
method: 'GET',
params: { id },
custom: {
showLoading: false,
showError: false,
},
});
},
};
export default SpuApi;
+16
View File
@@ -0,0 +1,16 @@
import request from '@/sheep/request';
const ActivityApi = {
// 获得单个商品,进行中的拼团、秒杀、砍价活动信息
getActivityListBySpuId: (spuId) => {
return request({
url: '/promotion/activity/list-by-spu-id',
method: 'GET',
params: {
spuId,
},
});
},
};
export default ActivityApi;
+12
View File
@@ -0,0 +1,12 @@
import request from '@/sheep/request';
export default {
// 获得文章详情
getArticle: (id, title) => {
return request({
url: '/promotion/article/get',
method: 'GET',
params: { id, title }
});
}
}
+78
View File
@@ -0,0 +1,78 @@
import request from '@/sheep/request';
// 拼团 API
const CombinationApi = {
// 获得拼团活动分页
getCombinationActivityPage: (params) => {
return request({
url: '/promotion/combination-activity/page',
method: 'GET',
params,
});
},
// 获得拼团活动明细
getCombinationActivity: (id) => {
return request({
url: '/promotion/combination-activity/get-detail',
method: 'GET',
params: {
id,
},
});
},
// 获得拼团活动列表,基于活动编号数组
getCombinationActivityListByIds: (ids) => {
return request({
url: '/promotion/combination-activity/list-by-ids',
method: 'GET',
params: {
ids,
},
});
},
// 获得最近 n 条拼团记录(团长发起的)
getHeadCombinationRecordList: (activityId, status, count) => {
return request({
url: '/promotion/combination-record/get-head-list',
method: 'GET',
params: {
activityId,
status,
count,
},
});
},
// 获得我的拼团记录分页
getCombinationRecordPage: (params) => {
return request({
url: '/promotion/combination-record/page',
method: 'GET',
params,
});
},
// 获得拼团记录明细
getCombinationRecordDetail: (id) => {
return request({
url: '/promotion/combination-record/get-detail',
method: 'GET',
params: {
id,
},
});
},
// 获得拼团记录的概要信息
getCombinationRecordSummary: () => {
return request({
url: '/promotion/combination-record/get-summary',
method: 'GET',
});
},
};
export default CombinationApi;
+84
View File
@@ -0,0 +1,84 @@
import request from '@/sheep/request';
const CouponApi = {
// 获得优惠劵模板列表
getCouponTemplateListByIds: (ids) => {
return request({
url: '/operations/coupon-template/list-by-ids',
method: 'GET',
params: { ids },
custom: {
showLoading: false, // 不展示 Loading,避免领取优惠劵时,不成功提示
showError: false,
},
});
},
// 获得优惠劵模版列表
getCouponTemplateList: (spuId, productScope, count) => {
return request({
url: '/operations/coupon-template/list',
method: 'GET',
params: { spuId, productScope, count },
});
},
// 获得优惠劵模版分页
getCouponTemplatePage: (params) => {
return request({
url: '/operations/coupon-template/page',
method: 'GET',
params,
});
},
// 获得优惠劵模版
getCouponTemplate: (id) => {
return request({
url: '/operations/coupon-template/get',
method: 'GET',
params: { id },
});
},
// 我的优惠劵列表
getCouponPage: (params) => {
return request({
url: '/operations/coupon/page',
method: 'GET',
params,
});
},
// 领取优惠券
takeCoupon: (templateId) => {
return request({
url: '/operations/coupon/take',
method: 'POST',
data: { templateId },
custom: {
auth: true,
showLoading: true,
loadingMsg: '领取中',
showSuccess: true,
successMsg: '领取成功',
},
});
},
// 获得优惠劵
getCoupon: (id) => {
return request({
url: '/operations/coupon/get',
method: 'GET',
params: { id },
});
},
// 获得未使用的优惠劵数量
getUnusedCouponCount: () => {
return request({
url: '/operations/coupon/get-unused-count',
method: 'GET',
custom: {
showLoading: false,
auth: true,
},
});
},
};
export default CouponApi;
+38
View File
@@ -0,0 +1,38 @@
import request from '@/sheep/request';
const DiyApi = {
getUsedDiyTemplate: () => {
return request({
url: '/promotion/diy-template/used',
method: 'GET',
custom: {
showError: false,
showLoading: false,
},
});
},
getDiyTemplate: (id) => {
return request({
url: '/promotion/diy-template/get',
method: 'GET',
params: {
id
},
custom: {
showError: false,
showLoading: false,
},
});
},
getDiyPage: (id) => {
return request({
url: '/promotion/diy-page/get',
method: 'GET',
params: {
id
}
});
},
};
export default DiyApi;
+31
View File
@@ -0,0 +1,31 @@
import request from '@/sheep/request';
const KeFuApi = {
sendKefuMessage: (data) => {
return request({
url: '/promotion/kefu-message/send',
method: 'POST',
data,
custom: {
auth: true,
showLoading: true,
loadingMsg: '发送中',
showSuccess: true,
successMsg: '发送成功',
},
});
},
getKefuMessagePage: (params) => {
return request({
url: '/promotion/kefu-message/page',
method: 'GET',
params,
custom: {
auth: true,
showLoading: false,
},
});
},
};
export default KeFuApi;
+30
View File
@@ -0,0 +1,30 @@
import request from '@/sheep/request';
const PointApi = {
// 获得积分商城活动分页
getPointActivityPage: (params) => {
return request({ url: 'promotion/point-activity/page', method: 'GET', params });
},
// 获得积分商城活动列表,基于活动编号数组
getPointActivityListByIds: (ids) => {
return request({
url: '/promotion/point-activity/list-by-ids',
method: 'GET',
params: {
ids,
},
});
},
// 获得积分商城活动明细
getPointActivity: (id) => {
return request({
url: 'promotion/point-activity/get-detail',
method: 'GET',
params: { id },
});
},
};
export default PointApi;
+14
View File
@@ -0,0 +1,14 @@
import request from '@/sheep/request';
const RewardActivityApi = {
// 获得满减送活动
getRewardActivity: (id) => {
return request({
url: '/promotion/reward-activity/get',
method: 'GET',
params: { id },
});
}
};
export default RewardActivityApi;
+44
View File
@@ -0,0 +1,44 @@
import request from '@/sheep/request';
const SeckillApi = {
// 获得秒杀时间段列表
getSeckillConfigList: () => {
return request({ url: 'promotion/seckill-config/list', method: 'GET' });
},
// 获得当前秒杀活动
getNowSeckillActivity: () => {
return request({ url: 'promotion/seckill-activity/get-now', method: 'GET' });
},
// 获得秒杀活动分页
getSeckillActivityPage: (params) => {
return request({ url: 'promotion/seckill-activity/page', method: 'GET', params });
},
// 获得秒杀活动列表,基于活动编号数组
getSeckillActivityListByIds: (ids) => {
return request({
url: '/promotion/seckill-activity/list-by-ids',
method: 'GET',
params: {
ids,
},
});
},
/**
* 获得秒杀活动明细
* @param {number} id 秒杀活动编号
* @return {*}
*/
getSeckillActivity: (id) => {
return request({
url: 'promotion/seckill-activity/get-detail',
method: 'GET',
params: { id },
});
},
};
export default SeckillApi;
+13
View File
@@ -0,0 +1,13 @@
import request from '@/sheep/request';
const AreaApi = {
// 获得地区树
getAreaTree: () => {
return request({
url: '/system/area/tree',
method: 'GET'
});
},
};
export default AreaApi;
+16
View File
@@ -0,0 +1,16 @@
import request from '@/sheep/request';
const DictApi = {
// 根据字典类型查询字典数据信息
getDictDataListByType: (type) => {
return request({
url: `/system/dict-data/type`,
method: 'GET',
params: {
type,
},
});
},
};
export default DictApi;
+63
View File
@@ -0,0 +1,63 @@
import request from '@/sheep/request';
const AfterSaleApi = {
// 获得售后分页
getAfterSalePage: (params) => {
return request({
url: `/trade/after-sale/page`,
method: 'GET',
params,
custom: {
showLoading: false,
},
});
},
// 创建售后
createAfterSale: (data) => {
return request({
url: `/trade/after-sale/create`,
method: 'POST',
data,
});
},
// 获得售后
getAfterSale: (id) => {
return request({
url: `/trade/after-sale/get`,
method: 'GET',
params: {
id,
},
});
},
// 取消售后
cancelAfterSale: (id) => {
return request({
url: `/trade/after-sale/cancel`,
method: 'DELETE',
params: {
id,
},
});
},
// 获得售后日志列表
getAfterSaleLogList: (afterSaleId) => {
return request({
url: `/trade/after-sale-log/list`,
method: 'GET',
params: {
afterSaleId,
},
});
},
// 退回货物
deliveryAfterSale: (data) => {
return request({
url: `/trade/after-sale/delivery`,
method: 'PUT',
data,
});
}
};
export default AfterSaleApi;
+93
View File
@@ -0,0 +1,93 @@
import request from '@/sheep/request';
const BrokerageApi = {
// 绑定分销用户
bindBrokerageUser: (data)=>{
return request({
url: '/trade/brokerage-user/bind',
method: 'PUT',
data
});
},
// 获得个人分销信息
getBrokerageUser: () => {
return request({
url: '/trade/brokerage-user/get',
method: 'GET'
});
},
// 获得个人分销统计
getBrokerageUserSummary: () => {
return request({
url: '/trade/brokerage-user/get-summary',
method: 'GET',
});
},
// 获得分销记录分页
getBrokerageRecordPage: params => {
if (params.status === undefined) {
delete params.status
}
const queryString = Object.keys(params)
.map(key => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/trade/brokerage-record/page?${queryString}`,
method: 'GET',
});
},
// 创建分销提现
createBrokerageWithdraw: data => {
return request({
url: '/trade/brokerage-withdraw/create',
method: 'POST',
data,
});
},
// 获得商品的分销金额
getProductBrokeragePrice: spuId => {
return request({
url: '/trade/brokerage-record/get-product-brokerage-price',
method: 'GET',
params: { spuId }
});
},
// 获得分销用户排行(基于佣金)
getRankByPrice: params => {
const queryString = `times=${params.times[0]}&times=${params.times[1]}`;
return request({
url: `/trade/brokerage-user/get-rank-by-price?${queryString}`,
method: 'GET',
});
},
// 获得分销用户排行分页(基于佣金)
getBrokerageUserChildSummaryPageByPrice: params => {
const queryString = Object.keys(params)
.map(key => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/trade/brokerage-user/rank-page-by-price?${queryString}`,
method: 'GET',
});
},
// 获得分销用户排行分页(基于用户量)
getBrokerageUserRankPageByUserCount: params => {
const queryString = Object.keys(params)
.map(key => encodeURIComponent(key) + '=' + params[key])
.join('&');
return request({
url: `/trade/brokerage-user/rank-page-by-user-count?${queryString}`,
method: 'GET',
});
},
// 获得下级分销统计分页
getBrokerageUserChildSummaryPage: params => {
return request({
url: '/trade/brokerage-user/child-summary-page',
method: 'GET',
params,
})
}
}
export default BrokerageApi
+50
View File
@@ -0,0 +1,50 @@
import request from '@/sheep/request';
const CartApi = {
addCart: (data) => {
return request({
url: '/trade/cart/add',
method: 'POST',
data: data,
custom: {
showSuccess: true,
successMsg: '已添加到购物车~',
}
});
},
updateCartCount: (data) => {
return request({
url: '/trade/cart/update-count',
method: 'PUT',
data: data
});
},
updateCartSelected: (data) => {
return request({
url: '/trade/cart/update-selected',
method: 'PUT',
data: data
});
},
deleteCart: (ids) => {
return request({
url: '/trade/cart/delete',
method: 'DELETE',
params: {
ids
}
});
},
getCartList: () => {
return request({
url: '/trade/cart/list',
method: 'GET',
custom: {
showLoading: false,
auth: true,
},
});
},
};
export default CartApi;
+13
View File
@@ -0,0 +1,13 @@
import request from '@/sheep/request';
const TradeConfigApi = {
// 获得交易配置
getTradeConfig: () => {
return request({
url: `/trade/config/get`,
method: 'GET',
});
},
};
export default TradeConfigApi;
+31
View File
@@ -0,0 +1,31 @@
import request from '@/sheep/request';
const DeliveryApi = {
// 获得快递公司列表
getDeliveryExpressList: () => {
return request({
url: `/trade/delivery/express/list`,
method: 'get',
});
},
// 获得自提门店列表
getDeliveryPickUpStoreList: (params) => {
return request({
url: `/trade/delivery/pick-up-store/list`,
method: 'GET',
params,
});
},
// 获得自提门店
getDeliveryPickUpStore: (id) => {
return request({
url: `/trade/delivery/pick-up-store/get`,
method: 'GET',
params: {
id,
},
});
},
};
export default DeliveryApi;
+165
View File
@@ -0,0 +1,165 @@
import request from '@/sheep/request';
import { isEmpty } from '@/sheep/helper/utils';
const OrderApi = {
// 计算订单信息
settlementOrder: (data) => {
const data2 = {
...data,
};
// 移除多余字段
if (!(data.couponId > 0)) {
delete data2.couponId;
}
if (!(data.addressId > 0)) {
delete data2.addressId;
}
if (!(data.pickUpStoreId > 0)) {
delete data2.pickUpStoreId;
}
if (isEmpty(data.receiverName)) {
delete data2.receiverName;
}
if (isEmpty(data.receiverMobile)) {
delete data2.receiverMobile;
}
if (!(data.combinationActivityId > 0)) {
delete data2.combinationActivityId;
}
if (!(data.combinationHeadId > 0)) {
delete data2.combinationHeadId;
}
if (!(data.seckillActivityId > 0)) {
delete data2.seckillActivityId;
}
if (!(data.pointActivityId > 0)) {
delete data2.pointActivityId;
}
// 解决 SpringMVC 接受 List<Item> 参数的问题
delete data2.items;
for (let i = 0; i < data.items.length; i++) {
data2[encodeURIComponent('items[' + i + '' + '].skuId')] = data.items[i].skuId + '';
data2[encodeURIComponent('items[' + i + '' + '].count')] = data.items[i].count + '';
if (data.items[i].cartId) {
data2[encodeURIComponent('items[' + i + '' + '].cartId')] = data.items[i].cartId + '';
}
}
const queryString = Object.keys(data2)
.map((key) => key + '=' + data2[key])
.join('&');
return request({
url: `/trade/order/settlement?${queryString}`,
method: 'GET',
custom: {
showError: true,
showLoading: true,
},
});
},
// 获得商品结算信息
getSettlementProduct: (spuIds) => {
return request({
url: '/trade/order/settlement-product',
method: 'GET',
params: { spuIds },
custom: {
showLoading: false,
showError: false,
},
});
},
// 创建订单
createOrder: (data) => {
return request({
url: `/trade/order/create`,
method: 'POST',
data,
});
},
// 获得订单详细:sync 是可选参数
getOrderDetail: (id, sync) => {
return request({
url: `/trade/order/get-detail`,
method: 'GET',
params: {
id,
sync,
},
custom: {
showLoading: false,
},
});
},
// 订单列表
getOrderPage: (params) => {
return request({
url: '/trade/order/page',
method: 'GET',
params,
custom: {
showLoading: false,
},
});
},
// 确认收货
receiveOrder: (id) => {
return request({
url: `/trade/order/receive`,
method: 'PUT',
params: {
id,
},
});
},
// 取消订单
cancelOrder: (id) => {
return request({
url: `/trade/order/cancel`,
method: 'DELETE',
params: {
id,
},
});
},
// 删除订单
deleteOrder: (id) => {
return request({
url: `/trade/order/delete`,
method: 'DELETE',
params: {
id,
},
});
},
// 获得交易订单的物流轨迹
getOrderExpressTrackList: (id) => {
return request({
url: `/trade/order/get-express-track-list`,
method: 'GET',
params: {
id,
},
});
},
// 获得交易订单数量
getOrderCount: () => {
return request({
url: '/trade/order/get-count',
method: 'GET',
custom: {
showLoading: false,
auth: true,
},
});
},
// 创建单个评论
createOrderItemComment: (data) => {
return request({
url: `/trade/order/item/create-comment`,
method: 'POST',
data,
});
},
};
export default OrderApi;
+196
View File
@@ -0,0 +1,196 @@
<!-- TODO 是不是怎么复用 s-count-down 组件 -->
<template>
<view class="time" :style="justifyLeft">
<text class="" v-if="tipText">{{ tipText }}</text>
<text
class="styleAll p6"
v-if="isDay === true"
:style="{ background: bgColor.bgColor, color: bgColor.Color }"
>{{ day }}{{ bgColor.isDay ? '天' : '' }}</text
>
<text
class="timeTxt"
v-if="dayText"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ dayText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ hour }}</text
>
<text
class="timeTxt"
v-if="hourText"
:class="isCol ? 'whit' : ''"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ hourText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ minute }}</text
>
<text
class="timeTxt"
v-if="minuteText"
:class="isCol ? 'whit' : ''"
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
>{{ minuteText }}</text
>
<text
class="styleAll"
:class="isCol ? 'timeCol' : ''"
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
>{{ second }}</text
>
<text class="timeTxt" v-if="secondText">{{ secondText }}</text>
</view>
</template>
<script>
export default {
name: 'countDown',
props: {
justifyLeft: {
type: String,
default: '',
},
//距离开始提示文字
tipText: {
type: String,
default: '倒计时',
},
dayText: {
type: String,
default: '天',
},
hourText: {
type: String,
default: '时',
},
minuteText: {
type: String,
default: '分',
},
secondText: {
type: String,
default: '秒',
},
datatime: {
type: Number,
default: 0,
},
isDay: {
type: Boolean,
default: true,
},
isCol: {
type: Boolean,
default: false,
},
bgColor: {
type: Object,
default: null,
},
},
data: function () {
return {
day: '00',
hour: '00',
minute: '00',
second: '00',
};
},
created: function () {
this.show_time();
},
mounted: function () {},
methods: {
show_time: function () {
let that = this;
function runTime() {
//时间函数
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差;
let day = 0,
hour = 0,
minute = 0,
second = 0;
if (intDiff > 0) {
//转换时间
if (that.isDay === true) {
day = Math.floor(intDiff / (60 * 60 * 24));
} else {
day = 0;
}
hour = Math.floor(intDiff / (60 * 60)) - day * 24;
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
second = Math.floor(intDiff) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
if (hour <= 9) hour = '0' + hour;
if (minute <= 9) minute = '0' + minute;
if (second <= 9) second = '0' + second;
that.day = day;
that.hour = hour;
that.minute = minute;
that.second = second;
} else {
that.day = '00';
that.hour = '00';
that.minute = '00';
that.second = '00';
}
}
runTime();
setInterval(runTime, 1000);
},
},
};
</script>
<style scoped>
.p6 {
padding: 0 8rpx;
}
.styleAll {
/* color: #fff; */
font-size: 24rpx;
height: 36rpx;
line-height: 36rpx;
border-radius: 6rpx;
text-align: center;
/* padding: 0 6rpx; */
}
.timeTxt {
text-align: center;
/* width: 16rpx; */
height: 36rpx;
line-height: 36rpx;
display: inline-block;
}
.whit {
color: #fff !important;
}
.time {
display: flex;
justify-content: center;
}
.red {
color: #fc4141;
margin: 0 4rpx;
}
.timeCol {
/* width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align:center;
border-radius: 6px;
background: #fff;
font-size: 24rpx; */
color: #e93323;
}
</style>
@@ -0,0 +1,155 @@
<template>
<uni-card is-shadow spacing="0" padding="30rpx">
<template v-slot:title>
<view class="title">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/27698.png" style="width:20rpx;height:32rpx;margin-right: 10rpx;"></image>
<text>
{{detail.equipmentModelRespVO.type===1?'快充':'慢充'}}
</text>
</view>
</template>
<view class="container">
<view class="left">
<view class="icon" :class="[WORK_STATE.get(detail.workState).state,{plug:detail.insertArmsState==='1'}]">
{{detail.insertArmsState===1?'已插枪':WORK_STATE.get(detail.workState).text}}
</view>
<view class="text">
{{detail.connectorCode.slice(-5)}}
</view>
</view>
<view class="right">
<view class="line">
<view class="label">
编号
</view>
<view class="value">
{{detail.connectorCode}}
</view>
</view>
<view class="line">
<view class="label">
电流
</view>
<view class="value">
{{ detail.equipmentModelRespVO.maxa }}
</view>
</view>
<view class="line">
<view class="label">
电压
</view>
<view class="value">
{{ detail.equipmentModelRespVO.minv }}V-{{ detail.equipmentModelRespVO.maxv }}V
</view>
</view>
<view class="line">
<view class="label">
{{detail.equipmentModelRespVO.type===1?'直流快充':'交流慢充'}}
</view>
<view class="value">
{{detail.equipmentModelRespVO.maxp}}
</view>
</view>
</view>
</view>
</uni-card>
</template>
<script setup>
defineProps({
detail: {
type: Object
}
})
const WORK_STATE = new Map([
[0, { state: 'hitch', text: '故障' }],
[1, { state: 'hitch', text: '故障'}],
[2, { state: 'idle', text: '空闲'}],
[3, { state: 'charging', text: '充电中' }],
[4, { state: 'idle', text: '空闲' }]
])
</script>
<style scoped lang="scss">
.title {
background: rgba(252, 130, 41, .1);
font-size: 28rpx;
color: #FC8229;
padding: 10rpx 30rpx;
&.slow {
background: rgba(64, 197, 168, .1);
color: #40C5A8;
}
}
.container {
display: flex;
.left {
width: 98rpx;
text-align: center;
.icon {
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;
}
}
.text {
font-size: 28rpx;
color: #999999;
}
}
.right {
font-size: 28rpx;
color: #333333;
margin-left: 30rpx;
.line {
display: flex;
}
.line:not(:last-child) {
margin-bottom: 20rpx;
}
.label {
color: #999999;
margin-right: 20rpx;
}
}
}
</style>
+63
View File
@@ -0,0 +1,63 @@
<template>
<view class="flex align-center" :style="style">
<uni-search-bar @confirm="onConfirm" @input="onInput" bgColor="#fff" textColor="#555" style="flex:1"
placeholder="输入电站名称/地点" v-bind="$attrs" />
<view class="scan-code" @click="onScan">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16424@2x.png" mode="aspectFit"></image>
</view>
</view>
</template>
<script setup>
const emit = defineEmits(['input', 'confirm', 'scan'])
const props = defineProps({
scroll: {
type: Boolean,
default: false
}
})
// onPageScroll
const {
scrollTop
} = usePageScroll()
const style = computed(() => {
return props.scroll ? {
top: sheep.$platform.navbar + 'px',
backgroundColor: scrollTop.value > 0 ? '#f5f9ff' : 'transparent',
position: 'sticky',
zIndex: 100
} : null
})
const onConfirm = val => {
emit('confirm', val)
}
const onInput = val => {
emit('input', val)
}
const onScan = () => {
emit('scan')
}
</script>
<style lang="scss" scoped>
image {
width: 100%;
height: 100%;
vertical-align: middle;
}
.scan-code {
width: 68rpx;
height: 68rpx;
background: #FFFFFF;
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-right: 20rpx;
padding: 18rpx;
}
</style>
+214
View File
@@ -0,0 +1,214 @@
<template>
<uni-card is-shadow spacing="0" padding="30rpx" @click="toDetail">
<view class="container">
<view class="content">
<view class="header">
<view class="name">
{{station.stationName}}
</view>
<view class="distance">
<uni-icons type="paperplane-filled" color="#3072F6" :size="13"></uni-icons>
{{ station.distance < 1 ? (station.distance * 1000).toFixed(2) + 'm' : station.distance.toFixed(2) + 'km' }}
</view>
</view>
<view class="abstract">
<!-- //todo 公共;50:个人;100:公交(专用);101:环卫(专用);102:物流(专用);103:出租车(专用);255:其他-->
<text>{{(stationType)}}</text>
<view class="line"></view>
<text>{{station.openExplain}}</text>
<view class="line"></view>
<text>{{station.remarks}}</text>
</view>
<view class="detail">
<view class="img">
<image :src="station.imgMainEntrance" mode="aspectFit" style="width:100%;height:100%"></image>
</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="info">
<!-- 慢充部分条件渲染 -->
<view class="slow" v-if="station.slowConnectorNumber && station.slowConnectorNumber > 0">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16566@2x.png" mode="aspectFit"></image>
<text>
{{station.slowLeisureConnectorNumber}}/{{station.slowConnectorNumber}}
</text>
</view>
<!-- 快充部分条件渲染 -->
<view class="fast" v-if="station.connectorNumber && station.connectorNumber > 0">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16567@2x.png" mode="aspectFit"></image>
<text>
{{station.leisureConnectorNumber}}/{{station.connectorNumber}}
</text>
</view>
</view>
</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>
</template>
<script setup>
const props = defineProps({
station: {
type: Object
}
})
const stationTypes = sheep.$store('dict')['station_type']
const stationType = computed(() => {
return stationTypes.filter(el => (el.value + '') === (props.station.stationType + ''))[0]?.label
})
function toDetail() {
const {
id,
stationLng,
stationLat
} = props.station
uni.navigateTo({
url: '/pages/station-detail/index?id=' + id + '&longitude=' + stationLng + '&latitude=' + stationLat
})
}
</script>
<style lang="scss" scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.name {
font-weight: 800;
font-size: 30rpx;
color: #333;
}
.distance {
float: right;
background: rgba(48, 114, 246, .1);
border-radius: 25rpx;
font-weight: bold;
font-size: 24rpx;
color: #3072F6;
padding: 0 10rpx;
}
.abstract {
margin-top: 10rpx;
display: flex;
align-items: center;
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;
display: flex;
justify-content: space-between;
align-items: center;
.img {
width: 200rpx;
height: 112rpx;
}
}
.charge {
.price {
font-size: 24rpx;
color: #333;
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;
margin-top: 20rpx;
image {
width: 82rpx;
height: 45rpx;
}
text {
font-weight: bold;
font-size: 24rpx;
color: #3A3E5E;
}
}
}
.info {
font-size: 26rpx;
image {
width: 44rpx;
height: 44rpx;
}
.slow {
background: rgba(64, 197, 168, .1);
color: #40C5A8;
}
.fast {
background: rgba(252, 130, 41, .1);
color: #FC8229;
margin-top: 25rpx;
}
text {
padding: 0 10rpx;
}
}
.tip {
padding-top: 20rpx;
border-top: 1rpx solid #E3E3E3;
image {
width: 28rpx;
height: 28rpx;
}
text {
font-weight: 500;
font-size: 24rpx;
color: #666;
margin-left: 10rpx;
}
}
</style>
@@ -0,0 +1,20 @@
<template>
<view :style="{height:height+'px',width:'100%'}">
</view>
</template>
<script setup>
const height = computed(() => {
let height = sheep.$store('sys').tabbarPlaceholderHeight
//#ifdef H5
if (height === 0) {
height = 50
}
//#endif
//#ifndef H5
height = 0
//#endif
return height + 30
});
</script>
@@ -0,0 +1,13 @@
<template>
<view class="u-page__item">
<su-tabbar :fixed="true" :placeholder="true" :safeAreaInsetBottom="true">
<slot></slot>
</su-tabbar>
</view>
</template>
<script setup>
</script>
<style>
</style>
+62
View File
@@ -0,0 +1,62 @@
<template>
<kx-tabbar-wrapper>
<view class="flex tabbar-container">
<view class="flex align-center flex-column left" @click="contactService">
<image src="https://kxcharge.oss-cn-beijing.aliyuncs.com/16585.png"></image>
<text>联系客服</text>
</view>
<view class="right" @click="onScan">
扫码充电
</view>
</view>
</kx-tabbar-wrapper>
</template>
<script setup>
const emit = defineEmits(['contact-service'])
const onScan = () => {
uni.scanCode({
success(res) {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
},
fail(res) {
console.log('扫码失败:' + res)
}
});
}
const contactService = () => {
emit('contact-service')
}
</script>
<style scoped lang="scss">
.tabbar-container {
width: 100%;
justify-content: space-evenly;
align-items: center;
.left {
image {
width: 50rpx;
height: 50rpx;
margin-bottom: 2rpx;
}
text {
font-size: 26rpx;
color: #333;
}
}
.right {
background: linear-gradient(132deg, #4A8EFC 0%, #50C3FD 100%);
border-radius: 45rpx;
font-weight: bold;
font-size: 30rpx;
color: #FFF;
padding: 24rpx 226rpx;
}
}
</style>
@@ -0,0 +1,255 @@
<!-- 商品信息满减送等营销活动的弹窗 -->
<template>
<su-popup :show="show" type="bottom" round="20" @close="emits('close')" showClose>
<view class="model-box">
<view class="title ss-m-t-16 ss-m-l-20 ss-flex">优惠</view>
<view v-if="state.rewardActivity && state.rewardActivity.id > 0">
<view class="titleLi">促销</view>
<scroll-view
class="model-content"
scroll-y
:scroll-with-animation="false"
:enable-back-to-top="true"
>
<view
class="actBox"
v-for="(item, index) in getRewardActivityRuleGroupDescriptions(state.rewardActivity)"
:key="index"
>
<view
class="boxCont ss-flex ss-col-top ss-m-b-40"
@tap="onGoodsList(state.rewardActivity)"
>
<view class="model-content-tag ss-flex ss-row-center">{{ item.name }}</view>
<view class="model-content-title">
<view class="contBu">
{{ item.values.join(';') }}
</view>
<view class="ss-m-b-24 cotBu-txt">
{{ sheep.$helper.timeFormat(state.rewardActivity.startTime, 'yyyy.mm.dd') }}
-
{{ sheep.$helper.timeFormat(state.rewardActivity.endTime, 'yyyy.mm.dd') }}
</view>
</view>
<text class="cicon-forward" />
</view>
</view>
</scroll-view>
</view>
<view class="titleLi">可领优惠券</view>
<scroll-view
class="model-content"
scroll-y
:scroll-with-animation="false"
:enable-back-to-top="true"
v-if="state.couponInfo.length"
>
<view class="actBox" v-for="item in state.couponInfo" :key="item.id">
<view class="boxCont ss-flex ss-col-top ss-m-b-40">
<view class="model-content-tag2">
<view class="usePrice"> {{ fen2yuan(item.discountPrice) }} </view>
<view class="impose"> {{ fen2yuan(item.usePrice) }}可用 </view>
</view>
<view class="model-content-title2">
<view class="contBu">
{{ item.name }}
</view>
<view class="ss-m-b-24 cotBu-txt">
{{
item.validityType == 1
? sheep.$helper.timeFormat(item.validStartTime, 'yyyy.mm.dd') -
sheep.$helper.timeFormat(item.validEndTime, 'yyyy.mm.dd')
: '领取后' + item.fixedStartTerm + '-' + item.fixedEndTerm + '天可用'
}}
</view>
</view>
<view class="coupon" @click.stop="getBuy(item.id)" v-if="item.canTake"> 立即领取 </view>
<view class="coupon2" v-else> 已领取 </view>
</view>
</view>
</scroll-view>
<view class="nullBox" v-else> 暂无可领优惠券 </view>
</view>
</su-popup>
</template>
<script setup>
import sheep from '@/sheep';
import { getRewardActivityRuleGroupDescriptions } from '@/sheep/hooks/useGoods';
import { computed, reactive, watch, ref } from 'vue';
import { fen2yuan } from '@/sheep/hooks/useGoods';
const props = defineProps({
modelValue: {
type: Object,
default() {},
},
show: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['close']);
const state = reactive({
rewardActivity: computed(() => props.modelValue.rewardActivity),
couponInfo: computed(() => props.modelValue.couponInfo),
});
// 领取优惠劵
const getBuy = (id) => {
emits('get', id);
};
function onGoodsList(e) {
sheep.$router.go('/pages/activity/index', {
activityId: e.id,
});
}
</script>
<style lang="scss" scoped>
.model-box {
height: 60vh;
.title {
justify-content: center;
font-size: 36rpx;
height: 80rpx;
font-weight: bold;
color: #333333;
}
}
.model-content {
height: fit-content;
max-height: 380rpx;
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 20rpx;
.model-content-tag {
// background: rgba(#ff6911, 0.1);
font-size: 35rpx;
font-weight: 500;
color: #ff6911;
line-height: 150rpx;
width: 200rpx;
height: 150rpx;
text-align: center;
// border-radius: 5rpx;
}
.model-content-title {
width: 450rpx;
height: 150rpx;
font-size: 26rpx;
font-weight: 500;
color: #333333;
overflow: hidden;
}
.cicon-forward {
font-size: 28rpx;
color: #999999;
margin: 0 auto;
}
}
// 新增的
.titleLi {
margin: 10rpx 0 10rpx 20rpx;
font-size: 26rpx;
}
.actBox {
width: 700rpx;
height: 150rpx;
background-color: #fff2f2;
margin: 10rpx auto;
border-radius: 10rpx;
}
.boxCont {
width: 700rpx;
height: 150rpx;
align-items: center;
}
.contBu {
height: 80rpx;
line-height: 80rpx;
overflow: hidden;
font-size: 30rpx;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
.cotBu-txt {
height: 70rpx;
line-height: 70rpx;
font-size: 25rpx;
color: #999999;
}
.model-content-tag2 {
font-size: 35rpx;
font-weight: 500;
color: #ff6911;
width: 200rpx;
height: 150rpx;
text-align: center;
}
.usePrice {
width: 200rpx;
height: 90rpx;
line-height: 100rpx;
// background-color: red;
}
.impose {
width: 200rpx;
height: 50rpx;
// line-height: 75rpx;
font-size: 23rpx;
// background-color: gold;
}
.model-content-title2 {
width: 330rpx;
height: 150rpx;
font-size: 26rpx;
font-weight: 500;
color: #333333;
overflow: hidden;
}
.coupon {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
background-color: rgb(255, 68, 68);
color: white;
border-radius: 30rpx;
text-align: center;
font-size: 25rpx;
}
.coupon2 {
width: 150rpx;
height: 50rpx;
line-height: 50rpx;
background-color: rgb(203, 192, 191);
color: white;
border-radius: 30rpx;
text-align: center;
font-size: 25rpx;
}
.nullBox {
width: 100%;
height: 300rpx;
font-size: 25rpx;
line-height: 300rpx;
text-align: center;
color: #999999;
}
</style>
@@ -0,0 +1,110 @@
<!-- 地址卡片 -->
<template>
<view
class="address-item ss-flex ss-row-between ss-col-center"
:class="[{ 'border-bottom': props.hasBorderBottom }]"
>
<view class="item-left" v-if="!isEmpty(props.item)">
<view class="area-text ss-flex ss-col-center">
<uni-tag
class="ss-m-r-10"
size="small"
custom-style="background-color: var(--ui-BG-Main); border-color: var(--ui-BG-Main); color: #fff;"
v-if="props.item.defaultStatus"
text="默认"
/>
{{ props.item.areaName }}
</view>
<view class="address-text">
{{ props.item.detailAddress }}
</view>
<view class="person-text"> {{ props.item.name }} {{ props.item.mobile }} </view>
</view>
<view v-else>
<view class="address-text ss-m-b-10">请选择收货地址</view>
</view>
<slot>
<button class="ss-reset-button edit-btn" @tap.stop="onEdit">
<view class="edit-icon ss-flex ss-row-center ss-col-center">
<image :src="sheep.$url.static('/static/img/shop/user/address/edit.png')" />
</view>
</button>
</slot>
</view>
</template>
<script setup>
/**
* 基础组件 - 地址卡片
*
* @param {String} icon = _icon-edit - icon
*
* @event {Function()} click - 点击
* @event {Function()} actionClick - 点击工具栏
*
* @slot - 默认插槽
*/
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
const props = defineProps({
item: {
type: Object,
default() {},
},
hasBorderBottom: {
type: Boolean,
defult: true,
},
});
const onEdit = () => {
sheep.$router.go('/pages/user/address/edit', {
id: props.item.id,
});
};
</script>
<style lang="scss" scoped>
.address-item {
padding: 24rpx 30rpx;
.item-left {
width: 600rpx;
}
.area-text {
font-size: 26rpx;
font-weight: 400;
color: $dark-9;
}
.address-text {
font-size: 32rpx;
font-weight: 500;
color: #333333;
line-height: 48rpx;
}
.person-text {
font-size: 28rpx;
font-weight: 400;
color: $dark-9;
}
}
.edit-btn {
width: 44rpx;
height: 44rpx;
background: $gray-f;
border-radius: 50%;
.edit-icon {
width: 24rpx;
height: 24rpx;
}
}
image {
width: 100%;
height: 100%;
}
</style>
@@ -0,0 +1,107 @@
<!-- 账号密码登录 accountLogin -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60 ss-flex-col">
<view class="ss-flex ss-m-b-20">
<view class="head-title-active head-title-line" @tap="showAuthModal('smsLogin')">
短信登录
</view>
<view class="head-title ss-m-r-40 head-title-animation">账号登录</view>
</view>
<view class="head-subtitle">如果未设置过密码请点击忘记密码</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="accountLoginRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<uni-forms-item name="mobile" label="账号">
<uni-easyinput placeholder="请输入账号" v-model="state.model.mobile" :inputBorder="false">
<template v-slot:right>
<button class="ss-reset-button forgot-btn" @tap="showAuthModal('resetPassword')">
忘记密码
</button>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="password" label="密码">
<uni-easyinput
type="password"
placeholder="请输入密码"
v-model="state.model.password"
:inputBorder="false"
>
<template v-slot:right>
<button class="ss-reset-button login-btn-start" @tap="accountLoginSubmit">登录</button>
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
</view>
</template>
<script setup>
import { ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { mobile, password } from '@/sheep/validate/form';
import { showAuthModal, closeAuthModal } from '@/sheep/hooks/useModal';
import AuthUtil from '@/sheep/api/member/auth';
const accountLoginRef = ref(null);
const emits = defineEmits(['onConfirm']);
const props = defineProps({
agreeStatus: {
type: Boolean,
default: false,
},
});
// 数据
const state = reactive({
model: {
mobile: '', // 账号
password: '', // 密码
},
rules: {
mobile,
password,
},
});
// 账号登录
async function accountLoginSubmit() {
// 表单验证
const validate = await unref(accountLoginRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) return;
// 同意协议
if (!props.agreeStatus) {
emits('onConfirm', true)
sheep.$helper.toast('请勾选同意');
return;
}
// 提交数据
const { code, data } = await AuthUtil.login(state.model);
if (code === 0) {
closeAuthModal();
}
}
</script>
<style lang="scss" scoped>
@import '../index.scss';
</style>
@@ -0,0 +1,127 @@
<!-- 绑定/更换手机号 changeMobile -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60">
<view class="head-title ss-m-b-20">
{{ userInfo.mobile ? '更换手机号' : '绑定手机号' }}
</view>
<view class="head-subtitle">为了您的账号安全请使用本人手机号码</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="changeMobileRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<uni-forms-item name="mobile" label="手机号">
<uni-easyinput
placeholder="请输入手机号"
v-model="state.model.mobile"
:inputBorder="false"
type="number"
>
<template v-slot:right>
<button
class="ss-reset-button code-btn-start"
:disabled="state.isMobileEnd"
:class="{ 'code-btn-end': state.isMobileEnd }"
@tap="getSmsCode('changeMobile', state.model.mobile)"
>
{{ getSmsTimer('changeMobile') }}
</button>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="code" label="验证码">
<uni-easyinput
placeholder="请输入验证码"
v-model="state.model.code"
:inputBorder="false"
type="number"
maxlength="4"
>
<template v-slot:right>
<button class="ss-reset-button login-btn-start" @tap="changeMobileSubmit">
确认
</button>
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
<!-- 微信独有读取手机号 -->
<button
v-if="'WechatMiniProgram' === sheep.$platform.name"
class="ss-reset-button type-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
使用微信手机号
</button>
</view>
</template>
<script setup>
import { computed, ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { code, mobile } from '@/sheep/validate/form';
import { closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
import UserApi from '@/sheep/api/member/user';
const changeMobileRef = ref(null);
const userInfo = computed(() => sheep.$store('user').userInfo);
// 数据
const state = reactive({
isMobileEnd: false, // 手机号输入完毕
model: {
mobile: '', // 手机号
code: '', // 验证码
},
rules: {
code,
mobile,
},
});
// 绑定手机号
async function changeMobileSubmit() {
const validate = await unref(changeMobileRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
// 提交更新请求
const { code } = await UserApi.updateUserMobile(state.model);
if (code !== 0) {
return;
}
sheep.$store('user').getInfo();
closeAuthModal();
}
// 使用微信手机号
async function getPhoneNumber(e) {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
return;
}
const result = await sheep.$platform.useProvider().bindUserPhoneNumber(e.detail);
if (result) {
sheep.$store('user').getInfo();
closeAuthModal();
}
}
</script>
<style lang="scss" scoped>
@import '../index.scss';
</style>
@@ -0,0 +1,106 @@
<!-- 修改密码登录时 -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60">
<view class="head-title ss-m-b-20">修改密码</view>
<view class="head-subtitle">如密码丢失或未设置,请点击忘记密码重新设置</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="changePasswordRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<uni-forms-item name="code" label="验证码">
<uni-easyinput
placeholder="请输入验证码"
v-model="state.model.code"
type="number"
maxlength="4"
:inputBorder="false"
>
<template v-slot:right>
<button
class="ss-reset-button code-btn code-btn-start"
:disabled="state.isMobileEnd"
:class="{ 'code-btn-end': state.isMobileEnd }"
@tap="getSmsCode('changePassword')"
>
{{ getSmsTimer('resetPassword') }}
</button>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="reNewPassword" label="密码">
<uni-easyinput
type="password"
placeholder="请输入密码"
v-model="state.model.password"
:inputBorder="false"
>
<template v-slot:right>
<button class="ss-reset-button login-btn-start" @tap="changePasswordSubmit">
确认
</button>
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
<button class="ss-reset-button type-btn" @tap="closeAuthModal">
取消修改
</button>
</view>
</template>
<script setup>
import { ref, reactive, unref } from 'vue';
import { code, password } from '@/sheep/validate/form';
import { closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
import UserApi from '@/sheep/api/member/user';
const changePasswordRef = ref(null);
// 数据
const state = reactive({
model: {
mobile: '', // 手机号
code: '', // 验证码
password: '', // 密码
},
rules: {
code,
password,
},
});
// 更改密码
async function changePasswordSubmit() {
// 参数校验
const validate = await unref(changePasswordRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
// 发起请求
const { code } = await UserApi.updateUserPassword(state.model);
if (code !== 0) {
return;
}
// 成功后,只需要关闭弹窗
closeAuthModal();
}
</script>
<style lang="scss" scoped>
@import '../index.scss';
</style>
@@ -0,0 +1,152 @@
<!-- 微信授权信息 mpAuthorization -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60 ss-flex-col">
<view class="ss-flex ss-m-b-20">
<view class="head-title ss-m-r-40 head-title-animation">授权信息</view>
</view>
<view class="head-subtitle">完善您的头像昵称手机号</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="accountLoginRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<!-- 获取头像昵称https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html -->
<uni-forms-item name="avatar" label="头像">
<button
class="ss-reset-button avatar-btn"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
<image
class="avatar-img"
:src="sheep.$url.cdn(state.model.avatar)"
mode="aspectFill"
@tap="sheep.$router.go('/pages/user/info')"
/>
<text class="cicon-forward" />
</button>
</uni-forms-item>
<uni-forms-item name="nickname" label="昵称">
<uni-easyinput
type="nickname"
placeholder="请输入昵称"
v-model="state.model.nickname"
:inputBorder="false"
/>
</uni-forms-item>
<view class="foot-box">
<button class="ss-reset-button authorization-btn" @tap="onConfirm"> 确认授权 </button>
</view>
</uni-forms>
</view>
</template>
<script setup>
import { computed, ref, reactive } from 'vue';
import sheep from '@/sheep';
import { closeAuthModal } from '@/sheep/hooks/useModal';
import FileApi from '@/sheep/api/infra/file';
import UserApi from '@/sheep/api/member/user';
const props = defineProps({
agreeStatus: {
type: Boolean,
default: false,
},
});
const userInfo = computed(() => sheep.$store('user').userInfo);
const accountLoginRef = ref(null);
// 数据
const state = reactive({
model: {
nickname: userInfo.value.nickname,
avatar: userInfo.value.avatar,
},
rules: {},
disabledStyle: {
color: '#999',
disableColor: '#fff',
},
});
// 选择头像(来自微信)
function onChooseAvatar(e) {
const tempUrl = e.detail.avatarUrl || '';
uploadAvatar(tempUrl);
}
// 选择头像(来自文件系统)
async function uploadAvatar(tempUrl) {
if (!tempUrl) {
return;
}
let { data } = await FileApi.uploadFile(tempUrl);
state.model.avatar = data;
}
// 确认授权
async function onConfirm() {
const { model } = state;
const { nickname, avatar } = model;
if (!nickname) {
sheep.$helper.toast('请输入昵称');
return;
}
if (!avatar) {
sheep.$helper.toast('请选择头像');
return;
}
// 发起更新
const { code } = await UserApi.updateUser({
avatar: state.model.avatar,
nickname: state.model.nickname,
});
// 更新成功
if (code === 0) {
sheep.$helper.toast('授权成功');
await sheep.$store('user').getInfo();
closeAuthModal();
}
}
</script>
<style lang="scss" scoped>
@import '../index.scss';
.foot-box {
width: 100%;
display: flex;
justify-content: center;
}
.authorization-btn {
width: 686rpx;
height: 80rpx;
background-color: var(--ui-BG-Main);
border-radius: 40rpx;
color: #fff;
}
.avatar-img {
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
}
.cicon-forward {
font-size: 30rpx;
color: #595959;
}
.avatar-btn {
width: 100%;
justify-content: space-between;
}
</style>
@@ -0,0 +1,119 @@
<!-- 重置密码未登录时 -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60">
<view class="head-title ss-m-b-20">重置密码</view>
<view class="head-subtitle">为了您的账号安全设置密码前请先进行安全验证</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="resetPasswordRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<uni-forms-item name="mobile" label="手机号">
<uni-easyinput
placeholder="请输入手机号"
v-model="state.model.mobile"
type="number"
:inputBorder="false"
>
<template v-slot:right>
<button
class="ss-reset-button code-btn code-btn-start"
:disabled="state.isMobileEnd"
:class="{ 'code-btn-end': state.isMobileEnd }"
@tap="getSmsCode('resetPassword', state.model.mobile)"
>
{{ getSmsTimer('resetPassword') }}
</button>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="code" label="验证码">
<uni-easyinput
placeholder="请输入验证码"
v-model="state.model.code"
type="number"
maxlength="4"
:inputBorder="false"
/>
</uni-forms-item>
<uni-forms-item name="password" label="密码">
<uni-easyinput
type="password"
placeholder="请输入密码"
v-model="state.model.password"
:inputBorder="false"
>
<template v-slot:right>
<button class="ss-reset-button login-btn-start" @tap="resetPasswordSubmit">
确认
</button>
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
<button v-if="!isLogin" class="ss-reset-button type-btn" @tap="showAuthModal('accountLogin')">
返回登录
</button>
</view>
</template>
<script setup>
import { computed, ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { code, mobile, password } from '@/sheep/validate/form';
import { showAuthModal, closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
import UserApi from '@/sheep/api/member/user';
const resetPasswordRef = ref(null);
const isLogin = computed(() => sheep.$store('user').isLogin);
// 数据
const state = reactive({
isMobileEnd: false, // 手机号输入完毕
model: {
mobile: '', // 手机号
code: '', // 验证码
password: '', // 密码
},
rules: {
code,
mobile,
password,
},
});
// 重置密码
const resetPasswordSubmit = async () => {
// 参数校验
const validate = await unref(resetPasswordRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
// 发起请求
const { code } = await UserApi.resetUserPassword(state.model);
if (code !== 0) {
return;
}
// 成功后,用户重新登录
showAuthModal('accountLogin')
};
</script>
<style lang="scss" scoped>
@import '../index.scss';
</style>
@@ -0,0 +1,119 @@
<!-- 短信登录 - smsLogin -->
<template>
<view>
<!-- 标题栏 -->
<view class="head-box ss-m-b-60">
<view class="ss-flex ss-m-b-20">
<view class="head-title head-title-line head-title-animation">短信登录</view>
<view class="head-title-active ss-m-r-40" @tap="showAuthModal('accountLogin')">
账号登录
</view>
</view>
<view class="head-subtitle">未注册的手机号验证后自动注册账号</view>
</view>
<!-- 表单项 -->
<uni-forms
ref="smsLoginRef"
v-model="state.model"
:rules="state.rules"
validateTrigger="bind"
labelWidth="140"
labelAlign="center"
>
<uni-forms-item name="mobile" label="手机号">
<uni-easyinput
placeholder="请输入手机号"
v-model="state.model.mobile"
:inputBorder="false"
type="number"
>
<template v-slot:right>
<button
class="ss-reset-button code-btn code-btn-start"
:disabled="state.isMobileEnd"
:class="{ 'code-btn-end': state.isMobileEnd }"
@tap="getSmsCode('smsLogin', state.model.mobile)"
>
{{ getSmsTimer('smsLogin') }}
</button>
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="code" label="验证码">
<uni-easyinput
placeholder="请输入验证码"
v-model="state.model.code"
:inputBorder="false"
type="number"
maxlength="4"
>
<template v-slot:right>
<button class="ss-reset-button login-btn-start" @tap="smsLoginSubmit"> 登录 </button>
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
</view>
</template>
<script setup>
import { ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { code, mobile } from '@/sheep/validate/form';
import { showAuthModal, closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
import AuthUtil from '@/sheep/api/member/auth';
const smsLoginRef = ref(null);
const emits = defineEmits(['onConfirm']);
const props = defineProps({
agreeStatus: {
type: Boolean,
default: false,
},
});
// 数据
const state = reactive({
isMobileEnd: false, // 手机号输入完毕
codeText: '获取验证码',
model: {
mobile: '', // 手机号
code: '', // 验证码
},
rules: {
code,
mobile,
},
});
// 短信登录
async function smsLoginSubmit() {
// 参数校验
const validate = await unref(smsLoginRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
if (!props.agreeStatus) {
emits('onConfirm', true)
sheep.$helper.toast('请勾选同意');
return;
}
// 提交数据
const { code } = await AuthUtil.smsLogin(state.model);
if (code === 0) {
closeAuthModal();
}
}
</script>
<style lang="scss" scoped>
@import '../index.scss';
</style>
+151
View File
@@ -0,0 +1,151 @@
@keyframes title-animation {
0% {
font-size: 32rpx;
}
100% {
font-size: 36rpx;
}
}
.login-wrap {
padding: 50rpx 34rpx;
min-height: 500rpx;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
}
.head-box {
.head-title {
min-width: 160rpx;
font-size: 36rpx;
font-weight: bold;
color: #333333;
line-height: 36rpx;
}
.head-title-active {
width: 160rpx;
font-size: 32rpx;
font-weight: 600;
color: #999;
line-height: 36rpx;
}
.head-title-animation {
animation-name: title-animation;
animation-duration: 0.1s;
animation-timing-function: ease-out;
animation-fill-mode: forwards;
}
.head-title-line {
position: relative;
&::before {
content: '';
width: 1rpx;
height: 34rpx;
background-color: #e4e7ed;
position: absolute;
left: -30rpx;
top: 50%;
transform: translateY(-50%);
}
}
.head-subtitle {
font-size: 26rpx;
font-weight: 400;
color: #afb6c0;
text-align: left;
display: flex;
}
}
// .code-btn[disabled] {
// background-color: #fff;
// }
.code-btn-start {
width: 160rpx;
height: 56rpx;
line-height: normal;
border: 2rpx solid var(--ui-BG-Main);
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 400;
color: var(--ui-BG-Main);
opacity: 1;
}
.forgot-btn {
width: 160rpx;
line-height: 56rpx;
font-size: 30rpx;
font-weight: 500;
color: #999;
}
.login-btn-start {
width: 158rpx;
height: 56rpx;
line-height: normal;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.type-btn {
padding: 20rpx;
margin: 40rpx auto;
width: 200rpx;
font-size: 30rpx;
font-weight: 500;
color: #999999;
}
.auto-login-box {
width: 100%;
.auto-login-btn {
width: 68rpx;
height: 68rpx;
border-radius: 50%;
margin: 0 30rpx;
}
.auto-login-img {
width: 68rpx;
height: 68rpx;
border-radius: 50%;
}
}
.agreement-box {
margin: 80rpx auto 0;
.protocol-check {
transform: scale(0.7);
}
.agreement-text {
font-size: 26rpx;
font-weight: 500;
color: #999999;
.tcp-text {
color: var(--ui-BG-Main);
}
}
}
// 修改密码
.editPwd-btn-box {
.save-btn {
width: 690rpx;
line-height: 70rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.forgot-btn {
width: 690rpx;
line-height: 70rpx;
font-size: 28rpx;
font-weight: 500;
color: #999999;
}
}
@@ -0,0 +1,247 @@
<template>
<!-- 规格弹窗 -->
<su-popup :show="authType !== ''" round="10" :showClose="true" @close="closeAuthModal">
<view class="login-wrap">
<!-- 1. 账号密码登录 accountLogin -->
<account-login
v-if="authType === 'accountLogin'"
:agreeStatus="state.protocol"
@onConfirm="onConfirm"
/>
<!-- 2. 短信登录 smsLogin -->
<sms-login
v-if="authType === 'smsLogin'"
:agreeStatus="state.protocol"
@onConfirm="onConfirm"
/>
<!-- 3. 忘记密码 resetPassword-->
<reset-password v-if="authType === 'resetPassword'" />
<!-- 4. 绑定手机号 changeMobile -->
<change-mobile v-if="authType === 'changeMobile'" />
<!-- 5. 修改密码 changePassword-->
<changePassword v-if="authType === 'changePassword'" />
<!-- 6. 微信小程序授权 -->
<mp-authorization v-if="authType === 'mpAuthorization'" />
<!-- 7. 第三方登录 -->
<view
v-if="['accountLogin', 'smsLogin'].includes(authType)"
class="auto-login-box ss-flex ss-flex-col ss-row-center ss-col-center"
>
<!-- 7.1 微信小程序的快捷登录 -->
<view v-if="sheep.$platform.name === 'WechatMiniProgram'" class="ss-flex register-box">
<view class="register-title">还没有账号?</view>
<button
class="ss-reset-button login-btn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
快捷登录
</button>
<view class="circle" />
</view>
<!-- 7.2 微信的公众号App小程序的登录基于 openid + code -->
<button
v-if="
['WechatOfficialAccount', 'WechatMiniProgram', 'App'].includes(sheep.$platform.name) &&
sheep.$platform.isWechatInstalled
"
@tap="thirdLogin('wechat')"
class="ss-reset-button auto-login-btn"
>
<image
class="auto-login-img"
:src="sheep.$url.static('/static/img/shop/platform/wechat.png')"
/>
</button>
<!-- 7.3 iOS 登录 TODO 等后面搞 App 再弄 -->
<button
v-if="sheep.$platform.os === 'ios' && sheep.$platform.name === 'App'"
@tap="thirdLogin('apple')"
class="ss-reset-button auto-login-btn"
>
<image
class="auto-login-img"
:src="sheep.$url.static('/static/img/shop/platform/apple.png')"
/>
</button>
</view>
<!-- 用户协议的勾选 -->
<view
v-if="['accountLogin', 'smsLogin'].includes(authType)"
class="agreement-box ss-flex ss-row-center"
:class="{ shake: currentProtocol }"
>
<label class="radio ss-flex ss-col-center" @tap="onChange">
<radio
:checked="state.protocol"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onChange"
/>
<view class="agreement-text ss-flex ss-col-center ss-m-l-8">
我已阅读并遵守
<view class="tcp-text" @tap.stop="onProtocol('用户协议')"> 《用户协议》 </view>
<view class="agreement-text">与</view>
<view class="tcp-text" @tap.stop="onProtocol('隐私协议')"> 《隐私协议》 </view>
</view>
</label>
</view>
<view class="safe-box" />
</view>
</su-popup>
</template>
<script setup>
import { computed, reactive, ref } from 'vue';
import sheep from '@/sheep';
import accountLogin from './components/account-login.vue';
import smsLogin from './components/sms-login.vue';
import resetPassword from './components/reset-password.vue';
import changeMobile from './components/change-mobile.vue';
import changePassword from './components/change-password.vue';
import mpAuthorization from './components/mp-authorization.vue';
import { closeAuthModal, showAuthModal } from '@/sheep/hooks/useModal';
const modalStore = sheep.$store('modal');
// 授权弹窗类型
const authType = computed(() => modalStore.auth);
const state = reactive({
protocol: false,
});
const currentProtocol = ref(false);
// 勾选协议
function onChange() {
state.protocol = !state.protocol;
}
// 查看协议
function onProtocol(title) {
closeAuthModal();
sheep.$router.go('/pages/public/richtext', {
title,
});
}
// 点击登录 / 注册事件
function onConfirm(e) {
currentProtocol.value = e;
setTimeout(() => {
currentProtocol.value = false;
}, 1000);
}
// 第三方授权登陆(微信小程序、Apple)
const thirdLogin = async (provider) => {
if (!state.protocol) {
currentProtocol.value = true;
setTimeout(() => {
currentProtocol.value = false;
}, 1000);
sheep.$helper.toast('请勾选同意');
return;
}
const loginRes = await sheep.$platform.useProvider(provider).login();
if (loginRes) {
const userInfo = await sheep.$store('user').getInfo();
closeAuthModal();
// 如果用户已经有头像和昵称,不需要再次授权
if (userInfo.avatar && userInfo.nickname) {
return;
}
// 触发小程序授权信息弹框
// #ifdef MP-WEIXIN
showAuthModal('mpAuthorization');
// #endif
}
};
// 微信小程序的“手机号快速验证”:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html
const getPhoneNumber = async (e) => {
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
sheep.$helper.toast('快捷登录失败');
return;
}
let result = await sheep.$platform.useProvider().mobileLogin(e.detail);
if (result) {
closeAuthModal();
}
};
</script>
<style lang="scss" scoped>
@import './index.scss';
.shake {
animation: shake 0.05s linear 4 alternate;
}
@keyframes shake {
from {
transform: translateX(-10rpx);
}
to {
transform: translateX(10rpx);
}
}
.register-box {
position: relative;
justify-content: center;
.register-btn {
color: #999999;
font-size: 30rpx;
font-weight: 500;
}
.register-title {
color: #999999;
font-size: 30rpx;
font-weight: 400;
margin-right: 24rpx;
}
.or-title {
margin: 0 16rpx;
color: #999999;
font-size: 30rpx;
font-weight: 400;
}
.login-btn {
color: var(--ui-BG-Main);
font-size: 30rpx;
font-weight: 500;
}
.circle {
position: absolute;
right: 0rpx;
top: 18rpx;
width: 8rpx;
height: 8rpx;
border-radius: 8rpx;
background: var(--ui-BG-Main);
}
}
.safe-box {
height: calc(constant(safe-area-inset-bottom) / 5 * 3);
height: calc(env(safe-area-inset-bottom) / 5 * 3);
}
.tcp-text {
color: var(--ui-BG-Main);
}
.agreement-text {
color: $dark-9;
}
</style>
@@ -0,0 +1,83 @@
<template>
<view>
<!-- 基础组件搜索框 -->
<s-search-block v-if="type === 'SearchBar'" :data="data" :styles="styles" :navbar="false" />
<!-- 基础组件公告栏 -->
<s-notice-block v-if="type === 'NoticeBar'" :data="data" />
<!-- 基础组件菜单导航 -->
<s-menu-button v-if="type === 'MenuSwiper'" :data="data" :styles="styles" />
<!-- 基础组件列表导航 -->
<s-menu-list v-if="type === 'MenuList'" :data="data" />
<!-- 基础组件宫格导航 -->
<s-menu-grid v-if="type === 'MenuGrid'" :data="data" :styles="styles" />
<!-- 基础组件弹窗广告 -->
<s-popup-image v-if="type === 'Popover'" :data="data" />
<!-- 基础组件悬浮按钮 -->
<s-float-menu v-if="type === 'FloatingActionButton'" :data="data" />
<!-- 图文组件图片展示 -->
<s-image-block v-if="type === 'ImageBar'" :data="data" :styles="styles" />
<!-- 图文组件图片轮播 -->
<s-image-banner v-if="type === 'Carousel'" :data="data" :styles="styles" />
<!-- 基础组件标题栏 -->
<s-title-block v-if="type === 'TitleBar'" :data="data" :styles="styles" />
<!-- 图文组件广告魔方 -->
<s-image-cube v-if="type === 'MagicCube'" :data="data" :styles="styles" />
<!-- 图文组件视频播放 -->
<s-video-block v-if="type === 'VideoPlayer'" :data="data" :styles="styles" />
<!-- 基础组件分割线 -->
<s-line-block v-if="type === 'Divider'" :data="data" />
<!-- 图文组件热区 -->
<s-hotzone-block v-if="type === 'HotZone'" :data="data" :styles="styles" />
<!-- 商品组件商品卡片 -->
<s-goods-card v-if="type === 'ProductCard'" :data="data" :styles="styles" />
<!-- 商品组件商品栏 -->
<s-goods-shelves v-if="type === 'ProductList'" :data="data" :styles="styles" />
<!-- 营销组件拼团 -->
<s-groupon-block v-if="type === 'PromotionCombination'" :data="data" :styles="styles" />
<!-- 营销组件秒杀 -->
<s-seckill-block v-if="type === 'PromotionSeckill'" :data="data" :styles="styles" />
<!-- 营销组件积分商城 -->
<s-point-block v-if="type === 'PromotionPoint'" :data="data" :styles="styles" />
<!-- 营销组件小程序直播暂时没有这个功能 -->
<s-live-block v-if="type === 'MpLive'" :data="data" :styles="styles" />
<!-- 营销组件优惠券 -->
<s-coupon-block v-if="type === 'CouponCard'" :data="data" :styles="styles" />
<!-- 营销组件文章 -->
<s-richtext-block v-if="type === 'PromotionArticle'" :data="data" :styles="styles" />
<!-- 用户组件用户卡片 -->
<s-user-card v-if="type === 'UserCard'" :data="data" :styles="styles" />
<!-- 用户组件用户订单 -->
<s-order-card v-if="type === 'UserOrder'" :data="data" :styles="styles" />
<!-- 用户组件用户资产 -->
<s-wallet-card v-if="type === 'UserWallet'" :data="data" :styles="styles" />
<!-- 用户组件用户卡券 -->
<s-coupon-card v-if="type === 'UserCoupon'" :data="data" :styles="styles" />
</view>
</template>
<script setup>
/**
* 装修组件 - 组件集
*/
const props = defineProps({
type: {
type: String,
default: '',
},
data: {
type: Object,
default() {},
},
styles: {
type: Object,
default() {},
},
});
function onSearch() {}
</script>
<style></style>
+54
View File
@@ -0,0 +1,54 @@
<!-- 装修组件容器 -->
<template>
<view :style="[elStyles, elBackground]"><slot /></view>
</template>
<script setup>
/**
* 容器组件 - 装修组件的样式容器
*/
import { computed, provide, unref } from 'vue';
import sheep from '@/sheep';
const props = defineProps({
styles: {
type: Object,
default() {},
},
});
// 组件样式
const elBackground = computed(() => {
if (props.styles) {
if (props.styles.bgType === 'color')
return { background: props.styles.bgColor };
if (props.styles.bgType === 'img')
return {
background: `url(${sheep.$url.cdn(
props.styles.bgImage,
)}) no-repeat top center / 100% auto`,
};
}
});
const elStyles = computed(() => {
if (props.styles) {
return {
marginTop: `${props.styles.marginTop || 0}px`,
marginBottom: `${props.styles.marginBottom || 0}px`,
marginLeft: `${props.styles.marginLeft || 0}px`,
marginRight: `${props.styles.marginRight || 0}px`,
paddingTop: `${props.styles.paddingTop || 0}px`,
paddingRight: `${props.styles.paddingRight || 0}px`,
paddingBottom: `${props.styles.paddingBottom || 0}px`,
paddingLeft: `${props.styles.paddingLeft || 0}px`,
borderTopLeftRadius: `${props.styles.borderTopLeftRadius || 0}px`,
borderTopRightRadius: `${props.styles.borderTopRightRadius || 0}px`,
borderBottomRightRadius: `${props.styles.borderBottomRightRadius || 0}px`,
borderBottomLeftRadius: `${props.styles.borderBottomLeftRadius || 0}px`,
overflow: 'hidden',
};
}
});
</script>
@@ -0,0 +1,173 @@
<template>
<view class="time" :style="justifyLeft">
<text class="" v-if="tipText">{{ tipText }}</text>
<text class="styleAll p6" v-if="isDay === true"
:style="{background:bgColor.bgColor,color:bgColor.Color}">{{ day }}{{bgColor.isDay?'天':''}}</text>
<text class="timeTxt" v-if="dayText"
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}">{{ dayText }}</text>
<text class="styleAll" :class='isCol?"timeCol":""'
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width}">{{ hour }}</text>
<text class="timeTxt" v-if="hourText" :class='isCol?"whit":""'
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}">{{ hourText }}</text>
<text class="styleAll" :class='isCol?"timeCol":""'
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width}">{{ minute }}</text>
<text class="timeTxt" v-if="minuteText" :class='isCol?"whit":""'
:style="{width:bgColor.timeTxtwidth,color:bgColor.bgColor}">{{ minuteText }}</text>
<text class="styleAll" :class='isCol?"timeCol":""'
:style="{background:bgColor.bgColor,color:bgColor.Color,width:bgColor.width}">{{ second }}</text>
<text class="timeTxt" v-if="secondText">{{ secondText }}</text>
</view>
</template>
<script>
export default {
name: "countDown",
props: {
justifyLeft: {
type: String,
default: ""
},
//距离开始提示文字
tipText: {
type: String,
default: "倒计时"
},
dayText: {
type: String,
default: "天"
},
hourText: {
type: String,
default: "时"
},
minuteText: {
type: String,
default: "分"
},
secondText: {
type: String,
default: "秒"
},
datatime: {
type: Number,
default: 0
},
isDay: {
type: Boolean,
default: true
},
isCol: {
type: Boolean,
default: false
},
bgColor: {
type: Object,
default: null
}
},
data: function() {
return {
day: "00",
hour: "00",
minute: "00",
second: "00"
};
},
created: function() {
this.show_time();
},
mounted: function() {},
methods: {
show_time: function() {
let that = this;
function runTime() {
//时间函数
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差;
let day = 0,
hour = 0,
minute = 0,
second = 0;
if (intDiff > 0) {
//转换时间
if (that.isDay === true) {
day = Math.floor(intDiff / (60 * 60 * 24));
} else {
day = 0;
}
hour = Math.floor(intDiff / (60 * 60)) - day * 24;
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
second =
Math.floor(intDiff) -
day * 24 * 60 * 60 -
hour * 60 * 60 -
minute * 60;
if (hour <= 9) hour = "0" + hour;
if (minute <= 9) minute = "0" + minute;
if (second <= 9) second = "0" + second;
that.day = day;
that.hour = hour;
that.minute = minute;
that.second = second;
} else {
that.day = "00";
that.hour = "00";
that.minute = "00";
that.second = "00";
}
}
runTime();
setInterval(runTime, 1000);
}
}
};
</script>
<style scoped>
.p6 {
padding: 0 8rpx;
}
.styleAll {
/* color: #fff; */
font-size: 24rpx;
height: 36rpx;
line-height: 36rpx;
border-radius: 6rpx;
text-align: center;
/* padding: 0 6rpx; */
}
.timeTxt {
text-align: center;
/* width: 16rpx; */
height: 36rpx;
line-height: 36rpx;
display: inline-block;
}
.whit {
color: #fff !important;
}
.time {
display: flex;
justify-content: center;
}
.red {
color: #fc4141;
margin: 0 4rpx;
}
.timeCol {
/* width: 40rpx;
height: 40rpx;
line-height: 40rpx;
text-align:center;
border-radius: 6px;
background: #fff;
font-size: 24rpx; */
color: #E93323;
}
</style>

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