Vue3 + TS + Antd + Pinia 从零搭建后台系统(四) ant-design-vue Layout布局,导航栏,标签页

news2024/11/28 4:28:31
书接上回

本篇主要介绍: Layout布局,导航栏,标签页

继续填充

目录

  • 按需引入组件
  • Layout布局,导航栏,标签页
  • css样式

按需引入组件

使用unplugin-vue-components插件完成ant-design-vue组件的按需加载。
前文中已处理过,详情见前文
链接: Vue3 + TS + Antd + Pinia 从零搭建后台系统(一)
此处还需在tsconfig.json同级添加文件 components.d.ts。
tsconfig.json文件配置如下:

{
  "compilerOptions": {
    "target": "es2019",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "allowJs": true,
    "importHelpers": true,
    "moduleResolution": "node",
    "outDir": "temp",
    "resolveJsonModule": true,
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "paths": {
      "@/*": ["src/*"]
    },
    "types": [
      "@intlify/unplugin-vue-i18n/types",
      "vite/client",
      "element-plus/global",
      "@types/qrcode",
      "vite-plugin-svg-icons/client",
      "./components.d.ts"
    ],
    "baseUrl": "./",
    "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
  },
  "include": [
    "src/types/**/*.ts",
    "src/types/**/*.tsx",
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "src/components/BpmnEditor/lib/ReadOnly/ReadOnly.js",
    "src/plugin/index.js"
  ],
  "exclude": ["dist", "node_modules"]
}

Layout布局,导航栏,标签页

这里统一写在layout文件夹下面
此处用到的图标为iconfont图标库中的图标,可替换为ant-design-vue中的图标

index.vue文件:


  <Layout style="height: 100%; width: 100%">
    <!-- 左侧菜单栏 -->
    <Layout-sider
      :class="collapsed ? 'side-logo' : 'side-title'"
      v-model:collapsed="collapsed"
      :trigger="null"
      collapsible
    >
      <div style="height: 50px">
        <img class="logo-style" src="../../assets/images/logo.png" />
        <div v-if="!collapsed" class="title-style">管理平台</div>
      </div>
      <Menu
        v-model:selectedKeys="selectedKeys"
        v-model:openKeys="openKeys"
        theme="dark"
        mode="inline"
      >
        <Sub-menu v-for="sub in menuList" :key="sub.path">
          <template #title>
            <span :class="sub.meta.icon" style="margin-right: 8px"></span>
            <span>{{ !collapsed ? sub.meta.title : "" }}</span>
          </template>
          <Menu-item v-for="item in sub.children" :key="item.path">
            <span :class="item.meta.icon"></span>
            <Router-link :to="`${sub.path}/${item.path}`">{{ item.meta.title }}</Router-link>
          </Menu-item>
        </Sub-menu>
      </Menu>
    </Layout-sider>
    <!-- 右侧面包屑、标签页、界面 -->
    <Layout>
      <Layout-header style="height: 75px">
        <div class="layHeader">
          <div
            :class="`trigger icon-new ${!collapsed ? 'icon-new-outdent' : 'icon-new-indent'}`"
            @click="() => (collapsed = !collapsed)"
          />
          <!-- separator=">" 设置层级间展示的符号 默认‘/’-->
          <Breadcrumb>
            <Breadcrumb-item v-for="item in breadcrumbList" :key="item.path">
              <span>{{ item.meta.title }}</span>
            </Breadcrumb-item>
          </Breadcrumb>
          <Dropdown>
            <Button class="icon-new icon-new-user loginOut" type="primary" ghost></Button>
            <template #overlay>
              <Menu>
                <Menu-item>{{ `用户名:${username}` }}</Menu-item>
                <Menu-item @click="loginOut">退出系统</Menu-item>
              </Menu>
            </template></Dropdown
          >
        </div>
        <div style="display: flex">
          <span class="icon-new icon-new-doubleleft spanIcon"></span>
          <Tabs
            v-model:activeKey="activeKey"
            hide-add
            type="editable-card"
            @edit="onEdit"
            @tabClick="onTabClick"
          >
            <Tab-pane v-for="item in tabPanes" :key="item.path" :tab="item.meta.title"> </Tab-pane>
          </Tabs>
          <Dropdown :trigger="['click']">
          	<div class="icon-new icon-new-doubleright spanIcon" ></div>
            <template #overlay>
              <Menu>
                <Menu-item v-for="item in tabPanes" :key="item.path">
                	<Router-link :to="item.path">{{ item.meta.title }}</Router-link>
                </Menu-item>
              </Menu>
            </template>
           </Dropdown>
          <div
            class="icon-new icon-new-close-circle spanIcon"
            title="关闭其他"
            @click="closeOthers"
          ></div>
          <div class="icon-new icon-new-sync spanIcon" title="刷新" @click="reLoad"></div>
        </div>
      </Layout-header>
      <Layout-content class="content-style">
        <Router-view />
      </Layout-content>
    </Layout>
  </Layout>

