Vue combat - book store mobile project

Introduce the function of the project

All adopt the idea of ​​componentized encapsulation, as far as possible to conform to enterprise-level projects, encapsulate custom instructions, app skinning, etc...

1. Clicking to log in will judge the regularity, and there will be a small icon prompt at the same time, the error is displayed X, the correct display ✔

2. The login button is gray by default, and the account and password are all correct, it will turn green

3. CSS layout (the most basic!)

4. Vue3 animation library

5. Input box search

6、。。。

Early review     

 The top 6 in the comprehensive hot list, the first in the js list, it is recommended to check

Vue project actual combat - Bilibili mobile terminal development_0. Blog living in the storm laughing all the time. . . Technology selection Vue2, technology stacks include axios, Vh, etc., come and knock after work https://blog.csdn.net/m0_57904695/article/details/123594836

I deliberately write two projects, one is to write login registration and book store separately, but there is no js logic. Another project is to write the js separately in the book mall, so that the two are separated. If you want to practice, you can write js logic. One project is written, and the other is not written, which is convenient for practice.


 

renderings

 

 

 

 

 

 

 

 

 

 

 Log in

<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: {}
})

 login subcomponent

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

post login page

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

 The source code has been pushed to the resources of the homepage, the buddies who need it go to the resources of the homepage, down

If you feel it is helpful to you, bookmark it and turn it over quickly when you are looking for it.

Guess you like

Origin blog.csdn.net/m0_57904695/article/details/123747295