Vue实例:医院统一信息平台(构建项目)

前言

前面一段时间公司里忙得很晚,睡觉时间都不太够了,就隔了几天。服务端的用户系统已经完成。现在来一点前端的内容。我觉得前后端分离就可以各开发各的。相互之间用某种协议进行交互,整合。我的理解整个平台会由N个服务端项目、N个前端项目、加一些中间件构成。前端可能有WEB页面、手机APP、微信公众号页面、H5页面等等。
这里我们开始做PC端的后台管理页面。我把PC端分为前台、后台、登录三个项目。目前先这样,后续可能还会根据实际情况细分。几个项目都是Vue项目,就把它们合到一个工程中进行,省去共用模块、组件的重复管理。这个工程就需要多入口的Vue工程。

创建项目

因为我已经写过Vue多入口项目创建的文章,这里就不再重复了。移步vue多入口项目。项目名称huip。git地址:https://gitee.com/biboheart/huip-vue.git
创建完成后的目录结构:

目录结构

引入图标

在应用型前端中,图标会用的比较多。fontawesome图标库中的免费图标是比较丰富的。只是npm中最高版本到4.7.2就没有再更新。而官网已经到了5.1.0 。所以我选择下载到本地静态引入的方法使用5.1.0(哪位朋友有好的方法还忘告知)。
从官网下载,拷贝到static目录中。在index.html文件中引入css

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link href="static/fontawesome/css/fontawesome-all.min.css" rel="stylesheet">
    <title>huip</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

引入前端框架

我采用element-ui作为vue前端框架。里面的组件比较丰富。包含了常用的管理组件了。

cnpm i element-ui -S

在三个main.js加入element-ui库,完成后的main.js内容如下

import Vue from 'vue'
import App from '@/components/App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.config.productionTip = false
Vue.use(ElementUI)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

界面布局

在这个工程中,各项目(除了登录)的布局都是基本相同的。因此,需要一个公共的布局组件。利用element的Container 布局容器进行开发。
element-ui的NavMenu 导航菜单,官方提供的每个菜单项是需要自己去生成的,并不能通过树型结构的数据生成出整个菜单。因此,我们先来写个组件,递归生成树型菜单。
以下是一个菜单项的组件代码。

<template>
  <component v-bind:is="currentItemComponent" :index="menu.path" :key="menu.path">
    <i :class="menu.icon" v-if="menu.icon && !hc"></i><span v-if="!hc">{{menu.text}}</span>
    <template slot="title" v-if="hc">
      <i :class="menu.icon" v-if="menu.icon"></i><span slot="title">{{menu.text}}</span>
    </template>
    <tree-menu-item :menu="child" :key="child.name" v-for="child in menu.children" v-if="hc && child.menu"></tree-menu-item>
  </component>
</template>

<script>
export default {
  name: 'TreeMenuItem',
  props: {
    menu: Object
  },
  data () {
    return {
      hc: false
    }
  },
  computed: {
    currentItemComponent: function () {
      return this.hasChildren() ? 'el-submenu' : 'el-menu-item'
    }
  },
  methods: {
    hasChildren () {
      this.hc = !!this.menu.hasChildren && this.menu.children && this.menu.children.length > 0
      return this.hc
    }
  }
}
</script>

<style scoped>
</style>

在公共布局组件中layout.vue中使用它:

