精华内容
下载资源
问答
  • 项目中遇到的技术难点
    千次阅读
    2021-09-03 17:49:21

    1. input中type为number时 max-length 失效

      解决:input type="tel"

    2. vue组件中click事件失效

      解决:使用了bette-scroll插件做滚动。加.native也不好使,发现better-scroll的配置中没有设置 click:true,设置过之后click事件成功。

    3. 不同商品页,id变化,商品不变

      监听$route变化,然后获取id重新去取数据。

    4. 修改完数据点击提交后,数据变了 页面数据没有更新

      原因:因为vue的检查机制在进行视图更新时无法监测 数组中的某个对象的属性值的变化。(数据是引用数据类型,数据上没有get set方法)

      方案一:利用 this.set(this.obj,key,val)

      解析:第一个参数目标对象,第二个参数要修改数据下标,第三个参数修改后的新值。

      方案二:也可以使用$nextTick()这个方法

      解析:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM,

      Vue 在更新 DOM时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新!

      方案三:this.$forceUpdate(); //可以直接使用这个是强制更新 但不推荐使用

      调用强制更新方法this.$forceUpdate()会更新视图和数据,触发updated生命周期。

    更多相关内容
  • vue项目中遇到难点面试.pdf
  • 1,在页面布局,使用img图片设置宽高为父元素100%,会出现有默认空隙问题。 解决:1:给img标签添加float:left 2:给img添加display:black

    一,在页面布局中,使用img图片设置宽高为父元素100%,会出现有默认空隙问题。

    解决:
    	1:给img标签添加float:left
    	2:给img添加display:black
    

    二,BFC是什么?解决了什么问题?

    解决:
    	1,BEC全称:Block Flrmatting Context ,翻译为块级格式化上下文,它是css2.1规范定义的,关于css渲染定位的一个概念。
    	2,可以解决因float浮动造成的父元素塌陷问题。
    	3,可以解决div浮动造成的遮盖问题【overflow:hidden;触发BFC来解决遮挡问题】
    	4,可以解决margin塌陷的问题【overflow:hidden;产生BFC来解决】、
    

    三,项目中input输入框获取焦距,会出现虚拟键盘会挡住导航栏的bug。

    解决:
    	可以在获取焦距时使用display:none把导航栏隐藏掉,在失去焦距时使用display:block在显示就可以了。
    

    四,渐进增强和优雅降级之间的区别

    .渐进增强:一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
    .优雅降级:一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
    .其实渐进增强和优雅降级并非什么新概念,只是旧的概念换了一个新的说法。在传统软件开发中,经常会提到向上兼容和向下兼容的概念。渐进增强就是相当于向上兼容,而优雅降级就相当于向下兼容
    

    js操作DOM方法

    1. 查找:getElementsByTagName,getElementsByClassName,getElementById.
    2. 创建元素节点:createElement
    3. 创建文本节点:createTextNode
    4. 赋值节点:cloneNode
    5. 插入节点:appendChild,insertBefore.
    6. 删除节点:removeChild
    7. 替换节点:replaceChild
    8. 设置属性获取属性:setAttribute,getAttribute
    9. 插入HTML结构:innerHTML
    10. 插入TEXT文本:innerTEXT

    js如何获取ul中鼠标点的行号

    	<ul>
    		<li>a</li>
    		<li>b</li>
    		<li>c</li>
    	</ul>
    	<script>
    		let list=document.getElementsByTagName('li')
    		for(let i=0;i<list.length;i++){
    			(function(){
    				list[i].onclick=function(){
    				console.log(i)
    			}
    			})(i)
    		}
    	</script>
    

    使用getElementSByTagName获取列表,循环,函数自执行

    本地图片地址转base64编码

    btoa('D:\Documents\Pictures\Saved Pictures/111.png')
    [https://www.runoob.com/jsref/met-win-btoa.html
    

    (https://www.runoob.com/jsref/met-win-btoa.html)

    设置 letter-spacing 后文字不能居中的解决方法

    text-align: center;
    letter-spacing: 1em;
    text-indent: 1em;
    

    css3动画

    动画使用地址

    animation-fill-mode : none | forwards | backwards | both;
    

    none:不改变默认行为。
    forwards :当动画完成后,保持最后一个属性值(在最后一个关键帧中定义)。
    backwards:在 animation-delay 所指定的一段时间内,在动画显示之前,应用开始属性值(在第一个关键帧中定义)。
    both:向前和向后填充模式都被应用。

    引入gif图片不动问题

    在scr地址栏后添加随机数

    <img src="../../../../assets/subpage/Exchange_moon.gif?`+ Math.random())`" alt="">
    

    css添加伪元素

    			.null_text:after {
                      content: '';                 /*CSS伪类用法*/
                      position: absolute;         /*定位背景横线的位置*/
                      top: 4rem;       /*宽和高做出来的背景横线*/
                      width: 0.02rem;
                      height: .48rem;
                      background:rgba(255,255,255,1);
                      opacity:1;
                      border-radius:1px;
                  }
    

    JavaScript 获取当前时间戳:

    第一种方法:

    var timestamp = Date.parse(new Date());
    

    结果:1280977330000
    第二种方法:

    var timestamp = (new Date()).valueOf();
    

    结果:1280977330748
    第三种方法:

    var timestamp=new Date().getTime();
    

    结果:1280977330748
    第一种:获取的时间戳是把毫秒改成000显示,
    第二种和第三种是获取了当前毫秒的时间戳。
    时间戳与日期格式之间的转换

    将时间戳转换日期格式

    // 简单的一句代码

    var date = new Date(时间戳); //获取一个时间对象
    
    1. 下面是获取时间日期的方法,需要什么样的格式自己拼接起来就好了
    2. 更多好用的方法可以在这查到 -> http://www.w3school.com.cn/jsref/jsref_obj_date.asp
    date.getFullYear(); // 获取完整的年份(4位,1970)
    date.getMonth(); // 获取月份(0-11,0代表1月,用的时候记得加上1)
    date.getDate(); // 获取日(1-31)
    date.getTime(); // 获取时间(从1970.1.1开始的毫秒数)
    date.getHours(); // 获取小时数(0-23)
    date.getMinutes(); // 获取分钟数(0-59)
    date.getSeconds(); // 获取秒数(0-59)
    

    例子

    // 比如需要这样的格式 yyyy-MM-dd hh:mm:ss
    var date = new Date(1522785844000);
    Y = date.getFullYear() + '-';
    M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
    D = date.getDate() + ' ';
    h = date.getHours() + ':';
    m = date.getMinutes() + ':';
    s = date.getSeconds(); 
    console.log(Y+M+D+h+m+s); 
    // 输出结果:2018-04-04 04:04:04
    

    2、将日期格式转换时间戳

    var strtime = '2014-04-23 18:55:49:123';
    var date = new Date(strtime); 
    //传入一个时间格式,如果不传入就是获取现在的时间了,这样做不兼容火狐。
    // 可以这样做
    var date = new Date(strtime.replace(/-/g, '/'));
      
    

    // 有三种方式获取

    time1 = date.getTime();
    time2 = date.valueOf();
    time3 = Date.parse(date);
    

    /*
    三种获取的区别:
    第一、第二种:会精确到毫秒
    第三种:只能精确到秒,毫秒将用0来代替
    比如上面代码输出的结果(一眼就能看出区别):
    1398250549123
    1398250549123
    1398250549000
    */
    3、Date()参数形式有7种

    new Date("month dd,yyyy hh:mm:ss");
    new Date("month dd,yyyy");
    new Date("yyyy/MM/dd hh:mm:ss");
    new Date("yyyy/MM/dd");
    new Date(yyyy,mth,dd,hh,mm,ss);
    new Date(yyyy,mth,dd);
    new Date(ms);
    

    例子

    new Date("September 16,2016 14:15:05");
    new Date("September 16,2016");
    new Date("2016/09/16 14:15:05");
    new Date("2016/09/16");
    new Date(2016,8,16,14,15,5); // 月份从0~11
    new Date(2016,8,16);
    new Date(1474006780);
    

    加载图片出现403的问题

    有时候服务器会做一些限制,这样我们有时候访问图片的话就会出现403的报错。
    但是把图片地址复制出来在地址栏打开的话是可以正常查看图片的。
    解决方案是在head中加一句代码:

    <meta name="referrer" content="no-referrer"/>
    

    这样我们就可以愉快的查看图片了。

    vue播放音频

    在这里插入图片描述

    vue项目页面转图片base64

    转载地址
    1.将整个页面转成图片;
    2.将页面内部分内容转成图片。
    解决方案如下:

    1.引入html2canvas
    为了更便捷有效的开发,这里可以引入html2canvas这个插件,如果读者不想采用这种方案,可以跳过下面内容,自行寻求其他解决方案。
    点击可以查看相关文档:html2canvas官方文档
    引入方式:

    npm install --save html2canvas
    

    或者:

    yarn add html2canvas
    

    2.将html2canvas引入到组件中
    该插件安装完毕后,在你需要使用的vue组件中,按照以下方式,将插件引入:

    import html2canvas from "html2canvas"
    

    到此,基本的环境已经配置完毕,接下来就可以使用了。

    3.将制定区域内转成图片
    首先,你需要让html2canvas获取到你想要转换的节点内容,因此,你需要添加ref标记。
    示例如下:

    <div class="container" ref="imageDom"></div>
    

    imageDom需要是你想转换的页面内容的父容器,即你想转换的页面内容需要全部包含在imageDom节点内。
    转换方法如下:

    /**
     * 将页面指定节点内容转为图片
     * 1.拿到想要转换为图片的内容节点DOM;
     * 2.转换,拿到转换后的canvas
     * 3.转换为图片
     */
    
      clickGeneratePicture() {
          html2canvas(this.$refs.imageDom).then(canvas => {
            // 转成图片,生成图片地址
            this.imgUrl = canvas.toDataURL("image/png");
          });
        }
    

    官方示例如下:

    html2canvas(document.querySelector("#capture")).then(canvas => {
        document.body.appendChild(canvas)
    });
    

    返回的canvas参数就是一个生成好的canvas元素,如果你想将他转成图片,直接使用toDataURL方法即可,将转换的图片地址赋值给你想显示的图片元素,就可以在页面上看到转换后的图片。

    问题1:微信浏览器不能直接下载生成后的图片。
    在chrome等浏览器下可以使用如下方法,将生成的图片直接下载下来:

    // 创建隐藏的可下载链接

    var eleLink = document.createElement(“a”);
    eleLink.href = imgUrl; // 转换后的图片地址
    eleLink.download = “pictureName”;
    // 触发点击
    document.body.appendChild(eleLink);
    eleLink.click();
    // 然后移除
    document.body.removeChild(eleLink);

    但是微信浏览器禁用了下载链接,你只能采用引导的方式,引导用户将页面内容转成图片显示出来,用户长按显示的图片即可保存到本地。

    判断安卓或ios微信

    var browser = {
        versions: function () {
            var u = navigator.userAgent, app = navigator.appVersion;
            return {
                trident: u.indexOf('Trident') > -1, //IE内核
                presto: u.indexOf('Presto') > -1, //opera内核
                webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
                gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
                mobile: !!u.match(/AppleWebKit.*Mobile.*/) || !!u.match(/AppleWebKit/), //是否为移动终端
                ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
                android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器
                iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者QQHD浏览器
                iPad: u.indexOf('iPad') > -1, //是否iPad
                webApp: u.indexOf('Safari') == -1 //是否web应该程序,没有头部与底部
            };
        }(),
        language: (navigator.browserLanguage || navigator.language).toLowerCase()
    }
    
    if (browser.versions.ios || browser.versions.iPhone || browser.versions.iPad) {
        //如果是ios系统
    }
    else if (browser.versions.android) {
        //如果是Android系统 就
    }
    var ua = navigator.userAgent.toLowerCase();
    if (ua.match(/MicroMessenger/i) == "micromessenger") {
        console.log('这是微信')
    } else {
    
    }
    

    在线图片压缩地址

    https://tinypng.com/

    在线二维码生成地址

    https://cli.im/

    去掉button按钮点击默认篮边

    outline: none;
    

    去掉滑动条默认条边

    		.box::-webkit-scrollbar {
                display: none;
            }
    

    去掉超链接a标签的下划线

    text-decoration: none;
    

    去掉titile显示内容

    js开头直接添加

    document.title='\u200E';
    

    在线图片转base64码或反转

    http://www.atool9.com/img2base64.php

    判断客户端ios或安卓,pc,微信

    https://blog.csdn.net/zfz5720/article/details/79989361

    vue-awesome-swiper中动画不执行问题

    在vue页面的swiper上加ref
    
     <swiper id="swiperBox" v-bind:options="swiperOption" ref="mySwiper">
    
    每一个swiper页面加判断当前页数
    
    <!-- 第一页 -->
            <swiper-slide class="swiper-slide1">
            <div class="page">
                <pageRecallTime v-bind:lists="lists" :firstAcRun='mySwiperIndex>=0'></pageRecallTime>
            </div>
            </swiper-slide>
            <!-- 第二页 -->
    
            <swiper-slide class="swiper-slide2">
            <div class="page">
              <!-- {{mySwiperIndex}} -->
                <pageRecallMonth v-bind:lists="lists" :firstAcRun='mySwiperIndex>=1'></pageRecallMonth>
            </div>
            </swiper-slide>
    
    js代码在swiper控制中加入
    
    on: {
                slideChange: () => {
                  let swiper = this.$refs.mySwiper.swiper
                  // console.log(swiper.activeIndex, JSON.parse(this.lists.firstFeed).feed_list)
                  if (swiper.activeIndex === 1 && !JSON.parse(this.lists.firstFeed).feed_list) {
                    // console.log(1)
                    let i = swiper.activeIndex
                    if (this.mySwiperIndex < swiper.activeIndex) {
                      i++
                    } else {
                      i--
                    }
                    swiper.slideTo(i, 0, false)
                  }
                  if (swiper.activeIndex === 4 && ! (this.lists.mostFeed && JSON.parse(this.lists.mostFeed).feed_list) && (this.lists.lastFeed && !JSON.parse(this.lists.lastFeed).feed_list)) {
                    // console.log(1)
                    let i = swiper.activeIndex
                    if (this.mySwiperIndex < swiper.activeIndex) {
                      i++
                    } else {
                      i--
                    }
                    swiper.slideTo(i, 0, false)
                  }
                  // console.log(swiper.activeIndex, this.lists.friendShare.head || this.lists.friendLike.head || this.lists.friendComment.head)
                  if (swiper.activeIndex === 5 && !(this.lists.friendShare.head || this.lists.friendLike.head || this.lists.friendComment.head)) {
                    let i = swiper.activeIndex
                    if (this.mySwiperIndex < swiper.activeIndex) {
                      i++
                    } else {
                      i--
                    }
                    swiper.slideTo(i, 0, false)
                  }
                  this.mySwiperIndex = swiper.activeIndex
                }
              }
    
    这个时候会出现底下的swiper覆盖上面的swier控制css样式
    
    .flyback{
      position: fixed;
      left: 0;
      top: 0;
    }
    .box{
      width:100vw;
      height:100vh;
      position: relative;
      overflow: hidden;
    }
    .fade-enter,
    .fade-leave-to {
      opacity: 0;
    }
    .swiper-slide {
      width:100%;
      height:100%;
    }
    
    .swiper-slide-active{
      position: relative;
      z-index: 111;
    }
    
    展开全文
  • 技术总结(自己做项目遇到的问题整理)java 技术总结(自己做项目遇到的问题整理)java
  • vue后台项目中遇到技术难点以及解决方案

    万次阅读 多人点赞 2020-06-09 08:07:08
    在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很多时候都会被问到 在这个项目中你有遇到什么技术难点,你是怎么解决的? 其实这个问题旨在了解你在遇到...

    作者:yeyan1996

    https://juejin.im/post/5c76843af265da2ddd4a6dd0

    写在前面

    马上到了金三银四的时间,很多公司开启了今年第一轮招聘的热潮,虽说今年是互联网的寒冬,但是只要对技术始终抱有热情以及有过硬的实力,即使是寒冬也不会阻挠你前进的步伐。在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很多时候都会被问到

    在这个项目中你有遇到什么技术难点,你是怎么解决的?

    其实这个问题旨在了解你在遇到问题的时候的解决方法,毕竟现在前端技术领域广,各种框架和组件库层出不穷,而业务需求上有时纷繁复杂,观察一个程序员在面对未知问题时是如何处理的,这个过程相对于只出一些面试题来考面试者更能了解面试者实际解决问题的能力

    而很多人会说我的项目不大,并没有什么难点,或者说并不算难点,只能说是一些坑,只要google一下就能解决,实在不行请教我同事,这些问题并没有困扰我很久。其实我也遇到过相同的情况,和面试官说如何通过搜索引擎解决这些坑的吧不太好,让面试官认为你只是一个API Caller,但是又没有什么值得一谈的项目难点

    我的建议是,如果没有什么可以深聊的技术难点,不妨在日常开发过程中,试着封装几个常用的组件,同时尝试分析项目的性能瓶颈,寻找一些优化的方案,同样也能让面试官对你有一个整体的了解

    在这篇文章中,我会分享在我目前公司的项目里,是如何在满足业务需求的基础上,让整个系统焕然一新的过程

    技术栈是Vue + Element的单页面应用

    起源

    在我刚入职的那会,编码能力不怎么好,加上之前离职的前端技术栈是React,接手这个Vue项目的时候,代码高度的耦合,而那个时候因为能力有限,也只是在他的基础上继续开发,好在接手的时候开发进度也只是刚刚开始,因此在几个月后的某一天,我做了一个决定:准备把整个项目重写

    得益于整个后台管理系统都是我独立开发的,项目的不足点我都深有体会,并且修改的时候能够更加的自由,恰好在那段时间看了花裤衩的vue-element-admin,我决定新开一个工程把之前的代码全部重写

    项目结构

    之前我有打算基于Webpack4自己写个脚手架用来打包文件,但是那段时间刚好Vue-cli3刚刚发布正式版并且也是基于Webpack4封装的,于是想了一下还决定使用新的Vue-cli3脚手架搭建,最后我将项目分为以下层级

    ├─api                                 //api接口
    ├─assets                              //项目运行时使用到的图片和静态资源
    ├─components                          //组件
    │  ├─BaseEllipsis                     //业务组件 (Base开头都是全局组件)
    │  ├─BasePagination                   //分页器组件   
    │  ├─BaseIcon                         //svg图标组件
    │  ├─BaseToggle                       //业务组件
    │  ├─BaseTable                        //表格组件
    │  ├─FormPanel                        //业务组件(Form开头是围绕表单相关的小组件)
    │  ├─TableOptions                     //业务组件(Table开头是围绕表格相关的小组件)
    │  ├─TheBreadcrumb                    //面包屑组件(The开头是每个页面组件只会引入一次的无状态组件)
    |  ├─TheSidebar                       //侧边栏组件
    │  ├─TransitionSildeDown              //业务组件(Transition开头是动画组件)
    │  └─index.js                         //全局组件自动注册的脚本
    │  
    ├─directives                          //自定义指令
    ├─element                             //elementui
    ├─errorLog                            //错误捕获
    ├─filters                             //全局过滤器
    ├─icons                               //svg图标存放文件夹
    ├─interface                           //TypeScript接口
    ├─mixins                              //局部混入
    ├─router                              //vue-router
    │  ├─modules                      
    │  └─index.js           
    ├─store                                //vuex
    │  ├─modules                      
    │  └─index.js                      
    ├─style                                //全局样式/局部页面可复用的样式
    ├─util                                //公共的模块(axios,cookie封装,工具函数)
    ├─vendor                              //类库文件
    └─views                               //页面组件(所有给用户显示的页面)
    

    一个良好的项目分层在业务迭代的时候能够快速找到对应的模块进行修改,而不是在茫茫的代码海中找到其中的某一行代码

    性能优化

    在我重写整个系统之前,每次打包都会花费好几分钟的时间,并且打包后的项目超过了17M

    然而在我优化系统之后,打包后的体积只有2M,缩小了8倍

    这里我从以下4个方面分享一下我在项目中是如何改善系统的性能,让系统"步履如飞"的

    • 网络请求相关

    • 构建相关

    • 静态资源优化

    • 编码相关

    网络请求相关

    这部分旨在实现需求的前提下尽量减少http请求的开销,或者减少响应时间

    CDN

    将第三方的类库放到CDN上,能够大幅度减少生产环境中的项目体积,另外CDN能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上

    另外因为CDN和服务器的域名一般不是同一个,可以缓解同一域名并发http请求的数量限制,有效分流以及减少多余的cookie的发送(CDN上面的静态资源请求时不需要携带任何cookie)

    通俗的来说就是使用CDN会一定程度上提升项目中的静态文件的传输速度,在vue-cli3中可以通过externals配置项,将第三方的类库的引用地址从本地指向你提供的CDN地址

    externals只适用于ES Module的默认导入

    这里通过环境变量来判断生产环境才启用CDN,除了需要开启CDN外,你还需要在index.html注入CDN的域名,所以我这里通过html-webpack-plugin根据cdn域名动态的注入script标签,同时需要在index.html中通过模版的语法声明循环的数组和注入的元素

    打包前的index.html:

    打包后的index.html:

    可以看到通过这个插件可以将cdn域名动态的注入到打包后的index.html中

    还有一点要注意的是,externals对象的属性为你引入包的名字,而属性值是对应的全局变量名称(CDN引入的类库文件会自动挂载到window对象下面,而挂载时的属性名需要去对应的CDN在源码中寻找,一般在开头行都会有声明,除此之外导入还有困难的还可以看下这篇博客webpack externals 深入理解)

    这里还是建议尽量放到公司专用的CDN上,不推荐使用公共的CDN,因为容易挂,生产环境还是以稳定为主吧

    合理的缓存策略

    将长时间不会改变的第三方类库或者静态资源设置为强缓存,将max-age设置为一个非常长的时间,再将访问路径加上哈希达到哈希值变了以后保证获取到最新资源(vue-cli3会自动构建,自己搭建的webpack脚手架需要自行配置contentHash,chunkHash)

    CDN上的缓存策略,可以看到max-age的值非常大,这样下次访问就只会读取本地磁盘/内存中缓存的资源:

    对于index.html和一些图片等多媒体资源,可以选择协商缓存(max-age<=0,Last-Modified,ETag),保证返回服务器最新的资源

    一个好的缓存策略,有助于减轻服务器的压力,并且显著的提升用户的体验

    gzip

    为你的文件开启gzip压缩是一个不错的选择,通常开启gzip压缩能够有效的缩小传输资源的大小,如果你的项目是用nginx作为web服务器的话,只需在nginx的配置文件中配置相应的gzip选项就可以让你的静态资源服务器开启gzip压缩

        #开启和关闭gzip模式
        gzip on;
        #gizp压缩起点,文件大于1k才进行压缩
        gzip_min_length 1k;
        # gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
        gzip_comp_level 6;
        # 进行压缩的文件类型。
        gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;
        #nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
        gzip_static on
        # 是否在http header中添加Vary: Accept-Encoding,建议开启
        gzip_vary on;
        # 设置gzip压缩针对的HTTP协议版本
        gzip_http_version 1.1;
    

    但是我们这里要说的是前端输出gzip文件,利用compression-webpack-plugin让webpack在打包的时候输出.gz后缀的压缩文件

    这样不需要服务器主动压缩我们就已经可以得到gzip文件,在上面的nginx配置项中可以发现这一行

     #nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
        gzip_static on
    

    只要把.gz的文件放到服务器上,开始gzip_static就可以让服务器优先返回.gz文件,在面对高流量时,也能一定程度减轻对服务器的压力,属于用空间来换时间(.gz文件会额外占有服务器的空间)

    资源嗅探

    对于现代浏览器来说,可以给link标签添加preload,prefetch,dns-prefetch属性

    preload

    对于SPA应用来说,当浏览器解析完script脚本才会生成DOM节点,如果你的项目中没有使用服务端渲染的话且需要加载一个比较耗时的首屏图片时,可以考虑将这个首屏图片放在preload标签中让浏览器预先请求并加载执行,这样当script脚本执行完毕后就会瞬间加载图片(否则需要等脚本执行完毕后再向后台请求图片)

    另外使用preload预加载首屏需要的css样式也是一个不错的选择,类似的库有critical

    没有使用preload

    使用preload

    通过Waterfall可以看到这个webp图片需要等到脚本加载完之后才回去请求,如果这个图片比较大就会浪费不必要的时间

    在工程中,利用一些preload的webpack插件可以很方便的给打包后的index.html注入预加载的资源标签,有兴趣的朋友可以试着搜索一下相关的插件

    prefetch

    prefetch可以让浏览器提前加载下个页面可能会需要的资源,vue-cli3默认会给所有懒加载的路由添加prefetch属性,这样可以在你访问使用到懒加载的路由页面时能够获得更快的加载速度

    preload和prefetch的区别在于,preload的资源会和页面需要的静态资源并行加载,而prefetch则会等到浏览器加载完必要的资源后,在空闲时间加载被标记为prefetch的资源

    dns-prefetch

    dns-prefetch可以让浏览器提前对域名进行解析,减少DNS查找的开销,如果你的静态资源和后端接口不是同一个服务器的话,可以将考虑你后端的域名放入link标签加入dns-prefetch属性

    京东首页也使用到了dns-prefetch技术

    http2

    http2从2015年问世以来已经走过了4个年头,如今在国内也有超过50%的覆盖率,得益于http2的分帧传输,它能够极大的减少http(s)请求开销

    http2和http1.1的性能差异对比

    如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同一域名的tcp连接数量是有限制的(chrome为6个)超过规定数量的tcp连接,则必须要等到之前的请求收到响应后才能继续发送,而http2则可以在一个tcp连接中并发多个请求没有限制,在一些网络较差的环境开启http2性能提升尤为明显

    这里极力推荐在支持https协议的服务器中使用http2协议,可以通过web服务器Nginx配置,或是直接让服务器支持http2

    nginx开启http2非常简单,在nginx.conf中只需在原来监听的端口后面加上http2就可以了,前提是你的nginx版本不能低于1.95,并且已经开启了https

    listen 443 ssl http2;
    

    在network中通过protocol可以查看到当前的资源是通过哪个版本的http协议传输的

    h2代表http2

    构建相关

    构建方面通过合理的配置构建工具,达到减少生产环境的代码的体积,减少打包时间,缩短页面加载时间

    路由懒加载

    传统的路由组件是通过import静态的打包到项目中,这样做的缺点是因为所有的页面组件都打包在同一个脚本文件中,导致生产环境下首屏因为加载的代码量太多会有明显的卡顿(白屏)

    通过import()使得ES6的模块有了动态加载的能力,让url匹配到相应的路径时,会动态加载页面组件,这样首屏的代码量会大幅减少,webpack会把动态加载的页面组件分离成单独的一个chunk.js文件

    当然懒加载也有缺点,就是会额外的增加一个http请求,如果项目非常小的话可以考虑不使用路由懒加载

    预渲染

    由于浏览器在渲染出页面之前,需要先加载和解析相应的html,css和js文件,为此会有一段白屏的时间,如何尽可能的减少白屏对用户的影响,目前我选择的是在html模版中,注入一个loading动画,这里我拿D2-Admin中的loading动画举例

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>icon.ico">
        <title><%= VUE_APP_TITLE %></title>
        <style>
          html, body, #app { height: 100%; margin: 0px; padding: 0px; }
          .d2-home { background-color: #303133; height: 100%; display: flex; flex-direction: column; }
          .d2-home__main { user-select: none; width: 100%; flex-grow: 1; display: flex; justify-content: center; align-items: center; flex-direction: column; }
          .d2-home__footer { width: 100%; flex-grow: 0; text-align: center; padding: 1em 0; }
          .d2-home__footer > a { font-size: 12px; color: #ABABAB; text-decoration: none; }
          .d2-home__loading { height: 32px; width: 32px; margin-bottom: 20px; }
          .d2-home__title { color: #FFF; font-size: 14px; margin-bottom: 10px; }
          .d2-home__sub-title { color: #ABABAB; font-size: 12px; }
        </style>
      </head>
      <body>
        <noscript>
          <strong>
            很抱歉,如果没有 JavaScript 支持,D2Admin 将不能正常工作。请启用浏览器的 JavaScript 然后继续。
          </strong>
        </noscript>
        <div id="app">
          <div class="d2-home">
            <div class="d2-home__main">
              <img
                class="d2-home__loading"
                src="./image/loading/loading-spin.svg"
                alt="loading">
              <div class="d2-home__title">
                正在加载资源
              </div>
              <div class="d2-home__sub-title">
                初次加载资源可能需要较多时间 请耐心等待
              </div>
            </div>
            <div class="d2-home__footer">
              <a
                href="https://github.com/d2-projects/d2-admin"
                target="_blank">
                https://github.com/d2-projects/d2-admin
              </a>
            </div>
          </div>
        </div>
      </body>
    </html>
    

    在打包完成后,在这个index.html下方还会注入页面的脚本,当用户访问你的项目时,脚本还没有执行,但是可以显示loading动画,因为它是直接注入在html中的,等到脚本执行完毕后,Vue会新生成一个app的节点然后将旧的同名节点删除,这样可以有效的过渡白屏的时间

    loading动画只是一个让用户感知到你程序正在启动的效果,只是一个静态页面没有任何的功能

    另外预渲染还可以使用服务端渲染(SSR),通过后端输出一个首页的模版,或者使用骨架屏的方案,这里本人没有深入的了解过,有兴趣的朋友可以去实践一下

    升级到最新的webpack版本

    webpack4相对于webpack3来说在打包优化方面性能提升还是比较明显的,如果觉得自己配置脚手架比较复杂的话,可以使用vue-cli3来构建你的项目,同样是基于webpack4搭建的

    DllPlugin

    当没有一个稳定的CDN时,使用DllPlugin这个webpack插件同样可以将类库从业务代码中分离出去,其原理是预先将类库文件打包,随后创建一个关联表,在业务代码依赖第三方类库时,会直接去关联表中获取已经打包好的类库文件。这样做的好处在于因为业务代码会常常需要打包上线,而第三方类库基本不会改变,所以每次打包可以跳过这些类库文件,减少不必要的重复打包

    DllPlugin是一个webpack内置的插件,无需安装,但是要让打包后的index.html注入这些打包后第三方类库,需要额外安装add-asset-html-webpack-plugin插件

    当你需要在index.html中注入多个类库时,需要实例化多次add-asset-html-webpack-plugin,这里我们可以利用nodejs的fs模块,遍历DllPlugin打包后的目录,根据类库的数量决定需要生成多少个实例,非常的灵活,具体的配置项可以查看我底部的链接

    合理使用第三方库

    如果项目中有一些日期操作的需求,不妨将目光从moment转移到day,相对于笨重的moment,它只有2kb,day和moment的api完全一样,并且中文文档也比较友好

    另外对于lodash这类的库如果只需要部分功能,则只要引入其中一部分,这样webpack在treeshaking后在生产环境也只会引入这一部分的代码

    对于UI库(element-ui)打包后的体积也会非常大,尽量使用按需加载,官方文档上也有详细教程

    element-ui的压缩后的体积竟然是Vue的十倍

    常用的路径创建文件别名

    给常用的模块路径创建一个别名是一个不错的选择,可以减少模块查找时耗费的时间,项目越大收益也就越明显

    vue-cli3中的配置和使用方法(webpack链式调用文档)

    使用可视化工具分析打包后的模块体积

    我通过webpack-bundle-analyzer这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化

    这是我在优化前的各模块体积:

    因为业务需求,要求前端导出pdf和excel文件,我这里引入了xlsx和pdf.js这2个包,但是打包后通过可视化工具发现光着2个文件就占了一半的项目体积,另外elementui和moment也非常的大

    这是优化后通过可视化工具观察到的各模块体积,通过将这些类库放到CDN上或者使用dllPlugin将类库和业务文件分离,可以看到没有明显特别大的模块了

    静态资源优化

    这部分旨在减少请求一些图片资源所造成的影响

    图片懒加载

    如果你的系统是一个偏展示的项目需要给用户展示大量图片,是否启用图片懒加载可能是你需要考虑的一个点,不在用户视野中的图片是没有必要加载的,图片懒加载通过让图片先加载成一张统一的图片,再给进入用户视野的图片替换真正的图片地址,可以同一时间减少http请求开销,避免显示图片导致的画面抖动,提高用户体验

    下面我提供2种图片懒加载的思路,这2个方案最终都是用将占位的图片替换成真正的图片,然后给img标签设置一个自定义属性src存放真正的图片地址,src存放占位图片的地址

    getBoundingClientRect

    DOM元素包含一个getBoundingClientRect方法,执行该方法返回当前DOM节点相关的CSS边框集合

    其中有一个top属性代表当前DOM节点距离浏览器窗口顶部的高度,只需判断top值是否小于当前浏览器窗口的高度(window.innerHeight),若小于说明已经进入用户视野,然后替换为真正的图片即可

    另外使用getBoundingClientRect作图片懒加载需要注意3点

    1. 因为需要监听scroll事件,不停的判断top的值和浏览器高度的关系,请对监听事件进行函数节流

    2. 当屏幕首次渲染时,不会触发scroll事件,请主动调用一次事件处理程序,否则若用户不滚动则首屏的图片会一直使用懒加载的默认图片

    3. 当所有需要懒加载的图片都被加载完,需要移除事件监听,避免不必要的内存占用

    InterpObserver

    InterpObserver作为一个构造函数,传入一个回调函数作为参数,生成一个实例observer,这个实例有一个observe方法用来观察指定元素是否进入了用户的可视范围,随即触发传入构造函数中的回调函数

    同时给回调函数传入一个entries的参数,记录着这个实例观察的所有元素的一些阈值信息(对象),其中interpRatio属性表示图片进入可视范围的百分比,大于0表示已经有部分进入了用户视野

    此时替换为真实的图片,并且调用实例的unobserve将这个img元素从这个实例的观察列表的去除

    实例代码

    对懒加载还有迷惑的同学我这里写了一个DEMO可以参考一下实现的方式源代码

    结论

    这2种的区别在于监听的方式,我个人更推荐使用Interp Observer,因为通过监听scroll事件开销比较大,而让将这个工作交给另一个线程异步的去监听开销会小很多,但是它的缺点是一些老版本的浏览器可能支持率不高,好在社区有polyfill的方案

    或者可以直接使用第三方的组件库vue-lazyload

    使用svg图标

    相对于用一张图片来表示图标,svg拥有更好的图片质量,体积更小,并且不需要开启额外的http请求,svg是一个未来的趋势,阿里的图标库iconfont支持导出svg格式的图标,但是在项目中需要封装一个支持svg的组件,具体封装的教程可以参考花裤衩的文章这里就不多赘述了手摸手,带你优雅的使用 icon,或者可以参考我的github

    使用webp图片

    webp图片最初在2010年发布,目标是减少文件大小,但达到和JPEG格式相同的图片质量,希望能够减少图片档在网络上的发送时间。webp图片无损比png图片无损的平均体积要小 20%~40%,并且图片质量用肉眼看几乎没什么差别

    webp图片的缺点是兼容性并不是那么的好,在can l use 上查到webp图片的支持率并不是那么的理想。但是我们仍可以在支持webp图片的浏览器中使用它,而在不支持的浏览器提供png图片

    这里需要使用到响应式图片,HTML提供了picture标签让我们可以在不同设备中使用不同的图片格式

    MDN:

    HTML元素通过包含零或多个元素和一个  元素来为不同的显示/设备场景提供图像版本。浏览器会选择最匹配的子元素,如果没有匹配的,就选择  元素的 src 属性中的URL。然后,所选图像呈现在元素占据的空间中。

    在工程中我们可以这样使用

    picture标签包裹2个source标签,一个提供webp图片,通过srcset属性让浏览器从上到下选择可以支持的图片格式,如果浏览器不支持webp图片会只使用第二个source,会回退到png图片,如果浏览器不支持picture标签,会使用底部的img标签,同样也会生成一个png图片

    picture标签的浏览器支持率,相对于webp要好很多(注意底部的img标签无论如何都要有,否则就算支持webp图片也无法渲染出图片)

    压缩图片

    对于一些png图片可能质量会非常的高,但是对于Web平台来说,用户可能并不care图片的画质问题,但是如果加载图片导致页面出现卡顿那就显得得不偿失了,我们可以考虑将一些画质较高的图片做压缩处理,我这里使用tinypng帮我压缩图片,同样能够保证在肉眼几乎分辨不出区别的情况下,提供一个体积较小的图片,如果有其他好的压缩软件也可以推荐给我

    另外有一个图片压缩的 loader  image-webpack-loader,在用户肉眼分辨不清的情况下一定程度上压缩图片

    编码相关

    编码这方面主要是减少对DOM的访问,减少浏览器的重排/重绘,访问DOM是非常昂贵的操作,因为会涉及到2个不同的线程交互(JS线程和UI渲染线程)并且DOM本身又是一个非常笨重的对象,这里给出几个建议

    • 如果有需要动态创建DOM的需求,可以创建一个文档碎片(DocumentFragment),在文档碎片中操作因为不是在当前文档流不会引起重排/重绘,最后再一次性插入DOM节点

    • 避免频繁获取视图信息(getBoundingClientRect,clientWidth,offsetWidth),当发生重排/重绘操作时浏览器会维护一个队列,等到超过了最大值或过了指定时间(1000ms/60 = 16.6ms)才会去清空队列一次性执行操作,这样可以节省性能,而获取视图信息会立刻清空队列执行重排/重绘

    • 高频的监听事件使用函数防抖/节流(可以使用lodash库的throttle函数,但是推荐先搞懂原理)

    • 特效可以考虑单独触发渲染层(CSS3的transform会触发渲染层),动画可以使用绝对定位脱离文档流

    开发过程中小技巧

    使用require.context这个webpack的api可以避免每次引入一个文件都需要显式的用import导入,它可以扫描你指定的文件,然后全部导入到指定文件,可以用在

    • vue-router的路由自动导入

    • vuex的模块自动导入

    • svg图标的自动导入

    • 全局组件的自动导入

    vuex:

    这样在创建一个新的模块时,不需要在index.js中用import引入,在modules中再声明一遍了

    全局组件和svg图标:

    避免了每创建一个全局组件都需要引入,在调用一次Vue.component的过程,而当加载到Svg组件会自动扫描icons文件夹,将所有svg图标导入进来

    展开全文
  • 相信很多人都有类似的经历,在面试快要结束的时候经常会被问到一个问题:讲讲项目中技术难点? 这是一个比较开放的问题,首先它没有固定的答案,因为每个人做过的项目不同,使用的框架不同,对应的架构不同,自然...

    原创:猿天地(微信公众号 ID:cxytiandi),欢迎分享,转载请保留出处。

    相信很多人都有类似的经历,在面试快要结束的时候经常会被问到一个问题:讲讲项目中的技术难点?

    这是一个比较开放的问题,首先它没有固定的答案,因为每个人做过的项目不同,使用的框架不同,对应的架构不同,自然遇到的技术难点也不同。

    1. 一定要真实

    在回答这个问题的时候,一定要仔细想想之前真实遇到的问题,不要随便编一个,这样很容易出问题,因为面试官会顺着细节一层层的问下去,如果你是编出来的,到最后就圆不回去了。

    举个例子:

    求职者说我们下单的接口最开始只能支持几百的 TPS,被我优化后 TPS 破万了,只要你说完这句话面试官就开始进入继续追问细节了。

    • 破万具体是多少的 TPS?
    • 有多少台机器?
    • 机器分别是什么配置?
    • 数据库是什么配置?
    • 你们是怎么进行压测的?
    • 下单链路跟多少个服务进行了交互?
    • 每个服务的耗时多久?
    • 如何进行优化的?
    • 如何发现接口中的性能瓶颈?

    你只有抗住了这一系列的连环炮追问,而且面试官通过你的描述和你说的指标进行对比,如果比较匹配那么你就过关了。如果不匹配,肯定就面失败了。

    2. 技术层面的难点

    技术层面的难点可以是做了 GC 的优化,从多少 GC 次优化到多少次,STW 的时间降低了多少,通过哪些手段做的优化。

    可以是压测时性能一直上不去,通过什么手段进行了优化,从多少优化到多少。期间有没有加机器,有没有升配服务器,升配数据库等。

    可以是项目运行一段时间后就出现假死的情况,处理不了任何请求。然后你是怎么一步步去分析并找到具体原因的,然后又是如何去解决的。

    一定要有细节有数据,这样的案例才真实可信。并且面试官会认为你是具备去分析并解决问题的能力。

    3. 不一定是技术层面的难点

    虽然问的是技术难点,如果你确实没有遇到过什么技术难点,这个时候可以往其他方面去靠,不要直接回答说:没有遇到过什么难点。我敢保证,你要你这样回答了,面试成功的可能性不大。

    可以往业务层面,领导力方面去讲,比如你可以说当时做某个业务的时候,没有这块经验。然后通过查找资料,去咨询有经验的朋友等独立的完成了某个系统的设计。并且在做完后取得了什么样的成绩,这个过程对自己来说是非常具体挑战性的,所以这是在项目中遇到的一个难点。

    也可以是自己主动请缨,在领导的支持下主导了老项目的重构,给团队的同学培训了 DDD,并且通过 DDD 成功的将某个业务成功的进行了重构。这样可以体现你的主动性,分享精神,领导力等多方面综合的能力。

    最后送给大家的就是:一定要先准备好,想好自己要说什么,临时发挥效果肯定没有事先准备的好。

    如果对你有用,来个转发呗!

    关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者, 公众号猿天地发起人。

    展开全文
  • jdk序列化怎么实现,有测试过他的性能吗,serialVersionUID的作用是什么,用过一些其他序列化方式没,为什么需要序列化这个技术 hashmap1.7 和 1.8的区别 hashmap怎么解决hash冲突的 查询时间复杂度 数据结构 ...
  • 最近接手一个React项目,在IE下碰到了俩问题 IE11报错如下: 跟踪一下之后,发现是一些其他的npm包里面用到了startsWith这个方法,可以自己polyfill一下: if (!String.prototype.startsWith) { String....
  • Vue项目遇到的一些难点

    千次阅读 2022-05-09 15:56:28
    解决方法:配合qs插件使用 也就是将对象格式化成 Form Data 类似jquery的serialiZeArray()方法 2、使用vue-router时 URL模式引发的问题? Vue-router 提供一个mode参数 用来控制 URL 格式 默认使用的是 hash...
  • 主要介绍了浅谈vue项目重构技术要点和总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 问题背景:今天例会,伙伴提了一个技术问题,就是标准单据的上传附件功能必须要单据保存后才能上传,无法控制必须上传附件的要求。我一时间不知道该怎么回复,因为深入的技术问题我不太在行,我不知道怎样才能帮助...
  • 项目中遇到哪些难点,如何解决的

    万次阅读 2020-03-27 12:30:17
    亮点:在高并发情况下的秒杀优化,我们知道当并发数达到一定量的时候,会对数据库服务器带来很大的压力,那么如何缓解这些压力以及提高并发的QPS就是整个项目的重点。(不断的提高QPS)。 亮点3个: 1.利用缓存...
  • 在本篇文章里小编给大家分享的是关于vue开发中遇到的问题总结,有兴趣的朋友们可以学习参考下。
  • 625134081349)] [外链图片转存…(img-hFkzYE3K-1625134081350)] 企业IT架构转型之道 阿里巴巴台战略思想与架构实战 本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。
  • 前端开发技术难点汇总(一)

    千次阅读 2019-12-01 21:18:10
    1.vue组件在main.js引用组件不起效果时,可直接在页面引用 import引用 2.在vue使用scroller组件,上拉加载时内容虽然会会弹,但底部会留有一段空白未完全会弹,在源文件修改,vue-scroller/src/module/...
  • 一、vue相关 Q:vue的底层原理? A:Vue是采用数据劫持配合发布者-订阅者模式,通过Object.defineProperty来()来劫持各个属性的getter和setter。 在数据发生变化的时候,发布消息给依赖收集器,去通知观察者,做出...
  • 在每个前端求职者的面试过程,一定都有过被面试官问到项目中难点,而答不上来的情况。为了让大家在前端面试前准备得更加充足,小编准备了一些易被忽视且难度较高的前端面试题,希望可以帮助大家更加顺利完成求职...
  • 1.路由跳转: vue项目中的router的hash方式和history方式的区别,this. r o u t e r . p u s h 方 法 和 t h i s . router.push方法和this. router.push方法和this.router.go方法的使用,this. r o u t e r . r e p ...
  • 每个页面即服务,由每个业务线团队里的每个同学,用他们熟悉的技术栈,通过的前面介绍的微应用解决方案,独立运维。 二、滥用promise.all 用户信息页面挂掉了, 整个页面的是空白的(无数据)。打开浏览器调试了一下...
  • (参考:链接:[高级]Zookeeper介绍(四)——Zookeeper的基本概念) 问:数据一致性怎么保证答:先扯一段CAP和BASE(参考:链接:分布式的CAP理论 和 ),再说说2PC,3PC(参考:链接:深入理解分布式系统的2PC和...
  • “在整个春招和秋招过程,作者除了拿到产品经理岗位的OFFER之外,还拿到了网易游戏、大疆、商汤科技等公司的项目管理岗位的OFFER。有些学弟学妹会对产品经理和项目管理两个岗位在面试过程具体的回答有疑问,所以...
  • 前端面试题之项目中难点

    千次阅读 2021-04-25 15:39:00
    项目中遇到难点 1.发表动态 后端代码: async insert (data) { let {filename,url} = data; // console.log(filename,url,table) var base64 = url.replace(/^data:image\/\w+;base64,/, "");//去掉图片base...
  • 近期一直在做一个xxx中心的项目,先来吐槽下内心的想法, 要开发的项目需求很不明确,需求两周两周的更改,感觉每天并没有特别多实际的产出,总是感觉有点儿浪费时间。 虽然这样,但是作为开发,我们只能服从上级...
  • 被问到项目亮点、难点遇到的问题、解决思路

    万次阅读 多人点赞 2021-05-23 20:36:38
    面试被问到你的项目亮点、难点遇到的问题、解决思路是不是很蒙,现在我拆分一下问题 什么是项目亮点: 你负责的业务是什么?(学会发现问题) 你真的想过业务是什么吗? 有为业务想过什么吗? 有了你,业务有什么...
  • vue项目中遇到的问题汇总

    千次阅读 2020-12-21 12:00:20
    前:项目用到的技术栈为webpack+vue2.x+pug+stylus+elementUI1、IE9, 请求服务器数据并用v-for渲染option标签出现只显示第一个字的问题解决方法:/*** 强制重绘页面的select 输入框,解决IE9只显示单个字符串问题...
  • 2.padding 代表的则是填充,而填充的对象针对的是组件的元素,比如 TextView 的文字 比如为 TextView 设置 paddingleft = "5dp",则是在组件里的元素的左边填充 5dp 的空间!3. margin 针对的是容器的组件4....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,615
精华内容 12,646
热门标签
关键字:

项目中遇到的技术难点