plpo vue实战版教程

什么是plpo

它是一个命令行工具,专门用于构建生成器,这些生成器可以帮助开发者快速生成代码模板,特别是对于大型的后台管理系统,页面很多相似的内容,重复率很高的项目,我们可以设立一个模板(列表、详情、路由等)(vue、js、css文件等’),一次构建重复创建。

安装

1.将plop添加到您的项目

 npm install --save-dev plop
  • ps: 如果你没有全局安装plop,你需要在设置一个 npm 脚本命令来为你运行polp:
//package.json
{
	...,
	"scripts":{
		"plop":"plop"
	},
	...
}

2.全局安装plop(可选,但建议使用方便)

npm install -g plop

3.在项目根目录下创建一个 plopfile.js

创建一个基本的生成器

// plop 的入口文件 plopfile.js
// 需要导出一个函数,函数接收一个plop对象,用于创建生成器任务
module.exports = plop => {
  // setGenerator可以设置一个生成器,每个生成器都可用于生成特定的文件
  // 接收两个参数,生成器的名称和配置选项
  plop.setGenerator('component', {
    // 生成器的描述
    description: 'create a component',
    
    // 发起命令行询问(将来生成器工作时发起的询问,它是一个数组,每个对象都是一次询问)
    prompts: [{
      // 类型
      type: 'input',
      // 接收变量的参数
      name: 'name',
      // 询问提示信息
      message: 'component name',
      // 默认值
      default: 'MyComponent'
    }],
    
    // 完成命令行后执行的操作,每个对象都是动作对象
    actions: [{
      // 动作类型
      type: 'add',
      // 生成文件的输出路径
      path: 'src/views/${name}/list/index.vue',
      // template 模板的文件路径,目录下的文件遵循hbs的语法规则
      templateFile: 'plopTemplate/view/list/index.vue'
    }]
  })
}


  • actions 之 path
path中的{{name}}是一种变量书写形式,它对应的就是prompts对象数组中的name接收的值
假如 name='Test1',那这里的path就相当于src/views/Test1/list/index.vue
  • actions 之 templateFile

templateFile 就是该文件的模板文件路径
在这里插入图片描述
我们在模板中同样可以使用{{变量名称}},的方式来传入我们在prompts中接收的变量

在这里插入图片描述
多个动作
同样的,如果我们希望生成多个文件,就可以配置多个动作对象和对应的模板即可即可,如

  actions: [{
    type: "add",
    path: `src/views/${name}/list/index.vue`,
    templateFile: "plopTemplate/view/list/index.vue",
  }, {
    type: "add",
    path: `src/views/${name}/detail/index.vue`,
    templateFile: "plopTemplate/view/detail/index.vue",
  }]

vue 实战(后台管理系统 - 增删改查)

所需文件

在这里插入图片描述

文件介绍

在项目跟目录下,创建配置文件 plopfile.js,内容如下:

创建配置文件 plopfile.js

const viewGenerator = require("./plopTemplate/prompt");
module.exports = (plop) => {
  plop.setGenerator("create", viewGenerator);
};

plopfile.js 中导出一个函数,该函数接受 plop 对象作为它的第一个参数;
plop 对象公开包含 setGenerator(name, config)函数的 plop api 对象。

创建模板和脚本命令文件

在项目根目录下新建文件夹(如plopTemplates),放置模板(view/list view/detail文件)和脚本命令(prompt.js)文件

模板创建逻辑

prompt.js脚本命名如下,提示语和创建动作
1.手动输入页面名称
2.是否添加增改组件
3.选择是(选择添加抽屉or弹窗)
4.是否添加路由

