vue3 uses the plop template directive to generate pages

1. Create a new vue3 project with vue-cli

vue-cli requires version 3.0, you can use vue -V to view the version, node requires version 16 and above (node: v16.20.2, npm: 8.19.4
, vue-cli: 5.0.8)

Create a new vue3 project named test3-vue

vue create test1-vue3

After the new creation is successful, enter the project folder.

cd test1-vue3

2. Install plop

According to the yarn or npm selected when creating a new project in the first step, use the corresponding instructions to install (version: ^4.0.0)

 // yarn
yarn add plop --dev
// npm
npm install plop

3. Install other plug-ins (inquirer-file-path, read-metadata)

//inquirer-file-path plugin can use directives to select file names (version: ^1.0.1)
npm install --save inquirer-file-path
// read-metadata gets the content of the json file based on the address (version: ^1.0.0)
npm install read-metadata

The vue2 project can use the inquirer-parse-json-file plug-in to replace these two plug-ins.

The purpose of these two plug-ins is to select the configuration file in the command and merge the content in the configuration file with the command line content and save it in the configuration item. The configuration file: plop_template/config mainly stores the different data content of each page. For example, query parameters in the list page, display items in the table, interface names and other information.

4. Configure the startup plop command in package.json

Add: “plop”: “plop” to scripts


5. Create a new plop_template folder

Table of contents:

config: json file, configuration file with specific data for each page
default.json content:

{<!-- -->
  "model":[{<!-- -->
    "prop":"title",
    "label":"title"
  },{<!-- -->
    "prop":"sub_title",
    "label":"subtitle"
  },{<!-- -->
    "prop":"type_id",
    "label":"type"
  },{<!-- -->
    "prop":"enabled",
    "label":"Enable",
    "type":"Boolean"
  },{<!-- -->
    "prop":"remark",
    "label":"Remarks",
    "type":"textarea",
    "mode":["form"],
    "placeholder":"placeholder content",
    "default":"This is a note"

  }],
  "api":{<!-- -->
    "list": "/2685-11",
    "view": "/2685-21",
    "add": "/2685-2",
    "edit": "/2685-3",
    "del": "/2685-3"
  },
  "query":[{<!-- -->
    "prop":"title",
    "placehold":"Search placehold"
  }]
}

script: some helper code methods that need to be used in templates

module.exports = function (plop) {<!-- -->
  plop.setHelper('ifcode', function (listitem, opts) {<!-- -->
    if (!listitem.mode || listitem.mode.find(item => item === 'table')) {<!-- --> // Whether to display
      if (listitem.isview) {<!-- --> // If there is a details page, click the link column
        return opts.fn(this)
      } else {<!-- -->
        return opts.inverse(this)
      }
    } else {<!-- -->
      return ''
    }
  })
  plop.setHelper('difcode', function (val, opts) {<!-- -->
    if (!val || val.find(item => item === 'form')) {<!-- -->
      return opts.fn(this)
    } else {<!-- -->
      return opts.inverse(this)
    }
  })
  plop.setHelper('modetype', function (val, opts) {<!-- -->
    let result = ''
    switch (val.type) {<!-- -->
      case 'select':
        result = '<el-select v-model="listdetail.' + val.prop + '" :disabled="editdisable" placeholder="' + val.placeholder + '" class="w-full mr-3">' +
          '<el-option label="option" value="1" /></el-select>'
        break
      case 'textarea':
        result = '<el-input v-model="listdetail.' + val.prop + '" :disabled="editdisable" type="textarea" placeholder="' + val .placeholder + '" />'
        break
      case 'number':
        result = '<el-input v-model="listdetail.' + val.prop + '" :disabled="editdisable" type="number" placeholder="' + val .placeholder + '" />'
        break
      default:
        result = '<el-input v-model="listdetail.' + val.prop + '" :disabled="editdisable" placeholder="' + val.placeholder + '" />'
        break
    }
    return result
  })
  plop.setHelper('eachIndex', function (context, options) {<!-- -->
    let ret = ''
    for (let i = 0, j = context.length; i < j; i + + ) {<!-- -->
      ret = ret + options.fn({<!-- --> it: context[i], isLast: i == context.length - 1 })
    }
    return ret
  })
  plop.setHelper('rulesIndex', function (context, options) {<!-- -->
    let ret = ''
    const newliat = []
    for (let i = 0, j = context.length; i < j; i + + ) {<!-- -->
      if (context[i].isrequired) {<!-- -->
        newliat.push(context[i])
      }
    }
    for (let ii = 0, j = newliat.length; ii < j; ii + + ) {<!-- -->
      ret = ret + options.fn({<!-- --> it: newliat[ii], isLast: ii == newliat.length - 1 })
    }
    return ret
  })
}

