首页 > 其他分享 >响应式项目(RxJS+Vue.js+Spring)大决战(5):主页的实现(前端视图模块)

响应式项目(RxJS+Vue.js+Spring)大决战(5):主页的实现(前端视图模块)

时间:2024-10-31 17:16:53浏览次数:7  
标签:vue Spring 视图 js Vue import 组件 home id

书接上篇:响应式项目(RxJS+Vue.js+Spring)大决战(4):主页的实现(后端服务模块)

5.2 前端视图模块

5.2.1 整体结构的设计

        前端模块app-view/home负责主页视图的建构,其结构如下图所示:

        本篇所述方法,体现了极强的独特性、技巧性! 

5.2.2 主页home.html

        对home.html现有内容作如下修改:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>教务辅助管理系统--官方主页</title>
    <link href="image/favicon.ico" rel="icon" type="image/x-icon">
    <script type="importmap">
        {
          "imports": {
            "vue": "./lib/vue.esm-browser.prod.js",
            "vue-demi": "./lib/vue-demi.js",
            "pinia": "./lib/pinia.esm-browser.js",
            "rxjs": "./lib/rxjs.min.js",
            "rxjsFetch": "./lib/rxjs-fetch.min.js",
            "rxjsAjax": "./lib/rxjs-ajax.min.js",
            "rxjsWebsocket": "./lib/rxjs-websocket.min.js",
            "echarts": "./lib/echarts.esm.min.js",
            "vue3SfcLoader": "./lib/vue3-sfc-loader.esm.js"
          }
        }
    </script>
</head>
<body>
<div id="tamsApp"></div>
<script src="home.js" type="module"></script>
</body>
</html>

        type="module"是ES6中的语法规范:启用模块模式,以便能够在“.js”文件中使用import导入其他模块。

5.2.3 主页脚本home.js

        主页脚本home.js的代码非常简洁:

import {createApp} from 'vue'
import {createPinia} from 'pinia'                    //导入状态管理
import sfcLoader from './sfc-loader.js'             //导入SFC加载器
import appPlugins from "./plugins/app.plugins.js"    //导入public模块中的插件

createApp(sfcLoader)                                //创建Vue应用
    .use(createPinia())                             //创建Pinia实例以便进行状态管理
    .use(appPlugins)                                //安装插件
    .mount('#tamsApp')                              //挂载到tamsApp层  

        SFC加载器负责加载各.vue组件,而app.plugins.js中定义了全局公共插件。

        注意,由于app.plugins.js中定义的插件,是为整个项目的各模块服务的,因此并没有放置在模块home下,而是保存在app-view/public模块的resources/static/plugins文件夹下。

5.2.4 SFC加载器sfc-loader.js

        通常,对于.vue组件,需要使用专门的编译打包工具,例如webpack、rollup等等,将其编译成浏览器可识别的代码。这些编译打包工具,往往需要作各种繁琐的构建配置,还需要Node.js支持!那么,对于非webpack、非Node.js环境,如何处理?

        第三方插件vue3-sfc-loader.js,专门用于在运行时动态加载.vue文件,能够加载、解析、编译.vue文件中的模板、JavaScript脚本和CSS样式,并分析依赖项进行递归解析。vue3-sfc-loader.js不需要安装配置Node.js,也不需要webpack!vue3-sfc-loader.js包含了Vue3(Vue2)编译器、Babel JavaScript编译器、CSS转换器postcss、JavaScript标准库补丁库core-js,且内置了对ES6的支持!vue3-sfc-loader.js简单易用,几乎不需要做任何繁琐配置!!

  关于vue3-sfc-loader.js,更详细的内容,可通过这个网址https://www.jsdelivr.com/package/npm/vue3-sfc-loader进行了解。

        模块加载器sfc-loader.js,基于vue3-sfc-loader.js,为系统加载单文件组件SFC(.vue文件)提供强大支持。代码如下:

import * as vue from 'vue'
import * as rxjs from 'rxjs'
import * as rxjsFetch from 'rxjsFetch'
import * as rxjsAjax from 'rxjsAjax'
import * as rxjsWebsocket from 'rxjsWebsocket'
import * as echarts from 'echarts'
import {loadModule} from 'vue3SfcLoader'

const options = {
    moduleCache: {
        vue: vue,                        //缓存vue
        rxjs: rxjs,                      //缓存RxJS常规函数库
        fetch: rxjsFetch,                //缓存RxJS的fetch函数
        ajax: rxjsAjax,                  //缓存RxJS的ajax函数
        websocket: rxjsWebsocket,        //缓存RxJS的websocket函数
        echarts: echarts                 //缓存Echarts图形库
    },
    getFile(url) {                       //加载.vue组件
        return fetch(url).then(response => response.ok ?
            response.text() : Promise.reject(response))
    },
    addStyle(styleStr) {                 //加载.css文件
        const style = document.createElement('style')
        style.textContent = styleStr
        const ref = document.head
            .getElementsByTagName('style')[0] || null
        document.head.insertBefore(style, ref)
    }
}
//异步加载页面主体组件home.index.vue
export default vue.defineAsyncComponent(() => loadModule('home.index.vue', options))

5.2.5 页面主体组件home.index.vue

        页面主体组件将整个主页划分成4大部分:header(头部)、menu(菜单)、body(主体)、footer(底部),并利用Vue的<component>动态组件属性is,来动态加载系统的各个功能组件。代码如下:

<script setup>
import HomeLayout from './home.layout.vue'    //导入页面布局组件
import HomeHeader from './home.header.vue'    //导入标题及消息显示组件
import HomeMenu from './home.menu.vue'        //导入导航菜单组件
import HomeBody from './home.body.vue'        //导入页面主体组件
import HomeFooter from './home.footer.vue'    //导入页面底部组件
</script>

<template>
  <home-layout>                        <!--对应HomeLayout组件-->
    <template #header>
      <home-header></home-header>      <!--对应HomeHeader组件-->
    </template>
    <template #menu>
      <home-menu></home-menu>          <!--对应HomeMenu组件-->
    </template>
    <template #body>
      <home-body></home-body>           <!--对应HomeBody组件-->
    </template>
    <template #footer>
      <home-footer></home-footer>       <!--对应HomeFooter组件-->
    </template>
  </home-layout>
</template>

5.2.6 页面布局组件home.layout.vue

        布局组件使用具名插槽slot来匹配显示页面的4大部分:header、menu、body和footer,代码如下:

<template>
    <div id="tamsApp">
        <header>
            <slot name="header"></slot>
        </header>
        <menu>
            <slot name="menu"></slot>
        </menu>
        <main>
            <slot name="body"></slot>
        </main>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>
</template>
<style scoped>
#tamsApp {
    position: absolute; /* 绝对定位 */
    width: 851px;
    height: 551px;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0 auto; /* 上下外边距为0,左右自动,即居中 */
    padding: 0;
    text-align: center; /* 文字居中 */
    border: 1px dotted #4071e2; /* 边框:1像素、点线、浅蓝色 */
    /* 水平、垂直方向阴影距离为1px、阴影展开大小为1px、颜色为灰色 */
    box-shadow: 1px 1px 2px #ecdfdf;
}

:global(input, span, button) {
    font-size: 15px;
}

:global(img) {
    vertical-align: middle; /* 图片垂直方向对齐 */
}
</style>

5.2.7 标题及消息显示组件home.header.vue

        home.header.vue用来构建主页标题部分的内容,主要包括:系统名称、登录用户的logo头像、后端推送的消息数量及消息内容。代码如下:

<script setup>
import {getCurrentInstance} from 'vue'

const store = getCurrentInstance().appContext.config
    .globalProperties.$pinia._s.get('loginer')
</script>

<template>
  <div class="home-header">
    教务辅助<img alt="logo" src="image/logo.png"/>管理系统
    <div v-show="store.user.username!=null" class="home-loginer">
      <img :src="store.user.logo" :style="store.msgStyle"
           alt="消息" height="16" width="16"/>
      <span :style="store.msgStyle">{{ store.user.username }}</span>
      <div :title="store.message" class="msg-title">
        {{ store.msgCount }}
      </div>
    </div>
  </div>
</template>

<style scoped>
.home-header {
  width: 848px;
  height: 28px;
  float: left;
  text-align: center;
  margin: 0 auto;
  padding: 2px;
  font-size: 20px;
  border: 0;
  background: rgba(216, 234, 234, 0.6);
}

.home-loginer {
  position: relative;
  width: 8em;
  height: 1.7em;
  font-size: 0.7em;
  color: #00f;
  top: 3px;
  right: 1em;
  border: 0 solid #f15555;
  float: right;
  text-align: right;
  padding: 1px;
  margin: 0;
  display: table-cell;
  z-index: 99999;
}

.msg-title {
  position: relative;
  left: 0;
  top: -6px;
  border-radius: 50%;
  display: inline-block;
  text-align: center;
  font-style: normal;
  color: #fff;
  background-color: #f15555;
  width: 1.9em;
  height: 1.7em;
  line-height: 1.7em;
  font-size: 0.7em;
  cursor: pointer;
}
</style>

        值得注意的是代码中的store对象!这里通过Vue当前实例,再通过上下文属性appContext获取应用的配置,并利用globalProperties.$pinia._s,拿到id为loginer的pinia状态管理数据。也就是说,系统利用Pinia,将用户登录状态数据保存起来。该状态数据的id号为loginer,后续会专门实现状态管理代码。

5.2.8 导航菜单组件home.menu.vue

        导航组件使用无序列表构建页面的导航菜单,代码如下:

<script setup>
import {defineComponent, getCurrentInstance, h} from 'vue'

const menus = [
  {id: 'home', name: '首\u3000页'},
  {id: 'login.index', name: '用户登录'},
  {id: 'regist.index', name: '用户注册'},
  {id: 'college.index', name: '学院风采'},
  {id: 'query.index', name: '学生查询'},
  {id: 'enroll.index', name: '招生一览'},
  {id: 'upload.index', name: '资料上传'},
  {id: 'chat.index', name: '畅论空间'}
]
const appCtx = getCurrentInstance().appContext //获取当前Vue应用程序上下文的相关数据
//异步加载组件
Promise.all(  //若为home,则设置为null组件
    menus.map(m => m.id === 'home' ? null : import(`./modules/${m.id}.vue`))
).then(modules => modules.forEach(m => m == null ?
    appCtx.app.component('home', m) : appCtx.app.component(m.__name, m)))
const store = appCtx.config.globalProperties.$pinia._s.get('loginer')

const TamsMenu = defineComponent({
  render() {
    return h('div', {class: 'home-menu'},
        h('ul', {class: 'home-ul'},
            menus.map(({id, name}) =>
                h('li', {
                  id: id,
                  innerText: name,
                  //利用Pinia的Action方法setModule更改当前模块
                  onClick: event => store.setModule(event.target.id)
                }))
        ))
  }
})
</script>

<template>
  <tams-menu></tams-menu>
</template>

<style>
.home-menu {
  position: relative;
  float: left;
  width: 850px;
  height: 30px;
  font-size: 16px;
  left: -40px;
  top: 0;
  /* 背景色:设置红、绿、蓝色彩值,不透明度为0.2 */
  background: rgba(236, 223, 223, 0.2);
}

.home-ul {
  position: relative;
  width: 848px;
  height: 23px;
  top: 0;
  font-size: 16px;
  list-style: none; /* 去掉列表符号 */
  padding: 1px; /* 空白填充量 **/
  margin: 1px;
  display: flex; /* 弹性布局 */
  justify-content: space-around; /* 列表项间隔均等 */
}

