主頁 > 企業開發 > 圖書商城專案練習①管理后臺Vue2/ElementUI

圖書商城專案練習①管理后臺Vue2/ElementUI

2023-06-30 08:22:43 企業開發

image.png

本系列文章是為學習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模式),

image.png


01、創建專案/準備

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

image.png

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

image.png

  • 在手動模式下,選擇需要的組件,如Vuex、Vue-Router、Less等,
  • 更多@vue/cli參考《Vue專案工程@vue/cli入門》

vue創建的專案已經包含了一個基礎的架子了,如下圖,主html頁面檔案“index.html”,入口JS檔案“main.js”,入口Vue檔案“App.vue”,

image.png


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.gif

???♂?實作程序

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、配置路由影片

就是在路由切換頁面視圖的時候,有一個過渡影片效果,如下圖,

1.gif

主要是使用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瀏覽器的多標簽效果,打開的路由視圖示題顯示在標簽欄,可以重繪、關閉、切換,切換時會保留視圖狀態,這樣就可以很方便的多標簽操作了,

image.png

??方案設計:

  • 監測路由變化,記錄打開的頁面路由,保存在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白名單中移除,
  • 重新打開路由視圖

為了視覺效果更佳,這里用一個中間頁面進行跳轉,效果如下:

1.gif

設計了一個中級頁面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中,這樣下次進入系統可以保持個性化配置了,

1.gif

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

  • 主題樣式,只實作了標題欄顏色樣式,前景色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插件,

1.gif

安裝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,值為顯示的文本內容,結構可以按照專案情況自行定義,葉子節點屬性是一個語言項,

image.png

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

image.png

登錄頁面主要就是用戶名、密碼的表單,然后呼叫后端登錄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資訊,保存在vuexstore中或本地sessionStorage,然后跳轉到主頁面 this.$router.push('/home')

04、首頁/Home.vue

管理類系統大概率都有一個首頁Home.vue,作為默認頁面,展示系統的一些概況、用戶的一些統計資訊、通知資訊等,為保持各個“豆腐塊”風格一致,推薦用<el-card>組件包裝內容,

image.png

用到了圖表組件 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、圖書管理模塊

圖書管理為圖書的綜合管理,包含,是管理后臺的典型功能,如用戶管理、商品管理、活動管理、公告管理等等都類似,

1.gif

功能結構如下圖:

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

image.png

5.1、圖書串列

image.png

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

5.2、分頁組件

分頁是串列常用組件,Element-UI提供了分頁組件<el-pagination>,在此基礎上做一個簡單的封裝,統一規范、簡化使用,

image.png

??封裝了些什么?

  • 統一風格樣式、布局,也方便統一修改,
  • 統一分頁大小配置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

如下效果圖,相比常規的模態框,只是遮住了當前視圖(圖書管理),不影響其他功能操作,

1.gif

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

image.png

  • 取消遮罩層: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”,效果如下:

image.png

  • 上傳介面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支持粘貼圖片,

image.png

??配置工具列

默認提供的工具列功能比較豐富,如果需要調整,則需要先獲取工具列的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,效果圖如下,

image.png

<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,

image.png

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

image.png

資料是在本地進行樹形組裝和排序的,根級節點的父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

標籤:其他

上一篇:typescript的必要性及使用

下一篇:返回列表

