從0到1搭建Element的後臺框架
由於最近公司要開發一個後臺管理系統,查閱了很多vue框架,本人覺得element簡潔,方便,於是選擇它作為我們的首選框架,並分享給大家,如果您覺得有需要改進的地方可以提出來一起探討,Github地址 。本文篇幅比較長,希望同學們可以耐心的讀下去,如有不懂可以下方留言
一、目錄
目錄 |
---|
二、初始化專案
首先全域性安裝的vue框架,這裡是用的npm包管理工具來安裝的,如果你的網不是很好的話可以先安裝淘寶映象npm install -g cnpm -registry=https://registry.npm.taobao.org
,然後通過cnpm來安裝
npm install vue or cnpm install vue 複製程式碼
其次開始安裝vue腳手架,當前是第三版本vue-cli 3.x
cnpm install -g @vue/cli 複製程式碼
安裝完成後,你還可以用這個命令來檢查其版本是否正確 (3.x):
vue --version 複製程式碼
安裝腳手架後開始建立我們的專案
vue create vue-admin-project 複製程式碼
隨後會出現兩個選項
選擇第二項並繼續,並選擇自己需要配置的功能,完成後並繼續,然後開始生成專案專案初始化成功接下來按照上面的提示執行cd app
以及啟動本地伺服器npm run serve
,當執行完成之後會提示你打來本地埠http://localhost:8080
,會出現歡迎頁面,此時代表你的vue專案初始化完成。
三、檔案目錄介紹與整理
整理前的初始目錄
|-- vue-admin-project |-- .gitignore//git專案忽視檔案 |-- babel.config.js//babel 配置檔案 |-- package-lock.json//記錄安裝包的具體版本號 |-- package.json//包的型別 |-- README.md |-- public//專案打包後的目錄 ||-- favicon.ico ||-- index.html |-- src//專案開發目錄 |-- App.vue//主入口檔案 |-- main.js//主入口檔案 |-- router.js//vue-router檔案 |-- store.js//vuex |-- assets //靜態檔案 |-- logo.png |-- components//元件存放目錄 |-- HelloWorld.vue |-- views//檢視目錄 |-- About.vue |-- Home.vue 複製程式碼
整理後的目錄,主要更改/src
資料夾下的目錄
|-- vue-admin-project |-- .gitignore |-- babel.config.js |-- package-lock.json |-- package.json |-- README.md |-- public |-- favicon.ico |-- index.html |-- src |-- App.vue |-- main.js |-- assets |-- logo.png |-- components |-- HelloWorld.vue |-- router//路由配置資料夾 |-- router.js |-- store//狀態管理資料夾 |-- store.js |-- views |-- About.vue |-- Home.vue 複製程式碼
四、開發環境與線上環境配置
vue-cli 3.0x與vue-cli 2.0x最主要的區別是專案結構目錄精簡化,這也帶來了許多問題,很多配置需要自己配置,由於2.0x版本中直接在cofig/
資料夾下面配置開發環境與線上環境,3.0x則需要自己配置。
首先配置開發環境,在專案根目錄下新建一個檔案.env
檔案。
NODE_ENV="development"//開發環境 BASE_URL="http://localhost:3000/"//開發環境介面地址 複製程式碼
接下來我們配置線上環境,同樣在專案根目錄新建一個檔案.env.prod
這就表明是生產環境。
NODE_ENV="production"//生產環境 BASE_URL="url"//生產環境的地址 複製程式碼
現在我們如何在專案中判斷當前環境呢?
我們可以根據process.env.BASE_URL
來獲取它是線上環境還是開發環境,後面會有運用
if(process.env.NODE_ENV='development'){ console.log( process.env.BASE_URL) //http://localhost:3000/ }else{ console.log( process.env.BASE_URL) //url } 複製程式碼
至此,我們成功的配置好了開發環境與線上環境。
五、vue.config.js配置
講到vue.config.js
專案配置檔案,又不得不說下3.x和2.x的區別,2.x裡面webpack相關的配置項直接在專案的build/webpack.base.conf.js
裡面配置,而3.x完全在vue.config.js
中配置,這使得整個專案看起來更加簡潔明瞭,專案執行速度更快。
由於專案初始化的時候沒有vue.config.js
配置檔案,因此我們需要在專案根目錄下新建一個vue.config.js
配置項。
在這個配置項裡面,本專案主要是配置三個東西,第一個就是目錄別名alias
,另一個是專案啟動時自動開啟瀏覽器,最後一個就是處理引入的全域性scss檔案。當然有vue.config.js
的配置遠遠不止這幾項,有興趣的同學可以去看看vue.config.js具體配置,具體程式碼如下。
let path=require('path'); function resolve(dir){ return path.join(__dirname,dir) } module.exports = { chainWebpack: config => { //設定別名 config.resolve.alias .set('@',resolve('src')) }, devServer: { open:true//開啟瀏覽器視窗 }, //定義scss全域性變數 css: { loaderOptions: { sass: { data: `@import "@/assets/scss/global.scss";` } } } } 複製程式碼
六、ElementUI引入
開始安裝ElementUI
vue add element 複製程式碼
接下來兩個選項,第一個是全部引入,第二個是按需引入,我選擇第一個Fully import
,大家可以按照自己的專案而定。接下來會詢問是否引入scss,這裡選擇是,語言選擇zh-cn。
接下來會提示安裝成功,並在專案首頁有一個element樣式的按鈕。
七、vue-router路由介紹入
路由管理也是本專案核心部分。
1.引入檔案
import Vue from 'vue' import Router from 'vue-router' import store from '../store/store' //引入狀態管理 import NProgress from 'nprogress' //引入進度條元件 cnpm install nprogress --save import 'nprogress/nprogress.css' Vue.use(Router) 複製程式碼
2.路由懶載入
/** *@parma {String} name 資料夾名稱 *@parma {String} component 檢視元件名稱 */ const getComponent = (name,component) => () => import(`@/views/${name}/${component}.vue`); 複製程式碼
3.路由配置
const myRouter=new Router({ routes: [ { path: '/', redirect: '/home', component: getComponent('login','index') }, { path: '/login', name: 'login', component: getComponent('login','index') }, { path: '/', component:getComponent('layout','Layout'), children:[{ path:'/home', name:'home', component: getComponent('home','index'), meta:{title:'首頁'} }, { path:'/icon', component: getComponent('icons','index'), name:'icon', meta:{title:'自定義圖示'} }, { path:'/editor', component: getComponent('component','editor'), name:'editor', meta:{title:'富文字編譯器'} }, { path:'/countTo', component: getComponent('component','countTo'), name:'countTo', meta:{title:'數字滾動'} }, { path:'/tree', component: getComponent('component','tree'), name:'tree', meta:{title:'自定義樹'} }, { path:'/treeTable', component: getComponent('component','treeTable'), name:'treeTable', meta:{title:'表格樹'} }, { path:'/treeSelect', component: getComponent('component','treeSelect'), name:'treeSelect', meta:{title:'下拉樹'} }, { path:'/draglist', component: getComponent('draggable','draglist'), name:'draglist', meta:{title:'拖拽列表'} }, { path:'/dragtable', component: getComponent('draggable','dragtable'), name:'dragtable', meta:{title:'拖拽表格'} }, { path:'/cricle', component: getComponent('charts','cricle'), name:'cricle', meta:{title:'餅圖'} }, ] } ] }) 複製程式碼
4.本專案存在一個token,來驗證許可權問題,因此進入頁面的時候需要判斷是否存在token,如果不存在則跳轉到登陸頁面
//判斷是否存在token myRouter.beforeEach((to,from,next)=>{ NProgress.start() if (to.path !== '/login' && !store.state.token) { next('/login')//跳轉登入 NProgress.done()// 結束Progress } next() }) myRouter.afterEach(() => { NProgress.done() // 結束Progress }) 複製程式碼
5.匯出路由
export default myRouter 複製程式碼
八、axios引入並封裝
1.介面處理我選擇的是axios,由於它遵循promise規範,能很好的避免回撥地獄。現在我們開始安裝
cnpm install axios -S 複製程式碼
2.在src
目錄下新建資料夾命名為api
,裡面新建兩個檔案,一個是api.js
,用於介面的整合,另一個是request.js
,根據相關業務封裝axios請求。
-
request.js
1.引入依賴
import axios from "axios"; import router from "../router/router"; import { Loading } from "element-ui"; import {messages} from '../assets/js/common.js' //封裝的提示檔案 import store from '../store/store' //引入vuex 複製程式碼
2.編寫axios基本設定
axios.defaults.timeout = 60000;//設定介面超時時間 axios.defaults.baseURL = process.env.BASE_URL;//根據環境設定基礎路徑 axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8";//設定編碼 let loading = null;//初始化loading 複製程式碼
3.編寫請求攔截,也就是說在請求介面前要做的事情
/* *請求前攔截 *用於處理需要請求前的操作 */ axios.interceptors.request.use( config => { loading = Loading.service({ text: "正在載入中......", fullscreen: true }); if (store.state.token) { config.headers["Authorization"] = "Bearer " + store.state.token; } return config; }, error => { return Promise.reject(error); } ); 複製程式碼
4.編寫請求響應攔截,用於處理資料返回操作
/* *請求響應攔截 *用於處理資料返回後的操作 */ axios.interceptors.response.use( response => { return new Promise((resolve, reject) => { //請求成功後關閉載入框 if (loading) { loading.close(); } const res = response.data; if (res.err_code === 0) { resolve(res) } else{ reject(res) } }) }, error => { console.log(error) //請求成功後關閉載入框 if (loading) { loading.close(); } //斷網處理或者請求超時 if (!error.response) { //請求超時 if (error.message.includes("timeout")) { console.log("超時了"); messages("error", "請求超時,請檢查網際網路連線"); } else { //斷網,可以展示斷網元件 console.log("斷網了"); messages("error", "請檢查網路是否已連線"); } return; } const status = error.response.status; switch (status) { case 500: messages("error", "伺服器內部錯誤"); break; case 404: messages( "error", "未找到遠端伺服器" ); break; case 401: messages("warning", "使用者登陸過期,請重新登陸"); localStorage.removeItem("token"); setTimeout(() => { router.replace({ path: "/login", query: { redirect: router.currentRoute.fullPath } }); }, 1000); break; case 400: messages("error", "資料異常,詳情請諮詢聚保服務熱線"); break; default: messages("error", error.response.data.message); } return Promise.reject(error); } ); 複製程式碼
5.請求相關的事情已經完成,現在開始封裝get,post請求
/* *get方法,對應get請求 *@param {String} url [請求的url地址] *@param {Object} params [請求時候攜帶的引數] */ export function get(url, params) { return new Promise((resolve, reject) => { axios .get(url, { params }) .then(res => { resolve(res); }) .catch(err => { reject(err); }); }); } /* *post方法,對應post請求 *@param {String} url [請求的url地址] *@param {Object} params [請求時候攜帶的引數] */ export function post(url, params) { return new Promise((resolve, reject) => { axios .post(url, params) .then(res => { resolve(res); }) .catch(err => { reject(err); }); }); } 複製程式碼
-
api.js
封裝好axios的業務邏輯之後自然要開始,運用,首先引入
get
以及post
方法
import {get,post} from './request'; 複製程式碼
接下來開始封裝介面,並匯出
//登陸 export constlogin=(login)=>post('/api/post/user/login',login) //上傳 export constupload=(upload)=>get('/api/get/upload',upload) 複製程式碼
那我們如何呼叫介面呢?以登陸頁面為例。
import { login } from "@/api/api.js"; //引入login 複製程式碼
/** * @oarma {Object} login 介面傳遞的引數 */ login(login) .then(res => { //成功之後要做的事情 }) .catch(err => { //出錯時要做的事情 }); 複製程式碼
介面相關的邏輯已經處理完。
九、vuex引入
由於vue專案中元件之間傳遞資料比較複雜,因此官方引入了一個全域性狀態管理的東東,也就是現在要說的vuex,vuex能更好的管理資料,方便元件之間的通訊。
現在在store資料夾下面新建四個檔案state.js
,mutations.js
,getter.js
,action.js
。
-
state.js
state就是Vuex中的公共的狀態, 我是將state看作是所有元件的data, 用於儲存所有元件的公共資料.
const state = { token: '',//許可權驗證 tagsList: [], //開啟的標籤頁個數, isCollapse: false, //側邊導航是否摺疊 } export default state //匯出 複製程式碼
-
mutations.js
我將mutaions理解為store中的methods, mutations物件中儲存著更改資料的回撥函式,該函式名官方規定叫type, 第一個引數是state, 第二引數是payload, 也就是自定義的引數.改變state的值必須經過mutations
const mutations = { //儲存token COMMIT_TOKEN(state, object) { state.token = object.token; }, //儲存標籤 TAGES_LIST(state, arr) { state.tagsList = arr; }, IS_COLLAPSE(state, bool) { state.isCollapse = bool; } } export default mutations 複製程式碼
-
getter.js
我將getters屬性理解為所有元件的computed屬性,也就是計算屬性。vuex的官方文件也是說到可以將getter理解為store的計算屬性, getters的返回值會根據它的依賴被快取起來,且只有當它的依賴值發生了改變才會被重新計算。
const getters={ //你要計算的屬性 } export default getters 複製程式碼
-
action.js
actions 類似於 mutations,不同在於:
1.actions提交的是mutations而不是直接變更狀態
2.actions中可以包含非同步操作, mutations中絕對不允許出現非同步
3.actions中的回撥函式的第一個引數是context, 是一個與store例項具有相同屬性和方法的物件
const actions={ } export default actions 複製程式碼
-
store.js
store.js是vuex模組整合檔案,由於重新整理頁面會造成vuex資料丟失,所以這裡引入了一個vuex資料持久話外掛,將state裡面的資料儲存到localstorage。
安裝
vuex-persistedstate
npm install vuex-persistedstate --save 複製程式碼
import Vue from 'vue' import Vuex from 'vuex' import state from "./state"; import mutations from "./mutations"; import actions from "./actions"; import getters from "./getters"; //引入vuex 資料持久化外掛 import createPersistedState from "vuex-persistedstate" Vue.use(Vuex) export default new Vuex.Store({ state, mutations, actions, getters, plugins: [createPersistedState()] }) 複製程式碼
至此vuex引入完畢,如同學們還有不明白的可以去翻閱vuex文件。
十、首頁佈局介紹
現在我們開始進行頁面的佈局。首先我們來分析下首頁的情況
- 側邊欄
- 頂部欄
-
內容部分
首先我們在view
資料夾下面新建一個layout
資料夾,裡面再新增一個layout.vue
,以及compentents
資料夾。
-
側邊欄
在compentents資料夾下面新建一個
Aside.vue
檔案,實現路由跳轉相關的邏輯,運用了element導航選單的路由模式,如有不明白的可以去ElementUI導航選單去看看。
<template> <div class="aside"> <el-menu :default-active="onRoutes" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" :collapse="isCollapse" active-text-color="#bdb7ff" router > <template v-for="item in items"> <template v-if="item.subs"> <el-submenu :index="item.index" :key="item.index"> <template slot="title"> <i :class="item.icon"></i> <span slot="title">{{ item.title }}</span> </template> <template v-for="subItem in item.subs"> <el-submenu v-if="subItem.subs" :index="subItem.index" :key="subItem.index"> <template slot="title">{{ subItem.title }}</template> <el-menu-item v-for="(threeItem,i) in subItem.subs" :key="i" :index="threeItem.index" >{{ threeItem.title }}</el-menu-item> </el-submenu> <el-menu-item v-else :index="subItem.index" :key="subItem.index">{{ subItem.title }}</el-menu-item> </template> </el-submenu> </template> <template v-else> <el-menu-item :index="item.index" :key="item.index"> <i :class="item.icon"></i> <span slot="title">{{ item.title }}</span> </el-menu-item> </template> </template> </el-menu> </div> </template> 複製程式碼
import { mapState } from "vuex"; export default { data() { return { //配置目錄 items: [ { icon: "el-icon-edit-outline", index: "home", title: "系統首頁" }, { icon: "el-icon-edit-outline", index: "icon", title: "自定義圖示" }, { icon: "el-icon-edit-outline", index: "component", title: "元件", subs: [ { index: "editor", title: "富文字編譯器" }, { index: "countTo", title: "數字滾動" }, { index: "trees", title: "樹形控制元件", subs: [ { index: "tree", title: "自定義樹" }, { index: "treeSelect", title: "下拉樹" } // ,{ //index:'treeTable', //title:'表格樹', // } ] }, ] }, { icon: "el-icon-edit-outline", index: "draggable", title: "拖拽", subs: [ { index: "draglist", title: "拖拽列表" }, { index: "dragtable", title: "拖拽表格" } ] }, { icon: "el-icon-edit-outline", index: "charts", title: "圖表", subs: [ { index: "cricle", title: "餅圖" }, ] }, { icon: "el-icon-edit-outline", index: "7", title: "錯誤處理", subs: [ { index: "permission", title: "許可權測試" }, { index: "404", title: "404頁面" } ] }, ] }; }, computed: { onRoutes() { return this.$route.path.replace("/", ""); }, ...mapState(["isCollapse"]) //從vuex裡面獲取選單是否摺疊 }, methods: { //下拉展開 handleOpen(key, keyPath) { console.log(key, keyPath); }, //下來關閉 handleClose(key, keyPath) { console.log(key, keyPath); } } }; 複製程式碼
-
頂部欄
在
view/compentents
資料夾下面新建一個Header.vue
<template> <div class="head-container clearfix"> <div class="header-left"> <showAside :toggle-click="toggleClick"/> </div> <div class="header-right"> <div class="header-user-con"> <!-- 全屏顯示 --> <div class="btn-fullscreen" @click="handleFullScreen"> <el-tooltip effect="dark" :content="fullscreen?`取消全屏`:`全屏`" placement="bottom"> <i class="el-icon-rank"></i> </el-tooltip> </div> <!-- 訊息中心 --> <div class="btn-bell"> <el-tooltip effect="dark" :content="message?`有${message}條未讀訊息`:`訊息中心`" placement="bottom"> <router-link to="/tabs"> <i class="el-icon-bell"></i> </router-link> </el-tooltip> <span class="btn-bell-badge" v-if="message"></span> </div> <!-- 使用者名稱下拉選單 --> <el-dropdown class="avatar-container" trigger="click"> <div class="avatar-wrapper"> <img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3266090804,66355162&fm=26&gp=0.jpg" class="user-avatar" > {{username }}<i class="el-icon-caret-bottom"/> </div> <el-dropdown-menu slot="dropdown" class="user-dropdown"> <router-link class="inlineBlock" to="/"> <el-dropdown-item>首頁</el-dropdown-item> </router-link> <el-dropdown-item>個人設定</el-dropdown-item> <el-dropdown-item divided> <span style="display:block;" @click="logout">退出登陸</span> </el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </div> </div> </template> 複製程式碼
import showAside from "@/components/showAside.vue";//引入了一個側邊欄是否摺疊的元件 export default { // name:'header', components: { showAside }, data() { return { fullscreen: false, name: "linxin", message: 2, username: "zyh" }; }, computed: { isCollapse: { get: function() { return this.$store.state.isCollapse; }, set: function(newValue) { console.log(newValue); this.$store.commit("IS_COLLAPSE", newValue);//提交到vuex } } }, methods: { toggleClick() { this.isCollapse = !this.isCollapse; }, // 使用者名稱下拉選單選擇事件 logout(command) { this.$router.push("/login"); }, // 全屏事件 handleFullScreen() { let element = document.documentElement; if (this.fullscreen) { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } } else { if (element.requestFullscreen) { element.requestFullscreen(); } else if (element.webkitRequestFullScreen) { element.webkitRequestFullScreen(); } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen(); } else if (element.msRequestFullscreen) { // IE11 element.msRequestFullscreen(); } } this.fullscreen = !this.fullscreen; } } }; 複製程式碼
現在在src/components
資料夾下面新建一個showAside.vue
元件
<template> <div class="clearfix"> <div class="showAside pull-left" @click="toggleClick"> <i class="el-icon-menu"></i> </div> </div> </template> 複製程式碼
export default { name: "showAside", props: { toggleClick: { type: Function, default: null } } }; 複製程式碼
-
頂部導航欄標籤元件
在
view/compentents
資料夾下面新建一個Tags.vue
<template> <!-- 開啟標籤的容器 --> <div class="tags"> <ul> <li class="tags-li" v-for="(item,index) in tagsList" :key="index" :class="{'active': isActive(item.path)}" > <router-link :to="item.path" class="tags-li-title">{{item.title}}</router-link> <span class="tags-li-icon" @click="closeTags(index)"> <i class="el-icon-close"></i> </span> </li> </ul> <div class="tags-close-box"> <el-dropdown @command="handleCommand"> <el-button size="mini" type="primary"> 標籤選項 <i class="el-icon-arrow-down el-icon--right"></i> </el-button> <el-dropdown-menu size="small" slot="dropdown"> <el-dropdown-item command="closeOther">關閉其他</el-dropdown-item> <!-- <el-dropdown-item command="all">關閉所有</el-dropdown-item> --> </el-dropdown-menu> </el-dropdown> </div> </div> </template> 複製程式碼
import { messages } from "@/assets/js/common.js"; export default { created() { //判斷標籤裡面是否有值 有的話直接載入 if (this.tagsList.length == 0) { this.setTags(this.$route); } }, computed: { //computed 方法裡面沒有set方法因此不能使用mapState,需要重新定義set方法 tagsList: { get: function() { return this.$store.state.tagsList; }, set: function(newValue) { this.$store.commit("TAGES_LIST", newValue); // this.$store.state.tagsList = newValue; } } }, watch: { //監聽路由變化 $route(newValue, oldValue) { this.setTags(newValue); } }, methods: { //選中的高亮 isActive(path) { return path === this.$route.fullPath; }, handleCommand(command) { if (command == "closeOther") { // 關閉其他標籤 const curItem = this.tagsList.filter(item => { return item.path === this.$route.fullPath; }); this.tagsList = curItem; } }, //新增標籤 setTags(route) { let isIn = this.tagsList.some(item => { //判斷標籤是否存在 return item.path === route.fullPath; }); //不存在 if (!isIn) { // 判斷當前的標籤個數 if (this.tagsList.length >= 10) { messages("warning", "當標籤大於10個,請關閉後再開啟"); } else { this.tagsList.push({ title: route.meta.title, path: route.fullPath, name: route.name }); //存到vuex this.$store.commit("TAGES_LIST", this.tagsList); } } }, closeTags(index) { console.log(this.tagsList.length); if (this.tagsList.length == 1) { messages("warning", "不可全都關閉"); } else { //刪除當前 let tags = this.tagsList.splice(index, 1); this.$store.commit("TAGES_LIST", this.tagsList); } } } }; 複製程式碼
接下來在view/compentents
資料夾下面新建一個Main.vue
,主要是將頂部導航標籤欄以及內容部分結合起來。
<template> <div class="container"> <tags /> <div class="contents"> <transition name="fade-transform" mode="out-in"> <router-view></router-view> </transition> </div> </div> </template> 複製程式碼
import Tags from './Tags.vue' export default { components:{ Tags } } 複製程式碼
相關元件寫好,在layout元件中彙總
<template> <div class="wrapper"> <Aside class="aside-container"/> <div class="main-container" :class="isCollapse==true?'container_collapse':''"> <Header/> <Main/> </div> </div> </template> 複製程式碼
import Aside from "./components/Aside.vue"; import Header from "./components/Header.vue"; import Main from "./components/Main.vue"; import { mapState } from "vuex"; export default { name: "Layout", components: { Aside, Header, Main }, computed: { ...mapState(["isCollapse"]) } }; 複製程式碼
至此首頁佈局已經規劃完成,如有不太清楚的可以檢視專案地址
十一、結語
管理系統是多種多樣的,每家公司都有不同的業務邏輯,本篇文章也只是拋磚引玉,還有許多需要修正改進的地方,如果同學們有更好的想法可以提出來希望大家一起完善本專案。
|-- undefined |-- .env |-- .env.prod |-- .env.test |-- .gitignore |-- babel.config.js |-- package-lock.json |-- package.json |-- README.md |-- vue.config.js |-- public ||-- favicon.ico ||-- index.html |-- src |-- App.vue |-- element-variables.scss |-- main.js |-- api ||-- api.js ||-- request.js |-- assets ||-- logo.png ||-- css |||-- normalize.css |||-- public.css ||-- icon |||-- demo.css |||-- demo_index.html |||-- iconfont.css |||-- iconfont.eot |||-- iconfont.js |||-- iconfont.svg |||-- iconfont.ttf |||-- iconfont.woff |||-- iconfont.woff2 ||-- img |||-- tou.jpg ||-- js |||-- common.js ||-- scss ||-- global.scss |-- components ||-- showAside.vue |-- plugins ||-- element.js |-- router ||-- router.js |-- store ||-- actions.js ||-- getters.js ||-- mutations.js ||-- state.js ||-- store.js |-- views |-- layout ||-- Layout.vue ||-- components ||-- Aside.vue ||-- Header.vue ||-- Main.vue ||-- Tags.vue 複製程式碼
最後專案目錄檔案結構