vue实战 —— 图书商城移动端项目

介绍下项目功能

全部采用组件化封装思想,尽可能的去符合企业级项目,封装了自定义指令、app换肤等...

1、点击登录会判断正则,同时有小图标提示,错误显 X 正确显 ✔

2、登录按钮默认灰色,账号密码全部正确则变绿色

3、css布局 (最基础!)

4、Vue3 动画库

5、输入框搜索

6、。。。

 

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾    89a5d93bcce94f7cbe42539567637cb3.gif 

 综合热榜前6,js榜单第一,建议查看

Vue项目实战 —— 哔哩哔哩移动端开发_0.活在风浪里的博客-CSDN博客撑着下班前半小时我用vue写《哔哩哔哩 项目》移动端、新手还在哭、老鸟一直在笑。。。技术选型Vue2,技术栈有axios、Vh等,下班过来敲哈哈https://blog.csdn.net/m0_57904695/article/details/123594836

我故意写成两个项目,一个单独写登录注册和图书商城,但是没有js逻辑。另一个项目,单独写图书商城有js,这样将俩个分开,如果大家想要练习最适合不过,可以去写js逻辑,有一个项目是写好的,一个是没写的,方便 练习


fb567df02bc1457aba8ade75a1374fcf.gif 

效果图

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16 

 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16 

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16 

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMC7mtLvlnKjpo47mtarph4w=,size_20,color_FFFFFF,t_70,g_se,x_16

 登录

<template>
  <div class="home">
    <my-header
      v-back="$store.state.backColor"
      right-text="切换主题"
      title="用户登录"
      @right-click="rightClick"
    />
    <div class="form">
      <my-input
        label="手机号码"
        placeholder="请输入手机号码"
        :icon="mobileIcon"
        v-model="mobile"
      ></my-input>
      <my-input
        label="密码"
        placeholder="请输入密码"
        :icon="pwdIcon"
        v-model="pwd"
      ></my-input>
      <button class="btn" :disabled="disabled" @click="$router.push('/about')">
        登陆
      </button>
    </div>
  </div>
</template>

<script>
import myHeader from "@/components/myHeader";
import myInput from "@/components/myInput";
import { ref, watch, watchEffect } from "vue";
import { useStore } from "vuex";
export default {
  name: "Home",
  components: {
    myHeader,
    myInput,
  },

  setup() {
    const disabled = ref(true);
    const mobile = ref("");
    const pwd = ref("");
    const mobileIcon = ref("");
    const pwdIcon = ref("");
    const color = ref("#cccccc");
    const store = useStore();
    // 手机号码正则表达式
    let regMobile = /^1[356789]\d{9}$/;
    let regPwd = /^[a-zA-Z]\w{5}$/;
    // watchEffect兼容数据变化的方法 但是只能用来兼容ref数据
    watchEffect(() => {
      if (regMobile.test(mobile.value) && regPwd.test(pwd.value)) {
        disabled.value = false;
        // 判断成功则将变量颜色赋值按钮
        color.value = store.state.backColor;
      } else {
        disabled.value = true;
      }
    });

    watch(
      () => mobile.value,
      () => {
        if (regMobile.test(mobile.value)) {
          mobileIcon.value = "iconfont duihao";
        } else {
          mobileIcon.value = "iconfont chahao";
        }
      }
    );

    watch(
      () => pwd.value,
      () => {
        // console.log(regPwd.test(pwd.value));
        if (regPwd.test(pwd.value)) {
          pwdIcon.value = "iconfont duihao";
        } else {
          pwdIcon.value = "iconfont chahao";
        }
      }
    );

    function rightClick() {
      // console.log(1);
      store.commit("changeBackColor");
    }

    return {
      rightClick,
      disabled,
      mobile,
      pwd,
      mobileIcon,
      pwdIcon,
      color,
    };
  },
};
</script>
<style lang="scss" scoped>
.form {
  width: 100%;
  margin-top: 200px;
  padding: 0 40px;
}
.btn {
  display: block;
  margin: 0 auto;
  width: 150px;
  height: 36px;
  border: none;
  outline: none;
  border-radius: 6px;
  background-color: v-bind(color);
  color: #fff;
}
.btn[disabled] {
  background-color: #ccc;
  color: #fff;
}
</style>

 vuex store/index.js

import { createStore } from 'vuex'
import axios from 'axios'
export default createStore({
    state: {
        // 默认颜色
        backColor: '#cccccc',
        // 存放数据,页面默认渲染的数据
        books: [],
        // 用于搜索
        _books: [],
    },
    mutations: {
        changeBackColor(state) {
            state.backColor = '#' + Math.round(Math.random() * 16777216).toString(16)
        },
        changeBooks(state, arr) {
            state.books = arr
            state._books = deepClone(arr)

            function deepClone(obj) {
                if (typeof obj !== 'object' || obj == null) {
                    return obj
                }
                let result
                if (obj instanceof Array) {
                    result = []
                } else {
                    result = {}
                }
                for (let key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        result[key] = deepClone(obj[key])
                    }
                }
                return result
            }
        },
    },

    actions: {
        async getDate({ commit }) {
            //将请求的数据中的 list取出来
            let data = await axios.get('/goods.json')
            console.log(data.data.list);
            commit('changeBooks', data.data.list)
        }
    },
    modules: {}
})

 登录子组件

<template>
    <div class="my-header">
        <span class="left">{{leftText}}</span><span class="title">{{title}}</span><span class="right" @click="clickEve">{{rightText}}</span>
    </div>
