Compare commits
1 Commits
书栖网最后一个版本
...
tencent-cl
Author | SHA1 | Date | |
---|---|---|---|
f3a7351fd2 |
29
.gitignore
vendored
@@ -1,28 +1 @@
|
||||
# 后端部分
|
||||
target
|
||||
|
||||
# 前端部分
|
||||
# 排除数据库配置文件
|
||||
dbconfig.js
|
||||
|
||||
# 排除nodejs模块文件夹
|
||||
node_modules/
|
||||
|
||||
# 排除package-lock.json
|
||||
package-lock.json
|
||||
|
||||
# 排除所有调试产生的log文件
|
||||
*.log
|
||||
|
||||
# 排除idea项目 iml 文件
|
||||
bookshelfplus.iml
|
||||
|
||||
# TODO文件
|
||||
TODO*
|
||||
|
||||
# 不提交仓库代码量统计相关文件
|
||||
.VSCodeCounter
|
||||
|
||||
# 排除开发测试文件
|
||||
bookshelfplus-dev.sql
|
||||
development.md
|
||||
target
|
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 程序员小墨
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
886
README.md
@@ -1,886 +0,0 @@
|
||||
<div align="center">
|
||||
<h1>书栖网 网站开源项目</h1>
|
||||
<p>一个完全免费无门槛的计算机类电子书下载网站</p>
|
||||
</div>
|
||||
|
||||
项目官网:https://bookshelf.plus
|
||||
|
||||
开源仓库:<a href="https://github.com/bookshelfplus/bookshelfplus" target="_blank">GitHub</a> <a href="https://gitee.com/bookshelfplus/bookshelfplus" target="_blank">Gitee</a>
|
||||
|
||||

|
||||
|
||||
## 简介
|
||||
|
||||
前项目为书栖网官网开源项目,你也可以通过这个项目搭建一个属于自己的电子书分享与管理平台。
|
||||
|
||||
> 目前项目已上线,网址:https://bookshelf.plus/
|
||||
>
|
||||
> 项目介绍视频(B站):https://www.bilibili.com/video/BV1VT4y1k7Lv
|
||||
|
||||
> 项目部分功能仍在开发中,另外文档部分内容尚未更新,部署使用需要掌握一定的技术,如果项目后期看好的人多,将会花精力完善文档以及适当增添新功能。
|
||||
|
||||
|
||||
|
||||
## 项目许可证
|
||||
|
||||
本项目使用 MIT 许可证,但**不得删除或修改项目原作者信息,不得使用本项目作为毕业设计项目,或者将本项目传至诸如CSDN等付费下载平台**。除此之外,不做其他限制,祝您使用愉快 :)
|
||||
|
||||
|
||||
|
||||
## 开始使用
|
||||
|
||||
> 所需环境:Java JDK 8,Maven,MySQL 5.7+,Node.js,Redis等。
|
||||
>
|
||||
> MySQL推荐使用8.0以上版本。
|
||||
>
|
||||
> 本项目配置项较多,暂未测试宝塔服务器环境,如果可能,建议使用飞豹他环境。
|
||||
|
||||
> **下面的配置有些没有说明命令的执行目录,请自行判断。**这部分文档后期将会完善。
|
||||
|
||||
### 安装环境
|
||||
|
||||
```bash
|
||||
# 安装 nodejs
|
||||
# 官方网站:https://nodejs.org/zh-cn/
|
||||
# 下载地址:https://nodejs.org/dist/v16.14.0/node-v16.14.0-x64.msi
|
||||
|
||||
# 安装 JDK 8
|
||||
# TODO
|
||||
|
||||
# 安装 Maven
|
||||
# TODO
|
||||
|
||||
# 安装 MySQL (5.7 以上版本)
|
||||
# TODO
|
||||
|
||||
# 安装 Redis
|
||||
# TODO
|
||||
|
||||
# pm2
|
||||
npm i pm2 -g
|
||||
|
||||
# nodemon (可选)
|
||||
# 开发使用 nodemon (代码变动后自动重启)
|
||||
# 使用以下代码安装 nodemon
|
||||
npm i nodemon -g
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 环境配置
|
||||
|
||||
#### 配置国内镜像源
|
||||
|
||||
##### 配置 npm 国内镜像源
|
||||
|
||||
```bash
|
||||
# 查看当前配置的镜像源 默认为: https://registry.npmjs.org/
|
||||
npm config get registry
|
||||
|
||||
# 修改为国内镜像源 这里使用淘宝镜像源: https://registry.npm.taobao.org/
|
||||
npm config set registry https://registry.npm.taobao.org/
|
||||
```
|
||||
|
||||
|
||||
|
||||
##### 配置 Maven 国内镜像源
|
||||
|
||||
编辑 Maven 安装目录下 `conf/settings.xml` 文件,如下
|
||||
|
||||
```xml
|
||||
<mirrors>
|
||||
<!-- mirror
|
||||
| Specifies a repository mirror site to use instead of a given repository. The repository that
|
||||
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
|
||||
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
||||
|
|
||||
<mirror>
|
||||
<id>mirrorId</id>
|
||||
<mirrorOf>repositoryId</mirrorOf>
|
||||
<name>Human Readable Name for this Mirror.</name>
|
||||
<url>http://my.repository.com/repo/path</url>
|
||||
</mirror>
|
||||
-->
|
||||
<!-- ######### 从这里开始 ######### -->
|
||||
<mirror>
|
||||
<id>alimaven</id>
|
||||
<name>aliyun maven</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
</mirror>
|
||||
<!-- ######### 到这里结束 ######### -->
|
||||
</mirrors>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 下载项目
|
||||
|
||||
#### 方式1: 在 Release 页面下载压缩包
|
||||
|
||||
访问以下网址(国内推荐使用Gitee),下载最新版代码包并解压即可
|
||||
https://gitee.com/bookshelfplus/bookshelfplus/releases
|
||||
|
||||
https://github.com/bookshelfplus/bookshelfplus/releases
|
||||
|
||||
|
||||
|
||||
#### 方式2: 克隆 Git 代码仓库
|
||||
|
||||
```bash
|
||||
# 首先,先切换到希望克隆到的本地路径。之后克隆项目会在该目录下创建一个 bookshelf 文件夹
|
||||
# 例如,您可以切换到用户 家 目录
|
||||
# cd ~/
|
||||
|
||||
# 通过 码云 克隆 (也可通过 GitHub 克隆)
|
||||
git clone https://gitee.com/bookshelfplus/bookshelfplus
|
||||
# git clone https://github.com/bookshelfplus/bookshelfplus
|
||||
|
||||
# 进入克隆的项目文件夹
|
||||
cd ./bookshelfplus
|
||||
|
||||
# 切换到 master 分支下
|
||||
git checkout master
|
||||
|
||||
# 设置文件夹权限 (Windows 用户可跳过此步,Linux 用户需要设置)
|
||||
# TODO
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 项目配置
|
||||
|
||||
#### 配置 nginx.conf
|
||||
|
||||
参考配置如下(此处仅列出核心配置,完整配置文件可以参考[这里](./server/nginx/conf/nginx.conf))
|
||||
|
||||
```
|
||||
|
||||
http {
|
||||
upstream frontendNodejsServer {
|
||||
server 127.0.0.1:3000;
|
||||
}
|
||||
upstream backendSpringbootServer {
|
||||
server 127.0.0.1:8090;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
# server_name bookshelf.plus;
|
||||
|
||||
#禁止访问的文件或目录
|
||||
location ~ ^/(\.user.ini|\.htaccess|\.git|LICENSE|README.md)
|
||||
{
|
||||
return 404;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://frontendNodejsServer;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://backendSpringbootServer/api/;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
# 配置 nginx.conf
|
||||
# TODO
|
||||
|
||||
# [前端]
|
||||
# 配置后台 Api 地址
|
||||
# TODO
|
||||
# 配置前端网站名称
|
||||
# TODO
|
||||
|
||||
# [后端]
|
||||
# 配置 MySQL 数据库地址
|
||||
# TODO
|
||||
# 配置 Redis 地址
|
||||
# TODO
|
||||
# 配置第三方登录
|
||||
# TODO thirdparty.sample.properties -> thirdparty.properties
|
||||
```
|
||||
|
||||
配置好后,需要重启 nginx
|
||||
|
||||
```bash
|
||||
# 重启 nginx
|
||||
nginx -s reload
|
||||
# 或者使用其他重启命令
|
||||
# 例如 Ubuntu 系统下使用 systemctl restart nginx
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 处理依赖
|
||||
|
||||
```bash
|
||||
# [前端]
|
||||
npm install
|
||||
|
||||
# [后端]
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 数据导入
|
||||
|
||||
```bash
|
||||
# [数据库]
|
||||
# 导入数据库 SQL 脚本
|
||||
# TODO
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 编译后端项目
|
||||
|
||||
```bash
|
||||
mvn clean install
|
||||
|
||||
# 如果提示: Cannot create resource output directory: xxx
|
||||
# 那么说明权限不够,在前面加上 sudo
|
||||
# sudo mvn clean install
|
||||
```
|
||||
|
||||
编译成功后,可以看到如下输出(其中有 `BUILD SUCCESS`):
|
||||
|
||||
```bash
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Total time: 12:17 min
|
||||
[INFO] Finished at: 2022-04-03T14:22:18+08:00
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 创建云服务(腾讯云)
|
||||
|
||||
#### 对象存储(COS)
|
||||
|
||||
> 本项目云服务使用腾讯云,其他云厂商暂不支持。开始之前,你需要有一个腾讯云账号。
|
||||
>
|
||||
> 请注意,使用云服务可能会产生费用,使用前请您详细了解相关服务的收费策略,以免产生不必要的费用。
|
||||
|
||||
##### 创建存储桶
|
||||
|
||||
> 仅支持腾讯云 COS 存储桶,其他云厂商的对象存储暂不支持。
|
||||
|
||||
> 需要为本服务单独创建存储桶,不支持和其他业务共用一个存储桶。
|
||||
|
||||
首先登录腾讯云控制台,进入对象存储页面:
|
||||
|
||||
https://console.cloud.tencent.com/cos/bucket
|
||||
|
||||
点击创建存储桶。
|
||||
|
||||
创建COS存储桶的时候一定要创建在CSF可用区域。(建议选择上海,因为后面创建云函数需要与对象存储在同一地域,而云函数只支持以下地区)
|
||||
|
||||
> - 华南地区:广州
|
||||
> - 华东地区:上海
|
||||
> - 华北地区:北京
|
||||
> - 西南地区:成都
|
||||
|
||||
【TODO】图需要更新
|
||||
|
||||

|
||||
|
||||
高级可选配置可以根据自己实际需求进行配置,此处保持默认不做更改。
|
||||
|
||||

|
||||
|
||||
点击创建,完成存储桶的创建。
|
||||
|
||||
【TODO】图需要更新
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
##### 配置存储桶
|
||||
|
||||
###### 配置跨域访问
|
||||
|
||||
由于腾讯云存储桶和我们的业务域名不在同一主域下,所以需要配置 CORS 跨域访问,否则浏览器请求的时候会出现报错,无法完成请求。
|
||||
|
||||
> 如果您不了解 CORS 是什么的话,建议您阅读一下这篇 MDN 文档:[跨源资源共享CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS)(其中的描述可能有些专业,大概看看就好)
|
||||
>
|
||||
> 也可以看一下腾讯云的官方文档:[设置跨域访问](https://cloud.tencent.com/document/product/436/13318)
|
||||
|
||||
这里添加一条**跨域访问CORS规则**
|
||||
|
||||

|
||||
|
||||
**来源 Origin** 填写自己的业务域名,注意后续对存储桶的请求需要通过此域名发出。如果您只是自己本地测试,方便起见可以直接填写 `*` ,但是如果您希望向他人分享,您最好还是设置一下,否则容易被别人刷流量。
|
||||
|
||||
**操作 Methods** 可以全部勾上。目前项目使用到的由 `PUT`,`GET` 和 `DELETE` 三种。
|
||||
|
||||
其余配置保持默认即可。
|
||||
|
||||

|
||||
|
||||
配置好后点击保存即可。
|
||||
|
||||
|
||||
|
||||
###### 配置防盗链(可选)
|
||||
|
||||
这个配置不配不会影响业务正常使用,但是强烈建议您配置一下。因为不配置的话其他人可以将您的下载链接嵌入他们的网站,这样的话您需要为其支付费用。
|
||||
|
||||
> referer 在浏览器发请求的时候会将所在的网站域名通过 referer 请求标头发给服务端。③ 空 referer 一般指的是用户直接访问资源链接,而不是通过点击网页上的超链接访问的情景(也可能是通过设置了不发送 referer 请求头的网页访问过来),自己视情况设置。
|
||||
>
|
||||
> 防盗链可以参考腾讯云官方文档:[防盗链](https://cloud.tencent.com/document/faq/436/56651)
|
||||
|
||||

|
||||
|
||||
>需要注意的是,http 和 https ,www 和 不带www 需要都需要配置。
|
||||
>
|
||||
>例如,若您配置后用户可以通过 http://bookshelf.plus、http://www.bookshelf.plus、https://bookshelf.plus、https://www.bookshelf.plus 域名访问到,那么需要配置如下:
|
||||
>
|
||||
>```
|
||||
>http://bookshelf.plus
|
||||
>http://www.bookshelf.plus
|
||||
>https://bookshelf.plus
|
||||
>https://www.bookshelf.plus
|
||||
>```
|
||||
>
|
||||
>若您在二级域名下部署,例如 site.bookshelf.plus ,那么需要配置如下:
|
||||
>
|
||||
>```
|
||||
>http://site.bookshelf.plus
|
||||
>https://site.bookshelf.plus
|
||||
>```
|
||||
>
|
||||
>对于上述几种情况,您也可以使用通配符,以允许域名下所有子域对资源的访问。
|
||||
>
|
||||
>```
|
||||
>http://*.bookshelf.plus
|
||||
>https://*.bookshelf.plus
|
||||
>```
|
||||
|
||||
|
||||
|
||||
###### 配置自定义 CDN 加速域名(TODO)
|
||||
|
||||
> 参考腾讯云官方文档:[CDN 加速域名](https://cloud.tencent.com/document/faq/436/56558)
|
||||
|
||||
TODO
|
||||
|
||||
上传:后端生成带有效期的预授权URL,前端使用这个 URL 进行上传。
|
||||
|
||||
下载:后端计算好 CDN 回源鉴权返回给前端,前端通过这个鉴权 URL 下载文件。
|
||||
|
||||
|
||||
|
||||
##### 配置日志记录(可选)
|
||||
|
||||
如果您需要开启日志记录,可以按照下图步骤进行配置,如果不需要就不配置。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#### 云函数(Serverless 函数服务)
|
||||
|
||||
> 为什么要创建云函数?
|
||||
>
|
||||
> 云函数对象存储文件上传成功时触发,后台上传文件后通过该云函数告知后端服务文件上传成功,以便后端服务及时更新数据库中记录及进行进一步处理。
|
||||
>
|
||||
> 不创建可以吗?
|
||||
>
|
||||
> 可以,但不推荐。如果不创建,那么上传文件后部分弱网情况下后端文件上传状态不会更新,需要手动点击刷新才会更新。
|
||||
|
||||
##### 创建 Serverless 函数
|
||||
|
||||
> 腾讯云官方文档:COS 触发器说明
|
||||
> https://cloud.tencent.com/document/product/583/9707
|
||||
|
||||
> 注意,一个存储桶同一个类别的触发事件仅能选择一个
|
||||
|
||||
打开腾讯云控制台 Serverless后台:https://console.cloud.tencent.com/scf/list
|
||||
|
||||
点击新建。
|
||||
|
||||

|
||||
|
||||
**基础配置:**修改如下信息。
|
||||
|
||||

|
||||
|
||||
**函数代码:**选择在线编辑,执行方法保持默认(`index.main_handler`)。
|
||||
|
||||
将项目文件夹下 [utils/QCloudSCF/index.js](utils/QCloudSCF/index.js) 代码文件中内容粘贴至下方在线编辑器(在线编辑器中的默认内容需要删除)。
|
||||
|
||||
> 注意,图中 ③ 处请修改为您自己的域名(例如网站部署在 `abc.example.com`,此处就填写 `abc.example.com`),若您的网站配置了多个域名,选择其中一个可以访问后台api的域名填写即可。
|
||||
|
||||

|
||||
|
||||
**高级配置**:保持默认即可。
|
||||
|
||||
**触发器配置:**`创建触发器` 处选择 `自定义创建`。
|
||||
|
||||
> 前缀过滤:不填
|
||||
>
|
||||
> 后缀过滤:不填
|
||||
|
||||

|
||||
|
||||
点击完成,创建成功。
|
||||
|
||||
。
|
||||
|
||||
稍等一下,等待部署完成后,会自动跳转到函数管理页面。
|
||||
|
||||
创建成功后,建议将 `执行超时时间` 适当调大一点,特别是当服务器和腾讯云COS节点较远的情况,以减少因网络问题导致COS文件上传后网站后台不能及时感知。一般建议大于 `5秒`,此处调为 `10秒`。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#### 生成 SecretId、SecretKey
|
||||
|
||||
> 有两种方式,一种是创建子用户,然后生成子用户的SecrectKey,另一种是直接生成当前账号的SecrectKey。两种方式均可。如果您对权限管理有需求,建议使用第一种;如果您希望尽可能简单的配置,可以使用第二种。如果您不确定使用哪种,那么请用第一种。
|
||||
|
||||
##### 第1种:创建子用户
|
||||
|
||||
登录腾讯云后台,进入访问管理下的用户列表页:https://console.cloud.tencent.com/cam
|
||||
|
||||
点击新建用户
|
||||
|
||||

|
||||
|
||||
点击快速创建。
|
||||
|
||||

|
||||
|
||||
接下来这里有四个地方需要配置。下图仅标出需要配置的项目,具体应该配置成什么请继续往下看。
|
||||
|
||||

|
||||
|
||||
> ① 用户名:自己随便起一个,满足要求即可。(用户名创建后不可以修改)
|
||||
>
|
||||
> ② 访问方式:修改为编程访问。
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ③ 用户权限:
|
||||
>
|
||||
> <1> 取消 `AdministratorAccess` 权限;
|
||||
>
|
||||
> <2> 搜索 `QcloudCOSDataFullControl` ,并勾选 `QcloudCOSDataFullControl` (对象存储(COS)数据读、写、删除、列出的访问权限)
|
||||
>
|
||||
> 
|
||||
>
|
||||
> ④ 根据自己的情况选择即可
|
||||
|
||||
点击创建用户,用户创建成功,获得密钥。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
##### 第2种:直接生成
|
||||
|
||||
访问:https://console.cloud.tencent.com/cam/capi
|
||||
|
||||
点击继续使用。
|
||||
|
||||

|
||||
|
||||
点击新建密钥。
|
||||
|
||||

|
||||
|
||||
密钥创建完成。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 启动项目
|
||||
|
||||
```bash
|
||||
# [前端]
|
||||
# 启动前端服务 (默认监听 3000 端口)
|
||||
npm run prod
|
||||
|
||||
|
||||
# [后端]
|
||||
# 启动后端服务 (默认监听 8090 端口)
|
||||
# mvn install -Djar.forceCreation spring-boot:run
|
||||
java -jar ./bookshelfplus/target/bookshelfplus-1.0-SNAPSHOT.jar
|
||||
|
||||
# 在后台执行:
|
||||
# java -jar ./bookshelfplus/target/bookshelfplus-1.0-SNAPSHOT.jar &
|
||||
|
||||
# 如果提示: Cannot create resource output directory: xxx
|
||||
# 那么说明权限不够,在前面加上 sudo
|
||||
|
||||
# 启动 nginx
|
||||
# TODO
|
||||
```
|
||||
|
||||
若启动时提示以下 `WARNING`,是因为 `JDK` 版本过高,一般不影响使用。
|
||||
|
||||
```bash
|
||||
WARNING: An illegal reflective access operation has occurred
|
||||
WARNING: Illegal reflective access by com.google.inject.internal.cglib.core.$ReflectUtils$1 (file:/usr/share/maven/lib/guice.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
|
||||
WARNING: Please consider reporting this to the maintainers of com.google.inject.internal.cglib.core.$ReflectUtils$1
|
||||
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
|
||||
WARNING: All illegal access operations will be denied in a future release
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 验证是否部署成功
|
||||
|
||||
#### 查看后端运行状态
|
||||
|
||||
```bash
|
||||
# 查看后端运行状态
|
||||
# ps -A | grep java
|
||||
ubuntu@xxxxxx:~$ ps -A | grep java
|
||||
558861 ? 00:00:13 java
|
||||
|
||||
# 结束在后台运行的 Java 进程
|
||||
# sudo kill -9 [端口号]
|
||||
ubuntu@xxxxxx:~$ sudo kill -9 558861
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 验证云函数是否配置成功
|
||||
|
||||
检查云函数是否能与后端服务进行交互
|
||||
|
||||
点击云函数名称,进入云函数详情页面。
|
||||
|
||||

|
||||
|
||||
切换到“函数代码”标签页(如下图)。
|
||||
|
||||

|
||||
|
||||
将测试模板切换为 `COS 对象存储的 PUT 事件模板`。
|
||||
|
||||

|
||||
|
||||
往下滑动,编辑器下方有两个按钮,分别是**部署**和**测试**。
|
||||
|
||||

|
||||
|
||||
> 注意,每次在修改云函数的代码后都需要点击**部署**按钮,所做的修改才会被更新。
|
||||
|
||||
我们点击**测试**,如果您看到如下返回信息,说明您的后端服务和云函数已经部署成功。
|
||||
|
||||

|
||||
|
||||
返回结果类似于:
|
||||
|
||||
```json
|
||||
{"status":"success","data":{"data":"您的云函数配置成功。","status":"success"},"tryTime":1}
|
||||
```
|
||||
|
||||
> 您还可能见到如下错误:
|
||||
>
|
||||
> **请求超时**: `Invoking task timed out after 3 seconds`
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 返回结果类似于:
|
||||
>
|
||||
> ```json
|
||||
> {"errorCode":-1,"errorMessage":"Invoking task timed out after 3 seconds","requestId":"3fed8ed2-da82-41a5-beda-eaab1e12a019","statusCode":433}
|
||||
> ```
|
||||
>
|
||||
> 解决方法:可以尝试调大云函数的超时时间(函数管理→函数配置→右上角**编辑**→环境配置分类下面执行超时时间适当调大一点)。如果调大超时时间后仍然不行,那么说明云函数无法访问到后端服务,请检查后端服务是否已经部署并启动,云函数中的域名是否配置正确。
|
||||
>
|
||||
>
|
||||
>
|
||||
> **未创建 COS 触发器**:`JSON解析出错!`
|
||||
>
|
||||
> 
|
||||
>
|
||||
> 返回结果类似于:
|
||||
>
|
||||
> ```json
|
||||
> {"status":"success","data":{"data":{"errCode":10001,"errMsg":"JSON解析出错!"},"status":"failed"},"tryTime":1}
|
||||
> ```
|
||||
>
|
||||
> 解决方法:检查您的云函数“触发管理”页面是否已创建如下触发器。如果没有或不正确,请创建或修改为正确配置。
|
||||
>
|
||||
> 
|
||||
>
|
||||
>
|
||||
>
|
||||
> 如果您还遇到了其他返回结果,请参考错误提示进行处理。
|
||||
|
||||
|
||||
|
||||
## 停止项目
|
||||
|
||||
```bash
|
||||
# 停止 nginx
|
||||
# 有如下几种停止方式
|
||||
nginx -s quit # 从容停止
|
||||
nginx -s stop # 立即停止
|
||||
systemctl stop nginx # 使用 systemctl 停止
|
||||
# 也可直接杀掉 nginx 进程
|
||||
|
||||
# 停止前端服务
|
||||
npm run prod-stop
|
||||
|
||||
# 停止后端服务
|
||||
# TODO
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 其他命令
|
||||
|
||||
```bash
|
||||
# 清除前端字体缓存
|
||||
node ./bookshelfplus-frontend/cleanup.js
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 功能展示
|
||||
|
||||
### 功能列表(TODO)
|
||||
|
||||
- [x] 首页。简约,一个搜索框就够了。后期考虑添加热门搜索功能。
|
||||
- [x] 书籍列表页,也是搜索结果页。就是一个书单列表,带分页功能。
|
||||
- [x] 书籍详情页。主要是显示书籍的各种详细信息(书名,简介,缩略图等),还有下载方式,同时还有反馈功能(连接失效反馈,版权问题申诉下架等)
|
||||
- [x] 分类列表页
|
||||
- [x] 分类详情页
|
||||
- [x] 管理员后台。
|
||||
- [x] 用户登录后台。
|
||||
|
||||
### 功能截图
|
||||
|
||||
【TODO】截图待补充...
|
||||
|
||||
|
||||
|
||||
## 项目架构
|
||||
|
||||
> 项目前后端分离开发,使用了不同的技术,通过nginx进行反向代理
|
||||
|
||||
**前端**采用`nodejs`开发,使用`axios`、`jQuery`等组件。
|
||||
|
||||
**后端**采用`SpringBoot`开发,数据库连接使用`mybatis`、`alibaba druid`,接口文档生成使用`swagger2`,参数验证采用`hibernate`,日期时间处理使用`joda-time`工具类,同时还使用了`lombok`简化代码。
|
||||
|
||||
**数据库**采用`MySQL`,会话缓存采用`redis`。
|
||||
|
||||
**反向代理**使用`nginx`。
|
||||
|
||||
**对象存储**对接腾讯云COS存储(`cos_api`)。
|
||||
|
||||
**第三方登录**使用 `JustAuth` 开源项目(配合 `okhttp3`)
|
||||
|
||||
|
||||
|
||||
## 开发工具
|
||||
|
||||
前端:VS Code,后端:idea,数据库:Navicat
|
||||
|
||||
> 以上为项目开发时所使用的开发工具,也可以使用其他的开发工具打开,但建议使用以上工具打开本项目,避免产生一些莫名错误。
|
||||
|
||||
|
||||
|
||||
## 疑难解答
|
||||
|
||||
### 动态压缩字体技术
|
||||
|
||||
项目使用了动态压缩字体技术,因为中文字体包过大,无法快速加载,所以在用户访问网页加载完成后,使用js取得页面上显示的所有文字,然后发回给后端,后端返回一个压缩后的字体包。
|
||||
|
||||
由于页面上显示的文字相对字体包而言很少,所以压缩后的字体基本上只有几十K到几百K,这样便于网络传输。
|
||||
|
||||
|
||||
|
||||
## 常见问题 FAQ
|
||||
|
||||
### Nginx 无法启动
|
||||
|
||||
【问题特征】启动时,报如下错误:
|
||||
|
||||
```bash
|
||||
nginx: [emerg] CreateFile() "xxxxxxx/nginx.conf" failed (1113: No mapping for the Unicode character exists in the target multi-byte code page)
|
||||
```
|
||||
|
||||
【问题原因】
|
||||
|
||||
nginx启动目录不能包含中文,否则无法启动
|
||||
|
||||
【解决方法】
|
||||
|
||||
将 nginx 安装到不包含中文和特殊字符的目录中
|
||||
|
||||
|
||||
|
||||
### 项目启动后,自定义字体加载失败
|
||||
|
||||
【问题原因】
|
||||
|
||||
因为项目文件夹的权限不够,导致无法生成字体文件,进而导致前端访问不到字体文件。
|
||||
|
||||
【解决方法】
|
||||
|
||||
修改项目文件夹的权限和用户组,参考命令如下(修改成你自己的配置,不要直接执行)
|
||||
|
||||
```bash
|
||||
# 修改用户组
|
||||
sudo chown -R www-data:www-data bookshelf.plus/
|
||||
# www-data:www-data 改成你自己的用户组;bookshelf.plus/改成你本项目的文件夹
|
||||
|
||||
# 修改权限
|
||||
chmod -R 755 bookshelf.plus/
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 项目启动后,可以看到项目界面,但是无法查询、登录等
|
||||
|
||||
【问题原因】
|
||||
|
||||
可能是后端服务没有成功启动
|
||||
|
||||
【解决方法】
|
||||
|
||||
首先点击页脚的“网站状态检测”,看看后台服务器能否正常连接。
|
||||
|
||||
如果能够连接,那么就是后端与数据库之间的连接出现了问题。
|
||||
|
||||
> 例如数据库没开,数据库没有导入SQL文件,后端的数据库连接信息配置错误等等
|
||||
|
||||
如果不能连接,那么就是后端服务的问题,检查一下后端服务是否已经打开了。
|
||||
|
||||
|
||||
|
||||
### 云服务器上,项目启动成功,但是无法访问网页
|
||||
|
||||
【问题原因】
|
||||
|
||||
可能是 nginx 文件配置错误,或者服务器的端口没有对外开放
|
||||
|
||||
【解决方法】
|
||||
|
||||
检查一下
|
||||
|
||||
- 云服务器的“安全组”(不同厂商有不同的叫法)中是否开放了80端口
|
||||
- nginx 配置是否正确(主要看 `server_name`, `listen`, `location` 等配置)
|
||||
|
||||
|
||||
|
||||
### 项目启动后,DruidDataSource 疯狂报错 create connection SQLException, ...
|
||||
|
||||
【错误原因】数据库访问不了
|
||||
|
||||
【解决方法】
|
||||
|
||||
检查一下数据库是否开启,配置文件中的数据库配置是否正确,以及配置的 MySQL 用户是否有访问权限
|
||||
|
||||
【错误日志】
|
||||
|
||||
```bash
|
||||
2022-04-25 00:34:32.726 ERROR 18344 --- [reate-335731589] com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://127.0.0.1:3306/bookshelfplus?useSSL=false&serverTimezone=Asia/Shanghai, errorCode 0, state 08S01
|
||||
|
||||
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
|
||||
|
||||
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
|
||||
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:829) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:449) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:242) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1657) ~[druid-1.2.8.jar:1.2.8]
|
||||
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1723) ~[druid-1.2.8.jar:1.2.8]
|
||||
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2838) ~[druid-1.2.8.jar:1.2.8]
|
||||
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
|
||||
|
||||
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
|
||||
at sun.reflect.GeneratedConstructorAccessor59.newInstance(Unknown Source) ~[na:na]
|
||||
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_201]
|
||||
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_201]
|
||||
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:167) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:89) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.NativeSession.connect(NativeSession.java:120) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:949) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:819) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
... 6 common frames omitted
|
||||
Caused by: java.net.ConnectException: Connection refused: connect
|
||||
at java.net.DualStackPlainSocketImpl.connect0(Native Method) ~[na:1.8.0_201]
|
||||
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) ~[na:1.8.0_201]
|
||||
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_201]
|
||||
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_201]
|
||||
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_201]
|
||||
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_201]
|
||||
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_201]
|
||||
at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_201]
|
||||
at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:156) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:63) ~[mysql-connector-java-8.0.28.jar:8.0.28]
|
||||
... 9 common frames omitted
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 项目启动报错 Error parsing HTTP request header
|
||||
|
||||
【问题原因】客户端没有按照规范发HTTP请求
|
||||
|
||||
【解决方法】不影响项目运行,无需修改,可忽略该错误
|
||||
|
||||
【错误日志】
|
||||
|
||||
```bash
|
||||
2022-04-25 00:19:43.698 INFO 434027 --- [nio-8090-exec-1] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header
|
||||
Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.
|
||||
|
||||
java.lang.IllegalArgumentException: Invalid character found in method name [0x030x000x00/*0xe00x000x000x000x000x00Cookie: ]. HTTP method names must be tokens
|
||||
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:419) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:271) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
|
||||
at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na]
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 其他问题
|
||||
|
||||
以上仅列出了部分常见问题,如果您没有找到相关解决方法,可以在 Gitee 或 GitHub 仓库中创建一个 issue。提问时请注意要尽可能详细地描述问题,以及社区提问基本礼仪。
|
||||
|
||||
|
||||
|
||||
## 联系我们
|
||||
|
||||
目前该项目由 程序员小墨 独立开发,你可以在 [GitHub](https://github.com/coder-xiaomo)、[Gitee](https://gitee.com/coder-xiaomo)、[B站](https://space.bilibili.com/457109942)或微信公众号等平台找到我(所有平台都是“程序员小墨”这个名字)。
|
||||
|
||||
如您希望合作,或者共同维护本项目,可以通过 `admin@only4.work` 与我取得联系。邮件主题中请注明 `[书栖网丨开源项目]` 方便我们快速了解您的来意,谢谢。
|
||||
|
||||
精力有限,暂不提供免费客服服务,如您遇到问题,请自行搜索解决。这类相关邮件我们将不予回复,望理解!
|
@@ -1,5 +0,0 @@
|
||||
# 设置开发/测试环境 development / production
|
||||
NODE_ENV=development
|
||||
|
||||
# 设置是否开启GZIP,此处因为Nginx开了,所以nodejs就不开了
|
||||
gzip=false
|
@@ -1,114 +0,0 @@
|
||||
'use strict';
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var favicon = require('serve-favicon');
|
||||
var logger = require('morgan');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var bodyParser = require('body-parser');
|
||||
let dotenv = require('dotenv');
|
||||
|
||||
// 读取配置文件
|
||||
dotenv.config('./env');
|
||||
// console.log(process.env);
|
||||
// 引入路由文件
|
||||
var indexRoute = require('./routes/index');
|
||||
var fontminRoute = require('./routes/fontmin');
|
||||
const { copyFileSync } = require('fs');
|
||||
|
||||
// 创建应用
|
||||
var app = express();
|
||||
|
||||
// gzip
|
||||
if (process.env.gzip == "true") {
|
||||
const compression = require('compression');
|
||||
app.use(compression());
|
||||
console.log("[GZIP] gzip enabled");
|
||||
} else {
|
||||
console.log("[GZIP] gzip disabled");
|
||||
}
|
||||
|
||||
// 设置视图引擎 view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs'); // pug
|
||||
app.engine('.html', require('ejs').__express);
|
||||
app.set('view engine', 'html');
|
||||
|
||||
app.use(favicon(__dirname + '/public/favicon.ico'));
|
||||
app.use(logger('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// 路由
|
||||
app.use('/', indexRoute);
|
||||
app.use('/fontmin', fontminRoute);
|
||||
|
||||
// 捕获404并转发到错误处理程序 catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// 错误处理 error handlers
|
||||
|
||||
// development error handler
|
||||
// will print stacktrace
|
||||
if (app.get('env') === 'development') {
|
||||
console.log("[NODE_ENV] development");
|
||||
app.use(function (err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: err,
|
||||
title: '出错啦'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// production
|
||||
console.log("[NODE_ENV] production");
|
||||
}
|
||||
|
||||
// production error handler
|
||||
// no stacktraces leaked to user
|
||||
app.use(function (err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
|
||||
var server = app.listen(app.get('port'), function () {
|
||||
console.log('Express server listening on port ' + server.address().port);
|
||||
|
||||
// 引入站点配置文件
|
||||
global.site = require("./settings.json");
|
||||
|
||||
// 取得API路径
|
||||
if (!global.site) {
|
||||
console.log('settings.json is not defined');
|
||||
process.exit(1);
|
||||
}
|
||||
// console.log("[global.site]");
|
||||
// console.log(JSON.stringify(global.site));
|
||||
console.log(" ***************************** 启动成功 ***************************** ");
|
||||
});
|
||||
|
||||
|
||||
// 注册SIGINT信号事件
|
||||
process.on('SIGINT', function () {
|
||||
console.clear();
|
||||
console.log(" ***************************** 正在清理 ***************************** ");
|
||||
|
||||
require('./cleanup.js');
|
||||
|
||||
console.log(" ************************* 清理完毕,已退出 ************************* ");
|
||||
console.log('Exit now!');
|
||||
|
||||
process.exit();
|
||||
// return true;
|
||||
});
|
@@ -1,22 +0,0 @@
|
||||
const fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
function cleanup() {
|
||||
var fontminPath = path.join(__dirname, 'public/fontmin');
|
||||
if (fs.existsSync(fontminPath)) {
|
||||
console.log("Cleaning up temporary files: public/fontmin/*.ttf");
|
||||
let files = fs.readdirSync(fontminPath);
|
||||
files.forEach((file, index) => {
|
||||
if (file.endsWith('.ttf')) {
|
||||
let curPath = fontminPath + "/" + file;
|
||||
fs.unlinkSync(curPath); //删除文件
|
||||
console.log(" | removed file: " + file);
|
||||
}
|
||||
});
|
||||
console.log("Temporary files cleared successfully");
|
||||
}
|
||||
}
|
||||
|
||||
cleanup();
|
||||
|
||||
module.exports = cleanup;
|
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "bookshelf-plus",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node app",
|
||||
"dev": "nodemon",
|
||||
"test": "set NODE_ENV=development & nodemon",
|
||||
"prod": "set NODE_ENV=production & pm2 start app.js --name bookshelfplus-frontend",
|
||||
"prod-stop": "pm2 stop bookshelfplus-frontend",
|
||||
"clean": "node cleanup.js"
|
||||
},
|
||||
"description": "书栖网",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.0.0",
|
||||
"ejs": "^3.1.6",
|
||||
"express": "^4.17.3",
|
||||
"fontmin": "^0.9.9",
|
||||
"fs": "^0.0.1-security",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"serve-favicon": "^2.5.0"
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1088 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="212.5" height="200"><path d="M492.50419 1021.607197c-175.510584-20.915438-326.587843-116.410081-416.96556-264.033415-31.341176-51.169266-57.245617-118.456852-70.357742-184.017474C1.21527 553.728217 0 533.452395 0 483.114629c0-57.117694 0.895462-68.950587 6.971813-98.948569C46.755917 188.315693 186.575938 49.519058 387.031539 6.217066 411.145056 0.908254 424.768873 0.012792 477.98491 0.012792 547.703036-0.243054 569.897705 3.210871 631.428748 22.655193c72.084704 23.473901 128.690705 57.949194 185.360668 112.764271 69.718126 67.543432 111.037308 142.442446 132.208592 238.832551 9.018583 39.912028 9.914045 131.760861 1.790924 170.201772-4.669196 22.386554-4.669196 25.712556-0.639615 33.068139 4.989003 9.594237 4.669196 9.338391 74.131474 27.119711 52.576421 13.559856 58.97258 16.693973 62.746313 31.660984 4.093541 15.862473-33.707754 97.861222-68.247009 147.87918-84.877021 123.190009-217.085613 206.595913-370.785297 233.459778-30.829483 5.372773-123.445855 7.67539-155.426647 3.965618z m135.150825-195.082828a363.301792 363.301792 0 0 0 172.312505-86.156253c34.859063-31.149291 73.555821-85.708521 89.226408-126.643934 14.775126-38.440911 15.990396-37.609411-91.592987-64.473276-30.509675-7.931236-36.010371-16.118319-31.341176-48.354957 11.896854-81.678942-20.659591-171.864774-83.981558-231.54093a291.025203 291.025203 0 0 0-364.069331-28.207058c-82.510442 57.117694-126.132242 149.542181-121.2072 257.509333 4.093541 91.273179 40.103913 170.457619 106.623959 234.611087 84.557213 81.99875 206.020259 116.729889 324.02938 93.255988z" fill="#4D74FF"></path></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M773.9392 301.8752m-200.0384 0a200.0384 200.0384 0 1 0 400.0768 0 200.0384 200.0384 0 1 0-400.0768 0Z" fill="#13227a"></path><path d="M924.4672 706.2528a24.32 24.32 0 0 1-24.2688 24.2688h-145.7664a24.3712 24.3712 0 0 0-24.2688 24.32 24.2688 24.2688 0 0 0 24.2688 24.2688h48.5888a24.32 24.32 0 0 1 24.2688 24.32 24.32 24.32 0 0 1-24.2688 24.2688h-64.512A388.7616 388.7616 0 0 1 122.88 390.4512h-48.5888a24.32 24.32 0 0 1 0-48.5888h97.28a24.2688 24.2688 0 0 0 6.8096-47.5648l0.768-0.9728H122.88a24.32 24.32 0 1 1 0-48.5888h101.632a388.7616 388.7616 0 0 1 619.52 437.248h56.32a24.32 24.32 0 0 1 24.1152 24.2688z" fill="#1296db"></path><path d="M724.224 575.232c-5.7344-40.2432-26.112-70.656-62.0544-89.9584a111.1552 111.1552 0 0 0-66.9184-12.6976c-3.2768 0.3584-4.5568-0.5632-4.2496-4.096v-6.7584-5.12a133.12 133.12 0 0 0-0.3584-13.7728 116.5312 116.5312 0 0 0-12.2368-40.96 24.064 24.064 0 0 0-1.3312-2.5088v-0.5632a120.4224 120.4224 0 0 0-19.9168-26.4704l-2.048-1.9456A22.9376 22.9376 0 0 0 552.96 368.64a116.1216 116.1216 0 0 0-93.3376-28.8768 114.8416 114.8416 0 0 0-91.904 68.9664A116.224 116.224 0 0 0 358.4 467.456c0.3584 4.4544-0.8704 5.12-4.352 4.8128a97.28 97.28 0 0 0-18.8928-0.7168 117.76 117.76 0 0 0-107.52 143.7696c0.8192 3.5328 1.8432 7.0656 2.9696 10.24 0.3584 1.1776 0.768 2.304 1.1776 3.4304l1.3312 3.3792 0.3072 0.6656a26.4192 26.4192 0 0 0 1.0752 2.5088 117.4528 117.4528 0 0 0 224.4608-48.4352v-3.9424a10.5984 10.5984 0 0 1 11.008-10.9056h3.8912a116.736 116.736 0 0 0 80.1792-31.6928l3.8912-3.7888h0.3072a68.0448 68.0448 0 0 1 70.144-16.5376c29.7984 9.1136 46.4896 30.72 50.688 61.44a23.7568 23.7568 0 0 0 10.24 16.9984 21.2992 21.2992 0 0 0 24.6784 0 22.7328 22.7328 0 0 0 10.24-23.4496zM341.504 660.48a71.2192 71.2192 0 0 1-71.3728-71.68c-0.3584-37.4272 31.4368-72.3968 71.2704-70.9632a71.2192 71.2192 0 1 1 0 142.3872z m133.12-133.12a71.68 71.68 0 1 1 71.68-71.3728A71.0656 71.0656 0 0 1 474.7264 527.36zM724.5824 682.24a22.3744 22.3744 0 0 1-22.1696 22.784 23.0912 23.0912 0 0 1-22.8864-22.9376 22.5792 22.5792 0 0 1 22.8352-22.1696 21.4528 21.4528 0 0 1 22.2208 22.3232z" fill="#FFFFFF"></path></svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M898 178.7H665.3c4.3-9.8 6.7-20.6 6.7-32 0-44-36-80-80-80H432c-44 0-80 36-80 80 0 11.4 2.4 22.2 6.7 32H126c-13.2 0-24 10.8-24 24s10.8 24 24 24h772c13.2 0 24-10.8 24-24s-10.8-24-24-24zM812 280.7H212c-13.3 0-24 10.7-24 24v530c0 68.4 55.6 124 124 124h400c68.4 0 124-55.6 124-124v-530c0-13.2-10.7-24-24-24z m-416 461c0 18.1-14.9 33-33 33h-2c-18.2 0-33-14.8-33-33v-334c0-18.2 14.8-33 33-33h2c18.1 0 33 14.8 33 33v334z m150 0c0 18.1-14.9 33-33 33h-2c-18.2 0-33-14.8-33-33v-334c0-18.2 14.8-33 33-33h2c18.1 0 33 14.8 33 33v334z m150 0c0 18.1-14.9 33-33 33h-2c-18.2 0-33-14.8-33-33v-334c0-18.2 14.8-33 33-33h2c18.1 0 33 14.8 33 33v334z"></path></svg>
|
Before Width: | Height: | Size: 931 B |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M341.333333 640a42.666667 42.666667 0 0 1-42.666666 42.666667H256a170.666667 170.666667 0 0 1-40.277333-336.554667 298.709333 298.709333 0 0 1 570.154666-81.408A213.333333 213.333333 0 0 1 725.333333 682.666667a42.666667 42.666667 0 0 1 0.042667-85.333334 128 128 0 0 0 36.394667-250.794666l-38.144-11.264-15.914667-36.437334a213.376 213.376 0 0 0-407.296 58.026667l-7.381333 58.368-57.173334 13.824A85.418667 85.418667 0 0 0 256 597.333333h42.666667a42.666667 42.666667 0 0 1 42.666666 42.666667z m321.706667 87.338667a42.666667 42.666667 0 0 1 0 60.330666l-120.917333 120.832c-16.682667 16.64-43.690667 16.64-60.373334 0l-120.917333-120.832a42.666667 42.666667 0 0 1 60.330667-60.330666L469.333333 775.509333V426.666667a42.666667 42.666667 0 0 1 85.333334 0v348.714666l48.042666-48.042666a42.666667 42.666667 0 0 1 60.330667 0z" fill="#333333"></path></svg>
|
Before Width: | Height: | Size: 971 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" fill="#0056ff"><path d="M896 672c-17.066667 0-32 14.933333-32 32v128c0 6.4-4.266667 10.666667-10.666667 10.666667H170.666667c-6.4 0-10.666667-4.266667-10.666667-10.666667v-128c0-17.066667-14.933333-32-32-32s-32 14.933333-32 32v128c0 40.533333 34.133333 74.666667 74.666667 74.666667h682.666666c40.533333 0 74.666667-34.133333 74.666667-74.666667v-128c0-17.066667-14.933333-32-32-32z"></path><path d="M488.533333 727.466667c6.4 6.4 14.933333 8.533333 23.466667 8.533333s17.066667-2.133333 23.466667-8.533333l213.333333-213.333334c12.8-12.8 12.8-32 0-44.8-12.8-12.8-32-12.8-44.8 0l-157.866667 157.866667V170.666667c0-17.066667-14.933333-32-32-32s-34.133333 14.933333-34.133333 32v456.533333L322.133333 469.333333c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8l211.2 213.333334z"></path></svg>
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M875.3 217.5c-49.9-50-116.5-77.5-187.5-77.5-33.8 0-66.7 6.3-97.8 18.7-12.3 4.9-18.3 18.9-13.4 31.2s18.9 18.3 31.2 13.4c25.5-10.1 52.4-15.3 80.1-15.3 119.6 0 217 97.4 217 217 0 58-22.6 112.6-63.7 153.6l-0.1 0.1-283.4 285.8c-12.2 12.4-28.6 19.2-46 19.2s-33.7-6.8-45.9-19.2L182.6 558.7l-0.1-0.1c-41.1-41-63.7-95.6-63.7-153.6 0-119.6 97.3-217 216.9-217 119.9 0 217.5 97.6 217.5 217.5 0 13.3 10.7 24 24 24s24-10.7 24-24c0-70.9-27.6-137.6-77.8-187.8-50.1-50.1-116.7-77.7-187.6-77.7h-0.1-0.1c-70.9 0-137.5 27.5-187.4 77.5C98.3 267.4 70.8 334 70.8 405c0 70.9 27.6 137.5 77.8 187.5l283.2 285.8c21.3 21.5 49.8 33.4 80 33.4 30.3 0 58.7-11.9 80-33.4L875 592.5c50.1-50.1 77.8-116.7 77.8-187.5 0-71-27.6-137.6-77.5-187.5z"></path></svg>
|
Before Width: | Height: | Size: 969 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M875.3 217.5c-49.9-50-116.5-77.5-187.5-77.5-55.4 0-108.5 16.9-153.5 49-13.5 9.6-31.6 9.6-45 0-45-32-98.1-49-153.5-49-71 0-137.6 27.5-187.5 77.5S70.8 334 70.8 405c0 70.9 27.6 137.5 77.8 187.5l283.2 285.8c21.3 21.5 49.8 33.4 80 33.4s58.7-11.9 80-33.4L875 592.5c50.1-50.1 77.8-116.7 77.8-187.5 0-71-27.6-137.6-77.5-187.5z" fill="#E5404F"></path></svg>
|
Before Width: | Height: | Size: 595 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M896 117.333333A32 32 0 0 1 928 149.333333v618.666667a32 32 0 0 1-32 32H654.272l-117.589333 118.528a32 32 0 0 1-42.794667 2.389333l-2.453333-2.197333-119.786667-118.72H128a32 32 0 0 1-31.850667-28.928L96 768V149.333333A32 32 0 0 1 128 117.333333zM544 597.333333h-64v64h64v-64z m0-330.666666h-64v256h64v-256z" fill="#111111"></path></svg>
|
Before Width: | Height: | Size: 627 B |
Before Width: | Height: | Size: 18 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#0056ff"></path></svg>
|
Before Width: | Height: | Size: 930 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M511.542857 14.057143C228.914286 13.942857 0 242.742857 0 525.142857 0 748.457143 143.2 938.285714 342.628571 1008c26.857143 6.742857 22.742857-12.342857 22.742858-25.371429v-88.571428c-155.085714 18.171429-161.371429-84.457143-171.771429-101.6C172.571429 756.571429 122.857143 747.428571 137.714286 730.285714c35.314286-18.171429 71.314286 4.571429 113.028571 66.171429 30.171429 44.685714 89.028571 37.142857 118.857143 29.714286 6.514286-26.857143 20.457143-50.857143 39.657143-69.485715-160.685714-28.8-227.657143-126.857143-227.657143-243.428571 0-56.571429 18.628571-108.571429 55.2-150.514286-23.314286-69.142857 2.171429-128.342857 5.6-137.142857 66.4-5.942857 135.428571 47.542857 140.8 51.771429 37.714286-10.171429 80.8-15.542857 129.028571-15.542858 48.457143 0 91.657143 5.6 129.714286 15.885715 12.914286-9.828571 76.914286-55.771429 138.628572-50.171429 3.314286 8.8 28.228571 66.628571 6.285714 134.857143 37.028571 42.057143 55.885714 94.514286 55.885714 151.2 0 116.8-67.428571 214.971429-228.571428 243.314286a145.714286 145.714286 0 0 1 43.542857 104v128.571428c0.914286 10.285714 0 20.457143 17.142857 20.457143 202.4-68.228571 348.114286-259.428571 348.114286-484.685714 0-282.514286-229.028571-511.2-511.428572-511.2z" fill="#0056ff"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M999.936 509.44l-449.024-389.12c-9.216-11.264-23.552-18.432-38.912-18.432s-29.696 7.168-38.912 18.432l-449.024 389.12C9.728 518.656 0 534.528 0 552.96c0 26.624 19.968 48.128 46.08 50.688v0.512h81.92v317.44c0 28.16 23.04 51.2 51.2 51.2h204.8c28.16 0 51.2-23.04 51.2-51.2v-209.92c0-28.16 23.04-51.2 51.2-51.2h51.2c28.16 0 51.2 23.04 51.2 51.2v209.92c0 28.16 23.04 51.2 51.2 51.2h204.8c28.16 0 51.2-23.04 51.2-51.2v-317.44h76.8c28.16 0 51.2-23.04 51.2-51.2 0-18.432-9.728-34.304-24.064-43.52z" fill="white"></path></svg>
|
Before Width: | Height: | Size: 807 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1029 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.9765625" height="200"><path d="M2.56 2.553616h1021.446384v1021.446384H2.56z" fill="#FFFFFF"></path><path d="M0.006384 817.157107c17.875312-25.025436 54.647382-69.458354 117.466334-102.144638 49.029426-25.53616 93.973067-33.197007 122.573566-35.750624-31.664838-112.359102 13.789526-230.33616 107.251871-286.004987 76.608479-45.454364 175.178055-43.411471 255.361596 5.107232 14.300249-31.154115 70.990524-145.045387 204.289276-204.289277 94.994514-42.390025 181.306733-35.750623 219.610973-30.643392V5.107232c-342.184539-1.53217-684.369077-3.575062-1026.553616-5.107232v817.157107z" fill="#FF6600"></path></svg>
|
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 29 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M170.666667 0h682.666666c93.866667 0 170.666667 76.8 170.666667 170.666667v682.666666c0 93.866667-76.8 170.666667-170.666667 170.666667h-682.666666c-93.866667 0-170.666667-76.8-170.666667-170.666667V170.666667c0-93.866667 76.8-170.666667 170.666667-170.666667z" fill="#FFD251"></path><path d="M742.4 618.666667c-8.533333 34.133333-17.066667 59.733333-29.866667 72.533333-4.266667 4.266667-8.533333 12.8-12.8 17.066667l-17.066666 17.066666c-17.066667 12.8-38.4 25.6-68.266667 34.133334-42.666667 8.533333-81.066667 12.8-102.4 12.8s-59.733333 0-102.4-12.8c-29.866667-8.533333-55.466667-21.333333-68.266667-34.133334-4.266667-4.266667-12.8-8.533333-17.066666-17.066666-4.266667-4.266667-12.8-12.8-12.8-17.066667-12.8-17.066667-21.333333-42.666667-29.866667-72.533333-8.533333-42.666667-12.8-85.333333-12.8-106.666667s0-64 12.8-106.666667c8.533333-34.133333 17.066667-59.733333 29.866667-72.533333 4.266667-4.266667 8.533333-12.8 12.8-17.066667l17.066666-17.066666c17.066667-12.8 38.4-25.6 68.266667-34.133334 42.666667-8.533333 81.066667-12.8 102.4-12.8s59.733333 0 102.4 12.8c29.866667 8.533333 55.466667 21.333333 68.266667 34.133334 4.266667 4.266667 12.8 8.533333 17.066666 17.066666 4.266667 4.266667 12.8 12.8 12.8 17.066667 12.8 17.066667 21.333333 42.666667 29.866667 72.533333 8.533333 42.666667 8.533333 85.333333 8.533333 106.666667 4.266667 21.333333 4.266667 64-8.533333 106.666667z m-145.066667-110.933334c-12.8 0-38.4-8.533333-72.533333-29.866666-38.4-21.333333-72.533333-34.133333-93.866667-34.133334-46.933333-4.266667-89.6 25.6-132.266666 85.333334l46.933333 29.866666c25.6-29.866667 46.933333-46.933333 72.533333-46.933333 12.8 0 29.866667 4.266667 51.2 17.066667l42.666667 21.333333c25.6 12.8 51.2 21.333333 68.266667 21.333333h4.266666c46.933333 0 93.866667-29.866667 132.266667-85.333333l-46.933333-29.866667c-21.333333 34.133333-46.933333 51.2-72.533334 51.2z m256 4.266667c0-132.266667-42.666667-204.8-98.133333-256-68.266667-59.733333-145.066667-85.333333-243.2-85.333333-93.866667 0-174.933333 25.6-243.2 85.333333-55.466667 51.2-98.133333 123.733333-98.133333 256s42.666667 204.8 98.133333 256c68.266667 59.733333 145.066667 85.333333 243.2 85.333333 93.866667 0 174.933333-21.333333 243.2-85.333333 51.2-51.2 98.133333-128 98.133333-256z" fill="#585858"></path></svg>
|
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 24 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 960c-246.4 0-448-201.6-448-448s201.6-448 448-448 448 201.6 448 448-201.6 448-448 448z" fill="#C71D23"></path><path d="M721.664 467.968h-235.52a22.272 22.272 0 0 0-20.736 20.736v51.776c0 10.368 10.368 20.736 20.736 20.736H628.48c10.368 0 20.736 10.304 20.736 20.672v10.368c0 33.664-28.48 62.08-62.144 62.08H392.896a22.272 22.272 0 0 1-20.672-20.672V436.928c0-33.664 28.48-62.08 62.08-62.08h287.36a22.272 22.272 0 0 0 20.736-20.736v-51.84a22.272 22.272 0 0 0-20.736-20.672h-287.36A152.96 152.96 0 0 0 281.6 434.368v287.36c0 10.304 10.368 20.672 20.736 20.672h302.848c75.072 0 137.216-62.08 137.216-137.216v-116.48a22.272 22.272 0 0 0-20.736-20.736z" fill="#FFFFFF"></path></svg>
|
Before Width: | Height: | Size: 971 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 85.333333C276.266667 85.333333 85.333333 276.266667 85.333333 512a426.410667 426.410667 0 0 0 291.754667 404.821333c21.333333 3.712 29.312-9.088 29.312-20.309333 0-10.112-0.554667-43.690667-0.554667-79.445333-107.178667 19.754667-134.912-26.112-143.445333-50.133334-4.821333-12.288-25.6-50.133333-43.733333-60.288-14.933333-7.978667-36.266667-27.733333-0.554667-28.245333 33.621333-0.554667 57.6 30.933333 65.621333 43.733333 38.4 64.512 99.754667 46.378667 124.245334 35.2 3.754667-27.733333 14.933333-46.378667 27.221333-57.045333-94.933333-10.666667-194.133333-47.488-194.133333-210.688 0-46.421333 16.512-84.778667 43.733333-114.688-4.266667-10.666667-19.2-54.4 4.266667-113.066667 0 0 35.712-11.178667 117.333333 43.776a395.946667 395.946667 0 0 1 106.666667-14.421333c36.266667 0 72.533333 4.778667 106.666666 14.378667 81.578667-55.466667 117.333333-43.690667 117.333334-43.690667 23.466667 58.666667 8.533333 102.4 4.266666 113.066667 27.178667 29.866667 43.733333 67.712 43.733334 114.645333 0 163.754667-99.712 200.021333-194.645334 210.688 15.445333 13.312 28.8 38.912 28.8 78.933333 0 57.045333-0.554667 102.912-0.554666 117.333334 0 11.178667 8.021333 24.490667 29.354666 20.224A427.349333 427.349333 0 0 0 938.666667 512c0-235.733333-190.933333-426.666667-426.666667-426.666667z" fill="#000000"></path></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 64a448 448 0 1 1 0 896A448 448 0 0 1 512 64z" fill="#3DAB53"></path><path d="M508.032 598.912c-47.744 0-87.808-30.784-89.344-89.344-3.072-53.888 44.672-90.88 90.88-92.416 52.352-1.536 84.736 58.56 84.736 58.56l135.552-49.28S674.368 281.6 521.856 281.6C380.16 283.136 281.6 380.16 281.6 512.64c0 117.12 92.416 235.712 235.648 229.504 158.72-6.144 212.608-144.768 212.608-144.768l-140.16-46.208c1.536 1.536-27.776 47.744-81.664 47.744" fill="#FFFFFF"></path></svg>
|
Before Width: | Height: | Size: 757 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.268258 64.433103c-247.183323 0-447.569968 200.380501-447.569968 447.563825 0 247.189467 200.385621 447.570992 447.569968 447.570992s447.569968-200.380501 447.569968-447.569968c0-247.184347-200.386645-447.564849-447.569968-447.564849z m252.85872 584.692787c-18.997168 16.287968-43.668709-53.628042-47.2134-42.875198-8.642616 26.161294-12.695154 43.646184-38.148944 72.127602-1.35972 1.521494 29.43056 12.647032 38.148944 36.396051 8.346713 22.756875 24.596797 58.811973-81.725503 70.125906-62.389428 6.635801-107.471099-33.244533-111.964932-32.85648-8.325212 0.734126-4.618747 0-13.568528 0-7.321804 0-7.807126 0.534468-14.69685 0-1.899307-0.140272-22.632985 32.85648-115.364231 32.85648-71.878798 0-90.48177-45.243445-76.032701-70.125906 14.464428-24.877342 38.579999-32.122354 35.176604-36.06636-16.73643-19.39546-28.287904-40.1404-35.176604-58.882621-1.705793-4.666869-3.135137-9.209848-4.262434-13.574672-2.611931-10.008479-22.627866 58.76385-44.111028 42.875198-21.483162-15.883533-19.567472-56.309597-5.659014-95.003248 14.033372-39.006959 49.37687-76.562049 49.771065-84.854496 1.412962-30.849665-3.044011-35.975235 0-44.078263 6.780169-18.149391 15.034732-11.190043 15.034733-20.609788 0-118.64476 88.172909-214.829571 196.933079-214.829571 108.755051 0 196.928984 96.184811 196.928984 214.829571 0 4.554242 11.815637 0 17.474651 20.609788 1.165181 4.256291 1.968931 20.684531 0.58771 44.078263-0.658358 11.238165 29.954789 24.914202 45.777913 84.854496 15.845649 59.945414 0 88.215912-7.909514 95.003248z" fill="#68A5E1"></path></svg>
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,63 +0,0 @@
|
||||
function copyToClipboard(content) {
|
||||
|
||||
// firefox 下必须为用户同步调用,不可以使用异步调用隔开
|
||||
// refer: http://www.caotama.com/43769.html
|
||||
|
||||
function oldMethod() {
|
||||
var aux = document.createElement('input');
|
||||
aux.setAttribute('value', content);
|
||||
// 在 firefox for windows 93.0 版本下测试,添加这一行会无法复制到剪切板
|
||||
// aux.style.display = "none";
|
||||
document.body.appendChild(aux);
|
||||
aux.select();
|
||||
aux.focus();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(aux);
|
||||
|
||||
console.log("isSuccess", isSuccess);
|
||||
}
|
||||
if (navigator.clipboard) {
|
||||
navigator.permissions.query({ name: "clipboard-write" }).then(result => {
|
||||
if (result.state == "granted") {
|
||||
navigator.clipboard.writeText(content)
|
||||
.then(function () {
|
||||
// 复制成功
|
||||
console.log("API复制成功");
|
||||
}, function () {
|
||||
// 使用API复制失败
|
||||
console.log("API复制失败");
|
||||
oldMethod();
|
||||
})
|
||||
} else {
|
||||
console.log("支持API,但没有权限,使用旧方法复制");
|
||||
oldMethod();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("不支持API,使用旧方法复制");
|
||||
oldMethod();
|
||||
}
|
||||
}
|
||||
function showTip(e, text) {
|
||||
var $i = $("<span>").text(text);
|
||||
$("body").append($i);
|
||||
var x = e.pageX - $i.outerWidth() / 2,
|
||||
y = e.pageY - $i.outerHeight() - 1;
|
||||
$i.css({
|
||||
"position": "absolute",
|
||||
"z-index": "10000",
|
||||
"top": y,
|
||||
"left": x,
|
||||
"color": "red",
|
||||
"font-size": "14px",
|
||||
"font-weight": "bold",
|
||||
});
|
||||
$i.animate({
|
||||
"top": y - 60,
|
||||
"left": x,
|
||||
"opacity": "0"
|
||||
}, 600, function () {
|
||||
$i.remove();
|
||||
});
|
||||
e.stopPropagation();
|
||||
}
|
@@ -1,197 +0,0 @@
|
||||
// 表单验证基本函数
|
||||
function getValidateUtils() {
|
||||
var validateUtils = {
|
||||
|
||||
validateValue: "",
|
||||
result: true,
|
||||
msg: [],
|
||||
|
||||
setValue: function (validateValue) {
|
||||
this.validateValue = validateValue;
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为空
|
||||
notNull: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (value === null || value === undefined) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为空 或者为空字符串
|
||||
notEmpty: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (value === null || value === undefined || value === "") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为空 或者为空字符串 或者 trim() 为空字符串
|
||||
notEmptyAfterTrim: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (value === null || value === undefined || value === "" || value.trim() === "") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证字符串长度
|
||||
length: function (min, max, notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (value.length < min || value.length > max) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否包含特殊字符
|
||||
specialChar: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
let reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
|
||||
if (reg.test(value)) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 不是字符串
|
||||
notString: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "string") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 不是数字
|
||||
notNumber: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "number" || isNaN(value)) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 不是字符串类型的数字
|
||||
notStringNumber: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if ((typeof value !== "string" && typeof value !== "number") || isNaN(value)) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为整数
|
||||
notInteger: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
// parseInt(value) === value
|
||||
if (typeof value !== "number" || isNaN(value) || value % 1 !== 0) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为字符串类型的整数
|
||||
notStringInteger: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "string" || isNaN(value) || value % 1 !== 0) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为正整数
|
||||
notPositiveInteger: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if ((typeof value !== "string" && typeof value !== "number") || isNaN(value) || value % 1 !== 0 || value <= 0) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 不是数组
|
||||
notArray: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "object" || !Array.isArray(value)) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 不是对象
|
||||
notObject: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "object") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为布尔值
|
||||
notBoolean: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "boolean") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为函数
|
||||
notFunction: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "function") {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为日期
|
||||
notDate: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "object" || !(value instanceof Date)) {
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 验证是否为正则表达式
|
||||
notRegExp: function (notValidMsg) {
|
||||
let value = this.validateValue;
|
||||
if (typeof value !== "object" || !(value instanceof RegExp)) {
|
||||
|
||||
this.result = false;
|
||||
this.msg.push(notValidMsg);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
// 返回结果
|
||||
isValid: function () {
|
||||
// console.log("验证内容", this.validateValue, "验证结果", this.result, "错误信息", this.msg);
|
||||
return {
|
||||
result: this.result,
|
||||
msg: this.msg.join(";")
|
||||
}
|
||||
},
|
||||
};
|
||||
return validateUtils;
|
||||
}
|
@@ -1,91 +0,0 @@
|
||||
function getNetdiskShareDetails(shareText) {
|
||||
var result = {
|
||||
success: false,
|
||||
url: null,
|
||||
pwd: "",
|
||||
platform: null,
|
||||
origin: shareText
|
||||
};
|
||||
try {
|
||||
result.url = shareText.match(/https?:\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g)[0];
|
||||
var pwdRegExpResult = shareText.match(/[提取码|密码][:|:][ ]*([^ \n]*)[ |\n]?/);
|
||||
// console.log(shareText, pwdRegExpResult);
|
||||
// console.log("--------")
|
||||
// return;
|
||||
result.pwd = pwdRegExpResult && pwdRegExpResult.length > 1 ? pwdRegExpResult[1] : "";
|
||||
result.platform =
|
||||
result.url.indexOf("pan.baidu.com") > -1 ? { display: "百度网盘", name: "BAIDU_NETDISK" }
|
||||
: (result.url.indexOf("aliyundrive.com") > -1 ? { display: "阿里云盘", name: "ALIYUN_DRIVE" }
|
||||
: (result.url.indexOf("feishu.cn") > -1 ? { display: "飞书云文档", name: "FEISHU_DRIVE" }
|
||||
: (result.url.indexOf("lanzoul.com") > -1 ? { display: "蓝奏云", name: "LANZOUYUN" }
|
||||
: (result.url.indexOf("quqi.avyeld.com") > -1 ? { display: "曲奇云盘", name: "QUQIYUN" }
|
||||
: { display: "其他", name: "UNKNOWN_DRIVE" }
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
result.success = true;
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
// console.log(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// // 百度网盘(手机版)有密码
|
||||
// getNetdiskShareDetails(`--来自百度网盘超级会员V3的分享
|
||||
// hi,这是我用百度网盘分享的内容~复制这段内容打开「百度网盘」APP即可获取
|
||||
// 链接:https://pan.baidu.com/s/1cxv2Rw5WgXrnE-kfyfkpxA?pwd=k2hk
|
||||
// 提取码:k2hk`);
|
||||
|
||||
// // 百度网盘(电脑版)有密码
|
||||
// getNetdiskShareDetails(`链接:https://pan.baidu.com/s/1YBeqYwrka7Z9G0H0q0GO_w?pwd=60va
|
||||
// 提取码:60va
|
||||
// --来自百度网盘超级会员V3的分享`);
|
||||
|
||||
// // 百度网盘(PC端)有密码
|
||||
// getNetdiskShareDetails(`链接:https://pan.baidu.com/s/1YBeqYwrka7Z9G0H0q0GO_w?pwd=60va
|
||||
// 提取码:60va
|
||||
// 复制这段内容后打开百度网盘手机App,操作更方便哦`);
|
||||
|
||||
// // 百度网盘无密码
|
||||
// console.log("👇无提取码");
|
||||
// getNetdiskShareDetails(`链接:https://pan.baidu.com/s/1YBeqYwrka7Z9G0H0q0GO_w?pwd=60va`);
|
||||
|
||||
// // 阿里云盘有密码
|
||||
// getNetdiskShareDetails(`「实战Nginx.pdf」https://www.aliyundrive.com/s/pquxbpPfj2u 提取码: en86
|
||||
// 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。`);
|
||||
|
||||
// // 阿里云盘无密码
|
||||
// console.log("👇无提取码");
|
||||
// getNetdiskShareDetails(`「ZenTaoPMS.16.4.win64.exe」https://www.aliyundrive.com/s/aZLhoqNFyiv
|
||||
// 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。`);
|
||||
|
||||
// // 飞书(仅链接)
|
||||
// console.log("👇无提取码");
|
||||
// getNetdiskShareDetails(`https://x7xrycxzti.feishu.cn/file/boxcnVzbBjAwqxCePIHgoOcECto`);
|
||||
|
||||
// // 飞书(带密码)
|
||||
// getNetdiskShareDetails(`飞书链接:https://x7xrycxzti.feishu.cn/file/boxcnVzbBjAwqxCePIHgoOcECto 密码:w2z9`);
|
||||
|
||||
// // 蓝奏云(仅链接)
|
||||
// console.log("👇无提取码");
|
||||
// getNetdiskShareDetails(`https://zhangxiaodi.lanzoul.com/iN86f03zh5ab`);
|
||||
|
||||
// // 蓝奏云(带密码)
|
||||
// getNetdiskShareDetails(`下载:https://zhangxiaodi.lanzoul.com/iN86f03zh5ab 密码:e0c0`);
|
||||
|
||||
// // 曲奇云盘(仅链接)
|
||||
// console.log("👇无提取码");
|
||||
// getNetdiskShareDetails(`我在曲奇云盘分享了文件 链接: https://quqi.avyeld.com/s/3336039/6QDmYteBw9kUGORN`);
|
||||
|
||||
// // 曲奇云盘(带密码)
|
||||
// getNetdiskShareDetails(`我在曲奇云盘分享了文件 链接: https://quqi.avyeld.com/s/3336039/6QDmYteBw9kUGORN 查阅码: 9kfs`);
|
||||
|
||||
// // 其他情况
|
||||
// console.log("👇以下是非分享链接");
|
||||
// getNetdiskShareDetails(`非链接`);
|
||||
// getNetdiskShareDetails(`其他的链接https://www.baidu.com/s?wd=60va dsadsads`);
|
||||
// getNetdiskShareDetails(`链接:https://pan.woshijiade.com/s/1YBeqYwrka7Z9G0H0q0GO_w?pwd=60va
|
||||
// 提取码:60va`);
|
@@ -1,78 +0,0 @@
|
||||
// #######################################################
|
||||
// 渲染元素
|
||||
// #######################################################
|
||||
function renderElements(control) {
|
||||
var element = document.createElement(control.tag);
|
||||
|
||||
// 为元素添加属性
|
||||
Object.keys(control.attr).forEach((key, index) => {
|
||||
var value = control.attr[key];
|
||||
element.setAttribute(key, value);
|
||||
});
|
||||
|
||||
// 如果设置了 innerHTML 属性,则添加 innerHTML 属性
|
||||
if (Object.keys(control).indexOf('innerHTML') > -1) {
|
||||
element.innerHTML = control.innerHTML;
|
||||
}
|
||||
|
||||
// 如果有 children 属性,则递归渲染 children
|
||||
if (Object.keys(control).indexOf('children') > -1) {
|
||||
control.children.forEach(function (child) {
|
||||
var childElement = renderElements(child);
|
||||
element.appendChild(childElement);
|
||||
});
|
||||
}
|
||||
|
||||
// select 下拉框直接设置 value 会不生效,需要等其插入 DOM 之后再操作
|
||||
if (control.tag == "select") {
|
||||
setTimeout(function () {
|
||||
element.value = control.attr.value;
|
||||
}, 0);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// #######################################################
|
||||
// 渲染表单对象
|
||||
// #######################################################
|
||||
function renderFormControls({ Controls = [] }) {
|
||||
console.log(Controls);
|
||||
|
||||
var controlList = [];
|
||||
Controls.forEach(function (control) {
|
||||
if (Object.keys(control).indexOf('tag') === -1 || Object.keys(control).indexOf('attr') === -1) {
|
||||
console.log("元素渲染出错");
|
||||
return;
|
||||
}
|
||||
|
||||
// #########################################
|
||||
// 创建元素对象
|
||||
// #########################################
|
||||
var element = renderElements(control);
|
||||
|
||||
// 为 element 添加 "form-elements" class,以便点击提交时可以获取到表单元素,进行批量处理
|
||||
element.classList.add("form-elements");
|
||||
|
||||
// #########################################
|
||||
// 创建元素label
|
||||
// #########################################
|
||||
var label = document.createElement("label");
|
||||
label.innerHTML = control.label.value;
|
||||
label.setAttribute("for", control.attr.id);
|
||||
|
||||
label.innerHTML += `<span style='color: red; user-select: none;'> ${control.required ? '*' : ' '}</span>`
|
||||
|
||||
// 为 element 添加 "form-elements" class,以便点击提交时可以获取到表单元素,进行批量处理
|
||||
label.classList.add("form-labels");
|
||||
|
||||
// #########################################
|
||||
// 返回结果
|
||||
// #########################################
|
||||
controlList.push({
|
||||
label: label,
|
||||
control: element
|
||||
});
|
||||
});
|
||||
// console.log(controlList);
|
||||
return controlList;
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
// 将分类按照层级关系进行处理
|
||||
function generateCategoryHierarchy(data) {
|
||||
var maxRenderLevel = 5; // 最多渲染几层
|
||||
var datas = []; // 用于保存每个层级的数组, [ [ level 1 ], [ level 2 ], ... ]
|
||||
for (let i = 0; i < maxRenderLevel; i++)
|
||||
datas.push([]);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const cate = data[i];
|
||||
if (!cate) continue; // 分类对象为空
|
||||
if (cate.level > maxRenderLevel) continue; // 分类的层级大于最大渲染层级
|
||||
datas[cate.level - 1].push(cate);
|
||||
|
||||
// 顺便为每个分类添加 children 字段
|
||||
cate.children = [];
|
||||
}
|
||||
|
||||
// 从最低的层级开始渲染,因为最低层级无子分类,所以略过
|
||||
for (let i = maxRenderLevel - 1 - 1; i >= 0; i--) {
|
||||
var parentLevel = i;
|
||||
var childLevel = i + 1;
|
||||
for (let parentIndex = 0; parentIndex < datas[parentLevel].length; parentIndex++) {
|
||||
const parentCategory = datas[parentLevel][parentIndex];
|
||||
for (let childIndex = 0; childIndex < datas[childLevel].length; childIndex++) {
|
||||
const childCategory = datas[childLevel][childIndex];
|
||||
// console.log(parentCategory, childCategory);
|
||||
if (parentCategory.id === childCategory.parentId) {
|
||||
parentCategory.children.push(childCategory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 对元素进行排序
|
||||
function sortChildren(categoryList) {
|
||||
function compare(property) {
|
||||
return function (a, b) {
|
||||
var value1 = a[property];
|
||||
var value2 = b[property];
|
||||
return value1 - value2;
|
||||
}
|
||||
}
|
||||
|
||||
function sortCategoryList(categoryList) {
|
||||
// console.log("categoryList", categoryList);
|
||||
categoryList.sort(compare("order"));
|
||||
for (let i = 0; i < categoryList.length; i++)
|
||||
sortCategoryList(categoryList[i].children);
|
||||
// console.log(Array.isArray(categoryList));
|
||||
}
|
||||
|
||||
sortCategoryList(categoryList);
|
||||
}
|
||||
sortChildren(datas[0]);
|
||||
|
||||
// console.log(datas);
|
||||
// console.log(datas[0]);
|
||||
|
||||
return datas[0];
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
function getParams() {
|
||||
var params = {};
|
||||
var parts = window.location.href.split('#')[0].replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
|
||||
params[key] = decodeURIComponent(value);
|
||||
});
|
||||
return params;
|
||||
}
|
||||
getParams()
|
@@ -1,39 +0,0 @@
|
||||
// 请求头 Content-Type
|
||||
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
|
||||
// 带参数的post请求
|
||||
function postRequest(url, params) {
|
||||
var data = [];
|
||||
for (var key in params) {
|
||||
data.push(key + '=' + encodeURIComponent(params[key]));
|
||||
}
|
||||
return axios({
|
||||
method: 'post',
|
||||
url: url,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: data.join('&'),
|
||||
})
|
||||
}
|
||||
|
||||
// 带参数的get请求
|
||||
function getRequest(url, params) {
|
||||
return axios({
|
||||
method: 'get',
|
||||
url: url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
params: params,
|
||||
})
|
||||
}
|
||||
|
||||
// // 带参数的put请求
|
||||
// function putRequest(url, params) {
|
||||
// return axios({
|
||||
// method: 'put',
|
||||
// url: url,
|
||||
// data: params
|
||||
// })
|
||||
// }
|
@@ -1,69 +0,0 @@
|
||||
// 统一 localStorage 的读取与写入
|
||||
|
||||
var localStorageUtils = {
|
||||
// 检查浏览器 localStorage 是否支持
|
||||
checkLocalStorage: function () {
|
||||
try {
|
||||
localStorage.setItem('test', 'test');
|
||||
localStorage.removeItem('test');
|
||||
return true;
|
||||
} catch (e) {
|
||||
swal("您的浏览器不支持localStorage,请更换浏览器!").then(function () {
|
||||
window.location.href = "/";
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取 localStorage
|
||||
get: function (key) {
|
||||
return localStorage.getItem(key);
|
||||
// return JSON.parse(localStorage.getItem(key));
|
||||
},
|
||||
// 设置 localStorage
|
||||
set: function (key, value) {
|
||||
localStorage.setItem(key, value);
|
||||
// localStorage.setItem(key, JSON.stringify(value));
|
||||
},
|
||||
// 删除 localStorage
|
||||
remove: function (key) {
|
||||
localStorage.removeItem(key);
|
||||
},
|
||||
// 清空 localStorage
|
||||
clear: function () {
|
||||
localStorage.clear();
|
||||
},
|
||||
|
||||
// 用户登录
|
||||
userLogin: function ({ token, is_admin }) {
|
||||
this.set('token', token);
|
||||
this.set('is_admin', is_admin);
|
||||
},
|
||||
|
||||
// 用户退出登录
|
||||
userLogout: function () {
|
||||
this.remove('token');
|
||||
this.remove('is_admin');
|
||||
},
|
||||
|
||||
// 获取用户登录信息 (从浏览器本地缓存读取)
|
||||
getUserLogin: function () {
|
||||
return {
|
||||
token: this.get('token'),
|
||||
is_admin: this.get('is_admin')
|
||||
}
|
||||
},
|
||||
getToken: function () {
|
||||
return this.get('token');
|
||||
},
|
||||
getIsAdmin: function () {
|
||||
return this.get('is_admin') === 'true';
|
||||
},
|
||||
getIsUser: function () {
|
||||
return this.get('is_admin') === 'false';
|
||||
},
|
||||
|
||||
getLoginStatus: function () {
|
||||
return !!this.get('token') && !!this.get('is_admin');
|
||||
},
|
||||
};
|
@@ -1,151 +0,0 @@
|
||||
function renderTable({
|
||||
tableId = "",
|
||||
data,
|
||||
renderTableHead = true,
|
||||
}) {
|
||||
var tbodyHtml = "";
|
||||
var theadHtml = "";
|
||||
if (!data) return null;
|
||||
if (!data.length) return null;
|
||||
|
||||
if (Array.isArray(data[0])) {
|
||||
// 是数组
|
||||
// 如果元素是数组 ["a", "b", "c"],则数组的第一项作为表头
|
||||
|
||||
// 生成表格填充内容
|
||||
for (var i = renderTableHead ? 1 : 0; i < data.length; i++) {
|
||||
tbodyHtml += "<tr>";
|
||||
for (var cell in data[i]) {
|
||||
tbodyHtml += "<td>" + cell + "</td>";
|
||||
}
|
||||
tbodyHtml += "</tr>";
|
||||
}
|
||||
|
||||
// 生成表头内容 (使用第一条数据)
|
||||
if (renderTableHead) {
|
||||
theadHtml += "<tr>";
|
||||
for (var cell in data[0]) {
|
||||
theadHtml += "<th>" + cell + "</th>";
|
||||
}
|
||||
theadHtml += "</tr>";
|
||||
}
|
||||
|
||||
} else if (typeof data === "object") {
|
||||
// 是字典
|
||||
// 如果元素是字典 {a: "a", b: "b"},则字典的key作为表头
|
||||
|
||||
// 生成表格填充内容
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
tbodyHtml += "<tr>";
|
||||
// for (var key in data[0]) {
|
||||
for (var key in data[i]) {
|
||||
tbodyHtml += "<td>" + data[i][key] + "</td>";
|
||||
}
|
||||
tbodyHtml += "</tr>";
|
||||
}
|
||||
|
||||
// 生成表头内容 (以第一条数据的key为准)
|
||||
if (renderTableHead) {
|
||||
theadHtml += "<tr>";
|
||||
for (var key in data[0]) {
|
||||
theadHtml += "<th>" + key + "</th>";
|
||||
}
|
||||
theadHtml += "</tr>";
|
||||
}
|
||||
|
||||
} else if (typeof data === "string") {
|
||||
// 是文字 (一般用于未查到结果,显示提示信息)
|
||||
theadHtml += `<tr><th>${data}</th></tr>`;
|
||||
} else {
|
||||
throw new DOMException("Failed to render table: data is not array, dictionary or string.");
|
||||
}
|
||||
|
||||
// 获取table
|
||||
var table = document.getElementById(tableId);
|
||||
if (!table) {
|
||||
table = document.createElement("table");
|
||||
table.id = tableId;
|
||||
// document.body.appendChild(oldScriptDom);
|
||||
}
|
||||
table.innerHTML = '';
|
||||
|
||||
// 获取tbody & 填充tbody
|
||||
var tbody = document.createElement("tbody");
|
||||
tbody.innerHTML = tbodyHtml;
|
||||
|
||||
if (!!renderTableHead) {
|
||||
var thead = document.createElement("thead");
|
||||
thead.innerHTML = theadHtml;
|
||||
table.appendChild(thead);
|
||||
}
|
||||
|
||||
// 填充table
|
||||
table.appendChild(tbody);
|
||||
return table;
|
||||
}
|
||||
|
||||
// console.clear();
|
||||
|
||||
// var data1 = [
|
||||
// { a: "a1", b: "b1", c: "c1", },
|
||||
// { c: "c2", a: "a2", b: "b2", },
|
||||
// { a: "a3", c: "c3", b: "b3", }
|
||||
// ];
|
||||
|
||||
// var data2 = [
|
||||
// ["a1", "b1", "c1"],
|
||||
// ["a1", "b1", "c1"],
|
||||
// ["a1", "b1", "c1"],
|
||||
// ];
|
||||
|
||||
// var table1 = renderTable({
|
||||
// tableId: "table1",
|
||||
// data: data1,
|
||||
// renderTableHead: true,
|
||||
// });
|
||||
// document.body.appendChild(table1);
|
||||
|
||||
// var table2 = renderTable({
|
||||
// tableId: "table2",
|
||||
// data: data2,
|
||||
// renderTableHead: true,
|
||||
// });
|
||||
// document.body.appendChild(table2);
|
||||
|
||||
// var table3 = renderTable({
|
||||
// tableId: "table3",
|
||||
// data: data1,
|
||||
// renderTableHead: false,
|
||||
// });
|
||||
// document.body.appendChild(table3);
|
||||
|
||||
// var table4 = renderTable({
|
||||
// tableId: "table4",
|
||||
// data: data2,
|
||||
// renderTableHead: false,
|
||||
// });
|
||||
// document.body.appendChild(table4);
|
||||
|
||||
// $('table').css('border', '1px solid red');
|
||||
|
||||
/*
|
||||
表格结构:
|
||||
<table>
|
||||
<thead>
|
||||
<th>
|
||||
<td>表头</td>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>表体</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>表脚</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
*/
|
@@ -1,70 +0,0 @@
|
||||
function search({ tableElementId = "", searchText = "", categoryId = 0 }) {
|
||||
getRequest("/book/search", { bookName: searchText, categoryId: categoryId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data)
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
var mainDivWidth = 80/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
var columnWidth = [23, 17, 30, 10, 20];
|
||||
renderData.push({
|
||||
书名: ` <a target="_blank" href="/book?id=${element.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.bookName}
|
||||
</span>
|
||||
</a>`,
|
||||
分类: ` <a target="_blank" href="/category?id=${element.category.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.category.name}
|
||||
</span>
|
||||
</a>`,
|
||||
简介: `<span class="overflow-omit" style="max-width: ${columnWidth[2] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.description}
|
||||
</span>`,
|
||||
语言: `<span class="overflow-omit" style="max-width: ${columnWidth[3] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.language}
|
||||
</span>`,
|
||||
出版社: `<span class="overflow-omit" style="max-width: ${columnWidth[4] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.publishingHouse}
|
||||
</span>`,
|
||||
})
|
||||
});
|
||||
|
||||
if (renderData.length == 0) {
|
||||
console.log("没有搜索到相关书籍");
|
||||
function htmlEncode(str) {
|
||||
// refer: https://stackoverflow.com/questions/4183801/escape-html-chracters
|
||||
var div = document.createElement('div');
|
||||
var txt = document.createTextNode(str);
|
||||
div.appendChild(txt);
|
||||
return div.innerHTML;
|
||||
}
|
||||
if (searchText && searchText != "") {
|
||||
//
|
||||
renderTable({ data: `没有搜索到与 <span style="color: red;">${htmlEncode(searchText)}</span> 相关的书籍,请换个关键词再试试吧`, tableId: tableElementId, renderTableHead: true });
|
||||
} else if (categoryId && categoryId != 0) {
|
||||
//
|
||||
renderTable({ data: `该分类下暂无电子书`, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
// 渲染后重新获取一次字体
|
||||
if (typeof (fontmin) === "function") {
|
||||
fontmin(getPageText());
|
||||
}
|
||||
});
|
||||
}
|
@@ -1,146 +0,0 @@
|
||||
|
||||
var timeout = null;
|
||||
$('.info').css("display", "none");
|
||||
|
||||
$(document).ready(function () {
|
||||
startCheck();
|
||||
$('#container').css("visibility", "");
|
||||
$(".removeAfterScriptLoaded").remove();
|
||||
});
|
||||
|
||||
function startCheck() {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
|
||||
document.getElementById("checkBtn").value = "检测中";
|
||||
document.getElementById("checkBtn").disabled = "disabled";
|
||||
|
||||
$('.info').html("loading...");
|
||||
$('.info-disabled').html("暂不提供检测");
|
||||
$('.info').css("display", "");
|
||||
|
||||
$(".info-ok").removeClass("info-ok");
|
||||
$(".info-err").removeClass("info-err");
|
||||
|
||||
//执行前等待一段时间,显得更加真实
|
||||
// 给定一个阶梯值,每次增加一个阶梯值同时加上一个随机数
|
||||
var i = 0, timeSpan = 80;
|
||||
setTimeout(checkOnlineStatus, timeSpan * ++i + Math.random() * 600);
|
||||
setTimeout(checkBackendStatus, timeSpan * ++i + Math.random() * 600);
|
||||
setTimeout(checkFrontendStatus, timeSpan * ++i + Math.random() * 600);
|
||||
setTimeout(checkTimeOff, timeSpan * ++i + Math.random() * 600);
|
||||
setTimeout(finishCheck, timeSpan * ++i + Math.random() * 600);
|
||||
}
|
||||
|
||||
function checkOnlineStatus() {
|
||||
var onlineStatus = window.navigator.onLine;
|
||||
$("#onlineStatus").text(onlineStatus ? "已连接" : "您当前未连接互联网");
|
||||
$("#onlineStatus").addClass(onlineStatus ? "info-ok" : "info-err");
|
||||
}
|
||||
|
||||
function checkBackendStatus() {
|
||||
var backendStatus = false;
|
||||
getRequest("/status/get", {})
|
||||
.then(function (response) {
|
||||
console.log("backend response data:", response.data);
|
||||
if (response.data.status === "success") {
|
||||
$("#backendStatus").text("后台连接正常");
|
||||
$("#backendStatus").addClass("info-ok");
|
||||
} else {
|
||||
$("#backendStatus").text("服务器内部异常");
|
||||
$("#backendStatus").addClass("info-err");
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
$("#backendStatus").text("无法连接到服务器");
|
||||
$("#backendStatus").addClass("info-err");
|
||||
});
|
||||
}
|
||||
|
||||
function checkFrontendStatus() {
|
||||
var backendStatus = false;
|
||||
getRequest("../get-frontend-status", {})
|
||||
.then(function (response) {
|
||||
console.log("frontend response data:", response.data);
|
||||
if (response.data.server === "OK") {
|
||||
$("#frontendStatus").text("前台连接正常");
|
||||
$("#frontendStatus").addClass("info-ok");
|
||||
} else {
|
||||
$("#frontendStatus").text("服务器内部异常");
|
||||
$("#frontendStatus").addClass("info-err");
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
$("#frontendStatus").text("无法连接到服务器");
|
||||
$("#frontendStatus").addClass("info-err");
|
||||
});
|
||||
}
|
||||
|
||||
function finishCheck() {
|
||||
document.getElementById("checkBtn").value = "重新检测";
|
||||
document.getElementById("checkBtn").disabled = "";
|
||||
|
||||
if (timeout) clearTimeout(timeout);
|
||||
// timeout = setTimeout(startCheck, 10 * 1000);
|
||||
}
|
||||
|
||||
function checkTimeOff(targetUrl = null) {
|
||||
if (!targetUrl) targetUrl = location.href;
|
||||
|
||||
// refer: https://juejin.cn/post/6844903960705236999
|
||||
var xhr = new window.XMLHttpRequest;
|
||||
xhr.responseType = "document";
|
||||
// 通过get的方式请求当前文件
|
||||
xhr.open("head", targetUrl);
|
||||
xhr.send(null);
|
||||
|
||||
// // 考虑请求花费时间
|
||||
// var requestStartTime = getNowDate(Date.now(), 8);
|
||||
// var requestEndTime;
|
||||
|
||||
// 监听请求状态变化
|
||||
xhr.onreadystatechange = function () {
|
||||
var time = null,
|
||||
curDate = null;
|
||||
if (xhr.readyState === 2) {
|
||||
// 获取响应头里的时间戳
|
||||
time = xhr.getResponseHeader("Date");
|
||||
// console.log("请求结束时本地时间(东八区时间): " + new Date(time).getTime());
|
||||
|
||||
// requestEndTime = Date.now();
|
||||
// console.log("请求开始时本地时间(用于计算时间差): " + requestStartTime);
|
||||
// console.log("请求结束时本地时间(用于计算时间差): " + requestEndTime);
|
||||
// console.log("发送请求到收到响应的时间差: " + (requestEndTime - requestStartTime));
|
||||
// console.log("发送请求到收到响应的时间差/2: " + (requestEndTime - requestStartTime) / 2);
|
||||
|
||||
var timeOff = getTimeOff(new Date(time).getTime());
|
||||
// timeOff = timeOff + (requestEndTime - requestStartTime) / 2;
|
||||
|
||||
var target = document.getElementById("timeOff");
|
||||
if (target) {
|
||||
// 产生随机时间(测试功能用)
|
||||
// timeOff = Math.random() * 10 * 60 * 1000;
|
||||
var spanSeconds = Math.abs(timeOff / 1000);
|
||||
target.innerHTML = "本地时间比服务器" + (timeOff > 0 ? "慢" : "快") + spanSeconds.toFixed(3) + "秒";
|
||||
$(target).addClass((spanSeconds >= 5 * 60) ? "info-err" : "info-ok");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getTimeOff(time) {
|
||||
let serverTime = getNowDate(time, 8); // Head请求,返回服务器当前时间戳
|
||||
let localTime = getNowDate(Date.now(), 8); // 用户本地时间戳
|
||||
|
||||
let timeOff = serverTime - localTime;
|
||||
return timeOff;
|
||||
}
|
||||
|
||||
function getNowDate(localTime, timeZone) {
|
||||
var timezone = timeZone || 8; //目标时区时间,东八区
|
||||
// 本地时间和格林威治的时间差,单位为分钟
|
||||
var offset_GMT = new Date().getTimezoneOffset();
|
||||
// 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
|
||||
var nowDate = localTime;
|
||||
var targetDate = nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000;
|
||||
return targetDate;
|
||||
}
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
/**
|
||||
* 本代码来自: https://bbs.csdn.net/topics/391839640 评论区,有修改
|
||||
*/
|
||||
/*
|
||||
* Crypto-JS v2.5.1
|
||||
* http://code.google.com/p/crypto-js/
|
||||
* (c) 2009-2011 by Jeff Mott. All rights reserved.
|
||||
* http://code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(typeof Crypto=="undefined"||!Crypto.util)&&function(){var e=self.Crypto={},g=e.util={rotl:function(a,b){return a<<b|a>>>32-b},rotr:function(a,b){return a<<32-b|a>>>b},endian:function(a){if(a.constructor==Number)return g.rotl(a,8)&16711935|g.rotl(a,24)&4278255360;for(var b=0;b<a.length;b++)a[b]=g.endian(a[b]);return a},randomBytes:function(a){for(var b=[];a>0;a--)b.push(Math.floor(Math.random()*256));return b},bytesToWords:function(a){for(var b=[],c=0,d=0;c<a.length;c++,d+=8)b[d>>>5]|=a[c]<<24-
|
||||
d%32;return b},wordsToBytes:function(a){for(var b=[],c=0;c<a.length*32;c+=8)b.push(a[c>>>5]>>>24-c%32&255);return b},bytesToHex:function(a){for(var b=[],c=0;c<a.length;c++)b.push((a[c]>>>4).toString(16)),b.push((a[c]&15).toString(16));return b.join("")},hexToBytes:function(a){for(var b=[],c=0;c<a.length;c+=2)b.push(parseInt(a.substr(c,2),16));return b},bytesToBase64:function(a){if(typeof btoa=="function")return btoa(f.bytesToString(a));for(var b=[],c=0;c<a.length;c+=3)for(var d=a[c]<<16|a[c+1]<<8|
|
||||
a[c+2],e=0;e<4;e++)c*8+e*6<=a.length*8?b.push("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>>6*(3-e)&63)):b.push("=");return b.join("")},base64ToBytes:function(a){if(typeof atob=="function")return f.stringToBytes(atob(a));for(var a=a.replace(/[^A-Z0-9+\/]/ig,""),b=[],c=0,d=0;c<a.length;d=++c%4)d!=0&&b.push(("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c-1))&Math.pow(2,-2*d+8)-1)<<d*2|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c))>>>
|
||||
6-d*2);return b}},e=e.charenc={};e.UTF8={stringToBytes:function(a){return f.stringToBytes(unescape(encodeURIComponent(a)))},bytesToString:function(a){return decodeURIComponent(escape(f.bytesToString(a)))}};var f=e.Binary={stringToBytes:function(a){for(var b=[],c=0;c<a.length;c++)b.push(a.charCodeAt(c)&255);return b},bytesToString:function(a){for(var b=[],c=0;c<a.length;c++)b.push(String.fromCharCode(a[c]));return b.join("")}}}();
|
||||
|
||||
/*
|
||||
* sha1File v1.0.1
|
||||
* https://github.com/dwsVad/sha1File
|
||||
* (c) 2014 by Protsenko Vadim. All rights reserved.
|
||||
* https://github.com/dwsVad/sha1File/blob/master/LICENSE
|
||||
*/
|
||||
async function sha1File(settings, progressingCallback)
|
||||
{
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
var hash = [1732584193, -271733879, -1732584194, 271733878, -1009589776];
|
||||
var buffer = 1024 * 16 * 64;
|
||||
var sha1 = function (block, hash)
|
||||
{
|
||||
var words = [];
|
||||
var count_parts = 16;
|
||||
var h0 = hash[0],
|
||||
h1 = hash[1],
|
||||
h2 = hash[2],
|
||||
h3 = hash[3],
|
||||
h4 = hash[4];
|
||||
for(var i = 0; i < block.length; i += count_parts)
|
||||
{
|
||||
var th0 = h0,
|
||||
th1 = h1,
|
||||
th2 = h2,
|
||||
th3 = h3,
|
||||
th4 = h4;
|
||||
for(var j = 0; j < 80; j++)
|
||||
{
|
||||
if(j < count_parts)
|
||||
words[j] = block[i + j] | 0;
|
||||
else
|
||||
{
|
||||
var n = words[j - 3] ^ words[j - 8] ^ words[j - 14] ^ words[j - count_parts];
|
||||
words[j] = (n << 1) | (n >>> 31);
|
||||
}
|
||||
var f,k;
|
||||
if(j < 20)
|
||||
{
|
||||
f = (h1 & h2 | ~h1 & h3);
|
||||
k = 1518500249;
|
||||
}
|
||||
else if(j < 40)
|
||||
{
|
||||
f = (h1 ^ h2 ^ h3);
|
||||
k = 1859775393;
|
||||
}
|
||||
else if(j < 60)
|
||||
{
|
||||
f = (h1 & h2 | h1 & h3 | h2 & h3);
|
||||
k = -1894007588;
|
||||
}
|
||||
else
|
||||
{
|
||||
f = (h1 ^ h2 ^ h3);
|
||||
k = -899497514;
|
||||
}
|
||||
|
||||
var t = ((h0 << 5) | (h0 >>> 27)) +h4 + (words[j] >>> 0) + f + k;
|
||||
h4 = h3;
|
||||
h3 = h2;
|
||||
h2 = (h1 << 30) | (h1 >>> 2);
|
||||
h1 = h0;
|
||||
h0 = t;
|
||||
}
|
||||
h0 = (h0 + th0) | 0;
|
||||
h1 = (h1 + th1) | 0;
|
||||
h2 = (h2 + th2) | 0;
|
||||
h3 = (h3 + th3) | 0;
|
||||
h4 = (h4 + th4) | 0;
|
||||
}
|
||||
return [h0, h1, h2, h3, h4];
|
||||
}
|
||||
|
||||
var run = function(file,inStart,inEnd)
|
||||
{
|
||||
var end = Math.min(inEnd, file.size);
|
||||
var start = inStart;
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function()
|
||||
{
|
||||
file.sha1_progress = (end * 100 / file.size);
|
||||
var event = event || window.event;
|
||||
var result = event.result || event.target.result
|
||||
var block = Crypto.util.bytesToWords( new Uint8Array(result));
|
||||
|
||||
if (end === file.size)
|
||||
{
|
||||
var bTotal, bLeft, bTotalH, bTotalL;
|
||||
bTotal = file.size * 8;
|
||||
bLeft = (end - start) * 8;
|
||||
|
||||
bTotalH = Math.floor(bTotal / 0x100000000);
|
||||
bTotalL = bTotal & 0xFFFFFFFF;
|
||||
|
||||
// Padding
|
||||
block[bLeft >>> 5] |= 0x80 << (24 - bLeft % 32);
|
||||
block[((bLeft + 64 >>> 9) << 4) + 14] = bTotalH;
|
||||
block[((bLeft + 64 >>> 9) << 4) + 15] = bTotalL;
|
||||
|
||||
hash = sha1(block, hash);
|
||||
file.sha1_hash = Crypto.util.bytesToHex(Crypto.util.wordsToBytes(hash));
|
||||
// console.log(file.sha1_hash);
|
||||
resolve(file.sha1_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = sha1(block, hash);
|
||||
start += buffer;
|
||||
end += buffer;
|
||||
run(file,start,end);
|
||||
}
|
||||
progressingCallback(file);
|
||||
// console.log(file.sha1_progress);
|
||||
}
|
||||
var blob = file.slice(start, end);
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}
|
||||
|
||||
var checkApi = function()
|
||||
{
|
||||
if((typeof File == 'undefined'))
|
||||
return false;
|
||||
|
||||
if (!File.prototype.slice) {
|
||||
if(File.prototype.webkitSlice)
|
||||
File.prototype.slice = File.prototype.webkitSlice;
|
||||
else if(File.prototype.mozSlice)
|
||||
File.prototype.slice = File.prototype.mozSlice;
|
||||
}
|
||||
|
||||
if (!window.File || !window.FileReader || !window.FileList || !window.Blob || !File.prototype.slice)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(checkApi())
|
||||
{
|
||||
run(settings,0,buffer);
|
||||
}
|
||||
else
|
||||
// return false;
|
||||
reject("File API is not supported");
|
||||
});
|
||||
return await promise;
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g<p.length;g+=16){var j=o;var h=n;var f=m;var e=l;o=md5_ff(o,n,m,l,p[g+0],7,-680876936);l=md5_ff(l,o,n,m,p[g+1],12,-389564586);m=md5_ff(m,l,o,n,p[g+2],17,606105819);n=md5_ff(n,m,l,o,p[g+3],22,-1044525330);o=md5_ff(o,n,m,l,p[g+4],7,-176418897);l=md5_ff(l,o,n,m,p[g+5],12,1200080426);m=md5_ff(m,l,o,n,p[g+6],17,-1473231341);n=md5_ff(n,m,l,o,p[g+7],22,-45705983);o=md5_ff(o,n,m,l,p[g+8],7,1770035416);l=md5_ff(l,o,n,m,p[g+9],12,-1958414417);m=md5_ff(m,l,o,n,p[g+10],17,-42063);n=md5_ff(n,m,l,o,p[g+11],22,-1990404162);o=md5_ff(o,n,m,l,p[g+12],7,1804603682);l=md5_ff(l,o,n,m,p[g+13],12,-40341101);m=md5_ff(m,l,o,n,p[g+14],17,-1502002290);n=md5_ff(n,m,l,o,p[g+15],22,1236535329);o=md5_gg(o,n,m,l,p[g+1],5,-165796510);l=md5_gg(l,o,n,m,p[g+6],9,-1069501632);m=md5_gg(m,l,o,n,p[g+11],14,643717713);n=md5_gg(n,m,l,o,p[g+0],20,-373897302);o=md5_gg(o,n,m,l,p[g+5],5,-701558691);l=md5_gg(l,o,n,m,p[g+10],9,38016083);m=md5_gg(m,l,o,n,p[g+15],14,-660478335);n=md5_gg(n,m,l,o,p[g+4],20,-405537848);o=md5_gg(o,n,m,l,p[g+9],5,568446438);l=md5_gg(l,o,n,m,p[g+14],9,-1019803690);m=md5_gg(m,l,o,n,p[g+3],14,-187363961);n=md5_gg(n,m,l,o,p[g+8],20,1163531501);o=md5_gg(o,n,m,l,p[g+13],5,-1444681467);l=md5_gg(l,o,n,m,p[g+2],9,-51403784);m=md5_gg(m,l,o,n,p[g+7],14,1735328473);n=md5_gg(n,m,l,o,p[g+12],20,-1926607734);o=md5_hh(o,n,m,l,p[g+5],4,-378558);l=md5_hh(l,o,n,m,p[g+8],11,-2022574463);m=md5_hh(m,l,o,n,p[g+11],16,1839030562);n=md5_hh(n,m,l,o,p[g+14],23,-35309556);o=md5_hh(o,n,m,l,p[g+1],4,-1530992060);l=md5_hh(l,o,n,m,p[g+4],11,1272893353);m=md5_hh(m,l,o,n,p[g+7],16,-155497632);n=md5_hh(n,m,l,o,p[g+10],23,-1094730640);o=md5_hh(o,n,m,l,p[g+13],4,681279174);l=md5_hh(l,o,n,m,p[g+0],11,-358537222);m=md5_hh(m,l,o,n,p[g+3],16,-722521979);n=md5_hh(n,m,l,o,p[g+6],23,76029189);o=md5_hh(o,n,m,l,p[g+9],4,-640364487);l=md5_hh(l,o,n,m,p[g+12],11,-421815835);m=md5_hh(m,l,o,n,p[g+15],16,530742520);n=md5_hh(n,m,l,o,p[g+2],23,-995338651);o=md5_ii(o,n,m,l,p[g+0],6,-198630844);l=md5_ii(l,o,n,m,p[g+7],10,1126891415);m=md5_ii(m,l,o,n,p[g+14],15,-1416354905);n=md5_ii(n,m,l,o,p[g+5],21,-57434055);o=md5_ii(o,n,m,l,p[g+12],6,1700485571);l=md5_ii(l,o,n,m,p[g+3],10,-1894986606);m=md5_ii(m,l,o,n,p[g+10],15,-1051523);n=md5_ii(n,m,l,o,p[g+1],21,-2054922799);o=md5_ii(o,n,m,l,p[g+8],6,1873313359);l=md5_ii(l,o,n,m,p[g+15],10,-30611744);m=md5_ii(m,l,o,n,p[g+6],15,-1560198380);n=md5_ii(n,m,l,o,p[g+13],21,1309151649);o=md5_ii(o,n,m,l,p[g+4],6,-145523070);l=md5_ii(l,o,n,m,p[g+11],10,-1120210379);m=md5_ii(m,l,o,n,p[g+2],15,718787259);n=md5_ii(n,m,l,o,p[g+9],21,-343485551);o=safe_add(o,j);n=safe_add(n,h);m=safe_add(m,f);l=safe_add(l,e)}return Array(o,n,m,l)}function md5_cmn(h,e,d,c,g,f){return safe_add(bit_rol(safe_add(safe_add(e,h),safe_add(c,f)),g),d)}function md5_ff(g,f,k,j,e,i,h){return md5_cmn((f&k)|((~f)&j),g,f,e,i,h)}function md5_gg(g,f,k,j,e,i,h){return md5_cmn((f&j)|(k&(~j)),g,f,e,i,h)}function md5_hh(g,f,k,j,e,i,h){return md5_cmn(f^k^j,g,f,e,i,h)}function md5_ii(g,f,k,j,e,i,h){return md5_cmn(k^(f|(~j)),g,f,e,i,h)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))};
|
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
|
||||
* in FIPS 180-1
|
||||
* Version 2.2 Copyright Paul Johnston 2000 - 2009.
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for details.
|
||||
*/
|
||||
var hexcase=0;var b64pad="";function hex_sha1(a){return rstr2hex(rstr_sha1(str2rstr_utf8(a)))}function hex_hmac_sha1(a,b){return rstr2hex(rstr_hmac_sha1(str2rstr_utf8(a),str2rstr_utf8(b)))}function sha1_vm_test(){return hex_sha1("abc").toLowerCase()=="a9993e364706816aba3e25717850c26c9cd0d89d"}function rstr_sha1(a){return binb2rstr(binb_sha1(rstr2binb(a),a.length*8))}function rstr_hmac_sha1(c,f){var e=rstr2binb(c);if(e.length>16){e=binb_sha1(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binb_sha1(a.concat(rstr2binb(f)),512+f.length*8);return binb2rstr(binb_sha1(d.concat(g),512+160))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binb(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(24-c%32)}return a}function binb2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(24-c%32))&255)}return a}function binb_sha1(v,o){v[o>>5]|=128<<(24-o%32);v[((o+64>>9)<<4)+15]=o;var y=Array(80);var u=1732584193;var s=-271733879;var r=-1732584194;var q=271733878;var p=-1009589776;for(var l=0;l<v.length;l+=16){var n=u;var m=s;var k=r;var h=q;var f=p;for(var g=0;g<80;g++){if(g<16){y[g]=v[l+g]}else{y[g]=bit_rol(y[g-3]^y[g-8]^y[g-14]^y[g-16],1)}var z=safe_add(safe_add(bit_rol(u,5),sha1_ft(g,s,r,q)),safe_add(safe_add(p,y[g]),sha1_kt(g)));p=q;q=r;r=bit_rol(s,30);s=u;u=z}u=safe_add(u,n);s=safe_add(s,m);r=safe_add(r,k);q=safe_add(q,h);p=safe_add(p,f)}return Array(u,s,r,q,p)}function sha1_ft(e,a,g,f){if(e<20){return(a&g)|((~a)&f)}if(e<40){return a^g^f}if(e<60){return(a&g)|(a&f)|(g&f)}return a^g^f}function sha1_kt(a){return(a<20)?1518500249:(a<40)?1859775393:(a<60)?-1894007588:-899497514}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))};
|
@@ -1,220 +0,0 @@
|
||||
/* 字体引入 */
|
||||
/* @font-face {
|
||||
font-family: HarmonyOS_Sans;
|
||||
src: url("../fonts/HarmonyOS_Sans_SC_Regular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
} */
|
||||
|
||||
/* 全局样式 */
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: bookshelfplusFont;
|
||||
}
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
color: #0056ff;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: solid 1px #9f9f9f;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.navbar {
|
||||
background-color: #2b2b2b;
|
||||
color: #fff;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.navbar-grid {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 10vw 1fr 280px 10vw;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.navbar-grid .grid-item {
|
||||
/* 下面三行为辅助样式 */
|
||||
/* border: white solid 1px;
|
||||
border-top: 0;
|
||||
border-bottom: 0; */
|
||||
}
|
||||
|
||||
.navbar-grid .grid-item a,
|
||||
.navbar-grid .grid-item a:visited {
|
||||
color: #fff;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.navbar-grid .grid-item .active {
|
||||
border-bottom: dotted;
|
||||
}
|
||||
|
||||
.navbar-grid .grid-item * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar > ul {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar > ul > li > a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 标题 siteTitle */
|
||||
.siteTitle h1 {
|
||||
margin-top: 130px;
|
||||
font-size: 36px;
|
||||
transition: 0.3s;
|
||||
cursor: default;
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
}
|
||||
.siteTitle h1:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.siteTitle span {
|
||||
display: inline-block;
|
||||
color: crimson;
|
||||
}
|
||||
|
||||
/* 正文样式 */
|
||||
.main {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
/* max-width: 600px; */
|
||||
max-width: min(90vw, 720px);
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.main > h1 {
|
||||
margin-block-start: 40px;
|
||||
}
|
||||
|
||||
.main p {
|
||||
font-size: 17.5px;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.searchBox #searchInput {
|
||||
/* padding: 0 10px; */
|
||||
width: 300px;
|
||||
width: min(70vw, 280px);
|
||||
height: 30px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
transition: 0.3s;
|
||||
|
||||
border: 1px solid rgb(133, 133, 133);
|
||||
border-radius: 3px;
|
||||
|
||||
padding: 0.1em 0.5em;
|
||||
font-size: 1em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.searchBox #searchInput:focus {
|
||||
padding: 0 18px;
|
||||
height: 35px;
|
||||
width: min(78vw, 300px);
|
||||
}
|
||||
|
||||
.searchBox #searchButton {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
/* width: auto; */
|
||||
|
||||
margin-left: 10px;
|
||||
transition: 0.25s;
|
||||
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.searchBox #searchButton:hover {
|
||||
background-color: rgb(39, 39, 39);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 网站Slogan */
|
||||
.sloganBox .emphasize {
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
color: #3f3f3f;
|
||||
}
|
||||
|
||||
/* 自适应 */
|
||||
@media (max-width: 600px) {
|
||||
.narrowHide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-grid {
|
||||
grid-template-columns: 10vw 1fr 110px 10vw;
|
||||
}
|
||||
|
||||
/* 正文样式 */
|
||||
.main {
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.main p {
|
||||
font-size: initial;
|
||||
/* line-height: initial; */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 300px) {
|
||||
.exnarrowHide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-grid {
|
||||
grid-template-columns: 10vw 1fr 0 10vw;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局样式 */
|
||||
/* 超出内容显示为... */
|
||||
.overflow-omit {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1 +0,0 @@
|
||||
*.ttf
|
@@ -1,90 +0,0 @@
|
||||
'use strict';
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
const bodyParser = require('body-parser');
|
||||
var rename = require('gulp-rename');
|
||||
const fs = require('fs');
|
||||
|
||||
// refer: https://blog.csdn.net/qq_36812165/article/details/108363511 , https://github.com/font-size/node-fontmin-project
|
||||
|
||||
// 设置可用字体
|
||||
const fonts = [
|
||||
{ font: 'HarmonyOS_Sans_SC_Regular', name: 'HarmonyOS Sans' },
|
||||
]
|
||||
|
||||
// 路径是以 app.js 所在路径为准的
|
||||
var destPath = './public/fontmin/'; // 输出路径
|
||||
|
||||
// 转化参数设置
|
||||
router.use(bodyParser.json());
|
||||
|
||||
router.use(bodyParser.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
|
||||
// post 接口
|
||||
router.post('/getfont', function (request, response) {
|
||||
const params = request.body
|
||||
const font = params.font || "HarmonyOS_Sans_SC_Regular"
|
||||
const text = params.text
|
||||
// 如果传递的font字体在后台没有就返回400
|
||||
const item = fonts.find(e => e.font === font)
|
||||
|
||||
var targetFileName = require('crypto').createHash('md5').update(text).digest('hex') + '.ttf';
|
||||
var exists = fs.existsSync(destPath + "/" + targetFileName);
|
||||
if (exists) {
|
||||
response.send({
|
||||
url: '/fontmin/' + targetFileName,
|
||||
font: font,
|
||||
info: "使用已生成字体"
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
if (item && text) {
|
||||
fontmin(font, text, targetFileName, function (e) {
|
||||
if (e === 'done') {
|
||||
// 拼接参数 返回请求
|
||||
let back = {
|
||||
url: '/fontmin/' + targetFileName,
|
||||
font: font,
|
||||
info: "新生成字体"
|
||||
}
|
||||
response.send(back);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
response.status(400);
|
||||
response.send(JSON.stringify({
|
||||
"errMsg": '没有请求的字体文件或没有传递文字'
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
function fontmin(font, text, targetFileName, callback) {
|
||||
const Fontmin = require('fontmin')
|
||||
var srcPath = `./fonts/${font}.ttf`; // 字体源文件
|
||||
var text = text || '';
|
||||
|
||||
// 初始化
|
||||
var fontmin = new Fontmin()
|
||||
.src(srcPath) // 输入配置
|
||||
.use(rename(targetFileName))
|
||||
.use(Fontmin.glyph({ // 字型提取插件
|
||||
text: text // 所需文字
|
||||
}))
|
||||
// .use(Fontmin.ttf2eot()) // eot 转换插件
|
||||
// .use(Fontmin.ttf2woff()) // woff 转换插件
|
||||
// .use(Fontmin.ttf2svg()) // svg 转换插件
|
||||
// .use(Fontmin.css()) // css 生成插件
|
||||
.dest(destPath); // 输出配置
|
||||
|
||||
// 执行
|
||||
fontmin.run(function (err, files, stream) {
|
||||
if (err) // 异常捕捉
|
||||
console.error(err);
|
||||
return callback('done') // 成功
|
||||
});
|
||||
}
|
@@ -1,260 +0,0 @@
|
||||
'use strict';
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
function getPageTitle(title) {
|
||||
return `${title} | ${site.title}`
|
||||
}
|
||||
router.get('/', function (req, res) {
|
||||
res.render('index', {
|
||||
title: site.title,
|
||||
headText: "书栖网",
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/search', function (req, res) {
|
||||
res.render('search', {
|
||||
title: getPageTitle("搜一下"),
|
||||
headText: "搜一下",
|
||||
minfontOnLoad: false,
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/category', function (req, res) {
|
||||
if (req.query.id) {
|
||||
// 分类详情页
|
||||
res.render('category-details', {
|
||||
title: getPageTitle("书籍分类"),
|
||||
headText: "书籍分类",
|
||||
minfontOnLoad: false,
|
||||
});
|
||||
} else {
|
||||
// 分类首页
|
||||
res.render('category', {
|
||||
title: getPageTitle("书籍分类"),
|
||||
headText: "书籍分类",
|
||||
minfontOnLoad: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/book', function (req, res) {
|
||||
res.render('book', {
|
||||
title: getPageTitle("书籍详情"),
|
||||
headText: "书籍详情",
|
||||
minfontOnLoad: false,
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/download', function (req, res) {
|
||||
res.render('download', {
|
||||
title: getPageTitle("下载书籍"),
|
||||
headText: "下载书籍"
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/about', function (req, res) {
|
||||
res.render('about', {
|
||||
title: getPageTitle("关于"),
|
||||
headText: "关于"
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/feedback', function (req, res) {
|
||||
res.render('feedback', {
|
||||
title: getPageTitle("用户反馈"),
|
||||
headText: "用户反馈"
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/login', function (req, res) {
|
||||
res.render('login', {
|
||||
title: getPageTitle("用户登录"),
|
||||
headText: "用户登录"
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/register', function (req, res) {
|
||||
res.render('register', {
|
||||
title: getPageTitle("用户注册"),
|
||||
headText: "用户注册"
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/callback/:platform', function (req, res) {
|
||||
// 第三方登录回调页面
|
||||
res.render('callback', {
|
||||
title: getPageTitle("正在跳转"),
|
||||
platform: req.params.platform
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/dashboard/iframe/:page', function (req, res) {
|
||||
res.render(`dashboard/component/iframe/${req.params.page}`, {
|
||||
pageUrl: (req._parsedUrl.pathname + "/").replace("//", "/"),
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/dashboard/:group/:page/:subpage?', function (req, res) {
|
||||
// baseTemplate 基于哪个html模板渲染页面
|
||||
// pageTemplate 引入这个文件中的页面脚本
|
||||
if (req.params.group === "admin") {
|
||||
var dashboardPage = {
|
||||
"index": {
|
||||
title: "仪表盘",
|
||||
baseTemplate: "index",
|
||||
},
|
||||
"category-manage": {
|
||||
title: "分类管理",
|
||||
baseTemplate: "table",
|
||||
pageTemplate: "CategoryManage",
|
||||
},
|
||||
"book-manage": {
|
||||
title: "书籍管理",
|
||||
baseTemplate: "table",
|
||||
pageTemplate: "BookManage",
|
||||
childPage: {
|
||||
"detail": {
|
||||
title: req.query.id ? "修改书籍" : "添加书籍",
|
||||
baseTemplate: "form",
|
||||
pageTemplate: "BookManage_Detail",
|
||||
},
|
||||
}
|
||||
},
|
||||
"file-manage": {
|
||||
title: "文件管理",
|
||||
baseTemplate: "table",
|
||||
pageTemplate: "FileManage",
|
||||
childPage: {
|
||||
"upload": {
|
||||
title: "上传文件",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "FileManage_Upload",
|
||||
},
|
||||
"detail": {
|
||||
title: "文件详情",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "FileManage_Detail",
|
||||
},
|
||||
"object-manage": {
|
||||
title: "文件对象管理",
|
||||
baseTemplate: "table",
|
||||
pageTemplate: "FileManage_ObjectManage",
|
||||
},
|
||||
"object-detail": {
|
||||
title: "文件对象详情",
|
||||
baseTemplate: "form",
|
||||
pageTemplate: "FileManage_ObjectManage_Detail",
|
||||
},
|
||||
"get-share-url": {
|
||||
title: "粘贴网盘分享链接",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "FileManage_ObjectManage_getShareUrl",
|
||||
},
|
||||
}
|
||||
},
|
||||
"user-manage": {
|
||||
title: "用户管理",
|
||||
baseTemplate: "form",
|
||||
pageTemplate: "UserManage",
|
||||
},
|
||||
"account": {
|
||||
title: "账号设置",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "Account",
|
||||
},
|
||||
"export": {
|
||||
title: "导出数据",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "Export",
|
||||
},
|
||||
"debug": {
|
||||
title: "系统配置",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "Debug",
|
||||
}
|
||||
};
|
||||
var headText = "后台管理";
|
||||
} else if (req.params.group === "user") {
|
||||
var dashboardPage = {
|
||||
"index": {
|
||||
title: "仪表盘",
|
||||
baseTemplate: "index",
|
||||
},
|
||||
// "my-bookshelf": {
|
||||
// title: "我的书架",
|
||||
// baseTemplate: "form",
|
||||
// pageTemplate: "myBookshelf",
|
||||
// },
|
||||
"my-collection": {
|
||||
title: "我的收藏",
|
||||
baseTemplate: "table",
|
||||
pageTemplate: "myCollection",
|
||||
},
|
||||
"my-account": {
|
||||
title: "账号设置",
|
||||
baseTemplate: "blank",
|
||||
pageTemplate: "myAccount",
|
||||
}
|
||||
};
|
||||
var headText = "用户中心";
|
||||
}
|
||||
|
||||
// 如果请求的页面在 dashboardPage 中
|
||||
if (Object.keys(dashboardPage).indexOf(req.params.page) > -1) {
|
||||
// 当前请求的页面
|
||||
var currentPage = dashboardPage[req.params.page];
|
||||
|
||||
// 如果请求的就是主页面,或者当前页没有子页面
|
||||
if (!req.params.subpage) {
|
||||
// 渲染主页面
|
||||
console.log("page", req.params.page, req.params.subpage);
|
||||
res.render(`dashboard/${currentPage.baseTemplate}`, {
|
||||
pageUrl: (req._parsedUrl.pathname + "/").replace("//", "/"),
|
||||
htmlTitle: getPageTitle(headText),
|
||||
title: currentPage.title,
|
||||
pageTemplate: "./" + req.params.group + "/" + currentPage.pageTemplate + ".html",
|
||||
dashboardPage: dashboardPage,
|
||||
group: req.params.group,
|
||||
page: req.params.page,
|
||||
});
|
||||
} else {
|
||||
// 渲染子页面
|
||||
if (!currentPage.childPage || Object.keys(currentPage.childPage).indexOf(req.params.subpage) === -1) {
|
||||
// 请求的子页面不存在,直接返回404
|
||||
throw new Error("404 Not Found");
|
||||
}
|
||||
// 如果当前 page 有 subpage,则渲染子页面
|
||||
var currentSubPage = currentPage.childPage[req.params.subpage];
|
||||
console.log("subpage", req.params.page, req.params.subpage);
|
||||
res.render(`dashboard/${currentSubPage.baseTemplate}`, {
|
||||
pageUrl: (req._parsedUrl.pathname + "/").replace("//", "/"),
|
||||
htmlTitle: getPageTitle(headText),
|
||||
title: currentSubPage.title,
|
||||
pageTemplate: "./" + req.params.group + "/" + currentSubPage.pageTemplate + ".html",
|
||||
dashboardPage: dashboardPage,
|
||||
group: req.params.group,
|
||||
page: req.params.page,
|
||||
subpage: req.params.subpage
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果请求的页面不在 dashboardPage 中,则渲染错误页面
|
||||
throw new Error("404 Not Found");
|
||||
});
|
||||
|
||||
router.get('/status', function (req, res) {
|
||||
res.render('status', {
|
||||
title: getPageTitle("网站状态检测"),
|
||||
headText: "网站状态检测"
|
||||
});
|
||||
});
|
||||
|
||||
// 网站状态检测Api接口
|
||||
router.get('/get-frontend-status', function (req, res) {
|
||||
res.end(JSON.stringify({ "server": "OK" }));
|
||||
});
|
||||
|
||||
module.exports = router;
|
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"NODE_ENV": "development",
|
||||
"api": {
|
||||
"_prefix": "http://bookshelf.plus/api",
|
||||
"prefix": "/api"
|
||||
},
|
||||
"title": "书栖网 (内测中)"
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<div class="main">
|
||||
<h1>关于 · 书栖网</h1>
|
||||
<p>一个完全免费无门槛的计算机类电子书下载网站</p>
|
||||
|
||||
<h2>随便唠唠</h2>
|
||||
<p>本网站是“计算机类电子书”仓库的前身,我们希望能够搭建一个电子书共享平台,供大家在学习中方便搜索以及下载。</p>
|
||||
<p>目前市面上的网站下载有很多门槛,要么是收费,要么是要关注公众号等等。我们要做的就是一个没有任何门槛的下载平台。</p>
|
||||
<p style="font-weight: bold; color: crimson;">我们承诺,这个网站从出生到消失,永不投放广告,永远不会让您付费或者关注公众号来进行下载,甚至也不投放收款二维码,永远是这样。</p>
|
||||
<hr>
|
||||
<p>目前已收集到100GB左右的与计算机相关的书籍(主要是PDF)。但由于人手不足,尽管我们花费了大量时间对其进行归类、筛选、去重等工作,但仅仅将其中的四分之一整理到仓库中供大家下载。所以我们暂时停止了手动整理的重复工作,着手来开发这个网站,以减轻后期我们的重复劳动,也为了能给大家提供更好更方便的下载服务。
|
||||
</p>
|
||||
<p style="font-size: small; color: #c8c8c8;">
|
||||
【备注】在我们建设好网站前,对于希望能够打包下载全部电子书的类似需求我们无暇兼顾,因为文件太多,我们需要花很多精力,甚至分成很多个渠道才能够将其分享出去不被和谐,所以还请大家多多谅解。</p>
|
||||
<hr>
|
||||
<p>目前,我们使用业余时间进行网站开发,所以这个过程很慢,也请您耐心等待。如果您与我们一样,希望为这个项目添砖加瓦,欢迎通过 <u>contact@only4.work</u>
|
||||
与我们取得联系。当然,话说在前面,我们没有能力支付相应酬劳,包括我们自己也是一样,做这个项目不为什么,纯粹的是为爱发电罢了。同时,如果您想加入我们,你需要拥有相应编码能力,以及一些空闲时间。</p>
|
||||
<p>如果你只是单纯的喜欢本项目,或者认同我们的做事风格等等,欢迎去GitHub仓库为我们点个Star,这对我们来说比金钱更能维持我们为爱发电的热情。</p>
|
||||
<hr>
|
||||
<p>也许你会问,为爱发电的话,高昂的运维费用怎么办?这个我没有办法给出肯定的回答,就像目前很多的开源项目作者一样,他们也被这个问题所困扰。但我可以肯定的告诉你,我们会尽一切力量来维持这个网站的运行,从服务器到下载地址,我们会尽量压缩成本。压缩成本意味着尽量使用免费或者低价的云服务,这同时意味着可能会让网站加载变慢甚至少数情况下加载不出来,我们会尽量在这二者之间找到一个平衡点。
|
||||
</p>
|
||||
<p>在网站的后续运营过程中,我们将主要使用网盘分享链接的形式进行分享,但由于网盘分享链接极其容易失效,所以部分情况下我们会为压缩包设置密码。若压缩包有密码,一律为:<u>bookshelf.plus</u></p>
|
||||
<p>但在网站的细节方面,我们绝对会将用户体验放在第一位,该有的通通都给安排上。</p>
|
||||
<hr>
|
||||
<p>最后,还是要说一下版权的问题。我们十分重视版权。我们所整理的电子书全部来自于互联网,其中部分来自于下载站、公众号等。如果其中包含您的作品,且您不希望我们将您的作品作为免费分享出来,请联系<span>contact@only4.work</span>并提供相应证明材料,我们核实后将会第一时间删除。同时,您在本网站上下载的所有电子书文件,仅供学习交流使用,不可二次传播,特别是不可设置扫码关注等门槛二次分享。
|
||||
</p>
|
||||
|
||||
<h2>特别说明</h2>
|
||||
<p>本站电子书由“张小弟之家”整理,出于方便学习之目的,您从本站下载的电子书仅供学习交流使用,如需他用请联系原作者。<a href="https://gitee.com/only4/computer-related-books" target="_blank">查看同步更新Gitee仓库</a></p>
|
||||
<p>由于信息量较大,我们无法做到一一确认相关电子书的权属管理,如本站不慎侵犯了您的权利,请发送邮件至<b>contact@only4.work</b>,来信请注明相关链接以及您的相关证明材料,我们收到邮件后会第一时间与您取得联系并积极处理,多谢理解!</p>
|
||||
</div>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,648 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<!-- 书籍信息部分 -->
|
||||
<style>
|
||||
#container {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 6fr;
|
||||
place-items: center;
|
||||
grid-gap: 1rem;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
#bookImage {
|
||||
/* width: 100%; */
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.group-button>* {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#favorties-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#favorties-button:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.download-link {
|
||||
user-select: none;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
#download-icon {
|
||||
width: 0px;
|
||||
height: 1.2em;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
.download-link:hover #download-icon {
|
||||
width: 1.2em;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
<!-- 文件下载部分 -->
|
||||
<style>
|
||||
.download-container {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
#file-container {
|
||||
width: min(80vw, 650px);
|
||||
margin: 0 auto;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
text-align: left;
|
||||
background-color: #e6e6e6;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
text-align: left;
|
||||
background-color: #dfdfdf;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.file-detail {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-object {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
place-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item {
|
||||
border: solid 2px #c6c6c6;
|
||||
border-radius: 5px;
|
||||
padding: 5px 8px;
|
||||
min-width: 135px;
|
||||
/* max-width: 150px; */
|
||||
width: 80%;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item:hover {
|
||||
/* transform: scale(1.03); */
|
||||
border-color: #5e5e5e;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item>.file-object-item-title {
|
||||
height: 20px;
|
||||
}
|
||||
.file-object>.file-object-item>.file-object-item-title>* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item .outdated-feedback {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.file-object>.file-object-item .outdated-feedback>.outdated-feedback-tip {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: max-content;
|
||||
background: aliceblue;
|
||||
padding: 3px 5px;
|
||||
border: 2px solid grey;
|
||||
border-radius: 4px;
|
||||
z-index: 99999;
|
||||
margin-left: 5px;
|
||||
font-family: initial;
|
||||
}
|
||||
.file-object>.file-object-item .outdated-feedback:hover .outdated-feedback-tip {
|
||||
display: initial;
|
||||
}
|
||||
.file-object>.file-object-item .outdated-feedback img {
|
||||
opacity: 0.5;
|
||||
transition: all 0.18s;
|
||||
}
|
||||
.file-object>.file-object-item .outdated-feedback:hover img {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item>.file-object-item-content {
|
||||
height: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.file-object>.file-object-item>.file-object-item-link {
|
||||
height: 20px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
/* 统一 */
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.size12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.size14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.size16 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.size20 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.overflow-hide {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<!-- <h1><%= headText %></h1> -->
|
||||
<div id="container"></div>
|
||||
<hr style="opacity: 0.3; margin-top: 30px; margin-bottom: 25px;">
|
||||
<div class="download-container">
|
||||
<h3 id="scrollTarget">下载这本书</h3>
|
||||
<div id="file-container"></div>
|
||||
</div>
|
||||
<p style="font-size: 12px;">
|
||||
* 由于直链下载成本很高,为降低运营成本,目前仅提供给登录用户使用。
|
||||
</p>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 点击复制及反馈样式 -->
|
||||
<script src="/assets/javascripts/cssUtils.js"></script>
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
var bookId = Number(requestParams["id"]) ?? "";
|
||||
if (bookId === "") {
|
||||
location.href = "/search";
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 由于多个请求之后都需要调用该方法,为避免多次压缩,使用计数器,等最后一个请求完成后执行一次
|
||||
var requestCount = 2;
|
||||
function doFontmin() {
|
||||
if (--requestCount == 0) {
|
||||
console.log("开始获取字体");
|
||||
fontmin(getPageText());
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 获取书籍信息
|
||||
getRequest("/book/get", { id: bookId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data.description == "") {
|
||||
data.description = "暂无描述";
|
||||
}
|
||||
document.getElementById("container").innerHTML = `
|
||||
<div class="grid">
|
||||
<div style="width: 100%; user-select: none;">
|
||||
<img id="bookImage" src="${data.thumbnail == "" ? "/assets/image/svg/no_photo.svg" : data.thumbnail}" alt="书籍缩略图">
|
||||
</div>
|
||||
<div style="text-align: left; min-height: 80%; min-width: 200px;">
|
||||
<h1>${data.bookName}</h1>
|
||||
<p>
|
||||
作者:${data.author}<br>
|
||||
所属分类:<a href="/category?id=${data.category.id}">${data.category.name}</a><br>
|
||||
语言:${data.language}<br>
|
||||
出版社:${data.publishingHouse}<br>
|
||||
</p>
|
||||
<div class="group-button">
|
||||
<img id="favorties-button" src="/assets/image/svg/favorites_empty.svg" style="visibility: hidden; opacity: 1; transition: all 0.3s;" title="点击收藏/取消收藏" onclick="swal('请先登录!')" />
|
||||
<!-- 预加载图片 --><img src="/assets/image/svg/favorites_fill.svg" style="display: none;" />
|
||||
|
||||
<a class="download-link" href="javaScript:scrollToTarget()"><img id="download-icon" src="/assets/image/svg/download.svg" /><span>下载这本书</span></a>
|
||||
<!--<a class="download-link" href="/download/?bookId=${data.id}"><img id="download-icon" src="/assets/image/svg/download.svg" /><span>下载这本书</span></a>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>书本介绍</h2>
|
||||
<p>${data.description}</p>
|
||||
<h2>来源信息</h2>
|
||||
<p>${data.copyright}</p>
|
||||
</div>`;
|
||||
|
||||
// 获取用户收藏信息
|
||||
getUserFavouritesStatus();
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
doFontmin();
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// 显示收藏还是取消收藏图标
|
||||
var favortiesIcon = false;
|
||||
// 获取用户收藏信息
|
||||
function getUserFavouritesStatus() {
|
||||
localStorageUtils.checkLocalStorage();
|
||||
|
||||
if (!localStorageUtils.getLoginStatus()) {
|
||||
// 用户未登录
|
||||
$("#favorties-button").css("visibility", "visible");
|
||||
return;
|
||||
}
|
||||
postRequest("/book/getFavoritesStatus", { token: localStorageUtils.getToken(), bookId: bookId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data.status == 1) {
|
||||
// 用户已收藏
|
||||
console.log("已收藏");
|
||||
favortiesIcon = true;
|
||||
} else {
|
||||
// 用户没有收藏本书
|
||||
console.log("没有收藏");
|
||||
favortiesIcon = false;
|
||||
}
|
||||
toggleDisplayButton();
|
||||
} else {
|
||||
if (data.errCode == "20004") {
|
||||
// 登录过期,小问题,这里不弹窗显示
|
||||
localStorageUtils.userLogout();
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
// swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
$("#favorties-button").css("visibility", "visible");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 正在请求标记
|
||||
var requestingFlag = false;
|
||||
// 添加收藏/取消收藏
|
||||
function toggleFavorites(toggleStatus) {
|
||||
if (requestingFlag) return;
|
||||
requestingFlag = true;
|
||||
$("#favorties-button").css("opacity", "0.3");
|
||||
postRequest("/book/toggleFavorites", { token: localStorageUtils.getToken(), bookId: bookId, status: toggleStatus ? 1 : 0 })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data == "success") {
|
||||
if (toggleStatus) {
|
||||
console.log("收藏成功");
|
||||
favortiesIcon = true;
|
||||
} else {
|
||||
console.log("取消收藏成功");
|
||||
favortiesIcon = false;
|
||||
}
|
||||
} else {
|
||||
console.log("操作失败");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
setTimeout(toggleDisplayButton, 500);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDisplayButton() {
|
||||
requestingFlag = false;
|
||||
$("#favorties-button").attr("src", "");
|
||||
$("#favorties-button").css("opacity", "1");
|
||||
if (favortiesIcon) {
|
||||
$("#favorties-button").attr("src", "/assets/image/svg/favorites_fill.svg");
|
||||
$("#favorties-button").attr("onclick", "toggleFavorites(false)");
|
||||
} else {
|
||||
$("#favorties-button").attr("src", "/assets/image/svg/favorites_empty.svg");
|
||||
$("#favorties-button").attr("onclick", "toggleFavorites(true)");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- 页面滚动 -->
|
||||
<script>
|
||||
$(".download-container").css("transition", "0.5s");
|
||||
$(".download-container").css("border-radius", "20px");
|
||||
function scrollToTarget() {
|
||||
$('html, body').animate({
|
||||
scrollTop: $("#scrollTarget").offset().top
|
||||
}, 600);
|
||||
setTimeout(function () {
|
||||
$(".download-container").css("background-color", "#6DA122");
|
||||
$(".download-container").css("transform", "scale(1.05)");
|
||||
setTimeout(function () {
|
||||
$(".download-container").css("background-color", "");
|
||||
$(".download-container").css("transform", "");
|
||||
}, 500);
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
<!-- 获取文件下载链接信息 -->
|
||||
<script>
|
||||
var direckLinkInfo = {};
|
||||
// 获取文件信息
|
||||
function getFileInfo() {
|
||||
function stringifyFileSize(nBytes = 0) {
|
||||
// 美化输出文件大小
|
||||
let sOutput = nBytes + " bytes";
|
||||
const aMultiples = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple];
|
||||
}
|
||||
return sOutput;
|
||||
}
|
||||
function getLinkDOM(fileObjectInfo) {
|
||||
// 获取文件下载链接DOM
|
||||
var iconSrc = "/assets/image/svg/direct_link.svg", title = fileObjectInfo.storageMediumForDisplay, content = "", downloadLink = "";
|
||||
if(fileObjectInfo.fileShareCode != "" && fileObjectInfo.fileShareCode != null) {
|
||||
content = `<span>提取码: <span class="click2copy" style="user-select: all; cursor: pointer;">${fileObjectInfo.fileShareCode}</span> </span>`; // 最后一个 是为了保证选中时不会选中后面的回车
|
||||
downloadLink = `<a class="baiduNetdiskLink" share-code="${fileObjectInfo.fileShareCode}" share-link="${fileObjectInfo.filePath}" style="cursor: pointer;" title="复制提取码并前往">前往</a>`;
|
||||
} else {
|
||||
downloadLink = `<a href="${fileObjectInfo.filePath}" target="_blank">前往</a>`;
|
||||
}
|
||||
switch (fileObjectInfo.storageMedium) {
|
||||
case "QCLOUD_COS":
|
||||
title = "直链下载";
|
||||
if (!localStorageUtils.getLoginStatus()) {
|
||||
downloadLink = "<span><a href='{}'>登录</a>后方可使用</span>".replace("{}", "/login?redirect=" + encodeURIComponent(location.pathname + location.search));
|
||||
} else {
|
||||
content = `<span style="font-size: 12px;"><nobr>每次点击都会扣减下载次数</nobr></span>`;
|
||||
downloadLink = `<a style="cursor: pointer;" onclick="getDirectLink(${fileObjectInfo.id});">下载</a>`;
|
||||
}
|
||||
break;
|
||||
case "BAIDU_NETDISK":
|
||||
iconSrc = "/assets/image/svg/baidu_netdisk.svg";
|
||||
break;
|
||||
case "ALIYUN_DRIVE":
|
||||
iconSrc = "/assets/image/svg/aliyun_drive.svg";
|
||||
break;
|
||||
case "FEISHU_DRIVE":
|
||||
iconSrc = "/assets/image/svg/feishu_drive.svg";
|
||||
break;
|
||||
case "LANZOUYUN":
|
||||
iconSrc = "/assets/image/svg/lanzouyun.svg";
|
||||
break;
|
||||
case "QUQIYUN":
|
||||
iconSrc = "/assets/image/svg/quqiyun.svg";
|
||||
break;
|
||||
case "UNKNOWN_DRIVE":
|
||||
default:
|
||||
title = fileObjectInfo.storageMediumForDisplay;
|
||||
downloadLink = `<a href="${fileObjectInfo.filePath}" target="_blank">前往</a>`;
|
||||
break;
|
||||
}
|
||||
var dom = `<div class="file-object-item">
|
||||
<div class="file-object-item-title">
|
||||
<img src="${iconSrc}"/> <span>${title}</span>
|
||||
</div>
|
||||
<div class="file-object-item-content">${content}</div>
|
||||
<div class="file-object-item-link">${downloadLink}</div>
|
||||
<div class="outdated-feedback">
|
||||
<img src="/assets/image/svg/feedback.svg" onclick="linkFeedback(${fileObjectInfo.fileId},${fileObjectInfo.id});"/>
|
||||
<span class="outdated-feedback-tip">链接失效?点击反馈!</span>
|
||||
</div>
|
||||
</div>`.replace(/\ [ ]+?/g,"").replace(/[\n]/g,"") //去掉多余空格、换行
|
||||
// console.log(dom);
|
||||
return dom;
|
||||
}
|
||||
getRequest("/file/getFileByBookId", { bookId: bookId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
|
||||
// 先处理文件对象
|
||||
var fileObjectList = {};
|
||||
for (let i = 0; i < data.fileObject.length; i++) {
|
||||
const element = data.fileObject[i];
|
||||
|
||||
if (!fileObjectList[element.fileId])
|
||||
fileObjectList[element.fileId] = [];
|
||||
|
||||
fileObjectList[element.fileId].push(getLinkDOM(element));
|
||||
|
||||
// 从文件中找到fileSha1, fileName, fileExt,并填入文件对象中,否则下载时获取不到
|
||||
var fileId = element.fileId;
|
||||
for (let j = 0; j < data.file.length; j++) {
|
||||
const fileElement = data.file[j];
|
||||
if (element.fileId == fileElement.id) {
|
||||
// 找到文件
|
||||
element.fileSha1 = fileElement.fileSha1;
|
||||
element.fileName = fileElement.fileName;
|
||||
element.fileExt = fileElement.fileExt;
|
||||
}
|
||||
}
|
||||
|
||||
direckLinkInfo[element.id] = element;
|
||||
}
|
||||
console.log("fileObjectList", fileObjectList);
|
||||
|
||||
// 再处理文件
|
||||
var fileContainer = document.getElementById("file-container");
|
||||
var innerHtmlList = [];
|
||||
for (let i = 0; i < data.file.length; i++) {
|
||||
const fileInfo = data.file[i];
|
||||
innerHtmlList.push(`
|
||||
<div class="file-item">
|
||||
<div class="file-title">
|
||||
<span class="file-name size20">${fileInfo.fileName}</span><span class="file-ext size16">${fileInfo.fileExt == "" ? "" : ("." + fileInfo.fileExt)}</span>
|
||||
<span class="file-size size14" style="padding-left: 30px;">${stringifyFileSize(fileInfo.fileSize)}</span>
|
||||
</div>
|
||||
<div class="file-detail">
|
||||
<span class="file-copyright size14 overflow-hide">${fileInfo.source == "" ? "" : ("来源信息:" + fileInfo.source) }</span>
|
||||
<span class="file-copyright size14" style="margin-top: 12px; display: block;">下载地址:</span>
|
||||
</div>
|
||||
<div class="file-object">
|
||||
${fileObjectList[fileInfo.id] ? fileObjectList[fileInfo.id].join('') : "暂无可用下载链接"}
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
fileContainer.innerHTML = innerHtmlList.join("");
|
||||
|
||||
if (innerHtmlList.length == 0) {
|
||||
var fileItem = document.createElement("div");
|
||||
fileItem.className = "file-item";
|
||||
fileItem.style.textAlign = "center";
|
||||
fileItem.innerHTML = "暂无可用文件源";
|
||||
fileContainer.appendChild(fileItem);
|
||||
}
|
||||
|
||||
// 最后绑定点击复制事件
|
||||
// 点击复制
|
||||
$(".click2copy").click(function (e) {
|
||||
copyToClipboard($(this).text());
|
||||
showTip(e, "复制成功");
|
||||
});
|
||||
|
||||
//
|
||||
$(".baiduNetdiskLink").click(function (e) {
|
||||
copyToClipboard($(this).attr("share-code"));
|
||||
showTip(e, "提取码已复制到剪切板");
|
||||
var shareLink = $(this).attr("share-link");
|
||||
setTimeout(function () {
|
||||
window.open(shareLink);
|
||||
}, 500);
|
||||
});
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
doFontmin();
|
||||
});
|
||||
}
|
||||
getFileInfo();
|
||||
</script>
|
||||
<script>
|
||||
// 获取直链下载链接,并下载该文件
|
||||
async function getDirectLink(fileObjectId) {
|
||||
var fileObject = direckLinkInfo[fileObjectId];
|
||||
console.log("fileObject", fileObject);
|
||||
|
||||
if(!localStorageUtils.getLoginStatus()) {
|
||||
swal("请先登录!");
|
||||
return;
|
||||
}
|
||||
|
||||
var fileNameForUser = `${fileObject.fileName}.${fileObject.fileExt}`;
|
||||
var fileKeyForCos = fileObject.filePath;
|
||||
|
||||
var visitorId = await getVisitorId();
|
||||
postRequest("/file/object/cos/get", { fileSha1: fileObject.fileSha1, fileNameAndExt: fileNameForUser, token: localStorageUtils.getToken(), visitorId: visitorId, expireMinute: 30 })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
// let guid = data.guid;
|
||||
var downloadLink = document.createElement("a");
|
||||
downloadLink.href = data.url;
|
||||
downloadLink.target = "_blank";
|
||||
console.log(downloadLink);
|
||||
downloadLink.click();
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 链接失效反馈
|
||||
async function linkFeedback(fileId, fileObjectId) {
|
||||
if (!confirm("确定这个下载链接失效了吗?(没失效的链接不可以乱点着玩儿哦)"))
|
||||
return;
|
||||
|
||||
if (!localStorageUtils.getLoginStatus()) {
|
||||
if (confirm("您尚未登录,登录后反馈处理更快,是否前往登录?")) {
|
||||
location.href = "/login?redirect=" + encodeURIComponent(location.pathname + location.search);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var visitorId = await getVisitorId();
|
||||
postRequest("/file/object/FailureFeedback", { bookId: bookId, fileId: fileId, fileObjectId: fileObjectId, token: localStorageUtils.getToken(), visitorId: visitorId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data);
|
||||
if (data) {
|
||||
swal("真棒!反馈成功!");
|
||||
} else {
|
||||
swal("反馈失败,请稍后再试!");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,68 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title><%= title %></title>
|
||||
<!-- <script src="/assets/lib/jquery/3.6.0/jquery.min.js"></script> -->
|
||||
<script src="/assets/lib/axios/0.26.1/axios.min.js"></script>
|
||||
<script src="/assets/javascripts/httpRequestUtils.js"></script>
|
||||
<script src="/assets/javascripts/localStorageUtils.js"></script>
|
||||
<script src="/assets/lib/sweetalert/2.1.2/sweetalert.min.js"></script>
|
||||
<script>
|
||||
// API地址
|
||||
const APIHOST = '<%= global.site.api.prefix %>';
|
||||
axios.defaults.baseURL = APIHOST;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="displayText">
|
||||
正在跳转中,请稍候...
|
||||
</p>
|
||||
<script>
|
||||
// 带 token 的为绑定第三方账号,不带 token 的为第三方登录
|
||||
var token = localStorageUtils.getToken();
|
||||
getRequest("/third-party/callback/<%=platform%>" + location.search + (token ? ("&token=" + token) : ""))
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
// 默认直接跳转 user 后台,如果是管理员则由 user 后台跳转
|
||||
if (token) {
|
||||
// 绑定第三方账号
|
||||
|
||||
// 绑定第三方账号成功
|
||||
swal("绑定成功").then(() => {
|
||||
location.href = "/dashboard/user/my-account";
|
||||
});
|
||||
} else {
|
||||
// 第三方登录成功
|
||||
localStorageUtils.userLogin({
|
||||
token: data.token,
|
||||
is_admin: data.group === "ADMIN",
|
||||
});
|
||||
if (localStorageUtils.getIsAdmin()) {
|
||||
window.location.href = "/dashboard/admin/index";
|
||||
} else {
|
||||
window.location.href = "/dashboard/user/index";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`).then(function () {
|
||||
location.replace("/login");
|
||||
});
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
document.getElementById("displayText").innerHTML = "系统错误,请稍后再试!";
|
||||
document.getElementById("displayText").style.color = "red";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -1,106 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 80vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#book-table {
|
||||
width: 100%;
|
||||
/* border: 1px solid black; */
|
||||
margin-top: 30px;
|
||||
line-height: 2.3em;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tr a {
|
||||
cursor: pointer;
|
||||
/* cursor: alias; */
|
||||
}
|
||||
|
||||
/* 在分类下隐藏书籍的分类 */
|
||||
tr>*:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<!-- <h1><%= headText %></h1> -->
|
||||
<div id="container">
|
||||
<!-- <a href="./book">书本详情页</a> -->
|
||||
</div>
|
||||
<hr style="margin: 30px 0;">
|
||||
<h3>该分类下的书籍</h3>
|
||||
<div id="container-book">
|
||||
<table id="book-table" style="width: 100%;"></table>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 渲染表格 -->
|
||||
<script src="/assets/javascripts/renderTable.js"></script>
|
||||
<!-- 搜索书籍 -->
|
||||
<script src="/assets/javascripts/searchBooks.js"></script>
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
var categoryId = requestParams["id"] ?? "";
|
||||
console.log("categoryId", categoryId);
|
||||
if (categoryId === "") {
|
||||
location.href = "/search";
|
||||
}
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: null,
|
||||
categoryId: categoryId
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
getRequest("/category/get", { id: categoryId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data.description == "")
|
||||
data.description = "暂无描述";
|
||||
|
||||
var topCategory = data.parentId !== 0 ? `<a href="/category?id=${data.parentId}">上级分类</a>` : "";
|
||||
document.getElementById("container").innerHTML = `
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h1>${data.name}</h1>
|
||||
<p>分类ID: ${data.id}</p>
|
||||
<p>${topCategory}</p>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>简介</h2>
|
||||
<p>${data.description}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
// 渲染后重新获取一次字体
|
||||
if (typeof (fontmin) === "function") {
|
||||
fontmin(getPageText());
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,130 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 70vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#result-table {
|
||||
width: 100%;
|
||||
/* border: 1px solid black; */
|
||||
margin-top: 30px;
|
||||
line-height: 2.3em;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tr > th:last-child,
|
||||
tr > td:last-child {
|
||||
border-left: 2px dashed;
|
||||
}
|
||||
tr > th {
|
||||
border-bottom: 2px dotted;
|
||||
}
|
||||
|
||||
tr a {
|
||||
cursor: pointer;
|
||||
/* cursor: alias; */
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
tr > th:last-child,
|
||||
tr > td:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<div id="container">
|
||||
<table id="result-table"></table>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- 渲染表格 -->
|
||||
<script src="/assets/javascripts/renderTable.js"></script>
|
||||
<!-- 生成分类层级关系 -->
|
||||
<script src="/assets/javascripts/generateCategoryHierarchy.js"></script>
|
||||
<script>
|
||||
getRequest("/category/list")
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log("获取的分类信息");
|
||||
console.log(data)
|
||||
|
||||
var renderData = [];
|
||||
|
||||
// 按照列表渲染
|
||||
// data.forEach(element => {
|
||||
// var mainDivWidth = 70/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
// var columnWidth = [60, 40];
|
||||
// renderData.push({
|
||||
// 分类: `<a target="_blank" href="/category?id=${element.id}">
|
||||
// <span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
// ${element.name}
|
||||
// </span>
|
||||
// </a>`,
|
||||
// 简介: `<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
// ${element.description}
|
||||
// </span>`,
|
||||
// })
|
||||
// });
|
||||
|
||||
// 按照层级关系进行渲染
|
||||
// console.log("按照层级关系排列好之后");
|
||||
hierarchyData = generateCategoryHierarchy(data);
|
||||
function render(category) {
|
||||
function renderCategory(category, renderData) {
|
||||
var mainDivWidth = 70/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
var columnWidth = [70, 30];
|
||||
renderData.push({
|
||||
分类: `<a target="_blank" href="/category?id=${category.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; text-align: left;">
|
||||
${" ".repeat((category.level-1)*8)}${category.name}
|
||||
</span>
|
||||
</a>`,
|
||||
简介: `<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; text-align: left; text-indent: 1em;">
|
||||
${category.description}
|
||||
</span>`,
|
||||
})
|
||||
}
|
||||
renderCategory(category, renderData);
|
||||
// console.log("category", category)
|
||||
for (let i = 0; i < category.children.length; i++)
|
||||
render(category.children[i]);
|
||||
}
|
||||
render({ children: hierarchyData });
|
||||
renderData.shift(); // 删除数组中的第一个元素
|
||||
|
||||
if(renderData.length == 0) {
|
||||
renderTable({ data: `系统中暂时还没有书籍分类`, tableId: "result-table", renderTableHead: true });
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: "result-table", renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
// 渲染后重新获取一次字体
|
||||
if (typeof (fontmin) === "function") {
|
||||
fontmin(getPageText());
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,83 +0,0 @@
|
||||
<div class="footer" style="margin-top: 10vh;">
|
||||
<hr>
|
||||
<p>
|
||||
书栖网 • 2021-2022
|
||||
<br>
|
||||
<small>
|
||||
<br>
|
||||
❤️本项目是开源项目,项目的发展离不开大家的支持,欢迎前往项目仓库点亮Star支持我们❤️
|
||||
<br>
|
||||
<a target="_blank" href="https://gitee.com/bookshelfplus/bookshelfplus"><img
|
||||
src="/assets/image/svg/gitee.svg" style="height: 1.05em; vertical-align: middle;" /><span
|
||||
style="vertical-align: middle; margin-left: 3px;">码云</span></a>
|
||||
<span style="vertical-align: middle;"> · </span>
|
||||
<a target="_blank" href="https://github.com/bookshelfplus/bookshelfplus"><img
|
||||
src="/assets/image/svg/github.svg" style="height: 1.08em; vertical-align: middle;" /><span
|
||||
style="vertical-align: middle; margin-left: 3px;">GitHub</span></a>
|
||||
<br>
|
||||
<a href="/status">网站状态检测</a>
|
||||
·
|
||||
<a href="/about">关于</a>
|
||||
·
|
||||
<a href="/feedback">反馈</a>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- https://gist.github.com/macbookandrew/f33dbbc0aa582d0515919dc5fb95c00a -->
|
||||
<script>
|
||||
// 获取网页上的所有文字
|
||||
// refer: https://www.cnblogs.com/yzeng/p/8268731.html
|
||||
function getPageText() {
|
||||
var str = document.documentElement.innerText;
|
||||
str += "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 数字和英文全部包含进去,避免因为部分数字或字母造成大量生成字体
|
||||
var res = [].filter.call(str, (s, i, o) => o.indexOf(s) == i).sort().join('').trim();
|
||||
return res;
|
||||
}
|
||||
function fontmin(text) {
|
||||
axios.post('../fontmin/getfont', { text: text, font: "" }, { timeout: 1000 })
|
||||
.then(function (response) {
|
||||
// 当接口成功返回时,动态设置css
|
||||
loadFont(response.data.url);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log("字体加载失败", error);
|
||||
}).finally(function () {
|
||||
finishFontLoading();
|
||||
});
|
||||
}
|
||||
|
||||
function loadFont(fontUrl) {
|
||||
let styleDom = document.createElement('style');
|
||||
styleDom.type = 'text/css';
|
||||
const cssText = `
|
||||
@font-face {
|
||||
font-family: bookshelfplusFont;
|
||||
src: url("${fontUrl}") format("truetype");
|
||||
}
|
||||
`
|
||||
styleDom.appendChild(document.createTextNode(cssText));
|
||||
document.getElementsByTagName('head')[0].appendChild(styleDom)
|
||||
console.log("字体加载成功");
|
||||
}
|
||||
|
||||
function finishFontLoading() {
|
||||
$("html,body").css("transition", ".05s");
|
||||
setTimeout(() => {
|
||||
$("html,body").css("opacity", "1");
|
||||
}, 100);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var allText = getPageText();
|
||||
// console.log(allText);
|
||||
if ('<%= typeof(minfontOnLoad) === "undefined" ? "true" : minfontOnLoad %>' != "false") {
|
||||
fontmin(allText);
|
||||
} else {
|
||||
setTimeout(finishFontLoading, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
console.error(`%c请注意,未经允许获取网站数据属于【非法获取计算机信息系统数据】,需要承担响应法律责任。\n%c以下是《刑法》第二百八十五条节选。\n\n非法获取计算机信息系统数据、非法控制计算机信息系统罪\n根据《中华人民共和国刑法》第二百八十五条规定,非法获取计算机信息系统数据、非法控制计算机信息系统罪,是指违反国家规定,侵入国家事务、国防建设、尖端科学技术领域以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,情节严重的行为。\n刑法第285条第2款明确规定,犯本罪的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。\n\n提供侵入、非法控制计算机信息系统程序、工具罪\n根据《中华人民共和国刑法》第二百八十五条规定,刑法第285条第1款保护的是国家事务、国防建设、尖端科学技术领域的计算机信息系统。《刑法修正案(七)》第9条第1款在刑法第285条中增加1款作为第2款,扩大了对计算机信息系统的保护范围,将违反国家规定,侵入第1款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的行为规定为犯罪。`, "color: red; font-weight: bold;", "color: red; line-height: 1.8em;");
|
||||
</script>
|
@@ -1,55 +0,0 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<title><%= typeof title !=='undefined' ? title : "前端服务出现异常"; %></title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/stylesheets/style.css">
|
||||
<script src="/assets/lib/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="/assets/lib/axios/0.26.1/axios.min.js"></script>
|
||||
|
||||
<!-- refer: https://www.sweetalert.cn/ -->
|
||||
<script src="/assets/lib/sweetalert/2.1.2/sweetalert.min.js"></script>
|
||||
|
||||
<script src="/assets/javascripts/httpRequestUtils.js"></script>
|
||||
<script src="/assets/javascripts/localStorageUtils.js"></script>
|
||||
<script>
|
||||
// API地址
|
||||
const APIHOST = '<%= global.site.api.prefix %>';
|
||||
axios.defaults.baseURL = APIHOST;
|
||||
</script>
|
||||
<style>
|
||||
/* 字体加载前先隐藏,不然文字会闪一下 */
|
||||
html, body { opacity: 0; }
|
||||
</style>
|
||||
<script>
|
||||
/**
|
||||
* 获取用户浏览器指纹
|
||||
*
|
||||
* refer:
|
||||
* - https://fingerprintjs.com/
|
||||
* - https://github.com/fingerprintjs/fingerprintjs
|
||||
*
|
||||
*/
|
||||
// Initialize the agent at application startup.
|
||||
const fpPromise = import('/assets/lib/fingerprintjs/3.3.3/esm.min.js')
|
||||
// const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3')
|
||||
.then(FingerprintJS => FingerprintJS.load())
|
||||
|
||||
// // Get the visitor identifier when you need it.
|
||||
// fpPromise
|
||||
// .then(fp => fp.get())
|
||||
// .then(result => {
|
||||
// // This is the visitor identifier:
|
||||
// const visitorId = result.visitorId
|
||||
// console.log(visitorId)
|
||||
// });
|
||||
|
||||
async function getVisitorId() {
|
||||
try {
|
||||
return (await fpPromise.then(fp => fp.get())).visitorId;
|
||||
} catch (error) {
|
||||
return JSON.stringify(navigator.userAgent);
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,50 +0,0 @@
|
||||
<div class="navbar">
|
||||
<div class="navbar-grid">
|
||||
<div class="grid-item"></div>
|
||||
<div class="grid-item">
|
||||
<nobr>
|
||||
<h1 onclick="location.href='/'" style="cursor: pointer; display: inline;">书栖网</h1>
|
||||
</nobr>
|
||||
</div>
|
||||
<div class="grid-item exnarrowHide" style="text-align: right; color: white;">
|
||||
<a class="home narrowHide" href="/">首页</a>
|
||||
<a href="/search">搜索</a>
|
||||
<a class="narrowHide" href="/category">分类</a>
|
||||
<a id="dashboard" href="/login" target="_blank">登录</a>
|
||||
</div>
|
||||
<div class="grid-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
if (localStorageUtils.getLoginStatus()) {
|
||||
$("#dashboard").html("后台");
|
||||
}
|
||||
|
||||
function navbarHighlight() {
|
||||
// 导航栏中突出当前页面
|
||||
var route = location.pathname.split('/').filter(s => !!s);
|
||||
var page = route[route.length - 1];
|
||||
|
||||
// 首页
|
||||
if (route.length === 0) {
|
||||
$(".home").addClass("active");
|
||||
return;
|
||||
}
|
||||
|
||||
//其他页面
|
||||
// console.log("page:", page);
|
||||
$("a").toArray().forEach(element => {
|
||||
var linkRoute = element.getAttribute("href").split('/').filter(s => !!s);
|
||||
// console.log(element.href, linkRoute);
|
||||
if (linkRoute.length > 0) {
|
||||
var linkPage = linkRoute[linkRoute.length - 1];
|
||||
// console.log(element, linkPage);
|
||||
if (page == linkPage) {
|
||||
$(element).addClass("active");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
navbarHighlight();
|
||||
</script>
|
@@ -1,80 +0,0 @@
|
||||
<div class="searchBox">
|
||||
<input id="searchInput" type="text" placeholder="只需两步:搜索、下载 就这么简单" /><!--
|
||||
--><input id="searchButton" type="button" value="搜一下" />
|
||||
</div>
|
||||
<script>
|
||||
// /**
|
||||
// * 内容改变时并不会触发事件,但是在失去焦点的时候会触发。
|
||||
// */
|
||||
// $("#inputid").change(function () {
|
||||
// console.log($(this).val());
|
||||
// });
|
||||
// /**
|
||||
// * 只要文本类容发生改变,就会触发该事件
|
||||
// */
|
||||
// $("#inputid").bind("input propertychange", function () {
|
||||
// console.log($(this).val());
|
||||
// });
|
||||
|
||||
// // 搜索框文字改变事件(实时)
|
||||
// $("#searchInput").bind("input propertychange", function () {
|
||||
// if ($('#searchInput').val() !== "")
|
||||
// $('#searchInput').val("这个功能还没做呢,再等等吧");
|
||||
// });
|
||||
|
||||
// 搜索框获得焦点事件
|
||||
$('#searchInput').focus(() => {
|
||||
// $('#searchInput').val("");
|
||||
$('#searchInput').attr('placeholder', "在这里输入您想搜索的电子书吧")
|
||||
})
|
||||
|
||||
// 搜索框失去焦点事件
|
||||
$('#searchInput').blur((that) => {
|
||||
// $('#searchInput').val("");
|
||||
$('#searchInput').attr('placeholder', "只需两步:搜索、下载 就这么简单")
|
||||
})
|
||||
|
||||
// 文本框回车事件
|
||||
$('#searchInput').keydown(function (e) {
|
||||
var curKey = e.which;
|
||||
if (curKey == 13) {
|
||||
$("#searchButton").click();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索按钮点击事件
|
||||
if (location.pathname == "/" || !(window.history && history.pushState)) {
|
||||
// 首页 或者不支持 history.pushState
|
||||
$('#searchButton').click(function () {
|
||||
var searchBoxValue = $('#searchInput').val();
|
||||
if (!searchBoxValue || searchBoxValue.trim() == "") {
|
||||
searchBoxValue = "";
|
||||
}
|
||||
window.location.href = "./search?keyword=" + encodeURIComponent(searchBoxValue.trim());
|
||||
});
|
||||
} else {
|
||||
// 搜索页
|
||||
$('#searchButton').click(function () {
|
||||
var searchBoxValue = $('#searchInput').val();
|
||||
// 如果搜索内容未变化,则不搜索
|
||||
if (keyword == searchBoxValue.trim()) return;
|
||||
if (!searchBoxValue || searchBoxValue.trim() == "") {
|
||||
history.pushState({}, "", "./search"); // 第二个参数为保留参数
|
||||
} else {
|
||||
keyword = searchBoxValue.trim();
|
||||
history.pushState({}, "", "./search?keyword=" + encodeURIComponent(keyword));
|
||||
doSearch(keyword);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function (event) {
|
||||
var requestParams = getParams();
|
||||
var keyword = (requestParams["keyword"] || "").trim();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
|
||||
searchbox.value = keyword;
|
||||
doSearch(keyword);
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,4 +0,0 @@
|
||||
<div>
|
||||
<h2>第三方账号管理</h2>
|
||||
<%- include("../component/third-party-manage.html"); %>
|
||||
</div>
|
@@ -1,125 +0,0 @@
|
||||
<p>
|
||||
<a href="<%= pageUrl %>detail" target="_blank">添加书籍</a>
|
||||
</p>
|
||||
<input id="searchInput" type="text" />
|
||||
<input id="searchButton" type="button" value="搜索" />
|
||||
<!-- 搜索书籍 -->
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
var keyword = (requestParams["keyword"] || "").trim();
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: null,
|
||||
categoryId: null
|
||||
});
|
||||
|
||||
$("#searchButton").click(function () {
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: $("#searchInput").val(),
|
||||
categoryId: null
|
||||
});
|
||||
});
|
||||
|
||||
function search({ tableElementId = "", searchText = "", categoryId = 0 }) {
|
||||
getRequest("/book/search", { bookName: searchText, categoryId: categoryId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data)
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
var mainDivWidth = 96/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
var columnWidth = [20, 15, 10, 15, 5, 35];
|
||||
renderData.push({
|
||||
编号: `${element.id}`,
|
||||
书名: `<a target="_blank" href="/book?id=${element.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.bookName}
|
||||
</span>
|
||||
</a>`,
|
||||
分类: `<a target="_blank" href="/category?id=${element.category.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.category.name}
|
||||
</span>
|
||||
</a>`,
|
||||
作者: `<span class="overflow-omit" style="max-width: 300px; max-height: 2em; margin: 0 auto;">
|
||||
${element.author}
|
||||
</span>`,
|
||||
语言: `<span class="overflow-omit" style="max-width: ${columnWidth[2] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.language}
|
||||
</span>`,
|
||||
出版社: `<span class="overflow-omit" style="max-width: ${columnWidth[3] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.publishingHouse}
|
||||
</span>`,
|
||||
来源: `<span class="overflow-omit" style="max-width: ${columnWidth[4] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.publishingHouse}
|
||||
</span>`,
|
||||
管理: `<span class="overflow-omit" style="max-width: ${columnWidth[5] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
<a href="<%= pageUrl %>detail?id=${element.id}">修改</a>
|
||||
<a href="javascript:deleteBook(${element.id});">删除</a>
|
||||
</span>`,
|
||||
})
|
||||
});
|
||||
|
||||
if (renderData.length == 0) {
|
||||
console.log("没有搜索到相关书籍");
|
||||
function htmlEncode(str) {
|
||||
// refer: https://stackoverflow.com/questions/4183801/escape-html-chracters
|
||||
var div = document.createElement('div');
|
||||
var txt = document.createTextNode(str);
|
||||
div.appendChild(txt);
|
||||
return div.innerHTML;
|
||||
}
|
||||
if (searchText && searchText != "") {
|
||||
//
|
||||
renderTable({ data: `没有搜索到与 <span style="color: red;">${htmlEncode(searchText)}</span> 相关的书籍,请换个关键词再试试吧`, tableId: tableElementId, renderTableHead: true });
|
||||
} else if (categoryId && categoryId != 0) {
|
||||
//
|
||||
renderTable({ data: `该分类下暂无电子书`, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
function deleteBook(deleteBookId) {
|
||||
if (!confirm(`确认要删除编号为 ${deleteBookId} 的书籍吗?(关联文件不会被删除)`)) return;
|
||||
|
||||
postRequest("/book/delete", { token: localStorageUtils.getToken(), id: deleteBookId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data == "success") {
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: $("#searchInput").val(),
|
||||
categoryId: null
|
||||
});
|
||||
} else {
|
||||
swal("删除失败!");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,286 +0,0 @@
|
||||
<!-- 是否连续录入复选框 -->
|
||||
<p style="text-align: center;">
|
||||
<!-- 只在新增书籍的时候显示 -->
|
||||
<input type="checkbox" id="isContinuous" checked="checked" />连续录入
|
||||
</p>
|
||||
<!-- 生成分类结构 -->
|
||||
<script src="/assets/javascripts/generateCategoryHierarchy.js"></script>
|
||||
<script>
|
||||
// 如果传入了 id 那么就是修改书籍,否则就是添加书籍
|
||||
var bookId = getParams().id;
|
||||
var isModify = bookId ? true : false;
|
||||
if (!isModify) {
|
||||
// 新增书籍
|
||||
bookId = 0;
|
||||
} else {
|
||||
// 修改书籍
|
||||
$("#isContinuous").parent().remove();
|
||||
}
|
||||
|
||||
// 点击提交按钮
|
||||
function btnSubmitClick() {
|
||||
formSubmit({
|
||||
type: 'POST',
|
||||
url: '/book/detail',
|
||||
data: { id: bookId },
|
||||
success: function (data) {
|
||||
console.log(data);
|
||||
swal(isModify ? "修改成功!" : "添加成功!").then(function () {
|
||||
if (!isModify && document.getElementById("isContinuous").checked) {
|
||||
location.reload();
|
||||
} else {
|
||||
// 回到书籍管理页
|
||||
location.href = "<%= pageUrl %>../";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是修改书籍,则需要获取书籍详情
|
||||
async function getBookDetail(bookId) {
|
||||
var responseData = await getRequest("/book/get", { id: bookId });
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
return data;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
// 回到书籍管理页
|
||||
location.href = "<%= pageUrl %>../";
|
||||
}
|
||||
}
|
||||
|
||||
async function getControlsProfile(getValidateUtils) {
|
||||
|
||||
// 获取分类列表
|
||||
var categoryOptions = [];
|
||||
|
||||
var responseData = await getRequest("/category/list");
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data)
|
||||
hierarchyData = generateCategoryHierarchy(data);
|
||||
console.log(hierarchyData);
|
||||
function render(category) {
|
||||
categoryOptions.push({
|
||||
"tag": "option",
|
||||
"attr": { "value": category.id },
|
||||
"innerHTML": `${" ".repeat((category.level - 1) * 8)}${category.name}`,
|
||||
})
|
||||
for (let i = 0; i < category.children.length; i++)
|
||||
render(category.children[i]);
|
||||
}
|
||||
render({ children: hierarchyData });
|
||||
categoryOptions.shift(); // 删除数组中的第一个元素
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
|
||||
console.log(categoryOptions);
|
||||
|
||||
// 修改书籍: 获取书籍详情
|
||||
var bookDetail = {};
|
||||
if (isModify) {
|
||||
bookDetail = await getBookDetail(bookId);
|
||||
} else {
|
||||
console.log("新增书籍无需获取书籍详情");
|
||||
}
|
||||
|
||||
return [
|
||||
// 【模板】
|
||||
// {
|
||||
// // 必填
|
||||
// "tag": "input",
|
||||
// "attr": {
|
||||
// // 可选
|
||||
// "id": "",
|
||||
// "name": "", // 用于 POST 提交
|
||||
// "class": "",
|
||||
// "style": "",
|
||||
// "placeholder": "电子书的名称",
|
||||
// },
|
||||
// "label": {
|
||||
// "value": "书本名称",
|
||||
// },
|
||||
// "required": true, // 是否必填
|
||||
|
||||
// // 可选
|
||||
// "innerHTML": "",
|
||||
// "children": [
|
||||
// {
|
||||
// "tag": "option",
|
||||
// "attr": { "value": "1" },
|
||||
// "innerHTML": "选项1",
|
||||
// },
|
||||
// {
|
||||
// "tag": "option",
|
||||
// "attr": { "value": "2" },
|
||||
// "innerHTML": "选项2",
|
||||
// },
|
||||
// ],
|
||||
// "validate": function (value) {
|
||||
// var validate = validateUtils.setValue(value)
|
||||
// .validate.notNull(value, "书本名称不能为空,请输入书本名称")
|
||||
// .validate.notNull(value, "书本名称不能超过 50 个字符")
|
||||
// .validate.notNull(value, "书本名称不能包含特殊字符")
|
||||
// .result();
|
||||
// console.log(validate);
|
||||
|
||||
// if (validateUtils.isEmpty(value)) {
|
||||
// result = false;
|
||||
// msg = "不能为空";
|
||||
// }
|
||||
// return { result: result, msg: msg };
|
||||
// }
|
||||
// },
|
||||
// 必须设置 id, name
|
||||
// 只有设置了 id 才会使用 validate 校验取值
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "bookName",
|
||||
"name": "bookName",
|
||||
"placeholder": "电子书的名称",
|
||||
"value": bookDetail.bookName || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "书本名称",
|
||||
},
|
||||
"required": true, // 是否必填
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.notEmptyAfterTrim("书本名称不能为空,请输入书本名称")
|
||||
.length(0, 250, "书本名称不能超过 250 个字符")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "author",
|
||||
"name": "author",
|
||||
"placeholder": "电子书的作者",
|
||||
"value": bookDetail.author || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "作者姓名",
|
||||
},
|
||||
"required": false, // 是否必填
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.length(0, 250, "作者姓名不能超过 250 个字符")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "textarea",
|
||||
"attr": {
|
||||
"id": "description",
|
||||
"name": "description",
|
||||
"style": "height:100px;",
|
||||
"placeholder": "电子书的简介",
|
||||
},
|
||||
"label": {
|
||||
"value": "书籍简介",
|
||||
},
|
||||
"required": false, // 是否必填
|
||||
"innerHTML": bookDetail.description || "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.length(0, 5000, "书籍简介不能超过 5000 个字符")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "select",
|
||||
"attr": {
|
||||
"id": "language",
|
||||
"name": "language",
|
||||
"placeholder": "书籍语言",
|
||||
"value": bookDetail.language || "Chinese",
|
||||
},
|
||||
"label": {
|
||||
"value": "语言",
|
||||
},
|
||||
"required": true, // 是否必填
|
||||
"children": [
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "Chinese" },
|
||||
"innerHTML": "中文",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "English" },
|
||||
"innerHTML": "英文",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "" },
|
||||
"innerHTML": "其他",
|
||||
},
|
||||
],
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notNull("书籍语言传入值错误")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "publishingHouse",
|
||||
"name": "publishingHouse",
|
||||
"placeholder": "出版社",
|
||||
"value": bookDetail.publishingHouse || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "出版社",
|
||||
},
|
||||
"required": false, // 是否必填
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.length(0, 50, "出版社不能超过 50 个字符")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "copyright",
|
||||
"name": "copyright",
|
||||
"placeholder": "来源信息 & 版权信息",
|
||||
"value": bookDetail.copyright || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "来源(版权)",
|
||||
},
|
||||
"required": false, // 是否必填
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notNull("来源(版权)传入值错误")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "select",
|
||||
"attr": {
|
||||
"id": "categoryId",
|
||||
"name": "categoryId",
|
||||
"value": (bookDetail.category && bookDetail.category.id) ? bookDetail.category.id : "",
|
||||
},
|
||||
"label": {
|
||||
"value": "书籍分类",
|
||||
},
|
||||
"required": true, // 是否必填
|
||||
"children": categoryOptions,
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notEmpty("请选择书籍分类")
|
||||
.notStringNumber("书籍分类传入值错误")
|
||||
.isValid()
|
||||
},
|
||||
];
|
||||
}
|
||||
</script>
|
@@ -1,23 +0,0 @@
|
||||
<p>
|
||||
查看后端项目配置是否配置正确
|
||||
</p>
|
||||
<p>
|
||||
请按 F12 查看详情
|
||||
</p>
|
||||
<script>
|
||||
postRequest('/system/status', { token: localStorageUtils.getToken() })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
</script>
|
@@ -1,3 +0,0 @@
|
||||
<p>
|
||||
导出为 Markdown 格式文件(TODO)
|
||||
</p>
|
@@ -1,127 +0,0 @@
|
||||
<style>
|
||||
/* 限制 来源 列的宽度 */
|
||||
tr>*:nth-child(7),
|
||||
td>*:nth-child(7) {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
/* 限制 哈希 列的宽度 */
|
||||
tr>*:nth-child(9),
|
||||
td>*:nth-child(9) {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
/* 限制 时间 列的宽度 */
|
||||
tr>*:nth-child(10),
|
||||
td>*:nth-child(10) {
|
||||
max-width: 100px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<a href="<%= pageUrl %>object-manage">文件对象管理</a>
|
||||
|
||||
<a href="<%= pageUrl %>upload"target="_blank">上传文件</a><br>
|
||||
删除文件前必须先删除该文件关联的所有文件对象
|
||||
</p>
|
||||
<script>
|
||||
list({
|
||||
tableElementId: "book-table"
|
||||
});
|
||||
|
||||
function stringifyFileSize(nBytes = 0) {
|
||||
// 美化输出文件大小
|
||||
let sOutput = nBytes + " bytes";
|
||||
const aMultiples = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple];
|
||||
}
|
||||
return sOutput;
|
||||
}
|
||||
|
||||
function list({ tableElementId }) {
|
||||
postRequest("/file/list", { token: localStorageUtils.getToken() })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data);
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
console.log(element);
|
||||
|
||||
var watermarkAndAdvertising = [];
|
||||
if (element.watermark)
|
||||
watermarkAndAdvertising.push("有水印");
|
||||
if (element.advertising)
|
||||
watermarkAndAdvertising.push("有广告");
|
||||
watermarkAndAdvertising = watermarkAndAdvertising.join(",") || "无";
|
||||
|
||||
renderData.push({
|
||||
编号: `${element.id}`,
|
||||
关联书籍: element.bookId === 0 ? "<span style=\"font-size: 10px;\">未关联书籍</span>" : `<span class="overflow-omit" style="margin: 0 auto;">
|
||||
ID: ${element.bookId}
|
||||
<a href="/book?id=${element.bookId}">查看</a>
|
||||
<a href="/dashboard/admin/book-manage/detail?id=${element.bookId}">修改</a>
|
||||
</span>`,
|
||||
文件名: `${element.fileName}`,
|
||||
格式: `${element.fileExt}`,
|
||||
页数: `${element.numberOfPages ?element.numberOfPages : ""}`,
|
||||
"水印&广告": `<span style="font-size: 10px;">${watermarkAndAdvertising}</span>`,
|
||||
来源: `<span class="overflow-omit" style="font-size: 10px;">${element.source}</span>`,
|
||||
大小: `${stringifyFileSize(element.fileSize)}`,
|
||||
"哈希(双击全选)": `<span class="overflow-omit" style="font-size: 10px;">${element.fileSha1}</span>`,
|
||||
时间: `<span class="overflow-omit" style="font-size: 12px; line-height: 1.2em; display: block;">
|
||||
${new Date(element.fileCreateAt).toLocaleString()}
|
||||
</span>`,
|
||||
管理: `<span style="margin: 0 auto;">
|
||||
<a href="<%= pageUrl %>detail?id=${element.id}">详情</a>
|
||||
<a href="<%= pageUrl %>get-share-url?fileId=${element.id}">添加网盘链接</a><br>
|
||||
<a href="javascript:deleteFile(${element.id});">删除(TODO)</a>
|
||||
</span>`,
|
||||
})
|
||||
});
|
||||
if (renderData.length == 0) {
|
||||
renderTable({ data: `暂无文件`, tableId: tableElementId, renderTableHead: true });
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
// function deleteFile(deleteFileId) {
|
||||
// if (!confirm(`确认要删除编号为 ${deleteFileId} 的文件吗?`)) return;
|
||||
|
||||
// postRequest("/book/delete", { token: localStorageUtils.getToken(), id: deleteFileId })
|
||||
// .then(function (responseData) {
|
||||
// var axiosData = responseData.data;
|
||||
// var status = axiosData.status;
|
||||
// var data = axiosData.data;
|
||||
// if (status === "success") {
|
||||
// console.log(data)
|
||||
// if (data == "success") {
|
||||
// search({
|
||||
// tableElementId: "book-table",
|
||||
// searchText: $("#searchInput").val(),
|
||||
// categoryId: null
|
||||
// });
|
||||
// } else {
|
||||
// swal("删除失败!");
|
||||
// }
|
||||
// } else {
|
||||
// swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
// }
|
||||
// }).catch(function (error) {
|
||||
// console.log(error);
|
||||
// swal("无法连接到服务器,请检查网络连接!");
|
||||
// });
|
||||
// }
|
||||
</script>
|
@@ -1,180 +0,0 @@
|
||||
<style>
|
||||
.main {
|
||||
max-width: 90%;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<a href="<%= pageUrl %>../">返回文件管理</a>
|
||||
</p>
|
||||
<h3>文件详情</h3>
|
||||
<div id="file-detail-container"></div>
|
||||
<div id="book-selector-container" style="display: none;">
|
||||
<p>
|
||||
请选择需要绑定的书籍
|
||||
</p>
|
||||
<iframe id="book-selector-iframe" src="" style="width: 100%; height: 55vh;"></iframe>
|
||||
</div>
|
||||
<hr>
|
||||
<h3>关联文件对象</h3>
|
||||
<div id="file-object-container"></div>
|
||||
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var fileId = Number(requestParams["id"]) ?? "";
|
||||
if (fileId === "") {
|
||||
location.href = "<%= pageUrl %>../";
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 获取文件信息
|
||||
function getFileInfo() {
|
||||
function stringifyFileSize(nBytes = 0) {
|
||||
// 美化输出文件大小
|
||||
let sOutput = nBytes + " bytes";
|
||||
const aMultiples = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple];
|
||||
}
|
||||
return sOutput;
|
||||
}
|
||||
getRequest("/file/getFileById", { fileId: fileId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log("file", data);
|
||||
|
||||
let bookLinkHtml = `(<a href="/book?id=${data.bookId}" target="_blank">查看关联书籍</a>)`;
|
||||
document.getElementById("file-detail-container").innerHTML =
|
||||
`<table border="1" style="margin: 0 auto;">
|
||||
<tr><th>key</th><th>value</th></tr>
|
||||
<tr><td>文件名</td><td>${data.fileName}</td></tr>
|
||||
<tr><td>文件扩展名</td><td>${data.fileExt}</td></tr>
|
||||
<tr><td>文件大小</td><td>${stringifyFileSize(data.fileSize)}</td></tr>
|
||||
<tr><td>SHA1</td><td>${data.fileSha1}</td></tr>
|
||||
<tr><td>文件Id</td><td>${data.id}</td></tr>
|
||||
<tr><td>关联书籍Id</td><td>${data.bookId == 0 ? "未关联书籍" : data.bookId + bookLinkHtml} <button onclick="toggleSelectBook();">关联书籍</button></td></tr>
|
||||
<tr><td>是否有广告</td><td>${data.advertising ? "是" : "否"}</td></tr>
|
||||
<tr><td>是否有水印</td><td>${data.watermark ? "是" : "否"}</td></tr>
|
||||
<tr><td>文件创建日期</td><td>${data.fileCreateAt}</td></tr>
|
||||
<tr><td>页数</td><td>${data.numberOfPages}</td></tr>
|
||||
<tr><td>来源信息</td><td>${data.source}</td></tr>
|
||||
</table>`;
|
||||
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
getFileInfo();
|
||||
|
||||
|
||||
// 获取文件对象信息
|
||||
function getFileObjectInfo() {
|
||||
getRequest("/file/object/getByFileId", { fileId: fileId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log("fileObject", data);
|
||||
|
||||
var items = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var item = data[i];
|
||||
items.push(`<tr>
|
||||
<td>${item.id}</td>
|
||||
<td>${item.storageMediumForDisplay}</td>
|
||||
<td style="font-size: 12px; max-width: 120px;">
|
||||
<span class="overflow-omit">${item.filePath}</span>
|
||||
</td>
|
||||
<td>${item.filePwd}</td>
|
||||
<td>${item.fileShareCode}</td>
|
||||
<td>${(item.uploadStatus ? item.uploadStatus : "<span style='color: grey; font-weight: bold;'>未知</span>")
|
||||
.replace("SUCCESS", "<span style='color: green; font-weight: bold;'>成功</span>")
|
||||
.replace("UPLOADING", "<span style='color: orange; font-weight: bold;'>正在上传</span>")
|
||||
.replace("NOT_EXIST", "<span style='color: red; font-weight: bold;'>不存在</span>")}</td>
|
||||
<td><a href="<%= pageUrl %>../get-share-url?id=${item.id}&fileId=${item.fileId}">修改</a></td>
|
||||
</tr>`);
|
||||
}
|
||||
document.getElementById("file-object-container").innerHTML =
|
||||
`<a href="<%= pageUrl %>../get-share-url?fileId=${fileId}">添加网盘链接</a><br>
|
||||
<table border="1" style="margin: 0 auto; width: 100%;">
|
||||
<tr>
|
||||
<th>文件对象Id</th>
|
||||
<th>存储介质</th>
|
||||
<th>链接</th>
|
||||
<th>文件密码</th>
|
||||
<th>提取码</th>
|
||||
<th>状态</th>
|
||||
<th>管理</th>
|
||||
</tr>
|
||||
${items.join("")}
|
||||
</table>`;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
getFileObjectInfo();
|
||||
</script>
|
||||
<script>
|
||||
window.addEventListener('message', function (event) {
|
||||
var data = JSON.parse(event.data);
|
||||
console.log("子页面消息:", data);
|
||||
var bookId = data.id;
|
||||
if (data.iframe != "book-selector") return;
|
||||
if (data.id == null) {
|
||||
toggleSelectBook();
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户选择了书籍,现在需要绑定到文件
|
||||
postRequest("/file/bindBook", { token: localStorageUtils.getToken(), fileId: fileId, bookId: bookId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if(data == "success") {
|
||||
// swal("绑定成功!");
|
||||
} else {
|
||||
swal("绑定失败!");
|
||||
}
|
||||
getFileInfo();
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode}) `);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
toggleSelectBook();
|
||||
});
|
||||
}, false);
|
||||
|
||||
function toggleSelectBook() {
|
||||
if($("#book-selector-container").css("display") === "none") {
|
||||
document.getElementById("book-selector-iframe").src = "/dashboard/iframe/book-selector";
|
||||
$("#book-selector-container").slideDown(200, function () { });
|
||||
$('body,html').animate({
|
||||
scrollTop: $("#book-selector-container").offset().top - 20
|
||||
});
|
||||
} else {
|
||||
$("#book-selector-container").slideUp();
|
||||
}
|
||||
}
|
||||
function selectBook() {
|
||||
$("#book-selector-container").slideDown();
|
||||
}
|
||||
</script>
|
@@ -1,122 +0,0 @@
|
||||
<style>
|
||||
/* 限制 来源 列的宽度 */
|
||||
tr>*:nth-child(3),
|
||||
td>*:nth-child(3) {
|
||||
max-width: 100px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<a href="<%= pageUrl %>../">返回上一级</a>
|
||||
|
||||
<a href="<%= pageUrl %>../upload" target="_blank">上传文件</a><br><br>
|
||||
您现在在「文件管理 > 文件对象管理」,以下是系统所有文件对象信息
|
||||
</p>
|
||||
<script>
|
||||
list({ tableElementId: "book-table" });
|
||||
|
||||
function list({ tableElementId }) {
|
||||
postRequest("/file/object/list", { token: localStorageUtils.getToken() })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data);
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
console.log(element);
|
||||
renderData.push({
|
||||
编号: `${element.id}`,
|
||||
关联文件: `<span class="overflow-omit" style="margin: 0 auto;">
|
||||
ID: ${element.fileId}
|
||||
<a href="<%= pageUrl %>../detail?id=${element.fileId}">查看</a>
|
||||
</span>`,
|
||||
链接: `<span class="overflow-omit" style="font-size: 12px; line-height: 1.2em; display: block;">
|
||||
${element.filePath}
|
||||
</span>`,
|
||||
密码: `<span class="overflow-omit" style="font-size: 12px; line-height: 1.2em; display: block;"><nobr>
|
||||
文件密码: ${element.filePwd}<br>
|
||||
提取码: ${element.fileShareCode}
|
||||
</nobr></span>`,
|
||||
存储介质: `<span class="overflow-omit" style="font-size: 12px; line-height: 1.2em; display: block;"><nobr>
|
||||
${element.storageMediumForDisplay}
|
||||
</nobr></span>`,
|
||||
状态: `${(element.uploadStatus ? element.uploadStatus : "<span style='color: grey; font-weight: bold;'>未知</span>")
|
||||
.replace("SUCCESS", "<span style='color: green; font-weight: bold;'>成功</span>")
|
||||
.replace("UPLOADING", "<span style='color: orange; font-weight: bold;'>正在上传</span>")
|
||||
.replace("NOT_EXIST", "<span style='color: red; font-weight: bold;'>不存在</span>")}`,
|
||||
管理: `<span span class="overflow-omit" style="margin: 0 auto;">
|
||||
<a href="javascript:refreshFileObjectStatus(${element.id});" style="${element.storageMedium == "QCLOUD_COS" ? "" : "display: none;"}">刷新状态</a>
|
||||
<a href="<%= pageUrl %>../get-share-url?id=${element.id}&fileId=${element.fileId}">修改</a>
|
||||
<a href="javascript:deleteFileObject(${element.id});">删除</a>
|
||||
</span >`,
|
||||
})
|
||||
});
|
||||
if (renderData.length == 0) {
|
||||
function htmlEncode(str) {
|
||||
// refer: https://stackoverflow.com/questions/4183801/escape-html-chracters
|
||||
var div = document.createElement('div');
|
||||
var txt = document.createTextNode(str);
|
||||
div.appendChild(txt);
|
||||
return div.innerHTML;
|
||||
}
|
||||
renderTable({ data: `暂无文件`, tableId: tableElementId, renderTableHead: true });
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
function refreshFileObjectStatus(fileObjectId) {
|
||||
postRequest("/file/object/refreshFileObjectStatus", { token: localStorageUtils.getToken(), fileObjectId: fileObjectId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
swal("刷新成功!");
|
||||
list({ tableElementId: "book-table" });
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件对象
|
||||
function deleteFileObject(deleteFileObjectId) {
|
||||
if (!confirm(`确认要删除编号为 ${deleteFileObjectId} 的文件对象吗?`)) return;
|
||||
|
||||
postRequest("/file/object/delete", { token: localStorageUtils.getToken(), id: deleteFileObjectId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data == "success") {
|
||||
list({ tableElementId: "book-table" });
|
||||
swal("删除成功!");
|
||||
} else {
|
||||
swal("删除失败!");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,236 +0,0 @@
|
||||
<div class="show-modify-or-add" style="text-align: center;">
|
||||
当前为:<span id="upload-type" style="font-weight: bold;">新增文件对象</span>
|
||||
</div>
|
||||
<div class="show-only-modify" style="text-align: center;">
|
||||
上传状态:<span id="upload-status"></span>
|
||||
</div>
|
||||
<script>
|
||||
// 如果传入了 id 那么就是修改文件对象,否则就是添加文件对象
|
||||
var params = getParams();
|
||||
var fileObjectId = params.id;
|
||||
var fileId = params.fileId;
|
||||
if (!fileId) {
|
||||
swal("未传入 fileId !").then(function () {
|
||||
history.go(-1);
|
||||
});
|
||||
}
|
||||
|
||||
var isModify = fileObjectId ? true : false;
|
||||
if (!isModify) {
|
||||
// 新增文件对象
|
||||
fileObjectId = 0;
|
||||
$(".show-only-modify").hide();
|
||||
$("#upload-type").html("新增文件对象");
|
||||
$("#upload-type").css("color", "green");
|
||||
} else {
|
||||
// 修改文件对象
|
||||
$("#upload-type").html("修改文件对象");
|
||||
$("#upload-type").css("color", "red");
|
||||
}
|
||||
|
||||
// 点击提交按钮
|
||||
function btnSubmitClick() {
|
||||
formSubmit({
|
||||
type: 'POST',
|
||||
url: '/file/object/detail',
|
||||
data: { id: fileObjectId },
|
||||
success: function (data) {
|
||||
console.log(data);
|
||||
swal(isModify ? "修改成功!" : "添加成功!").then(function () {
|
||||
if (params.referrer)
|
||||
location.replace(params.referrer);
|
||||
else
|
||||
history.go(-1);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是修改文件对象,则需要获取文件对象详情
|
||||
async function getFileObjectDetail(fileObjectId) {
|
||||
var responseData = await getRequest("/file/object/get", { id: fileObjectId });
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
return data;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`).then(function () {
|
||||
// 回到上一页
|
||||
history.go(-1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function getControlsProfile(getValidateUtils) {
|
||||
|
||||
// 修改文件对象: 获取文件对象详情
|
||||
var fileObjectDetail = {};
|
||||
if (isModify) {
|
||||
fileObjectDetail = await getFileObjectDetail(fileObjectId);
|
||||
} else {
|
||||
console.log("新增文件对象无需获取文件对象详情");
|
||||
}
|
||||
|
||||
var filePathDisabledAttr = {};
|
||||
var modifyDisabledAttr = {};
|
||||
if (fileObjectDetail.storageMediumForDisplay == "腾讯云对象存储") {
|
||||
filePathDisabledAttr.disabled = "true";
|
||||
if (isModify) {
|
||||
modifyDisabledAttr.disabled = "true";
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是修改文件对象,那么显示文件上传状态
|
||||
$("#upload-status").html(fileObjectDetail.uploadStatus || "");
|
||||
|
||||
return [
|
||||
// 必须设置 id, name
|
||||
// 只有设置了 id 才会使用 validate 校验取值
|
||||
{
|
||||
"tag": "select",
|
||||
"attr": {
|
||||
"id": "storageMedium",
|
||||
"name": "storageMedium",
|
||||
"value": params.disk || fileObjectDetail.storageMedium || "",
|
||||
...modifyDisabledAttr
|
||||
},
|
||||
"label": {
|
||||
"value": "存储位置",
|
||||
},
|
||||
"required": true,
|
||||
"children": [
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "QCLOUD_COS" },
|
||||
"innerHTML": "腾讯云对象存储",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "BAIDU_NETDISK" },
|
||||
"innerHTML": "百度网盘",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "ALIYUN_DRIVE" },
|
||||
"innerHTML": "阿里云盘",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "FEISHU_DRIVE" },
|
||||
"innerHTML": "飞书云文档",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "LANZOUYUN" },
|
||||
"innerHTML": "蓝奏云",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "QUQIYUN" },
|
||||
"innerHTML": "曲奇云盘",
|
||||
},
|
||||
{
|
||||
"tag": "option",
|
||||
"attr": { "value": "UNKNOWN_DRIVE" },
|
||||
"innerHTML": "其他",
|
||||
},
|
||||
],
|
||||
"innerHTML": "",
|
||||
"validate": (val) => {
|
||||
if (val == "QCLOUD_COS") {
|
||||
return {
|
||||
result: false,
|
||||
msg: "腾讯云对象存储请使用上传文件功能上传"
|
||||
};
|
||||
} else {
|
||||
return { result: true };
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "filePath",
|
||||
"name": "filePath",
|
||||
"placeholder": "文件对象链接或路径",
|
||||
"value": params.url || fileObjectDetail.filePath || "",
|
||||
...filePathDisabledAttr // 如果是对象存储,那么就不允许编辑
|
||||
},
|
||||
"label": {
|
||||
"value": "文件链接",
|
||||
},
|
||||
"required": true,
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.notEmptyAfterTrim("文件链接不能为空,请输入文件链接")
|
||||
.length(0, 150, "文件链接不能超过 150 个字符")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "fileId",
|
||||
"name": "fileId",
|
||||
"placeholder": "关联文件Id",
|
||||
"value": fileId || "",
|
||||
"disabled": true,
|
||||
},
|
||||
"label": {
|
||||
"value": "关联文件Id",
|
||||
},
|
||||
"required": true,
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notPositiveInteger("关联文件id需要为正整数")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "filePwd",
|
||||
"name": "filePwd",
|
||||
"placeholder": "文件密码",
|
||||
"value": fileObjectDetail.filePwd || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "文件密码",
|
||||
},
|
||||
"required": false,
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.isValid()
|
||||
},
|
||||
{
|
||||
"tag": "input",
|
||||
"attr": {
|
||||
"id": "fileShareCode",
|
||||
"name": "fileShareCode",
|
||||
"placeholder": "提取码",
|
||||
"value": params.pwd || fileObjectDetail.fileShareCode || "",
|
||||
},
|
||||
"label": {
|
||||
"value": "提取码",
|
||||
},
|
||||
"required": false,
|
||||
"innerHTML": "",
|
||||
"validate": (val) => getValidateUtils().setValue(val)
|
||||
.notString("传入的值为非字符串类型")
|
||||
.isValid()
|
||||
},
|
||||
// {
|
||||
// "tag": "span",
|
||||
// "attr": {
|
||||
// },
|
||||
// "innerHTML": fileObjectDetail.uploadStatus || "",
|
||||
// "label": {
|
||||
// "value": "上传状态",
|
||||
// },
|
||||
// "required": false,
|
||||
// },
|
||||
];
|
||||
}
|
||||
</script>
|
@@ -1,87 +0,0 @@
|
||||
<style>
|
||||
#sharecode_input {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#next-button {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
<input id="sharecode_input" placeholder="请在此处粘贴网盘分享链接">
|
||||
<input id="paste-button" value="点击粘贴" type="button" onclick="pasteText();">
|
||||
<input id="next-button" value="直接前往添加/修改" type="button" onclick="location.replace(redirectUrl);">
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
|
||||
// 文件对象 id (修改文件对象用)
|
||||
var id = requestParams["id"] ?? "";
|
||||
|
||||
// 文件 id
|
||||
var fileId = Number(requestParams["fileId"]) ?? "";
|
||||
if (fileId === "")
|
||||
history.go(-1);
|
||||
|
||||
console.log(id, fileId);
|
||||
</script>
|
||||
|
||||
<!-- 获取网盘分享链接 -->
|
||||
<script src="/assets/javascripts/dashboard/netdiskShareStringUtils.js"></script>
|
||||
<script>
|
||||
var redirectUrl = `<%= pageUrl %>../object-detail?id=${id}&fileId=${fileId}&referrer=${encodeURIComponent(document.referrer)}`
|
||||
$(document).ready(function () {
|
||||
$("#sharecode_input").on({
|
||||
// copy: function () {
|
||||
// alert('复制');
|
||||
// },
|
||||
paste: onPaste,
|
||||
// cut: function () {
|
||||
// alert('剪切');
|
||||
// }
|
||||
});
|
||||
|
||||
// 页面打开后默认选中输入框
|
||||
$("#sharecode_input").focus();
|
||||
});
|
||||
|
||||
// input 粘贴事件
|
||||
function onPaste() {
|
||||
// alert('粘贴');
|
||||
|
||||
// 直接获取的话,获取到的值还是粘贴之前的,所以这里使用了 setTimeout
|
||||
// console.log(this.value);
|
||||
setTimeout(function (val) {
|
||||
var inputValue = $("#sharecode_input").val();
|
||||
console.log("用户输入", inputValue);
|
||||
var result = getNetdiskShareDetails(inputValue);
|
||||
console.log(result);
|
||||
if (result && result.success) {
|
||||
redirectUrl += `&disk=${result.platform.name}&url=${encodeURIComponent(result.url)}&pwd=${result.pwd}`
|
||||
} else {
|
||||
if (!confirm("未检测到网盘分享链接,是否直接跳转?"))
|
||||
return;
|
||||
}
|
||||
// 跳转
|
||||
console.log("将要跳转", redirectUrl);
|
||||
location.replace(redirectUrl);
|
||||
}, 1)
|
||||
}
|
||||
|
||||
// 点击粘贴按钮
|
||||
function pasteText() {
|
||||
if (!navigator.clipboard) {
|
||||
swal("浏览器不支持读取剪切板,请手动粘贴!");
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.readText().then(text => {
|
||||
console.log("剪切板内容:" + text);
|
||||
$("#sharecode_input").val(text);
|
||||
onPaste();
|
||||
}).catch(err => {
|
||||
console.error('Failed to read clipboard contents: ', err);
|
||||
swal("读取剪切板失败,请手动粘贴!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,547 +0,0 @@
|
||||
<!--
|
||||
文件选择框
|
||||
refer: https://developer.mozilla.org/zh-CN/docs/web/api/file/using_files_from_web_applications
|
||||
-->
|
||||
<style>
|
||||
#dropbox {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#dropbox:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.lightgrey {
|
||||
background-color: lightgrey;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #39a705;
|
||||
color: #caf6b6;
|
||||
}
|
||||
|
||||
.grey {
|
||||
background-color: grey;
|
||||
color: #b5b5b5;
|
||||
}
|
||||
|
||||
.process-bar {
|
||||
background-color: #b5b5b5;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.process-bar-inner {
|
||||
background-color: #39a705;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.file-info-paragraph>span {
|
||||
background-color: lightpink;
|
||||
color: darkred;
|
||||
padding: 1px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/* 上传按钮样式 */
|
||||
#beginUpload {
|
||||
margin-top: 30px;
|
||||
padding: 5px 12px;
|
||||
transition: all 0.25s;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#beginUpload:hover {
|
||||
transform: scale(1.1);
|
||||
background-color: #39a705;
|
||||
color: #caf6b6;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#beginUpload[disabled]:hover {
|
||||
transform: initial;
|
||||
background-color: initial;
|
||||
color: initial;
|
||||
border-radius: initial;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
#fileAssociator {
|
||||
padding: 3px 12px;
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
<a href="<%= pageUrl %>../">返回文件管理</a>
|
||||
</p>
|
||||
<div id="dropbox" class="lightgrey">
|
||||
<p>
|
||||
点击此处或将文件拖入此处以选择文件<br>
|
||||
<!-- 文件个数: <span id="fileNum">0</span>; -->
|
||||
文件大小: <span id="fileSize">0</span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- <input type="file" id="input" multiple onchange="handleFiles(this.files)"> -->
|
||||
<input type="file" id="fileSelector" style="display: none;">
|
||||
<p style="font-size: 12px;">
|
||||
仅支持 Chrome 内核浏览器
|
||||
</p>
|
||||
<div id="file-detail-container" style="display: none;">
|
||||
<p style="text-align: left;" class="file-info-paragraph">
|
||||
文件名: <span id="file-name"></span><br>
|
||||
文件扩展名:<span id="file-ext"></span><br>
|
||||
文件名(不含扩展名):<span id="file-name-no-ext"></span><br>
|
||||
文件大小: <span id="file-size"></span><br>
|
||||
修改日期: <span id="file-lastModified"></span><br>
|
||||
文件SHA1: <span id="file-sha1"></span><br>
|
||||
</p>
|
||||
<p>计算文件哈希进度</p>
|
||||
<div id="processBar1" class="process-bar">
|
||||
<div id="processBar1Inner" class="process-bar-inner"></div>
|
||||
</div>
|
||||
<p>文件上传进度<br><span id="upload-speed"><!-- 上传速度 --></span></p>
|
||||
<div id="processBar2" class="process-bar">
|
||||
<div id="processBar2Inner" class="process-bar-inner"></div>
|
||||
</div>
|
||||
<p>关联文件<br><span style="font-size: 12px;">(仅可关联系统中未设置SHA1,或SHA1值相同的文件记录)</span></p>
|
||||
<select id="fileAssociator">
|
||||
<!-- <option value="0">新建文件</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<button id="beginUpload" disabled="true">开始上传</button>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" id="checkbox-auto-upload"><!-- checked -->
|
||||
<label for="checkbox-auto-upload" style="font-size: small;">连续上传(上传完成后留在本页)</label>
|
||||
</p>
|
||||
|
||||
<script src="/assets/lib/crypto-js/sha1.js"></script>
|
||||
<script>
|
||||
var file = null;
|
||||
var fileInfo = {
|
||||
fileName: "",
|
||||
fileSize: 0,
|
||||
fileType: "",
|
||||
lastModified: "",
|
||||
fileSha1: "",
|
||||
fileExt: "",
|
||||
fileNameWithoutExt: "",
|
||||
};
|
||||
|
||||
const inputElement = document.getElementById("fileSelector");
|
||||
inputElement.addEventListener("change", fileSelectorHandleFiles, false);
|
||||
|
||||
const beginUpload = document.getElementById("beginUpload");
|
||||
beginUpload.addEventListener("click", upload);
|
||||
|
||||
//##############################################
|
||||
// 选择文件
|
||||
//##############################################
|
||||
function fileSelectorHandleFiles() {
|
||||
const fileList = this.files; /* now you can work with the file list */
|
||||
// console.log(fileList);
|
||||
handleFiles(fileList);
|
||||
}
|
||||
async function handleFiles(fileList) {
|
||||
updateUI({ isFileEmpty: true });
|
||||
if (fileList.length === 0) {
|
||||
// 还未选择文件
|
||||
return;
|
||||
}
|
||||
if (fileList.length > 1) {
|
||||
swal("一次只能选择1个文件!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户选择的文件
|
||||
file = fileList[0];
|
||||
console.log(file);
|
||||
|
||||
// // 判断用户选择的文件是否为文件夹
|
||||
// if (await isFolder(file)) {
|
||||
// swal("不支持文件夹,请选择文件!");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 输出所选文件的总大小
|
||||
let nBytes = 0;
|
||||
for (let nFileId = 0; nFileId < fileList.length; nFileId++) {
|
||||
nBytes += fileList[nFileId].size;
|
||||
}
|
||||
let sOutput = nBytes + " bytes";
|
||||
// optional code for multiples approximation
|
||||
const aMultiples = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple] + " (" + nBytes + " bytes)";
|
||||
}
|
||||
|
||||
// document.getElementById("fileNum").innerHTML = fileList.length;
|
||||
document.getElementById("fileSize").innerHTML = sOutput;
|
||||
|
||||
updateUI({ isFileEmpty: false });
|
||||
|
||||
// 记录文件信息
|
||||
fileInfo.fileName = file.name;
|
||||
fileInfo.fileSize = file.size;
|
||||
fileInfo.fileType = file.type;
|
||||
fileInfo.lastModified = file.lastModified;
|
||||
// 获取文件扩展名:首先按照 . 拆分,然后删掉第一个元素(考虑无扩展名文件),再取出最后一个元素
|
||||
let fileNameSplit = file.name.split(".");
|
||||
fileNameSplit.shift();
|
||||
fileInfo.fileExt = fileNameSplit.pop() || "";
|
||||
// 获取文件名(不包含扩展名)
|
||||
fileInfo.fileNameWithoutExt = file.name.substr(0, (file.name.length + file.name.lastIndexOf('.')) % (file.name.length));
|
||||
|
||||
// 更新UI
|
||||
document.getElementById("file-name").innerHTML = fileInfo.fileName;
|
||||
document.getElementById("file-ext").innerHTML = fileInfo.fileExt;
|
||||
document.getElementById("file-name-no-ext").innerHTML = fileInfo.fileNameWithoutExt;
|
||||
document.getElementById("file-size").innerHTML = sOutput;
|
||||
document.getElementById("file-lastModified").innerHTML = new Date(fileInfo.lastModified).toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
||||
|
||||
// 计算文件哈希
|
||||
let sha1 = await sha1File(file, (file) => {
|
||||
document.getElementById("processBar1Inner").style.width = `${file.sha1_progress}%`;
|
||||
// console.log("计算文件哈希 进度", file.sha1_progress);
|
||||
});
|
||||
fileInfo.fileSha1 = sha1;
|
||||
document.getElementById("file-sha1").innerHTML = fileInfo.fileSha1;
|
||||
|
||||
// 获取关联文件下拉框列表并渲染
|
||||
getFileAssociatorList(sha1);
|
||||
|
||||
// 完成
|
||||
console.log(fileInfo);
|
||||
}
|
||||
|
||||
// dropbox 点击时触发 file 的 click 事件
|
||||
const fileSelect = document.getElementById("dropbox");
|
||||
fileSelect.addEventListener("click", function (e) {
|
||||
if (inputElement) {
|
||||
inputElement.click();
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
//##############################################
|
||||
// 使用拖放来选择文件
|
||||
//##############################################
|
||||
let dropbox = document.getElementById("dropbox");
|
||||
dropbox.addEventListener("dragenter", dragenter, false);
|
||||
dropbox.addEventListener("dragleave", dragleave, false);
|
||||
dropbox.addEventListener("dragover", dragover, false);
|
||||
dropbox.addEventListener("drop", drop, false);
|
||||
|
||||
var lastenter = null; // refer: https://www.jianshu.com/p/f96b754032a1
|
||||
|
||||
function dragenter(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
lastenter = e.target; // 记录最后进入的元素
|
||||
}
|
||||
|
||||
function dragleave(e) {
|
||||
if (lastenter == e.target)
|
||||
dropbox.classList.remove('grey');
|
||||
}
|
||||
|
||||
function dragover(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropbox.classList.add('grey');
|
||||
}
|
||||
|
||||
function drop(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropbox.classList.remove('grey');
|
||||
|
||||
var dt = e.dataTransfer;
|
||||
var files = dt.files;
|
||||
|
||||
console.log("dt", dt.items);
|
||||
console.log("dt", dt.items[0]);
|
||||
console.log("dt", dt.items[0].webkitGetAsEntry());
|
||||
|
||||
updateUI({ isFileEmpty: true });
|
||||
if (dt.items.length > 1) {
|
||||
swal("一次只能选择1个文件!");
|
||||
return;
|
||||
}
|
||||
if (dt.items[0].webkitGetAsEntry().isDirectory) {
|
||||
swal("不支持文件夹,请选择文件!");
|
||||
return;
|
||||
}
|
||||
handleFiles(files);
|
||||
}
|
||||
|
||||
|
||||
// //##############################################
|
||||
// // 判断拖入的文件是否是文件夹 (非 Chrome 内核浏览器逻辑)
|
||||
// // 读取文件的第一个字符,如果能够读取成功,那么就是文件,否则就是文件夹
|
||||
// // refer: https://segmentfault.com/a/1190000013298317
|
||||
// //##############################################
|
||||
// async function isFolder(file) {
|
||||
// return await new Promise((resolve, reject) => {
|
||||
// try {
|
||||
// var fileReader = new FileReader();
|
||||
|
||||
// fileReader.addEventListener('load', function (e) {
|
||||
// console.log(e, 'load');
|
||||
// resolve(false);
|
||||
// }, false);
|
||||
|
||||
// fileReader.addEventListener('error', function (e) {
|
||||
// console.log(e, 'error,不可以上传文件夹');
|
||||
// resolve(true);
|
||||
// }, false);
|
||||
|
||||
// fileReader.readAsDataURL(file.slice(0, 3));
|
||||
// } catch (e) {
|
||||
// console.log(e, 'catch error,不可以上传文件夹');
|
||||
// resolve(true);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
//##############################################
|
||||
// 更新界面显示
|
||||
//##############################################
|
||||
function updateUI({ isFileEmpty }) {
|
||||
if (isFileEmpty) {
|
||||
// 未选择文件
|
||||
this.outerHTML = this.outerHTML; // 清除选择的文件
|
||||
file = null;
|
||||
fileInfo = {};
|
||||
document.getElementById("fileSize").innerHTML = "0";
|
||||
document.getElementById("processBar1Inner").style.width = 0;
|
||||
document.getElementById("processBar2Inner").style.width = 0;
|
||||
document.getElementById("file-name").innerHTML = "";
|
||||
document.getElementById("file-ext").innerHTML = "";
|
||||
document.getElementById("file-name-no-ext").innerHTML = "";
|
||||
document.getElementById("file-size").innerHTML = "";
|
||||
document.getElementById("file-sha1").innerHTML = "";
|
||||
dropbox.classList.remove("green");
|
||||
beginUpload.setAttribute("disabled", "true");
|
||||
document.getElementById("file-detail-container").style.display = "none";
|
||||
} else {
|
||||
// 已选择文件
|
||||
dropbox.classList.add("green");
|
||||
beginUpload.removeAttribute("disabled");
|
||||
document.getElementById("file-detail-container").style.display = "";
|
||||
|
||||
// 隐藏文件选择器,避免多次选择文件造成后面计算哈希及上传文件出现混乱
|
||||
$("#dropbox").css("transition", "0.3s");
|
||||
$("#dropbox").css("height", 0);
|
||||
$("#dropbox").css("opacity", 0);
|
||||
setTimeout(function () {
|
||||
$("#dropbox").css("display", "none");
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
//##############################################
|
||||
// 获取预授权URL
|
||||
//##############################################
|
||||
function upload() {
|
||||
if (!file) {
|
||||
swal("您还未选择文件!");
|
||||
return;
|
||||
}
|
||||
var postParams = {
|
||||
token: localStorageUtils.getToken(),
|
||||
expireMinute: 30,
|
||||
fileName: fileInfo.fileNameWithoutExt,
|
||||
fileSize: fileInfo.fileSize,
|
||||
// fileType: fileInfo.fileType,
|
||||
lastModified: fileInfo.lastModified,
|
||||
fileSha1: fileInfo.fileSha1,
|
||||
fileExt: fileInfo.fileExt,
|
||||
fileId: $("#fileAssociator").val() // 关联的文件ID,创建新文件则为0
|
||||
};
|
||||
console.log(postParams);
|
||||
// 获取预授权URL
|
||||
postRequest("/file/object/cos/put", postParams)
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log("data", data);
|
||||
// 取得预授权URL,使用该URL进行文件上传
|
||||
uploadFile(file, data.url, data.fileId, data.fileObjectId);
|
||||
} else {
|
||||
if (data.errCode == "60001") {
|
||||
// 文件已存在于对象存储中
|
||||
console.log(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
|
||||
// 再次发送请求,查询这个已存在文件的 fileId
|
||||
postRequest("/file/getFileByHash", { token: localStorageUtils.getToken(), fileSha1: fileInfo.fileSha1 })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data) {
|
||||
// 查询到之后,询问用户是否跳转到文件详情页
|
||||
var isRedirect = confirm(`文件已存在,是否前往查看详情?\n(文件ID: ${data.id})`);
|
||||
if (isRedirect)
|
||||
location.href = `<%= pageUrl %>../detail?fileId=${data.id}`;
|
||||
} else {
|
||||
// 对象存储中存在该文件,但是系统数据库中找不到
|
||||
swal("出错啦!该文件已存在于腾讯云对象存储中,但是系统中不存在")
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
//##############################################
|
||||
//传入预授权 URL ,将文件上传到这个地址
|
||||
//##############################################
|
||||
function uploadFile(file, preSignedUrl, fileId, fileObjectId) {
|
||||
// refer: https://cloud.tencent.com/document/product/436/35651
|
||||
|
||||
// 存储上次更新上传进度和速度时,时间和已上传字节 (用于计算上传进度和上传速度)
|
||||
var sTime = new Date().getTime(), sLoaded = 0;
|
||||
|
||||
// 获取到 Url 后,前端可以这样 ajax 上传
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', preSignedUrl, true);
|
||||
xhr.upload.onprogress = function (e) {
|
||||
// e.lengthComputable是一个布尔值,表示当前上传的资源是否有可计算的长度
|
||||
if (e.lengthComputable) {
|
||||
// 计算出上传的进度
|
||||
// e.loaded 已传输的字节
|
||||
// e.total 需传输的总字节
|
||||
var procentComplete = Math.ceil((e.loaded / e.total) * 100);
|
||||
|
||||
// 距离上次更新界面显示经过的毫秒数 & 上传的文件大小
|
||||
let timeSpan = new Date().getTime() - sTime;
|
||||
let loadedSpan = e.loaded - sLoaded;
|
||||
|
||||
// 这段时间内的上传速度
|
||||
let speed = loadedSpan * 1000 / timeSpan; // 1000: 毫秒 转 秒
|
||||
|
||||
// 更新 sTime, sLoaded
|
||||
sTime = new Date().getTime();
|
||||
sLoaded = e.loaded;
|
||||
|
||||
// 输出所选文件的总大小
|
||||
let nBytes = speed;
|
||||
let sOutput = nBytes + " bytes/s";
|
||||
// optional code for multiples approximation
|
||||
const aMultiples = ["KiB/s", "MiB/s", "GiB/s", "TiB/s", "PiB/s", "EiB/s", "ZiB/s", "YiB/s"];
|
||||
for (nMultiple = 0, nApprox = nBytes / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
|
||||
sOutput = nApprox.toFixed(2) + " " + aMultiples[nMultiple];
|
||||
}
|
||||
|
||||
// 更新界面显示
|
||||
document.getElementById("processBar2Inner").style.width = `${procentComplete}%`;
|
||||
$("#upload-speed").html(`上传进度: ${procentComplete.toFixed(0)}%,上传速度:${sOutput}`);
|
||||
console.log("上传进度", procentComplete);
|
||||
}
|
||||
};
|
||||
xhr.onload = function (e) {
|
||||
console.log('上传成功', xhr.status, xhr.statusText);
|
||||
// 等待进度条走到 100% 否则小文件进度条还没有走完就提示上传完成会让人感觉有点奇怪
|
||||
setTimeout(function () {
|
||||
// 上传成功触发一次 “刷新文件对象上传状态”,避免因为云函数回调不成功导致文件对象上传状态没有及时更新
|
||||
refreshFileObjectStatus(fileObjectId, function () {
|
||||
swal("上传成功!").then(function () {
|
||||
if ($("#checkbox-auto-upload").is(":checked")) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
// location.href = "<%= pageUrl %>../";
|
||||
location.href = "<%= pageUrl %>../detail?id=" + fileId;
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
xhr.onerror = function (e) {
|
||||
swal("上传失败,请检查网络连接并重试!").then(function () {
|
||||
location.reload();
|
||||
});
|
||||
console.log('上传出错', xhr.status, xhr.statusText);
|
||||
};
|
||||
xhr.send(file); // file 是要上传的文件对象
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 关联文件下拉框列表
|
||||
function getFileAssociatorList(fileSha1) {
|
||||
var fileAssociator = document.getElementById("fileAssociator");
|
||||
// 下拉框列表
|
||||
postRequest("/file/list/MatchfileHashWithNullValue", { token: localStorageUtils.getToken(), fileSha1: fileSha1 })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data);
|
||||
// 数据进行转换
|
||||
var optionHTML = "";
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
const element = data[i];
|
||||
optionHTML += `<option value="${element.id}">[${element.id}] ${element.fileName}.${element.fileExt}</option>`;
|
||||
}
|
||||
// 渲染到下拉框内
|
||||
// fileAssociator.innerHTML += optionHTML;
|
||||
fileAssociator.innerHTML = `<option value="0">新建文件</option>` + optionHTML;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 刷新文件对象上传状态
|
||||
async function refreshFileObjectStatus(fileObjectId, callback) {
|
||||
postRequest("/file/object/refreshFileObjectStatus", { token: localStorageUtils.getToken(), fileObjectId: fileObjectId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data);
|
||||
if (callback)
|
||||
callback();
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= title %></h1>
|
||||
<div id="container">
|
||||
<% if ( pageTemplate != "" ) { %>
|
||||
<!-- 引入对应页面渲染配置 -->
|
||||
<%- include(pageTemplate); %>
|
||||
<% } %>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,91 +0,0 @@
|
||||
<style>
|
||||
#account-cancellation-info>p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
color: #fff;
|
||||
background-color: #d9534f;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
color: #fff;
|
||||
background-color: #c9302c;
|
||||
border-color: #ac2925;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#account-cancellation-password {
|
||||
margin: 20px auto;
|
||||
width: min(50%, 600px);
|
||||
padding: 3px 5px;
|
||||
font-size: large;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<!-- 注销账号 -->
|
||||
<p style="font-size: 0.5em;">注销账号非退出登录,退出登录请点击右上角“<b>退出登录</b>”按钮</p>
|
||||
<div id="account-cancellation-info" class="hide">
|
||||
<h3>注销账号须知</h3>
|
||||
<p>注销账号后,您将无法登录本网站,并且不能再次注册账号。</p>
|
||||
<p>注销账号后,您的所有账号信息将被删除,包括您的账号名、密码、账号绑定信息等。</p>
|
||||
<p>注销账号后,您的账号信息将被永久删除,并且不可恢复。</p>
|
||||
<h3>注销账号流程</h3>
|
||||
<p>1. 仔细阅读以上须知,并手动将您账号中有用信息保存下来,账号一旦注销,数据永久删除,无法找回。</p>
|
||||
<p>2. 输入您的帐号密码并点击下方按钮,注销即刻完成。</p>
|
||||
</div>
|
||||
<div id="account-cancellation-buttons">
|
||||
<button class="btn-danger" id="btn-account-cancellation">注销账号</button>
|
||||
<input class="hide" type="password" id="account-cancellation-password" placeholder="请输入您的密码"
|
||||
autocomplete="new-password" />
|
||||
<button class="btn-danger hide" id="btn-account-cancellation-confirm" style="font-size: large;">
|
||||
我已阅读以上须知,并知晓账号注销后果,愿意放弃账号内所有数据及权益,确认注销账号
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#btn-account-cancellation").click(function () {
|
||||
$("#account-cancellation-info").removeClass("hide");
|
||||
$("#btn-account-cancellation").addClass("hide");
|
||||
$("#account-cancellation-password").removeClass("hide");
|
||||
$("#btn-account-cancellation-confirm").removeClass("hide");
|
||||
});
|
||||
$("#btn-account-cancellation-confirm").click(function () {
|
||||
var accountCancellationPassword = $("#account-cancellation-password").val();
|
||||
if (accountCancellationPassword == "") {
|
||||
swal("请输入您的密码");
|
||||
return;
|
||||
}
|
||||
postRequest("/user/cancelAccount", { token: localStorageUtils.getToken(), password: accountCancellationPassword })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data == "success") {
|
||||
swal("注销成功!").then(function () {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
swal("出错啦,刷新页面重新试试吧");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
$("#account-cancellation-password").val("");
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
});
|
||||
</script>
|
@@ -1,14 +0,0 @@
|
||||
|
||||
<% if (group == "admin") {%>
|
||||
<div class="footer" style="margin-top: 10vh;">
|
||||
<hr>
|
||||
<p>
|
||||
<a href="/status">网站状态检测</a>
|
||||
</p>
|
||||
</div>
|
||||
<%} else {%>
|
||||
<div class="footer" style="margin-top: 10vh;">
|
||||
<hr>
|
||||
<p>书栖网</p>
|
||||
</div>
|
||||
<%}%>
|
@@ -1,71 +0,0 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<title><%= htmlTitle %></title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/stylesheets/style.css">
|
||||
<script src="/assets/lib/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="/assets/lib/axios/0.26.1/axios.min.js"></script>
|
||||
|
||||
<!-- refer: https://www.sweetalert.cn/ -->
|
||||
<script src="/assets/lib/sweetalert/2.1.2/sweetalert.min.js"></script>
|
||||
|
||||
<script src="/assets/javascripts/httpRequestUtils.js"></script>
|
||||
<script src="/assets/javascripts/localStorageUtils.js"></script>
|
||||
<script>
|
||||
// API地址
|
||||
const APIHOST = '<%= global.site.api.prefix %>';
|
||||
axios.defaults.baseURL = APIHOST;
|
||||
</script>
|
||||
<% if (group == "admin") {%>
|
||||
<script>
|
||||
if(localStorageUtils.getIsUser()) {
|
||||
// 是普通用户,跳转到普通用户后台页面
|
||||
window.location.href = "/dashboard/user/index";
|
||||
}
|
||||
</script>
|
||||
<%} else {%>
|
||||
<script>
|
||||
if(localStorageUtils.getIsAdmin()) {
|
||||
// 是管理员用户,跳转到管理员用户后台页面
|
||||
window.location.href = "/dashboard/admin/index";
|
||||
}
|
||||
</script>
|
||||
<%}%>
|
||||
<script>
|
||||
function getUserStatus() {
|
||||
localStorageUtils.checkLocalStorage();
|
||||
|
||||
if(!localStorageUtils.getLoginStatus()) {
|
||||
localStorageUtils.userLogout();
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
postRequest("/user/getUserStatus", { token: localStorageUtils.getToken() })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data) {
|
||||
document.getElementById("nickname").innerHTML = "当前登录用户:" + data.nickname;
|
||||
} else {
|
||||
window.location.href = "/login";
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`).then(function () {
|
||||
if(data.errCode == "20004") { // 登录过期
|
||||
localStorageUtils.userLogout();
|
||||
window.location.href = "/login?redirect=" + encodeURIComponent(location.pathname + location.search);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
getUserStatus();
|
||||
</script>
|
@@ -1,151 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<% htmlTitle = "选择书籍" %>
|
||||
<%- include("./header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 92vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#book-table {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
line-height: 2.3em;
|
||||
}
|
||||
|
||||
table, tr, th, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
tr {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tr a {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 渲染表格 -->
|
||||
<script src="/assets/javascripts/renderTable.js"></script>
|
||||
<main class="main">
|
||||
<input id="searchInput" type="text" />
|
||||
<input id="searchButton" type="button" value="搜索" />
|
||||
<table id="book-table"></table>
|
||||
</div>
|
||||
<button onclick="closeWindow();" style="position: fixed; right: 0; top: 0;">×</button>
|
||||
</main>
|
||||
<!-- 搜索书籍 -->
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
var keyword = (requestParams["keyword"] || "").trim();
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: null,
|
||||
categoryId: null
|
||||
});
|
||||
|
||||
$("#searchButton").click(function () {
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: $("#searchInput").val(),
|
||||
categoryId: null
|
||||
});
|
||||
});
|
||||
|
||||
function search({ tableElementId = "", searchText = "", categoryId = 0 }) {
|
||||
getRequest("/book/search", { bookName: searchText, categoryId: categoryId })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data)
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
var mainDivWidth = 96/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
var columnWidth = [20, 15, 10, 15, 5, 35];
|
||||
renderData.push({
|
||||
编号: `${element.id}`,
|
||||
管理: `<span class="overflow-omit" style="max-width: ${columnWidth[5] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
<a href="javascript:selectItem(${element.id});">选择</a>
|
||||
</span>`,
|
||||
书名: `<a target="_blank" href="/book?id=${element.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.bookName}
|
||||
</span>
|
||||
</a>`,
|
||||
分类: `<a target="_blank" href="/category?id=${element.category.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.category.name}
|
||||
</span>
|
||||
</a>`,
|
||||
作者: `${element.author}`,
|
||||
语言: `<span class="overflow-omit" style="max-width: ${columnWidth[2] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.language}
|
||||
</span>`,
|
||||
出版社: `<span class="overflow-omit" style="max-width: ${columnWidth[3] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.publishingHouse}
|
||||
</span>`,
|
||||
来源: `<span class="overflow-omit" style="max-width: ${columnWidth[4] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.publishingHouse}
|
||||
</span>`,
|
||||
})
|
||||
});
|
||||
|
||||
if (renderData.length == 0) {
|
||||
console.log("没有搜索到相关书籍");
|
||||
function htmlEncode(str) {
|
||||
// refer: https://stackoverflow.com/questions/4183801/escape-html-chracters
|
||||
var div = document.createElement('div');
|
||||
var txt = document.createTextNode(str);
|
||||
div.appendChild(txt);
|
||||
return div.innerHTML;
|
||||
}
|
||||
if (searchText && searchText != "") {
|
||||
//
|
||||
renderTable({ data: `没有搜索到与 <span style="color: red;">${htmlEncode(searchText)}</span> 相关的书籍,请换个关键词再试试吧`, tableId: tableElementId, renderTableHead: true });
|
||||
} else if (categoryId && categoryId != 0) {
|
||||
//
|
||||
renderTable({ data: `该分类下暂无电子书`, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
|
||||
function selectItem(id) {
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
id: id,
|
||||
iframe: "book-selector"
|
||||
}), "*");
|
||||
}
|
||||
|
||||
function closeWindow() {
|
||||
window.parent.postMessage(JSON.stringify({
|
||||
id: null,
|
||||
iframe: "book-selector"
|
||||
}), "*");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,17 +0,0 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
|
||||
<title><%= htmlTitle %></title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/stylesheets/style.css">
|
||||
<script src="/assets/lib/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="/assets/lib/axios/0.26.1/axios.min.js"></script>
|
||||
|
||||
<script src="/assets/javascripts/httpRequestUtils.js"></script>
|
||||
<script src="/assets/javascripts/localStorageUtils.js"></script>
|
||||
<script>
|
||||
// API地址
|
||||
const APIHOST = '<%= global.site.api.prefix %>';
|
||||
axios.defaults.baseURL = APIHOST;
|
||||
</script>
|
@@ -1,82 +0,0 @@
|
||||
<style>
|
||||
#btn-logout {
|
||||
color: grey;
|
||||
vertical-align: middle;
|
||||
float: right;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
#btn-logout:hover {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
color: rgb(176, 176, 176);
|
||||
vertical-align: middle;
|
||||
float: right;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="navbar-grid">
|
||||
<div class="grid-item"></div>
|
||||
<div class="grid-item" style="grid-column-start: span 2;">
|
||||
<h1 style="display: inline; vertical-align: middle; margin-right: 20px; cursor: pointer;" onclick="window.open('/');">
|
||||
<img src="/assets/image/svg/home.svg" style="width: 1em; height: 1em; transform: translateY(2px);" title="返回首页">
|
||||
</h1>
|
||||
<% Object.keys(dashboardPage).forEach(item => { %>
|
||||
<a href="/dashboard/<%= group %>/<%= item %>" style="vertical-align: middle;"><%= dashboardPage[item].title %></a>
|
||||
<% }); %>
|
||||
<a id="btn-logout" href="javascript:logout();">退出登录</a>
|
||||
<span id="nickname"><!-- 用户昵称 --></span>
|
||||
</div>
|
||||
<div class="grid-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function logout() {
|
||||
postRequest("/user/logout", { token: localStorageUtils.getToken() })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data) {
|
||||
localStorageUtils.userLogout();
|
||||
location.href = "/login";
|
||||
} else {
|
||||
swal("退出登录失败");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
var choice = confirm("服务器连接失败,无法正常退出登录,是否要强行退出登录?");
|
||||
if (choice) {
|
||||
localStorageUtils.userLogout();
|
||||
location.href = "/login";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// 导航栏中突出当前页面
|
||||
var group = '<%= group %>';
|
||||
var page = '<%= page %>';
|
||||
console.log(group, page);
|
||||
// document.getElementsByTagName("a").asArray().forEach(element => {
|
||||
$("a").toArray().forEach(element => {
|
||||
var linkRoute = element.href.split('/').filter(s=>!!s);
|
||||
if(linkRoute.length > 2) {
|
||||
var linkGroup = linkRoute[linkRoute.length-2];
|
||||
var linkPage = linkRoute[linkRoute.length-1];
|
||||
// console.log(element, linkGroup, linkPage);
|
||||
if(page == linkPage) {
|
||||
$(element).addClass("active");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
@@ -1,44 +0,0 @@
|
||||
<style>
|
||||
.btn-third-party {
|
||||
border: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-third-party:hover {
|
||||
transform: scale(1.2) rotate(-6deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="color: #ebebeb;">
|
||||
<img class="btn-third-party" id="btn_gitee" src="/assets/image/svg/third-party-login/gitee.svg" onclick="thirdPartyLogin('gitee')" alt="Gitee" /> |
|
||||
<img class="btn-third-party" id="btn_oschina" src="/assets/image/svg/third-party-login/oschina.svg" onclick="thirdPartyLogin('OSCHINA')" alt="OSCHINA" /> |
|
||||
<img class="btn-third-party" id="btn_feishu" src="/assets/image/svg/third-party-login/feishu.svg" onclick="thirdPartyLogin('feishu')" alt="飞书" /> |
|
||||
<img class="btn-third-party" id="btn_github" src="/assets/image/svg/third-party-login/github.svg" onclick="thirdPartyLogin('GitHub')" alt="GitHub (不稳定)" /> |
|
||||
<img class="btn-third-party" id="btn_qq" src="/assets/image/svg/third-party-login/qq.svg" onclick="thirdPartyLogin('qq')" alt="QQ (不稳定)" />
|
||||
</div>
|
||||
<script>
|
||||
// 第三方授权登录逻辑
|
||||
function thirdPartyLogin(platform) {
|
||||
getRequest("/third-party/login", { platform: platform })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
location.href = data;
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,67 +0,0 @@
|
||||
<div>
|
||||
<h3>绑定第三方账号</h3>
|
||||
<%- include("./third-party-login-button.html"); %>
|
||||
<div id="withdraw-container" style="display: none;">
|
||||
<h3>取消第三方账号绑定</h3>
|
||||
<div id="withdraw-buttons"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function thirdPartyWithdraw(platform) {
|
||||
postRequest("/third-party/withdrawThirdPartyBings", { token: localStorageUtils.getToken(), platform: platform })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data == "success") {
|
||||
swal("取消绑定成功!").then(function () {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
swal("出错啦,刷新页面重新试试吧");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`).then(function () {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
if (localStorageUtils.getLoginStatus() != null) {
|
||||
getRequest("/third-party/getBindingStatus", { token: localStorageUtils.getToken() })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data) {
|
||||
$("#withdraw-container").show();
|
||||
data.forEach(platformName => {
|
||||
// 隐藏绑定按钮
|
||||
var btn = $(`#btn_${platformName.toLowerCase()}`);
|
||||
var platformChineseName = btn.html();
|
||||
btn.attr("disabled", true);
|
||||
btn.html(platformChineseName + "(已绑定)");
|
||||
|
||||
// 添加取消绑定按钮
|
||||
var withdrawBtn = $(`<button class="btn-third-party" id="btn_${platformName.toLowerCase()}_withdraw" onclick="thirdPartyWithdraw('${platformName}')">取消绑定${platformChineseName}</button>`);
|
||||
$("#withdraw-buttons").append(withdrawBtn);
|
||||
$("#withdraw-buttons").append(" ");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$(".btn-third-party").hide();
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,158 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
#container-controls {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 20px;
|
||||
place-items: center right;
|
||||
}
|
||||
|
||||
#container-controls>.form-labels {
|
||||
font-size: 1.05em;
|
||||
font-family: cursive;
|
||||
}
|
||||
|
||||
#container-controls>.form-elements {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#container-submit {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 0 30%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= title %></h1>
|
||||
<% if (typeof(subpage) !== "undefined") { %>
|
||||
<p><a href="<%= pageUrl %>../">返回上一级</a></p>
|
||||
<% } %>
|
||||
<div id="container">
|
||||
<div id="container-controls">页面加载中,请稍候 ...</div>
|
||||
<div id="container-submit">
|
||||
<button class="btn" id="btn-submit">提交</button>
|
||||
<button class="btn" id="btn-clear" disabled="true">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 验证组件用户输入值 -->
|
||||
<script src="/assets/javascripts/dashboard/getValidateUtils.js"></script>
|
||||
<!-- 渲染组件 -->
|
||||
<script src="/assets/javascripts/dashboard/renderFormControls.js"></script>
|
||||
<% if ( pageTemplate != "" ) { %>
|
||||
<!-- 引入对应页面渲染配置 -->
|
||||
<%- include(pageTemplate); %>
|
||||
<% } %>
|
||||
<!-- 进行渲染 -->
|
||||
<script>
|
||||
var group = '<%= group %>';
|
||||
var page = '<%= page %>';
|
||||
var title = '<%= title %>';
|
||||
var controlsProfile = {};
|
||||
|
||||
async function renderFormControlsFunc() {
|
||||
// 获取将要渲染的 Controls
|
||||
controlsProfile = await getControlsProfile(getValidateUtils);
|
||||
|
||||
// 渲染控件
|
||||
var formControls = renderFormControls({ Controls: controlsProfile });
|
||||
console.log(formControls);
|
||||
|
||||
// 将控件填充到网页上
|
||||
var containerControls = document.getElementById('container-controls');
|
||||
containerControls.innerHTML = "";
|
||||
formControls.forEach(function (item) {
|
||||
containerControls.appendChild(item.label);
|
||||
containerControls.appendChild(item.control);
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染控件
|
||||
renderFormControlsFunc();
|
||||
|
||||
// 提交表单事件
|
||||
function formSubmit({
|
||||
type = 'POST',
|
||||
url = '',
|
||||
data = {},
|
||||
success = (response) => { console.log(response) }
|
||||
}) {
|
||||
var data = data || {};
|
||||
for (var i = 0; i < controlsProfile.length; i++) {
|
||||
const controlsProfileItem = controlsProfile[i];
|
||||
var control = document.getElementById(controlsProfileItem.attr.id);
|
||||
// 判断 control 是否为空
|
||||
if (!control) {
|
||||
swal("控件不存在:" + controlsProfileItem.attr.id);
|
||||
return;
|
||||
}
|
||||
var name = control.name;
|
||||
var value = control.value;
|
||||
// console.log("name:", name, "value:", value, "control:", control);
|
||||
var validateResult = controlsProfileItem.validate(value);
|
||||
if (validateResult.result) {
|
||||
data[name] = value;
|
||||
} else {
|
||||
swal(validateResult.msg).then(function () {
|
||||
control.focus();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// var controls = document.getElementsByClassName('form-elements');
|
||||
// for (var i = 0; i < controls.length; i++) {
|
||||
// var control = controls[i];
|
||||
// var name = control.name;
|
||||
// var value = control.value;
|
||||
// data[name] = value;
|
||||
// }
|
||||
// 添加管理员 token 信息
|
||||
data['token'] = localStorageUtils.getToken();
|
||||
console.log(data);
|
||||
|
||||
postRequest(url, data)
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
success(data);
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`).then(function () {
|
||||
if (data.errCode == "20004") {
|
||||
// 登录过期
|
||||
localStorageUtils.userLogout();
|
||||
location.href = "/login?redirect=" + encodeURIComponent(location.pathname + location.search);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
$("#favorties-button").css("visibility", "visible");
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定提交按钮事件
|
||||
$("#btn-submit").click(btnSubmitClick);
|
||||
</script>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
place-items: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.mainNav {
|
||||
vertical-align: middle;
|
||||
margin: 0 8px;
|
||||
|
||||
transition: 0.26s;
|
||||
}
|
||||
.mainNav:hover {
|
||||
color: #fff;
|
||||
background-color: black;
|
||||
padding: 5px 8px;
|
||||
border-radius: 4px;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1>
|
||||
<%= title %>
|
||||
</h1>
|
||||
<div id="container">
|
||||
<% Object.keys(dashboardPage).forEach(item => { %>
|
||||
<% if (item.indexOf("index") == -1) { /* 跳过 仪表盘 */ %>
|
||||
<a class="mainNav" href="./<%= item %>"><%= dashboardPage[item].title %></a>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -1,52 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 92vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#book-table {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
line-height: 2.3em;
|
||||
}
|
||||
|
||||
table, tr, th, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
tr {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tr a {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 渲染表格 -->
|
||||
<script src="/assets/javascripts/renderTable.js"></script>
|
||||
<main class="main">
|
||||
<h1><%= title %></h1>
|
||||
<div id="container">
|
||||
<% if ( pageTemplate != "" ) { %>
|
||||
<!-- 引入对应页面渲染配置 -->
|
||||
<%- include(pageTemplate); %>
|
||||
<% } %>
|
||||
<table id="book-table"></table>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,7 +0,0 @@
|
||||
<div>
|
||||
<h2>第三方账号管理</h2>
|
||||
<%- include("../component/third-party-manage.html"); %>
|
||||
<!-- <hr>
|
||||
<h2>注销账号</h2>
|
||||
<%- include("../component/account-cancellation.html"); %> -->
|
||||
</div>
|
@@ -1,105 +0,0 @@
|
||||
<!-- 搜索书籍 -->
|
||||
<script>
|
||||
search({
|
||||
tableElementId: "book-table",
|
||||
searchText: null,
|
||||
categoryId: null
|
||||
});
|
||||
|
||||
function search({ tableElementId = "" }) {
|
||||
postRequest("/book/getFavoritesList", { token: localStorageUtils.getToken() })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
// console.log(data)
|
||||
|
||||
// 数据进行转换
|
||||
var renderData = [];
|
||||
data.forEach(element => {
|
||||
var mainDivWidth = 96/*vw*/; // 定义div的宽度(用于计算表格中的数据的显示长度)
|
||||
var columnWidth = [20, 15, 10, 35];
|
||||
renderData.push({
|
||||
编号: `${element.id}`,
|
||||
书名: ` <a target="_blank" href="/book?id=${element.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[0] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.bookName}
|
||||
</span>
|
||||
</a>`,
|
||||
分类: ` <a target="_blank" href="/category?id=${element.category.id}">
|
||||
<span class="overflow-omit" style="max-width: ${columnWidth[1] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.category.name}
|
||||
</span>
|
||||
</a>`,
|
||||
作者: `${element.author}`,
|
||||
语言: `<span class="overflow-omit" style="max-width: ${columnWidth[2] * mainDivWidth / 100}vw; max-height: 2em; margin: 0 auto;">
|
||||
${element.language}
|
||||
</span>`,
|
||||
收藏: `<span style="display: block; width: 80px; max-height: 2em; margin: 0 auto;">
|
||||
<a id="favorites_button_${element.id}" href="javascript:toggleFavorites(false, ${element.id});">取消收藏</a>
|
||||
</span>`,
|
||||
})
|
||||
});
|
||||
|
||||
if (renderData.length == 0) {
|
||||
console.log("没有搜索到相关书籍");
|
||||
renderTable({ data: `在您的收藏夹没有找到电子书噢,快去收藏一本吧`, tableId: tableElementId, renderTableHead: true });
|
||||
} else {
|
||||
renderTable({ data: renderData, tableId: tableElementId, renderTableHead: true });
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
// 渲染后重新获取一次字体
|
||||
if (typeof (fontmin) === "function") {
|
||||
fontmin(getPageText());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 正在请求标记
|
||||
var requestingFlag = false;
|
||||
// 添加收藏/取消收藏
|
||||
function toggleFavorites(toggleStatus, bookId) {
|
||||
if (requestingFlag) return;
|
||||
requestingFlag = true;
|
||||
$("#favorties-button").css("opacity", "0.3");
|
||||
$("#favorites_button_" + bookId).html($("#favorites_button_" + bookId).html() + "中...");
|
||||
$("#favorites_button_" + bookId).css("color", "grey");
|
||||
postRequest("/book/toggleFavorites", { token: localStorageUtils.getToken(), bookId: bookId, status: toggleStatus ? 1 : 0 })
|
||||
.then(function (responseData) {
|
||||
var axiosData = responseData.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
if (status === "success") {
|
||||
console.log(data)
|
||||
if (data == "success") {
|
||||
if (toggleStatus) {
|
||||
console.log("收藏成功");
|
||||
$("#favorites_button_" + bookId).html("取消收藏");
|
||||
$("#favorites_button_" + bookId).attr("href", "javascript:toggleFavorites(false, " + bookId + ");");
|
||||
} else {
|
||||
console.log("取消收藏成功");
|
||||
$("#favorites_button_" + bookId).html("收藏");
|
||||
$("#favorites_button_" + bookId).attr("href", "javascript:toggleFavorites(true, " + bookId + ");");
|
||||
}
|
||||
$("#favorites_button_" + bookId).css("color", "");
|
||||
} else {
|
||||
console.log("操作失败");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
requestingFlag = false;
|
||||
});
|
||||
}
|
||||
</script>
|
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<div class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<div id="container">
|
||||
</div>
|
||||
</div>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<div class="main">
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
</div>
|
||||
<%- include("./component/footer.html"); %>
|
||||
</body>
|
||||
</html>
|
@@ -1,149 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.wrap {
|
||||
margin: 20px auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
label {
|
||||
height: 25%;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
label:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
label>div {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 31px;
|
||||
background: #eeeeee;
|
||||
display: none;
|
||||
}
|
||||
|
||||
input:checked+div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.main {
|
||||
min-height: max-content;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: calc(400px + 10vh) !important;
|
||||
}
|
||||
|
||||
.click2copy {
|
||||
cursor: pointer;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.main p {
|
||||
font-size: 14px !important;
|
||||
font-family: initial !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<div id="container">
|
||||
<p>
|
||||
反馈前请选择正确的分类,否则反馈经过二次转接,响应时间将会变长。
|
||||
</p>
|
||||
<div class="wrap">
|
||||
<label>
|
||||
<input type="radio" name="tab">版权投诉
|
||||
<div>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
版权问题请发送邮件至 <span class="click2copy">2291200076@qq.com</span>,并请在标题前注明<b class="click2copy">【加急丨书栖网丨版权投诉】</b>
|
||||
</p>
|
||||
<p>
|
||||
(版权投诉与其他反馈邮箱不同,请注意)
|
||||
</p>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
实在抱歉对您造成了困扰,版权投诉我们将会优先加急处理,最迟会在3个工作日内以邮件形式回复至您的发件邮箱中,届时若您未收到相关邮件,烦请检查下是否被归垃圾邮件。
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="tab" checked>下载链接失效
|
||||
<div>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
请前往下载页面,点击链接旁的 <img src="/assets/image/svg/feedback.svg" style="width: 1em; height: 1em;"/> 按钮进行反馈。
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="tab">合作
|
||||
<div>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
如需合作,请发送邮件至 <span class="click2copy">admin@only4.work</span>,并在标题前注明<b class="click2copy">【书栖网丨合作】</b>
|
||||
</p>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
ps: 本站谢绝一切形式的商业合作,所有的合作都是建立在免费、无偿的基础上,望理解。
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="tab">意见建议、其他
|
||||
<!-- <div>
|
||||
<p>
|
||||
此处仅支持纯文字反馈,如果需要添加图片或其他附件,请发送邮件至 <span class="click2copy">admin@only4.work</span>
|
||||
</p>
|
||||
<textarea style="width: 100%; height: 87.5%;">请输入...</textarea>
|
||||
<div style="width: 100%; display: grid; place-items: center;">
|
||||
<input type="button" value="提交" style="width: 100px; height: 30px;">
|
||||
</div>
|
||||
</div> -->
|
||||
<div>
|
||||
<p>
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
请发送邮件至 <span class="click2copy">admin@only4.work</span>,并在标题前注明<b class="click2copy">【书栖网丨意见建议】</b>
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- 点击复制及反馈样式 -->
|
||||
<script src="/assets/javascripts/cssUtils.js"></script>
|
||||
<script>
|
||||
// 点击复制
|
||||
$(".click2copy").click(function (e) {
|
||||
copyToClipboard($(this).text());
|
||||
showTip(e, "复制成功");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<div class="main">
|
||||
<div class="siteTitle">
|
||||
<h1><%=headText%></h1>
|
||||
</div>
|
||||
<%- include("./component/searchbox.html"); %>
|
||||
<div class="sloganBox">
|
||||
<p class="emphasize">
|
||||
一个完全免费无门槛的计算机类电子书下载网站
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%- include("./component/footer.html"); %>
|
||||
<script>
|
||||
$('#searchInput').focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,177 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 80vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#bookImage {
|
||||
/* width: 100%; */
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
width: 250px;
|
||||
margin: 0 auto;
|
||||
row-gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.form-group > input {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
height: 30px;
|
||||
width: 72px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
localStorageUtils.checkLocalStorage();
|
||||
|
||||
if(localStorageUtils.getToken()) {
|
||||
// 用户已登录
|
||||
if(localStorageUtils.getIsAdmin()) {
|
||||
// 是管理员
|
||||
window.location.href = "/dashboard/admin/index";
|
||||
} else {
|
||||
// 是普通用户
|
||||
window.location.href = "/dashboard/user/index";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<div id="container">
|
||||
<div>
|
||||
<!-- 用户登录 输入用户名和密码的文本框 -->
|
||||
<div class="form">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" placeholder="用户名">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" placeholder="密码">
|
||||
</div>
|
||||
<button class="btn-submit">登录</button>
|
||||
<p>
|
||||
<a href="/register">注册账号</a>
|
||||
</p>
|
||||
<hr style="opacity: .3;">
|
||||
<div>
|
||||
<p style="margin-bottom: 0;">快捷登录</p>
|
||||
<%- include("./dashboard/component/third-party-login-button.html"); %>
|
||||
<p style="font-size: 12px; color: #7a7a7a;">注:快捷登录需要先绑定已有帐号才可登录,新用户请先注册本站账号</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- <script src="/assets/lib/cryptography/2.2/md5-min.js"></script> -->
|
||||
<!-- <script src="/assets/lib/cryptography/2.2/sha1-min.js"></script> -->
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<script>
|
||||
// 避免重复点击
|
||||
var isOnLogin = false;
|
||||
|
||||
// 用户正常登录逻辑
|
||||
// $("#username").val("xiaomo");
|
||||
// $("#password").val("123456");
|
||||
$("#username").keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
if ($("#password").val() === "") {
|
||||
$("#password").focus();
|
||||
} else {
|
||||
$(".btn-submit").click();
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#password").keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
if ($("#username").val() === "") {
|
||||
$("#username").focus();
|
||||
} else {
|
||||
$(".btn-submit").click();
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".btn-submit").click(async function () {
|
||||
if ($("#username").val() === "" || $("#password").val() === "") {
|
||||
await swal("用户名或密码不能为空!");
|
||||
if ($("#username").val() === "") {
|
||||
$("#username").focus();
|
||||
} else {
|
||||
$("#password").focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免用户重复点击
|
||||
if (isOnLogin) return;
|
||||
isOnLogin = true;
|
||||
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
// var encryptpwd = hex_sha1(password);
|
||||
// var encryptpwd = hex_md5(password);
|
||||
|
||||
localStorageUtils.userLogout();
|
||||
var visitorId = await getVisitorId();
|
||||
postRequest("/user/login", { username: username, password: password, visitorId: visitorId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if (data) {
|
||||
localStorageUtils.userLogin({
|
||||
token: data.token,
|
||||
is_admin: data.group === "ADMIN",
|
||||
});
|
||||
// 用户登录成功
|
||||
|
||||
// 如果指定了 Redirect 链接,则跳到指定链接
|
||||
var requestParams = getParams();
|
||||
var redirectUrl = requestParams["redirect"] ?? "";
|
||||
if (redirectUrl) {
|
||||
location.href = location.origin + redirectUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有指定 Redirect 链接,则跳到后台
|
||||
if (localStorageUtils.getIsAdmin()) {
|
||||
window.location.href = "/dashboard/admin/index";
|
||||
} else {
|
||||
window.location.href = "/dashboard/user/index";
|
||||
}
|
||||
} else {
|
||||
swal("用户名或密码错误");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
}).finally(function () {
|
||||
isOnLogin = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,134 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 80vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#bookImage {
|
||||
/* width: 100%; */
|
||||
height: auto;
|
||||
max-height: 300px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
width: 250px;
|
||||
margin: 0 auto;
|
||||
row-gap: 20px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.btn-register {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.form-group > input {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
height: 30px;
|
||||
width: 72px;
|
||||
}
|
||||
|
||||
.password-tips {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
border: solid black 2px;
|
||||
width: 300px;
|
||||
margin: 0 auto;
|
||||
background-color: aliceblue;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<div id="container">
|
||||
<div>
|
||||
<!-- 用户登录 输入用户名和密码的文本框 -->
|
||||
<div class="form">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" placeholder="用户名">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" placeholder="密码">
|
||||
</div>
|
||||
<div style="height: 0; margin-top: 6px;">
|
||||
<div class="password-tips" style="display: none;">
|
||||
密码 8 - 16 位均可,可以包含:大小写字母、数字、下划线(_)、英文感叹号(!)、艾特(@)、井号(#)
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-register">注册</button>
|
||||
<p>
|
||||
<a href="/login">登录</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<script>
|
||||
$("#password").focus(function () {
|
||||
$("#password").attr("type", "text");
|
||||
$(".password-tips").fadeIn(150);
|
||||
});
|
||||
$("#password").blur(function () {
|
||||
$("#password").attr("type", "password");
|
||||
$(".password-tips").fadeOut(150);
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
localStorageUtils.checkLocalStorage();
|
||||
|
||||
$("#username").val("xiaomo");
|
||||
$("#password").val("123456");
|
||||
$(".btn-register").click(async function() {
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
|
||||
var visitorId = await getVisitorId();
|
||||
postRequest("/user/register", { username: username, password: password, visitorId: visitorId })
|
||||
.then(function (response) {
|
||||
var axiosData = response.data;
|
||||
var status = axiosData.status;
|
||||
var data = axiosData.data;
|
||||
|
||||
if (status === "success") {
|
||||
console.log(data);
|
||||
if(data) {
|
||||
localStorageUtils.userLogin({
|
||||
token: data.token,
|
||||
is_admin: data.group === "ADMIN",
|
||||
});
|
||||
swal("注册成功").then(function () {
|
||||
if (localStorageUtils.getIsAdmin()) {
|
||||
window.location.href = "/dashboard/admin/index";
|
||||
} else {
|
||||
window.location.href = "/dashboard/user/index";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
swal("出错啦!");
|
||||
}
|
||||
} else {
|
||||
swal(`出错啦!${data.errMsg} (错误码: ${data.errCode})`);
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
swal("无法连接到服务器,请检查网络连接!");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,66 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.main {
|
||||
width: 80vw !important;
|
||||
max-width: initial !important;
|
||||
}
|
||||
|
||||
#result-table {
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
line-height: 2.3em;
|
||||
}
|
||||
|
||||
tr {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
tr a {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<div class="main">
|
||||
<h1><%= headText %></h1>
|
||||
<%- include("./component/searchbox.html"); %>
|
||||
<div id="container">
|
||||
<table id="result-table"></table>
|
||||
<!-- <table id="origin-table"></table> -->
|
||||
</div>
|
||||
</div>
|
||||
<%- include("./component/footer.html"); %>
|
||||
|
||||
<!-- 获取参数 -->
|
||||
<script src="/assets/javascripts/getParams.js"></script>
|
||||
<!-- 渲染表格 -->
|
||||
<script src="/assets/javascripts/renderTable.js"></script>
|
||||
<!-- 搜索书籍 -->
|
||||
<script src="/assets/javascripts/searchBooks.js"></script>
|
||||
<script>
|
||||
var requestParams = getParams();
|
||||
var searchbox = document.getElementById("searchInput");
|
||||
var keyword = (requestParams["keyword"] || "").trim();
|
||||
searchbox.value = keyword;
|
||||
if (keyword === "")
|
||||
searchbox.focus();
|
||||
|
||||
function doSearch(keyword) {
|
||||
search({
|
||||
tableElementId: "result-table",
|
||||
searchText: keyword,
|
||||
categoryId: null
|
||||
});
|
||||
}
|
||||
doSearch(keyword);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,57 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<%- include("./component/header.html"); %>
|
||||
<style>
|
||||
.info-disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.info-ok {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.info-err {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%- include("./component/navbar.html"); %>
|
||||
<main class="main">
|
||||
<h1>
|
||||
<%= headText %>
|
||||
</h1>
|
||||
|
||||
<div class="removeAfterScriptLoaded">
|
||||
<p>正在准备检测环境,请稍候...</p>
|
||||
<p>若长时间无响应请尝试刷新页面</p>
|
||||
</div>
|
||||
|
||||
<div id="container" style="visibility: hidden;">
|
||||
<input id="checkBtn" type="button" value="检测" onclick="startCheck()">
|
||||
<div class="parentNode">
|
||||
<div class="childrenNode">
|
||||
<h3 class="title">网络连通性</h3>
|
||||
<span id="onlineStatus" class="info"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="title">网络后台状态</h3>
|
||||
后台服务器:<span id="backendStatus" class="info"></span><br>
|
||||
前台服务器:<span id="frontendStatus" class="info"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="title">网络数据库状态</h3>
|
||||
<span id="databaseStatus" class="info info-disabled">暂不提供检测</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="title">服务器时间与本机时间差</h3>
|
||||
<span id="timeOff" class="info">正在计算</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<%- include("./component/footer.html"); %>
|
||||
<script async="true" src="/assets/javascripts/siteStatus.js"></script>
|
||||
</body>
|
||||
</html>
|
@@ -11,7 +11,7 @@
|
||||
Target Server Version : 50726
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 23/04/2022 22:43:31
|
||||
Date: 12/03/2022 21:55:59
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
@@ -33,11 +33,12 @@ CREATE TABLE `book_info` (
|
||||
`thumbnail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '缩略图',
|
||||
`author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '作者',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of book_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `book_info` VALUES (1, '程序员小墨', '这是书栖网的第一本书', 3, '电子工业出版社', 'Chinese', '中国工信出版集团', 0, '', '小墨');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for category_info
|
||||
@@ -52,89 +53,54 @@ CREATE TABLE `category_info` (
|
||||
`level` int(11) NOT NULL COMMENT '分类层级(一、二、三级)',
|
||||
`parent_id` int(11) NOT NULL DEFAULT 0 COMMENT '父分类ID',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of category_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `category_info` VALUES (1, 'Git与代码版本管理', '暂无简介', 1, 1, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (2, 'LeetCode与面试', '暂无简介', 1, 2, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (3, '操作系统(Android、Linux等)', '暂无简介', 1, 3, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (4, 'Android', '暂无简介', 1, 1, 2, 3);
|
||||
INSERT INTO `category_info` VALUES (5, 'Linux', '暂无简介', 1, 2, 2, 3);
|
||||
INSERT INTO `category_info` VALUES (6, '产品与时代', '暂无简介', 1, 4, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (7, '分布式与高并发(Hadoop、ElasticSearch、区块链、架构)', '暂无简介', 1, 5, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (8, 'ElasticSearch 分布式搜索引擎', '暂无简介', 1, 1, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (9, 'Hadoop', '暂无简介', 1, 2, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (10, '架构', '暂无简介', 1, 3, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (11, '区块链', '暂无简介', 1, 4, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (12, '机器学习', '暂无简介', 1, 6, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (13, '计算机基础(计算机组成原理、计算机网络、数据结构与算法)', '暂无简介', 1, 7, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (14, '计算机网络', '暂无简介', 1, 1, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (15, '计算机组成原理', '暂无简介', 1, 2, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (16, '数据结构与算法', '暂无简介', 1, 3, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (17, '开发语言(C、C++、Java、Go、Python、HTML、JavaScript、CSS、汇编等)', '暂无简介', 1, 8, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (18, 'C++', '暂无简介', 1, 2, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (19, 'Go', '暂无简介', 1, 3, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (20, 'Java', '暂无简介', 1, 4, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (21, 'Java工具', '暂无简介', 1, 1, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (22, 'Java基础', '暂无简介', 1, 2, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (23, 'Netty', '暂无简介', 1, 3, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (24, 'Spring', '暂无简介', 1, 4, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (25, '线程', '暂无简介', 1, 5, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (26, '虚拟机', '暂无简介', 1, 6, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (27, 'Python', '暂无简介', 1, 5, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (28, '汇编语言', '暂无简介', 1, 6, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (29, '前端(HTML、JavaScript、CSS)', '暂无简介', 1, 7, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (30, '设计模式', '暂无简介', 1, 9, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (31, '数据库(MySQL、Redis、SQLite、Mybatis、MongoDB等)', '暂无简介', 1, 10, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (32, 'MongoDB', '暂无简介', 1, 1, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (33, 'Mybatis', '暂无简介', 1, 2, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (34, 'MySQL', '暂无简介', 1, 3, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (35, 'Redis', '暂无简介', 1, 4, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (36, 'SQLite', '暂无简介', 1, 5, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (37, '消息队列', '暂无简介', 1, 11, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (38, 'C语言', '暂无简介', 1, 1, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (39, 'Spring Boot', '暂无简介', 1, 7, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (40, 'Java进阶', '暂无简介', 1, 8, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (41, 'Java Web', '暂无简介', 1, 9, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (42, 'Spring Cloud', '暂无简介', 1, 10, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (43, '其他', '暂无简介', 1, 12, 1, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for cos_presigned_url_generate_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `cos_presigned_url_generate_log`;
|
||||
CREATE TABLE `cos_presigned_url_generate_log` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增Id',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户Id',
|
||||
`time` datetime NOT NULL COMMENT '链接生成时间',
|
||||
`expire_minute` int(255) NOT NULL COMMENT '链接有效期(单位:分钟)',
|
||||
`method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作方法(上传请求用 PUT,下载请求用 GET,删除请求用 DELETE)',
|
||||
`file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '下载的文件链接',
|
||||
`url_guid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '链接生成时创建的全局唯一NanoID,便于出现异常下载记录与腾讯云下载日志做对应',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of cos_presigned_url_generate_log
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for failure_feedback
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `failure_feedback`;
|
||||
CREATE TABLE `failure_feedback` (
|
||||
`book_id` int(11) NOT NULL DEFAULT 0,
|
||||
`file_id` int(11) NOT NULL DEFAULT 0,
|
||||
`file_object_id` int(11) NOT NULL DEFAULT 0,
|
||||
`user_id` int(11) NOT NULL DEFAULT 0,
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '失效反馈' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of failure_feedback
|
||||
-- ----------------------------
|
||||
INSERT INTO `category_info` VALUES (1, 'Git与代码版本管理', '简介123', 1, 1, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (2, 'LeetCode与面试', '\r\n', 1, 2, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (3, '操作系统(Android、Linux等)', '\r\n', 1, 3, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (4, 'Android', '\r\n', 1, 1, 2, 3);
|
||||
INSERT INTO `category_info` VALUES (5, 'Linux', '\r\n', 1, 2, 2, 3);
|
||||
INSERT INTO `category_info` VALUES (6, '产品与时代', '\r\n', 1, 4, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (7, '分布式与高并发(Hadoop、ElasticSearch、区块链、架构)', '\r\n', 1, 5, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (8, 'ElasticSearch 分布式搜索引擎', '\r\n', 1, 1, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (9, 'Hadoop', '\r\n', 1, 2, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (10, '架构', '\r\n', 1, 3, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (11, '区块链', '\r\n', 1, 4, 2, 7);
|
||||
INSERT INTO `category_info` VALUES (12, '机器学习', '\r\n', 1, 6, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (13, '计算机基础(计算机组成原理、计算机网络、数据结构与算法)', '\r\n', 1, 7, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (14, '计算机网络', '\r\n', 1, 1, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (15, '计算机组成原理', '\r\n', 1, 2, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (16, '数据结构与算法', '\r\n', 1, 3, 2, 13);
|
||||
INSERT INTO `category_info` VALUES (17, '开发语言(C、C++、Java、Go、Python、HTML、JavaScript、CSS、汇编等)', '\r\n', 1, 8, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (18, 'C++', '\r\n', 1, 2, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (19, 'Go', '\r\n', 1, 3, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (20, 'Java', '\r\n', 1, 4, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (21, 'Java工具', '\r\n', 1, 1, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (22, 'Java基础', '\r\n', 1, 2, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (23, 'Netty', '\r\n', 1, 3, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (24, 'Spring', '\r\n', 1, 4, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (25, '线程', '\r\n', 1, 5, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (26, '虚拟机', '\r\n', 1, 6, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (27, 'Python', '\r\n', 1, 5, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (28, '汇编语言', '\r\n', 1, 6, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (29, '前端(HTML、JavaScript、CSS)', '\r\n', 1, 7, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (30, '设计模式', '\r\n', 1, 9, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (31, '数据库(MySQL、Redis、SQLite、Mybatis、MongoDB等)', '\r\n', 1, 10, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (32, 'MongoDB', '\r\n', 1, 1, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (33, 'Mybatis', '\r\n', 1, 2, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (34, 'MySQL', '\r\n', 1, 3, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (35, 'Redis', '\r\n', 1, 4, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (36, 'SQLite', '\r\n', 1, 5, 2, 31);
|
||||
INSERT INTO `category_info` VALUES (37, '消息队列', '\r\n', 1, 11, 1, 0);
|
||||
INSERT INTO `category_info` VALUES (38, 'C语言', '\r\n', 1, 1, 2, 17);
|
||||
INSERT INTO `category_info` VALUES (39, 'Spring Boot', '\r\n', 1, 7, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (40, 'Java进阶', '\r\n', 1, 8, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (41, 'Java Web', '\r\n', 1, 9, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (42, 'Spring Cloud', '\r\n', 1, 10, 3, 20);
|
||||
INSERT INTO `category_info` VALUES (43, '其他', '\r\n', 1, 12, 1, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for file_info
|
||||
@@ -143,17 +109,22 @@ DROP TABLE IF EXISTS `file_info`;
|
||||
CREATE TABLE `file_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增编号',
|
||||
`book_id` int(11) NOT NULL,
|
||||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名(不含扩展名)',
|
||||
`file_ext` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_size` bigint(20) UNSIGNED NOT NULL DEFAULT 0,
|
||||
`file_sha1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_display_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_format` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`number_of_pages` int(11) NOT NULL DEFAULT 0,
|
||||
`watermark` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`advertising` tinyint(1) NOT NULL DEFAULT 0 COMMENT '1为已删除项',
|
||||
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`book_origin` tinyint(4) NOT NULL,
|
||||
`thumbnail` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_create_at` datetime NOT NULL,
|
||||
`file_modified_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`file_size` int(18) NOT NULL DEFAULT 0,
|
||||
`hash_md5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`hash_sha1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`hash_sha256` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of file_info
|
||||
@@ -165,126 +136,17 @@ CREATE TABLE `file_info` (
|
||||
DROP TABLE IF EXISTS `file_object_info`;
|
||||
CREATE TABLE `file_object_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`file_id` int(11) NOT NULL,
|
||||
`upload_status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '上传状态(上传成功 SUCCESS,正在上传 UPLOADING,上传终止 NOT_EXIST)',
|
||||
`storage_medium` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||
`fileId` int(11) NOT NULL,
|
||||
`storage_medium_type` tinyint(4) NOT NULL DEFAULT 0,
|
||||
`file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '本地文件保存相对路径(本地维护用,非线上使用)',
|
||||
`file_pwd` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`file_share_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
|
||||
`additional_fields` json NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of file_object_info
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for third_party_user_auth_relation
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `third_party_user_auth_relation`;
|
||||
CREATE TABLE `third_party_user_auth_relation` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`user_id` int(11) NOT NULL COMMENT '系统用户ID',
|
||||
`third_party_user_id` int(11) NOT NULL COMMENT '社会化用户ID',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `relation`(`user_id`, `third_party_user_id`) USING BTREE,
|
||||
INDEX `third_party_user_id`(`third_party_user_id`) USING BTREE,
|
||||
CONSTRAINT `third_party_user_auth_relation_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||
CONSTRAINT `third_party_user_auth_relation_ibfk_2` FOREIGN KEY (`third_party_user_id`) REFERENCES `third_party_user_info` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of third_party_user_auth_relation
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for third_party_user_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `third_party_user_info`;
|
||||
CREATE TABLE `third_party_user_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方系统的唯一ID',
|
||||
`source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'gitee、qq、github、......',
|
||||
`access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户的授权令牌',
|
||||
`expire_in` int(255) NULL DEFAULT NULL COMMENT '第三方用户的授权令牌有效期',
|
||||
`refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '刷新令牌',
|
||||
`open_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的Open ID',
|
||||
`uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方的用户ID',
|
||||
`access_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息',
|
||||
`union_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 Union ID',
|
||||
`scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户授予的权限',
|
||||
`token_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息',
|
||||
`id_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'id token',
|
||||
`mac_algorithm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性',
|
||||
`mac_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性',
|
||||
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户的授权Code',
|
||||
`oauth_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性',
|
||||
`oauth_token_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'refer: https://justauth.wiki/features/integrate-existing-systems' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of third_party_user_info
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_book_favorites_relation
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_book_favorites_relation`;
|
||||
CREATE TABLE `user_book_favorites_relation` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`book_id` int(11) NOT NULL,
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `user_id`(`user_id`, `book_id`) USING BTREE,
|
||||
INDEX `book_id`(`book_id`) USING BTREE,
|
||||
CONSTRAINT `user_book_favorites_relation_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
|
||||
CONSTRAINT `user_book_favorites_relation_ibfk_2` FOREIGN KEY (`book_id`) REFERENCES `book_info` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of user_book_favorites_relation
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for user_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `user_info`;
|
||||
CREATE TABLE `user_info` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`encript_pwd` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`group` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`weixin_third_party_auth_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`qq_third_party_auth_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of user_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `user_info` VALUES (1, 'admin', '7c4a8d09ca3762af61e59520943dc26494f8941b', '小小墨(密码是123456)', 'ADMIN', '', '', '', '', '');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for visitor_fingerprint_log
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `visitor_fingerprint_log`;
|
||||
CREATE TABLE `visitor_fingerprint_log` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`visitor_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`user_id` int(11) NULL DEFAULT NULL COMMENT '未登录用户 userId 为 0',
|
||||
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`action` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '注意,用户浏览器指纹有概率重复,所以仅供参考,不能作为判断的依据\r\n\r\n获取不到浏览器指纹的将使用浏览器UA代替' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of visitor_fingerprint_log
|
||||
-- ----------------------------
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
1
bookshelfplus/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
src/main/resources/application.properties
|