当前位置: 首页 > news >正文

政务服务网站建设运行情况培训学校网站

政务服务网站建设运行情况,培训学校网站,学做网站论,网站建设 锋云科技公司此文前端框架使用 rax#xff0c;全篇代码暂未开源#xff08;待开源#xff09;原文链接地址#xff1a;Nealyang/PersonalBlog前言貌似在面试中#xff0c;你如果设计一个 react/vue 组件#xff0c;貌似已经是司空见惯的问题了。本文不是理论片#xff0c;更多的是自…此文前端框架使用 rax全篇代码暂未开源待开源 原文链接地址Nealyang/PersonalBlog前言貌似在面试中你如果设计一个 react/vue 组件貌似已经是司空见惯的问题了。本文不是理论片更多的是自己的一步步思考和实践。文中会有很多笔者的思考过程欢迎评论区多多交流和讨论。从需求讨论、技术方案探讨到编码、到最终的测试经历过了很多次的脑暴也遇到过非常多的坑其中有可能跟业务有关、也有可能跟框架有关基于这些坑又讨论了很多解决方案和非常 hack歪门邪道的对策。但是随着时间的推移再回头看看当时的 hack 代码很多都不太记得为什么这么写了所以这里简单记录下Filter 组件的开发过程。以便后面查询更希望能大家一起探讨以求得更优质的代码架构和实现思路。由于代码编写使用基于底层 weex 的 rax 框架所以有些坑或许对于正在使用 react 或者 vue 的你并不会遇到可以直接忽略说说业务Filter已经常见的不可再常见的组件了顾名思义就是个筛选过滤器。我们先看看现有 app 上的一些 filter 展现 形式。既然做组件我们就需要它足够的通用足够的易于扩展。阿里拍卖的 Filter 飞猪的 Filter在说 Filter 的业务特征之前我们先约束下每一部分的命名以便于你更好的阅读此文上面分别是拍卖和飞猪的 filter 页面从这两个页面中我们大概可以总结出关于 Filter 的一下几点业务画像随着页面滚动Filter 可能具有吸附能力但是可能距离顶部存在一定的距离Panel 面板多样性点击navItem 展开的面板Panel 面板以及 navItem 都可能会有动画navBar 内容可变panel 面板展示形式不定panel 面板内容可能非常复杂需要考虑性能优化navBar 上可能存在非 Filter 的内容关注按钮有的navBar 的 navItem 没有对应的 panel 面板Filter 上存在影响搜索结果但是没有影响的”快排“按钮filter 配置参数能够指定通过 url 传入相关筛选 id 能够初始化面板选中…最终组件产出由于 rax 1.0 tshooks 开源版本还在开发中所以仓库链接暂时就不放上了rax-pui-filter-utils Filter 的内部工具库仅供 Filter 开发者提供的工具库rax-pui-filter-tools配合使用 Filter 的一些工具集比如 提高性能的 HOC 组件、占位符组件等可用可不用根据自己业务需求来思考原由并不是每一个 Filter 的使用者都需要这些功能做成可插拔式为了降低没必要的 bundle 大小pui-filterFilter 核心功能开发库效果图console 处可见抛出的查询参数设计与思考前端组件架构图初版组件架构图(终板)src ├─ Filter.js //Filter 最外层父容器 ├─ constant.js //项目代码常量定义 ├─ index.js //入口文件 ├─ navbar // navBar 文件夹 │ ├─ NavBase.js //navBar 基类 NavQuickSearch 和 NavRelatePanel 父类 │ ├─ NavQuickSearch.js // 快速搜索无 panel的 navBar │ ├─ NavRelatePanel.js // 带有 panel 的 navBar │ └─ index.js // 导出文件 ├─ panel │ └─ index.js // panel 面板组件代码 └─ style.js 组件功能 Feature筛选头 UI 可动态配置扩展支持点击动画提供三种筛选项类型RelatePanel筛选项关联Panel型即筛选头和 Panel 是一对一关系点击筛选头展示 PanelQuickSearch筛选项快速搜索排序型即筛选头没有对应 Panel点击筛选头直接触发搜索PureUI纯 UI占位类型即纯 UI 放置不涉及搜索比如订阅按钮场景筛选面板显示隐藏统一管理支持下拉和左滑展示隐藏动画统一搜索回调函数Filter 组件在和业务面板隔离支持任意组件接入业务组件里搜索变更通过 onChange(params)回调函数来触发提供了三种业务通用的面板组件rax-pui-list-select列表选择业务面板rax-pui-location-select省市区级联选择业务面板rax-pui-multi-selection-panel多选业务面板查看组件使用文档这里指的是 Filter 的功能 Feature跟上文提及的 Filter 组件功能可能并不能完全覆盖但是我们提供解决方案组件的设计始终秉持着不侵入业务的原则所有与业务相关均给予配置入口。期望组件使用形式 import Filter from rax-pui-filter;render(FilternavConfig{[]}onChange{(){}}Filter.Panel业务组件1 //Filter.PanelFilter.Panel业务组件2 //Filter.Panel/Filter); 组件功能与业务需求边界划分何为业务功能何为组件功能这个需要具体的探讨其实也没有严格意义上的区分。说白了就是你买个手机他都会送你充电器。但是。。。为什么很多手机也送手机壳小米、华为、荣耀但是 iPhone 却不送呢所以到底是不是标配对于我们这个组件简而言之我们能做到的我们都做但是其中我们还是梳理出某些功能还是数据业务功能navBar 上每一个 navItem 展示什么文案、样式属于业务功能整个 Filter 的数据处理包括 url 上的查询参数需要抛给对应 navItem要展示的文案也是业务功能Filter 是否点击滚动到顶部也是业务功能毕竟很多搜索页 Filter 本身置顶。而且对于 rax 而言不同容器滚动方式还不同但是我们提供这样的方法给你去调用panel 面板里面数据请求、逻辑处理都是你自己的业务逻辑。Filter 只提供基本的容器能力和接口换言之Filter 里面任何功能都可以说为业务功能。但是我们需要提供 80%业务都需要的功能封装作为 Filter 的 Future。这就是我们的目的。根据上面的业务功能和组件功能的区分我们就知道在使用 Filter 的时候你应该给我传递什么配置以及什么方法。Filter API参数说明类型默认值是否必填navConfig筛选头配置 点击查看详细配置项效果图ArrayObject- (必填)offsetTopFilter组件展开面板状态下距离页面顶部的高度有两种状态固定位置和跟随页面滚动吸附置顶固定位置 状态下距离页面顶部的高度跟随页面滚动吸附置顶 状态下距离页面顶部的高度 效果图Number0styles配置样式Filter中所有样式都可使用styles集合对象来配置覆盖styles 格式Object{}getStickyRef获取 Sticky 节点的 ref 实例用于滚动吸附场景内部配合 pm-app-plus 容器组件点击 Filter 时自动吸附置顶示例图FunctionkeepHighlight筛选条件改变后是否需要在筛选头保持高亮效果图BooleanfalseclickMaskClosable开启 mask 背景的点击隐藏BooleantrueonChangeFilter 搜索变更回调函数 签名Function(params:Object,index:Number, urlQuery: Object) void参数params: Object 搜索参数index:Number 触发搜索的 Panel 搜索urlQuery:Object URL query 对象FunctiononPanelVisibleChangePanel 显示隐藏回调函数 签名Function({ visible:Boolean, triggerIndex:Number, triggerType:String }) void参数visible:Boolean 显示隐藏标志量 triggerIndex:Number触发的筛选项索引值 triggerType:String 触发类型 triggerType详解 包含三种触发类型Navbar:来自筛选头的点击触发Mask:来自背景层的点击触发Panel:来自Panel 的 onChange 回调触发FunctionFilter prop navConfig 数组配置详解navConfig筛选项类型 typeRelatePanel筛选项关联Panel型即筛选头和 Panel 是一对一关系点击筛选头展示 PanelQuickSearch筛选项快速搜索排序型即筛选头没有对应 Panel点击筛选头直接触发搜索PureUI纯 UI占位类型即纯 UI 放置不涉及搜索比如订阅按钮场景注意 如果 navConfig 内置的UI参数不满足您的需求请使用renderItem自定义渲染函数来控制筛选头 UI参数说明类型默认值是否必填type筛选项类型 三种类型RelatePanel: 筛选项关联数据面板类型QuickSearch: 筛选项快速搜索排序类型PureUI: 纯 UI占位类型StringRelatePaneltext 注意RelatePanel类型生效筛选头显示文案 文字溢出用...展示String- (必填)icons 注意RelatePanel类型生效筛选头 iconnormal 正常态 和 active 激活态 图标 数据格式Object类型 :String类型 : 效果图Object or String-options 注意QuickSearch类型生效快速搜索排序类型的数据源 数据格式Array(必填)optionsIndex 注意QuickSearch类型生效快速搜索排序类型默认选中的索引String0optionsKey 注意QuickSearch类型生效指定快速搜索排序对应的搜索 key,用到 onChange 回调中String不提供默认使用当前筛选项的索引formatText文案格式化函数签名Function(text:String) text参数text: String 筛选头文案Function(text)textdisabled禁用筛选头点击BooleantruehasSeperator是否展示右侧分隔符效果图BooleanfalsehasPanel当前筛选头是否有对应的 panelBooleantruerenderItem自定义渲染注意 提供的配置项无法满足你的 UI 需求时使用签名Function(isActive:Boolean, this:Element) Element参数isActive:Boolean 筛选头是否为激活状态this:Element 筛选头this实例Function-animation动画配置采用内置的动画 参数说明注意 目前只内置了一种rotate动画类型ObjectanimationHook用户自定义动画的钩子函数内置动画无法满足需求时使用 签名Function(refImg:Element, isActive:Boolean) text参数refImg:Element 筛选头图标的 ref 实例 isActive:Boolean 筛选头是否为激活状态Function-Filter.Panel API参数说明类型默认值是否必填styles配置样式Filter中所有样式都可使用styles集合对象来配置覆盖Object{}displayModePanel 展现形式全屏、下拉 参数说明全屏Fullscreen下拉DropdownStringDropdownnoAnimation禁止动画BooleantruehighPerformance内部通过 Panel 的显示隐藏控制 panel 的 render 次数避免不必要的 render高性能模式下只会在 Panel 展示 或者 展示隐藏状态变化时才会重新 renderBooleantrueanimationPanel 展示动画配置内置上下左右动画 参数说明direction 控制动画方向分别有 up、down、left、rightObjectFilter 的代码使用Filter 的参数配置 navConfig: [{type: RelatePanel, // type可以不提供默认值为RelatePaneltext: 向下, // 配置筛选头文案icons: {// 配置 icon分为正常形态和点击选中形态normal: //gw.alicdn.com/tfs/TB1a7BSeY9YBuNjy0FgXXcxcXXa-27-30.png,active: //gw.alicdn.com/tfs/TB1NDpme9CWBuNjy0FhXXb6EVXa-27-30.png,},hasSeperator: true, // 展示竖线分隔符formatText: text text ↓, // 筛选文案的格式化函数},{type: QuickSearch,optionsIndex: 0,optionsKey: price,options: [// 快速排序列表{text: 价格,icon: ,value: 0,},{text: 升序,icon: //gw.alicdn.com/tfs/TB1PuVHXeL2gK0jSZFmXXc7iXXa-20-20.png,value: 1,},{text: 降序,icon: //gw.alicdn.com/tfs/TB1a7BSeY9YBuNjy0FgXXcxcXXa-27-30.png,value: 2,},],},{type: RelatePanel, // type可以不提供默认值为RelatePaneltext: 旋转,icons: {// 配置 icon分为正常形态和点击选中形态normal: //gw.alicdn.com/tfs/TB1PuVHXeL2gK0jSZFmXXc7iXXa-20-20.png,active: //gw.alicdn.com/tfs/TB1l4lIXhv1gK0jSZFFXXb0sXXa-20-20.png,},animation: { type: rotate }, // 配置动画点击后旋转图片默认没有动画},{type: RelatePanel, // type可以不提供默认值为RelatePaneltext: 向左,},{type: PureUI,text: 订阅,renderItem: () {// 渲染自定义的 UIreturn (Imagestyle{{width: 120,height: 92,}}source{{ uri: https://gw.alicdn.com/tfs/TB1eubQakL0gK0jSZFAXXcA9pXa-60-45.png }}/);},},]// ...FilteroffsetTop{100} // offsetTop RecycleView上面的组件的高度当前为 100navConfig{this.state.navConfig} // Filter Navbar 配置项keepHighlight{true} // 保持变更的高亮styles{styles} // 配置覆盖内置样式大样式对象集合onChange{this.handleSearchChange}// Panel 面板显示隐藏变更事件onPanelVisibleChange{this.handlePanelVisibleChange}Panel highPerformance{true}ListSelect {...this.state.data1} //PanelPanelLocationSelect {...this.state.data2} //PanelPaneldisplayMode{Fullscreen} // 配置 Panel 全屏展示默认为下拉展示animation{{// 动画配置timingFunction: cubic-bezier(0.22, 0.61, 0.36, 1),duration: 200,direction: left, // 动画方向从右往左方向滑出}}MultiSelect {...this.state.data3} //Panel/Filter 代码运行效果图如上截图。下面简单说下代码的实现。核心源码展示开源版本Tshookslerna还未公布所以目前还是采用 rax 0.x 的版本编写的代码。这里只做有坑的地方代码处理讲解。欢迎各位大佬评论留出各位想法Filter.js先从 render 方法看起 render() {const { style {}, styles {}, navConfig, keepHighlight } this.props;const { windowHeight, activeIndex } this.state;if (!windowHeight) return null;return (View style{[defaultStyle.container, styles.container, style]}{this.renderPanels()}Navbarref{r {this.refNavbar r;}}navConfig{navConfig}styles{styles}keepHighlight{keepHighlight}activeIndex{activeIndex}onNavbarPress{this.handleNavbarPress}onChange{this.handleSearchChange}//View);} 获取一些基本配置以及 windowHeight屏幕高度和 activeIndex当前第几个item 处于 active 状态被点开。之所以我们的 renderPanels 写在 NavBar 上面是因为在 weex 中zIndex 是不生效的。若想 A 元素在 B 元素上面则 render 的时候A 必须在 B 后面。这样写是为了 panel 面板展开的下拉动画看起来是从 navBar 下面出来的。renderPanel 方法就是渲染对应的 panel /*** 渲染 Panel*/renderPanels () {const { activeIndex, windowHeight } this.state;let { children } this.props;if (!Array.isArray(children)) {children [children];}let index 0;return children.map(child {let panelChild null;let hasPanel this.panelIndexes[index];if (!hasPanel) {index;}if (!this.panelManager[index]) {this.panelManager[index] {};}let injectProps {index,visible: activeIndex index,windowHeight,filterBarHeight: this.filterBarHeight,maxHeight: this.filterPanelMaxHeight,shouldInitialRender: this.panelManager[index].shouldInitialRender,onChange: this.handleSearchChange.bind(this, index),onNavTextChange: this.handleNavTextChange.bind(this, index),onHidePanel: this.setPanelVisible.bind(this, false, index),onMaskClick: this.handleMaskClick,disableNavbarClick: this.disableNavbarClick,};if (child.type ! Panel) {panelChild Panel {...injectProps}{child}/Panel;} else {panelChild cloneElement(child, injectProps);}index;return panelChild;});}; 准确的说这是一个 HOC我们将代理、翻译传给 Filter 的影响或者 panel 面板需要使用的 props 传递给 Panel 面板。比如 onChange 回调或者面板隐藏的回调以及当前哪一个 panel 需要展开等。由于 Panel 的面板复杂度我们未知。为了避免不断的展开和收齐不必要的 render我们采用 transform的方式将面板不需要显示的面板移除屏幕外需要展示的在移入到屏幕内部。具体可见 Panel 的render return return (Viewref{r {this.refPanelContainer r;}}style{[defaultStyle.panel,styles.panel,this.panelContainerStyle,{transform: translateX(-${this.containerTransformDes}),opacity: 0,},]}Viewrefmaskstyle{[defaultStyle.mask,styles.mask,showStyle,isWeb ? { top: 0, zIndex: -1 } : { top: 0 },]}onClick{this.handleMaskClick}onTouchMove{this.handleMaskTouchMove}/{cloneElement(child, injectProps)}/View); 注意 Panel 面板的坑远不止这些比如我们都知道render 是最消耗页面性能的而页面初始化进来面板名没有展示出来此时面板 Panel 在屏幕外那么是否需要走 Panel 面板的 render 呢但是目前的这种写法Panel 组件的生命周期是会都走到的。但是如果遇到 Panel 里面需要请求数据然后页面 url 里查询参数有 locationId123 navItem 需要展示对应的地理位置.如果不渲染 Panel 如何根据 id 拿到对应的地名传递给 navItem 去展示对我们可以拦截 Panel 面板的 render 方法让 Panel render null然后别的生命周期照样运行。但是如果 render 中用户有对 ref 的使用那么就可能会造成难以排查的 bug。所以最终为了提高页面的可交互率但是又不影响页面需求的情况下我们提供了一个可选的工具Performance HOC 。 注意是可选。export default function performance(Comp) {return class Performance extends Comp {static displayName Performance(${Comp.displayName});render() {const { shouldInitialRender } this.props.panelAttributes;if (shouldInitialRender) {return super.render();} else {return View /;}}}; } 通过配置Panel 的 shouldInitialRender 属性来告诉我是否第一次进来拦截 render。当然Panel 也有很多别的坑比如现在 Panel 为了重复 render将 Panel 移除屏幕外那么动画从上而下展开设置初始动画闪屏如何处理Filter 的代码就是初始化、format、检查校验各种传参以及 Panel 和 NavBar 通信中转 比如 format、比如 handleNavbarPress NavBar 核心代码NavBar 架构核心代码从架构图中大概可以看出NavBar 中通过不同的配置展示不同的 NavBarItem 的类型NavQuickSearch,NavRelatePanel这里需要注意的是 NavBar 的数据是通过 Filter props 传入的,如果状态放到 Filter 也就是 NavBar 的父组件管理的话会导致 Panel 组件不必要的渲染虽然已经提供 Panel 层的 shouldComponentUpdate 的配置参数同时也是为了组件设计的高内聚、低耦合我们将传入的 props 封装到 NavBar 的 state 中自己管理状态。 constructor(props) {super(props);const navConfig formatNavConfig(props.navConfig);this.state {navConfig,};}// 这里我们提供内部的 formatNavConfig 方法具体内容根据不同组件业务需求不同代码逻辑不同这里就不展开说明了 NavBar 中还需要注意的就是被动更新Panel 层点击后NavBar 上文字的更新因为这里我们利用父组件来进行 Panel 和 NavBar 的通信 //Filter.js 调用 NavBar 的方法/*** 更新 Navbar 文案*/handleNavTextChange (index, navText, isChange true) {// Navbar 的 render 抽离到内部处理可以减少一次 Filter.Panel 的额外 renderthis.asyncTask(() {this.refNavbar.updateOptions(index, navText, isChange);});};//NavBar.js 提供给 Filter.js 调用的 updateOptions/*** 更新 navConfigFilter 组件调用* 异步 setState 规避 rax 框架 bug: 用户在 componentDidMount 函数中调用中 this.props.onChange 回调* 重现Codehttps://jsplayground.taobao.org/raxplayground/cefec50a-dfe5-4e77-a29a-af2bbfcfcda3* param index* param text* param isChange*/updateOptions (index, text, isChange true) {setTimeout(() {const { navConfig } this.state;this.setState({navConfig: navConfig.map((item, i) {if (index i) {return {...item,text,isChange,};}return item;}),});}, 0);}; 最后 NavBar 中的 item 分为 快速搜索和带有 panel 的 NavBarItem两种但是对于其公共功能比如渲染的 UI 逻辑等这里我们采用的方法是抽离 NavBase 组件供给 NavQuickSearch 和 NavRelatePanel 调用NavBase 部分代码 renderDefaultItem ({ text, icons, active }) {const { formatText, hasSeperator, length, keepHighlight, isChange } this.props;const hasChange keepHighlight isChange;const iconWidth icons ? this.getStyle(navIcon).width || 18 : 0;return [TextnumberOfLines{1}style{[this.getStyle(navText),ifElse(active || hasChange, this.getStyle(activeNavText)),{ maxWidth: 750 / length - iconWidth },]}{ifElse(is(Function)(formatText), formatText(text), text)}/Text,ifElse(icons,Imageref{r {this.refImg r;}}style{this.getStyle(navIcon)}source{{uri: ifElse(active || hasChange, icons icons.active, icons icons.normal),}}/,null,),ifElse(hasSeperator, View style{this.navSeperatorStyle} /),];}; NavRelatePanel.js export default class NavRelatePanel extends NavBase {static displayName NavRelatePanel;handleClick () {const { disabled, onNavbarPress } this.props;if (disabled) return false;onNavbarPress(NAV_TYPE.RelatePanel);};render() {const { renderItem, active, text, icons } this.props;return (Viewstyle{[this.getStyle(navItem), ifElse(active, this.getStyle(activeNavItem))]}onClick{this.handleClick}{ifElse(is(Function)(renderItem),renderItem renderItem({ active, instance: this }),this.renderDefaultItem({ text, icons, active }),)}/View);}} Panel 核心代码Panel 的核心功能是对用户定义的 Panel.child 进行基本的功能添加比如背景 mask 遮罩、动画时机的处理.Panel 的使用 PaneldisplayMode{Fullscreen} // 配置 Panel 全屏展示默认为下拉展示animation{{// 动画配置timingFunction: cubic-bezier(0.22, 0.61, 0.36, 1),duration: 200,direction: left, // 动画方向从右往左方向滑出}}MultiSelect {...this.state.data3} //Panel 我们提供基础的动画配置但是同时也提供动画的 functionHook,这些都取决于动画的触发时机 get animationConfig() {const { animation } this.props;if (!animation || !is(Object)(animation)) {return PANEL_ANIMATION_CONFIG;}return Object.assign({}, PANEL_ANIMATION_CONFIG, animation);}// ... /*** 执行动画* param nextProps*/componentWillReceiveProps(nextProps) {if (nextProps.visible ! this.props.visible) {if (nextProps.visible) {setNativeProps(findDOMNode(this.refPanelContainer), {style: {transform: translateX(-${rem2px(750)}),},});this.props.disableNavbarClick(true);this.enterAnimate(this.currentChildref, () {this.props.disableNavbarClick(false);});this.handleMaskAnimate(true);} else {this.handleMaskAnimate(false);this.props.disableNavbarClick(true);this.leaveAnimate(this.currentChildref, () {this.props.disableNavbarClick(false);setNativeProps(findDOMNode(this.refPanelContainer), {style: {transform: translateX(0),},});});}}} 由于动画的执行需要时间所以这个时间段我们应该给 Filter 中的 NavBar 加锁 锁的概念也同样提供给用户毕竟业务逻辑我们是不会侵入的在上一次的搜索没有结果返回时候应该给 NavBar 加锁禁止再次点击虽然用户可以再 onchange 回调函数中处理但是作为组件同样应该考虑并且提供这个能力同样对于动画也是如此在该动画正在执行的时候应该禁止 NavBar 的再次点击。上面的动画配置效果如下Panel 中还有核心的处理或许就是关于动画时机的处理。比如在触发动画前我们需要设置动画初始状态但是如若如下写法会出现 Panel 闪动的现象毕竟我们通过第二次的事件轮训回来才执行初始化所以这里如果用户配置启动动画那么我们需要在 Panel 的最外层添加一个可见的 flag默认进来 opacity 设置为 0当动画初始状态设置完毕后在将最外层容器的 opacity 设置为 1其实 Panel 还是闪了一下只是你看不到而已。 // 设置动画初始样式setTimeout(() {setNativeProps(node, {style: {transform: !visible ? translate(0, 0) : v,},});}, 0);// 执行动画setTimeout(() {transition(node,{transform: visible ? translate(0, 0) : v,},{timingFunction: timingFunction,duration: duration,delay: 0,},cb,);}, 50); 设置动画初始化样式中添加 setNativeProps(findDOMNode(this.refPanelContainer), {style: {opacity: 1,},}); 结束语Filter 的组件看似简单但是如果想写一个市场上较为通用和广泛的 Filter 组件不仅仅是组件的颗粒度、耦合度和性能需要考虑更多的是其中还是有太多的业务逻辑需要去思考。对于目前的初版还未修改成正式开源版已经基本涵盖了目前我们能够想到的业务场景也已经有相关业务落地使用。当然对于如果是直接放到业务中使用而不作为开源组件的话我们可已经 Panel下的 child 通过 renderPortal 降低层级通过 EventBus 或者 redux、mobx 等管理数据状态。那样会让整个代码逻辑看起来清晰很多。但是为了降低bundle 大小我们尽可能的减少通用包的使用以及第三方插件的依赖。关于文章中没有提及的想法或者对于这些Filter业务需求坑你有更好的处理方法和想法都欢迎在评论区交流~技术交流欢迎关注微信公众号全栈前端精选每日获取高质量文章推送。也可以加我个人微信交流~
http://www.zqtcl.cn/news/150454/