標籤雲
其他(161883) Python(38266) JavaScript(25517) Java(18284) C(15238) 區塊鏈(8274) C#(7972) AI(7469) 爪哇(7425) MySQL(7273) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5876) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4609) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2438) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1985) HtmlCss(1979) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 圖書商城專案練習①管理后臺Vue2/ElementUI

    本系列文章是為學習Vue的專案練習筆記,盡量詳細記錄一下一個完整專案的開發程序。面向初學者,本人也是初學者,搬磚技識訓不成熟。專案在技術上前端為主,包含一些后端代碼,從基礎的資料庫(Sqlite)、到后端服務Node.js(Express),再到Web端的Vue,包含服務端、管理后臺、商城網站、小程... ......

    uj5u.com 2023-06-30 08:22:43 more
  • typescript的必要性及使用

    作為一個前端語言,Javascript從最初只是用來寫頁面,到如今的移動終端、后端服務、神經網路等等,它變得幾乎無處不在。如此廣闊的應用領域,對語言的安全性、健壯性以及可維護性都有了更高的要求。盡管ECMAScript標準在近幾年有了長足的進步,但是在型別檢查方面依然毫無建樹。在這種情況下TypeS... ......

    uj5u.com 2023-06-30 08:22:16 more
  • Vue Router 原始碼分析

    最終成果,實作了一個可運行的核心路由工程:柏成/vue-router3.x。地址如下:https://gitee.com/lbcjs/vue-router3.x ......

    uj5u.com 2023-06-30 08:22:12 more
  • typescript的必要性及使用

    作為一個前端語言,Javascript從最初只是用來寫頁面,到如今的移動終端、后端服務、神經網路等等,它變得幾乎無處不在。如此廣闊的應用領域,對語言的安全性、健壯性以及可維護性都有了更高的要求。盡管ECMAScript標準在近幾年有了長足的進步,但是在型別檢查方面依然毫無建樹。在這種情況下TypeS... ......

    uj5u.com 2023-06-30 08:21:59 more
  • CSS基礎-背景

    # 背景 ### **background-color** 背景顏色, 可以使用十六進制、rgb、rgba表示。 **語法** ```css /**selector 背景元素的原則去*/ /** color 背景顏色的值, 可以是 顏色名稱、十六進制值、RGB、RGBA*/ selector { b ......

    uj5u.com 2023-06-30 08:20:28 more
  • 京東到家小程式-在性能及多端能力的探索實踐

    為了提高研發效率,經過技術選型采用了taro3+原生混合開發模式,本文主要講解我們是如何基于taro框架,進行多端能力的探索和性能優化。 ......

    uj5u.com 2023-06-30 08:20:15 more
  • 初入前端-HTML

    ## HTML ### HTML歷史 HTML(Hypertext Markup Language)的歷史可以追溯到上世紀90年代初,以下是HTML的主要歷史階段: 1. HTML 1.0:在1991年發布,是HTML的最初版本,用于創建基本的文本和鏈接結構,但功能有限。 2. HTML 2.0:于 ......

    uj5u.com 2023-06-30 08:20:10 more
  • 圖書商城Vue+Element+Node專案練習(...)

    本系列文章是為學習Vue的專案練習筆記,盡量詳細記錄一下一個完整專案的開發程序。面向初學者,本人也是初學者,搬磚技識訓不成熟。專案在技術上前端為主,包含一些后端代碼,從基礎的資料庫(Sqlite)、到后端服務Node.js(Express),再到Web端的Vue,包含服務端、管理后臺、商城網站、小程... ......

    uj5u.com 2023-06-29 08:54:51 more
  • 記錄--不定高度展開收起影片 css/js 實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 不定高度展開收起影片 最近在做需求的時候,遇見了元素高度展開收起的影片需求,一開始是想到了使用 transition: all .3s; 來做影片效果,在固定高度的情況下,transition 影片很好使,滿足了需求,但是如果要考慮之后可 ......

    uj5u.com 2023-06-29 08:54:35 more
  • 前端打包部署后介面BASE_URL不對問題解決辦法

    在前端打包部署時,為了免去不同環境打包的麻煩,專案用的流水線觸發方式。在這里不細說,重點說說下面情況。 當專案提交打包部署后,訪問壓測環境或者生產環境的地址來使用專案時,發現介面報錯404。 在NETWORK里發現介面的BASEURL和當前環境需要呼叫的后端baseurl不同。 主要問題在于配置問題 ......

    uj5u.com 2023-06-29 08:54:22 more