Browse Source

Merge branch 'lw_test' of http://git.bms16.com/liuwei/zx_flk_app into lw_test

liuwei 2 months ago
parent
commit
83900ce82a

+ 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) {

+ 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>

+ 3 - 3
component/comPopup/Confirm.vue

@@ -1,13 +1,13 @@
 <template>
   <view class="modal-mask" v-if="value">
     <view class="confirm-container">
-      <view class="popup-title">{{ dialogInfo.title }}</view>
+      <view class="popup-title">{{ dialogInfo.title || $t('温馨提示') }}</view>
       <view class="popup-content">{{ dialogInfo.text }}</view>
       <view class="flex-row modal-footer">
         <view class="show-btn cencel-btn-pop" v-if="dialogInfo.showCancelButton" @tap="cancel">
-          {{ dialogInfo.cancelBtnText }}
+          {{ dialogInfo.cancelBtnText || $t('取消') }}
         </view>
-        <view class="show-btn ok-btn-pop" @tap="confirm">{{ dialogInfo.confirmBtnText }}</view>
+        <view class="show-btn ok-btn-pop" @tap="confirm">{{ dialogInfo.confirmBtnText || $t('确定') }}</view>
       </view>
     </view>
   </view>

+ 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>

+ 51 - 0
libs/css/layout.scss

@@ -0,0 +1,51 @@
+@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, $baseBgColor 0%, $pageBgColor 100%), $pageBgColor;
+    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;
+  }
+  &.fix-bottom-btn {
+    width: 93%;
+    position: absolute;
+    bottom: 48rpx;
+  }
+}
+
+.zx-container {
+  width: 100%;
+  min-height: 100vh;
+  padding: 32rpx 24rpx;
+  background: $pageBgColor;
+  .zx-wrap {
+    width: 100%;
+    background: #fff;
+    padding: 40rpx 32rpx;
+    border-radius: 40rpx;
+  }
+}

+ 2 - 0
libs/css/variables.scss

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

+ 2 - 1
package.json

@@ -31,6 +31,7 @@
     "dependencies": {
         "crypto-js": "^4.2.0",
         "uview-ui": "^1.8.8",
-        "vue-i18n": "^11.0.1"
+        "vue-i18n": "^11.0.1",
+        "vuedraggable": "^2.24.3"
     }
 }

+ 76 - 0
pages.json

@@ -17,6 +17,82 @@
 				"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/carFunctionSet/more",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "更多功能"
