开发一个后台管理系统的主框架页面(登录、主界面、子界面)

登录

1. 找到 src/router/index.js 路由文件,添加登录路由代码:
{
  path: '/login',
  component: (resolve) => require(['@/views/login.vue'], resolve)
}
2. 在 src/views 目录下新增 login.vue 文件,内容如下:
<template>
  <div class="login-bg">
    <div class="login-box">
      <h2 class="login-tit">My Vue2</h2>
      <el-form :model="formData" @keyup.enter.native="submit">
        <el-form-item prop="userName">
          <el-input v-model="formData.userName" clearable prefix-icon="el-icon-user" placeholder="请输入用户名" autofocus></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input v-model="formData.password" clearable prefix-icon="el-icon-unlock" placeholder="请输入密码" show-password></el-input>
        </el-form-item>
        <p>用户名密码随意填写即可!</p>
        <el-button class="btn-submit" type="primary" @click="submit" :loading="loading">登 录</el-button>
      </el-form>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      // 表单相关
      formData: {
        userName: '',
        password: ''
      }
    }
  },
  mounted() {
  },
  methods: {
    /**
     * 登录
     */
    submit() {
      this.loading = true
      this.$store.commit('SET_USER', { userName: this.formData.userName })
      this.$router.replace('/home')
    }
  }
}
</script>
<style lang="less" scoped>
.login-bg {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: #f7f7f7;
}
.login-box {
  width: 400px;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  .login-tit {
    margin-bottom: 20px;
    text-align: center;
  }
  .btn-submit {
    width: 100%;
  }
}
</style>

以上代码实现了一个简单的登录页面,在表单中输入用户名和密码后,点击登录按钮可以进行登录操作,并跳转到系统主页。

3. 定义表单验证文件

src/assets/scripts目录下新增一个名为validate.js的文件,内容如下:

export default {
  // 非空 输入框
  RI: { required: true, message: '请输入' },
  // 非空 选择器
  RS: { required: true, message: '请选择' },
  // 不以空格开头或结尾
  space: { pattern: /^[^\s]+(\s+[^\s]+)*$/, message: '不以空格开头或结尾' }
}

以上代码是一个验证规则的定义文件,通过导出一个对象来提供几个常用的验证规则。
这样我们就能将整个应用的验证规则都写在这个文件进行统一管理,避免后期维护时在页面组件中进行修改。

4. 在登录中使用定义的验证规则

打开之前新建的login.vue文件,在script中添加引入验证文件代码:

import V from '@/assets/scripts/validate'

export default {
  data() {
    return {
      V
    }
  }
}

template模板中,将规则绑定到表单项中:

<el-form-item prop="userName" :rules="[V.RI, V.space]">
···
</el-form-item>
<el-form-item prop="password" :rules="[V.RI], V.space">
···
</el-form-item>

提交时调用验证:

submit() {
  this.$refs.formData.validate((valid) => {
    if (valid) {
      this.loading = true
      this.$store.commit('SET_USER', { userName: this.formData.userName })
      this.$router.replace('/home')
    }
  })
}

login.vue文件修改完成后的完整内容如下:

<template>
  <div class="login-bg">
    <div class="login-box">
      <h2 class="login-tit">My Vue2</h2>
      <el-form :model="formData" ref="formData" @keyup.enter.native="submit">
        <el-form-item prop="userName" :rules="[V.RI, V.space]">
          <el-input v-model="formData.userName" clearable prefix-icon="el-icon-user" placeholder="请输入用户名" autofocus></el-input>
        </el-form-item>
        <el-form-item prop="password" :rules="[V.RI, V.space]">
          <el-input v-model="formData.password" clearable prefix-icon="el-icon-unlock" placeholder="请输入密码" show-password></el-input>
        </el-form-item>
        <p>用户名密码随意填写即可!</p>
        <el-button class="btn-submit" type="primary" @click="submit" :loading="loading">登 录</el-button>
      </el-form>
    </div>
  </div>
</template>

<script>
import V from '@/assets/scripts/validate'

export default {
  data() {
    return {
      V,
      loading: false,
      // 表单相关
      formData: {
        userName: '',
        password: ''
      }
    }
  },
  mounted() {
  },
  methods: {
    /**
     * 登录
     */
    submit() {
      this.$refs.formData.validate((valid) => {
        if (valid) {
          this.loading = true
          this.$store.commit('SET_USER', { userName: this.formData.userName })
          this.$router.replace('/home')
        }
      })
    }
  }
}
</script>
<style lang="less" scoped>
.login-bg {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background: #f7f7f7;
}
.login-box {
  width: 400px;
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  .login-tit {
    margin-bottom: 20px;
    text-align: center;
  }
  .btn-submit {
    width: 100%;
  }
}
</style>

这样我们一个基础的登录就完成了,其中包括了:进入页面自动聚焦到用户名输入框、密码框内容可见切换、登录表单中输入框聚焦时回车键触发提交事件、表单验证和验证成功后将输入的用户名放入Vuex用户对象中并跳转到首页。


主子界面路由嵌套

1. 在src/components目录中新建一个目录layout并在目录中新建一个名为wrap.vue的文件,内容如下:
<template>
  <div class="wrapper">
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template>

