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

介绍下项目功能

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

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

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

3、css布局 (最基础!)

4、Vue3 动画库

5、输入框搜索

6、。。。

前期回顾     

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

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

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


 

效果图

 

 

 

 

 

 

 

 

 

 登录

<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

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

猜你喜欢

转载自blog.csdn.net/m0_57904695/article/details/123747295