中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

網(wǎng)架公司廠家seo技術(shù)優(yōu)化技巧

網(wǎng)架公司廠家,seo技術(shù)優(yōu)化技巧,獵頭公司網(wǎng)站建設(shè),名字設(shè)計網(wǎng)站前段時間閱讀了 https://juejin.cn/post/7260144602471776311#heading-25 這篇文章;本文做一個梳理和筆記; 主要聚焦的知識點如下: 如何搭建腳手架工程如何開發(fā)調(diào)試如何處理命令行參數(shù)如何實現(xiàn)用戶交互如何拷貝文件夾或文件如何動態(tài)生成文件…

前段時間閱讀了 https://juejin.cn/post/7260144602471776311#heading-25 這篇文章;本文做一個梳理和筆記;

主要聚焦的知識點如下:

  • 如何搭建腳手架工程
  • 如何開發(fā)調(diào)試
  • 如何處理命令行參數(shù)
  • 如何實現(xiàn)用戶交互
  • 如何拷貝文件夾或文件
  • 如何動態(tài)生成文件
  • 如何處理路徑
  • 如何自動安裝依賴

step1:初始化工程

推薦使用pnpm搭建mono-repo風(fēng)格的工程;

mono-repo工程可以包含多個子工程,并且每個子工程都可以獨立編譯打包,并將打包的產(chǎn)物發(fā)成npm包;

這樣在同一個工程中,我們可以寫庫,也可以寫demo;

步驟如下:

  1. 執(zhí)行 pnpm init 命令,生成package.json文件;
  2. 新建 pnpm-workspace.yaml 文件,添加如下配置:
packages: - 'packages/*' - 'demos/*'

配置后,聲明了 packages 和 demos 文件夾中子工程是同屬一個工作空間的,工作空間中的子工程編譯打包的產(chǎn)物都可以被其它子工程引用。

  1. 在packages文件夾下 新建zy-cli文件夾;
  2. cd 到 zy-cli文件夾下,運(yùn)行 pnpm init 初始化;
  3. zy-cli 下的 packages.json 中聲明 bin 命令 zy-script;
"bin": {"zy-script": "./bin/index.js"},
  1. 添加 bin文件夾,添加index.js文件;寫入如下代碼
#!/usr/bin/env node
console.log('hellow, zy-cli');
  1. demos 文件夾中存放使用腳手架的演示項目,我們先建一個app文件夾;
  2. 執(zhí)行 pnpm init 命令,生成package.json文件;
  3. 在app中依賴 @zy/zy-cli ( 此處依賴名稱與zy-cli 的package.json 中 name一致 )

pnpm add @zy/zy-cli -F app 會在dependencies自動添加依賴;

  1. 并添加script指令 zy (與zy-cli中聲明的指令一致);
"scripts": {"zy": "zy-script"},"dependencies": {"@zy/zy-cli": "workspace:^"},
  1. 執(zhí)行pnpm -i 安裝依賴;

  1. 在app目錄下運(yùn)行:pnpm zy;成功輸出了 hellow, zy-cli

小節(jié):

到目前為止,我們成功創(chuàng)建了mono-repo風(fēng)格的項目結(jié)構(gòu);

packages > zy-cli 是我們腳手架工程,在bin中自定義了指令;

demos > app 是使用 zy-cli 腳手架的示例工程,利用pnpm的workspace,指定了工作區(qū)中zy-cli依賴,在script中自定義使用 zy-cli中聲明的命令;

整個工程結(jié)構(gòu)如下:

  |-- my-cli|-- package.json|-- pnpm-lock.yaml|-- pnpm-workspace.yaml|-- demos|   |-- app|       |-- package.json|-- packages|-- zy-cli|-- package.json|-- bin|-- index.js

現(xiàn)在,我們思考一下,一個腳手架工程需要哪些模塊?

  • 命令參數(shù)模塊
  • 用戶交互模塊
  • 文件拷貝模塊
  • 動態(tài)文件生成模塊
  • 自動安裝依賴模塊

接下來,我們一步一步實現(xiàn)他們;

step2:命令參數(shù)模塊

當(dāng)我們執(zhí)行命令的時候,經(jīng)常會帶一些參數(shù),如何獲取并利用這些參數(shù);

nodeJS 中 process 模塊,可以獲取當(dāng)前進(jìn)程相關(guān)的全局環(huán)境信息 - 命令行參數(shù),環(huán)境變量,命令運(yùn)行路徑等;

