尚硅谷系统项目(实时更新)

前端Vue核心

开发一个前端模块可以概括为以下几个步骤:
(1)写静态页面、拆分为静态组件;
(2)发请求(API);
(3)vuex(actions、mutations、state三连操作);
(4)组件获取仓库数据,动态展示;

1、vue文件目录分析

node_modules文件夹 项目依赖 比如babel:es6语法翻译为es5兼容性更好 以及vue框架等

public文件夹:静态资源 ,webpack进行打包的时候会原封不动打包到dist文件夹中。

pubilc/index.html:是一个模板文件,作用是生成项目的入口文件,webpack打包的js,css也会自动注入到该页面中。我们浏览器访问项目的时候就会默认打开生成好的index.html。

src文件夹(程序员代码文件夹)

assets:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在assets文件夹里面静态资源,在webpack打包的时候,webpack会把静态资源当做一个模块,打包Js文件里面。
components: 非路由组件(全局组件),其他组件放在views或者pages文件夹中
App.vue: 唯一的根组件
main.js: 程序入口文件,最先执行的文件

babel.config.js: 配置文件(babel相关)
package.json: 项目的详细信息记录 项目中有哪些依赖 项目怎么运行
package-lock.json: 缓存性文件(各种包的来源)

两者的区别解释1

package.json里面定义的是版本范围(比如^1.0.0),具体跑npm install的时候安的什么版本,要解析后才能决定,这里面定义的依赖关系树,可以称之为逻辑树(logical tree)。

node_modules文件夹下才是npm实际安装的确定版本的东西,这里面的文件夹结构我们可以称之为物理树(physical tree)。

安装过程中有一些去重算法,所以你会发现逻辑树结构和物理树结构不完全一样。

package-lock.json可以理解成对结合了逻辑树和物理树的一个快照(snapshot),里面有明确的各依赖版本号,实际安装的结构,也有逻辑树的结构。

其最大的好处就是能获得可重复的构建(repeatable build),当你在CI(持续集成)上重复build的时候,得到的artifact是一样的,因为依赖的版本都被锁住了。在npm5以后,其内容和npm-shrinkwrap.json一模一样。

两者的区别解释2

package-lock.json是在运行“npm install”时生成的一个文件,用于记录当前状态下项目中实际安装的各个package的版本号、模块下载地址、及这个模块又依赖了哪些依赖。

为什么有了package.json,还需要package-lock.json文件呢,当node_modules文件夹并不存在或被删除时,需要用到npm install重新装载全部依赖时,通过package-lock.json可以直接表明下载地址和相关依赖,相对下载速度也更快,也不容易报错。

2、项目配置

2.1 项目运行,浏览器自动打开

找到package.json 文件 加上 --open
    "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
    },

2.2 关闭eslint校验工具

(不关闭会有各种规范,不按照规范就会报错)

  • 根目录下创建vue.config.js,进行配置
module.exports = {
  //关闭eslint
  lintOnSave: false
  }

2.3 src文件夹配置别名

创建jsconfig.json,用@/代替src/,exclude表示不可以使用该别名的文件

 {
    "compilerOptions": {
        "baseUrl": "./",
            "paths": {
            "@/*": [
                "src/*"
            ]
        }
    },

    "exclude": [
        "node_modules",
        "dist"
    ]
 }

3、项目路由的分析(整体划分组件)

路由组件和非路由组件的区别

组件切换基于组件内的状态变化(data)

路由切换基于浏览器URL的变化

最大的区别在于刷新浏览器后:

vue会匹配当前URL,并且渲染匹配到的路由定义的组件

而基于组件内部状态切换的组件

会被重置成初始状态

尚硅谷系统项目(实时更新)

两个非路由组件定义在components当中 Headeer组件和Footer组件

  1. 找静态组件将样式等复制出来(由此可见less和scss等的代码健壮性)
  2. 安装less等插件

