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

浙江建设厅网站 打不开做网站容易还是编程容易

浙江建设厅网站 打不开,做网站容易还是编程容易,做商铺的网站有那些,网站建设开题报告Vuex是一个专为Vue服务#xff0c;用于管理页面数据状态、提供统一数据操作的生态系统。它集中于MVC模式中的Model层#xff0c;规定所有的数据操作必须通过 action - mutation - state change 的流程来进行#xff0c;再结合Vue的数据视图双向绑定特性来实现页面的展示更新…Vuex是一个专为Vue服务用于管理页面数据状态、提供统一数据操作的生态系统。它集中于MVC模式中的Model层规定所有的数据操作必须通过 action - mutation - state change 的流程来进行再结合Vue的数据视图双向绑定特性来实现页面的展示更新。统一的页面状态管理以及操作处理可以让复杂的组件交互变得简单清晰同时可在调试模式下进行时光机般的倒退前进操作查看数据改变过程使code debug更加方便。最近在开发的项目中用到了Vuex来管理整体页面状态遇到了很多问题。决定研究下源码在答疑解惑之外能深入学习其实现原理。先将问题抛出来使学习和研究更有针对性1. 使用Vuex只需执行 Vue.use(Vuex)并在Vue的配置中传入一个store对象的示例store是如何实现注入的2. state内部是如何实现支持模块配置和模块嵌套的3. 在执行dispatch触发action(commit同理)的时候只需传入(type, payload)action执行函数中第一个参数store从哪里获取的4. 如何区分state是外部直接修改还是通过mutation方法修改的5. 调试时的“时空穿梭”功能是如何实现的注本文对有Vuex有实际使用经验的同学帮助更大能更清晰理解Vuex的工作流程和原理使用起来更得心应手。初次接触的同学可以先参考Vuex官方文档进行基础概念的学习。一、框架核心流程进行源码分析之前先了解一下官方文档中提供的核心思想图它也代表着整个Vuex框架的运行流程。vuex-core如图示Vuex为Vue Components建立起了一个完整的生态圈包括开发中的API调用一环。围绕这个生态圈简要介绍一下各模块在核心流程中的主要功能Vue ComponentsVue组件。HTML页面上负责接收用户操作等交互行为执行dispatch方法触发对应action进行回应。dispatch操作行为触发方法是唯一能执行action的方法。actions操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作支持多个同名方法按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装以支持action的链式触发。commit状态改变提交操作方法。对mutation进行提交是唯一能执行mutation的方法。mutations状态改变操作方法。是Vuex修改state的唯一推荐方法其他修改方式在严格模式下将会报错。该方法只能进行同步操作且方法名只能全局唯一。操作之中会有一些hook暴露出来以进行state的监控等。state页面状态管理容器对象。集中存储Vue components中data对象的零散数据全局唯一以进行统一的状态管理。页面显示所需的数据从该对象中进行读取利用Vue的细粒度数据响应机制来进行高效的状态更新。gettersstate对象读取方法。图中没有单独列出该模块应该被包含在了render中Vue Components通过该方法读取全局state对象。Vue组件接收交互行为调用dispatch方法触发action相关处理若页面状态需要改变则调用commit方法提交mutation修改state通过getters获取到state新值重新渲染Vue Components界面随之更新。二、目录结构介绍打开Vuex项目看下源码目录结构。dir_structureVuex提供了非常强大的状态管理功能源码代码量却不多目录结构划分也很清晰。先大体介绍下各个目录文件的功能 * module提供module对象与module对象树的创建功能 * plugins提供开发辅助插件如“时光穿梭”功能state修改的日志记录功能等 * helpers.js提供action、mutations以及getters的查找API * index.js是源码主入口文件提供store的各module构建安装 * mixin.js提供了store在Vue实例上的装载注入 * util.js提供了工具方法如find、deepCopy、forEachValue以及assert等方法。三、初始化装载与注入了解大概的目录及对应功能后下面开始进行源码分析。index.js中包含了所有的核心代码从该文件入手进行分析。3.1 装载实例先看个简单的例子/*** store.js文件* 创建store对象配置state、action、mutation以及getter***/import Vue fromvueimport Vuex fromvuex//install Vuex框架Vue.use(Vuex)//创建并导出store对象。为了方便不配置任何参数export default new Vuex.Store()store.js文件中加载Vuex框架创建并导出一个空配置的store对象实例。/*** vue-index.js文件****/import Vue fromvueimport App from./../pages/app.vueimport store from./store.jsnewVue({el:#root,router,store,render: hh(App)})然后在index.js中正常初始化一个页面根级别的Vue组件传入这个自定义的store对象。如问题1所述以上实例除了Vue的初始化代码只是多了一个store对象的传入。一起看下源码中的实现方式。3.2 装载分析index.js文件代码执行开头定义局部 Vue 变量用于判断是否已经装载和减少全局作用域查找。let Vue然后判断若处于浏览器环境下且加载过Vue则执行install方法。//auto install in dist modeif (typeof window ! undefined window.Vue) {install(window.Vue)}install方法将Vuex装载到Vue对象上Vue.use(Vuex) 也是通过它执行先看下Vue.use方法实现function (plugin: Function |Object) {/*istanbul ignore if*/if(plugin.installed) {return}//additional parametersconst args toArray(arguments, 1)args.unshift(this)if (typeof plugin.install function) {//实际执行插件的install方法plugin.install.apply(plugin, args)}else{plugin.apply(null, args)}plugin.installed truereturn this}若是首次加载将局部Vue变量赋值为全局的Vue对象并执行applyMixin方法install实现如下functioninstall (_Vue) {if(Vue) {console.error([vuex] already installed. Vue.use(Vuex) should be called only once.)return}Vue_VueapplyMixin(Vue)}来看下applyMixin方法内部代码。如果是2.x.x以上版本可以使用 hook 的形式进行注入或使用封装并替换Vue对象原型的_init方法实现注入。export default function(Vue) {const version Number(Vue.version.split(.)[0])if (version 2) {const usesInit Vue.config._lifecycleHooks.indexOf(init) -1Vue.mixin(usesInit?{ init: vuexInit } : { beforeCreate: vuexInit })}else{//override init and inject vuex init procedure//for 1.x backwards compatibility.const _init Vue.prototype._initVue.prototype._init function (options {}) {options.initoptions.init?[vuexInit].concat(options.init): vuexInit_init.call(this, options)}}具体实现将初始化Vue根组件时传入的store设置到this对象的$store属性上子组件从其父组件引用$store属性层层嵌套进行设置。在任意组件中执行 this.$store 都能找到装载的那个store对象vuexInit方法实现如下functionvuexInit () {const options this.$options//store injectionif(options.store) {this.$store options.store}else if (options.parent options.parent.$store) {this.$store options.parent.$store}}看个图例理解下store的传递。页面Vue结构图cart_vue_structure对应store流向cart_vue_structure四、store对象构造上面对Vuex框架的装载以及注入自定义store对象进行分析解决了问题1。接下来详细分析store对象的内部功能和具体实现来解答 为什么actions、getters、mutations中能从arguments[0]中拿到store的相关数据? 等问题。store对象实现逻辑比较复杂先看下构造方法的整体逻辑流程来帮助后面的理解cart_vue_structure4.1 环境判断开始分析store的构造函数分小节逐函数逐行的分析其功能。constructor (options {}) {assert(Vue, must call Vue.use(Vuex) before creating a store instance.)assert(typeof Promise ! undefined, vuex requires a Promise polyfill in this browser.)在store构造函数中执行环境判断以下都是Vuex工作的必要条件1. 已经执行安装函数进行装载2. 支持Promise语法。assert函数是一个简单的断言函数的实现一行代码即可实现。functionassert (condition, msg) {if (!condition) throw newError([vuex] ${msg})}4.2 数据初始化、module树构造环境判断后根据new构造传入的options或默认值初始化内部数据。const {state{},plugins[],strict false}options//store internal statethis._committing false //是否在进行提交状态标识this._actions Object.create(null) //acitons操作对象this._mutations Object.create(null) //mutations操作对象this._wrappedGetters Object.create(null) //封装后的getters集合对象this._modules new ModuleCollection(options) //Vuex支持store分模块传入存储分析后的modulesthis._modulesNamespaceMap Object.create(null) //模块命名空间mapthis._subscribers [] //订阅函数集合Vuex提供了subscribe功能this._watcherVM new Vue() //Vue组件用于watch监视变化调用 new Vuex.store(options) 时传入的options对象用于构造ModuleCollection类下面看看其功能。constructor (rawRootModule) {//register root module (Vuex.Store options)this.root new Module(rawRootModule, false)//register all nested modulesif(rawRootModule.modules) {forEachValue(rawRootModule.modules, (rawModule, key){this.register([key], rawModule, false)})}ModuleCollection主要将传入的options对象整个构造为一个module对象并循环调用 this.register([key], rawModule, false) 为其中的modules属性进行模块注册使其都成为module对象最后options对象被构造成一个完整的组件树。ModuleCollection类还提供了modules的更替功能详细实现可以查看源文件module-collection.js。4.3 dispatch与commit设置继续回到store的构造函数代码。//bind commit and dispatch to selfconst store thisconst { dispatch, commit } thisthis.dispatch functionboundDispatch (type, payload) {returndispatch.call(store, type, payload)}this.commit functionboundCommit (type, payload, options) {returncommit.call(store, type, payload, options)}封装替换原型中的dispatch和commit方法将this指向当前store对象。dispatch和commit方法具体实现如下dispatch (_type, _payload) {//check object-style dispatchconst {type,payload} unifyObjectStyle(_type, _payload) //配置参数处理//当前type下所有action处理函数集合const entry this._actions[type]if (!entry) {console.error([vuex] unknown action type: ${type})return}return entry.length 1? Promise.all(entry.map(handler handler(payload))): entry[0](payload)}前面提到dispatch的功能是触发并传递一些参数(payload)给对应type的action。因为其支持2种调用方法所以在dispatch中先进行参数的适配处理然后判断action type是否存在若存在就逐个执行(注上面代码中的this._actions[type] 以及 下面的 this._mutations[type] 均是处理过的函数集合具体内容留到后面进行分析)。commit方法和dispatch相比虽然都是触发type但是对应的处理却相对复杂代码如下。commit (_type, _payload, _options) {//check object-style commitconst {type,payload,options}unifyObjectStyle(_type, _payload, _options)const mutation{ type, payload }const entry this._mutations[type]if (!entry) {console.error([vuex] unknown mutation type: ${type})return}//专用修改state方法其他修改state方法均是非法修改this._withCommit(() {entry.forEach(functioncommitIterator (handler) {handler(payload)})})//订阅者函数遍历执行传入当前的mutation对象和当前的statethis._subscribers.forEach(sub sub(mutation, this.state))if (options options.silent) {console.warn([vuex] mutation type: ${type}. Silent option has been removed. Use the filter functionality in the vue-devtools)}}该方法同样支持2种调用方法。先进行参数适配判断触发mutation type利用_withCommit方法执行本次批量触发mutation处理函数并传入payload参数。执行完成后通知所有_subscribers(订阅函数)本次操作的mutation对象以及当前的state状态如果传入了已经移除的silent选项则进行提示警告。4.4 state修改方法_withCommit是一个代理方法所有触发mutation的进行state修改的操作都经过它由此来统一管理监控state状态的修改。实现代码如下。_withCommit (fn) {//保存之前的提交状态const committing this._committing//进行本次提交若不设置为true直接修改statestrict模式下Vuex将会产生非法修改state的警告this._committing true//执行state的修改操作fn()//修改完成还原本次修改之前的状态this._committing committing}缓存执行时的committing状态将当前状态设置为true后进行本次提交操作待操作完毕后将committing状态还原为之前的状态。4.5 module安装绑定dispatch和commit方法之后进行严格模式的设置以及模块的安装(installModule)。由于占用资源较多影响页面性能严格模式建议只在开发模式开启上线后需要关闭。//strict modethis.strict strict//init root module.//this also recursively registers all sub-modules//and collects all module getters inside this._wrappedGettersinstallModule(this, state, [], this._modules.root)4.5.1 初始化rootState上述代码的备注中提到installModule方法初始化组件树根组件、注册所有子组件并将其中所有的getters存储到this._wrappedGetters属性中让我们看看其中的代码实现。functioninstallModule (store, rootState, path, module, hot) {const isRoot !path.lengthconst namespacestore._modules.getNamespace(path)//register in namespace mapif(namespace) {store._modulesNamespaceMap[namespace]module}//非根组件设置 state 方法if (!isRoot !hot) {const parentState getNestedState(rootState, path.slice(0, -1))const moduleName path[path.length - 1]store._withCommit((){Vue.set(parentState, moduleName, module.state)})}······判断是否是根目录以及是否设置了命名空间若存在则在namespace中进行module的存储在不是根组件且不是 hot 条件的情况下通过getNestedState方法拿到该module父级的state拿到其所在的 moduleName 调用 Vue.set(parentState, moduleName, module.state) 方法将其state设置到父级state对象的moduleName属性中由此实现该模块的state注册(首次执行这里因为是根目录注册所以并不会执行该条件中的方法)。getNestedState方法代码很简单分析path拿到state如下。functiongetNestedState (state, path) {returnpath.length? path.reduce((state, key) state[key], state): state}4.5.2 module上下文环境设置const local module.context makeLocalContext(store, namespace, path)命名空间和根目录条件判断完毕后接下来定义local变量和module.context的值执行makeLocalContext方法为该module设置局部的 dispatch、commit方法以及getters和state(由于namespace的存在需要做兼容处理)。4.5.3 mutations、actions以及getters注册定义local环境后循环注册我们在options中配置的action以及mutation等。逐个分析各注册函数之前先看下模块间的逻辑关系流程图complete_flow下面分析代码逻辑//注册对应模块的mutation供state修改使用module.forEachMutation((mutation, key) {const namespacedType namespace keyregisterMutation(store, namespacedType, mutation, local)})//注册对应模块的action供数据操作、提交mutation等异步操作使用module.forEachAction((action, key) {const namespacedType namespace keyregisterAction(store, namespacedType, action, local)})//注册对应模块的getters供state读取使用module.forEachGetter((getter, key) {const namespacedType namespace keyregisterGetter(store, namespacedType, getter, local)})registerMutation方法中获取store中的对应mutation type的处理函数集合将新的处理函数push进去。这里将我们设置在mutations type上对应的 handler 进行了封装给原函数传入了state。在执行 commit(xxx, payload) 的时候type为 xxx 的mutation的所有handler都会接收到state以及payload这就是在handler里面拿到state的原因。functionregisterMutation (store, type, handler, local) {//取出对应type的mutations-handler集合const entry store._mutations[type] || (store._mutations[type] [])//commit实际调用的不是我们传入的handler而是经过封装的entry.push(functionwrappedMutationHandler (payload) {//调用handler并将state传入handler(local.state, payload)})}action和getter的注册也是同理的看一下代码(注前面提到的 this.actions 以及 this.mutations在此处进行设置)。functionregisterAction (store, type, handler, local) {//取出对应type的actions-handler集合const entry store._actions[type] || (store._actions[type] [])//存储新的封装过的action-handlerentry.push(functionwrappedActionHandler (payload, cb) {//传入 state 等对象供我们原action-handler使用let res handler({dispatch: local.dispatch,commit: local.commit,getters: local.getters,state: local.state,rootGetters: store.getters,rootState: store.state}, payload, cb)//action需要支持promise进行链式调用这里进行兼容处理if (!isPromise(res)) {resPromise.resolve(res)}if(store._devtoolHook) {return res.catch(err {store._devtoolHook.emit(vuex:error, err)throwerr})}else{returnres}})}functionregisterGetter (store, type, rawGetter, local) {//getters只允许存在一个处理函数若重复需要报错if(store._wrappedGetters[type]) {console.error([vuex] duplicate getter key: ${type})return}//存储封装过的getters处理函数store._wrappedGetters[type] functionwrappedGetter (store) {//为原getters传入对应状态returnrawGetter(local.state,//local statelocal.getters, //local gettersstore.state, //root statestore.getters //root getters)}}action handler比mutation handler以及getter wrapper多拿到dispatch和commit操作方法因此action可以进行dispatch action和commit mutation操作。4.5.4 子module安装注册完了根组件的actions、mutations以及getters后递归调用自身为子组件注册其stateactions、mutations以及getters等。module.forEachChild((child, key) {installModule(store, rootState, path.concat(key), child, hot)})4.5.5 实例结合前面介绍了dispatch和commit方法以及actions等的实现下面结合一个官方的购物车实例中的部分代码来加深理解。Vuex配置代码/* store-index.js store配置文件*/import Vue fromvueimport Vuex fromvueximport* as actions from ./actionsimport* as getters from ./gettersimport cart from./modules/cartimport products from./modules/productsimport createLogger from../../../src/plugins/loggerVue.use(Vuex)const debug process.env.NODE_ENV ! productionexportdefault newVuex.Store({actions,getters,modules: {cart,products},strict: debug,plugins: debug?[createLogger()] : []})Vuex组件module中各模块state配置代码部分/*** cart.js***/const state{added: [],checkoutStatus:null}/*** products.js***/const state{all: []}加载上述配置后页面state结构如下图cart_statestate中的属性配置都是按照option配置中module path的规则来进行的下面看action的操作实例。Vuecart组件代码部分/*** Cart.vue 省略template代码只看script部分***/exportdefault{methods: {//购物车中的购买按钮点击后会触发结算。源码中会调用 dispatch方法checkout (products) {this.$store.dispatch(checkout, products)}}}Vuexcart.js组件action配置代码部分const actions {checkout ({ commit, state }, products) {const savedCartItems [...state.added] //存储添加到购物车的商品commit(types.CHECKOUT_REQUEST) //设置提交结算状态shop.buyProducts( //提交api请求并传入成功与失败的cb-funcproducts,() commit(types.CHECKOUT_SUCCESS), //请求返回成功则设置提交成功状态() commit(types.CHECKOUT_FAILURE, { savedCartItems }) //请求返回失败则设置提交失败状态)}}Vue组件中点击购买执行当前module的dispatch方法传入type值为 ‘checkout’payload值为 ‘products’在源码中dispatch方法在所有注册过的actions中查找’checkout’的对应执行数组取出循环执行。执行的是被封装过的被命名为wrappedActionHandler的方法真正传入的checkout的执行函数在wrappedActionHandler这个方法中被执行源码如下(注前面贴过这里再看一次)functionwrappedActionHandler (payload, cb) {let reshandler({dispatch: local.dispatch,commit: local.commit,getters: local.getters,state: local.state,rootGetters: store.getters,rootState: store.state}, payload, cb)if (!isPromise(res)) {resPromise.resolve(res)}if(store._devtoolHook) {return res.catch(err {store._devtoolHook.emit(vuex:error, err)throwerr})}else{returnres}}handler在这里就是传入的checkout函数其执行需要的commit以及state就是在这里被传入payload也传入了在实例中对应接收的参数名为products。commit的执行也是同理的实例中checkout还进行了一次commit操作提交一次type值为types.CHECKOUT_REQUEST的修改因为mutation名字是唯一的这里进行了常量形式的调用防止命名重复执行跟源码分析中一致调用 function wrappedMutationHandler (payload) { handler(local.state, payload) } 封装函数来实际调用配置的mutation方法。看到完源码分析和上面的小实例应该能理解dispatch action和commit mutation的工作原理了。接着看源码看看getters是如何实现state实时访问的。4.6 store._vm组件设置执行完各module的install后执行resetStoreVM方法进行store组件的初始化。//initialize the store vm, which is responsible for the reactivity//(also registers _wrappedGetters as computed properties)resetStoreVM(this, state)综合前面的分析可以了解到Vuex其实构建的就是一个名为store的vm组件所有配置的state、actions、mutations以及getters都是其组件的属性所有的操作都是对这个vm组件进行的。一起看下resetStoreVM方法的内部实现。functionresetStoreVM (store, state) {const oldVm store._vm //缓存前vm组件//bind store public gettersstore.getters {}const wrappedGettersstore._wrappedGettersconst computed{}//循环所有处理过的getters并新建computed对象进行存储通过Object.defineProperty方法为getters对象建立属性使得我们通过this.$store.getters.xxxgetter能够访问到该gettersforEachValue(wrappedGetters, (fn, key) {//use computed to leverage its lazy-caching mechanismcomputed[key] () fn(store)Object.defineProperty(store.getters, key, {get: ()store._vm[key],enumerable:true //for local getters})})//use a Vue instance to store the state tree//suppress warnings just in case the user has added//some funky global mixinsconst silent Vue.config.silent//暂时将Vue设为静默模式避免报出用户加载的某些插件触发的警告Vue.config.silent true//设置新的storeVm将当前初始化的state以及getters作为computed属性(刚刚遍历生成的)store._vm newVue({data: { state },computed})//恢复Vue的模式Vue.config.silent silent//enable strict mode for new vmif(store.strict) {//该方法对state执行$watch以禁止从mutation外部修改stateenableStrictMode(store)}//若不是初始化过程执行的该方法将旧的组件state设置为null强制更新所有监听者(watchers)待更新生效DOM更新完成后执行vm组件的destroy方法进行销毁减少内存的占用if(oldVm) {//dispatch changes in all subscribed watchers//to force getter re-evaluation.store._withCommit(() {oldVm.state null})Vue.nextTick(()oldVm.$destroy())}}resetStoreVm方法创建了当前store实例的_vm组件至此store就创建完毕了。上面代码涉及到了严格模式的判断看一下严格模式如何实现的。functionenableStrictMode (store) {store._vm.$watch(state, () {assert(store._committing, Do not mutate vuex store state outside mutation handlers.)}, { deep:true, sync: true})}很简单的应用监视state的变化如果没有通过 this._withCommit() 方法进行state修改则报错。4.7 plugin注入最后执行plugin的植入。plugins.concat(devtoolPlugin).forEach(plugin plugin(this))devtoolPlugin提供的功能有3个//1. 触发Vuex组件初始化的hookdevtoolHook.emit(vuex:init, store)//2. 提供“时空穿梭”功能即state操作的前进和倒退devtoolHook.on(vuex:travel-to-state, targetState {store.replaceState(targetState)})//3. mutation被执行时触发hook并提供被触发的mutation函数和当前的state状态store.subscribe((mutation, state) {devtoolHook.emit(vuex:mutation, mutation, state)})源码分析到这里Vuex框架的实现原理基本都已经分析完毕。五、总结最后我们回过来看文章开始提出的5个问题。1.  问使用Vuex只需执行 Vue.use(Vuex)并在Vue的配置中传入一个store对象的示例store是如何实现注入的答Vue.use(Vuex) 方法执行的是install方法它实现了Vue实例对象的init方法封装和注入使传入的store对象被设置到Vue上下文环境的$store中。因此在Vue Component任意地方都能够通过this.$store访问到该store。2.  问state内部支持模块配置和模块嵌套如何实现的答在store构造方法中有makeLocalContext方法所有module都会有一个local context根据配置时的path进行匹配。所以执行如dispatch(submitOrder, payload)这类action时默认的拿到都是module的local state如果要访问最外层或者是其他module的state只能从rootState按照path路径逐步进行访问。3.  问在执行dispatch触发action(commit同理)的时候只需传入(type, payload)action执行函数中第一个参数store从哪里获取的答store初始化时所有配置的action和mutation以及getters均被封装过。在执行如dispatch(submitOrder, payload)的时候actions中type为submitOrder的所有处理方法都是被封装后的其第一个参数为当前的store对象所以能够获取到 { dispatch, commit, state, rootState } 等数据。4.  问Vuex如何区分state是外部直接修改还是通过mutation方法修改的答Vuex中修改state的唯一渠道就是执行 commit(xx, payload) 方法其底层通过执行 this._withCommit(fn) 设置_committing标志变量为true然后才能修改state修改完毕还需要还原_committing变量。外部修改虽然能够直接修改state但是并没有修改_committing标志位所以只要watch一下statestate change时判断是否_committing值为true即可判断修改的合法性。5.  问调试时的”时空穿梭”功能是如何实现的答devtoolPlugin中提供了此功能。因为dev模式下所有的state change都会被记录下来’时空穿梭’ 功能其实就是将当前的state替换为记录中某个时刻的state状态利用 store.replaceState(targetState) 方法将执行this._vm.state state 实现。源码中还有一些工具函数类似registerModule、unregisterModule、hotUpdate、watch以及subscribe等如有兴趣可以打开源码看看这里不再细述。六、作者简介明裔美团外卖高级前端研发工程师2014年加入美团外卖负责Web主站开发。先后参与了外卖B端、C端、配送等全业务线系统开发后目前主要负责商家券活动系统。
http://www.sadfv.cn/news/278887/

