npm i报 code E404
1 npm config get registry
2 npm config set registry https://registry.npmjs.org/
3 npm unistall vue-loader -D
4 npm install vue-loader@13.7.2 -D
svn基本使用
1 下载svn及中文语言包
2 附带英文图案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKfgflVk-1623896595753)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210517170441064.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgKLfd96-1623896595756)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210517170448116.png)]
3 获取账号密码
4 输入服务器地址,进行代码拉取
5 右键文件夹,选择导出。也可选择版本或者最新。
6 检出和导出 检出受服务器控制,可进行提交。导出就仅仅是导出,无法提交。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39YrV0LK-1623896595758)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518113440924.png)]
浏览器窗口变化
检测浏览器窗口变化,可以动态改变图片大小。onresize会触发两次,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdmhRjwa-1623896595760)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518093643253.png)]
常用的js中的offsetwidth,clientwidth,innerwidth
<script>
元素视图属性
offsetWidth 水平方向 width + 左右padding + 左右border-width
offsetHeight 垂直方向 height + 上下padding + 上下border-width
clientWidth 水平方向 width + 左右padding
clientHeight 垂直方向 height + 上下padding
offsetTop 获取当前元素到 定位父节点 的top方向的距离
offsetLeft 获取当前元素到 定位父节点 的left方向的距离
scrollWidth 元素内容真实的宽度,内容不超出盒子高度时为盒子的clientWidth
scrollHeight 元素内容真实的高度,内容不超出盒子高度时为盒子的clientHeight
Window视图属性
(低版本IE浏览器[<IE9]不支持) 【自测包含滚动条,但网络教程都说不包含???】
innerWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏)
innerHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏)
Document文档视图
(低版本IE的innerWidth、innerHeight的代替方案)
document.documentElement.clientWidth 浏览器窗口可视区宽度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
document.documentElement.clientHeight 浏览器窗口可视区高度(不包括浏览器控制台、菜单栏、工具栏、滚动条)
document.documentElement.offsetHeight 获取整个文档的高度(包含body的margin)
document.body.offsetHeight 获取整个文档的高度(不包含body的margin)
document.documentElement.scrollTop 返回文档的滚动top方向的距离(当窗口发生滚动时值改变)
document.documentElement.scrollLeft 返回文档的滚动left方向的距离(当窗口发生滚动时值改变)
Document文档视图结束
元素方法
1. getBoundingClientRect() 获取元素到body
bottom: 元素底边(包括border)到可视区最顶部的距离
left: 元素最左边(不包括border)到可视区最左边的距离
right: 元素最右边(包括border)到可视区最左边的距离
top: 元素顶边(不包括border)到可视区最顶部的距离
height: 元素的offsetHeight
width: 元素的offsetWidth
x: 元素左上角的x坐标
y: 元素左上角的y坐标
2. scrollIntoView() 让元素滚动到可视区
元素方法结束
</script>
检测输入的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jQnkr6x8-1623896595762)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518094616872.png)]
document.onkeydown 键盘事件
e.keycode 键盘对应的状态码
e = e || window.event是js在事件处理兼容IE和非IE的写法
加上e = e || window.event ;e存在就用e不存在就用windon.event。
输出e和window.event的结果是一样的。
(window.event == undefined火狐浏览器会出现这个情况。
object.assign() 对象的深拷贝
用法
let obj=Object.assign(target, ...sources) 【target:目标对象】,【souce:源对象(可多个)】
this.$nextTick(()=>{})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ie5PnEBP-1623896595763)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518101726924.png)]
this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中;所以一般用在mounted中对dom节点进行操作。
,在created中获取不到dom节点,但是使用**this.$nextTick(()=>{})**就可以获取到
meta路由元信息做路由跳转验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9l2hqsce-1623896595763)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518110246258.png)]
1 requireAuth为true,表示路由需要验证。最简单,使用的方法
2 判断路径中是否包含某一字段,有弊端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oViqYxG2-1623896595764)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518110848167.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3X0DGjP-1623896595765)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518113604940.png)]
meta的其他属性
router.beforeEach((to, from, next)=>{
if(to.meta.content) { //路由发生变化时候修改meta中的content
let head = document.getElementByTagName(‘head’)
let meta = document.createElemnet('meta')
document.querySelector('meta[name="keywords"]').setAttribute('content',to.meta.content.keywords)
document.querySelector('meta[name="description"]').setAttribute('content',to.meta.content.description)
meta.content = to.meta.content
dead[0].appendChild(meta)
}
if(to.meta.title) { //路由发生变化时候修改页面中的title
document.title = to.meta.title
}
next()
})
~[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PaPLy79n-1623896595765)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518114616553.png)]
获取meta属性[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHodsM3w-1623896595766)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518115732026.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSZi4Fg3-1623896595767)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518144730209.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5cTPGRp-1623896595768)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518144755138.png)]
用法一
通过name属性,为一个页面中不同的router-view渲染不同的组件,如:将上面代码的Hello渲染在 name为Hello的router-view中,将text渲染在name为text的router-view中。不设置name的将为默认的渲染组件。
<template>
<div id="app">
<router-view></router-view>
<router-view name="Hello"></router-view> //将渲染Hello组件
<router-view name="text"></router-view> //将渲染text组件
</div>
</template>
用法二
可以用name传参 使用$route.name获取组件name值
<template>
<div id="app">
<p>{{ $route.name }}</p> //可以获取到渲染进来的组件的name值
<router-view></router-view>
</div>
</template>
用法三
用于pramas传参的引入 pramas必须用name来引入 query可以用name或者path来引入(不明白vue-router传参的可以参考我的另一篇文章vue-router中 query传参和params传参的区别和注意事项)
var router = new VueRouter({
routes: [
{ name:'register', path: '/register/:id/:name', component: register }
]
})
<router-link :to="{name:'register',params:{id:10,name:'lili'}}">注册</router-link>
vue的路由模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeZ90H50-1623896595769)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518111312893.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjNc8SG6-1623896595770)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518144409900.png)]
hash模式和history模式
1、大家都知道vue是一种单页应用,单页应用就是仅在页面初始化的时候加载相应的html/css/js一单页面加载完成,不会因为用户的操作而进行页面的重新加载或者跳转,用javascript动态的变化html的内容
优点: 良好的交互体验,用户不需要刷新页面,页面显示流畅, 良好的前后端工作分离模式,减轻服务器压力,
缺点: 不利于SEO,初次加载耗时比较多
2、hash模式
vue-router默认的是hash模式—使用URL的hash来模拟一个完整的URL,于是当URL改变的时候,页面不会重新加载,也就是单页应用了,当#后面的hash发生变化,不会导致浏览器向服务器发出请求,浏览器不发出请求就不会刷新页面,并且会触发hasChange这个事件,通过监听hash值的变化来实现更新页面部分内容的操作
对于hash模式会创建hashHistory对象,在访问不同的路由的时候,会发生两件事:
HashHistory.push()将新的路由添加到浏览器访问的历史的栈顶,和HasHistory.replace()替换到当前栈顶的路由
3、history模式
主要使用HTML5的pushState()和replaceState()这两个api来实现的,pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改
window.history.pushState(stateObject, title, URL)
window.history.replaceState(stateObject, title, URL)
包括back,forward , go 三个方法
history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward();//前进
区别:
前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误
v-once
只渲染元素和组件一次,随后的渲染,使用了此指令的元素/组件及其所有的子节点,都会当作静态内容并跳过,这可以用于优化更新性能
。
常见用法如下:
当修改input框的值时,使用了v-once指令的p元素不会随之改变,而第二个p元素时可以随之改变的
<div id="app">
<p v-once>{{msg}}</p> //msg不会改变
<p>{{msg}}</p>
<p>
<input type="text" v-model = "msg" name="">
</p>
</div>
<script type="text/javascript">
let vm = new Vue({
el : '#app',
data : {
msg : "hello"
}
});
</script>
怎么防止用户点击统一按钮
1 防抖和节流
2 点击按钮后跳转页面,或者将按钮变为不可选,或者将已填写的数据变为空。
禁止短时间内多次点击
export default {
install(Vue) {
// 禁止短时间内重复点击
Vue.directive('preventClick', {
inserted(button, bind) {
button.addEventListener('click', () => {
if (!button.disabled) {
button.disabled = true;
setTimeout(() => {
but.disabled = false
}, 6000)
}
})
}
})
}
}
wangEditor富文本
toISOString
使用 toISOString() 转换日期时,会丢失时区,这也就导致不论我插入的日期是什么,总会少 8 小时。所以直接加上8个小时
toISOString() 方法可以使用ISO标准将 Date 对象转换为字符串。该标准称为 ISO-8601 ,格式为: YYYY-MM-DDTHH:mm:ss.sssZ
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vh5USFZt-1623896595773)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518175511727.png)]
filters: {
formatDate(value) {
var dateee = new Date(value).toJSON();
var date = new Date(+new Date(dateee) + 8 * 3600 * 1000)
.toISOString()
.replace(/T/g, " ")
.replace(/\.[\d]{3}Z/, "");
return date;
},
},
responseType: “blob”
elementui的tab设置
formatter
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49UVLiyW-1623896595774)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210519133158916.png)]
a标签的下载
检测当前浏览器是否支持download属性:
let canSupportDownload = 'download' in document.createElement('a');
下载一张图片
1. 使用href属性,指定图片地址:
<a href='./img/img01.jpg'>查看图片</a>
这样的情况,我们得到的效果是,页面跳转到指定的url地址。
2. 增加download属性试试:
<a href="./img/img01.jpg" download="dog.jpg">下载图片</a>
.native .sync
.native 当绑定的点击事件不生效时,可以尝试使用。
.sync 用于父子组件 自定义事件 elementui的分页支持该修饰符
//父组件给子组件传入一个函数
<MyFooter :age="age" @setAge="(res)=> age = res">
</MyFooter>
//子组件通过调用这个函数来实现修改父组件的状态。
mounted () {
console.log(this.$emit('setAge',1234567));
}
//父组件将age传给子组件并使用.sync修饰符。
<MyFooter :age.sync="age">
</MyFooter>
//子组件触发事件
mounted () {
console.log(this.$emit('update:age',1234567));
}
update:是被固定的也就是vue为我们约定好的名称部分
vue的过滤器
export default中的name属性
判断是否是nan
let a = isNaN(data)
elementui
elementui样式作用不上去
1 很有可能是scoped的影响,原因未知。
为避免影响其他页面的样式,可以在最外层添加一个大的div,并设置id
2 设置/deep/ 不用去掉scoped 使用层级选择器,只需要给**最外层**
的添加/deep/
3 不使用less或scss,可以使用深度选择器>>>
4 vue支持多个style
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6gMB9lQ-1623896595775)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518103431304.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EkbIao9p-1623896595776)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210518103445669.png)]
elementui常见样式修改
修改分页器按钮颜色 页数和最大条数变化都会触发事件
.block {
.el-pagination {
color: white;
.el-pager {
}
}
}
slot插槽自定义属性,需要在layout进行配置
修改下拉框,输入框颜色,以及下拉箭头位置
.el-select {
height: 32px;
width: 280px;
/deep/ .el-input {
.el-input__suffix {
right: -122px;
}
input {
width: 280px;
height: 32px;
line-height: 32px;
background: radial-gradient(
circle,
rgba(16, 234, 166, 0.26) 0%,
rgba(16, 234, 166, 0.1) 100%
);
opacity: 1;
}
}
}
上传图片失败
注意控件 name 的值
input失焦事件
<el-input
@blur="onBlur"
placeholder="请输入物种名称">
</el-input>
elementui上传图片
1.引入样式,将action改为上传地址
2.注意加name属性
3.调用上传成功的函数,将res的返回结果添加到data中传给后端
elementui表单自带验证
动态绑定rules
设置加载动画
1 给按钮设置提交成功后的加载动画
<el-button
size="small"
v-if="saveBtnShow"
class="camera-btn2"
@click.native="saveDataZzzy"
:loading="loadingButton">保存</el-button>
2 给弹窗设置点击编辑时数据过多引起的数据为空
(1)
加载部分页面
(2)
加载整个页面,在最外面的div添加
<div
v-loading="tckloading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)">
</div>
echarts
echarts样式修改
1.收藏夹有基础配置
修改图标的样式formatter
this.option = {
tooltip: {
trigger: "item",
},
title: {
x: "35%",
y: "38%",
// subtext: "{a|总数量} \n {b|222}",
subtextStyle: {
rich: {
a: {
fontSize: 14,
padding: [5, 55],
},
b: {
color: "white",
padding: [3, 50],
fontSize: 16,
},
},
},
},
legend: {
//图标位置
top: "15%",
right: "5%",
orient: "vertical",
itemGap: 20,
formatter: function (name) {
for (let i = 0; i < data.length; i++) {
if (data[i].Name == name) {
let perStr =
(data[i].Percent * 100).toFixed(2) + "%";
console.log(
name + "\xa0" + "|" + "\xa0" + perStr
);
return (
//"\xa0" 相当于是空格
name +
"\xa0" +
"\xa0" +
"\xa0" +
"\xa0" +
"\xa0" +
"|" +
"\xa0" +
"\xa0" +
"\xa0" +
"\xa0" +
perStr
);
}
}
// return name;
},
},
series: [
{
name: "数据",
type: "pie",
radius: ["40%", "55%"], //调整大小
avoidLabelOverlap: false,
center: ["30%", "50%"], //调整位置
itemStyle: {
normal: {
label: {
show: true, //隐藏标示文字
},
labelLine: {
//引导线长度
show: true,
length: 30,
length2: 60,
maxSurfaceAngle: 80, //隐藏标示线
},
},
},
emphasis: {
label: {
show: true,
fontSize: "30",
fontWeight: "bold",
},
},
data: data.map((item) => {
return {
name: item.Name,
value: item.Sum,
};
}),
},
],
};
效果图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neZ1AS9r-1623896595777)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210610163942271.png)]
wangeditor富文本
解析富文本
//转义
Formatter(row, column) {
console.log("转义", row);
var textstr =
row.NEIRONG.replace(/<[^>]*>|/g, "").substr(0, 15) + "...";
console.log("textstr", textstr);
return textstr;
},
node启动问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IhohosCU-1623896595778)(C:\Users\hky\AppData\Roaming\Typora\typora-user-images\image-20210615165240306.png)]
npm install -g nodemon
无感token刷新
/* 是否有请求正在刷新token */
window.isRefreshing = false
/* 被挂起的请求数组 */
let refreshSubscribers = []
/* push所有请求到数组中 */
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb)
}
/* 刷新请求(refreshSubscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据) */
function onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token))
}
const fetch = axios.create({
baseURL: '',
timeout: '30000'
})
function computedTime() {
let r = getRefresh();
if (!r || !r.expires_in) return false;
let currentTime = Date.parse(new Date()) / 1000;
let expiresTime = r.expires_in;
// 600秒后即将过期,true则不需要刷新
return expiresTime - currentTime <= 600
}
fetch.interceptors.request.use(async (config) => {
if (config.url !== '/oauth/token') {//获取token的接口不进行拦截
getToken() && (config.headers.Authorization = getToken());
if (computedTime()) {
if (!window.isRefreshing) {
window.isRefreshing = true;
let r = getRefresh();
if (!r) return;
let refreshData = {
grant_type: 'refresh_token',
client_id: r.client_id,
client_secret: r.client_secret,
refresh_token: r.refresh_token
}
getTokens(refreshData).then((data) => {
window.isRefreshing = false;
let rData = {
client_id: r.client_id,
client_secret: r.client_secret,
expires_in: (Date.parse(new Date()) / 1000 + data.expires_in),
grant_type: 'refresh_token',
org_id: r.org_id,
refresh_token: r.refresh_token
}
// 存储token,存进cookie里面
store.commit('setTokenInfo', data.token_type + data.access_token);
// 存储refresh_token
store.commit('setRefreshToken', rData);
onRrefreshed(data.token_type + data.access_token);
/* 执行onRefreshed函数后清空数组中保存的请求 */
refreshSubscribers = [];
}).catch(err => {
console.log(err);
router.replace({
path: '/login'
})
})
}
/* 把请求(token)=>{....}都push到一个数组中 */
let retry = new Promise((resolve, reject) => {
/* (token) => {...}这个函数就是回调函数 */
subscribeTokenRefresh((token) => {
config.headers.Authorization = token
/* 将请求挂起 */
resolve(config)
})
})
return retry
} else {
return config
}
} else {
return config
}
}, (error) => {
return Promise.reject(error);
})
a);
onRrefreshed(data.token_type + data.access_token);
/* 执行onRefreshed函数后清空数组中保存的请求 /
refreshSubscribers = [];
}).catch(err => {
console.log(err);
router.replace({
path: ‘/login’
})
})
}
/ 把请求(token)=>{…}都push到一个数组中 /
let retry = new Promise((resolve, reject) => {
/ (token) => {…}这个函数就是回调函数 /
subscribeTokenRefresh((token) => {
config.headers.Authorization = token
/ 将请求挂起 */
resolve(config)
})
})
return retry
} else {
return config
}
} else {
return config
}
}, (error) => {
return Promise.reject(error);
})