[Vue3+TS-Projekt] Silicon Valley-Auswahltag03 – Layout-Komponentenkonstruktion + Routing-Konfiguration + linkes Menü-Konstruktion

1. Routing-Konfiguration

Erfordert mindestens vier Routen der ersten Ebene: Startseite, Anmeldeseite, 404-Seite, beliebige Route (zeigt auf 404)

Installieren Sie das Routing-Plugin: Version 4

pnpm i vue-router

Erstellen Sie unter src eine neue Ordneransicht

Erstellen Sie jeweils 404-, Login- und Home-Routing-Komponenten

Erstellen Sie unter src einen neuen Router-Ordner – enthält index.ts undroutes.ts

src/router/routes.ts

// 对外暴露配置路由(常量路由)
export const constantRoute = [
    {
        name: 'login',// 命名路由--做权限会用到
        path: '/login',
        component: () => import('@/views/login/index.vue')
    },
    {
        // 登入成功后展示数据的路由
        name: 'home',
        path: '/',
        component: () => import('@/views/home/index.vue')
    },
    {
        // 404路由
        name: '404',
        path: '/404',
        component: () => import('@/views/404/index.vue')
    },
    {
        // 任意路由
        name: 'any',
        path: '/:pathMatch(.*)*',
        redirect: '/404'
    }
]

src/router/index.ts

// 通过 vue-router 插件实现模板路由配置
import { createRouter, createWebHashHistory } from "vue-router";
import { constantRoute } from "./routes";
// 创建路由器
let router = createRouter({
    // 路由模式hash
    history: createWebHashHistory(),
    routes: constantRoute,
    // 滚动行为
    scrollBehavior() {
        return {
            left: 0,
            top: 0
        }
    }
})
export default router

Geben Sie Routen in die Eintragsdatei ein und registrieren Sie src/main.ts

// 引入路由
import router from '@/router'
// 注册模板路由
app.use(router)

Das Routing der ersten Ebene kann in der App-Komponente angezeigt werden.

<router-view></router-view>

2. Aufbau der Login-Schnittstelle

Erstellen Sie mit Elementlayout

Rasterlayout: span ist der Bruch, xs ist der Bruch, wenn der Bildschirm kleiner als 760 ist und das Raster insgesamt 24 beträgt

Verwenden Sie die Komponenten des Symboldiagramms: Benutzer, Sperre (Präfixsymbol: Präfixsymbol, Suffixsymbol, Symbol zum Wechseln des Kennworts, Anzeigen des Kennworts)

Melden Sie sich bei der statischen Vorlagenkomponente src/views/login/index.vue an

<template>
    <div class="login_container">
        <el-row>
            <el-col :span="12" :xs="0">左侧占位的</el-col>
            <el-col :span="12" :xs="24">
                <el-form class="login_form">
                    <h1>Hello!</h1>
                    <h2>Welcome to 鱼仔甄选</h2>
                    <el-form-item>
                        <el-input :prefix-icon="User" v-model="loginForm.username"></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-input type="password" :prefix-icon="Lock" v-model="loginForm.password" show-password></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-button :loading="loading" type="primary" class="login_btn">登入</el-button>
                    </el-form-item>
                </el-form>
            </el-col>
        </el-row>
    </div>
</template>
  
<script setup lang="ts">
// 输入框前置的图标
import { User, Lock } from '@element-plus/icons-vue'
import { reactive } from 'vue';
//收集账号密码
let loginForm = reactive({ username: 'admin', password: '111111' })
</script>
  
<style scoped lang="scss">
.login_container {
    width: 100%;
    height: 100vh;
    background: url('@/assets/images/background.jpg') no-repeat;
    background-size: cover;
    .login_form {
        position: relative;
        width: 80%;
        top: 30vh;
        background: url('@/assets/images/login_form.png') no-repeat;
        background-size: cover;
        padding: 40px;
        h1 {
            color: white;
            font-size: 40px;
        }
        h2 {
            color: white;
            font-size: 20px;
            margin: 20px 0px;
        }
        .login_btn {
            width: 100%;
        }
    }
}
</style>
 

