
本系列文章是為學習Vue的專案練習筆記,盡量詳細記錄一下一個完整專案的開發程序,面向初學者,本人也是初學者,搬磚技識訓不成熟,專案在技術上前端為主,包含一些后端代碼,從基礎的資料庫(Sqlite)、到后端服務Node.js(Express),再到Web端的Vue,包含服務端、管理后臺、商城網站、小程式/App,分為下面多個篇檔案,
??系列目錄:
- 圖書商城Vue+Element+Node+TS專案練習??
- 圖書商城①管理后臺Vue2+ElementUI??
- 圖書商城②后端服務Node+Express+Sqlite??
- 未完成:商城網站Vue3+TS、商城APP端Vue3+TS+uniapp
00、管理后臺Vue2+ElementUI
這是一個比較典型的管理后臺練習專案,包含登錄、框架頁、導航路由、導航標簽、資料管理、字典管理等基礎功能,管理后臺的業務大多是資料管理CRUD功能,該專案只是是簡單實作了幾個模塊,同時針對CRUD,整理了一個模板??template,
??技術路線:
- Vue v2.*
- ElementUI v2.*
??相關組件:
vuex:狀態管理vue-router:前端路由axios:HTTP呼叫echarts:圖表組件,按需定制i18n:多語言國際化vue-i18nv8.*版本@wangeditor:富文本編輯器Less:CSS前處理器/語言
??源代碼地址:Github / KWebNote,Gitee / KWebNote,管理后臺代碼在目錄??book_admin下,
??在線體驗地址????:http://kanding.gitee.io/kwebnote (任意用戶名、密碼,通過gitee靜態頁面Gitee Pages部署的,所以這里部署的版本是寫了個mock模擬api,路由用的hash模式),

01、創建專案/準備
創建圖書管理后臺專案“book_admin”,基于@vue/cli,通過其vue ui管理工具,可視化操作創建專案,

- 選擇專案目錄,開始創建專案,
- 可以選擇內置的多種預設模式,也可選擇“手動”模式,按需設定專案的詳細規則,

- 在手動模式下,選擇需要的組件,如Vuex、Vue-Router、Less等,
- 更多
@vue/cli參考《Vue專案工程@vue/cli入門》
vue創建的專案已經包含了一個基礎的架子了,如下圖,主html頁面檔案“index.html”,入口JS檔案“main.js”,入口Vue檔案“App.vue”,

02、主頁面/框架頁面
管理后端是SPA單頁應用,創建主框架頁面“Main.vue”,登錄后的所有內容都在這個主頁面內呈現和管理,頁面視圖關系如下圖:

2.1、Main.vue
因此,主頁面就比較重要,是搬磚的基座,實際效果和布局結構圖如下:

