Jelajahi Sumber

注册&绑定

panhui 3 tahun lalu
induk
melakukan
ec02a3ecb1

+ 3 - 1
package.json

@@ -11,9 +11,11 @@
   "dependencies": {
     "@ionic/vue": "^6.3.8",
     "@ionic/vue-router": "^6.3.8",
+    "@vant/area-data": "^1.3.2",
     "@vueuse/core": "^9.6.0",
     "axios": "^1.2.0",
     "date-fns": "^2.29.3",
+    "eruda": "^2.6.1",
     "ionicons": "^6.0.4",
     "less": "^4.1.3",
     "less-loader": "^11.1.0",
@@ -21,7 +23,7 @@
     "pinia": "^2.0.26",
     "qs": "^6.11.0",
     "swiper": "^8.4.5",
-    "vant": "^4.0.0",
+    "vant": "^3.2.3",
     "vue": "^3.2.45",
     "vue-i18n": "9",
     "vue-router": "^4.1.6"

TEMPAT SAMPAH
src/assets/login_icon_bukejian.png


TEMPAT SAMPAH
src/assets/login_icon_kejian.png


TEMPAT SAMPAH
src/assets/login_icon_mima.png


TEMPAT SAMPAH
src/assets/login_icon_yanzhengma.png


TEMPAT SAMPAH
src/assets/login_icon_zhanghao.png


TEMPAT SAMPAH
src/assets/nav_icon_del.png


TEMPAT SAMPAH
src/assets/png_moren.png


+ 2 - 2
src/components/AddressItem.vue

@@ -24,7 +24,7 @@
     </div>
 </template>
 <script>
-import { userUserStore } from '@/stores/user'
+import { useUserStore } from '@/stores/user'
 import { mapState } from 'pinia'
 import activeIcon from '@/assets/icon_xuanzhong_pre.png'
 import inactiveIcon from '@/assets/icon_xuanzhong.png'
@@ -53,7 +53,7 @@ export default {
         }
     },
     computed: {
-        ...mapState(userUserStore, ['user'])
+        ...mapState(useUserStore, ['user'])
     },
     methods: {
         chooseAddress() {

+ 7 - 0
src/mixins/common.js

@@ -18,6 +18,13 @@ export default {
             } else {
                 return ''
             }
+        },
+        checkWebDriver(e) {
+            if (window.navigator.webdriver == true || e.isTrusted == false) {
+                return false
+            } else {
+                return true
+            }
         }
     }
 }

+ 98 - 0
src/mixins/phone.js

@@ -0,0 +1,98 @@
+//手机号码相关
+export default {
+    data() {
+        return {
+            phonePattern: /^[1][3,4,5,6,7,8,9][0-9]{9}$/,
+            isSend: false,
+            msgCode: '',
+            sendNum: 60,
+            timer: null,
+            captchaKey: '',
+            codeImg: '',
+            captcha: '',
+            phone: ''
+        }
+    },
+    methods: {
+        getCode() {
+            return this.$http.get('/captcha/get').then(res => {
+                this.captchaKey = res.key
+                this.codeImg = res.image
+                this.$toast.clear()
+                this.emitter.emit('phoneShow')
+            })
+        },
+        sendMsg(phone) {
+            this.phone = phone
+            this.isSend = true
+            this.setTime(60)
+            return this.$http
+                .get('/sms/sendSecureVerify', {
+                    phone: phone,
+                    captcha: this.captcha,
+                    captchaKey: this.captchaKey
+                })
+                .then(res => {
+                    this.msgCode = res
+                    this.$toast.success('发送成功')
+                    this.captcha = ''
+                    this.captchaKey = ''
+                    return Promise.resolve()
+                })
+                .catch(e => {
+                    if (e) {
+                        this.$toast(e.error)
+                        this.captcha = ''
+                        this.captchaKey = ''
+                    }
+                    this.setTime(0)
+                    return Promise.reject()
+                })
+        },
+        setTime(num) {
+            this.sendNum = num
+            if (this.timer) {
+                clearTimeout(this.timer)
+            }
+
+            if (num <= 0) {
+                this.isSend = false
+                return
+            } else {
+                this.timer = setTimeout(() => {
+                    this.setTime(num - 1)
+                }, 1000)
+            }
+        },
+        verifyMsg(phone, code) {
+            return this.$http
+                .get('/sms/verify', {
+                    phone: phone,
+                    code: code
+                })
+                .catch(e => {
+                    if (e) {
+                        this.$toast(e.error)
+                    }
+                    return Promise.reject()
+                })
+        },
+        loginByPhone(phone, psd) {
+            return this.$http
+                .post('/auth/phonePwdLogin', {
+                    phone: phone,
+                    password: psd
+                })
+                .catch(e => {
+                    if (e) {
+                        this.$toast(e.error)
+                    }
+                    return Promise.reject()
+                })
+                .then(res => {
+                    localStorage.setItem('nineToken', res)
+                    return this.$store.dispatch('getUserInfo')
+                })
+        }
+    }
+}

+ 28 - 3
src/router/index.js

@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from '@ionic/vue-router'
 import TabsPage from '../views/TabsPage.vue'
 import { Page } from './Page'
 import { useUserStore } from '../stores/user'