<script>
import TreeMenuItem from '../packages/tree-menu/index.js'
export default {
  name: 'HuipLayout',
  components: {
    TreeMenuItem
  },
  props: {
    hmenus: Array,
    vmenus: Array,
    tips: Array,
    logoSrc: String,
    badgeValue: [String, Number]
  },
  data: function () {
    return {
      isCollapse: false,
      asideWidth: '230px',
      vDefActive: this.activePath(3),
      hDefActive: this.activePath(2),
      defaultLogo: require('@/assets/logo.png')
    }
  },
  watch: {
    '$route': function (val) {
      this.vDefActive = this.activePath(3)
      this.hDefActive = this.activePath(2)
    }
  },
  methods: {
    collapseChange: function () {
      this.isCollapse = !this.isCollapse
      this.$emit('changeCollapse', this.isCollapse)
      if (this.isCollapse) {
        this.asideWidth = '65px'
      } else {
        this.asideWidth = '230px'
      }
    },
    activePath: function (max) {
      let pathArr = this.$route.path.split('/')
      let def = ''
      if (max < 2) {
        def = this.$route.path
      } else {
        if (pathArr && pathArr.length > max) {
          for (let i = 1; i < max; i++) {
            def += '/' + pathArr[i]
          }
        } else {
          def = this.$route.path
        }
      }
      return def
    },
    clickLogo: function (...args) {
      this.$emit('logo-click', ...args)
    }
  }
}
</script>

<template>
  <el-container>
    <el-header class="layout-main-header clear">
      <!-- logo -->
      <div class="logo" @click="clickLogo">
        <img :src="logoSrc" v-if="logoSrc"/>
        <img :src="defaultLogo" v-else/>
      </div>
      <!-- 右侧菜单 -->
      <div class="tool-body">
        <div class="user-info-cell">
          <el-badge :value="badgeValue" class="user-info-badge" v-if="badgeValue"></el-badge>
          <slot name="user-info"></slot>
        </div>
        <ul class="tips-box clearfix">
          <li class="tips-item" v-for="item in tips" :key="item.name" v-if="!!tips && tips.length > 0">
            <a :href="item.url" :target="item.target || '_self'">{{item.text}}</a>
          </li>
        </ul>
      </div>
      <!-- 如果有顶菜单则显示顶菜单 -->
      <div class="hmenu-wrap" v-if="hmenus && hmenus.length > 0">
        <el-menu
          :default-active="hDefActive"
          unique-opened
          router
          mode="horizontal"
          background-color="#545c64"
          text-color="#fff"
          active-text-color="#ffd04b">
          <tree-menu-item :menu="item" :key="item.name" v-for="item in hmenus" v-if="item.menu"></tree-menu-item>
        </el-menu>
      </div>
    </el-header>
    <!-- 如果提供了左侧菜单的数据则显示左侧aside -->
    <el-container v-if="vmenus && vmenus.length > 0">
      <el-aside class="layout-main-aside" :width="asideWidth">
        <div class="aside-container">
          <div class="aside-header">
            <div class="tools" @click.prevent="collapseChange">
              <i :class="isCollapse ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'"></i>
            </div>
          </div>
          <div class="aside-main aside-main-hasheader">
            <el-scrollbar class="full-scrollbar">
              <div :class="isCollapse ? 'menu-collapsed' : 'menu-expanded'">
                <el-menu
                  :default-active="vDefActive"
                  class="el-menu-vertical-demo vmenu"
                  unique-opened
                  router
                  :collapse="isCollapse">
                  <tree-menu-item :menu="item" :key="item.name" v-for="item in vmenus" v-if="item.menu"></tree-menu-item>
                </el-menu>
              </div>
            </el-scrollbar>
          </div>
        </div>
      </el-aside>
      <el-container>
        <slot></slot>
      </el-container>
    </el-container>
    <slot v-else></slot>
  </el-container>