index.ts文件:

import { defineComponent, reactive, toRefs, watch } from "vue";
import router from "@/router";
import { useUserStore } from "@/pinia/user";

export default defineComponent({
  name: "Layout",
  setup() {
    const datas = reactive({
      selectedKeys: ["BBB"],
      openKeys: ["/AAA"],
      collapsed: false,
      menuList: [] as any,
      breadcrumbList: [],
      activeKey: "",
      tabPanes: [] as any,
      username: null as any,
    });
    const methods = reactive({
      init() {
        datas.username = JSON.parse(localStorage.getItem("user") as any).userInfo?.username;
        datas.menuList = router.options.routes.filter((v: any) => !v.meta.hideInMenu);
        datas.selectedKeys = [router.currentRoute.value.name];
        datas.openKeys = [router.currentRoute.value.matched[0].path];
        datas.breadcrumbList = router.currentRoute.value.matched;
      },
      onEdit(val: any) {
        let lastIndex = 0;
        if (datas.tabPanes.length > 1) {
          datas.tabPanes.forEach((item: any, i: number) => {
            if (item.path == val) {
              lastIndex = i - 1;
            }
          });
          datas.tabPanes = datas.tabPanes.filter((v: { path: any }) => v.path !== val);
          if (datas.tabPanes.length && datas.activeKey == val) {
            if (lastIndex >= 0) {
              datas.activeKey = datas.tabPanes[lastIndex].path;
              datas.selectedKeys = [datas.tabPanes[lastIndex].name];
              router.push(datas.tabPanes[lastIndex].path);
            } else {
              datas.activeKey = datas.tabPanes[0].path;
              datas.selectedKeys = [datas.tabPanes[0].name];
            }
          }
        }
      },
      onTabClick(val: any) {
        router.push(val);
      },
      loginOut() {
      	// 使用Pinia中定义的退出系统的方法
        const userStore = useUserStore();
        userStore.logoutConfirm();
      },
      closeOthers() {
        datas.tabPanes = datas.tabPanes.filter((v: { path: any }) => v.path == datas.activeKey);
      },
      reLoad() {
        window.location.reload();
      },
    });
    methods.init();
    watch(
      () => router.currentRoute.value.matched,
      (val) => {
        // 路由变化时,更新面包屑及标签页
        datas.breadcrumbList = val;
        datas.activeKey = router.currentRoute.value.path;
        if (datas.tabPanes.findIndex((v: any) => v.path == router.currentRoute.value.path) == -1) {
          datas.tabPanes.push(router.currentRoute.value);
        }
      },
      { immediate: true }
    );
    return {
      ...toRefs(datas),
      ...toRefs(methods),
    };
  },
});

css样式

style文件夹下,创建index.css 、antd.css、 layout.css
index.css
@import './antd.css';

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

.card {
  padding: 2em;
}

#app {
  margin: 0 auto;
}

.main-content {
  margin: 10px;
  height: 100%;
  overflow-y: auto;
}
layout.css 样式
.side-logo {
  min-width: 60px !important;
  flex: 0 0 60px !important;
}
.side-title {
  flex: 0 0 180px !important;
  min-width: 180px !important;
}
.logo-style {
  float: left;
  margin: 4px;
  width: 40px;
}
.title-style {
  color: white;
  font-size: 18px;
  line-height: 48px;
  float: left;
  margin: 4px;
  font-weight: 700;
}
.trigger {
  width: 32px;
  font-size: 18px;
  line-height: 40px;
  padding: 0px;
  cursor: pointer;
  transition: color 0.3s;
  margin: 0 6px;
}

.trigger:hover {
  color: #5487eae0 !important;
}
.content-style {
  margin: 8px;
  padding: 4px;
  background: #fff;
  min-height: 280px;
}
.loginOut {
  margin: 6px 0px 0;
  font-size: 15px;
  cursor: pointer;
  border-radius: 50% !important;
  padding: 0px 0px !important;
  width: 30px;
}
.loginOut:hover {
  color: #5487eae0 !important;
}
.fade-enter-active .fade-leave-active {
  transition: opacity 0.5s;
}
.layHeader {
  display: flex;
  height: 40px;
  border-bottom: 1px solid #05050530;
}
.spanIcon {
  width: 32px;
  height: 32px;
  border: 1px solid #05050530;
  padding: 8px 6px;
  opacity: 0.5;
  line-height: 14px;
}
.msg-style {
  background-color: #5487eae0;
}
.collased-menu-dropdown {
  transition: background 0.2s ease-in-out;
}

