创建Vue项目流程

137 阅读5分钟

创建Vue项目流程

一、创建vue项目

  • vue CLI 基于webpack

    vue create 项目名  
    
  • create vue 基于vite

    npm init vue@latest
    

二、项目基本配置

  • icon 配置图标

    public / favicon.ico

  • 标题

    index.html / title

  • jsconfig.json(友好的代码提示)

三、目录结构的划分

Snipaste_2023-02-06_17-02-29.png

四、重置样式

  • 安装normalize.css 在main.js中引入

    npm install --save normalize.css
    
    main.js
    import "normalize.css"
    
  • assets/css/reset.css

    body,h1,h2,h3,h4,ul,li{
        padding: 0;
        margin: 0;
    }
    
    ul,li{
        list-style: none;
    }
    
    a{
        text-decoration: none;
        color: #333;
    }
    
    img{
        vertical-align: top;
    }
    
  • assets/css/common.css

    body{
        font-size: 14px;
    }
    
  • assets/css/index.css

    @import "common.css";
    @import "reset.css";
    
  • main.js

    import "normalize.css"
    import "@/assets/css/index.css"
    

五、配置路由

  • 安装路由

    npm install vue-router
    
  • 配置路由 router/index.js

    import {createRouter, createWebHashHistory} from "vue-router";
    
    const router = createRouter({
        history: createWebHashHistory(),
        //配置映射关系
        routes: [
            {
                path: '/',
                redirect: '/home' //重定向
            },
            {
                path: '/home',
                component: () => import('@/views/home/Home.vue')
            },
            {
                path: '/favor',
                component: () => import('@/views/favor/Favor.vue')
            },
            {
                path: '/order',
                component: () => import('@/views/order/Order.vue')
            },
            {
                path: '/message',
                component: () => import('@/views/message/Message.vue')
            }
    
        ]
    })
    
    export default router
    
  • main.js中使用

    import router from "@/router"
    app.use(router)
    
  • 在需要使用的地方

    <router-view/>
    <router-link to="/home">首页</router-link>
    <router-link to="/favor">收藏</router-link>
    <router-link to="/order">订单</router-link>
    <router-link to="/message">消息</router-link>
    

六、状态管理

  • 安装pinia

    npm install pinia
    
  • store/index.js

    import {createPinia} from "pinia";
    
    const pinia = createPinia()
    
    export default pinia
    
  • store/modules/city.js

    import {defineStore} from "pinia";
    
    const useCityStore = defineStore('city',{
        state:()=>({
            cities:[]
        }),
        actions:{
    
        }
    })
    
    export default useCityStore()
    
  • main.js

    import pinia from "@/stores";
    app.use(pinia)
    

七、UI组件库(vant)

1、vant官网

vant-ui.github.io/vant/#/zh-C…

2、vant使用

查看官网(介绍、快速上手)

3、修改第三方UI组件库的样式

  1. 若是用插槽,插入了自己的元素,那么在自己的作用域中直接修改这个元素
  2. 全局定义一个变量,覆盖它默认变量的值,但是缺点是全局修改
  3. 局部定义一个变量,覆盖它默认变量的值
  4. 直接查找对应的子组件选择器,进行修改,:deep(子组件中元素的选择器)进行修改,直接修改的是css

4、使用UI组件库场景

  • 类型一:后台管理系统

    Element-UI(vue2) / Element-Plus(vue3)

  • 类型二:小程序

    小程序原生UI库 / Vant

  • 类型三:移动端Web页面

    Vant UI

  • 类型四:网易云/知乎/Bilibili

    Element-Plus

八、tabBar隐藏

1、通过路由隐藏

router/index.js
{
    path: '/city',
        component: () => import('@/views/city/City.vue'),
            meta:{
                hideTabBar:true
            }
},
    
  
App.vue
<TabBar v-if="!route.meta.hideTabBar"/>
   
import {useRoute} from "vue-router";
const route = useRoute()  

2、通过class隐藏

common.css
.hideTabBar{
    position: relative;
    z-index: 9;
    height: 100vh;
    overflow-y: auto;
    background: #fff;
}

九、servers

使用axios二次封装来进行网络请求

网络请求,如果直接在组件内部进行请求的话,若有一天URL地址变了不好维护,所以对网络请求还进行二次封装

文件夹结构:

services.png

但是如果直接在组件内发送网络请求有两个缺点:

1.如果请求太多,那么页面组件中就包含大量对于网络请求和数据的处理逻辑

2.如果页面封装了很多的子组件,子组件需要这些数据,还需要一步一步将数据传过去

所以采用pinia存储数据

stores/modules/city.js
import {defineStore} from "pinia";
import {getCityRequest} from "@/services";

const useCityStore = defineStore('city', {
    state: () => ({
        citiesData: {}
    }),
    actions: {
        async fetchCitiesData() {   //发送网络请求  请求过来的数据保存在state中  组件用的话直接取
            const res = await getCityRequest()  
            this.citiesData = res.data
        }
    }
})

export default useCityStore


City.vue
const cityStore = useCityStore()
cityStore.fetchCitiesData()
const {citiesData} = storeToRefs(cityStore)

image-20230216221309751.png

十、页面请求数据

