手把手教你前后分离架构(三) 前端项目美化

        前面我们已经实现了前后两端项目的基础搭建以及前后数据交互,目前的UI页面十分的简陋,所以今天咱们来完善前端项目,让UI更加美观。
        如果你的html、js、css的基础很好,那么前端项目研发起来相信不会很困难,不管是传统技术JSTL、JQuery与easyUI、bootstrap等UI框架,还是vue、react、angular与elmentui、layui等新技术,前端底层实现都是大同小异的,无非每项技术的特点各有不同。后期可根据团队人员和项目的基本情况来进行配套组合的技术选型。我们继续以Vue.js和elmentui为例研发本项目。

基础组件整合

为了后续不断完善前端项目,需要一些VUE组件来帮助我们更好的实现前端功能。

页面模块化

require和import的区别

vue编程中最重要的思想就是模块化,import和require都是被模块化所使用。
遵循规范
require 是 AMD规范引入方式。
import是ES6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法。
调用时间
require是运行时调用,所以require理论上可以运用在代码的任何地方。
import是编译时调用,所以必须放在文件开头。
原来得样例中页面引入咱们使用   import login from '@/views/login'的方式。
后续如果页面过多,开发环境如果使用懒加载, 页面太多的话会造成webpack热更新太慢。所以生产环境使用懒加载,开发环境不使用懒加载。
添加模块化工具类
router目录下添加工具类根据不同环境采用不同的模块化方式

import-development.js

module.exports = file => require('@/views/' + file + '.vue').default

import-production.js

module.exports = file => () => import('@/views/' + file + '.vue')

嵌套路由

嵌套路由又称为子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL中各段动态路景观也按某种结构对应嵌套的各层组件,例如我们看Vue的官网教程。
当我们点击左边的项目的时候,我们知道,他左边和上面两个导航栏是不会改变的。改变的只是右边那一块区域。将鼠标放在某个标签上面,我们可以看到,他就是一个路由。或者我们应该说,他就是一个子路由。

 index.js 添加路由

const _import = require('./import-' + process.env.NODE_ENV)


//全局路由
const routes = [
  { path: '/login', name:'login',component: _import('login') },
]

// 嵌套路由入口
const mainRoutes = {
  path: '/',
  component: _import('layout/main'),
  name: 'main',
  redirect: { name: 'home' },
  meta: { title: '主入口整体布局' },
  children: [
    { path: '/home', component: _import('demo/list'), name: 'home', meta: { title: '首页' } },
 { path: '/test1', component: _import('demo/test1'), name: 'test1', meta: { title: '选项一', isTab: true } },
    { path: '/test2', component: _import('demo/test2'), name: 'test2', meta: { title: '选项二', isTab: true } },
    { path: '/test', component: _import('demo/add'), name: 'test', meta: { title: '测试功能', isTab: true } },
    { path: '/theme', component: _import('common/theme'), name: 'theme', meta: { title: '主题', isTab: true } },
  ],
}

export default new Router({
  routes: routes.concat(mainRoutes)
})

状态管理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex 包含有五个基本的对象:

state:存储状态。也就是变量;
getters:派生状态。也就是set、get中的get,有两个可选参数:state、getters分别可以获取state中的变量和其他的getters。外部调用方式:store.getters.personInfo()。就和vue的computed差不多;
mutations:提交状态修改。也就是set、get中的set,这是vuex中唯一修改state的方式,但不支持异步操作。第一个参数默认是state。外部调用方式:store.commit(‘SET_AGE’, 18)。和vue中的methods类似。
actions:和mutations类似。不过actions支持异步操作。第一个参数默认是和store具有相同参数属性的对象。外部调用方式:store.dispatch(‘nameAsyn’)。this.$store.dispatch(‘user/login’, this.loginForm)
modules:store的子模块,内容就相当于是store的一个实例。调用方式和前面介绍的相似,只是要加上当前子模块名,如:store.a.getters.xxx()。

有了Vuex可以实现组件全局状态(数据)的管理,方便的实现组件之间的数据共享。

Sass整合