Gemäß dem Konto und dem Passwort werden nach dem Klicken zum Anmelden die Informationen zur Identitätsüberprüfung, z. B. ein Token, zurückgegeben, sodass wir Pinia zum Speichern verwenden

Was muss in der Rückruffunktion nach dem Klicken auf „Anmelden“ getan werden?

  • Benachrichtigen Sie das Lager, um eine Anmeldeanfrage zu senden

  • Erfolgreiche Anfrage: Daten auf der Homepage anzeigen

  • Anfrage fehlgeschlagen: Es erscheint eine Fehlermeldung

Installieren Sie zunächst das Pinia-Repository

pnpm i [email protected]

Erstellen Sie eine neue große Warehouse-Datei src/store/index.ts

import { createPinia } from 'pinia'
// 创建大仓库
let pinia = createPinia()
// 对外暴露:入口文件需要安装大仓库
export default pinia

Installieren Sie das große Warehouse src/main.ts in der Eintragsdatei

import pinia from '@/store'
app.use(pinia)

ein Fehler

Das Laden der Ressource ist fehlgeschlagen: Der Server hat mit dem Status 504 (Veraltete Optimierungsabteilung) geantwortet.

Die Pinia-Version sollte nicht zu hoch sein (es wird empfohlen, 2.0.34 zu installieren, derzeit 2.1.3 [2023.05.20])

Erstellen Sie ein kleines Lager src/store/modules/user.ts

// 用户相关的小仓库
import { defineStore } from 'pinia'
// 引入登入请求接口
import { reqLogin } from '@/api/user/index'
// 引入数据类型
import type { loginForm, loginResponseData } from '@/api/user/type'
import type { UserState } from '@/store/modules/types/type'
// 引入操作本地存储的工具方法
import { SET_TOKEN, GET_TOKEN } from '@/utils/token'
// 创建用户小仓库
let useUserStore = defineStore('User', {
    // 小仓库存储数据的地方
    state: (): UserState => {
        return {
            token: GET_TOKEN(),//用户的唯一标识token
        }
    },
    // 异步、逻辑
    actions: {
        // 用户登入的方法
        async userLogin(data: loginForm) {
            let res: loginResponseData = await reqLogin(data)
            //登入请求:成功200----token
            //登入请求:失败201--登入失败错误的信息
            if (res.code === 200) {
                // pinia仓库存储token
                // pinia|vuex存储数据都是利用js对象,并非持久化
                this.token = (res.data.token as string)
                // 本地持久化存储一份
                SET_TOKEN((res.data.token as string))
                // 能保证当前async函数返回一个成功的promise
                return 'ok'
            } else {
                return Promise.reject(new Error(res.data.message))
            }
        }
    },
    getters: {
    }
})
export default useUserStore

Das kleine Lager wird auf der Anmeldeseite vorgestellt. Wenn der Benutzer zum Anmelden klickt, wird er benachrichtigt, dass das kleine Lager eine Anfrage sendet und Daten speichert

project\src\views\login\index.vue

Die userLogin-Funktion gibt ein versprochenes Ergebnis zurück und fährt entsprechend dem Ergebnis mit dem nächsten Schritt fort (versuchen Sie es oder als können Sie schreiben).

// 引入用户相关的小仓库
import useUserStore from '@/store/modules/user'
let useStore = useUserStore()
import { useRouter } from 'vue-router';
// element提示框
import { ElNotification } from 'element-plus';
// 获取路由器
let $router = useRouter()
// 登入按钮加载效果,element的小动画
let loading = ref(false)
// 点击登入的回调
const login = async () => {
    loading.value = true
    // 通知仓库发送登入请求---收集的账户密码带给服务器
    // 请求成功:首页展示数据
    // 请求失败:弹出错误信息
    try {
        // 保证登入成功,userLogin这个函数会返回一个promise结果,根据结果来进行下一步
        await useStore.userLogin(loginForm)
        // 编程式路由导航跳转到展示数据的首页
        $router.push('/')
        // 登入成功的提示消息
        ElNotification({
            type: 'success',
            message: '登入成功'
        })
        loading.value = false
    } catch (error) {
        loading.value = false
        // 登入失败的提示消息
        ElNotification({
            type: 'error',
            message: (error as Error).message
        })
    }
}
loading写在finally中也可以
finally {
    loading.value = false
}