+			}
+		},
+		{
+			"path": "pages/carFunctionSet/unbind",
+			"style": {
+				"titleNView": false,
+				"navigationBarTitleText": "解除绑定"
+			}
+		},
+
 		{
 			"path": "pages/service/service",
 			"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>

+ 37 - 0
pages/bluetoothUnlock/bluetoothUnlockAuth.vue

@@ -0,0 +1,37 @@
+<template>
+    <view>
+        <AndroidUnlockAuth v-model="show" v-if="platform === 'android'" />
+        <IosUnlockAuth v-model="show" v-else-if="platform === 'ios'" />
+    </view>
+</template>
+
+<script>
+import AndroidUnlockAuth from './components/AndroidUnlockAuth.vue'
+import IosUnlockAuth from './components/IosUnlockAuth.vue'
+
+export default {
+    components: {
+        AndroidUnlockAuth,
+        IosUnlockAuth
+    },
+    data() {
+        return {
+            platform: 'android',
+            show: false
+        }
+    },
+    onLoad() {
+        this._initPlatform();
+    },
+    methods: {
+        open() {
+            this.show = true;
+            console.log(111, this.platform)
+        },
+        _initPlatform() {
+            const systemInfo = uni.getSystemInfoSync();
+            this.platform = systemInfo.platform;
+        }
+    }
+}
+</script>

+ 214 - 0
pages/bluetoothUnlock/components/AndroidUnlockAuth.vue

@@ -0,0 +1,214 @@
+<template>
+    <u-popup v-model="showDialog" mode="bottom" border-radius="28" @close="close">
+        <view class="dialog-content">
+            <view class="title">{{ $t('开启感应解锁') }}</view>
+            <view class="tips">为保证 感应解锁 的正常使用,请依次开以下权限,开启后可以显著提高解锁成功率,且不会明显增加手机电量消耗</view>
+            <view class="authorization-wrap">
+                <view class="corner-mark">{{ $t('授权引导') }}</view>
+                <view class="step-item-container">
+                    <view class="step-item" v-for="(auth, idx) in authStepList" :key="idx">
+                        <view :class="['icon', `icon_${auth.type}`]"></view>
+                        <view class="desc-wrap">
+                            <view class="title-row">
+                                <view class="title">{{ auth.title }}</view>
+                                <view class="turn-on-switch is-open">{{ $t('已开启') }}</view>
+                            </view>
+                            <view class="desc">{{ auth.desc }}</view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="btn" @tap="linkTo">{{ $t('我已开启') }}</view>
+        </view>
+    </u-popup>
+</template>
+
+<script>
+export default {
+    props: {
+        value: {
+            type: Boolean,
+            default: false
+        }
+    },
+    data() {
+        return {
+            showDialog: this.value,
+        }
+    },
+    computed: {
+        authStepList() {
+            const lang = v => this.$t(v)
+            return  [
+                { title: lang('位置权限'), type: 'location', desc: '打开手机定位,并运行APP始终使用' },
+                { title: lang('电池优化'), type: 'battery', desc: '打开电池优化设置, 将弗兰克APP加入保护名单' },
+                { title: lang('后台运行'), type: 'backstage', desc: '打开后台运行权限 清选择手动控制' },
+                { title: lang('打开应用锁'), type: 'appLock', desc: '打开应用权限锁' },
+            ]
+        }
+    },
+    watch: {
+        value(newValue) {
+            this.showDialog = newValue;
+        }
+    },
+    methods: {
+        linkTo() {
+            uni.navigateTo({ url: '/pages/bluetoothUnlock/bluetoothPair' })
+        },
+        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%;
+        border-radius: 40rpx;
+        position: relative;
+
+        .corner-mark {
+            width: 224rpx;
+            height: 100rpx;
+            background: #0A59F7;
+            border-radius: 40rpx 0 0 0;
+            display: flex;
+            color: #fff;
+            line-height: 86rpx;
+            text-align: center;
+            justify-content: flex-end;
+
+            &::after {
+                content: "";
+                width: 86rpx;
+                height: 80rpx;
+                background: url('https://qiniu.bms16.com/FibAaPERzqi6m95EP2jREUKixjUi');
+                background-size: 100%;
+            }
+        }
+
+        .step-item-container {
+            background: #fff;
+            border-top-left-radius: 40rpx;
+            padding: 30rpx 24rpx;
+            margin-top: -24rpx;
+        }
+
+        .step-item {
+            width: 100%;
+            background: #F4F5F6;
+            border-radius: 24rpx;
+            padding: 32rpx 28rpx;
+            margin-bottom: 24rpx;
+            display: flex;
+            align-items: center;
+
+            &:last-child {
+                margin-bottom: 0;
+            }
+
+            .desc-wrap {
+                flex: 1;
+                margin-left: 24rpx;
+            }
+
+            .title-row {
+                display: flex;
+                justify-content: space-between;
+                margin-bottom: 24rpx;
+
+                .title {
+                    font-family: PingFangSC, PingFang SC;
+                    font-weight: 500;
+                    font-size: 32rpx;
+                    color: #060809;
+                    font-weight: bold;
+                }
+
+                .turn-on-switch {
+                    background: #2ADA62;
+                    border-radius: 20rpx;
+                    font-size: 22rpx;
+                    color: #FFFFFF;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    padding: 8rpx 20rpx;
+                }
+            }
+
+            .desc {
+                font-size: 24rpx;
+                color: #060809;
+            }
+            .icon {
+                width: 66rpx;
+                height: 70rpx;
+            }
+            .icon_location {
+                background: url('https://qiniu.bms16.com/FpNU0wp-E5Iin60nT8_NwT1_h_xm');
+                background-size: 100% 100%;
+            }
+
+            .icon_battery {
+                background: url('https://qiniu.bms16.com/FjD4CXHuNUL85_JYI7w2MDucjeI-');
+                background-size: 100% 100%;
+            }
+
+            .icon_backstage {
+                background: url('https://qiniu.bms16.com/Fo7RGbv1gokn1iUQpF8tca5aUWkD');
+                background-size: 100% 100%;
+            }
+
+            .icon_appLock {
+                background: url('https://qiniu.bms16.com/FoWg_FjfV5_v9fxvFQ2dHzXOCDPD');
+                background-size: 100% 100%;
+            }
+        }
+    }
+
+    .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>

+ 94 - 0
pages/bluetoothUnlock/components/IosUnlockAuth.vue

@@ -0,0 +1,94 @@
+<template>
+    <u-popup v-model="showDialog" mode="bottom" border-radius="28" @close="close">
+        <view class="dialog-content">
+            IOS
+            <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>

+ 228 - 0
pages/carFunctionSet/more.vue

@@ -0,0 +1,228 @@
+<template>
+  <view class="zx-container car-function-set-more">
+    <view class="fn-wrap">
+      <view class="title">
+        {{ $t('固定导航栏') }}
+        <text class="sort-num">{{ realActiveTabs.length }} / {{ MAX_TAB_LEN }}</text>
+      </view>
+      <draggable v-model="activeTabs" :delay="0" :animation="200" class="tab-item-container">
+        <view v-for="(tab, index) in activeTabs" :class="['tab-item']" :key="index" @longpress="toggleEdit"
+          @tap="deleteTab(tab)">
+          <image v-if="tab.isLock && isActiveEdit" :src="icons.lock" class="right-icon" />
+          <image v-if="getMovableItem(tab)" :src="icons.edit" class="right-icon" />
+          <image :src="tab.iconUrl" class="icon" />
+          <view :class="['name', getMovableItem(tab) && 'shake']">{{ tab.name }}</view>
+        </view>
+      </draggable>
+    </view>
+    <view class="fn-wrap">
+      <view class="title">{{ $t('其他功能') }}</view>
+      <view class="tab-item-container is-to-be-select">
+        <view v-for="tab in toBeSelectTabs" :key="tab.name" class="tab-item" @longpress="toggleEdit"
+          @tap="addItemTab(tab)">
+          <image v-if="showAddIcon" :src="icons.add" class="right-icon" />
+          <image :src="tab.iconUrl" class="icon" />
+          <view class="name">{{ tab.name }}</view>
+        </view>
+      </view>
+    </view>
+    <view class="tips">{{ $t('长按拖动可调整顺序,可增减固定导航栏内容') }}</view>
+    <view class="un-bind-btn" @tap="toUnbind">{{ $t('解除绑定') }}</view>
+  </view>
+</template>
+
+<script>
+import draggable from 'vuedraggable'
+import { QINIU_URL } from '@/common/constant'
+
+const ICONS = {
+  lock: `${QINIU_URL}FgMCTZCySvEgLYgkzU65BUR4f4Ls`,
+  edit: `${QINIU_URL}Fq3V1Zi5tKH7ibTwG1nO7N96CU8m`,
+  add: `${QINIU_URL}FqLEWbMe8DcnQtNz6GdDDD87ObZK`,
+  placeholder: `${QINIU_URL}FpKvfFNSDbx0d9RDwyGAQdFb7Kt6`
+}
+
+export default {
+  components: {
+    // draggable
+  },
+  data() {
+    const MAX_TAB_LEN = 4
+    return {
+      QINIU_URL,
+      MAX_TAB_LEN,
+      icons: ICONS,
+      isActiveEdit: false,
+      placeholderTab: {
+        name: '',
+        icon: ICONS.placeholder,
+        isLock: false,
+        isPlaceholder: true
+      },
+      activeTabs: [],
+      toBeSelectTabs: []
+    }
+  },
+  computed: {
+    realActiveTabs() {
+      return this.activeTabs.filter(tab => tab.name)
+    },
+    showAddIcon() {
+      return this.isActiveEdit && this.realActiveTabs.length < this.MAX_TAB_LEN
+    }
+  },
+  created() {
+    this.initializeTabs()
+  },
+  methods: {
+    getMovableItem(tab) {
+      return this.isActiveEdit && !tab.isLock && !tab.isPlaceholder
+    },
+    initializeTabs() {
+      const lang = v => this.$t(v)
+      this.activeTabs = [
+        { name: lang('开机'), iconUrl: `${QINIU_URL}Fp5T9lSNakoiioji6S7W4DmFQ_ys`, isLock: true },
+        { name: lang('闪灯鸣笛'), iconUrl: `${QINIU_URL}FpeQsDh2dbeTullNLI-HhWj4WAQS` },
+        { name: lang('打开座桶'), iconUrl: `${QINIU_URL}FppwhbFzrEmDeJQgZJtDTu6WOoMZ` },
+        { name: lang('打开尾箱'), iconUrl: `${QINIU_URL}Fv3KLuYWEeV5IM4_2sMbmur7yZtz` }
+      ]
+      this.toBeSelectTabs = [
+        { name: lang('胎压'), iconUrl: `${QINIU_URL}FmbcjmvoB4J3CT1hrbjNX4kxv9Zq` },
+        { name: lang('点出信息'), iconUrl: `${QINIU_URL}Fnx_4tLoq1ytvVA0UemepWisI73A` },
+        { name: lang('导航'), iconUrl: `${QINIU_URL}FrA_ouJtDp-39g7rMBI0EYr2czVE` }
+      ]
+      this.addPlaceholders()
+    },
+    toggleEdit() {
+      this.isActiveEdit = !this.isActiveEdit
+    },
+    deleteTab(tab) {
+      if (tab.isLock) return
+      const index = this.activeTabs.findIndex(t => t.name === tab.name)
+      if (index !== -1) {
+        const removedTab = this.activeTabs.splice(index, 1)[0]
+        this.toBeSelectTabs.push(removedTab)
+        this.addPlaceholders()
+      }
+    },
+    addItemTab(tab) {
+      const index = this.toBeSelectTabs.findIndex(t => t.name === tab.name)
+      if (index !== -1) {
+        const removedTab = this.toBeSelectTabs.splice(index, 1)[0]
+        const placeholderIndex = this.activeTabs.findIndex(v => v.isPlaceholder)
+        this.activeTabs.splice(placeholderIndex, 1, removedTab)
+        if (this.realActiveTabs.length > this.MAX_TAB_LEN - 1) {
+          this.isActiveEdit = false
+        }
+      }
+    },
+    addPlaceholders() {
+      while (this.activeTabs.length < this.MAX_TAB_LEN) {
+        this.activeTabs.push({ ...this.placeholderTab })
+      }
+    },
+    toUnbind() {
+      uni.navigateTo({
+        url: '/pages/carFunctionSet/unbind'
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/libs/css/layout.scss";
+
+.car-function-set-more {
+  .fn-wrap {
+    width: 100%;
+    background: #FFFFFF;
+    border-radius: 40rpx;
+    margin-bottom: 20rpx;
+    padding: 40rpx 0 48rpx;
+
+    .title {
+      font-family: PingFangSC, PingFang SC;
+      font-weight: 400;
+      font-size: 36rpx;
+      color: #060809;
+      margin-bottom: 16rpx;
+      padding-left: 40rpx;
+
+      .sort-num {
+        margin-left: 12rpx;
+        width: 36rpx;
+        font-family: Futura, Futura;
+        font-size: 30rpx;
+        color: #060809;
+      }
+    }
+
+    .tab-item-container {
+      display: flex;
+
+      &.is-to-be-select {
+        flex-wrap: wrap;
+      }
+    }
+
+    .tab-item {
+      text-align: center;
+      width: 25%;
+      position: relative;
+      margin-top: 24rpx;
+
+      .right-icon {
+        position: absolute;
+        right: 20rpx;
+        top: 0;
+        width: 40rpx;
+        height: 40rpx;
+        z-index: 9;
+      }
+
+      .icon {
+        width: 112rpx;
+        height: 112rpx;
+        margin-bottom: 20rpx;
+      }
+
+      .name {
+        font-size: 28rpx;
+        color: #060809;
+
+        &.shake {
+          animation: shake 0.2s ease-in-out infinite;
+        }
+      }
+    }
+  }
+
+  .tips {
+    font-size: 24rpx;
+    color: #060809;
+    text-align: center;
+    margin-top: 32rpx;
+  }
+
+  .un-bind-btn {
+    position: absolute;
+    bottom: 56rpx;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 93%;
+    padding: 24rpx;
+    text-align: center;
+    background: #E4E7EC;
+    border-radius: 40rpx;
+    font-family: PingFangSC, PingFang SC;
+    font-weight: bold;
+    font-size: 32rpx;
+    color: #060809;
+
+    &:active {
+      opacity: .7;
+    }
+  }
+}
+</style>

+ 127 - 0
pages/carFunctionSet/unbind.vue

@@ -0,0 +1,127 @@
+<template>
+    <view class="zx-container unbind-page">
+      <view class="zx-wrap car-info-wrap">
+        <image :src="icons.carImg" class="car-img" />
+        <view class="car-name">
+          {{ '智能电动摩托车智驾 M7' }}
+        </view>
+      </view>
+      <view class="zx-wrap input-wrap">
+        <view class="title">{{ $t('输入注册账号的密码即可解绑') }}</view>
+        <ZxInput
+          v-model="passwd"
+          :placeholder="$t('请输入密码')"
+          background="#F3F8FF"
+          is-password
+        />
+      </view>
+      <view class="tips-wrap">
+        <view class="title">{{ $t('提示信息') }}</view>
+        <view class="text">
+          <text>1、</text>
+          解绑后,您将失去当前设备的控制权,其他人可以连接绑定您的设备。
+        </view>
+        <view class="text">
+          <text>2、</text>
+          解绑后将立即删除家庭账号,感应解锁等数据。
+        </view>
+      </view>
+      <view
+        :class="[
+          'zx-form-btn', 'fix-bottom-btn',
+          passwd && 'is-submit'
+        ]"
+        @tap="unbindTap"
+      >
+        {{ $t('完成并解绑') }}
+      </view>
+      <Confirm
+        v-model="showConfirm"
+        :dialog-info="{
+          text: $t('是否确定解除绑定'),
+          showCancelButton: true
+        }"
+        @confirm="unbindSubmit"
+      />
+    </view>
+  </template>
+  
+  <script>
+  import Confirm from '@/component/comPopup/Confirm'
+  import ZxInput from '../loginRegister/components/ZxInput.vue'
+  import { QINIU_URL } from '@/common/constant'
+  
+  const ICONS = {
+    carImg: `${QINIU_URL}Fr7v719WrP6TmCfGtvJd-nAHhiCj`
+  }
+  
+  export default {
+    components: {
+      Confirm,
+      ZxInput
+    },
+    data() {
+      return {
+        icons: ICONS,
+        showConfirm: false,
+        passwd: ''
+      }
+    },
+    methods: {
+      unbindTap() {
+        if (!this.passwd) {
+          return
+        }
+        this.showConfirm = true
+      },
+      unbindSubmit() {
+        console.log('解除绑定', this.passwd)
+      }
+    }
+  }
+  </script>
+  
+  <style lang="scss" scoped>
+  @import "@/libs/css/layout.scss";
+  .unbind-page {
+    .car-info-wrap {
+        .car-img {
+            width: 100%;
+            height: 492rpx;
+        }
+        .car-name {
+            text-align: center;
+            color: #060809;
+            font-weight: bold;
+            font-size: 34rpx;
+        }
+    }
+    .input-wrap {
+        margin: 20rpx 0 40rpx;
+        .title {
+            font-family: PingFangSC, PingFang SC;
+            font-weight: 400;
+            font-size: 36rpx;
+            color: #060809;
+            margin-bottom: 40rpx;
+        }
+    }
+    .tips-wrap {
+        padding: 0 32rpx;
+        .title {
+            font-size: 32rpx;
+            color: #060809;
+            font-weight: bold;
+            margin-bottom: 20rpx;
+        }
+        .text {
+            font-weight: 400;
+            font-size: 24rpx;
+            color: #828DA2;
+            line-height: 36rpx;
+            display: flex;
+        }
+    }
+  }
+  </style>
+  

+ 6 - 1
pages/index/components/control/control.vue

@@ -17,7 +17,7 @@
 				<img class="contril-item-img" src="https://qiniu.bms16.com/FjgrfPEufqSzOxb6Aobuc_bbbtJE" alt="">
 				<text>{{$t("打开尾箱")}}</text>
 			</view>
-			<view class="contril-item flex-row">
+			<view class="contril-item flex-row" @tap="toMoreFunctionSet">
 				<img class="contril-item-img" src="https://qiniu.bms16.com/Ft3pNyStT22LP8Ds1Mru2LoTHadx" alt="">
 				<text>{{$t("更多功能")}}</text>
 			</view>
@@ -107,6 +107,11 @@ export default {
 			uni.navigateTo({
 				url:'/pages/carLocation/carLocation'
 			})
+		},
+		toMoreFunctionSet() {
+			uni.navigateTo({
+				url: '/pages/carFunctionSet/more'
+			})
 		}
 	}
 };

+ 8 - 3
pages/index/index.vue

@@ -57,7 +57,7 @@
 			<view class="config-view">
 				<view class="flex-row config-car-view">
 					<view class="margin_r_20"><img class="icon_style_64" src="https://qiniu.bms16.com/Ftzyvs5whxDdMFksYChHaWKVb0Uk" alt=""></view>
-					<view class="flex-row config-text-view">
+					<view class="flex-row config-text-view" @tap="inductiveUnlockHandle">
 						<view class="flex-row font_w_600">{{$t("感应解锁")}}</view>
 						<view class="flex-row tip-text-config">{{$t("请先链接蓝牙")}}</view>
 					</view>
@@ -85,6 +85,7 @@
 		<block>
 			<!-- <UnleasedPages/> -->
 		</block>
+		<BluetoothUnlockAuth ref="bluetoothUnlockAuth" />
 		<CustomTabbar />
 	</view>
 </template>
@@ -99,7 +100,7 @@
 	import MapCard from './components/mapCard/mapCard'
 	import UnleasedPages from './components/unleasedPages/unleasedPages'
 	import CustomTabbar from '@/component/customTabbar/index';
-	
+	import BluetoothUnlockAuth from '../bluetoothUnlock/bluetoothUnlockAuth.vue'
 
 
 	export default {
@@ -125,7 +126,8 @@
 			Control,
 			MapCard,
 			CustomTabbar,
-			UnleasedPages
+			UnleasedPages,
+			BluetoothUnlockAuth
 		},
 		/**
 		 * 生命周期函数--监听页面加载
@@ -154,6 +156,9 @@
 					fail: function(res) {},
 					complete: function(res) {}
 				});
+			},
+			inductiveUnlockHandle() {
+				this.$refs.bluetoothUnlockAuth.open();
 			}
 
 		}

+ 60 - 0
pages/loginRegister/changePassword.vue

@@ -0,0 +1,60 @@
+<template>
+    <view class="changePassword-page">
+        <ZxInput
+            v-model="form.oldPassword"
+            :placeholder="$t('请输入旧密码')"
+            isPassword
+        />
+        <ZxInput
+            v-model="form.password"
+            :placeholder="$t('请输入新密码')"
+            isPassword
+        />
+        <ZxInput
+            v-model="form.passwordAgain"
+            :placeholder="$t('请再次输入新密码')"
+            isPassword
+        />
+   
+        <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 {
+            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>

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

@@ -0,0 +1,159 @@
+<template>
+  <view
+    :class="[
+      'input-item',
+      isFocus && 'is-focus',
+      hightlight && 'hightlight'
+    ]"
+    :style="{ background }"
+  >
+    <input
+      v-model="inputValue"
+      :type="isPassword ? localType : 'text'"
+      :placeholder="placeholder"
+      placeholder-class="input-placeholder"
+      @focus="handleFocus"
+      @blur="handleBlur"
+    >
+    <view v-if="showIcon">
+      <view
+        v-if="isPassword"
+        :class="[
+          'input-icon',
+          'eye-icon', localType === 'text' && 'show'
+        ]"
+        @tap="togglePasswordType"
+      />
+      <view
+        v-else
+        class="input-icon clear-icon"
+        @tap="clearInput"
+      />
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    value: {
+      type: String,
+      default: ''
+    },
+    inputType: {
+      type: String,
+      default: ''
+    },
+    placeholder: {
+      type: String,
+      default: ''
+    },
+    hightlight: {
+      type: Boolean,
+      default: false
+    },
+    isPassword: {
+      type: Boolean,
+      default: false
+    },
+    background: {
+      type: String,
+      default: '#fff'
+    }
+  },
+  data() {
+    return {
+      isFocus: false,
+      inputValue: this.value,
+      localType: 'password'
+    }
+  },
+  computed: {
+    showIcon() {
+      return this.isFocus && this.inputValue
+    }
+  },
+  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'
+    }
+  }
+}
+</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 74rpx 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>

+ 69 - 0
pages/loginRegister/forgetPassword.vue

@@ -0,0 +1,69 @@
+<template>
+    <view class="forgetPassword-page">
+        <zx-input
+            v-model="email"
+            :placeholder="$t('请输入要重置的邮箱账号')"
+        />
+        <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,
+            email: ''
+        }
+    },
+    computed: {
+        noticeText({ email }) {
+            return `我们向 <span style="color: #0A59F7;">${email}</span> 发送了一封密码重置邮件,请您登录邮箱操作处理。`
+        }
+    },
+    methods: {
+        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>

+ 269 - 0
pages/loginRegister/login.vue

@@ -0,0 +1,269 @@
+<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('请输入邮箱账号')"
+        />
+        <ZxInput
+            v-model="form.passwd"
+            :placeholder="$t('请输入密码')"
+            isPassword
+        />
+        <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,
+        isCheckAgreement: false,
+        checkShakeObj: {
+          email: false,
+          password: false,
+          agreemen: false
+        },
+        form: {
+          email: '',
+          passwd: ''
+        }
+      }
+    },
+    components: {
+      ZxInput
+    },
+    computed: {
+      isSubmt({ form }) {
+        return form.email && form.passwd
+      }
+    },
+    methods: {
+      clearInput(val) {
+        this.$set(this.form, val, '')
+      },
+      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>
+  

+ 154 - 0
pages/loginRegister/register.vue

@@ -0,0 +1,154 @@
+<template>
+    <view class="register-page">
+        <ZxInput
+            v-model="form.email"
+            :placeholder="$t('请输入邮箱账号')"
+        />
+        <ZxInput
+            v-model="form.passwd"
+            :placeholder="$t('请输入密码')"
+            isPassword
+        />
+        <ZxInput
+            v-model="form.second_passwd"
+            :placeholder="$t('请再次输入密码')"
+            isPassword
+        />
+        <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,
+            isSendSucceed: false,
+            form: {},
+            emailFocus: false,
+            checkShakeObj: {
+                agreemen: false
+            }
+        }
+    },
+    computed: {
+        noticeText({ email }) {
+            return `我们向 <span style="color: #0A59F7;">${email}</span> 发送了一封注册邮件,请您登录邮箱点击链接完成注册。`
+        },
+        isSubmt({ form }) {
+            return form.email && form.passwd && form.second_passwd
+        }
+    },
+    methods: {
+        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' })
+        },
+        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>

+ 87 - 0
pages/message/deviceInfo.vue

@@ -0,0 +1,87 @@
+<template>
+    <view class="zx-container 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 {
+    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>

+ 236 - 0
pages/message/index.vue

@@ -0,0 +1,236 @@
+<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: 24rpx 0;
+
+        .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;
+            &:last-child {
+                border-bottom: none;
+            }
+
+            .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>

+ 13 - 11
pages/my/my.scss

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

+ 114 - 84
pages/my/my.vue

@@ -1,98 +1,128 @@
 <template>
-    <view class="container-view">
-        <view class="user-switch-row">
-            <view class="name-wrap">{{ userInfo.name }}</view>
-            <image class="message" @tap="routerLink({ url: '/pages/message/index' })"
-                :src="QINIU_URL + 'FghCVNMWDBKJpqbIrqoxT-de9Has'" />
-        </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="container-view zx-page-linear">
+      <view class="user-switch-row">
+        <image
+          :src="QINIU_URL + 'FlL5BtEdMES2-mntjR9D3CX_LWYv'"
+          class="message"
+          @tap="routerLink({ url: '/pages/message/index' })"
+        />
+      </view>
+      <view class="user-info-wrap" @tap="loginHandle">
+        <image :src="userInfo.headimg || defaultAvatarUrl" class="head-img" />
+        <view class="user-name">{{ userInfo.nickname || '请点击登录' }}</view>
+        <view
+          v-if="userInfo.user_name"
+          class="e-mail"
+        >
+          {{ userInfo.user_name }}
         </view>
-        <view class="common-tabs">
-            <view class="item" v-for="(item, index) in commonTabs" :key="index" @click="routerLink(item)">
-                <image :src="QINIU_URL + item.icon" class="icon" />
-                <view class="name">{{ item.name }}</view>
-            </view>
+      </view>
+      <view class="common-tabs">
+        <view
+          v-for="(item, index) in commonTabs"
+          :key="index"
+          class="item"
+          @click="routerLink(item)"
+        >
+          <image :src="QINIU_URL + item.icon" class="icon" />
+          <view class="name">{{ item.name }}</view>
         </view>
-        <view class="tabs-wrap">
-            <view class="tab-item" v-for="(item, index) in baseTabs" :key="index" @click="routerLink(item)">
-                <image :src="QINIU_URL + item.icon" class="icon" />
-                <view class="name">{{ item.name }}</view>
-            </view>
+      </view>
+      <view class="tabs-wrap">
+        <view
+          v-for="(item, index) in baseTabs"
+          :key="index"
+          class="tab-item"
+          @click="routerLink(item)"
+        >
+          <image :src="QINIU_URL + item.icon" class="icon" />
+          <view class="name">{{ item.name }}</view>
         </view>
-        <Confirm 
-            v-model="comboDialoginfo.showConfirm"
-            :dialogInfo="comboDialoginfo"
-            @confirm="dialogConfirm"
-        />
+      </view>
+      <Confirm
+        v-model="comboDialoginfo.showConfirm"
+        :dialog-info="comboDialoginfo"
+        @confirm="dialogConfirm"
+      />
+      <CustomTabbar curt-tab="my" />
     </view>
-</template>
-
-<script>
-import Confirm from '@/component/comPopup/Confirm'
-import { QINIU_URL } from '@/common/constant'
-export default {
+  </template>
+  
+  <script>
+  const storage = require('@/common/storage.js')
+  import Confirm from '@/component/comPopup/Confirm'
+  import { QINIU_URL, defaultHeadImg } from '@/common/constant'
+  import CustomTabbar from '@/component/customTabbar/index'
+  export default {
     components: {
-        Confirm
+      Confirm,
+      CustomTabbar
     },
     data() {
-        return {
-            QINIU_URL,
-            comboDialoginfo: {
-                showConfirm: false,
-                title: '温馨提示',
-                opType: 'combo',
-                text: '您还未购买换电套餐,是否前往进行换电套餐?',
-                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'
-            }
-        }
+      return {
+        QINIU_URL,
+        defaultAvatarUrl: defaultHeadImg,
+        comboDialoginfo: {
+          showConfirm: false,
+          title: '温馨提示',
+          opType: 'combo',
+          text: '您还未购买换电套餐,是否前往进行换电套餐?',
+          confirmBtnText: '前往购买',
+          showCancelButton: false
+        },
+        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' },
-            ]
-        },
-        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' },
-            ]
-        }
+      commonTabs() {
+        return [
+          { name: `${this.$t('我的车辆')}`, 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: '/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: {
-        checkHandle_combo() {
-            this.comboDialoginfo.showConfirm = true
-        },
-        dialogConfirm(type) {
-            console.log('dialogConfirm', type)
-        },
-        routerLink({ url, jumpCheck }) {
-            if (jumpCheck) {
-                this[`checkHandle_${jumpCheck}`]()
-                return
-            }
-            uni.navigateTo({ url })
+      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
+      },
+      dialogConfirm(type) {
+        console.log('dialogConfirm', type)
+      },
+      routerLink({ url, jumpCheck }) {
+        if (jumpCheck) {
+          this[`checkHandle_${jumpCheck}`]()
+          return
         }
+        uni.navigateTo({ url })
+      }
     }
-}
-</script>
-
-<style lang="scss" scoped>
-@import './my.scss';
-</style>
+  }
+  </script>
+  
+  <style lang="scss" scoped>
+  @import './my.scss';
+  </style>
+  

+ 145 - 0
pages/my/set.vue

@@ -0,0 +1,145 @@
+<template>
+    <view class="set-page zx-container">
+      <image :src="userInfo.headImg || defaultHeadImg" class="head-img" />
+      <view class="list-wrap">
+        <view
+          v-for="(item, index) in list"
+          :key="index"
+          class="item"
+          @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="zx-form-btn fix-bottom-btn 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;
+    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 {
+        color: #FA2918;
+        background: #E4E7EC;
+    }
+  }
+  </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>