vue3后台管理系統(模板)


🍎系統簡介

  • 此管理系統是基於Vite2和Vue3.0構建生成的后台管理系統。目的在於學習vite和vue3等新技術,以便於后續用於實際開發工作中;
  • 本文章將從管理系統頁面布局、vue路由鑒權、vuex狀態管理、數據持久化、用戶信息加密等方面進行介紹和記錄;
  • 這也是我邊學習邊實踐的過程,此次記錄一是方便自己日后開發過程中有用到時候便於借鑒和復習,再次是為了初學vue3和嘗試上手vite2和vue3搭建管理系統的小伙伴提供一些學習方法和技術點;
  • 本Vue后台管理系統使用的技術點主要有:vite2vue3vue-router4.xvuex4.x、vuex-persistedstate(vuex數據持久化)、Element Plus等。

🚗用戶登錄

在這里插入圖片描述

登錄頁面代碼

<template>
    <div class="login">
        <el-card class="login_center">
            <template #header>
                <div class="card_header">
                    <span>用戶登錄</span>
                </div>
            </template>
            <el-form :model="loginFormState" :rules="rules" ref="loginFormRef">
                <el-form-item prop="name">
                    <el-input
                        prefix-icon="el-icon-user-solid"
                        v-model.trim="loginFormState.name"
                        maxlength="32"
                        placeholder="請輸入賬號"
                        clearable
                    ></el-input>
                </el-form-item>
                <el-form-item prop="pwd">
                    <el-input
                        prefix-icon="el-icon-lock"
                        v-model.trim="loginFormState.pwd"
                        maxlength="16"
                        show-password
                        placeholder="請輸入密碼"
                        clearable
                        @keyup.enter.exact="handleLogin"
                    ></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" style="width: 100%" :loading="loginFormState.loading" @click="handleLogin">登 錄</el-button>
                </el-form-item>
            </el-form>
        </el-card>
    </div>
</template>

登錄邏輯代碼