<script>
export default {
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
</style>

<keep-alive> 是 Vue 中的一个内置组件,用于保留组件状态并缓存组件实例。它可以在组件切换时将组件保存在内存中,以便下次再使用时不需要重新渲染和初始化组件。

在 Vue Router 配置中,可以通过嵌套多个 <router-view> 来构建复杂的页面布局。每个 <router-view> 将根据不同的路由匹配来渲染相应的组件内容,其中最外层 <router-view> 对应着根路由,而嵌套的 <router-view> 对应着子路由。
这样我们就能通过了文件中的 <router-view> 匹配渲染子路由的组件内容,从而实现主子页面的嵌套效果。

2. 新建首页页面组件

src/views/目录中新增一个名为home的子目录,并在新目录下添加一个名为 index.vue 的文件,内容如下:

<template>
  <div class="center">
    <h2>欢迎使用!</h2>
  </div>
</template>
<style lang="less" scoped>
  .center {
    display: grid;
    place-items: center;
    height: 100%;
  }
</style>

3. 配置路由

src/router/目录中新增一个名为menus.js的子路由文件,内容如下:

export default [
  {
    path: '/home',
    component: (resolve) => require(['@/views/home/index.vue'], resolve),
    meta: {
      keepAlive: true
    }
  },
  {
    path: '/one/two',
    component: (resolve) => require(['@/views/home/index.vue'], resolve)
  }
  // 其他菜单页面路由
]

上述代码中 meta 对象中 keepAlive 属性用于设置 wrap.vue<keep-alive> 标签的缓存是否开启。

找到src/router/index.js文件,引入wrap.vuemenus.js:

import wrap from '@/components/layout/wrap.vue'
import menusRouter from './menus' // 菜单路由

将原来的/home路由配置代码改为由嵌套的结构,并将子页面路由嵌套进去,内容如下:

{
  path: '/',
  component: wrap,
  children: menusRouter
}

src/router/index.js 路由文件修改完成后的完整代码内容如下:

import Vue from 'vue'
import VueRouter from 'vue-router'
import wrap from '@/components/layout/wrap.vue'
import menusRouter from './menus' // 菜单路由

Vue.use(VueRouter)

const pages = [
  {
    path: '/',
    component: wrap,
    children: menusRouter
  },
  {
    path: '/login',
    component: (resolve) => require(['@/views/login.vue'], resolve)
  },
  {
    path: '/errorPage/404',
    component: (resolve) => require(['@/views/404.vue'], resolve)
  }
]

const router = new VueRouter({
  routes: [
    // 默认路由
    {
      path: '/',
      redirect: '/home'
    },
    // 页面路由
    ...pages,
    // 没有匹配的路由重定向到404页面
    {
      path: '*',
      redirect: '/errorPage/404'
    }
  ]
})

// 路由跳转前
router.beforeEach((to, from, next) => {
  // 可用于拦截导航并执行一些操作,例如验证用户身份、权限控制等。
  next()
})

// 路由跳转后
router.afterEach((to, from) => {
  window.scrollTo(0, 0) // 每次路由改变滚动条都回到顶部
})

export default router

这样一个基本的主子界面路由嵌套关系就完成了,之后我们只需要在wrap.vue中添加主界面的基本功能:菜单栏、头部导航栏、子界面显示区域。

主子界面的基本功能和样式布局

菜单栏

菜单部分我们当前应用做常规的左侧菜单栏。

1. 在layout目录中新建一个名为menu-tree.vue的无限菜单组件文件,内容如下:
<template>
  <div class="menu-tree">
    <template v-for="item in menuList">
      <el-submenu :key="item.path" :index="item.path" v-if="item.children && item.children.length > 0">
        <template slot="title">
          <i :class="item.icon"></i>
          <span slot="title">{{item.title}}</span>
        </template>
        <!-- 组件自调用 -->
        <MenuTree :menuList="item.children"></MenuTree>
      </el-submenu>
      <el-menu-item :key="item.path" :index="item.path" v-else>
        <i :class="item.icon"></i>
        <span slot="title">{{item.title}}</span>
      </el-menu-item>
    </template>
  </div>
</template>

<script>
export default {
  name: 'MenuTree', // name 必须写用于组件自调用
  props: {
    // 菜单列表
    menuList: {
      type: Array,
      default: () => []
    }
  }
}
</script>
2. 在layout目录中新建一个名为menu.vue的菜单组件文件,并在其中使用无限菜单组件,内容如下:
<template>
  <div class="menu-box" :class="{'menu-collapse': isCollapse}">
    <i class="collapse-icon" :class="isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'" @click="isCollapse = !isCollapse"></i>
    <div class="menu-logo">
      <img class="logo-img" src="@img/logo.png" alt="logo">
      <span class="logo-name">My Vue2</span>
    </div>
    <el-scrollbar>
      <el-menu
        :default-active="$route.path"
        :collapse="isCollapse"
        :collapse-transition="false"
        unique-opened
        router
        background-color="#202123"
        text-color="#fff"
        active-text-color="#409EFF"
      >
        <MenuTree :menuList="menuList" />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import MenuTree from './menu-tree.vue'

export default {
  components: { MenuTree }, // 组件
  data() {
    return {
      isCollapse: false,
      menuList: [
        {
          path: '/home',
          title: '首页',
          icon: 'el-icon-s-home'
        },
        {
          path: '/one',
          title: '一级页面',
          icon: 'el-icon-menu',
          children: [
            {
              path: '/one/two',
              title: '二级页面'
            }
          ]
        }
      ]
    }
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
.menu-box {
  position: relative;
  flex-shrink: 0;
  width: 300px;
  background: #202123;
  transition: width .3s;
  &.menu-collapse {
    width: 64px;
    .logo-name {
      display: none;
    }
    /deep/ .el-submenu__title span {
      display: none;
    }
  }
  .collapse-icon {
    position: absolute;
    right: -30px;
    top: 15px;
    font-size: 30px;
    color: #fff;
  }
  .menu-logo {
    display: flex;
    align-items: center;
    padding: 0 15px;
    height: 60px;
    color: #fff;
    font-size: 20px;
    white-space: nowrap;
    overflow: hidden;
    .logo-img {
      margin-right: 10px;
      height: 30px;
    }
  }
  .el-scrollbar {
    height: calc(100% - 60px);
    /deep/ .el-scrollbar__wrap {
      overflow-x: hidden;
    }
  }
  .el-menu {
    border: 0;
  }
}
</style>
3. 在wrap.vue中引用组件并使用:

引入菜单组件

import Menu from './menu.vue'

注册组件

components: { Menu }, // 组件

template中使用组件

<Menu/>

wrap.vue修改后的完整内容如下:

<template>
  <div class="wrapper">
    <Menu />
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template>

<script>
import Menu from './menu.vue'

export default {
  components: { Menu }, // 组件
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
</style>

头部导航栏

1. 在 layout 目录下新建一个名为 header.vue 的头部导航栏组件,内容如下:
<template>
  <div class="header-box">
  </div>
</template>

<script>
export default {
  data() {
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
</style>
2. 在wrap.vue中引用组件并使用:

引入菜单组件

import Header from './header.vue'

注册组件

components: { Header }, // 组件

template中使用组件,并修改菜单组件、头部组件、子界面容器的代码结构:

<template>
  <div class="wrapper">
    <Menu />
    <div class="header-subpage-content">
      <Header />
      <div class="subpage-content">
        <keep-alive>
          <router-view v-if="$route.meta.keepAlive"></router-view>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive"></router-view>
      </div>
    </div>
  </div>
</template>

添加主子界面布局样式

.wrapper {
  display: flex;
  width: 100%;
  height: 100vh;
  .header-subpage-content {
    display: flex;
    flex-direction: column;
    width: 100%;
    .subpage-content {
      height: 100%;
    }
  }
}

wrap.vue修改后的完整内容如下:

<template>
  <div class="wrapper">
    <Menu />
    <div class="header-subpage-content">
      <Header />
      <div class="subpage-content">
        <keep-alive>
          <router-view v-if="$route.meta.keepAlive"></router-view>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive"></router-view>
      </div>
    </div>
  </div>
</template>

<script>
import Menu from './menu.vue'
import Header from './header.vue'

export default {
  components: { Menu, Header }, // 组件
  data() {
    return {
    }
  },
  mounted() {
  },
  methods: {
  }
}
</script>

<style lang="less" scoped>
  .wrapper {
    display: flex;
    width: 100%;
    height: 100vh;
    .header-subpage-content {
      display: flex;
      flex-direction: column;
      width: 100%;
      .subpage-content {
        height: 100%;
      }
    }
  }
</style>
3. 用户基本操作模块(用户头像、用户名、退出登录)

header.vue 中内容替换为以下内容:

<template>
  <div class="header-box">
    <el-dropdown class="user-dropdown" @command="handleUser">
      <img class="user-pic" src="@img/pic.png" />
      <el-dropdown-menu slot="dropdown">
        <el-dropdown-item disabled>{{ $store.getters.GET_USER.userName }}</el-dropdown-item>
        <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
      </el-dropdown-menu>
    </el-dropdown>
  </div>
</template>

<script>
export default {
  data() {
    return {
    }
  },
  methods: {
    /**
     * 用户相关操作
     * @param {String} functionName 函数名称
     */
    handleUser(functionName) {
      this[functionName]()
    },
    /**
     * 退出登录
     */
    logout() {
      this.$router.replace('/login')
    }
  }
}
</script>

<style lang="less" scoped>
  .header-box {
    min-height: 60px;
    color: #fff;
    background: #202123;
    .user-dropdown {
      float: right;
      display: flex;
      align-items: center;
      margin-right: 10px;
      height: 100%;
      color: #fff;
      cursor: pointer;
      .user-pic {
        width: 40px;
        height: 40px;
      }
    }
  }
</style>

到这里整个后台管理系统的基本主框架页面就完成了,后续再根据自己的添加或修改内容。



框架搭建整体流程

点击下载步骤 1-7 配置完成的完整 Demo



本框架更多功能 Demo 下载

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容