Sass 是一个 CSS 的扩展,它在 CSS 语法的基础上,允许使用变量 (variables),嵌套规则 (nested rules), 混合 (mixins), 导入 (inline imports) 等功能,令 CSS 更加强大与优雅。使用 Sass 以及 Compass 样式库 有助于更好地组织管理样式文件,以及更高效地开发项目。

npm install node-sass --save-dev //安装node-sass 
npm install sass-loader --save-dev //安装sass-loader 
npm install style-loader --save-dev //安装style-loader

此处安装后可能会因sass-loader版本过高报错,可手动指定版本

npm install sass-loader@7.3.1 --save-dev   //版本不能太高

也可在package.json中更改版本,然后npm install

基本UI设计

我们先研后台管理系统,管理系统一般有一下几类UI页面。
1、登录页面
这个不多说了用户密码登录页面。
2、主页面
登录验证成功后跳转到本页面,左侧菜单右侧详情的常规布局。
3、功能列表页
4、功能编辑详情

登录页面

登录页面前边我们已经实现了简单表单,样子比较丑陋,登录页比较简单,样式大家可以自行找网上提供的布局样式也可,基础较好的同学可以自己调整布局。这里举一个简单的登录样式的样例。

<template>
  <div class="login">
    <el-form ref="dataForm" :model="dataForm" :rules="loginRules" size="mini" class="login-form" status-icon>
      <h2 class="title">欢迎登录后台管理系统</h2>
      <el-form-item prop="username">
        <el-input
          v-model="dataForm.username"
          type="text"
          auto-complete="off"
          placeholder="账号"
        >
          <i slot="prefix" class="el-input__icon el-icon-s-custom svg-external-icon"></i>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="dataForm.password"
          type="password"
          auto-complete="off"
          placeholder="密码"
          @keyup.enter.native="dataFormSubmit"
        >
          <i slot="prefix" class="el-input__icon el-icon-s-goods svg-external-icon"></i>
        </el-input>
      </el-form-item>
<!--      <el-form-item prop="captcha" v-if="captchaOnOff">
        <el-input
          v-model="dataForm.captcha"
          auto-complete="off"
          placeholder="验证码"
          style="width: 63%"
          @keyup.enter.native="dataFormSubmit"
        >
          <i slot="prefix" class="el-input__icon el-icon-menu svg-external-icon"></i>
        </el-input>
        <div class="login-code">
          <img :src="captchaPath" @click="getCaptcha()" style="width: 100%"  class="login-code-img"/>
        </div>
      </el-form-item>-->
      <!--      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>-->
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="dataFormSubmit"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © 2021-2022 All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>

export default {
  name: "Login",
  data() {
    return {
      codeUrl: "",
      dataForm: {
        username: '',
        password: '',
        uid: '',
        captcha: '',
        client_id:'webApp',
        client_secret:'webApp',
        grant_type:'password'
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" }
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" }
        ],
        captcha: [{ required: true, trigger: "change", message: "请输入验证码" }]
      },
      captchaPath: '',
      loading: false,
      // 验证码开关
      captchaOnOff: true,
      // 注册开关
      register: false,
    };
  },
  created() {
    // this.getCaptcha()
    var info=this.$route.params.data
    if(info!=null)
      this.$message({
        showClose: true,
        message: info,
        type: 'error',
        duration:1000
      });
  },
  methods: {
    // 提交表单
    dataFormSubmit () {
      this.$refs['dataForm'].validate((valid) => {
        if (valid) {
          this.loading = true;
          this.$router.replace({ name: 'home'})
       
        }
      })
    },
    // 获取验证码
    getCaptcha () {
      this.dataForm.uid = getUUID()
      this.captchaPath = this.$http.adornUrl(`/api-auth/captcha/captcha.jpg?uid=${this.dataForm.uid}`)
    },
  }
};
</script>

<style rel="stylesheet/scss" lang="scss">
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  /*background-image: url("../assets/images/login-background.jpg");*/
  background: #3a8ee6;
  background-size: cover;
}
.title {
  margin: 0px auto 20px auto;
  text-align: center;
  color: #707070;
}

.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 450px;

  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 36px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code-img {
  height: 38px;
}
.svg-external-icon{
  font-size: 12px;
  //margin-right: 18px;
  margin-left: 3px;
  display: flex;
  align-items: center;
}
body {
  height: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}

html {
  height: 100%;
  box-sizing: border-box;
}

