Browse Source

feat: 登录注册/个人中心

xiongyubin 2 tháng trước cách đây
mục cha
commit
2d1c02c33c

+ 21 - 1
common/config.js

@@ -104,7 +104,27 @@ var config = {
 	API_CABINET_BLUETOOTH_RETURN_SUCCESS: api_web_url + '?r=cabinet/bluetooth-return-success',
 
 	//发送指令
-	API_CAR_SEND_COMMAND: api_web_url + '?r=dayhire/car/send-car-command'
+	API_CAR_SEND_COMMAND: api_web_url + '?r=dayhire/car/send-car-command',
+
+
+
+	// 登录
+	API_LOGIN: api_web_url + '?r=flk/account/login',
+
+	// 注册
+	API_REGISTER_EMAIL: api_web_url + '?r=flk/account/register-email',
+
+	// 找回密码
+	API_RESET_PASSWORD: api_web_url + '?r=flk/account/reset-password',
+
+	// 消息列表
+	API_MESSAGE_LIST: api_web_url + '?r=flk/message/list',
+
+	// 消息详情
+	API_MESSAGE_DTL: api_web_url + '?r=flk/message/info',
+
+	// 设备消息
+	API_DEVICE_MSG: api_web_url + '?r=flk/message/device-list',
 
 };
 module.exports = config;

+ 2 - 0
common/constant.js

@@ -1,5 +1,7 @@
 
 export const QINIU_URL = 'https://qiniu.bms16.com/';