module.exports = {
  description: "新建一个模块",
  prompts: [
    {
      type: "input",
      name: "name",
      message: "页面名称:",
      validate(name) {
        if (!name) {
          return "请输入页面名称";
        }
        return true;
      },
    },
    {
      type: "confirm",
      name: "hasDetail",
      message: "你想要给新页面添加详情组件(抽屉/弹窗)吗?",
    },
    {
      type: "confirm",
      name: "hasDrawer",
      message: "你想要给模块添加详情抽屉吗?",
      when: function (answer) {
        // 当hasDetail为true的时候才会到达这步
        return answer.hasDetail; // 只有我return true才会这个confirm
      },
    },
    {
      type: "confirm",
      name: "hasModal",
      message: "你想要给模块添加详情弹窗吗?",
      when: function (answer) {
        return !answer.hasDrawer && answer.hasDetail;
      },
    },
    {
      type: "confirm",
      name: "hasRoute",
      message: "你想要给模块增加路由吗?(在route下创建为name的路径)",
    },
  ],
  actions: (data) => {
    const { hasDrawer, hasModal, hasRoute, name } = data;
    let listActions = [];
    const baseActions = [
      {
        type: "add",
        path: `src/views/${name}/list/index.vue`,
        templateFile: "plopTemplate/view/list/index.vue",
      },
      {
        type: "add",
        path: `src/views/${name}/list/index.less`,
        templateFile: "plopTemplate/view/list/index.less",
      },
      {
        type: "add",
        path: `src/views/${name}/list/columns.js`,
        templateFile: "plopTemplate/view/list/columns.js",
      },
    ];
    listActions = listActions.concat(baseActions);
    const drawerActions = [
      {
        type: "add",
        path: `src/views/${name}/detail/drawer/index.vue`,
        templateFile: "plopTemplate/view/detail/drawer/index.vue",
      },
      {
        type: "add",
        path: `src/views/${name}/detail/drawer/index.less`,
        templateFile: "plopTemplate/view/detail/drawer/index.less",
      },
    ];
    const modalActions = [
      {
        type: "add",
        path: `src/views/${name}/detail/modal/index.vue`,
        templateFile: "plopTemplate/view/detail/modal/index.vue",
      },
      {
        type: "add",
        path: `src/views/${name}/detail/modal/index.less`,
        templateFile: "plopTemplate/view/detail/modal/index.less",
      },
    ];
    const routeAction = [
      {
        type: "add",
        path: `src/router/routes/${name}.js`,
        templateFile: "plopTemplate/route/index.js",
      },
    ];
    if (hasDrawer) {
      listActions = listActions.concat(drawerActions);
    }
    if (hasModal) {
      listActions = listActions.concat(modalActions);
    }
    if (hasRoute) {
      listActions = listActions.concat(routeAction);
    }
    return listActions;
  },
};
根据自己的需求创建view模板文件

下面只做于逻辑参考

view --list 文件夹
  • index.vue