#app {
  height: 100%;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
  font-family: inherit;
  font-weight: 500;
  line-height: 1.1;
  color: inherit;
}
</style>

访问 :http://localhost:9081/#/login 显示如下

 这里先不做后台校验,随便输入用户名、密码后跳转主页面。

主页面实现

主页面是整个后台管理系统的门面,它的重要程度不言而喻。

先实现常规的左侧是菜单、右侧是详情页面,头部有登录人的基础信息提示。更炫酷的要求后面逐步实现。

整体布局我们参考element官网布局容器来实现,我门以这个样例作为参考样例。

 新增主页面main.vue

<template>
  <div class="site-wrapper" :class="classObj">
    <template >
      <main-sidebar />
      <main-navbar />
      <div class="site-content__wrapper" :style="styleObj">
        <main-content />
      </div>
    </template>
  </div>
</template>

<script>
  import MainNavbar from './main-navbar'
  import MainSidebar from './main-sidebar'
  import MainContent from './main-content'

  export default {
    data () {
      return {
        loading: true,
        styleObj: {'min-height': this.documentClientHeight + 'px'}
      }
    },
    components: {
      MainNavbar,
      MainSidebar,
      MainContent
    },
    computed: {
      documentClientHeight: {
        get () { return this.$store.state.common.documentClientHeight },
        set (val) { this.$store.commit('common/updateDocumentClientHeight', val) }
      },
      userId: {
        get () { return this.$store.state.user.id },
        set (val) { this.$store.commit('user/updateId', val) }
      },
      userName: {
        get () {
          return this.$store.state.user.name
        },
        set (val) {
          this.$store.commit('user/updateName', val)
        }
      },
      sidebarFold: {
        get () { return this.$store.state.common.sidebarFold }
      },
      clientType() {
        let type = this.$store.state.common.clientType;
        if (type === 'phone') {
          this.styleObj['margin-left'] = '0px';
          this.styleObj['padding-top'] = '100px';
        } else {
          if (this.styleObj['margin-left']) this.$delete(this.styleObj, 'margin-left');
          if (this.styleObj['padding-top']) this.$delete(this.styleObj, 'padding-top');
        }
        return type
      },
      showScreen() {
        if (this.clientType == 'phone' && !this.sidebarFold) return true;
        return false;
      },
      classObj() {
        return {
          'site-sidebar--hide': this.sidebarFold && this.clientType == 'phone',
          'site-sidebar--fold': this.sidebarFold && this.clientType != 'phone',
          'tab-phone-top': this.clientType == 'phone'
        };
      }
    },
    created () {

    },
    mounted () {
      this.resetDocumentClientHeight()
    },
    methods: {
    
      }
    }
  }
</script>

封装Aside、Header、Main-content三个组件,在Main.vue主页面中使用。

Aside组件存放菜单
参照element官网 导航菜单组件

 main-sidebar.vue

<template>
  <aside class="site-sidebar" :class="'site-sidebar--' + sidebarLayoutSkin">
    <div class="site-sidebar__inner">
      <el-menu
        :collapse="sidebarFold"
        default-active="home"
        :collapseTransition="false"
        class="site-sidebar__menu"
       >
        <el-menu-item index="home" @click="$router.push({ name: 'home' })"  v-show="showSidebar">
          <i class="el-icon-s-home"></i>
          <span slot="title">首页</span>
        </el-menu-item>
        <el-submenu index="1">
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>导航一</span>
          </template>
          <el-menu-item-group>
            <template slot="title">分组一</template>
            <el-menu-item index="1-1" @click="$router.push({ name: 'test1' })">选项1</el-menu-item>
            <el-menu-item index="1-2" @click="$router.push({ name: 'test2' })">选项2</el-menu-item>
          </el-menu-item-group>
          <el-menu-item-group title="分组2">
            <el-menu-item index="1-3">选项3</el-menu-item>
          </el-menu-item-group>
          <el-submenu index="1-4">
            <template slot="title">选项4</template>
            <el-menu-item index="1-4-1">选项1</el-menu-item>
          </el-submenu>
        </el-submenu>
        <el-menu-item index="2">
          <i class="el-icon-menu"></i>
          <span slot="title">导航二</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <i class="el-icon-document"></i>
          <span slot="title">导航三</span>
        </el-menu-item>
        <el-menu-item index="4">
          <i class="el-icon-setting"></i>
          <span slot="title">导航四</span>
        </el-menu-item>
      </el-menu>
    </div>
  </aside>