</template>
<script>
export default {
    //  props接受数据有两种形式 一种是数组 另外一种是对象
    // 数组用法简单不多介绍
    // 对象
    props: {
        show: Boolean, // 只定义需要接受的数据的数据类型时的写法
        leftText: {
            type: String, // 要求接收的数据时字符串
            required: false, // 该数据不是必须传递的
            default: ''
        },
        rightText: {
            type: String,
            required: false,
            default: ''
        },
        title: String
    },

    setup(props, { emit }) {
        function clickEve() {
            emit('right-click')
        }

        return {
            clickEve
        }
    }
}
</script>
<style lang="scss" scoped>
    .my-header {
        width: 100%;
        height: 50px;
        display: flex;
        justify-content: space-between;
        background-color: orangered;
        align-items: center;
        .left,.right {
            flex-basis: 30%;
            text-align: center;
        }
        .right {
            color: skyblue;
        }
        .title {
            color: #fff;
            font-size: 24px;
            font-weight: bold;
        }
    }
</style>

 

<template>
    <div class="my-input">
        <label>{{label}}</label>
        <input type="text" :placeholder="placeholder" :value="modelValue" @input="iptChange">
        <span :class="icon" ></span>
    </div>
</template>
<script>
export default {
    props: {
        label: String,
        placeholder: String,
        icon: String,
        modelValue: String
    },

    setup(props, { emit }) {
        function iptChange(e) {
            emit('update:modelValue', e.target.value)
        }

        return {
            iptChange
        }
    }
}
</script>
<style lang="scss" scoped>
    .my-input {
        width: 100%;
        display: flex;
        align-items: center;
        margin-bottom: 15px;
        label {
            width: 56px;
            flex-shrink: 0;
            font-size: 14px;
            overflow: hidden;
            white-space: nowrap;
            text-align: justify;
            text-align-last: justify;
        }
        input {
            height: 36px;
            margin-left: 5px;
            border: 1px solid #ccc;
            outline: none;
            border-radius: 6px;
            padding-left: 10px;
            margin-right: 10px;
        }
        .duihao {
            color: green;
            font-weight: 900;
        }
        .chahao {
            color: red;
        }
    }
</style>

登录后页面

<template>
  <div class="about">
    <myHeader left-text="" title="图书商城" right-text="我的书架"></myHeader>
    <div class="search">
      <input type="text" placeholder="请根据书名进行搜素" />
    </div>
    <div class="my-card">
      <!-- 子组件负责接受父组件数据,定义结构样式,父组件负责传值 念及此 为封装思路 -->
      <myCard v-for="item in books" :key="item._id" :item="item"></myCard>
    </div>
  </div>
</template>
<script>
import myHeader from "../components/myHeader";
import myCard from "../components/myCard.vue";
import { useStore } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    const store = useStore();
    store.dispatch("getDate");
    // 在计算属性拿到这个值赋值一个变量,就不用每次都$store.state.books
    const books = computed(() => store.state.books);

    return {
      books,
    };
  },
  components: {
    myHeader,
    myCard,
  },
};
</script>
<style lang="scss" scoped>
.search {
  width: 100%;
  height: 40px;
  background-color: #ccc;
  padding: 5px;
  input {
    width: 100%;
    height: 30px;
    border: none;
    font-size: 17px;
    background-color: transparent;
    outline: none;
    padding-left: 10px;
  }
}
.my-card {
  height: calc(100% - 90px);
  overflow: auto;
  display: flex;
  flex-wrap: wrap;
}
</style>

 源码推送到主页的资源里了,需要的哥们儿到主页资源,down

如果感觉对你有帮助,收藏下方便找时快速翻到

 

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
vue2严选商城移动端项目中,可以使用导航守卫来实现路由跳转的权限控制和页面拦截功能。通过导航守卫,可以在路由跳转前进行一系列的操作,如验证用户登录状态、判断用户权限、拦截非法页面等。根据引用中提到的技术解决方案,可以推测该项目可能使用了vue-router作为路由管理工具,因此可以通过配置路由的导航守卫来实现相应的功能。 具体来说,在vue-router中,导航守卫分为三个不同的钩子函数,分别是全局前置守卫(beforeEach)、路由独享的守卫(beforeEnter)和全局后置守卫(afterEach)。我们可以在这些钩子函数中编写逻辑来实现导航守卫的功能。 例如,对于用户登录状态的验证,可以在全局前置守卫中进行判断,如果用户未登录,则可以通过router.push()方法将其导航到登录页面。对于页面的权限拦截,可以通过在路由的meta字段中设置相应的权限标识,在全局前置守卫中进行判断,如果用户没有相应的权限,则可以将其导航到无权限页面。 另外,该项目中可能还使用了element-ui和vant组件库,这些组件库提供了一些常用的界面组件和样式,可以方便快速地构建移动端界面。同时,axios可以用于前后端的数据交互,可以通过axios发送请求获取后端接口的数据。 综上所述,在vue2严选商城移动端项目中,可以通过配置vue-router的导航守卫来实现对路由跳转的权限控制和页面拦截功能。在全局前置守卫中可以验证用户登录状态和权限,确保用户访问合法页面。通过使用element-ui和vant组件库,可以快速构建移动端界面。同时,使用axios可以实现前后端的数据交互。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [0基础学习前端要多久?](https://blog.csdn.net/JACK_SUJAVA/article/details/130077656)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

彩色之外

你的打赏是我创作的氮气加速动力

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

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

打赏作者

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

抵扣说明:

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

余额充值