初始化
yarn add vue-router@3.5.3
yarn add vuex@3.6.2
装router和store
Git
git remote remove origin
git remote add origin https://gitee.com/nikki-u/headline.git
git push -u origin "master"
如果远程仓库被占用
git remote remove origin
git remote add XXX//路径
git push XXX//分支
iconfont
资源管理-我的项目-新建项目-头条
1 创建项目--2fontclass--3添加图片--4去掉颜色提交--5生成在线链接--
6复制css--7粘贴进 新建style/icon.css--8在main.js中引进--使用
vant导入所有组件(项目需求大导入所有组件,其他推荐导入部分)
1、yarn add vant@2.12.24
2、引入全局main.js
import Vant from 'vant';
import 'vant/lib/index.css';
Vue.use(Vant);
3、直接使用
axios
1、yarn add axios
2、utils/request.js
import axios from "axios";
const request=axios.create({
baseURL:"http://toutiao.itheima.net/"
})
export default request
rem适配
1、
yarn add amfe-flexible
2、main.js
import 'amfe-flexible'
3、postcss-pxtorem将px转换为rem
yarn add postcss postcss-pxtorem@5.1.1
4、.postcssrc.js
module.exports = {
plugins: {
'autoprefixer': {
browsers: ['Android >= 4.0', 'iOS >= 8']
},
'postcss-pxtorem': {
//rootValue: 37.5,
rootValue({ file }) {
return file.indexOf('vant') !== -1 ? 37.5 : 75
},//用于两个设计稿,vant设计稿和产品设计稿
propList: ['*']
}
}
}
登录注册
1、切换分支login
2、设置路由
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/layout/home' },
{ path: '/login', component: () => import('@/views/login') },
{ path: '/search', component: () => import('@/views/search') },
{ path: '/profile', component: () => import('@/views/user-profile') },
{ path: '/article/:articleId',
component: () => import('@/views/article') ,props:true},
{
path: '/layout', component: () => import('@/views/layout'), children: [
{ path: 'home', component: () => import('@/views/home') },
{ path: 'my', component: () => import('@/views/my') },
{ path: 'video', component: () => import('@/views/video') },
{ path: 'qa', component: () => import('@/views/qa') },
]
},
]
})
3、使用vant组件
navbar导航栏
form表单
field输入框
button按钮
4、ui布局
样式
5、styles/index.less写入样式
6、
yarn add less less-loader@5.0.0
7、main.js引入样式
index.vue
8、登录图标和验证码template
9、clearable
10、登录
11、this.$toast.success("登录成功");
12、加载
13、表单校验
:rules="userFormRules.mobile"
data、、
userFormRules: {
mobile: [
{required:true,message:'不能为空'},
{pattern:/^1[3|5|6|7|8]\d{9}$/,message:'电话格式错误'},
],
code: [
{required:true,message:'不能为空'},
{pattern:/^\d{6}$/,message:'验证码格式错误'},
],
},
14、点击发送验证码
native-type="button"
await this.$refs.loginForm.validate("mobile");
验证手机号+发送验证码
15、倒计时
16、获取验证码
查询参数params:{}+get
请求参数data:{}+post
路径参数/:参数${}
17、保存到vuex和本地
18、封装storage.js
19、上传到git git部分指令
git add .
git commit -m ''
git checkout master
git merge login
git branch -d login
git push
日常工作如何使用git
git clone从无到有
git pull早上远程拉取代码
git pull晚上远程拉取代码
git push每次push之前先git pull
个人中心
1、创建路由
layout
-home
-qa
-video
-my
2、路由懒加载 利用回调函数导入组件()=>import '@/...'
提高性能 缩短首屏加载时间
3、开启路由route
4、登录ui页面
5、添加导航栏
6、v-if v-else登录未登录切换
7、获取用户信息
8、渲染
9、退出登录
10、提示框toast
11、axios请求拦截器
axios里面有两个拦截器
请求拦截器
响应拦截器
12、优化 判断如果没有请求就返回
13、axios响应拦截器
文章列表
/deep/
不加的情况下会在类名后加上属性选择器 如果加上/deep/就不会改变名字
//1. 可能是因为权重优先级
//2. 没有属性 (摸一下)
<style lang='less' scoped>
.layout-container {
.text {
color: blueviolet
}
}
</style>
'>>>'
<style lang='css' scoped>
.layout-container >>> .text{
color: blueviolet!important
}
</style>
1、渲染ui结构
2、导航栏
3、处理汉堡按钮
4、获取展示频道列表
5、channel.js
6、ArticleList组件
7、组件里list数据
8、article.js关于文章获取列表
9、动态加载数据
10、添加加载失败
方案一:抛出错误
方案二:padding-bottom断网
11、下拉刷新
12、刷新成功文本
13、文章列表项组件
14、图片
15、处理相对时间
yarn add dayjs
频道列表-汉堡按钮
1、创建channel分支
2、popup弹出层
3、channel-edit.vue
4、频道渲染
5、加号、删除图标
6、展示我的频道
7、获取所有频道
8、筛选没有的频道
方法一:使用filter、some/filter、find
方法二:import _ from 'lodash'
recommendChannels() {
return _.differenceWith(this.allChannels, this.myChannels, _.isEqual);
}
9、处理高亮
10、添加到我的频道
12、删除我的频道
13、编辑频道
14、数据持久化
15、删除频道到接口
16、添加频道到接口
文章搜索
1、设置路由
2、渲染ui结构
3、封装三个模块
4、判断显示组件
5、失焦隐藏搜索结果
6、联想搜索监听输入框内容变化
immediate:true
7、获取联想建议
8、防抖
yarn add lodash
import { debounce } from "lodash"//局部防抖
handler: debounce(function (val) {
this.loadSearchSuggestion(val)
}, 1000)
9、处理高亮文字
搜索结果
10、处理搜索结果
11、请求数据
12、处理失败
13、固定顶部
14、添加历史记录
15、设置清除事件
16、去重
onSearch(val) {
if(!this.searchText){
return
}
this.searchText = val;
// 方法一
// let index=this.searchHistories.indexOf(val)
// if(index>=0){
// this.searchHistories.splice(index,1)
// }
// this.searchHistories.unshift(val)
// 方法二
// this.searchHistories.unshift(val)
// this.searchHistories=[...new Set(this.searchHistories)]
// 方法三
this.searchHistories.unshift(val)
this.searchHistories=_.uniq(this.searchHistories)
this.isResult = true;
//console.log(this.isResult);
},
onCancel() {
this.$router.back();
},
17、点击删除历史记录
18、持久化
文章详情
1、创建组件
2、设置路由
3、to设置跳转
//在路由里面写props:true this.$route.params.articleId=>this.articleId
console.log(this.$route.params.articleId);
console.log(this.articleId);
4、根据文章id获取对应文章
5、修改样式07笔记
https://github.com/sindresorhus/github-markdown-css/blob/main/github-markdown.css
@click-left="$router.back()"
如果没有图片,可以设置
<meta name="referrer" content="no-referrer" />
6、图片预览
setTimeout(() => {
this.ImagePre();
}, 100);
ImagePre() {
let contentEl = this.$refs.contentRef;
let allImages = contentEl.querySelectorAll("img");
// console.log(allImages);
let images = [];
allImages.forEach((element, index) => {
// console.log(element);
// console.log(index);
images.push(element.src);
// console.log(images);
element.onclick = function () {
ImagePreview({
images,
// 预览图片的起始位置
startPosition: index,
});
};
});
},
7、关注用户
8、删除用户
9、comment-list
10、获取评论列表
11、获取总评论
$event是出现在vue中 如果传了参数$event就是参数 如果没有传参 代表事件
@on-success='totalCount=$event'
12、comment-item
13、喜欢
14、comment-post
15、发布文章评论
16、回复成功文章
17、回复评论
18、评论回复
19、comment-reply
20、获取评论
21、获取评论的评论
22、comment子传父传父传子
23、隐藏评论的评论的回复
24、发布回复
用户页面
1、设置路由
2、渲染用户ui页面
3、获取用户信息
4、修改昵称
5、封装user-name组件
6、设置关闭按钮
7、修改昵称接口(注意传参数)
8、右键确定
9、回显
10、编辑性别
11、v-model包含哪两个指令 v-bind v-on
<div>
<!-- 2、合并写法 -->
<input type="text" v-model="name">
<!-- 3、拆分语法 -->
<br>
<input type="text"
:value="message"
@input="message=$event.target.value">
<input type="checkbox"
:checked='myChecked'
@change="myChecked=$event.target.checked">
</div>
message: this.name,
myChecked:true
11.4、高级用法
11.5、.sync用法
12、改变性别
13、van-picker
14、发起改变请求
15、改变生日
编辑头像
1、隐藏input框 设置头像
2、获取元素
3、添加弹窗
4、添加裁切工具
yarn add cropperjs
import 'cropperjs/dist/cropper.css'
import Cropper from 'cropperjs'
//在mounted里面写入代码 获取cropper实例
const image = this.$refs.img;
this.cropper = new Cropper(image, {
viewMode: 1, // 只能在裁剪的图片范围内移动
dragMode: 'move', // 画布和图片都可以移动
aspectRatio: 1, // 裁剪区默认正方形
autoCropArea: 1, // 自动调整裁剪图片
cropBoxMovable: false, // 禁止裁剪区移动
cropBoxResizable: false, // 禁止裁剪区缩放
background: false // 关闭默认背景
})
在方法里获取裁剪区域
confirm () {
// console.log(this.cropper.getData())
// blob base64 baseUrl this.cropper.getCroppedCanvas().toBlob(blob => {
console.log(blob)
})
}
5、设置请求接口
6、传递数据
// 创建一个空formdata实例:用来传递文件数据
let fd = new FormData();
fd.append("photo", blob);
// 打印fd
// fd.forEach((v,k)=>{
// console.log(k,v);
// })
let res = await updateUserAvatar(fd);//请求
// console.log(res.data.data.photo);
this.$emit("close");
this.$emit("update:img", res.data.data.photo);
token失效问题
token
refresh_token
request.interceptors.response.use(
response => {
// 成功的
return response
},
async error => {
// 对响应错误做点什么
//1 token 问题
// 失效 ? 没有token ?
if (error.response.status === 401) {
//2. 判断 refresh_token 有没有
let user = store.state.user
// 2.1 没有 refresh_token
if (!user.refresh_token) {
// 提示
Toast.fail(error.response.data.message)
// 跳转
router.push('/login')
return
}
// 2.2 有 refresh_token => 换取 新的token
try {
let res = await axios({
method: 'put',
url: 'http://toutiao.itheima.net/v1_0/authorizations',
headers: {
Authorization: `Bearer ${user.refresh_token}`,
},
})
console.log(res.data)
// 3 替换本地的token
store.commit('setUser', {
token: res.data.data.token,
refresh_token: user.refresh_token,
})
//4. 把之前 失效的token `请求` ==> 改为用最新的token `请求` 替代 => 继续请求
return request(error.config)
} catch (error) {
// refresh_token 有值 , 但是过期了, 请求失败
router.push('/login')
}
}
return Promise.reject(error)
}
)