随着vue.js
越来越火,很多不太懂前端的小伙伴想要入坑。而入坑最好的办法就是上手实际操作
,因此本文将重点放在实际操作上,对理论不做过多解释,不明白的地方小伙伴们可以去看官方文档或者相关书籍。
目录
本文的示例项目操作环境如下:
平台/工具 | 说明 |
---|---|
操作系统 | Mac OS 10.13.6 (17G65) |
浏览器 | Chrome 77.0.3865.120 |
nvm下nodejs版本 | 10.13.0 |
编辑器 | Visual Studio Code 1.39.2 |
npm有多种安装方式,这里推荐使用nvm进行安装。
地址:https://github.com/coreybutler/nvm-windows/releases
根据文档说明安装,记得设置淘宝的镜像。
nvm node_mirror https://npm.taobao.org/mirrors/node/
nvm npm_mirror https://npm.taobao.org/mirrors/npm/
或者修改settings.txt文件,在后面加上淘宝镜像设置。
node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
地址:https://github.com/creationix/nvm
根据文档说明安装,记得设置淘宝的镜像。
# bash_profile
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node
npm config set registry https://registry.npm.taobao.org
npm config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass
npm install -g yarn
已配置过npm则无需再次配置。
yarn config set registry https://registry.npm.taobao.org -g
yarn config set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass -g
细心的小伙伴会发现vue官网是不推荐入门直接上vue-cli的,这里考虑到很多小伙伴不是前端工程师,而仅仅是需要了解vue项目的基本结构,所以直接使用vue-cli创建项目吧。
使用npm或者yarn全局安装vue-cli:
注意:这里的@vue/cli不要写成了vue-cli,vue-cli是旧版本。
npm install -g @vue/cli
# OR
yarn global add @vue/cli
现在可以开始使用vue-cli创建项目了,这里项目名称为example-curd
。
这里也可以通过vue ui来创建,使用方法是控制台输入vue ui,具体内容本文不做展示
vue create example-curd
这时会有一个选项,第一项为默认配置,这里我们选择第二项手动配置,回车确定。
之后会出现一个多选,这时的操作是空格键选择,空格选择完所有需要的配置之后,最后按回车确定。
本文用于演示基本的项目流程,只选择3项,其它项小伙伴们自行查阅资料按需选择
选好依赖项后,进入vue-router设置提示,是否使用history mode,输入n
然后回车确定。
这里的区别小伙伴们自己查阅资料。
然后会问如何处理Babel、ESLint之类的配置文件,这里为了便于修改,不与package.json混在一起,选择第一项。
最后问你是否记录下这套配置,由于本文只是演示,选择的配置项并不适合正式项目,所以选择不记录。
选择包管理器,这里推荐使用yarn。
项目配置完成。
生成的项目目录结构如图:
为了避免默认端口被占用的情况,我们新建一个名为vue.config.js
的配置文件,指定host
与port
。配置参考
module.exports = {
devServer: {
open: true,
host: '0.0.0.0',
port: 9000,
},
};
可以在package.json
中修改脚本命令,例如增加一个与serve功能相同的dev
命令:
{
"name": "example-curd",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"dev": "vue-cli-service serve"
},
"dependencies": {
"core-js": "^3.1.2",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vuex": "^3.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-router": "^4.0.0",
"@vue/cli-plugin-vuex": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"vue-template-compiler": "^2.6.10"
}
}
启动项目
yarn dev
至此,基本的项目创建与启动演示完毕,接下来进入实例演示。
本文主要目的是演示创建基本CURD的前端WEB页面,因此不做后端API的创建,这里使用mock.js
拦截请求并返回模拟数据
。
本文需要用到的依赖有:
使用yarn安装以上依赖
如果已经启动了项目,可以先ctrl + c退出,安装完依赖后再启动。
yarn add element-ui mockjs axios
修改App.vue、main.js、router.js,同时删除项目创建时的默认文件About.vue、Home.vue、HelloWorld.vue,在src/views
目录下创建一个layout
目录和page
目录,分别用于存放布局和页面。
引入element组件库及其样式
这里的@在vue-cli默认语法中代表项目中的src目录
src/main.js:
import Vue from 'vue';
import App from '@/App.vue';
import router from '@/router';
import store from '@/store';
import ElementUI from 'element-ui'; // ElementUI组件库
import 'element-ui/packages/theme-chalk/lib/index.css'; // ElementUI组件库样式
// 注册饿了么UI
Vue.use(ElementUI);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
同时调整一些必要的全局html样式。
src/App.vue:
<template>
<div id="app">
<router-view>router-view>
div>
template>
<style>
html,
body {
padding: 0;
margin: 0;
}
#app {
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, SimSun, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
style>
在layout
目录下创建一个default.vue作为默认布局文件,初学可以使用element的Container 布局容器组件。
src/views/layout/default.vue:
<template>
<el-container>
<el-aside width="200px">Asideel-aside>
<el-container>
<el-header>Headerel-header>
<el-main>
<router-view>router-view>
el-main>
el-container>
el-container>
template>
指定src/views/page/index.vue为本项目默认首页,且作用于默认布局下。
() => import(‘xxx’) 为动态加载组件,这样做的好处是用到哪个页面加载哪个页面,避免导致首页加载时间过长。
src/router/index.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
import DefaultLayout from '@/views/layout/default'
Vue.use(VueRouter)
const routes = [
{
path: '',
component: DefaultLayout,
redirect: 'index',
children: [{
path: 'index',
name: 'index',
component: () => import('@/views/page/index'),
}],
},
]
const router = new VueRouter({
routes
})
export default router;
修改首页内容,测试布局是否生效。
src/views/page/index.vue:
<template>
<div>
<el-button type="primary">首页的按钮el-button>
div>
template>
可以看到首页的ElementUI的按钮组件正常显示,且首页位于默认布局之下。
此时项目结构如下:
最后只剩下界面优化及基本的业务功能页面了。
增加左侧菜单以及右侧上方的导航栏。可以使用ElementUI的NavMenu 导航菜单组件来实现。
此时可以将菜单栏和标题栏拆分成独立组件引入默认布局,创建src/views/layout/components
目录并新建header.vue及menu.vue。
此时我们希望菜单根据router配置自动生成,所以需要修改router,可以将router配置单独提取成routes.js便于其它页面调用。同时新建一个CURD页面curd.vue。
[标题栏] src/views/layout/components/header.vue:
<template>
<div class="layout-header">
<i class="icon-collapse" :class="`el-icon-s-${collapse ? 'unfold' : 'fold'}`" @click="collapseMenu">i>
<div v-if="$route.name === 'index'" class="el-page-header">
<div class="el-page-header__content">首页div>
div>
<el-page-header v-else @back="goBack" :content="content">el-page-header>
div>
template>
<script>
export default {
props: {
collapse: {
type: Boolean,
}
},
data: () => ({
content: ''
}),
watch: {
$route: {
handler(to) {
this.content = to.meta.title;
},
immediate: true
}
},
methods: {
goBack() {
this.$router.back();
},
collapseMenu() {
this.$emit('collapse-menu');
}
},
}
script>
<style>
.layout-header {
position: relative;
height: 100%;
padding: 0 20px;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
display: flex;
align-items: center;
}
.layout-header .icon-collapse {
cursor: pointer;
font-size: 20px;
padding-right: 20px;
}
style>
[导航菜单] src/views/layout/components/menu.vue:
<template>
<el-menu
ref="menu"
class="layout-menu"
background-color="#001529"
text-color="#fff"
active-text-color="#1890ff"
:collapse="collapse"
:default-active="$route.name"
>
<router-link v-for="(menu, index) in menus" :key="index" :to="menu.path">
<el-menu-item :index="menu.name">
<i :class="`el-icon-${menu.meta.icon || 'menu'}`">i>
<span slot="title">{{ menu.meta.title }}span>
el-menu-item>
router-link>
el-menu>
template>
<script>
import routes from '@/router/routes';
export default {
props: {
collapse: Boolean
},
computed: {
menus() {
return routes[0].children || [];
}
},
}
script>
<style>
.layout-menu {
height: 100%;
width: 100%;
}
style>
[路由设置] src/router/index.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
Vue.use(VueRouter)
const router = new VueRouter({
routes
})
export default router;
[路由表] src/router/routes.js:
import DefaultLayout from '@/views/layout/default';
export default [
{
path: '',
component: DefaultLayout,
redirect: 'index',
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/page/index'),
meta: { title: '首页', icon: 's-home' }
},
{
path: 'curd',
name: 'curd',
component: () => import('@/views/page/curd'),
meta: { title: '增删改查', icon: 's-opportunity' }
}
],
},
]
src/views/page/curd.vue:
<template>
<div>CURD页面div>
template>
src/views/layout/default.vue
<template>
<el-container class="layout-default">
<el-aside class="layout-default__menu" :style="{ width: `${collapse ? 64 : 256}px` }">
<layout-menu :collapse="collapse">layout-menu>
el-aside>
<el-container class="layout-default__main" :style="{ 'margin-left': `${collapse ? 64 : 256}px` }">
<el-header>
<layout-header @collapse-menu="collapse = !collapse" :collapse="collapse">layout-header>
el-header>
<el-main><router-view>router-view>el-main>
el-container>
el-container>
template>
<script>
import LayoutMenu from './components/menu';
import LayoutHeader from './components/header';
export default {
components: { LayoutMenu, LayoutHeader },
data: () => ({
collapse: false,
}),
}
script>
<style>
.layout-default .el-header {
padding: 0;
}
.layout-default__menu {
position: fixed;
overflow-x: hidden;
background-color: #001529;
transition: width 300ms;
overflow-x: hidden !important;
height: 100vh;
}
.layout-default__main {
transition: margin 300ms;
}
.layout-default__menu, .layout-default__menu .el-menu {
border: 0 !important;
}
style>
此时的目录结构是这样的:
这时可以看到页面已经有了较为美观的样式:
一个基本的CURD
页面应该包括:查询条件、操作按钮、数据列表、列表分页、编辑表单、详情展示等几个基本模块。
因此我们可以修改原有的curd.vue为一个curd目录
,目录下包括index.vue及其相关组件。由于表单页面相对而言重复性较高,代码较为冗长,所以我们将其拆分为index.vue、search.vue、form.vue三个组件,其中index.vue为列表分页展示及数据控制,search.vue和form.vue为表单组件。
search.vue搜索表单我们可以参照element的行内表单示例进行修改:
src/views/curd/search.vue:
<template>
<el-card shadow="never">
<el-form inline :model="model" size="small">
<el-form-item label="姓名">
<el-input v-model="model.name" placeholder="请输入姓名">el-input>
el-form-item>
<el-form-item label="性别">
<el-select v-model="model.gender" placeholder="请选择性别">
<el-option label="小姐姐" value="famale">el-option>
<el-option label="小哥哥" value="male">el-option>
el-select>
el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">查询el-button>
<el-button plain @click="handleReset">重置el-button>
el-form-item>
el-form>
el-card>
template>
<script>
export default {
props: {
value: {
type: Object,
default: () => {
return {};
}
},
},
data: () => ({
model: {}
}),
watch: {
value: {
handler(val) {
if (val) {
Object.keys(this.model).forEach(key => {
this.model[key] = val[key];
});
} else {
Object.keys(this.model).forEach(key => {
this.model[key] = null;
});
}
},
immediate: true,
},
model: {
handler(val) {
this.$emit("input", val);
},
deep: true
}
},
methods: {
handleSubmit() {
this.$emit('search', this.model);
},
handleReset() {
Object.keys(this.model).forEach(key => {
this.model[key] = null;
});
}
}
}
script>
form.vue搜索表单我们可以参照element的表单验证示例进行修改:
src/views/curd/form.vue:
<template>
<el-form ref="form" :model="model" :rules="rules" size="small" label-width="100px">
<el-form-item label="姓名" prop="name">
<el-input v-model="model.name">el-input>
el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="model.gender" placeholder="请选择性别">
<el-option label="小姐姐" value="famale">el-option>
<el-option label="小哥哥" value="male">el-option>
el-select>
el-form-item>
<el-form-item label="年龄" prop="age">
<el-input-number v-model="model.age">el-input-number>
el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleSubmit">确定el-button>
<el-button plain size="small" @click="handleCancel" style="margin-left: 8px">取消el-button>
el-form-item>
el-form>
template>
<script>
export default {
props: {
value: {
type: Object,
default: () => {
return {};
}
},
},
data: () => ({
model: {
name: '',
gender: null,
age: 18,
},
rules: {
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
age: [{ required: true, message: '请输入年龄', trigger: 'change' }],
}
}),
watch: {
value: {
handler(val) {
if (val) {
Object.keys(this.model).forEach(key => {
this.model[key] = val[key];
});
} else {
Object.keys(this.model).forEach(key => {
this.model[key] = null;
});
}
},
immediate: true,
},
model: {
handler(val) {
this.$emit("input", val);
},
deep: true
}
},
methods: {
// 点击确定提交表单的操作
handleSubmit(name) {
this.$refs.form.validate(valid => {
if (valid) {
const result = this.submitPure ? this.getPureModel() : JSON.parse(JSON.stringify(this.model));
this.$emit("submit", result);
}
});
},
// 校验表单
validate() {
this.$refs.form.validate(valid => {
this.$emit("validate", valid);
});
},
// 重置表单
reset() {
Object.keys(this.model).forEach(key => {
this.model[key] = undefined;
});
this.$nextTick(() => {
this.$refs.form.clearValidate();
});
},
// 点击取消的操作
handleCancel() {
this.$emit("cancel");
}
}
};
script>
index.vue则是对以上组件的整合以及列表数据的展示操作。
src/views/curd/index.vue:
<template>
<div class="page-curd">
<curd-search @search="handleSearch">curd-search>
<div class="page-curd__action-bar">
<el-button type="primary" icon="el-icon-plus" size="small" @click="handleNew">新增el-button>
<el-button icon="el-icon-delete" size="small" :disabled="tableSelection && tableSelection.length <= 0" @click="handleDeleteMul">批量删除el-button>
div>
<el-table size="mini" :data="tableData" class="page-curd__table" border @selection-change="handleTableSelectionChange">
<el-table-column type="selection" min-width="35">el-table-column>
<el-table-column label="姓名" prop="name" min-width="100">
<template slot-scope="{ row }">
<span><i class="el-icon-user-solid">i>{{ row.name }}span>
template>
el-table-column>
<el-table-column label="性别" prop="gender">
<template slot-scope="{ row }">
<el-tag v-if="row.gender === 'male'" size="small">小哥哥 ♂el-tag>
<el-tag v-else type="danger" size="small">小姐姐 ♀el-tag>
template>
el-table-column>
<el-table-column label="年龄" prop="age">el-table-column>
<el-table-column label="操作" min-width="140" fixed="right">
<template slot-scope="slotScope">
<el-button class="eagle-scheme__table-btn" type="text" icon="el-icon-view" title="详情" @click="handleView(slotScope)">el-button>
<el-button class="eagle-scheme__table-btn" type="text" icon="el-icon-edit" title="编辑" @click="handleEdit(slotScope)">el-button>
<el-button type="text" icon="el-icon-delete" title="删除" @click="handleDelete(slotScope)">el-button>
template>
el-table-column>
el-table>
<el-pagination
class="page-curd__pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
>
el-pagination>
<el-dialog :visible.sync="dialogVisible" :title="title" width="450px" :close-on-click-modal="dialogMode === 'view'" append-to-body>
<template v-if="dialogMode === 'view'">
<el-row :gutter="20">
<el-col :span="12">姓名:{{ formModel.name }}el-col>
<el-col :span="12">
性别:
<template v-if="formModel.gender">
<el-tag v-if="formModel.gender === 'male'" size="small">小哥哥 ♂el-tag>
<el-tag v-else type="danger" size="small">小姐姐 ♀el-tag>
template>
el-col>
<el-col :span="12" style="margin-top: 20px;">年龄:{{ formModel.age }}el-col>
el-row>
template>
<template v-else>
<curd-form ref="curdForm" v-model="formModel" @submit="handleSubmit" @cancel="handleCancel">curd-form>
template>
el-dialog>
div>
template>
<script>
import CurdSearch from './search';
import CurdForm from './form';
export default {
components: { CurdSearch, CurdForm },
data: () => ({
dialogMode: 'new',
dialogVisible: false,
tableData: [
{ name: '古力娜扎', age: 27, gender: 'famale' },
{ name: '迪丽热巴', age: 27, gender: 'famale' },
{ name: '尼格买提', age: 36, gender: 'male' },
],
currentPage: 1,
pageSize: 10,
total: 3,
tableSelection: [],
formModel: {}
}),
computed: {
title() {
if (this.dialogMode === 'view') {
return '查看';
}
return this.dialogMode === 'edit' ? '编辑' : '新增';
}
},
methods: {
async handleSearch(value, needReset) {
if (needReset) {
this.currentPage = 1;
}
const param = {
...value,
currentPage: this.currentPage,
pageSize: this.pageSize,
};
console.log(param);
},
handleNew() {
if (this.$refs['curdForm']) { this.$refs['curdForm'].reset(); }
this.dialogMode = 'new';
this.dialogVisible = true;
this.formModel = {};
},
handleView({ row }) {
this.dialogMode = 'view';
this.dialogVisible = true;
this.formModel = { ...row };
},
handleEdit({ row }) {
if (this.$refs['curdForm']) { this.$refs['curdForm'].reset(); }
this.dialogMode = 'edit';
this.dialogVisible = true;
this.formModel = { ...row };
},
handleDelete({ row }) {
console.log('delete:', row);
},
handleDeleteMul() {
console.log('mul delete:', this.tableSelection);
},
handleSubmit() {
if (this.dialogMode === 'new') {
console.log('new', this.formModel);
} else if (this.dialogMode === 'edit') {
console.log('edit', this.formModel);
}
},
handleCancel() {
this.dialogVisible = false;
},
handleTableSelectionChange(selection) {
this.tableSelection = selection;
},
handleSizeChange(val) {
this.currentPage = 1;
this.pageSize = val;
this.handleSearch();
},
handleCurrentChange(val) {
this.currentPage = val;
this.handleSearch();
},
}
}
script>
<style>
.page-curd__action-bar {
margin: 20px 0px;
}
.page-curd__table {
margin-bottom: 20px;
}
.page-curd .el-icon-user-solid {
padding-right: 10px;
}
.page-curd__pagination {
text-align: right;
}
style>
假如你有强迫症的话,那就顺便把首页略微修改一下。
src/views/page/index.vue:
<template>
<div class="page-index">
Vue入门项目CURD示例
div>
template>
<style>
.page-index {
text-align: center;
padding-top: calc(50vh - 60px - 24px);
font-size: 24px;
}
style>
此时的项目效果如下:
本示例项目为纯前端项目,所以使用mockjs
来模拟增删改查的基本API。
在项目中新建一个mock
目录用于存放模拟mockjs配置文件。包括index.js、util.js、module/user.js。
src/mock/index.js:
import Mock from 'mockjs';
import { parseUrl } from './util';
import userAPI from './module/user';
// 临时缓存mockjs数据
let MockCache = {};
const MockBot = {
// 通用模板API
fastAPI: {
// 获取数据列表
page: (base) => config => {
const list = MockCache[base] || [];
const param = parseUrl(config.url) || {};
const { page = 1, size = 10, ...query } = param;
// 计算有几个搜索条件
let queryCount = false;
for (let key in query) {
if (query[key]) {
queryCount += 1;
break;
}
}
// 根据搜索条件过滤结果
const filteredList = queryCount > 0 ? list.filter(data => {
let result = false;
for (let key in query) {
if (data[key] === query[key]) {
result = true;
break;
}
}
return result;
}) : list;
// 根据结果处理分页数据
const _page = Number(page);
const _limit = Number(size);
const pageList = filteredList.filter((item, index) => index < _limit * _page && index >= _limit * (_page - 1));
const response = {
page: _page,
size: _limit,
result: {
list: pageList,
total: filteredList.length,
},
success: true,
};
return response;
},
// 查询数据详情
get: (base) => config => {
const list = MockCache[base] || [];
const param = parseUrl(config.url) || {};
const id = param.id;
const result = list.find((item) => item.id == id);
return {
result: result,
success: true,
};
},
// 新增数据
add: (base) => config => {
const param = JSON.parse(config.body);
MockCache[base].unshift(param);
return {
success: true,
};
},
// 编辑数据
update: (base) => config => {
const param = JSON.parse(config.body);
const index = MockCache[base].findIndex(item => item.id === param.id);
MockCache[base][index] = param;
return {
success: true,
};
},
// 删除数据
delete: (base) => config => {
const ids = JSON.parse(config.body);
ids.forEach(id => {
MockCache[base] = MockCache[base].filter(item => item.id !== id);
});
return {
success: true,
};
}
},
// 根据通用模板API快速创建模拟接口
fastMock: (url, list) => {
MockCache[url] = list;
Mock.mock(new RegExp(`\/${url}\/page`), 'get', MockBot.fastAPI.page(url));
Mock.mock(new RegExp(`\/${url}\/get`), 'get', MockBot.fastAPI.get(url));
Mock.mock(new RegExp(`\/${url}\/add`), 'post', MockBot.fastAPI.add(url));
Mock.mock(new RegExp(`\/${url}\/update`), 'post', MockBot.fastAPI.update(url));
Mock.mock(new RegExp(`\/${url}\/delete`), 'post', MockBot.fastAPI.delete(url));
},
}
// 产品管理
MockBot.fastMock('user', userAPI.userList);
src/mock/util.js:
import Mock from 'mockjs';
export const parseUrl = (url) => {
let obj = {};// 创建一个Object
let reg = /[?&][^?&]+=[^?&]+/g;// 正则匹配 ?&开始 =拼接 非?&结束 的参数
let arr = url.match(reg);// match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
// arr数组形式 ['?id=12345','&a=b']
if (arr) {
arr.forEach((item) => {
/**
* tempArr数组 ['id','12345']和['a','b']
* 第一个是key,第二个是value
* */
let tempArr = item.substring(1).split('=');
let key = decodeURIComponent(tempArr[0]);
let val = decodeURIComponent(tempArr[1]);
obj[key] = val;
});
}
return obj;
}
export default {
// 根据配置文件和数量快速生成数据列表
fastList: (config, count) => {
const list = []
for (let i = 0; i < count; i++) {
list.push(Mock.mock(config));
}
return list;
},
}
src/mock/module/user.js:
import MockUtil from '../util';
export default {
// 用户列表
userList: MockUtil.fastList({
id: '@guid()',
name: '@cname()',
'gender|1': ['male', 'famale'],
'age|16-40': 1,
}, 43),
}
模拟好接口,接下来就需要进行接口对接了,首先我们修改main.js,将axios
创建一个实例并设置为Vue的prototype,这样可以在每个Vue页面或组件中直接通过this进行访问。
创建axios实例后可以设置request和response拦截器,具体用法请查看axios官方文档
src/main.js:
import Vue from 'vue';
import axios from 'axios';
import App from '@/App.vue';
import router from '@/router';
import store from '@/store';
import ElementUI from 'element-ui'; // ElementUI组件库
import 'element-ui/packages/theme-chalk/lib/index.css'; // ElementUI组件库样式
import '@/mock';
const request = axios.create({
baseURL: 'http://localhost',
timeout: 1000 * 60,
withCredentials: true,
});
// respone 拦截器
request.interceptors.response.use(
response => {
const { data = {} } = response;
const { success } = data;
if (success) {
return data;
} else {
return { success };
}
},
error => {
return { success: false };
});
Vue.prototype.$request = request;
// 注册饿了么UI
Vue.use(ElementUI);
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
配置好axios之后,我们就可以在curd页面中进行对接了。
src/views/page/curd/index.vue:
<template>
<div class="page-curd">
<curd-search @search="handleSearch">curd-search>
<div class="page-curd__action-bar">
<el-button type="primary" icon="el-icon-plus" size="small" @click="handleNew">新增el-button>
<el-button icon="el-icon-delete" size="small" :disabled="tableSelection && tableSelection.length <= 0" @click="handleDeleteMul">批量删除el-button>
div>
<el-table size="mini" :data="tableData" class="page-curd__table" border @selection-change="handleTableSelectionChange">
<el-table-column type="selection" min-width="35">el-table-column>
<el-table-column label="姓名" prop="name" min-width="100">
<template slot-scope="{ row }">
<span><i class="el-icon-user-solid">i>{{ row.name }}span>
template>
el-table-column>
<el-table-column label="性别" prop="gender">
<template slot-scope="{ row }">
<el-tag v-if="row.gender === 'male'" size="small">小哥哥 ♂el-tag>
<el-tag v-else type="danger" size="small">小姐姐 ♀el-tag>
template>
el-table-column>
<el-table-column label="年龄" prop="age">el-table-column>
<el-table-column label="操作" min-width="140" fixed="right">
<template slot-scope="slotScope">
<el-button class="eagle-scheme__table-btn" type="text" icon="el-icon-view" title="详情" @click="handleView(slotScope)">el-button>
<el-button class="eagle-scheme__table-btn" type="text" icon="el-icon-edit" title="编辑" @click="handleEdit(slotScope)">el-button>
<el-button type="text" icon="el-icon-delete" title="删除" @click="handleDelete(slotScope)">el-button>
template>
el-table-column>
el-table>
<el-pagination
class="page-curd__pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
>
el-pagination>
<el-dialog :visible.sync="dialogVisible" :title="title" width="450px" :close-on-click-modal="dialogMode === 'view'" append-to-body>
<template v-if="dialogMode === 'view'">
<el-row :gutter="20">
<el-col :span="12">姓名:{{ formModel.name }}el-col>
<el-col :span="12">
性别:
<template v-if="formModel.gender">
<el-tag v-if="formModel.gender === 'male'" size="small">小哥哥 ♂el-tag>
<el-tag v-else type="danger" size="small">小姐姐 ♀el-tag>
template>
el-col>
<el-col :span="12" style="margin-top: 20px;">年龄:{{ formModel.age }}el-col>
el-row>
template>
<template v-else>
<curd-form ref="curdForm" v-model="formModel" @submit="handleSubmit" @cancel="handleCancel">curd-form>
template>
el-dialog>
div>
template>
<script>
import CurdSearch from './search';
import CurdForm from './form';
export default {
components: { CurdSearch, CurdForm },
data: () => ({
dialogMode: 'new',
dialogVisible: false,
tableData: [],
currentPage: 1,
pageSize: 10,
total: 3,
tableSelection: [],
formModel: {},
currentRow: {},
}),
computed: {
title() {
if (this.dialogMode === 'view') {
return '查看';
}
return this.dialogMode === 'edit' ? '编辑' : '新增';
}
},
created() {
this.handleSearch();
},
methods: {
async handleSearch(value, needReset) {
if (needReset) {
this.currentPage = 1;
}
const params = {
...value,
page: this.currentPage,
size: this.pageSize,
};
this.$request.get('/user/page', { params }).then(response => {
const { result: { list, total } = {} } = response;
this.tableData = list;
this.total = total;
});
},
handleNew() {
if (this.$refs['curdForm']) { this.$refs['curdForm'].reset(); }
this.dialogMode = 'new';
this.dialogVisible = true;
this.formModel = {};
},
handleView({ row }) {
this.dialogMode = 'view';
this.dialogVisible = true;
this.formModel = { ...row };
},
handleEdit({ row }) {
if (this.$refs['curdForm']) { this.$refs['curdForm'].reset(); }
this.dialogMode = 'edit';
this.$request.get('/user/get', { params: { id: row.id } }).then(response => {
const { result } = response;
this.formModel = result;
this.currentRow = result;
this.dialogVisible = true;
});
},
handleDelete({ row }) {
this.$request.post('/user/delete', [row.id]).then(response => {
const { success } = response;
if (success) {
this.$message.success('删除成功');
this.handleSearch();
}
});
},
handleDeleteMul() {
const ids = this.tableSelection.map(selection => selection.id);
this.$request.post('/user/delete', ids).then(response => {
const { success } = response;
if (success) {
this.$message.success('删除成功');
this.handleSearch();
}
});
},
handleSubmit() {
if (this.dialogMode === 'new') {
this.$request.post('/user/add', { ...this.formModel, id: undefined }).then(response => {
const { success } = response;
if (success) {
this.$message.success('新增成功');
this.handleSearch();
this.dialogVisible = false;
}
});
} else if (this.dialogMode === 'edit') {
this.$request.post('/user/update', { ...this.formModel, id: this.currentRow.id }).then(response => {
const { success } = response;
if (success) {
this.$message.success('编辑成功');
this.handleSearch();
this.dialogVisible = false;
}
});
}
},
handleCancel() {
this.dialogVisible = false;
},
handleTableSelectionChange(selection) {
this.tableSelection = selection;
},
handleSizeChange(val) {
this.currentPage = 1;
this.pageSize = val;
this.handleSearch();
},
handleCurrentChange(val) {
this.currentPage = val;
this.handleSearch();
},
}
}
script>
<style>
.page-curd__action-bar {
margin: 20px 0px;
}
.page-curd__table {
margin-bottom: 20px;
}
.page-curd .el-icon-user-solid {
padding-right: 10px;
}
.page-curd__pagination {
text-align: right;
}
style>
至此,一个基本的CURD示例就完成了。
在本实例项目中,并没有使用多少依赖项,只有基本的UI组件库element、基于promise
的HTTP库axios、前端模拟数据的mockjs,至于本文开始时,创建项目选择的vuex并没有提到,这个建议小伙伴们自己查阅相关资料去学习。
在我的学习理念中,每当学习到一个新的技术时,可以先查阅学习技术的简单教程,然后上手实践,从模仿到举一反三再到创新,千万不要只学习理论而不主动去实践,这样效果并不深刻。
本文中有一些ES6
以上的语法并没有过多提及,有许多技巧也并没有过多的给大家解释,更多的技术细节我会在以后的文章中提到,如果有BUG或者不对的地方,欢迎各位小伙伴留言指正!
谢谢。