vue 模拟商城 storage持久化存储

新建vue项目

 

 main.js

import Vue from 'vue'
import App from './App.vue'
import store from "@/store";
import router from "@/router";
import '@/utils/vant-ui';
import '@/styles/common.css'

Vue.config.productionTip = false;

new Vue({
  render: h => h(App),
  router,
  store,
}).$mount('#app');

app.vue

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>

export default {
  name: 'App'
}
</script>

<style>

</style>

search index.vue

<template>
    <div>我是Search</div>
</template>

<script>
    export default {
        name: "SearchIndex"
    }
</script>

<style scoped>

</style>

search list.vue

<template>
    <div>我是List</div>
</template>

<script>
    export default {
        name: "ListIndex"
    }
</script>

<style scoped>

</style>

productDetails index.vue

<template>
    <div>我是ProductDetails</div>
</template>

<script>
    export default {
        name: "ProductDetailsIndex"
    }
</script>

<style scoped>

</style>

pay index.vue

<template>
    <div>我是Pay</div>
</template>

<script>
    export default {
        name: "PayIndex"
    }
</script>

<style scoped>

</style>

myorder index.vue

<template>
    <div>我是MyOrder</div>
</template>

<script>
    export default {
        name: "MyOrderIndex"
    }
</script>

<style scoped>

</style>

login index.vue

<template>
    <div class="login">
        <!-- 头部       -->
        <van-nav-bar
            title="会员登录"
            left-arrow
            @click-left="$router.go(-1)"></van-nav-bar>
        <!--  底部      -->
        <div class="container">
            <div class="title">
                <h3>手机号登录</h3>
                <p>未注册的手机号登录后自动注册</p>
            </div>

            <div class="form">
                <div class="form-item">
                    <input type="text" v-model="mobile" class="inp" placeholder="请输入手机号码" maxlength="11">
                </div>
                <div class="form-item">
                    <input type="text" v-model="picCode" class="inp" placeholder="请输入图形验证码" max="5">
                    <img v-if="picUrl" :src="picUrl" alt="" @click="getPicCode">
                </div>
                <div class="form-item">
                    <input type="text" v-model="smsCode" class="inp" placeholder="请输入短信验证码" >
                    <button @click="handleCode">
                        {{ currentSecond === totalSecond ? '获取验证码' : currentSecond + '秒之后重新发送'}}
                    </button>
                </div>
            </div>

            <div class="login-btn" @click="handleLogin">登录</div>
        </div>
    </div>
</template>

<script>
    //按需导入
    import { getRandomPicCode, getMsgCode, doLogin } from '@/api/login'
    //import { Toast } from 'vant'

    export default {
        name: "LoginIndex",
        data(){
          return {
              picKey:'', //请求后台时的图形验证码唯一标识
              picUrl:'', //存储请求渲染的图片地址,
              totalSecond: 60, //总秒数
              currentSecond: 60, //当前数秒
              timer: null, //定时器
              mobile:'', //手机号
              picCode:'', //用户输入的图形验证码
              smsCode:'' //短信验证码
          }
        },
        created(){
            //页面一加载,就请求验证码
            this.getPicCode()
        },
        methods:{
            //获取图形验证码
            async getPicCode(){
                //展开分解
                const { data:{ base64, key }} = await getRandomPicCode();
                this.picUrl = base64;  //存储地址
                this.picKey = key; // 存储唯一标识
                //Toast('获取图形验证码成功!');
                this.$toast('获取验证码成功');
            },
            //获取短信验证码
            async handleCode(){
                //校验没通过,不发送短信验证码
                if(!this.validFn()){
                    return;
                }

                //当前没有开启定时器,且totalSecond==CurrentSecond时,
                if (!this.timer && this.currentSecond === this.totalSecond){
                    //发送请求,
                    const res = await getMsgCode(this.picCode,this.picKey,this.mobile);
                    //console.log(res);
                    this.$toast('短信发送成功,请注意查收!' + res.message);

                    this.timer = setInterval( ()=>{
                        console.log('正在倒计时...');
                        //开始数秒
                        this.currentSecond--;
                        //如果当前秒 = 0时,关闭清除定时器,恢复当前秒
                        if(this.currentSecond <= 0){
                            clearInterval(this.timer);
                            this.timer = null;
                            this.currentSecond = this.totalSecond;
                        }
                    },1000);
                }
            },
            //校验 手机号和图形验证码是否合法
            validFn(){
                //手机号正则表达式
                if(!(/^1[3-9]\d{9}$/.test(this.mobile))){
                    this.$toast('请输入正确的手机号码');
                    return false;
                }
                //图形验证码正则表达式
                if(!(/^\w{4}$/.test(this.picCode))){
                    this.$toast('请输入正确的图形验证码');
                    return  false;
                }
                return true;
            },
            //登录
            async handleLogin(){
                if(!this.validFn()){
                    return;
                }

                if(!(/^\d{6}$/.test(this.smsCode))){
                    this.$toast('请输入正确的短信验证码');
                    return;
                }

                const res = await doLogin(this.mobile,this.smsCode);
                //console.log(res);
                if(res.status === 200){
                    //存入vuex
                    this.$store.commit('user/setUserInfo',res.data);
                    this.$toast(res.message);
                    await this.$router.push('/');
                }
            }
        },
        destroyed(){
            //离开页面,清除定时器
            clearInterval(this.timer);
        }

    }