-import { showConfirmDialog } from 'vant'
+import { Dialog } from 'vant'
 
 const router = createRouter({
     history: createWebHistory(import.meta.env.BASE_URL),
@@ -43,6 +43,22 @@ const router = createRouter({
                 pageType: Page.Login
             }
         },
+        {
+            path: '/loginPhone',
+            name: 'loginPhone',
+            component: () => import('@/views/LoginPhonePage.vue'),
+            meta: {
+                pageType: Page.Login
+            }
+        },
+        {
+            path: '/register',
+            name: 'register',
+            component: () => import('@/views/RegisterPage.vue'),
+            meta: {
+                pageType: Page.Login
+            }
+        },
         {
             path: '/order',
             name: 'order',
@@ -78,6 +94,16 @@ const router = createRouter({
             meta: {
                 pageType: Page.Every
             }
+        },
+        {
+            path: '/setting',
+            name: 'setting',
+            component: () => import('@/views/SettingPage.vue')
+        },
+        {
+            path: '/changeText',
+            name: 'changeText',
+            component: () => import('@/views/ChangeTextPage.vue')
         }
     ]
 })
@@ -92,8 +118,7 @@ router.beforeEach((to, from, next) => {
                     next()
                 })
                 .catch(() => {
-                    console.log('37366')
-                    showConfirmDialog({
+                    Dialog.confirm({
                         title: '提示',
                         message: '用户未登录,是否立即登录'
                     })

+ 49 - 1
src/stores/user.js

@@ -20,11 +20,59 @@ export const useUserStore = defineStore('user', () => {
                 })
         })
     }
+    const loginByPhone = (phone, password) => {
+        return new Promise((resolve, reject) => {
+            http.post('/auth/phonePwdLogin', { phone, password })
+                .then(token => {
+                    http.setToken(token)
+                    return http.get('/user/my')
+                })
+                .then(res => {
+                    user.value = res
+                    resolve(res)
+                })
+                .catch(e => {
+                    reject(e)
+                })
+        })
+    }
+    const loginByCode = (phone, code) => {
+        return new Promise((resolve, reject) => {
+            http.post('/auth/phoneLogin', { phone, code })
+                .then(token => {
+                    http.setToken(token)
+                    return http.get('/user/my')
+                })
+                .then(res => {
+                    user.value = res
+                    resolve(res)
+                })
+                .catch(e => {
+                    reject(e)
+                })
+        })
+    }
+    const register = (phone, code, password) => {
+        return new Promise((resolve, reject) => {
+            http.post('/auth/phoneRegister', { phone, code, password })
+                .then(token => {
+                    http.setToken(token)
+                    return http.get('/user/my')
+                })
+                .then(res => {
+                    user.value = res
+                    resolve(res)
+                })
+                .catch(e => {
+                    reject(e)
+                })
+        })
+    }
     const get = async () => {
         return http.get('/user/my').then(res => {
             console.log(res)
             user.value = res
         })
     }
-    return { user, login, get }
+    return { user, login, get, loginByPhone, loginByCode, register }
 })

+ 13 - 1
src/styles/main.less

@@ -4,7 +4,7 @@
     --van-tabbar-item-font-size: 10px;
     --van-tabbar-item-icon-size: 28px;
     --van-tabbar-item-icon-margin-bottom: 2px;
-    --van-dialog-confirm-button-text-color: #ff0000;
+    --van-dialog-confirm-button-text-color: #a108ff;
     --van-button-default-color: #aaacad;
     --van-gray-6: #c8c9cc;
     --yellow: #ffd563;