+export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+export const defaultHeadImg = `${QINIU_URL}/FpzmRTePsa2QSxemAbc-xWdzSsn1`
 
 // 租赁周期
 export const LEASE_TYPE = {

+ 15 - 15
common/http.js

@@ -1,4 +1,7 @@
 // import common = require("./common");
+const appid = '2025014122441122'
+
+const language = 'en'
 
 var common = require('./common.js');
 
@@ -103,6 +106,12 @@ function postRequest(url, data, successCallBack, failCallBack) {
 		method: 'POST',
 		success: function(res) {
 			//wx.hideLoading();
+			if (res.data.code == 200) {
+				res.succeed = true
+				res.body = res.data
+			} else {
+				uni.showToast({ title: res.data.msg, icon: 'none' })
+			}
 			if (_checkTokenValid(res)) {
 				successCallBack(res);
 			}
@@ -157,13 +166,9 @@ function getApi(url, data, successCallBack, failCallBack) {
 	var token = storage.getUserToken(); //wx.getStorageSync(config.STORAGE_USER_TOKEN)
 	data.token = token;
 	data.v = config.APP_VERSION;
-	data.appid =  uni.getAccountInfoSync().miniProgram.appId;
-	//#ifdef MP-ALIPAY
+	data.appid =  appid
 	data.from = 'ali'
-	//#endif
-	//#ifdef MP-WEIXIN
-	const _from = 'wx'
-	//#endif
+	data.language = language
 	getRequest(url, data, successCallBack, failCallBack);
 }
 
@@ -171,13 +176,9 @@ function postApi(url, data, successCallBack, failCallBack) {
 	var token = storage.getUserToken(); // wx.getStorageSync(config.STORAGE_USER_TOKEN)
 	data.token = token;
 	data.v = config.APP_VERSION;
-	data.appid =  uni.getAccountInfoSync().miniProgram.appId;
-	//#ifdef MP-ALIPAY
+	data.appid = appid
 	data.from = 'ali'
-	//#endif
-	//#ifdef MP-WEIXIN
-	const _from = 'wx'
-	//#endif
+	data.language = language
 	postRequest(url, data, successCallBack, failCallBack);
 }
 /**
@@ -188,15 +189,14 @@ function reportFormId(formId) {
 	const accountInfo = uni.getAccountInfoSync();
 	var postData = {
 		formId: formId,
-		appid: uni.getAccountInfoSync().miniProgram.appId
+		appid
 	};
 	postApi(config.API_FORMID_REPORT, postData, function() {});
 }
 
 function getAppConfig(successCallBack) {
-	var appId =  uni.getAccountInfoSync().miniProgram.appId;
 	const pData = {
-		appid: appId,
+		appid,
 		terminal: 'wx_app'
 	};
 	getApi(config.API_INDEX_APP_CONFIG, pData, function(resp) {

+ 8 - 8
common/params.js

@@ -1,15 +1,15 @@
 module.exports = {
 	jybms_url: 'https://zd2.jybms.cn/',
 
-	api_web_url: 'https://zx.uwenya.cc/new_energy/server/api/web/',
-	up_img_url: 'https://zx.uwenya.cc/new_energy/server/api/web/',
-	gps_url: 'https://zx.uwenya.cc/new_energy/server/gps/web/',
-	map_url: 'https://map.bms16.com/',
+	// api_web_url: 'https://zx.uwenya.cc/new_energy/server/api/web/',
+	// up_img_url: 'https://zx.uwenya.cc/new_energy/server/api/web/',
+	// gps_url: 'https://zx.uwenya.cc/new_energy/server/gps/web/',
+	// map_url: 'https://map.bms16.com/',
 
-	// api_web_url: 'https://dev.bms16.com/new_energy/server/api/web/',
-	// up_img_url: 'https://dev.bms16.com/new_energy/server/api/web/',
-	// gps_url: 'https://dev.bms16.com/new_energy/server/api/web/',
-	// map_url: 'https://mapdev.bms16.com/'
+	api_web_url: 'https://dev.bms16.com/new_energy/server/api/web/',
+	up_img_url: 'https://dev.bms16.com/new_energy/server/api/web/',
+	gps_url: 'https://dev.bms16.com/new_energy/server/api/web/',
+	map_url: 'https://mapdev.bms16.com/'
 
 	// 预发布
 	// api_web_url: 'https://pre.bms16.com/new_energy/server/api/web/',

+ 156 - 0
component/comPopup/CenterDialog.vue

@@ -0,0 +1,156 @@
+<template>
+    <view class="mask" v-if="visible">
+        <view class="dialog">
+            <view class="title">{{ dialogInfo.title }}</view>
+            <view class="content">
+                <rich-text :nodes="dialogInfo.content"></rich-text>
+            </view>
+            <view class="options">
+                <view class="btn cancle" @tap="visible = false">{{ cancelText }}</view>
+                <view class="btn confirm" @tap="confirm">{{ confirmText }}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+export default {
+    props: {
+        cancelText: {
+            type: String,
+            default: '取消'
+        },
+        confirmText: {
+            type: String,
+            default: '确定'
+        }
+    },
+    data() {
+        return {
+            visible: false,
+            dialogInfo: {
+                title: '',
+                content: ''
+            }
+        }
+    },
+    methods: {
+        open(info) {
+            this.visible = true
+            this.dialogInfo = info
+        },
+        confirm() {
+            this.visible = false
+            this.$emit('confirm')
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    &-enter-active,
+    &-leave-active {
+        transition: opacity 0.3s ease;
+    }
+
+    &-enter,
+    &-leave-to {
+        opacity: 0;
+    }
+}
+
+.dialog {
+    padding: 48rpx 48rpx 0;
+    width: 568rpx;
+    min-height: 354rpx;
+    background: #FFFFFF;
+    border-radius: 40rpx;
+    animation: fadeIn 0.3s ease;
+    position: relative;
+    padding-bottom: 80px;
+
+    .title {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: bold;
+        font-size: 32rpx;
+        color: #000;
+        text-align: center;
+        margin-bottom: 20rpx;
+    }
+
+    .content {
+        font-weight: 400;
+        font-size: 28rpx;
+        color: #666666;
+        line-height: 38rpx;
+    }
+
+    .options {
+        background: #fff;
+        border-bottom-left-radius: 40rpx;
+        border-bottom-right-radius: 40rpx;
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        z-index: 10;
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+        border-top: 2rpx solid #EAEAEA;
+
+        &::after {
+            content: "";
+            position: absolute;
+            left: 50%;
+            top: 0;
+            height: 100%;
+            width: 2rpx;
+            background: #EAEAEA;
+        }
+
+        .btn {
+            flex: 1;
+            text-align: center;
+            padding: 32rpx 0;
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 32rpx;
+            &:active {
+                background: #EAEAEA;
+            }
+            &.cancle {
+                border-bottom-left-radius: 40rpx;
+                color: #191D23;
+            }
+            
+            &.confirm {
+                border-bottom-right-radius: 40rpx;
+                color: #0A59F7;
+            }
+        }
+    }
+}
+
+@keyframes fadeIn {
+    from {
+        transform: scale(0.8);
+        opacity: 0;
+    }
+
+    to {
+        transform: scale(1);
+        opacity: 1;
+    }
+}
+</style>

+ 101 - 0
component/comPopup/Notice.vue

@@ -0,0 +1,101 @@
+<template>
+    <u-popup v-model="showDialog" mode="bottom" border-radius="28" @close="close">
+        <view class="dialog-content">
+            <image :src="QINIU_URL + 'Fqb-i2uJWlZOg8EvUXHr_1qhlndf'" class="icon"/>
+            <view class="title">{{ title }}</view>
+            <view class="tips">
+                <rich-text :nodes="text"></rich-text>
+            </view>
+            <view class="btn" @tap="close">{{ btnText }}</view>
+        </view>
+    </u-popup>
+</template>
+
+<script>
+import { QINIU_URL } from '@/common/constant'
+export default {
+    props: {
+        value: {
+            type: Boolean,
+            default: false
+        },
+        title: {
+            type: String,
+            default: ''
+        },
+        text: {
+            type: String,
+            default: ''
+        },
+        btnText: {
+            type: String,
+            default: '确定'
+        }
+    },
+    data() {
+        return {
+            QINIU_URL,
+            showDialog: this.value
+        }
+    },
+    watch: {
+        value(newValue) {
+            this.showDialog = newValue
+        }
+    },
+    methods: {
+        close() {
+            this.$emit('input', false)
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.dialog-content {
+    width: 100%;
+    height: 100%;
+    background: #F1F3F4;
+    padding: 64rpx 32rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    .icon {
+        width: 88rpx;
+        height: 88rpx;
+    }
+    .title {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 600;
+        font-size: 40rpx;
+        color: #060809;
+        margin: 32rpx 0 24rpx;
+    }
+    .tips {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 28rpx;
+        color: #8B939C;
+        line-height: 42rpx;
+        text-align: center;
+        padding: 0 80rpx;
+    } 
+    .btn {
+        margin-top: 68rpx;
+        width: 100%;
+        height: 80rpx;
+        background: #060809;
+        border-radius: 40rpx;
+        color: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 500;
+        font-size: 32rpx;
+        &:active {
+            opacity: 0.8;
+        }
+    }
+}
+</style></style>

+ 33 - 0
libs/css/layout.scss

@@ -0,0 +1,33 @@
+@import "./variables.scss";
+
+@keyframes shake {
+    0% { margin-left: 0; }
+    25% { margin-left: 20rpx; }
+    75% { margin-left: -20rpx; }
+    100% { margin-left: 0; }
+  }
+
+.zx-page-linear {
+    width: 100%;
+    min-height: 100vh;
+    background: linear-gradient(180deg, #CFD1DE 0%, #F1F3F4 100%), #F1F3F4;
+    background-size: 100% 528rpx;
+    background-repeat: no-repeat;
+}
+.zx-form-btn {
+  width: 100%;
+  text-align: center;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: #C2C4C5;
+  border-radius: 44rpx;
+  font-family: PingFangSC, PingFang SC;
+  font-size: 32rpx;
+  color: #FFFFFF;
+  &:active {
+    opacity: .7;
+  }
+  &.is-submit {
+      background: #000;
+  }
+}

+ 1 - 0
libs/css/variables.scss

@@ -0,0 +1 @@
+$baseBgColor: #F1F3F4;

+ 61 - 0
pages.json

@@ -17,6 +17,67 @@
 				"titleNView": false
 			}
 		},
+		{
+			"path": "pages/loginRegister/login",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "登录"
+			}
+		},
+		{
+			"path": "pages/loginRegister/forgetPassword",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "忘记密码"
+			}
+		},
+		{
+			"path": "pages/loginRegister/register",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "账号注册"
+			}
+		},
+		{
+			"path": "pages/loginRegister/changePassword",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "修改密码"
+			}
+		},
+		{
+			"path": "pages/my/set",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "设置"
+			}
+		},
+		{
+			"path": "pages/message/index",
+			"style": {
+				"titleNView": false
+			}
+		},
+		{
+			"path": "pages/message/deviceInfo",
+			"style": {
+				"titleNView": false
+			}
+		},
+		{
+			"path": "pages/bluetoothUnlock/bluetoothPair",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "开启感应解锁"
+			}
+		},
+		{
+			"path": "pages/bluetoothUnlock/unlockSet",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "开启感应解锁"
+			}
+		},
 		{
 			"path": "pages/aboutMy/aboutMy",
 			"style": {

+ 167 - 0
pages/bluetoothUnlock/bluetoothPair.vue

@@ -0,0 +1,167 @@
+<template>
+    <view class="bluetoothPair-page">
+        <view class="car-wrap">
+            <view class="name">智能电动摩托车智驾 M7</view>
+            <image :src="QINIU_URL + 'Fi6CPKj4-raA86oizhL3PiXD4DkH'" class="car-img" />
+            <view>
+                <viwe class="pair-title">是否配对改设备</viwe>
+                <view class="pair-options">
+                    <view class="btn cancle">取消</view>
+                    <view class="btn confirm">配对</view>
+                </view>
+            </view>
+        </view>
+        <view class="pair-desc">
+            点击下方 “ <text class="t">开始配对</text>”,请在系统弹窗中选择 “<text class="t">配对</text>”,以完成功能开启。
+        </view>
+        <view class="pair-tips">
+            <view class="title">提示信息</view>
+            <view class="text">使用感应解锁功能时,请务必保证手机蓝牙功能开启,在蓝牙列表中忽略此设备会导致感应解锁失败</view>
+        </view>
+
+        <viwe class="pair-btn" @tap="initiateBluetoothPairing">开始配对</viwe>
+        <CenterDialog confirmText="配对" ref="centerDialog" />
+        <Notice 
+            v-model="showNotice" 
+            title="感应解锁已开启" 
+            btnText="关闭"
+            text='注意:请勿在手机蓝牙中忽略“电动车蓝牙02"设备,否则感应解锁功能将无法使用'
+        />
+    </view>
+</template>
+
+<script>
+import { QINIU_URL } from '@/common/constant'
+import CenterDialog from '@/component/comPopup/CenterDialog.vue';
+import Notice from '@/component/comPopup/Notice.vue';
+export default {
+    components: {
+        CenterDialog,
+        Notice
+    },
+    data() {
+        return {
+            QINIU_URL,
+            showNotice: false
+        }
+    },
+    methods: {
+        initiateBluetoothPairing() {
+            const deviceName = '电动车蓝牙';
+            const pairingCode = '1234567890';
+            this.$refs.centerDialog.open({
+                title: '蓝牙配对请求',
+                content: `<span style="color:#060809;font-weight:bold">${deviceName}</span>
+                想与您配对,请确保显示的配对密钥为
+                <span style="color:#060809;font-weight:bold">${pairingCode}</span>`
+            });
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.bluetoothPair-page {
+    padding: 56rpx 32rpx 48rpx;
+
+    .car-wrap {
+        width: 100%;
+        background: #F1F3F4;
+        border-radius: 32rpx;
+        padding: 48rpx 32rpx 32rpx;
+        text-align: center;
+
+        .name {
+            color: #060809;
+            font-size: 32rpx;
+            font-weight: bold;
+        }
+
+        .car-img {
+            width: 480rpx;
+            height: 324rpx;
+            margin: 48rpx auto;
+            display: block;
+        }
+
+        .pair-title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #060809;
+        }
+
+        .pair-options {
+            margin-top: 30rpx;
+            display: flex;
+            justify-content: space-between;
+
+            .btn {
+                width: 302rpx;
+                height: 80rpx;
+                border-radius: 40rpx;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-size: 32rpx;
+
+                &.cancle {
+                    color: #060809;
+                    background: #E4E8E9;
+                }
+
+                &.confirm {
+                    color: #FFFFFF;
+                    background: #060809;
+                }
+            }
+        }
+    }
+
+    .pair-desc {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 24rpx;
+        color: #828DA2;
+        text-align: left;
+        margin: 24rpx 0 48rpx;
+
+        .t {
+            color: #060809;
+        }
+    }
+
+    .pair-tips {
+        .title {
+            font-weight: 500;
+            font-size: 32rpx;
+            color: #060809;
+            margin-bottom: 20rpx;
+        }
+
+        .text {
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #828DA2;
+        }
+    }
+
+    .pair-btn {
+        position: absolute;
+        width: 91%;
+        bottom: 48rpx;
+        height: 80rpx;
+        background: #060809;
+        border-radius: 40rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 32rpx;
+        color: #FFFFFF;
+
+        &:active {
+            opacity: .7;
+        }
+    }
+}
+</style>

+ 93 - 0
pages/bluetoothUnlock/components/UnlockNotification.vue

@@ -0,0 +1,93 @@
+<template>
+    <u-popup v-model="showDialog" mode="bottom" border-radius="28" @close="close">
+        <view class="dialog-content">
+            <view class="title">清先开启位置授权</view>
+            <view class="tips">为保证 感应解锁 的正常使用,请 开启位置授权 并 打开精准位置 开关,可提高解锁成功率,且不会明显增加手机电量消耗。</view>
+            <view class="authorization-wrap">
+                <!-- <view class="corner-mark">授权引导</view> -->
+                 <view class="step-item">
+                    
+                 </view>
+            </view>
+            <view class="btn" @tap="close">我已开启</view>
+        </view>
+    </u-popup>
+</template>
+
+<script>
+export default {
+    props: {
+        value: {
+            type: Boolean,
+            default: false
+        }
+    },
+    data() {
+        return {
+            showDialog: this.value
+        }
+    },
+    watch: {
+        value(newValue) {
+            this.showDialog = newValue
+        }
+    },
+    methods: {
+        close() {
+            this.$emit('input', false)
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.dialog-content {
+    width: 100%;
+    height: 100%;
+    background: #F1F3F4;
+    padding: 40rpx 32rpx;
+    .title {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 600;
+        font-size: 40rpx;
+        color: #060809;
+    }
+    .tips {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 24rpx;
+        color: #828DA2;
+        line-height: 40rpx;
+        margin: 32rpx 0 40rpx;
+    } 
+    .authorization-wrap {
+        background: #fff;
+        width: 100%;
+        height: 728rpx;
+        width: 100%;
+        border-radius: 40rpx;
+        padding: 30rpx 24rpx;
+        position: relative;
+        .corner-mark {
+
+        }
+    }
+    .btn {
+        margin-top: 40rpx;
+        width: 100%;
+        height: 80rpx;
+        background: #060809;
+        border-radius: 40rpx;
+        color: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 500;
+        font-size: 32rpx;
+        &:active {
+            opacity: 0.8;
+        }
+    }
+}
+</style>

+ 149 - 0
pages/bluetoothUnlock/unlockSet.vue

@@ -0,0 +1,149 @@
+<template>
+    <view class="unlockSet-page">
+        <view class="lock-img"></view>
+        <view class="main">
+            <view class="primary-text">开启感应解锁后,携带手机靠近车辆并按Auto 键即可开机</view>
+            <view class="car-option-wrap">
+                <view class="img"></view>
+                <view class="opt-text-wrap">
+                    <view class="text">{{ $t('按键解锁') }}</view>
+                    <view class="sub-text">手机接近车辆后,长按Auto键即可解锁</view>
+                </view>
+            </view>
+            <view class="sensitivity-set" @tap="showSensitivityDialog = true">
+                <view class="txt">{{ $t('灵敏度设置') }}</view>
+            </view>
+        </view>
+        <view class="switch-btn">{{ $t('关闭感应解锁') }}</view>
+
+        <u-popup v-model="showSensitivityDialog" mode="bottom" border-radius="28" @close="close">
+            <view class="popup-content">
+                <view class="title">灵敏度设置</view>
+                <view class="text">距离建议适中,设置过近会降低成功率,设置</view>
+                <u-slider v-model="sensitivityValue"></u-slider>
+
+            </view>
+        </u-popup>
+    </view>
+</template>
+
+<script>
+export default {
+    data () {
+        return {
+            showSensitivityDialog: true,
+            sensitivityValue: 50
+        }
+    },
+    methods: {
+        close () {
+            this.showSensitivityDialog = false
+        }
+    }
+}
+</script>
+
+
+<style lang="scss" scoped>
+.unlockSet-page {
+    background: #fff;
+    min-height: 100vh;
+    .lock-img {
+        width: 100%;
+        height: 400rpx;
+        background: url('https://qiniu.bms16.com/Fi7jWl2Uf5zBsZHd77SK0RSGXiWr');
+        background-size: 100%;
+    }
+    .main {
+        padding: 0 40rpx;
+        .primary-text {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: bold;
+            font-size: 36rpx;
+            color: #060809;
+        }
+        .car-option-wrap {
+            border-radius: 40rpx;
+            margin: 40rpx 0 24rpx;
+            .img {
+                width: 100%;
+                height: 316rpx;
+                background: url('https://qiniu.bms16.com/FsscWX4rYSUO_RxdlCqHUPqYqXu1');
+                background-size: 100%;
+            }
+            .opt-text-wrap {
+                background: #DCF4FF;
+                border-radius: 0rpx 0rpx 40rpx 40rpx;
+                padding: 32rpx 0;
+                text-align: center;
+                .text {
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: bold;
+                    font-size: 32rpx;
+                    color: #060809;
+                    margin-bottom: 16rpx;
+                }
+                .sub-text {
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: 500;
+                    font-size: 24rpx;
+                    color: 5C676B;
+                }
+            }
+        }
+        .sensitivity-set {
+            width: 100%;
+            padding: 40rpx 32rpx;
+            background: #F1F3F4;
+            border-radius: 40rpx;
+            .txt {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: bold;
+                font-size: 32rpx;
+                color: #060809;
+                text-align: left;
+                font-style: normal;
+                display: flex;
+                align-items: center;
+                &::before {
+                    content: "";
+                    width: 64rpx;
+                    height: 64rpx;
+                    margin-right: 16rpx;
+                    background: url('https://qiniu.bms16.com/Fo-sYIixqyae6oTXXyq3RAruvosJ');
+                    background-size: 100%;
+                }
+            }
+        }
+    }
+    .switch-btn {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 32rpx;
+        color: #FA2918;
+        position: absolute;
+        bottom: 64rpx;
+        left: 50%;
+        transform: translateX(-50%);
+        &:active {
+            opacity: .6;
+        }
+    }
+    .popup-content {
+        padding: 40rpx 32rpx 164rpx;
+        .title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: bold;
+            font-size: 40rpx;
+            color: #060809;
+        }
+        .text {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #828DA2;
+            margin: 32rpx 0 40rpx;
+        }
+    }
+}
+</style>

+ 73 - 0
pages/loginRegister/changePassword.vue

@@ -0,0 +1,73 @@
+<template>
+    <view class="changePassword-page">
+        <ZxInput
+            v-model="form.oldPassword"
+            :type="passwordType"
+            :placeholder="$t('请输入旧密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('oldPassword')"
+            @blur="handleFocusOrBlur('oldPassword', false)"
+            @update:type="passwordType = $event"
+        />
+        <ZxInput
+            v-model="form.password"
+            :type="passwordType"
+            :placeholder="$t('请输入新密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('oldPassword')"
+            @blur="handleFocusOrBlur('oldPassword', false)"
+            @update:type="passwordType = $event"
+        />
+        <ZxInput
+            v-model="form.passwordAgain"
+            :type="passwordType"
+            :placeholder="$t('请再次输入新密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('oldPassword')"
+            @blur="handleFocusOrBlur('oldPassword', false)"
+            @update:type="passwordType = $event"
+        />
+   
+        <view :class="['zx-form-btn', isSubmit && 'is-submit']" style="margin-top: 64rpx;" @tap="changePassword">
+            {{ $t('确认修改') }}
+        </view>
+    </view>
+</template>
+
+
+<script>
+import ZxInput from './components/ZxInput.vue'
+export default {
+    data() {
+        return {
+            passwordType: 'password',
+            form: {
+
+            }
+        }
+    },
+    components: {
+        ZxInput
+    },
+    computed: {
+        isSubmit({ form }) {
+            return form.oldPassword && form.password && form.passwordAgain
+        }
+    },
+    methods: {
+        changePassword() {
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+ @import "@/libs/css/layout.scss";
+
+.changePassword-page {
+    padding: 58rpx 32rpx;
+    min-height: 100vh;
+    width: 100%;
+    background: #F1F3F4;
+}
+</style>

+ 140 - 0
pages/loginRegister/components/ZxInput.vue

@@ -0,0 +1,140 @@
+<template>
+    <view :class="['input-item', isFocus && 'is-focus', hightlight && 'hightlight']">
+      <input
+        v-model="inputValue"
+        :type="localType"
+        :placeholder="placeholder"
+        placeholder-class="input-placeholder"
+        @focus="handleFocus"
+        @blur="handleBlur"
+      >
+      <view v-if="isFocus && inputValue && !showEyeIcon" class="input-icon clear-icon" @tap="clearInput" />
+      <view
+        v-if="isFocus && inputValue && showEyeIcon"
+        :class="['input-icon', 'eye-icon', localType === 'text' && 'show']"
+        @tap="togglePasswordType"
+      />
+    </view>
+  </template>
+  
+  <script>
+  export default {
+    props: {
+      value: {
+        type: String,
+        default: ''
+      },
+      type: {
+        type: String,
+        default: 'text'
+      },
+      inputType: {
+        type: String,
+        default: ''
+      },
+      placeholder: {
+        type: String,
+        default: ''
+      },
+      showEyeIcon: {
+        type: Boolean,
+        default: false
+      },
+      hightlight: {
+        type: Boolean,
+        default: false
+      }
+    },
+    data() {
+      return {
+        isFocus: false,
+        inputValue: this.value,
+        localType: this.type
+      };
+    },
+    watch: {
+      inputValue(val) {
+        this.$emit('input', val);
+      },
+      type(newVal) {
+        this.localType = newVal;
+      }
+    },
+    methods: {
+      handleFocus() {
+        this.isFocus = true;
+        this.$emit('focus');
+      },
+      handleBlur() {
+        this.isFocus = false;
+        this.$emit('blur');
+      },
+      clearInput() {
+        this.inputValue = '';
+        this.$emit('input', '');
+      },
+      togglePasswordType() {
+        this.localType = this.localType === 'text' ? 'password' : 'text';
+        this.$emit('update:type', this.localType);
+      }
+    }
+  };
+  </script>
+  
+  <style lang="scss" scoped>
+  @import "@/libs/css/layout.scss";
+  
+.input-item {
+  position: relative;
+  width: 100%;
+  height: 112rpx;
+  background: #FFFFFF;
+  border-radius: 24rpx;
+  margin-bottom: 32rpx;
+  padding: 0 32rpx;
+  box-sizing: border-box;
+  border: 4rpx solid transparent;
+  input {
+      height: 100%;
+      font-weight: 500;
+      font-size: 36rpx;
+      color: #060809;
+  }
+  /deep/ .input-placeholder {
+      font-size: 32rpx;
+      color: #BAC1D0;
+  }
+  &.is-focus {
+      border: 4rpx solid #060809;
+  }
+  &.hightlight {
+      border-color: #f85658;
+      background: rgba(248, 86, 88, .1);
+      input {
+        color: #f85658;
+      }
+  }
+  .input-icon {
+      position: absolute;
+      width: 40rpx;
+      height: 40rpx;
+      top: 36rpx;
+      right: 32rpx;
+      z-index: 10;
+      pad: 30rpx;
+  }
+  .clear-icon {
+      background: url('https://qiniu.bms16.com/FhKzwftEPo70kloqIVxKH7g0pD6I');
+      background-size: 100%;
+  }
+  .eye-icon {
+      background: url('https://qiniu.bms16.com/FhGVSXLjP7rcS1eC-I2cD3eIXB6V');
+      background-size: 100%;
+      &.show {
+          background: url('https://qiniu.bms16.com/FmYDP5QlyBeKER3jqrH12rfZrBRc');
+          background-size: 100%;
+      }
+  }
+}
+
+  </style>

+ 75 - 0
pages/loginRegister/forgetPassword.vue

@@ -0,0 +1,75 @@
+<template>
+    <view class="forgetPassword-page">
+        <zx-input
+            v-model="email"
+            :placeholder="$t('请输入要重置的邮箱账号')"
+            @focus="handleFocusOrBlur(true)"
+            @blur="handleFocusOrBlur( false)"
+        />
+        <view :class="['zx-form-btn', email && 'is-submit']" style="margin-top: 48rpx;"  @tap="resetHandle">
+            {{ $t('重置密码') }}
+        </view>
+        <NoticeDialog
+            :title="$t('重置密码邮件已发送')"
+            :btnText="$t('我知道了')"
+            :text="noticeText"
+            v-model="isSendSucceed"
+        />
+    </view>
+</template>
+
+<script>
+import { emailRegex } from '@/common/constant'
+import NoticeDialog from '@/component/comPopup/Notice.vue'
+import ZxInput from './components/ZxInput.vue'
+const http = require('@/common/http.js');
+const config = require('@/common/config.js');
+const common = require('@/common/common.js');
+export default {
+    components: {
+        ZxInput,
+        NoticeDialog
+    },
+    data() {
+        return {
+            isSendSucceed: false,
+            emailFocus: false,
+            email: ''
+        }
+    },
+    computed: {
+        noticeText({ email }) {
+            return `我们向 <span style="color: #0A59F7;">${email}</span> 发送了一封密码重置邮件,请您登录邮箱操作处理。`
+        }
+    },
+    methods: {
+        handleFocusOrBlur(show) {
+            this.emailFocus = show
+        },
+        resetHandle() {
+            if (!emailRegex.test(this.email)) {
+                uni.showToast({ title: this.$t('请输入有效的邮箱地址'), icon: 'none' })
+                return
+            }
+            http.postApi(config.API_RESET_PASSWORD, { email: this.email }, res => {
+                if (res.succeed) {
+                    this.isSendSucceed = true
+                } else {
+                }
+                uni.showToast({ title: res.data.msg, icon: 'none' })
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/libs/css/layout.scss";
+
+.forgetPassword-page {
+    width: 100%;
+    background: #F1F3F4;
+    min-height: 100vh;
+    padding: 32rpx;
+}
+</style>

+ 284 - 0
pages/loginRegister/login.vue

@@ -0,0 +1,284 @@
+<template>
+    <view class="zx-page-linear login-page">
+      <view class="title-wrap">
+        <view class="title">{{ $t('欢迎来到智寻出行') }}</view>
+        <view class="sub-title">{{ $t('邮箱密码登录') }}</view>
+      </view>
+      <view class="main">
+        <ZxInput
+            v-model="form.email"
+            :hightlight="checkShakeObj.email"
+            :placeholder="$t('请输入邮箱账号')"
+            @focus="handleFocusOrBlur('email')"
+            @blur="handleFocusOrBlur('email', false)"
+        />
+        <ZxInput
+            v-model="form.passwd"
+            :type="passwordType"
+            :placeholder="$t('请输入密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('password')"
+            @blur="handleFocusOrBlur('password', false)"
+            @update:type="passwordType = $event"
+        />
+        <view 
+          :class="['agreement-row', checkShakeObj.agreemen && 'shake']" 
+          @tap="isCheckAgreement = !isCheckAgreement"
+        >
+          <view :class="['checkbox', isCheckAgreement && 'is-checked']"/>
+          {{ $t('已阅读并同意') }}
+          <text class="text" @tap.stop="handleAgreementLink('270')">《{{$t('用户协议')}}》</text>
+          {{ $t('和') }}<text class="text" @tap.stop="handleAgreementLink('102')">《{{$t('隐私政策')}}》</text>
+        </view>
+        <view
+          :class="['zx-form-btn', isSubmt && 'is-submit']"
+          @tap="loginHandle"
+        >
+          {{ $t('登录') }}
+        </view>
+        <view class="register-row">
+          <view class="forget" @tap="routerLink('/pages/loginRegister/forgetPassword')">
+            {{ $t('忘记密码') }}
+          </view>
+          <view class="split-line"/>
+          <view class="register" @tap="routerLink('/pages/loginRegister/register')">
+            {{ $t('没有账号 立即注册') }}
+          </view>
+        </view>
+        <view class="other-type-login">
+          <view class="title">{{ $t('其他方式登录') }}</view>
+          <view class="types">
+            <image :src="QINIU_URL + 'Fg14B6UDuR6pLD1uR10mBE_y2vbf'" class="icon" />
+            <image :src="QINIU_URL + 'FlZRWQZ301H8rP2_LwUdjnSUTQop'" class="icon" />
+          </view>
+        </view>
+      </view>
+    </view>
+  </template>
+  
+  <script>
+  import { QINIU_URL, emailRegex } from '@/common/constant'
+  import ZxInput from './components/ZxInput.vue'
+	const config = require('@/common/config.js');
+	const http = require('@/common/http.js');
+  const common = require('@/common/common.js');
+	const storage = require('@/common/storage.js');
+  export default {
+    data() {
+      return {
+        QINIU_URL,
+        passwordType: 'password',
+        emailFocus: false,
+        passwordFocus: false,
+        isCheckAgreement: false,
+        checkShakeObj: {
+          email: false,
+          password: false,
+          agreemen: false
+        },
+        form: {
+          email: '',
+          passwd: ''
+        }
+      }
+    },
+    components: {
+      ZxInput
+    },
+    computed: {
+      isSubmt({ form }) {
+        return form.email && form.passwd
+      }
+    },
+    methods: {
+      handleFocusOrBlur(val, flag = true) {
+        this[`${val}Focus`] = flag
+      },
+      clearInput(val) {
+        this.$set(this.form, val, '')
+      },
+      eyeTypeChange() {
+        this.passwordType = this.passwordType === 'text' ? 'password' : 'text';
+      },
+      routerLink(url) {
+        uni.navigateTo({ url })
+      },
+      handleAgreementLink(id) {
+				uni.navigateTo({
+					url: `/pages/contract/contract?contract_id=${id}`
+				})
+			},
+      _applyCheck(field, message) {
+        this.checkShakeObj[field] = true;
+        setTimeout(() => {
+          this.checkShakeObj[field] = false;
+        }, 500)
+        uni.showToast({ title: message, icon: 'none' })
+      },
+      loginHandle() {
+        if (!this.isCheckAgreement) {
+          this._applyCheck('agreemen', this.$t('请勾选用户协议和隐私政策'))
+          return
+        }
+        if (!emailRegex.test(this.form.email)) {
+          this._applyCheck('email', this.$t('请输入有效的邮箱地址'))
+          return
+        }
+        http.postApi(config.API_LOGIN, this.form, res => {
+          if (res.succeed) {
+            // 
+            const { baseInfo } = res.body.data
+            storage.setUserInfoData(baseInfo)
+            storage.setUserToken(baseInfo.token)
+            common.simpleToast('登录成功')
+            uni.reLaunch({
+              url: '/pages/index/index',
+            })
+          }
+        })
+      }
+    }
+  }
+  </script>
+  
+  <style lang="scss" scoped>
+  @import "@/libs/css/layout.scss";
+  .login-page {
+    padding: 94rpx 40rpx 64rpx;
+    box-sizing: border-box;
+  
+    .title-wrap {
+        .title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 600;
+            font-size: 56rpx;
+            color: #060809;
+            line-height: 56rpx;
+            text-align: left;
+            font-style: normal;
+            margin-bottom: 24rpx;
+        }
+  
+        .sub-title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #060809;
+            line-height: 28rpx;
+            text-align: left;
+            font-style: normal;
+  
+        }
+    }
+  
+    .main {
+        margin: 64rpx 0 0 0;
+        .agreement-row {
+            margin: 2rpx 0 64rpx;
+            display: flex;
+            align-items: center;
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #060809;
+            &.shake {
+              animation: shake 0.2s ease-in-out 0s 6;
+            }
+  
+            .checkbox {
+                width: 32rpx;
+                height: 32rpx;
+                margin-right: 16rpx;
+                background: url('https://qiniu.bms16.com/Fh5aqDCxGKxEEVfxIQD9u6Ym9OLk');
+                background-size: 100%;
+                &.is-checked {
+                    background: url('https://qiniu.bms16.com/FhAi08ilxiBqUhFezVF9H9ff2VMm');
+                    background-size: 100%;
+                }
+            }
+  
+            .text {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: 400;
+                font-size: 28rpx;
+                color: #0A59F7;
+                &:active {
+                  opacity: .6;
+                }
+            }
+        }
+  
+        .register-row {
+            margin: 32rpx 80rpx 0;
+            font-family: PingFangSC, PingFang SC;
+            font-weight: bold;
+            font-size: 28rpx;
+            color: #0A59F7;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+  
+            .split-line {
+                width: 2rpx;
+                height: 28rpx;
+                background: #CED3DE;
+                margin: 0 30rpx 0 32rpx;
+            }
+  
+            .forget {
+                display: flex;
+                align-items: center;
+  
+                &::after {
+                    content: "";
+                    width: 28rpx;
+                    height: 28rpx;
+                    background: url('https://qiniu.bms16.com/FrQ-eKUnkECvMImnNgd4w3p5-NLd');
+                    background-size: 100%;
+                }
+            }
+  
+            .register {
+                display: flex;
+                align-items: center;
+  
+                &::after {
+                    content: "";
+                    width: 28rpx;
+                    height: 28rpx;
+                    background: url('https://qiniu.bms16.com/FtGhNkwKlhR7hOZsaj0gmRl9KjPx');
+                    background-size: 100%;
+                }
+            }
+        }
+    }
+    .other-type-login {
+      position: absolute;
+      bottom: 64rpx;
+      left: 0;
+      right: 0;
+      width: 100%;
+      display: flex;
+      align-items: center;
+      flex-direction: column;
+     .title {
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 28rpx;
+        color: #060809;
+        margin-bottom: 40rpx;
+     }
+     .types {
+        display: flex;
+        .icon {
+          width: 96rpx;
+          height: 96rpx;
+          &:nth-child(1) {
+            margin-right: 146rpx;
+          }
+        }
+     }
+    }
+  }
+  </style>
+  

+ 176 - 0
pages/loginRegister/register.vue

@@ -0,0 +1,176 @@
+<template>
+    <view class="register-page">
+        <ZxInput
+            v-model="form.email"
+            :placeholder="$t('请输入邮箱账号')"
+            @focus="handleFocusOrBlur('email')"
+            @blur="handleFocusOrBlur('email', false)"
+        />
+        <ZxInput
+            v-model="form.passwd"
+            :type="passwordType"
+            :placeholder="$t('请输入密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('password')"
+            @blur="handleFocusOrBlur('password', false)"
+            @update:type="passwordType = $event"
+        />
+        <ZxInput
+            v-model="form.second_passwd"
+            :type="passwordType"
+            :placeholder="$t('请再次输入密码')"
+            :showEyeIcon="true"
+            @focus="handleFocusOrBlur('second_passwd')"
+            @blur="handleFocusOrBlur('second_passwd', false)"
+            @update:type="passwordType = $event"
+        />
+        <view 
+          :class="['agreement-row', checkShakeObj.agreemen && 'shake']" 
+          @tap="isCheckAgreement = !isCheckAgreement"
+        >
+          <view :class="['checkbox', isCheckAgreement && 'is-checked']"/>
+          {{ $t('已阅读并同意') }}
+          <text class="text" @tap.stop="handleAgreementLink('270')">《{{$t('用户协议')}}》</text>
+          {{ $t('和') }}<text class="text" @tap.stop="handleAgreementLink('102')">《{{$t('隐私政策')}}》</text>
+        </view>
+        <view :class="['zx-form-btn', isSubmt && 'is-submit']" @tap="submit">
+            {{ $t('立即注册') }}
+        </view>
+        <NoticeDialog
+            :title="$t('注册邮件已发送')"
+            :btnText="$t('我知道了')"
+            :text="noticeText"
+            v-model="isSendSucceed"
+        />
+    </view>
+</template>
+
+<script>
+import { emailRegex } from '@/common/constant'
+import NoticeDialog from '@/component/comPopup/Notice.vue'
+import ZxInput from './components/ZxInput.vue'
+const config = require('@/common/config.js');
+const http = require('@/common/http.js');
+export default {
+    components: {
+        NoticeDialog,
+        ZxInput
+    },
+    data() {
+        return {
+            isCheckAgreement: false,
+            passwordType: 'password',
+            isSendSucceed: false,
+            form: {},
+            emailFocus: false,
+            checkShakeObj: {
+                agreemen: false
+            },
+            focusInput: {
+                email: false,
+                passwd: false,
+                second_passwd: false
+            }
+        }
+    },
+    computed: {
+        noticeText({ email }) {
+            return `我们向 <span style="color: #0A59F7;">${email}</span> 发送了一封注册邮件,请您登录邮箱点击链接完成注册。`
+        },
+        isSubmt({ form }) {
+            return form.email && form.passwd && form.second_passwd
+        }
+    },
+    methods: {
+        eyeTypeChange() {
+            this.passwordType = this.passwordType === 'text' ? 'password' : 'text';
+        },
+        handleAgreementLink(id) {
+            uni.navigateTo({
+                url: `/pages/contract/contract?contract_id=${id}`
+            })
+        },
+        handleFocusOrBlur(val, flag = true) {
+            this.focusInput[val] = flag
+        },
+        _applyCheck(field, message) {
+            this.checkShakeObj[field] = true;
+            setTimeout(() => {
+            this.checkShakeObj[field] = false;
+            }, 500)
+            uni.showToast({ title: message, icon: 'none' })
+        },
+        submit() {
+            if (!this.isCheckAgreement) {
+                this._applyCheck('agreemen', this.$t('请勾选用户协议和隐私政策'))
+                return
+            }
+            if (!emailRegex.test(this.form.email)) {
+                uni.showToast({ title: this.$t('请输入有效的邮箱地址'), icon: 'none' })
+                return
+            }
+            if (this.form.passwd !== this.form.second_passwd) {
+                uni.showToast({ title: this.$t('两次输入的密码不一致'), icon: 'none' })
+                return
+            }
+            http.postApi(config.API_REGISTER_EMAIL, {
+                ...this.form
+            }, res => {
+                const { msg: title } = res.body
+                if (res.succeed) {
+                    uni.showToast({ title, icon: 'none' })
+                    this.isSendSucceed = true
+                } else {
+                    uni.showToast({ title, icon: 'none' })
+                }
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/libs/css/layout.scss";
+
+.register-page {
+    width: 100%;
+    background: #F1F3F4;
+    min-height: 100vh;
+    padding: 32rpx;
+    .agreement-row {
+        margin: 2rpx 0 64rpx;
+        display: flex;
+        align-items: center;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 400;
+        font-size: 28rpx;
+        color: #060809;
+        &.shake {
+            animation: shake 0.2s ease-in-out 0s 6;
+        }
+
+        .checkbox {
+            width: 32rpx;
+            height: 32rpx;
+            margin-right: 16rpx;
+            background: url('https://qiniu.bms16.com/Fh5aqDCxGKxEEVfxIQD9u6Ym9OLk');
+            background-size: 100%;
+            &.is-checked {
+                background: url('https://qiniu.bms16.com/FhAi08ilxiBqUhFezVF9H9ff2VMm');
+                background-size: 100%;
+            }
+        }
+
+        .text {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #0A59F7;
+            &:active {
+                opacity: .6;
+            }
+        }
+    }
+  
+}
+</style>

+ 90 - 0
pages/message/deviceInfo.vue

@@ -0,0 +1,90 @@
+<template>
+    <view class="deviceInfo-container">
+        <view v-for="(item, index) in msgList" :key="index" class="list-item">
+            <view class="time">{{ item.time }}</view>
+            <view class="msg-wrap">
+                <view class="map"></view>
+                <view class="msg-text">
+                    <view class="title">{{ item.title }}</view>
+                    <view class="dtl-txt">{{ item.msg }}</view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+const config = require('@/common/config.js');
+const http = require('@/common/http.js');
+export default {
+    data() {
+        return {
+            msgList: [
+                { time: '今天 13:14', title: '车辆推动报警', msg: '车辆正在被人推送,请及时查看' },
+                { time: '今天 13:14', title: '车辆推动报警', msg: '车辆正在被人推送,请及时查看' },
+                { time: '今天 13:14', title: '车辆推动报警', msg: '车辆正在被人推送,请及时查看' },
+            ]
+        }
+    },
+    onLoad(option) {
+        this.queryList(option.car_id)
+    },
+    methods: {
+        queryList(car_id) {
+            http.postApi(config.API_MESSAGE_LIST, { car_id, msg_type: 'DEVICE' }, res => {
+                if (res.succeed) {
+                    console.log(111, res.body)
+                }
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/libs/css/layout.scss";
+.deviceInfo-container {
+    width: 100%;
+    min-height: 100vh;
+    background: $baseBgColor;
+    padding: 24rpx 24rpx 150rpx;
+    .list-item {
+        width: 100%;
+        .time {
+            font-family: Roboto, Roboto;
+            font-weight: 400;
+            font-size: 28rpx;
+            color: #060809;
+            text-align: center;
+            margin: 40rpx 24rpx;
+        }
+        .msg-wrap {
+            width: 100%;
+            background: #FFFFFF;
+            border-radius: 40rpx;
+            padding: 8rpx;
+            .map {
+                width: 100%;
+                height: 384rpx;
+                background: url('https://qiniu.bms16.com/FufXj_x1qGs3iy7itHZ9oJ3FqG_Q');
+                background-size: 100%;
+            }
+            .msg-text {
+                padding: 32rpx 24rpx 40rpx;
+                .title {
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: bold;
+                    font-size: 36rpx;
+                    color: #060809;
+                    margin-bottom: 16rpx;
+                }
+                .dtl-tet {
+                    font-family: PingFangSC, PingFang SC;
+                    font-size: 24rpx;
+                    color: #060809;
+                }
+            }
+        }
+    }
+}
+</style>

+ 233 - 0
pages/message/index.vue

@@ -0,0 +1,233 @@
+<template>
+    <view class="message-page">
+        <view class="device-msg-wrap base-wrap" @tap="toDeviceMsgPage">
+            <view class="row">
+                <view class="title">
+                    <view>{{ $t('设备消息') }}</view>
+                    <view class="bage">{{ deviceInfo.unread }}</view>
+                </view>
+                <view class="time">06:57</view>
+            </view>
+            <view class="device-info">
+                <image class="img" :src="deviceInfo.image" />
+                <view class="info">
+                    <view class="name">{{ deviceInfo.car_name }}</view>
+                    <view class="status">异常震动</view>
+                </view>
+            </view>
+        </view>
+        <view class="sys-msg-wrap base-wrap">
+            <view class="title">{{ $t('系统消息') }}</view>
+            <view class="msg-item" v-for="(item, index) in sysMsgList" :key="index">
+                <view class="msg">
+                    {{ item.title }}
+                    <view v-if="item.title" class="btn">绑定设备</view>
+                </view>
+                <view class="time">{{ item.ctime }}</view>
+                <view class="dtl">{{ item.overview }}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+const config = require('@/common/config.js');
+const http = require('@/common/http.js');
+const common = require('@/common/common.js');
+export default {
+    data() {
+        return {
+            value: 10,
+            sysMsgList: [],
+            deviceInfo: {}
+        }
+    },
+    created() {
+        this.querySysMsgList()
+        this.queryDeviceMsg()
+        // http.postApi(config.API_MESSAGE_DTL, { id: '7' }, res => {
+        // })
+    },
+    methods: {
+        toDeviceMsgPage() {
+            const { car_id } = this.deviceInfo
+            uni.navigateTo({ url: `/pages/message/deviceInfo?car_id=${car_id}` })
+        },
+        queryDeviceMsg() {
+            http.postApi(config.API_DEVICE_MSG, {}, res => {
+                if (res.succeed) {
+                    console.log(111, res.body)
+                    this.setData({
+                        deviceInfo: res.body.data[0]
+                    })
+                }
+            })
+        },
+        querySysMsgList() {
+            http.postApi(config.API_MESSAGE_LIST, { msg_type: 'PLAT' }, res => {
+                if (res.succeed) {
+                    this.setData({
+                        sysMsgList: res.body.data.list
+                    })
+                    console.log(111, res.body)
+                }
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.message-page {
+    width: 100%;
+    min-height: 100vh;
+    overflow: auto;
+    background: #F1F3F4;
+    padding: 0 24rpx;
+
+    .base-wrap {
+        background: #FFFFFF;
+        border-radius: 40rpx;
+        padding: 40rpx 32rpx 32rpx 32rpx;
+        width: 100%;
+    }
+
+    .device-msg-wrap {
+        margin: 60rpx 0 24rpx;
+
+        .row {
+            margin-bottom: 40rpx;
+            display: flex;
+            justify-content: space-between;
+
+            .title {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: 400;
+                font-size: 36rpx;
+                color: #060809;
+                position: relative;
+
+                .bage {
+                    position: absolute;
+                    left: 96%;
+                    top: -12rpx;
+                    background: #FA2918;
+                    font-family: Futura, Futura;
+                    font-weight: 500;
+                    font-size: 24rpx;
+                    color: #FFFFFF;
+                    display: inline-flex;
+                    justify-content: center;
+                    align-items: center;
+                    line-height: 16px;
+                    padding: 0 5px;
+                    border-radius: 68rpx;
+                    z-index: 9;
+                }
+            }
+
+            .time {
+                font-family: Futura, Futura;
+                font-weight: 500;
+                font-size: 32rpx;
+                color: #060809;
+            }
+        }
+
+        .device-info {
+            display: flex;
+            align-items: center;
+
+            .img {
+                width: 128rpx;
+                height: 128rpx;
+                background: #f2f2f2;
+                margin-right: 24rpx;
+            }
+
+            .info {
+                .name {
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: bold;
+                    font-size: 40rpx;
+                    color: #060809;
+                    margin-bottom: 32rpx;
+                }
+
+                .status {
+                    font-family: PingFangSC, PingFang SC;
+                    font-size: 32rpx;
+                    color: #426BF2;
+                    display: flex;
+
+                    &::before {
+                        content: "";
+                        width: 40rpx;
+                        height: 40rpx;
+                        margin-right: 16rpx;
+                        border-radius: 50%;
+                        background: url('https://qiniu.bms16.com/Foxu2x1lQKqT5K4LqrUtWIA6Lbw8');
+                        background-size: 100%;
+                    }
+                }
+            }
+        }
+    }
+
+    .sys-msg-wrap {
+        .title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 36rpx;
+            color: #060809;
+        }
+
+        .msg-item {
+            padding: 40rpx 0;
+            border-bottom: 1px solid #F1F4F5;
+
+            .msg {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: bold;
+                font-size: 36rpx;
+                color: #060809;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+
+                .btn {
+                    width: 192rpx;
+                    height: 64rpx;
+                    background: #060809;
+                    border-radius: 32rpx;
+                    color: #fff;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    font-family: AlibabaPuHuiTiM;
+                    font-size: 32rpx;
+
+                    &:active {
+                        opacity: 0.8;
+                    }
+                }
+            }
+
+            .time {
+                font-family: Futura, Futura;
+                font-weight: 500;
+                font-size: 24rpx;
+                color: #060809;
+                margin: 16rpx 0 24rpx;
+            }
+
+            .dtl {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: 400;
+                font-size: 24rpx;
+                color: #060809;
+            }
+        }
+    }
+}
+</style>

+ 12 - 8
pages/my/my.scss

@@ -1,6 +1,6 @@
 .container-view {
-    height: calc(100vh - 96rpx);
-    padding: 36rpx 32rpx;
+    min-height: 100vh;
+    padding: 36rpx 32rpx 264rpx;
     box-sizing: border-box;
     background: linear-gradient(180deg, #CFD1DE 0%, #F1F3F4 100%), #F1F3F4;
     background-size: 100% 528rpx;
@@ -9,7 +9,7 @@
     .user-switch-row {
         display: flex;
         align-items: center;
-        justify-content: space-between;
+        justify-content: flex-end;
 
         .name-wrap {
             font-family: DIN, DIN;
@@ -43,8 +43,8 @@
         }
 
         .message {
-            width: 44rpx;
-            height: 34rpx;
+            width: 48rpx;
+            height: 48rpx;
 
             &:active {
                 opacity: .8;
@@ -85,7 +85,9 @@
         justify-content: space-between;
 
         .item {
-            width: 216rpx;
+            // width: 216rpx;
+            flex: 1;
+            margin-right: 22rpx;
             height: 216rpx;
             background: #FFFFFF;
             border-radius: 40rpx;
@@ -93,7 +95,9 @@
             flex-direction: column;
             align-items: center;
             justify-content: center;
-
+            &:last-child {
+                margin-right: 0;
+            }
             .icon {
                 width: 80rpx;
                 height: 80rpx;
@@ -115,7 +119,7 @@
         background: #FFFFFF;
         border-radius: 40rpx;
         margin-top: 24rpx;
-        padding: 40rpx 32rpx 32rpx 32rpx;
+        padding: 8rpx 32rpx;
 
         .tab-item {
             display: flex;

+ 34 - 26
pages/my/my.vue

@@ -1,14 +1,14 @@
 <template>
     <view class="container-view">
         <view class="user-switch-row">
-            <view class="name-wrap">{{ userInfo.name }}</view>
+            <!-- <view class="name-wrap">{{ userInfo.name }}</view> -->
             <image class="message" @tap="routerLink({ url: '/pages/message/index' })"
-                :src="QINIU_URL + 'FghCVNMWDBKJpqbIrqoxT-de9Has'" />
+                :src="QINIU_URL + 'FlL5BtEdMES2-mntjR9D3CX_LWYv'" />
         </view>
-        <view class="user-info-wrap">
-            <image class="head-img" :src="userInfo.headImg" />
-            <view class="user-name">{{ userInfo.userName }}</view>
-            <view class="e-mail">{{ userInfo.eMail }}</view>
+        <view class="user-info-wrap" @tap="loginHandle">
+            <image class="head-img" :src="userInfo.headimg || defaultAvatarUrl" />
+            <view class="user-name">{{ userInfo.nickname || '请点击登录' }}</view>
+            <view class="e-mail" v-if="userInfo.user_name">{{ userInfo.user_name }}</view>
         </view>
         <view class="common-tabs">
             <view class="item" v-for="(item, index) in commonTabs" :key="index" @click="routerLink(item)">
@@ -22,24 +22,25 @@
                 <view class="name">{{ item.name }}</view>
             </view>
         </view>
-        <Confirm 
-            v-model="comboDialoginfo.showConfirm"
-            :dialogInfo="comboDialoginfo"
-            @confirm="dialogConfirm"
-        />
+        <Confirm v-model="comboDialoginfo.showConfirm" :dialogInfo="comboDialoginfo" @confirm="dialogConfirm" />
+        <CustomTabbar curtTab="my" />
     </view>
 </template>
 
 <script>
+const storage = require('@/common/storage.js');
 import Confirm from '@/component/comPopup/Confirm'
-import { QINIU_URL } from '@/common/constant'
+import { QINIU_URL, defaultHeadImg } from '@/common/constant'
+import CustomTabbar from '@/component/customTabbar/index';
 export default {
     components: {
-        Confirm
+        Confirm,
+        CustomTabbar
     },
     data() {
         return {
             QINIU_URL,
+            defaultAvatarUrl: defaultHeadImg,
             comboDialoginfo: {
                 showConfirm: false,
                 title: '温馨提示',
@@ -48,34 +49,41 @@ export default {
                 confirmBtnText: '前往购买',
                 showCancelButton: false
             },
-            userInfo: {
-                name: 'Real name',
-                userName: 'Kabuda-4',
-                eMail: '1007929522@Mail.com',
-                headImg: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.lmGw9aECIAIIvJK_U92f_gAAAA?w=169&h=180&c=7&r=0&o=5&pid=1.7'
-            }
+            userInfo: {}
         }
     },
     computed: {
         commonTabs() {
             return [
                 { name: `${this.$t('我的车辆')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${this.$t('用车人')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${this.$t('换电套餐')}`, jumpCheck: 'combo', url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
+                { name: `${this.$t('用车人')}`, url: '', icon: 'FnxGW52BCkTkK9HxsTdVrghU7B4D' },
+                { name: `${this.$t('换电套餐')}`, jumpCheck: 'combo', url: '', icon: 'FsOsd1SxYDHDm00aiwrTib_k0Mbr' },
             ]
         },
         baseTabs() {
             const lang = t => this.$t(t)
             return [
-                { name: `${lang('我的订单')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${lang('换电记录')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${lang('关于我们')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${lang('客服中心')}`, url: '', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
-                { name: `${lang('设置')}`, url: '/pages/my/set', icon: 'Fp6G-Kzb-YUGkP2WR-kjTlIbbTj1' },
+                { name: `${lang('我的订单')}`, url: '/pages/order/order', icon: 'FkLJGLo1faYtJWhW4Q0gt5dphI7g' },
+                { name: `${lang('换电记录')}`, url: '', icon: 'FnSjwcN7Mcpa-WA7Cqx2cGTvX2V1' },
+                { name: `${lang('关于我们')}`, url: '/pages/bluetoothUnlock/unlockSet', icon: 'Fmin1_DG6ZkENCdsI1qJZJpDNkhQ' },
+                { name: `${lang('客服中心')}`, url: '/pages/bluetoothUnlock/bluetoothPair', icon: 'FhA9TUbTMF0e7ma6NZXqPrkscN6l' },
+                { name: `${lang('设置')}`, url: '/pages/my/set', icon: 'Fu3f2iRi5BspRfbVLPcw8ryWc4lu' },
             ]
         }
     },
+    onShow() {
+        const user_token = storage.getUserToken()
+        user_token && this.loadUserInfo()
+    },
     methods: {
+        loadUserInfo() {
+            const userInfo = storage.getUserInfoData()
+            console.log('userInfo', userInfo)
+            this.setData({ userInfo })
+        },
+        loginHandle() {
+            uni.navigateTo({ url: '/pages/loginRegister/login' })
+        },
         checkHandle_combo() {
             this.comboDialoginfo.showConfirm = true
         },

+ 156 - 0
pages/my/set.vue

@@ -0,0 +1,156 @@
+<template>
+    <view class="set-page base-bg-wrap">
+        <image class="head-img" :src="userInfo.headImg || defaultHeadImg" />
+        <view class="list-wrap">
+            <view class="item" v-for="(item, index) in list" :key="index" @tap="routerLink(item.url)">
+                <view class="title">{{ item.title }}</view>
+                <view :class="['text-right', item.hideArrow && 'hide-arrow']">
+                    {{ userInfo[item.textProp] || '' }}
+                </view>
+            </view>
+        </view>
+        <view class="logout-btn" @tap="handleQuit">退出登录</view>
+    </view>
+</template>
+
+<script>
+const storage = require('@/common/storage.js');
+import { QINIU_URL, defaultHeadImg } from '@/common/constant'
+export default {
+    data() {
+        return {
+            QINIU_URL,
+            defaultHeadImg,
+            userInfo: {},
+            list: [
+                { title: '昵称', url: '', textProp: 'nickname' },
+                { title: '注册时间', textProp: 'registrationTime', hideArrow: true },
+                { title: '修改密码', url: '/pages/loginRegister/changePassword' },
+                { title: '关于我们', url: '/pages/aboutMy/aboutMy', textProp: 'version' },
+                { title: '隐私协议', url: '/pages/contract/contract?contract_id=270' },
+                { title: '用户条款', url: '/pages/contract/contract?contract_id=102' },
+            ]
+        }
+    },
+    onShow() {
+        const user_token = storage.getUserToken()
+        user_token && this.loadUserInfo()
+    },
+    methods: {
+        loadUserInfo() {
+            const userInfo = storage.getUserInfoData()
+            console.log('userInfo', userInfo)
+            this.setData({ userInfo })
+        },
+        routerLink(url) {
+            uni.navigateTo({ url })
+        },
+        handleQuit() {
+            uni.showModal({
+                title: '提示',
+                content: '您确定要退出当前账号吗?',
+                showCancel: true,
+                cancelText: '取消',
+                confirmText: '确定',
+                success: function (res) {
+                    if (res.confirm) {
+                        storage.clearStorage()
+                        uni.reLaunch({
+                            url: '/pages/index/index',
+                        })
+                    }
+                }
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+@import "@/libs/css/layout.scss";
+
+.set-page {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background: $baseBgColor;
+    width: 100%;
+    min-height: 100vh;
+    padding: 66rpx 32rpx 48rpx;
+
+    .head-img {
+        width: 174rpx;
+        height: 174rpx;
+        margin: 0 auto 40rpx;
+        border-radius: 50%;
+    }
+
+    .list-wrap {
+        width: 100%;
+        background: #FFFFFF;
+        border-radius: 40rpx;
+        padding: 8rpx 32rpx;
+
+        .item {
+            padding: 40rpx 0;
+            display: flex;
+            justify-content: space-between;
+            border-bottom: 2px solid #F1F4F5;
+
+            &:last-child {
+                border-bottom: 0;
+            }
+
+            .title {
+                font-family: PingFangSC, PingFang SC;
+                font-weight: bold;
+                font-size: 32rpx;
+                color: #060809;
+            }
+
+            .text-right {
+                font-family: Futura, Futura;
+                font-weight: 500;
+                font-size: 30rpx;
+                color: #060809;
+                display: flex;
+                align-items: center;
+
+                &::after {
+                    content: "";
+                    width: 28rpx;
+                    height: 28rpx;
+                    margin-left: 12rpx;
+                    background: url('https://qiniu.bms16.com/FtGhNkwKlhR7hOZsaj0gmRl9KjPx');
+                    background-size: 100%;
+                }
+
+                &.hide-arrow {
+                    &::after {
+                        display: none;
+                    }
+                }
+            }
+        }
+    }
+
+    .logout-btn {
+        width: 100%;
+        height: 80rpx;
+        margin-top: 100rpx;
+        background: #E4E7EC;
+        border-radius: 40rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-family: PingFangSC, PingFang SC;
+        font-weight: 500;
+        font-size: 32rpx;
+        color: #FA2918;
+
+        &:active {
+            opacity: .7;
+        }
+    }
+}
+</style>

+ 17 - 16
pages/order/order.vue

@@ -1,9 +1,9 @@
 <template>
 	<view class="container">
-		<view v-for="(item,index) in hireOrderList" :key="index" @tap="loadToNav" :data-orderSn="item.order_sn"
+		<view v-for="(item, index) in hireOrderList" :key="index" @tap="loadToNav" :data-orderSn="item.order_sn"
 			class="order-card">
 			<view class="card-top flex-row">
-				<view>{{item.car_model}}</view>
+				<view>{{ item.car_model }}</view>
 				<view class="card card-k" v-if="item.order_status == 0">待支付</view>
 				<view class="card card-k" v-else-if="item.order_status == 1">待取车</view>
 				<view class="card card-k" v-else-if="item.order_status == 2">待激活</view>
@@ -12,7 +12,7 @@
 				<view class="card card-k" v-else-if="item.order_status == 5">还车申请中</view>
 				<view class="card card-k" v-else-if="item.order_status == 6">还车中</view>
 				<view class="card card-k" v-else-if="item.order_status == 7">车辆已归还</view>
-				<view class="card card-k" v-else-if="item.order_status == 8||item.order_status == 9">已取消</view>
+				<view class="card card-k" v-else-if="item.order_status == 8 || item.order_status == 9">已取消</view>
 			</view>
 			<view class="card-border"></view>
 			<view class="card-bottom">
@@ -23,18 +23,21 @@
 						<view v-if="item.order_status == 3">剩余租期</view>
 					</view>
 					<view class="item-right">
-						<view>{{tools.formatTime(item.ctime)}}</view>
+						<view>{{ tools.formatTime(item.ctime) }}</view>
 						<view>
-							{{item.hire_duration_time.day > 0 ? item.hire_duration_time.day :'' }}<text
-								v-if="item.hire_duration_time.day>0">日</text>{{item.hire_duration_time.hour > 0 ? item.hire_duration_time.hour :'' }}<text
-								v-if="item.hire_duration_time.hour>0">小时</text>{{item.hire_duration_time.minute > 0 ? item.hire_duration_time.minute :'' }}<text
-								v-if="item.hire_duration_time.minute>0">分</text>
+							{{ item.hire_duration_time.day > 0 ? item.hire_duration_time.day : '' }}<text
+								v-if="item.hire_duration_time.day > 0">日</text>{{ item.hire_duration_time.hour > 0 ?
+									item.hire_duration_time.hour : '' }}<text
+								v-if="item.hire_duration_time.hour > 0">小时</text>{{ item.hire_duration_time.minute > 0 ?
+									item.hire_duration_time.minute : '' }}<text
+								v-if="item.hire_duration_time.minute > 0">分</text>
 						</view>
 						<view v-if="item.order_status == 3">
-							{{item.order_time.day > 0 ? item.order_time.day :'' }}<text
-								v-if="item.order_time.day>0">日</text>{{item.order_time.hour > 0 ? item.order_time.hour :'' }}<text
-								v-if="item.order_time.hour>0">小时</text>{{item.order_time.minute > 0 ? item.order_time.minute :'' }}<text
-								v-if="item.order_time.minute>0">分</text>
+							{{ item.order_time.day > 0 ? item.order_time.day : '' }}<text
+								v-if="item.order_time.day > 0">日</text>{{ item.order_time.hour > 0 ?
+									item.order_time.hour
+									: '' }}<text v-if="item.order_time.hour > 0">小时</text>{{ item.order_time.minute > 0 ?
+								item.order_time.minute : '' }}<text v-if="item.order_time.minute > 0">分</text>
 							<!-- hire_end_time -当前时间 -->
 						</view>
 					</view>
@@ -43,7 +46,7 @@
 					订单金额
 					<!-- <view class="price-text">¥<text>{{((item.money-0) + (item.deposit-0) + (item.insurance-0)) / 100}}</text></view> -->
 					<view class="price-text">
-						¥<text>{{item.money / 100}}</text></view>
+						¥<text>{{ item.money / 100 }}</text></view>
 				</view>
 			</view>
 		</view>
@@ -128,12 +131,10 @@
 					sort_num: me.sort_num
 				}, (resp) => {
 					if (resp.data.code === 200) {
-						// me.hireOrderList = resp.data.data.list
 						me.hireOrderList.push.apply(me.hireOrderList, resp.data.data.list)
 						if (me.hireOrderList.length > 0) {
 							me.sort_time = me.hireOrderList[me.hireOrderList.length - 1].ctime;
 							me.sort_num = me.hireOrderList[me.hireOrderList.length - 1].sort_num;
-							// timeToDay
 							me.hireOrderList = me.hireOrderList.map(item => {
 								item.order_time = common.getTimeToDay(Math.ceil(item.hire_end_time - me.nowTime)/60)
 								item.hire_duration_time = common.getTimeToDay(Math.ceil(item.hire_end_time - item.hire_begin_time)/60)
@@ -177,5 +178,5 @@
 </script>
 
 <style>
-	@import './order.css';
+@import './order.css';
 </style>