</template>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.layout-main-header {
  background-color: #545c64;
  color: #333;
  line-height: 60px;
  text-align: left;
  font-size: 12px;
}
.layout-main-aside {
  color: #333;
  background-color: rgb(238, 241, 246);
}
.logo {
  height:60px;
  width:auto;
  font-size: 12px;
  padding-right:20px;
  border-color: rgba(238,238,238,0.3);
  border-right-width: 0px;
  border-right-style: solid;
  color: #fff;
  float: left;
  box-sizing: border-box;
  cursor: pointer;
}
.logo img {
  width: 40px;
  height: 40px;
  margin: 10px 10px 10px 10px;
}
.tool-body {
  height: 60px;
  width: auto;
  float: right;
  box-sizing: border-box;
  text-align: right;
  padding: 0;
}
.user-info-cell {
  position: relative;
  height:60px;
  width:auto;
  font-size: 12px;
  border-color: rgba(238,238,238,0.3);
  border-style: solid;
  border-width: 0px;
  border-left-width: 1px;
  color: #fff;
  float: right;
  box-sizing: border-box;
}
.user-info-badge {
  position: absolute;
  top: -10px;
  right: -10px;
}
.tool-body ul,
.tool-body li {
  padding: 0;
  margin: 0;
  list-style: outside none none;
}
.tips-box {
  float: right;
  display: inline;
}
.tips-box .tips-item {
  box-sizing: border-box;
  float: left;
  height: 60px;
  line-height: 60px;
  margin-right: 22px;
  text-align: left;
}
.tips-box .tips-item a {
  font-size: 14px;
  text-decoration: none;
  color: #bfcbd9;
}
.tips-box .tips-item a:hover {
  color: #00a2ca;
}
.hmenu-wrap {
  display: block;
  float: left;
  height: 60px;
  line-height: 60px;
}
.tools{
  width: 100%;
  line-height: 44px;
  cursor: pointer;
  text-align: center;
  font-size: 14px;
  background: #e4e8f1;
}
.menu-expanded .vmenu {
  position: relative;
}
.menu-collapsed .vmenu {
  position: fixed;
  z-index: 8888;
}
</style>

完成布局

/src/project/index/pages下创建index.vue文件,这是前台页面的主体组件

<script>
import HuipLayout from '@/components/HuipLayout'
export default {
  name: 'AdminMain',
  components: {
    HuipLayout
  },
  data () {
    return {
      defaultLogo: require('@/assets/logo.png'),
      user: {
        name: 'admin'
      },
      tips: [{
        text: '控制台',
        name: 'admin',
        url: '/admin.html'
      }],
      hmenus: [{
        path: '/dashboard',
        name: 'dashboard',
        text: '仪表盘',
        menu: true,
        icon: 'fas fa-tachometer-alt'
      }, {
        path: '/core',
        name: 'core',
        text: '系统配置',
        menu: true,
        hasChildren: true,
        icon: 'fa fa-cogs',
        children: [{
          path: '/core/app',
          name: 'core_app',
          text: '版本管理',
          menu: true
        }, {
          path: '/core/reedback',
          name: 'core_reedback',
          text: '用户反馈',
          menu: true
        }]
      }, {
        path: '/user',
        name: 'user',
        text: '用户系统',
        menu: true,
        hasChildren: true,
        icon: 'fa fa-users',
        children: [{
          path: '/user/client',
          name: 'user_client',
          text: '客户端管理',
          menu: true
        }, {
          path: '/user/resource',
          name: 'user_resource',
          text: '资源管理',
          menu: true
        }, {
          path: '/user/auth',
          name: 'user_auth',
          text: '权限管理',
          menu: true,
          hasChildren: true,
          children: [{
            path: '/user/auth/system',
            name: 'user_auth_system',
            text: '系统权限',
            menu: true
          }, {
            path: '/user/auth/operation',
            name: 'user_auth_operation',
            text: '操作权限',
            menu: true
          }]
        }, {
          path: '/user/role',
          name: 'user_role',
          text: '角色管理',
          menu: true
        }, {
          path: '/user/user',
          name: 'user_user',
          text: '用户管理',
          menu: true
        }]
      }]
    }
  }
}
</script>

<template>
  <huip-layout :hmenus="hmenus" :tips="tips">
    <div v-popover:userInfoPopover class="user-info clear" slot="user-info">
      <img v-if="user && user.pic" :src="user.pic" class="headimg">
      <img v-else :src="defaultLogo" class="headimg">
    </div>
    <el-popover
      ref="userInfoPopover"
      placement="bottom-start"
      trigger="hover"
      width="240"
      :visible-arrow="false"
      popper-class="user-info-popover">
      <div class="topbar-info-dropdown-memu">
        <div class="topbar-user-info">
          <img v-if="user && user.pic" :src="user.pic" class="topbar-user-avatar">
          <img v-else :src="defaultLogo" class="topbar-user-avatar">
          <p class="topbar-user-name">{{user ? user.name : ''}}</p>
        </div>
        <div class="topbar-user-entrance-list">
          <div class="topbar-user-entrance clear">
            <i class="fas fa-clipboard-list info-prefix-icon"></i>
            <span class="left-text">个人中心</span>
          </div>
          <div class="topbar-user-entrance clear">
            <i class="fas fa-comment-dots info-prefix-icon"></i>
            <span class="left-text">消息中心</span>
            <span class="right-text">20</span>
          </div>
        </div>
        <div>
          <div class="user-btn-list">
            <span class="user-btn-link">退出登录</span>
          </div>
        </div>
      </div>
    </el-popover>
    <router-view class="content-wrap"></router-view>
  </huip-layout>
</template>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.user-info {
  cursor: pointer;
  color: #bfcbd9;
}
.user-info .headimg {
  width: 40px;
  height: 40px;
  border-radius: 20px;
  margin: 10px;
  float: left;
}
.topbar-info-dropdown-memu {
  padding: 0;
  list-style: none;
  background-color: #fff;
  background-clip: padding-box;
  font-size: 12px;
  min-width: 100%;
  margin: 0;
  border: none;
  -webkit-box-shadow: 0 1px 3px rgba(0,0,0,.1);
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
  white-space: nowrap;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.topbar-info-dropdown-memu a,
.topbar-info-dropdown-memu li,
.topbar-info-dropdown-memu p,
.topbar-info-dropdown-memu span {
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  letter-spacing: .02em;
  text-decoration: none;
}
.topbar-info-dropdown-memu .topbar-user-info {
  text-align: center;
  padding-top: 16px;
  border-bottom: 1px solid #eaeaea;
}
.topbar-info-dropdown-memu .topbar-user-info .topbar-user-avatar {
  width: 36px;
  height: 36px;
  border-radius: 18px;
  vertical-align: middle;
}
.topbar-info-dropdown-memu .topbar-user-info .topbar-user-name {
  margin: 8px 0;
}
.topbar-info-dropdown-memu .topbar-user-entrance-list {
  overflow: hidden;
  width: 240px;
}
.topbar-info-dropdown-memu .topbar-user-entrance {
  cursor: pointer;
  height: 20px;
  line-height: 20px;
  padding: 0 16px;
  margin: 12px 0;
  font-size: 12px;
  line-height: 16px;
  position: relative;
}
.topbar-info-dropdown-memu .topbar-user-entrance .info-prefix-icon {
  width: 16px;
  height: 16px;
  vertical-align: text-bottom;
  margin-right: 8px;
  color: #333;
}
.topbar-info-dropdown-memu .topbar-user-entrance .right-text {
  float: right;
  font-size: 10px;
}
.topbar-info-dropdown-memu .user-btn-link {
  cursor: pointer;
  height: 50px;
  line-height: 50px;
  display: block;
  -webkit-transition: all .15s;
  transition: all .15s;
  text-align: center;
  color: #333;
  background-color: #f5f5f6;
  border-top: #eaeaea;
}
</style>

后台(admin)中也同样创建一个主体页面组件。
这里的菜单数据是临时数据,为了看布局效果,最终是根据路由文件生成菜单的。

当前效果

前台页面:


前台页面效果图1

前台页面效果图2

后台页面:


效果图1

效果图2

总结

完成前端页面总体布局,公共组件开发。

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

推荐阅读更多精彩内容