自学内容网 自学内容网

小说阅读工具

文字说明

采用vue3实现的简单的小说阅读器,导入小说的txt文本,即可进行阅读;目前对于面板设置以及翻页的效果实现的并不好;可以等后续有新思路了来进行完善

采用indexDB存储上传的文本数据

可利用Hbuilder的5+APP将源码打包为手机APP

核心代码

列表页面源码

<script setup>
import {onBeforeMount, reactive, ref} from "vue";
import {confirm, loading, message} from "@/util";
import {openBookContentLog} from "@/util/config";
import {dbOperation} from "@/util/dbOperation";
import {useRouter} from "vue-router";
import jschardet from 'jschardet';

const data = reactive({
  bookForm: {
    bookName: "",
    bookContent: "",
    bookBrief: "",
  },
  bookRules: {
    bookName: [{required: true, message: '请填写书籍名称', trigger: 'blur'}],
    bookContent: [{required: true, message: '请上传书籍内容', trigger: 'blur'}],
    bookBrief: [{required: true, message: '请填写书籍简介', trigger: 'blur'}],
  },
  bookUploadVisible: false,
  bookList: [],
});

onBeforeMount(() => {
  initBookList();
});

function initBookList() {
  const bookList = localStorage.getItem("bookList");
  if (bookList) {
    data.bookList = JSON.parse(bookList);
  }
}

function openUpload() {
  data.bookForm.bookName = "";
  data.bookForm.bookContent = "";
  data.bookForm.bookBrief = "";
  data.bookUploadVisible = true;
}

function cancelUpload() {
  confirm("关闭后导入信息会被清空,是否继续?", () => {
    data.bookUploadVisible = false;
  });
}

function uploadFile(event) {
  const file = event.target.files[0];
  if (!file) {
    return;
  }
  const loadingInstance = loading("导入中...");
  setTimeout(() => {
    getEncoding(file, (encoding) => {
      const reader = new FileReader();
      reader.readAsText(file, encoding);
      reader.onload = (e) => {
        loadingInstance.close();
        const bookContent = e.target.result;
        if (!bookContent) {
          message("导出书籍内容为空", "warning");
          return;
        }
        data.bookForm.bookContent = bookContent;
        bookFormRef.value.validateField("bookContent");
      }
    });
  }, 100);
}

function getEncoding(file, success) {
  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = (e) => {
    const dataUrl = e.target.result;
    const encoding = jschardet.detect(atob(dataUrl.split(';base64,')[1]));
    success(encoding.encoding);
  }
}

const bookFormRef = ref();

function uploadBook() {
  bookFormRef.value.validate(async (valid) => {
    if (valid) {
      const loadingInstance = loading("数据保存中...");
      const book_id = Date.now();
      data.bookList.push({
        book_id: book_id,
        book_name: data.bookForm.bookName,
        book_brief: data.bookForm.bookBrief,
      });
      localStorage.setItem("bookList", JSON.stringify(data.bookList));
      await openBookContentLog();
      await dbOperation.add([{
        book_id: book_id,
        book_content: data.bookForm.bookContent,
      }]);
      loadingInstance.close();
      data.bookUploadVisible = false;
      initBookList();
    }
  });
}

function moveIndex(item, step) {
  let i;
  for (i = 0; i < data.bookList.length; i++) {
    if (item.book_id === data.bookList[i].book_id) {
      break;
    }
  }
  if (step === 0) {
    if (i !== 0) {
      data.bookList.splice(i, 1);
      data.bookList.unshift(item);
      localStorage.setItem("bookList", JSON.stringify(data.bookList));
    }
  } else if (step === 1) {
    if (i < data.bookList.length - 1) {
      data.bookList.splice(i, 1);
      data.bookList.splice(i + 1, 0, item);
      localStorage.setItem("bookList", JSON.stringify(data.bookList));
    }
  } else if (step === -1) {
    if (i > 0) {
      data.bookList.splice(i, 1);
      data.bookList.splice(i - 1, 0, item);
      localStorage.setItem("bookList", JSON.stringify(data.bookList));
    }
  }
}

function deleteBook(item) {
  confirm("确认删除该书籍吗?", async () => {
    let i;
    for (i = 0; i < data.bookList.length; i++) {
      if (item.book_id === data.bookList[i].book_id) {
        break;
      }
    }
    data.bookList.splice(i, 1);
    localStorage.setItem("bookList", JSON.stringify(data.bookList));
    await openBookContentLog();
    const res = await dbOperation.getDataByField("book_id", item.book_id);
    if (res.data.length > 0) {
      await dbOperation.delete([res.data[0].id]);
    }
  });
}

const router = useRouter();

function read(item) {
  router.push({
    path: "/bookRead",
    query: {
      book_id: item.book_id,
      book_name: item.book_name,
    }
  });
}
</script>