- Header:頭部區域,包含Logo、標簽欄(存放打開的頁面,類似瀏覽器的多頁簽)、系統按鈕(最右側區域),
- 路由選單:系統導航選單,資料來自路由配置資訊,
- 面包屑:內容區域當前視圖的路由資訊,
- 內容區域:當前視圖內容呈現區域,
- 快取
<keep-alive>,配合多標簽組件視圖快取,切換標簽后視圖的狀態會被保持, - 切換影片
<transition>,切換內容時的影片效果, - 內容滾動處理:內容區域的高度、寬度自適應鋪滿,如果內容超出該區域,則顯示滾動條,不會導致整個頁面出現滾動條,
- 快取
- Footer:底部區域,好像也沒啥用,先放這吧!
<template>
<el-container style="height:100%">
<el-container >
<!-- 左側 :logo+導航選單 -->
<el-aside :>
<MenuSidebar />
</el-aside>
<!-- 右側 :頭部+主內容-->
<el-container>
<!-- 頭部 -->
<el-header :style="config.thema" >
<!-- 標簽工具列 -->
<div style="flex:1;overflow:hidden">
<TabsBar ref="tabsBar"></TabsBar>
</div>
<!-- 右側的系統操作按鈕 -->
<i v-on:click="$refs.userConfig.show()" title="系統設定"></i>
<el-dropdown @command="handleCommand">
<span>
<img :src="https://www.cnblogs.com/anding/archive/2023/06/29/$api.URL.proxy+'/file/f1.jpg'" alt="頭像" />
[ {{$store.state.user.name}} ]
<i style="font-size:12px"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="pwd">修改密碼</el-dropdown-item>
<el-dropdown-item command="about">
<i ></i>關于
</el-dropdown-item>
<el-dropdown-item command="user">
<i ></i>個人中心
</el-dropdown-item>
<el-divider></el-divider>
<el-dropdown-item command="logout" icon="el-icon-circle-close">退出登錄</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<!-- 主內容區域 -->
<el-main >
<!-- 面包屑 -->
<div >
<el-button type="text"
:icon="config.menuCollapse?'el-icon-s-unfold':'el-icon-s-fold'"
v-on:click="config.menuCollapse=!config.menuCollapse" ></el-button>
<el-breadcrumb separator="/" style="display:inlne-block">
<el-breadcrumb-item v-for="r in $route.matched" :key="r.name">{{r.meta?.lang ? $t('menu.' + r.meta.lang) : r.meta?.title}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<!-- 頁面內容的容器 -->
<div >
<!-- 加了切換影片、保存頁面狀態 -->
<transition :name="config.routerAnimation?'fade-transform':''" mode="out-in">
<keep-alive :include="cacheNames">
<router-view></router-view>
</keep-alive>
</transition>
</div>
</el-main>
</el-container>
</el-container>
<!-- footer -->
<el-footer height="30px">{{$consts.footer}}</el-footer>
<UserConfig ref="userConfig"></UserConfig>
</el-container>
</template>
2.2、導航選單&路由
每個后臺系統都會有導航選單,支持多級展示、可收縮,效果如下圖:

???♂?實作程序:
1、路由的配置:這里用的是本地路由(vue-router中的路由配置資訊),實際專案中路由可后臺管理,或者本地+后臺結合,本地路由配置“routes.js”詳見Github / KWebNote,
import constants from '@/assets/constants'
import Vue from 'vue'
import VueRouter from 'vue-router'
//路由配置
import baseRoutes from './routes'
//注冊路由插件
Vue.use(VueRouter)
// 創建路由
const router = new VueRouter({
mode: 'history', //模式
base: process.env.BASE_URL,
routes: baseRoutes, //路由配置
})
// 路由全域守衛-導航前,登錄token判斷
router.beforeEach((to, from, next) => {
if (to.path === '/login')
return next();
// 除開登錄頁面,其他頁面都驗證token,如果沒有token則跳轉到登錄頁面
const token = sessionStorage.getItem('admin_token');
if (!token)
return next('/login');
else
next();
})
router.afterEach((to, from) => {
//更新網頁標題
document.title = constants.sysName + '-' + to.meta.title;
})
??404頁面的配置:路由的匹配是從上而下的,404頁面路由放到最后即可,然后路徑使用通配符匹配所有地址,
{ path: '*', component: 404 }
2、導航選單組件:創建“MenuSidebar.vue”,用<el-menu>組件顯示多級選單,啟用路由導航router,選單的資料就是來自前面的路由,
3、樹形選單:導航選單項-遞回,選單項用一個“MenuItem.vue”組件來實作遞回路由樹,如果路由還有子節點children,則遞回呼叫組件自身,
<template>
<el-menu-item v-if="!hasChildren" :index="item.path">
<i :></i>
<!-- 名稱用title插槽 -->
<span slot="title">{{title(item)}}</span>
</el-menu-item>
<el-submenu v-else :index="item.path">
<template slot="title">
<i :></i>
<span slot="title">{{title(item)}}</span>
</template>
<MenuItem v-for="child in children" :item="child" :key="child.path"></MenuItem>
</el-submenu>
</template>
<script>
export default {
name: 'MenuItem',
props: ['item'],
computed: {
children() {
return this.item?.children?.filter(s => !s.hidden);
},
hasChildren() {
return this.item?.children?.length > 0;
},
},
methods: {
title(item) {
return item.meta?.lang ? this.$t('menu.' + item.meta.lang) : item.meta?.title;
}
}
}
</script>
2.3、配置路由影片
就是在路由切換頁面視圖的時候,有一個過渡影片效果,如下圖,

主要是使用Vue的<transition>組件來實作過渡影片,設定其name和對應CSS影片即可,可參考另外一篇《Vue2快速上門(2)-模板語法 / Vue影片》,
<transition :name="config.routerAnimation?'fade-transform':''" mode="out-in">
<keep-alive :include="cacheNames">
<router-view></router-view>
</keep-alive>
</transition>
<style>
// 路由切換影片
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.5s;
}
.fade-transform-enter {
opacity: 0;
transform: translateX(30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(-30px);
}
</style>
??注意影片模式
mode="out-in",避免布局變更引起的顯示例外,name值是用于影片CSS樣式的類名,這里的的name用了一個用戶配置屬性,目的是可以配置是否開啟路由轉場影片,
2.4、多標簽工具列
如下效果圖,類似Chrome瀏覽器的多標簽效果,打開的路由視圖示題顯示在標簽欄,可以重繪、關閉、切換,切換時會保留視圖狀態,這樣就可以很方便的多標簽操作了,

??方案設計:

- 監測路由變化,記錄打開的頁面路由,保存在vuex的store中,同時記錄打開的頁面名稱,用于
<keep-alive>的include實作定向路由快取,過濾不需要快取的組件,也是實作視圖重繪的關鍵, - 顯示快取的路由串列,就是看到的標簽欄,
- 關閉按鈕,激活的路由視圖顯示關閉按鈕,關閉后自動路由到下一個頁面/標簽,
- 固定的標簽:對于如“主頁”的路由固定在標簽欄(會在路由資訊中配置),在初始化的時候就顯示在標簽欄,固定的標簽不支持關閉,
- 右鍵選單功能:重繪、關閉、關閉其他、關閉所有,更新快取的路由串列,
???♂?實作程序:
1、在vuex中添加一個子模塊“tabBars.js”,單獨管理標簽的狀態資訊,提供快取路由串列的操作方法:添加、洗掉、洗掉其他、洗掉所有、清空,
export default {
namespaced: true,
state: {
cacheRoutes: [], //快取的路由,用于標簽欄使用
cacheNames: [], //快取的打開的路由Name,用于Keep-alive的快取白名單
},
mutations: {
add(state, obj) {
if (!state.cacheRoutes.some(s => s.path === obj.path)) {
//添加打開的路由,只需要path、name、mata
state.cacheRoutes.push({ path: obj.path, name: obj.name, meta: obj.meta });
state.cacheNames = state.cacheRoutes.map(s => s.name);
}
},
remove(state, obj) {
const i = state.cacheRoutes.findIndex(s => s.path === obj.path);
if (i < 0)
return;
state.cacheRoutes.splice(i, 1);
state.cacheNames = state.cacheRoutes.map(s => s.name);
},
removeName(state, obj) {
//只移除快取名字
const i = state.cacheNames.findIndex(s => s === obj.name);
if (i < 0)
return;
state.cacheNames.splice(i, 1);
},
},
}
2、路由資訊配置,來自vue-router的路由配置,添加了幾個自定義的屬性,
- 注意
name和組件內部的name定義應該一致,會在<keep-alive :include="cacheNames">中使用, meta.title:標題meta.icon:icon圖示meta.affix:是否固定,固定在標簽欄
{
path: '/home',
name: 'Home',
meta: { title: '首頁', lang: 'home', icon: 'el-icon-s-home', affix: true },
component: () => import('@/views/Home.vue'),
},
{
path: '/books',
name: 'Books',
meta: { title: '圖書管理', lang: 'book', icon: 'el-icon-notebook-2' },
component: () => import('@/views/book/Books.vue'),
},
3、創建標簽欄組件TabsBar.vue,核心功能、代碼都在這里,標簽的顯示、功能操作,包括右鍵選單,完整代碼:Github / KWebNote
<template>
<div >
<router-link
v-for="r in cachedRoutes"
:to="r"
:key="r.path"
:
@contextmenu.prevent.native="showMenu(r,$event)"
>
<i :></i>
{{r.meta?.lang ? $t('menu.' + r.meta.lang) : r.meta?.title}}
<i v-if="!isAffix(r)" @click.prevent.stop="handleClose(r)"></i>
</router-link>
<!-- 頁簽按鈕的右鍵選單 -->
<el-card v-show="tagMenu.visible" :style="{left:tagMenu.left+'px',top:tagMenu.top+'px'}">
<ul>
<li @click="refresh(selectedTag)" v-show="isActive(selectedTag)">
<i ></i> 重繪
</li>
<li @click="handleClose()" v-show="!isAffix(selectedTag)">
<i ></i> 關閉
</li>
<li @click="handleCloseOther()">
<i ></i> 關閉其他
</li>
<li @click="handleCloseAll">
<i ></i> 關閉所有
</li>
</ul>
</el-card>
</div>
</template>
?注意:這里的標簽關閉按鈕,一定要加上修飾符“.prevent.stop”,阻止冒泡、及其他事件,因為標簽本身也是有點擊事件的,開始沒加,莫名其妙沒有跳轉,被卡了好半天,
<i v-if="!isAffix(r)" @click.prevent.stop="handleClose(r)"></i>
復習一下:
| 修飾符 | 描述 |
|---|---|
| .stop | 呼叫 event.stopPropagation(),停止向上冒泡(propagation /?pr?p??ɡe??n/ 傳播) |
| .prevent | 呼叫 event.preventDefault(),取消默認事件行為,如checkbox、<a>的默認事件行為,不影響冒泡 |
??重繪怎么實作?
重繪的實作稍微復雜一點點,因為這是本地路由,不能重繪整個頁面,而當前路由視圖是用了<keep-alive>快取的,因此實作重繪的的基本程序:
- 去除快取并關閉路由:去除
<keep-alive>的快取,就是從其include白名單中移除, - 重新打開路由視圖,
為了視覺效果更佳,這里用一個中間頁面進行跳轉,效果如下:

設計了一個中級頁面Redirect.vue,作用只有一個,就是用于跳轉,跳轉目標用路由引數傳遞,
// 用于中轉跳轉的頁面
<script>
export default {
created() {
this.$router.replace({ path: '/' + this.$route.params.path, query: this.$route.query });
},
render: function (h) {
return h()
}
}
</script>
需要注意中間頁面,不快取、不顯示標簽欄,重繪時,移除快取,然后重定向到當前頁面,重新加載當前頁面,
refresh(tag) {
//移除去掉快取,再重定向跳轉到當前頁面
this.$store.commit('tabBars/removeName', this.$route);
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + tag.path
})
})
},
2.5、用戶配置本地化保存
系統選單“用戶設定”,實作用戶自定義的一些個性化配置,并本地存盤、加載,保存到localStorage中,這樣下次進入系統可以保持個性化配置了,

??效果如上圖,需求分析:

- 主題樣式,只實作了標題欄顏色樣式,前景色
color、背景色backgroundColor,應用在標題欄Header的樣式上, - 路由切換影片是否開啟,
- 多語言設定,實作了中文、英文語言,詳見下一章節,
- 導航選單的折疊狀態,
- 用戶配置保存到本地
localStorage中,系統初始化時從localStorage加載用戶配置,
???♂?實作程序:
1、創建一個單獨的vue組件“UserConfig.vue”管理用戶的設定項,內部用抽屜<el-drawer>來實作從右側滑出的效果,
2、監聽資料的變化(深度監聽),如果變化則保存用戶配置資料到localStorage,同時更新多語言的配置項,
created() {
//監聽配置變更,持久化存盤到本地
this.$watch('config', () => {
localStorage.setItem('admin-userconfig', JSON.stringify(this.config));
//手動更新語言
this.$i18n.locale = this.config.language;
}, { deep: true })
},
3、在main.js中添加初始化代碼,從localStorage加載上次保存的用戶配置資訊,
created: function () {
LoadUserConfig();
}
function LoadUserConfig() {
let vstr = localStorage.getItem('admin-userconfig');
if (vstr) {
Object.assign(userConfig, JSON.parse(vstr));
userConfig.thema = themas.filter(s => s.name == userConfig.thema.name)[0];
//語言
i18n.locale = userConfig.language;
}
2.6、國際化多語言
實作多語言(國際化)的最主流、成熟的方案就是i18n(internationalization /??nt??n??n?la??ze??n/ 國際化,首字母i、尾字母n加中間的18個字母),官方檔案,Vue中使用vue-i18n插件,

安裝i18n插件,Vue2.*不太兼容最新版的v9.*,安裝8.*版本:
vue add i18n
# 或者
cnpm i -S [email protected]
安裝完成后,“package.json”檔案中就有了"vue-i18n": "^8.26.3",
vue add i18n方式安裝,除了安裝插件,還把基本的配置、語言檔案都準備好了,屬于完成了簡裝可以擰包入住了,npm指令安裝只會安裝插件,需要自己完成配置和注冊,
??配置i18n
i18n的配置、使用還是比較簡單的,先配置語言資訊,然后在代碼(JavaScript、Vue模板)中使用,
|- src
|-lang
|-index.js # 配置i18n
|-lang-en.js # 英文語言資源
|-lang-cn.js # 中文語言資源
1、分別創建不同的語言包檔案,語言資訊為一個鍵值結構的JSON物件,鍵為語言項的key,值為顯示的文本內容,結構可以按照專案情況自行定義,葉子節點屬性是一個語言項,

2、注冊插件,配置i18n實體,
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import lang_zhcn from './lang-cn'
import lang_en from './lang-en'
//注冊
Vue.use(VueI18n);
//申明i18n
const i18n = new VueI18n({
locale: 'en', //選中的語言
messages: {
en: {
...lang_en, //英文語言配置
},
zh: {
...lang_zhcn, //中文語言配置
}
}
})
locale屬性為當前選中的語言,更改值實作語言切換,- Element的國際化,按照官方檔案配置即可:element-國際化
3、在main.js中引入,并注入到Vue根實體中,
import i18n from './lang'
new Vue({
router,
store,
i18n,
}).$mount('#app')
??使用
使用就簡單了,使用i18n提供的方法$t('key')即可,
<p >{{$t('home.user')}}</p>
//JS
title(item) {
return item.meta?.lang ? this.$t('menu.' + item.meta.lang) : item.meta?.title;
}
03、登錄頁面/Login.vue

登錄頁面主要就是用戶名、密碼的表單,然后呼叫后端登錄api介面驗證、獲得token完成登錄,
- 表單規則:表單就需要用到輸入驗證規則
rules,element表單驗證,
{
user: { name: '', pwd: '' },
rules: {
name: [{ required: true, message: '用戶名不能為空' }, { min: 3, max: 8, message: "長度應為3-8" }],
pwd: [{ required: true, message: '密碼不能為空' }, { min: 3, max: 8, message: "長度應為3-8" }],
},
}
- 表單驗證:表單驗證的執行是在表單
<el-form>組件上呼叫validate()方法,因此需要系結model物件,在表單項<el-form-item>``prop上系結model物件欄位名,
this.$refs.userForm.validate((valid, mes) => {
if (!valid) {
this.$message.error('輸入有誤,請修改后重新提交!');
return;
}
//提交...
- 記住用戶名:保存到本地
localStorage中,加載該頁面的時候讀取, - 登錄成功:獲取并保存
token資訊,保存在vuex的store中或本地sessionStorage,然后跳轉到主頁面this.$router.push('/home'),
04、首頁/Home.vue
管理類系統大概率都有一個首頁Home.vue,作為默認頁面,展示系統的一些概況、用戶的一些統計資訊、通知資訊等,為保持各個“豆腐塊”風格一致,推薦用<el-card>組件包裝內容,

用到了圖表組件 echarts,安裝最新版本:
$ cnpm install echarts -S
// 引入echarts
import * as echarts from 'echarts'
// 在Vue原型上掛載$echarts,在vue示例中this.$echarts
Vue.prototype.$echarts = echarts
這里只用了2個圖表,卻引入了所有的echarts組件,打包后的JS檔案6M多,實在太大了,通過官方提供的在線定制功能按需定制JS檔案,然后引入該JS檔案即可,
05、圖書管理模塊
圖書管理為圖書的綜合管理,包含增、刪、改、查,是管理后臺的典型功能,如用戶管理、商品管理、活動管理、公告管理等等都類似,

功能結構如下圖:

代碼結構如下圖,圖書管理模塊包含多個頁面,其中“Books.vue”為入口頁面,

5.1、圖書串列

- 圖書串列由表格
<el-table>和分頁<el-pagination>組成,統一設計了查詢結構,分頁組件做了一個簡單的封裝, - 表格中的狀態用到了一個自己寫的列舉組件,詳見《前端列舉enum的應用(Element)封裝》,
- 圖書詳情用了抽屜組件
<el-drawer>,點擊名稱從右側彈出, - 圖書的新增、修改用的彈框組件
<el-dialog>,默認的BookDialog是彈出帶遮罩的模態框,額外實作了一個Plus版本BookDialogPlus(詳見下文),
5.2、分頁組件
分頁是串列常用組件,Element-UI提供了分頁組件<el-pagination>,在此基礎上做一個簡單的封裝,統一規范、簡化使用,

??封裝了些什么?
- 統一風格樣式、布局,也方便統一修改,
- 統一分頁大小配置
page-sizes="[5, 10, 20, 50]" - 統一分頁事件
pagination,統一處理了頁碼、頁數的變更,
<template>
<el-pagination
style="text-align:right;margin:6px 2px" background
:total="total" :current-page="currentPage" :page-size="pageSize" :page-sizes="[5, 10, 20, 50]"
@current-change="pageChanged" @size-change="pageSizeChanged"
layout="total, sizes, prev, pager, next, jumper"
></el-pagination>
</template>
<script>
export default {
props: {
//總數
total: { type: Number, default: 0, },
//頁碼,外部系結,加修飾符.sync
size: { type: Number, default: 10, },
// 當前頁碼,外部系結,加修飾符.sync
index: { type: Number, default: 1, }
},
computed: {
// 用修飾符“.sync”來實作更新父組件的值
currentPage: {
get() { return this.index },
set(val) { this.$emit('update:index', val) }
},
pageSize: {
get() { return this.size },
set(val) { this.$emit('update:size', val) }
}
},
methods: {
pageSizeChanged(v) {
// 修改父組件值
this.$emit('update:size', v);
// 觸發分頁事件
this.$emit('pagination');
},
pageChanged(v) {
// 修改父組件值
this.$emit('update:index', v);
// 觸發分頁事件
this.$emit('pagination');
},
}
}
</script>
這里的頁行數size、頁碼index,是外部傳入的的prop值,但內部也會修改,導致了“雙重系結”更新,這就需要用到.sync修飾符了,其實就是基于事件通知實作的,可參考官網檔案,
- 子組件內部通過Vue的事件
this.$emit('update:myPropName', v);觸發變更通知, - 外部系結的時候用
.sync修飾符,.sync實作了更新update事件的監聽和賦值,需注意不支持運算式,只能用property名,
??使用:
<Pagination :total="total" :size.sync="search.size" :index.sync="search.index" @pagination="loadData"></Pagination>
5.3、圖書編輯Plus
如下效果圖,相比常規的模態框,只是遮住了當前視圖(圖書管理),不影響其他功能操作,

用的依然是彈框組件<el-dialog>,在此基礎上做了一點點調整,完整代碼見 Github / KWebNote,

- 取消遮罩層
:modal="false", - 操作按鈕放到了標題欄上,
- 通過樣式讓彈層剛好覆寫到當前視圖,
.dialogPlus {
position: absolute;
overflow: inherit;
.el-dialog {
min-height: 100% !important;
max-height: 100%;
display: flex;
flex-flow: column;
.el-dialog__header {
padding: 4px 10px;
}
.el-dialog__body {
overflow: auto;
max-height: 100%;
}
}
}
5.4、圖片上傳upLoad
圖片上傳使用檔案上傳組件<el-upload>,這里涉及一些基礎通用操作,因此針對圖片上傳封裝為一個組件“ImgUpload.vue”,效果如下:

- 上傳介面
action,后端檔案介面實作詳見后端章節, - 檔案格式
accept="image/*",這里的accept值為檔案的型別, - 檔案數量
limit,配置最大支持的檔案個數,鉤子on-exceed超過檔案數量限制時觸發,當達到限制時,隱藏上傳按鈕, - 上傳前
before-upload上傳前的鉤子,可用來驗證檔案的合法性, - 上傳成功
on-success檔案上傳成功的鉤子,可用來同步上傳的檔案資源, - 檔案資訊
prop."value",組件中定義了一個value的props,接受父組件傳入的已有檔案集合(字串,多個逗號隔開),組件內檔案變化通過this.$emit('input',nval)更新value值, - 預覽:用一個
<el-dialog>來展示預覽圖,
<template>
<div>
<el-upload
ref="upload" :action="$api.URL.upload" list-type="picture-card"
:multiple="true" accept="image/*" name="file"
:limit="limit" : :file-list="fileList"
:on-exceed="onOutOfLimit" :on-success="handleSuccess"
:on-error="handleError" :before-upload="handeleBefore" >
<!-- 上傳按鈕 -->
<i slot="default" ></i>
<!-- 提示內容 -->
<div slot="tip" style="font-size:0.8em">支持最多{{limit}}張圖片,每張圖片不超過{{maxSize}}Kb</div>
<!-- file模板 -->
<div slot="file" slot-scope="{file}" :>
<!-- 縮略圖的路徑,如果相對路徑則添加代理前綴-->
<img :src="https://www.cnblogs.com/anding/archive/2023/06/29/proxyURL(file.url)" alt />
<span >
<span @click="handlePictureCardPreview(file)">
<i ></i>
</span>
<span @click="handleRemove(file)">
<i ></i>
</span>
</span>
</div>
</el-upload>
<!-- 嵌套的dialog,需要設定append-to-body,嵌入自身到body元素 -->
<el-dialog :visible.sync="dialogVisible" append-to-body custom->
<img :src="https://www.cnblogs.com/anding/archive/2023/06/29/proxyURL(dialogImageUrl)" alt style="max-width: 100%;max-heigt: 100%;object-fit:contain" />
</el-dialog>
</div>
</template>
再加上一點點JS和CSS就完成了,完整代碼見 Github / KWebNote,,使用:
import ImgUpload from '@/components/ImgUpload.vue'
<ImgUpload v-model="book.imgs"></ImgUpload>
5.5、富文本@wangeditor
wangEditor 是一個輕量級 web 富文本編輯器,配置方便,使用簡單,安裝vue版本的“@wangeditor/editor-for-vue”:$ cnpm i @wangeditor/editor-for-vue -S,然后使用參考官方vue使用檔案,Ctrl+CV即可,
要實作圖片、視頻上傳還需要自己配置,因此就基于@wangeditor封裝了一個富文本編輯器Editor.vue,效果如下,
- 調整了工具列,排除了一些不需要的,
- 配置了圖片上傳服務,
@wangeditor支持粘貼圖片,

??配置工具列
默認提供的工具列功能比較豐富,如果需要調整,則需要先獲取工具列的toolbarKeys,引入@wangeditor/editor,在編輯器組件準備完成后獲取toolbarKeys,如updated(),
import { DomEditor } from '@wangeditor/editor'
updated() {
////在這里獲取工具列的配置toolbarKeys,用于自定義配置工具列
const toolbar = DomEditor.getToolbar(this.editor)
console.log(toolbar.getConfig().toolbarKeys)
},
獲取到的keys如下(整理后):
[
"headerSelect", "blockquote",
"|",
"bold", "underline", "italic",
{
"key": "group-more-style", "title": "更多", "iconSvg": "",
"menuKeys": [ "through", "code", "sup", "sub", "clearStyle" ]
},
"color", "bgColor",
"|",
"fontSize", "fontFamily", "lineHeight",
"|",
"bulletedList", "numberedList", "todo",
{
"key": "group-justify", "title": "對齊", "iconSvg": "",
"menuKeys": [ "justifyLeft", "justifyRight", "justifyCenter", "justifyJustify" ]
},
{
"key": "group-indent", "title": "縮進", "iconSvg": "",
"menuKeys": [ "indent", "delIndent" ]
},
"|",
"emotion", "insertLink",
{
"key": "group-image", "title": "圖片", "iconSvg": "",
"menuKeys": [ "insertImage", "uploadImage" ]
},
{
"key": "group-video",
"title": "視頻", "iconSvg": "",
"menuKeys": [ "insertVideo", "uploadVideo" ]
},
"insertTable", "codeBlock", "divider",
"|",
"undo", "redo", "|", "fullScreen"
]
通過toolbarConfig.excludeKeys配置不需要的工具列按鈕:
data() {
return {
editor: null,
toolbarConfig: { excludeKeys: ['group-video', 'emotion', 'lineHeight'] },
editorConfig: { placeholder: '請輸入內容...', maxLength: 8000 },
mode: 'default', // default simple
}
},
完整代碼見 Github / KWebNote,
??配置圖片上傳
在Editor的配置項editorConfig中配置圖片上傳引數,如下代碼:
editorConfig: {
placeholder: '請輸入內容...', maxLength: 8000,
MENU_CONF: {
uploadImage: { //配置圖片上傳
server: this.$api.URL.upload, //后端檔案上傳地址
fieldName: 'file', //表單引數名,和后端一致
maxFileSize: 2 * 2048 * 2048, //最大檔案大小
maxNumberOfFiles: 1, //每次檔案個數
allowedFileTypes: ['image/*'], //檔案型別:圖片
timeout: 9 * 1000, //超時時長
// 自定義插入圖片,根據后端回傳的結構,加上跨域代理
customInsert: (res, insertFn) => {
const url = this.$api.URL.proxy + res.url
insertFn(url)
},
}
}
},
5.6、樹形下拉框
圖書的型別是來自字典資料(詳見后續《字典管理》章節),樹形結構,ElementUI2版本中么有樹形的下拉框組件,Element3(ElementPlus)有,結合下拉框組件<el-select>和樹形組件<el-tree>封裝實作了一個樹形下拉框組件TreeSelect,效果圖如下,

<el-tree>作為<el-select>的一個選項值<el-option>,然后JS代碼實作選擇值的同步管理即可,邏輯比較簡單,
<el-select v-model="currentText" placeholder="請選擇" @clear="handelClear" clearable>
<el-option :value="https://www.cnblogs.com/anding/archive/2023/06/29/currentItem[options.value]" :label="currentItem[options.label]">
<!-- data:資料-->
<!-- props:資料結構配置 -->
<!-- node-key:唯一標識欄位 -->
<el-tree ref="tree" :https://www.cnblogs.com/anding/archive/2023/06/29/data="data" :node-key="options.value" :props="options" @current-change="handleCurrentChange"></el-tree>
</el-option>
</el-select>
完整代碼見Github / KWebNote,
06、字典管理模塊
字典管理為一個比較通用的字典資料管理模塊Dictionary.vue,用來管理一些可變的分類資料,如圖書分類、商品促銷型別、品牌、國家、省市區地址等,包含兩部分資料:
- 字典類別,定義有哪些字典類別,包含分類名稱、編碼、是否樹形結構等關鍵欄位,
- 字典資料,每一個字典類別的字典資料,統一存盤,用字典編碼區分,樹形結構,結構:id、名稱、類別編碼、排序號、父id,

樹形結構的資料編輯時,可以選擇父級,這里用的是<el-cascader>級聯選擇器組件,

資料是在本地進行樹形組裝和排序的,根級節點的父idpid為0,用buildDicTree方法遞回構造一顆樹,
export function queryDicData(type, istree = false) {
return api.dicdata({ code: type }).then(res => {
if (!res.data || res.data.length <= 0) return [];
//構造樹形結構
if (!istree)
return res.data.sort(sortDicData);
let sortItems = buildDicTree(res.data, ROOT_PID);
return sortItems;
})
}
function buildDicTree(items, pid) {
let sortItems = items.filter(s => s.pid == pid);
if (!sortItems || sortItems.length <= 0) return [];
sortItems.sort(sortDicData).forEach(item => {
const res = buildDicTree(items, item.id);
if (res && res.length > 0)
item.children = res.sort(sortDicData);
});
return sortItems;
}
function sortDicData(item1, item2) {
return item1.sort - item2.sort;
}
??需要注意的是,這里修改字典資料時,父級節點不能選擇自己及自己的子節點,否則會導致死回圈,因此需要對上面構造的樹做一個處理,把不能選擇的節點設定
disabled屬性,
參考資料
- 在線體驗地址:http://kanding.gitee.io/kwebnote (任意用戶名、密碼,通過gitee靜態頁面Gitee Pages部署的,所以這里部署的版本是寫了個mock模擬api,路由用的hash模式),
- vue-element-admin vue2的版本后臺框架,比較全面,適合學習
- Vue 官方檔案
- element-ui 2
- Vue2快速上門
- Vue專案工程@vue/cli入門
??著作權申明:著作權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請注明出處!原文編輯地址-語雀
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/556347.html
標籤:其他
下一篇:返回列表