</template>

<script>
  import { isURL } from '@/utils/validate'
  export default {
    data () {
      return {
        dynamicMenuRoutes: [],
        menuValue: '',
        leafMenu: []
      }
    },
    components: {

    },
    computed: {
      sidebarLayoutSkin: {
        get () { return this.$store.state.common.sidebarLayoutSkin }
      },
      sidebarFold: {
        get () { return this.$store.state.common.sidebarFold }
      },
      menuList: {
        get () { return this.$store.state.common.menuList },
        set (val) { this.$store.commit('common/updateMenuList', val) }
      },
      menuActiveName: {
        get () { return this.$store.state.common.menuActiveName },
        set (val) { this.$store.commit('common/updateMenuActiveName', val) }
      },
      mainTabs: {
        get () { return this.$store.state.common.mainTabs },
        set (val) { this.$store.commit('common/updateMainTabs', val) }
      },
      mainTabsActiveName: {
        get () { return this.$store.state.common.mainTabsActiveName },
        set (val) { this.$store.commit('common/updateMainTabsActiveName', val) }
      },
      showSidebar() {//在phone模式并且关闭菜单状态下不展示菜单
        if (this.$store.state.common.clientType == 'phone' && this.sidebarFold) return false;
        return true;
      }
    },
    watch: {
      $route: 'routeHandle'
    },
    created () {
      this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
      this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
      this.routeHandle(this.$route)
      this.getLeafMenu(this.menuList);
    },
    methods: {
      getLeafMenu(menuList) {
        menuList.forEach((item)=>{
          if (item.list) {
            this.getLeafMenu(item.list)
          } else {
            this.leafMenu.push(item)
          }
        })
      },
      chose() {
        let route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === this.menuValue)
        if (route.length >= 1) {
          this.$router.push({name: route[0].name})
        }
        this.menuValue = ''
      },
      // 路由操作
      routeHandle (route) {
        if (route.meta.isTab&&this.$store.state.common.navbarTab) {
          // tab选中, 不存在先添加
          var tab = this.mainTabs.filter(item => item.name === route.name)[0]
          if (!tab) {
            if (route.meta.isDynamic) {
              var globalRoutes=this.$router.options.routes
              var mainRoutes
              for (var i = 0; i < globalRoutes.length; i++) {
                if (globalRoutes[i].children && globalRoutes[i].children.length >= 1 && globalRoutes[i].name == 'main-dynamic') {
                  mainRoutes = globalRoutes[i].children
                }
              }
              route = mainRoutes.filter(item => item.name === route.name)[0]
              if (!route) {
                return console.error('未能找到可用标签页!')
              }
            }
            tab = {
              menuId: route.meta.menuId || route.name,
              name: route.name,
              title: route.meta.title,
              type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module',
              iframeUrl: route.meta.iframeUrl || ''
            }
            this.mainTabs = this.mainTabs.concat(tab)
          }
          this.menuActiveName = tab.menuId + ''
          this.mainTabsActiveName = tab.name
        }
      }
    }
  }
</script>

Header
 

<template>
  <nav class="site-navbar" :class="'site-navbar--' + navbarLayoutType">
   <div class="site-navbar__header" :style="brandStyle">
      <h1 class="site-navbar__brand" :style="brandStyle" @click="$router.push({ name: 'home' })">
        <a class="site-navbar__brand-lg" href="javascript:;">后台管理系统</a>
        <a class="site-navbar__brand-mini" href="javascript:;"></a>
      </h1>
    </div>
    <div class="site-navbar__body clearfix">
      <el-menu
        class="site-navbar__menu"
        mode="horizontal">
        <el-menu-item class="site-navbar__switch" index="0" @click="sidebarFold = !sidebarFold">
          <i class="el-icon-s-fold"></i>
        </el-menu-item>
      </el-menu>
      <el-menu
        class="site-navbar__menu site-navbar__menu--right"
        mode="horizontal">
       <el-menu-item class="site-navbar__avatar" index="3">
          <el-dropdown :show-timeout="0" placement="bottom">

            <span class="el-dropdown-link">
             欢迎,登录!
            </span>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item @click.native="updatePasswordHandle()">修改密码</el-dropdown-item>
              <el-dropdown-item @click.native="logoutHandle()">退出</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
        </el-menu-item>
      </el-menu>
    </div>
  </nav>