ts-Typdefinition der Benutzer-Warehouse-Daten

project\src\store\modules\types\type.ts

// 定义小仓库数据state类型
export interface UserState {
    token: null | string
}

Der von der Anmeldeschnittstelle zurückgegebene Datentyp

project\src\api\user\type.ts

interface dataType {
    token?: string,
    message?: string
}
//定义登录接口返回数据类型
export interface loginResponseData {
    code: number,
    data: dataType
}

Kapseln Sie lokale Speichermethoden

project\src\utils\token.ts

// 封装本地存储 存储数据与读取数据的方法
export const SET_TOKEN = (token: string) => {
    localStorage.setItem('TOKEN', token)
}
export const GET_TOKEN = () => {
    return localStorage.getItem('TOKEN')
}

Beurteilung und Verpackung der Anmeldezeit

project\src\utils\time.ts

// 获取时间的函数:早上|上午|下午|晚上
export const getTime = () => {
    let time = '';
    let hour = new Date().getHours()
    if (hour <= 9) {
        time = '早上好'
    } else if (hour <= 12) {
        time = '上午好'
    } else if (hour <= 14) {
        time = '中午好'
    } else if (hour <= 18) {
        time = '下午好'
    } else {
        time = '晚上好'
    }
    return time
}

Verwendete Login-Komponente

import { getTime } from '@/utils/time'
ElNotification({
    title: `Hi!${getTime()}!`,
    type: 'success',
    message: '登入成功'
})

Validierung des Anmeldeformulars

Regeln: Der Anmeldename ist größer oder gleich 5 Zeichen und das Passwort ist größer oder gleich 6 Zeichen

Verwenden Sie die Validierungsfunktion der Elementformularkomponente

Schritt:

Verwenden Sie das Modell, um Formulardaten für das Proxy-Objekt zu sammeln: :model="ruleForm"

Fügen Sie dem Formular das Rules-Attribut hinzu und erstellen Sie Validierungsregeln (auch Objekte): :rules="rules"

Fügen Sie dem Formularelement, das überprüft werden muss, ein Prop-Attribut hinzu: props="username"

1
<el-form class="login_form" :model="loginForm" :rules="rules">
2
<el-form-item prop="username">
<el-form-item prop="password">
3
// 定义表单验证规则rules
// required是否需要验证、trigger是触发时机:blur/change
const rules = {
    username: [
        { required: true, min: 5, max: 16, message: '用户名长度应为5-16位', trigger: 'change' },
    ],
    password: [
        { required: true, min: 6, max: 16, message: '密码长度应为6-16位', trigger: 'change' },
    ]
}

Nur durch das Formular verifizierte Kontokennwörter dürfen Anfragen senden, daher erhalten wir Formularelemente über ref, und es gibt ein Attribut in der Formularkomponente: validieren (den Inhalt des gesamten Formulars validieren. Eine Rückruffunktion empfangen oder zurückgeben), Wir verwenden das zurückgegebene PromiseVersprechensergebnis für den nächsten Schritt

//获取表单元素
let loginForms = ref()
const login = async () => {
    //保证所有表单项通过才发请求
    await loginForms.value.validate()
    ......
}

benutzerdefiniertes Validierungsformular

Die obige Überprüfung ist zu einfach. Wenn wir komplexere Regeln wünschen, müssen wir das Überprüfungsformular anpassen (wird auch vom Element bereitgestellt).

In der benutzerdefinierten Regel ist ein Validatorattribut erforderlich, und der Wert ist eine Methode

Beurteilungsregeln können tatsächlich als reguläre Regeln geschrieben werden. Hier erfahren Sie nur mehr über die benutzerdefinierte Überprüfung von Elementen

project\src\views\login\index.vue

