Vue项目入门实例
前言
本文记录 Vue2.x + Element-UI + TypeScript 语法入门实例
为什么要用 TypeScript?
1、TypeScript 是 JavaScript 的超集,利用 es6 语法,实现对 js 的面向对象编程思想;
2、TypeScript 会像强类型语言一样,可以避免出现不可预期的运行时 bug;
Vue 官网:https://cn.vuejs.org/
Element-UI 官网:https://element.eleme.cn/#/zh-CN
2021-03-03 更新:
element-ui 已经升级适配 vue3,并全新发布新项目:Element Plus
官网:https://element-plus.gitee.io/#/zh-CN
GitHub:https://github.com/element-plus/element-plus
Vue 对 TypeScript 的支持:https://cn.vuejs.org/v2/guide/typescript.html
TypeScript 的学习可以看回我们之前的《typescript》系列:https://www.cnblogs.com/huanzi-qch/category/1509796.html
vue+typescript 整合,推荐阅读这篇文章:https://segmentfault.com/a/1190000011744210
安装、启动
vue 项目需要 node 环境,开始安装之前,先安装 node 环境,安装 node 环境看之前的博客:TypeScript 环境安装,以及配置 idea 开发环境
装好 node 环境之后,先安装 Vue CLI 手脚架,通过手脚架来快速创建项目
npm install -g @vue/cli
装好之后使用命令查看版本号,确认一下
vue --version
装好了手脚架,然后就可以通过手脚架快速创建 portal 门户项目
PS:element-ui 现在还不支持 Vue3,所以我们现在 Vue2 就可以了,选择自定义安装把配置项全都勾上,或者可以先选择默认安装
vue create portal
后续再在 package.json 里面指定依赖包,直接使用 IDE(或者手敲命令也一样),运行安装命令下载依赖包
package.json 依赖配置
"dependencies": { "core-js": "^3.6.5", "register-service-worker": "^1.7.1", "vue": "^2.6.11", "vue-class-component": "^7.2.3", "vue-property-decorator": "^8.4.2", "vue-router": "^3.2.0", "vuex": "^3.4.0", "axios": "0.21.0", "element-ui": "^2.13.2", "js-cookie": "2.2.1" },
在 src 的同级目录下面,创建 vue.config.js 配置文件,配置端口等
module.exports = { publicPath: './', outputDir: 'dist', assetsDir: 'static', lintOnSave: true, productionSourceMap: false, devServer: { port: '10010', open: false, overlay: { warnings: false, errors: true }, proxy: { '/auth': { target: 'http://localhost:10086', secure: false, changeOrigin: true, pathRewrite: {'^/auth': '/'} }, '/api': { target: 'http://localhost:10086', secure: false, changeOrigin: true, pathRewrite: {'^/api': '/'} } } } }
使用 idea 打开项目,配置 config,即可在 idea 运行 vue 项目
或者也可以手动输入命令启动项目(vue-cli-service serve),或者在 package.json 文件中运行脚本
启动成功
项目结构
Vue Router
Vue Router 官网:https://router.vuejs.org/zh/
路由配置
import Vue from 'vue' import VueRouter, {RouteConfig} from 'vue-router'Vue.use(VueRouter);
/ 公用模块菜单路由 /
const commonRoutes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
meta: { title: '主页' },
component: () => import( '@/views/Home.vue')
},
{
path: '/404',
name: '404',
meta: { title: '404' },
component: () => import('@/views/common/404.vue')
},
{ path: '*', redirect: '/404'}
];/ test 模块菜单路由 /
const testRoutes: Array<RouteConfig> = [
{
path: '/test',
name: 'Test',
meta: { title: 'demo 测试' },
component: () => import( '@/views/test/Test.vue')
}
];const router = new VueRouter({
base:"/",
mode: 'hash',//history hash
routes:commonRoutes.concat(testRoutes)
});router.beforeEach(async(to, from, next) => {
console.log("跳转开始,目标:"+to.path);
document.title =${to.meta.title}
;</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">跳转页面</span>
next();
});router.afterEach(() => {
console.log("跳转结束");});
export default router
路由跳转、页面接参
/** * 工具类 */ export default class CommonUtil {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">* * 从url中获取参数 * 实例:http://xxxxx/index?id=1&name=张三 * getQueryVariable("id")//1 * getQueryVariable("name")//张三 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> public static getQueryVariable(variable:string): string { let vars </span>= window.location.search.substring(1).split("&"<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i < vars.length; i++<span style="color: rgba(0, 0, 0, 1)">) { let pair </span>= vars[i].split("="<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (pair[0] ===<span style="color: rgba(0, 0, 0, 1)"> variable) { </span><span style="color: rgba(0, 0, 255, 1)">return</span> pair[1<span style="color: rgba(0, 0, 0, 1)">]; } } </span><span style="color: rgba(0, 0, 255, 1)">return</span> ""<span style="color: rgba(0, 0, 0, 1)">; };
}
import CommonUtil from "@/utils/commonUtil"//跳转 params 是路由的一部分, 必须要有。query 是拼接在 url 后面的参数,没有也没关系
this.$router.push({name:'Home',
params: {id:'001'},
query: {id:'001'}
});//接参
let id = this.$route.params.id;
//如果为空,尝试从 url 中获取参数
if(!id){
id = CommonUtil.getQueryVariable("id");
}
或者直接在浏览器上输入 path 路径,即可跳转页面
Vuex
Vuex 官网:https://vuex.vuejs.org/zh/
vuex 配置
import Vue from 'vue' import Vuex from 'vuex'Vue.use(Vuex);
/
约定,组件不允许直接变更属于 store 实例的 state,而应执行 action 来分发 (dispatch) 事件通知 store 去改变
/
export default new Vuex.Store({
state: {
},
getters:{
},
mutations: {
},
actions: {
},
modules: {
}
})
项目入口
App.vue
<!-- 这里是项目路口,配置 <router-view/> 即可 --> <template> <div id="app"> <router-view/> </div> </template><script lang="ts">
</script>
<style lang="less">
html,body{
margin: 0 !important;
padding: 0 !important;
}
</style>
main.ts
import Vue from 'vue' import App from './App.vue' import './registerServiceWorker' import router from './router' import store from './store' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' // @ts-ignore import locale from 'element-ui/lib/locale/lang/zh-CN'Vue.use(ElementUI, { locale});
Vue.config.productionTip = false;
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
TypeScript 语法
Vue 对 TypeScript 的支持:https://cn.vuejs.org/v2/guide/typescript.html
vue+typescript 整合,推荐阅读这篇文章:https://segmentfault.com/a/1190000011744210
在使用 typescript 语法的过程中,我们使用官方维护的 vue-class-component 装饰器,这里是它的文档:https://class-component.vuejs.org/
这个就是它一个简单的写法
<template> <div> 简单页面 </div> </template><script lang="ts">
import {Component, Vue} from 'vue-property-decorator';@Component
export default class Home extends Vue {}
</script><style scoped>
</style>
完整测试例子
接下来介绍具体的使用,里面包含了常用的:data 数据、生命周期钩子函数、methods 普通方法、computed 获取 / 设置计算属性、watch 监听、props 组件数据传递
HelloWorld 组件
<template> <div class="hello"> <h1>{{msg}}</h1> </div> </template><script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator';@Component
export default class HelloWorld extends Vue {//props 组件数据传递
@Prop({ type: String,default: 'default value' }) private msg!: string;
}
</script><style scoped lang="less">
</style>
Test.vue 测试页面
<template> <div style="padding: 20px;"> <el-row> <el-col :span="12"> <div style="padding: 20px;"> <el-divider content-position="left"> 数据绑定测试 </el-divider> <el-row> <el-input placeholder="请输入新 msg 内容" v-model="msg" clearable></el-input> <p> 直接绑定 data 数据:{{msg}}</p> <p> 普通方法获取 data 数据:{{getMsg()}}</p> <p>computed 的 get 方法获取 data 数据:{{computedTest}}</p> <el-button type="primary" plain @click="buttonClick"> 调用 computed 的 set 方法修改 data 数据 </el-button> </el-row><el-divider content-position="left">引用HelloWorld组件测试</el-divider> <el-row> <HelloWorld :msg="msg"/> </el-row> <el-divider content-position="left"><span style="color: rgba(0, 0, 255, 1)">if</span>-else条件渲染测试</el-divider> <el-row> <p style="color: green" v-<span style="color: rgba(0, 0, 255, 1)">if</span>="flag">if条件渲染:<span style="color: rgba(0, 0, 255, 1)">true</span></p> <p style="color: red" v-<span style="color: rgba(0, 0, 255, 1)">else</span>="flag">if条件渲染:<span style="color: rgba(0, 0, 255, 1)">false</span></p> <el-button type="primary" plain @click="flag=!flag">if条件渲染取反</el-button> </el-row> </div> </el-col> <el-col :span="12"> <div style="padding: 20px;"> <el-divider content-position="left">for循环-数组渲染测试</el-divider> <el-row> <p v-<span style="color: rgba(0, 0, 255, 1)">for</span>="(item,index) in items"><span style="color: rgba(0, 0, 0, 1)"> 序号:{{index}},编号:{{item.id}},姓名:{{item.name}} </span></p> <el-button type="primary" plain @click="items.push({id:'0000',name:'new item'})">新增记录</el-button> </el-row> <el-divider content-position="left">for循环-对象渲染测试</el-divider> <el-row> <p v-<span style="color: rgba(0, 0, 255, 1)">for</span>="(value,key,index) in itemsByObj"><span style="color: rgba(0, 0, 0, 1)"> 序号:{{index}},{{key}}:{{value}} </span></p> </el-row> </div> </el-col> </el-row> </div>
</template>
<script lang="ts">
import {Component, Emit, Watch, Prop, Vue} from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue';@Component({ components: { HelloWorld, }, }) export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> class Test extends Vue { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">data 数据</span> private msg:string = "test测试"<span style="color: rgba(0, 0, 0, 1)">; private flag:</span><span style="color: rgba(0, 0, 255, 1)">boolean</span> = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">; private items:any </span>=<span style="color: rgba(0, 0, 0, 1)"> [ {id:</span>1001,name:"张三"<span style="color: rgba(0, 0, 0, 1)">}, {id:</span>1002,name:"李四"<span style="color: rgba(0, 0, 0, 1)">}, {id:</span>1002,name:"王五"<span style="color: rgba(0, 0, 0, 1)">}, ]; private itemsByObj:object </span>=<span style="color: rgba(0, 0, 0, 1)"> { id:</span>1001<span style="color: rgba(0, 0, 0, 1)">, name:</span>"huanzi-qch"<span style="color: rgba(0, 0, 0, 1)">, age:</span>18<span style="color: rgba(0, 0, 0, 1)">, email:</span>"huanzi-qch@qq.com"<span style="color: rgba(0, 0, 0, 1)">, phone:</span>"12345678900"<span style="color: rgba(0, 0, 0, 1)">, }; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生命周期钩子函数</span>
created(){
console.log("created");
};
mounted(){
console.log("mounted");
};</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">methods 普通方法</span>
@Emit()
getMsg(): string{
return this.msg;
}
@Emit()
buttonClick(): void{
this.computedTest = 'test 测试 0001';
}</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">computed 获取/设置计算属性</span>
get computedTest(): string{
return this.msg;
}
set computedTest(newMsg:string){
this.msg = newMsg;
}</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">watch 监听</span> @Watch('msg'<span style="color: rgba(0, 0, 0, 1)">) onMsgChanged(newVal: string, oldVal: string) { </span><span style="color: rgba(0, 0, 255, 1)">this</span>.$message.info("msg值发生改变,旧值:" + oldVal + ",新值:" +<span style="color: rgba(0, 0, 0, 1)"> newVal); } }
</script>
<style scoped>
</style>
效果演示
路由配置哪里要注意
//history:路径直接是 /test,部署到 Tomcat 后不能直接访问地址栏 //hash:路径会多一层 /#/test,部署到 Tomcat 后能直接访问地址栏 mode: 'hash',
环境配置文件
配置文件的 key,要以 VUE_APP_ 开头
读取
<template> <div> {{adminUrl}} </div> </template><script lang="ts">
import {Component, Vue} from 'vue-property-decorator';@Component
export default class Home extends Vue {
//读取环境配置值
private adminUrl:string = process.env.VUE_APP_ADMIN_URL;
}
</script><style scoped>
</style>
favicon.ico 图标
通常情况下,我们是这样设置自己的 favicon.ico
在项目 HTML 页面中,引入我们的 favicon 图片
但是不管是在运行之后的页面,还是打包之后的页面,这行代码都是被注释起来的
我们可以再打包生成的 index 页面中,把这个注释放开,这样就可以正常显示我们的 favicon 图片了
同时、我们可以再 vue.config.js 中进行设置
pwa 配置项说明:https://cli.vuejs.org/zh/config/#pwa
效果
打包、部署
vue.conifg.js 中指定好打包路径
publicPath: './', outputDir: 'dist', assetsDir: 'static',
同时,路由配置那里要注意,模式要改成 mode: 'hash'
const router = new VueRouter({ base:"/", mode: 'hash',//history hash routes:commonRoutes.concat(testRoutes,adminRoutes) });
直接运行打包脚本,或者手动输入命令
打包成功
复制 dist 文件夹到 Tomcat 容器,把 Tomcat 运行起来
访问:http://172.16.35.52:10086/dist/,即可跳转到我们配置的 / 路径页面
在此路径基础上就可以访问我们配置的路由路径了,如:http://172.16.35.52:10086/dist/#/test
2020-11-20 更新
路由的配置要注意
//history:路径直接是 /test,文件丢到 Tomcat 的 webapps,文件夹名 + url 路径不能访问(需要把文件放在 ROOT 默认文件夹下面) //hash:路径会多一层 /#,/#/test,文件丢到 Tomcat 的 webapps,文件夹名 + url 路径能访问const router = new VueRouter({
base:"/portal",
mode: 'history',
routes:commonRoutes.concat(testRoutes)
});
同时要注意,我们在 vue.conifg.js 中配置的服务信息,依赖 node 环境,如果是 Tomcat,没有代理功能,需要配合 nginx 上配置代理地址
devServer: { port: '10010', open: false, overlay: { warnings: false, errors: true }, proxy: { '/auth': { target: 'http://localhost:10086', secure: false, changeOrigin: true, pathRewrite: { '^/auth': '/' } }, '/api': { target: 'http://localhost:10086', secure: false, changeOrigin: true, pathRewrite: { '^/api': '/' } } } }
后记
Vue 项目入门实例就暂时记录到这,后续再继续更新
代码开源
注:portal 前端就是本文中的 vue 项目
代码已经开源、托管到我的 GitHub、码云: