尚品汇项目笔记

0、开发项目步骤

  1. 静态页面(HTML+CSS)
  2. 拆分组件
  3. 获取服务器的数据动态展示
  4. 完成相应业务逻辑

1、初始化项目与文件目录分析

需要 node+ webpack + 淘宝镜像
项目文件目录命令控制行输入:vue create 项目名称
public文件夹:静态资源,webpack进行打包的时候会原封不动打包到dist文件夹中。

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

assets: 存放公用的静态资源
components: 非路由组件(全局组件),其他组件放在views或者pages文件夹中
App.vue: 唯一的根组件,vue当中的组件.vue
main.js: 程序入口文件,最先执行的文件

babel.config.js: 配置文件(babel相关)
package.json: 项目的详细信息记录
package-lock.json: 缓存性文件(各种包的来源)

2、项目配置

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

package.json
    "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
    },

报错npm WARN saveError ENOENT: no such file or directory, open ‘/Users/....../package.json‘
原因:终端路径与项目路径不一致,导致找不到package.json文件
解决方法cd 项目路径(更改终端路径)

2.2 关闭eslint校验工具 (两种方法)
2.2.1 根目录下创建vue.config.js,进行配置

module.exports = {
  //关闭eslint
  lintOnSave: false
  }

2.2.2 vue UI可视化界面关闭

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

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

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

3、项目路由分析

Vue中使用vue-router去实现

前端所谓路由:KV键值对
key: URL(地址栏中的路径)
value: 相应的路由组件
注意:项目上中下结构

路由组件:
Home组件、Search组件 、Login组件、Refister组件

非路由组件:
Header组件【首页、搜索页】、
Footer组件【首页、搜索页】【登录与注册页没有】

4、组件页面样式

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

npm install --save less less-loader@5

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

<script scoped lang="less"> 

5、安装路由

npm install --save vue-router 

–save:可以让你安装的依赖,在package.json文件当中进行记录
一定要注意版本兼容问题,这里遇到了一次错误,安装了vue-router之后,编译没有问题,页面变成空白
报错:Uncaught TypeError: (0 , vue__WEBPACK_IMPORTED_MODULE_0__.defineComponent) is not a function
原因:vue-router版本不兼容,自动下载了最新版"vue-router": "^4.0.14"
解决方法:降低vue-router的版本。

https://blog.csdn.net/qq_54353631/article/details/123598926?spm=1001.2014.3001.5501

6、创建路由组件

创建路由组件【一般放在views|pages文件夹】
components文件夹:经常放置非路由组件(共用全局组件)
pages文件夹:经常放置路由组件

7、配置路由

项目中配置的路由一般放置在router文件夹中:
创建router文件夹,并创建index.js进行路由配置,最终在main.js中引入注册
报错:TypeError: Cannot read properties of undefined (reading ‘$createElement‘)
原因:单词拼写错误,在配置路由时,component写成components
解决方法:修改单词

https://blog.csdn.net/qq_62449212/article/details/123044736

8、路由传参

1、query、params
  • query、params两个属性可以传递参数
  • query参数:不属于路径当中的一部分,类似于get请求,地址栏表现为 /search?k1=v1&k2=v2
  • query参数对应的路由信息 path: “/search”
  • params参数:属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 ,地址栏表现为 /search/v1/v2
  • params参数对应的路由信息要修改为path: “/search/:keyword” 这里的/:keyword就是一个params参数的占位符
2、传参方法
  • 字符串形式
 //第一种:字符串形式
this.$router.push("/search/" + this.keyword + "?k=" + this.keyword.toUpperCase()); 
  • 模板字符串