import { getCurrentInstance, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import { encode } from "js-base64";

export default {
    setup() {
        const { proxy } = getCurrentInstance();
        const router = useRouter();
        const store = useStore();
        const loginFormRef = ref();

        const loginFormState = reactive({
            name: "",
            pwd: "",
            loading: false
        });

        const rules = {
            name: [{ required: true, message: "賬號不能為空", trigger: "blur" }],
            pwd: [
                { required: true, message: "密碼不能為空", trigger: "blur" },
                { min: 5, max: 16, message: "密碼長度為5-16位", trigger: "blur" }
            ]
        };

        const handleLogin = () => {
            loginFormRef.value.validate(valid => {
                if (!valid) {
                    return false;
                }

                loginFormState.loading = true;

                let params = { name: loginFormState.name, pwd: loginFormState.pwd };

                setTimeout(() => {
                    let users = { role: loginFormState.name === "admin" ? "admin" : "", username: loginFormState.name };
                    Object.assign(params, users);
                    sessionStorage.setItem("jwt", encode(JSON.stringify(params)));
                    store.dispatch("setUser", params);
                    loginFormState.loading = false;
                    router.replace("/");
                }, 1000);

                // proxy.$axios
                // 	.post("/user/login", proxy.$qs.stringify(params))
                // 	.then(res => {
                // 		let { code, result_data, message } = res.data;
                // 		if (code == 1) {
                // 			console.log("login_success", result_data);
                // 			ElMessage.success("登錄成功");
                // 		} else {
                // 			ElMessage.error("登錄失敗:" + message);
                // 		}
                // 	})
                // 	.catch(err => {
                // 		console.log("login err", err);
                // 		ElMessage.error("登錄失敗");
                // 	});
            });
        };

        return { loginFormRef, loginFormState, rules, handleLogin };
    }
};

登錄簡介:

  • 登錄頁面采用的是一級錄用,與控制台的路由同級,這樣寫便於對vue-router路由權限校驗的控制;
  • 在vue2中我們頻繁使用 this 來處理事件函數和組件數據,vue3大多事件函數和數據狀態的存儲基本都實在setup函數中完成的,在vue3中無法通過 this來獲取當前組件的實例,故無法像vue2中那樣操作數據和事件函數;
  • vue3中為了獲取到當前組件的實例,我們可以采用 vue3中提供的 getCurrentInstance來獲取組件的實例;
  • 當我們使用全局對象或者函數時,我們大多是將事件函數綁定在vue的原型實例上,當再次訪問時只需使用過this來訪問自己指定的事件名即可;
  • 在vue3中我們若是使用全局變量或者事件函數時,我們需要借助 globalProperties來實現全局事件函數的綁定;此時在需要使用的地方可以通過當前組件實例來訪問全局的 property屬性;
  • 對登錄用的的信息進行加密處理,我采用的是 js-base64 的 encode 方法來實現登錄信息的加密。使用方式為:encode(“需要加密的JSON字符串”)。

🚆系統主頁

在這里插入圖片描述

Layout布局代碼

<template>
    <el-header height="56px">
        <!-- header -->
        <div class="header_left">Element-Plus Create By Vite</div>
        <div class="header_right">
            <!-- 退出全屏、進入全屏按鈕 -->
            <el-tooltip :content="isFullScreen ? '退出全屏' : '全屏'">
                <i class="el-icon-full-screen" @click="handleFullScreen"></i>
            </el-tooltip>
            <!-- 下拉菜單 -->
            <el-dropdown size="medium" @command="handleCommand">
                <!-- 用戶信息 -->
                <div class="user_info">
                    <!-- 用戶頭像 -->
                    <el-avatar :size="36" :src="avatar" />
                    <!-- 用戶名寧 -->
                    <span class="username">{{ userName }}</span>
                </div>
                <template #dropdown>
                    <!-- 折疊菜單 -->
                    <el-dropdown-menu>
                        <el-dropdown-item icon="el-icon-user" command="user">個人中心</el-dropdown-item>
                        <el-dropdown-item icon="el-icon-switch-button" command="logout">退出登錄</el-dropdown-item>
                    </el-dropdown-menu>
                </template>
            </el-dropdown>
        </div>
    </el-header>
</template>

<!-- 二級路由公用路由頁面 -->
<template>
    <router-view v-slot="{ Component }">
        <transition name="fade" mode="out-in">
            <component :is="Component" />
        </transition>
    </router-view>
</template>

主頁Header相關邏輯

import { computed, getCurrentInstance, reactive, toRefs } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import screenfull from "screenfull";
import avatar from "@/assets/img/admin.png";

export default {
    setup() {
        const { proxy } = getCurrentInstance();
        const router = useRouter();
        const store = useStore();

        const state = reactive({
            isFullScreen: false,
            avatar,
            screenfull
        });
        const userName = computed(() => store.getters.getUserName);

        const handleCommand = command => {
            if (command === "user") {
                router.push("/user");
            } else {
                proxy.$message.success("退出成功");
                store.dispatch("clearUser");
                router.replace("/login");
                sessionStorage.clear();
                localStorage.clear();
            }
        };

        const handleFullScreen = () => {
            if (screenfull.isEnabled) {
                state.isFullScreen = !state.isFullScreen;
                screenfull.toggle();
            }
        };

        return {
            userName,
            handleCommand,
            handleFullScreen,
            ...toRefs(state)
        };
    }
};

注:

  • Header分左右兩部分,其中左側為系統的名字,右側為用戶登錄的賬戶相關的信息以及進入和退出全屏的按鈕;
  • 不同用戶權限會對應不同的賬戶頭像,會對不同賬戶的用戶權限做相應的限制處理;
  • 全屏的切換借助的是第三方的插件進行處理的,此方式減少代碼量的同時也減少了不同瀏覽器兼容性問題的出現;
  • 退出賬戶邏輯的處理,當用戶點擊退出賬戶的時候進行相應的退出登錄的彈窗提示,在退出后進行數據的初始化和本地存儲信息的清除處理,並跳轉到用戶登錄頁。
  • 主頁使用了地圖模塊,地圖模塊是借助的“高德地圖”API實現的H5版的網頁地圖,此Demo需要使用注冊高德地圖開發者來獲取開發的keys來創建地圖實例;
  • 本筆記主要就后台管理系統做筆記分析,高德地圖此處不做過多介紹,若想進一步了解,請前往高德開放平台進行了解學習。

📕數據管理

在這里插入圖片描述

<template>
    <el-card shadow="never" class="index">
        <template #header>
            <div class="card_header">
                <b>數據列表</b>
            </div>
        </template>
        <el-empty description="暫無數據"></el-empty>
    </el-card>
</template>

<script></script>

<style lang="scss" scoped>
.card_header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
</style>

注:沒有數據時的提示信息;

📷視頻播放器

在這里插入圖片描述

<template>
    <el-card shadow="never" class="index">
        <template #header>
            <div class="card_header">
                <b>🍉西瓜播放器</b>
            </div>
        </template>
        <div id="xg"></div>
    </el-card>
</template>

<script>
import { onMounted, onBeforeUnmount, getCurrentInstance, ref } from "vue";
import Player from "xgplayer";

export default {
    setup() {
        const { proxy } = getCurrentInstance();

        let player;
        onMounted(() => {
            initPlayer();
        });

        onBeforeUnmount(() => {
            player.destroy();
            player = null;
        });

        const initPlayer = () => {
            player = new Player({
                id: "xg",
                url: "https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4",
                poster: "https://img03.sogoucdn.com/app/a/07/f13b5c3830f02b6db698a2ae43ff6a67",
                fitVideoSize: "auto",
                fluid: true /* 流式布局 */,
                // download: true /* 視頻下載 */
                // pip: true /* 畫中畫 */,
                // errorTips: `請<span>刷新頁面</span>試試` /* 自定義錯誤提示 */,
                lang: "zh-cn"
            });
        };

        return {};
    }
};
</script>

<style lang="scss" scoped>
.card_header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
</style>

注:

  • 安裝西瓜視頻播放器:yarn add xgplayer
  • 🍉西瓜播放器官方文檔:http://v2.h5player.bytedance.com/
  • 西瓜播放器適合手機版和PC電腦版視頻點播或直播使用,詳細參數配置請參考官方文檔。

🖊富文本編輯器

在這里插入圖片描述

富文本編輯器插件封裝

<template>
    <div ref="editor" class="editor_ref"></div>
</template>

<script>
import { onMounted, onBeforeUnmount, watch, getCurrentInstance, ref } from "vue";
import WEditor from "wangeditor";

export default {
    props: {
        defaultText: { type: String, default: "" }
    },
    setup(props, context) {
        const { proxy } = getCurrentInstance();
        const editor = ref();

        let instance;
        onMounted(() => {
            initEditor();
        });

        onBeforeUnmount(() => {
            instance.destroy();
            instance = null;
        });

        watch(
            () => props.defaultText,
            nv => {
                instance.txt.html(nv);
                !!nv && context.emit("richHtml", nv);
            }
        );

        const initEditor = () => {
            instance = new WEditor(editor.value);
            // 配置富文本
            Object.assign(instance.config, {
                zIndex: 100,
                // placeholder: "" /* 提示文字 */,
                showFullScreen: true /* 顯示全屏按鈕 */,
                showLinkImg: true /* 顯示插入網絡圖片 */,
                showLinkVideo: true /* 顯示插入網絡視頻 */,
                onchangeTimeout: 400 /* 觸發 onchange 的時間頻率,默認 200ms */,
                uploadImgMaxLength: 1 /* 單次上傳圖片數量限制 */,
                uploadImgMaxSize: 5 * 1024 * 1024 /* 上傳圖片大小限制 */,
                uploadVideoAccept: ["mp4", "mov"] /* 上傳視頻格式限制 */,
                uploadVideoMaxSize: 1024 * 1024 * 1024 /* 上傳視頻大小限制1024m */,
                excludeMenus: ["strikeThrough", "todo", "code"] /* 移除系統菜單 */,
                customAlert(msg, type) {
                    type == "success" ? proxy.$message.success(msg) : proxy.$message.error(msg);
                },
                customUploadImg(resultFiles, insertImgFn) {
                    /**
                     * @param {Object} file - 文件對象
                     * @param {String} rootPath - 文件根路徑(默認為空、例:“filepath/”)
                     * @param {Array} fileType - 文件類型限制(默認 [] 不限制,例:['.png','.jpeg'])
                     * @param {Number} size - 文件大小限制(單位:兆、默認 0 不限制、例:1)
                     **/
                    proxy.$oss(resultFiles[0]).then(imgUrl => !!imgUrl && insertImgFn(imgUrl));
                },

                customUploadVideo(resultFiles, insertVideoFn) {
                    proxy.$oss(resultFiles[0]).then(videoUrl => !!videoUrl && insertVideoFn(videoUrl)); /* 參數同上 */
                },
                onchange(nv) {
                    context.emit("richHtml", nv);
                }
            });
            instance.create();
        };

        return { editor };
    }
};
</script>

<style scoped>
div.editor_ref :deep(iframe) {
    max-width: 100%;
    max-height: auto;
    width: 360px;
    height: 180px;
}
</style>

組件內使用

<template>
    <el-card shadow="never" class="index">
        <template #header>
            <div class="card_header">
                <b>富文本編輯器</b>
            </div>
        </template>
        <!-- 富文本 -->
        <WEditor :defaultText="defaultText" @richHtml="getRichHtml" />
    </el-card>
</template>

<script>
import { onMounted, ref } from "vue";
import WEditor from "../../components/WEditor.vue";

export default {
    components: { WEditor },
    setup() {
        const defaultText = ref("");
        const richText = ref("");

        onMounted(() => {
            // 初始化數據
            defaultText.value = "<h1>Editor</h1>";
        });

        const getRichHtml = nv => {
            richText.value = nv;
        };

        return { defaultText, getRichHtml };
    }
};
</script>

注:

  • 此次是基於Vue3封裝的富文本編輯器,編輯器使用的是開源的富文本編輯器wangeditor;
  • 代碼塊一是基於官方文檔和配置信息對富文本編輯器進行的相關配置,其中富文本編輯器使用的ali-OSS的雲存儲,若想詳細了解請參照之前的“阿里雲文件直傳”博客筆記進行了解和學習;
  • ref相當於DOM元素的Id,要保持唯一,若一個頁面要使用多個富文本編輯器,請做好區分,以便於區分組件的數據。

⚽個人中心

在這里插入圖片描述

🎉總結:

本篇文章主要介紹使用element-plus進行頁面的布局和數據展示處理,后續筆記將繼續分享和介紹動態路由菜單的處理,以及用戶權限的動態校驗。(。・・)ノ


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM