[Проект Vue3+TS] Выбор Силиконовой долины day03 — построение компонентов макета + конфигурация маршрутизации + построение левого меню

1. Конфигурация маршрутизации

Требуется как минимум четыре маршрута первого уровня: домашняя страница, страница входа, страница 404, любой маршрут (указывающий на 404)

Установите плагин маршрутизации: версия 4

pnpm i vue-router

Создайте новые представления папки в src

Создайте компоненты 404, входа в систему, домашней маршрутизации соответственно.

Создайте новую папку маршрутизатора в src, которая содержит index.ts и route.ts.

источник/маршрутизатор/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'
    }
]

источник/маршрутизатор/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

Введите маршруты в файл ввода и пропишите src/main.ts

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

Маршрутизацию первого уровня можно отобразить в компоненте приложения.

<роутер-просмотр></роутер-просмотр>

2. Построение интерфейса входа

Построить с макетом элемента

Разметка сетки: span — это дробь, xs — дробь, когда размер экрана меньше 760, а всего сетка — 24.

Использовать компоненты диаграммы значков: Пользователь, Блокировка (значок префикса: значок префикса, значок суффикса, значок переключения пароля показать-пароль)

Компонент статического шаблона входа src/views/login/index.vue

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

В соответствии с учетной записью и паролем, после нажатия для входа будет возвращена информация для проверки личности, такая как токен, поэтому мы используем pinia для хранения

Что нужно сделать в функции обратного вызова после нажатия входа

  • Уведомить склад об отправке запроса на вход

  • Успешный запрос: отобразить данные на главной странице

  • Запрос не выполнен: появляется сообщение об ошибке

Сначала установите репозиторий pinia

pnpm i [email protected]

Создайте новый большой файл хранилища src/store/index.ts.

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

Установите большой склад src/main.ts в файл записи

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

ошибка

Не удалось загрузить ресурс: сервер ответил со статусом 504 (устаревшая оптимизация)

Версия pinia не должна быть слишком высокой (рекомендуется установить 2.0.34, на данный момент 2.1.3 [2023.05.20])

Создайте небольшой склад 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

Небольшой склад представлен на странице входа в систему, и когда пользователь нажимает, чтобы войти, пользователь получает уведомление о том, что небольшой склад отправляет запрос и сохраняет данные.

проект\SRC\представления\логин\index.vue

Функция userLogin вернет результат обещания и перейдет к следующему шагу в соответствии с результатом (попробуйте или чем можно написать)

// 引入用户相关的小仓库
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 данных пользовательского хранилища

проект\src\store\modules\types\type.ts

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

Тип данных, возвращаемый интерфейсом входа

проект\src\api\user\type.ts

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

Инкапсулировать локальные методы хранения

проект\src\utils\token.ts

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

Оценка и упаковка времени входа в систему

проект\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
}

Используемый компонент входа

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

Проверка формы входа

Правила: Логин больше или равен 5 символам, а пароль больше или равен 6 символам

Используйте функцию проверки компонента формы элемента

шаг:

Используйте модель для сбора данных формы в прокси-объект: :model="ruleForm"

Добавьте в форму атрибут rules и создайте правила проверки (также объекты): :rules="rules"

Добавьте атрибут prop к элементу формы, который необходимо проверить: 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' },
    ]
}

Только пароли учетных записей, проверенные формой, разрешены для отправки запросов, поэтому мы получаем элементы формы через ref, а на компоненте формы есть атрибут: validate (проверить содержимое всей формы. Получить функцию обратного вызова или возврат), мы используем возвращенный Promiseрезультат обещания для следующего шага

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

пользовательская форма проверки

Вышеупомянутая проверка слишком проста, если мы хотим более сложные правила, нам нужно настроить форму проверки (также предоставленную элементом)

В пользовательском правиле требуется атрибут валидатора, а значением является метод.

Правила суждения на самом деле могут быть записаны как обычные правила, здесь просто нужно узнать о пользовательской проверке элемента.

проект\SRC\представления\логин\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 },
    ]
}

Три, построение компонента макета

Статическая конструкция (использование элемента проще)

Домашняя страница разделена на три части: левое меню отображения, верхнее правое — верхняя навигация, а нижняя — отображение содержимого (дополнительное отображение маршрута).

проект\источник\макет\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>
  

переменная стиля

проект\источник\стиль\переменная.scss

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

стиль полосы прокрутки

проект\SRC\стиль\index.scss

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

Упаковка компонентов логотипа

Установите файл конфигурации изображения и заголовка (легко вводить и изменять)

проект\src\settings.ts

//用于项目logo|标题配置
export default {
    title: '鱼仔甄选', //项目的标题
    logo: '/logo.png', //项目logo设置
    logoHidden: true, //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>
  

Построение левого меню

Создайте новый компонент меню, введите и используйте его в макете.<Menu />

проект\источник\макет\меню\index.vue

Добавить вторичную конфигурацию маршрутизации

проект\SRC\маршрутизатор\routes.ts

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

Поместите массив маршрутизации в хранилище, чтобы другие компоненты могли использовать его позже (обход или что-то в этом роде)

проект\src\store\modules\user.ts

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

определение типа menuRoutes

проект\src\store\modules\types\type.ts

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

Другие компоненты могут использовать массив конфигурации маршрутизации.

Можно использовать компонент макета (передайте его компоненту меню)

проект\источник\макет\index.vue

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

Метаинформация маршрутизации добавляется в маршрутизацию.

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

компонент меню: используйте menuList для создания динамического меню, не нужно сворачивать только один второй уровень

Обратите внимание на рекурсивный компонент, обратите внимание на условие оценки, обратите внимание на записываемый индекс и обратите внимание на то, что скрипт можно написать дважды, но ls должен быть одинаковым (рекурсивному компоненту нужно имя)

Щелчок по меню для элемента перехода маршрутизации предоставляет два метода, атрибуты пункта меню или события, обратите внимание на перенаправление маршрутизации ---- @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>
  

Значок меню (с помощью elememnt): динамически отображать, регистрировать значок как глобальный компонент, а затем помещать его в метаинформацию о маршрутизации.

проект\источник\компоненты\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)
        }
    }
}

Добавьте компоненты маршрутизации, завершите маршрутизацию

Маршрутизация первого уровня: большой экран данных, управление полномочиями (вторичные компоненты: пользователь, роль, управление меню), управление товарами (вторичная маршрутизация: артикул, spu, бренд, атрибуты)

Маршрутизация разрешений и продуктов первого уровня по-прежнему использует компоновку компонентов.

Домашняя страница перенаправляет прямо на главную

проект\SRC\маршрутизатор\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',
                },
            },
        ],
    },

Область отображения в правой части макета инкапсулирована в основной компонент, и я хочу сделать небольшую анимацию.

проект\источник\макет\основной\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>

Компонент макета вводит основные и отображает

проект\источник\макет\index.vue

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

Supongo que te gusta

Origin blog.csdn.net/m0_55644132/article/details/130962719
Recomendado
Clasificación