.home-ul li:hover {
  color: #fff;
  background-color: #f1625d;
  border-radius: 3px; /* 矩形四边角的弧度 */
  cursor: pointer; /* 手形鼠标 */
  box-shadow: 2px 2px 2px #d7d4d4;
}
</style>

        代码利用加载组件的“__name”属性,将该组件注册到Vue应用中。注意:__name是两个下画线“_”加上name组成!

        在构建TamsMenu组件时,直接利用渲染函数render()构建主页菜单。当点击某个菜单项时,通过状态函数setModule(event.target.id)传递当前组件的id号,并利用Vue的<component :is="store.module"></component>动态组件属性is,达到响应式动态加载功能模块的目的!

5.2.9 页面主体组件home.body.vue

        该组件比较简单,主要是利用Vue动态组件的is属性,来加载并显示当前导航菜单所对应的功能模块:

<script setup>
import {getCurrentInstance} from 'vue'
//获取状态数据
const store = getCurrentInstance().appContext.config
    .globalProperties.$pinia._s.get('loginer')
</script>
<template>
  <div class="home-body">
    <component :is="store.module"></component>
  </div>
</template>

<style scoped>
.home-body {
  position: relative;
  width: 100%;
  height: 460px;
  font-size: 16px;
  top: 0;
  float: left; /* 靠左浮动 */
  text-align: center;
  margin: 0 auto;
  padding: 1px;
  border: 0; /* 无边框 */
  /* 背景图像:图片源、水平垂直方向不拉伸、泊靠顶部、居中 */
  background: url("image/homebg.png") no-repeat top center;
  background-size: 848px 475px; /* 背景图像固定大小 */
  background-origin: padding-box; /* 背景图像相对于内边距框来定位 */
  z-index: 9;
}
</style>

5.2.10 页面底部组件

        该组件代码非常简单:

<template>
  <span class="copyright">
    版权所有 ©2023 Copyrights all reserved
  </span>
</template>

<style scoped>
.copyright {
  font-size: 10px; /* 文字大小 */
  color: #cecccc; /* 前景色 */
}
</style>

5.3 插件

        在app-view/public模块(注意不是home模块)的src/main/resources/static/plugins文件夹下新建文件app.plugins.js。我们在该文件中定义下面2个自定义指令:

  • focus:设置当某个input元素被Vue插入到DOM中后,自动获得焦点。该指令的应用形式为:v-focus。为什么不通过设置input元素的autofocus属性自动获得焦点?因为autofocus仅在首次加载时有效,当Vue组件模块动态切换时,并不能自动获得焦点。
  • submitButton:定义一个具有统一外观样式的提交按钮,并允许通过传递width参数值来定制按钮长度。该指令的应用形式为:v-submit-button。

        这些指令以插件形式,注册到应用层级,这样的话系统中的所有组件均可使用。

import {h, render} from 'vue'
import {filter, fromEvent, mergeWith, Observable, scan, switchMap, tap} from 'rxjs'
import {useStore} from '../store/index.js' //导入状态管理


export default {
    name: 'app.plugins',
    install: (app, _) => {
        useStore() //初始化状态数据
        app.directive('focus', { //定义focus指令
            mounted: (element) => element.focus()
        })
        app.directive('submitButton', { //定义submitButton指令
            mounted: (element, binding) => { 
                const style = {
                    cursor: 'pointer',
                    width: binding.value.width, //binding.value是用户传送的CSS值
                    height: '27px',
                    textAlign: 'center',
                    color: '#fff',
                    background: binding.value.background,
                    border: '1px solid #70abe7',
                    borderRadius: '3px',
                    fontSize: '15px'
                }
                Reflect.ownKeys(style).forEach(key => { //反射获取style的各属性
                    //将style的key所对应属性值一一赋值给element元素的对应属性
                    element.style[key] = style[key]
                })
            }
        })
    }
}

5.4 状态管理

        状态管理相关组件位于public模块下,其结构如下图所示。

5.4.1 states定义状态量

        在public/src/main/resources/static/store文件夹下新建states.js。然后,在states.js中定义4个状态量:module,当前加载的模块;user,登录用户;token,登录用户的令牌;message,后台服务器推送的消息。代码如下:

export default {
    name: 'store.states',
    module: null,
    user: {
        username: null,
        logo: null,
        role: null
    },
    token: null,
    message: ''
}

5.4.2 actions.js更改状态数据

        在public/src/main/resources/static/store文件夹下新建actions.js文件。与上一节states.js中的状态量相对应,actions.js定义了4个setter方法,可用来更改前述4个状态量的值:

export default {
    name: 'store.actions',
    setModule: function (module) {
        this.module = module
    },
    setUser: function (user) {
        this.user = user
    },
    setToken: function (token) {
        this.token = token
    },
    setMessage: function (message) {
        this.message = message
    }
}

5.4.3 getters.js计算函数

        在public/src/main/resources/static/store文件夹下新建getters.js文件。getters.js中主要包含2个计算函数:

  • msgStyle,定义后台推送消息的样式。如果用户登录状态已失效,则将用户logo头像设置为灰色,以便提示该用户处于登录失效状态。
  • msgCount,统计后端服务推送的消息总数。

getters.js的代码如下:

export default {
    name: 'store.getters',
    msgStyle: ({token}) =>
        ({
            color: token == null ? '#ccc' : '#00f',  //若登录失效则文字设置为灰色
            filter: token == null ? 'grayscale(1)' : 'grayscale(0)' //应用灰度转换滤镜
        }),
    msgCount: ({message}) => {
        let regex = new RegExp(/\u000D/g)     //匹配消息中的换行符\u000D
        let m = message.match(regex)          //匹配消息
        return m ? m.length : 0                //根据匹配结果返回消息数量
    }
}

5.4.4 index.js创建状态实例

        在public/src/main/resources/static/store文件夹下新建index.js文件。现在,可利用前面创建好的状态量、计算函数、Action方法,来定义并输出Pinia Store对象useStore:

import storeStates from './states.js'
import storeGetters from './getters.js'
import storeActions from './actions.js'
import {defineStore} from 'pinia'

export const useStore = defineStore({
    id: 'loginer',                      //Pinia状态管理标识ID 
    state: () => ({...storeStates}),    //展开storeStates中的属性或对象
    getters: {...storeGetters},         //展开storeGetters中的计算函数
    actions: {...storeActions}          //展开storeActions中的操作方法
})

5.5 通用进度提示组件

        通用进度提示组件类似于进度条,用于提示用户当前任务的处理状态。该组件采用动画gif图片+提示文字的组合形式,例如下图表示当前正在上传文件的进度提示。用户可自由控制:组件的显示/隐藏;动画gif图片的显示/隐藏;提示文字的内容。

        在public/src/main/resources/static/modules文件夹下新建文件loading.vue,代码如下: 

<script setup>
import {toRef} from 'vue'

const props = defineProps({
  isLoading: Object,                          //该属性控制是否显示进度提示组件
  imaged: {type: Boolean, default: true}     //该属性控制是否显示动画gif图片
})
// visible链接到isLoading.visible,与其值保持同步
const visible = toRef(() => props.isLoading.visible)
</script>

<template>
    <span v-show="visible">
        <img v-show="imaged" alt="正在进行"
             height="45" src="image/doing.gif" width="50"/>
        <slot>正在进行...</slot>
    </span>
</template>

<style scoped>
span {
  color: #F00;
  font-size: 14px;
  width: max-content;    /*元素中的最大宽度作为整体宽度*/
  height: max-content;   /*元素中的最大高度作为整体高度*/
}
</style>

        至此,整个主页模块的代码编写工作结束。现在可继续通过Gradle面板再次启动项目,然后在浏览器地址栏输入:http://192.168.1.5/,将显示主页界面,主页构建成功! 

        提示:由于其他功能模块并没有编写完成,import(`./modules/${m.id}.vue`)并不能导入这些模块,因此如果打开浏览器控制台,会显示报错信息,但这并不妨碍主页界面的成功渲染。后续,逐步补充完善这些模块。

