在本篇文章中,我们将带你逐步实现一个完备的小说网站项目,技术栈包括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,总结

你看看有什么?这什么都没有怎么总结?没有总结散会

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