// 自定义校验规则函数
const validateUsername = (rule: any, value: any, callback: any) => {
    //rule:即为校验规则对象
    //value:即为表单元素文本内容
    //函数:如果符合条件callBack放行通过即为
    //如果不符合条件callBack方法,注入错误提示信息
    if (value.length >= 5) {
        callback();
    } else {
        callback(new Error('账号长度至少五位'));
    }
}
const validatePassword = (rule: any, value: any, callback: any) => {
    // 判断条件可以写正则
    if (value.length >= 6) {
        callback();
    } else {
        callback(new Error('密码长度至少六位'));
    }
}
const rules = {
    username: [
        { trigger: 'change', validator: validateUsername },
    ],
    password: [
        { trigger: 'change', validator: validatePassword },
    ]
}

Drei, Layoutkomponentenkonstruktion

Statische Konstruktion (Elementverwendung ist einfacher)

Die Homepage ist in drei Teile unterteilt: das linke Anzeigemenü, oben rechts die obere Navigation und unten die Inhaltsanzeige (sekundäre Routenanzeige).

project\src\layout\index.vue

<template>
    <div class="layout_container">
        <!-- 左侧菜单展示 -->
        <div class="layout_slider">
            111
        </div>
        <!-- 顶部导航 -->
        <div class="layout_tabbar">
            222
        </div>
        <!-- 内容展示区域 -->
        <div class="layout_main">
            <p style="height: 1000px">111</p>
        </div>
    </div>
</template>
  
<script setup lang="ts">
</script>
  
<style scoped lang="scss">
.layout_container {
    width: 100%;
    height: 100vh;
    background: blue;
    .layout_slider {
        width: $base-menu-width;
        height: 100vh;
        background: $base-menu-background;
    }
    .layout_tabbar {
        position: fixed;
        top: 0;
        right: 0;
        width: calc(100% - $base-menu-width);
        height: $base-tabbar-height;
        background: $base-tabbar-background;
    }
    .layout_main {
        position: absolute;
        right: 0;
        top: $base-tabbar-height;
        width: calc(100% - $base-menu-width);
        height: calc(100vh - $base-tabbar-height);
        background: $base-main-background;
        padding: 20px;
        overflow: auto;
    }
}
</style>
  

Stilvariable

project\src\style\variable.scss

// layout组件
// 左侧菜单的宽度
$base-menu-width: 260px;
// 左侧菜单的颜色
$base-menu-background: #846e89;
// 顶部导航的高度
$base-tabbar-height: 100px;
// 顶部导航的颜色
$base-tabbar-background: #e0c7e3;
// 内容展示区域颜色
$base-main-background: #c6d182;

Stil der Bildlaufleiste

project\src\style\index.scss

// 滚动条外观
::-webkit-scrollbar {
    width: 10px;
}
::-webkit-scrollbar-track {
    background: $base-menu-background;
}
::-webkit-scrollbar-thumb {
    width: 10px;
    background-color: yellowgreen;
    border-radius: 10px;
}

Logo-Komponentenverpackung

Legen Sie die Konfigurationsdatei für das Bild und den Titel fest (einfache Eingabe und Änderung).

project\src\settings.ts

//用于项目logo|标题配置
export default {
    title: '鱼仔甄选', //项目的标题
    logo: '/logo.png', //项目logo设置
    logoHidden: true, //logo组件是否隐藏设置
}

Logo-Komponente

project\src\layout\logo\index.vue

<template>
    <div class="logo"  v-if="setting.logoHidden">
        <img :src="setting.logo" alt="">
        <p>{
   
   { setting.title }}</p>
    </div>
</template>
  
<script setup lang="ts">
// 引入设置标题与图片的配置文件
import setting from '@/settings'
</script>
  
<style scoped lang="scss">
.logo {
    display: flex;
    width: 100%;
    height: 50px;
    align-items: center;
    color: wheat;
    padding: 20px;
    img {
        width: 60px;
        height: 40px;
    }
    p {
        font-size: 20px;
        margin-left: 30px;
    }
}
</style>
  

Aufbau des linken Menüs

Erstellen Sie eine neue Menükomponente, führen Sie sie ein und verwenden Sie sie im Layout<Menu />

project\src\layout\menu\index.vue

Fügen Sie eine sekundäre Routing-Konfiguration hinzu

