Vue2.0搭建前端系统
1. Vue2.0搭建前端系统本文讲述用Vue2.0 + element-ui创建一个web前端页面,这属于全栈系统中的WEB端,项目包含以下内容:入口页面:定义登录页面和路由跳转登录页面:实现系统登录功能业务页面:写了两个业务页面1.> three.js加载gltf格式3D模型;2.> echart画图;系统页面:实现用户信息管理页面效果图:1.1. 前端页面开发选用的技术栈如下:开
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创建移动端应用。
更多推荐
所有评论(0)