1. Vue2.0搭建前端系统

前两篇介绍了全栈系统里面后台和前端:
后台篇:Flask搭建后台
移动端篇:H5+搭建移动端应用
项目线上地址:项目访问链接,账号:admin 密码:admin

本文讲述用Vue2.0 + element-ui创建一个web前端页面,这属于全栈系统中的WEB端,项目包含以下内容:
入口页面:定义登录页面和路由跳转
登录页面:实现系统登录功能
业务页面:写了两个业务页面1.> three.js加载gltf格式3D模型;2.> echart画图;
系统页面:实现用户信息管理页面

效果图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1. 前端页面开发选用的技术栈如下:

开发语言:HTML+JS
开发框架:Vue2.0 + element-ui + axios + echart + three.js
开发工具:Hbuilder X 2.7.9
系统后台:FlaskDemo

1.2. 系统的详细开发过程

1.2.1. 用Hbuilder创建项目

在这里插入图片描述

项目创建完成后运行如下图:
在这里插入图片描述
这里要注意两点:
1、创建vue项目时,会生成node_modules包,网络不好时,创建的时间会比较久
2、创建好项目时,需要创建一个vue.config.js的配置文件,配置内容如下:

const webpack = require('webpack')

module.exports = {
  //baseUrl: './',// 部署应用时的根路径(默认'/'),也可用相对路径(存在使用限制)
  publicPath: './',// 部署应用时的根路径(默认'/'),也可用相对路径(存在使用限制)
  outputDir: 'dist',// 运行时生成的生产环境构建文件的目录(默认''dist'',构建之前会被清除)
  assetsDir: '',//放置生成的静态资源(s、css、img、fonts)的(相对于 outputDir 的)目录(默认'')
  indexPath: 'index.html',//指定生成的 index.html 的输出路径(相对于 outputDir)也可以是一个绝对路径。
  pages: {//pages 里配置的路径和文件名在你的文档目录必须存在 否则启动服务会报错
    index: {//除了 entry 之外都是可选的
      entry: 'src/main.js',// page 的入口,每个“page”应该有一个对应的 JavaScript 入口文件
      template: 'public/index.html',// 模板来源
      filename: 'index.html',// 在 dist/index.html 的输出
      title: 'VueDemo',// 当使用 title 选项时,在 template 中使用:<title><%= htmlWebpackPlugin.options.title %></title>
      // 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk
      chunks: ['chunk-vendors', 'chunk-common', 'index'] 
    },
    //官方解释:当使用只有入口的字符串格式时,模板会被推导为'public/subpage.html',
    //若找不到就回退到'public/index.html',输出文件名会被推导为'subpage.html'
    subpage: 'src/main.js'
  },
  lintOnSave: false,// 是否在保存的时候检查
  productionSourceMap: false,// 生产环境是否生成 sourceMap 文件,false表示隐藏vue代码
  css: {
    extract: true,// 是否使用css分离插件 ExtractTextPlugin
    sourceMap: false,// 开启 CSS source maps
    loaderOptions: {},// css预设器配置项
    modules: false// 启用 CSS modules for all css / pre-processor files.
  },
  devServer: {// 环境配置
    host: '0.0.0.0',
    port: 8081,
    https: false,
    hotOnly: false,
    open: true, //配置自动启动浏览器
    proxy: {// 配置多个代理(配置一个 proxy: 'http://localhost:4000' )
      '/api': {
        target: 'http://192.168.0.189:8089',
        changeOrigin: true,
        pathRewrite: {
          "^/api": ""
        }
      },
    }
  },
  pluginOptions: {// 第三方插件配置

  },
  configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({
        $:"jquery",
        jQuery:"jquery",
        "windows.jQuery":"jquery"
      })
    ]
  }
}

1.2.2. 安装项目需要的依赖库

项目里面需要用到axios、jquery、vue-router、vuex、echarts,需要安装,命令如下:
npm install --save axios jquery vue-router vuex element-ui
编译菜单截图:
在这里插入图片描述

编译完成截图:
在这里插入图片描述

注意:如果编译过程中报错,根据提示安装缺失的包:npm install --save xxxx

1.2.3. 创建项目配置文件和目录

在这里插入图片描述

项目目录结构如上图,文件和目录的说明如下:
dist:项目编译后生成的目录,该目录内容放到FlaskDemo中static目录下,就可以访问web页面
node_modules:项目依赖包安装目录
public:项目资源文件目录,这里存放着一个3D模型文件,用于加载到页面展示
src:vue源文件目录,assets存放资源,components存放实现的业务组件,后面详细描述
vue.config.js:Vue-cli3配置文件
其他文件:创建Vue项目时自动生成的

下面详细介绍vue源文件目录

1.2.3.1. 创建App.vue

这个文件定义前端页面入口,引入了路由和页面布局,
页面布局方式:左边导航菜单 + 右边显示主体内容,
主体内容布局方式:上面头部 + 下面内容
页面布局是用element-ui中el-container组件实现

<template>
	<div id="app">
		<router-view v-if="this.$router.currentRoute.name == 'login'" />
		<el-container v-else="" class="main-container">
			<el-aside class="aside-nav left-nav">
				<!-- 导航栏 -->
			</el-aside>
			<el-main class="right-container">
				<el-header>
					<!-- 头部 -->
				</el-header>
				<el-container>
					<el-main class="container-center">
						<!-- 主体内容 -->
					</el-main>
				</el-container>
			</el-main>
		</el-container>
	</div>
</template>
1.2.3.2. 创建main.js

这个文件引入项目需要的组件,创建Vue app,定义全局访问的方法
引入组件

import Vue from 'vue'
import App from './App.vue'

import store from './store'
import router from './router'
import $ from 'jquery'

import echarts from 'echarts'
import 'echarts/dist/extension/dataTool.min.js'

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI);

Vue.config.productionTip = false

Vue.prototype.$echarts = echarts

//配置axios
import axios from 'axios'
import qs from 'qs'

定义全局方法,如http访问

//配置axios
import axios from 'axios'
import qs from 'qs'

axios.defaults.timeout = 5000; //响应时间
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; //配置请求头
axios.defaults.baseURL = 'http://127.0.0.1:5000'; //配置接口地址

//POST传参序列化(添加请求拦截器)
axios.interceptors.request.use((config) => {
	//在发送请求之前做某件事
	if (config.method === 'post') {
		config.data = qs.stringify(config.data);
	}
	return config;
}, (error) => {
	//console.log('错误的传参')
	return Promise.reject(error);
});

//返回状态判断(添加响应拦截器)
axios.interceptors.response.use((res) => {
	//对响应数据做些事
	if (!res.data.success) {
		return Promise.resolve(res);
	}
	return res;
}, (error) => {
	//console.log('网络异常')
	return Promise.reject(error);
});

创建Vue,绑定路由和存储模块

new Vue({
	render: h => h(App),
	store,
	router,
}).$mount('#app')
1.2.3.3. 创建router.js

这个文件定义前端路由,关联导航菜单,跳转到具体页面

export default new Router({
  routes: [
    { path: '/', name: 'login', lable: '登录', component: login },
    { path: '/home', name: 'home', lable: '首页', component: home },
	{ path: '/business1', name: 'business1', lable: '3D模型', component: business1 },
	{ path: '/business2', name: 'business2', lable: '画图展示', component: business2 },
	{ path: '/business3', name: 'business3', lable: '业务3', component: business3 },
	{ path: '/usermanage', name: 'usermanage', lable: '用户管理', component: usermanage },
	{ path: '/logmanage', name: 'logmanage', lable: '系统日志', component: logmanage }
  ]
})

代码中定义的lable就是导航菜单里面的名称,导航菜单内容根据用户权限返回,就可以根据不同用户动态展示导航菜单

1.2.3.4. 创建store.js

这个文件定义vuex保存数据

export default new vuex.Store({
	state: {
		//xxxx: 'xxxxx',
	},
	mutations: {
		setData(state, obj) {
			for (let k in state) {
				if (obj.hasOwnProperty(k)) {
					//xxxx = xxxxx;
				}
			}
		},
		clearData(state) {
			for (let k in state) {
				//xxxx = '';
			}
		}
	}
});

由于vuex保存的数据在内存里面,页面一刷新,数据就会丢失,这里采用把数据临时保存到sessionStorage里面,刷后读取,再删除sessionStorage
具体代码在App.vue中created()方法实现。

created() {//处理刷新时vuex里面数据保存
    //在页面加载时读取sessionStorage里的状态信息
    if (sessionStorage.getItem("store")) {
    	this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem("store"))));
    	sessionStorage.removeItem('store');
    }
    // console.log(sessionStorage.getItem("store"))
    //在页面刷新时将vuex里的信息保存到sessionStorage里
    window.addEventListener("beforeunload", () => {
    	sessionStorage.setItem("store", JSON.stringify(this.$store.state))
    });
}
1.2.3.5. 创建login.vue

这个文件创建登录页面,登录框是通过element-ui中的表单el-form实现,

<template>
	<div class="login-container">
		<el-col :span="8" :offset="8" class="login-panel">
			<p class="login-title">Vue前端页面</p>
			<el-form ref="form" :model="form" label-width="80px">
				<el-form-item label="账号">
					<el-input v-model="form.username"><i slot="prefix" class="icon_username"></i></el-input>
				</el-form-item>
				<el-form-item label="密码">
					<el-input type="password" v-model="form.password">
					    <i slot="prefix" class="icon_password"></i>
					</el-input>
				</el-form-item>
				<el-form-item>
					<el-checkbox v-model="form.record">记住密码</el-checkbox>
					<label class="" v-on:click="showMessage('请联系公司的管理帮忙重置密码!');">忘记密码?</label>
				</el-form-item>
				<el-form-item>
					<el-button class="btn-login" @click="login">登录</el-button>
				</el-form-item>
			</el-form>
		</el-col>
	</div>
</template>

数据结构定义

data() {
	return {
		form: {
			username: '',
			password: '',
			record: false
		}
	}
},

登录功能实现

async login() {
	if (this.form.username == "" || this.form.password == "") {
		this.$message({
			message: '请输入用户名和密码',
			type: 'warning'
		});
	} else {
		let argc = {
			'username': this.form.username,
			'password': this.form.password
		};
		let result = await this.$fetchPost('/login', argc);
		if (result.status == 200) {
			console.log(result.data);
			if (result.data.code == '0') {
				let groups = '{"首页": [], "业务菜单": ["3D模型", "画图展示", "业务3"], 
				"系统设置": ["用户管理", "系统日志"]}';
				let roles = '{"首页": ["读"], "3D模型": ["读", "写"], "业务2": ["读", "写"], "业务3": ["读", "写"], 
				"用户管理": ["读", "写"], "系统日志": ["读", "写"]}';
				localStorage.setItem('record', this.form.record);
				localStorage.setItem('username', this.form.username);
				this.$store.commit('setData', {
					'access_token': this.form.username,
					'userInfo': this.form.username,
					'groups': this.$isJSONStr(groups) ? JSON.parse(groups) : {},
					'roles': this.$isJSONStr(roles) ? JSON.parse(roles) : {},
				});
				this.$router.push('/home');
				this.form.password = '';
			} else {
				this.$message({
					message: result.data.msg,
					type: 'warning'
				});
			}
		}
	}
},

login函数说明:
1.> async配合await使用,http请求接口this.$fetchPost不需要写回调函数处理请求返回的结果,按顺序写处理结果代码,这样写逻辑清晰还能避免回调地狱
2.> 收到http请求后定义两个变量groups、roles模拟用户返回的权限,这里可以自己修改里面内容,看下登录后菜单显示的内容

1.2.3.6. 创建navmenu.vue

这个文件根据用户返回的权限动态显示导航菜单

<template>
	<el-menu :default-openeds="['0']">
		<div :key="i" v-for="(item,i) in initmenu">
			<el-submenu :index="`${i+1}`" v-if="item['submenu'].length > 0">
				<template slot="title"><i class="el-icon-menu"></i>{{item.name}}</template>
				<el-menu-item :index="`${i+1}-${subi+1}`" v-for="(subitem,subi) in item['submenu']" 
				:key='subi' @click="turntopage(subitem.name)">{{subitem.name}}
				</el-menu-item>
			</el-submenu>
			<el-menu-item :index="`${i+1}`" v-else="" @click="turntopage(item.name)">
				<i class="el-icon-menu"></i>
				<span slot="title">{{item.name}}</span>
			</el-menu-item>
		</div>
	</el-menu>
</template>

组件加载时初始化菜单数据,从vuex中读取访问权限数据,初始化到this.initmenu里面,页面根据这个动态渲染,
这里目前只支持二级菜单

mounted: function() {
	let groupsObj = this.$store.state.groups;
	let rolesObj = this.$store.state.roles;
	let user = this.$store.state.access_token;

	let submenu = [];
	this.initmenu = [];
	if (user == null) {
		this.$router.push('/');
	} //没有登录时页面跳转
	
	let router = {};
	for (let i in groupsObj) {
		if (groupsObj[i].length > 0) {
			submenu = [];
			for (var j in groupsObj[i]) {
				router = this.get_router(groupsObj[i][j]);
				submenu.push({
					'name': router.lable,
					'path': router.path
				});
			}
			this.initmenu.push({
				'name': i,
				'path': '',
				'submenu': submenu
			});
		} else {
			router = this.get_router(i);
			this.initmenu.push({
				'name': router.lable,
				'path': router.path,
				'submenu': []
			});
		}
	}
}

实现页面跳转函数

methods: {
	get_router(menu) {
		for (let i of this.$router.options.routes) {
			if (i.lable == menu) {
				return i;
			}
		}

		return false;
	},
	turntopage: function(text) {
		let router = this.get_router(text);
		if ('/'+this.$router.currentRoute.name == router.path) {
			return;
		}
		this.$emit("updatetitle", router.lable);
		this.$router.push(router.path);
	},
}
1.2.3.7. 创建loadmodel.vue

这个文件是展示3D模型的组件,用来加载3D模型,了解更多WEB 3D知识:three.js
定义页面

<template>
	<div class="container" id="scene-container"></div>
</template>

引入组件

import * as THREE from 'three'
import {OBJLoader, MTLLoader} from 'three-obj-mtl-loader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'

定义数据模型

data() {
	return {
		camera: null,
		scene: null,
		light: null,
		renderer: null,
		controls: null,
		stats: null,
	}
}

定义展示three.js3D模型的基本方法

methods: {
    initThree() {},//初始化three.js对象
    initCamera(),//初始化相机
    initScene() {},//初始场景
    initLight() {},//初始化灯光
    loadmodels() {},//加载gltf格式3D模型
    initControl() {},//初始化模型控制器
    onWindowResize() {},//渲染模型
    render() {},
    threeStart() { //启动流程函数
    	this.initThree(); 
    	this.initCamera(); 
    	this.initScene(); 
    	this.initLight(); 
    	this.loadmodels(); 
    	this.initControl(); 
    	this.renderer.clear(); 
        this.renderer.render(this.scene, this.camera);
    }
}
1.2.3.8. 创建business2.vue

这个文件是展示echart画图的组件
定义页面

<template>
	<div class="container" id="container" :style="`height: ${height}px;`">
	</div>
</template>

定义数据模型

data() {
	return {
		height: document.documentElement.clientHeight - 160,
		builderJson: {},
		downloadJson: {},
		themeJson: {},
	}
}

定义画图方法

methods: {
	setOptionData(item, option) {
		var data;
		if (typeof option == "object") {
		    data = option;
		} else {
		    data = JSON.parse(option);
		}
		
		data["animation"] = true;
		var dom = document.getElementById(item);
		var myChart = this.$echarts.getInstanceByDom(dom);
		if (myChart != null && myChart != "" && myChart != undefined) {
		    myChart.dispose();
		}
		
		myChart = this.$echarts.init(dom, "roma");
		
		if (data && typeof data === "object") {
		    myChart.setOption(data, true);
		}
	}
}
1.2.3.9. 创建usermanage.vue

这个文件创建用户管理页面,页面功能:增加、编辑、修改、查询、分页、模糊搜索功能,
页面定义
在这里插入图片描述
数据模型定义

data() {
	return {
		userDialog: false, //用户信息对话框控制标志
		titleDialog: '增加用户', //用户信息对话框标题
		//表头
		headData: [
		    {prop: "Name", label: "账号", align: "left"},
			{prop: "Nick", label: "员工姓名", align: "left"},
			{prop: "Mobile", label: "手机号", align: "left"},
			{prop: "Email", label: "邮箱", align: "left"},
			{prop: "depart_name", label: "部门", align: "left"},
			{prop: "post_name", label: "岗位", align: "left"},
			{prop: "Valid", label: "账号状态", align: "center"}
		],
		tableData: [], //用户表格数据
		userForm: { //用户表单信息
			Name: '',
			Nick: '',
			PostId: '',
			Mobile: '',
			Email: '',
			Valid: true
		},
		postValue: [], //二级选择框绑定的值
		postOptions: [{ //二级选择框列表的值
			value: 'zhinan',
			label: '指南',
			children: [{
				value: 'shejiyuanze',
				label: '设计原则',
			}]
		}],
		pagination: { //分页信息
			total: 1,
			size: 5,
			current: 1,
		},
		search: ''
	}
}

处理用户信息方法

//编辑用户
handleEdit(index, row) {
	this.titleDialog = '编辑用户';
	this.userDialog = true;
	this.userForm.Id = row.Id;
	this.userForm.Name = row.Name;
	this.userForm.Nick = row.Nick;
	this.userForm.PostId = row.PostId;
	this.userForm.Mobile = row.Mobile;
	this.userForm.Email = row.Email;
	
	for (let i in this.postOptions) {
		for (let j in this.postOptions[i].children) {
			if (row.PostId == this.postOptions[i].children[j].value) {
				this.postValue = [this.postOptions[i].value, row.PostId];
				break;
			}
		}
	}
}

//删除用户
async handleDelete(index, row) {
	this.$confirm(`确定删除 ${row.Name} 吗?`, '提示', {
		confirmButtonText: '确定',
		cancelButtonText: '取消',
		callback: action => {
			if (action === 'confirm') {
				this.delete_user(row.Id);
			}
		}
	});
}

//增加或编辑用户
async userDialogConfirm() {
	this.userForm.PostId = this.postValue[1];
	this.userForm.Valid = this.userForm.Valid ? 1 : 0;
	console.log(this.postValue, this.userForm);

	let results = [];
	if (this.titleDialog == '增加用户') {
		results = await this.$fetchPost('/add/AccountUsers/add_value', this.userForm);
	} else if (this.titleDialog == '编辑用户') {
		results = await this.$fetchPut(`/update/AccountUsers/update_value/${this.userForm.Id}`, JSON.stringify(this.userForm));
	} else {
		this.$message({
			message: '错误对话框',
			type: 'error'
		});
		return;
	}

	if (results.status == 200) {
		if (results.data.code == 0) {
			this.$message({
				message: this.titleDialog == '增加用户' ? '增加成功' : '编辑成功',
				type: 'success'
			});
			this.get_all_users();
			this.userDialog = false;
		} else {
			this.$message({
				message: results.data.msg,
				type: 'error'
			});
		}
	}
}

1.3. 源码文件

后台源码:VueDemo.zip
访问地址:http://127.0.0.1:5000
默认用户名:admin
默认密码:admin

1.4. 后记

本文完整讲述了全栈系统中的WEB端:利用Vue2.0创建前端页面(就是上一章中FlaskDemo中演示的页面),下一章介绍用Vue2.0+mint-ui创建移动端应用。

Logo

前往低代码交流专区

更多推荐