利用 progress.argv 獲取;

const process = require('process');
// 獲取命令參數(shù)
console.log(process.argv); 

或者可以采用更便捷的方案 yargs 開源庫;

我們?yōu)閦y-cli 安裝 yargs:

pnpm add yargs --F zy-cli

注意:--F zy-cli 指的是指定給該工程安裝;-W 是全局安裝的意思

我們在zy-cli - bin - index.js 中寫入如下測試代碼

#!/usr/bin/env node
// 此處遵循CommonJS規(guī)范
const yargs = require('yargs');console.log('name', yargs.argv.name);

在demos - app 目錄下執(zhí)行 pnpm zy --name=zhang

打印輸出如下:

step3:創(chuàng)建子命令

我們在使用vue-cli的時候,都用過 vue creat app 之類的命令;creat就是子命令;

我們通過 yarg.command 來實現(xiàn);

yargs.command(cmd, desc, builder, handler)

  • cmd:字符串,子命令名稱,也可以傳遞數(shù)組,如 ['create', 'c'],表示子命令叫 create,其別名是 c;
  • desc:字符串,子命令描述信息;
  • builder:子命令參數(shù)信息配置,比如可以設(shè)置參數(shù)(builder也可以是一個函數(shù)):
    • alias:別名;
    • demand:是否必填;
    • default:默認(rèn)值;
    • describe:描述信息;
    • type:參數(shù)類型,string | boolean | number。
  • handler: 函數(shù),可以在這個函數(shù)中專門處理該子命令參數(shù)。

下面我們定義一個creat命令:

#!/usr/bin/env node
const yargs = require('yargs');
console.log('name', yargs.argv.name);
yargs.command({// 字符串,子命令名稱,也可以傳遞數(shù)組,如 ['create', 'c'],表示子命令叫 create,其別名是 ccommand: 'create <name>',// 字符串,子命令描述信息;describe: 'create a new project',// 對象,子命令的配置項;builder也可以是一個函數(shù)builder: {name: {alias: 'n', // 別名demandOption: true, // 是否必填describe: 'name of a project', // 描述default: 'app' // 默認(rèn)}},// 函數(shù)形式的// builder: (yargs) => {//   return yargs.option('name', {//     alias: 'n',//     demand: true,//     describe: 'name of a project',//     type: 'string'//   })// },handler: (argv) => {console.log('argv', argv);}
});

我們運(yùn)行一下這個命令:pnpm zy create my-app

輸出如下:

step4:增加用戶交互

當(dāng)我們使用vue create xxx 的時候,命令行會出現(xiàn)選項式的交互,讓我們選擇配置;

這里我們也實現(xiàn)一下;使用 inquirer 庫;

運(yùn)行命令安裝 pnpm add inquirer@8.2.5 --F zy-cli

inquirer主要做了三件事情:

  1. 詢問用戶問題
  2. 獲取用戶輸入
  3. 校驗用戶輸入