antd.css 调整组件库中的样式
.ant-btn {
  font-size: 14px !important;
  height: 28px !important;
  padding: 0px 10px !important;
  border-radius: 4px !important; 
  margin: 2px 8px 2px 0px;
}
.ant-btn::before {
  margin: 4px;
}
.ant-btn:hover { 
  opacity: 0.8;
}
.ant-form-inline .ant-form-item {
  margin: 0 8px 0 0 !important;
}
.ant-menu-dark .ant-menu-item-selected {
  border-radius: 4px !important; 
}
.ant-layout .ant-layout-header {
  padding-inline: 3px !important;
  background: #fff;
}
.ant-breadcrumb {
  padding: 8px 0 !important;
  font-size: 15px !important;
  width: calc(100% - 80px) !important;
  height: 50px;
}
.ant-tabs {
  width: calc(100% - 80px);
}
.ant-tabs-nav {
  height: 32px !important;
  margin-bottom: 8px !important;
}
.ant-tabs-nav .ant-tabs-nav-wrap {
  border-bottom: 1px solid #e6ebf3;
}
.ant-tabs-nav .ant-tabs-tab {
   border-top-right-radius: 4px !important;
   border-top-left-radius: 4px !important;
   padding: 8px 8px !important;
}
.ant-tabs .ant-tabs-tab-remove {
  margin: 2px -4px 0px 4px !important;
}
.ant-modal .ant-modal-header {
  margin-bottom: 20px;
}

项目效果图:
在这里插入图片描述

项目至此基本搭建结束。后续优化或添加功能,不定期更新。ヾ(•ω•`)o

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1851539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

调试器烧录失败的几种常见解决办法

目录 1. 检查接线、Keil配置是否正确 2. 降低下载速度 3. SWD引脚被禁用或被复用为其他功能 4. 使用CubeMX生成的工程&#xff0c;无法调试&#xff1f; 5. 能识别到芯片但是下载时弹出报错对话框&#xff08;Command not supported&#xff09; 6. 内部flash锁死&#x…

媒体邀约中媒体采访应该如何做?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传加速季&#xff0c;100万补贴享不停&#xff0c;一手媒体资源&#xff0c;全国100城线下落地执行。详情请联系胡老师。 在媒体邀约中&#xff0c;媒体采访应该遵循以下几个步骤和…

用户态协议栈05—架构优化

优化部分 添加了in和out两个环形缓冲区&#xff0c;收到数据包后添加到in队列&#xff1b;经过消费者线程处理之后&#xff0c;将需要发送的数据包添加到out队列。添加数据包解析线程&#xff08;消费者线程&#xff09;&#xff0c;架构分层 #include <rte_eal.h> #inc…

ionic7 从安装 到 项目启动最后打包成 apk

报错处理 在打包的时候遇到过几个问题&#xff0c;这里记录下来两个 Visual Studio Code运行ionic build出错显示ionic : 无法加载文件 ionic 项目通过 android studio 打开报错 capacitor.settings.gradle 文件不存在 说明 由于之前使用的是 ionic 3&#xff0c;当时打包的…

模式分解的概念(上)-分解、无损连接性、保持函数依赖特性

一、分解的概念 1、分解的定义 2、判断一个关系模式的集合P是否为关系模式R的一个分解 只要满足以下三个条件&#xff0c;P就是R的一个分解 &#xff08;1&#xff09;P中所有关系模式属性集的并集是R的属性集 &#xff08;2&#xff09;P中所有不同的关系模式的属性集之间…

【计算机网络体系结构】计算机网络体系结构实验-DHCP实验

服务器ip地址 2. 服务器地址池 3. 客户端ip 4. ping Ipconfig

星闪指向遥控,做家电交互的破壁人

“面壁者罗辑&#xff0c;我是你的破壁人。” 科幻小说《三体》中&#xff0c;当人类的基础科学被三体人封锁&#xff0c;变得停步不前&#xff0c;人类启动了自救的面壁计划&#xff0c;通过一次又一次破壁&#xff0c;找到战胜三体人的办法。 现实中&#xff0c;有一点已经成…

教师数字素养标准

老师们有没有想过如何让自己的课堂更加生动、互动&#xff0c;甚至超越传统的教学模式&#xff1f;是否思考过&#xff0c;数字技术如何成为我们教学的得力助手&#xff0c;让我们的课堂焕发出新的活力&#xff1f; 数字素养&#xff0c;这个听起来充满科技感的词汇&#xff0c…

Github上传大于100M的文件(ubuntu教程)

安装Git-lfs Git Large File Storage (LFS) 使用 Git 内部的文本指针替换音频样本、视频、数据集和图形等大文件&#xff0c;同时将文件内容存储在 GitHub.com 或 GitHub Enterprise 等远程服务器上。官网下载&#xff1a;https://git-lfs.github.com/ ./install.sh上传 比如…

软银CEO孙正义:10年内将出现比人类聪明1万倍的人工智能|TodayAI

2024年6月20日&#xff0c;软银集团公司&#xff08;SoftBank&#xff09;董事长兼首席执行官孙正义在日本东京举行的公司年度股东大会上发表讲话&#xff0c;表示比人类聪明1万倍的人工智能将在10年内出现。这是他近年来一次罕见的公开露面&#xff0c;在会上他质疑了自己的人…

57.SAP MII产品介绍(07)功能详解(06)Workbench-SQLQuery

1.SQLQuery概念 您可以使用SAP Manufacturing Integration and Intelligence&#xff08;SAP MII&#xff09;Workbench中的SQLQuery来创建访问面向SQL的连接器&#xff08;如IDBC连接器&#xff09;的模板。此查询的扩展名为tqsq。 简而言之&#xff0c;SQLQuery就是一段…

Github 2024-06-22 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-22统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目3JavaScript项目2Python项目2HTML项目1Rust项目1Dart项目1Dockerfile项目1Shell项目1C++项目1Swift项目1RustDesk: 用Rust编写的…

数据结构与算法-B(B-)树的简单实现

B(B-)树定义 B树&#xff08;或B-tree&#xff09;是一个在计算机科学中广泛使用的数据结构&#xff0c;它是一种自平衡的树&#xff0c;能够保持数据有序。 以下是B树的特性 每个节点最多右m个孩子&#xff0c;二叉树是B-树的特例&#xff0c;其有2个孩子。除了叶节点和根节点…

探索 Kubernetes v1.30:与 MinIO 部署相关的增强功能

Kubernetes v1.30 的发布带来了一系列更新&#xff0c;其中一些更新对于高性能 Kubernetes 原生对象存储 MinIO 的用户来说可能意义重大。随着组织继续利用这两种技术来提供可扩展且安全的存储解决方案&#xff0c;了解这些新 Kubernetes 功能的影响非常重要。以下是 Kubernete…

华为开发者大会:全场景智能操作系统HarmonyOS NEXT

文章目录 一、全场景智能操作系统 - HarmonyOS NEXT1.1 系统特性1.2 关于架构、体验和生态 二、应用案例2.1 蚂蚁mpaas平台的性能表现 三、新版本应用框架发布3.1 新语言发布3.2 新数据库发布3.3 新版本编译器的发布 四、CodeArts和DataArts4.1 CodeArts4.2 DataArts 五、总结 …

网络富集显著性检验NEST(?)

https://doi.org/10.1002/hbm.26714 背景 一般情况下&#xff0c;研究者通过评估统计量较大的脑区与功能网络重叠的情况&#xff0c;或者计算网络的体素占比&#xff0c;来确定行为和功能网络的相关性。NEST能检测行为表型和大脑表型的相关性是否富集在特定的功能网络中。例如下…

[数据集][目标检测]棉花叶子害虫检测数据集VOC+YOLO格式595张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;595 标注数量(xml文件个数)&#xff1a;595 标注数量(txt文件个数)&#xff1a;595 标注类别…

手动重新平衡您的 MinIO Modern Datalake

当通过添加新的服务器池来扩展 MinIO Modern Datalake 部署时&#xff0c;默认情况下它不会重新平衡对象。相反&#xff0c;MinIO 会将新文件/对象写入具有更多可用空间的池中。MinIO 的手动重新平衡触发器会扫描整个部署&#xff0c;然后在服务器池周围移动对象&#xff08;如…

textarea标签改写为富文本框编辑器KindEditor

下载 - KindEditor - 在线HTML编辑器 KindEditor的简单使用-CSDN博客 一、 Maven需要的依赖&#xff1a; 如果依赖无法下载&#xff0c;可以多添加几个私服地址&#xff1a; 在Maven框架中加入镜像私服 <mirrors><!-- mirror| Specifies a repository mirror site to…