</template>

<script>
  export default {
    name: 'screenfull',
    data () {
      return {
        updatePassowrdVisible: false,
        isFullscreen: false,
      }
    },
    components: {

    },
    computed: {
      navbarLayoutType: {
        get () { return this.$store.state.common.navbarLayoutType }
      },
      sidebarFold: {
        get () { return this.$store.state.common.sidebarFold },
        set (val) { this.$store.commit('common/updateSidebarFold', val) }
      },
      mainTabs: {
        get () { return this.$store.state.common.mainTabs },
        set (val) { this.$store.commit('common/updateMainTabs', val) }
      },
      userName: {
        get () { return this.$store.state.user.name }
      },
      brandStyle: {
        get() {
          let type = this.$store.state.common.clientType;
          if (type === 'phone') {
            return this.brandStyle = {
              float: 'none',
              width: '100%'
            }
          } else {
            return this.brandStyle = {}
          }
        },
        set() {}
      }
    },
    methods: {
      // 修改密码
      updatePasswordHandle () {
        this.updatePassowrdVisible = true
        this.$nextTick(() => {
          this.$refs.updatePassowrd.init()
        })
      },
      // 退出
      logoutHandle () {
       this.$router.push({ name: 'login' })
      }
    }
  }
</script>

右侧主页面
主页面多个tabs标签页组成,
参考官网

main-content.vue

<template>
  <main class="site-content" :class="{ 'site-content--tabs': $route.meta.isTab && this.$store.state.common.navbarTab}">
    <!-- 主入口标签页 s -->
    <el-tabs
      v-if="$route.meta.isTab && this.$store.state.common.navbarTab"
      v-model="mainTabsActiveName"
      :closable="true"
      @tab-click="selectedTabHandle"
      @tab-remove="removeTabHandle">
      <el-dropdown class="site-tabs__tools" :show-timeout="0">
        <i class="el-icon-arrow-down el-icon--right"></i>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item @click.native="tabsCloseCurrentHandle">关闭当前标签页</el-dropdown-item>
          <el-dropdown-item @click.native="tabsCloseOtherHandle">关闭其它标签页</el-dropdown-item>
          <el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签页</el-dropdown-item>
          <el-dropdown-item @click.native="tabsRefreshCurrentHandle">刷新当前标签页</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <el-tab-pane
        v-for="item in mainTabs"
        :key="item.name"
        :label="item.title"
        :name="item.name">
        <el-card :body-style="siteContentViewHeight">
          <iframe
            v-if="item.type === 'iframe'"
            :src="item.iframeUrl"
            width="100%" height="100%" frameborder="0" scrolling="yes">
          </iframe>
          <keep-alive v-else>
            <router-view v-if="item.name === mainTabsActiveName" />
          </keep-alive>
        </el-card>
      </el-tab-pane>
    </el-tabs>
    <!-- 主入口标签页 e -->
    <el-card v-else :body-style="siteContentViewHeight">
      <keep-alive>
        <router-view />
      </keep-alive>
    </el-card>
  </main>
</template>