<!-- eslint-disable -->
<template>
  <div class="{{name}}Container">
    <div
      style="padding: 10px 0"
      class="search"
    >
      <div class="search_form">
        <a-input
          style="width: 200px"
          placeholder="请输入用户名"
          suffix-icon="el-icon-search"
          v-model="query.username"
        ></a-input>
      </div>
      <div class="search_btns">
        <a-button
          class="btn"
          type="primary"
          @click="getList(query)"
          >搜索</a-button
        >
        <a-button
          class="btn"
          type="warning"
          @click="getList"
          >重置</a-button
        >
      </div>
    </div>
    <div class="btns">
      {{#if hasDetail}}
      <a-button
        @click="handleAdd"
        class="float-left"
        type="primary"
        >新增</a-button
      >
      {{/if}}
      <div class="btns_right">
        <a-button
          type="primary"
          @click="handleImport"
          >导入</a-button
        >
        <a-button
          type="primary"
          @click="handleExport"
          >导出</a-button
        >
      </div>
    </div>
    <a-table
      bordered
      :data-source="dataSource"
      :columns="columns"
    >
      <template
        slot="action"
        slot-scope="text, record"
      >
        {{#if hasDetail}}
        <a @click="handleDetail(record)">编辑</a>
        {{/if}}
        <a
          style="color: red; margin-left: 10px"
          @click="handleDel(record)"
          >删除</a
        >
      </template>
    </a-table>
    {{#if hasModal}}
    <modal-com
      v-model="detailVisible"
      :detailData="detailData"
    ></modal-com>
    {{/if}}
    {{#if hasDrawer}}
    <drawer-com
      v-model="detailVisible"
      :detailData="detailData"
    ></drawer-com>
    {{/if}}
  </div>
</template>
<script>
import getColumns from "./columns";
{{#if hasDrawer}}
import drawerCom from "../detail/drawer/index.vue";
{{/if}}
{{#if hasModal}}
import modalCom from "../detail/modal/index.vue";
{{/if}}
export default {
  name:"{{name}}",
  components: {
    {{#if hasDrawer}}
    drawerCom,
    {{/if}}
    {{#if hasModal}}
    modalCom,
    {{/if}}
  },
  data() {
    return{
      columns: getColumns.call(this),
      dataSource: [
        { id:1,name: "李荣浩", age: 18, sex: "男" },
        { id:2,name: "李菲儿", age: 20, sex: "女" },
        { id:3,name: "李小龙", age: 26, sex: "男" },
      ],
      detailVisible: false,
      detailData: {},
      query:{username:''}
    }
  },
  mounted() {
    this.getList();
  },
  methods: {
    // 请求数据
    getList(query={}){
      //请求逻辑
    },
     // 导入
     handleImport() {},
    // 导出
    handleExport() {},
    // 跳转添加
    handleAdd() {
      this.detailVisible = true;
      this.detailData={}
    },
    // 跳转详情
    handleDetail(record) {
      this.detailVisible = true;
      this.detailData=record
    },
    // 删除
    handleDel(record) {
      this.$confirm({
        title: "系统提示",
        content: "确定要删除该条数据吗",
        onOk: async () => {
          try {
             //删除逻辑
          } catch (e) {
            console.log(e);
          }
        },
      });
    },
  },
}
</script>
<style lang="less">
@import "./index.less";
</style>

  • columns.js
const getColumns = function () {
  return [
    {
      title: "序号",
      dataIndex: "index",
      customRender: (text, record, index) => index + 1,
      fixed: "left",
    },
    {
      title: "姓名",
      dataIndex: "name",
    },
    {
      title: "年龄",
      dataIndex: "age",
    },
    {
      title: "性别",
      dataIndex: "sex",
    },

    {
      title: "操作",
      width: 120,
      scopedSlots: {
        customRender: "action",
      },
      fixed: "right",
    },
  ];
};

export default getColumns;

  • index.less
.{{name}}Container {
  margin: 10px;
  .search {
    float: left;
    &_form {
      float: left;
    }
    &_btns {
      float: left;
      .ant-btn {
        margin-left: 10px;
      }
    }
  }
  .btns {
    height: 60px;
    display: flex;
    width: 100%;
    justify-content: space-between;
    position: relative;
    &_right {
      position: absolute;
      right: 0;
    }
    .ant-btn {
      margin: 10px 10px 10px 0px;
    }
  }
}

view --detail/drawer文件夹
  • index.vue
<template>
  <a-drawer
    width="500"
    placement="right"
    :visible="visible"
    @close="handleCancel"
    :z-index="2024"
    :title="title"
  >
    <div class="{{name}}Detail">
      <a-form
        :form="form"
        class="fields"
      >
        <a-form-item
          label="姓名"
          class="fields-item"
        >
          <a-input
            v-decorator="['name', { rules: [{ required: true, message: '请填写姓名!' }] }]"
          />
        </a-form-item>
        <a-form-item
          label="年龄"
          class="fields-item"
        >
          <a-input v-decorator="['age', { rules: [{ required: true, message: '请填写年龄!' }] }]" />
        </a-form-item>
        <a-form-item
          label="性别"
          class="fields-item"
        >
          <a-radio-group
            v-decorator="['sex', { rules: [{ required: true, message: '请选择性别!' }] }]"
          >
            <a-radio value="男"></a-radio>
            <a-radio value="女"></a-radio>
          </a-radio-group>
        </a-form-item>
      </a-form>
    </div>
    <div class="button-wrap">
      <a-button
        type="plain"
        class="mg-r_2"
        @click="handleCancel"
        >取消</a-button
      >
      <a-button
        type="primary"
        :loading="loading"
        @click="onSave"
        >保存</a-button
      >
    </div>
  </a-drawer>
</template>

<script>
export default {
  name: "{{name}}Detail",
  props: {
    value: {
      type: Boolean,
      default: false,
    },
    detailData: {
      type: Object,
      default: () => {},
    },
  },
  model: {
    prop: "value",
    event: "change",
  },
  computed: {
    visible: {
      get() {
        return this.value;
      },
      set(val) {
        this.$emit("change", val);
      },
    },
    title() {
      return this.detailData?.id ? "编辑" : "新增";
    },
  },
  watch: {
    visible(newVal) {
      if (newVal) {
        if (this.detailData.id) {
          const { age, sex, name } = this.detailData;
          this.$nextTick(() => {
            this.form.setFieldsValue({
              age,
              sex,
              name,
            });
          });
        } else {
          this.form.resetFields();
        }
      }
    },
  },
  data() {
    return {
      loading: false,
      form: this.$form.createForm(this),
    };
  },

  methods: {
    handleCancel() {
      this.$emit("change", false);
    },
    onSave() {
      this.form.validateFields(async (err, values) => {
        if (!err) {
          // 保存逻辑
        }
      });
    },
  },
};
</script>

<style lang="less">
@import "./index.less";
</style>

  • index.less
.{{name}}Detail{
  position: relative;
  .fields {
    &-item {
      display: flex;
      width: 100%;
      .ant-form-item-control-wrapper {
        flex: 1;
      }
    }
  }

}
.button-wrap {
  width: calc(100% - 60px);
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  bottom: 20px;
}
view --detail/modal文件夹 同理(不做描述了)

运行创建项目

pnpm run plop view

在这里插入图片描述

效果展示

在这里插入图片描述

  • 抽屉详情
    在这里插入图片描述

在这里插入图片描述

  • 弹窗详情
    在这里插入图片描述

在这里插入图片描述

  • 无详情
    在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值