4、配置组件页面样式(less 第一个问题

组件页面的样式使用的是less样式,浏览器不识别该样式,需要下载相关依赖

npm install --save less less-loader@5

注意 如果安装最新版的less 会报函数错误

建议直接安装指定的低版本

如果想让组件识别less样式,则在组件中设置

<script scoped lang="less">

注意导入的组件需要大写

尚硅谷系统项目(实时更新)

5、清除vue页面默认的样式

vue是单页面开发,我们只需要修改public下的index.html文件

<link rel="stylesheet" href="<%= BASE_URL %>reset.css">
@import "./iconfont.css";
 
/* 清除内外边距 */
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
fieldset, lengend, button, input, textarea,
th, td {
    margin: 0;
    padding: 0;
}

/* 设置默认字体 */
body,
button, input, select, textarea { /* for ie */
    /*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/
    font: 12px/1.3 "Microsoft YaHei",Tahoma, Helvetica, Arial, "\5b8b\4f53", sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */
    color: #333;
}


h1 { font-size: 18px; /* 18px / 12px = 1.5 */ }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
h4, h5, h6 { font-size: 100%; }

address, cite, dfn, em, var, i{ font-style: normal; } /* 将斜体扶正 */
b, strong{ font-weight: normal; } /* 将粗体扶细 */
code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */
small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */

/* 重置列表元素 */
ul, ol { list-style: none; }

/* 重置文本格式元素 */
a { text-decoration: none; color: #666;}


/* 重置表单元素 */
legend { color: #000; } /* for ie6 */
fieldset, img { border: none; }
button, input, select, textarea {
    font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */
}

/* 重置表格元素 */
table {
    border-collapse: collapse;
    border-spacing: 0;
}

/* 重置 hr */
hr {
    border: none;
    height: 1px;
}
.clearFix::after{
	content:"";
	display: block;
	clear:both;
}
/* 让非ie浏览器默认也显示垂直滚动条,防止因滚动条引起的闪烁 */
html { overflow-y: scroll; }

a:link:hover{
    color : rgb(79, 76, 212) !important;
    text-decoration: underline;
}

/* 清除浮动 */
.clearfix::after {
    display: block;
    height: 0;
    content: "";
    clear: both;
    visibility: hidden;
}

5、路由组件相关配置

01.安装路由

npm install --save vue-router

可查看package.json判断组件是否完成

02.创建专用文件夹放置路由组件

创建一个pages文件夹,并创建路由组件

在上面分析的时候,路由组件应该有四个: Home、 Search、Login、 Register

  • -components文件夹:经常放置的非路由组件(共用全局组件)
  • -pages |views文件夹:经常放置路由组件

03.配置路由

创建router文件夹,并创建index.js进行路由配置

尚硅谷系统项目(实时更新)

//引入vue-router路由插件
import VueRouter from "vue-router";
//引入Vue
import Vue from "vue";
//使用插件
Vue.use(VueRouter);

import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Register from '@/pages/Register'
import Search from '@/pages/Search'

export default new VueRouter({
  routes: [
    {
      path: "/home",
      component: Home
    },
    {
      path: "/search",
      component: Search
    },
    {
      path: "/login",
      component: Login
    },
    {
      path: "/register",
      component: Register
    },
  ]

})

在main.js中引入注册

import Vue from 'vue'
import App from './App.vue'
//01.此处
import router from '@/router'
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  //02.此处
  router
}).$mount('#app')

04.总结

路由组件和非路由组件区别:

路由组件与非路由组件的区别?
1:路由组件一般放置在pages|views文件夹,非路由组件一般放置components文件夹中
2;路由组件一般需要在router文件夹中进行注册(使用的即为组件的名字),非路由组件在使用的时候,一般都是以标签的形式使用
3:注册完路由,不管路由路由组件、还是非路由组件身上都有$route、I $router属性

$route:一般获取路由信息【路径、query.params等等】

$router:一般进行编程式导航进行路由跳转【push/replace】

05.路由的跳转

路由的跳转有两种形式:
声明式导航router-link,可以进行路由的跳转尚硅谷系统项目(实时更新)

编程式导航push|replace,可以进行路由跳转
尚硅谷系统项目(实时更新)

编程式导航:声明式导航能做的,编程式导航都能在,
但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。

6、footer组件显示与隐藏(Header fotter)

实现的基本思路

  • footer在登录注册页面是不存在的,所以要隐藏,v-if 或者 v-show
  • 这里使用v-show,因为v-if会频繁的操作dom元素消耗性能,v-show只是通过样式将元素显示或隐藏

实现的两种方式

  • 配置路由的时候,可以给路由配置元信息meta,

    <Footer v-show="$route.meta.show"></Footer>
    
    {
          path: "/register",
          component: Register,
          meta: { show: false }
    },
    
  • 在路由的原信息中定义show属性,用来给v-show赋值,判断是否显示footer组件

    <Footer v-show="$route.path=='/home'||$route.path=='/search'"></Footer>
    

7、路由传参(Header fotter)

01.路由跳转有几种方式?

比如:A->B
声明式导航:router-link(务必要有to属性),可以实现路由的跳转
编程式导航:利用的是组件实例的$router.push/replace方法,可以实现路由的跳转。[(可以书写一些自己业务)

02.路由传参的两种方式params query

01.字符串传参

研究实例

按钮点击将参数传入下个页面

尚硅谷系统项目(实时更新)

params传参实现方法

第一步:搜索框数据双向绑定

尚硅谷系统项目(实时更新)

第二步:路由占位

尚硅谷系统项目(实时更新)

第三步:函数路由传参

gosearch() {
 this.$router.push('/search' + '/' + this.keyword)
}

query传参

需要使用**?以及kv**的方式进行传参

gosearch() {
	this.$router.push('/search' + '/' + this.keyword + '?k=' + this.keyword.toUpperCase())
}

尚硅谷系统项目(实时更新)

尚硅谷系统项目(实时更新)

数据展示

组建内部

<h1>我是params参数{{$route.params.keyword}}</h1>
<h1>我是query参数{{$route.query.k}}</h1>

尚硅谷系统项目(实时更新)

02.模板字符串

就是个ES6写法

this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`)

03.对象写法

push后面装载的需要是一个参数

第一步

给路由添加名字

{
   path: "/search/:keyword",
   component: Search,
   meta: { show: true },
   name: "search"
},

第二步

传入参数

gosearch() {
// this.$router.push('/search' + '/' + this.keyword + '?k=' + this.keyword.toUpperCase())
// this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`)
this.$router.push({
 name: 'search',
 query: {
   k: this.keyword.toUpperCase()
 },
 params: {
   keyword: this.keyword
 }
})
}

