分享不易,喜歡的話必定別忘了點💖javascript
只關注不點💖的都是耍流氓
css
只收藏也不點💖的也同樣是耍流氓
。html
ts香不香這裏我就不作過多吹捧了,基礎api官方文檔有比較清晰的介紹,本篇博客主要分享Vue+typescript+element-ui,後臺管理系統實戰篇。前端
簡單來講,ts屬於強類型語言,它的優點在於靜態類型檢查,歸納來講主要包括如下幾點:vue
yarn install //依賴安裝
yarn run serve //項目啓動
yarn run build:prod //打包
複製代碼
本次項目基礎框架爲Vue,跟正常的Vue項目仍是或多或少有很多差距的。衆所周知,js是一門弱類型的語言,尤爲是在變量賦值時,永遠都是給變量直接賦值各類類型值來初始化,線上一些隱藏的bug就冷不防會暴露出來。把這種錯誤扼殺在項目開發編譯階段而非上線階段,全部就有了typescript超集的出現。java
那Vue中是怎麼引用typescript項目的呢,項目開始以前,先大體介紹一番ts在Vue項目中的基礎用法node
vue-property-decoratorwebpack
vue-property-decorator
在vue-class-component
的基礎上增長了更多與Vue
相關的裝飾器,使Vue
組件更好的跟TS結合使用。這二者都是離不開裝飾器的,(decorator)裝飾器已在ES提案中。Decorator
是裝飾器模式的實踐。裝飾器模式呢,它是繼承關係的一個替代方案。動態地給對象添加額外的職責。在不改變接口的前提下,加強類的性能。ios
vue-property-decorator
是這個Vue項目文件中徹底依賴的庫,它是Vue官方推薦的而且依賴於vue-class-component,先介紹下它在項目中的常見用法。git
首先,Vue頁面中的script部分要加一個lang=ts,這樣安裝好typescript正能引用
<script lang="ts">
import {Vue, Component} from 'vue-property-decorator';
import BaseHeader from '@/components/BaseHeader';
//公共頭部組件
@Component({
components: {
BaseHeader
}
})
export default class extends Vue {
private stateA:boolean = true
private stateB:string = ''
private stateC:number = 0
private stateD:any = {}
stateE:any[] = []
}
</script>
複製代碼
等同於
<script>
import Vue from 'vue';
import BaseHeader from '@/components/BaseHeader'; //公共頭部組件
export default {
components: {
BaseHeader
},
data(){
return {
stateA: true,
stateB: '',
stateC: 0,
stateD: {},
stateE: []
}
}
}
</script>
複製代碼
vue-property-decorator
在項目中的應用最主要是起一個裝飾器的做用,差別化的話看對比就很是直觀了
data變量的定義比較多元化,這裏區別有加private,不加就是public,當變量標記爲private時,它就不能在聲明它的類的外部訪問。
@Component
裝飾器屬性名必須得寫上
父子組件之間的屬性傳值
export default class extends Vue {
@Prop({ default: 0 }) private propA!: number
@Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
@Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
@Prop({ default: true }) private propD!: boolean,
@prop([String, Boolean]) propE: string | boolean;
}
複製代碼
等同於
export default {
props: {
propA: {
type: Number
},
propB: {
type: Array,
default: [10, 20, 30, 50]
},
propC: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
propD: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
propE: {
type: [String, Boolean]
}
}
}
複製代碼
這裏有兩個經常使用修飾符!``?
,!
和可選參數?
是相對的, !
表示強制解析(也就是告訴typescript編譯器,我這裏必定有值),你寫?
的時候再調用,typescript
會提示可能爲undefined
Component
export default class YourComponent extends Vue {
count = 0
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue() {
return 10
}
@Emit()
onInputChange(e) {
return e.target.value
}
}
複製代碼
等同於
export default {
data() {
return {
count: 0
}
},
methods: {
resetCount() {
this.count = 0
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
}
}
}
複製代碼
@Emit裝飾器
的函數會在運行以後觸發等同於其函數名(駝峯式會轉爲橫槓式寫法)
的事件, 並將其函數傳遞給$emit
@Emit觸發事件有兩種寫法
@Watch裝飾器主要用於替代Vue
屬性中的watch
屬性,監聽依賴的變量值變化而作一系列的操做
@Component
export default class YourComponent extends Vue {
@Watch('child')
onChildChanged(val: string, oldVal: string) {}
@Watch('person', { immediate: true, deep: true })
onPersonChanged(val: Person, oldVal: Person) {}
}
複製代碼
等同於
export default {
watch: {
child(val, oldVal) {},
person: {
handler(val, oldVal) {},
immediate: true,
deep: true
}
}
}
複製代碼
watch 是一個對象,對象就有鍵,有值。
@Watch
使用很是簡單,接受第一個參數爲要監聽的屬性名, 第二個屬性爲可選對象。@Watch所裝飾的函數即監聽到屬性變化以後應該執行的函數。
@Watch
裝飾的函數的函數名並不是如上onStateChanged
嚴格命名,它是多元化的,你能夠爲所欲爲的命名,固然,能按照規範化的命名會使你的代碼閱讀性更好。
// myMixin.ts
@Component
export default class MyMixin extends Vue {
mixinValue:string = 'Hello World!!!'
}
複製代碼
// 引用mixins
import MyMixin from './myMixin.js'
@Component
export default class extends mixins(MyMixin) {
created () {
console.log(this.mixinValue) // -> Hello World!!!
}
}
複製代碼
而後我又偷學到了另一種mixins寫法,記錄一下
先改造一下myMixin.ts
,定義vue/type/vue
模塊,實現Vue接口
// myMixin.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
interface Vue {
mixinValue: string;
}
}
@Component
export default class myMixins extends Vue {
mixinValue: string = 'Hello World!!!'
}
複製代碼
引用
import { Vue, Component, Prop } from 'vue-property-decorator';
import MyMixin from './myMixin.js'
@Component({
mixins: [MyMixin]
})
export default class extends Vue{
created(){
console.log(mixinValue) // => Hello World!!!
}
}
複製代碼
兩種方式不一樣在於定義mixins
時若是沒有定義vue/type/vue
模塊, 那麼在混入的時候就要繼承該mixins
; 若是定義vue/type/vue
模塊,在混入時能夠在@Component
中mixins
直接混入。
@Model裝飾器
容許咱們在一個組件上自定義v-model
,接收兩個參數:
import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class MyInput extends Vue {
@Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
}
複製代碼
等同於
<template>
<input
type="text"
:value="value"
@change="$emit('change', $event.target.value)"
/>
</template>
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: String,
default: 'Hello world!!!'
}
}
}
複製代碼
@Provide
聲明一個值 , 在其餘地方用 @Inject
接收,在實戰項目中用得很少,通常用於不依賴於任何第三方狀態管理庫(如vuex)的組件編寫
@Ref
裝飾器接收一個可選參數,用來指向元素或子組件的引用信息。若是沒有提供這個參數,會使用裝飾器後面的屬性名充當參數
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'
@Componentexport default class MyComponent extends Vue {
@Ref() readonly loginForm!: Form
@Ref('changePasswordForm') readonly passwordForm!: Form
public handleLogin() {
this.loginForm.validate(valide => {
if (valide) {
// login...
} else {
// error tips
}
})
}
}
複製代碼
等同於
export default {
computed: {
loginForm: {
cache: false,
get() {
return this.$refs.loginForm
}
},
passwordForm: {
cache: false,
get() {
return this.$refs.changePasswordForm
}
}
}
}
複製代碼
使用時切記要引入修飾器
import {
Vue,
Component,
Prop,
Component,
Emit,
Provice,
Inject,
Watch,
Model,
Minxins,
} from 'vue-property-decorator'
複製代碼
如下的public、private
在引入tslint後是必寫的,不然會有警告,若是沒有引的話是能夠不寫的
Ts | Js | 說明 |
---|---|---|
public created() {} | created() {} | 初始化 |
public mounted() {} | mounted() {} | 掛載完畢 |
private _getInitData() {} | methods: { _getInitData() {} } | 方法 |
private get _userName() {} | computed: { _userName() {} } | 計算屬性 |
public destroyed() {} | destroyed() {} | 銷燬生命週期 |
安裝vue-cli
最新版
npm install -g @vue/cli
# OR
yarn global add @vue/cli
//查看是否安裝正確
vue --version
複製代碼
建立項目
? Please pick a preset:(使用上下箭頭)
◯ default (babel, eslint) //默認配置
❯◉ Manually select features //手動選擇
複製代碼
? Check the features needed for your project:
◉ Babel // javascript轉譯器
◉ TypeScript // 使用 TypeScript 書寫源碼
◯ Progressive Web App (PWA) Support // 漸進式WEB應用
◉ Router // 使用vue-router
◉ Vuex // 使用vuex
◉ CSS Pre-processors // 使用css預處理器
❯◉ Linter / Formatter // 代碼規範標準
◯ Unit Testing // 單元測試
◯ E2E Testing // e2e測試
複製代碼
是否使用class風格的組件語法: 使用前:home = new Vue()建立vue實例 使用後:class home extends Vue{}
? Use class-style component syntax? (Y/n) Y
// 使用Babel與TypeScript一塊兒用於自動檢測的填充
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Y
// 路由
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
// 預處理器
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
❯◉ Sass/SCSS (with dart-sass) // 保存後編譯
◯ Sass/SCSS (with node-sass) // 實時編譯
◯ Less
◯ Stylus
// 代碼格式化檢測
? Pick a linter / formatter config: (Use arrow keys)
◯ ESLint with error prevention only // 只進行報錯提醒
◯ ESLint + Airbnb config // 不嚴謹模式
◯ ESLint + Standard config // 正常模式
◯ ESLint + Prettier // 嚴格模式
❯◉ TSLint(deprecated) // typescript格式驗證工具
// 代碼檢查方式
? Pick additional lint features: (Press <space> to select, <a>
to toggle all, <i> to invert selection)
❯◉ Lint on save // 保存檢查
◯ Lint and fix on commit // commit時fix
// 文件配置
? Where do you prefer placing config for Babel, ESLint, etc.? (
Use arrow keys)
❯ In dedicated config files // 配置在獨立的文件中
In package.json
// 保存上述配置,保存後下一次可直接根據上述配置生成項目
? Save this as a preset for future projects? (y/N) N
// 建立成功
🎉 Successfully created project vue-typescript-admin-demo.
複製代碼
yarn run serve
運行項目以後會報一堆莫名的錯誤,這都是 tslint.json 搞的鬼,配置一下從新運行便可
// tsconfig.json
Error: Calls to 'console.log' are not allowed.
Error: 去除行尾必加';'
Error: 禁止自動檢測末尾行必須使用逗號,always老是檢測,never從不檢測,ignore忽略檢測
"rules": {
"no-console": false,
"semicolon": [
false,
"always"
],
"trailing-comma": [true, {
"singleline": "never",
"multiline": {
"objects": "ignore",
"arrays": "ignore",
"functions": "never",
"typeLiterals": "ignore"
}
}]
}
複製代碼
至此,整個項目算是正常運行起來了。But... 這仍是傳統的Vue項目,咱們要開發的是Vue+ts實戰項目,因此須要改造一番,詳細的目錄結構,等改造完以後再附上吧。
按照如下的目錄結構改造項目
.
├─ public/ # 模板文件
├─ dist/ # build 生成的生產環境下的項目
├─ src/ # 源碼目錄(開發都在這裏進行)
│ ├─ api/ # 服務(SERVICE,統一Api管理)
│ ├─ assets/ # 靜態資源文件
│ ├─ components/ # 組件
│ ├─ filters/ # 全局過濾器
│ ├─ icons/ # svg轉ts格式的icon
│ ├─ lang/ # 國際化語言
│ ├─ layout/ # 架構佈局
│ ├─ router/ # 路由(ROUTE)
│ ├─ store/ # 模塊化狀態管理vuex
│ ├─ styles/ # 公共樣式
│ ├─ utils/ # 工具庫
│ ├─ views/ # 視圖頁(pages)
│ ├─ App.vue # 啓動文件
│ ├─ main.ts # 主入口頁
│ ├─ permission.ts # 路由鑑權
│ ├─ shims-tsx.d.ts # 相關 tsx 模塊注入
│ ├─ shims-vue.d.ts # Vue 模塊注入
│ ├─ .env.development # 開發環境默認API屬性配置
│ ├─ .env.production # 線上環境默認API屬性配置
│ ├─ babel.config.js # babel配置
複製代碼
主要涉及 shims-tsx.d.ts
和 shims-vue.d.ts
兩個文件
shims-tsx.d.ts
,容許你以 .tsx
結尾的文件,在 Vue
項目中編寫 jsx
代碼shims-vue.d.ts
主要用於 TypeScript
識別 .vue
文件, ts
默認並不支持導入 .vue 文件
,這個文件告訴 ts
導入 .vue 文件
都按 VueConstructor<Vue>
處理。把vue-cli攜帶的home about文件刪除,清空components文件夾的內容,把路由的指向到主入口App.vue文件,修改App.vue的內容
<!--App.vue-->
<template>
<div id="app">
<p>hello world!!!</p>
<img alt="Vue logo" src="@/assets/logo.png" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'app'
})
export default class extends Vue {
private created() {
console.log(12345)
}
}
</script>
複製代碼
第一個ts的文件就這麼搞定了,接下來大刀闊斧的改造吧。
修改路由,新增login
登陸頁和主骨架搭建頁面、以及dashboard首頁
路由對象都以懶加載的形式引入,而且備註webpackChunkName
,便於查找。
根路由/
重定向到/dashboard
const routes = [
{
// webpackChunkName:懶加載後的文件名
component: () => import(/* webpackChunkName: "login" */ '@/views/login/index.vue'),
...
}
]
複製代碼
安裝normalize.css
初始化css
yarn add normalize.css
安裝element-ui
最新版
按需引入
配置babel.config.js
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
複製代碼
運行後報錯,須要安裝babel-plugin-component
解析
ERROR : Cannot find module 'babel-plugin-component' from '/Users/zeng/Desktop/hello/vue-typescript-admin-demo'
複製代碼
yarn add babel-plugin-component --dev
在main.ts
主入口文件裏面引用normalize.css
和element-ui
// main.ts
import 'normalize.css'
import ElementUI from 'element-ui'
Vue.use(ElementUI)
複製代碼
如今去Vue
頁面裏面就能爲所欲爲的引用element-ui
的組件了,並且不須要在任何頁面裏再次import
。
定製主題本想在搭建後管理後臺的主骨架的時候寫的,但轉眼一想,要開始搭建頁面UI了,到時候再回頭改一遍太折騰了,so...就先來個定製主題吧
每一個項目都有本身風格的主題,因爲在項目中應用的是sass預處理器,定製化修改UI顏色背景,固然要結合sass來完成
.
├─ src/ # 源碼目錄(開發都在這裏進行)
│ ├─ styles/
│ ├─── element-variables.scss
│ ├─── element-variables.scss.d.ts
複製代碼
/* Element Variables */
// Override Element UI variables
$--color-primary: #1890ff;
...
// Icon font path, required
$--font-path: '~element-ui/lib/theme-chalk/fonts';
// Apply overrided variables in Element UI
@import '~element-ui/packages/theme-chalk/src/index';
// The :export directive is the magic sauce for webpack
// https://mattferderer.com/use-sass-variables-in-typescript-and-javascript
:export {
theme: $--color-primary;
}
複製代碼
因爲這裏引用到了:export
,你沒看錯,確實是:export
,具體的解釋等大佬指教吧.... 官方是這麼解釋的 :export directive is the magic sauce for webpack`,充滿魔法,webpack的語法糖???
在ts的規則裏,你必須按照它的規範來進行每一步的開發,所以你要加上element-variables.scss.d.ts
對應的.d.ts
文件
// element-variables.scss.d.ts
export interface IScssVariables {
theme: string
}
export const variables: IScssVariables
export default variables
複製代碼
好了,如今你能夠在主入口文件裏面引用定製主題的文件配置了。
// main.ts
import '@/styles/element-variables.scss'
複製代碼
簡單的框架主題定製就這麼完成了,後面會加深擴展,加一個換膚的功能,先繼續下一步。定義全局的統一sass變量,方便統一規範css顏色
爲了防止後期再次聲明這個統一變量,這裏暫且把以後的菜單欄的變量也加上吧
// variables.scss
// Base color
$blue:#324157;
...
// Sidebar
$subMenuBg:#1f2d3d;
...
// Login page
$loginBg: #2d3a4b;
...
// The :export directive is the magic sauce for webpack
// https://mattferderer.com/use-sass-variables-in-typescript-and-javascript
:export {
menuBg: $menuBg;
}
複製代碼
同上,對應的解析文件
// variables.scss.d.ts
export interface IScssVariables {
menuBg: string
}
export const variables: IScssVariables
export default variables
複製代碼
全局變量定義好了,那如何加入到項目中呢?直接在主入口文件main.ts
裏面引用是不生效的,因此這裏咱們要換個思路,放在style-resources-loader
裏面去引入,在整個項目的配置文件裏去配置。
style-resources-loader
是個什麼鬼,搜索了個全局都沒發現這個配置項,vue-li 3.0+
的配置乾乾淨淨的,不想老版本vue-li 2.0
,有對應的config文件夾對應各類不一樣的環境來作選項配置,那怎麼配置vue-li 3.0+
呢
就不賣關子了,直接上代碼吧。首先咱們要在根目錄的同級新建一個vue.config.js
,也就是說,對應vue-li 2.0
版本乾的事,在vue-li 3.0
裏面就須要咱們本身按需配置了
新建vue.config.js
文件
把統一的scss變量引入到插件選項卡中去,這裏須要安裝兩個類庫,不然會沒法解析
yarn add vue-cli-plugin-style-resources-loader yarn add style-resources-loader --dev // 開發環境loader編譯
// vue.config.js
module.exports = {
pluginOptions: {
'style-resources-loader': {
preProcessor: 'scss',
patterns: [
path.resolve(__dirname, 'src/styles/variables.scss'),
path.resolve(__dirname, 'src/styles/mixins.scss')
]
}
},
}
複製代碼
除了variables.scss
,對應還新增了一個mixins.scss
,即全局混入的mixins混合變量。
好了,公共的sass變量就完成了,能夠應用到項目中去了。
具體的全局各個樣式設定能夠參考@/styles/
目錄下的文件。
如今能夠正式開始搭建頁面了。
在tslint
添加以下配置
// tslint.json
// 不檢測隱式類型
"arrow-parens": [
false,
"as-needed"
]
複製代碼
登陸頁面比較簡潔,沒有什麼酷炫的樣式
再強調一遍,開發新頁面,必定要轉換觀念,不要把原始的寫法風格引入進來,那就達不到引入ts
的目的了
template
模塊是幾乎沒有變化,script
須要加上lang="ts"
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
//類組件@Component 必寫
@Component({
name: 'login'
})
export default class extends Vue {
private loading:bollen = false // data變量
private get pageSize() { // 計算屬性
return 10
}
private created() { ... }
private mounted() { ... }
private handleLogin() { ... } // methods方法
public destroyed() {} // 銷燬聲明週期
}
</script>
複製代碼
登陸頁用了element-ui
的Form表單
組件。雖然template
模板部分沒有什麼變化,可是js Form表單
部分差別化仍是比較大的
!!!關閉tslint
這個tslint真的是讓有強迫症的我無法在繼續下去了,關了吧,一堆的warn
....
// tslint.json
{
"defaultSeverity": "none", // 值爲warn時爲警告
"rules": {
...
}
}
複製代碼
世界頓時清淨了~~~ 有硬須要的朋友能夠自行打開,前提是必定要配置好tslint
規則,不然仍是有點痛苦不堪的,畢竟warn
多了看着難受。告辭
迴歸主題。
這裏要提別提一下表單校驗,對,就是el-form
的rules
屬性值,經過Form-Item Attributes
的prop
達到表單校驗的功能
import { Form as ElForm } from 'element-ui'
export default class extends Vue {
private validateMobilePhone = (
rule: any,
value: string,
callback: Function
) => {
if (!value.trim()) {
callback(new Error('請輸入手機號'))
} else {
callback()
}
}
private validatePassword = (rule: any, value: string, callback: Function) => {
if (value.length < 6) {
callback(new Error('密碼長度不能小於6位'))
} else {
callback()
}
}
private loginRules = {
mobilePhone: [{ validator: this.validateMobilePhone, trigger: 'blur' }],
password: [{ validator: this.validatePassword, trigger: 'blur' }]
}
private handleLogin() {
(this.$refs.loginForm as ElForm).validate(async (valid: boolean) => {
if (valid) {
...
}
});
}
}
複製代碼
以上即爲表單登陸校驗,須要特別提醒的就是,登陸事件是(this.$refs.loginForm as ElForm).validate() => {}
,而非this.$refs.loginForm.validate() => {}
,與原始有着較爲明顯的差別。
直接用後者的話,是會直接報錯的
因此這裏你要先引用element-ui
的Form
組件,而後再使用this.$refs.loginForm as ElForm
,這樣你就有Form
組件的validate
方法了,自此登陸頁表單校驗就搭建完成了
接下來是api
請求
本項目使用axios api
請求工具庫
yarn add axios
封裝api
工具庫
/**
src->untils->request.ts
**/
import axios from 'axios'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// Request interceptors
service.interceptors.request.use(
(config) => {
// Add Authorization header to every request, you can add other custom headers here
config.headers['Authorization'] = 'token信息配置'
return config
},
(error) => {
Promise.reject(error)
}
)
// Response interceptors
service.interceptors.response.use(
(response) => {
攔截操做...
},
(error) => {
return Promise.reject(error)
}
)
export default service
複製代碼
總的來講,跟原始版本的工具庫封裝沒什麼明顯差別,主要就是配置一下幾個點
timeout
api
公共請求頭baseURL
Request interceptors
Response interceptors
config.headers
token
權限配置具體的api工具類能夠參考源碼
這裏有個process.env.VUE_APP_BASE_API
變量值,你能夠直接在當前的工具類根據環境配置,也能夠根據vue-cli 3.0
內置的文件配置,第二種方案在根目錄下
.
├─ src/
├─ .env.development // 開發環境配置
├─ .env.production // 線上環境配置
...
複製代碼
VUE_APP_BASE_API
變量名爲固定命名,沒法修改
// .env.development
VUE_APP_BASE_API = '192.168.1.1:8090'
// .env.production
VUE_APP_BASE_API = 'https://www.baidu.con'
複製代碼
注意:
在這裏配置好VUE_APP_BASE_API
,前提是服務端已經作好跨域處理,若是服務端接口沒有作跨域處理,那這裏配置api的請求頭就無效了。你能夠在vue.config.js
裏面配置跨域代理屬性,本項目接口已作跨域處理,因此這裏註釋了,須要能夠自行打開
const mockServerPort = 8090
module.exports = {
devServer: {
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:${mockServerPort}/mock-api/v1`,
changeOrigin: true, // 若是接口跨域,須要進行這個參數配置
// ws: true,// proxy websockets
pathRewrite: { // pathRewrite方法重寫url
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
}
}
}
複製代碼
配置好請求頭後,後面全部的接口調用都是
url = VUE_APP_BASE_API + url
好了,接下來按照順序應該安利一下使用,爲了體驗性和可維護性更友好點,api
接口模塊會分頁面定義
.
├─ src/ # 源碼目錄
│ ├─ api/
│ ├─── pageA.ts
│ ├─── pageB.ts
...
複製代碼
定義api
接口
import request from '@/utils/request'
export const login = (data: any) =>
request({
url: '/api/login',
method: 'post',
data
})
複製代碼
api
工具類封裝完成,在開始使用以前,先了解一波vuex+ts
版的狀態管理吧,由於調用login
接口成功後會存儲用戶和token
信息,會用到狀態管理
傳統的vuex在vue+ts的項目裏面是行不通的,vue 2.0
版本對ts的兼容性自己並非特別友好,因此要達到狀態管理的效果,這裏要額外引用一個類庫vuex-module-decorators
,它是基於vue-class-component
所作的拓展,它提供了一系列的裝飾器,讓vue+ts
結合的項目達到狀態管理的做用。
vue-class-component
主要提供瞭如下的裝飾器,接下來讓咱們一一的瞭解一遍吧
import { VuexModule, Module, Action, Mutation, getModule, State } from 'vuex-module-decorators'
先來看看要完成的模塊化管理的目錄結構
.
├─ src/
│ ├─ store/
│ ├─── modules/
│ │ ├─ app.ts
│ │ ├─ user.ts
│ ├─── index.ts
複製代碼
動手改造index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'
Vue.use(Vuex)
export interface IRootState {
app: IAppState
user: IUserState
}
// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store<IRootState>({})
複製代碼
等同於
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
user
}
})
export default store
複製代碼
這樣,模塊化狀態管理的雛形就完成了。對比來看,只是語法風格的變化,其它的變化不大。ts
版的狀態管理最大的改變體如今各個功能功能函數上
先看一看原始的vuex配置,輕車熟路
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
});
複製代碼
爲了顯得不那麼囉嗦,直接上版ts
版的狀態管理吧,能夠有個直觀的對比
// user.ts
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
import store from '@/store'
export interface IUserState {
id_token: string
}
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public id_token = ''
@Mutation
private SET_TOKEN(token: string) {
this.id_token = token
}
@Action
public async Login(params: any) {
this.SET_TOKEN(`token!!!`)
}
}
export const UserModule = getModule(User)
複製代碼
解析:
咱們看到了一堆的@
開頭的裝飾器函數@Mutation
@Mutation
@Module
...
先來一張表格對比一下差別化吧
Ts | Js |
---|---|
public State | state |
@Mutations | mutations |
@Action | action |
get | getters |
定義一個modules
,直接使用裝飾器@Module
注意:原始的vuex一樣有一個名爲Module
的類,但它不是一個裝飾器,因此別用混淆了
@Module({ dynamic: true, store, name: 'user' })
從上面能夠看到,咱們定義modules
不僅僅用了裝飾器,還帶了參數值,這個是代表是經過命名空間的形式來使用module
,如上,這裏的namespaced
值即爲user
詳細vuex命名空間
的說明,能夠參考vuex命名空間
除了namespaced
,咱們看到還有另一個參數值store
,它即爲主入口頁對應的整個vuex模塊的store
import store from '@/store'
若是去掉它的話,瀏覽器會報如下錯誤
這裏全部的state屬性由於加了tslint
都會添加上public
修飾,其它的用法都是類似的
原始的getters
計算函數,在這裏對應的即便get
方法,即
@Module
export default class UserModule extends VuexModule {
countsNum = 2020
get calculatCount() {
return countsNum / 2
}
}
複製代碼
等同於
export default {
state: {
countsNum: 2
},
getters: {
calculatCount: (state) => state.countsNum / 2
}
}
複製代碼
@Mutation
private SET_TOKEN(token: string) {
this.token = token
}
@Mutation
...
複製代碼
等同於
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
...
}
複製代碼
說明:
Mutation
同步方法都是定義在mutations
內,而ts
版的每個Mutation
都要加上裝飾器@Mutation
修飾注意: 一旦使用@Mutation裝飾
某一函數後, 函數內的this上下文
即指向當前的state
,因此想引用state
的值,能夠直接this.token
訪問便可。
Muation函數
不可爲async函數
, 也不能使用箭頭函數
來定義, 由於在代碼須要在運行從新綁定執行的上下文
@Action
public async Login(userInfo: { username: string, password: string}) {
...
this.SET_TOKEN(data.accessToken)
}
複製代碼
等同於
actions: {
async Login({ commit }, data) {
...
commit('SET_TOKEN', data.accessToken)
}
}
複製代碼
說明:
異步函數Action
和同步函數Mutation
使用方法大同小異,區別就是一個是同步,一個是異步,只要作好區分便可
注意:
=>
來定義action函數, 由於在運行時須要動態綁定this
上下文vuex+ts
版的配置搭建成功,接下來咱們把它運用到項目中來吧,這裏抽一個登錄頁面的模塊作介紹
import {
VuexModule,
Module,
Action,
Mutation,
getModule
} from 'vuex-module-decorators'
import { login } from '@/api/users' //調用api方法
import store from '@/store'
//聲明user模塊的state變量類型
//export interface 只是對一個東西的聲明(不能具體的操做)
//export class 導出一個類 類裏面可有參數 ,函數,方法(幹一些具體的事情)
export interface IUserState {
id_token: string
}
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public id_token = ''
@Mutation
private SET_TOKEN(token: string) {
//同步存儲id_token變量
this.id_token = token
}
@Action
public async Login(params: any) {
let { mobilePhone, password } = params
const { data } = await login({ mobilePhone, password })
this.SET_TOKEN(`Bearer ${data.id_token}`)
}
}
export const UserModule = getModule(User)
複製代碼
在login頁面中調用
import { UserModule } from '@/store/modules/user'
await UserModule.Login({
...this.loginForm,
router: this.$router
})
複製代碼
把路由對象做爲參數傳過去是爲了根據不一樣的響應狀態作判斷,當請求成功後,能夠直接應用傳過來的路由對象參數跳轉頁面。
router.push('/')
注意:
這一步操做實際上是調用了vuex的Action操做,即原始的this.$store.commit('action')
,可是在vuex+ts
項目中,調用異步函數Action
,不須要再用this.$store.commit('action')
這種方法,引用模塊後,直接調用裏面的Action
方法就行了,一樣的,同步的Mutation
也是這樣調用。這些都要歸功於vuex-module-decorators
類庫的封裝
好了,調用Action
後粗發Mutation
同步操做,保存好token
令牌,由於登陸以後全部的請求都要把token
值放在header
頭中發起請求
除了vuex
狀態管理,在項目中可能咱們還會結合工具類js-cookie
一塊兒使用,管理各類變量的值,具體用法跟原始版沒有什麼區別,最主要的是安裝類庫的過程當中,還得安裝一個開發ts
編譯版
yarn add js-cookie // dependencies yarn add @types/js-cookie --dev // devDependencies(必裝)
登陸頁面至此就完成了,它會直接重定向到首頁。接下來讓咱們來搭建項目的的骨架導航菜單
這裏其實沒太多可介紹的,直接用element-ui
的NavMenu 導航菜單
組件便可
Error:
Cannot find module '@/assets/401-images/401.gif'
解決: 加上模塊定義
//shims-vue.d.ts
declare module '*.png'
declare module '*.jpg'
declare module '*.gif'
複製代碼
Error:
在.vue文件裏 "import path from 'path' ’" 報錯 Cannot find module 'path'
解決: 在tsconfig.json的type項添加"node"便可。
"types": [
"node",
]
複製代碼
Error:
SassError: expected selector
解決:
yarn add node-sass --dev
若是安裝了node-sass
以後還有報錯,那有多是你的sass
類庫安裝錯誤,檢查下package.json
中是否在devDependencies
開發依賴中安裝了sass
,若是有的話yarn remove sass
便可,留一個sass-loader
就行了
Error: 在使用path-to-regexp
中出現以下錯誤警告
解決:
主要是 import 使用的問題:在引入path-to-regexp時應使用如下的方法
import * as pathToRegexp from 'path-to-regexp'
export const constantRoutes: RouteConfig[] = [
]
export const asyncRoutes: RouteConfig[] = [
]
const createRouter = () =>
new Router({
// scrollBehavior功能只在 HTML5 history 模式下可用,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像從新加載頁面那樣
// mode: 'history',
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
// 當且僅當 popstate 導航 (經過瀏覽器的 前進/後退 按鈕觸發) 時纔可用
return savedPosition
} else {
return { x: 0, y: 0 }
}
},
base: process.env.BASE_URL,
routes: constantRoutes
})
const router = createRouter()
export default router
複製代碼
這裏有兩個路由數組對象asyncRoutes
,constantRoutes
,字面量理解意思就好了
爲了讓項目體驗性更友好,固然要增長路由守衛功能,與此同時,會添加一個路由進度條nprogress
類庫
yarn add nprogress yarn add @types/nprogress --save
// @/src/permission.ts
import router from './router'
import NProgress from 'nprogress' // Progress 進度條
import 'nprogress/nprogress.css'// Progress 進度條樣式
import { Message } from 'element-ui'
import { PermissionModule } from '@/store/modules/permission'
import { UserModule } from '@/store/modules/user'
import { Route } from 'vue-router'
const whiteList = ['/login'] // 不重定向白名單
router.beforeEach(async(to: Route, _: Route, next: any) => {
NProgress.start()
if (UserModule.id_token) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
// Check whether the user has obtained his permission roles
if (UserModule.roles.length === 0) {
try {
// Note: roles must be a object array! such as: ['admin'] or ['developer', 'editor']
await UserModule.GetUserInfo()
const roles = UserModule.roles
// Generate accessible routes map based on role
PermissionModule.GenerateRoutes(roles)
// Dynamically add accessible routes
router.addRoutes(PermissionModule.dynamicRoutes)
// Hack: ensure addRoutes is complete
// Set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (err) {
// Remove token and redirect to login page
UserModule.ResetToken()
Message.error(err || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next('/login')
NProgress.done()
}
}
// next()
NProgress.done()
})
router.afterEach(() => {
NProgress.done() // 結束Progress
})
複製代碼
固然,要在主入口文件引入這個路由守衛文件
import '@/permission'
// main.ts
在這個路由守衛工具類中能夠經過角色控制來限制菜單欄的路由地址,各類不一樣的角色配置不一樣的路由
接下來構建骨架,比較中規中矩
該有的功能仍是得有的,體驗性要友好。具體的就不細講了,能夠參照源碼,在這裏,你能夠爲所欲爲的搭建你想要的UI風格。
本項目中的Icon,都是svg
類型的矢量圖標通過vue-svgicon
類庫轉化成的組件,用法較爲靈活,方便
一、安裝依賴
yarn add vue-svgicon
二、配置svg圖標目錄路徑(任意目錄)和輸出路徑
// package.json
"scripts": {
...
"svg": "vsvg -s ./src/icons/svg -t ./src/icons/components --ext ts --es6"
}
複製代碼
三、在主入口main.ts
中引入svgIcon
組件,而且全局註冊
// main.ts
import SvgIcon from 'vue-svgicon'
Vue.use(SvgIcon, {
tagName: 'svg-icon',
defaultWidth: '1em',
defaultHeight: '1em'
})
複製代碼
四、執行命令,生成Icon 組件引用
yarn run svg
五、使用參考SvgIcon
好了,至此,vue+ts
的整個模板後臺就搭建好了。由於每一個人的業務需求確定不同,因此到這裏算是一個比較乾淨的模板框架了。
固然,你覺得到這裏就結束了嗎,固然不是,確定要多幾個功能,否則,辛辛苦苦玩一次ts
項目,確定要玩得盡興一點
言歸正傳。
國際化對於大廠或者某些有國際化業務的公司來講,是必不可少的,那咱們就先擴展個國際化的功能玩玩吧。
一、首先,安裝vue-i18n
庫
yarn add vue-i18n
二、定製國際化語言包,這裏暫且就只搞中英兩種語言,其它都是共通的
值得注意的是,所謂的國際化語言包,在前端部分,你只能體如今一些固定的位置,好比菜單欄的頁面名稱,表單的label值,placeholder
屬性值...即全部非服務端響應的數據,由於服務端響應的數據是不固定的,你是無法作成語言包翻譯成對應語言的。因此,真正的國際化項目,應該是先後端協調好,當切換到對應語言後,傳值對應的國際化參數值,而後服務端同時也配置國際化的數據響應,從而達到整個項目的國際化
├─ src/ # 源碼目錄
│ ├─ lang/
│ ├─── en.ts
│ ├─── zh.ts
│ ├─── index.ts
...
複製代碼
英文版
// en.ts
export default {
route: {
dashboard: 'Dashboard',
commonTable: 'Common Table',
helpCenter: 'Help Center',
salary: 'Salary',
firstStep: 'First Step',
secondStep: 'Second Step',
sendRecord: 'Send Record'
},
login: {
mobilePhone: 'Please enter your mobile number',
password: 'Please enter your Password',
btn: 'login'
}
}
複製代碼
中文版
export default {
route: {
dashboard: '首頁',
commonTable: '表格',
helpCenter: '幫助中心',
salary: '工資條',
firstStep: '第一步',
secondStep: '第二步',
sendRecord: '發送記錄'
},
login: {
mobilePhone: '請輸入手機號碼',
password: '請輸入密碼',
btn: '登陸'
}
}
複製代碼
總的來講,就是取他們所表達意義的label值作成可配置化的對象
三、綜合國際化語言包
import Vue from 'vue'
import Cookies from 'js-cookie'
import VueI18n from 'vue-i18n'
// element-ui built-in lang
import elementEnLocale from 'element-ui/lib/locale/lang/en'
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'
// User defined lang
import enLocale from './en'
import zhLocale from './zh'
Vue.use(VueI18n)
const getLanguage = () => Cookies.get('language')
const messages = {
en: {
...enLocale,
...elementEnLocale
},
zh: {
...zhLocale,
...elementZhLocale
},
... // 這裏若是有其它語言包繼續按照規則添加便可
}
export const getLocale = () => {
const cookieLanguage = getLanguage()
if (cookieLanguage) {
return cookieLanguage
}
const language = navigator.language.toLowerCase()
const locales = Object.keys(messages)
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale
}
}
// Default language is english
return 'en'
}
const i18n = new VueI18n({
locale: getLocale(),
messages
})
export default i18n
複製代碼
四、主入口main.ts
注入
import i18n from '@/lang'
Vue.use(ElementUI, {
i18n: (key: string, value: string) => i18n.t(key, value)
})
new Vue({
...
i18n,
}).$mount('#app')
複製代碼
注意:
Vue.use
裏面的i18n
只是element-ui
的框架屬性配置,真正全局化的國際化配置,必定要跟路由和狀態管理同樣注入到new Vue()
中
國際化到這裏就配置完成了,那怎麼使用呢
五、國際化配置應用
① 登陸頁應用
登陸頁就一個表單,手機號和密碼,在這個頁面中的國際化就是對他們的label
和placeholder
屬性名的值作配置
<template>
<div class="login-container">
<lang-select />
<el-form>
<!--手機號-->
<el-form-item>
<el-input :placeholder="$t('login.mobilePhone')"
>
</el-input>
</el-form-item>
<!--密碼-->
<el-form-item>
<el-input :placeholder="$t('login.password')"
>
</el-input>
</el-form-item>
<footer>
<el-button>
{{ $t('login.btn') }}
</el-button>
</footer>
</el-form>
</div>
</template>
<script lang="ts">
import LangSelect from '@/components/LangSelect/index.vue'
@Component({
name: 'login',
components: {
LangSelect
}
})
export default class extends Vue {}
</script>
複製代碼
語法糖: 具體國際化語言切換,就是經過這種變量值取值方式根據以前的國際化語言包取值的$t('...')
固然,這裏還有個語言切換工具,以前全部的配置都是怎麼取值,那這個語言切換工具呢就是設值語言值得工具,即上面components
中的LangSelect
@Component
export default class extends Vue {
get language() { // 把language語言值存進vuex,這裏是取值
return AppModule.language
}
private handleSetLanguage(lang: string) {
this.$i18n.locale = lang
AppModule.SetLanguage(lang)
this.$message({
message: 'Switch Language Success',
type: 'success'
})
}
}
複製代碼
切換後報如下錯誤
解決:
// src/shims-vue.d.ts
declare module 'element-ui/lib/locale/lang/*' {
export const elementLocale: any
}
複製代碼
換膚和前面的定製主題仍是有區別的,概念的話就不作過多介紹了,能夠自行體會
這裏就不細說了,能夠查看@/components/ThemePicker
,更換的全部主題色在@/styles/element-variables.scss
配置
安裝依賴
yarn add echarts yarn add @types/echarts --dev
輪播圖和echarts都在首頁Dashboard
頁面裏,能夠自行參考
此外,還新加了一個表格分頁和搜索的頁面供參考
詳細請參考表格所在的路由頁面
這裏,爲了體驗性友好,表格的分頁切換引進了一個滾動到最頂端的小動畫
到這裏差很少這個項目就要結束了,整個的項目架構來講,越輕量級越好,因此就不作太多擴展了,儘可能開箱即用
在此,爲了登陸頁不顯得那麼寒磣,仍是給加個動效吧
好了,大功告成。撒花撒花~~~
分享不易,喜歡的話必定別忘了點💖!!!
只關注不點💖的都是耍流氓
,只收藏也不點💖的也同樣是耍流氓
。
結束👍👍👍。
vue-typescript-admin-element-ui typescript+vue
實戰
uni-app 小程序 uni-app小程序手把手項目實戰
React+antd+Redux-saga實戰 手把手帶你搭建React16+Router+Redux-saga+Antd後臺管理系統