template: the template required to generate the page, detail.hbs: details page, list.hbs list page

detail.hbs page content:

<template>
  <div>
    <el-drawer
      :title="!listdetail?'Add data':'Modify data'"
      :modal="false"
      size="30%"
      custom-class="absolute"
      :modal-append-to-body="false"
      :append-to-body="false"
      :wrapper-closable="false"
      :visible.sync="drawershow"
      direction="rtl"
      :before-close="handleClose"
    >
      <div class="pl-5 pr-5">
        <el-form ref="form" label-position="right" label-width="80px" :model="form" :rules="rules">
          {<!-- -->{<!-- -->#each config.model}}
          {<!-- -->{<!-- -->#difcode mode}}
          <el-form-item label="{<!-- -->{label}}"{<!-- -->{<!-- -->#if isrequired}} prop="{ <!-- -->{prop}}"{<!-- -->{<!-- -->/if}}>
            {<!-- -->{<!-- -->#modetype this}}{<!-- -->{<!-- -->/modetype}}
          </el-form-item>
          {<!-- -->{<!-- -->/difcode}}
          {<!-- -->{<!-- -->/each}}
          <el-form-item class="text-center">
            <el-button v-if="editdisable" type="primary" @click="formdisable = false">
              <span>Modify</span>
            </el-button>
            <el-button v-if="!editdisable" type="primary" @click="drawersubmit('form')">
              <span>\{<!-- -->{<!-- --> !listdetail?'Confirm addition':'Confirm modification' }}</span>
            </el-button>
            <el-button @click="handleClose">
              Cancel
            </el-button>
          </el-form-item>
        </el-form>
      </div>
    </el-drawer>
  </div>
</template>
<script>
  import {<!-- --> clone } from 'lodash'
export default {<!-- -->
  props: {<!-- -->
    listdetail: {<!-- -->
      type: [Object, Boolean]
    },
    drawershow: {<!-- -->
      type: Boolean,
      default: false
    }
  },
  data () {<!-- -->
    return {<!-- -->
      form: {<!-- -->},
      formdisable: true,
      rules: {<!-- -->
      {<!-- -->{<!-- -->#rulesIndex config.model}}
      {<!-- -->{<!-- -->#if it.isrequired}}
        {<!-- -->{<!-- -->it.prop}}: [
          {<!-- --> required: true, trigger: 'blur', message: 'Please enter {<!-- -->{it.label}}' }
        ]{<!-- -->{<!-- -->#if isLast}}{<!-- -->{<!-- -->else}},{<!-- --> {<!-- -->/if}}
      {<!-- -->{<!-- -->/if}}
      {<!-- -->{<!-- -->/rulesIndex}}
      }
    }
  },
  computed: {<!-- -->
    editdisable: {<!-- -->
      get () {<!-- -->
        return this.listdetail & amp; & amp; this.formdisable
      },
      set (val) {<!-- -->}
    }
  },
  watch: {<!-- -->
    listdetail: {<!-- -->
      handler (cval, oval) {<!-- --> // Monitor incoming data changes (if the type does not change, there is no way to recalculate editdisable if the parameters are changed)
        this.initForm(cval)
        if (oval._id !== cval._id) {<!-- --> // If the data changes, editdisable needs to be reset to true and the input box is changed to a non-inputable state.
          this.formdisable = true
        }
        if (this.$refs.form) {<!-- -->
          this.$refs.form.resetFields()
        }
      }
    },
    drawershow: {<!-- --> // Maybe the parent has changed and you need to do initForm
      handler (cval, oval) {<!-- -->
        if (!cval) {<!-- -->
          this.initForm(false)
        }
      }
    }
  },
  methods: {<!-- -->
    initForm (obj) {<!-- -->
      if (this.$refs.form) {<!-- -->
        this.$refs.form.resetFields()
      }
      this.editdisable = !!obj
      if (obj) {<!-- -->
        console.log(obj)
        this.form = clone(this.listdetail)
      } else {<!-- -->
        this.form = {<!-- -->}
      }
    },
    drawersubmit (formName) {<!-- -->
      this.$refs[formName].validate((valid) => {<!-- -->
        if (valid) {<!-- -->
          this.$emit('submit', this.form)
        }
      })
    },
    handleClose () {<!-- -->
      this.$emit('close')
    }
  }
}
</script>
<style scoped>
</style>

5. Configure the plopfile.js file

const helpler = require('./plop_template/script/helper') //Introduce the helper file
const inquirerDirectory = require('inquirer-file-path') // Select file
const read = require('read-metadata'); // Read json data from json

module.exports = function (plop) {<!-- -->
  helper(plop)
  plop.setPrompt('directory', inquirerDirectory)
  plop.setGenerator('page', {<!-- --> // The test here is a name set by yourself, which will be used when executing the command line
    description: 'Paging, adding, deleting, checking and modifying', // Here is the functional description of this plop
    prompts: [
      {<!-- -->
        type: 'confirm', // type of question
        name: 'haspagelist', // The variable name corresponding to the answer to the question, which can be used in actions
        message: 'Do you need paging:', // Problem in the command line
        default: true //Default answer to question
      },
      {<!-- -->
        type: 'input', // type of question
        name: 'filePath', // The variable name corresponding to the question and the answer, which can be used in actions
        message: 'Generate file in page directory:', // Problem in command line
        suffix: 'pages/',
        default: 'index.vue' //Default answer to question
      },
      {<!-- -->
        type: 'confirm', // type of question
        name: 'hasDetail', // The variable name corresponding to the answer to the question, which can be used in actions
        message: 'Do you need a details drawer:', // Problem on the command line
        default: true //Default answer to question
      },
      {<!-- -->
        type: 'directory', // type of question
        name: 'config', // The variable name corresponding to the answer to the question, which can be used in actions
        message: 'Select configuration file', // Problem in command line
        basePath: 'plop_template/config',
        default: {<!-- -->}, //Default answer to the question
      }
    ],
    actions: (data) => {<!-- -->
      read(`plop_template/config/${<!-- -->data.config}`, function(err, res){<!-- -->
        // Because the inquirer-parse-json-file plug-in is incompatible and no suitable plug-in was found, we had to get the file name and then manually get the file content.
        data.config = res
      });
      const path = `pages/${<!-- -->data.filePath}`
      const folder = path.substring(0, path.lastIndexOf('/'))
      const actions = [
        {<!-- -->
          type: 'add', // Operation type, here is adding files
          path, // path generated by template
          templateFile: 'plop_template/template/page/list.hbs', // The path of the template
          data
        }
      ]
      if (data.hasDetail) {<!-- -->
        actions.push({<!-- -->
          type: 'add', // Operation type, here is adding files
          path: `${<!-- -->folder}/-detail.vue`, // The path generated by the template
          templateFile: 'plop_template/template/page/-detail.hbs', // The path to the template
          data
        })
      }
      return actions
    }
  })
}

6. Run plop

npm run plop

Make a selection based on the prompt questions

The final generated file directory

Generated file content of detail.vue

<template>
  <div>
    <el-drawer
      :title="!listdetail?'Add data':'Modify data'"
      :modal="false"
      size="30%"
      custom-class="absolute"
      :modal-append-to-body="false"
      :append-to-body="false"
      :wrapper-closable="false"
      :visible.sync="drawershow"
      direction="rtl"
      :before-close="handleClose"
    >
      <div class="pl-5 pr-5">
        <el-form ref="form" label-position="right" label-width="80px" :model="form" :rules="rules">
          <el-form-item label="title">
            <el-input v-model="listdetail.title" :disabled="editdisable" placeholder="undefined" />
          </el-form-item>
          <el-form-item label="subtitle">
            <el-input v-model="listdetail.sub_title" :disabled="editdisable" placeholder="undefined" />
          </el-form-item>
          <el-form-item label="type">
            <el-input v-model="listdetail.type_id" :disabled="editdisable" placeholder="undefined" />
          </el-form-item>
          <el-form-item label="Enable">
            <el-input v-model="listdetail.enabled" :disabled="editdisable" placeholder="undefined" />
          </el-form-item>
          <el-form-item label="Remarks">
            <el-input v-model="listdetail.remark" :disabled="editdisable" type="textarea" placeholder="placeholder content" />
          </el-form-item>
          <el-form-item class="text-center">
            <el-button v-if="editdisable" type="primary" @click="formdisable = false">
              <span>Modify</span>
            </el-button>
            <el-button v-if="!editdisable" type="primary" @click="drawersubmit('form')">
              <span>{<!-- -->{<!-- --> !listdetail?'Confirm addition':'Confirm modification' }}</span>
            </el-button>
            <el-button @click="handleClose">
              Cancel
            </el-button>
          </el-form-item>
        </el-form>
      </div>
    </el-drawer>
  </div>
</template>
<script>
  import {<!-- --> clone } from 'lodash'
export default {<!-- -->
  props: {<!-- -->
    listdetail: {<!-- -->
      type: [Object, Boolean]
    },
    drawershow: {<!-- -->
      type: Boolean,
      default: false
    }
  },
  data () {<!-- -->
    return {<!-- -->
      form: {<!-- -->},
      formdisable: true,
      rules: {<!-- -->
      }
    }
  },
  computed: {<!-- -->
    editdisable: {<!-- -->
      get () {<!-- -->
        return this.listdetail & amp; & amp; this.formdisable
      },
      set (val) {<!-- -->}
    }
  },
  watch: {<!-- -->
    listdetail: {<!-- -->
      handler (cval, oval) {<!-- --> // Monitor incoming data changes (if the type does not change, there is no way to recalculate editdisable if the parameters are changed)
        this.initForm(cval)
        if (oval._id !== cval._id) {<!-- --> // If the data changes, editdisable needs to be reset to true and the input box is changed to a non-inputable state.
          this.formdisable = true
        }
        if (this.$refs.form) {<!-- -->
          this.$refs.form.resetFields()
        }
      }
    },
    drawershow: {<!-- --> // Maybe the parent has changed and you need to do initForm
      handler (cval, oval) {<!-- -->
        if (!cval) {<!-- -->
          this.initForm(false)
        }
      }
    }
  },
  methods: {<!-- -->
    initForm (obj) {<!-- -->
      if (this.$refs.form) {<!-- -->
        this.$refs.form.resetFields()
      }
      this.editdisable = !!obj
      if (obj) {<!-- -->
        console.log(obj)
        this.form = clone(this.listdetail)
      } else {<!-- -->
        this.form = {<!-- -->}
      }
    },
    drawersubmit (formName) {<!-- -->
      this.$refs[formName].validate((valid) => {<!-- -->
        if (valid) {<!-- -->
          this.$emit('submit', this.form)
        }
      })
    },
    handleClose () {<!-- -->
      this.$emit('close')
    }
  }
}
</script>
<style scoped>
</style>