相关文章:

  • 网站空间换了 使用原有域名福步外贸官网
  • 用mediawiki做的网站哈尔滨网站建设贴吧
  • 东莞企慕网站建设什么装修网站做的好的
  • 建设银行网站不能建行转他行了西宁制作网站需要多少钱
  • 重庆餐饮网站设计公司响应式网站建设报价
  • 南昌做网站比较好的公司广州注册公司核名在哪个网站
  • 免费建立网站论坛罗湖做网站哪家好
  • 站长工具传媒自己怎么做网站链接
  • 计算机编程代码大全优化关键词技巧
  • 三明市住房与建设局网站网站建设所需
  • diy网站营销案例100例简短
  • 网站分页效果wordpress _the_logo
  • 在局网站 作风建设佛山建网站定制
  • 十大搞笑素材网站视频生成链接
  • 河南微网站建设公司深圳seo优化公司搜索引擎优化方案
  • 采购网站建设vps搭建网站需要空间
  • 教育教研网站建设的意义重庆网络seo公司
  • 排名好的徐州网站开发wordpress手机如何登陆
  • 海尔建设此网站的目的是什么网站到期可以续费
  • 做网站一屏的尺寸是推广普通话手抄报内容
  • 网站建设公司.如何汇报网站建设
  • 做网站软文怎么弄广州知名网站建设有哪些
  • 网站后台管理界面模板郑州建设高端网站
  • 台州宇洋台州网站建设快速做网站公司哪家专业
  • 做创意小视频的网站百度手机助手
  • 常州市网站制作河北网站备案查询系统
  • 公司网站内容相近电子商城网站开发的背景
  • 成都营销型网站建设及推广那家好网站高端网站建设
  • 网站空间域名购买手机建设网站自适应的好处
  • 做企业网站需要多少钱wordpress评论表情不显示