project\src\router\routes.ts

    {
        // 登入成功后展示数据的路由
        name: 'layout',
        path: '/',
        component: () => import('@/layout/index.vue'),
        children: [
            {
                path: '/home',
                component: () => import('@/views/home/index.vue')
            }
        ]
    },

Platzieren Sie das Routing-Array im Warehouse, damit andere Komponenten es später verwenden können (Traversal oder so).

project\src\store\modules\user.ts

// 引入路由常量
import { constantRoute } from '@/router/routes'
menuRoutes: constantRoute,// 仓库存储生成菜单需要的数组(路由配置数组)

menuRoutes-Typdefinition

project\src\store\modules\types\type.ts

import type { RouteRecordRaw } from "vue-router"
// 定义小仓库数据state类型
export interface UserState {
    token: null | string,
    menuRoutes: RouteRecordRaw[]
}

Andere Komponenten können das Routing-Konfigurationsarray verwenden

Die Layout-Komponente kann verwendet werden (an die Menü-Komponente übergeben)

project\src\layout\index.vue

// 获取用户相关的小仓库
import useUserStore from '@/store/modules/user';
let userStore = useUserStore()
// 给Menu组件传过去,Menu组件就可以根据路由动态生成左侧菜单(其实也可以Menu组件自己从小仓库中取)
 <Menu :menuList="userStore.menuRoutes" />

Dem Routing werden Routing-Metainformationen hinzugefügt

        meta: {
            title: '登入',// 左侧菜单展示的名字
            hidden: true,// 是否在左侧菜单中显示
        }

Menükomponente: Verwenden Sie menuList, um ein dynamisches Menü zu generieren, nur eine zweite Ebene muss nicht gefaltet werden

Achten Sie auf die rekursive Komponente, achten Sie auf die Beurteilungsbedingung, achten Sie auf den zu schreibenden Index und achten Sie darauf, dass das Skript zweimal geschrieben werden kann, aber die ls müssen gleich sein (die rekursive Komponente benötigt einen Namen).

Klicken Sie auf das Menü, um das Routing-Sprungelement aufzurufen. Es bietet zwei Methoden: Menüelementattribute oder Ereignisse. Achten Sie auf die Routing-Umleitung ---- @click="goRoute"

<template>
    <div>
        <template v-for="item in menuList" :key="item.path">
            <!-- 没有子路由 -->
            <!-- 有些路由不需要展示,比如login,再套一层判断 hidden-->
            <template v-if="!item.children">
                <el-menu-item v-if="!item.meta.hidden" :index="item.path" @click="goRoute">
                    <template #title>
                        <el-icon>
                            <component :is="item.meta.icon"></component>
                        </el-icon>
                        <span>{
   
   { item.meta.title }}</span>
                    </template>
                </el-menu-item>
            </template>
            <!-- 只有一个子路由 -->
            <template v-if="item.children && item.children.length == 1">
                <el-menu-item v-if="!item.children[0].meta.hidden" :index="item.children[0].path" @click="goRoute">
                    <template #title>
                        <el-icon>
                            <component :is="item.children[0].meta.icon"></component>
                        </el-icon>
                        <span>{
   
   { item.children[0].meta.title }}</span>
                    </template>
                </el-menu-item>
            </template>
            <!-- 两个以上的子路由 -->
            <el-sub-menu v-if="item.children && item.children.length > 1" :index="item.path">
                <template #title>
                    <el-icon>
                        <component :is="item.meta.icon"></component>
                    </el-icon>
                    <span>{
   
   { item.meta.title }}</span>
                </template>
                <Menu :menu-list="item.children" />
            </el-sub-menu>
        </template>
    </div>
</template>
  
<script setup lang="ts">
import { useRouter } from 'vue-router';
defineProps(['menuList'])
// 获取路由对象
let $router = useRouter()
// 点击菜单进行路由跳转
const goRoute = (vc) => {
    $router.push(vc.index)
}
</script>
<script lang="ts">
export default {
    name: 'Menu'
}
</script>
  
<style scoped lang="scss"></style>
  

Menüsymbol (mit elememnt): Dynamisch anzeigen, das Symbol als globale Komponente registrieren und dann in die Routing-Metainformationen einfügen

project\src\components\index.ts

