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


            前面我们已经实现了前后两端项目的基础搭建以及前后数据交互,目前的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 添加路由

    1. const _import = require('./import-' + process.env.NODE_ENV)
    2. //全局路由
    3. const routes = [
    4. { path: '/login', name:'login',component: _import('login') },
    5. ]
    6. // 嵌套路由入口
    7. const mainRoutes = {
    8. path: '/',
    9. component: _import('layout/main'),
    10. name: 'main',
    11. redirect: { name: 'home' },
    12. meta: { title: '主入口整体布局' },
    13. children: [
    14. { path: '/home', component: _import('demo/list'), name: 'home', meta: { title: '首页' } },
    15. { path: '/test1', component: _import('demo/test1'), name: 'test1', meta: { title: '选项一', isTab: true } },
    16. { path: '/test2', component: _import('demo/test2'), name: 'test2', meta: { title: '选项二', isTab: true } },
    17. { path: '/test', component: _import('demo/add'), name: 'test', meta: { title: '测试功能', isTab: true } },
    18. { path: '/theme', component: _import('common/theme'), name: 'theme', meta: { title: '主题', isTab: true } },
    19. ],
    20. }
    21. export default new Router({
    22. routes: routes.concat(mainRoutes)
    23. })

    状态管理

    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 样式库 有助于更好地组织管理样式文件,以及更高效地开发项目。

    1. npm install node-sass --save-dev //安装node-sass
    2. npm install sass-loader --save-dev //安装sass-loader
    3. 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、功能编辑详情

    登录页面

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

    1. <template>
    2. <div class="login">
    3. <el-form ref="dataForm" :model="dataForm" :rules="loginRules" size="mini" class="login-form" status-icon>
    4. <h2 class="title">欢迎登录后台管理系统</h2>
    5. <el-form-item prop="username">
    6. <el-input
    7. v-model="dataForm.username"
    8. type="text"
    9. auto-complete="off"
    10. placeholder="账号"
    11. >
    12. <i slot="prefix" class="el-input__icon el-icon-s-custom svg-external-icon"></i>
    13. </el-input>
    14. </el-form-item>
    15. <el-form-item prop="password">
    16. <el-input
    17. v-model="dataForm.password"
    18. type="password"
    19. auto-complete="off"
    20. placeholder="密码"
    21. @keyup.enter.native="dataFormSubmit"
    22. >
    23. <i slot="prefix" class="el-input__icon el-icon-s-goods svg-external-icon"></i>
    24. </el-input>
    25. </el-form-item>
    26. <!-- <el-form-item prop="captcha" v-if="captchaOnOff">
    27. <el-input
    28. v-model="dataForm.captcha"
    29. auto-complete="off"
    30. placeholder="验证码"
    31. style="width: 63%"
    32. @keyup.enter.native="dataFormSubmit"
    33. >
    34. <i slot="prefix" class="el-input__icon el-icon-menu svg-external-icon"></i>
    35. </el-input>
    36. <div class="login-code">
    37. <img :src="captchaPath" @click="getCaptcha()" style="width: 100%" class="login-code-img"/>
    38. </div>
    39. </el-form-item>-->
    40. <!-- <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>-->
    41. <el-form-item style="width:100%;">
    42. <el-button
    43. :loading="loading"
    44. size="medium"
    45. type="primary"
    46. style="width:100%;"
    47. @click.native.prevent="dataFormSubmit"
    48. >
    49. <span v-if="!loading">登 录</span>
    50. <span v-else>登 录 中...</span>
    51. </el-button>
    52. <div style="float: right;" v-if="register">
    53. <router-link class="link-type" :to="'/register'">立即注册</router-link>
    54. </div>
    55. </el-form-item>
    56. </el-form>
    57. <!-- 底部 -->
    58. <div class="el-login-footer">
    59. <span>Copyright © 2021-2022 All Rights Reserved.</span>
    60. </div>
    61. </div>
    62. </template>
    63. <script>
    64. export default {
    65. name: "Login",
    66. data() {
    67. return {
    68. codeUrl: "",
    69. dataForm: {
    70. username: '',
    71. password: '',
    72. uid: '',
    73. captcha: '',
    74. client_id:'webApp',
    75. client_secret:'webApp',
    76. grant_type:'password'
    77. },
    78. loginRules: {
    79. username: [
    80. { required: true, trigger: "blur", message: "请输入您的账号" }
    81. ],
    82. password: [
    83. { required: true, trigger: "blur", message: "请输入您的密码" }
    84. ],
    85. captcha: [{ required: true, trigger: "change", message: "请输入验证码" }]
    86. },
    87. captchaPath: '',
    88. loading: false,
    89. // 验证码开关
    90. captchaOnOff: true,
    91. // 注册开关
    92. register: false,
    93. };
    94. },
    95. created() {
    96. // this.getCaptcha()
    97. var info=this.$route.params.data
    98. if(info!=null)
    99. this.$message({
    100. showClose: true,
    101. message: info,
    102. type: 'error',
    103. duration:1000
    104. });
    105. },
    106. methods: {
    107. // 提交表单
    108. dataFormSubmit () {
    109. this.$refs['dataForm'].validate((valid) => {
    110. if (valid) {
    111. this.loading = true;
    112. this.$router.replace({ name: 'home'})
    113. }
    114. })
    115. },
    116. // 获取验证码
    117. getCaptcha () {
    118. this.dataForm.uid = getUUID()
    119. this.captchaPath = this.$http.adornUrl(`/api-auth/captcha/captcha.jpg?uid=${this.dataForm.uid}`)
    120. },
    121. }
    122. };
    123. </script>
    124. <style rel="stylesheet/scss" lang="scss">
    125. .login {
    126. display: flex;
    127. justify-content: center;
    128. align-items: center;
    129. height: 100%;
    130. /*background-image: url("../assets/images/login-background.jpg");*/
    131. background: #3a8ee6;
    132. background-size: cover;
    133. }
    134. .title {
    135. margin: 0px auto 20px auto;
    136. text-align: center;
    137. color: #707070;
    138. }
    139. .login-form {
    140. border-radius: 6px;
    141. background: #ffffff;
    142. width: 450px;
    143. padding: 25px 25px 5px 25px;
    144. .el-input {
    145. height: 38px;
    146. input {
    147. height: 38px;
    148. }
    149. }
    150. .input-icon {
    151. height: 39px;
    152. width: 14px;
    153. margin-left: 2px;
    154. }
    155. }
    156. .login-tip {
    157. font-size: 13px;
    158. text-align: center;
    159. color: #bfbfbf;
    160. }
    161. .login-code {
    162. width: 33%;
    163. height: 36px;
    164. float: right;
    165. img {
    166. cursor: pointer;
    167. vertical-align: middle;
    168. }
    169. }
    170. .el-login-footer {
    171. height: 40px;
    172. line-height: 40px;
    173. position: fixed;
    174. bottom: 0;
    175. width: 100%;
    176. text-align: center;
    177. color: #fff;
    178. font-family: Arial;
    179. font-size: 12px;
    180. letter-spacing: 1px;
    181. }
    182. .login-code-img {
    183. height: 38px;
    184. }
    185. .svg-external-icon{
    186. font-size: 12px;
    187. //margin-right: 18px;
    188. margin-left: 3px;
    189. display: flex;
    190. align-items: center;
    191. }
    192. body {
    193. height: 100%;
    194. -moz-osx-font-smoothing: grayscale;
    195. -webkit-font-smoothing: antialiased;
    196. text-rendering: optimizeLegibility;
    197. font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
    198. }
    199. html {
    200. height: 100%;
    201. box-sizing: border-box;
    202. }
    203. #app {
    204. height: 100%;
    205. }
    206. .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
    207. font-family: inherit;
    208. font-weight: 500;
    209. line-height: 1.1;
    210. color: inherit;
    211. }
    212. </style>

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

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

    主页面实现

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

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

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

     新增主页面main.vue

    1. <template>
    2. <div class="site-wrapper" :class="classObj">
    3. <template >
    4. <main-sidebar />
    5. <main-navbar />
    6. <div class="site-content__wrapper" :style="styleObj">
    7. <main-content />
    8. </div>
    9. </template>
    10. </div>
    11. </template>
    12. <script>
    13. import MainNavbar from './main-navbar'
    14. import MainSidebar from './main-sidebar'
    15. import MainContent from './main-content'
    16. export default {
    17. data () {
    18. return {
    19. loading: true,
    20. styleObj: {'min-height': this.documentClientHeight + 'px'}
    21. }
    22. },
    23. components: {
    24. MainNavbar,
    25. MainSidebar,
    26. MainContent
    27. },
    28. computed: {
    29. documentClientHeight: {
    30. get () { return this.$store.state.common.documentClientHeight },
    31. set (val) { this.$store.commit('common/updateDocumentClientHeight', val) }
    32. },
    33. userId: {
    34. get () { return this.$store.state.user.id },
    35. set (val) { this.$store.commit('user/updateId', val) }
    36. },
    37. userName: {
    38. get () {
    39. return this.$store.state.user.name
    40. },
    41. set (val) {
    42. this.$store.commit('user/updateName', val)
    43. }
    44. },
    45. sidebarFold: {
    46. get () { return this.$store.state.common.sidebarFold }
    47. },
    48. clientType() {
    49. let type = this.$store.state.common.clientType;
    50. if (type === 'phone') {
    51. this.styleObj['margin-left'] = '0px';
    52. this.styleObj['padding-top'] = '100px';
    53. } else {
    54. if (this.styleObj['margin-left']) this.$delete(this.styleObj, 'margin-left');
    55. if (this.styleObj['padding-top']) this.$delete(this.styleObj, 'padding-top');
    56. }
    57. return type
    58. },
    59. showScreen() {
    60. if (this.clientType == 'phone' && !this.sidebarFold) return true;
    61. return false;
    62. },
    63. classObj() {
    64. return {
    65. 'site-sidebar--hide': this.sidebarFold && this.clientType == 'phone',
    66. 'site-sidebar--fold': this.sidebarFold && this.clientType != 'phone',
    67. 'tab-phone-top': this.clientType == 'phone'
    68. };
    69. }
    70. },
    71. created () {
    72. },
    73. mounted () {
    74. this.resetDocumentClientHeight()
    75. },
    76. methods: {
    77. }
    78. }
    79. }
    80. </script>

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

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

     main-sidebar.vue

    1. <template>
    2. <aside class="site-sidebar" :class="'site-sidebar--' + sidebarLayoutSkin">
    3. <div class="site-sidebar__inner">
    4. <el-menu
    5. :collapse="sidebarFold"
    6. default-active="home"
    7. :collapseTransition="false"
    8. class="site-sidebar__menu"
    9. >
    10. <el-menu-item index="home" @click="$router.push({ name: 'home' })" v-show="showSidebar">
    11. <i class="el-icon-s-home"></i>
    12. <span slot="title">首页</span>
    13. </el-menu-item>
    14. <el-submenu index="1">
    15. <template slot="title">
    16. <i class="el-icon-location"></i>
    17. <span>导航一</span>
    18. </template>
    19. <el-menu-item-group>
    20. <template slot="title">分组一</template>
    21. <el-menu-item index="1-1" @click="$router.push({ name: 'test1' })">选项1</el-menu-item>
    22. <el-menu-item index="1-2" @click="$router.push({ name: 'test2' })">选项2</el-menu-item>
    23. </el-menu-item-group>
    24. <el-menu-item-group title="分组2">
    25. <el-menu-item index="1-3">选项3</el-menu-item>
    26. </el-menu-item-group>
    27. <el-submenu index="1-4">
    28. <template slot="title">选项4</template>
    29. <el-menu-item index="1-4-1">选项1</el-menu-item>
    30. </el-submenu>
    31. </el-submenu>
    32. <el-menu-item index="2">
    33. <i class="el-icon-menu"></i>
    34. <span slot="title">导航二</span>
    35. </el-menu-item>
    36. <el-menu-item index="3" disabled>
    37. <i class="el-icon-document"></i>
    38. <span slot="title">导航三</span>
    39. </el-menu-item>
    40. <el-menu-item index="4">
    41. <i class="el-icon-setting"></i>
    42. <span slot="title">导航四</span>
    43. </el-menu-item>
    44. </el-menu>
    45. </div>
    46. </aside>
    47. </template>
    48. <script>
    49. import { isURL } from '@/utils/validate'
    50. export default {
    51. data () {
    52. return {
    53. dynamicMenuRoutes: [],
    54. menuValue: '',
    55. leafMenu: []
    56. }
    57. },
    58. components: {
    59. },
    60. computed: {
    61. sidebarLayoutSkin: {
    62. get () { return this.$store.state.common.sidebarLayoutSkin }
    63. },
    64. sidebarFold: {
    65. get () { return this.$store.state.common.sidebarFold }
    66. },
    67. menuList: {
    68. get () { return this.$store.state.common.menuList },
    69. set (val) { this.$store.commit('common/updateMenuList', val) }
    70. },
    71. menuActiveName: {
    72. get () { return this.$store.state.common.menuActiveName },
    73. set (val) { this.$store.commit('common/updateMenuActiveName', val) }
    74. },
    75. mainTabs: {
    76. get () { return this.$store.state.common.mainTabs },
    77. set (val) { this.$store.commit('common/updateMainTabs', val) }
    78. },
    79. mainTabsActiveName: {
    80. get () { return this.$store.state.common.mainTabsActiveName },
    81. set (val) { this.$store.commit('common/updateMainTabsActiveName', val) }
    82. },
    83. showSidebar() {//在phone模式并且关闭菜单状态下不展示菜单
    84. if (this.$store.state.common.clientType == 'phone' && this.sidebarFold) return false;
    85. return true;
    86. }
    87. },
    88. watch: {
    89. $route: 'routeHandle'
    90. },
    91. created () {
    92. this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
    93. this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
    94. this.routeHandle(this.$route)
    95. this.getLeafMenu(this.menuList);
    96. },
    97. methods: {
    98. getLeafMenu(menuList) {
    99. menuList.forEach((item)=>{
    100. if (item.list) {
    101. this.getLeafMenu(item.list)
    102. } else {
    103. this.leafMenu.push(item)
    104. }
    105. })
    106. },
    107. chose() {
    108. let route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === this.menuValue)
    109. if (route.length >= 1) {
    110. this.$router.push({name: route[0].name})
    111. }
    112. this.menuValue = ''
    113. },
    114. // 路由操作
    115. routeHandle (route) {
    116. if (route.meta.isTab&&this.$store.state.common.navbarTab) {
    117. // tab选中, 不存在先添加
    118. var tab = this.mainTabs.filter(item => item.name === route.name)[0]
    119. if (!tab) {
    120. if (route.meta.isDynamic) {
    121. var globalRoutes=this.$router.options.routes
    122. var mainRoutes
    123. for (var i = 0; i < globalRoutes.length; i++) {
    124. if (globalRoutes[i].children && globalRoutes[i].children.length >= 1 && globalRoutes[i].name == 'main-dynamic') {
    125. mainRoutes = globalRoutes[i].children
    126. }
    127. }
    128. route = mainRoutes.filter(item => item.name === route.name)[0]
    129. if (!route) {
    130. return console.error('未能找到可用标签页!')
    131. }
    132. }
    133. tab = {
    134. menuId: route.meta.menuId || route.name,
    135. name: route.name,
    136. title: route.meta.title,
    137. type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module',
    138. iframeUrl: route.meta.iframeUrl || ''
    139. }
    140. this.mainTabs = this.mainTabs.concat(tab)
    141. }
    142. this.menuActiveName = tab.menuId + ''
    143. this.mainTabsActiveName = tab.name
    144. }
    145. }
    146. }
    147. }
    148. </script>

    Header
     

    1. <template>
    2. <nav class="site-navbar" :class="'site-navbar--' + navbarLayoutType">
    3. <div class="site-navbar__header" :style="brandStyle">
    4. <h1 class="site-navbar__brand" :style="brandStyle" @click="$router.push({ name: 'home' })">
    5. <a class="site-navbar__brand-lg" href="javascript:;">后台管理系统</a>
    6. <a class="site-navbar__brand-mini" href="javascript:;"></a>
    7. </h1>
    8. </div>
    9. <div class="site-navbar__body clearfix">
    10. <el-menu
    11. class="site-navbar__menu"
    12. mode="horizontal">
    13. <el-menu-item class="site-navbar__switch" index="0" @click="sidebarFold = !sidebarFold">
    14. <i class="el-icon-s-fold"></i>
    15. </el-menu-item>
    16. </el-menu>
    17. <el-menu
    18. class="site-navbar__menu site-navbar__menu--right"
    19. mode="horizontal">
    20. <el-menu-item class="site-navbar__avatar" index="3">
    21. <el-dropdown :show-timeout="0" placement="bottom">
    22. <span class="el-dropdown-link">
    23. 欢迎,登录!
    24. </span>
    25. <el-dropdown-menu slot="dropdown">
    26. <el-dropdown-item @click.native="updatePasswordHandle()">修改密码</el-dropdown-item>
    27. <el-dropdown-item @click.native="logoutHandle()">退出</el-dropdown-item>
    28. </el-dropdown-menu>
    29. </el-dropdown>
    30. </el-menu-item>
    31. </el-menu>
    32. </div>
    33. </nav>
    34. </template>
    35. <script>
    36. export default {
    37. name: 'screenfull',
    38. data () {
    39. return {
    40. updatePassowrdVisible: false,
    41. isFullscreen: false,
    42. }
    43. },
    44. components: {
    45. },
    46. computed: {
    47. navbarLayoutType: {
    48. get () { return this.$store.state.common.navbarLayoutType }
    49. },
    50. sidebarFold: {
    51. get () { return this.$store.state.common.sidebarFold },
    52. set (val) { this.$store.commit('common/updateSidebarFold', val) }
    53. },
    54. mainTabs: {
    55. get () { return this.$store.state.common.mainTabs },
    56. set (val) { this.$store.commit('common/updateMainTabs', val) }
    57. },
    58. userName: {
    59. get () { return this.$store.state.user.name }
    60. },
    61. brandStyle: {
    62. get() {
    63. let type = this.$store.state.common.clientType;
    64. if (type === 'phone') {
    65. return this.brandStyle = {
    66. float: 'none',
    67. width: '100%'
    68. }
    69. } else {
    70. return this.brandStyle = {}
    71. }
    72. },
    73. set() {}
    74. }
    75. },
    76. methods: {
    77. // 修改密码
    78. updatePasswordHandle () {
    79. this.updatePassowrdVisible = true
    80. this.$nextTick(() => {
    81. this.$refs.updatePassowrd.init()
    82. })
    83. },
    84. // 退出
    85. logoutHandle () {
    86. this.$router.push({ name: 'login' })
    87. }
    88. }
    89. }
    90. </script>

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

    main-content.vue

    1. <template>
    2. <main class="site-content" :class="{ 'site-content--tabs': $route.meta.isTab && this.$store.state.common.navbarTab}">
    3. <!-- 主入口标签页 s -->
    4. <el-tabs
    5. v-if="$route.meta.isTab && this.$store.state.common.navbarTab"
    6. v-model="mainTabsActiveName"
    7. :closable="true"
    8. @tab-click="selectedTabHandle"
    9. @tab-remove="removeTabHandle">
    10. <el-dropdown class="site-tabs__tools" :show-timeout="0">
    11. <i class="el-icon-arrow-down el-icon--right"></i>
    12. <el-dropdown-menu slot="dropdown">
    13. <el-dropdown-item @click.native="tabsCloseCurrentHandle">关闭当前标签页</el-dropdown-item>
    14. <el-dropdown-item @click.native="tabsCloseOtherHandle">关闭其它标签页</el-dropdown-item>
    15. <el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签页</el-dropdown-item>
    16. <el-dropdown-item @click.native="tabsRefreshCurrentHandle">刷新当前标签页</el-dropdown-item>
    17. </el-dropdown-menu>
    18. </el-dropdown>
    19. <el-tab-pane
    20. v-for="item in mainTabs"
    21. :key="item.name"
    22. :label="item.title"
    23. :name="item.name">
    24. <el-card :body-style="siteContentViewHeight">
    25. <iframe
    26. v-if="item.type === 'iframe'"
    27. :src="item.iframeUrl"
    28. width="100%" height="100%" frameborder="0" scrolling="yes">
    29. </iframe>
    30. <keep-alive v-else>
    31. <router-view v-if="item.name === mainTabsActiveName" />
    32. </keep-alive>
    33. </el-card>
    34. </el-tab-pane>
    35. </el-tabs>
    36. <!-- 主入口标签页 e -->
    37. <el-card v-else :body-style="siteContentViewHeight">
    38. <keep-alive>
    39. <router-view />
    40. </keep-alive>
    41. </el-card>
    42. </main>
    43. </template>
    44. <script>
    45. import { isURL } from '@/utils/validate'
    46. export default {
    47. data () {
    48. return {
    49. }
    50. },
    51. computed: {
    52. documentClientHeight: {
    53. get () { return this.$store.state.common.documentClientHeight }
    54. },
    55. menuActiveName: {
    56. get () { return this.$store.state.common.menuActiveName },
    57. set (val) { this.$store.commit('common/updateMenuActiveName', val) }
    58. },
    59. mainTabs: {
    60. get () { return this.$store.state.common.mainTabs },
    61. set (val) { this.$store.commit('common/updateMainTabs', val) }
    62. },
    63. mainTabsActiveName: {
    64. get () { return this.$store.state.common.mainTabsActiveName },
    65. set (val) { this.$store.commit('common/updateMainTabsActiveName', val) }
    66. },
    67. siteContentViewHeight () {
    68. var height = this.documentClientHeight - 50 - 30 - 2
    69. if (this.$route.meta.isTab && this.$store.state.common.navbarTab) {
    70. height -= 40
    71. return isURL(this.$route.meta.iframeUrl) ? { height: height + 'px' } : { minHeight: height + 'px' }
    72. }
    73. return { minHeight: height + 'px' }
    74. }
    75. },
    76. methods: {
    77. // tabs, 选中tab
    78. selectedTabHandle (tab) {
    79. tab = this.mainTabs.filter(item => item.name === tab.name)
    80. if (tab.length >= 1) {
    81. this.$router.push({ name: tab[0].name })
    82. }
    83. },
    84. // tabs, 删除tab
    85. removeTabHandle (tabName) {
    86. this.mainTabs = this.mainTabs.filter(item => item.name !== tabName)
    87. if (this.mainTabs.length >= 1) {
    88. // 当前选中tab被删除
    89. if (tabName === this.mainTabsActiveName) {
    90. this.$router.push({ name: this.mainTabs[this.mainTabs.length - 1].name }, () => {
    91. this.mainTabsActiveName = this.$route.name
    92. })
    93. }
    94. } else {
    95. this.menuActiveName = ''
    96. this.$router.push({ name: 'home' })
    97. }
    98. },
    99. // tabs, 关闭当前
    100. tabsCloseCurrentHandle () {
    101. this.removeTabHandle(this.mainTabsActiveName)
    102. },
    103. // tabs, 关闭其它
    104. tabsCloseOtherHandle () {
    105. this.mainTabs = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)
    106. },
    107. // tabs, 关闭全部
    108. tabsCloseAllHandle () {
    109. this.mainTabs = []
    110. this.menuActiveName = ''
    111. this.$router.push({ name: 'home' })
    112. },
    113. // tabs, 刷新当前
    114. tabsRefreshCurrentHandle () {
    115. var tempTabName = this.mainTabsActiveName
    116. this.removeTabHandle(tempTabName)
    117. this.$nextTick(() => {
    118. this.$router.push({ name: tempTabName })
    119. })
    120. }
    121. }
    122. }
    123. </script>

    访问测试

     列表页

    list.vue
     

    1. <template>
    2. <div>
    3. <el-form :model="dataForm" ref="dataForm">
    4. <el-row :gutter="20" >
    5. <el-col :xs="24" :sm="6" >
    6. <el-form-item prop="classCode">
    7. <el-input v-model="dataForm.name" placeholder="姓名" maxlength="20" clearable></el-input>
    8. </el-form-item>
    9. </el-col>
    10. <el-col :xs="24" :sm="6" >
    11. <el-form-item prop="className">
    12. <el-input v-model="dataForm.province" placeholder="省份" maxlength="20" clearable></el-input>
    13. </el-form-item>
    14. </el-col>
    15. <el-col :xs="{span:24,offset:0}" :sm="{span:6,offset:6}">
    16. <el-form-item style="float: right">
    17. <el-button type="primary" icon="el-icon-search">查询</el-button>
    18. <el-button type="default" plain icon="el-icon-refresh-left">重置</el-button>
    19. </el-form-item>
    20. </el-col>
    21. </el-row>
    22. </el-form>
    23. <el-divider></el-divider>
    24. <el-row style="padding-bottom: 10px;" align="left">
    25. <el-button type="primary" icon="el-icon-plus" @click="addPage()">新增</el-button>
    26. <el-button type="success" icon="el-icon-mouse" @click="add()">测试</el-button>
    27. <el-button type="info" @click="addPage()">弹框按钮</el-button>
    28. </el-row>
    29. <el-table
    30. :data="tableData"
    31. border
    32. style="width: 100%">
    33. <el-table-column
    34. fixed
    35. prop="date"
    36. label="日期"
    37. >
    38. </el-table-column>
    39. <el-table-column
    40. prop="name"
    41. label="姓名"
    42. >
    43. </el-table-column>
    44. <el-table-column
    45. prop="province"
    46. label="省份"
    47. width="120">
    48. </el-table-column>
    49. <el-table-column
    50. prop="city"
    51. label="市区"
    52. width="120">
    53. </el-table-column>
    54. <el-table-column
    55. prop="address"
    56. label="地址"
    57. width="300">
    58. </el-table-column>
    59. <el-table-column
    60. prop="zip"
    61. label="邮编"
    62. width="120">
    63. </el-table-column>
    64. <el-table-column
    65. fixed="right"
    66. label="操作"
    67. width="100">
    68. <template slot-scope="scope">
    69. <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
    70. <el-button type="text" size="small">编辑</el-button>
    71. </template>
    72. </el-table-column>
    73. </el-table>
    74. <add-page ref="addPage" @refreshDataList="test"></add-page>
    75. </div>
    76. </template>
    77. <script>
    78. import axios from 'axios'
    79. import AddPage from './addPage'
    80. export default {
    81. components: {
    82. AddPage
    83. },
    84. methods: {
    85. handleClick(row) {
    86. console.log(row);
    87. },
    88. test(){
    89. axios
    90. .get('http://127.0.0.1:8888/test', {
    91. params: {
    92. id: 12345
    93. }
    94. })
    95. .then(response => (
    96. // this.info = response
    97. alert(response.data)
    98. // this.info = response
    99. ))
    100. .catch(function (error) { // 请求失败处理
    101. console.log(error);
    102. });
    103. },
    104. add(){
    105. this.$router.push({ path:'/add'})
    106. },
    107. addPage(id){
    108. this.$nextTick(() => {
    109. this.$refs.addPage.init(id);
    110. })
    111. }
    112. },
    113. mounted () {
    114. },
    115. data() {
    116. return {
    117. dataForm: {
    118. name: '',
    119. province: ''
    120. },
    121. addPageVisible: false,
    122. tableData: [{
    123. date: '2016-05-02',
    124. name: '王小虎',
    125. province: '上海',
    126. city: '普陀区',
    127. address: '上海市普陀区金沙江路 1518 弄',
    128. zip: 200333
    129. }, {
    130. date: '2016-05-04',
    131. name: '王小虎',
    132. province: '上海',
    133. city: '普陀区',
    134. address: '上海市普陀区金沙江路 1517 弄',
    135. zip: 200333
    136. }, {
    137. date: '2016-05-01',
    138. name: '王小虎',
    139. province: '上海',
    140. city: '普陀区',
    141. address: '上海市普陀区金沙江路 1519 弄',
    142. zip: 200333
    143. }, {
    144. date: '2016-05-03',
    145. name: '王小虎',
    146. province: '上海',
    147. city: '普陀区',
    148. address: '上海市普陀区金沙江路 1516 弄',
    149. zip: 200333
    150. }]
    151. }
    152. }
    153. }
    154. </script>
    155. <style scoped>
    156. .el-divider--horizontal {
    157. display: block;
    158. height: 1px;
    159. width: 100%;
    160. margin: 12px 0;
    161. }
    162. </style>

    演示截图

    明细页面 

    addPage.vue
     

    1. <template>
    2. <el-dialog
    3. class="custom_dialog"
    4. :fullscreen=false
    5. :show-close=false
    6. :close-on-click-modal=false
    7. :visible.sync="visible">
    8. <div slot="title" class="dialog-title">
    9. <i class="el-icon-edit-outline"></i>
    10. <span class="title-text">数据详情</span>
    11. </div>
    12. <el-form :model="dataForm" :rules="dataRule" ref="dataForm"
    13. label-width="100px">
    14. <el-row :gutter="40">
    15. <el-col :xs="24" :lg="24">
    16. <el-form-item label="姓名:" prop="classCode">
    17. <el-input v-model="dataForm.classCode" maxlength="20" placeholder="类型编码"></el-input>
    18. </el-form-item>
    19. </el-col>
    20. <el-col :xs="24" :lg="24">
    21. <el-form-item label="省份:" prop="className">
    22. <el-input v-model="dataForm.className" maxlength="20" placeholder="类型名称"></el-input>
    23. </el-form-item>
    24. </el-col>
    25. <el-col :xs="24" :lg="24">
    26. <el-form-item label="邮编:" prop="dataName">
    27. <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
    28. </el-form-item>
    29. </el-col>
    30. <el-col :xs="24" :lg="24">
    31. <el-form-item label="邮编:" prop="dataName">
    32. <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
    33. </el-form-item>
    34. </el-col>
    35. <el-col :xs="24" :lg="24">
    36. <el-form-item label="市区:" prop="dataCode">
    37. <el-input v-model="dataForm.dataCode" maxlength="20" placeholder="键值"></el-input>
    38. </el-form-item>
    39. </el-col>
    40. <el-col :xs="24" :lg="24">
    41. <el-form-item label="邮编:" prop="dataName">
    42. <el-input v-model="dataForm.dataName" maxlength="20" placeholder="名称"></el-input>
    43. </el-form-item>
    44. </el-col>
    45. <el-col :xs="24" :lg="24">
    46. <el-form-item label="地址:" prop="dataOrder">
    47. <el-input v-model="dataForm.dataOrder" maxlength="10" placeholder="排序"></el-input>
    48. </el-form-item>
    49. </el-col>
    50. </el-row>
    51. </el-form>
    52. <span slot="footer" class="dialog-footer">
    53. <el-button icon="el-icon-close" @click="visible = false">取消</el-button>
    54. <el-button icon="el-icon-check" type="primary">提交</el-button>
    55. </span>
    56. </el-dialog>
    57. </template>
    58. <script>
    59. export default {
    60. data() {
    61. return {
    62. visible: false,
    63. dataForm: {
    64. id: '',
    65. classCode: '',
    66. className: '',
    67. dataCode: '',
    68. dataName: '',
    69. dataOrder: '',
    70. status: ''
    71. },
    72. dataRule: {
    73. classCode: [
    74. {required: true, message: '类型编码不能为空', trigger: 'blur'}
    75. ],
    76. className: [
    77. {required: true, message: '类型名称不能为空', trigger: 'blur'}
    78. ],
    79. dataCode: [
    80. {required: true, message: '键值不能为空', trigger: 'blur'}
    81. ],
    82. dataName: [
    83. {required: true, message: '名称不能为空', trigger: 'blur'}
    84. ],
    85. dataOrder: [
    86. {required: true, message: '排序不能为空', trigger: 'blur'}
    87. ],
    88. status: [
    89. {required: true, message: '状态不能为空', trigger: 'blur'}
    90. ]
    91. }
    92. }
    93. },
    94. methods: {
    95. submitForm(formName) {
    96. this.$refs[formName].validate((valid) => {
    97. if (valid) {
    98. alert('submit!');
    99. } else {
    100. console.log('error submit!!');
    101. return false;
    102. }
    103. });
    104. },
    105. resetForm(formName) {
    106. this.$refs[formName].resetFields();
    107. },
    108. init (id) {
    109. // this.dataForm.id = id
    110. this.visible = true
    111. }
    112. }
    113. }
    114. </script>
    115. <style scoped>
    116. </style>

    演示样例

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

  • 相关阅读:
    (附源码)ssm汽车租赁 毕业设计 271621
    SD 交货单过账后自动产生服务确认单据
    【大话设计模式】依赖倒转原则
    C++类作用域
    腾讯大牛手撕SpringBoot,Cloud,Nginx与Docker,你凭什么还搞不懂啊!
    Leaflet结合Echarts实现迁徙图
    html实训大作业《简单的学生网页作业源码 基于html css javascript红色文化传媒网站(20页)》
    一区TOP期刊提出一种植被覆盖度估算新方法
    使用 PyTorch 搭建网络 - train_py篇
    【Delphi】使用TWebBrowser执行JavaScript命令传入JSON参数执行出错解决方案
  • 原文地址:https://blog.csdn.net/sundehui01/article/details/124882246