@@ -43,3 +43,15 @@ ion-toast.error-toast {
 .in-toolbar {
     --border-color: #fff;
 }
+
+.van-button--primary {
+    background: linear-gradient(135deg, #a108ff 0%, #5c7cff 100%);
+    border-width: 0;
+}
+.van-button--plain {
+    background: transparent;
+    --van-button-primary-background: #a108ff;
+}
+.van-button {
+    --van-button-normal-font-size: 14px;
+}

+ 125 - 0
src/views/ChangeTextPage.vue

@@ -0,0 +1,125 @@
+<template>
+    <ion-page>
+        <ion-content :fullscreen="true">
+            <div class="change" v-if="type === 'nickname'">
+                <van-field
+                    label="昵称"
+                    v-model="message"
+                    type="text"
+                    :maxlength="10"
+                    :placeholder="'请输入昵称'"
+                    clearable
+                />
+                <div class="btn">
+                    <van-button type="primary" block radius="4" @click="save"> 确认修改 </van-button>
+                </div>
+            </div>
+        </ion-content>
+    </ion-page>
+</template>
+
+<script>
+import { mapState } from 'pinia'
+import { useUserStore } from '@/stores/user'
+export default {
+    computed: {
+        ...mapState(useUserStore, ['user'])
+    },
+    data() {
+        return {
+            type: 'nickname',
+            message: '',
+            // briefIntroduction: '',
+            // school: '',
+            // occupation: "",
+            // company: '',
+            // mailbox: '',
+            info: {}
+        }
+    },
+    setup() {
+        const userStore = useUserStore()
+        return {
+            userStore
+        }
+    },
+    mounted() {
+        this.type = this.$route.query.type
+        this.message = this.user.nickname
+    },
+    methods: {
+        updateUser(info, sucess = true) {
+            return this.$http
+                .post(
+                    '/user/my/update',
+                    {
+                        ...this.user,
+                        ...info
+                    },
+                    { body: 'json' }
+                )
+                .then(() => {
+                    return this.userStore.get()
+                })
+                .then(() => {
+                    if (sucess) {
+                        this.$toast.success('更新成功')
+                    }
+                    return Promise.resolve()
+                })
+                .catch(e => {
+                    if (e) {
+                        this.$toast(e.error)
+                    }
+                    return Promise.reject()
+                })
+        },
+        save() {
+            let data = {
+                userId: this.user.id,
+                ...this.info,
+                nickname: this.message
+            }
+            this.updateUser({ [this.type]: this.message }).then(() => {
+                this.$router.back()
+            })
+            // /userDetail/save
+            // if (this.message) {
+            //     this.updateUser({ [this.type]: this.message }).then(() => {
+            //         setTimeout(() => {
+            //             this.$router.back();
+            //         }, 1500);
+            //     });
+            // }
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.title {
+    padding: 23px 16px;
+}
+
+:deep(.van-cell) {
+    --van-field-label-color: #000000;
+    --van-cell-value-color: #000000;
+    --van-field-label-width: 50px;
+    --van-cell-vertical-padding: 18px;
+    --van-cell-horizontal-padding: 15px;
+    .van-field__label {
+        font-weight: bold;
+    }
+
+    .van-field__value {
+        font-size: 16px;
+    }
+}
+
+.change {
+    padding: 16px;
+}
+.btn {
+    padding: 40px 12px;
+}
+</style>

+ 166 - 60
src/views/LoginPage.vue

@@ -1,69 +1,175 @@
 <template>
     <ion-page>
-        <ion-header>
-            <ion-toolbar>
-                <ion-buttons slot="start">
-                    <ion-back-button default-href="#" @click="$router.back(-1)"></ion-back-button>
-                </ion-buttons>
-                <ion-title>Login</ion-title>
-            </ion-toolbar>
-        </ion-header>
-        <ion-content :fullscreen="true">
-            <!-- <ion-header collapse="condense">
-                <ion-toolbar>
-                    <ion-title size="large">{{ $t('message.login') }}</ion-title>
-                </ion-toolbar>
-            </ion-header> -->
-            <ion-list>
-                <ion-item>
-                    <ion-label>{{ $t('message.username') }}</ion-label>
-                    <ion-input v-model="username" :placeholder="$t('message.username')"></ion-input>
-                </ion-item>
-                <ion-item>
-                    <ion-label>{{ $t('message.password') }}</ion-label>
-                    <ion-input v-model="password" type="password" :placeholder="$t('message.username')"></ion-input>
-                </ion-item>
-            </ion-list>
-            <ion-button @click="onLogin">{{ $t('message.login') }}</ion-button>
+        <ion-content :fullscreen="true" class="login">
+            <van-form @submit="onSubmit">
+                <div class="title">密码登录</div>
+                <van-cell-group :border="false">
+                    <van-field
+                        v-model="form.phone"
+                        name="手机号"
+                        placeholder="请输入手机号"
+                        type="tel"
+                        :rules="[
+                            { required: true, message: '请输入手机号' },
+                            {
+                                pattern: phonePattern,
+                                message: '手机号码格式错误'
+                            }
+                        ]"
+                        clearable
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_zhanghao.png" alt="" class="icon" />
+                        </template>
+                    </van-field>
+                    <van-field
+                        v-model="form.password"
+                        :type="showPsd ? 'text' : 'password'"
+                        name="密码"
+                        placeholder="请输入密码"
+                        clearable
+                        :rules="[{ required: true, message: '请输入密码' }]"
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_mima.png" alt="" class="icon" />
+                        </template>
+                        <template #button>
+                            <div class="showPsd" @click="showPsd = !showPsd">
+                                <img v-if="showPsd" src="../assets/login_icon_kejian.png" alt="" />
+                                <img v-else src="../assets/login_icon_bukejian.png" alt="" />
+                            </div>
+                        </template>
+                    </van-field>
+                    <van-cell class="xieyi">
+                        <van-checkbox v-model="checked">
+                            已阅读并同意
+                            <!-- <span @click.stop="$router.push('/agreement?page=service')"> 《用户服务协议》 </span> -->
+                            <span class="rule"> 注册协议 </span>
+                        </van-checkbox>
+                    </van-cell>
+                </van-cell-group>
+                <div class="submit">
+                    <van-button radius="4" block type="primary" native-type="submit"> 登录 </van-button>
+                </div>
+
+                <div class="btns">
+                    <div class="btn-left">暂无账号?<span @click="$router.replace('/register')">立即注册</span></div>
+                    <div class="btn-right" @click="$router.replace('/loginPhone')">验证码登录</div>
+                </div>
+            </van-form>
         </ion-content>
     </ion-page>
 </template>
-
-<script setup>
-import {
-    IonPage,
-    IonHeader,
-    IonToolbar,
-    IonTitle,
-    IonContent,
-    IonButtons,
-    IonBackButton,
-    IonInput,
-    IonButton,
-    IonLabel,
-    IonList,
-    IonItem
-} from '@ionic/vue'
+<script>
+import phone from '../mixins/phone'
 import { ref } from 'vue'
-import toast from '@/utils/toast'
-import { useI18n } from 'vue-i18n'
-import { useUserStore } from '@/stores/user'
-import { useRouter } from 'vue-router'
-
-const router = useRouter()
-const { user, login } = useUserStore()
-const { t } = useI18n()
-const username = ref()
-const password = ref()
-async function onLogin() {
-    try {
-        await toast.loading(t('message.logining'))
-        await login(username.value, password.value)
-        await toast.success(t('message.loginSuccess'))
-        router.replace('/home/')
-    } catch (e) {
-        console.log(e)
-        await toast.error(e.error)
+export default {
+    mixins: [phone],
+    setup() {
+        const form = ref({
+            phone: '',
+            password: ''
+        })
+
+        const checked = ref(false)
+
+        const showPsd = ref(false)
+
+        return { form, checked, showPsd }
     }
 }
 </script>
+
+<style lang="less" scoped>
+.login {
+    --ion-background-color: #fff;
+}
+
+.van-form {
+    padding: 30px 40px;
+
+    .title {
+        font-size: 24px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 34px;
+        padding-bottom: 30px;
+    }
+
+    :deep(.van-cell) {
+        .f();
+        align-items: flex-start;
+        --van-cell-vertical-padding: 10px;
+        --van-cell-horizontal-padding: 0;
+        --van-field-placeholder-text-color: #939599;
+        --van-padding-base: 10px;
+
+        .van-field__left-icon {
+            .f();
+        }
+    }
+
+    .van-cell + .van-cell {
+        margin-top: 20px;
+    }
+}
+
+.icon {
+    width: 24px;
+    height: 24px;
+}
+
+.xieyi {
+    --van-checkbox-label-color: #939599;
+    margin-top: 12px !important;
+    .rule {
+        color: #a108ff;
+    }
+    .van-checkbox {
+        box-sizing: content-box;
+    }
+
+    :deep(.van-checkbox__icon--checked .van-icon) {
+        background: linear-gradient(135deg, #a108ff 0%, #5c7cff 100%);
+        border-color: #fff;
+    }
+}
+
+.submit {
+    margin-top: 34px;
+
+    .van-button {
+        font-weight: bold;
+    }
+}
+
+.showPsd {
+    .f();
+    img {
+        width: 28px;
+        height: 28px;
+    }
+}
+
+.btns {
+    .f();
+    justify-content: space-between;
+    padding-top: 20px;
+    .btn-left {
+        font-size: 12px;
+        color: #939599;
+        line-height: 24px;
+        span {
+            color: #a108ff;
+        }
+    }
+
+    .btn-right {
+        font-size: 12px;
+        color: #000000;
+        line-height: 24px;
+        text-decoration: underline;
+        font-weight: bold;
+    }
+}
+</style>

+ 186 - 0
src/views/LoginPhonePage.vue

@@ -0,0 +1,186 @@
+<template>
+    <ion-page>
+        <ion-content :fullscreen="true" class="login">
+            <van-form ref="form" @submit="onSubmit">
+                <div class="title">密码登录</div>
+                <van-cell-group :border="false">
+                    <van-field
+                        v-model="form.phone"
+                        name="手机号"
+                        placeholder="请输入手机号"
+                        type="tel"
+                        :rules="[
+                            { required: true, message: '请输入手机号' },
+                            {
+                                pattern: phonePattern,
+                                message: '手机号码格式错误'
+                            }
+                        ]"
+                        clearable
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_zhanghao.png" alt="" class="icon" />
+                        </template>
+                    </van-field>
+                    <van-field
+                        v-model="form.password"
+                        :type="showPsd ? 'text' : 'password'"
+                        name="密码"
+                        placeholder="请输入密码"
+                        clearable
+                        :rules="[{ required: true, message: '请输入密码' }]"
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_mima.png" alt="" class="icon" />
+                        </template>
+                        <template #button>
+                            <van-button @click="sendPhone" :disabled="isSend" size="small" type="primary" plain>
+                                {{ isSend ? `已发送(${sendNum})S` : '发送验证码' }}
+                            </van-button>
+                        </template>
+                    </van-field>
+                    <van-cell class="xieyi">
+                        <van-checkbox v-model="checked">
+                            已阅读并同意
+                            <!-- <span @click.stop="$router.push('/agreement?page=service')"> 《用户服务协议》 </span> -->
+                            <span class="rule"> 注册协议 </span>
+                        </van-checkbox>
+                    </van-cell>
+                </van-cell-group>
+                <div class="submit">
+                    <van-button radius="4" block type="primary" native-type="submit"> 登录 </van-button>
+                </div>
+
+                <div class="btns">
+                    <div class="btn-right" @click="$router.replace('/login')">密码登录</div>
+                </div>
+            </van-form>
+        </ion-content>
+    </ion-page>
+</template>
+<script>
+import phone from '../mixins/phone'
+import { ref } from 'vue'
+export default {
+    mixins: [phone],
+    setup() {
+        const form = ref({
+            phone: '',
+            password: ''
+        })
+
+        const checked = ref(false)
+
+        const showPsd = ref(false)
+
+        return { form, checked, showPsd }
+    },
+    methods: {
+        sendPhone(e) {
+            this.$refs.form.validate('手机号').then(() => {
+                if (!this.checkWebDriver(e)) {
+                    return
+                }
+                this.sendMsg(this.form.phone)
+            })
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.login {
+    --ion-background-color: #fff;
+}
+
+.van-form {
+    padding: 30px 40px;
+
+    .title {
+        font-size: 24px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 34px;
+        padding-bottom: 30px;
+    }
+
+    :deep(.van-cell) {
+        .f();
+        align-items: flex-start;
+        --van-cell-vertical-padding: 10px;
+        --van-cell-horizontal-padding: 0;
+        --van-field-placeholder-text-color: #939599;
+        --van-padding-base: 10px;
+        --van-button-small-height: 24px;
+
+        .van-field__left-icon {
+            .f();
+        }
+        .van-field__button {
+            .f();
+        }
+    }
+
+    .van-cell + .van-cell {
+        margin-top: 20px;
+    }
+}
+
+.icon {
+    width: 24px;
+    height: 24px;
+}
+
+.xieyi {
+    --van-checkbox-label-color: #939599;
+    margin-top: 12px !important;
+    .rule {
+        color: #a108ff;
+    }
+    .van-checkbox {
+        box-sizing: content-box;
+    }
+
+    :deep(.van-checkbox__icon--checked .van-icon) {
+        background: linear-gradient(135deg, #a108ff 0%, #5c7cff 100%);
+        border-color: #fff;
+    }
+}
+
+.submit {
+    margin-top: 34px;
+
+    .van-button {
+        font-weight: bold;
+    }
+}
+
+.showPsd {
+    .f();
+    img {
+        width: 28px;
+        height: 28px;
+    }
+}
+
+.btns {
+    .f();
+    justify-content: center;
+    padding-top: 20px;
+    .btn-left {
+        font-size: 12px;
+        color: #939599;
+        line-height: 24px;
+        span {
+            color: #a108ff;
+        }
+    }
+
+    .btn-right {
+        font-size: 12px;
+        color: #000000;
+        line-height: 24px;
+        font-weight: bold;
+    }
+}
+</style>

+ 9 - 4
src/views/MinePage.vue

@@ -2,13 +2,13 @@
     <ion-page>
         <ion-content class="mine" :fullscreen="true">
             <img class="userInfo-bg" src="../assets/userBg.png" alt="" />
-            <div class="userInfo" v-if="user">
+            <div class="userInfo" v-if="user" @click="goSetting">
                 <van-image width="60" height="60" radius="4" :src="user.avatar"></van-image>
                 <span class="name">{{ user.nickname }}</span>
                 <img src="../assets/icon_bianji.png" class="icon" alt="" />
             </div>
             <div class="userInfo" v-else @click="goLogin">
-                <van-image width="60" height="60" radius="4"></van-image>
+                <van-image width="60" height="60" radius="4" :src="notLogo"></van-image>
                 <span class="name">未登录</span>
                 <img src="../assets/icon_bianji.png" class="icon" alt="" />
             </div>
@@ -84,7 +84,7 @@
                     </template>
                 </van-cell>
 
-                <van-cell title="设置" is-link>
+                <van-cell title="设置" is-link @click="goSetting">
                     <template #icon>
                         <img class="menu-icon" src="../assets/info_icon_shezhi.png" alt="" />
                     </template>
@@ -101,6 +101,7 @@
 import { computed } from 'vue'
 import { useUserStore } from '../stores/user'
 import { useRouter } from 'vue-router'
+import notLogo from '../assets/png_moren.png'
 const userStore = useUserStore()
 const user = computed(() => {
     return userStore.user
@@ -108,7 +109,11 @@ const user = computed(() => {
 
 const router = useRouter()
 const goLogin = () => {
-    router.push('/home')
+    router.push('/login')
+}
+
+const goSetting = () => {
+    router.push('/setting')
 }
 </script>
 

+ 281 - 0
src/views/RegisterPage.vue

@@ -0,0 +1,281 @@
+<template>
+    <ion-page>
+        <ion-content :fullscreen="true" class="login">
+            <van-form ref="form" @submit="onSubmit">
+                <div class="title">注册账号</div>
+                <van-cell-group :border="false">
+                    <van-field
+                        v-model="formData.phone"
+                        name="手机号"
+                        placeholder="请输入手机号"
+                        type="tel"
+                        :rules="[
+                            { required: true, message: '请输入手机号' },
+                            {
+                                pattern: phonePattern,
+                                message: '手机号码格式错误'
+                            }
+                        ]"
+                        clearable
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_zhanghao.png" alt="" class="icon" />
+                        </template>
+                    </van-field>
+                    <van-field
+                        v-model="formData.password"
+                        type="digit"
+                        name="验证码"
+                        placeholder="请输入验证码"
+                        clearable
+                        :rules="[{ required: true, message: '请输入验证码' }]"
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_yanzhengma.png" alt="" class="icon" />
+                        </template>
+                        <template #button>
+                            <van-button @click="sendPhone" :disabled="isSend" size="small" type="primary" plain>
+                                {{ isSend ? `已发送(${sendNum})S` : '发送验证码' }}
+                            </van-button>
+                        </template>
+                    </van-field>
+                    <van-field
+                        v-model="formData.password"
+                        :type="showPsd ? 'text' : 'password'"
+                        name="密码"
+                        placeholder="请输入密码"
+                        clearable
+                        :rules="[{ required: true, message: '建议8-16位,数字与字母的组合' }]"
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_mima.png" alt="" class="icon" />
+                        </template>
+                        <template #button>
+                            <div class="showPsd" @click="showPsd = !showPsd">
+                                <img v-if="showPsd" src="../assets/login_icon_kejian.png" alt="" />
+                                <img v-else src="../assets/login_icon_bukejian.png" alt="" />
+                            </div>
+                        </template>
+                    </van-field>
+                    <van-field
+                        v-model="formData.password2"
+                        :type="showPsd ? 'text' : 'password'"
+                        name="密码"
+                        placeholder="请再次输入密码"
+                        clearable
+                        :rules="[
+                            { required: true, message: '请再次输入密码' },
+                            {
+                                validator: val => {
+                                    if (val == formData.password) {
+                                        return true
+                                    } else {
+                                        return false
+                                    }
+                                },
+                                message: '两次密码输入不一致'
+                            }
+                        ]"
+                    >
+                        <template #left-icon>
+                            <img src="../assets/login_icon_mima.png" alt="" class="icon" />
+                        </template>
+                        <template #button>
+                            <div class="showPsd" @click="showPsd = !showPsd">
+                                <img v-if="showPsd" src="../assets/login_icon_kejian.png" alt="" />
+                                <img v-else src="../assets/login_icon_bukejian.png" alt="" />
+                            </div>
+                        </template>
+                    </van-field>
+                    <van-cell class="xieyi">
+                        <van-checkbox v-model="checked">
+                            已阅读并同意
+                            <!-- <span @click.stop="$router.push('/agreement?page=service')"> 《用户服务协议》 </span> -->
+                            <span class="rule"> 注册协议 </span>
+                        </van-checkbox>
+                    </van-cell>
+                </van-cell-group>
+                <div class="submit">
+                    <van-button radius="4" block type="primary" native-type="submit"> 登录 </van-button>
+                </div>
+
+                <div class="btns">
+                    <div class="btn-right" @click="$router.replace('/login')">已有账号,立即登录</div>
+                </div>
+            </van-form>
+        </ion-content>
+    </ion-page>
+</template>
+<script>
+import phone from '../mixins/phone'
+import { ref } from 'vue'
+import { useUserStore } from '../stores/user'
+
+let fromRoute = null
+export default {
+    mixins: [phone],
+    setup() {
+        const formData = ref({
+            phone: '',
+            code: '',
+            password: '',
+            password2: ''
+        })
+
+        const checked = ref(false)
+
+        const showPsd = ref(false)
+
+        const userStore = useUserStore()
+
+        return { formData, checked, showPsd, userStore }
+    },
+    beforeRouteEnter(to, from) {
+        fromRoute = from
+        console.log(to, from)
+    },
+    methods: {
+        sendPhone(e) {
+            this.$refs.form.validate('手机号').then(() => {
+                if (!this.checkWebDriver(e)) {
+                    return
+                }
+                this.sendMsg(this.formData.phone)
+            })
+        },
+        onSubmit(e) {
+            if (!this.checked) {
+                this.$toast('请先阅读并同意隐私政策')
+                return
+            }
+            if (!this.checkWebDriver(e)) {
+                return
+            }
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            })
+
+            this.userStore
+                .register(this.formData.phone, this.formData.code, this.formData.password)
+                .then(() => {
+                    this.$toast.success('注册成功')
+                    setTimeout(() => {
+                        if (
+                            !fromRoute.name ||
+                            fromRoute.name === 'register' ||
+                            fromRoute.name === 'login' ||
+                            fromRoute.name === 'loginPhone'
+                        ) {
+                            this.$router.replace('/home')
+                        } else {
+                            this.$router.back()
+                        }
+                    }, 1500)
+                })
+                .catch(e => {
+                    if (e) {
+                        this.$toast(e.error)
+                    }
+                })
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.login {
+    --ion-background-color: #fff;
+}
+
+.van-form {
+    padding: 30px 40px;
+
+    .title {
+        font-size: 24px;
+        font-weight: bold;
+        color: #000000;
+        line-height: 34px;
+        padding-bottom: 30px;
+    }
+
+    :deep(.van-cell) {
+        .f();
+        align-items: flex-start;
+        --van-cell-vertical-padding: 10px;
+        --van-cell-horizontal-padding: 0;
+        --van-field-placeholder-text-color: #939599;
+        --van-padding-base: 10px;
+        --van-button-small-height: 24px;
+
+        .van-field__left-icon {
+            .f();
+        }
+        .van-field__button {
+            .f();
+        }
+    }
+
+    .van-cell + .van-cell {
+        margin-top: 20px;
+    }
+}
+
+.icon {
+    width: 24px;
+    height: 24px;
+}
+
+.xieyi {
+    --van-checkbox-label-color: #939599;
+    margin-top: 12px !important;
+    .rule {
+        color: #a108ff;
+    }
+    .van-checkbox {
+        box-sizing: content-box;
+    }
+
+    :deep(.van-checkbox__icon--checked .van-icon) {
+        background: linear-gradient(135deg, #a108ff 0%, #5c7cff 100%);
+        border-color: #fff;
+    }
+}
+
+.submit {
+    margin-top: 34px;
+
+    .van-button {
+        font-weight: bold;
+    }
+}
+
+.showPsd {
+    .f();
+    img {
+        width: 28px;
+        height: 28px;
+    }
+}
+
+.btns {
+    .f();
+    justify-content: center;
+    padding-top: 20px;
+    .btn-left {
+        font-size: 12px;
+        color: #939599;
+        line-height: 24px;
+        span {
+            color: #a108ff;
+        }
+    }
+
+    .btn-right {
+        font-size: 12px;
+        color: #000000;
+        line-height: 24px;
+        font-weight: bold;
+    }
+}
+</style>

+ 290 - 0
src/views/SettingPage.vue

@@ -0,0 +1,290 @@
+<template>
+    <ion-page>
+        <ion-content :fullscreen="true">
+            <div class="setting">
+                <van-cell-group :border="false">
+                    <van-cell title="头像" is-link>
+                        <template #value>
+                            <van-image
+                                radius="4"
+                                width="36"
+                                height="36"
+                                :src="user.avatar || defaultLogo"
+                                fit="cover"
+                            />
+
+                            <van-uploader class="avatar" :after-read="afterRead" result-type="file" />
+                        </template>
+                        <template #right-icon>
+                            <img src="../assets/icon_inter.png" alt="" class="right-icon" />
+                        </template>
+                    </van-cell>
+                    <van-cell
+                        title="昵称"
+                        @click="$router.push('/changeText?type=nickname')"
+                        is-link
+                        :value="user.nickname"
+                    >
+                        <template #right-icon>
+                            <img src="../assets/icon_inter.png" alt="" class="right-icon" />
+                        </template>
+                    </van-cell>
+
+                    <van-cell
+                        title="性别"
+                        :class="{ not: !user.sex }"
+                        is-link
+                        :value="user.sex || '未设置'"
+                        @click="show = true"
+                    >
+                        <template #right-icon>
+                            <img src="../assets/icon_inter.png" alt="" class="right-icon" />
+                        </template>
+                    </van-cell>
+                </van-cell-group>
+                <!-- <van-cell-group :border="false">
+                    <van-cell title="出生日期" value="请选择出生日期" is-link></van-cell>
+                    <van-cell
+                        title="所在城市"
+                        :class="[cityName ? '' : 'not']"
+                        :value="cityName || '请选择城市'"
+                        is-link
+                    >
+                        <template #right-icon>
+                            <img src="../assets/icon_inter.png" alt="" class="right-icon" />
+                        </template>
+                    </van-cell>
+                </van-cell-group> -->
+                <van-cell-group :border="false">
+                    <van-cell title="绑定手机号" class="not" :value="user.phone"> </van-cell>
+                    <!-- <van-cell title="修改密码" is-link></van-cell> -->
+                    <van-cell class="not" title="用户ID" :value="user.id" @click="showConsoleEve" />
+                    <van-cell class="not" v-if="showConsole" title="审核版本" is-link @click="goReview" />
+                    <van-cell class="not" v-if="showConsole" title="测试app" is-link @click="goTest" />
+                </van-cell-group>
+
+                <van-action-sheet v-model:show="show" :actions="actions" cancel-text="取消" @select="chooseSex" />
+
+                <!-- <van-area title="选择地址" :area-list="areaList" :columns-num="2" /> -->
+            </div>
+        </ion-content>
+    </ion-page>
+</template>
+
+<script>
+import { mapState } from 'pinia'
+import { ref, computed } from 'vue'
+import eruda from 'eruda'
+import defaultLogo from '../assets/png_moren.png'
+import { useUserStore } from '@/stores/user'
+export default {
+    data() {
+        return {
+            showTest: false
+        }
+    },
+    computed: {
+        ...mapState(useUserStore, ['user'])
+    },
+    setup() {
+        //性别
+        const show = ref(false)
+        const actions = [{ name: '男' }, { name: '女' }]
+        const userStore = useUserStore()
+
+        const cityName = computed(() => {
+            return userStore.user.country + ' ' + userStore.user.city
+        })
+
+        return {
+            show,
+            actions,
+            clickNum: 0,
+            timeout: null,
+            defaultLogo,
+            userStore,
+            cityName
+        }
+    },
+    methods: {
+        chooseSex(val) {
+            this.updateUser({ sex: val.name }).then(() => {
+                this.show = false
+            })
+        },
+        afterRead(e) {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            })
+            this.updateFile(e, 'avatar', 300).then(img => {
+                this.updateUser({ avatar: img }).then(res => {
+                    this.$toast.clear()
+                })
+            })
+        },
+        afterRead2(e) {
+            this.$toast.loading({
+                message: '加载中...',
+                forbidClick: true
+            })
+            this.updateFile(e, 'bg', 1200).then(img => {
+                this.updateUser({ bg: img }).then(res => {
+                    this.$toast.clear()
+                })
+            })
+        },
+        showConsoleEve() {
+            this.clickNum++
+            if (this.clickNum >= 10) {
+                localStorage.setItem('showConsole', new Date().getTime() + 60 * 60 * 1000)
+                eruda.init()
+                this.clickNum = 0
+
+                this.$store.commit('setShowConsole', true)
+                return
+            } else {
+                if (this.timeout) {
+                    clearTimeout(this.timeout)
+                }
+                this.timeout = setTimeout(() => {
+                    this.clickNum = 0
+                }, 1000)
+            }
+        },
+        updateUser(info, sucess = true) {
+            console.log(info)
+            if (info) {
+                return this.$http
+                    .post(
+                        '/user/my/update',
+                        {
+                            ...this.user,
+                            ...info
+                        },
+                        { body: 'json' }
+                    )
+                    .then(() => {
+                        return this.userStore.get()
+                    })
+                    .then(() => {
+                        if (sucess) {
+                            this.$toast.success('更新成功')
+                        }
+                        return Promise.resolve()
+                    })
+                    .catch(e => {
+                        if (e) {
+                            this.$toast(e.error)
+                        }
+                        return Promise.reject()
+                    })
+            }
+        },
+        updateFile(e, type, size = 1000) {
+            const formData = new FormData()
+            formData.append('file', e.file, e.file.name)
+            formData.append('type', type)
+            formData.append('width', size)
+            formData.append('height', size)
+            formData.append('size', size)
+            return this.$http
+                .post('/upload/file', formData, {
+                    headers: { 'Content-Type': 'multipart/form-data' }
+                })
+                .then(res => {
+                    return Promise.resolve(res)
+                })
+        },
+        goReview() {
+            window.location.href = 'https://www.raex.vip/saas/home?review=true'
+        },
+        goTest() {
+            window.location.href = 'https://test.raex.vip/saas/home'
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+.setting {
+    padding: 16px;
+}
+
+.van-cell-group {
+    border-radius: 4px;
+    overflow: hidden;
+}
+.van-cell-group + .van-cell-group {
+    margin-top: 12px;
+}
+.tabs {
+    font-size: 20px;
+    font-weight: bold;
+    color: #000;
+    line-height: 30px;
+    padding: 0 16px;
+}
+
+.avatar {
+    position: absolute;
+    right: 0;
+    top: 0;
+    opacity: 0;
+    bottom: 0;
+}
+
+/deep/ .van-cell {
+    align-items: center;
+    height: 70px;
+    position: relative;
+    &:active {
+        background-color: #f2f4f5;
+    }
+    .van-cell__title {
+        span {
+            font-weight: bold;
+            font-size: 14px;
+        }
+    }
+
+    .van-cell__value {
+        span {
+            font-size: 16px;
+            color: #000;
+            line-height: 24px;
+        }
+    }
+
+    &.not {
+        .van-cell__value {
+            span {
+                font-size: 13px;
+                color: #c8c9cc;
+                line-height: 24px;
+            }
+        }
+    }
+
+    &.intro {
+        overflow: hidden;
+        .van-cell__value {
+            span {
+                font-size: 13px;
+                color: #000;
+                line-height: 24px;
+                display: -webkit-box;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                -webkit-line-clamp: 2;
+                -webkit-box-orient: vertical;
+            }
+        }
+    }
+}
+
+.right-icon {
+    width: 24px;
+    height: 24px;
+}
+</style>

+ 26 - 10
yarn.lock

@@ -246,14 +246,24 @@
   resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
   integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
 
-"@vant/popperjs@^1.3.0":
+"@vant/area-data@^1.3.2":
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/@vant/area-data/-/area-data-1.3.2.tgz#1672dcbeaf9ef33ceaba602ecd4f3cb0456c7737"
+  integrity sha512-KHGvvIxApxCXDTzsh5hPsIVrF4ll5J3pNgYNL3lmNJxye7aWySK97EgXiprVee+FshVa1jVW4esJ7VyW0l94WQ==
+
+"@vant/icons@^1.8.0":
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/@vant/icons/-/icons-1.8.0.tgz#36b13f2e628b533f6523a93a168cf02f07056674"
+  integrity sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg==
+
+"@vant/popperjs@^1.2.1":
   version "1.3.0"
-  resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
+  resolved "https://registry.yarnpkg.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
   integrity sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==
 
-"@vant/use@^1.4.3":
+"@vant/use@^1.4.2":
   version "1.4.3"
-  resolved "https://registry.npmmirror.com/@vant/use/-/use-1.4.3.tgz#0c542fe01a1439878e4aa8779d1791e189610d30"
+  resolved "https://registry.yarnpkg.com/@vant/use/-/use-1.4.3.tgz#0c542fe01a1439878e4aa8779d1791e189610d30"
   integrity sha512-rSnETN7P9qT1WbItMpQxBqe3cHeK2ZFYp1sCxWUXaTeI71TqA8sOdzC36ledZ36NQgFNTch9fsRPYOkrCgZfQA==
 
 "@vitejs/plugin-vue@^3.2.0":
@@ -1029,6 +1039,11 @@ error-ex@^1.2.0:
   dependencies:
     is-arrayish "^0.2.1"
 
+eruda@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/eruda/-/eruda-2.6.1.tgz#fa9453f0e5a41f16f9fc4ffdf7ecd36bbf2f3262"
+  integrity sha512-ZQGjB427oanCcDuoJKVA7vmrn2oIiV6vpW+gceDE/V/q4hKWdFK0bODMyrGDubHA0266NFovkQDPlK+15eAAJQ==
+
 esbuild-android-64@0.14.54:
   version "0.14.54"
   resolved "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
@@ -3553,13 +3568,14 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-vant@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/vant/-/vant-4.0.0.tgz#573e5bc5a98fbb452bf2069bc278e2c57edebbfc"
-  integrity sha512-s3FDA7mWfkgFBWq/8J09dQZETeCzfGT41289fTIcp7VEcME62zXEw+8S41+stJVjk8fNXYRvMXsGfcD7HTLpaQ==
+vant@^3.2.3:
+  version "3.6.9"
+  resolved "https://registry.yarnpkg.com/vant/-/vant-3.6.9.tgz#958eb47a01e1acdfb230e488290ab1a0ea674862"
+  integrity sha512-N7n66BMAU58HEPpUFRnCjdzK2swgRONFkkr7QLm1AoNBel7yvINZ0BYnrNU6+CMjEnTQmv8H3eN6t4bv9ZJ//A==
   dependencies:
-    "@vant/popperjs" "^1.3.0"
-    "@vant/use" "^1.4.3"
+    "@vant/icons" "^1.8.0"
+    "@vant/popperjs" "^1.2.1"
+    "@vant/use" "^1.4.2"
 
 vite-plugin-imagemin@^0.6.1:
   version "0.6.1"