<script>
  import { isURL } from '@/utils/validate'
  export default {
    data () {
      return {
      }
    },
    computed: {
      documentClientHeight: {
        get () { return this.$store.state.common.documentClientHeight }
      },
      menuActiveName: {
        get () { return this.$store.state.common.menuActiveName },
        set (val) { this.$store.commit('common/updateMenuActiveName', val) }
      },
      mainTabs: {
        get () { return this.$store.state.common.mainTabs },
        set (val) { this.$store.commit('common/updateMainTabs', val) }
      },
      mainTabsActiveName: {
        get () { return this.$store.state.common.mainTabsActiveName },
        set (val) { this.$store.commit('common/updateMainTabsActiveName', val) }
      },
      siteContentViewHeight () {
        var height = this.documentClientHeight - 50 - 30 - 2
        if (this.$route.meta.isTab && this.$store.state.common.navbarTab) {
          height -= 40
          return isURL(this.$route.meta.iframeUrl) ? { height: height + 'px' } : { minHeight: height + 'px' }
        }
        return { minHeight: height + 'px' }
      }
    },
    methods: {
      // tabs, 选中tab
      selectedTabHandle (tab) {
        tab = this.mainTabs.filter(item => item.name === tab.name)
        if (tab.length >= 1) {
          this.$router.push({ name: tab[0].name })
        }
      },
      // tabs, 删除tab
      removeTabHandle (tabName) {
        this.mainTabs = this.mainTabs.filter(item => item.name !== tabName)
        if (this.mainTabs.length >= 1) {
          // 当前选中tab被删除
          if (tabName === this.mainTabsActiveName) {
            this.$router.push({ name: this.mainTabs[this.mainTabs.length - 1].name }, () => {
              this.mainTabsActiveName = this.$route.name
            })
          }
        } else {
          this.menuActiveName = ''
          this.$router.push({ name: 'home' })
        }
      },
      // tabs, 关闭当前
      tabsCloseCurrentHandle () {
        this.removeTabHandle(this.mainTabsActiveName)
      },
      // tabs, 关闭其它
      tabsCloseOtherHandle () {
        this.mainTabs = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)
      },
      // tabs, 关闭全部
      tabsCloseAllHandle () {
        this.mainTabs = []
        this.menuActiveName = ''
        this.$router.push({ name: 'home' })
      },
      // tabs, 刷新当前
      tabsRefreshCurrentHandle () {
        var tempTabName = this.mainTabsActiveName
        this.removeTabHandle(tempTabName)
        this.$nextTick(() => {
          this.$router.push({ name: tempTabName })
        })
      }
    }
  }
</script>

访问测试

 列表页

list.vue
 

<template>
  <div>
    <el-form :model="dataForm" ref="dataForm">
      <el-row :gutter="20" >
        <el-col :xs="24" :sm="6" >
          <el-form-item prop="classCode">
            <el-input v-model="dataForm.name" placeholder="姓名" maxlength="20" clearable></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :sm="6" >
          <el-form-item prop="className">
            <el-input v-model="dataForm.province" placeholder="省份" maxlength="20" clearable></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="{span:24,offset:0}"  :sm="{span:6,offset:6}">
          <el-form-item style="float: right">
            <el-button type="primary"  icon="el-icon-search">查询</el-button>
            <el-button type="default"  plain icon="el-icon-refresh-left">重置</el-button>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <el-divider></el-divider>
    <el-row style="padding-bottom: 10px;" align="left">
      <el-button type="primary" icon="el-icon-plus" @click="addPage()">新增</el-button>
      <el-button type="success" icon="el-icon-mouse" @click="add()">测试</el-button>
      <el-button type="info" @click="addPage()">弹框按钮</el-button>
    </el-row>
    <el-table
      :data="tableData"
      border
      style="width: 100%">
      <el-table-column
        fixed
        prop="date"
        label="日期"
        >
      </el-table-column>
      <el-table-column
        prop="name"
        label="姓名"
        >
      </el-table-column>
      <el-table-column
        prop="province"
        label="省份"
        width="120">
      </el-table-column>
      <el-table-column
        prop="city"
        label="市区"
        width="120">
      </el-table-column>
      <el-table-column
        prop="address"
        label="地址"
        width="300">
      </el-table-column>
      <el-table-column
        prop="zip"
        label="邮编"
        width="120">
      </el-table-column>
      <el-table-column
        fixed="right"
        label="操作"
        width="100">
        <template slot-scope="scope">
          <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
          <el-button type="text" size="small">编辑</el-button>
        </template>
      </el-table-column>
    </el-table>
    <add-page ref="addPage" @refreshDataList="test"></add-page>
  </div>
</template>