相关文章:

  • 南宁网站seo大概多少钱门户网站建设公司渠道
  • 如何建国际商城网站海门做网站公司
  • 做网站应该画什么图注册子公司流程及所需资料
  • 嵊州市建设银行网站怎么自己做游戏软件
  • 用模板快速建站中园建设银行网站
  • 网站建设罒金手指下拉壹陆韩国最新新闻消息
  • 东莞企业网站推广技巧wordpress怎么汉化
  • 17网站一起做网店如何下单iis服务器网站301重定向怎么做
  • 网站如何做线上支付功能seo网站推广优化费用
  • 贵州灵溪seo整站优化wordpress进行不
  • 三网一体网站建设网站开发环境分析
  • 广州白云机场网站建设查询域名备案信息
  • 苗族网站建设中牟做网站
  • 潍坊网站建设建站哪个网站的课件做的好处
  • 网站建设平台杭州网上交易平台
  • 您提交的网站域名无备案我想学网站建设
  • 怎样做国际网站dw网页设计代码免费
  • wordpress做企业站基础微网站开发公司
  • 用上海注册的公司建的网站怎么做asp网站
  • 一个专做特卖的网站千鸟云网站建设
  • 哈尔滨网站优化seo知名公司
  • 企业网站的开发流程个人免费建网站
  • 旅游网站平台建设方案策划书wordpress 自建cdn
  • 网站开发回访话术内容电商网站有哪些
  • 网络广告投放网站网站如何做关
  • 葫芦岛住房和城乡建设厅网站野望王绩
  • 小说网站怎么做网站建设需求分析班级
  • 开设购物网站的方案网站免费seo
  • 免费手机h5模板网站模板下载wordpress所有插件
  • 北京做网站开发公司wordpress主题创建后门