下一步:用户登录,且听下回分解。 

标签:vue,Spring,视图,js,Vue,import,组件,home,id
From: https://blog.csdn.net/acoease/article/details/143369233

相关文章

  • Vue组件的动态加载和卸载
            组件的动态注册还是比较容易的,使用<component:is="组件id"></component>即可,但动态卸载有难度,相关文献较少。不过,如果巧妙使用vnode,就能轻松实现!       下图展示了4个代表不同文档材料的Vue组件。为简化起见,每个组件用一个DIV元素表示,其内容为一张图......
  • 记录springboot 3.3.5 版本整合 swagger +spring security + jwt
    springboot版本security版本wagger版本jwt版本redis版本pom文件如下引入redis是为了存储token<version>3.3.5</version><!--security--><dependency><groupId>org.springframework.boot</groupId><arti......
  • Vue 展示word\excel\pdf 内容
    Vue展示word\excel\pdf内容1、前言项目使用的是若依框架,有一个审核的功能,审核弹窗中需要显示出上传的文件内容,而没有原生的组件可以实现该功能。2、遇到的问题使用iframe组件、不报错,但最后实现的效果是下载而不是展示出来项目是vue2的版本使用@vue-office,直......
  • vue3 类组件装饰器模式配置
    2024年10月31日vue3支持装饰器模式插件借助插件vue-facing-decorator实现类组件装饰器转换npminstall--save-devvue-facing-decorator@rollup/plugin-babel@babel/plugin-proposal-decorators@babel/plugin-proposal-class-propertiesvite.config.ts配置//第一种支......
  • vue2之页面生成PDF导出并适应A4页面
    一、技术vue2 、 elementUI、html2canvas  、jsPDF二、技术官网vue2:https://cn.vuejs.org/elementUi:https://element.eleme.cn/#/zh-CNhtml2canvas:https://html2canvas.hertzen.com/jsPDF:https://www.npmjs.com/package/jspdf三、优缺点优......
  • java+vue计算机毕设冬季供热有限公司网站建设【开题+程序+论文+源码】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着冬季气温的逐渐降低,供热服务成为了城市居民生活中不可或缺的一部分。冬季供热有限公司作为城市供热的主要提供者,承担着保障居民温暖过冬的重要职......
  • java+vue计算机毕设第二课堂学分认定系统【开题+程序+论文+源码】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在高等教育日益重视综合素质培养的今天,第二课堂作为第一课堂的有效补充,其在拓宽学生知识面、提升实践能力、增强综合素质等方面发挥着不可替代的作用......
  • vue3知识点:reactive对比ref
    @目录二、常用CompositionAPI5.reactive对比ref本人其他相关文章链接二、常用CompositionAPI问题:啥叫“组合式API”?答案:请看官方文档:https://v3.cn.vuejs.org/guide/composition-api-introduction.html5.reactive对比ref从定义数据角度对比:ref用来定义:基本类型......
  • vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
    @目录一、vue2、vue3、vue-cli版本、vue-router版本的关联关系1.说明2.不同版本的vue对应的vue-router版本和vuex版本二、创建Vue3.0工程1.使用vue-cli创建2.使用vite创建一、vue2、vue3、vue-cli版本、vue-router版本的关联关系1.说明1.VueCLI4.5以下,对应的是Vue2;Vue......
  • Ant Design Vue 的 a-table 行选择分页时bug处理
    有bug的伪代码如下(示例以id来作为rowKey):<a-table:row-selection="{selectedRowKeys:selectedRowKeys,onChange:onSelectChange}":columns="columns"rowKey="id":pagination="pagination":data-source=&q......