const inquirer = require('inquirer');function inquirerPrompt(argv) {// 先獲取到了命令行中的nameconst { name } = argv;return new Promise((resolve, reject) => {inquirer.prompt([{type: 'input',name: 'name',message: '模板名稱',default: name,validate: function (val) {if (!/^[a-zA-Z]+$/.test(val)) {return "模板名稱只能含有英文";}if (!/^[A-Z]/.test(val)) {return "模板名稱首字母必須大寫"}return true;},},{type: 'list',name: 'type',message: '模板類型',choices: ['表單', '動態(tài)表單', '嵌套表單'],filter: function (value) {return {'表單': "form",'動態(tài)表單': "dynamicForm",'嵌套表單': "nestedForm",}[value];},},{type: 'list',message: '使用什么框架開發(fā)',choices: ['react', 'vue'],name: 'frame',}]).then(answers => {const { frame } = answers;if (frame === 'react') {inquirer.prompt([{type: 'list',message: '使用什么UI組件庫開發(fā)',choices: ['Ant Design',],name: 'library',}]).then(answers1 => {resolve({...answers,...answers1,})}).catch(error => {reject(error)})}if (frame === 'vue') {inquirer.prompt([{type: 'list',message: '使用什么UI組件庫開發(fā)',choices: [ 'Element'],name: 'library',}]).then(answers2 => {resolve({...answers,...answers2,})}).catch(error => {reject(error)})}}).catch(error => {reject(error)})})}exports.inquirerPrompt = inquirerPrompt;

其中 inquirer.prompt() 返回的是一個 Promise,我們可以用 then 獲取上個詢問的答案,根據(jù)答案再發(fā)起對應(yīng)的內(nèi)容。

在index.js 中引入并使用

#!/usr/bin/env nodeconst yargs = require('yargs');
const { inquirerPrompt } = require('./inquirer');// 命令配置
yargs.command({// 字符串,子命令名稱,也可以傳遞數(shù)組,如 ['create', 'c'],表示子命令叫 create,其別名是 ccommand: 'create <name>',// 字符串,子命令描述信息;describe: 'create a new project',// 對象,子命令的配置項;builder也可以是一個函數(shù)builder: {name: {alias: 'n', // 別名demandOption: true, // 是否必填describe: 'name of a project', // 描述default: 'app' // 默認(rèn)}},// 函數(shù)形式的// builder: (yargs) => {//   return yargs.option('name', {//     alias: 'n',//     demand: true,//     describe: 'name of a project',//     type: 'string'//   })// },handler: (argv) => {inquirerPrompt(argv).then((answers) => {console.log(answers);});}
}).argv;

我們運(yùn)行 pnpm zy create my-app 試試:

step5:文件夾拷貝

前幾節(jié)我們實現(xiàn)了一個可以讀取命令行的cli工程配置;

接下來,我們就要深入到cli腳手架的構(gòu)建;

首先是文件夾的拷貝。我們使用 copy-dir 庫來實現(xiàn)文件夾拷貝;

安裝:pnpm add copy-dir --F zy-cli

bin中創(chuàng)建copy.js,實現(xiàn)簡單的copy函數(shù),check函數(shù)

const copydir = require('copy-dir')
const fs = require('fs');function copyDir (from, to, option) {copydir.sync(from, to, option);
}/*** Checks if a directory or file exists at the given path.* @param {string} path - The path to check for existence.* @returns {boolean} - Returns true if the directory or file exists, false otherwise.*/
function checkMkdirExists(path){return fs.existsSync(path);
}exports.copyDir = copyDir;
exports.checkMkdirExists = checkMkdirExists;

這幾個函數(shù)比較簡單,但是主要難點在于使用;具體來說就是 from,to參數(shù);

先定個需求,我們運(yùn)行 creat 選擇 form類型 命令的時候,需要將 zy-cli > src > form 文件夾拷貝到 demos > app > src > <app-name> 中;

  1. 我們分析一下,如何獲取當(dāng)前模板的位置;也就是 copyDir 的 from 參數(shù);

__dirname 是用來動態(tài)獲取當(dāng)前文件模塊所屬目錄的絕對路徑。比如在 bin/index.js 文件中使用 __dirname ,__dirname 表示就是 bin/index.js 文件所屬目錄的絕對路徑 ~/Desktop/my-cli/zy-cli/bin。

使用 path.resolve( [from…], to )將相對路徑轉(zhuǎn)成絕對路徑;

那我們模板的路徑就是:path.resolve( __dirname,'../src/form' );或者path.resolve( __dirname,'../src/${type}')

  1. 接下來,我們確定 copyDir 的 to 參數(shù);也就是目標(biāo)文件夾 <app-name>

我們運(yùn)行腳手架命令是在 app 目錄下;pnpm zy 執(zhí)行的是 app > packages.json ,所以在node腳本中,可以使用 process.cwd() 獲取文件路徑;

那我們拷貝的目標(biāo)路徑就是:path.resolve(process.cwd(), './src/${<app-name>}')

handler: (argv) => {inquirerPrompt(argv).then((answers) => {// 此處已經(jīng)獲取到了完整的模版參數(shù);開始進(jìn)行文件處理const { name, type, frame, library } = answers;// 判斷是否存在該項目文件夾const isMkdirExists = checkMkdirExists(path.resolve(process.cwd(),`./${name}`));if (isMkdirExists) {console.log( `${name}文件夾已經(jīng)存在`);} else {const templatePath = path.resolve(__dirname, `../src/${type}`);const targetPath = path.resolve(process.cwd(), `./${name}`);copyDir(templatePath, targetPath);}});}

運(yùn)行一下命令:pnpm zy create my-app,選擇表單類型;回車,拷貝成功;

step6:目錄守衛(wèi)

如果我需要將文件拷貝到 app > pages > <name> 下,由于沒有pages目錄,命令會報錯;

我們簡單實現(xiàn)一個目錄守衛(wèi),幫我們創(chuàng)建不存在的目錄;

const copydir = require('copy-dir')
const fs = require('fs');function copyDir (from, to, option) {// 目錄守衛(wèi),不存在的目錄結(jié)構(gòu)會去創(chuàng)建mkdirGuard(to);copydir.sync(from, to, option);
}/*** Checks if a directory or file exists at the given path.* @param {string} path - The path to check for existence.* @returns {boolean} - Returns true if the directory or file exists, false otherwise.*/
function checkMkdirExists(path){return fs.existsSync(path);
}// 目錄守衛(wèi)
function mkdirGuard(target) {try {fs.mkdirSync(target, { recursive: true });} catch (e) {mkdirp(target)function mkdirp(dir) {if (fs.existsSync(dir)) { return true }const dirname = path.dirname(dir);mkdirp(dirname);fs.mkdirSync(dir);}}
}exports.copyDir = copyDir;
exports.checkMkdirExists = checkMkdirExists;
exports.mkdirGuard = mkdirGuard;

step7:文件拷貝

文件操作,主要使用 fs.readFileSync 讀取被拷貝的文件內(nèi)容,然后創(chuàng)建一個文件,再使用 fs.writeFileSync 寫入文件內(nèi)容;這兩個api都是比較熟悉的老朋友了;不做過多介紹;

我們定義一個 copyFile函數(shù):

function copyFile(from, to) {const buffer = fs.readFileSync(from);const parentPath = path.dirname(to);mkdirGuard(parentPath)fs.writeFileSync(to, buffer);
}exports.copyFile = copyFile;

使用方法與copyDir 類似,只不過需要精確到文件;這里就不演示了;

step8:動態(tài)文件生成

我們在定義腳手架的時候,會獲取很多類型的命令參數(shù),有些參數(shù)可能對我們模板文件產(chǎn)生影響。

例如,根據(jù)命令行中的name,動態(tài)修改packages中的name;

這里,我們需要依賴 mustache ;安裝:pnpm add mustache --F zy-cli

我們增加一個 renderTemplate 函數(shù):

接受動態(tài)模板的path路徑,data:動態(tài)模版的配置數(shù)據(jù);

Mustache.render(str, data) 接受動態(tài)模版和配置數(shù)據(jù);

Mustache.render('<span>{{name}}</span>',{name:'張三'})

const Mustache = require('mustache');
function renderTemplate(path, data = {}) {const str = fs.readFileSync(path, { encoding: 'utf8' })return Mustache.render(str, data);
}
exports.renderTemplate = renderTemplate;

再定義一個copyTemplate 函數(shù):

path.extname 獲取文件擴(kuò)展名,如果不是tpl類型的,直接當(dāng)做文件處理;

function copyTemplate(from, to, data = {}) {if (path.extname(from) !== '.tpl') {return copyFile(from, to);}const parentToPath = path.dirname(to);// 目錄守衛(wèi)mkdirGuard(parentToPath);// 寫入文件fs.writeFileSync(to, renderTemplate(from, data));
}

在index.js中試驗一下:

const templatePath = path.resolve(__dirname, `../src/${type}/packages.tpl`);
const targetPath = path.resolve(process.cwd(), `./${name}/packages.json`);
copyTemplate(templatePath, targetPath, {name: name})
{"name": "{{name}}","version": "1.0.0","description": "","main": "index.js","scripts": {},"keywords": [],"author": "","license": "ISC"
}

運(yùn)行完創(chuàng)建命令后,成功生成packages.json 文件,并且將 name字段替換成功;

擴(kuò)展:mustache 一些用法補(bǔ)充:

基礎(chǔ)綁定:

Mustache.render('<span>{{name}}</span>',{name:'張三'})

綁定子屬性

Mustache.render('<span>{{ifno.name}}</span>', { ifno: { name: '張三' } })

循環(huán)渲染

// {{#key}} {{/key}} 開啟和結(jié)束循環(huán)
Mustache.render('<span>{{#list}}{{name}}{{/list}}</span>',{list: [{ name: '張三' },{ name: '李四' },{ name: '王五' },]}
)

循環(huán)渲染 + 二次處理

Mustache.render('<span>{{#list}}{{info}}{{/list}}</span>',{list: [{ name: '張三' },{ name: '李四' },{ name: '王五' },],info() {return this.name + ',';}}
)

條件渲染

// 使用 {{#key}} {{/key}} 語法 和 {{^key}} {{/key}} 語法來實現(xiàn)條件渲染,
// 當(dāng) key 為 false、0、[]、{}、null,既是 key == false 為真,
// {{#key}} {{/key}} 包裹的內(nèi)容不渲染,
// {{^key}} {{/key}} 包裹的內(nèi)容渲染
Mustache.render('<span>{{#show}}顯示{{/show}}{{^show}}隱藏{{/show}}</span>',{show: false}
)

step9:實現(xiàn)自動安裝依賴

我們在選擇完框架和UI庫的時候,可以幫助目標(biāo)項目自動安裝依賴;

我們使用 node 中提供的 child_process 子進(jìn)程來實現(xiàn);

  • child_process.exec(command, options, callback)
    • command:命令,比如 pnpm install
    • options:參數(shù)
      • cwd:設(shè)置命令運(yùn)行環(huán)境的路徑
      • env:環(huán)境變量
      • timeout:運(yùn)行執(zhí)行現(xiàn)在
    • callback:運(yùn)行命令結(jié)束回調(diào),(error, stdout, stderr) =>{ },執(zhí)行成功后 error 為 null,執(zhí)行失敗后 error 為 Error 實例,stdout、stderr 為標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤,其格式默認(rèn)是字符串。

我們定義一個 manager 函數(shù);

const path = require('path');
const { exec } = require('child_process');// 組件庫映射,前面是用戶輸入/選擇,后面是目標(biāo)安裝的組件庫
const LibraryMap = {'Ant Design': 'antd','iView': 'view-ui-plus','Ant Design Vue': 'ant-design-vue','Element': 'element-plus',
}function install(cmdPath, options) {// 用戶選擇的框架 和 組件庫const { frame, library } = options;// 安裝命令const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`return new Promise(function (resolve, reject) {// 執(zhí)行安裝命令exec(command,{// 命令執(zhí)行的目錄cwd: path.resolve(cmdPath),},function (error, stdout, stderr) {console.log('error', error);console.log('stdout', stdout);console.log('stderr', stderr)})})
}
exports.install = install;

使用:

// 傳入當(dāng)前進(jìn)程的目錄,以及用戶選擇的配置
install(process.cwd(), answers)

試驗一下:pnpm create xxxx;成功安裝;

但是安裝過程沒有進(jìn)度展示;我們使用 ora 來豐富安裝加載動畫;

安裝:pnpm add ora@5.4.1 --F zy-cli

使用:

const path = require('path');
const { exec } = require('child_process');
const ora = require("ora");// 組件庫映射,前面是用戶輸入/選擇,后面是目標(biāo)安裝的組件庫
const LibraryMap = {'Ant Design': 'antd','iView': 'view-ui-plus','Ant Design Vue': 'ant-design-vue','Element': 'element-plus',
}function install(cmdPath, options) {// 用戶選擇的框架 和 組件庫const { frame, library } = options;// 串行安裝命令const command = `pnpm add ${frame} && pnpm add ${LibraryMap[library]}`return new Promise(function (resolve, reject) {const spinner = ora();spinner.start(`正在安裝依賴,請稍等`);// 執(zhí)行安裝命令exec(command,{// 命令執(zhí)行的目錄cwd: path.resolve(cmdPath),},function (error) {if (error) {reject();spinner.fail(`依賴安裝失敗`);return;}spinner.succeed(`依賴安裝成功`);resolve()})})
}
exports.install = install;

再次執(zhí)行,已經(jīng)有狀態(tài)提示了;

step10:推送到私有npm倉庫

使用verdaccio搭建私有npm倉庫的步驟本文不贅述,可以參考這篇文章;搭建自己的私有npm庫

// TODO 部署過程中使用docker-compose,遇到一些問題,預(yù)計單獨開一篇文章去記錄;

假設(shè)我們已經(jīng)有了npm私庫;ip:http://xxxxxx:4873/

我們使用 nrm 去管理 npm 的倉庫地址

// 全局安裝 
npm install -g nrm// 查看所有的倉庫 
nrm ls 
// 切換倉庫 
nrm use <name> 
// 添加倉庫 
nrm add <name> <address>

推送之前,我們需要修改 packages.json 中的信息:

{"name": "@zy/zy-cli","version": "1.0.0","description": "","main": "index.js","bin": {"zy-script": "./bin/index.js"},"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},// 規(guī)定上傳到npm包中的文件"files": ["bin","src"],"keywords": [],"author": "","license": "ISC","dependencies": {"copy-dir": "^1.3.0","inquirer": "8.2.5","mustache": "^4.2.0","ora": "5.4.1","yargs": "^17.7.2"}
}

推送:

pnpm publish --registry http://xxxxx:4873/

刷新我們的 vedaccio,已經(jīng)存在這個包了

使用:

我們在Desktop中新建一個空白文件夾;

mkdir cli-test

cd cli-test

pnpm init

nrm use zy

pnpm i @zy/zy-cli

此時,我們的cli-test項目已經(jīng)成功安裝了私有npm倉庫的 zy-cli 項目

在packages.json 中添加命令

"scripts": {"zy-script": "zy-script"},

執(zhí)行 pnpm zy-script create Myapp

成功安裝所有依賴并拷貝文件;

總結(jié):

  1. 我們搭建了一個mono-repo風(fēng)格的工程;包含了一個zy-cli腳手架工程,和demos-app的測試工程;
  2. zy-cli實現(xiàn)了用戶交互式的命令行,命令行參數(shù)獲取,文件拷貝,動態(tài)文件生成,自動安裝依賴;
  3. 我們將zy-cli推送到了npm私有倉庫上,并另開了一個工程,切換私庫源,成功安裝并且運(yùn)行;

展望:

目前初步實現(xiàn)了mono-repo工程,還需要添加統(tǒng)一的publish腳本,包含版本自增等;

cli 腳手架不需要打包,所以還需要為這個工程添加一個 組件庫,工具函數(shù)庫等類型的包;

http://www.risenshineclean.com/news/59866.html

相關(guān)文章:

  • 百度網(wǎng)站內(nèi)容網(wǎng)絡(luò)推廣公司深圳
  • 軟件源碼購買一般在哪個網(wǎng)站網(wǎng)址網(wǎng)域ip地址查詢
  • 重慶網(wǎng)站建設(shè) cqhtwl臨沂百度代理公司有幾個
  • 外貿(mào)網(wǎng)站做紙尿褲怎么樣wordpress自助建站
  • 長春網(wǎng)站制作可選源晟4推廣產(chǎn)品最好的方式
  • 代運(yùn)營公司排名前十西安關(guān)鍵詞優(yōu)化排名
  • 中國空間站太小了公司培訓(xùn)
  • 集團(tuán)網(wǎng)站 源碼網(wǎng)站設(shè)計公司排名
  • 廣州海珠區(qū)繁華嗎排名seo公司哪家好
  • wordpress 插件 論壇常州seo排名收費
  • 建設(shè)手機(jī)網(wǎng)站的公司灰色seo推廣
  • 網(wǎng)站平臺建設(shè)的作用巨量算數(shù)數(shù)據(jù)分析
  • 安徽網(wǎng)站設(shè)計流程軟文發(fā)布平臺媒體
  • 找人做一個網(wǎng)站要多少錢百度推廣客戶端怎么登陸
  • 制作花燈百度刷排名優(yōu)化軟件
  • wap游戲縱橫四海優(yōu)化設(shè)計卷子答案
  • 做月季評分表的工程網(wǎng)站叫什么西安百度推廣開戶多少錢
  • 室內(nèi)設(shè)計軟件手機(jī)版semseo是什么意思
  • 中國住房和城鄉(xiāng)建設(shè)部網(wǎng)站安全seo優(yōu)化排名軟件
  • 鞋圖相冊網(wǎng)站怎么做seo網(wǎng)站優(yōu)化網(wǎng)站編輯招聘
  • 建設(shè)彩票網(wǎng)站犯法嗎seo網(wǎng)站排名優(yōu)化公司哪家好
  • 推廣引流文案鄭州網(wǎng)站優(yōu)化外包
  • 知名做漫畫網(wǎng)站百度官網(wǎng)進(jìn)入
  • 南通市住房城鄉(xiāng)建設(shè)局網(wǎng)站百度服務(wù)中心人工24小時電話
  • 做網(wǎng)站公司漢獅團(tuán)隊google play三件套
  • 知名網(wǎng)站建設(shè)多少錢排名優(yōu)化工具下載
  • 網(wǎng)頁的定義seo推廣軟件排行榜
  • 網(wǎng)站備案 身份證廣州seo成功案例
  • 福鼎建設(shè)局網(wǎng)站首頁最簡單的網(wǎng)頁制作
  • 網(wǎng)站怎么做微信登錄四川網(wǎng)站制作