04.面试题

面试题1:

1.路由传递参数(对象写法)path是否可以结合params参数一起使用?

尝试一起使用

gosearch() {
//路由传递参数(对象写法)path是否可以结合params参数一起使用?
this.$router.push({
 path: 'search',
 params: {
   keyword: this.keyword
 }
})
}

发现路由无法跳转

答:路由跳转传参的时候,对象的写法可以是name、path形式,但是需要注意的是,path这种写法不能与params参数写法一起使用

面试提2

2.如何指定params参数可传可不传?

尝试在路由占位的情况下不传入params参数

gosearch() {
//如何指定params参数可传可不传?
this.$router.push({
 path: 'search',
 query: {
   k: this.keyword.toUpperCase()
 },
})
}

如果路由要求传递params参数,但是你就不传递params参数,发现一件事情,URL会有问题的

如何指定panams参数可以传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号【params可以传递或者不传递】

其实类似于正则 ?就是表示参数可传可不传

{
   path: "/search/:keyword?",
   component: Search,
   meta: { show: true },
   name: "search"
},

面试提3

params参数可以传递也可以不传递,但是如果传递是空串,如何解决?

尝试

gosearch() {
//**params参数可以传递也可以不传递,但是如果传递是空串,如何解决?**
this.$router.push({
 path: 'search',
 params: {
     keyword: ''
 }
})
}

发现路径也出现了问题 seach不显示

使用undefined解决:params参数可以传递、不传递(空的字符串)

gosearch() {
//**params参数可以传递也可以不传递,但是如果传递是空串,如何解决?**
this.$router.push({
 path: 'search',
 params: {
     keyword: ''|| undefined
 }
})
}

面试提4

路由组件能不能传递props数据?

可以的:三种写法 但是只能传递params参数,

params 布尔值写法

首先 router路由的位置添加props布尔值写法

尚硅谷系统项目(实时更新)

