vue实战版教程
- 什么是plpo
- 安装
- 1.将plop添加到您的项目
- 2.全局安装plop(可选,但建议使用方便)
- 3.在项目根目录下创建一个 plopfile.js
- vue 实战(后台管理系统 - 增删改查)
- 所需文件
- 文件介绍
- 创建配置文件 plopfile.js
- 创建模板和脚本命令文件
- 模板创建逻辑
- 根据自己的需求创建view模板文件
- view --list 文件夹
- view --detail/drawer文件夹
- view --detail/modal文件夹 同理(不做描述了)
- 运行创建项目
- 效果展示
什么是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
效果展示
- 抽屉详情
- 弹窗详情
- 无详情