</script>

<style scoped>

    .container{
        padding: 49px 29px;
    }

    .container .title{
        margin-bottom: 20px;

    }

    .title h3{
        font-size: 26px;
        font-weight: normal;
    }

    .title p{
        line-height: 40px;
        font-size: 14px;
        color: #b8b8b8;
    }

    .form-item{
        border-bottom: 1px solid #f3f1f2;
        padding: 8px;
        margin-bottom: 14px;
        display: flex;
        align-items: center;

    }

    .form-item .inp{
        display: block;
        border:none;
        outline: none;
        height: 32px;
        font-size: 14px;
        flex: 1;
    }

    .form-item img{
        width: 94px;
        height: 31px;
    }

    .form-item button{
        height: 31px;
        border: none;
        font-size: 13px;
        color: #cea26a;
        background-color: transparent;
        padding-right: 9px;
    }

    .login-btn{
        width: 100%;
        height: 42px;
        margin-top: 39px;
        background: linear-gradient(90deg,#ecb53c,#ff9211,#ff9211);
        color: #ffffff;
        border-radius: 39px;
        box-shadow: 0 10px 20px 0 rgba(0,0,0,.1);
        letter-spacing: 2px;
        display: flex;
        justify-content: center;
        align-items: center;
    }
</style>

layout user.vue

<template>
    <div>我是user</div>
</template>

<script>
    export default {
        name: "UserPage"
    }
</script>

<style scoped>

</style>

layout index.vue

<template>
    <div class="layout-index">
        <!-- 二级路由出口,二级组件展示的位置   -->
        <router-view></router-view>

        <van-tabbar route active-color="#ee0a24" inactive-color="#000">
            <van-tabbar-item to="/home" icon="wap-home-o">首页</van-tabbar-item>
            <van-tabbar-item to="/category" icon="apps-o">分类页</van-tabbar-item>
            <van-tabbar-item to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
            <van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
        </van-tabbar>
    </div>
</template>

<script>
    export default {
        name: "LayoutIndex"
    }
</script>

<style scoped>

</style>

layout home.vue

<template>
    <div>我是Home</div>
</template>

<script>
    export default {
        name: "HomePage"
    }
</script>

<style scoped>

</style>

layout category.vue

<template>
    <div>我是category</div>
</template>

<script>
    export default {
        name: "CategoryPage"
    }
</script>

<style scoped>

</style>

layout cart.vue

<template>
    <div>我是cart</div>
</template>

<script>
    export default {
        name: "CartPage"
    }
</script>

<style scoped>

</style>

vant-ui.js

import Vue from 'vue'

//Vant组件库,全部导入方式
// npm i vant@latest-v2 -s
// import Vant from 'vant'
// import 'vant/lib/index.css'
// Vue.use(Vant);

//按需导入
import { Button, Tabbar, TabbarItem, NavBar, Toast } from 'vant'
//注册使用
Vue.use(Button);
//底部菜单栏
Vue.use(Tabbar);
Vue.use(TabbarItem);
//登录页顶部导航栏
Vue.use(NavBar);
//轻提示
Vue.use(Toast);

storage.js

//约定一个通用的键名
const INFO_KEY = 'xx_shopping_info';
//获取个人登录信息
export const getInfo = () => {
    const defaultObj = { token:'',userId:''};
    const result = localStorage.getItem(INFO_KEY);
    return result ? JSON.parse(result) : defaultObj;
};

//设置个人信息
export const setInfo = (obj) => {
    localStorage.setItem(INFO_KEY,JSON.stringify(obj));
};

//删除个人信息
export const removeInfo = () => {
    localStorage.removeItem(INFO_KEY);
};

request.js

import axios from "axios";
import { Toast } from 'vant'
//创建 axios 实例,将来对创建出来的实例,进行自定义配置
//好处,不会污染原始的axios
const instance = axios.create({
   baseURL:'http://cba.itlike.com/public/index.php?s=/api/',
   timeout:5000
});

//自定义配置 请求-响应 拦截器
//添加请求拦截器
instance.interceptors.request.use(function (config) {
   //在发送请求之前做些什么
   return config;
},function (error) {
   //对请求错误做些什么
   return Promise.reject(error);
});

//添加响应拦截器
instance.interceptors.response.use(function (response) {
    //2xx 范围内的状态码都会触发该函数
    //对响应数据做点什么
    const res = response.data;
    if(res.status !== 200){
        //提示
        Toast(res.message);
        //抛出一个错误的Promise
        return Promise.reject(res.message);
    }
    return res;
},function (error) {
    //超出2xx 范围的状态码都会触发该函数
    //对响应错误做点什么
    return Promise.reject(error);
});


//导出配置好的实例
export default instance;

common.css

/*重置默认样式*/
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/*文字溢出省略号*/
.text-ellipsis-2{
    overflow: hidden;
    -webkit-line-clamp:2;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-box-orient:vertical;
}

/*
添加导航的通用样式
 */
.van-nav-bar .van-nav-bar__arrow{
    color: #333333;
}

store index.js

import Vue from 'vue'
import Vuex from 'vuex'
import user from "@/store/modules/user";

Vue.use(Vuex);

const store = new Vuex.Store({
    modules:{
        user
    }
});

export default store;

store user.js

import { getInfo, setInfo } from '@/utils/storage'

export default {
    //开启命名空间
    namespaced:true,
    state(){
        return {
            //个人权证相关
            userInfo: getInfo()
        }
    },
    mutations:{
        //所有mutation的参数都是state
        setUserInfo(state,obj){
            state.userInfo = obj;
            setInfo(obj);
        }
    },
    actions:{

    },
    getters:{

    }
}

router index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '@/views/login'
import Layout from '@/views/layout'
import MyOrder from '@/views/myorder'
import Pay from '@/views/pay'
import ProductDetails from '@/views/productdetails'
import Search from '@/views/search'
import SearchList from '@/views/search/list'
import Home from "@/views/layout/home";
import Cart from "@/views/layout/cart";
import Category from "@/views/layout/category";
import User from "@/views/layout/user";

Vue.use(VueRouter);

const router = new VueRouter({
    routes:[
        { path:'/login',component:Login},
        {
            path:'/',
            component: Layout,
            redirect:'/home',
            children:[
                { path:'/home',component:Home},
                { path:'/category',component:Category},
                { path:'/cart',component: Cart},
                { path:'/user',component:User}
            ]
        },
        { path:'/search',component:Search},
        { path:'/searchList',component: SearchList},
        { path:'/productDetails/:id',component:ProductDetails},
        { path:'/pay',component:Pay},
        { path:'/myOrder',component:MyOrder}
    ]
});

export default router;

api login.js

//用于存放所有登录有关的请求接口
import request from '@/utils/request'

//1 获取图形验证码
 export const getRandomPicCode = () =>{
    return request.get('/captcha/image');
 };

//2 获取短信验证码
export const getMsgCode = (captchaCode,captchaKey,mobile) => {
  return request.post('/captcha/sendSmsCaptcha',{
      form:{
          captchaCode, //输入的验证码
          captchaKey, //
          mobile //手机号
      }
  });
};

//3. 登录接口
export const doLogin = (mobile,smsCode) => {
    return request.post('/passport/login',{
        form:{
            isParty:false, //是否第三方登录
            partyData:{}, //第三方的登录信息
            mobile, //手机号
            smsCode // 短信验证码
        }
    })
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue.js 中的 Vuex 是一个专为 Vue 应用程序设计的状态管理模式,它主要用于管理应用中的共享状态。Vuex 的核心概念是 store,它是一个单一的、可复用的数据源,所有的组件都可以从 store 中获取状态和 dispatch(发送)动作。 关于 vuex 的持久化存储,通常有以下几种方法: 1. **本地存储 (LocalStorage):**使用浏览器提供的 localStorage API,将 store 中的数据序列化后存储在客户端。当应用程序重新加载时,可以通过读取 localStorage 来恢复状态。例如,`store.commit('SET_DATA', JSON.stringify(state))` 保存,`store.commit('LOAD_DATA', JSON.parse(localStorage.getItem('myKey')))` 读取。 ```javascript import { mapState } from 'vuex'; export default { computed: { ...mapState(['data']), loadedData() { return JSON.parse(localStorage.getItem('data') || 'defaultData'); }, }, beforeCreate() { try { this.data = this.loadedData; } catch (error) { // 处理错误 } }, created() { localStorage.setItem('data', JSON.stringify(this.data)); }, beforeDestroy() { localStorage.removeItem('data'); }, }; ``` 2. **Cookie:**虽然 Cookie 的数据量限制较小(一般为4KB),但也可以用于简单的状态持久化。 3. **IndexedDB 或 WebSQL:**对于更复杂的数据结构或需要离线支持的应用,可以考虑使用这些更强大的浏览器内置数据库技术。 4. **第三方插件:**比如 `vue-localstorage`、`vue-router-store` 等插件提供了更方便的接口和功能,比如自动同步状态等。 **相关问题--:** 1. 在Vuex中,为什么要将状态存储在本地而非直接在组件里? 2. 使用 Vuex 的持久化存储时,如何处理数据的同步和异步问题? 3. 如何在 Vuex 中实现状态的清除,而不仅仅是禁用或重置?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虾米大王

有你的支持,我会更有动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值