├── app.png ├── qq_group.png ├── src ├── static │ ├── menu │ │ ├── me.png │ │ ├── index.png │ │ ├── theme.png │ │ ├── settings.png │ │ ├── me_selected.png │ │ ├── index_selected.png │ │ ├── theme_selected.png │ │ └── settings_selected.png │ ├── overlay.png │ └── miku_loading.gif ├── sfc.d.ts ├── plugins │ ├── querystring │ │ ├── index.js │ │ ├── encode.js │ │ └── decode.js │ ├── injection.js │ └── qs2string.js ├── views │ ├── webview │ │ └── index.vue │ ├── dev │ │ └── index.vue │ ├── blogs │ │ └── index.vue │ ├── guide │ │ └── index.vue │ ├── index │ │ └── index.vue │ ├── topic │ │ └── index.vue │ ├── login │ │ └── index.vue │ ├── home │ │ └── index.vue │ ├── filter │ │ └── index.vue │ ├── reader │ │ └── index.vue │ ├── switch │ │ └── flow.vue │ ├── search │ │ └── index.vue │ ├── theme │ │ └── index.vue │ └── settings │ │ └── index.vue ├── config │ ├── assets.ts │ ├── index.ts │ └── profile.ts ├── utils │ ├── uni.ts │ ├── mirror.ts │ ├── qs.ts │ ├── is.ts │ ├── axios.ts │ ├── fs.ts │ ├── map.ts │ ├── time.ts │ └── index.ts ├── css │ └── dark.css ├── store │ ├── fs.ts │ ├── modules │ │ ├── user.ts │ │ ├── cache.ts │ │ ├── settings.ts │ │ ├── reader.ts │ │ └── comic.ts │ ├── index.ts │ └── types.ts ├── const │ ├── key.ts │ └── index.ts ├── interface │ ├── types.ts │ ├── enum.ts │ ├── tool.ts │ ├── index.ts │ └── pages.ts ├── main.ts ├── callback │ └── dark.ts ├── api │ ├── share.ts │ └── v1 │ │ ├── u.ts │ │ └── app.ts ├── components │ ├── test_image.vue │ ├── empty.vue │ ├── reload-button.vue │ ├── glass.vue │ ├── list.vue │ ├── dialog.vue │ ├── card.vue │ ├── wrapper.vue │ ├── topbar.vue │ └── card-preview.vue ├── uni.scss ├── App.vue ├── pages.json └── manifest.json ├── design └── logo │ └── logo.logoist ├── .gitattributes ├── script ├── run.sh └── changeVersion.js ├── .gitignore ├── LICENSE ├── postcss.config.js ├── tsconfig.json ├── vue.config.js ├── public └── index.html ├── babel.config.js ├── README.md └── package.json /app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/app.png -------------------------------------------------------------------------------- /qq_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/qq_group.png -------------------------------------------------------------------------------- /src/static/menu/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/me.png -------------------------------------------------------------------------------- /src/static/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/overlay.png -------------------------------------------------------------------------------- /design/logo/logo.logoist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/design/logo/logo.logoist -------------------------------------------------------------------------------- /src/sfc.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from 'vue' 3 | export default Vue 4 | } -------------------------------------------------------------------------------- /src/static/menu/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/index.png -------------------------------------------------------------------------------- /src/static/menu/theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/theme.png -------------------------------------------------------------------------------- /src/static/menu/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/settings.png -------------------------------------------------------------------------------- /src/static/miku_loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/miku_loading.gif -------------------------------------------------------------------------------- /src/static/menu/me_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/me_selected.png -------------------------------------------------------------------------------- /src/static/menu/index_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/index_selected.png -------------------------------------------------------------------------------- /src/static/menu/theme_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/theme_selected.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=Typescript 2 | *.vue linguist-language=Typescript 3 | *.js linguist-language=Typescript -------------------------------------------------------------------------------- /src/static/menu/settings_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TGbusWD/18comic/HEAD/src/static/menu/settings_selected.png -------------------------------------------------------------------------------- /src/plugins/querystring/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.decode = exports.parse = require('./decode'); 4 | exports.encode = exports.stringify = require('./encode'); -------------------------------------------------------------------------------- /src/plugins/injection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 页面路由数据 3 | */ 4 | export const pagejson = JSON.parse(PAGES_JSON) 5 | 6 | /** 7 | * package 8 | */ 9 | export const packages = JSON.parse(PACKAGES_JSON) 10 | 11 | export default { 12 | PAGES_JSON: pagejson, 13 | PACKAGES_JSON: packages 14 | } -------------------------------------------------------------------------------- /src/views/webview/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /script/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # create by d1y 4 | 5 | if [ "$1" = "" ]; then 6 | echo "请传递tag标签" 7 | exit 8 | fi 9 | 10 | node changeVersion.js $1 11 | 12 | cd .. 13 | git add . 14 | git commit -m "add tag $1" 15 | git push origin master 16 | 17 | git tag $1 18 | git push origin --tags -------------------------------------------------------------------------------- /src/config/assets.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 加载图片 4 | * link: https://giphy.com/gifs/uzfGv0BtxrWGk 5 | * 以下为参考图片 6 | * https://tenor.com/view/anime-loading-sending-virtual-hug-hug-gif-12129229 7 | * https://tenor.com/view/anime-dance-cute-muse-dash-girl-gif-17666530 8 | */ 9 | export let _loadingImage: string = require("../static/miku_loading.gif") -------------------------------------------------------------------------------- /src/utils/uni.ts: -------------------------------------------------------------------------------- 1 | export const setTitle = (title: string) => { 2 | uni.setNavigationBarTitle({ 3 | title 4 | }) 5 | } 6 | 7 | export const setFullScreen = (flag: boolean = true): void=> { 8 | try { 9 | plus.navigator.setFullscreen(flag) 10 | } catch (error) { 11 | throw new Error(`设置错误: ${ error }`) 12 | } 13 | } -------------------------------------------------------------------------------- /.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 | yarn.lock 15 | 16 | # Editor directories and files 17 | .project 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | 26 | local -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { getMirror } from '@/utils/mirror' 2 | 3 | const injection = require('@/plugins/injection') 4 | 5 | export const baseUrl: string = getMirror() 6 | 7 | export default { 8 | baseUrl 9 | } 10 | 11 | export let version: string = injection.packages.versionCode 12 | export let readme: string = '' 13 | 14 | export const isDev = process.env.NODE_ENV === 'development' -------------------------------------------------------------------------------- /src/css/dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * 暗色主题 3 | */ 4 | 5 | .dark-theme { 6 | background-color: #323232; 7 | color: white; 8 | } 9 | .dark-theme *:not(.cu-tag):not([class*="cuIcon-"]):not(.uni-swiper-dots):not(.dark-remove) { 10 | background-color: #323232 !important; 11 | color: #fff !important; 12 | border-color: #999 !important; 13 | } 14 | 15 | .dark-theme img 16 | .dark-theme image { 17 | filter: grayscale(30%); 18 | } -------------------------------------------------------------------------------- /src/config/profile.ts: -------------------------------------------------------------------------------- 1 | interface githubUserProfile { 2 | username: string 3 | repo: string 4 | } 5 | 6 | /** 7 | * github静态资源 8 | */ 9 | export const githubStaticProfile: githubUserProfile = { 10 | username: 'waifu-project', 11 | repo: '18comic-live' 12 | } 13 | 14 | /** 15 | * `github-release` 16 | */ 17 | export const githubReleaseProfile: githubUserProfile = { 18 | username: 'waifu-project', 19 | repo: '18comic' 20 | } -------------------------------------------------------------------------------- /src/store/fs.ts: -------------------------------------------------------------------------------- 1 | // 本地持久化 2 | // 参考文档: https://blog.csdn.net/kouryoushine/article/details/103762147 3 | // 官方文档: https://ask.dcloud.net.cn/article/166 4 | 5 | import createPersistedState from "vuex-persistedstate"; 6 | 7 | export default ()=> createPersistedState({ 8 | storage: { 9 | getItem: key => uni.getStorageSync(key), 10 | setItem: (key, value) => uni.setStorageSync(key, value), 11 | removeItem: key => uni.removeStorageSync(key) 12 | } 13 | }) -------------------------------------------------------------------------------- /src/const/key.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 存储到本地的 `key` 3 | */ 4 | export const mirror_key = 'mirror_key' 5 | 6 | /** 7 | * 镜像默认的域名后缀 8 | */ 9 | export const mirror_default_domain = 'https://18comic.fun' 10 | 11 | /** 12 | * 搜索页面是否重新加载 13 | */ 14 | export const onLoadSearchKey = 'reload_flag_key' 15 | 16 | /** 17 | * 更新创建的 `webview` id 18 | */ 19 | export const updateWebviewID = 'updateWebview' 20 | 21 | /** 22 | * 评论 `webview` id 23 | */ 24 | export const commentWebviewID = 'commentWebview' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) [2020] [d1y] 2 | [Software Name] is licensed under the Mulan PSL v1. 3 | You can use this software according to the terms and conditions of the Mulan PSL v1. 4 | You may obtain a copy of Mulan PSL v1 at: 5 | http://license.coscl.org.cn/MulanPSL 6 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR 7 | IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR 8 | PURPOSE. 9 | See the Mulan PSL v1 for more details. -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户相关 3 | */ 4 | 5 | import { userFace } from '../types' 6 | import { MutationTree } from 'vuex' 7 | 8 | export const state: userFace = { 9 | hasLogin: false, 10 | token: "" 11 | } 12 | 13 | export const mutations: MutationTree = { 14 | /** 15 | * 修改登录状态 16 | */ 17 | CHANGE_LOGIN_STATUS(state, token: string) { 18 | state.hasLogin = true 19 | state.token = token 20 | } 21 | } 22 | 23 | export default { 24 | state, 25 | mutations, 26 | namespaced: true, 27 | } -------------------------------------------------------------------------------- /src/interface/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 搜索时间 3 | */ 4 | export type searchOptionTimeType = 5 | /** 6 | * 全部 7 | */ 8 | 'a' | 9 | /** 10 | * 今天 11 | */ 12 | 't' | 13 | /** 14 | * 这周 15 | */ 16 | 'w' | 17 | /** 18 | * 这月 19 | */ 20 | 'm' 21 | 22 | /** 23 | * 搜索类型 24 | */ 25 | export type searchOptionsTypeByType = 26 | /** 27 | * 最新 28 | */ 29 | 'mr' | 30 | /** 31 | * 最多点阅的 32 | */ 33 | 'mv' | 34 | /** 35 | * 最多图片 36 | */ 37 | 'mp' | 38 | /** 39 | * 最多点赞 40 | */ 41 | 'tf' -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import fs from './fs' 5 | import comic from './modules/comic' 6 | import settings from './modules/settings' 7 | import reader from './modules/reader' 8 | import cache from './modules/cache' 9 | import user from './modules/user' 10 | 11 | Vue.use(Vuex) 12 | 13 | export default new Vuex.Store({ 14 | state: { 15 | 16 | }, 17 | mutations: { 18 | 19 | }, 20 | modules: { 21 | comic, 22 | settings, 23 | reader, 24 | cache, 25 | user, 26 | }, 27 | plugins: [ 28 | fs() 29 | ] 30 | }) -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import store from '@/store' 4 | 5 | // ####### 6 | import topbar from '@/components/topbar.vue' 7 | import glass from '@/components/glass.vue' 8 | import dialog from '@/components/dialog.vue' 9 | import xList from '@/components/list.vue' 10 | import wrapper from '@/components/wrapper.vue' 11 | // ####### 12 | 13 | Vue.component('topbar', topbar) 14 | Vue.component('glass', glass) 15 | Vue.component('dialog-box', dialog) 16 | Vue.component('x-list', xList) 17 | Vue.component('wrapper', wrapper) 18 | 19 | Vue.config.productionTip = false 20 | 21 | new App({ 22 | store 23 | }).$mount() 24 | -------------------------------------------------------------------------------- /src/utils/mirror.ts: -------------------------------------------------------------------------------- 1 | // 镜像站 2 | 3 | import { io } from "./fs" 4 | import { mirror_default_domain } from '@/const/key' 5 | 6 | /** 7 | * 设置镜像 `url` 8 | */ 9 | export const setMirror = (ext: string = mirror_default_domain) => { 10 | return io.setMirror(ext) 11 | } 12 | 13 | /** 14 | * 获取镜像 `url` 15 | */ 16 | export const getMirror = (): string=> { 17 | try { 18 | const _domain = io.getMirror() 19 | const value = _domain.value as string 20 | return value 21 | } catch (error) { 22 | console.error(error) 23 | return mirror_default_domain 24 | } 25 | } 26 | 27 | /** 28 | * 创建镜像的静态资源 29 | * @param path 路径 30 | */ 31 | export const createMirrorStaticFile = (path: string): string=> { 32 | if (path[0] == '/') path = path.substring(1) 33 | const _domain = getMirror() 34 | return `${ _domain }/${ path }` 35 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | module.exports = { 3 | parser: require('postcss-comment'), 4 | plugins: [ 5 | require('postcss-import')({ 6 | resolve (id, basedir, importOptions) { 7 | if (id.startsWith('~@/')) { 8 | return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3)) 9 | } else if (id.startsWith('@/')) { 10 | return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2)) 11 | } else if (id.startsWith('/') && !id.startsWith('//')) { 12 | return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1)) 13 | } 14 | return id 15 | } 16 | }), 17 | require('autoprefixer')({ 18 | remove: process.env.UNI_PLATFORM !== 'h5' 19 | }), 20 | require('@dcloudio/vue-cli-plugin-uni/packages/postcss') 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators":true, 12 | "sourceMap": true, 13 | "skipLibCheck": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env", 17 | "@dcloudio/types", 18 | "miniprogram-api-typings", 19 | "mini-types" 20 | ], 21 | "paths": { 22 | "@/*": [ 23 | "./src/*" 24 | ] 25 | }, 26 | "lib": [ 27 | "esnext", 28 | "dom", 29 | "dom.iterable", 30 | "scripthost" 31 | ] 32 | }, 33 | "exclude": [ 34 | "node_modules", 35 | "unpackage", 36 | "src/**/*.nvue" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/callback/dark.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 暗色模式匹配 3 | */ 4 | 5 | import store from '@/store' 6 | import { isDev } from '@/config' 7 | 8 | const _b = (s: string): boolean => 'dark'== s 9 | 10 | /** 11 | * 拿到当前颜色主题 12 | */ 13 | const getUiStyle = (): boolean=> { 14 | try { 15 | const wrapper = plus.navigator as any 16 | const style = wrapper.getUiStyle(); 17 | return _b(style) 18 | } catch (error) { 19 | return false 20 | } 21 | } 22 | 23 | try { 24 | // TODO 25 | // 20200724 更新: 总感觉暗色主题有点别扭, 嗯, 就是那种怪怪的感觉 26 | if (false) { 27 | if (!isDev) { 28 | const now_flag = getUiStyle() 29 | store.commit('settings/CHANGE_UI_THEME', now_flag) 30 | } 31 | const _uni = uni as any 32 | _uni.onUIStyleChange((res: any)=> { 33 | const flag = _b(res.style) 34 | store.commit('settings/CHANGE_UI_THEME', flag) 35 | }) 36 | } 37 | } catch (error) { 38 | console.error('监听系统主题变化失败..', error) 39 | } -------------------------------------------------------------------------------- /src/api/share.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/axios' 2 | 3 | // 参考: https://developer.hitokoto.cn 4 | export interface sayWordInterface { 5 | created_at?: string // 创建时间 6 | creator?: string // 未知 7 | creator_uid?: number // 创建id 8 | from?: string // 来自 9 | from_who?: string // 未知 10 | hitokoto?: string // say 11 | id?: number 12 | reviewer?: number // 阅读量 13 | type?: number // lei'xi 14 | uuid?: string 15 | } 16 | 17 | // 一言 18 | export const getWord = async (): Promise=> { 19 | try { 20 | const data: sayWordInterface = await get('https://v1.hitokoto.cn/') 21 | return data 22 | } catch (error) { 23 | // console.error(error) 24 | /** 25 | * 网抑云: https://music.163.com/song?id=462523121&userid=266341607 26 | * 附言: 这首电子音乐意境很高 27 | */ 28 | const defaultSayWord: sayWordInterface = { 29 | hitokoto: "登高极目知天地之大,置己苍茫知寸身之微。", 30 | from: "网抑云" 31 | } 32 | return defaultSayWord 33 | } 34 | } -------------------------------------------------------------------------------- /src/components/test_image.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | -------------------------------------------------------------------------------- /src/plugins/qs2string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * the source code by: https://github.com/goto-bus-stop/qs-stringify/blob/master/index.js 3 | */ 4 | var has = Object.prototype.hasOwnProperty 5 | 6 | /** 7 | * Stringify an object for use in a query string. 8 | * 9 | * @param {Object} obj - The object. 10 | * @param {string} prefix - When nesting, the parent key. 11 | * keys in `obj` will be stringified as `prefix[key]`. 12 | * @returns {string} 13 | */ 14 | 15 | module.exports = function queryStringify (obj, prefix) { 16 | var pairs = [] 17 | for (var key in obj) { 18 | if (!has.call(obj, key)) { 19 | continue 20 | } 21 | 22 | var value = obj[key] 23 | var enkey = encodeURIComponent(key) 24 | var pair 25 | if (typeof value === 'object') { 26 | pair = queryStringify(value, prefix ? prefix + '[' + enkey + ']' : enkey) 27 | } else { 28 | pair = (prefix ? prefix + '[' + enkey + ']' : enkey) + '=' + encodeURIComponent(value) 29 | } 30 | pairs.push(pair) 31 | } 32 | return pairs.join('&') 33 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const webpack = require('webpack') 4 | 5 | const resolve = _p => path.join(__dirname, _p) 6 | 7 | /** 8 | * 拿到 `pages.json` 参考: https://www.npmjs.com/package/uni-vue-router 9 | * @returns {string} 10 | */ 11 | const readPagesJSON = () => { 12 | const jsonFilePath = resolve('./src/pages.json') 13 | if (!fs.existsSync(jsonFilePath)) { 14 | throw new Error(jsonFilePath + ' 不存在') 15 | } 16 | return fs.readFileSync(jsonFilePath, 'utf8') 17 | } 18 | 19 | /** 20 | * 读取 `package.json` 文件 21 | */ 22 | const readPackageJson = () => { 23 | return fs.readFileSync(resolve('./package.json'), 'utf-8') 24 | } 25 | 26 | module.exports = { 27 | configureWebpack: { 28 | plugins: [ 29 | new webpack.DefinePlugin({ 30 | PAGES_JSON: JSON.stringify(readPagesJSON()), 31 | PACKAGES_JSON: JSON.stringify(readPackageJson()) 32 | }) 33 | ] 34 | }, 35 | transpileDependencies: [ 36 | "url-parse", 37 | "normalize-url", 38 | "url-regex", 39 | ] 40 | } -------------------------------------------------------------------------------- /src/components/empty.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 48 | 49 | -------------------------------------------------------------------------------- /src/components/reload-button.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 54 | 55 | -------------------------------------------------------------------------------- /src/utils/qs.ts: -------------------------------------------------------------------------------- 1 | import _url from 'url-parse' 2 | const querystring = require('@/plugins/querystring') 3 | 4 | /** 5 | * 将格式转为对象 6 | * 支持类型: **?search_query=人妻&b=test** 7 | */ 8 | export const decode = (qs: string): any=> { 9 | let n = qs 10 | // fixbug: 去除 `?` 11 | if (qs[0] == '?') n = qs.substring(1) 12 | return querystring.parse(n) 13 | } 14 | 15 | /** 16 | * 将 `对象` 转为 `字符串` 17 | * 支持类型: **{ search_query: '人妻' }** 18 | * @returns [string] 19 | */ 20 | export const encode = (obj: any): string=> { 21 | return '?' + querystring.stringify(obj) 22 | } 23 | 24 | /** 25 | * 合并查询字段 26 | * @param {String} [qs] - 查询字段 27 | * @param {Any} [obj] - 合并查询字段的对象 28 | */ 29 | export const mergeQueryString = (qs: string, obj: any)=> { 30 | const URL = new _url(qs) 31 | const _b = URL.query as unknown as string 32 | const data = decode(_b) 33 | Object.assign(data, obj) 34 | const _R = encode(data) 35 | const _result: string = `${ URL.pathname }${ _R }` 36 | return _result 37 | } 38 | 39 | /** 40 | * 根据文本生成搜索接口 41 | */ 42 | export const createSearchUrl = (text: string): string => { 43 | return `/search/photos?search_query=${ text }` 44 | } 45 | 46 | export default { 47 | decode, 48 | encode, 49 | mergeQueryString 50 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 17 | 18 | 19 | 20 | 21 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /script/changeVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 参考: https://github. com/long-hai-yang/change-version/blob/master/index.js 3 | * create by d1y 4 | */ 5 | const fs = require('fs') 6 | const path = require('path') 7 | 8 | const localFile = require('../package.json') 9 | 10 | const versionWord = `versionCode` 11 | 12 | /** 13 | * 获取标签 14 | */ 15 | const getTag = ()=> { 16 | const arr = process.argv 17 | if (arr.length < 3) { 18 | console.log('请传递参数..') 19 | proces.exit(3) 20 | } 21 | return arr[2] 22 | } 23 | 24 | /** 25 | * 修改版本号 26 | */ 27 | const changeVersion = (version)=> { 28 | localFile[versionWord] = version 29 | } 30 | 31 | /** 32 | * 将拿到的`json`转为字符串 33 | */ 34 | const version2string = (str)=> { 35 | try { 36 | const r = JSON.stringify(str, null, 2) 37 | return r 38 | } catch (error) { 39 | console.error(error) 40 | process.exit(2) 41 | } 42 | 43 | } 44 | 45 | /** 46 | * 写入文件 47 | */ 48 | const writePackageFile = (str)=> { 49 | const dir = process.cwd() 50 | const writeFile = path.join(dir, '../package.json') 51 | console.log('write file: ', writeFile) 52 | fs.writeFileSync(writeFile, str) 53 | console.log('write file is success') 54 | } 55 | 56 | ;(async ()=> { 57 | const tag = getTag() 58 | changeVersion(tag) 59 | const filestr = version2string(localFile) 60 | writePackageFile(filestr) 61 | })() -------------------------------------------------------------------------------- /src/interface/enum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 搜索时间 3 | */ 4 | export enum searchOptionTimeEnum { 5 | /** 6 | * 全部 7 | */ 8 | all = 'a', 9 | /** 10 | * 今天 11 | */ 12 | today = 't', 13 | /** 14 | * 这周 15 | */ 16 | week = 'w', 17 | /** 18 | * 这月 19 | */ 20 | month = 'm' 21 | } 22 | export enum searchOptionTimeEnumString { 23 | /** 24 | * 全部 25 | */ 26 | all = '全部', 27 | /** 28 | * 今天 29 | */ 30 | today = '今天', 31 | /** 32 | * 这周 33 | */ 34 | week = '这周', 35 | /** 36 | * 这月 37 | */ 38 | month = '这月' 39 | } 40 | 41 | /** 42 | * 搜索类型 43 | */ 44 | export enum searchOptionTypeEnum { 45 | /** 46 | * 最新 47 | */ 48 | new = 'mr', 49 | /** 50 | * 最多点阅的 51 | */ 52 | mostViewed = 'mv', 53 | /** 54 | * 最多图片 55 | */ 56 | mostPictures = 'mp', 57 | /** 58 | * 最多点赞 59 | */ 60 | mostLiked = 'tf' 61 | } 62 | export enum searchOptionTypeEnumString { 63 | /** 64 | * 最新 65 | */ 66 | new = '最新', 67 | /** 68 | * 最多点阅的 69 | */ 70 | mostViewed = '最多点阅的', 71 | /** 72 | * 最多图片 73 | */ 74 | mostPictures = '最多图片', 75 | /** 76 | * 最多点赞 77 | */ 78 | mostLiked = '最多点赞' 79 | } 80 | 81 | /** 82 | * 阅读页数枚举 83 | */ 84 | export enum readerPageNumberEnum { 85 | /** 86 | * 第一页 87 | */ 88 | frist, 89 | /** 90 | * 最后一页 91 | */ 92 | last, 93 | /** 94 | * 还有更多 95 | */ 96 | more 97 | } -------------------------------------------------------------------------------- /src/store/modules/cache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 缓存 3 | */ 4 | 5 | import { cacheInterface, themeConcatInterface } from "../types"; 6 | import { MutationTree, ActionTree } from 'vuex'; 7 | import { shareIndexComicData, waifuItem } from '@/interface'; 8 | import { sayWordInterface } from '@/api/share'; 9 | import { getWaifuer } from '@/api/v1'; 10 | 11 | const state: cacheInterface = { 12 | index: null, 13 | theme: null, 14 | homeSayWord: null, 15 | waifus: null, 16 | } 17 | 18 | const mutations: MutationTree = { 19 | /** 20 | * 修改 `index` 数据 21 | */ 22 | CHANGE_INDEX_DATA(state, data: shareIndexComicData[]) { 23 | state.index = data 24 | }, 25 | /** 26 | * 主题数据 27 | */ 28 | CHANGE_THEME_DATA(state, data: themeConcatInterface) { 29 | state.theme = data 30 | }, 31 | /** 32 | * 添加一言缓存 33 | */ 34 | CHANGE_HOME_SAY_WORD(state, data: sayWordInterface) { 35 | state.homeSayWord = data 36 | }, 37 | /** 38 | * 老婆们 39 | */ 40 | CHANGE_WAIFU(state, data: waifuItem[]) { 41 | state.waifus = data 42 | } 43 | } 44 | 45 | const actions: ActionTree = { 46 | /** 47 | * 获取老婆们 48 | */ 49 | async fetchWaifuer(ctx) { 50 | const { commit } = ctx 51 | const data = await getWaifuer() 52 | commit('CHANGE_WAIFU', data) 53 | } 54 | } 55 | 56 | export default { 57 | actions, 58 | state, 59 | mutations, 60 | namespaced: true 61 | } -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | import { copy } from '.' 2 | 3 | export const isObject = (x: any): boolean => { 4 | return typeof x === 'object' && x !== null 5 | } 6 | 7 | export const isString = (x: any): boolean => { 8 | return typeof x === 'string' 9 | } 10 | 11 | export const isNumber = (data: number | string, isReturn?: boolean): boolean | number=> { 12 | const newVal = copy(data) 13 | const newVal1 = +newVal 14 | if (Number.isNaN(newVal1)) return false 15 | if (typeof newVal1 === 'number') { 16 | if (isReturn) return newVal 17 | return true 18 | } 19 | return false 20 | } 21 | 22 | // https://stackoverflow.com/a/5717133/10272586 23 | export const isURL = (str: string): boolean=> { 24 | var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol 25 | '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name 26 | '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address 27 | '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path 28 | '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string 29 | '(\\#[-a-z\\d_]*)?$','i'); // fragment locator 30 | return !!pattern.test(str); 31 | } 32 | 33 | // 判断全屏 34 | export const isFullScreen = (): boolean=> { 35 | return plus.navigator.isFullscreen() 36 | } 37 | 38 | export const isIos = (): boolean=> { 39 | return uni.getSystemInfoSync().platform === 'ios' 40 | } 41 | 42 | export const isAndroid = (): boolean=> { 43 | return uni.getSystemInfoSync().platform === 'android' 44 | } -------------------------------------------------------------------------------- /src/store/modules/settings.ts: -------------------------------------------------------------------------------- 1 | import { settingsFace, cardColEnum } from '../types' 2 | import { MutationTree } from 'vuex' 3 | 4 | const state: settingsFace = { 5 | showDev: false, 6 | isDark: false, 7 | showIndexAD: true, 8 | firstRun: true, 9 | cardCol: cardColEnum.df, 10 | detailKanBanInfoShow: false 11 | } 12 | 13 | const mutations: MutationTree = { 14 | CHANGE_DEV_FLAG(state, flag) { 15 | state.showDev = flag 16 | }, 17 | /** 18 | * 取反设置 `showDev` 19 | */ 20 | REVERRSE_DEV_FLAG(state) { 21 | state.showDev = !state.showDev 22 | }, 23 | /** 24 | * 修改 `ui` 主题 25 | */ 26 | CHANGE_UI_THEME(state, themeFlag: boolean) { 27 | let style = { 28 | backgroundColor: 'rgba(0, 0, 0, .8)', 29 | color: '#fff', 30 | selectedColor: 'rgb(60, 197, 31)' 31 | } 32 | if (!themeFlag) style = { 33 | backgroundColor: `rgba(255, 255, 255, 0.4)`, 34 | color: '#333', 35 | selectedColor: 'rgb(60, 197, 31)' 36 | } 37 | // fixbug: setTimeout is work. 38 | setTimeout(() => { 39 | uni.setTabBarStyle(style) 40 | }, 120) 41 | state.isDark = themeFlag 42 | }, 43 | CHANGE_INDEX_AD_FLAG(state, flag: boolean) { 44 | state.showIndexAD = flag 45 | }, 46 | CHANGE_RUN_FLAG(state, flag: boolean) { 47 | state.firstRun = flag 48 | }, 49 | CHANGE_CARD_COL(state, col) { 50 | state.cardCol = col 51 | }, 52 | CHANGE_DETAIL_KANBAN_FLAG(state, flag: boolean) { 53 | state.detailKanBanInfoShow = flag 54 | } 55 | } 56 | 57 | export default { 58 | namespaced: true, 59 | state, 60 | mutations 61 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = [] 2 | 3 | if (process.env.UNI_OPT_TREESHAKINGNG) { 4 | plugins.push(require('@dcloudio/vue-cli-plugin-uni-optimize/packages/babel-plugin-uni-api/index.js')) 5 | } 6 | 7 | if ( 8 | ( 9 | process.env.UNI_PLATFORM === 'app-plus' && 10 | process.env.UNI_USING_V8 11 | ) || 12 | ( 13 | process.env.UNI_PLATFORM === 'h5' && 14 | process.env.UNI_H5_BROWSER === 'builtin' 15 | ) 16 | ) { 17 | const path = require('path') 18 | 19 | const isWin = /^win/.test(process.platform) 20 | 21 | const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path) 22 | 23 | const input = normalizePath(process.env.UNI_INPUT_DIR) 24 | try { 25 | plugins.push([ 26 | require('@dcloudio/vue-cli-plugin-hbuilderx/packages/babel-plugin-console'), 27 | { 28 | file (file) { 29 | file = normalizePath(file) 30 | if (file.indexOf(input) === 0) { 31 | return path.relative(input, file) 32 | } 33 | return false 34 | } 35 | } 36 | ]) 37 | } catch (e) {} 38 | } 39 | 40 | process.UNI_LIBRARIES = process.UNI_LIBRARIES || ['@dcloudio/uni-ui'] 41 | process.UNI_LIBRARIES.forEach(libraryName => { 42 | plugins.push([ 43 | 'import', 44 | { 45 | 'libraryName': libraryName, 46 | 'customName': (name) => { 47 | return `${libraryName}/lib/${name}/${name}` 48 | } 49 | } 50 | ]) 51 | }) 52 | module.exports = { 53 | presets: [ 54 | [ 55 | '@vue/app', 56 | { 57 | modules: 'commonjs', 58 | useBuiltIns: process.env.UNI_PLATFORM === 'h5' ? 'usage' : 'entry' 59 | } 60 | ] 61 | ], 62 | plugins 63 | } 64 | -------------------------------------------------------------------------------- /src/api/v1/u.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/axios' 2 | import cheerio from 'cheerio' 3 | import { createMirrorStaticFile } from '@/utils/mirror' 4 | 5 | const profile2Number = (ele: Cheerio): number=> { 6 | let _n = ele.find('span').text().trim() 7 | let _result = Number.parseInt(_n) 8 | if (isNaN(_result)) { 9 | if (_n == '男') return 1 10 | if (_n == '女') return 0 11 | } 12 | return _result 13 | } 14 | 15 | /** 16 | * 获取用户信息 17 | */ 18 | export const getUserProfile = async ()=> { 19 | try { 20 | const data = await get(`/user`) 21 | const $ = cheerio.load(data) 22 | const bodyWrapper = $('.panel-body') 23 | let img = bodyWrapper.find('img').attr('src') || "" 24 | img = createMirrorStaticFile(img) 25 | const profileList = Array.from(bodyWrapper.find('li')) 26 | 27 | // 人气 28 | let popularity: number = 0 29 | 30 | // 活动 31 | let activity: number = 0 32 | 33 | // 性别 34 | let gender: number = 1 // 1 男 0 女 35 | 36 | // 加入时间 37 | let joinTime: number = 0 38 | 39 | profileList.forEach((item, index)=> { 40 | const ele = $(item) 41 | const result = profile2Number(ele) 42 | switch (index) { 43 | case 0: 44 | // 人气 45 | popularity = result 46 | break; 47 | 48 | case 1: 49 | // 活动 50 | activity = result 51 | break; 52 | 53 | case 2: 54 | // 性别 55 | gender = result 56 | break; 57 | 58 | case 3: 59 | // 加入时间 60 | joinTime = result 61 | break; 62 | } 63 | }) 64 | const x = { 65 | avatar: img, 66 | popularity, 67 | activity, 68 | gender, 69 | joinTime 70 | } 71 | return x 72 | } catch (error) { 73 | throw new Error(error) 74 | } 75 | } -------------------------------------------------------------------------------- /src/store/modules/reader.ts: -------------------------------------------------------------------------------- 1 | import { readerFace } from "../types"; 2 | import { MutationTree, GetterTree, ActionTree } from 'vuex'; 3 | import { shareComicFace } from '@/interface'; 4 | 5 | const state: readerFace = { 6 | currentData: null, 7 | current_id: '' 8 | } 9 | 10 | const mutations: MutationTree = { 11 | /** 12 | * 设置当前阅读的数据 13 | */ 14 | SET_CURRENT_READER_DATA(state, data: shareComicFace) { 15 | state.currentData = data 16 | }, 17 | /** 18 | * 设置当前阅读的数据的 `id` 19 | */ 20 | SET_CURRENT_READER_DATA_ID(state, id: number | string) { 21 | state.current_id = id 22 | } 23 | } 24 | 25 | const actions: ActionTree = { 26 | /** 27 | * 下一话 28 | */ 29 | changeNextReader(ctx, flag: boolean) { 30 | const { commit, getters, state } = ctx 31 | const idx: number = getters.current_index 32 | if (idx === 0 && !flag) return 33 | const data = state.currentData 34 | if (!data) return 35 | const { episode } = data 36 | if (!episode) return 37 | if (+episode.length === 1) return 38 | const index = flag ? (idx + 1) : (idx - 1) 39 | commit('SET_CURRENT_READER_DATA_ID', episode[index].id) 40 | } 41 | } 42 | 43 | const getters: GetterTree = { 44 | /** 45 | * 当前索引 46 | */ 47 | current_index(ctx): number | null { 48 | const data = ctx.currentData 49 | if (!data) return null 50 | const { episode } = data 51 | if (!episode) return null 52 | const current_id = ctx.current_id 53 | let index = null 54 | episode.forEach((item, i)=> { 55 | const flag = (item.id == current_id) 56 | if (flag) index = i 57 | }) 58 | // debugger 59 | return index 60 | } 61 | } 62 | 63 | export default { 64 | namespaced: true, 65 | state, 66 | mutations, 67 | getters, 68 | actions 69 | } -------------------------------------------------------------------------------- /src/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24upx; 43 | $uni-font-size-base:28upx; 44 | $uni-font-size-lg:32upx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40upx; 48 | $uni-img-size-base:52upx; 49 | $uni-img-size-lg:80upx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4upx; 53 | $uni-border-radius-base: 6upx; 54 | $uni-border-radius-lg: 12upx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20upx; 60 | $uni-spacing-row-lg: 30upx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8upx; 64 | $uni-spacing-col-base: 16upx; 65 | $uni-spacing-col-lg: 24upx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40upx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36upx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30upx; -------------------------------------------------------------------------------- /src/utils/axios.ts: -------------------------------------------------------------------------------- 1 | import { isDev as debug } from '@/config' 2 | import { postBodyFace } from '@/interface/tool' 3 | import { getMirror } from './mirror' 4 | import URL from 'url-parse' 5 | import store from '@/store' 6 | import { userFace } from '@/store/types' 7 | 8 | const axios = require('@/plugins/axios') 9 | 10 | // fixbug: 解决傻逼的多重引用 `bug` 11 | // const { baseUrl } = config 12 | // import config from '@/config' 13 | const baseUrl = getMirror() 14 | 15 | axios.setConfig({ 16 | baseUrl, 17 | debug, 18 | skipInterceptorResponse: false 19 | }) 20 | 21 | /** 22 | * 镜像中间件 23 | */ 24 | const dynamicMirror = (req: any) => { 25 | let _defalut = req.url 26 | const temp = new URL(_defalut) 27 | const testFlag = temp.host.search('18comic') >= 0 28 | if (testFlag) { 29 | const baseUrl = getMirror() 30 | const baseURL = new URL(baseUrl) 31 | const url = new URL(req.url) 32 | url.set('hostname', baseURL.hostname) 33 | const _r = url.toString() 34 | req.url = _r 35 | return 36 | } 37 | req.url = _defalut 38 | } 39 | 40 | /** 41 | * 登录中间件 42 | */ 43 | const loginMiddleware = (req: any)=> { 44 | try { 45 | const user: userFace = (store.state as any).user 46 | const { hasLogin, token } = user 47 | if (hasLogin && token) { 48 | req['header']['cookie'] = `ipm5=${ token };` 49 | } 50 | } catch (error) { 51 | throw new Error(error) 52 | } 53 | } 54 | 55 | axios.interceptor.request = (req: any)=> { 56 | dynamicMirror(req) 57 | loginMiddleware(req) 58 | return req 59 | } 60 | 61 | // TODO 取消响应拦截器 62 | axios.interceptor.response = (res: any) => { 63 | return res 64 | } 65 | 66 | export default axios 67 | 68 | export const get = (url: string): Promise=> new Promise(res=> { 69 | _get(url).then(null, (fastData: any)=> { 70 | const { data } = fastData 71 | res(data) 72 | }) 73 | }) 74 | 75 | export const post = (data: postBodyFace): Promise=> new Promise(res=> { 76 | _post(data).then(null, (fastData: any)=> { 77 | const { data } = fastData 78 | res(data) 79 | }) 80 | }) 81 | 82 | export const _get = (url: string)=> axios.get({ url }) 83 | export const _post = (data: postBodyFace)=> axios.post(data) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 18comic 2 | 3 | ![](https://18comic.one/media/logo/new_logo.png?v=2020000525) 4 | 5 | **仅供学习参考** 6 | 7 | 基于 `uniapp` + `typescript` 开发的一个看漫画的软件(雾) 8 | 9 | 自豪的使用 `vscode` 来开发 10 | 11 | ![](https://img.shields.io/badge/style-hello--world-green?logo=visual-studio-code&style=for-the-badge&label=vscode) 12 | 13 | 14 | > TIP: 刚才有个朋友给我发来几个截图, 我一看, 哦, 原来是有些人拿出去买搞收费, 我说怎么回事, 这东西不是免费的吗, 我劝年轻人耗子尾汁, 好好反思, 以后不要搞这种小聪明, 小聪明啊, 谢谢朋友们! 15 | 16 | 17 | 18 | 19 | 20 | 21 | # 下载♂安装 22 | 23 | 参见 [`releases`]() 下 24 | 25 | 26 | 27 | # 界面 28 | 29 | | | | | 30 | |:----------:|:-------------:|:------:| 31 | | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/首页.png) 首页 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/主题.png) 主题 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/我的.png) 我的 | 32 | | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/漫画详情.png) 详情 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/阅读器.png) 阅读器 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/分流切换.png) 分流切换 | 33 | | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/搜索.png) 搜索 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/留言区.png) 留言区 | ![](https://cdn.jsdelivr.net/gh/waifu-project/18comic-live/previews/设置.png) 设置 | 34 | 35 | 36 | # FAQ 37 | 38 | 1. 为什么要做这么个东西? 39 | 40 | 主要是为了写一个完整的 `uniapp` 的项目练手, 实际上也是因为现在这个项目的源站广告特别多,毕竟要♂恰饭的啊, 而 `哔咔` 又♂用不了. 其实官方是有 `app` 的. 不过特别敷衍, 截止 `2020-06-12`, 官方的APP大小`30m`左右, 直接是一个 `webview` 套壳... 41 | 42 | ![image.png](https://i.loli.net/2020/06/12/hmWY6F87LCVsycz.png) 43 | 44 | 而用 `uniapp` 打包的不超过可以 `10m` 45 | 46 | 47 | 2. 太卡了太卡了, 一直是 `loading` 状态 48 | 49 | 是的, 我同样也出现了, 可以试着去切换节点试试 50 | 51 | **要不老铁你科学上网试一下?准行..** 52 | 53 | 3. `ios` 怎么安装 54 | 55 | ``` 56 | https://impactor.nullx.me 57 | ``` 58 | 59 | 60 | # Thank you 61 | 62 | 图标库 63 | 64 | - https://www.iconfont.cn/collections/detail?cid=8856 65 | 66 | # chat 67 | 68 | tg裙: https://t.me/send_18comic -------------------------------------------------------------------------------- /src/components/glass.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 72 | 73 | -------------------------------------------------------------------------------- /src/views/dev/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 68 | 69 | -------------------------------------------------------------------------------- /src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import url from 'url-parse' 2 | import { isObject } from './is' 3 | import { mirror_key, mirror_default_domain, onLoadSearchKey } from '@/const/key' 4 | import { getMirror } from './mirror' 5 | 6 | class fs { 7 | 8 | static Join(str: string): string { 9 | const now = getMirror() 10 | const __URL = new url(now) 11 | __URL.set('pathname', str) 12 | return __URL.toString() 13 | } 14 | 15 | static checkLocalStorage(): boolean { 16 | try { 17 | localStorage.setItem('x','1') 18 | localStorage.removeItem('x') 19 | return true 20 | } catch (error) { 21 | console.error('不支持 localStorage') 22 | return false 23 | } 24 | } 25 | 26 | static setData(key: string, value: any): boolean { 27 | try { 28 | if (isObject(value)) value = JSON.stringify(value) 29 | uni.setStorageSync(key, value) 30 | return true 31 | } catch (error) { 32 | console.error(`set item by ${ key } is error: `, error) 33 | return false 34 | } 35 | } 36 | 37 | static getData(key: string, defaultVal: any = []): any { 38 | let result = "" 39 | try { 40 | result = uni.getStorageSync(key) || "" 41 | if (!result) return defaultVal 42 | else return JSON.parse(result) 43 | } catch (error) { 44 | console.error('get the key item is error: ', error) 45 | result = defaultVal 46 | } 47 | return result 48 | } 49 | 50 | } 51 | 52 | export class io extends fs { 53 | 54 | /** 55 | * 设置镜像 56 | */ 57 | static setMirror = (value: any): boolean=> { 58 | const time = Date.now() 59 | // flag: 临时改变为对象 60 | const flag = io.setData(mirror_key, { 61 | value, 62 | time 63 | }) 64 | return flag 65 | } 66 | 67 | /** 68 | * 获取镜像站 69 | */ 70 | static getMirror = (): any => { 71 | // flag: 临时改变为对象 72 | const data = io.getData(mirror_key, { 73 | value: mirror_default_domain 74 | }) 75 | return data 76 | } 77 | 78 | /** 79 | * 是否需要重新获取 80 | */ 81 | static getReload = (): boolean=> { 82 | const data: boolean = io.getData(onLoadSearchKey, false) 83 | return data 84 | } 85 | 86 | /** 87 | * 设置重新获取 `flag` 88 | */ 89 | static setReload = (flag = true)=> { 90 | io.setData(onLoadSearchKey, flag) 91 | } 92 | 93 | } 94 | 95 | export default fs -------------------------------------------------------------------------------- /src/utils/map.ts: -------------------------------------------------------------------------------- 1 | import ghCDN from 'github-to-cdn' 2 | import { shareComicFace } from '@/interface'; 3 | import { githubStaticProfile } from '@/config/profile'; 4 | import { copy } from '.'; 5 | 6 | /** 7 | * @description 将搜索的数格式化一次 8 | * @param {shareComicFace[]} lists 9 | * @returns shareComicFace 10 | */ 11 | export const _coverSearchItem = (lists: shareComicFace[]): shareComicFace[]=> { 12 | return lists.map(item=> { 13 | let { title } = item 14 | // flag: 标题只取 `18` 个词 15 | const maxWordLength = 18 16 | if (title.length >= maxWordLength) { 17 | title = title.replace(/\[(.+?)\]/, '') 18 | } 19 | if (title.length >= maxWordLength) { 20 | title = title.substring(0, maxWordLength) 21 | } 22 | // debugger 23 | item.title = title 24 | const maxTagLength = 4 25 | const tags = item.tags.filter(tag=> { 26 | if (tag.length <= maxTagLength) return true 27 | }) 28 | item.tags = tags.slice(0, maxTagLength) 29 | return item 30 | }) 31 | } 32 | 33 | /** 34 | * 创建静态资源(CDN) 35 | */ 36 | export const createStaticByCDN = (path: string): string=> { 37 | const res = { ...githubStaticProfile, path } 38 | return ghCDN(res) 39 | } 40 | 41 | interface pushDiyInterface { 42 | /** 43 | * 数组 44 | */ 45 | lists: any[] 46 | /** 47 | * 最大长度 48 | */ 49 | maxLen: number 50 | /** 51 | * 当前 `push` 的数据 52 | */ 53 | data: any 54 | } 55 | 56 | /** 57 | * 重组数据 58 | */ 59 | export const _push = (args: pushDiyInterface)=> { 60 | const { lists, maxLen, data } = args 61 | const _list = copy(lists) 62 | const _now_len = _list.length 63 | if (maxLen && _now_len >= maxLen) { 64 | _list.pop() 65 | } 66 | _list.push(data) 67 | return _list 68 | } 69 | 70 | /** 71 | * 获取指定范围随机数 72 | * https://www.cnblogs.com/starof/p/4988516.html 73 | */ 74 | function randomNum(minNum: number,maxNum: number): string { 75 | const x = Math.random() 76 | const y = x * ( maxNum - minNum + 1 ) + minNum 77 | const b = Number.parseInt(`${ y }`) 78 | return `${ b }` 79 | } 80 | 81 | /** 82 | * 生成随机id 83 | * 生成的可能有错误 84 | * 2020-08-28 85 | */ 86 | export const createRandomID = (): string=> { 87 | // 因为没有接口, 所以只能把最大值写死了... 88 | const maxID20200828 = 211914 89 | // 从 `2` 开始算起, 因为测试 `1` 会跳转到 `4`, 不知道这是什么操作.. 90 | const minID20200828 = 2 91 | return randomNum(minID20200828, maxID20200828) 92 | } -------------------------------------------------------------------------------- /src/plugins/querystring/encode.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 'use strict'; 23 | 24 | var stringifyPrimitive = function(v) { 25 | switch (typeof v) { 26 | case 'string': 27 | return v; 28 | 29 | case 'boolean': 30 | return v ? 'true' : 'false'; 31 | 32 | case 'number': 33 | return isFinite(v) ? v : ''; 34 | 35 | default: 36 | return ''; 37 | } 38 | }; 39 | 40 | module.exports = function(obj, sep, eq, name) { 41 | sep = sep || '&'; 42 | eq = eq || '='; 43 | if (obj === null) { 44 | obj = undefined; 45 | } 46 | 47 | if (typeof obj === 'object') { 48 | return Object.keys(obj).map(function(k) { 49 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 50 | if (Array.isArray(obj[k])) { 51 | return obj[k].map(function(v) { 52 | return ks + encodeURIComponent(stringifyPrimitive(v)); 53 | }).join(sep); 54 | } else { 55 | return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 56 | } 57 | }).filter(Boolean).join(sep); 58 | 59 | } 60 | 61 | if (!name) return ''; 62 | return encodeURIComponent(stringifyPrimitive(name)) + eq + 63 | encodeURIComponent(stringifyPrimitive(obj)); 64 | }; -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 参考: https://github.com/zxccvms/dateAgo/blob/master/index.js 3 | */ 4 | const dispose = (dateTimeStamp: any) => { 5 | var minute = 1000 * 60; //把分,时,天,周,半个月,一个月用毫秒表示 6 | var hour = minute * 60; 7 | var day = hour * 24; 8 | var week = day * 7; 9 | var halfamonth = day * 15; 10 | var month = day * 30; 11 | var now = new Date().getTime(); //获取当前时间毫秒 12 | // console.log(now) 13 | var diffValue = now - dateTimeStamp;//时间差 14 | 15 | if (diffValue < 0) return 16 | 17 | var minC: any = diffValue / minute; //计算时间差的分,时,天,周,月 18 | var hourC: any = diffValue / hour; 19 | var dayC: any = diffValue / day; 20 | var weekC: any = diffValue / week; 21 | var monthC: any = diffValue / month; 22 | 23 | var result = '' 24 | 25 | if (monthC >= 1 && monthC <= 11) { 26 | result = " " + parseInt(monthC) + "个月前" 27 | } else if (weekC >= 1 && weekC <= 3) { 28 | result = " " + parseInt(weekC) + "周前" 29 | } else if (dayC >= 1 && dayC <= 6) { 30 | result = " " + parseInt(dayC) + "天前" 31 | } else if (hourC >= 1 && hourC <= 23) { 32 | result = " " + parseInt(hourC) + "小时前" 33 | } else if (minC >= 1 && minC <= 59) { 34 | result = " " + parseInt(minC) + "分钟前" 35 | } else if (diffValue >= 0 && diffValue <= minute) { 36 | result = "刚刚" 37 | } else { 38 | var datetime = new Date(); 39 | datetime.setTime(dateTimeStamp); 40 | var Nyear = datetime.getFullYear(); 41 | var Nmonth = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1; 42 | var Ndate = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate(); 43 | var Nhour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours(); 44 | var Nminute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes(); 45 | var Nsecond = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds(); 46 | result = Nyear + "年" + Nmonth + "月" + Ndate + "日" 47 | } 48 | return result; 49 | } 50 | 51 | export default (time: any) => { 52 | var dateTimeStamp = Date.now() 53 | if (time instanceof Date) { 54 | dateTimeStamp = time.getTime() 55 | } else if (typeof time == 'string') { 56 | dateTimeStamp = (new Date(time)).getTime() 57 | } else if (typeof time == 'number') { 58 | dateTimeStamp = time; 59 | } 60 | return dispose(dateTimeStamp) 61 | } 62 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 75 | -------------------------------------------------------------------------------- /src/plugins/querystring/decode.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 'use strict'; 23 | 24 | // If obj.hasOwnProperty has been overridden, then calling 25 | // obj.hasOwnProperty(prop) will break. 26 | // See: https://github.com/joyent/node/issues/1707 27 | function hasOwnProperty(obj, prop) { 28 | return Object.prototype.hasOwnProperty.call(obj, prop); 29 | } 30 | 31 | module.exports = function(qs, sep, eq, options) { 32 | sep = sep || '&'; 33 | eq = eq || '='; 34 | var obj = {}; 35 | 36 | if (typeof qs !== 'string' || qs.length === 0) { 37 | return obj; 38 | } 39 | 40 | var regexp = /\+/g; 41 | qs = qs.split(sep); 42 | 43 | var maxKeys = 1000; 44 | if (options && typeof options.maxKeys === 'number') { 45 | maxKeys = options.maxKeys; 46 | } 47 | 48 | var len = qs.length; 49 | // maxKeys <= 0 means that we should not limit keys count 50 | if (maxKeys > 0 && len > maxKeys) { 51 | len = maxKeys; 52 | } 53 | 54 | for (var i = 0; i < len; ++i) { 55 | var x = qs[i].replace(regexp, '%20'), 56 | idx = x.indexOf(eq), 57 | kstr, vstr, k, v; 58 | 59 | if (idx >= 0) { 60 | kstr = x.substr(0, idx); 61 | vstr = x.substr(idx + 1); 62 | } else { 63 | kstr = x; 64 | vstr = ''; 65 | } 66 | 67 | k = decodeURIComponent(kstr); 68 | v = decodeURIComponent(vstr); 69 | 70 | if (!hasOwnProperty(obj, k)) { 71 | obj[k] = v; 72 | } else if (Array.isArray(obj[k])) { 73 | obj[k].push(v); 74 | } else { 75 | obj[k] = [obj[k], v]; 76 | } 77 | } 78 | 79 | return obj; 80 | }; -------------------------------------------------------------------------------- /src/components/list.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 99 | 100 | -------------------------------------------------------------------------------- /src/store/types.ts: -------------------------------------------------------------------------------- 1 | import { shareComicFace, shareIndexComicData, themeListInterface, themeInterface, waifuItem } from '@/interface'; 2 | import { searchOptions } from '@/interface/tool'; 3 | import { sayWordInterface } from '@/api/share'; 4 | 5 | export interface searchDataInterface { 6 | /** 7 | * `title-bar` 文字 8 | */ 9 | barTitle: string 10 | /** 11 | * 搜索的接口 12 | */ 13 | url: string 14 | /** 15 | * 查询字段 16 | */ 17 | query: searchOptions 18 | /** 19 | * 页数(废弃) 20 | */ 21 | page?: number 22 | /** 23 | * 全部页数 24 | */ 25 | total_page?: number 26 | /** 27 | * 是否还有下一页 28 | */ 29 | isNext?: boolean 30 | } 31 | 32 | /** 33 | * 漫画接口 34 | */ 35 | export interface comicInterface { 36 | /** 37 | * 搜索数据 38 | */ 39 | searchData: searchDataInterface 40 | /** 41 | * 历史观看 42 | */ 43 | history_views: shareComicFace[] 44 | /** 45 | * 收藏 46 | */ 47 | collect_lists: shareComicFace[] 48 | } 49 | 50 | /** 51 | * 主题数据 52 | */ 53 | export interface themeConcatInterface { 54 | 55 | /** 56 | * 热门 57 | */ 58 | popular: themeListInterface[] 59 | 60 | /** 61 | * 主题数据 62 | */ 63 | data: themeInterface[] 64 | 65 | } 66 | 67 | /** 68 | * 缓存接口类型 69 | */ 70 | export interface cacheInterface { 71 | 72 | /** 73 | * 首页数据 74 | */ 75 | index: null | shareIndexComicData[] 76 | 77 | /** 78 | * 主题数据 79 | */ 80 | theme: null | themeConcatInterface 81 | 82 | /** 83 | * `home` 一言缓存 84 | */ 85 | homeSayWord: sayWordInterface | null 86 | 87 | /** 88 | * 老婆们s 89 | */ 90 | waifus: waifuItem[] | null 91 | 92 | } 93 | 94 | /** 95 | * 卡片几等分枚举 96 | */ 97 | export enum cardColEnum { 98 | 99 | /** 100 | * 默认 101 | */ 102 | df = "默认", 103 | 104 | /** 105 | * 大 106 | */ 107 | lg = "较大", 108 | 109 | /** 110 | * 超大 111 | */ 112 | xl = "超大", 113 | 114 | } 115 | 116 | /** 117 | * 设置接口 118 | */ 119 | export interface settingsFace { 120 | /** 121 | * 是否显示开发者菜单 122 | */ 123 | showDev: boolean 124 | /** 125 | * 是否开始暗色主题 126 | */ 127 | isDark: boolean 128 | /** 129 | * 首页广告 130 | */ 131 | showIndexAD: boolean 132 | /** 133 | * 首次启动引导页 134 | */ 135 | firstRun: boolean 136 | /** 137 | * 卡片几等分 138 | */ 139 | cardCol: cardColEnum 140 | /** 141 | * 详情页面信息板是否打开 142 | */ 143 | detailKanBanInfoShow: boolean 144 | } 145 | 146 | /** 147 | * 用户数据 148 | */ 149 | export interface userFace { 150 | /** 151 | * 是否登录 152 | */ 153 | hasLogin: boolean 154 | /** 155 | * 登录 `token` 156 | */ 157 | token: string 158 | } 159 | 160 | /** 161 | * 阅读器接口 162 | */ 163 | export interface readerFace { 164 | /** 165 | * 当前数据 166 | */ 167 | currentData: shareComicFace | null 168 | /** 169 | * 当前 `id` 可用来做索引判断 170 | */ 171 | current_id: string | number 172 | } 173 | 174 | export default interface RootState { 175 | comic: comicInterface, 176 | settings: settingsFace, 177 | reader: readerFace 178 | } -------------------------------------------------------------------------------- /src/interface/tool.ts: -------------------------------------------------------------------------------- 1 | import { searchOptionTimeEnum, searchOptionTypeEnum } from './enum' 2 | import { searchOptionTimeType, searchOptionsTypeByType } from './types' 3 | import { shareComicFace } from '.' 4 | import csstype from 'csstype' 5 | 6 | // post 默认请求体 7 | export interface postBodyFace { 8 | url: string 9 | data: any 10 | contentType?: string 11 | } 12 | 13 | /** 14 | * 颜色单个主题接口 15 | */ 16 | export interface colorItemInterface { 17 | /** 18 | * 标题 19 | */ 20 | title: string 21 | /** 22 | * 名称 23 | * bg-${ name } 24 | */ 25 | name: string 26 | /** 27 | * 颜色值, hex 28 | */ 29 | color: string 30 | } 31 | 32 | /** 33 | * 搜索参数 34 | */ 35 | export interface searchOptions { 36 | /** 37 | * 搜索页数, 默认为 `1` 38 | */ 39 | page: number 40 | /** 41 | * 搜索时间 42 | */ 43 | t: searchOptionTimeEnum | searchOptionTimeType 44 | /** 45 | * 类型 46 | */ 47 | o: searchOptionTypeEnum | searchOptionsTypeByType 48 | } 49 | 50 | /** 51 | * 搜索接口返回数据 52 | */ 53 | export interface searchResponseInterface { 54 | /** 55 | * 搜索的文字 56 | */ 57 | search_key: string 58 | /** 59 | * 当前页数 60 | */ 61 | current_page: string | number 62 | /** 63 | * 所有页数 64 | */ 65 | total_page: string | number 66 | /** 67 | * 是否有下一页 68 | */ 69 | isNext: boolean 70 | /** 71 | * 漫画总数 72 | */ 73 | total: string | number 74 | /** 75 | * 列表 76 | */ 77 | lists: shareComicFace[] 78 | } 79 | 80 | /** 81 | * 单个主题菜单列表 82 | */ 83 | export interface themeMenuItemInterface { 84 | /** 85 | * 标题 86 | */ 87 | title: string 88 | /** 89 | * 跳转链接 90 | */ 91 | link: string 92 | /** 93 | * 唯一的 `key` 94 | */ 95 | key: string 96 | /** 97 | * 背景图片 98 | */ 99 | bg?: string 100 | } 101 | 102 | export interface devInsDataPageItemInterface { 103 | /** 104 | * 路径 105 | */ 106 | path: string 107 | /** 108 | * TODO 样式 109 | */ 110 | style: any 111 | /** 112 | * 介绍, 说明 113 | */ 114 | description: string 115 | } 116 | 117 | /** 118 | * 注入的 `page.json` 文件接口类型 119 | * TODO: uniapp里应该有接口类型的 120 | */ 121 | export interface devInsDataInterface { 122 | pages: devInsDataPageItemInterface[] 123 | } 124 | 125 | /** 126 | * 引导页接口类型 127 | */ 128 | export interface guideDataItemInterface { 129 | /** 130 | * 标题 131 | */ 132 | title: string 133 | /** 134 | * 内容 135 | */ 136 | content: string 137 | /** 138 | * 图片 139 | */ 140 | img?: string 141 | /** 142 | * 标题背景颜色, 颜色值参考 colorui 143 | */ 144 | titleBg?: string 145 | /** 146 | * 是否全屏 147 | */ 148 | isFull?: boolean 149 | /** 150 | * 样式 151 | */ 152 | style?: csstype.Properties 153 | /** 154 | * 是否结束 155 | */ 156 | isEnd?: boolean 157 | } 158 | 159 | /** 160 | * 镜像站接口类型 161 | */ 162 | export interface mirrorItemInterface { 163 | /** 164 | * 标题 165 | */ 166 | title: string 167 | /** 168 | * 域名后缀(现在已废弃) 169 | */ 170 | ext: string 171 | /** 172 | * 完整的 `url` 173 | */ 174 | full_url: string 175 | } -------------------------------------------------------------------------------- /src/components/dialog.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 112 | 113 | -------------------------------------------------------------------------------- /src/views/blogs/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 107 | 108 | -------------------------------------------------------------------------------- /src/views/guide/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 119 | 120 | -------------------------------------------------------------------------------- /src/components/card.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 140 | 141 | -------------------------------------------------------------------------------- /src/components/wrapper.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 134 | 135 | -------------------------------------------------------------------------------- /src/views/index/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 148 | 149 | -------------------------------------------------------------------------------- /src/store/modules/comic.ts: -------------------------------------------------------------------------------- 1 | // 参考: https://www.jb51.net/article/172121.htm 2 | // 漫画设定 3 | // 包括: 4 | // - 历史访问 5 | // - 收藏的漫画 6 | 7 | import { comicInterface } from '../types' 8 | import { MutationTree, GetterTree } from 'vuex' 9 | import { history_views_max_length } from '@/const' 10 | import { shareComicFace } from '@/interface' 11 | import { copy } from '@/utils' 12 | import timediff from '@/utils/time' 13 | import { searchOptionTimeEnum, searchOptionTypeEnum } from '@/interface/enum' 14 | import { searchOptions } from '@/interface/tool' 15 | 16 | const state: comicInterface = { 17 | searchData: { 18 | barTitle: '', 19 | url: ``, 20 | query: { 21 | page: 1, 22 | t: searchOptionTimeEnum.all, 23 | o: searchOptionTypeEnum.new 24 | }, 25 | }, 26 | history_views: [], 27 | collect_lists: [] 28 | } 29 | 30 | /** 31 | * 中间方法类型 32 | */ 33 | export enum extType { 34 | 35 | /** 36 | * 收藏 37 | */ 38 | collect, 39 | 40 | /** 41 | * 历史 42 | */ 43 | history, 44 | 45 | } 46 | 47 | /** 48 | * 中间处理方法 49 | * @param {shareComicFace[]} [fullLists] - 拿到的总数组 50 | * @param {shareComicFace} [data] - 当前存储的数组 51 | */ 52 | const ext = (fullLists: shareComicFace[], data: shareComicFace, type: extType): shareComicFace[]=> { 53 | const list: shareComicFace[] = copy(fullLists) 54 | const isCollect = type == extType.collect 55 | let idx = -1 56 | for (let index = 0; index < list.length; index++) { 57 | const element = list[index]; 58 | if (element.id === data.id) { 59 | idx = index 60 | break 61 | } 62 | } 63 | if (idx >= 0) list.splice(idx, 1) 64 | const date = Date.now() 65 | const _now_len = list.length 66 | if (history_views_max_length && _now_len >= history_views_max_length) { 67 | list.pop() 68 | } 69 | data['reader_time'] = date 70 | if (!isCollect) list.unshift(data) 71 | if (isCollect && idx < 0) list.unshift(data) 72 | try { 73 | if (isCollect) { 74 | const ctx = list.length 75 | plus.runtime.setBadgeNumber(ctx) 76 | } 77 | } catch (error) { 78 | throw new Error(error) 79 | } 80 | return list 81 | } 82 | 83 | /** 84 | * 中间处理方法(get获取) 85 | */ 86 | const ext_get = (data: shareComicFace[]): shareComicFace[] => { 87 | const lists = data.map(item=> { 88 | const text = item.reader_time 89 | if (text) { 90 | item['reader_time_text'] = timediff(text) || "" 91 | } 92 | return item 93 | }) 94 | // debugger 95 | return lists 96 | } 97 | 98 | const mutations: MutationTree = { 99 | /** 100 | * 历史记录相关 101 | */ 102 | CHANGE_HISTORY_VIEWS(state, data: shareComicFace) { 103 | state.history_views = ext(state.history_views, data, extType.history) 104 | }, 105 | /** 106 | * 收藏相关 107 | */ 108 | CHANGE_COLLECT_LISTS(state, data: shareComicFace) { 109 | state.collect_lists = ext(state.collect_lists, data, extType.collect) 110 | }, 111 | /** 112 | * 清空历史记录 113 | */ 114 | CLEAN_HISTORY_VIEWS(state) { 115 | state.history_views = [] 116 | }, 117 | /** 118 | * 清空收藏 119 | */ 120 | CLEAN_COLLECT_LISTS(state) { 121 | state.collect_lists = [] 122 | }, 123 | // 修改搜索 `url` 124 | CHANGE_SEARCH_URL(state, url: string) { 125 | state.searchData.url = url 126 | }, 127 | // 修改 `bar` 标题 128 | CHANGE_SEARCH_BAR_TITLE(state, title: string) { 129 | state.searchData.barTitle = title 130 | }, 131 | /** 132 | * 修改 `query` 数据 133 | */ 134 | CHANGE_QUERY_DATA(state, data: searchOptions) { 135 | Object.assign(state.searchData.query, data) 136 | } 137 | } 138 | 139 | export const getters: GetterTree = { 140 | /** 141 | * 历史列表 142 | */ 143 | historyViews(state): shareComicFace[] { 144 | return ext_get(state.history_views) 145 | }, 146 | /** 147 | * 收藏列表 148 | */ 149 | collectLists(state): shareComicFace[] { 150 | return ext_get(state.collect_lists) 151 | }, 152 | /** 153 | * 判断是否收藏 154 | */ 155 | checkFavorite(...args): boolean { 156 | const lists = args[0].collect_lists 157 | const id = args[2].reader.currentData.id 158 | const flag = lists.some(item=> item.id === id) 159 | return flag 160 | } 161 | } 162 | 163 | export default { 164 | state, 165 | mutations, 166 | getters, 167 | namespaced: true 168 | } -------------------------------------------------------------------------------- /src/views/topic/index.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 125 | 126 | -------------------------------------------------------------------------------- /src/api/v1/app.ts: -------------------------------------------------------------------------------- 1 | import mdjs from 'marked' 2 | import { githubReleaseProfile } from "@/config/profile" 3 | import { get } from '@/utils/axios' 4 | import { version } from '@/config' 5 | import cheerio from 'cheerio' 6 | import { updateWebviewID } from '@/const/key' 7 | 8 | /** 9 | * 单个 `github-release` 10 | */ 11 | export interface githubReleaseItemInterface { 12 | /** 13 | * `tag-name` 用来判断版本号 14 | */ 15 | tag_name: string 16 | /** 17 | * 标题(名称) 18 | */ 19 | name: string 20 | /** 21 | * 说明(markdown) 22 | */ 23 | body: string 24 | /** 25 | * 其他的 `key` - `value` 26 | */ 27 | [propName: string]: any 28 | } 29 | 30 | /** 31 | * `github-release` 状态 32 | */ 33 | export enum githubReleaseStatus { 34 | /** 35 | * 已经是最新版本 36 | */ 37 | last, 38 | /** 39 | * 旧版本 40 | */ 41 | old, 42 | /** 43 | * 未知 44 | */ 45 | unknown 46 | } 47 | 48 | /** 49 | * `github-release` 返回的结果 50 | */ 51 | export interface githubReleaseResultCat { 52 | /** 53 | * 状态 54 | */ 55 | code: githubReleaseStatus 56 | /** 57 | * 内容 58 | */ 59 | body?: string 60 | } 61 | 62 | /** 63 | * 创建 `release-api` 64 | */ 65 | const createRelasesApi = (user?: string, repo?: string): string=> { 66 | const runGithub = Object.values(githubReleaseProfile) 67 | let [ u, r ] = runGithub 68 | if (user) u = user 69 | if (repo) r = repo 70 | const api = `https://api.github.com/repos/${ u }/${ r }/releases/latest` 71 | return api 72 | } 73 | 74 | /** 75 | * 获取`app`最新版本 76 | */ 77 | export const getAppUpdate = async (): Promise=> { 78 | try { 79 | const api = createRelasesApi() 80 | const data: githubReleaseItemInterface = await get(api) 81 | const lastVersionCode = data.tag_name 82 | const lastBody = data.body 83 | const ctx = mdjs(lastBody) 84 | const jquery = cheerio.load(`
`) 85 | const closeText = `关闭` 86 | const closeID = `close-action` 87 | jquery('head').append(``) 88 | jquery('head').append(` 89 | 94 | `) 95 | jquery('.markdown-body').append(ctx) 96 | jquery('body').append(` 97 |
98 | 117 |
118 | `) 119 | jquery('body').append(``) 120 | jquery('body').append(` 121 | 149 | 150 | `) 151 | const body = jquery.html() 152 | let code: githubReleaseStatus 153 | if (lastVersionCode == version) { 154 | code = githubReleaseStatus.last 155 | } else { 156 | code = githubReleaseStatus.old 157 | } 158 | const R: githubReleaseResultCat = { 159 | code 160 | } 161 | if (code == githubReleaseStatus.old) R.body = body 162 | return R 163 | } catch (error) { 164 | return { 165 | code: githubReleaseStatus.unknown 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import { isNumber, isObject } from './is' 3 | import { colors } from '@/const' 4 | import { colorItemInterface } from '@/interface/tool' 5 | 6 | const qs = require('@/plugins/qs2string') 7 | 8 | // 获取网络状态 9 | export const getNetwork = (): Promise => { 10 | return new Promise((res, rej) => { 11 | uni.getNetworkType({ 12 | success: r => res(r.networkType), 13 | fail: err => rej(err) 14 | }) 15 | }) 16 | } 17 | 18 | interface deviceWidthAndHeight { 19 | width: number 20 | height: number 21 | } 22 | 23 | // 获取设备宽高 24 | export const getWidthAndHeight = (): deviceWidthAndHeight=> { 25 | const result: deviceWidthAndHeight = { width: 0, height: 0 } 26 | const data = uni.getSystemInfoSync() 27 | result.width = data['windowWidth'] || 0 28 | result.height = data['windowHeight'] || 0 29 | return result 30 | } 31 | 32 | interface jqueryArgsInterface { 33 | hack: Vue 34 | ele: string | string[] 35 | } 36 | 37 | export const jquery = (data: jqueryArgsInterface): Promise => { 38 | try { 39 | const { hack, ele } = data 40 | const query = uni.createSelectorQuery().in(hack) 41 | const exec = (ele: string)=> new Promise((res=> { 42 | query.select(ele).boundingClientRect((data: any)=> { 43 | res(data) 44 | }).exec(()=>{}) 45 | })) 46 | return new Promise(async res=> { 47 | let result: any = null 48 | if (typeof ele === 'string') { 49 | result = await exec(ele) 50 | } else if (Array.isArray(ele)){ 51 | let temp = ele 52 | const next = temp.map(async item=> { 53 | const data = await exec(item) 54 | return data 55 | }) 56 | const sp = Promise.all(next) 57 | result = sp 58 | } 59 | res(result) 60 | }) 61 | } catch (error) { 62 | throw new Error(error) 63 | } 64 | } 65 | 66 | export const $ = jquery 67 | 68 | // 格式化 69 | // 参考: https://www.codota.com/web/assistant/code/rs/5c7cb0752ef5570001df2fd3#L97 70 | export const toIpAddress = (ipAddress: any): string => { 71 | try { 72 | let x1 = ipAddress & 0xff, x2 = ipAddress >> 8 & 0xff, x3 = ipAddress >> 16 & 0xff, x4 = ipAddress >> 24 & 0xff 73 | return `${ x1 }.${ x2 }.${ x3 }.${ x4 }` 74 | } catch (error) { 75 | console.error(error) 76 | return '' 77 | } 78 | } 79 | 80 | // 浅拷贝, 主要为了解决 `ts` 语法问题和js内存机制 81 | export const copy = (data: any): any=> { 82 | try { 83 | const newVal = data 84 | return JSON.parse(JSON.stringify(newVal)) 85 | } catch (error) { 86 | throw new Error(error) 87 | } 88 | } 89 | 90 | // 生成空的数组 91 | export const MockCreateArray = (len: number): any[]=> { 92 | if (!isNumber(len)) return [] 93 | return Object.keys([...new Array(len)]) 94 | } 95 | 96 | // 获取当前格式化好的时间 97 | export const getCurrentTime = (): string=> { 98 | const now = dayjs().format('YYYY-MM-DD-HH:mm:ss') 99 | return now 100 | } 101 | 102 | // 生成一个uuid 103 | // https://stackoverflow.com/a/46352326/10272586 104 | export const createID = ()=> Math.random().toString(26).slice(2) 105 | 106 | export const createRandomLen = (len: number): number=> { 107 | return Math.floor(Math.random()*len) 108 | } 109 | 110 | // 返回随机的颜色 111 | export const createRandomColor = (): colorItemInterface=> colors[createRandomLen(colors.length)] 112 | 113 | /** 114 | * 获取域名后缀 115 | * @param domain 域名 116 | */ 117 | export const easyGetDomainSuffix = (domain: string): string=> { 118 | const index = domain.lastIndexOf('.') 119 | return index > 0 ? domain.substring(index + 1) : "" 120 | } 121 | 122 | // 路由 123 | export const router = { 124 | push(url: string, query?: any, isFullPath?: boolean) { 125 | try { 126 | let _qs = '' 127 | if (query && isObject(query)) _qs = `?${ qs(query) }` 128 | let _c = `/views/${ url }${ _qs }` 129 | if (isFullPath) _c = `/${ url }${ _qs }` 130 | uni.navigateTo({ 131 | url: _c 132 | }) 133 | } catch (error) { 134 | uni.showModal({ 135 | title: '跳转错误, 未知错误', 136 | content: error 137 | }) 138 | throw new Error(error) 139 | } 140 | }, 141 | back(delta: number = 1) { 142 | uni.navigateBack({ 143 | delta, 144 | animationType: 'auto', 145 | animationDuration: +'500' 146 | }) 147 | }, 148 | redirect(url: string) { 149 | uni.redirectTo({ url: `/views/${ url }` }) 150 | }, 151 | tab(url: string) { 152 | uni.switchTab({ url: `/views/${ url }` }) 153 | }, 154 | // https://blog.csdn.net/liuxin00020/article/details/104842217 155 | // 获取当前路由信息 156 | current() { 157 | let curRoute: any 158 | try { 159 | curRoute = (this as any).$mp.page; // 直接获取当前页面路由 160 | } catch (error) { 161 | let routes = getCurrentPages(); // 获取当前打开过的页面路由数组 162 | curRoute = routes[routes.length - 1] 163 | } 164 | return curRoute 165 | }, 166 | reload() { 167 | // TODO 重新加载 168 | } 169 | } -------------------------------------------------------------------------------- /src/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | { 4 | "path": "views/index/index", 5 | "style": { 6 | "navigationStyle": "custom" 7 | }, 8 | "description": "首页" 9 | }, 10 | { 11 | "path": "views/switch/flow", 12 | "style": { 13 | "navigationStyle": "custom", 14 | "disableScroll": true 15 | }, 16 | "description": "切换镜像站" 17 | }, 18 | { 19 | "path": "views/detail/index", 20 | "style": { 21 | "navigationStyle": "custom" 22 | }, 23 | "description": "本子详情" 24 | }, 25 | { 26 | "path": "views/reader/index", 27 | "style": { 28 | "navigationStyle": "custom" 29 | }, 30 | "description": "阅读页面" 31 | }, 32 | { 33 | "path": "views/theme/index", 34 | "style": { 35 | "navigationStyle": "custom" 36 | }, 37 | "description": "主题页面" 38 | }, 39 | { 40 | "path": "views/home/index", 41 | "style": { 42 | "navigationStyle": "custom" 43 | }, 44 | "description": "我的页面" 45 | }, 46 | { 47 | "path": "views/settings/index", 48 | "style": { 49 | "navigationStyle": "custom" 50 | }, 51 | "description": "设置页面" 52 | }, 53 | { 54 | "path": "views/search/index", 55 | "style": { 56 | "navigationStyle": "custom" 57 | }, 58 | "description": "搜索页面" 59 | }, 60 | { 61 | "path": "views/topic/index", 62 | "style": { 63 | "navigationStyle": "custom" 64 | }, 65 | "description": "话题" 66 | }, 67 | { 68 | "path": "views/guide/index", 69 | "style": { 70 | "enablePullDownRefresh": false, 71 | "onReachBottomDistance": 100, 72 | "navigationStyle": "custom" 73 | }, 74 | "description": "引导页" 75 | }, 76 | { 77 | "path": "views/webview/index", 78 | "description": "webview页(传递一个`url`参数)" 79 | }, 80 | { 81 | "path": "views/dev/index", 82 | "style": { 83 | "navigationStyle": "custom" 84 | }, 85 | "description": "开发页面" 86 | }, 87 | { 88 | "path": "views/login/index", 89 | "style": { 90 | "navigationStyle": "custom" 91 | }, 92 | "description": "登录页面" 93 | }, 94 | { 95 | "path": "views/filter/index", 96 | "style": { 97 | "navigationBarTitleText": "筛选" 98 | }, 99 | "description": "搜索参数" 100 | }, 101 | { 102 | "path": "views/blogs/index", 103 | "style": { 104 | "navigationStyle": "custom" 105 | }, 106 | "description": "涨姿势" 107 | } 108 | ], 109 | "tabBar": { 110 | "blurEffect":"extralight", 111 | "color": "#7A7E83", 112 | "selectedColor": "#d4237a", 113 | "borderStyle": "black", 114 | "backgroundColor": "rgba(255, 255, 255, .4)", 115 | "list": [ 116 | { 117 | "pagePath": "views/index/index", 118 | "text": "首页", 119 | "iconPath": "static/menu/index.png", 120 | "selectedIconPath": "static/menu/index_selected.png" 121 | }, 122 | { 123 | "pagePath": "views/theme/index", 124 | "text": "主题", 125 | "iconPath": "static/menu/theme.png", 126 | "selectedIconPath": "static/menu/theme_selected.png" 127 | }, 128 | { 129 | "pagePath": "views/home/index", 130 | "text": "我的", 131 | "iconPath": "static/menu/me.png", 132 | "selectedIconPath": "static/menu/me_selected.png" 133 | }, 134 | { 135 | "pagePath": "views/settings/index", 136 | "text": "设置", 137 | "iconPath": "static/menu/settings.png", 138 | "selectedIconPath": "static/menu/settings_selected.png" 139 | } 140 | ] 141 | }, 142 | "globalStyle": { 143 | "navigationBarTextStyle": "black", 144 | "navigationBarTitleText": "uni-app", 145 | "navigationBarBackgroundColor": "#F8F8F8", 146 | "backgroundColor": "#F8F8F8" 147 | }, 148 | "condition": { 149 | "current": 0, 150 | "list": [ 151 | { 152 | "name": "涨姿势", 153 | "path": "views/blogs/index" 154 | }, 155 | { 156 | "name": "登录页", 157 | "path": "views/login/index" 158 | }, 159 | { 160 | "name": "搜索参数筛选", 161 | "path": "views/filter/index" 162 | }, 163 | { 164 | "name": "引导页", 165 | "path": "views/guide/index" 166 | }, 167 | { 168 | "name": "首页", 169 | "path":"views/index/index" 170 | }, 171 | { 172 | "name": "详情", 173 | "path": "views/detail/index", 174 | "query": "id=1" 175 | }, 176 | { 177 | "name": "阅读器", 178 | "path": "views/reader/index", 179 | "query": "id=195582" 180 | }, 181 | { 182 | "name": "分类", 183 | "path": "views/theme/index" 184 | }, 185 | { 186 | "name": "搜索", 187 | "path": "views/search/index" 188 | }, 189 | { 190 | "name": "我的", 191 | "path": "views/home/index" 192 | }, 193 | { 194 | "name": "web-view", 195 | "path": "views/webview/index" 196 | }, 197 | { 198 | "name":"首页切换流", 199 | "path":"views/switch/flow" 200 | } 201 | ] 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/interface/index.ts: -------------------------------------------------------------------------------- 1 | import { colorItemInterface } from './tool'; 2 | import { readerPageNumberEnum } from './enum'; 3 | 4 | /** 5 | * 首页 `modal` 6 | */ 7 | export interface shareIndexModal { 8 | /** 9 | * 标题 10 | */ 11 | title: string 12 | /** 13 | * html标签 14 | */ 15 | body: string 16 | } 17 | 18 | /** 19 | * 首页数据 20 | */ 21 | export interface shareIndexData { 22 | modal: shareIndexModal 23 | lists: shareIndexComicData[] 24 | } 25 | 26 | /** 27 | * `card` 用到的数据 28 | */ 29 | export interface shareIndexComicData { 30 | /** 31 | * 标题 32 | */ 33 | title: string 34 | /** 35 | * 列表 36 | */ 37 | lists: shareComicFace[] 38 | } 39 | 40 | /** 41 | * 主题列表 `item` 42 | */ 43 | export interface themeListInterface { 44 | /** 45 | * 搜索的 `url` 46 | */ 47 | url: string 48 | /** 49 | * 标题文本 50 | */ 51 | text: string 52 | /** 53 | * 背景颜色 54 | */ 55 | bg?: colorItemInterface 56 | } 57 | 58 | /** 59 | * 主题 60 | */ 61 | export interface themeInterface { 62 | /** 63 | * 标题 64 | */ 65 | title: string 66 | /** 67 | * 几列, 默认是 `3` 列 68 | */ 69 | col: number 70 | lists: themeListInterface[] 71 | } 72 | 73 | export interface episodeInterface { 74 | id: string | number 75 | /** 76 | * eq 77 | */ 78 | ep: string 79 | /** 80 | * ep标题 81 | */ 82 | ep_title: string 83 | /** 84 | * ep时间 85 | */ 86 | ep_date: string 87 | } 88 | 89 | /** 90 | * 漫画公共的接口 91 | */ 92 | export interface shareComicFace { 93 | /** 94 | * TODO 95 | * 该id是否存在 96 | */ 97 | isExist?: boolean 98 | /** 99 | * 设备id 100 | */ 101 | id: string | number 102 | /** 103 | * 封面 104 | */ 105 | cover: string 106 | /** 107 | * 标题 108 | */ 109 | title: string 110 | /** 111 | * 标签 112 | */ 113 | tags: string[] 114 | /** 115 | * 作者 116 | */ 117 | authors: string[] 118 | /** 119 | * 介绍 120 | */ 121 | desc?: string 122 | /** 123 | * 页数 124 | */ 125 | page_count?: string | number 126 | /** 127 | * 点赞数 128 | */ 129 | like_count?: string | number 130 | /** 131 | * 类型(汉化 | 日文) 132 | */ 133 | sub_text?: string 134 | /** 135 | * 创建时间 136 | */ 137 | date?: string 138 | /** 139 | * 浏览量 140 | */ 141 | review?: number | string 142 | /** 143 | * 评论数 144 | */ 145 | comment_count?: number | string 146 | /** 147 | * 预览图片, 数组 148 | */ 149 | previews?: string[] 150 | /** 151 | * 选集 152 | */ 153 | episode?: episodeInterface[] 154 | /** 155 | * 推荐 156 | */ 157 | recommends?: shareComicFace[] 158 | /** 159 | * 历史阅读的时间 160 | */ 161 | reader_time?: number | string | Date 162 | /** 163 | * 历史阅读生成的时间 164 | */ 165 | reader_time_text?: string 166 | } 167 | 168 | /** 169 | * 留言话题接口 170 | */ 171 | export interface topicResponseInterface { 172 | /** 173 | * 总数 174 | */ 175 | count: number | string 176 | /** 177 | * 消息体 178 | */ 179 | message: string[] // topicResponseMessageInterface[] 180 | /** 181 | * 消息 182 | */ 183 | msg: string 184 | /** 185 | * 回复消息 186 | */ 187 | reply_message: string 188 | } 189 | 190 | /** 191 | * 留言话题返回的数据类型 192 | */ 193 | export interface topicItemInterface { 194 | /** 195 | * 头像 196 | */ 197 | avatar: string 198 | /** 199 | * 内容 200 | */ 201 | content: string 202 | /** 203 | * 时间 204 | */ 205 | date: string 206 | /** 207 | * 作品id 208 | */ 209 | id: string 210 | /** 211 | * 点赞数 212 | */ 213 | like_count: string | number 214 | /** 215 | * 昵称 216 | */ 217 | nickname: string 218 | /** 219 | * 作品的标题 220 | */ 221 | title: string 222 | } 223 | 224 | /** 225 | * 阅读器当前类型 226 | */ 227 | export interface readerItemInterface { 228 | /** 229 | * 当前页数类型 230 | */ 231 | pageType: readerPageNumberEnum 232 | /** 233 | * 下一话(500页限制) 234 | */ 235 | nextPage: number | string | null 236 | /** 237 | * 上一话(500页限制) 238 | */ 239 | prevPage: number | string | null 240 | /** 241 | * 当前 `page` 242 | */ 243 | currPage: number | string | null 244 | /** 245 | * 图片 246 | */ 247 | pics: string[] 248 | } 249 | 250 | /** 251 | * 老婆... 252 | */ 253 | export interface waifuItem { 254 | /** 255 | * 排名 256 | */ 257 | scrope: string 258 | /** 259 | * 图片 260 | */ 261 | pic: string 262 | /** 263 | * 名称 264 | */ 265 | name: string 266 | } 267 | 268 | /** 269 | * 博客单个`item` 270 | */ 271 | export interface blogItemInterface { 272 | /** 273 | * id, 直接拿到是一个拼接的, 例如: `/blog/2333` 274 | */ 275 | id: string 276 | /** 277 | * 时间 278 | */ 279 | time: string 280 | /** 281 | * 标题 282 | */ 283 | title: string 284 | /** 285 | * 背景 286 | */ 287 | bg: string 288 | /** 289 | * 内容 290 | */ 291 | content: string 292 | } 293 | 294 | /** 295 | * `blog` 返回数据 296 | */ 297 | export interface blogResInterface { 298 | /** 299 | * 是否有下一页 300 | */ 301 | isNext: boolean 302 | /** 303 | * 数据 304 | */ 305 | lists: blogItemInterface[] 306 | } -------------------------------------------------------------------------------- /src/components/topbar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 183 | 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "18comic", 3 | "version": "0.1.0", 4 | "versionCode": "v20200901", 5 | "private": true, 6 | "scripts": { 7 | "status": "tokei", 8 | "open": "open -a 'Google Chrome' --args --disable-web-security --user-data-dir='~/Desktop/chromeTemp'", 9 | "dev": "yarn serve", 10 | "serve": "npm run dev:h5", 11 | "build": "npm run build:h5", 12 | "build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build", 13 | "build:custom": "cross-env NODE_ENV=production uniapp-cli custom", 14 | "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build", 15 | "build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build", 16 | "build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build", 17 | "build:mp-qq": "cross-env NODE_ENV=production UNI_PLATFORM=mp-qq vue-cli-service uni-build", 18 | "build:mp-toutiao": "cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build", 19 | "build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build", 20 | "build:quickapp-native": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build", 21 | "build:quickapp-webview": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build", 22 | "dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch", 23 | "dev:custom": "cross-env NODE_ENV=development uniapp-cli custom", 24 | "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve", 25 | "dev:mp-alipay": "cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch", 26 | "dev:mp-baidu": "cross-env NODE_ENV=development UNI_PLATFORM=mp-baidu vue-cli-service uni-build --watch", 27 | "dev:mp-qq": "cross-env NODE_ENV=development UNI_PLATFORM=mp-qq vue-cli-service uni-build --watch", 28 | "dev:mp-toutiao": "cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch", 29 | "dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch", 30 | "dev:quickapp-native": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch", 31 | "dev:quickapp-webview": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch", 32 | "info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js", 33 | "serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js", 34 | "test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i", 35 | "test:h5": "cross-env UNI_PLATFORM=h5 jest -i", 36 | "test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest -i", 37 | "test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest -i", 38 | "test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i" 39 | }, 40 | "dependencies": { 41 | "@dcloudio/uni-app-plus": "^2.0.0-28220200724002", 42 | "@dcloudio/uni-h5": "^2.0.0-28220200724002", 43 | "@dcloudio/uni-helper-json": "*", 44 | "@dcloudio/uni-mp-alipay": "^2.0.0-28220200724002", 45 | "@dcloudio/uni-mp-baidu": "^2.0.0-28220200724002", 46 | "@dcloudio/uni-mp-qq": "^2.0.0-28220200724002", 47 | "@dcloudio/uni-mp-toutiao": "^2.0.0-28220200724002", 48 | "@dcloudio/uni-mp-weixin": "^2.0.0-28220200724002", 49 | "@dcloudio/uni-quickapp-native": "^2.0.0-28220200724002", 50 | "@dcloudio/uni-quickapp-webview": "^2.0.0-28220200724002", 51 | "@dcloudio/uni-stat": "^2.0.0-28220200724002", 52 | "cheerio": "^1.0.0-rc.3", 53 | "core-js": "^3.6.4", 54 | "dayjs": "^1.8.27", 55 | "flyio": "^0.6.2", 56 | "github-to-cdn": "^0.0.2", 57 | "regenerator-runtime": "^0.12.1", 58 | "url-parse": "^1.4.7", 59 | "vue": "^2.6.11", 60 | "vue-class-component": "^6.3.2", 61 | "vue-property-decorator": "^8.0.0", 62 | "vuex": "^3.2.0", 63 | "vuex-persistedstate": "^3.0.1" 64 | }, 65 | "devDependencies": { 66 | "@babel/plugin-syntax-typescript": "^7.10.4", 67 | "@dcloudio/types": "*", 68 | "@dcloudio/uni-automator": "^2.0.0-28220200724002", 69 | "@dcloudio/uni-cli-shared": "^2.0.0-28220200724002", 70 | "@dcloudio/uni-migration": "^2.0.0-28220200724002", 71 | "@dcloudio/uni-template-compiler": "^2.0.0-28220200724002", 72 | "@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.0-28220200724002", 73 | "@dcloudio/vue-cli-plugin-uni": "^2.0.0-28220200724002", 74 | "@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.0-28220200724002", 75 | "@dcloudio/webpack-uni-mp-loader": "^2.0.0-28220200724002", 76 | "@dcloudio/webpack-uni-pages-loader": "^2.0.0-28220200724002", 77 | "@types/cheerio": "^0.22.18", 78 | "@types/marked": "^0.7.4", 79 | "@types/url-parse": "^1.4.3", 80 | "@vue/cli-plugin-babel": "^4.3.0", 81 | "@vue/cli-plugin-typescript": "*", 82 | "@vue/cli-service": "^4.3.0", 83 | "babel-plugin-import": "^1.11.0", 84 | "colorui": "^1.0.0", 85 | "cross-env": "^7.0.2", 86 | "csstype": "^2.6.10", 87 | "jest": "^25.4.0", 88 | "marked": "^1.1.1", 89 | "mini-types": "*", 90 | "miniprogram-api-typings": "*", 91 | "postcss-comment": "^2.0.0", 92 | "typescript": "^3.0.0", 93 | "vue-template-compiler": "^2.6.11" 94 | }, 95 | "browserslist": [ 96 | "Android >= 4", 97 | "ios >= 8" 98 | ], 99 | "uni-app": { 100 | "scripts": {} 101 | } 102 | } -------------------------------------------------------------------------------- /src/interface/pages.ts: -------------------------------------------------------------------------------- 1 | import css from 'csstype' 2 | import { shareComicFace, themeInterface, themeListInterface, topicItemInterface, readerItemInterface, blogItemInterface } from '.'; 3 | import { searchOptions, themeMenuItemInterface, mirrorItemInterface } from './tool'; 4 | import { searchOptionTimeEnum, searchOptionTypeEnum } from './enum'; 5 | import { sayWordInterface } from '@/api/share'; 6 | 7 | // flag: 旧的流列表数据 8 | interface flowDataArrayFace { 9 | text: string 10 | url: string 11 | } 12 | 13 | /** 14 | * flow页面数据 15 | */ 16 | export interface flowDataFace { 17 | flowBg: string // 背景 18 | flowBgBlur: number // 模糊尺寸 19 | flowBgDark: boolean // 背景是否黑白, 用于判断是否有网络 20 | flows: mirrorItemInterface[] // 流列表 21 | setup: number // 步骤 22 | logoText: string // logo文字 23 | /** 24 | * 加载状态 25 | */ 26 | isLoading: boolean 27 | /** 28 | * 测试接口状态 29 | */ 30 | isTestLoading: boolean 31 | /** 32 | * 测试是否连接成功 33 | */ 34 | testStatus: boolean 35 | /** 36 | * 当前镜像 37 | */ 38 | current_mirror: null | mirrorItemInterface 39 | /** 40 | * 当前镜像索引 41 | */ 42 | current_mirror_index: null | number 43 | } 44 | 45 | /** 46 | * 首页接口数据 47 | */ 48 | export interface indexDataFace { 49 | footerButtonStyle: css.Properties 50 | model: any 51 | dialogButtons: any[] 52 | swiperList: any[] 53 | dotStyle: boolean 54 | lists: any[] 55 | cover?: string 56 | body?: string 57 | isLoading: boolean 58 | } 59 | 60 | /** 61 | * 阅读器接口数据 62 | */ 63 | export interface readerDataFace { 64 | 65 | /** 66 | * 漫画内容 67 | */ 68 | comicData: readerItemInterface 69 | 70 | /** 71 | * 漫画 `id` 72 | */ 73 | currentComicID: string 74 | 75 | /** 76 | * 图片(已废弃) 77 | */ 78 | imgs?: string[] 79 | 80 | /** 81 | * 是否在加载中 82 | */ 83 | isLoading: boolean 84 | 85 | /** 86 | * 该字段主要是用来判断 `scroll-view` 的高度的 87 | */ 88 | scrollTop: number 89 | 90 | /** 91 | * 点击后的高亮效果 `x` 92 | */ 93 | effectX: number 94 | 95 | /** 96 | * 点击后的高亮效果 `y` 97 | */ 98 | effectY: number 99 | 100 | /** 101 | * 高亮显示 `flag` 102 | */ 103 | effectDisplay: boolean 104 | 105 | /** 106 | * `box` 宽度 107 | */ 108 | effectW: number 109 | 110 | /** 111 | * `box` 高度 112 | */ 113 | effectH: number 114 | 115 | } 116 | 117 | /** 118 | * 搜索页面`data` 119 | */ 120 | export interface searchPageInterface { 121 | /** 122 | * 数据列表 123 | */ 124 | lists: shareComicFace[] 125 | /** 126 | * 是否有下一页 127 | */ 128 | isNext: boolean 129 | /** 130 | * loading状态 131 | */ 132 | isLoading: boolean 133 | /** 134 | * 是否显示 `loading` 动画 135 | */ 136 | showLoading: boolean 137 | /** 138 | * 当前页数 139 | */ 140 | current_page: string | number 141 | /** 142 | * 总页数 143 | */ 144 | total_page: string | number 145 | /** 146 | * 不重要!(只是用来判断是否是双击) 147 | * https://blog.csdn.net/qq_45515863/article/details/104361322 148 | */ 149 | touchStartTime: number 150 | /** 151 | * 返回顶部的标识 152 | */ 153 | back2topFlag: boolean 154 | /** 155 | * 搜索界面数据为空时的文字 156 | */ 157 | search_empty_text: string 158 | } 159 | 160 | export interface themePageDataInterface { 161 | /** 162 | * 搜索框提示文本 163 | */ 164 | placeholder: string 165 | /** 166 | * goto id-input 167 | */ 168 | goto_placeholder: string 169 | /** 170 | * 列表数据 171 | */ 172 | data: themeInterface[] 173 | /** 174 | * 最热主题 175 | */ 176 | popularThemes: themeListInterface[] 177 | /** 178 | * 默认毛玻璃背景图片 179 | */ 180 | blur_default_url: string 181 | /** 182 | * 搜索的文本 183 | */ 184 | searchVal: string 185 | /** 186 | * go-to 文本 187 | */ 188 | goto_text: string 189 | /** 190 | * go-to input 191 | */ 192 | gotoInputVal: string 193 | /** 194 | * go-to 模态框flag 195 | */ 196 | gotoModal: boolean 197 | /** 198 | * 是否加载中 199 | */ 200 | isLoading: boolean 201 | } 202 | 203 | export interface topicDataInterface { 204 | /** 205 | * 消息体 206 | */ 207 | messages: topicItemInterface[] 208 | /** 209 | * 作为分页 210 | */ 211 | page: number 212 | /** 213 | * 是否结束 214 | */ 215 | isEnd: boolean 216 | /** 217 | * 是否加载中 218 | */ 219 | isLoading: boolean 220 | } 221 | 222 | export interface blogDataInterface { 223 | /** 224 | * 消息体 225 | */ 226 | lists: blogItemInterface[] 227 | /** 228 | * 作为分页 229 | */ 230 | page: number 231 | /** 232 | * 是否结束 233 | */ 234 | isNext: boolean 235 | /** 236 | * 是否加载中 237 | */ 238 | isLoading: boolean 239 | } 240 | 241 | export interface settingsDataInterface { 242 | /** 243 | * 点击次数 244 | */ 245 | count: number 246 | /** 247 | * 检测更新 `flag` 248 | */ 249 | checkUpdateIsLoading: boolean 250 | } 251 | 252 | export interface detailDataInterface { 253 | 254 | /** 255 | * 本地拿到的 `id` 256 | */ 257 | id: string | number 258 | 259 | /** 260 | * 数据 261 | */ 262 | data: shareComicFace 263 | 264 | /** 265 | * 加载状态 266 | */ 267 | isLoading: boolean 268 | 269 | /** 270 | * 是否显示漫画完整信息 271 | */ 272 | showComicInfoBox: boolean 273 | 274 | } 275 | 276 | /** 277 | * `filter` page data interface 278 | */ 279 | export interface filterDataInterface { 280 | /** 281 | * 时间 282 | */ 283 | time?: null | searchOptionTimeEnum 284 | /** 285 | * 类型 286 | */ 287 | type?: null | searchOptionTypeEnum 288 | } 289 | 290 | export interface homePageInterface { 291 | 292 | /** 293 | * 一句话 294 | */ 295 | sayWord: sayWordInterface 296 | 297 | /** 298 | * 随机背景图 299 | */ 300 | bgImg: string 301 | 302 | } -------------------------------------------------------------------------------- /src/components/card-preview.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 180 | 181 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 146 | 147 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "18comic", 3 | "appid" : "__UNI__4D582C0", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | "app-plus" : { 9 | /* 5+App特有相关 */ 10 | "modules" : {}, 11 | /* 模块配置 */ 12 | "distribute" : { 13 | /* 应用发布信息 */ 14 | "android" : { 15 | /* android打包配置 */ 16 | "permissions" : [ 17 | "", 18 | "", 19 | "", 20 | "", 21 | "", 22 | "", 23 | "", 24 | "", 25 | "", 26 | "", 27 | "", 28 | "", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "" 39 | ] 40 | }, 41 | "ios" : { 42 | "UIUserInterfaceStyle" : "Automatic" 43 | }, 44 | /* ios打包配置 */ 45 | "sdkConfigs" : { 46 | "ad" : {} 47 | }, 48 | "icons" : { 49 | "android" : { 50 | "hdpi" : "unpackage/res/icons/72x72.png", 51 | "xhdpi" : "unpackage/res/icons/96x96.png", 52 | "xxhdpi" : "unpackage/res/icons/144x144.png", 53 | "xxxhdpi" : "unpackage/res/icons/192x192.png" 54 | }, 55 | "ios" : { 56 | "appstore" : "unpackage/res/icons/1024x1024.png", 57 | "ipad" : { 58 | "app" : "unpackage/res/icons/76x76.png", 59 | "app@2x" : "unpackage/res/icons/152x152.png", 60 | "notification" : "unpackage/res/icons/20x20.png", 61 | "notification@2x" : "unpackage/res/icons/40x40.png", 62 | "proapp@2x" : "unpackage/res/icons/167x167.png", 63 | "settings" : "unpackage/res/icons/29x29.png", 64 | "settings@2x" : "unpackage/res/icons/58x58.png", 65 | "spotlight" : "unpackage/res/icons/40x40.png", 66 | "spotlight@2x" : "unpackage/res/icons/80x80.png" 67 | }, 68 | "iphone" : { 69 | "app@2x" : "unpackage/res/icons/120x120.png", 70 | "app@3x" : "unpackage/res/icons/180x180.png", 71 | "notification@2x" : "unpackage/res/icons/40x40.png", 72 | "notification@3x" : "unpackage/res/icons/60x60.png", 73 | "settings@2x" : "unpackage/res/icons/58x58.png", 74 | "settings@3x" : "unpackage/res/icons/87x87.png", 75 | "spotlight@2x" : "unpackage/res/icons/80x80.png", 76 | "spotlight@3x" : "unpackage/res/icons/120x120.png" 77 | } 78 | } 79 | } 80 | }, 81 | /* SDK配置 */ 82 | "usingComponents" : true, 83 | "splashscreen" : { 84 | "alwaysShowBeforeRender" : true, 85 | "waiting" : true, 86 | "autoclose" : true, 87 | "delay" : 0 88 | }, 89 | "networkTimeout" : { 90 | "request" : 30000 91 | }, 92 | "compatible" : { 93 | "ignoreVersion" : true 94 | } 95 | }, 96 | "quickapp" : {}, 97 | /* 快应用特有相关 */ 98 | "mp-weixin" : { 99 | /* 小程序特有相关 */ 100 | "usingComponents" : true, 101 | "appid" : "wx8a0f33010a52d157", 102 | "setting" : { 103 | "urlCheck" : true 104 | } 105 | }, 106 | "mp-alipay" : { 107 | "usingComponents" : true 108 | }, 109 | "mp-baidu" : { 110 | "usingComponents" : true 111 | }, 112 | "mp-toutiao" : { 113 | "usingComponents" : true 114 | }, 115 | "mp-qq" : { 116 | "usingComponents" : true 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 159 | 160 | -------------------------------------------------------------------------------- /src/const/index.ts: -------------------------------------------------------------------------------- 1 | import { colorItemInterface, themeMenuItemInterface, devInsDataInterface, guideDataItemInterface, mirrorItemInterface } from '@/interface/tool' 2 | import { createStaticByCDN } from '@/utils/map' 3 | 4 | const injection = require('@/plugins/injection') 5 | 6 | /******** 7 | > TODO 将所有的公共 `key` 移植到 `key.ts` 中 8 | *******/ 9 | 10 | /** 11 | * 滤镜默认背景 12 | */ 13 | export let blur_default_url = "https://i.loli.net/2020/05/25/ynGRv1z5s7OCtw9.png" 14 | 15 | blur_default_url = createStaticByCDN('resources/tiny_blur.png') 16 | 17 | export let empty_default_url = "https://i.loli.net/2020/06/09/LnB24yeIwxs8p1g.png" 18 | 19 | empty_default_url = createStaticByCDN('resources/empty.png') 20 | 21 | /** 22 | * `18comic` logo 23 | * raw url: https://18comic.vip/static/resources/images/Cover/%E5%A4%A9%E5%A0%82.jpg 24 | */ 25 | export const ng18comicLogo = createStaticByCDN('resources/18comic.jpg') 26 | 27 | /** 28 | * 主题默认列 29 | */ 30 | export const theme_default_col = 3 31 | 32 | /** 33 | * 主题默认的最大文字(将会变为 `2` 列) 34 | */ 35 | export const theme_item_max_word = 8 36 | 37 | /** 38 | * 设置里的开发者相关默认最大点击次数 39 | * 才会打开开发者相关功能 40 | */ 41 | export const settings_click_max_count = 8 42 | 43 | 44 | /** 45 | * 默认背景 46 | */ 47 | export const bg_default_url = "http://www.dmoe.cc/random.php" 48 | 49 | /** 50 | * 搜索界面数据为空时的文字 51 | */ 52 | export const search_empty_text = "没有数据哦" 53 | 54 | /** 55 | * 主题搜索区 `placeholder` 56 | */ 57 | export const theme_search_main_placeholder = 'bilibili干杯🍻, 请搜索' 58 | 59 | /** 60 | * goto id-input 61 | */ 62 | export const theme_search_goto_placeholder = '请输入id' 63 | 64 | export const theme_search_goto_text = '如果你知道某个作品的id话....' 65 | 66 | /** 67 | * 历史记录的最大长度 68 | * 20200715 修改为 `240`.. 69 | */ 70 | export const history_views_max_length = 240 71 | 72 | /** 73 | * 我的邮箱 74 | */ 75 | export let my_email = "" 76 | try { 77 | my_email = atob(`Y2hlbmhvbnpob3VAZ21haWwuY29t`) 78 | } catch (err) { 79 | console.error("base64解码失败: ", err) 80 | } 81 | 82 | /** 83 | * 官方的 `trello` 的 `id` 84 | */ 85 | export const trello_board_id = 'McDZAm8C' 86 | 87 | /** 88 | * 颜色列表 89 | */ 90 | export const colors: colorItemInterface[] = [ 91 | { 92 | title: "嫣红", 93 | name: "red", 94 | color: "#e54d42" 95 | }, 96 | { 97 | title: "桔橙", 98 | name: "orange", 99 | color: "#f37b1d" 100 | }, 101 | { 102 | title: "明黄", 103 | name: "yellow", 104 | color: "#fbbd08" 105 | }, 106 | { 107 | title: "橄榄", 108 | name: "olive", 109 | color: "#8dc63f" 110 | }, 111 | { 112 | title: "森绿", 113 | name: "green", 114 | color: "#39b54a" 115 | }, 116 | { 117 | title: "天青", 118 | name: "cyan", 119 | color: "#1cbbb4" 120 | }, 121 | { 122 | title: "海蓝", 123 | name: "blue", 124 | color: "#0081ff" 125 | }, 126 | { 127 | title: "姹紫", 128 | name: "purple", 129 | color: "#6739b6" 130 | }, 131 | { 132 | title: "木槿", 133 | name: "mauve", 134 | color: "#9c26b0" 135 | }, 136 | { 137 | title: "桃粉", 138 | name: "pink", 139 | color: "#e03997" 140 | }, 141 | { 142 | title: "棕褐", 143 | name: "brown", 144 | color: "#a5673f" 145 | }, 146 | { 147 | title: "玄灰", 148 | name: "grey", 149 | color: "#8799a3" 150 | }, 151 | { 152 | title: "草灰", 153 | name: "gray", 154 | color: "#aaaaaa" 155 | }, 156 | { 157 | title: "墨黑", 158 | name: "black", 159 | color: "#333333" 160 | }, 161 | { 162 | title: "雅白", 163 | name: "white", 164 | color: "#ffffff" 165 | } 166 | ] 167 | 168 | /** 169 | * 主题菜单 170 | */ 171 | export const theme_menus: themeMenuItemInterface[] = [ 172 | { 173 | title: "GOTO", 174 | link: 'detail/index', 175 | key: 'goto', 176 | }, 177 | { 178 | title: "随机看", 179 | link: 'detail/index', 180 | key: 'random_comic', 181 | }, 182 | { 183 | title: "留言板", 184 | link: 'topic/index', 185 | key: 'bbs', 186 | }, 187 | { 188 | title: "涨姿势", 189 | link: 'blogs/index', 190 | key: 'blogs' 191 | }, 192 | // TODO 2020-08-28 193 | // 我陈某就是死, 从这里跳下去, 我也不会做这个功能(flag已立) 194 | // { 195 | // title: '插件开发', 196 | // link: 'webview/index', 197 | // key: 'plugin_development' 198 | // } 199 | ] 200 | 201 | /** 202 | * 创建提示性`文字` 203 | */ 204 | const createSpanTips = (ctx: string): string=> { 205 | return `\`${ ctx }\`` 206 | } 207 | 208 | export const guideDatas: guideDataItemInterface[] = [ 209 | { 210 | title: '作者寄语🔞', 211 | content: `完全免费! 代码开源, 没有多余套路. 所有数据均来自网络, 侵权必删, ${ createSpanTips('仅供学习参考') }, 如果一直访问不了, 请尝试切换镜像站`, 212 | }, 213 | { 214 | title: '警告🔞', 215 | content: `前方高能!!!\n部分内容可能对您的生理及心理造成难以恢复的伤害。本应用作者不会对由本应用造成的任何后果负责。\n\n未成年人应在监护人指导下使用本应用。`, 216 | isEnd: true 217 | }, 218 | ] 219 | 220 | /** 221 | * QQ群 222 | */ 223 | export const joinQQGroup = "https://jq.qq.com/?_wv=1027&k=KaVypDjS" 224 | 225 | /** 226 | * 注入的 `pages.json` 文件 227 | */ 228 | export const devInsData: devInsDataInterface = injection.pagejson 229 | 230 | /** 231 | * 2020-08-27 更新 232 | * 网址发布页: http://jmcomic.xyz 233 | */ 234 | 235 | /* 236 | 237 | JM主站 238 | https://18comic.vip 239 | 240 | 海外分流 241 | https://18comic.org 242 | 243 | 中國用戶 〈有新網址會隨時更新〉 244 | 245 | JM中國主站 246 | https://18comic1.biz 247 | 248 | 分流1 249 | https://18comic2.biz 250 | 251 | 分流2 252 | https://18comic3.biz 253 | 254 | */ 255 | export const defaultMirrorArr: mirrorItemInterface[] = [ 256 | { 257 | title: 'JM主站', 258 | ext: 'vip', 259 | full_url: 'https://18comic.vip' 260 | }, 261 | { 262 | title: '海外分流', 263 | ext: 'org', 264 | full_url: 'https://18comic.org' 265 | }, 266 | { 267 | title: 'JM中國主站', 268 | ext: 'fun', 269 | full_url: 'https://18comic1.biz' 270 | }, 271 | { 272 | title: "分流1", 273 | ext: "biz", 274 | full_url: "https://18comic2.biz" 275 | }, 276 | { 277 | title: "分流2", 278 | ext: "biz", 279 | full_url: "https://18comic3.biz" 280 | } 281 | ] 282 | 283 | /** 284 | * 更新版本是最新版本 285 | */ 286 | export const updateIsLastVersion = `已经是最新版本了` 287 | 288 | /** 289 | * 更新版本网络连接错误 290 | */ 291 | export const updateVersionNetWorkError = `更新版本失败, 可能是网络问题(需要翻墙)` 292 | 293 | /** 294 | * 重新加载按钮的文字 295 | */ 296 | export const reloadButtonText = `刷新(*╹▽╹*)` 297 | 298 | /** 299 | * 详情id不存在 300 | */ 301 | export const detailIDNotExist = `该漫画不存在` 302 | 303 | /** 304 | * 开源地址 305 | */ 306 | export const openSourceRepo = `https://github.com/waifu-project/18comic` -------------------------------------------------------------------------------- /src/views/filter/index.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 225 | 226 | -------------------------------------------------------------------------------- /src/views/reader/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 218 | 219 | -------------------------------------------------------------------------------- /src/views/switch/flow.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 181 | 182 | -------------------------------------------------------------------------------- /src/views/search/index.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 231 | 232 | -------------------------------------------------------------------------------- /src/views/theme/index.vue: -------------------------------------------------------------------------------- 1 | 103 | 104 | 271 | 272 | -------------------------------------------------------------------------------- /src/views/settings/index.vue: -------------------------------------------------------------------------------- 1 | 138 | 139 | 316 | 317 | --------------------------------------------------------------------------------