vue:只操作数据,视图更新是异步的(所有的数据都更新完毕,再更新视图)
JQuery:从dom树中选择操作dom对象, $函数选择节点操作dom
数据驱动视图=模型驱动视图
数据=模型
VM视图和模型的双向数据绑定,v-model来实现双向绑定
JavaScript Standard Style 规范说明 https://standardjs.com/rules-zhcn. html
npm run lint 命令自动改成规范代码 (删除代码需要手动解决)
// 当保存的时候,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
"source.fixAll": true
},
// 保存代码,不自动格式化
"editor.formatOnSave": false
vant2-----vue2
//main.js
import Vue from 'vue';
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
//app.vue
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
解决版本依赖 (版本降级命令) npm i -D @vue/[email protected]
成功后将6.1.0降级为5.1.0
需要先安装插件 npm i babel-plugin-import -D
# 安装插件
npm i babel-plugin-import -D
//babel.config.js
"plugins": [
["import", {
"libraryName": "vant",
"libraryDirectory": "es",
"style": true
}]
]
import '@/utils/vant-utils.js'
<van-field label="文本" placeholder="请输入用户名" />
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
// 按需引入
import Vue from 'vue'
import { Field, Button } from 'vant'
Vue.use(Field)
Vue.use(Button)
vw适配----Viewport 布局(Vant2–进阶用法)
// postcss.config.js
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375,//视口宽度是375像素
},
},
};
//vant-utils.js
import Vue from 'vue';
import { Tabbar, TabbarItem } from 'vant';
Vue.use(Tabbar);
Vue.use(TabbarItem);
// layout.vue
<template>
<div class="layout-page">
<div>首页架子 - 内容区域</div>
<div>
<van-tabbar v-model="active">
<van-tabbar-item icon="notes-o">面经</van-tabbar-item>
<van-tabbar-item icon="star-o">收藏</van-tabbar-item>
<van-tabbar-item icon="like-o">喜欢</van-tabbar-item>
<van-tabbar-item icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</div>
</template>
<script>
export default {
name: 'LayoutPage',
data () {
return {
active: 0
}
}
}
</script>
<style lang="less" scoped></style>
<van-tabbar v-model="active">
<van-tabbar-item icon="notes-o">面经</van-tabbar-item>
<van-tabbar-item icon="star-o">收藏</van-tabbar-item>
<van-tabbar-item icon="like-o">喜欢</van-tabbar-item>
<van-tabbar-item icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
定制方法
1.步骤一 引入样式源文件
// 指定样式路径
style: (name) => `${name}/style/less`,
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
'text-color': '#111',
'border-color': '#eee',
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "your-less-file-path.less";`,
},
},
},
},
},
Object.defineProperty() 是 JavaScript 中用于定义或修改对象属性的方法。它允许你精确地定义对象属性的特性,包括可写性(writable)、可枚举性(enumerable)、可配置性(configurable)以及设置访问器属性(getter 和 setter)。
语法:Object.defineProperty(obj, prop, descriptor)
参数说明:
obj:要定义属性的对象。
prop:要定义或修改的属性名。
descriptor:一个包含属性的特性的对象。
descriptor 对象包含以下可选的属性:
value:设置属性的值(仅用于数据属性,而非访问器属性)。
writable:指定属性是否可写入(true/false)。
enumerable:指定属性是否可枚举(true/false)。
configurable:指定属性是否可配置(true/false)。
get:定义属性的 getter 函数(计算属性)。
set:定义属性的 setter 函数(计算属性)。
v-model 底层的原理 :value + @input
留一个问题: username 和 pssword 数据发生变化,都会导致 input 和 pNode 更新视图
响应式布局和移动端适配是为了在移动设备上提供更好的用户体验而采取的两种不同的方法。
**响应式布局是一种设计方法,它使网页可以自动适应不同的屏幕尺寸和设备类型,以提供一致的布局和体验。**在响应式布局中,使用媒体查询、弹性网格布局和其他技术来调整网页的布局、字体大小、图像大小和功能,以适应不同设备上的屏幕大小。
**移动端适配是指根据具体的移动设备类型和屏幕尺寸来调整和优化网页的布局和功能,以更好地适应移动设备的特点。**移动端适配可以通过多种技术实现,例如使用CSS媒体查询、JavaScript框架(如Bootstrap、Foundation等)或专门为移动设备设计的CSS框架(如Ionic、Ant Design Mobile等)。移动端适配可以根据具体的移动设备类型和屏幕大小提供不同的布局、字体大小、交互方式等。
区别:
设计理念:响应式布局是为了在不同设备上提供一致的体验,通过调整布局和功能来适应不同屏幕尺寸。移动端适配更关注于移动设备上的特定布局和功能,以提供更好的移动体验。
实现方式:响应式布局使用CSS媒体查询和弹性网格布局等技术,通过一个网页来适应不同设备。移动端适配可以使用不同的方式来适应移动设备,如媒体查询、JavaScript框架或移动专用的CSS框架。
维护和开发成本:响应式布局只需要维护一个网页,适应不同设备,因此维护和开发成本相对较低。移动端适配可能需要维护多个布局和功能,因此可能会涉及更高的维护和开发成本。
综上所述,响应式布局和移动端适配是两种不同的方法,用于在移动设备上提供更好的用户体验。选择哪种方法取决于项目需求、设备范围和开发资源。有些情况可能需要同时使用响应式布局和移动端适配来实现最佳的移动体验。
//index.js
import Article from '@/views/Article'
import Collect from '@/views/Collect'
import Like from '@/views/Like'
import User from '@/views/User'
const router = new VueRouter({
// 路由表
routes: [
{
path: '/login',
component: Login
},
{
path: '/detail',
component: Detail
},
{
path: '/register',
component: Register
},
{
path: '/',
component: Layout,
// 用户访问/跟路由的时候,会自动重定向到/article页面
redirect: '/article', // 重定向
children: [
{
path: '/article',
component: Article
},
{
path: '/collect',
component: Collect
},
{
path: '/like',
component: Like
},
{
path: '/user',
component: User
}
]
}
]
})
//Layout.vue
<router-view></router-view>
replace 是否在跳转时替换当前页面历史(是否能回退)
//Layout.vue
<van-tabbar v-model="active" route>
<van-tabbar-item replace to="/article" icon="notes-o">面经</van-tabbar-item>
<van-tabbar-item replace to="/collect" icon="star-o">收藏</van-tabbar-item>
<van-tabbar-item replace to="/like" icon="like-o">喜欢</van-tabbar-item>
<van-tabbar-item replace to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
NavBar 导航栏
//vant-utils.js
import Vue from 'vue';
import { NavBar } from 'vant';
Vue.use(NavBar);
<van-nav-bar title="面经登录"/>
Form 表单
//vant-utils.js
import Vue from 'vue';
import { Form } from 'vant';
import { Field } from 'vant';
Vue.use(Form);
Vue.use(Field);
round 提交的圆角
//Login.vue
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="用户名"
label="用户名"
placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<van-field
v-model="password"
type="password"
name="密码"
label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
<div style="margin: 16px;">
//round 提交的圆角
<van-button round block type="info" native-type="submit">提交</van-button>
</div>
</van-form>
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
onSubmit(values) {
console.log('submit', values);
},
},
};
trigger 本项规则的触发时机,可选值为 onChange、onBlur
<template>
<div class="login-page">
登录页
<van-nav-bar title="面经登录" />
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="username"
label="用户名"
placeholder="用户名"
:rules="usernameRules"
/>
<van-field
v-model="password"
type="password"
name="password"
label="密码"
placeholder="密码"
:rules="passwordRules"
/>
<div style="margin: 16px">
<van-button round block type="info" native-type="submit"
>提交</van-button
>
</div>
</van-form>
</div>
</template>
<script>
export default {
data () {
return {
username: '',
password: '',
usernameRules: [
{ required: true, message: '请填写用户名' },
{ pattern: /^\w{6,20}$/, message: '用户名长度必须在6~20位之间' }
],
passwordRules: [
{ required: true, message: '请填写密码' },
{ pattern: /^[0-9]{6,}$/, message: '密码必须是数字,且最短为6位' }
]
}
},
methods: {
onSubmit (values) {
// console.log('submit', values)
}
}
}
</script>
你可以使用以下正则表达式来匹配只包含数字且至少六位的字符串:
/^\d{6,}$/
解释一下这个正则表达式:
^
表示匹配字符串的开头\d
表示匹配任意数字字符(0-9){6,}
表示前面的表达式必须出现至少6次$
表示匹配字符串的结尾因此,这个正则表达式能够确保字符串只包含数字,并且至少有六个数字字符。
用于捕获同步异常,也就是说不能直接捕获promise异常
用async和await转为同步代码,就可以用try{}catch{}捕获错误
axios.post('http://interview-api-t.itheima.net/h5/user/login', {
// values
username: this.username,
password: this.password
})
//vant-utils.js
import { Toast } from 'vant';
Vue.use(Toast);
//Login.vue
Toast('提示内容');
methods: {
async onSubmit (values) {
console.log('submit', values)
try {
await axios.post('http://interview-api-t.itheima.net/h5/user/login', {
// values
username: this.username,
password: this.password
})
} catch (err) {
// conslole.log(err)
this.$toast(err.response.data.message)
}
}
}
<template>
<div class="login-page">
登录页
<van-nav-bar title="面经登录" />
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="username"
label="用户名"
placeholder="用户名"
:rules="usernameRules"
/>
<van-field
v-model="password"
type="password"
name="password"
label="密码"
placeholder="密码"
:rules="passwordRules"
/>
<div style="margin: 16px">
<van-button round block type="info" native-type="submit"
>提交</van-button
>
</div>
</van-form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
username: '',
password: '',
usernameRules: [
{ required: true, message: '请填写用户名' },
{ pattern: /^\w{6,20}$/, message: '用户名长度必须在6~20位之间' }
],
passwordRules: [
{ required: true, message: '请填写密码' },
{ pattern: /^[0-9]{6,}$/, message: '密码必须是数字,且最短为6位' }
]
}
},
methods: {
async onSubmit (values) {
console.log('submit', values)
try {
await axios.post('http://interview-api-t.itheima.net/h5/user/login', {
// values
username: this.username,
password: this.password
})
} catch (err) {
// conslole.log(err)
this.$toast(err.response.data.message)
}
}
}
}
</script>
<style lang="less" scoped>
.link {
color: #069;
font-size: 12px;
padding-right: 20px;
float: right;
}
</style>
用户接口模块
面经接口模块
只关注请求地址和请求类型
//Register.vue
<template>
<div class="register-page">
<van-nav-bar title="面经注册" />
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="username"
label="用户名"
placeholder="用户名"
:rules="usernameRules"
/>
<van-field
v-model="password"
type="password"
name="password"
label="密码"
placeholder="密码"
:rules="passwordRules"
/>
<div style="margin: 16px">
<van-button round block type="info" native-type="submit"
>提交</van-button
>
</div>
</van-form>
</div>
</template>
<script>
//Register.vue
import axios from 'axios'
export default {
name: 'RegisterPage',
data () {
return {
username: '',
password: '',
usernameRules: [
{ required: true, message: '请填写用户名' },
{ pattern: /^\w{6,20}$/, message: '用户名长度必须在6~20位之间' }
],
passwordRules: [
{ required: true, message: '请填写密码' },
{ pattern: /^[0-9]{6,}$/, message: '密码必须是数字,且最短为6位' }
]
}
},
methods: {
async onSubmit (values) {
console.log('submit', values)
try {
await axios.post('http://interview-api-t.itheima.net/h5/user/login', {
// values
username: this.username,
password: this.password
})
} catch (err) {
// conslole.log(err)
this.$toast(err.response.data.message)
}
}
}
}
</script>
//user.js
import axios from 'axios'
import { Toast } from 'vant'
// 用户登录
export function userLogin (values) {
return axios.post('http://interview-api-t.itheima.net/h5/user/login', values).catch(err => {
console.log(err)
Toast(err.response.data.message)
})
}
// 用户注册
export async function userRegister (values) {
return await axios.post('http://interview-api-t.itheima.net/h5/user/register', values).catch(err => {
Toast(err.response.data.message)
})
}
//Login.vue
import { userLogin } from '@/api/user'
methods: {
async onSubmit (values) {
await userLogin(values)
}
}
//Register.vue
import { userRegister } from '@/api/user'
methods: {
async onSubmit (values) {
console.log('submit', values)
const res = await userRegister(values)
if (res && res.data?.code === 10000) {
this.$router.push('/Login')
}
}
}
响应拦截器: 在响应回来之后,把响应给应用数据之前做最后的加工
基地址(基础地址)
axios.create 方法会创建一个新的请求实例
后续会使用 instance 对象去发送请求
封装到一个新的文件里面 utils/request.js
//request.js
import axios from 'axios'
import { Toast } from 'vant'
// axios.create方法会创建一个新的请求实例
// 后续会使用instance 对象去发送请求
const instance = axios.create({
// 基地址:会在后续的请求中,自动拼接到url上
baseURL: 'http://interview-api-t.itheima.net'
})
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
在api/user.js文件下二级封装中修改
//user.js
// import axios from 'axios'
// import { Toast } from 'vant'
// 因为 @/utils/request 是默认导出,所以导入的时候,可以随便重新命名
import request from '@/utils/request'
// 用户登录
export function userLogin (values) {
return request.post('/h5/user/login', values)
// return axios.post('http://interview-api-t.itheima.net/h5/user/login', values).catch(err => {
// console.log(err)
// Toast(err.response.data.message)
// })
}
// 用户注册
export async function userRegister (values) {
return request.post('/h5/user/register', values)
// return await axios.post('http://interview-api-t.itheima.net/h5/user/register', values).catch(err => {
// Toast(err.response.data.message)
// })
}
//Register.vue
methods: {
async onSubmit (values) {
console.log('submit', values)
const res = await userRegister(values)
if (res && res.data?.code === 10000) {
localStorage.setItem('username', JSON.stringify(res.data.data.token))
this.$router.push('/Login')
}
}
}
}
//Login.vue
methods: {
async onSubmit (values) {
const res = await userLogin(values)
localStorage.setItem('username', JSON.stringify(res.data.data.token))
}
}
// utils/storage.js
// 专门提供对本地存储的操作
const KEY = 'hm-token'
// 写数据==>写token
export function setToken (token) {
return localStorage.setItem(KEY, token)
}
// 读数据==>读token
export function getToken () {
return localStorage.getItem(KEY)
}
// 删数据==>删token
export function delToken () {
return localStorage.removeItem(KEY)
}
router.beforeEach((to, from, next) => {
// 1.to往哪里去,到哪去的路由信息对象
// 2.from 从哪里来,从哪来的路由信息对象
// 3.next() 是否放行
// 如果next()调用,就是放行
// next(路径) 拦截到某个路径页面
})
//index.js
router.beforeEach((to, from, next) => {
const token = getToken()
if (token) {
// 已登录
next()
} else {
// 是否是去登录或注册页面
if (to.path === '/login' || to.path === '/register') {
next()
} else {
// 未登录
next('/login')
}
}
})
//index.js
// 路由守卫
// 前置守卫 beforeEach
// to:去哪里
// from:从哪来
// next: 用来控制是否放行
// from: /login ==> to: /register
// from: /login ==> /user
// 定一个一个白名单列表
// 白名单:不需要登录就能访问的页面
import { getToken } from '@/utils/storage'
const whiteList = ['/login', '/register']
router.beforeEach((to, from, next) => {
const token = getToken()
// console.log(from, to)
// 不放行:让路由跳转到另一个页面next('/user')
if (token) {
// 已登录
next()
} else {
// 是否是去登录或注册页面
// if (whiteList.index0f(to.path) >= 0) {
if (whiteList.includes(to.path)) {
// if (to.path === '/login' || to.path === '/register') {
next()
} else {
// 未登录
next('/login')
}
}
// ②第二步 合并if 增加去掉else
// if (token) {
// next()
// return
// }
// if (whiteList.includes(to.path)) {
// next()
// }
// ③第三步 转为卫语句
// if (token || whiteList.includes(to.path)) {
// return next()
// }
// next('/login')
})
//index.js
const whiteList = ['/login', '/register']
router.beforeEach((to, from, next) => {
const token = getToken()
if (token || whiteList.includes(to.path)) {
return next()
}
next('/login')
})
在进入到某个路由页面之前,拦截一下,并根据特定条件决定是否允许继续导航
//vant-utils.js
import { Cell } from 'vant'
Vue.use(Cell)
//Article.vue
<template>
<div class="article-page">
<nav class="my-nav van-hairline--bottom">
<a
href="javascript:;"
>推荐</a
>
<a
href="javascript:;"
>最新</a
>
<div class="logo"><img src="@/assets/logo.png" alt=""></div>
</nav>
<van-cell class="article-item" >
<template #title>
<div class="head">
<img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
<div class="con">
<p class="title van-ellipsis">宇宙头条校招前端面经</p>
<p class="other">不风流怎样倜傥 | 2022-01-20 00-00-00</p>
</div>
</div>
</template>
<template #label>
<div class="body van-multi-ellipsis--l2">
笔者读大三, 前端小白一枚, 正在准备春招, 人生第一次面试, 投了头条前端, 总共经历了四轮技术面试和一轮hr面, 不多说, 直接上题 一面
</div>
<div class="foot">点赞 46 | 浏览 332</div>
</template>
</van-cell>
<van-cell class="article-item" >
<template #title>
<div class="head">
<img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
<div class="con">
<p class="title van-ellipsis">宇宙头条校招前端面经</p>
<p class="other">不风流怎样倜傥 | 2022-01-20 00-00-00</p>
</div>
</div>
</template>
<template #label>
<div class="body van-multi-ellipsis--l2">
笔者读大三, 前端小白一枚, 正在准备春招, 人生第一次面试, 投了头条前端, 总共经历了四轮技术面试和一轮hr面, 不多说, 直接上题 一面
</div>
<div class="foot">点赞 46 | 浏览 332</div>
</template>
</van-cell>
<van-cell class="article-item" >
<template #title>
<div class="head">
<img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
<div class="con">
<p class="title van-ellipsis">宇宙头条校招前端面经</p>
<p class="other">不风流怎样倜傥 | 2022-01-20 00-00-00</p>
</div>
</div>
</template>
<template #label>
<div class="body van-multi-ellipsis--l2">
笔者读大三, 前端小白一枚, 正在准备春招, 人生第一次面试, 投了头条前端, 总共经历了四轮技术面试和一轮hr面, 不多说, 直接上题 一面
</div>
<div class="foot">点赞 46 | 浏览 332</div>
</template>
</van-cell>
</div>
</template>
<script>
export default {
name: 'ArticlePage',
data () {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.article-page {
margin-bottom: 50px;
margin-top: 44px;
.my-nav {
height: 44px;
position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 999;
background: #fff;
display: flex;
align-items: center;
> a {
color: #999;
font-size: 14px;
line-height: 44px;
margin-left: 20px;
position: relative;
transition: all 0.3s;
&::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
width: 0;
height: 2px;
background: #222;
transition: all 0.3s;
}
&.active {
color: #222;
&::after {
width: 14px;
}
}
}
.logo {
flex: 1;
display: flex;
justify-content: flex-end;
> img {
width: 64px;
height: 28px;
display: block;
margin-right: 10px;
}
}
}
}
.article-item {
.head {
display: flex;
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
.con {
flex: 1;
overflow: hidden;
padding-left: 10px;
p {
margin: 0;
line-height: 1.5;
&.title {
width: 280px;
}
&.other {
font-size: 10px;
color: #999;
}
}
}
}
.body {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-top: 10px;
}
.foot {
font-size: 12px;
color: #999;
margin-top: 10px;
}
}
</style>
//main.js
// 注册一个全局组件 ArticleItem
import ArticleItem from '@/components/ArticleItem'
Vue.component(ArticleItem.name, ArticleItem)
//Article.vue
<template>
<div class="article-page">
<nav class="my-nav van-hairline--bottom">
<a
href="javascript:;"
>推荐</a
>
<a
href="javascript:;"
>最新</a
>
<div class="logo"><img src="@/assets/logo.png" alt=""></div>
</nav>
<article-item></article-item>
</div>
</template>
<script>
// import ArticleItem from '@/components/ArticleItem.vue'
export default {
name: 'ArticlePage',
comments: {
// ArticleItem
},
data () {
return {
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.article-page {
margin-bottom: 50px;
margin-top: 44px;
.my-nav {
height: 44px;
position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 999;
background: #fff;
display: flex;
align-items: center;
> a {
color: #999;
font-size: 14px;
line-height: 44px;
margin-left: 20px;
position: relative;
transition: all 0.3s;
&::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
width: 0;
height: 2px;
background: #222;
transition: all 0.3s;
}
&.active {
color: #222;
&::after {
width: 14px;
}
}
}
.logo {
flex: 1;
display: flex;
justify-content: flex-end;
> img {
width: 64px;
height: 28px;
display: block;
margin-right: 10px;
}
}
}
}
</style>
在其他几个组件like user中一样操作写 即可
//Collect.vue
<template>
<div class="collect-page">
<article-item></article-item>
</div>
</template>
//api/article.js
//封装跟面经相关的接口
import request from '@/utils/request'
export const getArticles = (query) => {
return request.get('/h5/interview/query', { params: query })
// get 请求的 query 参数,需要用 params 字段来指定,并且 params包含在一个对象中
}
?
//简写 default:()=>({})
default:()={
return {}
}
//ArticleItem.vue
export default {
name: 'article-item',
props: {
data: {
type: Object,
default: () => {
return {}
}
}
},
methods: {
clearAllTags (str) {
return str.replace(/<[^>]+>/ig, '')
}
}
}
//ArticleItem.vue
<template>
<van-cell class="article-item" >
<template #title>
<div class="head">
<img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
<div class="con">
<p class="title van-ellipsis">{{data.stem}}</p>
<p class="other">{{data.creator}} |{{data.createdAt}}</p>
</div>
</div>
</template>
<template #label>
<div class="body van-multi-ellipsis--l2" >
{{clearAllTags(data.content)}}
</div>
<div class="foot">点赞 {{ data.likeCount }} | 浏览 {{ data.views }}</div>
</template>
</van-cell>
</template>
//ArticleItem.vue
methods: {
clearAllTags (str) {
return str.replace(/<[^>]+>/ig, '')
}
}
// 如果这里发生的是 401 错误,就让用户重新登录:跳转到登录页面
if (error.response.status => 401){
// token 失效 (token错误,不能使用.无效) => token过期
}
//request.js
import axios from 'axios'
import { Toast } from 'vant'
import { getToken } from './storage'
import router from '@/router'
// axios.create方法会创建一个新的请求实例
// 后续会使用instance 对象去发送请求
const instance = axios.create({
// 基地址:会在后续的请求中,自动拼接到url上
baseURL: 'http://interview-api-t.itheima.net'
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
// 统一进行错误处理
// 给用户提示错误
// console.log('接口响应结果不是200 段')
// 如果这里发生的是401错误,就让用户重新登陆:跳转到登录页面
if (error.response.status === 401) {
// token 失数(token悟说,不能使用,无效) ==> token过期
// 不能这样写:组件里面才这么写
// this.$router.push("/login')
router.push('/Login')
Toast('token失败,请重新登录')
return Promise.reject(error)
}
Toast(error.response.data.message)
return Promise.reject(error)
})
// 导出instance,因为: instance是处理过公共基地址和公共错误的请求
export default instance
2.instance of 运算符在判断对象的类型时非常有用,可以帮助我们判断一个对象是否属于某个特定的类或构造函数的实例。
instanceof 运算符会检查 object 的原型链上是否存在 constructor.prototype,如果存在,则返回 true,表示 object 是 constructor 的实例;如果不存在,则返回 false。
console.log(listArray.push ===Array.prototype.push)//true
前后端并行开发
mock ⇒ 模拟接口数据
调数据要经过 axios (XML / XHR)
//mock/index.js
request.get = function (url, config) {
// console.log(123)
if (url === '/h5/interview/query') {
// console.log(21331)
return Promise.resolve(articleListData)
}
}
npm i -g http-server 是一个命令,用于通过npm全局安装一个名为http-server的软件包。该软件包是一个简单的命令行工具,可以帮助你在本地快速启动一个基于HTTP协议的静态文件服务器。
在vue.config.js中添加代码 publicPath: ‘./’,
npm run build
生成一个dist文件夹 ,单机index直接能打开
路由懒加载(Route Lazy Loading)是一种优化前端应用程序性能的方法。 不是一次性将所有路由模块都加载进来。
路由懒加载通常与模块打包工具(如Webpack)和前端框架(如React、Vue)一起使用。下面以Vue Router为例,介绍如何实现路由懒加载:
import Home from './views/Home.vue';
import About from './views/About.vue';
import Contact from './views/Contact.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact }
];
// ...
const router = new VueRouter({
routes
});
在这种方式下,所有路由模块都会在初始加载时一起被打包和加载。
const routes = [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
{ path: '/contact', component: () => import('./views/Contact.vue') }
];
// ...
const router = new VueRouter({
routes
});