然后 组建props的位置添加变量定义

尚硅谷系统项目(实时更新)

params 对象写法

首先 router路由的位置添加props对象写法

尚硅谷系统项目(实时更新)

然后跳转的组件直接使用即可

尚硅谷系统项目(实时更新)

params 函数写法

首先 router路由的位置添加props函数写法

尚硅谷系统项目(实时更新)

然后跳转的组件直接使用即可

尚硅谷系统项目(实时更新)

8、多次执行相同的push问题(Header fotter)

问题描述

编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?

–路由跳转有两种形式:声明式导航、编程式导航

–声明式导航没有这类问题的,因为vue-router底层已经处理好了

多次执行相同的push问题,控制台会出现警告
例如:使用this.$router.push({name:‘Search’,params:{keyword:"…"||undefined}})时,如果多次执行相同的push,控制台会出现警告。

let result = this.$router.push({name:"Search",query:{keyword:this.keyword}})
console.log(result)

执行一次上面代码:
尚硅谷系统项目(实时更新)

多次执行出现警告:

尚硅谷系统项目(实时更新)

原因:push是一个promise,promise需要传递成功和失败两个参数,我们的push中没有传递。方法:

this.$router.push({name:‘Search’,params:{keyword:"…"||undefined}},()=>{},()=>{})

后面两项分别代表执行成功和失败的回调函数。这种写法治标不治本,将来在别的组件中push|replace,编程式导航还是会有类似错误

如何解决

首先需要明白 在当前创建的组件当中

this:当前组件实例(search)
this. r o u t e r 属 性 : 当 前 的 这 个 属 性 , 属 性 值 V u e R o u t e r 类 的 一 个 实 例 , 当 在 入 口 文 件 注 册 路 由 的 时 候 , 给 组 件 实 例 添 加 router属性:当前的这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加 router属性:当前的这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加router| $route属性

push是VueRouter.prototype的一个方法,在router中的index文件中重写该方法即可(看不懂也没关系,这是前端面试题)

https://www.bilibili.com/video/BV1Vf4y1T7bw?p=10&spm_id_from=pageDriver 不理解可以看下视频 第10节 第15分钟

本质上虽说是重写 实际上还是调用了原来的方法 只是加工了一下

此代码复制到router文件夹下即可

//1、先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;//push方法
let originReplace= VueRouter.prototype.replace//方法
//2、重写push|replace
//第一个参数:告诉原来的push,跳转的目标位置和传递了哪些参数
//第二个参数:成功回调
//第三个参数:失败的回调
VueRouter.prototype.push = function (location,resolve,reject){
    //如果传递了成功和失败的回调函数 则不做处理
    if(resolve && reject){
         call| |apply区别
        //相同点,都可以调用函数一次,都可以篡改函数的上下文一次
        //不同点: call与apply传递参数: call传递参数用逗号隔开,apply方法执行,传递数组
        originPush.call(this,location,resolve,reject)
    }else{//否则自己添加两个函数
        originPush.call(this,location,() => {},() => {})
    }
}
VueRouter.prototype.replace = function (location,resolve,reject){
    if(resolve && reject){
         call| |apply区别
        //相同点,都可以调用函数一次,都可以篡改函数的上下文一次
        //不同点: call与apply传递参数: call传递参数用逗号隔开,apply方法执行,传递数组
        originReplace.call(this,location,resolve,reject)
    }else{
        originReplace.call(this,location,() => {},() => {})
    }
}

9.Home组件拆分(home)

拆成7个组件 按功能划分

部分通用的组件需要配置为全局组件

10、Home三级联动全局组件完成(home)

三级联动组件完成
—由于三级联动,在Home、Search、Detail,把三级联动注册为全局组件。好处:只需要注册一次,就可以在项目任意地方使用

01.创建基本组件

创建组件和编写基本html和css

尚硅谷系统项目(实时更新)

02.组件全局注册

注意 注册全局组件需要在main.ja文件中操作

第一步 导入组件

//将三级联动组件注册为全局组件
import TypeNav from '@/pages/Home/TypeNav';

第二步 注册

//第一个参数:全局组件名字,第二个参数:全局组件
Vue.component(TypeNav.name,TypeNav);