// 引入element全部图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 全局组件的对象
const allGlobalComponents = { SvgIcon: SvgIcon };
// 对外暴露一个插件对象
export default {
    install(app) {
        // 将图表组件全部注册为全局组件
        for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
            app.component(key, component)
        }
    }
}

Fügen Sie Routing-Komponenten hinzu und vervollständigen Sie das Routing

Routing der ersten Ebene: großer Datenbildschirm, Autoritätsverwaltung (sekundäre Komponenten: Benutzer, Rolle, Menüverwaltung), Warenverwaltung (sekundäres Routing: SKU, SPU, Marke, Attribute)

Das Routing von Berechtigungen und Produkten auf der ersten Ebene verwendet weiterhin das Komponentenlayout

Die Startseite leitet direkt zur Startseite weiter

project\src\router\routes.ts

{
        name: 'screen',
        path: '/screen',
        component: () => import('@/views/screen/index.vue'),
        meta: {
            hidden: false,
            title: '数据大屏',
            icon: 'Platform',
        },
    },
    {
        path: '/acl',
        component: () => import('@/layout/index.vue'),
        name: 'acl',
        meta: {
            title: '权限管理',
            icon: 'Lock',
        },
        redirect: '/acl/user',
        children: [
            {
                path: '/acl/user',
                component: () => import('@/views/acl/user/index.vue'),
                name: 'user',
                meta: {
                    title: '用户管理',
                    icon: 'User',
                },
            },
            {
                path: '/acl/role',
                component: () => import('@/views/acl/role/index.vue'),
                name: 'role',
                meta: {
                    title: '角色管理',
                    icon: 'UserFilled',
                },
            },
            {
                path: '/acl/permission',
                component: () => import('@/views/acl/permission/index.vue'),
                name: 'permission',
                meta: {
                    title: '菜单管理',
                    icon: 'Monitor',
                },
            },
        ],
    },
    {
        path: '/product',
        component: () => import('@/layout/index.vue'),
        name: 'product',
        meta: {
            title: '商品管理',
            icon: 'Goods',
        },
        redirect: '/product/trademark',
        children: [
            {
                path: '/product/trademark',
                component: () => import('@/views/product/trademark/index.vue'),
                name: 'trademark',
                meta: {
                    title: '品牌管理',
                    icon: 'ShoppingCartFull',
                },
            },
            {
                path: '/product/attr',
                component: () => import('@/views/product/attr/index.vue'),
                name: 'attr',
                meta: {
                    title: '属性管理',
                    icon: 'ChromeFilled',
                },
            },
            {
                path: '/product/spu',
                component: () => import('@/views/product/spu/index.vue'),
                name: 'spu',
                meta: {
                    title: 'SPU管理',
                    icon: 'Calendar',
                },
            },
            {
                path: '/product/sku',
                component: () => import('@/views/product/sku/index.vue'),
                name: 'sku',
                meta: {
                    title: 'SKU管理',
                    icon: 'Orange',
                },
            },
        ],
    },

Der Anzeigebereich auf der rechten Seite des Layouts ist in eine Hauptkomponente gekapselt, und ich möchte eine kleine Überanimation durchführen

project\src\layout\main\index.vue

<template>
    <!-- 路由组件出口的位置 -->
    <router-view v-slot="{ Component }">
        <transition name="fade">
            <!-- 渲染layout一级路由组件的子路由 -->
            <component :is="Component" />
        </transition>
    </router-view>
</template>
<script setup lang="ts">
</script>
<script lang="ts">
export default {
    name: "Main"
}
</script>
<style scoped>
.fade-enter-from {
    opacity: 0;
    transform: scale(0);
}
.fade-enter-active {
    transition: all .3s;
}
.fade-enter-to {
    opacity: 1;
    transform: scale(1);
}
</style>

Die Layout-Komponente stellt Haupt- und Displays vor

project\src\layout\index.vue

// 右侧内容展示组件
import Main from '@/layout/main/index.vue'
<!-- 内容展示区域 -->
<div class="layout_main">
    <Main />
</div>

Guess you like

Origin blog.csdn.net/m0_55644132/article/details/130962719