前言前言前言前端日常开发中,会遇见各种各样的 cli,使用 vue 技术栈的你一定用过 @vue/cli ,同样使用 react 技术栈的人也一定知道 create-react-app 。利用这些工具能够实现一行命令生成我们想要的代码模版,极大地方便了我们的日常开发,让计算机自己去干繁琐的工作,而我们,就可以节省出大量的时间用于学习、交流、开发。@vue/clicreate-react-appcli 工具的作用在于它能够将我们开发过程中经常需要重复做的事情利用一行代码来解决,比如我们在写需求的时候每新增一个页面就需要相应的增加该页面的初始化代码,而相同文件类型的初始化代码往往是一样的,比如 example.vue。同时我们还需要增加对应的路由,比如在 router.js 中增加对应的路由规则。这些工作都是很繁琐又重复的,每次遇到这种情况都重复一遍吗?是时候作出改变了,编写自己的 cli 工具,一行命令,3 秒钟进入 coding 状态!本文以自己的 fc-vue-cli 为例,将开发到发布过程完整记录下来,看完本文,你将学会如何从零开发一个 cli 项目,以及如何使用 npm 发布自己的包。提前放上该项目地址提前放上该项目地址提前放上该项目地址源代码地址: 源代码源代码npm 地址: npmnpm原文地址(github上):githubgithub要实现的功能fc-vue add-page
通过这行命令来新增一个页面的模版文件,省去了手动新建文件,手动复制初始化代码的麻烦,同时添加上对应的路由配置脚手架的名字定为 fc-vue,这个是通过 package.json 里面的 name 字段来定义的。目录结构 目录结构 目录结构
入口 (bin/index.js)入口文件只做了一件事,那就是判断当前node的版本是否大于10,如果版本号<10则提醒用户升级node
#!/usr/bin/env node

// 'use strict';
const chalk = require('chalk');

const currentNodeVersion = process.versions.node;
const major = currentNodeVersion.split('.')[0];
if (major < 10) {
console.error(
chalk.red(

`You are running Node \n${currentNodeVersion} \nvue-assist-cli requires Node 10 or higher.\nPlease update your version of Node`
)
);
process.exit(1);
}

require('../packages/init');
#!/usr/bin/env node

// 'use strict';
const chalk = require('chalk');

const currentNodeVersion = process.versions.node;
const major = currentNodeVersion.split('.')[0];
if (major < 10) {
console.error(
chalk.red(

`You are running Node \n${currentNodeVersion} \nvue-assist-cli requires Node 10 or higher.\nPlease update your version of Node`
)
);
process.exit(1);
}

require('../packages/init');初始化命令 (packages/init.js)初始化命令 (packages/init.js)初始化命令 (packages/init.js)在这里初始化你要实现的命令,比如我要实现 add-page 功能,这里要用到的 commander 库。commander
const { program } = require('commander');
const { log } = require('./lib/util');

