练习汇总
[toc]
手写_淘宝商城
手写_京东商城
手写_华为商城
手写_荣耀商城
手写静态页面积累
- 使用css变量来保存网站的主题色
- 先写结构,后写交互
- 把所有交互中,可能出现的结构,都写出来
- 一个好的结构分布,会简化交互的代码实现
购物车
购物车作业的回顾和实现
v-for中,点击切换颜色
1 | <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <style> .active{ color :red; } </style></head><body><div id="app"> <h2>{{message}}</h2> <ul> <li v-for="(item,index) in books" :class="{active: currentIndex === index}" @click = liClick(index)>{{index + 1}} - {{item}}</li> </ul></div><script src="js/vue.js"></script><script> const app = new Vue({ //用于挂载需要管理的元素 el: '#app', //定义数据 data: { message: 'hello world', books :['人民的名义','论持久战','天上最亮的星星','黑洞','Java语言精粹'], active : "active", currentIndex : 0 }, methods : { liClick (index) { this.currentIndex = index//不能写成this.index ,这个的值是undefined } } })</script></body></html> |
- 购物车案例-界面搭建
- 过滤器的使用
- 购物车案例-改变购买数量
- 高阶函数
1 | <!doctype html> |
购物车案例_界面搭建
购物车案例_过滤器的使用
购物车案例_改变购买数量
购物车案例_移除按钮、最终价格
购物车案例_高阶函数的使用-
手机商城
接口文档
1 | vue create mall |
准备
创建项目和项目托管
1 | git remote add origin https://gitee.com/mindcons/vue_supermall.git |
划分目录结构
src/
assets
components
- common 通用组件
- content 业务相关的组件
views
network
common
- common.js
- utils.js
- mixin.js
store
router 路由管理
1
初始化CSS文件的引入
base.css
1
@import "./normalize.css";
normalize.css
在App.vue中的style标签中引用
在
css
中import
需要加@
,最后要加一个分号的1
<style> @import "./assets/css/normalize.css";</style>
base.css
1 | @import "./normalize.css";/*:root -> 获取根元素html*/:root { --color-text: #666; --color-high-text: #ff5777; --color-tint: #ff8198; --color-background: #fff; --font-size: 14px; --line-height: 1.5;}*,*::before,*::after { margin: 0; padding: 0; box-sizing: border-box;}body { font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; user-select: none; /* 禁止用户鼠标在页面上选中文字/图片等 */ -webkit-tap-highlight-color: transparent; /* webkit是苹果浏览器引擎,tap点击,highlight背景高亮,color颜色,颜色用数值调节 */ background: var(--color-background); color: var(--color-text); /* rem vw/vh */ width: 100vw;}a { color: var(--color-text); text-decoration: none;}.clear-fix::after { clear: both; content: ''; display: block; width: 0; height: 0; visibility: hidden;}.clear-fix { zoom: 1;}.left { float: left;}.right { float: right;} |
normalize.css
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ |
配置vue.config.js和.editorconfig
cli3的配置隐藏在了modules中了,配置别名
在项目根目录下,需要创建vue.config.js
1 | export default { configureWebpack: { resolve: { alias: { // '@': 'src'// 默认的配置 'assets': '@/assets', 'common': '@/common', 'components': '@/components', 'network': '@/network', 'views': '@views' } } }} |
别名语法已经修改了,需要注意下
若上述配置未生效,请自行搜索vue-cli3配置别名
1 | const path = require('path');function resolve(dir) { return path.join(__dirname, dir);}console.log(__dirname)console.log(path.join(__dirname,'src/assets'))module.exports = { lintOnSave: true, chainWebpack: (config) => { config.resolve.alias .set('@',resolve('src')) .set('assets',resolve('src/assets')) .set('common',resolve('src/common')) .set('components',resolve('src/components')) .set('network',resolve('src/network')) .set('views',resolve('src/views')) }}; |
另外,修改别名后,在webstorm会出现按CTRL
+左键无法跳转的情况,可以进行以下配置
这时,我们只需要在项目根目录创建一个文件 alias.config.js
:
1 | /** * 由于 Vue CLI 3 不再使用传统的 webpack 配置文件,故 WebStorm 无法识别别名 * 本文件对项目无任何作用,仅作为 WebStorm 识别别名用 * 进入 WebStorm preferences -> Language & Framework -> JavaScript -> Webpack,选择这个文件即可 * */const resolve = dir => require('path').join(__dirname, dir);module.exports = { resolve: { alias: { '@': resolve('src'), 'assets': '@/assets', 'common': '@/common', 'components': '@/components', 'network': '@/network', 'views': '@views' } }}; |
如果以上不生效,可以把wepack configuration file配置成类似d:\project-name\node_modules\_@vue_cli-service@3.12.1@@vue\cli-service\alias.config.js
的形式
这里的配置还是得写成老语法
mall/.editorconfig
1 | root = true[*]charset = utf-8indent_style = spaceindent_size = 2end_of_line = lfinsert_final_newline = truetrim_trailing_whitespace = true |
功能模块
这个是一开始单独开发的组件,用的是vue-cli2创建项目的方式
1 | vue init webpack tabbar |
导航栏开发
实现思路
1.如果在下方有一个单独的TabBar组件,如何封装
- 自定义TabBar组件,在App中使用
- 让TabBar处于底部,并且设置相关样式
2.TabBar中显示的内容由外部决定
- 定义插槽
- flex布局平分TabBar
3.自定义TabBaritem,可以传入图片和文字
自定义TabBarItem,并且定义两个插槽:图片、文字
给两个插槽外层包装div,用于设置样式
填充插槽,实现底部TabBar效果
4.传入高亮图片
- 定义另一个插槽,插入active-icon的数据
- 定义一个变量isActive,通过v-show来决定是否显示对象的icon
5.TabBarItem绑定路由数据
安装路由:
1
npm install vue-router --save
完成router/index.js的内容,以及创建对应的组件
main.js中注册router
App中加入组件
6.点击item跳转到对应路由,并且动态决定isActive
- 监听item的点击,通过this.route.replace()替换路由路径
- 通过this.route.path.indexOf(this.link) !== -1 来判断是否是active
7.动态计算active样式
- 封装新的计算属性:this.isActive ? {‘color’:’red”‘}:{}
1 | vue init webpack TabBar |
tabbar 基本结构搭建
App.vue
1 | <template> |
1 | num run dev |
测试效果如下:
tabbar TabBar和TabBarItem组件的封装
components文件夹下,应该对每个功能,单独新建文件夹,新建tabbar文件
将上述的业务代码,剪切,至tabbar/TabBar.vue文件中,
tabbar/TabBar.vue
1 | <template> |
在App.vue中导入、挂载并使用TabBar组件
App.vue
1 | <template> |
我们的tabbar还要用到图片,创建对应的文件夹
asserts/img/tabbar
在tabbar组件新增图片,并且设置样式
tabbar/TabBar.vue
1 | <template> |
但是我们发现,此时这里面的东西有点乱,item相关的东西,不应该交给tabbar来管理
所以,我们给tabbar整一个插槽,让别人在用我的时候,自己指定内容就可以了
TabBar.vue:
1 | <template> |
App.vue
注意,此时的img的路径,引用是要修改的,就很麻烦
后面我们会讲到文件路径的引用问题
1 | <template> |
但是,此时App.vue里面的代码,又变得臃肿起来了
所以,我们就可以再封装一个组件: tabbaritem
并且,给文字包一个div,使其与图片上下显示
此时
tabbar/TabBarItem.vue
1 | <template> |
在App.vue中,引入tabbaritem,然后在tabbar的插槽中,使用4次即可
APP.vue
1 | <template> <div id="app"> <tab-bar> <tab-bar-item></tab-bar-item> <tab-bar-item></tab-bar-item> <tab-bar-item></tab-bar-item> <tab-bar-item></tab-bar-item> </tab-bar> </div></template><script> import TabBar from './components/tabbar/TabBar' import TabBarItem from "./components/tabbar/TabBarItem";export default { name: 'App', components: { TabBar, TabBarItem, }}</script><style> @import './assets/css/base.css';</style> |
但是,很明显我们将tabbaritem里面的内容那个写死了,
需要改成具名插槽,在APP.vue中使用的时候,再指定slot即可
效果如下:
App.vue
1 | <template> <div id="app"> <tab-bar> <tab-bar-item> <img src="./assets/img/tabbar/home.svg" alt="" slot="item-icon"> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/category.svg" alt="" slot="item-icon"> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/shopping.svg" alt="" slot="item-icon"> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/profile.svg" alt="" slot="item-icon"> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </div></template><script> import TabBar from './components/tabbar/TabBar' import TabBarItem from "./components/tabbar/TabBarItem"; export default { name: 'App', components: { TabBar, TabBarItem, } }</script><style> @import './assets/css/base.css';</style> |
TabBar.vue
1 | <template> <div id="tab-bar"> <slot></slot> </div></template><script> export default { name: 'tabBar' }</script><style> #tab-bar { display: flex; height: 49px; background-color: #f6f6f6; position: fixed; left: 0; bottom: 0; right: 0; box-shadow: 0 -1px 1px rgba(100, 100, 100, .2); }</style> |
TabBarItem.vue
1 | <template> <div class="tab-bar-item"> <slot name="item-icon"></slot> <slot name="item-text"></slot> </div></template><script> export default { name: 'TabBarItem' }</script><style> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; }</style> |
给TabBarItem传入active图片
当处于活跃状态时,更改图片状态
我们需要先将,激活与未激活的图片都放在对应的位置
TabBarItem.vue中再整一个插槽,App.vue中传入激活的图片
TabBarItem.vue
1 | <template> <div class="tab-bar-item"> <slot name="item-icon"></slot> <slot name="item-icon-activate"></slot> <slot name="item-text"></slot> </div></template> |
App.vue
注意,两个插槽的slot不一样的
1 | <template> <div id="app"> <tab-bar> <tab-bar-item> <img src="./assets/img/tabbar/home.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/home_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/category.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/category_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/shopping.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/shopping_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item> <img src="./assets/img/tabbar/profile.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/profile_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </div></template> |
效果如下:
此时我们需要定义一个变量,控制激活的状态
TabBarItem.vue
1 | <template> <div class="tab-bar-item"> <slot v-if="!isActive" name="item-icon"></slot> <slot v-else name="item-icon-activate"></slot> <slot name="item-text"></slot> </div></template><script> export default { name: 'TabBarItem', data() { return { isActive: false } } }</script> |
此时效果v-if的判断是true,只显示item-icon的插槽:
如果我们将isActive的值,改为true,则v-else条件激活:
我们发现,字体的颜色,是没有跟着改变的,我们要动态的决定,要不要给文字的div加某一个类,用v-bind
TabBarItem.vue / template
1 | <slot :class="{active:isActive}" name="item-text"></slot> |
TabBarItem.vue / style
1 | .active { color: pink; } |
我们给tabbaritem的插槽,动态绑定了class,但是看前台,字体并没有更改颜色
因为插槽最后,是被App.vue中的内容完全替换掉了
解决方案:把插槽放在一个div内,给这个div动态绑定class
TabBarItem.vue / template
1 | <div :class="{active:isActive}" > <slot name="item-text"></slot> </div> |
最后渲染的页面,div是没有被替换的,效果如下:
同理,我们将所有的插槽,都包装一个div
TabBarItem.vue / template
1 | <template> <div class="tab-bar-item"> <div v-if="!isActive"> <slot name="item-icon"></slot> </div> <div v-else > <slot name="item-icon-activate"></slot> </div> <div :class="{active:isActive}" > <slot name="item-text"></slot> </div> </div></template> |
TabBarItem和路由结合效果
我们在前台的点击事件,要和路由跳转对应起来
之前创建项目的时候,没有选中的话,可以手动安装vue-router
1 | npm install vue-router@3.0.1 --save |
详细步骤看vue-router那一小节
然后src下创建views文件夹,再分别创建home/category/cart/profile四个文件夹,同时创建Home.vue、Category.vue、Cart.vue、Profile.vue
如下结构:
各自的temaplate做一下标识:
1 | <template> <h2>Profile</h2></template> |
有了组件之后,
在main.js中挂载router
然后在router/index.js中创建映射关系
同时更改路由模式为history
/router/index.js
1 | import Vue from 'vue'import Router from 'vue-router'const Home = () => import('../views/home/Home')const Category = () => import('../views/category/Category')const Cart = () => import('../views/cart/Cart')const Profile = () => import('../views/profile/Profile')Vue.use(Router)const routes = [ { path: '/', redirect: '/home', }, { path: '/home', component: Home }, { path: '/category', component: Category }, { path: '/cart/', component: Cart }, { path: '/profile', component: Profile }]export default new Router({ routes, mode: 'history',}) |
我们需要监听点击事件,不用在App.vue中监听,直接到TabBarItem组件内部监听即可
监听点击的目的,是为了获得所点击的path,我们可以在tabbaritem中,定义props,之后在app.vue中使用的会后,给其传参即可
TabBarItem.vue / script
1 | <script> export default { name: 'TabBarItem', data() { return { isActive: true } }, props: { path: String }, methods: { itemClick() { this.$router.replace(this.path) } } }</script> |
App.vue中,传递固定的字符串给path
App.vue / template
加一个router-view
1 | <template> <div id="app"> <router-view></router-view> <tab-bar> <tab-bar-item path="/home"> <img src="./assets/img/tabbar/home.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/home_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">首页</div> </tab-bar-item> <tab-bar-item path="/category"> <img src="./assets/img/tabbar/category.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/category_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">分类</div> </tab-bar-item> <tab-bar-item path="/cart"> <img src="./assets/img/tabbar/shopping.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/shopping_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">购物车</div> </tab-bar-item> <tab-bar-item path="/profile"> <img src="./assets/img/tabbar/profile.svg" alt="" slot="item-icon"> <img src="./assets/img/tabbar/profile_activate_pink.svg" alt="" slot="item-icon-activate"> <div slot="item-text">我的</div> </tab-bar-item> </tab-bar> </div></template> |
解释一下,path属性是在tabbaritem的props中定义好的,所以会进行传递
如果是一个没有定义的变量,最终渲染时,只会原样输出
1 | <tab-bar-item path="/home" path_test="/home_test"> |
前台渲染效果如下:
就不就相当于,给html标签,整个自定义类名么,不难理解
TabBarItem的颜色动态控制
上面的isActive是写死的,我们用计算属性,判断当前的路由,和传递过来的路由是否一致
TabBarItem.vue
1 | <template> <div class="tab-bar-item" @click="itemClick"> <div v-if="!isActive"> <slot name="item-icon"></slot> </div> <div v-else > <slot name="item-icon-activate"></slot> </div> <div :class="{active:isActive}" > <slot name="item-text"></slot> </div> </div></template><script> export default { name: 'TabBarItem', data() { return { } }, props: { path: String }, methods: { itemClick() { console.log(this.$route.path) console.log(this.path) this.$router.replace(this.path) } }, computed :{ isActive() { return this.$route.path.indexOf(this.path) !== -1 } } }</script><style> .tab-bar-item { flex: 1; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img { width: 24px; height: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; } .active { color: pink; }</style> |
效果如下:
控制台打印两个路由:
1 | console.log(this.$route.path) console.log(this.path) |
此时还有另外一个问题,就是字体的颜色,也不希望写死,希望我在使用的时候,能够自己指定
在tabbaritem中,定义一个activeColor的props属性,给一个默认值,然后动态绑定style即可
TabBarItem.vue / template
1 | <div :style="activeStyle" > <slot name="item-text"></slot> </div> |
TabBarItem.vue / script
1 | props: { path: String, activeColor: { type: String, default: 'red', } }, computed: { isActive() { return this.$route.path.indexOf(this.path) !== -1 }, activeStyle() { return this.isActive ? {color: this.activeColor} : {} } } |
App.vue
默认的颜色是red,使用的时候可以指定文字的激活颜色
1 | <tab-bar-item path="/home" active-color="pink"> |
此时,我们看到App.vue的文件中,又有很多代码,我们再做一层抽象
components/中新建MainTabBar.vue,因为这一层代码是业务相关的,所以放在components目录下即可
MainTabBar.vue
1 | <template> |
此时的App.vue中,代码量就非常少了
App.vue
1 | <template> |
文件路径的引用问题
build/webpack.base.config.js
1 | resolve: { |
如果是import里的路径引用,则可以直接用别名,
1 |
如果是html里的src路径引用,则需要加波浪号~
1 |
vue-cli2中配置了,使用时没有生效,直接用@代替了src目录即可
1 | <img src="@/assets/img/tabbar/home.svg" alt="" slot="item-icon"> |
其他项目中引入tabbar
封装的tabbar文件夹,放在components/common文件夹下
与业务相关的MainTabBar.vue放在components/content/mainTabBar文件夹下
注意各种导入路径的问题即可
这里还是写相对路径吧,别名问题暂时没有解决
favicon.ico小图标的修改
我们换成自己小图标就可以了
mall/public/favicon.ico
备注:public文件夹,会原封不到的打包到dist文件夹下的,相当于复制
首页页面开发
首页-导航栏的封装和使用(nav-bar)
在components/common下新建navbar/NavBar.vue
1 | <template> |
在views/Home.vue中引用
1 | <template> <div id="home"> <nav-bar></nav-bar> </div></template><script> import NavBar from "../../components/common/navbar/NavBar"; export default { name: "Home", components: { NavBar } }</script><style scoped></style> |
我们发现啥也没有显示,要学会调试,
先用vuedevtools看组件有没有出来,然后看具体的元素有没有被渲染出来
经过排查发现,是navbar没有给高度,设置后出来了
1 | .nav-bar { display: flex; line-height: 44px; height: 44px; text-align: center; } |
Home.vue中使用插槽,并设置样式:
Home.vue
1 | <template> <div id="home"> <nav-bar class="home-nav"> <div slot="center">购物街</div> </nav-bar> </div></template><script> import NavBar from "../../components/common/navbar/NavBar"; export default { name: "Home", components: { NavBar } }</script><style scoped> .home-nav { /*background-color: var(--color-tint);*/ background-color: pink; color: white; }</style> |
效果如下:
首页-请求首页的多个数据
network/request.js
安装axios
request.js
1 | import axios from 'axios'export function request(config) { //1.创建axios的实例 const instance = axios.create({ baseURL: 'http://152.136.185.210:7878/api/m5', timeout: 5000 }) //2.axios的拦截器 //请求拦截 instance.interceptors.request.use(config => { return config },err => { // console.log(err) }) //响应拦截 instance.interceptors.response.use(res => { return res.data }, err => { console.log(err) }) //3.发送真正的网络请求 return instance(config)} |
再封装一层home.js
1 | import {request} from "./request"export function getHomeMultidata() { return request({url: '/home/multidata'})} |
在Home.vue中导入
1 | import {getHomeMultidata} from 'network/home' |
那么什么时候发送请求呢?
Home.vue中使用生命周期函数,来发送请求:
1 | created() { //1. 请求多个数据 getHomeMultidata().then(res => { console.log(res) }) } |
第一反应是,创建result用来接收数据
1 | data() { return { result: null } }, created() { //1. 请求多个数据 getHomeMultidata.then(res= => { console.log(res) this.result = res }) } |
但一般情况下,不会用一个变量来保存:
1 | data() { return { // result: null banners: [], recommends: [] } }, created() { //1. 请求多个数据 getHomeMultidata.then(res= => { // console.log(res) // this.result = res this.banners = res.data.banner.list this.recommends = res.data.recommend.list }) } |
九、首页-轮播图的展示¶
轮播图的封装,导入轮播图组件,并挂载组件
1 | import {Swiper, SwiperItem} from 'components/common/swiper' |
vue有很多UI库,可以直接使用,但在学习阶段中,不建议直接使用,得自己学会封装
在home.vue的template中使用轮播图组件
1 | <swiper> <swiper-item v-for="item in banners"> <a :href="item.link"> <img :src="item.image" alt=""> </a> </swiper-item> </swiper> |
但是,我们一般还是会抽离出单独的组件
新建home/childrenComponents/HomeSwiper.vue
1 | <template> <swiper> <swiper-item v-for="item in banners"> <a :href="item.link"> <img :src="item.image" alt=""> </a> </swiper-item> </swiper></template><script> import {Swiper, SwiperItem} from 'components/common/swiper' export default { name: "HomeSwiper", components: { Swiper, SwiperItem }, props: { banners: { type: Array, default() { return [] } } } }</script><style scoped></style> |
十、首页-推荐信息的展示¶
childrenComponens/HomeRecommendView.vue
1 | <template> <div class="recommend"> <div v-for="item in recommends" class="recommend-item"> <a :href="item.link"> <img :src="item.image" alt=""> <div>{{item.title}}</div> </a> </div> </div></template><script> export default { name: "HomeRecomendView", props: { recommends: { type: Array, default() { return [] } } } }</script><style scoped> .recommend{ display: flex; width: 100%; text-align: center; font-size: 12px; padding: 10px 0 20px; border-bottom: 8px solid #eee; } .recommend-item{ flex: 1; } .recommend-item img{ width: 80%; margin-bottom: 10px; }</style> |
十之一、首页-FeatureView的封装¶
childrenComponents/FeatureView.vue
1 | <template> <div class="feature"> <a href="#"> <img src="~assets/img/home/recommend_bg.jpg" alt=""> </a> </div></template><script> export default { name: "FeatureView" }</script><style scoped> .feature img{ width: 100%; }</style> |
十之二、首页-TabControl的封装¶
- 只是文字不一样的情况下,没有必要用插槽
- 点击切换颜色
- 吸顶效果
1 | <template> <div class="tab-control"> <div v-for="(item,index) in titles" class="tab-control-item" :class="{active: index === currentIndex}" @click="itemClick(index)"> <span> {{item}} </span> </div> </div></template><script> export default { name: "TabControl", props: { titles: { type: Array, default() { return [] } } }, data() { return { currentIndex: 0 } }, methods: { itemClick(index) { this.currentIndex = index } } }</script><style scoped> .tab-control{ display: flex; text-align: center; /*justify-content: center ;*/ height: 44px; line-height: 44px; font-size: 15px; } .tab-control-item{ flex: 1; } .tab-control-item span { padding-bottom: 5px; } .active{ color: var(--color-high-text); } .active span{ border-bottom: 3px solid var(--color-tint) }</style> |
十之三、首页-保存商品的数据结构设计¶
数据保存的模型
1 | goods: { 'pop': {page: 0, list: []}, 'news': {page: 0, list: []}, 'sell': {page: 0, list: []}} |
十之四、首页-数据的请求和保存¶
home.js
1 | import {request} from "./request"export function getHomeMultidata() { return request({ url: '/home/multidata' })}export function getHomeGoods(type,page) { return request({ url: 'home/data', params: { type, page } })} |
Home.vue
1 | <template> <div id="home"> <nav-bar class="home-nav"><div slot="center">购物街</div></nav-bar> <home-swiper :banners="banners"></home-swiper> <home-recomend-view :recommends="recommends"></home-recomend-view> <feature-view></feature-view> <tab-control :titles="['流行','新款','精选']" class="tab-control"></tab-control> <div class="temp"></div> </div></template><script> import NavBar from "components/common/navBar/NavBar"; import TabControl from "@/components/common/tabControl/TabControl"; import HomeSwiper from "views/home/childrenComponents/HomeSwiper"; import HomeRecomendView from "@/views/home/childrenComponents/HomeRecomendView"; import FeatureView from "@/views/home/childrenComponents/FeatureView"; import {getHomeMultidata, getHomeGoods} from 'network/home'; export default { name: "Home", components: { NavBar, TabControl, HomeSwiper, HomeRecomendView, FeatureView, }, data() { return{ banners: [], recommends: [], goods: { 'pop': {page: 0, list: []}, 'new': {page: 0, list: []}, 'sell': {page: 0, list: []} } } }, created() { //1. 请求多个数据 this.getHomeMultidata() //2.请求goods数据 this.getHomeGoods('pop') this.getHomeGoods('new') this.getHomeGoods('sell') }, methods: { getHomeMultidata() { getHomeMultidata().then(res => { // console.log(res) this.banners = res.data.banner.list this.recommends = res.data.recommend.list }) }, getHomeGoods(type) { const page = this.goods[type].page + 1 getHomeGoods(type,page).then(res => { // console.log(res) this.goods[type].list.push(...res.data.list) this.goods[type].page += 1 }) } } }</script><style scoped> #home{ padding-top: 44px; } .home-nav { background-color: var(--color-tint); color: white; position: fixed; top: 0; left: 0; right: 0; z-index: 9; } .temp { height: 900px; } .tab-control{ position: sticky; top: 44px; background-color: #ffffff; }</style> |
十之五、首页商品的展示¶
GoodList.vue
1 | <template> <div class="goods"> <good-list-item v-for="item in goods" :goods-item="item"></good-list-item> </div></template><script> import GoodListItem from "@/components/content/goods/GoodListItem"; export default { name: "GoodsList", props: { goods: { type: Array, default() { return [] } } }, components: { GoodListItem } }</script><style scoped> .goods { /*width: 100%;*/ display: flex; flex-wrap: wrap; justify-content: space-evenly; }</style> |
GoodListItem.vue
1 | <template> <div class="goods-item"> <img :src="goodsItem.show.img" alt=""> <div class="goods-info"> <p>{{goodsItem.title}}</p> <span class="price">{{goodsItem.price}}</span> <span class="collect">{{goodsItem.cfav}}</span> </div> </div></template><script> export default { name: "GoodListItem", props: { goodsItem: { type: Object, default() { return {} } } } }</script><style scoped> .goods-item{ padding-bottom: 40px; position: relative; width: 48%; } .goods-item img{ width: 100%; border-radius: 5px; } .goods-info { font-size: 12px; position: absolute; left: 0; right: 0; bottom: 5px; overflow: hidden; text-align: center; } .goods-info p{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 3px; } .goods-info .price{ color: var(--color-high-text); margin-right: 20px; } .goods-info .collect{ position: relative; } .goods-info .collect::before{ content: ''; position: absolute; left: -15px; top: -1px; width: 14px; height: 14px; background: url("~@/assets/img/common/collect.svg") 0 0/14px 14px; }</style> |
十之六、首页-TabControl点击切换商品¶
1 | /** * 事件监听相关的方法 */ tabClick(index) { // console.log(index) switch (index) { case 0: this.currentType = 'pop' break case 1: this.currentType = 'new' break case 2: this.currentType = 'sell' break // this.currentType = Object.keys(this.goods)[index] } }, |
十之七、首页-Scroll的安装和使用¶
1 | npm install better-scroll@1.13.2 --save |
备注:不能再created()函数中那tamplate的dom元素
十之八、Better-scroll的基本使用解析¶
使用两个进行嵌套,再把需要滚动的内容放在里面
1 | <div> <div class="content"> ul>li{列表数据$}*100 </div></div><script src="bscroll.js"></script><script> const bscroll = new Bscroll(document.querySelector(".content"))</script> |
监听用户滚动到哪个位置
probeType
- 0/1:都是不侦测
- 2:在手指滚动的过程中侦测,手指离开后的惯性滚动不侦测
- 3:只要是滚动,都侦测
1 | const bscroll = new BScroll(document.querySelector('.content'),{ probeType: 2 }) bscroll.on('scroll',(position) => { console.log(position) }) |
click
- better-scroll会默认阻止浏览器的原生click事件,当设置为true时,会派发一个click事件,我们会给派发的event参数加一个私有属性_constructed,值为true
pullUpload
- 上拉加载更多
1 | { pullUpload:true}bscroll.on('pullingup',() => { console.log('上拉加载更多') //请求数据并展示完成 setTimeout(() =>{ bscroll.finishPullUp() },2000)}) |
十之九、首页-Scroll在Vue中项目中使用过程¶
为了防止有一天BetterScroll不再维护了,需要对其进行封装
十之十、首页-BScroll的封装以及使用¶
在vue中,如果明确的想要拿到某一个元素,用ref,不要用query
100vh: 100%视口高度
scroll.vue
1 | <template> <div class="wrapper" ref="wrapper"> <div class="content"> <slot></slot> </div> </div></template><script> import BScroll from 'better-scroll' export default { name: "Scroll", data() { return { scroll: null } }, mounted() { this.scroll = new BScroll(this.$refs.wrapper, { }) } }</script><style scoped></style> |
在home.vue中需要调整外层的高度
1 | #home{ padding-top: 44px; height: 100vh; position: relative; } .content{ /*height: calc(100% - 93px);*/ /*overflow: hidden;*/ /*margin-top: 44px;*/ /*height: 300px;*/ overflow: hidden; position: absolute; top: 44px; bottom: 49px; left: 0; right: 0; } |
十之十一、首页-BackTop组件的封装和使用¶
组件是不能直接监听点击的,需要加.native修饰符
1 | //在home中通过$refs属性,拿到scroll组件,再调用scroll对象中的方法this.$refs.scroll.scroll.scrollTo(0,0,500) |
十之十二、首页-BackTop的显示和隐藏¶
子传父,监听滚动的y坐标,对指定高度进行布尔值判断传给v-show属性
1 | //监听滚动的位置 this.scroll.on('scroll', (position) => { // console.log(position) this.$emit('scroll',position) }) |
十之十三、首页- 完成上拉加载更多¶
1 | loadMore(){ // console.log('aa') this.getHomeGoods(this.currentType) this.$refs.scroll.scroll.refresh() } |
十之十四、首页-滚动区域的Bug分析和解决¶
监听图片是否加载完成
- 原生js
1 | javascript img.onload = function() {} |
- vue中
1 | @load = "方法名" //3.监听item中图片加载完成 this.$bus.$on('itemImageLoad', () => { // console.log('111') this.$refs.scroll.refresh() }) |
十之十五、首页-refresh函数找不到的bug¶
在mouted中监听图片是否加载完
使用&&操作符,检测scroll对象是否存在
1 | mounted() { //3.监听item中图片加载完成 this.$bus.$on('itemImageLoad', () => { this.$refs.scroll && this.$refs.scroll.refresh() }) }, |
十之十六、首页-刷新频繁的防抖函数的处理¶
1 | debounce(func, delay) { let timer = null return function(...args) { if(timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this.args) },delay) } mounted() { const refresh = this.debounce(this.$refs.scroll && this.$refs.scroll.refresh) //3.监听item中图片加载完成 this.$bus.$on('itemImageLoad', () => { console.log('111') refresh() }) |
十之十七、首页-上拉加载更多的完成¶
监听底部的滚动
scroll默认加载一次,需要在获取完数据后,执行一次finishPullUp
十之十八、首页-tabControl的offsetTop的获取¶
1 | //获取tabControl的offsetTop //所有组件都有一个$el属性,用于获取元素的 console.log(this.$refs.tabControl.$el.offsetTop) |
十之十九、首页-tabControl的吸顶效果¶
- 获取offsetTop
1 | swiperImageLoad() { //获取tabControl的offsetTop //所有组件都有一个$el属性,用于获取元素的 // console.log(this.$refs.tabControl.$el.offsetTop) this.tabOffsetTop = this.$refs.tabControl.$el.offsetTop }, |
- 监听滚动,并且同步两个tabControl的状态
1 | vue this.$refs.tabControl1.currentType = index this.$refs.tabControl2.currentType = index |
十之二十、首页-Home离开时记录状态¶
- keep-alive
- 离开时保存一个位置信息,进来时设置回去
1 | activated() { // this.$refs.scroll.scrollTo(0,this.saveY,0) // this.$refs.scroll.scrollTo(0,this.saveY,0) // this.$refs.scroll.refresh() // console.log('activated') this.$refs.scroll.refresh() this.$refs.scroll.scrollTo(0,this.saveY,0) }, deactivated() { // this.saveY = this.$refs.scroll.getScrollY() // console.log('deactivated') this.saveY = this.$refs.scroll.getScrollY() }, |
十之二十一、跳转到详情页并且携带id¶
创建detail.vue路由
1 | <template> <div> {{iid}} </div></template><script> export default { name: "Detail", data(){ return { iid: null } }, created() { this.iid = this.$route.params.iid } }</script><style scoped></style> |
十之二十二、导航栏的封装¶
DetailNavBar.vue
1 | <template> <nav-bar> <div slot="left" class="back" @click="backClick"> <img src="~assets/img/common/back.svg" alt=""> </div> <div slot="center" class="title"> <div v-for="(item,index) in titles" class="title-item" :class="{active: index === currentIndex}" @click="titleClick(index)"> {{item}} </div> </div> </nav-bar></template><script> import NavBar from "@/components/common/navBar/NavBar"; export default { name: "DetailNavBar", components: { NavBar }, data() { return { titles: ['商品','参数','评论','推荐'], currentIndex:0 } }, methods:{ titleClick(index) { this.currentIndex = index }, backClick() { this.$router.back() } } }</script><style scoped> .title{ display: flex; font-size: 14px; } .title-item{ flex: 1; } .active{ color: var(--color-high-text); } .back img{ vertical-align: middle; }</style> |
十之二十三、数据请求以及轮播图展示¶
DetailSwiper.vue
1 | <template> <swiper class="detail-swipper"> <swiper-item v-for="item in topImages"> <img :src="item" alt=""> </swiper-item> </swiper></template><script> import {Swiper, SwiperItem} from 'components/common/swiper' export default { name: "DetailSwiper", components: { Swiper, SwiperItem }, props: { topImages: { type: Array, default() { return [] } } } }</script><style scoped>.detail-swipper{ height: 300px; overflow: hidden;}</style> |
十之二十四、商品基本信息的展示&店铺信息的解析¶
封装对应的组件即可
十之二十五、加入滚动效果的scroll¶
引入betterscroll,并设置顶部nav的样式
十之二十六、商品详情页数据展示¶
十之二十七、商品参数信息的展示¶
十之二十八、从首页跳转到详情页¶
书签
接口文档
使用的是Yapi来管理接口的
准备规划
划分目录结构
- src/
- assets
- components
- common 通用组件
- Background.vue
- content 业务相关的组件
- common 通用组件
- views
- network
- common
- common.js
- utils.js
- mixin.js
- store
- router 路由管理
各模块抽象原则
所有的模块,都是App.vue的子组件,需要初始化的网络请求,如背景图片的加载、导航数据的加载,在App.vue组件的created函数中,就发请求,并传给子组件;
以背景图片模块为例
UI配色
- #fbfbf2
- #e5e6e4
- #cfd2cd
- #a6a2a2
- #847577
功能模块
背景图模块
- 获取背景图
- 定义axios基础配置
- 编写img样式
- 在BackGround.vue组件创建时,发送请求,获取背景图片数据
- 背景图在点击输入框时,高斯模糊
- 废弃
- 图片背景图过大
- 暂缓解决
request.js
1 | import axios from 'axios' |
background.js
1 | import {request} from "./request" |
Background.vue
1 | <template> |
搜索模块
- 搭建input结构
- input结构,展示Input
- input-content结构,展示搜索的结果列表
- input交互
- hover状态
- focus/click状态
- 触发背景图片高斯模糊
- 搭建三个搜索引擎结构
时间模块
点击事件
- 控制Nav的show
- 控制input的隐藏
- 控制Nav中的margin的初始状态恢复
Cover层
- 完全透明
导航模块
拖拽排序功能
实例化方法,要在父导航数据对应的
dom
节点,完全生成了才能调用,使用$netxTick
实例化
SortableJs
,el
的选择要用querySelector
,不能用ref
,设计到加载先后的问题导航的数据,在
v-for
渲染时,要将userid
为-1
的添加自定义类,并且SortableJS
的配置对象中,添加过滤类名父导航数据初始化时,存储id,移动后,根据提供索引值,调用自定义方法(功能:根据索引交换数组的值),得到变化后的
id
列表同步数据库时
交互处理
问题
- 操作过快时,存在延迟写入的问题
侧边栏模块
天气
书签模块
侧边栏拖拽改变宽度
基于dom的事件,核心代码见《前端常见场景解决方案》
更新日志
2022年4月1日
- 【修复】修复侧边栏淡入淡出,与头像框淡入淡出不同步问题
- 给父元素添加
overflow: hidden;
,视觉效果同步了 - 但出现了新问题:修改
overflow: hidden
后,菜单栏被覆盖- 取消菜单栏,将个人中心入口放在头像,侧边栏新增退出入口
- 每个图标添加
alt
提示文字
- 给父元素添加
- 【修复】视觉问题
- 调整搜索结果字体颜色
- 直接通过控制台进行调整,取得最佳视觉效果后修改
- 调整搜索结果字体颜色
- 【修复】修复书签页中点击搜索框跳转到首页
click
事件加到父级元素了,该打
- 导入书签后,应该出现滚动条,方便拖动
- 但设置
overflow: scroll
并未出现滚动条 - 将问题转化为:vue项目中el-tree 支持横向和纵向滚动条设置
- 我卡住了啊
- 给
el-tree
套了一层el-scrollbar
后,设置.el-tree
的display: inline-block
,的确会出现横向滚动条,但是吧,一旦全部收起后,hover
时的背景色就不再独占一行了- 横向滚动栏,似乎必要性不大,实际并不会有太多太多的嵌套,可以适当的把左侧栏拉长点。
- 竖向滚动栏
- 放弃
- 但设置
- 【修复】修复插件版本,多个窗口下,其中一个窗口注销后,另一个窗口token失效跳转报错的问题
- 使用
window.location.reload()
替代window.location.href = '/'
- 这种方案会有问题,如果插件版本在书签栏,则在网页端注销后,再刷新插件版本,会重复回调
- 本来打算用路由的前置钩子,还是有一点问题
- 最后用
window.history.back(-1)
替代window.location.reload()
- 使用
- 第三方天气插件,文件转成本地
- 在dev环境中正常
- 单在crx环境中,资源成功请求到,但是页面没有加载出来
crx
中重新引入线上文件- 涉及到
csp
的概念,将问题转化为:csp
中script-src
设置允许多个源
- 涉及到
2022年4月2日
- 【修复】input长度限制
- 由于前后新增了
icon
图标,增大左右内边距即可
- 由于前后新增了
- 【功能新增】搜索引擎可配置(参考掘金的交互)
- 进度,准备监听url获取Logo
2022年4月6日
- 【接口优化】默认搜索引擎接口调试
2022年4月7日
- 【代码优化】登出后认证失效,应该重新请求下默认搜索引擎数渲染
- 计算属性获取和
store
绑定的localstorage
的值,再watch
监听,直接赋值给data
是获取不到变化的
- 计算属性获取和
- 【交互优化】
- 默认只展示5个,加一个显示更多的按钮,如果未登录,则隐藏该按钮
- 从数据层入手,只取前5个,不论是不是自定义的
v-for
限制渲染数量
- 从数据层入手,只取前5个,不论是不是自定义的
- 默认只展示5个,加一个显示更多的按钮,如果未登录,则隐藏该按钮
2022年4月20日
- 【功能新增】搜索引擎可配置(参考掘金的交互)
- 进度,准备监听url获取Logo
- 已完成
2022年4月28/29日
【功能新增】书签页的左侧栏支持拖拽调整大小
搜索结果磨砂效果,需要颗粒感,不用内阴影+透明度的那种效果
- 暂缓
书签高亮
书签导出
书签键盘交互
- 书签中的搜索enter
插件的新增url时,是否已新增需要记录
资源响应时间过长,但有的时候响应很快,可能单纯是网络问题吧
2022年5月5日
- 【功能新增】书签导出功能
- 接口已提供
2022年5月13日
- 【功能新增】导航浏览记录
bug
修复日志
2022年5月6日-多个a
标签的href
值更新问题
【bug】问题代码
前台
问题描述:
html
渲染是没有问题的,问题在于click
时,由于要实现最新点击的搜索记录在第一行,所以要更新v-for
的数据源,而这会导致a
标签的item
发生变化,所以在点击的一瞬间,item
变成了点击之前的上一条记录- 直观上的感受是,
click
时调用的update
操作,比浏览器a
标签的跳转要快
- 直观上的感受是,
解决方案
setTImeout
- 问题:由于是跳转到新的标签页,跳转后,setTimeoout就不执行了,只有重新回到原来的标签页后,才会执行,即使设置了
delay
为0,仍有延迟更新的停顿感。
- 问题:由于是跳转到新的标签页,跳转后,setTimeoout就不执行了,只有重新回到原来的标签页后,才会执行,即使设置了
使用
window.open()
替代a
标签,这样保证可以在跳转时item
是对的
2022年5月16日-多端token
失效跳转问题
多端登陆后,其中一端注销,redist中token失效,对于其他端的处理是跳转到首页。
但这种处理方式对于crx
不行,不是跳转不跳转的问题,对于crx
,路径chrome://newtab
才是新的标签页路径,不是一个维度的问题
问题代码:
1 | instance.interceptors.response.use(res => { |
解决方案:获取当前访问的url
然后拼接,因为crx
和web
环境的url
是不一样的
插件的打印结果
web
的打印结果
1 | instance.interceptors.response.use(res => { |
2022年5月23日-crx
引入第三方js
问题
问题:第三方的天气插件,无法加载
报错信息如下
1 | 22.11ce897335d8ea0956a4.js:1 |
解决方案:
将第三方的
js
等资源文件,全部下载至本地后打包1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46//引用线上插件
export function weather() {
window.WIDGET = {
"CONFIG": {
"layout": "2",
"width": "400",
"height": "295",
"background": "5",
"dataColor": "FFFFFF",
"borderRadius": "5",
"key": "c0de456ad3ff436f83ace3bf82b98b5d"
}
}
const s = document.createElement('script')
s.type = 'text/JavaScript';
s.src = 'https://widget.qweather.net/standard/static/js/he-standard-common.js?v=2.0';
document.body.appendChild(s);
}
// 引用本地插件
export function linkLocalWeather() {
window.WIDGET = {
"CONFIG": {
"layout": "2",
"width": "400",
"height": "295",
"background": "5",
"dataColor": "FFFFFF",
"borderRadius": "5",
"key": "c0de456ad3ff436f83ace3bf82b98b5d"
}
}
var c = document.createElement('link');
c.rel = 'stylesheet';
c.href = '../../../../static/weather/he-standard.css';
// c.href = 'https://widget.heweather.net/standard/static/css/he-standard.css?v=1.4.0'
var s = document.createElement('script');
s.src = '../../../../static/weather/he-standard.js';
// s.src = 'https://widget.heweather.net/standard/static/js/he-standard.js?v=1.4.0'
var sn = document.getElementsByTagName('script')[0];
sn.parentNode.insertBefore(c, sn);
sn.parentNode.insertBefore(s, sn);
}值得注意的是,即使初始资源转成了本地资源,但初始资源内部可能还会请求其他外部资源,需要都转成本地:
但问题在于,你不知道新的外部资源,有没有再次引用
方案二:
利用
crx
的插件协议:看外部有哪些链接,配置
csp
字段unsafe-eval
:允许用eval
函数unsafe-inline
:允许内嵌脚本,再添加访问的外部域名
1
"content_security_policy": "script-src 'self' 'unsafe-eval' 'unsafe-inlie' https://webapi.amap.com https://cdn.qweather.com/ https://restapi.amap.com ; object-src 'self'"
知识点小结
pc
商城
项目概述
业务概述
功能概述
开发模式
技术选型
项目初始化
前端项目初始化步骤
安装当前项目的依赖vue版本,
ssh配置git
后台环境安装
- mysql
- node
登陆/退出功能
1 | "@vue/cli-plugin-babel": "~4.5.0", |
主页布局
头部布局
Login.vue
1 | <template> |
效果:
用户管理模块
权限管理模块
分离管理模块
参数管理模块
商品列表模块
订单管理模块
数据统计模块
pc
商城前后台
来源:https://www.bilibili.com/video/BV1Vf4y1T7bw
1.简介
- 前台项目:
- 技术架构:
vue + webapck + vuex + vue-router + axios + less
- 封装通用组件
- 登录注册
token
- 守卫
- 购物车
- 支付
- 项目性能优化
...
- 技术架构:
- 后台项目:
- 技术架构:
vue + webpack + vuex + vue-router + axios + scss + elementUI...
elementUI
- 菜单按钮
- 按钮权限
- 数据可视化
...
- 技术架构:
Vue3
+Nuxt3
开发房源网站
nvm
安装与使用
NVM
是一个非常方便的node
包管理工具,可以实现在NodeJS
各个不同版本之间自由的进行切换。
下面,介绍用root
权限安装NVM
工具。到2022年6月,nvm
的最新版本为v0.39
。
vite
依赖的node
版本 >=
12.0.0
nuxt3
依赖的node
版本>=
14.16.0
安装nvm
下载
1 | wget https://github.com/nvm-sh/nvm/archive/refs/tags/v0.39.1.tar.gz |
解压
1 | mkdir -p /root/.nvm |
配置环境,打开~/.bashrc
1 | vim ~/.bashrc |
在末尾添加
1 | # nvm path env |
保存退出并使配置生效
1 | source ~/.bashrc |
使用nvm
安装node
列出已经安装的版本
1 | nvm ls |
安装指定版本nodejs
1 | nvm install 16.14.0 |
卸载指定版本nodejs
1 | nvm uninstall 16.14.0 |
切换到其他版本nodejs
1 | nvm use 14.17.3 |
切换到iojs
1 | nvm use iojs-v3.2.0 |
vite
安装与使用
安装vite
vite
依赖的node
版本 >=
12.0.0
打开终端,查看npm
版本:npm -v
npm
版本在6.x
及以下使用如下命令
aribnb-ssr
表示项目名称template
表示预设模板(技术栈)
1 | npm init vite@latest airbnb-ssr --template vue-ts |
npm
版本在7
及7+
使用如下命令
1 | npm init vite@latest airbnb-ssr -- --template vue-ts |
切换到文件夹下,安装依赖
1 | cd airbnb-ssr |
vite
构建基本配置
由于是在linux
环境下,运行启动命令前,改下package.json
的配置
1 | "dev": "vite --host 0.0.0.0 --port 8081", // 指定ip和开放的端口号 |
vite
和webapck
的不同:
- 没有
vendor
文件,利用的是浏览器原生的esModule
配置@
别名
vite.config.ts
1 | import { defineConfig } from 'vite' |
tsconfig.json
1 | { |
目录结构解析
相比较于常规vue2
目录,index.html
抽离出来了,<scipt>
标签上多了type="module"
属性,使用了main.ts
作为项目的入口文件
项目技术选型
vue-router
安装
1 | npm i vue-router@next -D |
安装的是4.0.13
版本
引入
新建router
目录,并新建router.ts
router.ts
1 | import home from '@/views/home/index.vue'; |
main.ts
中使用vue-router
插件
1 | import { createApp } from 'vue' |
使用
组件中使用router
App.vue
1 | <script setup lang="ts"> |
@/views/home/index.vue
1 | <script setup lang="ts"> |
@/views/mine/index.vue
1 | <script setup lang="ts"> |
element-plus
安装
1 | npm i element-plus -D |
引入
按需引入
先下载依赖,实现自动导入
1 | npm i -D unplugin-vue-components unplugin-auto-import |
在vite.config.ts
中新增配置
1 | import { defineConfig } from 'vite' |
在入口文件中导入,并挂载
1 | import { createApp } from 'vue' |
使用
home/index.vue
1 | <script setup lang="ts"> |
可以看到,后台只引入了button
相关的样式
前台:
eleme-plus
是主包base.css
是基本样式文件el-button
是按钮相关样式
可以看到,UI
库占用的体积很小,这个就是按需加载的好处
我们再引入一下消息提示的组件
1 | <script setup lang="ts"> |
相比较于button
,消息提示框的组件,我们全局可能都会用到,可以在全局引入
使用vm
实例上的config.globalProperty.XXX = YYY
来配置全局属性
main.ts
1 | import { createApp } from 'vue' |
在组件中,使用vue
的上下文来使用$message
1 | <script setup lang="ts"> |
吐槽
为啥还会有个element-plus
的chunk
文件,2M
多!!
eslint
安装
1 | npm i eslint -D |
安装完之后,执行一下eslint
npx eslint --init
或npm init @eslint/config
- 备注:
npm5.2
之后,内置了npx
包,可以直接运行了(找自己的node_modules
)
1 | [root@VM-4-12-centos airbnb-ssr]# npm init @eslint/config |
安装完毕后,会根据我们的选择,生成.eslintrc.js
配置文件
1 | module.exports = { |
使用
如果是在本地开发
- 我们导入了未使用的组件,则会出现错误提示
- 统一变量定义等
自定义规则:
- 函数名和括号间不想强制加空格
1 | rules: { |
搭配vscode
插件:eslint
使用更佳
如果eslint
未生效,就要检查vscode
之前有没有安装过类似的格式化工具、自动美化的一些插件
如果是在linux
远程开发,暂时没办法
Sass
安装
1 | npm i sass -D |
引入
vite.config.js
Vue
前后台项目-尚品汇
链接:https://www.bilibili.com/video/BV1Vf4y1T7bw
前言:不以html
+css
为主,主要搞业务,所以静态页面直接拿来用
初始化项目及配置
- 配置
src
别名 - 配置浏览器自动打开
- 关闭
eslint
语法检查 - 安装
less
预处理器,也可以在项目初始化的时候选择
路由分析
- 路由组件:首页、登录
- 非路由组件:头部、底部
完成非路由组件
实际开发流程:
- 书写静态页面
- 拆分组件
- 获取服务器数据动态展示
- 完成相应的动态业务逻辑
components/Header/index.vue
components/Footer/index.vue
完成路由组件
安装
vue-router
,可以直接在项目初始化时选中新建路由并配置
配置重定向
配置路由跳转
Footer
组件的显示与隐藏