03.组件的使用

在Home组件中使用该全局组件

<template>
 <div>
 <!--  三级联动全局组件已经注册为全局组件,因此不需要引入-->
   <TypeNav/>
 </div>
</template>

全局组件可以在任一页面中直接使用,不需要导入声明

11、封装ListContainer组件

01.封装样式和css

注意改变图片的路径

12、封装TodayRecommend组件

01.封装样式和css

注意改变图片的路径

13、封装Rank组件

01.封装样式和css

注意改变图片的路径

14、封装Like组件

01.封装样式和css

注意改变图片的路径

15、封装Floor组件

01.封装样式和css

注意改变图片的路径

16、封装Brand组件

01.封装样式和css

注意改变图片的路径

17.封装axios

axios二次封装:XMLHttpRequest、fetch、JQ、axios

请求拦截器、响应拦截器:请求拦截器,可以在发请求之前可以处理一些业务、响应拦截器,当服务器数据返回以后,可以处理一些事情

安装axios

npm install --save axios

观察package.json查看是否安装成功

尚硅谷系统项目(实时更新)

01.封装request文件

axios中文文档,包含详细信息。
https://www.kancloud.cn/yunye/axios/234845
在根目录下创建api文件夹,创建request.js文件。
内容如下,当前文件代码还比较少,后续有需求可以增添内容。

import axios from "axios";
//1、对axios二次封装
const requests = axios.create({
    //配置对象
    //基础路径,requests发出的请求在端口号后面会跟改baseURl
    baseURL:'/api',
    //代表请求超时的时间是5s
    timeout: 5000,
})
//2、配置请求拦截器
requests.interceptors.request.use(config => {
    //config 配置对象,主要是对请求头Header配置
    //比如添加token
    
    return config;
})
//3、配置相应拦截器
requests.interceptors.response.use((res) => {
    //成功的回调函数
    return  res.data;
},(error) => {
    //失败的回调函数
    console.log("响应失败"+error)
    return Promise.reject(new Error('fail'))
}) 
//4、对外暴露
export default requests;

02.封装api统一管理文件

在文件夹api中创建index.js文件,用于封装所有请求
将每个请求封装为一个函数,并暴露出去,组件只需要调用相应函数即可,这样当我们的接口比较多时,如果需要修改只需要修改该文件即可。

如下所示:

//当前模块,API进行统一管理,即对请求接口统一管理
import requests from "@/api/request";

//首页三级分类接口 /api/product/getBaseCategoryList get请求 无参数
export const reqCateGoryList = () => {
    return  requests({
        url: '/product/getBaseCategoryList',
        method: 'GET'
    })
}

当组件想要使用相关请求时,只需要导入相关函数即可,以上图的reqCateGoryList 为例:

import {reqCateGoryList} from './api'
//发起请求
reqCateGoryList();

03.解决跨域问题

什么是跨域:协议、域名、端口号不同请求,称之为跨域

http: //localhost:8080/#/home ----前端项目本地服务器

http://39.98.123.211 ----后台服务器
解决跨域问题:JSONP、CROS、代理

而代理实现方案

尚硅谷系统项目(实时更新)

在根目录下的vue.config.js中配置,proxy为通过代理解决跨域问题。
我们在封装axios的时候已经设置了baseURL为api,所以所有的请求都会携带/api,这里我们就将/api进行了转换。如果你的项目没有封装axios,或者没有配置baseURL,建议进行配置。要保证baseURL和这里的代理映射相同,此处都为’/api’。

module.exports = {
    //关闭eslint
    lintOnSave: false,
    devServer: {
        // true 则热更新,false 则手动刷新,默认值为 true
        inline: false,
        // development server port 8000
        port: 8001,
        //代理服务器解决跨域
        proxy: {
            //会把请求路径中的/api换为后面的代理服务器
            '/api': {
                //提供数据的服务器地址
                target: 'http://39.98.123.211',
                //因为都有api 所以不再路径重写
			   //pathRewrite:{'^/api':''}
                
            }
        },
    }
}
上一篇:[模板引擎] thymeleaf 独立环境使用


下一篇:thymeleaf 模板使用 之 解决因HTML标签未闭合引起的错误