//第二种:模板字符串 
this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()}`) 

注意: 上面字符串的传参方法可以看出params参数和’/'结合,query参数和?结合 http://localhost:8080/#/search/asa?k=ASA
上面url中asd为params的值,keyword=asd为query传递的值。

  • 对象 (最常用)
//第三种:对象 
this.$router.push({name:"search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})

以对象方式传参时,如果我们传参中使用了params,只能使用name,不能使用path,如果只是使用query传参,可以使用path 。

3、传参问题
  • 路由传递参数(对象写法)path是否可以结合params参数一起使用?
不可以:不能这样书写,程序会崩掉  
  • 如何指定params参数可传可不传?比如配置路由时,params参数已经占位了,但是路由跳转时就是不传递。
    路径即地址栏URL会出现问题
 如果路由path要求传递params参数,但是没有传递,会发现地址栏URL有问题,详情如下:
 Search路由项的path已经指定要传一个keyword的params参数,如下所示:
  path: "/search/:keyword",
  执行下面进行路由跳转的代码:
  this.$router.push({name:"Search",query:{keyword:this.keyword}})
  当前跳转代码没有传递params参数
  地址栏信息:http://localhost:8080/#/?keyword=asd
  此时的地址信息少了/search
  正常的地址栏信息: http://localhost:8080/#/search?keyword=asd
  解决方法:可以通过改变path来指定params参数可传可不传  
  在配置路由时,在占位的后面加一个问号
  path: "/search/:keyword?",?表示该参数可传可不传
  • params可传可不传,但是如果传递的时空串,如何解决
 this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''}})
 出现的问题和1中的问题相同,地址信息少了/search
 解决方法: 加入||undefined,当我们传递的参数为空串时地址栏url也可以保持正常
 this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''||undefined}})
  • 路由组件能不能传递props数据?
可以,但是只能传递params参数,具体知识为props属性 。

9、多次执行相同的push问题

编程式导航路由跳转到当前路由(参数不变), 多次执行会抛出NavigationDuplicated的警告错误?
例如:使用this.$router.push({name:‘Search’,params:{keyword:“…”||undefined}})时,如果多次执行相同的push,控制台会出现警告。

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

执行一次上面代码:
在这里插入图片描述
多次执行出现警告:
在这里插入图片描述

  • 注意:编程式导航(push|replace)才会有这种情况的异常,声明式导航是没有这种问题,因为声明式导航内部已经解决这种问题。
    这种异常,对于程序没有任何影响的。
  • 为什么会出现这种现象:
    由于vue-router最新版本3.5.2,引入了promise,当传递参数多次且重复,会抛出异常,promise需要传递成功和失败两个参数,我们的push中没有传递,因此出现上面现象,
  • 解决方法
    第一种解决方案:是给push函数,传入相应的成功的回调与失败的回调
this.$router.push({name:‘Search’,params:{keyword:"…"||undefined}},()=>{},()=>{}) 
后面两项分别代表执行成功和失败的回调函数。
这种写法治标不治本,将来在别的组件中push|replace,编程式导航还是会有类似错误

第二种解决方案:push是VueRouter.prototype的一个方法,在router中的index重写该方法即可

//1、先把VueRouter原型对象的push,保存一份
let originPush = VueRouter.prototype.push;
//2、重写push|replace
//第一个参数:告诉原来的push,跳转的目标位置和传递了哪些参数 
//第二个参数:成功回调 
//第三个参数:失败回调
VueRouter.prototype.push = function (location,resolve,reject){
    if(resolve && reject){
        originPush.call(this,location,resolve,reject)
    }else{
        originPush.call(this,location,() => {},() => {})
    }
}

注意:call与apply区别
相同点:都可以调用函数一次,都可以篡改函数的上下文一次
不同点:call与apply传递参数,call传递参数用逗号隔开,apply方法执行,传递数组

10、全局组件定义

如果一些组件多次使用,可以注册为全局组件,只需要注册一次,就可以在任意地方使用。全局的配置都需要在main.js里面配置。

//三级联动组件--全局组件 
import TypeNav from '@/pages/Home/TypeNav'; 
//第一个参数:全局组件名字,第二个参数:哪一个组件 
Vue.component(TypeNav.name,TypeNav); 

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

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

11、Home其他组件定义

Home页面的index.vue文件下

<template>
<div>
<!--  三级联动全局组件已经注册为全局组件,因此不需要引入-->
  <TypeNav/>
<!--  轮播图列表-->
  <ListContainer/>
<!--  今日推荐-->
  <Recommend/>
<!--  商品排行-->
  <Rank/>
<!--  猜你喜欢-->
  <Like/>
<!-- 楼层 -->
  <Floor/>
  <Floor/>
<!--  商标-->
  <Brand/>
</div>
</template>

<script>
import ListContainer from './ListContainer'
import Recommend from './Recommend'
import Rank from './Rank'
import Like from './Like'
import Floor from './Floor'
import Brand from './Brand'
export default {
  name: "index",
  components: {
    ListContainer,
    Recommend,
    Rank,
    Like,
    Floor,
    Brand,
  }
}
</script>

<style scoped>

</style>

12、封装axios

在src根目录下创建api文件夹,创建request.js文件

//需要对axios进行二次封装 
import axios from 'axios';  

const request = axios.create({  
    //基础路径,requests发出的请求在端口号后面会跟改baseURl
    baseURL:"/api", 
    timeout:5000,
}); 
//请求拦截器:在发送请求之前,请求拦截器会监测到,可以在发出请求前做一些事情 
request.interceptors.request.use((config)=>{ 
    return config;
})
//相应拦截器 
request.interceptors.response.use((res)=>{  
    //成功的回调函数
    return res.data;
},(error)=>{  
    //失败的回调函数
    return Promise.reject(new Error('faile'));
})
 
//对外暴露 
export default request;

附:axios中文文档

https://www.kancloud.cn/yunye/axios/234845

13、前端解决跨域问题

  • 什么是跨域问题?
    浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域

  • 端口和协议的不同,只能通过后台来解决

  • localhost和127.0.0.1虽然都指向本机,但也属于跨域

  • 跨域限制:
    1、无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
    2、无法接触非同源网页的 DOM
    3、无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)

解决方法:

关于跨域的详细讲解

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

  devServer: {
    proxy: {
        //会把请求路径中的/api换为后面的代理服务器
        '/api': {
            //提供数据的服务器地址
            target: 'http://gmall-h5-api.atguigu.cn',
        }
    },
  }

问题ValidationError: Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.在这里插入图片描述
原因:webpack版本配置问题
解决方法:按报错修改DevServer的配置。

问题500 (Internal Server Error)
原因:服务器错误
解决方法:修改正确的服务器地址

14、请求接口统一封装

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

//当前这个模块:API进行统一管理 
import request from "./request"; 
 
//三级联动的接口 
///api/product/getBaseCategoryList  get请求  无参数 
 //箭头函数简写形式
export const reqCategoryList = ()=> request({url:'/product/getBaseCategoryList',method:'get'}); 

当组件想要使用相关请求时,只需要导入并调用相关函数,如:

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

15、nprogress进度条插件

首先需要安装nprogress插件:npm install --save nprogress
打开一个页面时,常常伴随着一些进度条的展示,原理为:发起请求时开启进度条插件,请求响应成功则结束进度条插件,需要在api/request.js文件里配置。

//需要对axios进行二次封装 
import axios from 'axios'; 
//引入进度条 
import nprogress  from 'nprogress';   
//引入进度条的样式 
import "nprogress/nprogress.css"; 

const request = axios.create({  
   //基础路径,requests发出的请求在端口号后面会跟改baseURl
    baseURL:"/api", 
    timeout:5000,
}); 
//请求拦截器:在发送请求之前,请求拦截器会监测到,可以在发出请求前做一些事情 
request.interceptors.request.use((config)=>{   
    //进度条开始
    nprogress.start();
    return config;
})
//响应拦截器 
request.interceptors.response.use((res)=>{   
    //成功的回调函数 
    // 进度条结束 
    nprogress.done();
    return res.data;
},(error)=>{  
    //失败的回调函数
    return Promise.reject(new Error('faile'));
})
 
//对外暴露 
export default request;

noprogress.css可以修改background来改变进度条颜色:

#nprogress .bar {
  background: #29d;

  position: fixed;
  z-index: 1031;
  top: 0;
  left: 0;

  width: 100%;
  height: 2px;
}

16、引入Vuex

首先需要安装vuex:npm install --save vuex@3
根目录src下创建store文件夹,再创建index.js文件。

import Vue from 'vue'; 
import Vuex from 'vuex'; 
//需要使用插件一次 
Vue.use(Vuex); 
//对外暴露store类的一个实例  
export default new Vuex.Store({   

})

在main.js文件中引入并注册仓库:

import Vue from 'vue'
import App from './App.vue'  

//三级联动组件--全局组件 
import TypeNav from '@/components/TypeNav';  
//第一个参数:全局组件名字,第二个参数:哪一个组件 
Vue.component(TypeNav.name,TypeNav);  

//引入路由 
import router from '@/router';  
//引入仓库 
import store from './store';

new Vue({
  render: h => h(App), 
  //注册路由 :下面的写法是KV一致省略V且router小写
  router,
  //注册仓库:组件实例会多一个$store属性 
  store,
}).$mount('#app')

16、动态展示三级联动数据

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

在store文件夹下创建组件对应的小仓库文件夹与对应的index.js文件:
在这里插入图片描述
在index.js文件中进行配置:

//search仓库 
//state :仓库存储数据的地方 
const state = {};
//mutations :修改state的唯一手段 
 const mutations = {};
// action:处理action,可以书写自己的业务逻辑,也可以处理异步 
 const actions = {};
//getters: 理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便 
 const getters = {};

//对外暴露
export default {  
    state, 
    mutations, 
    actions, 
    getters,
}

在主大仓库的index.js文件中进行配置:

import Vue from 'vue'; 
import Vuex from 'vuex'; 
//需要使用插件一次 
Vue.use(Vuex); 
import home from './home'; 
import search from './search';
//对外暴露store类的一个实例  
export default new Vuex.Store({  
    modules:{ 
        home, 
        search,
    }
})

在组件的mounted中通知vuex发送请求

export default {
  name: "TypeNav",
  //组件挂载完毕:可以向服务器发送请求
  mounted() {
    //通知Vuex发送请求,获取数据,存储于仓库当中
    this.$store.dispatch("categoryList");
  },

再配置组件对应的仓库配置:

//home仓库  
import {reqCategoryList} from '@/api';
//state :仓库存储数据的地方 
const state = {  
//根据获取到的数据再确定变量类型----------------1
    categoryList:[],
};
//mutations :修改state的唯一手段 
 const mutations = {   
 //修改state里的数据---------------3
    CATEGORYLIST(state,categoryList){ 
        state.categoryList = categoryList;
    }
 };
// action:处理action,可以书写自己的业务逻辑,也可以处理异步 
 const actions = { 
     //通过API里面的接口函数,向服务器发送请求,获取服务器数据--------------------2
     async categoryList({commit}){ 
         let result = await reqCategoryList(); 
         if (result.code==200){ 
             commit("CATEGORYLIST",result.data)
         }
     }
 };
//getters: 理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便 
 const getters = {};


export default {  
    state, 
    mutations, 
    actions, 
    getters,
}

这时候去看vuex后台就可以看见我们获取到的数据。
关于state可以浏览官网: 官网state介绍

组件获取相应数据

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from "vuex";
export default {
  name: "TypeNav",
  //组件挂载完毕:可以向服务器发送请求
  mounted() {
    //通知Vuex发送请求,获取数据,存储于仓库当中
    this.$store.dispatch("categoryList");
  },
  computed: { 
  // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
      //右侧需要的是一个函数,当使用这个计算属性的时候,右侧函数会立即执行一次
      //注入一个参数state,即为大仓库中的数据
      categoryList: (state) => state.home.categoryList,
    }),
  },
};

最后通过v-for替换且动态展示模板里的数据 :

<div class="sort">
        <div class="all-sort-list2">
          <div
            class="item" 
            v-for="(c1,index) in categoryList.slice(0, 16)"
            :key="c1.categoryId"
          > 
          <!--通过slice(0,16)获取前16条数据-->
            <h3>
              <a href="">{{ c1.categoryName }}</a>
            </h3>
            <div class="item-list clearfix">
              <div
                class="subitem"
                v-for="(c2,index) in c1.categoryChild"
                :key="c2.categoryId"
              >
                <dl class="fore">
                  <dt>
                    <a href="">{{ c2.categoryName }}</a>
                  </dt>
                  <dd>
                    <em v-for="(c3,index) in c2.categoryChild" :key="c3.categoryId">
                      <a href="">{{ c3.categoryName }}</a>
                    </em>
                  </dd>
                </dl>
              </div>
            </div>
          </div>
        </div>
      </div>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值