在本篇文章中,我们将带你逐步实现一个完备的小说网站项目,技术栈包括Spring Boot、MongoDB、Vue 2和Nginx。
额外附加:
2024年6月14日10:53:47
- 增加了搜索页面
- 小说的详细页面
- 章节内容页面
当你遇到问题可以尝试来这里解决问题
package.json
如果你的 “dependencies”和我的不一样,说明你vue并没有按照我的勾选router,自己去其他地方安装router
{
"name": "vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^1.7.2",
"core-js": "^3.8.3",
"echarts": "^5.5.0",
"element-ui": "^2.15.14",
"vue": "^2.6.14",
"vue-echarts": "^6.7.3",
"vue-router": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
1. 项目概述
我们将实现一个基本的小说网站,包含以下主要部分:
- 后端API:使用Spring Boot实现,负责处理数据和业务逻辑。
- 数据库:使用MongoDB存储小说数据。
- 前端页面:使用Vue 2实现,负责展示数据和用户交互。
- 反向代理:使用Nginx进行前后端分离。
2. 环境和依赖
2.1 后端 - Spring Boot
Spring Boot 3.x
Spring Data MongoDB
Spring Web
2.2 数据库 - MongoDB
- 安装MongoDB社区版
2.3 前端 - Vue 2
Vue CLI
Axios
(用于HTTP请求)
3. 项目结构
在这一步,我们将创建项目的基本目录结构:
novel-website
├── novel(Spring Boot项目)
├── frontend (Vue项目)
└── nginx (Nginx配置)
4. 后端开发
数据准备:可以去 https://blog.csdn.net/iku_n/article/details/139509931 这里有爬虫的代码,我使用的是改版,并且是直接把数据导入到mongodb里面
首先,我们创建Spring Boot项目,并添加相关依赖。
4.1 创建Spring Boot项目
使用Spring Initializr创建项目
注意选择Java和Maven
4.2 引入pom文件
这里直接复制粘贴我的就行 (高手无视即可)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot</name>
<description>springboot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<!-- poi依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 引入mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.1.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--json依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.3 写yml文件
先在这个位置创建一个yml文件
server:
port: 9099
spring:
data:
mongodb:
host: localhost
port: 27017
database: novel_database
4.4 编写实体类、仓库和服务
首先在Java下面创建一个com.sqm.model的文件包
构建启动类
启动类: NovelApplication
package com.sqm;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 功能: 启动类
* 作者: 沙琪马
* 日期: 2024/6/7 12:13
*/
@SpringBootApplication
public class NovelApplication {
public static void main(String[] args) {
SpringApplication.run(NovelApplication.class, args);
}
}
创建一个小说实体类:
实体类: Novel
package com.sqm.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.List;
import java.util.Map;
/**
* 功能: 小说模型
* 作者: 沙琪马
* 日期: 2024/6/7 12:10
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "novels")
public class Novel {
@Id
private String id;
private String title;
private String type; // 小说类型
private String author;
private String updateTime;
@Field(name = "jianjie")
private String intro;
private String imgUrl;
@Field(name = "zhangjie")
private List<Map<String, String>> chapter;
}
创建一个小说服务类
服务类:NovelService
package com.sqm.service;
import com.sqm.model.Novel;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Random;
/**
* 功能:
* 作者: 沙琪马
* 日期: 2024/6/7 12:35
*/
@Service
@Slf4j
public class NovelService {
@Resource
private MongoTemplate mongoTemplate;
public List<Novel> getNovels() {
// 1.生成七个随机数,随机获取7本书
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.sample(7)
);
return mongoTemplate.aggregate(aggregation, "novels", Novel.class).getMappedResults();
}
public Page<Novel> getNovelsByType(String type, int page, int size) {
// 1.构建查询条件
Query query = new Query(new Criteria("type").is(type));
long total = mongoTemplate.count(query, Novel.class);
// Apply pagination
Pageable pageable = PageRequest.of(page, size);
query.with(pageable);
// 2.返回条件
List<Novel> novels = mongoTemplate.find(query, Novel.class);
return new PageImpl<>(novels, pageable, total);
}
public Page<Novel> getNovelsByName(String name, int page, int size) {
// 1.构建查询条件
Query query = new Query(new Criteria("title").regex(name));
long total = mongoTemplate.count(query, Novel.class);
Pageable pageable = PageRequest.of(page, size);
query.with(pageable);
List<Novel> novels = mongoTemplate.find(query, Novel.class);
return new PageImpl<>(novels, pageable, total);
}
}
4.5 创建控制器
创建一个控制器类来处理HTTP请求:
控制类: NovelController
package com.sqm.conntroller;
import com.sqm.model.Novel;
import com.sqm.service.NovelService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 功能:
* 作者: 沙琪马
* 日期: 2024/6/7 12:40
*/
@RestController
@RequestMapping("novels")
@Slf4j
public class NovelController {
@Resource
private NovelService novelService;
@GetMapping
public List<Novel> getNovels() {
return novelService.getNovels();
}
@GetMapping("/first")
public Page<Novel> getNovelsByType(@RequestParam("type")String type,
@RequestParam("page")int page,
@RequestParam("size")int size) {
log.info("查询小说类型为:{}", type);
return novelService.getNovelsByType(type, page, size);
}
@GetMapping("/sou")
public Page<Novel> getNovelsByName(@RequestParam("name")String name,
@RequestParam("page")int page,
@RequestParam("size")int size) {
log.info("查询小说名称为:{}", name);
return novelService.getNovelsByName(name, page, size);
}
}
5. 前端开发
使用Vue CLI创建前端项目:
vue create frontend
注意:别跑错文件夹了,项目概述有前端的路径
按上下箭头,选择选最下面那个,回车是选择
然后选择下面这两个就足够了, 注意:空格是选择!!!
选2.x,千万不要选3.x,这两个区别非常大
其他选项如下:
创建完成后
5.1 安装依赖
安装axios用于HTTP请求:
cd vue
npm install axios
然后去 axios配置文件-CSDN博客 拷贝request.js
5.2 创建Vue组件
在src
目录下创建一个组件用于显示小说列表。
先整理一下目录
然后安装element ui
npm i element-ui -S
然后去main.js, 别迷路了
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import request from '@/utils/request'
Vue.config.productionTip = false
Vue.use(ElementUI, {size:'small'});
Vue.prototype.$request=request
new Vue({
router,
render: h => h(App)
}).$mount('#app')
然后改造App.vue
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer:{
port: 7070,
proxy: {
'/api': {
target: 'http://localhost:9099', // 目标服务器地址
changeOrigin: true, // 更改请求的源头
pathRewrite: { '^/api': '' }, // 重写路径
}
}
},
chainWebpack:config => {
config.plugin('html')
.tap(args => {
args[0].title = '沙琪马阅读网'
return args
})
}
})
5.3 路由
首先创建一个router的文件夹,在router文件夹中创建index.js文件(注:Js文件)
暂时先不写内容
5.4 开始写页面
先启动vue试试,记得是在vue的目录下
npm run serve
然后在components创建 CommonPage.vue文件
通用组件: CommonPage
<template>
<div class="card-container">
<el-card class="centered-card">
<el-table :data="tableData" stripe :header-cell-style="{backgroundColor: 'aliceblue'}" @row-click="details">
<el-table-column label="书名" prop="title" align="center"></el-table-column>
<el-table-column label="作者" prop="author" align="center"></el-table-column>
<el-table-column label="更新时间" prop="updateTime" align="center"></el-table-column>
</el-table>
<div class="block" style="margin: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5, 10, 15, 20]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</el-card>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
required: true
},
fetchUrl: {
type: String,
required: true
}
},
data() {
return {
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
}
},
created() {
this.load();
},
watch: {
type() {
this.pageNum = 1; // 重置分页为第一页
this.load();
},
fetchUrl() {
this.pageNum = 1; // 重置分页为第一页
this.load();
}
},
methods: {
details(row, event, column) {
this.$router.push({path: '/details', query: {row: JSON.stringify(row)}});
},
load() {
this.$request.get(this.fetchUrl, {
params: {
type: this.type,
page: this.pageNum,
size: this.pageSize
}
}).then(res => {
this.tableData = res.content;
this.total = res.totalElements;
}).catch(error => {
console.error('API request error:', error);
this.tableData = []; // 或根据需求初始化数据
this.total = 0;
});
},
handleSelectionChange(selection) {
console.log('Selection changed:', selection);
},
handleCurrentChange(pageNum) {
this.pageNum = pageNum;
this.load();
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.load();
}
}
}
</script>
<style scoped>
.card-container {
display: flex;
justify-content: center;
text-align: center;
}
.centered-card {
width: 50%;
margin-top: 5%;
}
</style>
然后在views目录下
创建HomeView.vue和manager目录
父组件:HomeView
<template>
<div>
<el-container>
<el-header class="nav-bar">
<div style="display: flex;justify-items: center;align-items: center">
<div style="display: flex;justify-items: center;align-items: center;width: auto">
<form method="get" target="_blank" action="/modules/article/search.php">
<el-input
style="width: 80%; margin-right: 1%;"
placeholder="输入少字也别输入错字"
type="text"
v-model="name"
></el-input>
<el-button type="primary" icon="el-icon-search" @click="getSou"></el-button>
</form>
</div>
<div style="flex: 1; width: 0;display: flex;align-items: center;justify-content: flex-end;">
<i class="el-icon-quanping" style="font-size: 26px;" @click="handleFull"></i>
<el-dropdown placement="bottom">
<div v-if="user" style="display: flex;align-items: center;cursor: default;">
<img :src="user.avatar" alt=""
style="border-radius:50%;width: 40px;height: 40px;margin:0 5px">
<span>{{ user.name }}</span>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="$router.push('/user')">
个人信息
</el-dropdown-item>
<el-dropdown-item @click.native="$router.push('/houtai')" v-if="user.avatar === '管理员'">
后台管理
</el-dropdown-item>
<el-dropdown-item @click.native="$router.push('/password')">修改密码</el-dropdown-item>
<el-dropdown-item @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div style="display: flex; justify-content: center; align-items: center">
<el-tabs v-model="activeName" @tab-click="handleClick" class="nav_cont">
<el-tab-pane label="首页" name="homePage"></el-tab-pane>
<el-tab-pane label="玄幻小说" name="first"></el-tab-pane>
<el-tab-pane label="修真小说" name="second"></el-tab-pane>
<el-tab-pane label="都市小说" name="third"></el-tab-pane>
<el-tab-pane label="历史小说" name="fourth"></el-tab-pane>
<el-tab-pane label="网游小说" name="wangyou"></el-tab-pane>
<el-tab-pane label="科幻小说" name="scienceFiction"></el-tab-pane>
<el-tab-pane label="我的书架" name="shujia"></el-tab-pane>
</el-tabs>
</div>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</div>
</template>
<script>
export default {
name: 'HomeView',
data() {
return {
name: '',
activeName: this.$route.name,
collapseIcon: 'el-icon-s-fold',
breadcrumbs: [],
user: JSON.parse(localStorage.getItem("honey-user")||'{}'),
};
},
watch: {
$route(to) {
this.activeName = to.name;
}
},
methods: {
handleClick(tab) {
if(tab.name === '我的书架'){
this.$router.push({path: '/shujia'})
return
}
if (tab.name !== this.$route.name) {
this.$router.push({name: tab.name});
}
},
getSou() {
this.$router.push({path: '/sou', query: {name: this.name}});
},
logout() {
localStorage.removeItem('honey-user') // 清除当前的token和用户的数据
this.$router.push('/login')
},
handleFull() {
document.documentElement.requestFullscreen()
},
},
mounted() {
this.activeName = this.$route.name;
}
}
</script>
<style scoped>
.el-header {
background-color: #B3C0D1;
color: #333;
text-align: center;
line-height: 60px;
}
.nav-bar {
background-color: #333;
}
::v-deep .el-tabs__item {
color: red !important; /* 修改标签页文字颜色 */
}
::v-deep .el-tabs__item.is-active {
color: blue !important; /* 修改活动标签页文字颜色 */
}
</style>
子组件:HomePage(首页)
<template>
<div class="wrapper">
<div class="cont">
<div class="left-cont">
<div class="ls-tit">
<h3>站长推荐</h3>
<div class="clear"></div>
</div>
<el-card class="recommend-card" shadow="hover">
<div class="ls-box">
<div class="shu-box" v-for="novel in novels" :key="novel.title">
<div @click="details(novel)">
<p class="p-img">
<a :href="novel.link" :title="novel.title">
<img :src="novel.imgUrl" :alt="novel.title">
</a>
</p>
<p class="line20 title"><a :href="novel.link" :title="novel.title">{{ novel.title }}</a></p>
<p class="line20 author">作者:{{ novel.author }}</p>
<p class="line20 update-time">{{ novel.updateTime }}</p>
</div>
</div>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
novels: [
{
title: '神印王座II皓月当空',
link: 'http://www.biqule.net/book/59265/',
imgUrl: 'http://www.biqule.net/files/article/image/59/59263/59263s.jpg',
author: '唐家三少',
updateTime: '3-9 16:33'
}
]
};
},
created() {
this.getNovel();
},
methods: {
getNovel() {
this.$request.get('/novels').then(res => {
this.novels = res;
});
},
details(row) {
this.$router.push({path: '/details', query: {row: JSON.stringify(row)}});
},
}
};
</script>
<style scoped>
/* Container and Layout Styles */
.wrapper {
padding: 20px;
background-color: #f8f8f8;
}
.cont {
display: flex;
justify-content: center;
background-color: #f4f4f4;
padding: 20px;
}
.left-cont {
flex: 1;
}
/* Title Styles */
.ls-tit h3 {
font-size: 24px;
margin-bottom: 15px;
color: #333;
}
/* Card Styles */
.recommend-card {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
background-color: #fff;
}
.ls-box {
display: flex;
flex-wrap: wrap;
gap: 20px;
width: 100%;
}
/* Novel Box Styles */
.shu-box {
flex: 1 1 220px;
background-color: #fff;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 10px;
transition: transform 0.2s ease;
}
.shu-box:hover {
transform: translateY(-10px);
}
/* Image Styles */
.p-img {
text-align: center;
margin-bottom: 15px;
}
.p-img img {
width: 100%;
max-width: 150px;
height: auto;
border-radius: 5px;
}
/* Text Styles */
.line20 {
line-height: 1.4;
margin: 5px 0;
}
.line20.title {
font-weight: bold;
font-size: 1.2em;
color: #333;
}
.line20.author,
.line20.update-time {
font-size: 0.9em;
color: #666;
}
.clear {
clear: both;
}
/* Footer Styles */
.footer {
background-color: #333;
color: #fff;
text-align: center;
padding: 20px 0;
margin-top: 20px;
}
</style>
其他组件(小说分类)
这些组件变化不大,我这里给出两个,其他的自己补充ok不?
<template>
<CommonPage fetchUrl="/novels/first" type="科幻小说" />
</template>
<script>
import CommonPage from '@/components/CommonPage.vue';
export default {
name: 'ScienceFictionTypeNovel',
components: {
CommonPage
}
}
</script>
<template>
<CommonPage fetchUrl="/novels/first" type="网游小说" />
</template>
<script>
import CommonPage from '@/components/CommonPage.vue';
export default {
name: 'WangyouTypeNovel',
components: {
CommonPage
}
}
</script>
子组件:details (小说详细)
小说的详细数据
<template>
<div style="margin-top: 5%">
<el-card>
<div slot="header">
<span>小说详情</span>
</div>
<div style="display: flex; align-items: center; justify-content: space-between;">
<div>
<p><strong>书名:</strong>{{ novel.title }}</p>
<p><strong>作者:</strong>{{ novel.author }}</p>
<p><strong>类型:</strong>{{ novel.type }}</p>
<p><strong>简介:</strong>{{ novel.intro }}</p>
<p><strong>更新时间:</strong>{{ novel.updateTime }}</p>
<el-button v-if="exist" type="danger" @click="yichu(novel)">移除书架</el-button>
<el-button v-else type="primary" @click="SetShujia(novel)">加入书架</el-button>
</div>
<div>
<img :src="novel.imgUrl" :alt="novel.title" style="width: 110px; height: 130px;">
</div>
</div>
</el-card>
<el-card style="width: 60%;margin-left: 20%;margin-top: 3%">
<div slot="header">
<span>目录</span>
</div>
<div class="chapter-container">
<div v-for="(item, index) in novel.chapter" :key="index" class="chapter-item">
<a @click="getNovel(item)"><strong>{{ item.chapter }}</strong></a>
</div>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: "details",
data() {
return {
novel: {}, // 初始化为空对象
exist: true
}
},
mounted() {
if (this.$route.query && this.$route.query.row) {
this.novel = JSON.parse(this.$route.query.row);
} else {
console.error('未找到小说数据');
}
this.$request.get('/novels/shujia/exist?id=' + this.novel.id).then(res => {
this.exist = res
})
},
methods: {
getNovel(item) {
localStorage.setItem('novel', JSON.stringify(item))
this.$router.push('/neirong')
},
SetShujia(novel) {
let shujia = {shuId: novel.id, type: novel.type}
this.$request.post('/novels/shujia', shujia).then(res => {
this.$message.success("添加成功")
this.exist = true
})
},
yichu(novel) {
this.$request.delete("/novels/shujia", {params: {id: novel.id}}).then(res => {
this.$message.success("移除成功")
this.exist = false
})
}
}
}
</script>
<style scoped>
.chapter-container {
display: flex;
flex-wrap: wrap; /* 允许换行 */
}
.chapter-item {
width: 33.33%; /* 三等分来显示,每行三个 */
box-sizing: border-box;
padding: 10px;
}
/* 示例中的其他样式 */
</style>
子组件: Neirong(内容)
就是看小说的主页面
<template>
<div class="novel-container">
<el-card style="width: 60%">
<div slot="header" class="novel-header">
<span class="chapter-title">{{ novel.chapter }}</span>
</div>
<div class="novel-text">
<p v-for="(paragraph, index) in novel.paragraphs" :key="index">{{ paragraph }}</p>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: "Nerong",
data() {
return {
novel: {}
};
},
mounted() {
const storedNovel = JSON.parse(localStorage.getItem('novel'));
if (storedNovel) {
// 假设小说内容是一个长字符串,将其按段落分割
const paragraphs = storedNovel.text.split(' ');
this.novel = { ...storedNovel, paragraphs };
}
}
};
</script>
<style scoped>
.novel-container {
margin-top: 5%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.novel-header {
text-align: center;
}
.chapter-title {
font-size: 1.5em;
font-weight: bold;
}
.novel-text {
padding: 20px;
line-height: 1.6;
font-size: 1.1em;
}
.novel-text p {
margin-bottom: 1em;
text-indent: 2em; /* 设置首行缩进 */
}
</style>
子组件:SouTypeNovel(搜索页面)
这个和CommonPage这个是差不多,但是我目前不知道直接引用Common来一键搞定,╮(╯▽╰)╭
<template>
<div class="card-container">
<el-card class="centered-card">
<el-table :data="tableData" stripe :header-cell-style="{backgroundColor: 'aliceblue'}"
@selection-change="handleSelectionChange" row-click="details(row, event, column)">
<el-table-column label="书名" prop="title" align="center"></el-table-column>
<el-table-column label="作者" prop="author" align="center"></el-table-column>
<el-table-column label="类型" prop="type" align="center"></el-table-column>
<el-table-column label="更新时间" prop="updateTime" align="center"></el-table-column>
</el-table>
<div class="block" style="margin: 10px 0">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="pageNum" :page-sizes="[5, 10, 15, 20]" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0,
}
},
created() {
this.load();
},
watch: {
name() {
this.load();
}
},
methods: {
details(row, event, column) {
this.$router.push({path: '/details', query: {row: JSON.stringify(row)}});
},
load() {
console.log('name', this.name)
this.$request.get('/novels/sou', {
params: {
name: this.name,
page: this.pageNum,
size: this.pageSize
}
}).then(res => {
this.tableData = res.content;
this.total = res.totalElements;
}).catch(error => {
console.error('API request error:', error);
this.tableData = []; // 或根据需求初始化数据
this.total = 0;
});
},
handleSelectionChange(selection) {
console.log('Selection changed:', selection);
},
handleCurrentChange(pageNum) {
this.pageNum = pageNum;
this.load();
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.load();
}
},
computed: {
name() {
return this.$route.query.name;
}
}
}
</script>
<style scoped>
.card-container {
display: flex;
justify-content: center;
text-align: center;
}
.centered-card {
width: 50%;
margin-top: 5%;
}
</style>
路由:index
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: () => import(/* webpackChunkName: "about" */ '../views/HomeView.vue'),
redirect: '/homePage',
children: [
{
path: '/homePage',
name: 'homePage',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/HomePage.vue')
},
{
path: '/first',
name: 'first',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/FirstTypeNovel.vue')
},
{
path: '/second',
name: 'second',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/SecondTypeNovel.vue')
},
{
path: '/third',
name: 'third',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/ThirdTypeNovel.vue')
},{
path: '/fourth',
name: 'fourth',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/FourthTypeNovel.vue')
},{
path: '/wangyou',
name: 'wangyou',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/WangyouTypeNovel.vue')
},{
path: '/scienceFiction',
name: 'scienceFiction',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/ScienceFictionTypeNovel.vue')
},{
path: '/sou',
name: 'sou',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/SouTypeNovel.vue')
}, {
path: '/details',
name: 'details',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/details.vue')
}, {
path: '/neirong',
name: 'neirong',
component: () => import(/* webpackChunkName: "about" */ '../views/manager/Nerong.vue')
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
6,展示
因为时间真的不够了,就这样草草的结束吧,突然意思到我搜索忘写了哈哈哈哈,就交给大家了
额外补充了下面这个页面(2024年6月14日10:52:36)
7,总结
你看看有什么?这什么都没有怎么总结?没有总结散会