发送axios 请求分页数据,民宿项目中,请求houstlist数据(codercba.com:1888/api/home/ho…

export function getHomeHoustList(currentPage) {
    return AxiosRequest.get({
        url: '/home/houselist',
        params: {
            page: currentPage
        }
    })
}

请求分页数据时,使用currentPage来记录当前页,把currentPage存在pinia中

import {defineStore} from "pinia";
import {getHomeCategories, getHomeHotSuggests, getHomeHoustList} from "@/services";

const useHomeStore = defineStore('home', {
    state: () => ({
        currentPage:1,
        houstList:[]
    }),
    actions: {
        async fetchHomeHouseList() {
            const res = await getHomeHoustList(this.currentPage)
            this.houstList.push(...res.data)
            this.currentPage++
        }
    }
})

export default useHomeStore

每当发送一次请求时,currentPage都++

十一、监听页面滚动到底部

监听:什么时候页面滚动到了底部

页面为什么会滚动

1.window窗口滚动

2.元素滚动:overflow-y:auto

hooks

import {onMounted, onUnmounted, ref} from "vue";
import {throttle} from "underscore";


//方法一:传入回调函数来判断是否到达底部
// export default function (reachBottom){
//     //监听window滚动
//     const scrollListenerHandler = ()=>{
//         const clientHeight = document.documentElement.clientHeight
//         const scrollTop = document.documentElement.scrollTop
//         const scrollHeight = document.documentElement.scrollHeight
//         // console.log(clientHeight, scrollTop, scrollHeight)
//         if (clientHeight + scrollTop + 1 >= scrollHeight) {
//             if(reachBottom) reachBottom()
//         }
//     }
//     onMounted(()=>{
//         window.addEventListener('scroll', scrollListenerHandler)
//     })
//     onUnmounted(()=>{
//         window.removeEventListener('scroll',scrollListenerHandler)
//     })
// }

//方法二:通过变量来监听是否滚动到底部
//防抖、节流  (使用节流)
export default function () {
    const isReachBottom = ref(false)
    const clientHeight = ref(0)
    const scrollTop = ref(0)
    const scrollHeight = ref(0)

    //监听window滚动
    const scrollListenerHandler = throttle(() => {
        clientHeight.value = document.documentElement.clientHeight
        scrollTop.value = document.documentElement.scrollTop
        scrollHeight.value = document.documentElement.scrollHeight
        // console.log(clientHeight, scrollTop, scrollHeight)
        if (clientHeight.value + scrollTop.value + 1 >= scrollHeight.value) {
            isReachBottom.value = true
        }
    }, 100)
    onMounted(() => {
        window.addEventListener('scroll', scrollListenerHandler)
    })
    onUnmounted(() => {
        window.removeEventListener('scroll', scrollListenerHandler)
    })

    return {isReachBottom, clientHeight, scrollTop, scrollHeight}
}

监听滚动

//监听滚动到底部
//方法一
// useScroll(()=>{
//   homeStore.fetchHomeHouseList()
// })
//方法二
const {isReachBottom, scrollTop} = useScroll()
watch(isReachBottom, (newValue) => {
  if (newValue) {
    homeStore.fetchHomeHouseList().then(() => {
      isReachBottom.value = false
    })
  }
})

// const isShowSearchBar = ref(false)
// watch(scrollTop, (newValue) => {
//   isShowSearchBar.value = newValue > 100
// })
//定义的可响应式数据,依赖另外一个可响应式的数据,那么可以使用计算属性(computed)
const isShowSearchBar = computed(()=>{
  return scrollTop.value >= 360
})

十二、loading显示

从服务器获取数据时,当数据还没有加载完毕时,给用户显示出一个loading样式

将loading放入pinia仓库main.js中,设置isLoading:false

每次向服务器发送请求时,都会来到request/index.js中

import axios from 'axios'

import { BASE_URL, TIMEOUT } from './config'
import useMainStore from "@/stores/modules/main";

const mainStore = useMainStore()

class AxiosRequest {
    constructor(baseURL, timeout=10000) {
        this.instance = axios.create({
            baseURL,
            timeout
        })

        // 拦截器实现isloading
        this.instance.interceptors.request.use(config => {
            mainStore.isLoading = true
            return config
        }, err => {
            return err
        })
        this.instance.interceptors.response.use(res => {
            mainStore.isLoading = false
            return res
        }, err => {
            mainStore.isLoading = false
            return err
        })

    }



    request(config) {
        // mainStore.isLoading = true
        return new Promise((resolve, reject) => {
            this.instance.request(config).then(res => {
                // mainStore.isLoading = false
                resolve(res.data)
            }).catch(err => {
                // mainStore.isLoading = false
                reject(err)
            })
        })
    }

    get(config) {
        return this.request({ ...config, method: "get" })
    }

    post(config) {
        return this.request({ ...config, method: "post" })
    }
}

export default new AxiosRequest(BASE_URL, TIMEOUT)

十三、点击热门精选跳转到房屋详情页

  1. 监听item点击(在HouseList.vue中,在组件上写@click事件,传入同一个点击事件,不用写重复代码)
  2. 跳转到详情页
  3. 携带对应的参数houseId

使用动态路由来传递参数

 {
     path: '/detail/:id',
     component: () => import('@/views/detail/Detail.vue')
}

十四、地图显示

百度地图开放管理 ---> 控制台 ---> AK ---> 官方文档 ---> 使用

十五、tabControl

  1. 开发tabcontrol
  2. 监听滚动
  3. 滚动到一定位置,显示出来
  4. 监听tabcontrol点击,点击之后滚动到正确的位置

需求:页面滚动,滚动到一定位置时,显示正确的tabCOntrol的索引(标题),和歌词匹配是一个算法

  1. 监听滚动的位置 scrollTop
  2. 利用scrollTop去匹配正确的位置

image-20230307174414370.png

十六、px转换vw

vite/webpack ---> postcss工具 ---> plugins ---> postcss-px-to-viewport

<注>:本人是学习coderwhy老师的项目,项目接口均来自coderwhy老师,如有想学习coderwhy老师的课程,请前往腾讯课堂。本篇文章是自己总结的流程,望周知。