├── src
├── static
│ ├── README.md
│ ├── guanyu.png
│ ├── lianxi.png
│ ├── lishi.png
│ ├── logo.png
│ ├── weixin.png
│ ├── yejian.png
│ ├── yijian.png
│ ├── fengmian.png
│ ├── fenxiang.png
│ ├── home_off.png
│ ├── home_on.png
│ ├── logo_wx.png
│ ├── logo_zfb.png
│ ├── nothing.png
│ ├── nothing1.png
│ ├── nothing2.png
│ ├── nothing3.png
│ ├── qingchu.png
│ ├── shuyuan.png
│ ├── user_img.png
│ ├── yejian1.png
│ ├── arrow-right.png
│ ├── none_data.png
│ ├── pengyouquan.png
│ ├── personal_on.png
│ ├── user_female.png
│ ├── user_male.png
│ ├── default_head.png
│ ├── personal_off.png
│ └── iconfont
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ ├── iconfont.woff2
│ │ ├── iconfont.css
│ │ └── iconfont.json
├── main.ts
├── types
│ ├── user.ts
│ └── index.ts
├── parser
│ ├── index.ts
│ ├── top.ts
│ ├── content.ts
│ ├── catalog.ts
│ └── search.ts
├── env.d.ts
├── components
│ ├── painter
│ │ ├── components
│ │ │ ├── l-painter-qrcode
│ │ │ │ └── l-painter-qrcode.vue
│ │ │ ├── l-painter-image
│ │ │ │ └── l-painter-image.vue
│ │ │ ├── l-painter-view
│ │ │ │ └── l-painter-view.vue
│ │ │ ├── l-painter-text
│ │ │ │ └── l-painter-text.vue
│ │ │ ├── l-painter
│ │ │ │ ├── props.js
│ │ │ │ └── nvue.js
│ │ │ └── common
│ │ │ │ └── relation.js
│ │ ├── package.json
│ │ ├── hybrid
│ │ │ └── html
│ │ │ │ ├── index.html
│ │ │ │ └── uni.webview.1.5.3.js
│ │ └── changelog.md
│ ├── Upload
│ │ ├── README.md
│ │ └── Image.vue
│ ├── InconFonts.vue
│ ├── global
│ │ ├── g-icon-fonts.vue
│ │ ├── g-statusbar.vue
│ │ ├── g-popup.vue
│ │ ├── g-page.vue
│ │ └── g-confirm.vue
│ ├── BookTip.vue
│ ├── TabBar.vue
│ ├── Expand.vue
│ └── share.vue
├── utils
│ ├── Config.ts
│ ├── Control.ts
│ └── request.ts
├── App.vue
├── directives
│ └── index.ts
├── store
│ ├── User.ts
│ ├── AppOption.ts
│ └── index.ts
├── uni.scss
├── api
│ └── common.ts
├── pages
│ ├── blank
│ │ ├── policy.vue
│ │ ├── about.vue
│ │ ├── feedback.vue
│ │ ├── agreement.vue
│ │ └── origin.vue
│ ├── tabBar
│ │ ├── components
│ │ │ ├── groupItem.vue
│ │ │ ├── addGroup.vue
│ │ │ └── bookDetail.vue
│ │ ├── export.vue
│ │ └── personal.vue
│ └── reader
│ │ └── components
│ │ ├── Renderjs.vue
│ │ └── Origin.vue
├── pages.json
├── styles
│ ├── loading.scss
│ └── index.scss
├── manifest.json
└── type.d.ts
├── .prettierignore
├── README.md
├── .gitignore
├── vite.config.ts
├── tsconfig.json
├── .prettierrc.js
├── index.html
└── package.json
/src/static/README.md:
--------------------------------------------------------------------------------
1 | # 静态文件存放目录
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /dist/*
2 | /node_modules/**
3 | /public/*
4 | **/*.svg
5 | **/*.sh
--------------------------------------------------------------------------------
/src/static/guanyu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/guanyu.png
--------------------------------------------------------------------------------
/src/static/lianxi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/lianxi.png
--------------------------------------------------------------------------------
/src/static/lishi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/lishi.png
--------------------------------------------------------------------------------
/src/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/logo.png
--------------------------------------------------------------------------------
/src/static/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/weixin.png
--------------------------------------------------------------------------------
/src/static/yejian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/yejian.png
--------------------------------------------------------------------------------
/src/static/yijian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/yijian.png
--------------------------------------------------------------------------------
/src/static/fengmian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/fengmian.png
--------------------------------------------------------------------------------
/src/static/fenxiang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/fenxiang.png
--------------------------------------------------------------------------------
/src/static/home_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/home_off.png
--------------------------------------------------------------------------------
/src/static/home_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/home_on.png
--------------------------------------------------------------------------------
/src/static/logo_wx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/logo_wx.png
--------------------------------------------------------------------------------
/src/static/logo_zfb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/logo_zfb.png
--------------------------------------------------------------------------------
/src/static/nothing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/nothing.png
--------------------------------------------------------------------------------
/src/static/nothing1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/nothing1.png
--------------------------------------------------------------------------------
/src/static/nothing2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/nothing2.png
--------------------------------------------------------------------------------
/src/static/nothing3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/nothing3.png
--------------------------------------------------------------------------------
/src/static/qingchu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/qingchu.png
--------------------------------------------------------------------------------
/src/static/shuyuan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/shuyuan.png
--------------------------------------------------------------------------------
/src/static/user_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/user_img.png
--------------------------------------------------------------------------------
/src/static/yejian1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/yejian1.png
--------------------------------------------------------------------------------
/src/static/arrow-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/arrow-right.png
--------------------------------------------------------------------------------
/src/static/none_data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/none_data.png
--------------------------------------------------------------------------------
/src/static/pengyouquan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/pengyouquan.png
--------------------------------------------------------------------------------
/src/static/personal_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/personal_on.png
--------------------------------------------------------------------------------
/src/static/user_female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/user_female.png
--------------------------------------------------------------------------------
/src/static/user_male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/user_male.png
--------------------------------------------------------------------------------
/src/static/default_head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/default_head.png
--------------------------------------------------------------------------------
/src/static/personal_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/personal_off.png
--------------------------------------------------------------------------------
/src/static/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/src/static/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/src/static/iconfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/herozse/eReader/HEAD/src/static/iconfont/iconfont.woff2
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createSSRApp } from "vue";
2 | import App from "./App.vue";
3 |
4 | export function createApp() {
5 | const app = createSSRApp(App);
6 | return {
7 | app,
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/types/user.ts:
--------------------------------------------------------------------------------
1 | /** 用户信息类型 */
2 | export interface UserInfo {
3 | /** 登录凭据 */
4 | token: string;
5 | /** 用户手机号 */
6 | phone: number | "";
7 | /** 用户`id` */
8 | id: number | "";
9 | }
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eReader
2 |
3 | ## 介绍
4 |
5 | 1. h5
6 |
7 | ```
8 | npm install ( 若node版本冲突,使用 npm install --force )
9 | npm run dev
10 | ```
11 |
12 | 2. ios
13 |
14 | [ipa自签教程](https://blog.csdn.net/m0_66504310/article/details/129842809)
15 |
16 | 3. Android
17 |
--------------------------------------------------------------------------------
/src/parser/index.ts:
--------------------------------------------------------------------------------
1 | import { getTopBook } from "./top";
2 | import { searchBook } from "./search";
3 | import { getCatalogs } from "./catalog";
4 | import { getPageContent } from "./content";
5 |
6 | export default {
7 | getTopBook,
8 | searchBook,
9 | getCatalogs,
10 | getPageContent,
11 | };
12 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module "*.vue" {
4 | import { DefineComponent } from "vue"
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | unpackage/
4 | dist/
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .project
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw*
24 |
25 | package-lock.json
26 | yarn.lock
27 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import { defineConfig } from "vite";
3 | import uni from "@dcloudio/vite-plugin-uni";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [uni()],
8 | base: "./",
9 | resolve: {
10 | alias: {
11 | "@": path.resolve(__dirname, "src")
12 | }
13 | },
14 | server: {
15 | port: 2023,
16 | host: "0.0.0.0"
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter-qrcode/l-painter-qrcode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 |
26 |
28 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter-image/l-painter-image.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
27 |
29 |
--------------------------------------------------------------------------------
/src/components/Upload/README.md:
--------------------------------------------------------------------------------
1 | # 上传组件
2 |
3 | ## 上传图片组件
4 |
5 | 使用示例
6 |
7 | ```html
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 | ```
28 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter-view/l-painter-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
32 |
33 |
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "lib": ["esnext", "dom"],
13 | "baseUrl": "./",
14 | "paths": {
15 | "@/*": ["src/*"]
16 | },
17 | "types": ["@dcloudio/types", "@types/node"]
18 | },
19 | "include": [
20 | "node_modules/@types/lodash",
21 | "src/**/*.ts",
22 | "src/**/*.d.ts",
23 | "src/**/*.tsx",
24 | "src/**/*.vue",
25 | "src/**/**/*.vue"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/InconFonts.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter-text/l-painter-text.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
31 |
32 |
34 |
--------------------------------------------------------------------------------
/src/utils/Config.ts:
--------------------------------------------------------------------------------
1 | function moduleConfig() {
2 | const env = process.env.NODE_ENV === "development" ? "dev" : "prod";
3 |
4 | const url = {
5 | dev: `http://127.0.0.1:3000`,
6 | prod: "http://127.0.0.1:3000",
7 | };
8 |
9 | return {
10 | /** 请求超时毫秒 */
11 | get requestOvertime() {
12 | return 60000;
13 | },
14 | /** `api`请求域名 */
15 | get apiUrl() {
16 | return url[env];
17 | },
18 | /** 当前环境模式 */
19 | get env() {
20 | return env;
21 | },
22 | /** 上传图片地址 */
23 | get uploadUrl() {
24 | return "http://xxx.com/upload";
25 | },
26 | };
27 | }
28 |
29 | /** 配置模块 */
30 | const config = moduleConfig();
31 |
32 | export default config;
33 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120, //单行长度
3 | tabWidth: 2, //缩进长度
4 | useTabs: false, //使用空格代替tab缩进
5 | semi: true, //句末使用分号
6 | singleQuote: false, //使用单引号
7 | quoteProps: "as-needed", //仅在必需时为对象的key添加引号
8 | trailingComma: "all", //多行时尽可能打印尾随逗号
9 | bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
10 | jsxBracketSameLine: true, //多属性html标签的‘>’折行放置
11 | requirePragma: false, //无需顶部注释即可格式化
12 | insertPragma: false, //在已被preitter格式化的文件顶部加上标注
13 | proseWrap: "preserve", //markdown字符数超过保持原样
14 | htmlWhitespaceSensitivity: "ignore", //对HTML全局空白不敏感
15 | vueIndentScriptAndStyle: false, //不对vue中的script及style标签缩进
16 | endOfLine: "lf", //结束行形式
17 | embeddedLanguageFormatting: "auto", //对引用代码进行格式化
18 | };
19 |
--------------------------------------------------------------------------------
/src/components/global/g-icon-fonts.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
26 |
41 |
--------------------------------------------------------------------------------
/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from "vue";
2 |
3 | // 声明模块扩展
4 | declare module "vue" {
5 | interface DirectiveBindings {
6 | value: () => void;
7 | }
8 | }
9 |
10 | export const vLongPress: Directive = {
11 | mounted(el, binding) {
12 | let timer: NodeJS.Timeout | null = null;
13 |
14 | const start = () => {
15 | timer = setTimeout(() => {
16 | binding.value(); // 触发长按逻辑
17 | }, 350); // 长按时间设为 350 毫秒
18 | };
19 |
20 | const cancel = () => {
21 | if (timer) {
22 | clearTimeout(timer);
23 | timer = null;
24 | }
25 | };
26 |
27 | el.addEventListener("touchstart", start);
28 | el.addEventListener("touchend", cancel);
29 | el.addEventListener("touchmove", cancel);
30 | },
31 |
32 | unmounted(el) {
33 | const cancel = () => {}; // 避免卸载时使用未定义的函数
34 | el.removeEventListener("touchstart", cancel);
35 | el.removeEventListener("touchend", cancel);
36 | el.removeEventListener("touchmove", cancel);
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/src/store/User.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { modifyData } from "@/utils";
3 | import { UserInfo } from "@/types/user";
4 |
5 | const cacheName = "user-info";
6 |
7 | function useUserInfo(): UserInfo {
8 | return {
9 | id: "",
10 | token: "",
11 | phone: ""
12 | }
13 | }
14 |
15 | /**
16 | * 用户状态管理模块
17 | */
18 | export default class ModuleUser {
19 | constructor() {
20 | this.init();
21 | }
22 |
23 | /** 初始化用户数据(从本地获取) */
24 | private init() {
25 | const data = uni.getStorageSync(cacheName);
26 | if (data) {
27 | this.update(JSON.parse(data));
28 | }
29 | }
30 |
31 | /** 用户信息 */
32 | readonly info = reactive>(useUserInfo());
33 |
34 | /**
35 | * 更新用户信息字段
36 | * @param value 修改的值
37 | */
38 | update(value: Partial) {
39 | modifyData(this.info, value);
40 | uni.setStorageSync(cacheName, JSON.stringify(this.info));
41 | }
42 |
43 | /** 重置用户信息 */
44 | reset() {
45 | modifyData(this.info, useUserInfo());
46 | uni.setStorageSync(cacheName, JSON.stringify(this.info));
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/uni.scss:
--------------------------------------------------------------------------------
1 | // 每个页面需要引入的文件,注意:vue.config.js 中配置的 css.loaderOptions 是无法在当前项目生效的,
2 | // 可能是uni-app项目设定和标准vue-cli项目设定不一样导致的,需要在当前文件全局引入即可
3 | // 全局样式写在`App.vue`里面,一些预处理工具样式可以写这里,因为每个页面都会注入一遍
4 | // 全局样式不写在这里的原因是因为每个页面都注入一遍的话会导致`html-style`有多个相同的代码标签
5 |
6 | $white: #fff;
7 | $black: #010101;
8 | $dark: #222;
9 | $red: #fa2d2d;
10 | $pink: #f04e7d;
11 | $blue: #2c72f3;
12 |
13 | // 溢出...显示
14 | @mixin ellipsis($number) {
15 | display: -webkit-box;
16 | -webkit-box-orient: vertical;
17 | -webkit-line-clamp: $number;
18 | overflow: hidden;
19 | }
20 |
21 | @mixin button($color, $bg) {
22 | font-size: 30rpx;
23 | min-width: 160rpx;
24 | padding: 0 20rpx;
25 | border-radius: 2px;
26 | height: 80rpx;
27 | background-color: $bg;
28 | border-color: $bg;
29 | color: $color;
30 | line-height: 1;
31 | display: flex;
32 | align-items: center;
33 | justify-content: center;
34 |
35 | &::after,
36 | &::before {
37 | border: none;
38 | background-color: transparent;
39 | }
40 |
41 | &:hover {
42 | background-color: $bg;
43 | border-color: $bg;
44 | color: $color;
45 | }
46 |
47 | &:active {
48 | opacity: 0.85;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter/props.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | board: Object,
4 | pathType: String, // 'base64'、'url'
5 | fileType: {
6 | type: String,
7 | default: 'png'
8 | },
9 | hidden: Boolean,
10 | quality: {
11 | type: Number,
12 | default: 1
13 | },
14 | css: [String, Object],
15 | // styles: [String, Object],
16 | width: [Number, String],
17 | height: [Number, String],
18 | pixelRatio: Number,
19 | customStyle: String,
20 | isCanvasToTempFilePath: Boolean,
21 | // useCanvasToTempFilePath: Boolean,
22 | sleep: {
23 | type: Number,
24 | default: 1000 / 30
25 | },
26 | beforeDelay: {
27 | type: Number,
28 | default: 100
29 | },
30 | afterDelay: {
31 | type: Number,
32 | default: 100
33 | },
34 | performance: Boolean,
35 | // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
36 | type: {
37 | type: String,
38 | default: '2d'
39 | },
40 | // #endif
41 | // #ifdef APP-NVUE
42 | hybrid: Boolean,
43 | timeout: {
44 | type: Number,
45 | default: 2000
46 | },
47 | // #endif
48 | // #ifdef H5 || APP-PLUS
49 | useCORS: Boolean,
50 | hidpi: {
51 | type: Boolean,
52 | default: true
53 | }
54 | // #endif
55 | }
56 | }
--------------------------------------------------------------------------------
/src/api/common.ts:
--------------------------------------------------------------------------------
1 | import { requestService, requestCrawlerHtml } from "@/utils/request";
2 |
3 | export function parseBookHtml(params: { origin: string }) {
4 | return requestCrawlerHtml(params.origin);
5 | }
6 |
7 | export function searchBook(params: {
8 | fuzzy: string;
9 | limit?: number;
10 | pageSize: number;
11 | pageNo: number;
12 | origins?: Array;
13 | }) {
14 | return requestService<{ data: searchBookList[]; complete: boolean }>("POST", "/books/search", params);
15 | }
16 | export function getCatalogs(params: { url: string }) {
17 | return requestService("POST", "/books/getCatalogs", params);
18 | }
19 |
20 | export function getContent(params: { url: string }) {
21 | return requestService<{ title: string; text: string }>("POST", "/books/getContent", params);
22 | }
23 |
24 | export function getTopBooks(params: { gender?: string; cate?: string; rank?: string }) {
25 | return requestService("POST", "/books/getTopBooks", params);
26 | }
27 |
28 | // APP端
29 | export async function getTopBooksByApp(url: string) {
30 | return requestCrawlerHtml(url);
31 | }
32 |
33 | export function searchBookByApp(url: string, method?: RequestMethod, data?: any, options?: object) {
34 | return requestCrawlerHtml(url, method, data, options);
35 | }
36 |
37 | export function getCatalogsByApp(url: string) {
38 | return requestCrawlerHtml(url);
39 | }
40 |
41 | export function getContentByApp(url: string) {
42 | return requestCrawlerHtml(url);
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/BookTip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 点击切换到上一页
8 |
9 |
10 |
11 |
12 |
13 |
14 | 点击换出菜单
15 |
16 |
17 |
18 |
19 |
20 |
21 | 点击切换到下一页
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
78 |
--------------------------------------------------------------------------------
/src/pages/blank/policy.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 隐私政策
7 | 一、总则
8 | 1.1 本软件尊重并保护用户隐私,本隐私政策旨在说明本软件对用户数据的收集、使用和保护方式。
9 |
10 | 1.2 用户在使用本软件前,请仔细阅读并理解本隐私政策。如用户不同意本隐私政策的任一内容,应立即停止使用本软件。
11 |
12 | 二、数据收集与使用
13 |
14 | 2.1 本软件不主动收集、存储或共享任何用户的个人信息。
15 |
16 | 2.2
17 | 用户在使用本软件时可能通过访问第三方服务或内容而涉及个人信息的处理,此类信息的处理和使用规则由该第三方的隐私政策约束,与本软件无关。
18 |
19 |
20 | 三、数据保护与共享
21 |
22 | 3.1 本软件不会与任何第三方共享用户数据。
23 | 3.2 在可控范围内,本软件采取合理的技术手段保护用户在使用过程中可能产生的临时数据。
24 | 3.3 用户应妥善保护自身设备和账户信息,防止因自身原因导致的数据泄露或安全问题。
25 |
26 | 四、第三方服务与链接
27 |
28 | 4.1 本软件通过第三方接口获取公开内容,开发者不对第三方内容的合法性、准确性或完整性负责。
29 | 4.2 用户因访问第三方网站或服务引发的隐私或数据问题,与本软件及开发者无关。
30 |
31 | 五、用户权利
32 | 5.1 用户有权知晓本软件的运行机制,并了解其数据使用方式。
33 | 5.2 用户有权随时停止使用本软件,并删除本地存储的所有相关数据。
34 | 5.3 若未来版本涉及用户数据的收集或处理,本软件将在明确告知并征得用户同意后实施。
35 | 六、联系我们
36 |
37 | 如您对本协议或隐私政策有任何疑问或建议,请通过GitHub平台提交Issue或发送邮件至linzesen021@163.com
38 | 。开发者将在合理时间内回复您的问题。
39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
68 |
--------------------------------------------------------------------------------
/src/components/global/g-statusbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
35 |
36 |
37 |
57 |
58 |
88 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // import { ComponentInternalInstance } from "vue";
2 |
3 | // /**
4 | // * `vue3`类型扩充
5 | // * - 弥补官方的类型不足问题
6 | // */
7 | // export declare namespace Vue3 {
8 | // /**
9 | // * `vue`实例上下文
10 | // * - `vue3`类型文件中貌似没有暴露可以使用的类型,所以自定义补充一个接口代替,和`vue2`中一样的类型一致
11 | // */
12 | // interface Ctx {
13 | // /** 当前组件节点 */
14 | // readonly $el: HTMLElement
15 | // /** 父节点 */
16 | // readonly $parent?: Ctx
17 | // /** 当前组件实例 */
18 | // readonly $root: Ctx
19 | // /** 选项配置 */
20 | // readonly $options: {
21 | // name: string
22 | // }
23 | // }
24 | // /**
25 | // * `hooks: getCurrentInstance()`钩子函数返回的类型扩充
26 | // */
27 | // interface Instance extends ComponentInternalInstance {
28 | // /** 实例上下文对象 */
29 | // ctx: Ctx
30 | // }
31 | // }
32 |
33 | /** 上传图片返回结果 */
34 | export interface UploadChange {
35 | /** 和当前上传组件绑定的`id` */
36 | id: string | number;
37 | /** 图片路径 */
38 | src: string;
39 | }
40 |
41 | /** 表单规则类型 */
42 | export interface TheFormRulesItem {
43 | /** 是否必填项 */
44 | required?: boolean;
45 | /** 提示字段 */
46 | message?: string;
47 | /** 指定类型 */
48 | type?: "number" | "array";
49 | /**
50 | * 自定义的校验规则(正则)
51 | * - 考虑到微信一些特殊的抽风机制,在微信小程序中,除`number|string|object|undefined|null`这几个基础类型外,其他类型是会被过滤掉,所以这里在写正则的时候,在末尾加上`.toString()`即可
52 | */
53 | reg?: string; // | RegExp
54 | }
55 |
56 | /** 表单规则类型 */
57 | export type TheFormRules = { [key: string]: Array };
58 |
59 | /** `label`布局位置 */
60 | export type LabelPosition = "left" | "right" | "top";
61 |
62 | /** 表单验证回调类型 */
63 | export interface TheFormValidateCallback {
64 | (
65 | /** 是否验证通过 */
66 | isValid: boolean,
67 | /** 验证不通过的规则列表 */
68 | rules: { [key: string]: Array },
69 | ): void;
70 | }
71 |
72 | /** 选择器`item`数据 */
73 | export interface PickerSelectItem {
74 | /** 展示字段 */
75 | label: string;
76 | /** 对应的值 */
77 | value: T;
78 | /**
79 | * 下级数据
80 | * @description 最多三层,选择器栏目数根据当前下级动态显示
81 | */
82 | children?: Array>;
83 | /** 其他携带的值 */
84 | [key: string]: any;
85 | }
86 |
--------------------------------------------------------------------------------
/src/components/painter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "lime-painter",
3 | "displayName": "海报画板",
4 | "version": "1.9.6.6",
5 | "description": "一款canvas海报组件,更优雅的海报生成方案,有限的支持富文本",
6 | "keywords": [
7 | "海报",
8 | "富文本",
9 | "生成海报",
10 | "生成二维码",
11 | "JSON"
12 | ],
13 | "repository": "https://gitee.com/liangei/lime-painter",
14 | "engines": {
15 | "HBuilderX": "^3.4.14"
16 | },
17 | "dcloudext": {
18 | "sale": {
19 | "regular": {
20 | "price": "0.00"
21 | },
22 | "sourcecode": {
23 | "price": "0.00"
24 | }
25 | },
26 | "contact": {
27 | "qq": "305716444"
28 | },
29 | "declaration": {
30 | "ads": "无",
31 | "data": "无",
32 | "permissions": "无"
33 | },
34 | "npmurl": "",
35 | "type": "component-vue"
36 | },
37 | "uni_modules": {
38 | "dependencies": [],
39 | "encrypt": [],
40 | "platforms": {
41 | "cloud": {
42 | "tcb": "y",
43 | "aliyun": "y",
44 | "alipay": "n"
45 | },
46 | "client": {
47 | "App": {
48 | "app-vue": "y",
49 | "app-nvue": "y"
50 | },
51 | "H5-mobile": {
52 | "Safari": "y",
53 | "Android Browser": "y",
54 | "微信浏览器(Android)": "y",
55 | "QQ浏览器(Android)": "y"
56 | },
57 | "H5-pc": {
58 | "Chrome": "y",
59 | "IE": "u",
60 | "Edge": "u",
61 | "Firefox": "u",
62 | "Safari": "y"
63 | },
64 | "小程序": {
65 | "微信": "y",
66 | "阿里": "y",
67 | "百度": "y",
68 | "字节跳动": "y",
69 | "QQ": "y",
70 | "钉钉": "u",
71 | "快手": "u",
72 | "飞书": "u",
73 | "京东": "u"
74 | },
75 | "快应用": {
76 | "华为": "u",
77 | "联盟": "u"
78 | },
79 | "Vue": {
80 | "vue2": "y",
81 | "vue3": "y"
82 | }
83 | }
84 | }
85 | },
86 | "name": "lime-painter",
87 | "main": "index.js",
88 | "scripts": {
89 | "test": "echo \"Error: no test specified\" && exit 1"
90 | },
91 | "author": "",
92 | "license": "ISC"
93 | }
94 |
--------------------------------------------------------------------------------
/src/pages/blank/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
19 |
20 |
21 |
22 |
23 |
24 |
33 |
34 |
86 |
--------------------------------------------------------------------------------
/src/pages/tabBar/components/groupItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 | {{ gp.group }}
13 |
14 |
15 |
16 |
17 |
18 | {{ gp.data.length }} 本
19 |
20 | {{ gp.data.map((i) => i.bookName).join(" · ") }}
21 |
22 |
23 |
24 |
25 |
26 |
43 |
44 |
90 |
--------------------------------------------------------------------------------
/src/components/global/g-popup.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
15 |
16 |
17 |
18 |
85 |
86 |
100 |
--------------------------------------------------------------------------------
/src/parser/top.ts:
--------------------------------------------------------------------------------
1 | import * as cheerio from "cheerio";
2 | import * as api from "@/api/common";
3 |
4 | export function getTopBook(params: { gender: string; cate: string; rank: string }) {
5 | return new Promise((resolve, reject) => {
6 | const url = formatUrl("https://vt.quark.cn/blm/novel-rank-627/index", params);
7 | api
8 | .getTopBooksByApp(url)
9 | .then((res) => {
10 | const value = paraseSearchContent(res as string);
11 | const result: TopBookInfo[] = handlerSearch(value);
12 | resolve(result);
13 | })
14 | .catch((e) => {
15 | reject(e);
16 | });
17 | });
18 | }
19 |
20 | function paraseSearchContent(res: string) {
21 | const $ = cheerio.load(res);
22 | let targetScript = "";
23 | let initialPropsStr = null;
24 | $("script").each((index, element) => {
25 | const scriptText = $(element).text();
26 | if (scriptText.includes("window.__INITIAL_PROPS__")) {
27 | targetScript = scriptText;
28 | return false; // 找到后就停止遍历
29 | }
30 | });
31 |
32 | // 找到window.__INITIAL_PROPS__在脚本中的起始位置
33 | const startIndex = targetScript.indexOf("window.__INITIAL_PROPS__ =");
34 | if (startIndex !== -1) {
35 | // 提取等号后面的JSON字符串部分(去除末尾可能多余的分号等)
36 | let jsonStr = targetScript.substring(startIndex + "window.__INITIAL_PROPS__ =".length).trim();
37 | if (jsonStr.endsWith(";")) {
38 | jsonStr = jsonStr.slice(0, -1).trim();
39 | }
40 | try {
41 | initialPropsStr = JSON.parse(jsonStr);
42 | } catch (error) {
43 | initialPropsStr = {};
44 | console.error("解析JSON字符串失败:", error);
45 | }
46 | }
47 | return initialPropsStr;
48 | }
49 |
50 | const handlerSearch = (value: any) => {
51 | let bookList = value?.initialData?.data?.item?.novel_list?.novel_item || [];
52 | bookList = bookList.map((i: any) => {
53 | return {
54 | bookName: i.title,
55 | author: i.author,
56 | image: i.icon,
57 | categories: i.second_category,
58 | description: i.description,
59 | status: i.status,
60 | tag: i.tag,
61 | };
62 | });
63 |
64 | return bookList;
65 | };
66 |
67 | const formatUrl = (origin: string, params: { gender: string; cate: string; rank: string }) => {
68 | let str = `${origin}`;
69 |
70 | Object.entries(params).forEach(([key, value], index) => {
71 | if (index === 0) {
72 | str += `?${key}=${value}`;
73 | } else {
74 | str += `&${key}=${value}`;
75 | }
76 | });
77 |
78 | return str;
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/global/g-page.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
29 |
30 |
66 |
--------------------------------------------------------------------------------
/src/pages/blank/feedback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
16 | {{ `${msg.length}/200` }}
17 |
18 |
19 | 您的联系方式:
20 |
21 |
22 | 提交
23 |
24 | 非常感谢您的反馈,您的意见和建议对我们非常重要和宝贵,我们将认真对待每一条建议和意见。
25 |
26 |
27 |
28 |
29 |
30 |
62 |
63 |
99 |
--------------------------------------------------------------------------------
/src/components/TabBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
20 |
21 | {{ cureentPage === item.key && isBackTop ? "回顶部" : item.text }}
22 |
23 |
24 |
25 |
26 |
27 |
86 |
87 |
102 |
--------------------------------------------------------------------------------
/src/pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | {
4 | "path": "pages/tabBar/home",
5 | "style": {
6 | "navigationBarTitleText": "首页"
7 | }
8 | },
9 | {
10 | "path": "pages/tabBar/personal",
11 | "style": {
12 | "navigationBarTitleText": "个人页"
13 | }
14 | },
15 | {
16 | "path": "pages/tabBar/export",
17 | "style": {
18 | "navigationBarTitleText": "导入"
19 | }
20 | },
21 | {
22 | "path": "pages/tabBar/book",
23 | "style": {
24 | "navigationBarTitleText": "搜书"
25 | }
26 | },
27 | {
28 | "path": "pages/search/index",
29 | "style": {
30 | "navigationBarTitleText": "搜书"
31 | }
32 | },
33 | {
34 | "path": "pages/groupDetail/index",
35 | "style": {
36 | "navigationBarTitleText": "分组详情"
37 | }
38 | },
39 | {
40 | "path": "pages/bookDetail/index",
41 | "style": {
42 | "navigationBarTitleText": "书籍详情"
43 | }
44 | },
45 | {
46 | "path": "pages/catalogs/index",
47 | "style": {
48 | "navigationBarTitleText": "目录"
49 | }
50 | },
51 | {
52 | "path": "pages/reader/index",
53 | "style": {
54 | "navigationBarTitleText": "阅读页",
55 | "disableSwipeBack": true
56 | }
57 | },
58 | {
59 | "path": "pages/blank/about",
60 | "style": {
61 | "navigationBarTitleText": "关于我们"
62 | }
63 | },
64 | {
65 | "path": "pages/blank/agreement",
66 | "style": {
67 | "navigationBarTitleText": "用户协议"
68 | }
69 | },
70 | {
71 | "path": "pages/blank/policy",
72 | "style": {
73 | "navigationBarTitleText": "隐私政策"
74 | }
75 | },
76 | {
77 | "path": "pages/blank/origin",
78 | "style": {
79 | "navigationBarTitleText": "书源管理"
80 | }
81 | },
82 | {
83 | "path": "pages/blank/history",
84 | "style": {
85 | "navigationBarTitleText": "浏览历史"
86 | }
87 | },
88 | {
89 | "path": "pages/blank/feedback",
90 | "style": {
91 | "navigationBarTitleText": "意见反馈"
92 | }
93 | }
94 | ],
95 | "globalStyle": {
96 | "navigationBarTextStyle": "black",
97 | "navigationBarTitleText": "uni-app",
98 | "navigationBarBackgroundColor": "#FEF7F1",
99 | "backgroundColor": "#FEF7F1",
100 | "app-plus": {
101 | "bounce": "none"
102 | },
103 | "navigationStyle": "custom"
104 | },
105 | "easycom": {
106 | "autoscan": true,
107 | "custom": {
108 | "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
109 | "^g-(.*)": "@/components/global/g-$1.vue",
110 | "^l-(.*)": "@/components/painter/components/l-$1/l-$1.vue"
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/store/AppOption.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { modifyData } from "@/utils";
3 | export default class ModuleAppOption {
4 | /** `APP`操作信息 */
5 | readonly appOption = reactive({
6 | theme: "light",
7 | /** `小程序`导航栏高度 */
8 | navBarHeight: 0,
9 | /** `小程序`胶囊距右方间距(方保持左、右间距一致) */
10 | menuRight: 0,
11 | /** `小程序`胶囊距底部间距(保持底部间距一致) */
12 | menuBottom: 0,
13 | /** `小程序`胶囊高度(自定义内容可与胶囊高度保证一致) */
14 | menuHeight: 0,
15 | /** `小程序`胶囊宽度 */
16 | menuWidth: 0,
17 | /** 状态栏高度 */
18 | statusBarHeight: 0,
19 | /** 原生底部`tabbar`高度 */
20 | tabBarHeight: 0,
21 | /** 可使用窗口高度 */
22 | windowHeight: 0,
23 | /** 可使用窗口宽度 */
24 | windowWidth: 0,
25 | /** 屏幕宽度 */
26 | screenWidth: 0,
27 | /** 屏幕高度 */
28 | screenHeight: 0,
29 | /** 是否为`iPhoneX`系列(做底部`UI`判断) */
30 | isIPhoneX: false,
31 | });
32 |
33 | /**
34 | * 初始化`APP`操作信息
35 | * @description 最好放在`App.onLaunch`执行,因为这时才是页页面初始化完成,各个尺寸值会比较准确
36 | * @learn 条件编译 https://uniapp.dcloud.io/platform
37 | */
38 | initAppOption() {
39 | const systemInfo = uni.getSystemInfoSync();
40 | this.appOption.statusBarHeight = systemInfo.statusBarHeight!;
41 | this.appOption.tabBarHeight = systemInfo.screenHeight - systemInfo.windowHeight - systemInfo.statusBarHeight!;
42 | this.appOption.windowHeight = systemInfo.windowHeight;
43 | this.appOption.windowWidth = systemInfo.windowWidth;
44 | this.appOption.screenWidth = systemInfo.screenWidth;
45 | this.appOption.screenHeight = systemInfo.screenHeight;
46 |
47 | const isIos = systemInfo.system.toLocaleLowerCase().includes("ios");
48 | const vaule = systemInfo.screenWidth / systemInfo.screenHeight < 0.5;
49 | this.appOption.isIPhoneX = isIos && vaule;
50 |
51 | // #ifdef H5
52 | this.appOption.tabBarHeight = 50;
53 | this.appOption.isIPhoneX = false; // 网页端不需要判断底部UI判断
54 | // #endif
55 |
56 | // #ifdef MP
57 | const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
58 | // 导航栏高度 = 状态栏到胶囊的间距(胶囊距上距离-状态栏高度) * 2 + 胶囊高度 + 状态栏高度
59 | this.appOption.navBarHeight =
60 | (menuButtonInfo.top - systemInfo.statusBarHeight!) * 2 + menuButtonInfo.height + systemInfo.statusBarHeight!;
61 | this.appOption.menuRight = systemInfo.screenWidth - menuButtonInfo.right;
62 | this.appOption.menuBottom = menuButtonInfo.top - systemInfo.statusBarHeight!;
63 | this.appOption.menuHeight = menuButtonInfo.height;
64 | this.appOption.menuWidth = menuButtonInfo.width;
65 | // #endif
66 |
67 | this.initAppStorage();
68 | }
69 |
70 | initAppStorage() {
71 | const data = uni.getStorageSync("app-option");
72 | if (data) {
73 | modifyData(this.appOption, JSON.parse(data));
74 | } else {
75 | this.saveAppOption();
76 | }
77 | }
78 |
79 | saveAppOption() {
80 | uni.setStorageSync("app-option", JSON.stringify(this.appOption));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/pages/reader/components/Renderjs.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
29 |
92 |
93 |
--------------------------------------------------------------------------------
/src/styles/loading.scss:
--------------------------------------------------------------------------------
1 | .preloader-inner {
2 | position: relative;
3 | display: block;
4 | width: 100%;
5 | height: 100%;
6 | animation: md-preloader-inner-rotate 5.25s cubic-bezier(0.35, 0, 0.25, 1) infinite;
7 |
8 | .preloader-inner-gap {
9 | position: absolute;
10 | width: 2px;
11 | left: 50%;
12 | margin-left: -1px;
13 | top: 0;
14 | bottom: 0;
15 | box-sizing: border-box;
16 | border-top: 3px solid #010101;
17 | }
18 |
19 | .preloader-inner-left,
20 | .preloader-inner-right {
21 | position: absolute;
22 | top: 0;
23 | height: 100%;
24 | width: 50%;
25 | overflow: hidden;
26 | }
27 |
28 | .preloader-inner-half-circle {
29 | position: absolute;
30 | top: 0;
31 | height: 100%;
32 | width: 200%;
33 | box-sizing: border-box;
34 | border: 3px solid #010101;
35 | border-bottom-color: transparent !important;
36 | border-radius: 50%;
37 | animation-iteration-count: infinite;
38 | animation-duration: 1.3125s;
39 | animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1);
40 | }
41 |
42 | .preloader-inner-left {
43 | left: 0;
44 |
45 | .preloader-inner-half-circle {
46 | left: 0;
47 | border-right-color: transparent !important;
48 | animation-name: md-preloader-left-rotate;
49 | }
50 | }
51 |
52 | .preloader-inner-right {
53 | right: 0;
54 |
55 | .preloader-inner-half-circle {
56 | right: 0;
57 | border-left-color: transparent !important;
58 | animation-name: md-preloader-right-rotate;
59 | }
60 | }
61 | }
62 |
63 | @keyframes md-preloader-left-rotate {
64 | 0%,
65 | 100% {
66 | transform: rotate(130deg);
67 | }
68 |
69 | 50% {
70 | transform: rotate(-5deg);
71 | }
72 | }
73 |
74 | @keyframes md-preloader-right-rotate {
75 | 0%,
76 | 100% {
77 | transform: rotate(-130deg);
78 | }
79 |
80 | 50% {
81 | transform: rotate(5deg);
82 | }
83 | }
84 |
85 | @keyframes md-preloader-inner-rotate {
86 | 12.5% {
87 | transform: rotate(135deg);
88 | }
89 |
90 | 25% {
91 | transform: rotate(270deg);
92 | }
93 |
94 | 37.5% {
95 | transform: rotate(405deg);
96 | }
97 |
98 | 50% {
99 | transform: rotate(540deg);
100 | }
101 |
102 | 62.5% {
103 | transform: rotate(675deg);
104 | }
105 |
106 | 75% {
107 | transform: rotate(810deg);
108 | }
109 |
110 | 87.5% {
111 | transform: rotate(945deg);
112 | }
113 |
114 | 100% {
115 | transform: rotate(1080deg);
116 | }
117 | }
118 |
119 | @keyframes md-preloader-outer {
120 | 0% {
121 | transform: rotate(0);
122 | }
123 |
124 | 100% {
125 | transform: rotate(360deg);
126 | }
127 | }
128 |
129 | .preloader {
130 | display: block;
131 | width: 40px;
132 | height: 40px;
133 | animation: md-preloader-outer 3.3s linear infinite;
134 | }
135 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "e-reader",
3 | "appid" : "__UNI__5E3CD06",
4 | "description" : "",
5 | "versionName" : "1.0.0",
6 | "versionCode" : "100",
7 | "transformPx" : false,
8 | "app-plus" : {
9 | "usingComponents" : true,
10 | "nvueStyleCompiler" : "uni-app",
11 | "compilerVersion" : 3,
12 | "splashscreen" : {
13 | "alwaysShowBeforeRender" : true,
14 | "waiting" : true,
15 | "autoclose" : true,
16 | "delay" : 0
17 | },
18 | "modules" : {
19 | "Share" : {}
20 | },
21 | "distribute" : {
22 | "android" : {
23 | "permissions" : [
24 | "",
25 | "",
26 | "",
27 | "",
28 | "",
29 | "",
30 | "",
31 | "",
32 | "",
33 | "",
34 | "",
35 | "",
36 | "",
37 | "",
38 | ""
39 | ]
40 | },
41 | "ios" : {
42 | "idfa" : true,
43 | "dSYMs" : false
44 | },
45 | "sdkConfigs" : {
46 | "share" : {}
47 | },
48 | "splashscreen" : {
49 | "androidStyle" : "common"
50 | }
51 | },
52 | "nvueLaunchMode" : ""
53 | },
54 | "quickapp" : {},
55 | "mp-weixin" : {
56 | "appid" : "",
57 | "setting" : {
58 | "urlCheck" : false
59 | },
60 | "usingComponents" : true
61 | },
62 | "mp-alipay" : {
63 | "usingComponents" : true
64 | },
65 | "mp-baidu" : {
66 | "usingComponents" : true
67 | },
68 | "mp-toutiao" : {
69 | "usingComponents" : true
70 | },
71 | "uniStatistics" : {
72 | "enable" : false
73 | },
74 | "vueVersion" : "3"
75 | }
76 |
--------------------------------------------------------------------------------
/src/parser/content.ts:
--------------------------------------------------------------------------------
1 | import * as cheerio from "cheerio";
2 | import * as api from "@/api/common";
3 | import sourceJson from "./source";
4 | import { URLPolyfill } from "@/utils";
5 |
6 | const source = sourceJson.local;
7 | type ContentParser = { title: string; text: string };
8 |
9 | export const getPageContent = async (params: { url: string }) => {
10 | const r = await getDeepthPage(params.url) as ContentParser;
11 | let content: ContentParser = {
12 | title: "Not Foud",
13 | text: "找不到资源",
14 | };
15 | if (r) {
16 | content = r;
17 | }
18 | const result: ContentParser = handlerPage(content, params.url);
19 |
20 | return Promise.resolve(result);
21 | };
22 |
23 | const getDeepthPage = async (url: string, content: AnyObject = {}) => {
24 | try {
25 | const res = (await api.getContentByApp(url)) as string;
26 | if (res) {
27 | const parse = URLPolyfill(url);
28 | const targetSource = source.find((i) => i.origin === parse.origin);
29 | if (targetSource) {
30 | const $ = cheerio.load(res);
31 | const { wrapContainer, titleContainer, pagination, lineBreak } = targetSource.content.rules;
32 |
33 | if (!content.isParse) {
34 | content = {
35 | isParse: true,
36 | text: "",
37 | title: $(titleContainer.selector).text() || "",
38 | };
39 | }
40 | if (lineBreak === "br") {
41 | const txtElement = $(wrapContainer.selector);
42 | // 将
标签替换为 \n
43 | const modifiedHtml = txtElement.html()?.replace(/
/g, "\n");
44 | // 获取修改后的文本内容
45 | const text = cheerio.load(modifiedHtml || "").text();
46 | content.text += text;
47 | } else {
48 | $(wrapContainer.selector).each((index, element) => {
49 | const text = $(element).text();
50 | content.text += `${text}\n`;
51 | });
52 | }
53 |
54 | if (pagination) {
55 | const nextPagination = $(pagination.selector).last();
56 | const nextPath = nextPagination.attr("href") || "";
57 | const nextPathText = nextPagination.text();
58 |
59 | if (nextPath.startsWith("/") && nextPathText === "下一页") {
60 | content = await getDeepthPage(`${parse.origin}${nextPath}`, content);
61 | }
62 | }
63 | }
64 | }
65 | } catch (error) {
66 | console.log(error, "error");
67 | return content;
68 | }
69 |
70 | return content as ContentParser;
71 | };
72 |
73 | const handlerPage = (content: ContentParser, url: string) => {
74 | const parse = URLPolyfill(url);
75 | const targetSource = source.find((i) => i.origin === parse.origin);
76 | let handler = targetSource?.content?.handler;
77 |
78 | let { text, title } = content;
79 | text = text.replace(/\n\s*\n/g, "\n");
80 | const p = text
81 | .split(/\n/)
82 | .map((i) => i.trim())
83 | .filter((i) => i);
84 | handler?.replace?.forEach((r) => {
85 | if (r.type === "Array") {
86 | p.splice(r.value, 1);
87 | }
88 | });
89 |
90 | text = p.join("\n");
91 |
92 | return {
93 | text,
94 | title,
95 | };
96 | };
97 |
--------------------------------------------------------------------------------
/src/pages/blank/agreement.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 用户协议
7 | 一、总则
8 |
9 | 1.1
10 | [e读](以下简称“本软件”)由个人开发并开源,旨在提供小说阅读功能,仅供学习和技术交流使用。用户在下载、安装、使用本软件前,应仔细阅读本用户协议(以下简称“本协议”)。
11 |
12 |
13 | 1.2
14 | 本协议构成用户与本软件开发者之间的法律协议。用户使用本软件即视为完全理解并接受本协议条款,如用户不同意本协议内容,应立即停止使用本软件。
15 |
16 |
17 | 1.3 本协议适用于用户对本软件的访问、安装和使用行为。因使用本软件而产生的行为及其后果须遵守本协议约定。
18 |
19 | 二、软件使用规则
20 |
21 | 2.1 本软件为开源项目,源代码已发布于 GitHub 平台,用户可按照开源许可协议(MIT License)使用和修改本软件。
22 |
23 | 2.2 用户承诺不得利用本软件从事以下行为:
24 |
25 | ·
26 | 违反法律法规的行为;
27 |
28 |
29 | ·
30 | 侵害第三方合法权益的行为,包括但不限于侵犯知识产权、隐私权等;
31 |
32 |
33 | ·
34 | 发布、传播违法或有害信息;
35 |
36 |
37 | ·
38 | 对软件功能进行反向工程、解密或尝试规避安全保护措施;
39 |
40 |
41 | ·
42 | 其他可能对本软件开发者、其他用户或第三方造成损害的行为。
43 |
44 |
45 | 2.3 用户应自行承担使用本软件可能产生的风险,包括但不限于因内容来源问题引发的法律责任、数据丢失或服务中断。
46 |
47 | 三、知识产权声明
48 |
49 | 3.1
50 | 本软件的源代码及相关文档的著作权归开发者所有。用户可基于开源许可协议使用和分发本软件,但不得将本软件用于任何未经授权的商业用途。
51 |
52 |
53 | 3.2 用户通过本软件访问的第三方内容的知识产权归其原始权利人所有,用户不得擅自复制、修改、发布或传播上述内容。
54 |
55 | 四、免责声明
56 | 4.1 本软件作为开源项目,以"现状"提供,不对功能的完整性、适用性、可靠性作任何明示或默示的担保。
57 | 4.2 本软件获取的内容均来源于第三方公开渠道,开发者对内容的合法性、准确性或完整性不承担责任。
58 |
59 | 4.3
60 | 开发者不对因使用本软件引发的任何直接、间接、附带或衍生损害承担责任,包括但不限于数据丢失、服务中断或版权纠纷。
61 |
62 | 4.4 如因用户使用本软件导致第三方提出索赔或诉讼,用户应自行解决并承担相关责任,与开发者无关。
63 | 五、终止和修改
64 | 5.1 用户违反本协议任一条款的,开发者有权随时终止其使用本软件的权利。
65 | 5.2 本协议的解释、修改及更新权归开发者所有,更新后的协议将通过适当方式通知用户。
66 | 六、联系我们
67 |
68 | 如您对本协议或隐私政策有任何疑问或建议,请通过GitHub平台提交Issue或发送邮件至linzesen021@163.com
69 | 。开发者将在合理时间内回复您的问题。
70 |
71 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |
102 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "e-reader",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "uni",
6 | "dev:custom": "uni -p",
7 | "dev:h5": "uni",
8 | "dev:h5:ssr": "uni --ssr",
9 | "dev:mp-alipay": "uni -p mp-alipay",
10 | "dev:mp-baidu": "uni -p mp-baidu",
11 | "dev:mp-jd": "uni -p mp-jd",
12 | "dev:mp-kuaishou": "uni -p mp-kuaishou",
13 | "dev:mp-lark": "uni -p mp-lark",
14 | "dev:mp-qq": "uni -p mp-qq",
15 | "dev:mp-toutiao": "uni -p mp-toutiao",
16 | "dev:mp-weixin": "uni -p mp-weixin",
17 | "dev:mp-xhs": "uni -p mp-xhs",
18 | "dev:quickapp-webview": "uni -p quickapp-webview",
19 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
20 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
21 | "build:custom": "uni build -p",
22 | "build:h5": "uni build",
23 | "build:h5:ssr": "uni build --ssr",
24 | "build:mp-alipay": "uni build -p mp-alipay",
25 | "build:mp-baidu": "uni build -p mp-baidu",
26 | "build:mp-jd": "uni build -p mp-jd",
27 | "build:mp-kuaishou": "uni build -p mp-kuaishou",
28 | "build:mp-lark": "uni build -p mp-lark",
29 | "build:mp-qq": "uni build -p mp-qq",
30 | "build:mp-toutiao": "uni build -p mp-toutiao",
31 | "build:mp-weixin": "uni build -p mp-weixin",
32 | "build:mp-xhs": "uni build -p mp-xhs",
33 | "build:quickapp-webview": "uni build -p quickapp-webview",
34 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
35 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union"
36 | },
37 | "dependencies": {
38 | "@dcloudio/uni-app": "3.0.0-4030620241128001",
39 | "@dcloudio/uni-app-harmony": "3.0.0-4030620241128001",
40 | "@dcloudio/uni-app-plus": "3.0.0-4030620241128001",
41 | "@dcloudio/uni-components": "3.0.0-4030620241128001",
42 | "@dcloudio/uni-h5": "3.0.0-4030620241128001",
43 | "@dcloudio/uni-mp-alipay": "3.0.0-4030620241128001",
44 | "@dcloudio/uni-mp-baidu": "3.0.0-4030620241128001",
45 | "@dcloudio/uni-mp-jd": "3.0.0-4030620241128001",
46 | "@dcloudio/uni-mp-kuaishou": "3.0.0-4030620241128001",
47 | "@dcloudio/uni-mp-lark": "3.0.0-4030620241128001",
48 | "@dcloudio/uni-mp-qq": "3.0.0-4030620241128001",
49 | "@dcloudio/uni-mp-toutiao": "3.0.0-4030620241128001",
50 | "@dcloudio/uni-mp-weixin": "3.0.0-4030620241128001",
51 | "@dcloudio/uni-mp-xhs": "3.0.0-4030620241128001",
52 | "@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
53 | "@dcloudio/uni-ui": "^1.5.7",
54 | "cheerio": "^1.0.0",
55 | "css-doodle": "^0.40.0",
56 | "lodash": "^4.17.21",
57 | "prettier": "^3.3.3",
58 | "uuid": "^11.0.2",
59 | "vue": "^3.4.21",
60 | "vue-i18n": "^9.1.9"
61 | },
62 | "devDependencies": {
63 | "@dcloudio/types": "^3.4.8",
64 | "@dcloudio/uni-automator": "3.0.0-4030620241128001",
65 | "@dcloudio/uni-cli-shared": "3.0.0-4030620241128001",
66 | "@dcloudio/uni-stacktracey": "3.0.0-4030620241128001",
67 | "@dcloudio/vite-plugin-uni": "3.0.0-4030620241128001",
68 | "@types/lodash": "^4.17.13",
69 | "@types/node": "17.0.40",
70 | "@vue/runtime-core": "^3.4.21",
71 | "sass": "1.52.2",
72 | "typescript": "4.8.3",
73 | "vite": "5.2.8"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/painter/hybrid/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/components/global/g-confirm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
80 |
81 |
120 |
--------------------------------------------------------------------------------
/src/static/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "iconfont"; /* Project id 4659366 */
3 | src: url('iconfont.woff2?t=1736477956755') format('woff2'),
4 | url('iconfont.woff?t=1736477956755') format('woff'),
5 | url('iconfont.ttf?t=1736477956755') format('truetype');
6 | }
7 |
8 | .iconfont {
9 | font-family: "iconfont" !important;
10 | font-size: 16px;
11 | font-style: normal;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | .icon-jinyong:before {
17 | content: "\e66f";
18 | }
19 |
20 | .icon-qiyong:before {
21 | content: "\e75e";
22 | }
23 |
24 | .icon-fuzhilianjie:before {
25 | content: "\e62c";
26 | }
27 |
28 | .icon-haibao:before {
29 | content: "\e674";
30 | }
31 |
32 | .icon-fenzu1:before {
33 | content: "\e602";
34 | }
35 |
36 | .icon-xinzeng:before {
37 | content: "\e759";
38 | }
39 |
40 | .icon-xinzengfenzu:before {
41 | content: "\e746";
42 | }
43 |
44 | .icon-huidaodingbu:before {
45 | content: "\e622";
46 | }
47 |
48 | .icon-shujia:before {
49 | content: "\e63c";
50 | }
51 |
52 | .icon-tianjiashujia:before {
53 | content: "\e634";
54 | }
55 |
56 | .icon-zaishujia:before {
57 | content: "\e636";
58 | }
59 |
60 | .icon-zhiding:before {
61 | content: "\e718";
62 | }
63 |
64 | .icon-fenzu:before {
65 | content: "\e726";
66 | }
67 |
68 | .icon-fenxiang1:before {
69 | content: "\e64c";
70 | }
71 |
72 | .icon-yichushujia:before {
73 | content: "\e601";
74 | }
75 |
76 | .icon-huo:before {
77 | content: "\e6c9";
78 | }
79 |
80 | .icon-xia:before {
81 | content: "\e61b";
82 | }
83 |
84 | .icon-shuaxin:before {
85 | content: "\e610";
86 | }
87 |
88 | .icon-shanchu:before {
89 | content: "\e642";
90 | }
91 |
92 | .icon-shang-:before {
93 | content: "\e63f";
94 | }
95 |
96 | .icon-expand:before {
97 | content: "\e61e";
98 | }
99 |
100 | .icon-arrowleft:before {
101 | content: "\e61a";
102 | }
103 |
104 | .icon-circle-filled:before {
105 | content: "\e611";
106 | }
107 |
108 | .icon-arrowright:before {
109 | content: "\e633";
110 | }
111 |
112 | .icon-circle1:before {
113 | content: "\e621";
114 | }
115 |
116 | .icon-baitian:before {
117 | content: "\e632";
118 | }
119 |
120 | .icon-night:before {
121 | content: "\e6fd";
122 | }
123 |
124 | .icon-shuqian:before {
125 | content: "\e603";
126 | }
127 |
128 | .icon-jiarushujia:before {
129 | content: "\e62a";
130 | }
131 |
132 | .icon-mulu:before {
133 | content: "\e620";
134 | }
135 |
136 | .icon-shezhi:before {
137 | content: "\e635";
138 | }
139 |
140 | .icon-huancun:before {
141 | content: "\e6b0";
142 | }
143 |
144 | .icon-qudangqian:before {
145 | content: "\e6eb";
146 | }
147 |
148 | .icon-xiazaiyun:before {
149 | content: "\e679";
150 | }
151 |
152 | .icon-qudingbu:before {
153 | content: "\e641";
154 | }
155 |
156 | .icon-qudibu:before {
157 | content: "\e70f";
158 | }
159 |
160 | .icon-dingyue:before {
161 | content: "\e6bc";
162 | }
163 |
164 | .icon-search:before {
165 | content: "\e600";
166 | }
167 |
168 | .icon-more:before {
169 | content: "\e6ad";
170 | }
171 |
172 | .icon-sousuo:before {
173 | content: "\e8b9";
174 | }
175 |
176 | .icon-icon-test:before {
177 | content: "\e618";
178 | }
179 |
180 | .icon-export:before {
181 | content: "\e669";
182 | }
183 |
184 | .icon-wode:before {
185 | content: "\101a9";
186 | }
187 |
188 |
--------------------------------------------------------------------------------
/src/components/painter/components/common/relation.js:
--------------------------------------------------------------------------------
1 | const styles = (v ='') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
2 | const key = v.slice(0, v.indexOf(':'))
3 | const value = v.slice(v.indexOf(':')+1)
4 | return {
5 | [key
6 | .replace(/-([a-z])/g, function() { return arguments[1].toUpperCase()})
7 | .replace(/\s+/g, '')
8 | ]: value.replace(/^\s+/, '').replace(/\s+$/, '') || ''
9 | }
10 | })
11 | export function parent(parent) {
12 | return {
13 | provide() {
14 | return {
15 | [parent]: this
16 | }
17 | },
18 | data() {
19 | return {
20 | el: {
21 | id: null,
22 | css: {},
23 | views: []
24 | },
25 | }
26 | },
27 | watch: {
28 | css: {
29 | handler(v) {
30 | if(this.canvasId) {
31 | this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
32 | this.canvasWidth = this.el.css && this.el.css.width || this.canvasWidth
33 | this.canvasHeight = this.el.css && this.el.css.height || this.canvasHeight
34 | }
35 | },
36 | immediate: true
37 | }
38 | }
39 | }
40 | }
41 | export function children(parent, options = {}) {
42 | const indexKey = options.indexKey || 'index'
43 | return {
44 | inject: {
45 | [parent]: {
46 | default: null
47 | }
48 | },
49 | watch: {
50 | el: {
51 | handler(v, o) {
52 | if(JSON.stringify(v) != JSON.stringify(o))
53 | this.bindRelation()
54 | },
55 | deep: true,
56 | immediate: true
57 | },
58 | src: {
59 | handler(v, o) {
60 | if(v != o)
61 | this.bindRelation()
62 | },
63 | immediate: true
64 | },
65 | text: {
66 | handler(v, o) {
67 | if(v != o) this.bindRelation()
68 | },
69 | immediate: true
70 | },
71 | css: {
72 | handler(v, o) {
73 | if(v != o)
74 | this.el.css = (typeof v == 'object' ? v : v && Object.assign(...styles(v))) || {}
75 | },
76 | immediate: true
77 | },
78 | replace: {
79 | handler(v, o) {
80 | if(JSON.stringify(v) != JSON.stringify(o))
81 | this.bindRelation()
82 | },
83 | deep: true,
84 | immediate: true
85 | }
86 | },
87 | created() {
88 | if(!this._uid) {
89 | this._uid = this._.uid
90 | }
91 | Object.defineProperty(this, 'parent', {
92 | get: () => this[parent] || [],
93 | })
94 | Object.defineProperty(this, 'index', {
95 | get: () => {
96 | this.bindRelation();
97 | const {parent: {el: {views=[]}={}}={}} = this
98 | return views.indexOf(this.el)
99 | },
100 | });
101 | this.el.type = this.type
102 | if(this.uid) {
103 | this.el.uid = this.uid
104 | }
105 | this.bindRelation()
106 | },
107 | // #ifdef VUE3
108 | beforeUnmount() {
109 | this.removeEl()
110 | },
111 | // #endif
112 | // #ifdef VUE2
113 | beforeDestroy() {
114 | this.removeEl()
115 | },
116 | // #endif
117 | methods: {
118 | removeEl() {
119 | if (this.parent) {
120 | this.parent.el.views = this.parent.el.views.filter(
121 | (item) => item._uid !== this._uid
122 | );
123 | }
124 | },
125 | bindRelation() {
126 | if(!this.el._uid) {
127 | this.el._uid = this._uid
128 | }
129 | if(['text','qrcode'].includes(this.type)) {
130 | this.el.text = this.$slots && this.$slots.default && this.$slots.default[0].text || `${this.text || ''}`.replace(/\\n/g, '\n')
131 | }
132 | if(this.type == 'image') {
133 | this.el.src = this.src
134 | }
135 | if (!this.parent) {
136 | return;
137 | }
138 | let views = this.parent.el.views || [];
139 | if(views.indexOf(this.el) !== -1) {
140 | this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
141 | } else {
142 | this.parent.el.views = [...views, this.el];
143 | }
144 | }
145 | },
146 | mounted() {
147 | // this.bindRelation()
148 | },
149 | }
150 | }
--------------------------------------------------------------------------------
/src/components/Expand.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.trim() }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
101 |
102 |
137 |
--------------------------------------------------------------------------------
/src/pages/tabBar/export.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 搜索
5 |
6 |
7 | 获取目录
8 |
9 |
10 | 获取内容
11 |
12 | 获取热搜
13 |
14 |
15 |
16 |
135 |
165 |
--------------------------------------------------------------------------------
/src/components/Upload/Image.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
117 |
--------------------------------------------------------------------------------
/src/components/painter/hybrid/html/uni.webview.1.5.3.js:
--------------------------------------------------------------------------------
1 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function i(e,i){return n.call(e,i)}var t=[];function o(){return window.__dcloud_weex_postMessage||window.__dcloud_weex_}var r=function(e,n){var i={options:{timestamp:+new Date},name:e,arg:n};if(o()){if("postMessage"===e){var r={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(r):window.__dcloud_weex_.postMessage(JSON.stringify(r))}var a={type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(a):window.__dcloud_weex_.postMessageToService(JSON.stringify(a))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:i,pageId:""},"*");if(0===t.length){var d=plus.webview.currentWebview();if(!d)throw new Error("plus.webview.currentWebview() is undefined");var s=d.parent(),w="";w=s?s.id:d.id,t.push(w)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}},"__uniapp__service");else{var u=JSON.stringify(i);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(u,",").concat(JSON.stringify(t),");"))}},a={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;r("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("redirectTo",{url:encodeURI(n)})},getEnv:function(e){o()?e({nvue:!0}):window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r("postMessage",e.data||{})}},d=/uni-app/i.test(navigator.userAgent),s=/Html5Plus/i.test(navigator.userAgent),w=/complete|loaded|interactive/;var u=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var g=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var v=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var m=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);var f=window.ks&&window.ks.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var l=window.tt&&window.tt.miniProgram&&/Lark|Feishu/i.test(navigator.userAgent);var _=window.jd&&window.jd.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);for(var E,b=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},h=[function(e){if(d||s)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&w.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),a},function(e){if(m)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(u){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(v)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(f)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.ks.miniProgram},function(e){if(l)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(_)return window.JDJSBridgeReady&&window.JDJSBridgeReady.invoke?setTimeout(e,0):document.addEventListener("JDJSBridgeReady",e),window.jd.miniProgram},function(e){return document.addEventListener("DOMContentLoaded",e),a}],y=0;y void) {
11 | // #ifdef APP-PLUS
12 | // learn: https://ask.dcloud.net.cn/article/35621
13 | const data = {
14 | qq: "mqq://",
15 | wx: "weixin://",
16 | zfb: "alipay://",
17 | sina: "sinaweibo://",
18 | taobao: "taobao://",
19 | };
20 | plus.runtime.openURL(data[name], callback);
21 | // #endif
22 | }
23 |
24 | /**
25 | * 显示加载提示
26 | * @param text 提示文字
27 | */
28 | export function showLoading(text: string = "加载中..") {
29 | uni.hideLoading();
30 | uni.showLoading({
31 | title: text,
32 | mask: false,
33 | });
34 | }
35 |
36 | /**
37 | * 显示提示条
38 | * @param tip 提示文字
39 | * @param duration 持续时间
40 | */
41 | export function showToast(tip: string, duration = 2000) {
42 | uni.showToast({
43 | title: tip,
44 | // position: "bottom",
45 | icon: "none",
46 | duration: duration,
47 | // image: src
48 | });
49 | }
50 |
51 | /**
52 | * 显示提示框
53 | * @param content 提示的内容
54 | * @param success 确认回调
55 | * @param title 提示标题
56 | */
57 | export function showAlert(content: string, success?: (res: UniApp.ShowModalRes) => void, title: string = "操作提示") {
58 | uni.showModal({
59 | title: title,
60 | content: content,
61 | showCancel: false,
62 | success: success,
63 | });
64 | }
65 |
66 | interface ShowConfirmOptions {
67 | /** 内容 */
68 | content: string;
69 | /** 标题 */
70 | title?: string;
71 | /** 确认回调 */
72 | confirm?(): void;
73 | /** 取消回调 */
74 | cancel?(): void;
75 | /** 确认按钮文字 */
76 | confirmText?: string;
77 | /** 取消按钮文字,超过4个中文会报错 */
78 | cancelText: string;
79 | }
80 |
81 | /**
82 | * 确认弹窗
83 | * @param options 传参对象
84 | */
85 | export function showConfirm(options: ShowConfirmOptions) {
86 | uni.showModal({
87 | title: options.title || "操作提示",
88 | content: options.content,
89 | showCancel: true,
90 | confirmText: options.confirmText || "确认",
91 | cancelText: options.cancelText || "取消",
92 | success(res) {
93 | if (res.confirm) {
94 | options.confirm && options.confirm();
95 | } else if (res.cancel) {
96 | options.cancel && options.cancel();
97 | }
98 | },
99 | });
100 | }
101 |
102 | /**
103 | * 复制文本
104 | * @param value 复制的内容
105 | * @param success 成功回调
106 | */
107 | export function copyText(value: string, success?: () => void) {
108 | value = value.replace(/(^\s*)|(\s*$)/g, "");
109 | if (!value) {
110 | return showToast("复制的内容不能为空!");
111 | }
112 |
113 | // #ifndef H5
114 | uni.setClipboardData({
115 | data: value,
116 | success() {
117 | showToast("复制成功");
118 | success && success();
119 | },
120 | });
121 | // #endif
122 |
123 | // #ifdef H5
124 | const id = "the-clipboard";
125 | let clipboard = document.getElementById(id) as HTMLTextAreaElement;
126 | if (!clipboard) {
127 | clipboard = document.createElement("textarea");
128 | clipboard.id = id;
129 | clipboard.readOnly = true;
130 | clipboard.style.cssText = "font-size: 15px; position: fixed; top: -1000%; left: -1000%;";
131 | document.body.appendChild(clipboard);
132 | }
133 | clipboard.value = value;
134 | clipboard.select();
135 | clipboard.setSelectionRange(0, clipboard.value.length);
136 | const state = document.execCommand("copy");
137 | if (state) {
138 | showToast("复制成功");
139 | success && success();
140 | } else {
141 | showToast("复制失败");
142 | }
143 | // #endif
144 | }
145 |
146 | interface ScrollviewCenterOptions {
147 | /**
148 | * 当前实例
149 | * ```js
150 | * import { getCurrentInstance } from "vue";
151 | * getCurrentInstance();
152 | * ```
153 | */
154 | ctx: T;
155 | /** 要滚动的目标节点`id` */
156 | id: string;
157 | /** ``的宽度,默认是屏幕宽度 */
158 | wrapWidth?: number;
159 | /** 点击事件 */
160 | event?: Event;
161 | /** 是否主动设置偏移到中心位置,设置值时,`event`不需要传入 */
162 | scrollValue?: number;
163 | /** 回调 */
164 | callback?: (left: number, info: UniApp.NodeInfo) => void;
165 | }
166 |
167 | /**
168 | * 监听``组件指定元素滚动到视图中心的偏移值
169 | * @param option 配置参数
170 | */
171 | export function onScrollviewCenter(option: ScrollviewCenterOptions) {
172 | const width = option.wrapWidth || uni.getSystemInfoSync().windowWidth;
173 | nextTick(function () {
174 | const node = uni.createSelectorQuery().in(option.ctx).select(`#${option.id}`);
175 | const left = option.event ? (option.event.currentTarget as any).offsetLeft : 0;
176 | node
177 | .boundingClientRect(function (nodeInfo) {
178 | let result = 0;
179 | if (nodeInfo && !Array.isArray(nodeInfo)) {
180 | if (typeof option.scrollValue === "number") {
181 | result = option.scrollValue + nodeInfo.left! + nodeInfo.width! / 2 - width / 2;
182 | } else {
183 | result = left + nodeInfo.width! / 2 - width / 2;
184 | }
185 | }
186 | typeof option.callback === "function" && option.callback(result, nodeInfo as any);
187 | })
188 | .exec();
189 | });
190 | }
191 |
--------------------------------------------------------------------------------
/src/pages/tabBar/components/addGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
37 |
38 |
39 |
40 |
133 |
134 |
183 |
--------------------------------------------------------------------------------
/src/type.d.ts:
--------------------------------------------------------------------------------
1 | // 全局声明文件,局部声明文件不要写在这里
2 | interface PageListItem {
3 | center: boolean;
4 | isTitle: boolean;
5 | lineIndex: number;
6 | pFirst: boolean;
7 | pIndex: number;
8 | text: string;
9 | textIndex: number;
10 | }
11 |
12 | type PageList = PageListItem[];
13 | type ChapterList = PageList[];
14 | interface PopupInstance {
15 | open: () => void;
16 | close: () => void;
17 | }
18 |
19 | interface PainterInstance {
20 | render: (options: any) => void;
21 | canvasToTempFilePathSync: (options: any) => void;
22 | }
23 |
24 | // confirm
25 | type ConfirmOptions = {
26 | type: "success" | "warn" | "error" | "info";
27 | cancelText: string;
28 | confirmText: string;
29 | title: string;
30 | content: string;
31 | beforeClose: boolean;
32 | mode: "base" | "input";
33 | maxlength: number;
34 | showClose: boolean;
35 | value: string;
36 | placeholder: string;
37 | key: string;
38 | };
39 |
40 | interface ConfirmInstance {
41 | show: (params: Partial) => void;
42 | close: () => void;
43 | }
44 |
45 | /** 深层递归所有属性为可选 */
46 | type DeepPartial = {
47 | [P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
48 | };
49 |
50 | /** 深层递归所有属性为只读 */
51 | type DeepReadonly = {
52 | readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
53 | };
54 |
55 | /** 深层递归所有属性为必选选(貌似不生效) */
56 | type DeepRequired = {
57 | [P in keyof T]-?: T[P] extends object ? Required : T[P];
58 | };
59 |
60 | /** 运算符号 */
61 | type NumberSymbols = "+" | "-" | "*" | "/";
62 |
63 | /**
64 | * `JavaScript`类型
65 | * - 这里只枚举一些常见类型,后续根据使用场景自行添加即可
66 | */
67 | type JavaScriptTypes =
68 | | "string"
69 | | "number"
70 | | "array"
71 | | "object"
72 | | "boolean"
73 | | "function"
74 | | "null"
75 | | "undefined"
76 | | "regexp"
77 | | "promise"
78 | | "formdata";
79 |
80 | /** 基础对象 */
81 | interface BaseObj {
82 | [key: string]: T;
83 | }
84 |
85 | /** 接口请求基础响应数据 */
86 | interface ApiResult {
87 | /** 接口状态`code === 1`为成功 */
88 | code: number;
89 | /** 接口响应数据 */
90 | data: T;
91 | /** 接口响应信息 */
92 | msg: string;
93 | }
94 |
95 | /** 接口请求列表响应数据 */
96 | interface ApiResultList extends PageInfo {
97 | /** 列表数据 */
98 | list: Array;
99 | }
100 |
101 | interface requestResultList {
102 | code: number | string;
103 | message: string;
104 | data: T;
105 | }
106 |
107 | /** 请求配置 */
108 | interface RequestOptions {
109 | /**
110 | * 请求头配置(header 中不能设置 Referer。),会覆盖默认设置
111 | * - 需要表单形式就`headers: { "codeMode": "form" }`;
112 | * - 其他头部设置自行定义
113 | */
114 | headers: BaseObj & {
115 | codeMode?: "json" | "form";
116 | };
117 | /** 请求数据类型 */
118 | dataType: "json" | "text" | "";
119 | /** 接口数据响应类型 */
120 | responseType: "json" | "arraybuffer" | "blob" | "stream" | "document" | "text";
121 | /** 是否在`res.code !== 1`的时候显示提示,默认`false`,传入`true`则用`res.msg`作为提示,也可以传入字符串作为提示内容 */
122 | showTip: string | boolean;
123 | }
124 |
125 | type RequestMethod = "GET" | "POST" | "DELETE" | "PUT";
126 |
127 | /** 页码信息 */
128 | interface PageInfo {
129 | /** 一页多少条 */
130 | pageSize: number;
131 | /** 当前页,从`1`开始 */
132 | currentPage: number;
133 | /** 总数 */
134 | total?: number;
135 | }
136 |
137 | interface parseRequest {
138 | origin: String;
139 | href?: String;
140 | }
141 |
142 | type OriginItem = { origin: string; pathname: string };
143 | type Origins = Array;
144 |
145 | interface bookInfo {
146 | author: string;
147 | bookName: string;
148 | origin: string;
149 | pathname: string;
150 | image: string;
151 | latestChapter: string;
152 | latestUpdateTime: string;
153 | description: string;
154 | categories: string;
155 | origins: Origins;
156 | links: {
157 | href: string;
158 | text: string;
159 | }[];
160 | }
161 | interface BooksShelvesData extends bookInfo {
162 | uid: string;
163 | latestReadeTimestamp?: number; // 上一次阅读时间
164 | chapter: number;
165 | pageIndex: number;
166 | stickyIndex?: number; // 是否置顶
167 | group?: string; // 所属分组
168 | isInBookShelves?: boolean; // 是否在书架
169 | show?: boolean; // 是否展示 -> 有分组的情况
170 | }
171 | interface groupBook {
172 | index: number;
173 | group: string;
174 | data: BooksShelvesData[];
175 | type: string;
176 | }
177 |
178 | interface BookGrop {
179 | name: string;
180 | stickyIndex?: number;
181 | }
182 |
183 | interface SourceMap {
184 | origin: string;
185 | name: string;
186 | enable: boolean;
187 | }
188 |
189 | interface BookShelves {
190 | sort: "default" | "join" | "chart";
191 | data: BooksShelvesData[];
192 | }
193 |
194 | interface TempDataReflect {
195 | author: string;
196 | bookName: string;
197 | uid: string;
198 | }
199 |
200 | interface searchBookList {
201 | author: string;
202 | bookName: string;
203 | origin: string;
204 | pathname: string;
205 | image: string;
206 | latestChapter: string;
207 | categories: string;
208 | }
209 |
210 | interface TopBookInfo {
211 | bookName: string;
212 | author: string;
213 | image: string;
214 | categories: string;
215 | description: string;
216 | status: string;
217 | tag: string;
218 | }
219 |
220 | interface cacheInfo {
221 | content: string;
222 | title: string;
223 | }
224 |
225 | interface chapterInfo {
226 | pageIndex: number;
227 | chapter: number;
228 | cache: cacheInfo[];
229 | mark: BookMark[];
230 | bookInfo: bookInfo;
231 | uid: string;
232 | }
233 |
234 | interface BookMark {
235 | pageIndex: number;
236 | pageCount: number;
237 | chapter: number;
238 | title: string;
239 | time: string;
240 | }
241 |
242 | /** `uni-app`change事件参数 */
243 | interface UniAppChangeEvent {
244 | detail: {
245 | /** `@change`事件触发的值 */
246 | value: T;
247 | };
248 | }
249 |
250 | interface Window {
251 | /**
252 | * 当前版本,方便在控制台查看调试用
253 | * @description 引用的是`package.json`中的`version`
254 | */
255 | version: string;
256 | }
257 |
258 | /** .nvue文件专用模块 */
259 | declare const weex: any;
260 |
--------------------------------------------------------------------------------
/src/parser/catalog.ts:
--------------------------------------------------------------------------------
1 | import * as cheerio from "cheerio";
2 | import * as api from "@/api/common";
3 | import sourceJson from "./source";
4 | import { URLPolyfill } from "@/utils";
5 | const source = sourceJson.local;
6 |
7 | export const getCatalogs = async (params: { url: string }) => {
8 | let content = (await getDeepthHtml(params.url)) as bookInfo;
9 |
10 | const result = handlerCatelogs(content);
11 | return Promise.resolve(result);
12 | };
13 |
14 | const getDeepthHtml = async (url: string, content: AnyObject = {}) => {
15 | try {
16 | let res = (await api.getCatalogsByApp(url)) as string;
17 | if (res) {
18 | const parse = URLPolyfill(url);
19 | const targetSource = source.find((i) => i.origin === parse.origin);
20 | if (targetSource) {
21 | const $ = cheerio.load(res);
22 | const { wrapContainer, infomation, pagination, redirect } = targetSource.catalogs.rules;
23 |
24 | let info = {
25 | bookName: "",
26 | author: "",
27 | image: "",
28 | categories: "",
29 | latestChapter: "",
30 | latestUpdateTime: "",
31 | description: "",
32 | };
33 |
34 | const reflect = [
35 | ["bookName", infomation.bookName],
36 | ["author", infomation.author],
37 | ["image", infomation.image],
38 | ["categories", infomation.categories],
39 | ["latestChapter", infomation.latestChapter],
40 | ["latestUpdateTime", infomation.latestUpdateTime],
41 | ["description", infomation.description],
42 | ];
43 |
44 | type HanderKeys =
45 | | "bookName"
46 | | "author"
47 | | "image"
48 | | "categories"
49 | | "latestChapter"
50 | | "latestUpdateTime"
51 | | "description";
52 |
53 | reflect.forEach((arr: any) => {
54 | const key: HanderKeys = arr[0];
55 | const rule = arr[1];
56 | let node = $(rule.selector);
57 | if (rule?.hasOwnProperty("nthchild")) {
58 | const num = rule.nthchild;
59 | if (node?.length >= num + 1) {
60 | node = node.eq(num);
61 | }
62 | }
63 | if (rule.type === "element") {
64 | if (rule.attr) {
65 | info[key] = node.attr(rule.attr) || "";
66 | } else {
67 | info[key] = node.text() || "";
68 | }
69 |
70 | if (rule.subPath) {
71 | info[key] = `${parse.origin}${info[key]}`;
72 | }
73 | } else {
74 | info[key] = node.attr("content") || "";
75 | }
76 |
77 | // 处理文字信息
78 | if (rule?.handler) {
79 | Object.entries(rule.handler).forEach(([type, value]) => {
80 | switch (type) {
81 | case "replace":
82 | info[key] = info[key].replace(new RegExp(value as string, "g"), rule.handler?.replaceValue || "");
83 | break;
84 | default:
85 | break;
86 | }
87 | });
88 | }
89 | });
90 |
91 | if (!content.isParse) {
92 | content = {
93 | isParse: true,
94 | origin: parse.origin,
95 | pathname: parse.pathname,
96 | links: [],
97 | ...info,
98 | };
99 | }
100 |
101 | const wrapperContainer = $(wrapContainer.selector);
102 | wrapperContainer.each((index, element) => {
103 | let href = $(element).attr("href");
104 | if (redirect) {
105 | href = `${parse.pathname}${href}`;
106 | }
107 |
108 | content.links.push({
109 | href,
110 | text: $(element).text(),
111 | });
112 | });
113 | if (redirect) {
114 | let node = $(redirect.selector);
115 | if (redirect?.hasOwnProperty("nthchild")) {
116 | const num = redirect.nthchild;
117 | if (node?.length >= num + 1) {
118 | node = node.eq(num);
119 | }
120 | }
121 | const redirectUrl = node.attr("href");
122 | if (redirectUrl) {
123 | const nextUrl = `${parse.origin}${redirectUrl}`;
124 |
125 | content = await getDeepthHtml(nextUrl, content);
126 | }
127 | }
128 | if (pagination) {
129 | const nextPagination = $(pagination.selector).last();
130 |
131 | let nextPath = nextPagination.attr("href");
132 | let nextButtonName = nextPagination.text();
133 |
134 | if (nextPath && nextButtonName === "下一页") {
135 | const nextUrl = pagination.fullpath
136 | ? `${parse.origin}${parse.pathname}${nextPath}`
137 | : `${parse.origin}${nextPath}`;
138 |
139 | content = await getDeepthHtml(nextUrl, content);
140 | }
141 | }
142 | }
143 | }
144 | } catch (error) {
145 | console.log(error);
146 | return content;
147 | }
148 |
149 | return content as bookInfo;
150 | };
151 |
152 | const handlerCatelogs = (content: bookInfo) => {
153 | const array = content.links || [];
154 |
155 | type LinkType = {
156 | href: string;
157 | text: string;
158 | };
159 | // 过滤重复的 href
160 | const uniqueArray = Array.from(new Set(array.map((item) => item.href))).map((href) =>
161 | array.find((item) => item.href === href),
162 | ) as LinkType[];
163 |
164 | // 按照 xxxx.html 从小到大排序
165 | uniqueArray.sort((a, b) => {
166 | const numA = parseInt(a?.href?.split("/")?.pop()?.split(".html")[0] || "0");
167 | const numB = parseInt(b?.href?.split("/")?.pop()?.split(".html")[0] || "0");
168 | return numA - numB;
169 | });
170 |
171 | content.links = uniqueArray;
172 | content.origins = [
173 | {
174 | origin: content.origin,
175 | pathname: content.pathname,
176 | },
177 | ];
178 |
179 | return content;
180 | };
181 |
--------------------------------------------------------------------------------
/src/components/painter/components/l-painter/nvue.js:
--------------------------------------------------------------------------------
1 | // #ifdef APP-NVUE
2 | import {
3 | sleep,
4 | getImageInfo,
5 | isBase64,
6 | networkReg
7 | } from './utils';
8 | const dom = weex.requireModule('dom')
9 | import {
10 | version
11 | } from '../../package.json'
12 |
13 | export default {
14 | data() {
15 | return {
16 | tempFilePath: [],
17 | isInitFile: false,
18 | osName: uni.getSystemInfoSync().osName
19 | }
20 | },
21 | methods: {
22 | getParentWeith() {
23 | return new Promise(resolve => {
24 | dom.getComponentRect(this.$refs.limepainter, (res) => {
25 | this.parentWidth = Math.ceil(res.size.width)
26 | this.canvasWidth = this.canvasWidth || this.parentWidth || 300
27 | this.canvasHeight = res.size.height || this.canvasHeight || 150
28 | resolve(res.size)
29 | })
30 | })
31 | },
32 | onPageFinish() {
33 | this.webview = this.$refs.webview
34 | this.webview.evalJS(`init(${this.dpr})`)
35 | },
36 | onMessage(e) {
37 | const res = e.detail.data[0] || null;
38 | if (res.event) {
39 | if (res.event == 'inited') {
40 | this.inited = true
41 | }
42 | if (res.event == 'fail') {
43 | this.$emit('fail', res)
44 | }
45 | if (res.event == 'layoutChange') {
46 | const data = typeof res.data == 'string' ? JSON.parse(res.data) : res.data
47 | this.canvasWidth = Math.ceil(data.width);
48 | this.canvasHeight = Math.ceil(data.height);
49 | }
50 | if (res.event == 'progressChange') {
51 | this.progress = res.data * 1
52 | }
53 | if (res.event == 'file') {
54 | this.tempFilePath.push(res.data)
55 | if (this.tempFilePath.length > 7) {
56 | this.tempFilePath.shift()
57 | }
58 | return
59 | }
60 | if (res.event == 'success') {
61 | if (res.data) {
62 | this.tempFilePath.push(res.data)
63 | if (this.tempFilePath.length > 8) {
64 | this.tempFilePath.shift()
65 | }
66 | if (this.isCanvasToTempFilePath) {
67 | this.setFilePath(this.tempFilePath.join(''), {
68 | isEmit: true
69 | })
70 | }
71 | } else {
72 | this.$emit('fail', 'canvas no data')
73 | }
74 | return
75 | }
76 | this.$emit(res.event, JSON.parse(res.data));
77 | } else if (res.file) {
78 | this.file = res.data;
79 | } else {
80 | console.info(res[0])
81 | }
82 | },
83 | getWebViewInited() {
84 | if (this.inited) return Promise.resolve(this.inited);
85 | return new Promise((resolve) => {
86 | this.$watch(
87 | 'inited',
88 | async val => {
89 | if (val) {
90 | resolve(val)
91 | }
92 | }, {
93 | immediate: true
94 | }
95 | );
96 | })
97 | },
98 | getTempFilePath() {
99 | if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
100 | return new Promise((resolve) => {
101 | this.$watch(
102 | 'tempFilePath',
103 | async val => {
104 | if (val.length == 8) {
105 | resolve(val.join(''))
106 | }
107 | }, {
108 | deep: true
109 | }
110 | );
111 | })
112 | },
113 | getWebViewDone() {
114 | if (this.progress == 1) return Promise.resolve(this.progress);
115 | return new Promise((resolve) => {
116 | this.$watch(
117 | 'progress',
118 | async val => {
119 | if (val == 1) {
120 | this.$emit('done')
121 | this.done = true
122 | this.runTask()
123 | resolve(val)
124 | }
125 | }, {
126 | immediate: true
127 | }
128 | );
129 | })
130 | },
131 | async render(args) {
132 | try {
133 | await this.getSize(args)
134 | const {
135 | width
136 | } = args.css || args
137 | if (!width && this.parentWidth) {
138 | Object.assign(args, {
139 | width: this.parentWidth
140 | })
141 | }
142 | const newNode = await this.calcImage(args);
143 | await this.getWebViewInited()
144 | this.webview.evalJS(`source(${JSON.stringify(newNode)})`)
145 | await this.getWebViewDone()
146 | await sleep(this.afterDelay)
147 | if (this.isCanvasToTempFilePath) {
148 | const params = {
149 | fileType: this.fileType,
150 | quality: this.quality
151 | }
152 | this.webview.evalJS(`save(${JSON.stringify(params)})`)
153 | }
154 | return Promise.resolve()
155 | } catch (e) {
156 | this.$emit('fail', e)
157 | }
158 | },
159 | async calcImage(args) {
160 | let node = JSON.parse(JSON.stringify(args))
161 | const urlReg = /url\((.+)\)/
162 | const {
163 | backgroundImage
164 | } = node.css || {}
165 | const isBG = backgroundImage && urlReg.exec(backgroundImage)[1]
166 | const url = node.url || node.src || isBG
167 | if (['text', 'qrcode'].includes(node.type)) {
168 | return node
169 | }
170 | if ((node.type === "image" || isBG) && url && !isBase64(url) && (this.osName == 'ios' || !networkReg
171 | .test(url))) {
172 | let {
173 | path
174 | } = await getImageInfo(url, true)
175 | if (isBG) {
176 | node.css.backgroundImage = `url(${path})`
177 | } else {
178 | node.src = path
179 | }
180 | } else if (node.views && node.views.length) {
181 | for (let i = 0; i < node.views.length; i++) {
182 | node.views[i] = await this.calcImage(node.views[i])
183 | }
184 | }
185 | return node
186 | },
187 | async canvasToTempFilePath(args = {}) {
188 | if (!this.inited) {
189 | return this.$emit('fail', 'no init')
190 | }
191 | this.tempFilePath = []
192 | if (args.fileType == 'jpg') {
193 | args.fileType = 'jpeg'
194 | }
195 |
196 | this.webview.evalJS(`save(${JSON.stringify(args)})`)
197 | try {
198 | let tempFilePath = await this.getTempFilePath()
199 |
200 | tempFilePath = await this.setFilePath(tempFilePath, args)
201 | args.success({
202 | errMsg: "canvasToTempFilePath:ok",
203 | tempFilePath
204 | })
205 | } catch (e) {
206 | console.log('e', e)
207 | args.fail({
208 | error: e
209 | })
210 | }
211 | }
212 | }
213 | }
214 | // #endif
--------------------------------------------------------------------------------
/src/pages/tabBar/personal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
20 |
21 |
22 | {{ item.name }}
23 |
24 | {{ item.text }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
142 |
217 |
--------------------------------------------------------------------------------
/src/parser/search.ts:
--------------------------------------------------------------------------------
1 | import * as cheerio from "cheerio";
2 | import * as api from "@/api/common";
3 | import sourceJson from "./source";
4 | const source = sourceJson.local;
5 |
6 | export async function searchBook(params: {
7 | fuzzy: string;
8 | limit?: number;
9 | pageSize: number;
10 | pageNo: number;
11 | origins?: Array;
12 | }) {
13 | let bookList: searchBookList[] = [];
14 |
15 | const { pageSize, pageNo, origins } = params;
16 | let allowSource = source;
17 | // 从指定源搜索
18 | if (origins?.length) {
19 | allowSource = source.filter((i) => origins.includes(i.origin));
20 | }
21 | const start = (pageNo - 1) * pageSize;
22 | const end = pageSize * pageNo;
23 | const requestList = allowSource.slice(start, end);
24 |
25 | for (let index = 0; index < requestList.length; index++) {
26 | try {
27 | const element = requestList[index];
28 | let res: string;
29 | if (element.search.method === "POST") {
30 | const options: AnyObject = {};
31 | if (element.search.queryType === "FormData") {
32 | options.sendFormData = true;
33 | }
34 | const [url, query] = formatPostUrl(element.origin, element.search.pathname, params, element.search.query);
35 | res = (await api.searchBookByApp(url, element.search.method, query, options)) as string;
36 | } else {
37 | const url = formatUrl(element.origin, element.search.pathname, params, element.search.query);
38 | res = (await api.searchBookByApp(url)) as string;
39 | }
40 |
41 | if (res) {
42 | let value = paraseSearchContent(res, element);
43 | if (params.limit) {
44 | value = value.slice(0, params.limit);
45 | }
46 |
47 | bookList = bookList.concat(value);
48 | }
49 | } catch (error) {
50 | console.log(error);
51 | }
52 | }
53 |
54 | const result: searchBookList[] = handlerSearch(bookList);
55 | const value = { data: result, complete: end >= allowSource.length - 1 };
56 | return Promise.resolve(value);
57 | }
58 |
59 | function paraseSearchContent(res: string, item: any) {
60 | const bookList: searchBookList[] = [];
61 | const { rules } = item.search;
62 | const $ = cheerio.load(res);
63 |
64 | // 第一步:获取类
65 | const wrapperContainer = $(rules.wrapContainer.selector);
66 | // 第二步:遍历每个元素,提取其内部具体内容
67 | wrapperContainer.each((index, element) => {
68 | // 封面 书名 作者 目录地址 分类 最新章节 最近更新时间
69 | const { image, bookName, author, pathname, categories, latestChapter } = rules.infomation;
70 |
71 | const bookInfo = {
72 | origin: item.origin,
73 | bookName: "",
74 | author: "",
75 | image: "",
76 | pathname: "",
77 | categories: "",
78 | latestChapter: "",
79 | };
80 |
81 | const handlers = [
82 | ["image", image],
83 | ["bookName", bookName],
84 | ["author", author],
85 | ["pathname", pathname],
86 | ["categories", categories],
87 | ["latestChapter", latestChapter],
88 | ];
89 |
90 | type HanderKeys = "image" | "bookName" | "author" | "pathname" | "categories" | "latestChapter";
91 | handlers.forEach((arr) => {
92 | const key: HanderKeys = arr[0];
93 | const rule = arr[1];
94 | if (!rule) return;
95 |
96 | const ele = $(element).find(rule.selector);
97 |
98 | if (ele) {
99 | if (key === "image") {
100 | bookInfo[key] = ele.attr(rule.attr || "src") || "";
101 | } else if (key === "pathname") {
102 | bookInfo[key] = ele.attr(rule.attr || "href") || "";
103 | } else {
104 | bookInfo[key] = ele.text() || "";
105 | }
106 |
107 | if (rule.subPath) {
108 | bookInfo[key] = `${item.origin}${bookInfo[key]}`;
109 | }
110 | }
111 |
112 | if (rule?.hasOwnProperty("nthchild")) {
113 | const num = rule.nthchild;
114 | if (ele?.length >= num + 1) {
115 | const text = ele.eq(num).text() || "";
116 | bookInfo[key] = text;
117 | }
118 | }
119 | if (rule?.handler) {
120 | Object.entries(rule.handler).forEach(([type, value]) => {
121 | switch (type) {
122 | case "replace":
123 | bookInfo[key] = bookInfo[key].replace(new RegExp(value as string, "g"), rule.handler?.replaceValue || "");
124 | break;
125 | default:
126 | break;
127 | }
128 | });
129 | }
130 |
131 | bookInfo[key] = bookInfo[key].trim();
132 | });
133 | bookList.push(bookInfo);
134 | });
135 |
136 | return bookList;
137 | }
138 |
139 | const handlerSearch = (bookList: searchBookList[]) => {
140 | bookList = bookList.filter((i) => i.bookName && i.pathname);
141 |
142 | return bookList;
143 | };
144 |
145 | const formatUrl = (origin: string, path: string, params: { fuzzy: string; limit?: number }, query: AnyObject) => {
146 | origin = origin.endsWith("/") ? origin.slice(0, origin.length - 1) : origin;
147 | path = path.startsWith("/") ? path : `/${path}`;
148 | let str = `${origin}${path}?`;
149 | let searchParams = ``;
150 |
151 | Object.entries(query).forEach(([key, value]) => {
152 | if (typeof value === "string") {
153 | searchParams += `&${key}=${encodeURIComponent(value)}`;
154 | }
155 | if (value instanceof Object) {
156 | switch (value.type) {
157 | case "date":
158 | searchParams += `&${key}=${new Date().getTime()}`;
159 | break;
160 | case "query":
161 | searchParams += `&${key}=${encodeURIComponent(params.fuzzy)}`;
162 | break;
163 | default:
164 | break;
165 | }
166 | }
167 | });
168 |
169 | str += searchParams.slice(1, searchParams.length);
170 | return str;
171 | };
172 |
173 | const formatPostUrl = (origin: string, path: string, params: { fuzzy: string; limit?: number }, query: AnyObject) => {
174 | origin = origin.endsWith("/") ? origin.slice(0, origin.length - 1) : origin;
175 | path = path.startsWith("/") ? path : `/${path}`;
176 | let str = `${origin}${path}`;
177 | let searchParams: AnyObject = {};
178 |
179 | Object.entries(query).forEach(([key, value]) => {
180 | if (typeof value === "string") {
181 | searchParams[key] = value;
182 | }
183 | if (value instanceof Object) {
184 | switch (value.type) {
185 | case "date":
186 | searchParams[key] = new Date().getTime();
187 | break;
188 | case "query":
189 | searchParams[key] = params.fuzzy;
190 | break;
191 | default:
192 | break;
193 | }
194 | }
195 | });
196 | const res = [str, searchParams];
197 |
198 | return res as [string, AnyObject];
199 | };
200 |
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import config from "./Config";
2 | import store from "@/store";
3 | import { showLoading, showToast } from "@/utils/Control";
4 | import { setUserAgent } from "./RequestHeader";
5 |
6 | uni.addInterceptor("request", {
7 | invoke(args) {
8 | // console.log(args.header);
9 | },
10 | success(args) {
11 | //成功回调拦截
12 | if (!args || !args.statusCode) {
13 | return Promise.reject("错误的消息内容。");
14 | }
15 | //中文显示
16 | switch (args.statusCode) {
17 | case 100:
18 | args.message = "客户端应继续其请求";
19 | break;
20 | case 101:
21 | args.message = "服务器根据客户端的请求切换协议。";
22 | break;
23 | case 200:
24 | args.message = "请求成功。";
25 | break;
26 | case 201:
27 | args.message = "成功请求并创建了新的资源。";
28 | break;
29 | case 202:
30 | args.message = "已经接受请求,但未处理完成。";
31 | break;
32 | case 203:
33 | args.message = "非授权信息。";
34 | break;
35 | case 204:
36 | args.message = "服务器成功处理,但未返回内容。";
37 | break;
38 | case 205:
39 | args.message = "服务器处理成功,用户终端(例如:浏览器)应重置文档视图。";
40 | break;
41 | case 206:
42 | args.message = "服务器成功处理了部分GET请求。";
43 | break;
44 | case 300:
45 | args.message = "请求的资源可包括多个位置。";
46 | break;
47 | case 301:
48 | args.message = "请求的资源已被永久的移动到新URI。";
49 | break;
50 | case 302:
51 | args.message = "临时移动。";
52 | break;
53 | case 303:
54 | args.message = "查看其它地址。";
55 | break;
56 | case 304:
57 | args.message = "所请求的资源未修改。";
58 | break;
59 | case 305:
60 | args.message = "所请求的资源必须通过代理访问。";
61 | break;
62 | case 306:
63 | args.message = "已经被废弃的HTTP状态码。";
64 | break;
65 | case 307:
66 | args.message = "临时重定向。";
67 | break;
68 | case 400:
69 | args.message = "客户端请求的语法错误,服务器无法理解。";
70 | break;
71 | case 401:
72 | args.message = "请先登录系统。";
73 | //跳转到登录页面
74 | break;
75 | case 402:
76 | args.message = "暂未定义。";
77 | break;
78 | case 403:
79 | args.message = "服务器理解请求客户端的请求,但是拒绝执行此请求。";
80 | break;
81 | case 404:
82 | args.message = "服务器无法根据客户端的请求找到资源(网页)。";
83 | break;
84 | case 405:
85 | args.message = "客户端请求中的方法被禁止。";
86 | break;
87 | case 406:
88 | args.message = "服务器无法根据客户端请求的内容特性完成请求。";
89 | break;
90 | case 407:
91 | args.message = "请求要求代理的身份认证。";
92 | break;
93 | case 408:
94 | args.message = "服务器等待客户端发送的请求时间过长。";
95 | break;
96 | case 409:
97 | args.message = "服务器处理请求时发生了冲突。";
98 | break;
99 | case 410:
100 | args.message = "客户端请求的资源已经不存在。";
101 | break;
102 | case 411:
103 | args.message = "服务器无法处理客户端发送的不带Content-Length的请求信息。";
104 | break;
105 | case 412:
106 | args.message = "客户端请求信息的先决条件错误。";
107 | break;
108 | case 413:
109 | args.message = "由于请求的实体过大,服务器无法处理,因此拒绝请求。";
110 | break;
111 | case 414:
112 | args.message = "请求的URI过长(URI通常为网址),服务器无法处理。";
113 | break;
114 | case 415:
115 | args.message = "服务器无法处理请求附带的媒体格式。";
116 | break;
117 | case 416:
118 | args.message = "客户端请求的范围无效。";
119 | break;
120 | case 417:
121 | args.message = "服务器无法满足Expect的请求头信息。";
122 | break;
123 | case 500:
124 | args.message = "服务器内部错误,无法完成请求。";
125 | break;
126 | case 501:
127 | args.message = "服务器不支持请求的功能,无法完成请求。";
128 | break;
129 | case 502:
130 | args.message = "作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应。";
131 | break;
132 | case 503:
133 | args.message = "由于超载或系统维护,服务器暂时的无法处理客户端的请求。";
134 | break;
135 | case 504:
136 | args.message = "充当网关或代理的服务器,未及时从远端服务器获取请求。";
137 | break;
138 | case 505:
139 | args.message = "服务器不支持请求的HTTP协议的版本,无法完成处理。";
140 | break;
141 | default:
142 | args.message = "状态错误(" + args.statusCode + ")";
143 | break;
144 | }
145 |
146 | //处理状态码
147 | if (args.statusCode !== 200) {
148 | // showToast(args.message);
149 | return Promise.resolve({ data: "" });
150 | // return Promise.reject(args.message);
151 | }
152 |
153 | return Promise.resolve(args);
154 | },
155 | fail(err) {
156 | //失败回调拦截
157 | // showToast("无法发起请求");
158 | return Promise.resolve({ data: "" });
159 | },
160 | });
161 | /**
162 | * 基础请求
163 | * @param method 请求方法
164 | * @param path 请求路径
165 | * @param data 请求参数
166 | * @param options 其他配置参数
167 | */
168 | export function requestService(
169 | method: "GET" | "POST" | "DELETE" | "PUT",
170 | path: string,
171 | data?: any,
172 | options: Partial = {},
173 | ) {
174 | const headers = options.headers || {};
175 | return new Promise>(function (resolve, reject) {
176 | uni.request({
177 | method: method,
178 | header: {
179 | Authorization: store.user.info.token,
180 | ...headers,
181 | },
182 | url: config.apiUrl + path,
183 | data: data,
184 | timeout: config.requestOvertime,
185 | success(res) {
186 | const data = res.data as requestResultList;
187 | if (data.code === 200) {
188 | resolve(data);
189 | } else {
190 | showToast(data.message || "操作失败");
191 | reject(data.message || "操作失败");
192 | }
193 | },
194 | fail(err) {
195 | showToast("操作失败");
196 | reject(err);
197 | },
198 | });
199 | });
200 | }
201 |
202 | export function requestCrawlerHtml(url: string, method: RequestMethod = "GET", data?: any, options?: AnyObject) {
203 | let header: AnyObject = {};
204 |
205 | const ua = setUserAgent();
206 | header = Object.assign(header, {
207 | Accept: "*/*",
208 | "accept-language": "zh-CN,zh;q=0.9",
209 | "Content-Type": "text/html; charset=utf-8",
210 | ...ua,
211 | });
212 | if (options?.sendFormData) {
213 | header["Content-Type"] = "application/x-www-form-urlencoded";
214 | }
215 | // #ifdef APP-PLUS
216 | //设置userAgent代理
217 | plus.navigator.setUserAgent(ua["User-Agent"]);
218 | // #endif
219 |
220 | const promise = new Promise((resolve, reject) => {
221 | uni.request({
222 | method,
223 | url,
224 | data,
225 | header,
226 | timeout: config.requestOvertime,
227 | success: (res: any) => {
228 | resolve(res.data as any);
229 | },
230 | fail: (err) => {
231 | reject(err);
232 | },
233 | });
234 | });
235 | return Promise.resolve(promise);
236 | }
237 |
--------------------------------------------------------------------------------
/src/components/painter/changelog.md:
--------------------------------------------------------------------------------
1 | ## 1.9.6.6(2024-09-25)
2 | - fix: 修复background-position无效的问题
3 | ## 1.9.6.5(2024-04-14)
4 | - fix: 修复`nvue`无法生图的问题
5 | ## 1.9.6.4(2024-03-10)
6 | - fix: 修复代理ctx导致H5不能使用ctx.save
7 | ## 1.9.6.3(2024-03-08)
8 | - fix: 修复支付宝真机无法使用的问题
9 | ## 1.9.6.2(2024-02-22)
10 | - fix: 修复使用render函数报错的问题
11 | ## 1.9.6.1(2023-12-22)
12 | - fix: 修复字节小程序非2d字体偏移
13 | - fix: 修复`canvasToTempFilePathSync`会触发两次的问题
14 | - fix: 修复`parser`图片没有宽度的问题
15 | ## 1.9.6(2023-12-06)
16 | - fix: 修复背景图受padding影响
17 | - fix: 修复因字节报错改了代理实现导致微信报错
18 | - 1.9.5.8(2023-11-16)
19 | - fix: 修复margin问题
20 | - fix: 修复borderWidth问题
21 | - fix: 修复textBox问题
22 | - fix: 修复字节开发工具报`could not be cloned.`问题
23 | ## 1.9.5.7(2023-07-27)
24 | - fix: 去掉多余的方法
25 | - chore: 更新文档,增加自定义字体说明
26 | ## 1.9.5.6(2023-07-21)
27 | - feat: 有限的支持富文本
28 | - feat: H5和APP 增加 `hidpi` prop,主要用于大尺寸无法生成图片时用
29 | - fix: 修复 钉钉小程序 缺少 `measureText` 方法
30 | - chore: 由于微信小程序 pc 端的 canvas 2d 时不时抽风,故不使用canvas 2d
31 | ## 1.9.5.5(2023-06-27)
32 | - fix: 修复把`emoji`表情字符拆分成多个字符的情况
33 | ## 1.9.5.4(2023-06-05)
34 | - fix: 修复因`canvasToTempFilePathSync`监听导致重复调用
35 | ## 1.9.5.3(2023-05-23)
36 | - fix: 因isPc错写成了isPC导致小程序PC不能生成图片
37 | ## 1.9.5.2(2023-05-22)
38 | - feat: 删除多余文件
39 | ## 1.9.5.1(2023-05-22)
40 | - fix: 修复 文字行数与`line-clamp`相同但不满一行时也加了省略号的问题
41 | ## 1.9.5(2023-05-14)
42 | - feat: 增加 `text-indent` 和 `calc` 方法
43 | - feat: 优化 布局时间
44 | ## 1.9.4.4(2023-04-15)
45 | - fix: 修复无法匹配负值
46 | - fix: 修复 Nvue IOS getImageInfo `useCORS` 为 undefined
47 | ## 1.9.4.3(2023-04-01)
48 | - feat: 增加支持文字描边 `text-stroke: '5rpx #fff'`
49 | ## 1.9.4.2(2023-03-30)
50 | - fix: 修复 支付宝小程序 isPC 在手机也为true的问题
51 | - feat: 由 微信开发工具 3060 版 无法获取图片尺寸,现 微信开发工具 3220 版 修复该问题,故还原上一版的获取图片方式。
52 | ## 1.9.4.1(2023-03-28)
53 | - fix: 修复固定高度不正确问题
54 | ## 1.9.4(2023-03-17)
55 | - fix: nvue ios getImageInfo缺少this报错
56 | - fix: pathType 非2d无效问题
57 | - fix: 修复 小米9se 可能会存在多次init 导致画面多次放大
58 | - fix: 修复 border 分开写 width style无效问题
59 | - fix: 修复 支付宝小程序IOS 再次进入不渲染的问题
60 | - fix: 修复 支付宝小程序安卓Zindex排序错乱问题
61 | - fix: 修复 微信开发工具 3060 版 无法获取图片的问题
62 | - feat: 把 for in 改为 forEach
63 | - feat: 增加 hidden
64 | - feat: 根节点 box-sizing 默认 `border-box`
65 | - feat: 增加支持 `vw` `wh`
66 | - chore: pathType 取消 默认值,因为字节开发工具不能显示
67 | - chore: 支付宝小程序开发工具不支持 生成图片 请以真机调试为准
68 | - bug: 企业微信 2.20.3无法使用
69 | ## 1.9.3.5(2022-06-29)
70 | - feat: justifyContent 增加 `space-around`、`space-between`
71 | - feat: canvas 2d 也使用`getImageInfo`
72 | - fix: 修复 `text`的 `text-decoration`错位
73 | ## 1.9.3.4(2022-06-20)
74 | - fix: 修复 因创建节点速度问题导致顺序出错。
75 | - fix: 修复 微信小程序 PC 无法显示本地图片
76 | - fix: 修复 flex-box 对齐问题
77 | - feat: 增加 `text-shadow`
78 | - feat: 重写 `text` 对齐方式
79 | - chore: 更新文档
80 | ## 1.9.3.3(2022-06-17)
81 | - fix: 修复 支付宝小程序 canvas 2d 存在ctx.draw问题导致报错
82 | - fix: 修复 支付宝小程序 toDataURL 存在权限问题改用 `toTempFilePath`
83 | - fix: 修复 支付宝小程序 image size 问题导致 `objectFit` 无效
84 | ## 1.9.3.2(2022-06-14)
85 | - fix: 修复 image 设置背景色不生效问题
86 | - fix: 修复 nvue 环境判断缺少参数问题
87 | ## 1.9.3.1(2022-06-14)
88 | - fix: 修复 bottom 定位不对问题
89 | - fix: 修复 因小数导致计算出错换行问题
90 | - feat: 增加 `useCORS` h5端图片跨域 在设置请求头无效果后试一下设置这个值
91 | - chore: 更新文档
92 | ## 1.9.3(2022-06-13)
93 | - feat: 增加 `zIndex`
94 | - feat: 增加 `flex-box` 该功能处于原始阶段,非常简陋。
95 | - tips: QQ小程序 vue3 不支持, 为 uni 官方BUG
96 | ## 1.9.2.9(2022-06-10)
97 | - fix: 修复`text-align`及`margin`居中问题
98 | ## 1.9.2.8(2022-06-10)
99 | - fix: 修复 Nvue `canvasToTempFilePathSync` 不生效问题
100 | ## 1.9.2.7(2022-06-10)
101 | - fix: 修复 margin及padding的bug
102 | - fix: 修复 Nvue `isCanvasToTempFilePath` 不生效问题
103 | ## 1.9.2.6(2022-06-09)
104 | - fix: 修复 Nvue 不显示
105 | - feat: 增加支持字体渐变
106 | ```html
107 |
110 | ```
111 | ## 1.9.2.5(2022-06-09)
112 | - chore: 更变获取父级宽度的设定
113 | - chore: `pathType` 在canvas 2d 默认为 `url`
114 | ## 1.9.2.4(2022-06-08)
115 | - fix: 修复 `pathType` 不生效问题
116 | ## 1.9.2.3(2022-06-08)
117 | - fix: 修复 `canvasToTempFilePath` 漏写 `success` 参数
118 | ## 1.9.2.2(2022-06-07)
119 | - chore: 更新文档
120 | ## 1.9.2.1(2022-06-07)
121 | - fix: 修复 vue3 赋值给this再传入导致image无法绘制
122 | - fix: 修复 `canvasToTempFilePathSync` 时机问题
123 | - feat: canvas 2d 更改图片生成方式 `toDataURL`
124 | ## 1.9.2(2022-05-30)
125 | - fix: 修复 `canvasToTempFilePathSync` 在 vue3 下只生成一次
126 | ## 1.9.1.7(2022-05-28)
127 | - fix: 修复 `qrcode`显示不全问题
128 | ## 1.9.1.6(2022-05-28)
129 | - fix: 修复 `canvasToTempFilePathSync` 会重复多次问题
130 | - fix: 修复 `view` css `backgroundImage` 图片下载失败导致 子节点不渲染
131 | ## 1.9.1.5(2022-05-27)
132 | - fix: 修正支付宝小程序 canvas 2d版本号 2.7.15
133 | ## 1.9.1.4(2022-05-22)
134 | - fix: 修复字节小程序无法使用xml方式
135 | - fix: 修复字节小程序无法使用base64(非2D情况下工具上无法显示)
136 | - fix: 修复支付宝小程序 `canvasToTempFilePath` 报错
137 | ## 1.9.1.3(2022-04-29)
138 | - fix: 修复vue3打包后uni对象为空后的报错
139 | ## 1.9.1.2(2022-04-25)
140 | - fix: 删除多余文件
141 | ## 1.9.1.1(2022-04-25)
142 | - fix: 修复图片不显示问题
143 | ## 1.9.1(2022-04-12)
144 | - fix: 因四舍五入导致有些机型错位
145 | - fix: 修复无views报错
146 | - chore: nvue下因ios无法读取插件内static文件,改由下载方式
147 | ## 1.9.0(2022-03-20)
148 | - fix: 因无法固定尺寸导致生成图片不全
149 | - fix: 特定情况下text判断无效
150 | - chore: 本地化APP Nvue webview
151 | ## 1.8.9(2022-02-20)
152 | - fix: 修复 小程序下载最多10次并发的问题
153 | - fix: 修复 APP端无法获取本地图片
154 | - fix: 修复 APP Nvue端不执行问题
155 | - chore: 增加图片缓存机制
156 | ## 1.8.8.8(2022-01-27)
157 | - fix: 修复 主动调用尺寸问题
158 | ## 1.8.8.6(2022-01-26)
159 | - fix: 修复 nvue 下无宽度时获取父级宽度
160 | - fix: 修复 ios app 无法渲染问题
161 | ## 1.8.8(2022-01-23)
162 | - fix: 修复 主动调用时无节点问题
163 | - fix: 修复 `box-shadow` 颜色问题
164 | - fix: 修复 `transform:rotate` 角度位置问题
165 | - feat: 增加 `overflow:hidden`
166 | ## 1.8.7(2022-01-07)
167 | - fix: 修复 image 方向为 `right` 时原始宽高问题
168 | - feat: 支持 view 设置背景图 `background-image: url(xxx)`
169 | - chore: 去掉可选链
170 | ## 1.8.6(2021-11-28)
171 | - feat: 支持`view`对`inline-block`的子集使用`text-align`
172 | ## 1.8.5.5(2021-08-17)
173 | - chore: 更新文档,删除 replace
174 | - fix: 修复 text 值为 number时报错
175 | ## 1.8.5.4(2021-08-16)
176 | - fix: 字节小程序兼容
177 | ## 1.8.5.3(2021-08-15)
178 | - fix: 修复线性渐变与css现实效果不一致的问题
179 | - chore: 更新文档
180 | ## 1.8.5.2(2021-08-13)
181 | - chore: 增加`background-image`、`background-repeat` 能力,主要用于背景纹理的绘制,并不是代替`image`。例如:大面积的重复平铺的水印
182 | - 注意:这个功能H5暂时无法使用,因为[官方的API有BUG](https://ask.dcloud.net.cn/question/128793),待官方修复!!!
183 | ## 1.8.5.1(2021-08-10)
184 | - fix: 修复因`margin`报错问题
185 | ## 1.8.5(2021-08-09)
186 | - chore: 增加margin支持`auto`,以达到居中效果
187 | ## 1.8.4(2021-08-06)
188 | - chore: 增加判断缓存文件条件
189 | - fix: 修复css 多余空格报错问题
190 | ## 1.8.3(2021-08-04)
191 | - tips: 1.6.x 以下的版本升级到1.8.x后要为每个元素都加上定位:position: 'absolute'
192 | - fix: 修复只有一个view子元素时不计算高度的问题
193 | ## 1.8.2(2021-08-03)
194 | - fix: 修复 path-type 为 `url` 无效问题
195 | - fix: 修复 qrcode `text` 为空时报错问题
196 | - fix: 修复 image `src` 动态设置时不生效问题
197 | - feat: 增加 css 属性 `min-width` `max-width`
198 | ## 1.8.1(2021-08-02)
199 | - fix: 修复无法加载本地图片
200 | ## 1.8.0(2021-08-02)
201 | - chore 文档更新
202 | - 使用旧版的同学不要升级!
203 | ## 1.8.0-beta(2021-07-30)
204 | - ## 全新布局方式 不兼容旧版!
205 | - chore: 布局方式变更
206 | - tips: 微信canvas 2d 不支持真机调试
207 | ## 1.6.6(2021-07-09)
208 | - chore: 统一命名规范,无须主动引入组件
209 | ## 1.6.5(2021-06-08)
210 | - chore: 去掉console
211 | ## 1.6.4(2021-06-07)
212 | - fix: 修复 数字 为纯字符串时不转换的BUG
213 | ## 1.6.3(2021-06-06)
214 | - fix: 修复 PC 端放大的BUG
215 | ## 1.6.2(2021-05-31)
216 | - fix: 修复 报`adaptor is not a function`错误
217 | - fix: 修复 text 多行高度
218 | - fix: 优化 默认文字的基准线
219 | - feat: `@progress`事件,监听绘制进度
220 | ## 1.6.1(2021-02-28)
221 | - 删除多余节点
222 | ## 1.6.0(2021-02-26)
223 | - 调整为uni_modules目录规范
224 | - 修复:transform的rotate不能为负数问题
225 | - 新增:`pathType` 指定生成图片返回的路径类型,可选值有 `base64`、`url`
226 |
--------------------------------------------------------------------------------
/src/pages/reader/components/Origin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 | {{ bookInfo.bookName }}
12 | {{ bookInfo.author }}
13 | {{ `${item.origin}${item.pathname}` }}
14 |
15 |
16 |
17 | 加载中
18 |
19 |
20 |
21 |
22 | 取消
23 |
24 |
25 | >
26 |
27 |
28 |
29 |
30 |
213 |
214 |
267 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import "../uni.scss";
2 |
3 | @mixin reset {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | page {
10 | background-color: #fff;
11 | width: 100%;
12 | min-height: 100vh;
13 | @include reset();
14 | }
15 | view,
16 | button,
17 | text,
18 | input,
19 | textarea {
20 | font-family: "Arial", "Helvetica Neue", "Helvetica", "sans-serif";
21 | @include reset();
22 | }
23 | image,
24 | input,
25 | textarea,
26 | picker {
27 | display: block;
28 | width: 100%;
29 | @include reset();
30 | }
31 | input::after,
32 | input::before,
33 | button::after,
34 | button::before {
35 | border: none;
36 | @include reset();
37 | }
38 |
39 | .loader {
40 | transform: scale(0.5);
41 | width: 50px;
42 | aspect-ratio: 1;
43 | display: grid;
44 | }
45 | .loader::before,
46 | .loader::after {
47 | content: "";
48 | grid-area: 1/1;
49 | --c: no-repeat radial-gradient(farthest-side, #fef8f2 90%, #0000);
50 | background:
51 | var(--c) 50% 0,
52 | var(--c) 50% 100%,
53 | var(--c) 100% 50%,
54 | var(--c) 0 50%;
55 | background-size: 12px 12px;
56 | animation: l12 1s infinite;
57 | }
58 | .loader::before {
59 | margin: 4px;
60 | filter: hue-rotate(45deg);
61 | background-size: 8px 8px;
62 | animation-timing-function: linear;
63 | }
64 |
65 | .loader-dark {
66 | transform: scale(0.5);
67 | width: 50px;
68 | aspect-ratio: 1;
69 | display: grid;
70 | }
71 | .loader-dark::before,
72 | .loader-dark::after {
73 | content: "";
74 | grid-area: 1/1;
75 | --c: no-repeat radial-gradient(farthest-side, #453f39 90%, #0000);
76 | background:
77 | var(--c) 50% 0,
78 | var(--c) 50% 100%,
79 | var(--c) 100% 50%,
80 | var(--c) 0 50%;
81 | background-size: 12px 12px;
82 | animation: l12 1s infinite;
83 | }
84 | .loader-dark::before {
85 | margin: 4px;
86 | filter: hue-rotate(45deg);
87 | background-size: 8px 8px;
88 | animation-timing-function: linear;
89 | }
90 |
91 | @keyframes l12 {
92 | 100% {
93 | transform: rotate(0.5turn);
94 | }
95 | }
96 |
97 | .button-loading {
98 | display: flex;
99 | align-content: center;
100 | justify-content: center;
101 | }
102 | .uni-easyinput {
103 | .is-focused {
104 | .content-clear-icon {
105 | color: #d44842 !important;
106 | }
107 | }
108 | }
109 |
110 | .uni-searchbar__cancel {
111 | color: var(--theme-primary-color-light-1) !important;
112 | }
113 |
114 | .hz-button-primary {
115 | margin-top: 24rpx;
116 | width: 100%;
117 | height: 100rpx;
118 | border-radius: 50rpx;
119 | line-height: 100rpx;
120 | text-align: center;
121 | background-color: #d44842;
122 | color: #fef8f2;
123 | }
124 |
125 | .hz-button-plain {
126 | margin-top: 24rpx;
127 | width: 100%;
128 | height: 100rpx;
129 | border-radius: 50rpx;
130 | line-height: 100rpx;
131 | text-align: center;
132 | background-color: #fae0da;
133 | color: #d44842;
134 | }
135 |
136 | .hz-button-cancel {
137 | margin-top: 24rpx;
138 | width: 100%;
139 | height: 100rpx;
140 | border-radius: 50rpx;
141 | line-height: 100rpx;
142 | text-align: center;
143 | background-color: var(--theme-bg-color-deep);
144 | color: var(--theme-grey-color);
145 | }
146 |
147 | uni-checkbox {
148 | margin-right: 16rpx;
149 | transform: scale(0.7);
150 | .uni-checkbox-wrapper {
151 | .uni-checkbox-input {
152 | border: 2px solid var(--theme-border-color);
153 | border-radius: 80rpx;
154 | background-color: var(--theme-bg-color);
155 | }
156 | }
157 | }
158 | uni-checkbox.checked {
159 | .uni-checkbox-wrapper {
160 | .uni-checkbox-input {
161 | border-color: var(--theme-icon-active-color);
162 | background-color: var(--theme-icon-active-color);
163 | svg {
164 | color: #f1edea;
165 | path {
166 | fill: #f1edea;
167 | }
168 | }
169 | }
170 | }
171 | }
172 | .theme-dark {
173 | // .hz-button-plain {
174 | // background-color: #d44842;
175 | // color: #fef8f2;
176 | // }
177 | // .hz-button-primary {
178 | // background-color: #fae0da;
179 | // color: #d44842;
180 | // }
181 | .uni-easyinput__content {
182 | background-color: var(--theme-bg-white-color) !important;
183 | }
184 | .uni-input-input {
185 | color: var(--theme-primary-color) !important;
186 | }
187 | .uni-textarea-textarea {
188 | color: var(--theme-primary-color) !important;
189 | }
190 | }
191 |
192 | // h5默认是100vh,即使页面没有内容也会滚动,这里取消这个设定
193 | // 注意:scss 条件编译必须以 css 注释为标准才能条件编译
194 | /* #ifdef H5 */
195 | uni-page-body {
196 | min-height: auto !important;
197 | }
198 | /* #endif */
199 |
200 | /* flex布局 */
201 | .flex {
202 | display: flex;
203 | flex-wrap: nowrap;
204 | }
205 | .fwrap {
206 | display: flex;
207 | flex-wrap: wrap;
208 | }
209 | .f1 {
210 | flex: 1;
211 | }
212 | .f2 {
213 | flex: 2;
214 | }
215 | .f3 {
216 | flex: 3;
217 | }
218 | /* 垂直居中 */
219 | .fvertical {
220 | display: flex;
221 | align-items: center;
222 | }
223 | /* 水平居中 */
224 | .fcenter {
225 | display: flex;
226 | justify-content: center;
227 | }
228 | /* 水平+垂直居中 */
229 | .fvc {
230 | display: flex;
231 | align-items: center;
232 | justify-content: center;
233 | }
234 | /* 右对齐 */
235 | .fright {
236 | display: flex;
237 | justify-content: flex-end;
238 | }
239 | /* 两端对齐 */
240 | .fbetween {
241 | display: flex;
242 | justify-content: space-between;
243 | }
244 | /* 靠底部对齐 */
245 | .fbottom {
246 | display: flex;
247 | align-items: flex-end;
248 | }
249 |
250 | /* 溢出...显示 当前节点生效 */
251 | .ellipsis {
252 | overflow: hidden;
253 | text-overflow: ellipsis;
254 | white-space: nowrap;
255 | }
256 |
257 | /* 溢出...显示 子节点生效 */
258 | .ellipsis_1 {
259 | @include ellipsis(1);
260 | }
261 | .ellipsis_2 {
262 | @include ellipsis(2);
263 | }
264 | .ellipsis_3 {
265 | @include ellipsis(3);
266 | }
267 |
268 | /* 卡片 */
269 | .card {
270 | border-radius: 2px;
271 | box-shadow:
272 | 0px 1px 5px 0px rgba(0, 0, 0, 0.2),
273 | 0px 2px 2px 0px rgba(0, 0, 0, 0.14),
274 | 0px 3px 1px -2px rgba(0, 0, 0, 0.12);
275 | background-color: #fff;
276 | overflow: hidden;
277 | }
278 |
279 | .button {
280 | @include button($dark, #eee);
281 | }
282 | .button-dark {
283 | @include button(#e3b480, $dark);
284 | }
285 | .button-pink {
286 | @include button($white, $pink);
287 | }
288 | .button-red {
289 | @include button($white, $red);
290 | }
291 |
292 | .line {
293 | width: 100%;
294 | padding-top: 40rpx;
295 | margin-bottom: 40rpx;
296 | border-bottom: solid 1px #eee;
297 | }
298 |
299 | /* 上下左右边距 */
300 | @for $index from 1 through 5 {
301 | .mgl_#{$index}0 {
302 | margin-left: 10rpx * $index;
303 | }
304 | .mgr_#{$index}0 {
305 | margin-right: 10rpx * $index;
306 | }
307 | .mgt_#{$index}0 {
308 | margin-top: 10rpx * $index;
309 | }
310 | .mgb_#{$index}0 {
311 | margin-bottom: 10rpx * $index;
312 | }
313 | .pdl_#{$index}0 {
314 | padding-left: 10rpx * $index;
315 | }
316 | .pdr_#{$index}0 {
317 | padding-right: 10rpx * $index;
318 | }
319 | .pdt_#{$index}0 {
320 | padding-top: 10rpx * $index;
321 | }
322 | .pdb_#{$index}0 {
323 | padding-bottom: 10rpx * $index;
324 | }
325 | }
326 |
327 | .uni-scroll-view-refresh {
328 | transform: scale(0.65);
329 | .uni-scroll-view-refresh-inner {
330 | background-color: #fdf6f0;
331 | }
332 | .uni-scroll-view-refresh__icon {
333 | fill: #d44842;
334 | }
335 | .uni-scroll-view-refresh__spinner {
336 | circle {
337 | stroke: #d44842;
338 | color: #d44842;
339 | }
340 | }
341 | }
342 |
--------------------------------------------------------------------------------
/src/pages/tabBar/components/bookDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
160 |
161 |
252 |
--------------------------------------------------------------------------------
/src/static/iconfont/iconfont.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "4659366",
3 | "name": "reader",
4 | "font_family": "iconfont",
5 | "css_prefix_text": "icon-",
6 | "description": "",
7 | "glyphs": [
8 | {
9 | "icon_id": "20168979",
10 | "name": "禁用",
11 | "font_class": "jinyong",
12 | "unicode": "e66f",
13 | "unicode_decimal": 58991
14 | },
15 | {
16 | "icon_id": "33307514",
17 | "name": "启用",
18 | "font_class": "qiyong",
19 | "unicode": "e75e",
20 | "unicode_decimal": 59230
21 | },
22 | {
23 | "icon_id": "1817762",
24 | "name": "复制链接",
25 | "font_class": "fuzhilianjie",
26 | "unicode": "e62c",
27 | "unicode_decimal": 58924
28 | },
29 | {
30 | "icon_id": "7579868",
31 | "name": "分享海报",
32 | "font_class": "haibao",
33 | "unicode": "e674",
34 | "unicode_decimal": 58996
35 | },
36 | {
37 | "icon_id": "7610933",
38 | "name": "分组",
39 | "font_class": "fenzu1",
40 | "unicode": "e602",
41 | "unicode_decimal": 58882
42 | },
43 | {
44 | "icon_id": "20754787",
45 | "name": "新增",
46 | "font_class": "xinzeng",
47 | "unicode": "e759",
48 | "unicode_decimal": 59225
49 | },
50 | {
51 | "icon_id": "37291154",
52 | "name": "新增分组",
53 | "font_class": "xinzengfenzu",
54 | "unicode": "e746",
55 | "unicode_decimal": 59206
56 | },
57 | {
58 | "icon_id": "8867799",
59 | "name": "回到顶部",
60 | "font_class": "huidaodingbu",
61 | "unicode": "e622",
62 | "unicode_decimal": 58914
63 | },
64 | {
65 | "icon_id": "21935586",
66 | "name": "我的书架",
67 | "font_class": "shujia",
68 | "unicode": "e63c",
69 | "unicode_decimal": 58940
70 | },
71 | {
72 | "icon_id": "25202491",
73 | "name": "添加书架",
74 | "font_class": "tianjiashujia",
75 | "unicode": "e634",
76 | "unicode_decimal": 58932
77 | },
78 | {
79 | "icon_id": "25273026",
80 | "name": "已添加书架",
81 | "font_class": "zaishujia",
82 | "unicode": "e636",
83 | "unicode_decimal": 58934
84 | },
85 | {
86 | "icon_id": "3437096",
87 | "name": "置顶",
88 | "font_class": "zhiding",
89 | "unicode": "e718",
90 | "unicode_decimal": 59160
91 | },
92 | {
93 | "icon_id": "4933315",
94 | "name": "分组",
95 | "font_class": "fenzu",
96 | "unicode": "e726",
97 | "unicode_decimal": 59174
98 | },
99 | {
100 | "icon_id": "37288742",
101 | "name": "分享",
102 | "font_class": "fenxiang1",
103 | "unicode": "e64c",
104 | "unicode_decimal": 58956
105 | },
106 | {
107 | "icon_id": "40183285",
108 | "name": "移除书架",
109 | "font_class": "yichushujia",
110 | "unicode": "e601",
111 | "unicode_decimal": 58881
112 | },
113 | {
114 | "icon_id": "1223993",
115 | "name": "火",
116 | "font_class": "huo",
117 | "unicode": "e6c9",
118 | "unicode_decimal": 59081
119 | },
120 | {
121 | "icon_id": "5485668",
122 | "name": "下",
123 | "font_class": "xia",
124 | "unicode": "e61b",
125 | "unicode_decimal": 58907
126 | },
127 | {
128 | "icon_id": "5835478",
129 | "name": "刷新",
130 | "font_class": "shuaxin",
131 | "unicode": "e610",
132 | "unicode_decimal": 58896
133 | },
134 | {
135 | "icon_id": "6582362",
136 | "name": "删除",
137 | "font_class": "shanchu",
138 | "unicode": "e642",
139 | "unicode_decimal": 58946
140 | },
141 | {
142 | "icon_id": "6629848",
143 | "name": "上",
144 | "font_class": "shang-",
145 | "unicode": "e63f",
146 | "unicode_decimal": 58943
147 | },
148 | {
149 | "icon_id": "17361895",
150 | "name": "展开",
151 | "font_class": "expand",
152 | "unicode": "e61e",
153 | "unicode_decimal": 58910
154 | },
155 | {
156 | "icon_id": "716229",
157 | "name": "左",
158 | "font_class": "arrowleft",
159 | "unicode": "e61a",
160 | "unicode_decimal": 58906
161 | },
162 | {
163 | "icon_id": "1404747",
164 | "name": "圆-填充",
165 | "font_class": "circle-filled",
166 | "unicode": "e611",
167 | "unicode_decimal": 58897
168 | },
169 | {
170 | "icon_id": "3315448",
171 | "name": "右",
172 | "font_class": "arrowright",
173 | "unicode": "e633",
174 | "unicode_decimal": 58931
175 | },
176 | {
177 | "icon_id": "27188531",
178 | "name": "圆",
179 | "font_class": "circle1",
180 | "unicode": "e621",
181 | "unicode_decimal": 58913
182 | },
183 | {
184 | "icon_id": "398280",
185 | "name": "白天",
186 | "font_class": "baitian",
187 | "unicode": "e632",
188 | "unicode_decimal": 58930
189 | },
190 | {
191 | "icon_id": "1621098",
192 | "name": "夜间",
193 | "font_class": "night",
194 | "unicode": "e6fd",
195 | "unicode_decimal": 59133
196 | },
197 | {
198 | "icon_id": "4171100",
199 | "name": "书签",
200 | "font_class": "shuqian",
201 | "unicode": "e603",
202 | "unicode_decimal": 58883
203 | },
204 | {
205 | "icon_id": "4895492",
206 | "name": "加入书架",
207 | "font_class": "jiarushujia",
208 | "unicode": "e62a",
209 | "unicode_decimal": 58922
210 | },
211 | {
212 | "icon_id": "9097247",
213 | "name": "目录",
214 | "font_class": "mulu",
215 | "unicode": "e620",
216 | "unicode_decimal": 58912
217 | },
218 | {
219 | "icon_id": "10715436",
220 | "name": "设置",
221 | "font_class": "shezhi",
222 | "unicode": "e635",
223 | "unicode_decimal": 58933
224 | },
225 | {
226 | "icon_id": "15705447",
227 | "name": "缓存",
228 | "font_class": "huancun",
229 | "unicode": "e6b0",
230 | "unicode_decimal": 59056
231 | },
232 | {
233 | "icon_id": "16322570",
234 | "name": "去当前",
235 | "font_class": "qudangqian",
236 | "unicode": "e6eb",
237 | "unicode_decimal": 59115
238 | },
239 | {
240 | "icon_id": "24516455",
241 | "name": "下载云",
242 | "font_class": "xiazaiyun",
243 | "unicode": "e679",
244 | "unicode_decimal": 59001
245 | },
246 | {
247 | "icon_id": "33680200",
248 | "name": "去顶部",
249 | "font_class": "qudingbu",
250 | "unicode": "e641",
251 | "unicode_decimal": 58945
252 | },
253 | {
254 | "icon_id": "40646857",
255 | "name": "去底部",
256 | "font_class": "qudibu",
257 | "unicode": "e70f",
258 | "unicode_decimal": 59151
259 | },
260 | {
261 | "icon_id": "41894611",
262 | "name": "订阅",
263 | "font_class": "dingyue",
264 | "unicode": "e6bc",
265 | "unicode_decimal": 59068
266 | },
267 | {
268 | "icon_id": "160206",
269 | "name": "search",
270 | "font_class": "search",
271 | "unicode": "e600",
272 | "unicode_decimal": 58880
273 | },
274 | {
275 | "icon_id": "269810",
276 | "name": "more",
277 | "font_class": "more",
278 | "unicode": "e6ad",
279 | "unicode_decimal": 59053
280 | },
281 | {
282 | "icon_id": "11372706",
283 | "name": "搜索",
284 | "font_class": "sousuo",
285 | "unicode": "e8b9",
286 | "unicode_decimal": 59577
287 | },
288 | {
289 | "icon_id": "3755552",
290 | "name": "设置",
291 | "font_class": "icon-test",
292 | "unicode": "e618",
293 | "unicode_decimal": 58904
294 | },
295 | {
296 | "icon_id": "6360795",
297 | "name": "export",
298 | "font_class": "export",
299 | "unicode": "e669",
300 | "unicode_decimal": 58985
301 | },
302 | {
303 | "icon_id": "39973232",
304 | "name": "我的",
305 | "font_class": "wode",
306 | "unicode": "101a9",
307 | "unicode_decimal": 65961
308 | }
309 | ]
310 | }
311 |
--------------------------------------------------------------------------------
/src/pages/blank/origin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ showMenu ? "取消" : "编辑" }}
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
29 |
30 |
31 | {{ item.name }}
32 | {{ item.origin }}
33 |
34 | switchOrigin(v, item)"
39 | />
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
80 |
81 |
82 |
202 |
287 |
--------------------------------------------------------------------------------
/src/components/share.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
32 |
33 |
34 |
35 |
36 | handSuccessPoster($event)"
40 | custom-style="position: fixed; left: 200%"
41 | css="width: 750rpx; padding: 64rpx 32rpx; background: #FEF7F1"
42 | >
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
73 |
74 |
75 | 保存图片
76 |
77 |
78 |
79 |
201 |
202 |
251 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { modifyData } from "@/utils";
2 | import { reactive, ref } from "vue";
3 | import ModuleAppOption from "./AppOption";
4 | import ModuleUser from "./User";
5 | import { cloneDeep } from "lodash";
6 | import { v4 as uuidv4 } from "uuid";
7 |
8 | // import { getIamgeByName } from "@/utils";
9 |
10 | const BOOK_OPTION = {
11 | /** 是否首次打开 */
12 | first: true,
13 | /** 主题 */
14 | theme: "eyeProtect",
15 | themeLight: "eyeProtect",
16 | themeIndex: 1,
17 | turnPageType: "cover", // 'cover' | 'transition' | 'none'
18 | fontFamilyList: [
19 | {
20 | name: "黑体",
21 | value: "Arial",
22 | },
23 | {
24 | name: "草书",
25 | value: "Helvetica Neue",
26 | },
27 | {
28 | name: "等宽",
29 | value: "Helvetica",
30 | },
31 | {
32 | name: "仿宋",
33 | value: "sans-serif",
34 | },
35 | ],
36 | fontFamilyIndex: 0,
37 | /** 阅读器字体信息 */
38 | sizeInfo: {
39 | /** 标题字体大小 */
40 | title: 24,
41 | /** 段落字体大小 */
42 | p: 16,
43 | /** 标题行高 */
44 | tLineHeight: 24 * 1.5,
45 | /** 段落行高 */
46 | pLineHeight: 18 * 1.5,
47 | /** 标题边距 */
48 | titleMargin: 16,
49 | /** 左右边距 */
50 | lrPadding: 24,
51 | /** 上边距 */
52 | tPadding: 12,
53 | /** 下边距 */
54 | bPadding: 32,
55 | margin: 12,
56 | // 小说信息高度
57 | infoHeight: 30,
58 | // 小说信息边距
59 | infoMarginBottom: 10,
60 | // 页码高度
61 | pagginationHeight: 20,
62 | },
63 | };
64 |
65 | export class ModuleStore extends ModuleAppOption {
66 | constructor() {
67 | super();
68 | this.initBookOption();
69 | this.initBookShelves();
70 | this.initHistoryBookShelves();
71 | this.initHistorySearchList();
72 | this.initBookGroups();
73 | this.initTempData();
74 | this.initSourceMap();
75 | }
76 |
77 | /** 图片对象集 */
78 | get imageInfo() {
79 | // 需要用作背景图的可以用`import`引入
80 | return {
81 | iconWx: "/static/logo_wx.png",
82 | iconZfb: "/static/logo_zfb.png",
83 | logo: "/static/logo.png",
84 | defaultHead: "/static/default_head.png",
85 | noneData: "/static/none_data.png",
86 | iconArrowRight: "/static/arrow-right.png",
87 | };
88 | }
89 |
90 | /** 用户状态 */
91 | readonly user = new ModuleUser();
92 |
93 | /** 小说操作信息 */
94 | readonly bookOption = reactive(cloneDeep(BOOK_OPTION));
95 | /** 书架信息 */
96 | readonly bookShelves = reactive({
97 | sort: "default",
98 | data: [],
99 | });
100 | /** 浏览历史 */
101 | readonly historyBookShelves = ref([]);
102 | /** 临时缓存 */
103 | readonly tempData = ref([]);
104 | /** 搜索历史 */
105 | readonly historySearchList = ref>([]);
106 | // 分组列表
107 | readonly bookGroups = ref>([]);
108 | readonly sourceMap = ref>([]);
109 |
110 | /** 保存小说操作信息 */
111 | saveBookOption() {
112 | uni.setStorageSync("book-app-option", JSON.stringify(this.bookOption));
113 | }
114 |
115 | /** 获取小说操作信息 */
116 | private initBookOption() {
117 | const data = uni.getStorageSync("book-app-option");
118 | if (data) {
119 | modifyData(this.bookOption, JSON.parse(data));
120 | } else {
121 | uni.setStorageSync("book-app-option", JSON.stringify(this.bookOption));
122 | }
123 | }
124 |
125 | /** 清除小说操作信息 */
126 | removeBookOption() {
127 | modifyData(this.bookOption, cloneDeep(BOOK_OPTION));
128 | uni.removeStorageSync("book-app-option");
129 | }
130 |
131 | saveBookShelves() {
132 | uni.setStorageSync("__User_Bookshelves_Data__", JSON.stringify(this.bookShelves));
133 | }
134 |
135 | private initBookShelves() {
136 | const data = uni.getStorageSync("__User_Bookshelves_Data__");
137 | if (data) {
138 | const values: BookShelves = JSON.parse(data);
139 | values.data.forEach((book) => {
140 | if (book.uid.length !== 36) {
141 | // 矫正uid, 一些中途或者意外的退出可能导致uid错误
142 | book.uid = uuidv4();
143 | }
144 | });
145 |
146 | modifyData(this.bookShelves, values);
147 | }
148 | }
149 |
150 | removeBookShelves() {
151 | uni.removeStorageSync("__User_Bookshelves_Data__");
152 | }
153 |
154 | saveHistoryBookShelves() {
155 | uni.setStorageSync("__User_History_BookShelves_Data__", JSON.stringify(this.historyBookShelves.value));
156 | }
157 | modifyHistoryBookShelves(data: BooksShelvesData) {
158 | const idx = this.historyBookShelves.value.findIndex(
159 | (i) => i.bookName === data.bookName && i.author === data.author,
160 | );
161 | if (idx > -1) {
162 | this.historyBookShelves.value.splice(idx, 1);
163 | }
164 | this.historyBookShelves.value.unshift(data);
165 |
166 | if (this.historyBookShelves.value.length > 100) {
167 | this.historyBookShelves.value.splice(100);
168 | }
169 | this.saveHistoryBookShelves();
170 | }
171 | private initHistoryBookShelves() {
172 | const data = uni.getStorageSync("__User_History_BookShelves_Data__");
173 | if (data) {
174 | this.historyBookShelves.value = JSON.parse(data);
175 | }
176 | }
177 |
178 | removeHistoryBookShelves() {
179 | this.historyBookShelves.value = [];
180 | uni.removeStorageSync("__User_History_BookShelves_Data__");
181 | }
182 |
183 | saveBookGroups() {
184 | uni.setStorageSync("__User_Book_Groups__", JSON.stringify(this.bookGroups.value));
185 | }
186 |
187 | private initBookGroups() {
188 | const data = uni.getStorageSync("__User_Book_Groups__");
189 | if (data) {
190 | this.bookGroups.value = JSON.parse(data);
191 | }
192 | }
193 | removeBookGroups() {
194 | this.bookGroups.value = [];
195 | uni.removeStorageSync("__User_Book_Groups__");
196 | }
197 |
198 | saveSourceMap() {
199 | uni.setStorageSync("__User_Source_Map__", JSON.stringify(this.sourceMap.value));
200 | }
201 |
202 | private initSourceMap() {
203 | const data = uni.getStorageSync("__User_Source_Map__");
204 | if (data) {
205 | this.sourceMap.value = JSON.parse(data);
206 | }
207 | }
208 | removeSourceMap() {
209 | this.sourceMap.value = [];
210 | uni.removeStorageSync("__User_Source_Map__");
211 | }
212 |
213 | saveHistorySearchList() {
214 | uni.setStorageSync("__User_History_Search__", JSON.stringify(this.historySearchList.value));
215 | }
216 |
217 | private initHistorySearchList() {
218 | const data = uni.getStorageSync("__User_History_Search__");
219 | if (data) {
220 | this.historySearchList.value = JSON.parse(data);
221 | }
222 | }
223 | removeHistorySearchList() {
224 | this.historySearchList.value = [];
225 | uni.removeStorageSync("__User_History_Search__");
226 | }
227 |
228 | modifyTempData(params: { author: string; bookName: string; uid: string }) {
229 | const idx = this.bookShelves.data.findIndex((i) => i.uid === params.uid);
230 | // 如果书架里面存在书籍信息,移除映射
231 | if (idx > -1) {
232 | const ix = this.tempData.value.findIndex((i) => i.author === params.author && i.bookName === params.bookName);
233 | if (ix > -1) {
234 | this.tempData.value.splice(ix, 1);
235 | }
236 | } else {
237 | this.tempData.value.push(params);
238 | }
239 | this.saveTempData();
240 | }
241 | saveTempData() {
242 | uni.setStorageSync("__User_Temp_Data__", JSON.stringify(this.tempData.value));
243 | }
244 | private initTempData() {
245 | const data = uni.getStorageSync("__User_Temp_Data__");
246 | if (data) {
247 | const cache: TempDataReflect[] = JSON.parse(data);
248 | cache.forEach((i) => {
249 | if (i.uid) {
250 | uni.removeStorage({
251 | key: i.uid,
252 | });
253 | }
254 | });
255 | }
256 | this.removeTempData();
257 | }
258 | removeTempData() {
259 | this.tempData.value = [];
260 | uni.removeStorageSync("__User_Temp_Data__");
261 | }
262 |
263 | clearAllCache() {
264 | this.bookShelves.data.forEach((i) => {
265 | uni.removeStorage({
266 | key: i.uid,
267 | });
268 | });
269 | this.removeBookShelves();
270 | this.removeHistorySearchList();
271 | this.removeHistoryBookShelves();
272 | this.removeTempData();
273 | this.removeBookOption();
274 | this.removeSourceMap();
275 | }
276 | }
277 |
278 | /**
279 | * 状态管理模块
280 | * - `OOP`单例设计模式
281 | * - 参考 [你不需要`Vuex`](https://juejin.cn/post/6844903904023429128)
282 | */
283 | const store = new ModuleStore();
284 |
285 | export default store;
286 |
--------------------------------------------------------------------------------