// 初始化版本,我们直接获取package.json里面的版本号就可以了
program.version(require('../package.json').version);
//开始添加命令 [name] 说明这个参数是可选的,我们想做到兼容不同的使用方法所以把这个参数设置未可选
//.description里面可以写上这个命名的一些描述,当用户fc-vue help add-page 的时候可以提供帮助文档
//.option 用来添加可选的参数
//.action用来响应用户的输入,这里我们单独用一个文件./commands/add-page来处理
program
.command('add-page [name]')
.description(

'add a page, 默认加在./src/views 或 ./src/pages 或./src/page目录下,同时添加路由\n支持"/"来创建子目录例如:add-page user/login\n使用时,支持 fc-vue add-page 【回车】 来选择输入信息'
)
.option('-s, --simple', '创建简单版的页面,只新增一个.vue文件')
.option('-t, --title ', '页面标题')<br> .action(require('./commands/add-page'))<br> .on('--help', () => {<br> log('支持 fc-vue add-page 【回车】 来选择输入信息');<br> });<br>//格式化命令行参数<br>program.parse(process.argv);<br>const { program } = require('commander');<br>const { log } = require('./lib/util');<br><br>// 初始化版本,我们直接获取package.json里面的版本号就可以了<br>program.version(require('../package.json').version);<br>//开始添加命令 [name] 说明这个参数是可选的,我们想做到兼容不同的使用方法所以把这个参数设置未可选<br>//.description里面可以写上这个命名的一些描述,当用户fc-vue help add-page 的时候可以提供帮助文档<br>//.option 用来添加可选的参数<br>//.action用来响应用户的输入,这里我们单独用一个文件./commands/add-page来处理<br>program<br> .command('add-page [name]')<br> .description(<br><br>'add a page, 默认加在./src/views 或 ./src/pages 或./src/page目录下,同时添加路由\n支持"/"来创建子目录例如:add-page user/login\n使用时,支持 fc-vue add-page 【回车】 来选择输入信息'<br> )<br> .option('-s, --simple', '创建简单版的页面,只新增一个.vue文件')<br> .option('-t, --title <title>', '页面标题')<br> .action(require('./commands/add-page'))<br> .on('--help', () => {<br> log('支持 fc-vue add-page 【回车】 来选择输入信息');<br> });<br>//格式化命令行参数<br>program.parse(process.argv);处理用户输入的命令 (packages/commands/add-page.js)处理用户输入的命令 (packages/commands/add-page.js)处理用户输入的命令 (packages/commands/add-page.js)这里需要使用到几个库, shelljs 用来处理 shell 命令的,我们用来操作文件, chalk 用来给打印输出增加样式。函数通过 name,cmdObj 来获取用户的输入,其中 name 是.command('add-page [name]')里面的 name, cmdObj 对象里面则包括其他参数shelljschalk<br>const fs = require('fs');<br>const shell = require('shelljs');<br>const chalk = require('chalk');<br>const { askQuestions, askCss } = require('../lib/ask-page');<br>const checkContext = require('../lib/checkContext');<br>const copyTemplate = require('../lib/copy-template');<br>const addRouter = require('../lib/add-router');<br>const { error, log, success } = require('../lib/util');<br>shell.config.fatal = true;<br><br>module.exports = async (name, cmdObj) => {<br> try {<br> //默认使用less,<br> let cssType = 'less';<br> let simple = cmdObj.simple;<br> let title = cmdObj.title;<br> if (!name && (simple || title)) {<br><br>error('错误的命令,缺少页面名称');<br><br>process.exit(1);<br> }<br> //如果用户没有输入name,[fc-vue add-page] 则进入问答模式,通过一问一答获取用户的输入<br> if (!name) {<br><br>const answers = await askQuestions();<br><br>// console.log(answers);<br><br>name = answers.FILENAME;<br><br>title = answers.TITLE;<br><br>simple = answers.SIMPLE;<br><br>if (!simple) {<br><br>const res = await askCss();<br><br>cssType = res.CSS_TYPE;<br><br>}<br> }<br> //其他情况则可以通过option拿到参数<br> // console.log(process.cwd());<br> //检查上下文环境,并返回目标文件目录路径<br> let { destDir, destDirRootName, rootDir } = checkContext(<br><br>name,<br><br>cmdObj,<br><br>'page'<br> );<br> //复制模版到目标文件<br> let { destFile } = copyTemplate(destDir, simple, cssType);<br><br> if (fs.existsSync(destFile)) {<br><br>await addRouter(name, rootDir, simple, destDirRootName, title);<br><br>log(`成功创建${name},请在${destDir}下查看`);<br> } else {<br><br>console.error(<br><br>chalk.red(`创建失败,请到项目【根目录】或者【@src】目录下执行该操作`)<br><br>);<br> }<br> } catch (error) {<br> console.error(chalk.red(error));<br> console.error(<br><br>chalk.red(<br><br>`创建页面失败,请确保在项目【根目录】或者【@src】目录下执行该操作\n,否则请联系@zhongyi`<br><br>)<br> );<br> }<br>};<br>const fs = require('fs');<br>const shell = require('shelljs');<br>const chalk = require('chalk');<br>const { askQuestions, askCss } = require('../lib/ask-page');<br>const checkContext = require('../lib/checkContext');<br>const copyTemplate = require('../lib/copy-template');<br>const addRouter = require('../lib/add-router');<br>const { error, log, success } = require('../lib/util');<br>shell.config.fatal = true;<br><br>module.exports = async (name, cmdObj) => {<br> try {<br> //默认使用less,<br> let cssType = 'less';<br> let simple = cmdObj.simple;<br> let title = cmdObj.title;<br> if (!name && (simple || title)) {<br><br>error('错误的命令,缺少页面名称');<br><br>process.exit(1);<br> }<br> //如果用户没有输入name,[fc-vue add-page] 则进入问答模式,通过一问一答获取用户的输入<br> if (!name) {<br><br>const answers = await askQuestions();<br><br>// console.log(answers);<br><br>name = answers.FILENAME;<br><br>title = answers.TITLE;<br><br>simple = answers.SIMPLE;<br><br>if (!simple) {<br><br>const res = await askCss();<br><br>cssType = res.CSS_TYPE;<br><br>}<br> }<br> //其他情况则可以通过option拿到参数<br> // console.log(process.cwd());<br> //检查上下文环境,并返回目标文件目录路径<br> let { destDir, destDirRootName, rootDir } = checkContext(<br><br>name,<br><br>cmdObj,<br><br>'page'<br> );<br> //复制模版到目标文件<br> let { destFile } = copyTemplate(destDir, simple, cssType);<br><br> if (fs.existsSync(destFile)) {<br><br>await addRouter(name, rootDir, simple, destDirRootName, title);<br><br>log(`成功创建${name},请在${destDir}下查看`);<br> } else {<br><br>console.error(<br><br>chalk.red(`创建失败,请到项目【根目录】或者【@src】目录下执行该操作`)<br><br>);<br> }<br> } catch (error) {<br> console.error(chalk.red(error));<br> console.error(<br><br>chalk.red(<br><br>`创建页面失败,请确保在项目【根目录】或者【@src】目录下执行该操作\n,否则请联系@zhongyi`<br><br>)<br> );<br> }<br>};问答模式 (packages/lib/ask-page.js)问答模式 (packages/lib/ask-page.js)问答模式 (packages/lib/ask-page.js)这里需要用到 inquirer 。这个就很简单了,基本上就是以数组的方式列出你想让用户输入的内容,每个问题的交互可以选择 input 输入,list 选择等等。在这里获取到的用户输入我们就可以在 packages/commands/add-page.js 调用,然后拿到这些参数。inquirer<br>const inquirer = require('inquirer');<br><br>const askQuestions = () => {<br> const questions = [<br> {<br><br>name: 'FILENAME',<br><br>type: 'input',<br><br>message: '请输入页面的名称?[支持多级目录,例如:user/login]',<br> },<br> {<br><br>name: 'TITLE',<br><br>type: 'input',<br><br>message: '请输入页面标题(meta.title)',<br> },<br> {<br><br>type: 'list',<br><br>name: 'SIMPLE',<br><br>message: 'What is the template type?',<br><br>choices: [<br><br>'normal:【同时创建 .vue .js .[style]】 ',<br><br>'simple: 【只创建 .vue】',<br><br>],<br><br>filter: function (val) {<br><br>return val.split(':')[0] === 'simple' ? true : false;<br><br>},<br> },<br> ];<br> return inquirer.prompt(questions);<br>};<br>const inquirer = require('inquirer');<br><br>const askQuestions = () => {<br> const questions = [<br> {<br><br>name: 'FILENAME',<br><br>type: 'input',<br><br>message: '请输入页面的名称?[支持多级目录,例如:user/login]',<br> },<br> {<br><br>name: 'TITLE',<br><br>type: 'input',<br><br>message: '请输入页面标题(meta.title)',<br> },<br> {<br><br>type: 'list',<br><br>name: 'SIMPLE',<br><br>message: 'What is the template type?',<br><br>choices: [<br><br>'normal:【同时创建 .vue .js .[style]】 ',<br><br>'simple: 【只创建 .vue】',<br><br>],<br><br>filter: function (val) {<br><br>return val.split(':')[0] === 'simple' ? true : false;<br><br>},<br> },<br> ];<br> return inquirer.prompt(questions);<br>};检查用户执行命令时所在的环境 (packages/lib/checkContext.js)检查用户执行命令时所在的环境 (packages/lib/checkContext.js)检查用户执行命令时所在的环境 (packages/lib/checkContext.js)因为我们不确定用户会不会按照我们所期望的方式来使用,所以在这里我们加上一些判断,来确保用户的行为规范,否则就抛出错误,提示用户该怎么使用。主要就是确保用户在项目根目录或者 src 目录路径下执行命令。然后还要确认用户所在项目的目录结构是否符合我们所提供的规范(基本上也是社区的规范)。最后当然还要判断下这个需要添加的页面是否已经存在。<br>const fs = require('fs');<br>const path = require('path');<br>const { error } = require('./util');<br>/**<br> * 检查 用户是否在项目根目录或者./src目录下执行,是否有约定的项目目录结构,是否已经存在该组件<br> * @param {Stirng} name<br> * @param {Object} cmdObj<br> * @return {Object} {destDirRootName ,destDir,rootDir} 目标文件夹名称,目标文件路径,项目所在目录<br> */<br>const checkContext = (name, cmdObj, type) => {<br> // console.log(process.cwd());<br> let destDir, destDirRoot, destDirRootName;<br> const curDir = path.resolve('.');<br> let rootDir = '.';<br> const basename = path.basename(curDir);<br><br> //兼容 用户在 ./src目录下执行该命令<br> if (basename === 'src') {<br> rootDir = path.resolve('..', rootDir);<br> }<br> //判断下项目根目录rootDir下面有没有src目录,如果没有那说明用户没有在正确的路径下执行该命令<br> if (!fs.existsSync(path.join(rootDir, 'src'))) {<br> error(`创建页面失败,请到项目【根目录】或者【@src】目录下执行该操作`);<br> process.exit(1);<br> }<br> // -c<br> if (type === 'component') {<br> //创建一个组件。兼容组件不同的目录名称 支持 src/components src/component 三种任一种<br><br> if (fs.existsSync(path.resolve(rootDir, 'src/components'))) {<br><br>destDir = path.resolve(rootDir, 'src/components', name);<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/component'))) {<br><br>destDir = path.resolve(rootDir, 'src/component', name);<br> } else {<br><br>error('您的通用组件存放文件目录不符合规范,请将其放在 /src/components下');<br> }<br> } else {<br> // 兼容路由页面不同的目录名称 支持 src/views src/pages src/page 三种任一种<br> if (fs.existsSync(path.resolve(rootDir, 'src/views'))) {<br><br>destDir = path.resolve(rootDir, 'src/views', name);<br><br>destDirRootName = 'views';<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/pages'))) {<br><br>destDir = path.resolve(rootDir, 'src/pages', name);<br><br>destDirRootName = 'pages';<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/page'))) {<br><br>destDir = path.resolve(rootDir, 'src/page', name);<br><br>destDirRootName = 'page';<br> } else {<br><br>error(<br><br>'您的页面组件存放文件目录不符合规范,请将其放在 /src/view 或者 /src/pages 或者 /src/page 目录'<br><br>);<br> }<br> }<br><br> //是否已经存在该组件<br> if (<br> (cmdObj.simple && fs.existsSync(destDir + '.vue')) ||<br> (!cmdObj.simple && fs.existsSync(destDir + '/index.vue'))<br> ) {<br> error(`${name} 页面/组件 已经存在,创建失败!`);<br> process.exit(1);<br> }<br> return { destDirRootName, destDir, rootDir };<br>};<br><br>module.exports = checkContext;<br>const fs = require('fs');<br>const path = require('path');<br>const { error } = require('./util');<br>/**<br> * 检查 用户是否在项目根目录或者./src目录下执行,是否有约定的项目目录结构,是否已经存在该组件<br> * @param {Stirng} name<br> * @param {Object} cmdObj<br> * @return {Object} {destDirRootName ,destDir,rootDir} 目标文件夹名称,目标文件路径,项目所在目录<br> */<br>const checkContext = (name, cmdObj, type) => {<br> // console.log(process.cwd());<br> let destDir, destDirRoot, destDirRootName;<br> const curDir = path.resolve('.');<br> let rootDir = '.';<br> const basename = path.basename(curDir);<br><br> //兼容 用户在 ./src目录下执行该命令<br> if (basename === 'src') {<br> rootDir = path.resolve('..', rootDir);<br> }<br> //判断下项目根目录rootDir下面有没有src目录,如果没有那说明用户没有在正确的路径下执行该命令<br> if (!fs.existsSync(path.join(rootDir, 'src'))) {<br> error(`创建页面失败,请到项目【根目录】或者【@src】目录下执行该操作`);<br> process.exit(1);<br> }<br> // -c<br> if (type === 'component') {<br> //创建一个组件。兼容组件不同的目录名称 支持 src/components src/component 三种任一种<br><br> if (fs.existsSync(path.resolve(rootDir, 'src/components'))) {<br><br>destDir = path.resolve(rootDir, 'src/components', name);<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/component'))) {<br><br>destDir = path.resolve(rootDir, 'src/component', name);<br> } else {<br><br>error('您的通用组件存放文件目录不符合规范,请将其放在 /src/components下');<br> }<br> } else {<br> // 兼容路由页面不同的目录名称 支持 src/views src/pages src/page 三种任一种<br> if (fs.existsSync(path.resolve(rootDir, 'src/views'))) {<br><br>destDir = path.resolve(rootDir, 'src/views', name);<br><br>destDirRootName = 'views';<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/pages'))) {<br><br>destDir = path.resolve(rootDir, 'src/pages', name);<br><br>destDirRootName = 'pages';<br> } else if (fs.existsSync(path.resolve(rootDir, 'src/page'))) {<br><br>destDir = path.resolve(rootDir, 'src/page', name);<br><br>destDirRootName = 'page';<br> } else {<br><br>error(<br><br>'您的页面组件存放文件目录不符合规范,请将其放在 /src/view 或者 /src/pages 或者 /src/page 目录'<br><br>);<br> }<br> }<br><br> //是否已经存在该组件<br> if (<br> (cmdObj.simple && fs.existsSync(destDir + '.vue')) ||<br> (!cmdObj.simple && fs.existsSync(destDir + '/index.vue'))<br> ) {<br> error(`${name} 页面/组件 已经存在,创建失败!`);<br> process.exit(1);<br> }<br> return { destDirRootName, destDir, rootDir };<br>};<br><br>module.exports = checkContext;复制模版到目标路径 (packages/lib/copy-template.js)复制模版到目标路径 (packages/lib/copy-template.js)复制模版到目标路径 (packages/lib/copy-template.js)当确认过上下文环境,拿到了用户的输入参数,这个时候我们就可以愉快的进行页面添加工作了,也就是复制我们事先准备好的模版到目标文件。这里需要考虑用户选择的是 normal 还是 simple 类型的根据不同的类型来添加不通的页面模版。当然同时还支持 less,scss 等。 比如用户执行 fc-vue add-page user/login --title=登录页 这个时候将会在 src/views/user/login 下创建初始化的模版文件包括 .js .vue .lessfc-vue add-page user/login --title=登录页src/views/user/login.js .vue .less<br>const shell = require('shelljs');<br>const path = require('path');<br>shell.config.fatal = true;<br><br>/**<br> *<br> * @param {String} destDir 目标文件路径<br> * @param {Boolean} simple<br> * @param {less,scss,sass,stylus} cssType<br> * @return { sourceDir, destFile} 模版原文件,生成的目标文件<br> */<br>const copyTemplate = (destDir, simple, cssType) => {<br> let sourceDir, destFile;<br> // -s<br> if (simple) {<br> //创建一个简单版.vue文件<br> sourceDir = path.resolve(<br><br>__dirname,<br><br>'../../template/vue-page-simple-template.vue'<br> );<br> shell.mkdir('-p', destDir.slice(0, destDir.lastIndexOf('/')));<br> destDir += '.vue';<br> shell.cp('-R', sourceDir, destDir);<br> destFile = destDir;<br> } else {<br> shell.mkdir('-p', destDir);<br> sourceDir = path.resolve(<br><br>__dirname,<br><br>`../../template/vue-page-template-${cssType}/*`<br> );<br> shell.cp('-R', sourceDir, destDir);<br> destFile = path.resolve(destDir, 'index.vue');<br> }<br> return { sourceDir, destFile };<br>};<br><br>module.exports = copyTemplate;<br>const shell = require('shelljs');<br>const path = require('path');<br>shell.config.fatal = true;<br><br>/**<br> *<br> * @param {String} destDir 目标文件路径<br> * @param {Boolean} simple<br> * @param {less,scss,sass,stylus} cssType<br> * @return { sourceDir, destFile} 模版原文件,生成的目标文件<br> */<br>const copyTemplate = (destDir, simple, cssType) => {<br> let sourceDir, destFile;<br> // -s<br> if (simple) {<br> //创建一个简单版.vue文件<br> sourceDir = path.resolve(<br><br>__dirname,<br><br>'../../template/vue-page-simple-template.vue'<br> );<br> shell.mkdir('-p', destDir.slice(0, destDir.lastIndexOf('/')));<br> destDir += '.vue';<br> shell.cp('-R', sourceDir, destDir);<br> destFile = destDir;<br> } else {<br> shell.mkdir('-p', destDir);<br> sourceDir = path.resolve(<br><br>__dirname,<br><br>`../../template/vue-page-template-${cssType}/*`<br> );<br> shell.cp('-R', sourceDir, destDir);<br> destFile = path.resolve(destDir, 'index.vue');<br> }<br> return { sourceDir, destFile };<br>};<br><br>module.exports = copyTemplate;添加路由 (package/lib/add-router.js)添加路由 (package/lib/add-router.js)添加路由 (package/lib/add-router.js)添加页面模版的同时我们希望能够自动配置上路由。其实思路很简单,就是读取 router.js 然后往里面插入用户添加的页面所在的路由。我们约定 src/views 目录下面的组件都是页面级的,也就是说/user/login/index.vue 对应的路由就是/user/login。 比如用户执行 fc-vue add-page user/login --title=登录页 ,那么在 src/router/index.js 里面就会加上一条路由规则,如下(src/router/index.js)fc-vue add-page user/login --title=登录页<br>import Vue from 'vue';<br>import VueRouter from 'vue-router';<br>import Home from '../views/Home.vue';<br>Vue.use(VueRouter);<br>const routes = [<br>******这里有很多其他代码*****<br> {<br><br>path: '/user/login',<br><br>name: 'user/login',<br><br>meta: {<br><br>title: '登录页'<br><br>},<br><br>component: () =><br><br>import(/* webpackChunkName: "user/login" */ './views/user/login/index.vue'),<br> }<br> ];<br><br>const router = new VueRouter({<br> mode: 'history',<br> base: process.env.BASE_URL,<br> routes,<br>});<br><br>export default router;<br>import Vue from 'vue';<br>import VueRouter from 'vue-router';<br>import Home from '../views/Home.vue';<br>Vue.use(VueRouter);<br>const routes = [<br>******这里有很多其他代码*****<br> {<br><br>path: '/user/login',<br><br>name: 'user/login',<br><br>meta: {<br><br>title: '登录页'<br><br>},<br><br>component: () =><br><br>import(/* webpackChunkName: "user/login" */ './views/user/login/index.vue'),<br> }<br> ];<br><br>const router = new VueRouter({<br> mode: 'history',<br> base: process.env.BASE_URL,<br> routes,<br>});<br><br>export default router;回到添加路由配置的实现,packages/lib/add-router.js。<br>const fs = require('fs');<br>const path = require('path');<br>const { promisify } = require('util');<br>const readFile = promisify(fs.readFile);<br>const writeFile = promisify(fs.writeFile);<br><br>/**<br> *<br> * @param {String} name 页面名称<br> * @param {String} rootDir 项目所在目录<br> * @param {Boolean} simple 简单模式<br> * @param {String} destDirRootName 目标文件夹的名称 pages views page<br> * @param {String} title 页面标题<br> */<br>const addRouter = async (name, rootDir, simple, destDirRootName, title) => {<br> let routerPath, pagePath;<br> if (fs.existsSync(path.resolve(rootDir, './src/router.js'))) {<br> routerPath = path.resolve(rootDir, './src/router.js');<br> } else if (fs.existsSync(path.resolve(rootDir, './src/router/index.js'))) {<br> routerPath = path.resolve(rootDir, './src/router/index.js');<br> } else {<br> error(<br><br>'您的项目路由文件不符合规范,请将其放在/src/router.js或者/src/router/index.js'<br> );<br> }<br> pagePath = `./${destDirRootName}/${name}/index.vue`;<br> if (simple) {<br> pagePath = `./${destDirRootName}/${name}.vue`;<br> }<br> try {<br> let content = await readFile(routerPath, 'utf-8');<br> //找到 const routes = 与 ]; 之间的内容,也就是routes数组<br> const reg = /const\s+routes\s*\=([\s\S]*)\]\s*\;/;<br><br> const pathStr = `path: '/${name}',`;<br> const nameStr = `name: '${name}',`;<br> const metaStr = title<br><br>? `meta: {<br><br>title: '${title}'<br><br>},`<br><br>: '';<br> let componentStr = `component: () =><br><br>import(/* webpackChunkName: "${name}" */ '${pagePath}'),`;<br><br> content = content.replace(reg, function (match, $1, index) {<br><br>$1 = $1.trim();<br><br>if (!$1.endsWith(',')) {<br><br>$1 += ',';<br><br>}<br><br>if (title) {<br><br>return `const routes = ${$1}<br> {<br> ${pathStr}<br> ${nameStr}<br> ${metaStr}<br> ${componentStr}<br> }<br>];`;<br><br>} else {<br><br>return `const routes = ${$1}<br> {<br> ${pathStr}<br> ${nameStr}<br> ${componentStr}<br> }<br>];`;<br><br>}<br> });<br> try {<br><br>await writeFile(routerPath, content, 'utf-8');<br> } catch (err) {<br><br>error(err);<br> }<br> } catch (err) {<br> error(err);<br> }<br>};<br><br>module.exports = addRouter;<br>const fs = require('fs');<br>const path = require('path');<br>const { promisify } = require('util');<br>const readFile = promisify(fs.readFile);<br>const writeFile = promisify(fs.writeFile);<br><br>/**<br> *<br> * @param {String} name 页面名称<br> * @param {String} rootDir 项目所在目录<br> * @param {Boolean} simple 简单模式<br> * @param {String} destDirRootName 目标文件夹的名称 pages views page<br> * @param {String} title 页面标题<br> */<br>const addRouter = async (name, rootDir, simple, destDirRootName, title) => {<br> let routerPath, pagePath;<br> if (fs.existsSync(path.resolve(rootDir, './src/router.js'))) {<br> routerPath = path.resolve(rootDir, './src/router.js');<br> } else if (fs.existsSync(path.resolve(rootDir, './src/router/index.js'))) {<br> routerPath = path.resolve(rootDir, './src/router/index.js');<br> } else {<br> error(<br><br>'您的项目路由文件不符合规范,请将其放在/src/router.js或者/src/router/index.js'<br> );<br> }<br> pagePath = `./${destDirRootName}/${name}/index.vue`;<br> if (simple) {<br> pagePath = `./${destDirRootName}/${name}.vue`;<br> }<br> try {<br> let content = await readFile(routerPath, 'utf-8');<br> //找到 const routes = 与 ]; 之间的内容,也就是routes数组<br> const reg = /const\s+routes\s*\=([\s\S]*)\]\s*\;/;<br><br> const pathStr = `path: '/${name}',`;<br> const nameStr = `name: '${name}',`;<br> const metaStr = title<br><br>? `meta: {<br><br>title: '${title}'<br><br>},`<br><br>: '';<br> let componentStr = `component: () =><br><br>import(/* webpackChunkName: "${name}" */ '${pagePath}'),`;<br><br> content = content.replace(reg, function (match, $1, index) {<br><br>$1 = $1.trim();<br><br>if (!$1.endsWith(',')) {<br><br>$1 += ',';<br><br>}<br><br>if (title) {<br><br>return `const routes = ${$1}<br> {<br> ${pathStr}<br> ${nameStr}<br> ${metaStr}<br> ${componentStr}<br> }<br>];`;<br><br>} else {<br><br>return `const routes = ${$1}<br> {<br> ${pathStr}<br> ${nameStr}<br> ${componentStr}<br> }<br>];`;<br><br>}<br> });<br> try {<br><br>await writeFile(routerPath, content, 'utf-8');<br> } catch (err) {<br><br>error(err);<br> }<br> } catch (err) {<br> error(err);<br> }<br>};<br><br>module.exports = addRouter;发布到 npm发布到 npm发布到 npm主要是配置好 package.json 文件。bin 里面定义好 npm 包的入口。<br> "name": "fc-vue",<br> "version": "1.0.6",<br> "bin": {<br> "fc-vue": "bin/index.js"<br> },<br> "name": "fc-vue",<br> "version": "1.0.6",<br> "bin": {<br> "fc-vue": "bin/index.js"<br> },运行npm login 先登录npm publish 发布,每次发布的版本号不能重复安装使用安装使用安装使用<br>$ npm i -g fc-vue<br>$ fc-vue add-page<br>$ npm i -g fc-vue<br>$ fc-vue add-page使用演示<br>结束结束结束这样就实现了一个简单的 fc-vue add-page 功能,是不是很简单。源代码地址: 源代码源代码npm 地址:npmnpm</div> <div class="yuan">原文地址:<a href="http://www.chinasgp.cn/article/3870.html" title="js手把手带你搭建一个node cli的方法示例js大全">http://www.chinasgp.cn/article/3870.html</a></div> <div class="page"> <div class="pl">上一条:<a href="http://www.chinasgp.cn/article/2915.html" target="_blank" class="l" >« js代码JavaScript对象访问器Getter及Setter原理解析</a></div> <div class="pr">下一条:<a class="r" href="http://www.chinasgp.cn/article/4374.html" target="_blank">js详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结js大全 »</a></div> </div> <div class="tags"> <span>标签:</span> <ul class="links inline"> <li class="first last taxonomy_term_6"><a href="http://www.chinasgp.cn/tag/607.html" target="_blank">js</a>  </li> </ul> </div> <div class="mutuality"> <h2 >相关文章:</h2> <ul ><li><a href="http://www.chinasgp.cn/article/3199.html" target="_blank" title="jsvue+elementUI中表格高亮或字体颜色改变操作js大全" >jsvue+elementUI中表格高亮或字体颜色改变操作js大全</a></li><li><a href="http://www.chinasgp.cn/article/3213.html" target="_blank" title="jsantd design table更改某行数据的样式操作js大全" >jsantd design table更改某行数据的样式操作js大全</a></li><li><a href="http://www.chinasgp.cn/article/3212.html" target="_blank" title="js微信小程序实现页面监听自定义组件的触发事件js大全" >js微信小程序实现页面监听自定义组件的触发事件js大全</a></li><li><a href="http://www.chinasgp.cn/article/3211.html" target="_blank" title="js解决antd 表单设置默认值initialValue后验证失效的问题js大全" >js解决antd 表单设置默认值initialValue后验证失效的问题js大全</a></li><li><a href="http://www.chinasgp.cn/article/3210.html" target="_blank" title="js在antd Form表单中select设置初始值操作js大全" >js在antd Form表单中select设置初始值操作js大全</a></li><li><a href="http://www.chinasgp.cn/article/3209.html" target="_blank" title="js微信小程序实现单个或多个倒计时功能js大全" >js微信小程序实现单个或多个倒计时功能js大全</a></li><li><a href="http://www.chinasgp.cn/article/3208.html" target="_blank" title="js在antd4.0中Form使用initialValue操作js大全" >js在antd4.0中Form使用initialValue操作js大全</a></li><li><a href="http://www.chinasgp.cn/article/3207.html" target="_blank" title="jsvue+iview使用树形控件的具体使用js大全" >jsvue+iview使用树形控件的具体使用js大全</a></li><li><a href="http://www.chinasgp.cn/article/3206.html" target="_blank" title="jsVue中使用Echarts仪表盘展示实时数据的实现js大全" >jsVue中使用Echarts仪表盘展示实时数据的实现js大全</a></li><li><a href="http://www.chinasgp.cn/article/3205.html" target="_blank" title="jsReact Ant Design树形表格的复杂增删改操作js大全" >jsReact Ant Design树形表格的复杂增删改操作js大全</a></li></ul> </ul> </div> </div> <div class="nav"> <!--分类--> <div class="con" > <ul class="xm2"> <li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=37" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/contact/">联系我们<span class="article-nums"> (2)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=49" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/javascript/">js开发<span class="article-nums"> (2523)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=38" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/tech/">前端开发<span class="article-nums"> (32)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=46" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/wenan/">朋友圈文案句子<span class="article-nums"> (31)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=47" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/laoshi/">送老师的礼物<span class="article-nums"> (29)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=44" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/chrome/">chrome插件开发<span class="article-nums"> (24)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=43" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/tool/">前端工具<span class="article-nums"> (20)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=45" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/mingzi/">企业名称大全<span class="article-nums"> (16)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=54" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/nodejs/">nodejs开发外包<span class="article-nums"> (15)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=39" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/Software/">软件开发外包<span class="article-nums"> (13)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=52" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/shilao/">网站适老化改造<span class="article-nums"> (13)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=40" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/mobile/">移动app外包<span class="article-nums"> (10)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=41" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/webpage/">网页制作外包<span class="article-nums"> (8)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=53" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/wuzhangai/">网站无障碍服务<span class="article-nums"> (8)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=48" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/lingdao/">大额优惠券<span class="article-nums"> (7)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=42" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/linux/">linux命令<span class="article-nums"> (3)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=50" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/rpa/">rpa数据采集<span class="article-nums"> (3)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=51" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/css/">css代码外包<span class="article-nums"> (1)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=55" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/huyan/">清大光德<span class="article-nums"> (1)</span></a></li><li><span class="feed-icon"><a href="http://www.chinasgp.cn/feed.asp?cate=30" target="_blank"><img title="rss" width="20" height="12" src="http://www.chinasgp.cn/IMAGE/LOGO/rss.png" border="0" alt="rss" /></a> </span><a href="http://www.chinasgp.cn/qianduan/">前端开发外包<span class="article-nums"> (11)</span></a></li> </ul> </div> <!--标签点击--> <div class="con" > <h3 ><b>热门标签</b></h3> <ul class="xm"> <li><a href="http://www.chinasgp.cn/catalog.asp?tags=js">js (2532)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=vue">vue (589)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=js%E4%BB%A3%E7%A0%81">js代码 (432)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=%E5%B0%8F%E7%A8%8B%E5%BA%8F">小程序 (227)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=%E5%BE%AE%E4%BF%A1">微信 (206)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F">微信小程序 (180)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=on">on (126)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=javascript">javascript (84)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=%E6%95%B0%E7%BB%84">数组 (81)</a></li><li><a href="http://www.chinasgp.cn/catalog.asp?tags=ui">ui (59)</a></li> </ul> <div class="clear"></div> </div> <!--最近发表--> <div class="con" > <h3 class="tab"><b class="current">刚刚发布</b><b>热门点击</b><b>发布时间</b></h3> <ul> <li><a href="http://www.chinasgp.cn/article/5528.html" title="chrome 浏览器插件外包:定制您的专属Chrome插件!专业外包团队助您轻松实现创意!"><span class="article-date">[02/23]</span>chrome 浏览器插件外包:定制您的专属Chrome插件!专业外包团队助您轻松实现创意!</a></li><li><a href="http://www.chinasgp.cn/article/5527.html" title="专业电脑浏览器插件外包服务,助力企业数字化转型!"><span class="article-date">[02/23]</span>专业电脑浏览器插件外包服务,助力企业数字化转型!</a></li><li><a href="http://www.chinasgp.cn/article/5526.html" title="解放双手,效率翻倍!Chrome插件前端自动化外包服务,助您轻松实现业务流程自动化!"><span class="article-date">[02/23]</span>解放双手,效率翻倍!Chrome插件前端自动化外包服务,助您轻松实现业务流程自动化!</a></li><li><a href="http://www.chinasgp.cn/article/5525.html" title="定制您的专属Chrome插件!专业外包团队助您轻松实现创意!"><span class="article-date">[02/23]</span>定制您的专属Chrome插件!专业外包团队助您轻松实现创意!</a></li><li><a href="http://www.chinasgp.cn/article/5523.html" title="Chrome插件外包:为你的浏览器赋能,开启无限可能"><span class="article-date">[02/23]</span>Chrome插件外包:为你的浏览器赋能,开启无限可能</a></li><li><a href="http://www.chinasgp.cn/article/5522.html" title="node js 调用kimi 接口批量生成网页"><span class="article-date">[09/06]</span>node js 调用kimi 接口批量生成网页</a></li><li><a href="http://www.chinasgp.cn/article/5521.html" title="Node.js数据采集外包服务-数据采集专家-用Node.js释放数据的力量"><span class="article-date">[07/21]</span>Node.js数据采集外包服务-数据采集专家-用Node.js释放数据的力量</a></li><li><a href="http://www.chinasgp.cn/article/5520.html" title="专业前端开发-从PSD到响应式网页的无缝转换"><span class="article-date">[07/21]</span>专业前端开发-从PSD到响应式网页的无缝转换</a></li><li><a href="http://www.chinasgp.cn/article/5519.html" title="专业Chrome插件开发- 为您的业务插上翅膀"><span class="article-date">[07/18]</span>专业Chrome插件开发- 为您的业务插上翅膀</a></li><li><a href="http://www.chinasgp.cn/article/5518.html" title="智能数据抓取 -浏览器插件接口抓取服务外包"><span class="article-date">[07/18]</span>智能数据抓取 -浏览器插件接口抓取服务外包</a></li><li><a href="http://www.chinasgp.cn/article/5517.html" title="vue3 setup 监听url变化"><span class="article-date">[07/17]</span>vue3 setup 监听url变化</a></li><li><a href="http://www.chinasgp.cn/article/5516.html" title="git 回滚到指定 commit"><span class="article-date">[07/17]</span>git 回滚到指定 commit</a></li><li><a href="http://www.chinasgp.cn/article/5515.html" title="浏览器插件使用场景:浏览器插件在数据抓取方面的使用场景非常广泛"><span class="article-date">[07/15]</span>浏览器插件使用场景:浏览器插件在数据抓取方面的使用场景非常广泛</a></li><li><a href="http://www.chinasgp.cn/article/5514.html" title="Chrome 浏览器插件的使用场景"><span class="article-date">[07/15]</span>Chrome 浏览器插件的使用场景</a></li><li><a href="http://www.chinasgp.cn/article/5513.html" title="Chrome 浏览器插件开发的特点"><span class="article-date">[07/15]</span>Chrome 浏览器插件开发的特点</a></li> </ul> <ul class="hidden"> <li><a href="http://www.chinasgp.cn/article/5528.html" title="[2025-2-23 17:16:48] chrome 浏览器插件外包:定制您的专属Chrome插件!专业外包团队助您轻松实现创意!">chrome 浏览器插件外包:定制您的...</a></li><li><a href="http://www.chinasgp.cn/article/5525.html" title="[2025-2-23 10:8:45] 定制您的专属Chrome插件!专业外包团队助您轻松实现创意!">定制您的专属Chrome插件!专业外包...</a></li><li><a href="http://www.chinasgp.cn/article/5523.html" title="[2025-2-23 10:3:31] Chrome插件外包:为你的浏览器赋能,开启无限可能">Chrome插件外包:为你的浏览器赋能...</a></li><li><a href="http://www.chinasgp.cn/article/5527.html" title="[2025-2-23 11:2:32] 专业电脑浏览器插件外包服务,助力企业数字化转型!">专业电脑浏览器插件外包服务,助力...</a></li><li><a href="http://www.chinasgp.cn/article/5526.html" title="[2025-2-23 10:48:14] 解放双手,效率翻倍!Chrome插件前端自动化外包服务,助您轻松实现业务流程自动化!">解放双手,效率翻倍!Chrome插件前...</a></li><li><a href="http://www.chinasgp.cn/article/5522.html" title="[2024-9-6 16:21:32] node js 调用kimi 接口批量生成网页">node js 调用kimi 接口批量生成网...</a></li> </ul> <ul class="hidden"> <li><a href="http://www.chinasgp.cn/article/2025_2.html">2025 February (5)</a></li><li><a href="http://www.chinasgp.cn/article/2024_9.html">2024 September (1)</a></li><li><a href="http://www.chinasgp.cn/article/2024_7.html">2024 July (9)</a></li><li><a href="http://www.chinasgp.cn/article/2024_5.html">2024 May (5)</a></li><li><a href="http://www.chinasgp.cn/article/2023_7.html">2023 July (5)</a></li><li><a href="http://www.chinasgp.cn/article/2022_12.html">2022 December (1)</a></li><li><a href="http://www.chinasgp.cn/article/2022_7.html">2022 July (16)</a></li><li><a href="http://www.chinasgp.cn/article/2022_6.html">2022 June (2)</a></li><li><a href="http://www.chinasgp.cn/article/2022_1.html">2022 January (4)</a></li><li><a href="http://www.chinasgp.cn/article/2021_12.html">2021 December (6)</a></li> </ul> </div> <div class="con"> <h3><b>友情链接</b></h3> <ul class="xm"> <li><a href="http://www.35ui.cn/" target="_blank" title="web前端">web网页外包</a></li> <li><a href="http://www.chinahegu.cn/" target="_blank" title="北京前端">北京前端</a></li> <li><a href="http://www.hnn8.com/ " target="_blank" title="北京seo顾问">北京seo顾问</a></li> <li><a href="/class/" target="_blank" title="外包项目服务">外包项目服务</a></li> <li><a href="/ruanjian/" target="_blank">软件外包</a></li> <li><a href="/wangye/" target="_blank">做网页外包</a></li> <li><a href="/xiao/" target="_blank">小程序外包</a></li> <li><a href="/design/" target="_blank">网页设计外包</a></li> <li><a href="/yuruarea/" target="_blank">羽绒服批发</a></li> <li><a href="http://zuopin.35ui.cn/kaishuo/" target="_blank">开锁网站</a></li> <li><a href="http://zuopin.35ui.cn/banjia/" target="_blank">搬家公司</a></li> <li><a href="http://zuopin.35ui.cn/taiyang/" target="_blank">太阳能路灯</a></li> <li><a href="http://www.chinasgp.cn/shilaoarea/">适老化外包</a></li> <li><a href="https://www.toyixin.cn/" target="_blank" title="大额优惠券">大额优惠券</a></li> <li><a href="http://tool.chinasgp.cn/" target="_blank" title="大小写转换">大小写转换</a></li> <li><a href="/guangde/" target="_blank">清大光德护眼</a></li> <li><a href="http://www.chinasgp.cn/html/" target="_blank">天猫优惠券</a></li> <li><a href="/gouliang/" target="_blank">淘宝狗粮</a></li> <li><a href="/taosuo/" target="_blank">智能锁批发</a> </ul> </div> </div> </div> <div style="clear:both"></div> <div style="width: 960px; margin: 20px auto; clear: both;"><a href="http://www.35ui.cn/post/20200406323.html"><img src="http://www.35ui.cn/images/contentAd960.jpg"></a></div> <div class="sub-box"> <div class="list"> <h2>前端技术</h2> <a class="more" href="/tech/">[MORE]</a> <ul> <li><a href="http://www.chinasgp.cn/article/5517.html" title="[2024-7-17 11:16:51] vue3 setup 监听url变化">vue3 setup 监听url变化</a></li><li><a href="http://www.chinasgp.cn/article/5485.html" title="[2022-6-19 10:36:29] JS获取时间戳的几种方式,js时间转时间戳">JS获取时间戳的几种方式,js时间转...</a></li><li><a href="http://www.chinasgp.cn/article/5460.html" title="[2021-4-30 15:34:19] 前端加密的几种方式总结前端加密/解密">前端加密的几种方式总结前端加密...</a></li><li><a href="http://www.chinasgp.cn/article/2914.html" title="[2020-11-30 9:46:22] vue如何优化seo?单页面vue如何seo?vue如何支持seo">vue如何优化seo?单页面vue如何se...</a></li><li><a href="http://www.chinasgp.cn/article/2901.html" title="[2020-10-31 23:9:10] 前端seo怎么优化? 前端如何进行seo">前端seo怎么优化? 前端如何进行s...</a></li><li><a href="http://www.chinasgp.cn/article/2847.html" title="[2020-6-30 22:32:23] HTTP Error 503. The service is unavailable.">HTTP Error 503. The service is...</a></li><li><a href="http://www.chinasgp.cn/article/2827.html" title="[2020-5-21 12:13:52] js捕获键盘事件|js键盘监听事件的代码|js键盘事件怎么用"> js捕获键盘事件|js键盘监听事件...</a></li><li><a href="http://www.chinasgp.cn/article/2826.html" title="[2020-5-18 21:56:3] websocket在vue项目的封装和使用|vue websocket封装">websocket在vue项目的封装和使用...</a></li><li><a href="http://www.chinasgp.cn/article/2821.html" title="[2020-5-7 14:6:27] JSON.stringify()的几种妙用">JSON.stringify()的几种妙用</a></li><li><a href="http://www.chinasgp.cn/article/2815.html" title="[2020-4-21 15:42:43] js数组操作哪些可以改变原数组">js数组操作哪些可以改变原数组</a></li></ul> </div> <div class="list"> <h2>前端开发外包</h2> <a class="more" href="/qianduan/">[MORE]</a> <ul> <li><a href="http://www.chinasgp.cn/article/5520.html" title="[2024-7-21 18:56:49] 专业前端开发-从PSD到响应式网页的无缝转换">专业前端开发-从PSD到响应式网页...</a></li><li><a href="http://www.chinasgp.cn/article/2822.html" title="[2020-5-9 18:37:28] 前端静态页面兼职个人接单,前端静态页面兼职">前端静态页面兼职个人接单,前端静...</a></li><li><a href="http://www.chinasgp.cn/article/2801.html" title="[2019-8-25 9:22:32] css动画或者文字上下来回循环上下移动">css动画或者文字上下来回循环上下...</a></li><li><a href="http://www.chinasgp.cn/article/2773.html" title="[2018-9-14 18:32:24] 静态html网页制作外包长期合作">静态html网页制作外包长期合作</a></li><li><a href="http://www.chinasgp.cn/article/2772.html" title="[2018-9-14 17:46:56] css外包 css自适应外包 css优化外包">css外包 css自适应外包 css优化...</a></li><li><a href="http://www.chinasgp.cn/article/2771.html" title="[2018-9-12 16:50:14] psd转html模版网页切图外包"> psd转html模版网页切图外包</a></li><li><a href="http://www.chinasgp.cn/article/2770.html" title="[2018-9-12 16:33:56] 网页切图外包|网页切图外包规范都有哪些?">网页切图外包|网页切图外包规范都...</a></li><li><a href="http://www.chinasgp.cn/article/2765.html" title="[2018-7-10 21:36:43] 前端切图外包-什么是切图?">前端切图外包-什么是切图?</a></li><li><a href="http://www.chinasgp.cn/article/2755.html" title="[2018-7-4 11:6:37] 前端开发外包工作模式"> 前端开发外包工作模式</a></li><li><a href="http://www.chinasgp.cn/article/2753.html" title="[2018-7-2 11:51:29] 前端外包服务流程">前端外包服务流程</a></li></ul> </div> <div class="list" style="margin-right:0px;"> <h2>软件开发外包</h2> <a class="more" href="/Software/">[MORE]</a> <ul> <li><a href="http://www.chinasgp.cn/article/5491.html" title="[2022-7-11 20:37:15] sass是什么意思 sass软件是什么意思">sass是什么意思 sass软件是什么意...</a></li><li><a href="http://www.chinasgp.cn/article/2809.html" title="[2019-12-18 22:53:56] 浏览器插件开发外包_火狐扩展Firefox extension开发流程">浏览器插件开发外包_火狐扩展Fir...</a></li><li><a href="http://www.chinasgp.cn/article/2784.html" title="[2018-11-21 7:24:35] 微信小程序外包开发,微信小程序开发外包">微信小程序外包开发,微信小程序开...</a></li><li><a href="http://www.chinasgp.cn/article/2783.html" title="[2018-11-21 7:8:37] 公司找外包开发一个微信小程序大概要多少钱"> 公司找外包开发一个微信小程序大...</a></li><li><a href="http://www.chinasgp.cn/article/2775.html" title="[2018-9-20 20:48:34] 知名北京软件外包公司都有哪些?">知名北京软件外包公司都有哪些?</a></li><li><a href="http://www.chinasgp.cn/article/2764.html" title="[2018-7-9 21:52:54] 【微信小程序外包】小程序外包定制微信小程序外包开发">【微信小程序外包】小程序外包定制...</a></li><li><a href="http://www.chinasgp.cn/article/2763.html" title="[2018-7-9 21:46:19] 手机网站制作-北京手机网站设计外包">手机网站制作-北京手机网站设计外...</a></li><li><a href="http://www.chinasgp.cn/article/2762.html" title="[2018-7-9 21:35:21] app开发订制-app开发公司">app开发订制-app开发公司</a></li><li><a href="http://www.chinasgp.cn/article/2761.html" title="[2018-7-9 21:31:17] 北京软件外包公司代理记账管理系统">北京软件外包公司代理记账管理系...</a></li><li><a href="http://www.chinasgp.cn/article/2760.html" title="[2018-7-9 21:26:1] 北京软件外包会务管理系统">北京软件外包会务管理系统</a></li></ul> </div> </div> <div style="clear:both"></div> <div class="about-box"> <div class="about-content"> <div class="index-title"> <h2>你有充足的理由选择我们</h2> <span class="greenLine"></span> <p><a href="/area/shenzhenshi.html" target="_blank">深圳</a>、<a href="/area/shanghai.html" target="_blank">上海</a>、<a href="/area/hangzhou.html" target="_blank">杭州</a>、<a href="/area/" target="_blank">北京前端外包</a>开发:工作10年以上的小伙伴团队,<a href="/area/" target="_blank">前端开发</a>工作是我们最大的事业。所有您担心的问题,都可以写到合同里。我们会100%努力完成,直到您满意!</p> </div> <ul> <li> <i class="fa"></i> <strong>高端专业开发团队</strong> <p>10年北京前端外包开发经验,可提供最专业<a href="/article/2792.html" target="_blank">定制ui设计</a>与<a href="/article/2773.html" target="_blank">前端开发</a></p> </li> <li> <i class="fa"></i> <strong>兼容各种设备</strong> <p>每次合作都是我们的<a href="http://zuopin.35ui.cn" target="_blank">经典案例</a>,<a href="/search.asp?q=静态网页模板">静态网页模板</a>、<a href="/search.asp?q=seo优化">seo优化</a>PC、Pad和各种手机端用户体验</p> </li> <li> <i class="fa"></i> <strong>我们的承诺</strong> <p>北京前端外包<a href="/search.asp?q=兼职">兼职</a><a href="/search.asp?q=私活">私活</a>:<a href="/search.asp?q=前端工程师">前端工程师</a>用100%努力去完成您交给我们的需求</p> </li> <li> <i class="fa"></i> <strong>靠谱售后服务</strong> <p>约定服务期内的,7*24小时随时邮件\电话支持</p> </li> </ul> </div> </div> <div class="footer"> <div class="bottommenu"> <a href="/" target="_blank">首页</a> <a href="/qianduan/" target="_blank">前端开发外包</a> <a href="/mobile/" target="_blank">移动app外包</a> <a href="/article/2757.html" target="_blank">切图外包</a> <a href="/article/2764.html" target="_blank">小程序外包</a> <a href="/article/2792.html" target="_blank">ui设计外包</a> <a href="/article/2757.html" >ps网页切图</a> <a href="/Software/" target="_blank">软件开发外包</a> <a href="/tags.asp" target="_blank">热门标签</a> <a href="/help.html"target="_blank">常见问题</a> <a target="_blank" href="/sitemap.xml">sitemap</a> </div> Copyright <a href="http://www.chinasgp.cn" target="_blank">北京前端外包|北京前端开发外包|北京前端兼职私活</a> chinasgp.cn 站点支持:<a href="http://www.35ui.cn" target="_blank">35ui</a>.cn<br>js手把手带你搭建一个node cli的方法示例js大全-<a href="/search.asp?q=js开发">js开发</a>-北京前端外包,<a href="/search.asp?q=北京前端">北京前端</a>外包、<a href="/search.asp?q=前端兼职">前端兼职</a> </div> <script src="/SCRIPT/jquery.js?v=2012"></script> <script src="/THEMES/m/js/base.js?v=2012"></script> <script language="JavaScript" type="text/javascript"> /* jQuery 1.1 API used */ var RevertID=0; $(document).ready(function(){ try{ var elScript = document.createElement("script"); elScript.setAttribute("language", "JavaScript"); elScript.setAttribute("src", "/function/c_html_js.asp?act=batch"+unescape("%26")+"view=" + escape(strBatchView)+unescape("%26")+"inculde=" + escape(strBatchInculde)+unescape("%26")+"count=" + escape(strBatchCount)); document.getElementsByTagName("body")[0].appendChild(elScript); } catch(e){}; if(document.getElementById("inpVerify")){ var objImageValid=$("img[@src^='"+str00+"function/c_validcode.asp?name=commentvalid']"); objImageValid.css("cursor","pointer"); objImageValid.click( function() { objImageValid.attr("src",str00+"function/c_validcode.asp?name=commentvalid"+"&random="+Math.random()); } ); } }); </script> <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> </body> </html> <!-- 2025/4/30 10:11:28 -->