需求分析
制作一个向客户展示事务所基本信息和概况、分享资讯信息的网站,网站设立七个栏目:首页、公司介绍、专业服务、财税资讯、法律法规、税务答疑、联系我们,其中“联系我们”使用一个弹出框展示信息,其余的栏目都对应一个独立的分页,每个分页都划分顶部导航区、中间主体区、底部版权信息区。首页主体部分由轮播图和精选信息区域组成,公司介绍和专业服务分页存放静态的介绍信息,其余的栏目采用相同的样式呈现文章且要求用户点击文章标题跳转至详情页查看文章内容。
前端部分
项目资源结构
项目基于vue-cli进行搭建,在vue-cli的可视化创建界面中额外勾选router选项使项目自带路由相关的文件。
使用vscode打开存放在“Vue”文件夹下的项目文件,删除自带的“Home”、“About”组件及其引用语句,根据需求分析在view文件夹下创建六个.vue文件作为栏目的分页文件、在components文件夹下创建两个.vue文件作为每个分页复用的顶部、底部组件;每个vue文件的命名采用其对应栏目的拼音进行驼峰命名,vue文件中分出template、style、script模块。最终的项目资源结构如下:
搭建路由并引入依赖文件
因为vue-cli已经创建了路由相关的文件并在入口文件main.js中引用,则可以直接在router文件夹下的index.js文件中定义路由(在routes中添加路由对象{path:'',name:'',component:''}),最后在App.vue的模板模块中加入<router-view></router-view>使路由生效
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'shouye',
component: () => import('../views/ShouYe.vue')
},
{
path: '/jieshao',
name: 'jieshao',
component: () => import('../views/JieShao.vue')
},
{
path: '/fuwu',
name: 'fuwu',
component: () => import('../views/FuWu.vue')
},
{
path: '/fagui',
name: 'fagui',
component: () => import('../views/FaGui.vue')
},
{
path: '/zixun',
name: 'zixun',
component: () => import('../views/ZiXun.vue')
},
{
path: '/dayi',
name: 'dayi',
component: () => import('../views/DaYi.vue')
}
]
const router = new VueRouter({
routes
})
export default router
此外,因为希望使用element组件库中的组件进行页面的搭建,还需要在入口文件中引入相关的文件 使其全局引用
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Components组件
顶部区域组件
各页面的顶栏区域包括两个版块:logo板块、导航栏板块
logo版块采用常规的html+css的模式进行布局,除常规的元素宽、高、外边距设置外,使用vertical-align属性使logo图标垂直居中
<!--logo版块-->
<template>
<div class="logo">
<img src="../assets/yaxinlogo.png" class="img_logo0">
<img src="../assets/logotext.png" class="img_logo1">
</div>
</template>
<style>
.logo{
height: 100px;
}
.img_logo0{
width: 90px;
height: 90px;
vertical-align: middle;
}
.img_logo1{
width: 160.8px;
height: 67.8px;
vertical-align: middle;
margin-left: 12px;
}
</style>
导航栏模块引用element-ui组件库中的横向导航栏元素,该模块分为外层的导航栏层el-menu和内层的导航元素层el-menu-item(el-submenu是特殊的导航元素,它提供一个内层的弹出栏展示“联系我们”栏目的信息)
在el-menu的设置中保持基本的属性不变,修改导航栏的背景颜色(background-color)为蓝色,使用“:”绑定选中导航元素的索引index和路由跳转(“:”同“v-bind”),使用flex布局使所有导航元素水平居中
一个el-menu-item对应一个栏目的导航元素,将其中index的值替换为对应栏目的路径使$router.path能正常跳转到对应分页,增加一个禁用且无文本的el-menu-item标签作为导航元素间的边距;最后使用el-submenu来实现弹出栏的功能,模板标签中“slot=title”属性使其包裹的文本在导航元素中展示,el-menu-item作为el-submenu的子元素在弹出栏中展示
<!--导航栏模块-->
<template>
<el-menu
:default-active="$route.path"
class="el-menu-demo"
mode="horizontal"
background-color="#006abf"
text-color="#fff"
style="display: flex;justify-content: center;"
active-text-color="#ffd04b">
<el-menu-item index="/">首页</el-menu-item>
<el-menu-item index="10" disabled> </el-menu-item>
<!--此处省略其它栏目的item标签-->
<el-submenu index="21">
<template slot="title">联系我们</template>
<el-menu-item index="21-1"><div class="text-2">电话:</div></el-menu-item>
<el-menu-item index="21-2"><div class="text-2">传真:</div></el-menu-item>
<el-menu-item index="21-3"><div class="text-2">Email:</div></el-menu-item>
</el-submenu>
</el-menu>
</template>
顶部组件效果图:
底部区域组件
底部区域包括栏目导航、版权声明、备案超链接,使用html+css完成组件的布局
栏目导航使用router-link标签绑定跳转路径,整体的信息区域通过修改css实现水平居中、宽高适中、边距合适的效果
<template>
<div class="bottom">
<div class="bottom-link">
<a><router-link to="/jieshao">公司介绍</router-link></a>
|
<a><router-link to="/fuwu">专业服务</router-link></a>
|
<a><router-link to="/fagui">法律法规</router-link></a>
|
<a><router-link to="/zixun">财税资讯</router-link></a>
|
<a><router-link to="/dayi">税务答疑</router-link></a>
<div class="link-img"></div>
</div>
<div class="bottom-link1">
<p>亚信税务师事务所有限公司 版权所有 2024-2025</p>
<p>地址: 邮政编码: </p>
<a href="https://beian.miit.gov.cn/#/Integrated/recordQuery">备案号1</a>
<a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=">备案号2</a><!--recordcode后拼接备案号能在跳转页面自动填充-->
</div>
</div>
</template>
<style>
.bottom{
height: 100;
}
.bottom-link{
height: 30px;
text-align: center;
padding-top: 11px;
}
.bottom-link a{
text-decoration: none;
height: 18px;
line-height: 18px;
color: black;
}
.bottom-link1{
height: 50px;
text-align: center;
margin: 0;
padding: 0;
}
.bottom-link1 a{
text-decoration: none;
line-height: 18px;
color: black;
}
.bottom-link1 p{
margin: 0;
}
.link-img{
width: 500px;
height: 1px;
margin-top: 5px;
margin-left: auto;
margin-right: auto;
background-color: black;
}
</style>
底部组件效果图:
各栏目分页引入components组件
在各栏目分页中首先使用element组件库的布局容器进行区域划分(head+main+footer),在script模块中引入组件并将组件标签填入布局的头部和底部区域
<template>
<el-container>
<el-header height="162px" style="padding: 0;"><DaoHang></DaoHang></el-header>
<el-main height="600px" style="padding: 0;">
</el-main>
<el-footer height="100px"><DiBu></DiBu></el-footer>
</el-container>
</template>
<script>
import DaoHang from '@/components/DaoHang.vue';
import DiBu from '@/components/DiBu.vue'
</script>
首页栏目
首页的主体部分包括轮播图和精选资讯,轮播图的动态效果需要引入js进行控制实现;第一步先完成页面内容的静态实现:轮播图区域分为外层背景区和内层展示区,背景区设置合适的高度和100%比例的宽度并以灰色为背景颜色,展示区以背景区相同的高度水平居中;展示区内含图片列表和切换按钮列表,通过在css中设置position属性使图片模块和按钮模块重叠展示。精选资讯区包含两个竖向排列的列表,为两列表设置合适宽、高、间隔、背景、字体
<!--html-->
<div class="content0">
<div class="banner-bck">
<div class="banner">
<span style="width: 9px;height:100%;background-color: blue;display: inline-block;margin-right: 33px;"></span>
<ul id="banner-img">
<li style="z-index: 800;display: block;"><img src="../assets/banner0.png"></li>
<li style="z-index: 900;display: none;"><img src="../assets/banner4.png"></li>
<li style="z-index: 900;display: none;"><img src="../assets/banner1.png"></li>
<li style="z-index: 900;display: none;"><img src="../assets/banner2.png"></li>
<li style="z-index: 900;display: none;"><img src="../assets/banner3.png"></li>
</ul>
<ul id="banner-button">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<span style="width: 9px;height: 100%;background-color: blue;display: inline-block;margin-left: 38px;"></span>
</div>
</div>
<div class="content_seq">
<div class="seq0">
<div class="seq-title0">专业服务</div>
<ul class="point0" style="padding-inline-start: 0px;">
<li><a>涉税鉴证/纳税审核</a></li>
<li><a>税务咨询</a></li>
<li><a>税收策划</a></li>
<li><a>税务代理</a></li>
<li><a>涉税培训</a></li>
<li><a>税务风险管理</a></li>
</ul>
</div>
<div class="seq1">
<div class="seq-title0">财税资讯</div>
<ul style="padding-inline-start: 0px;">
<li><a>《会计法(2017)》VS《会计法(2024)》</a></li>
<li><a>《中华人民共和国公司法》注册资本登记管理制度的规定</a></li>
<li><a>某税管员被判玩忽职守罪,有点冤</a></li>
<li><a>企业土地被收回,补偿款免征土增税的条件竟如此之“高”</a></li>
<li><a>又一财务造假</a></li>
<li><a>致全体纳税人、缴费人的一封信</a></li>
</ul>
</div>
</div>
</div>
<!--css-->
<style>
.content0{
height: 900px;
font-size: 0;
}
.banner{
width: 800px;
height: 400px;
margin-left: auto;
margin-right: auto;
white-space: nowrap;
position: relative;
}
.banner-bck{
width: 100%;
height: 400px;
background-image: url('../assets/backgroud0.jpg');
background-repeat: repeat-x;
}
#banner-img{
position: relative;
width: 760px;
height: 400px;
display: inline-block;
padding: 0;
}
#banner-button{
left: 56px;
bottom: 33px;
position: absolute;
z-index: 9900;
}
#banner-button li{
background-image: url('../assets/button0.jpg');
background-repeat: no-repeat;
float: left;
list-style-type: none;
width: 12px;
height: 12px;
margin-right: 5px;
cursor: pointer;
}
#banner-button li.current{
background-image: url('../assets/button1.jpg');
background-repeat: no-repeat;
}
.content_seq{
width: 860px;
height: 400px;
margin-left: auto;
margin-right: auto;
}
.seq0{
width: 48%;
height: 380px;
float: left;
margin-left: 1%;
}
.seq0 ul li{
background-color: #f8f9fc;
border-bottom: 1px solid #dedede;
border-bottom-style: dashed;
height: 40px;
}
.seq0 ul li a{
background-image: url('../assets/point0.png');
background-repeat: no-repeat;
background-position-y: center;
padding-left: 23px;
padding-top: 10px;
text-decoration: none;
height: 31px;
display: block;
font-size: 13px;
}
.seq1{
width: 48%;
height: 380px;
float:right;
}
.seq1 ul li{
background-color: #f8f9fc;
border-bottom: 1px solid #dedede;
border-bottom-style: dashed;
height: 40px;
}
.seq1 ul li a{
background-image: url('../assets/point0.png');
background-repeat: no-repeat;
background-position-y: center;
padding-left: 23px;
padding-top: 10px;
text-decoration: none;
height: 31px;
display: block;
font-size: 13px;
}
.seq-title0{
height: 31px;
background-image: url('../assets/blue.png');
background-repeat: no-repeat;
background-position-y: center;
border-bottom: 2px solid blue;
padding-left: 25px;
padding-top: 6px;
font-size: 16px;
color: blue;
}
</style>
静态效果:
轮播图的动态效果(自动轮播、点击切换)需要依赖js控制,在src目录下新建js目录并在js目录下新建banner.js文件;banner.js中导出一个Button_类,类内定义有与动态轮播相关的系列参数、初始化方法、自动切换定时器、切换事件
export class Button_{
constructor(option){//构造方法
this.$option=option;
this.$current=0;//轮播索引的计数
this._init();
this._button();
this._buttonEvent();
}
_init(){//使用css选择器获取图片、按钮列表的子控件
this.img=document.querySelector(this.$option.img).children;
this.button=document.querySelector(this.$option.button).children;
}
_button(){//自动轮播方法
this.button[this.$current].className='current';
setInterval(() => {
this.button[this.$current].className='';//当前按钮还原为未选中
this.img[this.$current].style='display:none'//隐藏当前图片
this.$current++;//索引计数加1
if(this.$current>=4)//索引计数大于等于图片数时还原为0
{
this.$current=0;
}
this.button[this.$current].className='current'//下一个按钮置为选中
this.img[this.$current].style='display:block'//显示下一张图片
}, 3000);
}
_buttonEvent(){//点击按钮切换事件
[...this.button].forEach((item,index)=>{//扩展符和迭代器获取按钮的实例
item.onclick=()=>{//点击按钮触发箭头函数
this.button[this.$current].className='';
this.img[this.$current].style='display:none';
this.$current=index;//将轮播索引记为选中按钮的索引
this.button[this.$current].className='current'
this.img[this.$current].style='display:block'
}
})
}
}
在首页的vue文件中引入js文件,定义一个实例化Button_类的move方法,向Button_的构造参数option传入css选择器所需的图片列表id、按钮列表id,在vue的生命周期钩子函数mounted(挂载完毕触发)中使用move方法
<script>
import {Button_} from '@/js/banner';
export default{
methods:{
move(){
new Button_(
{
img:'#banner-img',
button:'#banner-button'
}
)
}
},
mounted(){
this.move()
}
}
</script>
公司介绍栏目
公司介绍栏目的主体为简单样式的文字介绍,使用<p>标签包裹段落内容、用<strong>标签包裹标题、用 格式化文本,设置行高、字体、内容居中等样式
<div class="content1">
<div class="head">
<div class="left">
<div class="text-left">公司介绍</div>
</div>
</div>
<div class="main-text">
<!--省略部分重复内容-->
<strong style="font-size: 14px;">服务质量</strong>
<p style="font-family: 宋体;text-indent: 24px;line-height: 24px;">
我们始终坚持以市场为导向,以客户为中心,“忠诚服务、笃守信誉”是我们的服务宗旨。我们将服务质量视为生命线,紧跟新税改步伐,通过不断创新服务模式,力求在规模上不断突破,以实现社会效益和经济效益的稳步增长。</p>
</div>
</div>
专业服务栏目
专业服务栏目的主体为存放垂直排列的多个存放服务内容的容器,将class="contain0"的div元素作为此容器,通过css样式使容器具有合适的宽高、背景、阴影,在每个容器内存放 一个服务内容版块的标题和正文,使用pre标签包裹正文使其具备输入的格式
<div class="content-fu">
<div class="fu-head">
<img src="../assets/blue.png" style="margin-top: 2px;">
专业服务
</div>
<div class="contain0">
<div class="fuwu-title"><div class="title-text0">纳税审核</div></div>
<pre class="fuwu-contain" style="width: 580px;margin-left: 50px;font-size: 15px;">
企业所得税汇算清缴、资产损失、研发费加计扣除鉴证
房地产企业完工产品企业所得税清算、土地增值税清算鉴证
企业税务注销清算鉴证
全税种纳税审核
日常纳税情况健康检查
</pre>
</div>
<!--省略其它内容容器-->
</div>
<style>
.content-fu{
margin-left: auto;
margin-right: auto;
width: 880px;
height: 800px;
}
.fu-head{
height: 44px;
margin-top: 10px;
padding-top: 40px;
border-bottom: 1px solid blue;
}
.contain0{
width: 750px;
height: 280px;
background-image: url('../assets/box0.jpg');
background-position-x: 150px;
background-position-y: 30px;
background-repeat: no-repeat;
background-size: 580px 380px;
overflow: hidden;
}
.fuwu-title{
height: 30px;
width: 580px;
background-image: url('../assets/blue.png');
background-repeat: no-repeat;
margin-left: 165px;
margin-top: 40px;
}
.title-text0{
margin-left: 25px;
}
</style>
文章栏目(财税资讯、法律法规、税务答疑)
文章栏目的主体为栏目标题和文章信息列表(含分页), 列表内容通过axios向服务器接口异步请求文章信息(标题、时间、详情页url)进行渲染
<div class="zixun-content">
<div class="zixun-title">
<div style="margin-top: 8px;">税务咨询</div>
</div>
<div class="zixun-list">
<ul class="news0" style="list-style: none;padding: 0;">
<!--这里渲染文章信息列表-->
</ul>
</div>
<el-pagination
background="blue"
layout="prev, pager, next"
@current-change="handleCurrentChange"
@prev-click="handleCurrentChange"
@next-click="handleCurrentChange"
:total="total"
align="center">
</el-pagination>
</div>
<!--css-->
<style>
.zixun-content{
height: auto;
}
.zixun-title{
width: 850px;
height: 50px;
padding-top: 23px;
padding-left: 20px;
margin-left: auto;
margin-right: auto;
background-image: url('../assets/blue.png');
background-repeat: no-repeat;
background-position-y: 30px;
border-bottom: 1px solid blue;
}
.zixun-list{
width: 850px;
height: 800px;
padding: 25px 0 25px 10px;
margin-left: auto;
margin-right: auto;
}
.zixun-list ul li{
width: 800px;
height: 35px;
background-image: url('../assets/point0.png');
background-repeat: no-repeat;
background-position-y: 8px;
padding-left: 15px;
text-align: right;
margin-bottom: 15px;
}
.zixun-list ul li a{
display: block;
max-width: 420px;
color: #666;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-decoration: none;
float: left;
}
</style>
文章信息列表的渲染有两个阶段,初始化阶段和用户点击分页按钮响应阶段;初始化阶段需要向后端获取文章的总数,根据数量确定初始状态时获取哪些索引的文章;用户点击分页按钮时利用编写的计算函数,根据总数、分页索引、每页文章条目三个参数计算出写入sql语句【select * from xxx limit x,y]的x,y,携带计算的数据向后端接口请求
export default{
data(){
return {
total:10//通过v-bind绑定到分页按钮列表的文章总数数据
}
},
methods:{
unchar:function(arr){//对象数组反转函数,请求到的文章数据是从早到晚排列的故需反转
var newarr=[];
arr.forEach(element => {
newarr.unshift(element)
});
return newarr
},
initlist:function(){//初始化函数
if (this.total<10){//总数小于单页总数时请求所有文章
axios({url:'xxx/zixung',method:'get'}).then((result)=>{
const newslist=this.unchar(result.data.data);//反转对象数组,使用常量能节省性能
const htmlstr=newslist.map((item)=>{
return `<li><a href="${item.href}">${item.title}</a>${item.time}</li>`
}).join('')
document.querySelector('.news0').innerHTML=htmlstr
})
}
else{//总数大于单页总数时请求数据库表的后x条数据
var start=this.total - 10;
var end=10;
axios({url:'xxx/zixunpage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
const newslist=this.unchar(result.data.data);
const htmlstr=newslist.map((item)=>{
return `<li><a href="${item.href}">${item.title}</a>${item.time}</li>`
}).join('')//将完成处理的列表元素转化为字符串
document.querySelector('.news0').innerHTML=htmlstr
})
}
},
gettotal:function(){
axios.get('xxx/zixunt').then((result)=>{
const totallist=result.data.data;
this.total=totallist.total;
console.log(this.total);
})
},
handleCurrentChange(cpage){//切换分页的响应函数,参数为分页索引
if (cpage==Math.ceil(this.total/10)){//当前页为最后一页时
var start=0//请求的文章为0条之后总数的余数条文章
var end=this.total%10
axios({url:'xxx/zixunpage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
const newslist=this.unchar(result.data.data);
const htmlstr=newslist.map((item)=>{
console.log(item.title)
return `<li><a href="${item.href}">${item.title}</a>${item.time}</li>`//使用反引号可插入模板字面量
}).join('')
console.log(htmlstr)
document.querySelector('.news0').innerHTML=htmlstr
})
}
else{
start=this.total-10*cpage;
end=10
axios({url:'xxx/zixunpage',method:'post',data:{'start':start,'end':end}}).then((result)=>{
const newslist=this.unchar(result.data.data);
console.log(newslist)
const htmlstr=newslist.map((item)=>{
console.log(item.title)
return `<li><a href="${item.href}">${item.title}</a>${item.time}</li>`
}).join('')
console.log(htmlstr)
document.querySelector('.news0').innerHTML=htmlstr
})
}
}
},
mounted(){//构造函数在组件挂载完成时调用文章的初始化和请求总数方法
this.initlist();
this.gettotal();
}
}
通过Apifox的本地Mock环境测试axios请求
将axios请求的url替换为mock的url,测试文章信息列表及其分页的渲染情况
后端部分
项目资源结构
后端需要实现的功能包括向前端返回全部/指定分页的文章信息,将文章内容动态渲染到新页面上(文章详情页)。后端项目使用maven进行依赖管理,基于springboot、mybatis开发返回数据库中文章数据给前端、响应前端动态生成的详情页的接口;
项目目录中构建与启动类同级的三层目录:响应控制层(Controller)、服务端调用层(Sevice)、数据控制层(Mapper),以及存放接受/返回对象的pojo目录
添加依赖包
在pom.xml中添加thymeleaf和lombok的依赖,两个模块分别能实现html模板的动态渲染、pojo类冗余代码的减免
<!---dependencies块中添加-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!---maven-plugin的configuration块中添加-->
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
pojo类
项目中需要构建的pojo类有4个:用于传递文章信息的Article类、用于向前端返回响应结果的Result类、接收前端分页索引的Page类、返回文章总数的Total类;类中使用了lombok提供的@Data等注解免于编写一些冗余的代码
package com.inxay.api1.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
private Integer id;//文章在数据库表的id
private String title;
private String content;//文章正文
private String time;
private String href;//文章详情页的url
}
package com.inxay.api1.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private int code;//响应代码
private String message;//报错信息
private Object data;//返回数据
public static Result success(Object data) {//成功请求,返回数据
return new Result(200,"success",data);
}
public static Result success() {//成功请求,不返回数据
return new Result(200,"success",null);
}
public static Result error(String message) {//拒绝请求,返回原因
return new Result(500,message,null);
}
}
Page类和Total类只分别定义了索引数据、文章总数为私有变量,在此省略。
mybatis配置和html模板配置
在resources目录下的.properties文件中进行数据库链接的配置,内容包括数据库驱动、数据库名、用户名、密码
spring.application.name=Api1
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://xxx
spring.datasource.username=xxx
spring.datasource.password=xxx
在另一模块的resources-template目录下新建aerticle.html作为文章详情页的模板,基于html+css搭建模板的基本页面并分出标题、时间、内容区域,使用thymeleaf的语法<div th:text="">、<td th:utext="">(可传递富文本编辑器html格式的内容)与模板字面量${xxx}实现对模板三个区域内容的动态控制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title th:text="${title}"></title>
<style>
.contain{
width: 800px;
height: auto;
margin-left: auto;
margin-right: auto;
background: #fff;
box-shadow: 0 2px 9px 0 rgba(0,0,0,.1);
}
.article-title{
font-size: 1.24444rem;
font-weight: 700;
color: #343434;
text-align: center;
}
.article-time{
font-size: 13px;
line-height: 17px;
color: rgba(34,34,34,.5);
text-align: center;
}
.article-content{
width: 700px;
height: auto;
margin-left: auto;
margin-right: auto;
text-indent: 1.6rem;
font-size: .8rem;
color: #222;
margin-top: 32px;
line-height: 32px;
word-break: break-all;
word-wrap: normal;
white-space: pre-wrap;
overflow: hidden;
}
</style>
</head>
<body>
<div class="contain">
<div class="article-title" th:text="${title}">
</div>
<div class="article-time" th:text="${time}">
</div>
<div class="article-content">
<td th:utext="${content}">
</td>
</div>
</div>
</body>
</html>
数据处理层
在两模块Mapper目录下新建一个接口,接口内使用@Mapper注解将接口托管给IOC容器并定义从数据库获取文章数据的方法:1、获取全部文章数据的方法;2、根据分页索引(从第几条开始,取几条)获取文章数据的方法;3、根据路径参数id获取指定文章数据的方法;
package com.inxay.api1.Mapper;
import com.inxay.api1.pojo.Article;
import com.inxay.api1.pojo.Login;
import com.inxay.api1.pojo.Page;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface ArticleMapper {
@Select("select * from yaxin.zixun")
public List<Article> getzixun();
//省略两个文章栏目的方法
@Select("select * from yaxin.zixun limit #{start},#{end}")
public List<Article> getzixunpage(Page page);
//省略两个文章栏目的方法
import com.inxay.api2.pojo.Article;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface ArticleMapper {
@Select("select * from yaxin.zixun where id=#{id}")//使用mybatis提供的操作数据库的注解并拼写sql语句
public List<Article> getarticlez(int id);//返回值为泛型为Article类的列表
@Select("select * from yaxin.fagui where id=#{id}")
public List<Article> getarticlef(int id);
@Select("select * from yaxin.dayi where id=#{id}")
public List<Article> getarticled(int id);
}
服务端调用层
在Service目录下新建一个接口和一个实现类,接口中定义调用数据处理层方法的方法、实现类重写这些方法;实现类写入 @Service注解托管给IOC容器、使用@Autowired注解从容器中获得数据处理层的接口,在重新方法时调用该接口
import com.inxay.api1.pojo.Article;
import com.inxay.api1.pojo.Login;
import com.inxay.api1.pojo.Page;
import java.util.List;
public interface AllService {//省略两个栏目的相同方法
List<Article> getzixun();//获取全部文章的方法
List<Article> getzixunpage(Page page);//根据分页索引获取文章的方法
int gettotalz();//获取文章总数的方法
//获取文章各区域内容的方法
public String gettitlez(int id);
public String gettimez(int id);
public String getcontentz(int id);
import com.inxay.api1.Mapper.ArticleMapper;
import com.inxay.api1.pojo.Article;
import com.inxay.api1.pojo.Login;
import com.inxay.api1.pojo.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AllServiceimpl implements AllService{
@Autowired
private ArticleMapper articleMapper;
@Override
public List<Article> getzixun(){
return articleMapper.getzixun();
}
@Override
public int gettotalz(){
int totalz = articleMapper.getzixun().size();
return totalz;
}
@Override
public List<Article> getzixunpage(Page page){
return articleMapper.getzixunpage(page);
}
@Override
public String gettitlez(int id) {
List<Article> articles = articleMapper.getarticlez(id);
return articles.get(0).gettitlea();
}
@Override
public String gettimez(int id) {
List<Article> articles = articleMapper.getarticlez(id);
return articles.get(0).gettimea();
}
@Override
public String getcontentz(int id) {
List<Article> articles = articleMapper.getarticlez(id);
return articles.get(0).getcontenta();
}
响应控制层
在模块一的响应类中添加@RestController注解使其能向前端返回json数据,在模块二的响应类中添加@Controller注解使其能向前端返回动态渲染的html模板;
import com.inxay.api1.pojo.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin//解决不允许跨域的问题
public class WebController {
@Autowired
private AllService allService;//获取服务端调用层的接口
@GetMapping("/zixung")
public Result getzixun(){
List<Article> data = allService.getzixun();//获取全部文章数据
return Result.success(data);//向前端返回文章对象列表(json)
}
@GetMapping("/zixunt")
public Result gettotalz(){
int total = allService.gettotalz();//获取文章总数
Total new_total = new Total();
new_total.setTotal(total);//设置Total类的私有变量total
return Result.success(new_total);
}
@PostMapping("/zixunpage")
public Result getzixunpage(@RequestBody Page page){//page为请求体数据
List<Article> msg=allService.getzixunpage(page);
return Result.success(msg);
}
import com.inxay.api2.Service.ArticleServiceimpl;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
@CrossOrigin
public class IndexController {
@Autowired
private ArticleServiceimpl articleServiceimpl;
@GetMapping("/articlez/{id}")
String article0(HttpServletRequest request, @PathVariable int id){
String title= articleServiceimpl.gettitlez(id);//获取指定文章各区域的内容
String time= articleServiceimpl.gettimez(id);
String content=articleServiceimpl.getcontentz(id);
request.setAttribute("title", title);//设置article.html的模板字面量
request.setAttribute("time", time);
request.setAttribute("content", content);
return "article";//返回完成渲染的模板
}
通过Apifox的测试环境测试接口
通过项目启动类在本地的8080端口启动项目(内置tomcat),在Apifox的测试环境下(已配置本地url)请求各接口,返回响应码200即为请求成功
项目打包部署
后端项目
maven提供包管理的系列插件,可以根据需求在pom.xml内修改打包的配置
<groupId>com.inxay</groupId>
<artifactId>Api1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Api1</name>
<description>Api1</description>
使用maven生命周期插件中的clean和package,在项目目录下生成target文件夹,在文件夹内获取到打包成功的.war文件
将.war文件上传到宝塔面板的www/wwwroot目录下,选择添加java项目并选中war的项目路径 ,添加项目端口并在安全组中放行,添加接口域名
前端项目
在开始打包前先将异步请求的路径从mock路径改为正式环境的路径
vue-cli自动创建了package.json文件,选中该文件的npm脚本;脚本的build选项提供了打包功能,打包成功会在项目目录下生成dist文件夹
在宝塔面板添加纯静态的php站点,配置官网域名,将dist文件夹上传到站点目录下;修改网站目录为xxx/dist,运行目录为”/“
配置伪静态
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.html?s=/$1 last;
break;
}
}
在公网查验项目
查验的内容主要包括首页轮播图的效果、网页的静态展示效果、文章信息的展示和分页按钮能否正常使用、文章详情页的使用效果