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

书接上回

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

继续填充

目录

按需引入组件

使用unplugin-vue-components插件完成ant-design-vue组件的按需加载。

前文中已处理过,详情见前文

链接: Vue3 + TS + Antd + Pinia 从零搭建后台系统(一)

此处还需在tsconfig.json同级添加文件 components.d.ts。

tsconfig.json文件配置如下:

bash 复制代码
{
  "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文件:

bash 复制代码
  <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文件:

bash 复制代码
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
bash 复制代码
@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 样式
bash 复制代码
.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 调整组件库中的样式
bash 复制代码
.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

相关推荐
轻口味6 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami8 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
wakangda40 分钟前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡43 分钟前
lodash常用函数
前端·javascript
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O1 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235951 小时前
web复习(三)
前端
迷糊的『迷』1 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot