vue-admin-stepbystep
A Vue.js project
系列文章
- [juejin.im/post/5c1609… ]vue-admin詳細註釋,必須手把手做專案系列之(二)
- [juejin.im/post/5c18db…]vue-admin 詳細註釋,必須手把手做專案系列之(三)丟到伺服器中解決報錯
- 麻雀雖小五臟俱全:[專案地址 github.com/whylisa/vue…)
專案功能
- 1 登入
- 2 首頁
- 3 退出
- 4 table頁
專案技術點
- 1,使用vue
- 2,使用echarts
- 3, 使用json-server (系列二寫詳細文件)
- 4, 使用node起一個簡單的服務,服務於介面(系列二寫詳細文件)
- 5, 使用axios
- 6, vue-router的使用規則(非同步載入,和同步載入)
- 7, 回話攔截的使用(localstorage or cookie)
- 8, 配合element-UI
- 9, 修改元件裡面的樣式裡面的坑
- 10, 打包時的優化
- 11, DNS優化
- 12, 配置本地代理,使用介面(系列二寫詳細文件)
- 13, 使用axios配合json-server 模擬增刪改查(系列二寫詳細文件)
- 14, 使用nprogress 外掛
- 15, 鮮為人知的element-UI 的滾動條
- 16, 柵格佈局,大小屏適應配合媒體查詢
- 17, css使用less
- 18, 程式碼風格,個人風格,禁用了jslint 防止不懂得小夥伴抓狂
- 19, 相容性的處理(系列二寫詳細文件)
專案搭建
- 1
vue init webpack XX
使用vue-cli 2.0
Project name :預設
Project description :預設
Author :預設
Vue build :選擇 Runtime + Compiler
Install vue-router? :Y
Use ESLint to lint your code? :Y 選擇 Standard
Set up unit tests :n
Setup e2e tests with Nightwatch? : n
Should we run `npm install` for you after the project has been created? (recommended) : Yes, use NPM
複製程式碼
- 2 進入專案:cd vue-admin-stepbystep
- 3 執行專案:npm run dev
如何新增一個新的功能???
- 1 在
components
中新建一個資料夾(login),在檔案中建立元件(Login.vue) - 2 在
router/index.js
中匯入元件(login.vue) - 3 配置路由規則
在專案中使用 element-ui(其他自行gg加深映像)
- ElementUI 文件
- 安裝:
npm i element-ui -S
// main.js
// 匯入elementui - js
import ElementUI from `element-ui`
// 匯入elementui - css
import `element-ui/lib/theme-chalk/index.css`
// 安裝外掛
Vue.use(ElementUI)
複製程式碼
專案啟動做了什麼
- 1 在終端中執行:
npm run dev
,實際上就是執行了:webpack-dev-server ...
- 2 使用 webpack-dev-server 開啟一個伺服器
- 3 根據指定的入口
src/main.js
開始分析入口中使用到的模組 - 4 當遇到
import
的時候,webpack 就會載入這些模組內容(如果有重複模組,比如:Vue,實際上將來只會載入一次),遇到程式碼就執行這些程式碼 - 5 建立 Vue 例項,將 App 元件作為模板進行編譯,並且將 App 元件中 template 的內容渲染在頁面 #app 的位置
路由配置
- 1 非同步載入路由
- 2 使用進度條外掛
- 3 登入攔截 會話保持
import Vue from `vue`
import Router from `vue-router`
//引入nprogress進度條
import NProgress from `nprogress`
//引入nprogress進度條的樣式
import `nprogress/nprogress.css`
//在打包過程中每一個元件都會打包成一個js檔案,如果不使用使用/* webpackChunkName: "home" */
//在打包的時候就會生成0.js,1.js等等,使用了之後就會打包成home.js
// 匯入 Login 元件(注意,不要新增 .vue 字尾)
//這是路由的非同步載入,!important,這是優化專案必須的
//引入home元件
const Home = () => import(/* webpackChunkName: "home" */ `@/components/home`)
//引入登入元件
const Login = () => import(/* webpackChunkName: "home" */ `@/components/login`)
//引入table元件
const Table = () => import(/* webpackChunkName: "home" */ `@/components/table/table`)
//引入homeMain元件
const HomeMain = () => import(`@/components/HomeMain`)
//這裡是同步載入
//import Login from `@/components/login/Login`
Vue.use(Router)
const router = new Router({
mode: `history`,//開啟了history模式,去除了#,
// 在vue中,一般來說通過例項去訪問某個屬性的
// vm.xxxx vm.$set vm.$refs vm.$router
routes: [
{
path: `/`,
redirect: `/homeMain`//路由的重定向
},
{
path: `/login`,
name: `login`,
component: Login
},
{
path: `/home`,
name: `home`,
component: Home,
// children 用來配置子路由,將來匹配的元件會展示在 Home 元件的 router-view 中
// 對於子路由path來說:
// 1 如果不是以 / 開頭,那麼,雜湊值為: 父級path + / + 子級path
// 也就是: /home/homeMain
// 2 如果子級路由的path是以 / 開頭的,那麼將來的雜湊值為:/users 不再帶有父級的path了
// 也就是:/homeMain
//這是頁面中的子路由,在頁面中必須宣告router-view作為出口
children: [
{
path: `/homeMain`,
name: `homeMain`,
component: HomeMain
},
{
path: `/table`,
name: `table`,
component:Table
}
]
}
]
});
// 給router配置導航守衛
// to: 去哪兒
// from: from 哪兒來
// next() : next():放行 next(`/login`) 去login元件
// 在登入成功以後,將 token 儲存到 localStorage 中
// 在 導航守衛 中先判斷當前訪問的頁面是不是登入頁面
// 如果是登入頁面,直接放行(next())
// 如果不是登入頁面,就從 localStorage 中獲取 token,判斷有沒有登入
// 如果登入了,直接放行(next())
// 如果沒有登入,就跳轉到登入頁面讓使用者登入(next(`/login`)
router.beforeEach((to, from, next) => {
// 開啟進度條
NProgress.start()
// 獲取是否有token
let token = localStorage.getItem(`myToken`)
// 如果已經就是要去login了,就不需要攔截了
if (to.path === `/login` || token) {
next()
}else {
next(`/login`)
}
});
router.afterEach(() => {
// 關閉進度條
NProgress.done()
})
export default router
複製程式碼
登入功能
- 1 安裝:
npm i -S axios
- 2 在
Login.vue
元件中匯入 axios - 3 使用 axios 根據介面文件來傳送請求,完成登入
- 4 登入時設定token ,可以用localStorage cookie等任君選擇
<div class="l-right">
<div class="l-l">
<!-- @tab-click="handleClick" -->
<el-tabs v-model="activeName">
<el-tab-pane label="使用者登入" name="first">
<!-- el-form:自定義表單元件 -->
<!-- :model="form" 表單物件,用於收集收據 -->
<!-- label-width="80px":label的寬度 -->
<!-- el-form-item:表單項 -->
<el-form ref="form" status-icon :rules="rules" :model="form" label-width="80px">
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="請輸入使用者名稱" prefix-icon="iconfont icon-yonghuming"></el-input>
</el-form-item>
<el-form-item prop="password">
<!-- 將來我們給元件註冊事件的時候,可以會註冊不上 -->
<!--@keyup.enter點選鍵盤的enter觸發事件-->
<!-- .native: 註冊事件,給元件的根元素註冊事件 -->
<el-input type="password" v-model="form.password" placeholder="請輸入密碼" @keyup.enter.native="login" prefix-icon="iconfont icon-mima"></el-input>
</el-form-item>
<el-form-item>
<!--使用@語法糖繫結事件-->
<el-button type="primary" @click="login">登入</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="帥哥登入" name="second">長得很帥</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
複製程式碼
export default {
data () {
return {
// 定義一些變數,可以使用{{}}語法在頁面中直接獲取
activeName: `first`,
form: {
username: `why`,
password: "123456"
},
rules: {
// 使用者名稱的校驗
username: [
// 使用者名稱是必須
// required是否必須
// message提示資訊
// trigger如何觸發
{ required: true, message: `請輸入使用者名稱`, trigger: `change` },
{ min: 3, max: 6, message: `長度在 3 到 6 個字元`, trigger: `change` }
],
// 密碼的校驗
password: [
{ required: true, message: `請輸入密碼`, trigger: `change` },
{ min: 6, max: 12, message: `長度在 6 到 12 個字元`, trigger: `change` }
]
}
}
},
methods: {
login () {
// 先觸發頁面中的檢驗規則,不通過給提示,通過就向後臺傳送請求,
// $refs是vue中獲取頁面的,在html中要寫 ref="form"
this.$refs.form.validate(async (valid) => {
if (valid) {
// 使用axios向後臺傳送請求
// 在es6中的箭頭函式沒有this繫結,可以列印出來指向的是vue例項,這點可以自行百度,加深映像
this.axios(`/api/login`).then( res => {
console.log(res.data[0])//用來檢視介面裡面的資料
let lg = res.data[0] //把資料賦值給變數
console.log(lg.username,lg.password)//主要用來檢視資料
if(lg.username === this.form.username && lg.password==this.form.password){
localStorage.setItem(`myToken`,lg.username)//設定攔截,可以用cookie等,在控制檯中的Application中檢視
this.$message.success(`恭喜你,登入成功`)//登入成功的提示
this.$router.push(`homeMain`) //使用程式設計式導航路由進行跳轉
}else {
this.$message.error(`賬號或者密碼錯誤`)//賬號密碼錯誤時的提示
}
})
}
})
},
reset () {
this.$refs.form.resetFields()//清空輸入框中的資訊
// 資料被我寫死了,可以自行改動
}
}
};
複製程式碼
頂部和側邊
<el-container>
<!--給el-header設定 高度-->
<el-header style="height: 70px;">
<div class="logo">
<!--這裡可以放一般網站的logo-->
<!--<img src="../assets/main/logo.png" alt="">-->
</div>
<div class="header-right">
<div class="logout" @click="layout">
<!--javascript:;為了防止a標籤的預設行為,-->
<a href="javascript:;">退出</a>
</div>
<div class="people">
<!--映入iconfont 的字型圖示-->
<i class="iconfont icon-lianxirenwode"></i>
張三
</div>
<div class="call">
<i class="iconfont icon-lianxiwomen"></i>
聯絡我們
</div>
</div>
</el-header>
<el-container>
<el-aside width="160px" background-color="#26292E">
<el-scrollbar style="height: 100%;">
<!-- el-menu: 導航選單的元件 -->
<!-- default-active:預設高亮的選單 -->
<!-- open close 展開和關閉的事件 -->
<!-- el-submenu: 子選單 -->
<!-- el-menu-item-group: 子選單中分組 -->
<!-- el-menu-item:子選單中的每一項 -->
<!-- unique-opened: 保證只能開啟一個子選單 -->
<!-- router: 如果router為true,那麼index就會作為路由的連線 -->
<el-menu :unique-opened=`true` :router="true" text-color="#ffffff" active-text-color="#cccccc">
<el-submenu index="1">
<template slot="title"><i class="iconfont icon-shouye"></i>
<span @click="gomain">
首頁
</span>
</template>
</el-submenu>
<el-submenu index="2">
<template slot="title"><i class="iconfont icon-message-channel"></i>table</template>
<el-menu-item-group>
<el-menu-item index="/table">table</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</el-scrollbar>
</el-aside>
<el-container>
<!--使用element的自帶的滾動條,官方文件沒有-->
<el-scrollbar style="height: 100%;width: 100%;">
<el-main>
<keep-alive>
<!-- 這裡是會被快取的檢視元件 -->
<!-- $route.meta.keepAlive:如果是true,
說明是快取元件,通過keep-alive這個標籤把快取元件顯示出來 -->
<router-view v-if="$route.meta.keepAlive">
</router-view>
</keep-alive>
<!-- 這裡是不被快取的檢視元件 -->
<router-view v-if="!$route.meta.keepAlive">
</router-view>
</el-main>
</el-scrollbar>
</el-container>
</el-container>
</el-container>
複製程式碼
export default {
created() {
},
data() {
return {
}
},
methods: {
gomain() {
//程式設計式導航
this.$router.push(`/homeMain`)
},
//退出功能
layout() {
// 退出功能要移除localStorage中的myToken
localStorage.removeItem(`myToken`)
// 跳轉到首頁
this.$router.push(`login`)
// 退出成功提示
this.$message.success(`退出成功了`)
},
},
}
複製程式碼
首頁
- 程式碼有點多擷取一點,主要對echarts做了修改,x軸箭頭呀,修改柱狀圖的樣式呀等,還有element的柵格佈局配合媒體查詢
xAxis: {
data: ["三月", "四月", "五月", "六月", "七月"],
axisLine: {
symbol: [`none`, `arrow`],
lineStyle: {
color: `rgba(212,212,212,1)`, // x座標軸的軸線顏色
width: 1 //這裡是座標軸的寬度,為0就是不顯示
}
}
},
yAxis: [{
type: `value`,
axisLabel: {
show: false //這行程式碼控制著座標軸x軸的文字是否顯示
},
splitLine: {
show: false, // 網格線是否顯示
// 改變樣式
lineStyle: {
color: `#EDEDED` // 修改網格線顏色
}
},
axisLine: {
lineStyle: {
color: `#fff`, // x座標軸的軸線顏色
width: 0 //這裡是座標軸的寬度,為0就是不顯示
}
}
}],
複製程式碼
表格的使用
<div class="table">
<div class="t-top">
<!--使用el-input 要注意,他預設佔父級100%的寬度-->
<el-input v-model="query" placeholder="請輸入內容"></el-input>
<!--el-button 繫結點選事件向後臺傳送資料查詢-->
<!--在此處通常會涉及到模糊查詢,此時我們還需要繫結keyup事件,向後臺請求資料,然後渲染一個小的下拉框,我們需要做的是傳送查詢的欄位給後臺,
後臺使用sql語句模糊查詢,我們渲染就可以-->
<el-button type="primary" @click="search">查詢</el-button>
</div>
<div class="t-bottom">
<!-- el-table:表格元件 -->
<!-- :data=`tableData` 表格顯示的資料 -->
<!-- el-table-column:表格的一列 -->
<!-- prop: 當前列要顯示的資料 ,tableData內的資料-->
<!-- label:表頭 -->
<!-- width: 這一列的寬度 -->
<!--min-width:用來設定百分比-->
<!--:header-cell-style="{background:`red`}"-->
<!--align="center"表格內的資料居中-->
<el-table
:data="tableData"
style="width: 100%"
:header-cell-style="{background:`red`}"
>
<el-table-column
prop="date"
label="日期"
align="center"
width="180">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
align="center"
width="180">
</el-table-column>
<!--如果不設定百分比,就自動分配剩餘的寬度-->
<el-table-column
prop="address"
align="center"
label="地址">
</el-table-column>
</el-table>
</div>
</div>
複製程式碼
export default {
data() {
return {
// 繫結的input 查詢關鍵字
query: ``,
// 需要一個陣列用來存放table的資料
// 這是element元件裡面的,
tableData: []
}
},
mounted() {
// 在vue的生命週期的mounted中呼叫渲染列表
this.initTable()
},
methods:{
initTable() {
this.axios(`/api/table`).then( res => {
console.log(res.data)//檢視介面返回時什麼樣的資料,要常用
this.tableData = res.data //介面返回的是一個陣列,直接可以賦值給table
})
},
search() {
}
}
}
複製程式碼
改element-ui的樣式注意!
<!--使用scoped需要注意,使用的它之後,你就無法更改elelment元件中的樣式-->
<style lang="less" scoped="scoped">
/*如果不想使用scoped,你就用父級的class把樣式全部包裹起來,就不會相互影響頁面的樣式了*/
複製程式碼
以下是一些概念性的東西,其他的功能系列二將會陸續完善
程式設計式導航
- 就是通過 JS 程式碼來實現路由的跳轉功能
// 注意:是 router 不是 route
// router用來實現路由跳轉,route用來獲取路由引數
// push 方法的引數為:要跳轉到的路由地址(path)
this.$router.push(`/home`)
複製程式碼
密碼
- 給輸入框元件新增 type=”password” 就變為密碼框狀態了
<el-input type="password" v-model="loginForm.password"></el-input>
複製程式碼
登入攔截
- 說明:在沒有登入的情況不應該讓使用者來訪問除登入以外的任何頁面
登入和攔截的整個流程說明
- 1 在登入成功以後,將 token 儲存到 localStorage 中
- 2 在 導航守衛 中先判斷當前訪問的頁面是不是登入頁面
- 3 如果是登入頁面,直接放行(next())
- 4 如果不是登入頁面,就從 localStorage 中獲取 token,判斷有沒有登入
- 5 如果登入了,直接放行(next())
- 6 如果沒有登入,就跳轉到登入頁面讓使用者登入(next(`/login`))
token 機制的說明
- 在專案中,如果登入成功了,那麼,伺服器會給我們返回一個 token
- 這個 token 就是登入成功的標識
- 這個 token 就相當於使用 cookie+session 機制中的 sessionid
公司人員和專案開發流程
- 1 產品經理定製專案的需求
- 2 分配任務:先將所有的任務分配到專案組,然後,再由專案組具體分配給每個開發人員
- 3 開發:拿到 產品原型 + 需求文件 + UI 設計稿 資料,轉化為 HTML 頁面,完成功能
- 4 功能完成後,自己測試有沒有 Bug
- 5 由測試人員來測試你的功能,當測試出 Bug 後,就會通過 禪道 這樣的專案管理系統,來提出 Bug
- 6 由 自己 修改 測試人員提出來的 bug
- 7 最終,沒有 bug 了,專案才會上線
產品經理(Product Manager)
提需求
產出: 產品原型 + 需求文件
原型設計軟體:Axure 、墨刀
UI(設計)
將 產品經理 給的 原型圖 設計為 好看的UI稿
FE(前端)front-end
產品原型 + 需求文件 + UI設計稿 ===> HTML頁面
BE(後端) back-end
給前端提供資料介面
測試人員
產品原型 + 需求文件 + UI設計稿 來測試我們寫的功能
發現你的功能 與 需求 不一致,那就說明除Bug了,那麼,測試人員就會提Bug
Bug系統: 禪道
專案經理(管理技術)
技術攻堅,與其他專案組人員溝通,分配任務 等
複製程式碼
vue 單檔案元件中的 scoped
- 作用:給
style
標籤新增scoped
屬性以後,樣式只會對當前元件中的結構生效,而不會影響到其他元件
vue 單檔案元件中的 lang
- 作用:新增
lang="less"
後,就可以使用 less 語法了 - 但是需要自己安裝:
npm i -D less less-loader
VSCode 中使用 Vetur 外掛格式化單檔案元件的 template
- 開啟設定,配置:
"vetur.format.defaultFormatter.html": "js-beautify-html"
介面呼叫的說明
- 注意:所有介面都需要傳遞 token,只有傳遞了正確的 token,伺服器才會將資料返回給前端
- 如果沒有傳遞
token
,伺服器會返回401
,告訴介面呼叫者,沒有許可權來訪問這個介面
cookie+session VS token
Git 使用
# 克隆倉庫
git clone [倉庫地址]
# 推送
git push [倉庫地址] master
# 簡化推送地址
git remote add XX [倉庫地址]
git push -u XX master
# 第一次執行上面兩條命令,以後只需要輸入以下命令即可
git push XX
# 拉取
git pull [倉庫地址] master
git pull XX master
複製程式碼
路由引數分頁
- 1 配置分頁路由引數, 引數是可選的
- 引數可選後, 路由就能夠匹配:
/XX
或者/XX/3
- 引數可選後, 路由就能夠匹配:
- 2 使用路由來分頁, 有兩種情況需要處理:
- 3 第一種: 進入頁面,就要根據當前路由引數中的頁碼,來獲取到對應頁的資料
- 4 第二種: 點選分頁元件獲取資料, 需要做兩件事:
- 4.1 獲取到當前頁的資料( 呼叫獲取資料的方法 )
- 4.2 修改雜湊值為當前頁碼 ( this.$router.push() )
- 5 點選分頁按鈕獲取資料的第二種思路:
- 5.1 點選分頁按鈕, 觸發了分元件的 pageChange 事件
- 5.2 在 pageChange 事件中修改了路由( this.$router.push() )
- 5.3 路由發生改變後, watch 中的 $route 監視到了路由的改變
- 5.4 在
$route(to) {}
方法中, 通過引數 to 獲取到當前頁碼, 重新呼叫獲取資料的方法來獲取當前頁的資料
專案打包和優化
- 打包命令:
npm run build
按需載入
- 1 修改
router/index.js
中匯入元件的語法
// 使用:
const Home = () => import(`@/components/home/Home`)
// 替換:
// import Home from `@/components/home/Home`
// 給打包生產的JS檔案起名字
const Home = () => import(/* webpackChunkName: `home` */ `@/components/home/Home`)
// chunkName相同,將 goods 和 goods-add 兩個元件,打包到一起
const XX = () => import(/* webpackChunkName: `XX` */`@/components/XX`)
const XXX = () => import(/* webpackChunkName: `XX` */`@/components/XXX`)
複製程式碼
- 2 (該步可省略)修改
/build/webpack.prod.conf.js
中的chunkFilename
{
// [name] 代替 [id]
chunkFilename: utils.assetsPath(`js/[name].[chunkhash].js`)
}
複製程式碼
使用CDN
-
1 在
index.html
中引入CDN提供的JS檔案 -
2 在
/build/webpack.base.conf.js
中(resolve前面)新增配置 externals -
注意:通過CDN引入 element-ui 的樣式檔案後,就不需要在 main.js 中匯入 element-ui 的CSS檔案了。所以,直接註釋掉 main.js 中的匯入 element-ui 樣式即可
-
externals
配置:
externals: {
// 鍵:表示 匯入包語法 from 後面跟著的名稱
// 值:表示 script 引入JS檔案時,在全域性環境中的變數名稱
vue: `Vue`,
axios: `axios`,
`vue-router`: `VueRouter`,
`element-ui`: `ELEMENT`,
moment: `moment`,
echarts: `echarts`,
}
import ElementUI from `element-ui`
複製程式碼
常用包CDN
-
說明:
- 1 先在官方文件查詢提供的CDN
- 2 如果沒有,在
https://www.bootcdn.cn/
或其他 CDN提供商 查詢
<!-- Include the Quill library -->
<script src="https://cdn.bootcss.com/quill/1.3.4/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- Quill JS Vue -->
<script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.4/dist/vue-quill-editor.js"></script>
<!-- Include stylesheet -->
<link href="https://cdn.bootcss.com/quill/1.3.4/quill.core.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/quill/1.3.4/quill.snow.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/quill/1.3.4/quill.bubble.min.css" rel="stylesheet">
複製程式碼
移除console
new webpack.optimize.UglifyJsPlugin({
compress:{
warnings: false,
drop_debugger: true,
drop_console: true
}
})
複製程式碼
快取和保留元件狀態
- keep-alive
- 解決方式:使用
keep-alive
,步驟如下:
1 在需要被快取元件的路由中新增 meta 屬性
meta 屬性用來給路由新增一些元資訊(其實,就是一些附加資訊)
{
path: `/`,
name: `home`,
component: Home,
// 需要被快取
meta: {
keepAlive: true
}
}
2 修改路由出口,替換為以下形式:
根據 meta 是否有 keepAlive 屬性,決定該路由是否被快取
<keep-alive>
<!-- 這裡是會被快取的檢視元件 -->
<router-view v-if="$route.meta.keepAlive">
</router-view>
</keep-alive>
<!-- 這裡是不被快取的檢視元件 -->
<router-view v-if="!$route.meta.keepAlive">
</router-view>
複製程式碼
啟用路由的 History 模式
- 通過在路由中新增
mode: `history`
可以去掉 瀏覽器位址列中的 # - 在開發期間,只需要新增這個配置即可
- 但是,在專案打包以後,就會出現問題
// 去掉 # 後,地址變為:
http://localhost:1111/home
那麼,伺服器需要正確處理 /goods 才能正確的響應內容,
但是,/home 不是服務端的介面,而是 用來在瀏覽器中實現 VueRouter 路由功能的
複製程式碼
後端的處理方式
- 1 優先處理靜態資源
- 2 對於非靜態資源的請求,全部統一處理返回 index.html
- 3 當瀏覽器開啟 index.html 就會載入 路由的js 檔案,那麼路由就會解析 URL 中的 /login 這種去掉#的路徑了
待續中
[專案地址 github.com/whylisa/vue…)