<script>
import axios from 'axios'
import AddPage from './addPage'
export default {
  components: {
    AddPage
  },
  methods: {
    handleClick(row) {
      console.log(row);
    },
    test(){
      axios
        .get('http://127.0.0.1:8888/test', {
          params: {
            id: 12345
          }
        })
        .then(response => (
          // this.info = response
          alert(response.data)
          // this.info = response
        ))
        .catch(function (error) { // 请求失败处理
          console.log(error);
        });
    },
    add(){
      this.$router.push({ path:'/add'})
    },
    addPage(id){
      this.$nextTick(() => {
        this.$refs.addPage.init(id);
      })
    }
  },
  mounted () {

  },
  data() {
    return {
      dataForm: {
        name: '',
        province: ''
      },
      addPageVisible: false,
      tableData: [{
        date: '2016-05-02',
        name: '王小虎',
        province: '上海',
        city: '普陀区',
        address: '上海市普陀区金沙江路 1518 弄',
        zip: 200333
      }, {
        date: '2016-05-04',
        name: '王小虎',
        province: '上海',
        city: '普陀区',
        address: '上海市普陀区金沙江路 1517 弄',
        zip: 200333
      }, {
        date: '2016-05-01',
        name: '王小虎',
        province: '上海',
        city: '普陀区',
        address: '上海市普陀区金沙江路 1519 弄',
        zip: 200333
      }, {
        date: '2016-05-03',
        name: '王小虎',
        province: '上海',
        city: '普陀区',
        address: '上海市普陀区金沙江路 1516 弄',
        zip: 200333
      }]
    }
  }
}
</script>

<style scoped>
.el-divider--horizontal {
  display: block;
  height: 1px;
  width: 100%;
  margin: 12px 0;
}
</style>

演示截图

明细页面 

addPage.vue
 

<template>
  <el-dialog
    class="custom_dialog"
    :fullscreen=false
    :show-close=false
    :close-on-click-modal=false
    :visible.sync="visible">
    <div slot="title" class="dialog-title">
      <i class="el-icon-edit-outline"></i>
      <span class="title-text">数据详情</span>
    </div>

    <el-form :model="dataForm" :rules="dataRule" ref="dataForm"
             label-width="100px">
      <el-row :gutter="40">
        <el-col :xs="24" :lg="24">
          <el-form-item label="姓名:" prop="classCode">
            <el-input v-model="dataForm.classCode" maxlength="20" placeholder="类型编码"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="省份:" prop="className">
            <el-input v-model="dataForm.className" maxlength="20" placeholder="类型名称"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="邮编:" prop="dataName">
            <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="邮编:" prop="dataName">
            <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="市区:" prop="dataCode">
            <el-input v-model="dataForm.dataCode" maxlength="20" placeholder="键值"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="邮编:" prop="dataName">
            <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
          </el-form-item>
        </el-col>
        <el-col :xs="24" :lg="24">
          <el-form-item label="地址:" prop="dataOrder">
            <el-input v-model="dataForm.dataOrder" maxlength="10" placeholder="排序"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button icon="el-icon-close" @click="visible = false">取消</el-button>
      <el-button icon="el-icon-check" type="primary">提交</el-button>
    </span>
  </el-dialog>
</template>

<script>
export default {
  data() {
    return {
      visible: false,
      dataForm: {
        id: '',
        classCode: '',
        className: '',
        dataCode: '',
        dataName: '',
        dataOrder: '',
        status: ''
      },
      dataRule: {
        classCode: [
          {required: true, message: '类型编码不能为空', trigger: 'blur'}
        ],
        className: [
          {required: true, message: '类型名称不能为空', trigger: 'blur'}
        ],
        dataCode: [
          {required: true, message: '键值不能为空', trigger: 'blur'}
        ],
        dataName: [
          {required: true, message: '名称不能为空', trigger: 'blur'}
        ],
        dataOrder: [
          {required: true, message: '排序不能为空', trigger: 'blur'}
        ],
        status: [
          {required: true, message: '状态不能为空', trigger: 'blur'}
        ]
      }
    }
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        if (valid) {
          alert('submit!');
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
    init (id) {
      // this.dataForm.id = id
      this.visible = true
    }
  }
}
</script>

<style scoped>

</style>

演示样例

 以上就是前端系统主要页面及常用组件,有新的组件可通过elementUI官网,查询具体用法,另外样式的底层逻辑是相同的。

 

关注公众号”小猿架构“,发送 "前后分离架构" ,下载课程视频+课程源码+课件。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值