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&amp;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("&amp;"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i = 0; i &lt; 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>
                &lt;el-divider content-position="left"&gt;引用HelloWorld组件测试&lt;/el-divider&gt;
                &lt;el-row&gt;
                    &lt;HelloWorld :msg="msg"/&gt;
                &lt;/el-row&gt;

                &lt;el-divider content-position="left"&gt;<span style="color: rgba(0, 0, 255, 1)">if</span>-else条件渲染测试&lt;/el-divider&gt;
                &lt;el-row&gt;
                    &lt;p style="color: green" v-<span style="color: rgba(0, 0, 255, 1)">if</span>="flag"&gt;if条件渲染:<span style="color: rgba(0, 0, 255, 1)">true</span>&lt;/p&gt;
                    &lt;p style="color: red" v-<span style="color: rgba(0, 0, 255, 1)">else</span>="flag"&gt;if条件渲染:<span style="color: rgba(0, 0, 255, 1)">false</span>&lt;/p&gt;
                    &lt;el-button type="primary" plain @click="flag=!flag"&gt;if条件渲染取反&lt;/el-button&gt;
                &lt;/el-row&gt;
            &lt;/div&gt;
        &lt;/el-col&gt;
        &lt;el-col :span="12"&gt;
            &lt;div style="padding: 20px;"&gt;

                &lt;el-divider content-position="left"&gt;for循环-数组渲染测试&lt;/el-divider&gt;
                &lt;el-row&gt;
                    &lt;p v-<span style="color: rgba(0, 0, 255, 1)">for</span>="(item,index) in items"&gt;<span style="color: rgba(0, 0, 0, 1)">
                        序号:{{index}},编号:{{item.id}},姓名:{{item.name}}
                    </span>&lt;/p&gt;
                    &lt;el-button type="primary" plain @click="items.push({id:'0000',name:'new item'})"&gt;新增记录&lt;/el-button&gt;
                &lt;/el-row&gt;

                &lt;el-divider content-position="left"&gt;for循环-对象渲染测试&lt;/el-divider&gt;
                &lt;el-row&gt;
                    &lt;p v-<span style="color: rgba(0, 0, 255, 1)">for</span>="(value,key,index) in itemsByObj"&gt;<span style="color: rgba(0, 0, 0, 1)">
                        序号:{{index}},{{key}}:{{value}}
                    </span>&lt;/p&gt;
                &lt;/el-row&gt;

            &lt;/div&gt;
        &lt;/el-col&gt;
    &lt;/el-row&gt;
&lt;/div&gt;

</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、码云:

  GitHub:https://github.com/huanzi-qch/fast-scaffold

  码云:https://gitee.com/huanzi-qch/fast-scaffold