<template>
  <div class="container">
    <el-button type="danger" @click="openUpload">导入</el-button>

    <template v-for="item in data.bookList" :key="item.book_id">
      <el-card style="margin: 1rem 0">
        <h3>{{ item.book_name }}</h3>
        <p style="margin-top: 5px">{{ item.book_brief }}</p>
        <template #footer>
          <div style="display: flex; align-items: center; justify-content: center">
            <el-button type="info" @click="read(item)">阅读</el-button>
            <el-button type="info" @click="deleteBook(item)">删除</el-button>
            <el-button type="danger" @click="moveIndex(item, 0)">置顶</el-button>
            <el-button type="primary" @click="moveIndex(item, -1)">上移</el-button>
            <el-button type="primary" @click="moveIndex(item, 1)">下移</el-button>
          </div>
        </template>
      </el-card>
    </template>
  </div>

  <el-dialog v-model="data.bookUploadVisible" :before-close="cancelUpload" title="导入书籍" width="90%">
    <el-form ref="bookFormRef" :model="data.bookForm" :rules="data.bookRules" label-position="right"
             label-width="auto">
      <el-form-item label="名称:" prop="bookName">
        <el-input v-model="data.bookForm.bookName"/>
      </el-form-item>
      <el-form-item label="导入:" prop="bookContent">
        <input v-if="data.bookUploadVisible" accept=".txt" type="file" @change="uploadFile($event)">
      </el-form-item>
      <el-form-item label="简介:" prop="bookBrief">
        <el-input v-model="data.bookForm.bookBrief" :rows="15" resize="none" type="textarea"/>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button type="info" @click="cancelUpload">取消</el-button>
      <el-button type="danger" @click="uploadBook">保存</el-button>
    </template>
  </el-dialog>
</template>

<style lang="scss" scoped>

</style>

阅读页面源码

<script setup>
import {useRoute, useRouter} from "vue-router";
import {computed, nextTick, onBeforeMount, reactive} from "vue";
import {dbOperation} from "@/util/dbOperation";
import {openBookContentLog} from "@/util/config";
import {loading} from "@/util";

const data = reactive({
  bookContent: "",
  bookName: "",
  pageIndex: 0,
  total: 0,
});

const bookConvertContentList = [];
const gap = 1000;
let book_id;

const route = useRoute();

onBeforeMount(async () => {
  const loadingInstance = loading("数据加载中...");
  book_id = route.query.book_id;
  const book_name = route.query.book_name;
  if (book_name) {
    data.bookName = book_name;
  }
  if (book_id) {
    const book_id__page_index = localStorage.getItem("book_id__" + book_id);
    if (book_id__page_index) {
      data.pageIndex = Number(book_id__page_index);
    }

    await openBookContentLog();
    const res = await dbOperation.getDataByField("book_id", Number(book_id));
    if (res.data.length > 0) {
      const bookContent = res.data[0].book_content;

      let index = 0;
      while (index < bookContent.length) {
        let end = index + gap;
        const nextSplit = bookContent.indexOf("\r\n", end);
        if (nextSplit !== -1) {
          end = nextSplit;
        }
        const substr = bookContent.substring(index, end);
        const bookLineList = substr.split("\r\n");
        let bookConvertContent = "";
        for (let i = 0; i < bookLineList.length; i++) {
          bookConvertContent += `<p>${bookLineList[i]}</p><br/>`;
        }
        bookConvertContentList.push(bookConvertContent);
        index = end;
      }

      data.bookContent = bookConvertContentList[data.pageIndex];
      data.total = bookConvertContentList.length;
      loadingInstance.close();
    }
  }
});

const router = useRouter();

function goBack() {
  router.push({
    path: "/bookList",
  });
}

function lastPage() {
  if (data.pageIndex > 0) {
    data.pageIndex--;
    localStorage.setItem("book_id__" + book_id, data.pageIndex);
    data.bookContent = bookConvertContentList[data.pageIndex];
    nextTick(() => {
      const container = document.getElementsByClassName("container")[0];
      container.scrollTo(0, 0);
    });
  }
}

function nextPage() {
  if (data.pageIndex < bookConvertContentList.length - 1) {
    data.pageIndex++;
    localStorage.setItem("book_id__" + book_id, data.pageIndex);
    data.bookContent = bookConvertContentList[data.pageIndex];
    nextTick(() => {
      const container = document.getElementsByClassName("container")[0];
      container.scrollTo(0, 0);
    });
  }
}

const bottomTip = computed(() => {
  return (data.pageIndex + 1) + " / " + data.total;
});
</script>

<template>
  <div class="container">
    <el-page-header :content="data.bookName" @back="goBack"></el-page-header>
    <div style="padding: 1rem 0" v-html="data.bookContent"></div>

    <div style="display: flex; align-items: center; justify-content: center">
      <el-page-header style="width: fit-content" title="Last" @back="lastPage"></el-page-header>
      <div style="flex: 1; text-align: center; font-size: 18px">{{ bottomTip }}</div>
      <el-page-header class="next" style="transform: rotate(180deg); width: fit-content" title="Next"
                      @back="nextPage"></el-page-header>
    </div>
  </div>
</template>

<style lang="scss">
.next {
  .el-page-header__title, .el-page-header__content {
    transform: rotate(180deg) !important;
  }
}
</style>

效果展示

书籍列表
在这里插入图片描述

阅读页面
在这里插入图片描述

翻页
在这里插入图片描述

源码下载

小说阅读器


原文地址:https://blog.csdn.net/bingbingyihao/article/details/142766928

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!