程序员思维导图、web初学者必备、web前端知识集锦-不断更新中...
export function isWechat() { const ua = window.navigator.userAgent.toLocaleLowerCase() return !!ua.match(/MicroMessenger/i) }
/* 键盘弹起处理 ios 键盘弹起,window.innerHeight 不变,body、html 的 clientHeight、offsetHeight 增大,页面整体上移,暂不做特殊处理 android 键盘弹起,window.innerHeight、body、html 的 clientHeight、offsetHeight 均变小,需要将 input 框滚动到可视区 */ const originHeight = document.documentElement.clientHeight || document.body.clientHeight window.addEventListener('resize', () => { const resizeHeight = document.documentElement.clientHeight || document.body.clientHeight // android 键盘弹起 if (originHeight > resizeHeight) { // 将元素滚动到可视区域 const activeElement = document.activeElement const tagName = activeElement.tagName if (tagName === 'INPUT' || tagName === 'TEXTAREA') { activeElement.scrollIntoViewIfNeeded() } } }, false)
1. 父组件滚动时,只需要添加属性overflow: scroll;height: 100vh; 2. 子组件要滚动时,设置overflow: scroll;后还需要确定它的滚动高度,如maxHeight: 580px等等,反正滚动两个条件高度和设置滚动属性,重复3万篇 3. calc 动态高度 当组件无法撑满屏幕时,可以通过calc,如height: calc(100vh - 3.4rem), 这里的3.4rem是已知减去的高度
5.部署rms引发问题思考...
1. 部署文件不生效,首先想到的问题是部署的文件路径对不对,重复3.1万篇... 2. 有没有/需不需要刷新CDN。 3. 代码是否在有错误,打包是否成功。 4. 第一次生效了,其它时间没有生效,很可能是你的文件路径部署错了,不要部署根目录下,如根目录是/c,要部署在具体文件夹下如/c/项目目录。
6.清除sourcetree远程无用的分支
解决方法: 1、进入对应目录下,使用 git remote show origin 命令查看本地仓库追踪远程仓库的状态 2、使用 git remote prune origin 清除所有失效的远程分支或根据提示删除特定失效分支 3、重启 SourceTree 即可。
const itemFunction = function (){console.log("功能保持")} itemFunction.keep = true // 组件方法如: {click:itemFunction }
8. background-repeat: no-repeat; 设置背景时一定要加上这个属性,背景不重复,要不在移动端不同手机会出现怪异的现象
9.grid布局,待更新...
.container { width: 300px; height: 200px; margin: 20px; padding: 20px; border: 1px solid #eee; font-size: 13px; color: #555; } .c-red { color: red; } p { background-color: rgba(189, 227, 255, 0.28); padding: 2px 5px; } /*单行*/ .single { width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; word-break: break-all; } /*多行*/ .mutiple { display: -webkit-box; /*重点,不能用block等其他,将对象作为弹性伸缩盒子模型显示*/ -webkit-box-orient: vertical; /*从上到下垂直排列子元素(设置伸缩盒子的子元素排列方式)*/ -webkit-line-clamp: 4; /*行数,超出三行隐藏且多余的用省略号表示...*/ line-clamp: 4; word-break: break-all; overflow: hidden; max-width: 100%; }
<!-- 单行和多行溢出处理 -->
11. 解决安卓手机字体不加粗
// 设置优先级 font-weight: bold !important
12.实现菜单的展开与收起及modal视图
12.1 展开与收起
部分代码如下: // 1. div 部分 <nav id="navbar"> <div class="logo"> <img src="https://randomuser.me/api/portraits/men/76.jpg" alt="user" /> </div> <ul> <li><a href="#">Home</a></li> <li><a href="#">Portfolio</a></li> <li><a href="#">Blog</a></li> <li><a href="#">Contact Me</a></li> </ul> </nav> <!-- 菜单按钮 --> <button id="toggle" class="toggle"> <!-- 样式 --> <i class="fa fa-bars fa-2x"></i> </button> // 2. 左侧菜单栏,展示的菜单栏,设置为固定,宽度为200px,默认隐藏起来,设置水平偏移-100%,transform: translateX(-100%);也就是隐藏 css部分 :root { --modal-duration: 1s; --primary-color: #30336b; --secondary-color: #be2edd; } nav { background-color: var(--primary-color); border-right: 2px solid rgba(200, 200, 200, 0.1); color: #fff; position: fixed; top: 0; left: 0; width: 200px; height: 100%; z-index: 100; /* 移动-100%,也就是隐藏掉 */ transform: translateX(-100%); } // 重要,这里的css是紧挨着的,不可以有空格,水平向右偏移200px,也就是左侧菜单栏的宽度 body.show-nav { /* Width of nav */ transform: translateX(200px); } // 3. 调用js实现dom的单击事件 // 3.1 先找到菜单div const toggle = document.getElementById('toggle'); // 菜单按钮 const navbar = document.getElementById('navbar'); // navbar // 3.2 实现单击 function closeNavbar (e) { if (document.body.classList.contains('show-nav') && e.target !== toggle && !toggle.contains(e.target) && e.target !== navbar && !navbar.contains(e.target)) { document.body.classList.toggle('show-nav'); document.body.removeEventListener('click', closeNavbar); }else if (!document.body.classList.contains('show-nav')) { document.body.removeEventListener('click', closeNavbar); } } toggle.addEventListener('click', () => { document.body.classList.toggle('show-nav'); // 可以实现左边菜单关闭与打开 document.body.addEventListener('click', closeNavbar); })
展开收起
12.2 modal视图(添加删除css,classList)
13.vue-cli从搭建到优化
https://juejin.cn/post/6844903760917954567#heading-14
14. el-tabel 校验
// 关键代码如下: <el-form :model="form" ref="form" :rules="formRules"> <el-table :data="sccForm.tableList" border> <el-table-column label="测试" width="200" align="center"> <template slot-scope="scope"> <el-form-item :prop="'tableList.' + scope.$index + '.test'" :rules="sccFormRule.scc2Pre"> <input v-model="scope.row.scc2Pre" class="input-card"/> </el-form-item> </template> </el-table-column> </el-table> </el-form> // data data () { return { form: { tableList: [] }, formRules: {test: [{ required: true, message: 'test不能为空', trigger: 'blur' }]} } }
15. dialog 对话框中滚动
// 内容滚动 ::v-deep vue专用 ::v-deep .pub-dialog-class { .el-dialog__body{max-height: 60vh;overflow: auto;} // .el-form-item { // margin-bottom: 15px; // input { // width: 180px; // height: 36px; // border: 1px solid #DCDFE6; // color: #7e7878; // transition: border-color .2s cubic-bezier(.645,.045,.355,1); // border-radius: 4px; // outline: none; // padding: 0 10px; // } // } } this.$nextTick(() => { let container = this.$el.querySelector('.el-table__body-wrapper'); container.scrollTop = container.scrollHeight; })
16. 使用swiper3 及swiper时要注意,使用属性loop:true,不能在dom上使用点击事件,点击的话,会导致第一张滑到最后一张时不能点击
需要在swiper里实现onClick事件
if (this.swiper) { this.swiper.destroy(); this.swiper = null; } this.swiper = new Swiper(this.$refs.swiperDom, { direction: 'vertical', speed: 300, autoplay: 3000, autoplayDisableOnInteraction: false, observer: true, observeParents: true, onClick: (swiper, event) => { this.itemClick(this.swiper.realIndex); }, loop: true }); // css样式调整 .swiper-pagination-bullet { margin: 0 13px; }
17. el-upload 自定义上传,部分关键代码
data () { return { list:[{name: 'cs1', id: '5'}, {name: 'cs1', id: '24'}], selectModule: '', // 选择某列表中一个 selectModuleName: '', // 下载的名称 selectModuleUrl: '' // 下载的地址 } } <div> <span>下载模版:</span> <el-select v-model="selectModule" @change="handleSelectModule" placeholder="请选择下载模板" style="margin-left: 20px; width: 300px; height: 36px;"> <el-option v-for="item in list" :key="item.id" :value="item.id" :label="item.name" /> </el-select> <a :href="selectModuleUrl" :download="selectModuleName">下载</a> </div> handleSelectModule (val) { // 获取模版下载地址 const params = { fileId: val, type: 1 } downLoadExcel(params, '下载地址').then(res => { var reader = new FileReader() reader.readAsText(res.data, 'gbk') const _this = this reader.onload = function (e) { try { var result = JSON.parse(reader.result) console.log(result) _this.$message({ type: 'error', message: '下载模版不存在' }) } catch (err) { const url = window.URL.createObjectURL(res.data) let proName = '' for (let i = 0; i < _this.list.length; i++) { if (_this.list[i].proID === val) { proName = _this.list[i].proName break } } _this.selectModuleUrl = url _this.selectModuleName = `${proName}下载模版名称.xls` } } }).catch(error => { console.log(error) }) }, // 下载方法 import axios from 'axios' function downLoadExcel (params, url) { console.log(params, url) return axios({ baseURL: `${API_ROOT}`, // 下载的基本地址, url是拼接下载地址(后缀), 如 http://www.baidu.com/downUrl withCredentials: true, crossDomain: true, url: url, method: 'post', responseType: 'blob', params: params }) } // 上传 data () { return { size: null, // 文件大小 fileInfo: { name: '', file: undefined }, id: '' dialogVisible: false } } <input ref="fileUpload" type="file" @change="fileUpload($event)" style="width: 0px; height: 0px;visibility: hidden;"/> <span v-if="!(fileInfo.name)" @click="handleCheckFile" class="opItem">请选择上传类型</span> <span class="opItem">{{fileInfo.name}}</span> <div class="dialog-content"> <el-select filterable v-model="selectProject" @change="handleSelectModule" placeholder="请选择上传项目" style="margin-left: 20px; width: 300px; height: 36px;"> <el-option v-for="item in list" :key="item.id" :value="item.id" :label="item.name" /> </el-select> 上传: handleSelectModule () { // 调用下载方法 如上 } handleCheckFile () { this.$refs.fileUpload.click() }, fileUpload (e) { // console.log(e.target.files[0]) // let type = e.target.files[0].name.split('.')[1] this.fileInfo.name = e.target.files[0].name this.fileInfo.file = e.target.files[0] this.size = e.target.files[0].size // this.size = bigSize / 1024; this.$refs.fileUpload.value = null }, uploadList () { if (this.size > 30 * 1024 * 1024) { this.$message({ type: 'error', message: '文件不能超过30M' }) return } this.dialogVisible = false uploadFileAsync({ file: this.fileInfo.file, id: this.id, url: '/xxxx/uploadExcel' }) .then(res => { console.log('---成功操作') }) .catch(error => { this.fileInfo = { name: '', file: undefined } this.$message({ type: 'error', message: error.responseMsg }) }) }, function uploadFileAsync (params) { const { file, id, url } = params var formData = new FormData() formData.append('uploadFile', file) return axios({ baseURL: `${API_ROOT}`, url: url, data: formData, timeout: 600000, method: 'post', headers: { 'Content-Type': 'multipart/form-data' }, withCredentials: true, crossDomain: true, params: { id: id } }).then(res => { return Promise.resolve(res.data) }).catch(error => { console.log(error) return Promise.reject(new Error({ responseCode: '20089', responseMsg: '请求出错了,请稍后再试' })) })
18. encodeURIComponent() 浏览器转码问题要注意,特殊字符如,容易转换失败,传给后端无法解析
19. vue h5 移动端滚动不流畅,不丝滑,可以试试加上下面的css
overflow: auto; -webkit-overflow-scrolling: touch;
20. flex布局设置 ellipsis, css如下
.item { display: flex; align-items: center; .item-text { overflow: hidden; flex: 1; white-space: nowrap; text-overflow: ellipsis; } }
21. textarea的哪些坑
1. 高度自适应,textarea本身是不会自适应的,超过高度会滚动,这里我们就从滚动入手,最好不好用自适应高度
TODO: 目前撤消文字的话高度没有变还没有解决;
TODO: 回显时重置textarea的高度还没有解决,比如说已经发布的文字重新编辑时的回显。
解决方法很简单:就是把它的滚动高度赋值给高度,上源码如下:
// 其实就下面几行代码,是不是很简单 <div> <textarea class="textarea" onpropertychange="style.height=scrollHeight+'px';" oninput="style.height = scrollHeight+'px';"></textarea> </div> 核心代码就这onpropertychange="style.height=scrollHeight+'px';" oninput="style.height = scrollHeight+'px';"
完整代码:这里为了textarea,走了不少弯路,还用div来实现,不过发现了不少坑,换行的坑还没有埋,所以还是使用原生方法,无坑
2. maxlength 算字数限制时,超过2个换行符,字数就失效,Vue实现动态显示textarea剩余文字数量,解决办法如下:
<template> <div id="app"> <div class="discussWrap"> <textarea class="description" @input="descInput" cols="33" rows="8" v-model="des"></textarea> <div class="font-count">{{countNum}}/50</div> </div> </div> </template> methods: { descInput(){ if(this.desc.length>50){ this.desc=this.desc.slice(0, 50) } this.countNum= this.desc.length } }
需要安装vue,然后导入使用
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href=""> <script src="../../node_modules/vue/dist/vue.js"></script> <style> *{ margin: 0; padding: 0; } .input:empty::before{ color:lightgrey; content:attr(placeholder); } .input{ width: 400px; min-height: 40px; outline: 0 none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); } .textarea { resize: none; overflow-y: hidden; width: 100px; min-height:70px; } </style> </head> <body> <!-- contenteditable 换行问题还没有解决 坑1: 换行 坑2: 粘贴有格式的东西,需要去格式,例子中已经解决 --> <!-- <div id="app"> <div>顶面</div> <div> <div class="input" contenteditable placeholder="请输入文字" @keyup="editnameSet($event)"></div> </div> <div>底面</div> <textarea v-model="app" class="input"></textarea> </div> --> <!-- 单击输入框发现也会触发onpropertychange,输入一个值同样也会触发这个事件,这就证明了,只要有属性的值被修改就会触发该事件。 oninput 事件在用户输入时触发。--> <div id="app"> <div>顶部</div> <div> <textarea class="textarea" onpropertychange="style.height=scrollHeight+'px';" oninput="style.height = scrollHeight+'px';"></textarea> </div> <div>底部</div> </div> <script> //兼容处理,统一换行时的元素渲染 // document.execCommand("defaultParagraphSeparator", false, "div"); new Vue ({ el: '#app', data () { return { app: '2233' } }, mounted () { // const input = document.getElementsByClassName('input'); // if (input) { // console.log('----------input', input[0].innerHTML = this.app); // input.target.innerText = this.app; // } // const areas = document.getElementById('textarea'); // areas.addEventListener('input', function() { // this.style.height = this.scrollHeight+'px'; // }, false); }, watch: { app (newval) { const textarea = document.getElementsByClassName('textarea')[0]; console.log('----te', textarea.style); // textarea.style.height = 'auto';// 1. 让 textarea 的高度恢复默认 // textarea.style.height = textarea.scrollHeight + 'px';// 2. textarea.scrollHeight 表示 *textarea* 内容的实际高度 } }, methods: { editnameSet (set) { var re=/<[^>]*>/g; //清空标签的正则 const str = set.target.innerText.replace(re,"") set.target.innerText = str; console.log(); }, changeContent (val) { console.log(val); } } }) </script> </body> </html>
22.backImage和image的使用遇到的问题
1.background-image 使用时只显示颜色没有显示图片,怎么回事,你没有加下面属性,完整版 height: 184px; background-image: url('../../assets/img/announcement/announcement-template-ybtz.png'); background-position: top left; // 位置 background-size: 100%; background-repeat: no-repeat; // 不重复 2. image加载图片方法 第一种: 固定图片src直接添加地址 <img class="nd-img nd-img00" src="../assets/img/no_task_img00.png" alt=""> 第二种: 动态添加图片,这里用到require, item的遍历数组的每一页取的图片名称 <img :src="require(`../assets/img/approve_ing${item.name}.png`)"> 但require 的优化方便,快捷;缺点:会把导入的图片加载成base64,占用空间,加载图片比较慢 3. background-image加载图片方法 <div class="template-img" :class="handleTemplateBgImage(item)"> </div> methods: { handleTemplateBgImage (item) { let tempClass = `bg-img-${item.imgName}`; if (parseInt(this.templateId) === item.templateId) { tempClass = `${tempClass} active`; } return tempClass; }, } css部分: .bg-img-template-ybtz { background-image: url(~@/assets/img/announcement/ybtz.png) !important; } .bg-img-template-xb { background-image: url(~@/assets/img/announcement/xb.png) !important; } .bg-img-template-yjbb { background-image: url(~@/assets/img/announcement/yjbb.png) !important; } .bg-img-template-jctg { background-image: url(~@/assets/img/announcement/jctg.png) !important; } .template-img { width: auto; height: 180px; margin-top: 24px; margin-bottom: 32px; border: 2px solid #ffffff; border-radius: 12px; background-position: top left; background-size: 100%; background-repeat: no-repeat; }
23. filter的使用
filter的使用 1. 创建filter 创建文件夹filters,创建index.js const SelectTemplate = { TEMPLATE_LIST: [ { text: '模版一', imgName: 'xb', templateId: 2 }, { text: '模版二', imgName: 'yjbb', templateId: 3 }, { text: '模版三', imgName: 'template-jctg', templateId: 4 }, { text: '模版四', imgName: 'template-ybtz', templateId: 1 } ] }; export default Template; 或 import { templateBg } from '../utils/enum'; const filters = { // 选择模板背景 filterTemplateBg: value => { return `swiper-slide-${templateBg[parseInt(value)]}`; } }; export default filters; index.js文件 import Template from '../utils/Template'; const tempList = SelectTemplate.TEMPLATE_LIST; export const filterTemplateLabel = value => { const obj = tempList.filter(item => { return item.templateId === parseInt(value); }); if (obj && obj.length) { return obj[0].text; } else { return tempList[tempList.length - 1].text; } }; export const filterTemplateBgColor = value => { const obj = tempList.filter(item => { return item.templateId === parseInt(value); }); let bgColor = ''; if (obj && obj.length) { bgColor = obj[0].imgName; } else { bgColor = tempList[tempList.length - 1].imgName; } return `bg-${bgColor}`; }; 2. 全局引用 main.js中引用 import * as filters from '../platform/filters'; Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]); }); 3. 使用 <div class="input-value">{{ currentTemplateId | filterTemplateLabel }}</div> <div class="announcement-form" :class="currentTemplateId | filterTemplateBgColor"></div>
25. 千分位处理函数
function formatNumber(num) { if (isNaN(num)) { throw new TypeError("num is not a number"); } return ("" + num).replace(/(\d{1,3})(?=(\d{3})+(?:$|\.))/g, "$1,"); }
26. 本地后管访问测试环境数据,使用代理代码如下:
vue.config.js devServer: { proxy: { // proxy all requests starting with /api to jsonplaceholder '/api': { target: 'http://xxx.com.cn/', // 代理接口,这个是测试环境接口 changeOrigin: true, logLevel: 'debug', pathRewrite: { '^/api': '' // 路径中字符替换 } } } }
本地develop设置
27. js forEach循环调用异步方法,如何实现同步,参考文章:https://blog.csdn.net/alex_programmer/article/details/104383843
array.forEach(async (item, index) => { const res = await requestUrl({item.udmp}) if (res && res.responseCode === '000000') { url = (res.data && res.data.url) || '' } ... 逻辑代码 push(url) })
28.resolve拼接路径,一般用于链接跳转或读取生产或测试环境的图片
router.resolve() 的解释和用法 const resolved: { location: Location; route: Route; href: string; } = router.resolve(location, current?, append?) 解析目标位置 (格式和 <router-link> 的 to prop 一样)。 current 是当前默认的路由 (通常你不需要改变它) append 允许你在 current 路由上附加路径 (如同 router-link) 上面的内容为官网中的说明,可以得知其作用是:返回一个完整的路径信息。 location 参数为必填项;current 和 append 为可选参数,暂时没有遇到过相关用法。 应用场景 const routeUrl = this.$router.resolve({ name: 'ListDetail', query: { type: '001', id: id } }) // location.origin 代表ip地址如http:www.baidu.com,location.pathname相当于具体的子页面如zhidao,/zhidao***,routeUrl.href具体的列表详情信息 let url = `${location.origin}${location.pathname}${routeUrl.href}` windows.open(url, '_blank')
29.伪元素动态获取内容,待更新...
30. lang="scss" node-sass,sass-loader的安装
安装失败可能是最新版本问题,可以安装指定版本
cnpm install node-sass --save