Compare commits
123 Commits
Author | SHA1 | Date | |
---|---|---|---|
13786834a0 | |||
|
e0dccb6fd2 | ||
845b1b077e | |||
|
bb62066b82 | ||
63f1c9a54e | |||
|
c9156f12d1 | ||
325401e198 | |||
|
5126e31867 | ||
f7994d232d | |||
|
f78349a1d2 | ||
44c1ca91fc | |||
|
b0b608de5a | ||
|
523698a967 | ||
|
405a2e05ed | ||
|
6b59dfbf26 | ||
5a8f805a15 | |||
42a2829847 | |||
dfb39e5ee8 | |||
|
946c032ba5 | ||
|
3a77e5af4d | ||
|
1d282b1095 | ||
|
bb7deda121 | ||
|
0d000223a2 | ||
|
51eb22adaa | ||
|
c56ca2809c | ||
|
383450ebe4 | ||
|
ebc1da0972 | ||
|
9dc1fe47d9 | ||
|
35d3420470 | ||
|
529b42a185 | ||
|
58a8cefaf5 | ||
|
b9cef34a06 | ||
|
928e3fac48 | ||
|
c9429557b3 | ||
|
4bed4fd34d | ||
|
5d52e389d6 | ||
|
767c26d89b | ||
|
83125ae55a | ||
|
b5f715eda6 | ||
|
cc3298f3b4 | ||
|
2b69814296 | ||
|
2880b6282c | ||
|
ff560ae790 | ||
|
c5a35ea54e | ||
|
22c2c48eea | ||
|
580d18a500 | ||
|
5868aa4a98 | ||
|
42cbbe5999 | ||
|
9dc21ed87a | ||
|
32e63ed1fb | ||
|
35ee930661 | ||
|
19aeab2856 | ||
|
866ce22bb5 | ||
|
84c2c213a9 | ||
|
9cc1a7b871 | ||
|
5e12b5a8b1 | ||
|
8ab7d0c341 | ||
|
4b9adc3acd | ||
|
a67ef95b25 | ||
|
39e9f8221a | ||
|
40cdb1f19e | ||
|
106dccb68f | ||
|
5aeae15228 | ||
|
837a4a7650 | ||
|
8c81842571 | ||
|
64d4504178 | ||
|
3072d757f0 | ||
|
62c88d78fc | ||
|
ff323e6eea | ||
|
e240905d58 | ||
|
f23e720fb9 | ||
|
faffadeba9 | ||
|
b2353a0ea1 | ||
|
d5e2b55c28 | ||
|
35910c34e1 | ||
|
d19325aaad | ||
|
df5578f267 | ||
|
9a5a4cae52 | ||
|
9a689cac65 | ||
|
238cc21ffa | ||
|
b950c666cc | ||
|
828207f672 | ||
|
ce0028cc49 | ||
|
9e2c75c3ec | ||
|
d36967d852 | ||
|
9e3308d6ca | ||
|
85688595f8 | ||
|
9a97129c79 | ||
|
6444cd6ebb | ||
|
310ae76e8c | ||
|
473da94244 | ||
|
5ed2aa1690 | ||
|
c43cf5d5ae | ||
|
1087f5e48b | ||
|
b74be9f961 | ||
|
6029f9d9c0 | ||
|
2c10ec4831 | ||
|
89ad88e359 | ||
|
a7decc0c76 | ||
|
cb5e30a7bb | ||
|
e8a646dbe8 | ||
|
d44700a971 | ||
|
50e1e0e951 | ||
|
90b1dbcf9f | ||
|
1ba7617165 | ||
|
57a6944ec5 | ||
|
d251734267 | ||
|
c128cba5f6 | ||
|
a6f1380e9c | ||
|
3d7243e276 | ||
|
bd17be5dd5 | ||
|
77086daeb9 | ||
|
553cec1338 | ||
|
f424187334 | ||
|
8d639b6a8c | ||
|
91a78a8459 | ||
|
14b3c6da2f | ||
|
f048496d73 | ||
|
c467970e93 | ||
|
15ed9cab08 | ||
|
7bb4c42cb8 | ||
|
f936a6b71b | ||
|
47d4698ac3 |
24
.drone.yml
Normal file
24
.drone.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: bookmarkPublish
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
|
||||||
|
clone:
|
||||||
|
disable: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: deploy
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: devHost
|
||||||
|
port: 22
|
||||||
|
user: root
|
||||||
|
key:
|
||||||
|
from_secret: privateSsh
|
||||||
|
command_timeout: 30m
|
||||||
|
script:
|
||||||
|
- cd /root/bookmark && git pull && bash build.sh && bash syncFile.sh
|
20
DEPLOY.md
20
DEPLOY.md
@ -1,10 +1,20 @@
|
|||||||
本程序基于 docker 来进行部署,使用 docker-compose 管理服务。
|
本程序基于 docker 来进行部署,使用 docker-compose 管理服务。
|
||||||
|
|
||||||
部署过程如下:
|
**注意,仅在 x86 环境下测试,arm 下不保证可用性(目前测试可用)**
|
||||||
|
|
||||||
**注意,仅在 x86 环境下测试**
|
## 首次部署
|
||||||
|
|
||||||
1. 安装新版的 docker 和 docker-compose(注意:以下操作均在项目根目录下执行)
|
0. 克隆代码`git clone https://github.com/FleyX/bookmark.git`
|
||||||
2. 执行`build.sh`编译前后端代码
|
1. 进入文件夹`cd bookmark`
|
||||||
|
2. 安装新版的 docker,docker-compose,zip `apt install docker docker-compose zip`
|
||||||
3. 修改.env 文件中的参数,改为你的实际配置
|
3. 修改.env 文件中的参数,改为你的实际配置
|
||||||
4. root 权限运行 `docker-compose up -d` 启动服务。
|
4. 修改`浏览器插件/bookmarkBrowserPlugin/static/js/config.js`中的 bookmarkHost,改为你的实际部署路径
|
||||||
|
5. 修改`浏览器插件/bookmarkBrowserPlugin/tab/index.html`中的`<meta http-equiv="Refresh" content="0;url=https://bm.fleyx.com" />`,将 url 改为你的实际部署地址
|
||||||
|
6. 执行`build.sh`编译前后端代码 `bash build.sh`
|
||||||
|
7. root 权限运行 `docker-compose up -d` 启动服务。
|
||||||
|
|
||||||
|
## 更新系统
|
||||||
|
|
||||||
|
0. 代码库更新`cd bookmark;git pull`
|
||||||
|
1. 执行`build.sh`编译前后端代码 `bash build.sh`
|
||||||
|
2. root 权限运行 `docker-compose restart` 启动服务
|
||||||
|
16
HELP.md
16
HELP.md
@ -97,3 +97,19 @@ enter/回车: 未选中书签情况下,用于发起网页搜索。选中书签
|
|||||||
### 个人中心
|
### 个人中心
|
||||||
|
|
||||||
通过右上角悬浮菜单进入个人中心页面,可进行头像更换,密码修改等操作
|
通过右上角悬浮菜单进入个人中心页面,可进行头像更换,密码修改等操作
|
||||||
|
|
||||||
|
### 浏览器插件
|
||||||
|
|
||||||
|
浏览器插件功能终于有 0.1 版本,支持鼠标右键菜单添加书签。
|
||||||
|
|
||||||
|
#### 安装插件
|
||||||
|
|
||||||
|
1. 首先下载插件压缩包,下载地址:[点击下载](https://fleyx.com/static/bookmarkBrowserPlugin.7z)
|
||||||
|
|
||||||
|
2. 安装插件(以 chrome 浏览器为例,其他支持插件的浏览器差不多)进入插件管理页面->开启开发者模式->加载已解压的拓展程序
|
||||||
|
|
||||||
|
![](https://qiniupic.fleyx.com/blog/202204151605709.png)
|
||||||
|
|
||||||
|
3. 之后页面点击右键->添加到书签,即可
|
||||||
|
|
||||||
|
![](https://qiniupic.fleyx.com/blog/202204151607593.png)
|
||||||
|
100
README.md
100
README.md
@ -1,44 +1,56 @@
|
|||||||
本项目是一个云书签的项目,取名为:签签世界.
|
![图片](https://s3.fleyx.com/picbed/2023/08/Snipaste_2023-08-13_15-33-16.png)
|
||||||
|
|
||||||
部署地址:[fleyx.com](https://fleyx.com),
|
本项目是一个在线书签管理的项目,名为:签签世界.
|
||||||
|
|
||||||
也可自己搭建,教程如下:
|
在线使用地址(长期提供服务):[bm.fleyx.com](https://bm.fleyx.com),
|
||||||
|
|
||||||
部署教程:[docker-compose 部署](https://github.com/FleyX/bookmark/blob/master/DEPLOY.md)
|
**为获得更好的体验,建议将主页设置为 bm.fleyx.com,并安装浏览器拓展,[点击查看如何安装](https://blog.fleyx.com/blog/detail/20220329/#%e6%b5%8f%e8%a7%88%e5%99%a8%e6%8f%92%e4%bb%b6)**
|
||||||
|
|
||||||
# 缘由
|
也可自己部署搭建,教程见:[docker-compose 部署](https://github.com/FleyX/bookmark/blob/master/DEPLOY.md)
|
||||||
|
|
||||||
1. 主要用的是 chrome,但是有时候需要用其他的浏览器:Firefox,ie 等。然后这些浏览器上没有书签,想进个网站还得打开 chrome 复制 url,太麻烦。
|
# 缘由
|
||||||
|
|
||||||
2. chrome 必须翻墙才能同步书签,体验不是那么好。
|
1. 主要用的是 chrome,但是有时候需要用其他的浏览器:Firefox,ie 等。然后这些浏览器上没有书签,想进个网站还得打开 chrome 复制 url,太麻烦。
|
||||||
|
|
||||||
3. 如果书签全放在 chrome 上,相当于绑定死 chrome 浏览器了,很难迁移到别的优秀浏览器,比如 firfox 上。
|
2. chrome 必须翻墙才能同步书签,体验不是那么好。
|
||||||
|
|
||||||
所以有了这样这样一个项目,建立一个和平台无关的书签管理器,可在任意平台使用。
|
3. 如果书签全放在 chrome 上,相当于绑定死 chrome 浏览器了,很难迁移到别的优秀浏览器,比如 firfox 上。
|
||||||
|
|
||||||
# 主要功能
|
所以有了这样这样一个项目,建立一个和平台无关的书签管理器,可在任意平台使用。
|
||||||
|
|
||||||
使用帮助见:[使用帮助](https://github.com/FleyX/bookmark/blob/master/HELP.md)
|
# 主要功能
|
||||||
|
|
||||||
1. 基础的书签增删改查功能。支持 chrome、firefox 等浏览器书签文件导入,导出。
|
帮助文档:[点击跳转](https://blog.fleyx.com/blog/detail/20220329/)
|
||||||
|
|
||||||
![](https://qiniupic.fleyx.com/blog/20220329214126.png?imageView2/2/w/1920)
|
- 支持从 chrome,edge,firefox 等浏览器导入书签数据。
|
||||||
|
- 支持从 OneEnv 导入书签数据
|
||||||
2. 强大的书签检索功能,毫秒级的关键字检索。
|
- 树型多级目录支持
|
||||||
|
- 支持导出标准 html 书签文件
|
||||||
![](https://qiniupic.fleyx.com/blog/20220329214210.png?imageView2/2/w/1920)
|
- 强大的检索功能,支持拼音检索
|
||||||
|
- 支持浏览器插件,安装插件以后可右键添加书签
|
||||||
3. 首页功能,参考 bing 首页实现
|
|
||||||
|
# 更新日志
|
||||||
![](https://qiniupic.fleyx.com/blog/20220329214236.png?imageView2/2/w/1920)
|
|
||||||
|
## 1.4.1
|
||||||
4. 移动端支持,手机端也可使用(部分功能比如拖拽等无法使用)
|
|
||||||
|
- 修复书签名过长无法导入问题
|
||||||
![](https://qiniupic.fleyx.com/blog/20220329214312.png?imageView2/2/w/1920)
|
|
||||||
|
## 1.4
|
||||||
# TODO
|
|
||||||
|
- 优化首图加载逻辑
|
||||||
- 主页功能 Ok!
|
- 支持 OneEnv 备份文件导入
|
||||||
- 拼音检索 Ok!
|
|
||||||
- 书签导出 OK!
|
## 1.3
|
||||||
- 侧边栏显示
|
|
||||||
|
![pic](https://s3.fleyx.com/picbed/2023/08/Snipaste_2023-08-13_15-01-20.png)
|
||||||
|
|
||||||
|
搜索引擎支持自定义[#43](https://github.com/FleyX/bookmark/issues/43)
|
||||||
|
|
||||||
|
位置:右上角个人中心-管理搜索引擎
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
- [x] 主页功能
|
||||||
|
- [x] 拼音检索
|
||||||
|
- [x] 书签导出
|
||||||
|
- [x] 浏览器插件
|
||||||
|
@ -22,13 +22,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
<version>1.14.3</version>
|
<version>1.15.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.houbb</groupId>
|
<groupId>com.github.houbb</groupId>
|
||||||
<artifactId>pinyin</artifactId>
|
<artifactId>pinyin</artifactId>
|
||||||
<version>0.3.1</version>
|
<version>0.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial</groupId>
|
||||||
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
<version>3.44.1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.fanxb.bookmark.business.bookmark.constant;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
public class FileConstant {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网站icon存储路径
|
||||||
|
*/
|
||||||
|
public static final String FAVICON_PATH = Paths.get("files", "public", "favicon").toString();
|
||||||
|
}
|
@ -69,7 +69,7 @@ public class BookmarkController {
|
|||||||
*/
|
*/
|
||||||
@RequestMapping("/uploadBookmarkFile")
|
@RequestMapping("/uploadBookmarkFile")
|
||||||
public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("path") String path) throws Exception {
|
public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("path") String path) throws Exception {
|
||||||
bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file.getInputStream(), path);
|
bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file, path);
|
||||||
return Result.success(null);
|
return Result.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.fanxb.bookmark.business.bookmark.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface HostIconDao {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入一条数据
|
||||||
|
*
|
||||||
|
* @param host host
|
||||||
|
* @param iconPath path
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Insert("insert into host_icon(host,iconPath) value(#{host},#{iconPath})")
|
||||||
|
void insert(@Param("host") String host, @Param("iconPath") String iconPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据host获取iconPath
|
||||||
|
*
|
||||||
|
* @param host host
|
||||||
|
* @return {@link String}
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Select("select iconPath from host_icon where host=#{host} limit 1")
|
||||||
|
String selectByHost(String host);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一条
|
||||||
|
*
|
||||||
|
* @param host host
|
||||||
|
* @author FleyX
|
||||||
|
*/
|
||||||
|
@Delete("delete from host_icon where host=#{host}")
|
||||||
|
void deleteByHost(String host);
|
||||||
|
}
|
@ -3,6 +3,7 @@ package com.fanxb.bookmark.business.bookmark.service;
|
|||||||
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
|
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
|
||||||
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
|
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
|
||||||
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -54,7 +55,7 @@ public interface BookmarkService {
|
|||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/9 18:44
|
* @date 2019/7/9 18:44
|
||||||
*/
|
*/
|
||||||
void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception;
|
void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description: 详情
|
* Description: 详情
|
||||||
|
@ -1,21 +1,30 @@
|
|||||||
package com.fanxb.bookmark.business.bookmark.service.impl;
|
package com.fanxb.bookmark.business.bookmark.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.codec.Base64Decoder;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.*;
|
||||||
|
import cn.hutool.core.util.HashUtil;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.fanxb.bookmark.business.api.UserApi;
|
import com.fanxb.bookmark.business.api.UserApi;
|
||||||
|
import com.fanxb.bookmark.business.bookmark.constant.FileConstant;
|
||||||
import com.fanxb.bookmark.business.bookmark.dao.BookmarkDao;
|
import com.fanxb.bookmark.business.bookmark.dao.BookmarkDao;
|
||||||
|
import com.fanxb.bookmark.business.bookmark.dao.HostIconDao;
|
||||||
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
|
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
|
||||||
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
|
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
|
||||||
import com.fanxb.bookmark.business.bookmark.entity.redis.BookmarkDeleteMessage;
|
import com.fanxb.bookmark.business.bookmark.entity.redis.BookmarkDeleteMessage;
|
||||||
import com.fanxb.bookmark.business.bookmark.entity.redis.VisitNumPlus;
|
import com.fanxb.bookmark.business.bookmark.entity.redis.VisitNumPlus;
|
||||||
import com.fanxb.bookmark.business.bookmark.service.BookmarkService;
|
import com.fanxb.bookmark.business.bookmark.service.BookmarkService;
|
||||||
import com.fanxb.bookmark.business.bookmark.service.PinYinService;
|
import com.fanxb.bookmark.business.bookmark.service.PinYinService;
|
||||||
|
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||||
import com.fanxb.bookmark.common.constant.EsConstant;
|
import com.fanxb.bookmark.common.constant.EsConstant;
|
||||||
import com.fanxb.bookmark.common.constant.RedisConstant;
|
import com.fanxb.bookmark.common.constant.RedisConstant;
|
||||||
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
||||||
|
import com.fanxb.bookmark.common.exception.CustomException;
|
||||||
import com.fanxb.bookmark.common.util.*;
|
import com.fanxb.bookmark.common.util.*;
|
||||||
|
import com.mysql.cj.conf.url.SingleConnectionUrl;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
import org.elasticsearch.search.builder.SearchSourceBuilder;
|
||||||
@ -27,10 +36,24 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.awt.print.Book;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.Statement;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -39,7 +62,6 @@ import java.util.stream.Collectors;
|
|||||||
* 类功能详述:
|
* 类功能详述:
|
||||||
*
|
*
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/8 15:00
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -51,29 +73,38 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
private final PinYinService pinYinService;
|
private final PinYinService pinYinService;
|
||||||
private final UserApi userApi;
|
private final UserApi userApi;
|
||||||
private final EsUtil esUtil;
|
private final EsUtil esUtil;
|
||||||
|
private final HostIconDao hostIconDao;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public BookmarkServiceImpl(BookmarkDao bookmarkDao, PinYinService pinYinService, UserApi userApi, EsUtil esUtil) {
|
public BookmarkServiceImpl(BookmarkDao bookmarkDao, PinYinService pinYinService, UserApi userApi, EsUtil esUtil, HostIconDao hostIconDao) {
|
||||||
this.bookmarkDao = bookmarkDao;
|
this.bookmarkDao = bookmarkDao;
|
||||||
this.pinYinService = pinYinService;
|
this.pinYinService = pinYinService;
|
||||||
this.userApi = userApi;
|
this.userApi = userApi;
|
||||||
this.esUtil = esUtil;
|
this.esUtil = esUtil;
|
||||||
|
this.hostIconDao = hostIconDao;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception {
|
public void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception {
|
||||||
Document doc = Jsoup.parse(stream, "utf-8", "");
|
List<Bookmark> bookmarks = new ArrayList<>();
|
||||||
Elements elements = doc.select("html>body>dl>dt");
|
|
||||||
//获取当前层sort最大值
|
//获取当前层sort最大值
|
||||||
Integer sortBase = bookmarkDao.selectMaxSort(userId, path);
|
Integer sortBase = bookmarkDao.selectMaxSort(userId, path);
|
||||||
if (sortBase == null) {
|
if (sortBase == null) {
|
||||||
sortBase = 0;
|
sortBase = 0;
|
||||||
}
|
}
|
||||||
List<Bookmark> bookmarks = new ArrayList<>();
|
if (file.getOriginalFilename().endsWith(".db3")) {
|
||||||
for (int i = 0, length = elements.size(); i < length; i++) {
|
//处理db文件
|
||||||
dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
|
readFromOneEnv(bookmarks, userId, file, path, sortBase);
|
||||||
|
} else {
|
||||||
|
InputStream stream = file.getInputStream();
|
||||||
|
Document doc = Jsoup.parse(stream, "utf-8", "");
|
||||||
|
Elements elements = doc.select("html>body>dl>dt");
|
||||||
|
for (int i = 0, length = elements.size(); i < length; i++) {
|
||||||
|
dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//每一千条处理插入一次,批量更新搜索字段
|
//每一千条处理插入一次,批量更新搜索字段
|
||||||
List<Bookmark> tempList = new ArrayList<>(1000);
|
List<Bookmark> tempList = new ArrayList<>(1000);
|
||||||
for (int i = 0; i < bookmarks.size(); i++) {
|
for (int i = 0; i < bookmarks.size(); i++) {
|
||||||
@ -100,7 +131,6 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
* @param path 节点路径,不包含自身
|
* @param path 节点路径,不包含自身
|
||||||
* @param sort 当前层级中的排序序号
|
* @param sort 当前层级中的排序序号
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/8 14:49
|
|
||||||
*/
|
*/
|
||||||
private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) {
|
private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) {
|
||||||
if (!DT.equalsIgnoreCase(ele.tagName())) {
|
if (!DT.equalsIgnoreCase(ele.tagName())) {
|
||||||
@ -109,7 +139,7 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
Element first = ele.child(0);
|
Element first = ele.child(0);
|
||||||
if (A.equalsIgnoreCase(first.tagName())) {
|
if (A.equalsIgnoreCase(first.tagName())) {
|
||||||
//说明为链接
|
//说明为链接
|
||||||
Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), first.attr("icon")
|
Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), ""
|
||||||
, Long.parseLong(first.attr("add_date")) * 1000, sort);
|
, Long.parseLong(first.attr("add_date")) * 1000, sort);
|
||||||
//存入数据库
|
//存入数据库
|
||||||
insertOne(node);
|
insertOne(node);
|
||||||
@ -133,13 +163,63 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理oneenv的导出
|
||||||
|
*
|
||||||
|
* @param bookmarks 书签列表
|
||||||
|
* @param userId 用户id
|
||||||
|
* @param file file
|
||||||
|
* @param path path
|
||||||
|
* @param sort sort
|
||||||
|
*/
|
||||||
|
private void readFromOneEnv(List<Bookmark> bookmarks, int userId, MultipartFile file, String path, int sort) {
|
||||||
|
String filePath = CommonConstant.fileSavePath + "/files/" + IdUtil.simpleUUID() + ".db3";
|
||||||
|
try {
|
||||||
|
file.transferTo(FileUtil.newFile(filePath));
|
||||||
|
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + filePath)) {
|
||||||
|
Statement stat = conn.createStatement();
|
||||||
|
ResultSet rs = stat.executeQuery("select * from on_categorys");
|
||||||
|
Map<Long, Bookmark> folderMap = new HashMap<>();
|
||||||
|
Map<Long, Integer> childSortBaseMap = new HashMap<>();
|
||||||
|
while (rs.next()) {
|
||||||
|
long addTime = rs.getLong("add_time");
|
||||||
|
Bookmark folder = new Bookmark(userId, path, StrUtil.nullToEmpty(rs.getString("name")), addTime == 0 ? System.currentTimeMillis() : addTime * 1000, sort++);
|
||||||
|
int childSortBase = 0;
|
||||||
|
if (insertOne(folder)) {
|
||||||
|
childSortBase = ObjectUtil.defaultIfNull(bookmarkDao.selectMaxSort(userId, path), 0);
|
||||||
|
}
|
||||||
|
long id = rs.getLong("id");
|
||||||
|
folderMap.put(id, folder);
|
||||||
|
childSortBaseMap.put(id, childSortBase);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
rs = stat.executeQuery("select * from on_links");
|
||||||
|
while (rs.next()) {
|
||||||
|
long fId = rs.getLong("fid");
|
||||||
|
long addTime = rs.getLong("add_time");
|
||||||
|
int tempSort = childSortBaseMap.get(fId);
|
||||||
|
childSortBaseMap.put(fId, tempSort + 1);
|
||||||
|
Bookmark folder = folderMap.get(fId);
|
||||||
|
String curPath = folder == null ? "" : folder.getPath() + "." + folder.getBookmarkId();
|
||||||
|
Bookmark bookmark = new Bookmark(userId, curPath, StrUtil.nullToEmpty(rs.getString("title"))
|
||||||
|
, StrUtil.nullToEmpty(rs.getString("url")), "", addTime == 0 ? System.currentTimeMillis() : addTime * 1000, tempSort);
|
||||||
|
bookmarks.add(bookmark);
|
||||||
|
insertOne(bookmark);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
stat.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description: 插入一条书签,如果已经存在同名书签将跳过
|
* Description: 插入一条书签,如果已经存在同名书签将跳过
|
||||||
*
|
*
|
||||||
* @param node node
|
* @param node node
|
||||||
* @return boolean 如果已经存在返回true,否则false
|
* @return boolean 如果已经存在返回true,否则false
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/8 17:25
|
|
||||||
*/
|
*/
|
||||||
private boolean insertOne(Bookmark node) {
|
private boolean insertOne(Bookmark node) {
|
||||||
//先根据name,userId,parentId获取此节点id
|
//先根据name,userId,parentId获取此节点id
|
||||||
@ -201,11 +281,14 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
bookmark.setUserId(userId);
|
bookmark.setUserId(userId);
|
||||||
bookmark.setCreateTime(System.currentTimeMillis());
|
bookmark.setCreateTime(System.currentTimeMillis());
|
||||||
bookmark.setAddTime(bookmark.getCreateTime());
|
bookmark.setAddTime(bookmark.getCreateTime());
|
||||||
bookmark.setIcon(getIconBase64(bookmark.getUrl()));
|
bookmark.setIcon(bookmark.getType() == 1 ? "" : getIconPath(bookmark.getUrl(), bookmark.getIcon(), bookmark.getIconUrl(), true));
|
||||||
//文件夹和书签都建立搜索key
|
//文件夹和书签都建立搜索key
|
||||||
pinYinService.changeBookmark(bookmark);
|
pinYinService.changeBookmark(bookmark);
|
||||||
bookmarkDao.insertOne(bookmark);
|
bookmarkDao.insertOne(bookmark);
|
||||||
userApi.versionPlus(userId);
|
userApi.versionPlus(userId);
|
||||||
|
if (StrUtil.isEmpty(bookmark.getIcon()) && bookmark.getType() == 0) {
|
||||||
|
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
|
||||||
|
}
|
||||||
return bookmark;
|
return bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,13 +298,33 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
bookmark.setUserId(userId);
|
bookmark.setUserId(userId);
|
||||||
if (bookmark.getType() == 0) {
|
if (bookmark.getType() == 0) {
|
||||||
pinYinService.changeBookmark(bookmark);
|
pinYinService.changeBookmark(bookmark);
|
||||||
bookmark.setIcon(getIconBase64(bookmark.getUrl()));
|
bookmark.setIcon(getIconPath(bookmark.getUrl(), null, null, true));
|
||||||
|
if (StrUtil.isEmpty(bookmark.getIcon())) {
|
||||||
|
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bookmarkDao.editBookmark(bookmark);
|
bookmarkDao.editBookmark(bookmark);
|
||||||
userApi.versionPlus(userId);
|
userApi.versionPlus(userId);
|
||||||
return bookmark.getIcon();
|
return bookmark.getIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步更新书签icon
|
||||||
|
*
|
||||||
|
* @param id 书签id
|
||||||
|
* @param url 书签url
|
||||||
|
* @param userId userId
|
||||||
|
*/
|
||||||
|
private void updateIconAsync(int id, String url, int userId) {
|
||||||
|
ThreadPoolUtil.execute(() -> {
|
||||||
|
String icon = getIconPath(url, null, null, false);
|
||||||
|
if (StrUtil.isEmpty(icon)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bookmarkDao.updateIcon(id, icon);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@ -271,10 +374,10 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
int size = 100;
|
int size = 100;
|
||||||
int start = 0;
|
int start = 0;
|
||||||
List<Bookmark> deal;
|
List<Bookmark> deal;
|
||||||
while ((deal = bookmarkDao.selectUserNoIcon(userId, start, size)).size() > 0) {
|
while (!(deal = bookmarkDao.selectUserNoIcon(userId, start, size)).isEmpty()) {
|
||||||
start += size;
|
start += size;
|
||||||
deal.forEach(item -> {
|
deal.forEach(item -> {
|
||||||
String icon = getIconBase64(item.getUrl());
|
String icon = getIconPath(item.getUrl(), null, null, false);
|
||||||
if (StrUtil.isNotEmpty(icon)) {
|
if (StrUtil.isNotEmpty(icon)) {
|
||||||
bookmarkDao.updateIcon(item.getBookmarkId(), icon);
|
bookmarkDao.updateIcon(item.getBookmarkId(), icon);
|
||||||
}
|
}
|
||||||
@ -305,24 +408,96 @@ public class BookmarkServiceImpl implements BookmarkService {
|
|||||||
return resPath;
|
return resPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getIconBase64(String url) {
|
/**
|
||||||
if (StrUtil.isEmpty(url)) {
|
* 获取icon,通过网络获取,或者从base64还原
|
||||||
return "";
|
*
|
||||||
}
|
* @param url 书签url路径
|
||||||
|
* @param icon base64编码的icon
|
||||||
|
* @param iconUrl base64编码的文件,文件名,用于获取文件名后缀
|
||||||
|
* @param quick 是否快速获取
|
||||||
|
* @return {@link String}
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
private String getIconPath(String url, String icon, String iconUrl, boolean quick) {
|
||||||
|
String host;
|
||||||
try {
|
try {
|
||||||
URL urlObj = new URL(url);
|
URL urlObj = new URL(url);
|
||||||
byte[] data = HttpUtil.download(urlIconAddress + "/icon?url=" + urlObj.getHost() + "&size=8..16..64", false);
|
host = urlObj.getAuthority();
|
||||||
String base64 = new String(Base64.getEncoder().encode(data));
|
} catch (Exception e) {
|
||||||
if (StrUtil.isNotEmpty(base64)) {
|
|
||||||
return "data:image/png;base64," + base64;
|
|
||||||
} else {
|
|
||||||
log.warn("url无法获取icon:{}", url);
|
|
||||||
}
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
log.warn("url无法解析出domain:{}", url);
|
log.warn("url无法解析出domain:{}", url);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(icon)) {
|
||||||
|
//优先从base64还原出图片
|
||||||
|
try {
|
||||||
|
byte[] b = Base64Decoder.decode(icon.substring(icon.indexOf(",") + 1));
|
||||||
|
String iconPath = saveToFile(iconUrl, host, b);
|
||||||
|
hostIconDao.deleteByHost(host);
|
||||||
|
hostIconDao.insert(host, iconPath);
|
||||||
|
return iconPath;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析base64获取icon故障:{}", iconUrl, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String iconPath = hostIconDao.selectByHost(host);
|
||||||
|
if (iconPath != null) {
|
||||||
|
return iconPath;
|
||||||
|
}
|
||||||
|
//再根据url解析
|
||||||
|
iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..128..256", quick);
|
||||||
|
if (StrUtil.isNotEmpty(iconPath)) {
|
||||||
|
hostIconDao.insert(host, iconPath);
|
||||||
|
}
|
||||||
|
return iconPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存文件到icon路径
|
||||||
|
*
|
||||||
|
* @param host host
|
||||||
|
* @param url url
|
||||||
|
* @param quick 是否快速获取,快速获取超时时间1s
|
||||||
|
* @return {@link String}
|
||||||
|
* @author FleyX
|
||||||
|
*/
|
||||||
|
private String saveFile(String host, String url, boolean quick) {
|
||||||
|
try (Response res = (quick ? HttpUtil.getSHORT_CLIENT() : HttpUtil.getClient(false)).newCall(new Request.Builder().url(url)
|
||||||
|
.header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36")
|
||||||
|
.get().build()).execute()) {
|
||||||
|
assert res.body() != null;
|
||||||
|
if (!HttpUtil.checkIsOk(res.code())) {
|
||||||
|
throw new CustomException("请求错误:" + res.code());
|
||||||
|
}
|
||||||
|
byte[] data = res.body().byteStream().readAllBytes();
|
||||||
|
if (data.length > 0) {
|
||||||
|
String iconUrl = new URL(res.request().url().toString()).getPath();
|
||||||
|
return saveToFile(iconUrl, host, data);
|
||||||
|
} else {
|
||||||
|
log.info("未获取到icon:{}", url);
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException timeoutException) {
|
||||||
|
log.info("获取icon超时:{}", host);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("url获取icon故障:{}", url, e);
|
log.error("url获取icon故障:{}", url, e);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存到文件中
|
||||||
|
*
|
||||||
|
* @param iconUrl icon文件名
|
||||||
|
* @param host host
|
||||||
|
* @param b 数据
|
||||||
|
* @return {@link String}
|
||||||
|
* @author FleyX
|
||||||
|
*/
|
||||||
|
private String saveToFile(String iconUrl, String host, byte[] b) {
|
||||||
|
String fileName = host.replace(":", ".") + iconUrl.substring(iconUrl.lastIndexOf("."));
|
||||||
|
String filePath = Paths.get(FileConstant.FAVICON_PATH, host.replace("www", "").replaceAll("\\.", "").substring(0, 2), fileName).toString();
|
||||||
|
FileUtil.writeBytes(b, Paths.get(CommonConstant.fileSavePath, filePath).toString());
|
||||||
|
return File.separator + filePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,6 @@
|
|||||||
<artifactId>bookmark-common</artifactId>
|
<artifactId>bookmark-common</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.fanxb.bookmark.business.user.constant;
|
package com.fanxb.bookmark.business.user.constant;
|
||||||
|
|
||||||
|
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@ -17,6 +18,6 @@ public class FileConstant {
|
|||||||
/**
|
/**
|
||||||
* 用户头像目录
|
* 用户头像目录
|
||||||
*/
|
*/
|
||||||
public static String iconPath = Paths.get("files", "public", "icon").toString();
|
public static String iconPath = Paths.get(CommonConstant.fileSavePath, "files", "public", "icon").toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.fanxb.bookmark.business.user.controller;
|
||||||
|
|
||||||
|
import com.fanxb.bookmark.business.user.dao.SearchEngineDao;
|
||||||
|
import com.fanxb.bookmark.business.user.entity.SearchEngine;
|
||||||
|
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||||
|
import com.fanxb.bookmark.common.entity.Result;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/searchEngine")
|
||||||
|
public class SearchEngineController {
|
||||||
|
@Autowired
|
||||||
|
private SearchEngineService searchEngineService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result list() {
|
||||||
|
return Result.success(searchEngineService.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/insert")
|
||||||
|
public Result insert(@RequestBody SearchEngine body){
|
||||||
|
searchEngineService.insertOne(body);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/edit")
|
||||||
|
public Result edit(@RequestBody SearchEngine body){
|
||||||
|
searchEngineService.editOne(body);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public Result delete(@RequestBody SearchEngine body){
|
||||||
|
searchEngineService.deleteOne(body.getId());
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/setChecked")
|
||||||
|
public Result setChecked(@RequestBody SearchEngine body){
|
||||||
|
searchEngineService.setChecked(body.getId());
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.fanxb.bookmark.business.user.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fanxb.bookmark.business.user.entity.SearchEngine;
|
||||||
|
|
||||||
|
public interface SearchEngineDao extends BaseMapper<SearchEngine> {
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package com.fanxb.bookmark.business.user.dao;
|
package com.fanxb.bookmark.business.user.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.fanxb.bookmark.common.entity.po.User;
|
import com.fanxb.bookmark.common.entity.po.User;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
@ -16,7 +17,7 @@ import java.util.List;
|
|||||||
* @date 2019/7/6 11:36
|
* @date 2019/7/6 11:36
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public interface UserDao {
|
public interface UserDao extends BaseMapper<User> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description: 新增一个用户
|
* Description: 新增一个用户
|
||||||
@ -182,16 +183,6 @@ public interface UserDao {
|
|||||||
@Select("select userId from user order by userId limit #{start},#{size}")
|
@Select("select userId from user order by userId limit #{start},#{size}")
|
||||||
List<Integer> selectUserIdPage(@Param("start") int start, @Param("size") int size);
|
List<Integer> selectUserIdPage(@Param("start") int start, @Param("size") int size);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户搜索引擎
|
|
||||||
*
|
|
||||||
* @param userId userId
|
|
||||||
* @param engine engine
|
|
||||||
* @author fanxb
|
|
||||||
* @date 2021/3/14
|
|
||||||
**/
|
|
||||||
@Update("update user set defaultSearchEngine=#{engine} where userId=#{userId}")
|
|
||||||
void updateSearchEngine(@Param("userId") int userId, @Param("engine") String engine);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新一个字段-一个条件
|
* 更新一个字段-一个条件
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.fanxb.bookmark.business.user.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@TableName("search_engine")
|
||||||
|
public class SearchEngine {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer id;
|
||||||
|
private Integer userId;
|
||||||
|
private Integer checked;
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* url
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
/**
|
||||||
|
* 图标
|
||||||
|
*/
|
||||||
|
private String icon;
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.fanxb.bookmark.business.user.service;
|
||||||
|
|
||||||
|
import com.fanxb.bookmark.business.user.entity.SearchEngine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface SearchEngineService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表查询
|
||||||
|
*/
|
||||||
|
List<SearchEngine> list();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete one by id
|
||||||
|
*
|
||||||
|
* @param id id
|
||||||
|
*/
|
||||||
|
void deleteOne(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert one
|
||||||
|
*
|
||||||
|
* @param body body
|
||||||
|
*/
|
||||||
|
void insertOne(SearchEngine body);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* edit one
|
||||||
|
*
|
||||||
|
* @param body body
|
||||||
|
*/
|
||||||
|
void editOne(SearchEngine body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设为默认搜索项
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
void setChecked(Integer id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新用户初始化
|
||||||
|
*
|
||||||
|
* @param userId userId
|
||||||
|
*/
|
||||||
|
void newUserInit(int userId);
|
||||||
|
}
|
@ -87,6 +87,6 @@ public class BaseInfoServiceImpl implements BaseInfoService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeDefaultSearchEngine(User user) {
|
public void changeDefaultSearchEngine(User user) {
|
||||||
userDao.updateSearchEngine(user.getUserId(), user.getDefaultSearchEngine());
|
userDao.updateById(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.fanxb.bookmark.business.user.dao.UserDao;
|
import com.fanxb.bookmark.business.user.dao.UserDao;
|
||||||
import com.fanxb.bookmark.business.user.service.OauthService;
|
import com.fanxb.bookmark.business.user.service.OauthService;
|
||||||
|
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||||
import com.fanxb.bookmark.business.user.service.UserService;
|
import com.fanxb.bookmark.business.user.service.UserService;
|
||||||
import com.fanxb.bookmark.business.user.vo.OauthBody;
|
import com.fanxb.bookmark.business.user.vo.OauthBody;
|
||||||
import com.fanxb.bookmark.common.constant.CommonConstant;
|
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||||
@ -38,6 +39,8 @@ public class OauthServiceImpl implements OauthService {
|
|||||||
private String githubClientId;
|
private String githubClientId;
|
||||||
@Value("${OAuth.github.secret}")
|
@Value("${OAuth.github.secret}")
|
||||||
private String githubSecret;
|
private String githubSecret;
|
||||||
|
@Autowired
|
||||||
|
private SearchEngineService searchEngineService;
|
||||||
private final UserDao userDao;
|
private final UserDao userDao;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
@ -105,6 +108,7 @@ public class OauthServiceImpl implements OauthService {
|
|||||||
other.setLastLoginTime(System.currentTimeMillis());
|
other.setLastLoginTime(System.currentTimeMillis());
|
||||||
other.setVersion(0);
|
other.setVersion(0);
|
||||||
userDao.addOne(other);
|
userDao.addOne(other);
|
||||||
|
searchEngineService.newUserInit(other.getUserId());
|
||||||
return other;
|
return other;
|
||||||
} else {
|
} else {
|
||||||
if (!current.getEmail().equals(other.getEmail()) || !current.getGithubId().equals(other.getGithubId())) {
|
if (!current.getEmail().equals(other.getEmail()) || !current.getGithubId().equals(other.getGithubId())) {
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.fanxb.bookmark.business.user.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.druid.support.ibatis.SpringIbatisBeanNameAutoProxyCreator;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.fanxb.bookmark.business.user.dao.SearchEngineDao;
|
||||||
|
import com.fanxb.bookmark.business.user.dao.UserDao;
|
||||||
|
import com.fanxb.bookmark.business.user.entity.SearchEngine;
|
||||||
|
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||||
|
import com.fanxb.bookmark.common.entity.UserContext;
|
||||||
|
import com.fanxb.bookmark.common.entity.po.User;
|
||||||
|
import com.fanxb.bookmark.common.exception.CustomException;
|
||||||
|
import com.fanxb.bookmark.common.util.UserContextHolder;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SearchEngineServiceImpl implements SearchEngineService {
|
||||||
|
@Autowired
|
||||||
|
private SearchEngineDao searchEngineDao;
|
||||||
|
@Autowired
|
||||||
|
private UserDao userDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchEngine> list() {
|
||||||
|
return searchEngineDao.selectList(new LambdaQueryWrapper<SearchEngine>().eq(SearchEngine::getUserId, UserContextHolder.get().getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteOne(int id) {
|
||||||
|
SearchEngine engine = searchEngineDao.selectById(id);
|
||||||
|
if (engine.getUserId() != UserContextHolder.get().getUserId()) {
|
||||||
|
throw new CustomException("无法操作其他人数据");
|
||||||
|
}
|
||||||
|
if (engine.getChecked() == 1) {
|
||||||
|
throw new CustomException("默认搜索引擎无法删除");
|
||||||
|
}
|
||||||
|
searchEngineDao.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertOne(SearchEngine body) {
|
||||||
|
checkOne(body);
|
||||||
|
body.setId(null).setChecked(0).setUserId(UserContextHolder.get().getUserId());
|
||||||
|
searchEngineDao.insert(body);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkOne(SearchEngine body) {
|
||||||
|
if (StrUtil.hasBlank(body.getIcon(), body.getUrl(), body.getName())) {
|
||||||
|
throw new CustomException("请填写完整");
|
||||||
|
}
|
||||||
|
if (!body.getUrl().contains("%s")) {
|
||||||
|
throw new CustomException("路径中必须包含%s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void editOne(SearchEngine body) {
|
||||||
|
SearchEngine engine = searchEngineDao.selectById(body.getId());
|
||||||
|
if (engine.getUserId() != UserContextHolder.get().getUserId()) {
|
||||||
|
throw new CustomException("无法操作其他人数据");
|
||||||
|
}
|
||||||
|
checkOne(body);
|
||||||
|
searchEngineDao.updateById(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void setChecked(Integer id) {
|
||||||
|
int userId = UserContextHolder.get().getUserId();
|
||||||
|
LambdaUpdateWrapper<SearchEngine> update = new LambdaUpdateWrapper<SearchEngine>().set(SearchEngine::getChecked, 0).eq(SearchEngine::getUserId, userId).eq(SearchEngine::getChecked, 1);
|
||||||
|
searchEngineDao.update(null, update);
|
||||||
|
update = new LambdaUpdateWrapper<SearchEngine>().set(SearchEngine::getChecked, 1).eq(SearchEngine::getId, id).eq(SearchEngine::getUserId, userId);
|
||||||
|
searchEngineDao.update(null, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void newUserInit(int userId) {
|
||||||
|
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-baidu").setName("百度").setUrl("https://www.baidu.com/s?ie=UTF-8&wd=%s").setChecked(1));
|
||||||
|
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-bing").setName("必应").setUrl("https://www.bing.com/search?q=%s").setChecked(0));
|
||||||
|
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-google").setName("谷歌").setUrl("https://www.google.com/search?q=%s").setChecked(0));
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.fanxb.bookmark.business.api.BookmarkApi;
|
import com.fanxb.bookmark.business.api.BookmarkApi;
|
||||||
import com.fanxb.bookmark.business.user.constant.FileConstant;
|
import com.fanxb.bookmark.business.user.constant.FileConstant;
|
||||||
import com.fanxb.bookmark.business.user.dao.UserDao;
|
import com.fanxb.bookmark.business.user.dao.UserDao;
|
||||||
|
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||||
import com.fanxb.bookmark.business.user.service.UserService;
|
import com.fanxb.bookmark.business.user.service.UserService;
|
||||||
import com.fanxb.bookmark.business.user.vo.LoginBody;
|
import com.fanxb.bookmark.business.user.vo.LoginBody;
|
||||||
import com.fanxb.bookmark.business.user.vo.RegisterBody;
|
import com.fanxb.bookmark.business.user.vo.RegisterBody;
|
||||||
@ -20,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -42,6 +44,8 @@ public class UserServiceImpl implements UserService {
|
|||||||
* 登陆最大重试次数
|
* 登陆最大重试次数
|
||||||
*/
|
*/
|
||||||
private static final int LOGIN_COUNT = 5;
|
private static final int LOGIN_COUNT = 5;
|
||||||
|
@Autowired
|
||||||
|
private SearchEngineService searchEngineService;
|
||||||
|
|
||||||
private final UserDao userDao;
|
private final UserDao userDao;
|
||||||
private final StringRedisTemplate redisTemplate;
|
private final StringRedisTemplate redisTemplate;
|
||||||
@ -83,6 +87,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/6 11:30
|
* @date 2019/7/6 11:30
|
||||||
*/
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public String register(RegisterBody body) {
|
public String register(RegisterBody body) {
|
||||||
User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail());
|
User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail());
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
@ -102,6 +107,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
user.setLastLoginTime(System.currentTimeMillis());
|
user.setLastLoginTime(System.currentTimeMillis());
|
||||||
user.setVersion(0);
|
user.setVersion(0);
|
||||||
userDao.addOne(user);
|
userDao.addOne(user);
|
||||||
|
searchEngineService.newUserInit(user.getUserId());
|
||||||
Map<String, String> data = new HashMap<>(1);
|
Map<String, String> data = new HashMap<>(1);
|
||||||
data.put("userId", String.valueOf(user.getUserId()));
|
data.put("userId", String.valueOf(user.getUserId()));
|
||||||
return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME);
|
return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME);
|
||||||
|
@ -37,16 +37,10 @@
|
|||||||
<artifactId>commons-pool2</artifactId>
|
<artifactId>commons-pool2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--mybatis依赖-->
|
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
|
|
||||||
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
|
|
||||||
<!-- <version>2.0.1</version>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
<version>3.5.1</version>
|
<version>3.5.3.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -59,21 +53,66 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>druid-spring-boot-starter</artifactId>
|
<artifactId>druid-spring-boot-starter</artifactId>
|
||||||
<version>1.1.18</version>
|
<version>1.2.18</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--数据库版本管理-->
|
<!--数据库版本管理-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
<artifactId>flyway-core</artifactId>
|
<artifactId>flyway-core</artifactId>
|
||||||
<version>5.2.4</version>
|
<version>9.21.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-mysql</artifactId>
|
||||||
|
<version>9.21.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--mysql jdbc依赖-->
|
<!--mysql jdbc依赖-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<version>8.0.33</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <!–邮件依赖–>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-mail</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!-- <!–减负依赖–>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.projectlombok</groupId>-->
|
||||||
|
<!-- <artifactId>lombok</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- <!–json工具依赖–>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>com.alibaba</groupId>-->
|
||||||
|
<!-- <artifactId>fastjson</artifactId>-->
|
||||||
|
<!-- <version>1.2.83</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.elasticsearch.client</groupId>-->
|
||||||
|
<!-- <artifactId>elasticsearch-rest-high-level-client</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>cn.hutool</groupId>-->
|
||||||
|
<!-- <artifactId>hutool-all</artifactId>-->
|
||||||
|
<!-- <version>5.8.21</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!--单元测试-->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
|
||||||
|
<!--mysql jdbc依赖-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>mysql</groupId>-->
|
||||||
|
<!-- <artifactId>mysql-connector-java</artifactId>-->
|
||||||
|
<!-- <version>8.0.33</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
<!--邮件依赖-->
|
<!--邮件依赖-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -89,7 +128,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>1.2.73</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
@ -101,7 +140,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-all</artifactId>
|
<artifactId>hutool-all</artifactId>
|
||||||
<version>5.2.3</version>
|
<version>5.8.25</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--单元测试-->
|
<!--单元测试-->
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package com.fanxb.bookmark.common.configuration;
|
package com.fanxb.bookmark.common.configuration;
|
||||||
|
|
||||||
import com.fanxb.bookmark.common.factory.CustomThreadFactory;
|
import com.fanxb.bookmark.common.factory.CustomThreadFactory;
|
||||||
|
import com.fanxb.bookmark.common.factory.ThreadPoolFactory;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
|
||||||
@ -11,15 +14,16 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|||||||
* Created with IntelliJ IDEA
|
* Created with IntelliJ IDEA
|
||||||
*
|
*
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2020/1/26
|
|
||||||
*/
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
public class ScheduleConfig implements SchedulingConfigurer {
|
public class ScheduleConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
|
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
|
||||||
|
|
||||||
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule"));
|
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule"));
|
||||||
scheduledTaskRegistrar.setScheduler(service);
|
scheduledTaskRegistrar.setScheduler(service);
|
||||||
|
log.info("自定义schedule线程池成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,4 +13,8 @@ public class NumberConstant {
|
|||||||
* 2^10
|
* 2^10
|
||||||
*/
|
*/
|
||||||
public static final int K_SIZE = 1024;
|
public static final int K_SIZE = 1024;
|
||||||
|
/**
|
||||||
|
* 一天的秒数
|
||||||
|
*/
|
||||||
|
public static final int S_DAY = 24 * 60 * 60;
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,6 @@ public class RedisConstant {
|
|||||||
public static String getUserFailCountKey(String username) {
|
public static String getUserFailCountKey(String username) {
|
||||||
return "bookmark_user_fail_count_" + username;
|
return "bookmark_user_fail_count_" + username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String BING_IMG = "bing_img";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.fanxb.bookmark.common.dao;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.fanxb.bookmark.common.entity.po.GlobalConfigPo;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface GlobalConfigDao extends BaseMapper<GlobalConfigPo> {
|
||||||
|
}
|
@ -12,7 +12,6 @@ import java.util.List;
|
|||||||
* 类功能详述:
|
* 类功能详述:
|
||||||
*
|
*
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2019/7/8 11:19
|
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class Bookmark {
|
public class Bookmark {
|
||||||
@ -36,6 +35,7 @@ public class Bookmark {
|
|||||||
private String name;
|
private String name;
|
||||||
private String url = "";
|
private String url = "";
|
||||||
private String icon = "";
|
private String icon = "";
|
||||||
|
private String iconUrl;
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
private String searchKey = "";
|
private String searchKey = "";
|
||||||
private Long addTime;
|
private Long addTime;
|
||||||
@ -53,7 +53,7 @@ public class Bookmark {
|
|||||||
this.setUserId(userId);
|
this.setUserId(userId);
|
||||||
this.setPath(path);
|
this.setPath(path);
|
||||||
this.setType(FOLDER_TYPE);
|
this.setType(FOLDER_TYPE);
|
||||||
this.setName(name);
|
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||||
this.setAddTime(addTime);
|
this.setAddTime(addTime);
|
||||||
this.setSort(sort);
|
this.setSort(sort);
|
||||||
this.setCreateTime(System.currentTimeMillis());
|
this.setCreateTime(System.currentTimeMillis());
|
||||||
@ -64,7 +64,7 @@ public class Bookmark {
|
|||||||
this.setUserId(userId);
|
this.setUserId(userId);
|
||||||
this.setPath(path);
|
this.setPath(path);
|
||||||
this.setType(BOOKMARK_TYPE);
|
this.setType(BOOKMARK_TYPE);
|
||||||
this.setName(name);
|
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||||
this.setUrl(url);
|
this.setUrl(url);
|
||||||
this.setIcon(icon);
|
this.setIcon(icon);
|
||||||
this.setSort(sort);
|
this.setSort(sort);
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.fanxb.bookmark.common.entity.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局配置表
|
||||||
|
*
|
||||||
|
* @author FleyX
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("global_config")
|
||||||
|
public class GlobalConfigPo {
|
||||||
|
@TableId(value = "code")
|
||||||
|
private String code;
|
||||||
|
private String value;
|
||||||
|
private String description;
|
||||||
|
}
|
@ -2,6 +2,10 @@ package com.fanxb.bookmark.common.entity.po;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.annotation.JSONField;
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -16,9 +20,11 @@ import java.util.Map;
|
|||||||
* @date 2019/7/4 20:14
|
* @date 2019/7/4 20:14
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@TableName("user")
|
||||||
public class User {
|
public class User {
|
||||||
|
|
||||||
private int userId;
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Integer userId;
|
||||||
/**
|
/**
|
||||||
* 第三方github登陆id,-1说明非github登陆
|
* 第三方github登陆id,-1说明非github登陆
|
||||||
*/
|
*/
|
||||||
@ -30,6 +36,7 @@ public class User {
|
|||||||
/**
|
/**
|
||||||
* 是否未设置密码
|
* 是否未设置密码
|
||||||
*/
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
private Boolean noPassword;
|
private Boolean noPassword;
|
||||||
@JSONField(serialize = false)
|
@JSONField(serialize = false)
|
||||||
private String password;
|
private String password;
|
||||||
@ -42,9 +49,10 @@ public class User {
|
|||||||
* 书签同步版本
|
* 书签同步版本
|
||||||
*/
|
*/
|
||||||
private int version;
|
private int version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认搜索引擎
|
* 默认搜索引擎
|
||||||
*/
|
*/
|
||||||
private String defaultSearchEngine;
|
private Integer searchEngineId;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.fanxb.bookmark.common.entity.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局公共配置
|
||||||
|
*
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class GlobalConfigVo {
|
||||||
|
/**
|
||||||
|
* 是否存在网络代理(外网访问)
|
||||||
|
*/
|
||||||
|
private boolean proxyExist;
|
||||||
|
/**
|
||||||
|
* bing每日一图地址
|
||||||
|
*/
|
||||||
|
private String bingImgSrc;
|
||||||
|
/**
|
||||||
|
* 浏览器插件版本plugin
|
||||||
|
*/
|
||||||
|
private Map<String, String> map;
|
||||||
|
}
|
@ -142,7 +142,7 @@ public class LoginFilter implements Filter {
|
|||||||
UserContextHolder.set(context);
|
UserContextHolder.set(context);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("jwt解密失败:{},原因:{}", jwt, e.getMessage());
|
log.info("jwt解密失败:{},原因:{}", jwt, e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.fanxb.bookmark.common.schedule;
|
||||||
|
|
||||||
|
import com.fanxb.bookmark.common.service.ConfigService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BingImgSchedule {
|
||||||
|
private final ConfigService configService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public BingImgSchedule(ConfigService configService) {
|
||||||
|
this.configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
@Scheduled(cron = "* 0 0/1 * * *")
|
||||||
|
public void cache() {
|
||||||
|
configService.getCacheBingImg();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package com.fanxb.bookmark.common.service;
|
package com.fanxb.bookmark.common.service;
|
||||||
|
|
||||||
import java.util.Map;
|
import com.fanxb.bookmark.common.entity.vo.GlobalConfigVo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局配置相关
|
* 全局配置相关
|
||||||
@ -17,5 +17,12 @@ public interface ConfigService {
|
|||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2021/9/15 下午9:58
|
* @date 2021/9/15 下午9:58
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getGlobalConfig();
|
GlobalConfigVo getGlobalConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存bing每日一图到redis
|
||||||
|
*
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
String getCacheBingImg();
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,96 @@
|
|||||||
package com.fanxb.bookmark.common.service.impl;
|
package com.fanxb.bookmark.common.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUnit;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||||
|
import com.fanxb.bookmark.common.constant.NumberConstant;
|
||||||
|
import com.fanxb.bookmark.common.constant.RedisConstant;
|
||||||
|
import com.fanxb.bookmark.common.dao.GlobalConfigDao;
|
||||||
|
import com.fanxb.bookmark.common.entity.po.GlobalConfigPo;
|
||||||
|
import com.fanxb.bookmark.common.entity.vo.GlobalConfigVo;
|
||||||
import com.fanxb.bookmark.common.service.ConfigService;
|
import com.fanxb.bookmark.common.service.ConfigService;
|
||||||
import com.fanxb.bookmark.common.util.HttpUtil;
|
import com.fanxb.bookmark.common.util.HttpUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.io.File;
|
||||||
import java.util.Map;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author fanxb
|
* @author fanxb
|
||||||
* @date 2021-09-15-下午9:59
|
* @date 2021-09-15-下午9:59
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class ConfigServiceImpl implements ConfigService {
|
public class ConfigServiceImpl implements ConfigService {
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getGlobalConfig() {
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
Map<String, Object> res = new HashMap<>(1);
|
private final GlobalConfigDao globalConfigDao;
|
||||||
res.put("proxyExist", HttpUtil.getProxyExist());
|
|
||||||
return res;
|
@Autowired
|
||||||
|
public ConfigServiceImpl(StringRedisTemplate stringRedisTemplate, GlobalConfigDao globalConfigDao) {
|
||||||
|
this.stringRedisTemplate = stringRedisTemplate;
|
||||||
|
this.globalConfigDao = globalConfigDao;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value("${bing.host}")
|
||||||
|
private String bingHost;
|
||||||
|
@Value("${bing.onePic}")
|
||||||
|
private String bingUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GlobalConfigVo getGlobalConfig() {
|
||||||
|
List<GlobalConfigPo> pos = globalConfigDao.selectByMap(Collections.emptyMap());
|
||||||
|
Map<String, String> map = pos.stream().collect(Collectors.toMap(GlobalConfigPo::getCode, GlobalConfigPo::getValue));
|
||||||
|
GlobalConfigVo vo = new GlobalConfigVo();
|
||||||
|
vo.setBingImgSrc(getCacheBingImg());
|
||||||
|
vo.setMap(map);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCacheBingImg() {
|
||||||
|
String str = stringRedisTemplate.opsForValue().get(RedisConstant.BING_IMG);
|
||||||
|
if (str != null) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
str = getBingImg();
|
||||||
|
stringRedisTemplate.opsForValue().set(RedisConstant.BING_IMG, str, 2, TimeUnit.HOURS);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBingImg() {
|
||||||
|
try {
|
||||||
|
JSONObject bingObj = HttpUtil.getObj(bingHost + bingUrl, null, false);
|
||||||
|
String path = bingObj.getJSONArray("images").getJSONObject(0).getString("url");
|
||||||
|
String picUrl = bingHost + path;
|
||||||
|
Request request = new Request.Builder().url(picUrl).build();
|
||||||
|
try (Response res = HttpUtil.getClient(false).newCall(request).execute()) {
|
||||||
|
byte[] bytes = res.body().bytes();
|
||||||
|
String filePath = CommonConstant.fileSavePath + "/files/public/bing.jpg";
|
||||||
|
FileUtil.writeBytes(bytes, filePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取bing每日一图错误:{}", e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取bing每日一图错误:{}", e.getLocalizedMessage(), e);
|
||||||
|
}
|
||||||
|
return "/files/public/bing.jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ import org.springframework.stereotype.Component;
|
|||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -51,42 +49,41 @@ public class HttpUtil {
|
|||||||
/**
|
/**
|
||||||
* 无代理环境
|
* 无代理环境
|
||||||
*/
|
*/
|
||||||
private static final OkHttpClient CLIENT = new OkHttpClient.Builder().connectTimeout(2, TimeUnit.SECONDS)
|
private static final OkHttpClient CLIENT = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS)
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超时时间1s
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private static final OkHttpClient SHORT_CLIENT = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(1, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端
|
||||||
|
*
|
||||||
|
* @param proxy 是否代理
|
||||||
|
* @return {@link OkHttpClient}
|
||||||
|
* @author fanxb
|
||||||
|
*/
|
||||||
|
public static OkHttpClient getClient(boolean proxy) {
|
||||||
|
return proxy ? PROXY_CLIENT : CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS);
|
||||||
log.info("代理配置,ip:{},port:{}", proxyIp, proxyPort);
|
log.info("代理配置,ip:{},port:{}", proxyIp, proxyPort);
|
||||||
if (StrUtil.isNotBlank(proxyIp) && StrUtil.isNotBlank(proxyPort)) {
|
if (StrUtil.isNotBlank(proxyIp) && StrUtil.isNotBlank(proxyPort)) {
|
||||||
builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp, Integer.parseInt(proxyPort))));
|
builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp, Integer.parseInt(proxyPort))));
|
||||||
proxyExist = true;
|
proxyExist = true;
|
||||||
}
|
PROXY_CLIENT = builder.build();
|
||||||
PROXY_CLIENT = builder.connectTimeout(10, TimeUnit.SECONDS)
|
} else {
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
PROXY_CLIENT = CLIENT;
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* 下载文件
|
|
||||||
* @author fanxb
|
|
||||||
* @param url 下载链接
|
|
||||||
* @param proxy 是否使用代理
|
|
||||||
* @return java.io.InputStream
|
|
||||||
* @date 2021/3/12
|
|
||||||
**/
|
|
||||||
public static byte[] download(String url, boolean proxy) {
|
|
||||||
try (Response res = (proxy ? PROXY_CLIENT : CLIENT).newCall(new Request.Builder().url(url).build()).execute()) {
|
|
||||||
assert res.body() != null;
|
|
||||||
if (checkIsOk(res.code())) {
|
|
||||||
return res.body().byteStream().readAllBytes();
|
|
||||||
} else {
|
|
||||||
throw new CustomException("下载出现问题:" + res.body().string());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new CustomException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,6 +267,8 @@ public class HttpUtil {
|
|||||||
}
|
}
|
||||||
return ipAddress;
|
return ipAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
|
||||||
<!-- This is an automatically generated file.
|
|
||||||
It will be read and overwritten.
|
|
||||||
DO NOT EDIT! -->
|
|
||||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
|
||||||
<TITLE>Bookmarks</TITLE>
|
|
||||||
<H1>Bookmarks Menu</H1>
|
|
||||||
|
|
||||||
<DL><p>
|
|
||||||
<DT><A HREF="http://0.0.0.1/" ADD_DATE="1614837460" LAST_MODIFIED="1614837465">1</A>
|
|
||||||
<DT><A HREF="http://0.0.0.2/" ADD_DATE="1614837471" LAST_MODIFIED="1614837474">2</A>
|
|
||||||
<DT><H3 ADD_DATE="1614837478" LAST_MODIFIED="1614837497">f1</H3>
|
|
||||||
<DL><p>
|
|
||||||
<DT><A HREF="http://asdf/" ADD_DATE="1614837485" LAST_MODIFIED="1614837493" TAGS="ww">f11</A>
|
|
||||||
<DT><A HREF="http://f12/" ADD_DATE="1614837497" LAST_MODIFIED="1614837502">f12</A>
|
|
||||||
</DL><p>
|
|
||||||
</DL>
|
|
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
|
||||||
<!-- This is an automatically generated file.
|
|
||||||
It will be read and overwritten.
|
|
||||||
DO NOT EDIT! -->
|
|
||||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
|
||||||
<TITLE>Bookmarks</TITLE>
|
|
||||||
<H1>Bookmarks Menu</H1>
|
|
||||||
|
|
||||||
<DL><p>
|
|
||||||
<DT><A HREF="http://0.0.0.1/" ADD_DATE="1614837460" LAST_MODIFIED="1614837465">1</A>
|
|
||||||
<DT><A HREF="http://0.0.0.2/" ADD_DATE="1614837471" LAST_MODIFIED="1614837474">2</A>
|
|
||||||
<DT><H3 ADD_DATE="1614837478" LAST_MODIFIED="1614837497">f1</H3>
|
|
||||||
<DL><p>
|
|
||||||
<DT><A HREF="http://asdf/" ADD_DATE="1614837485" LAST_MODIFIED="1614837493" TAGS="ww">f11</A>
|
|
||||||
<DT><A HREF="http://f12/" ADD_DATE="1614837497" LAST_MODIFIED="1614837502">f12</A>
|
|
||||||
</DL><p>
|
|
||||||
</DL>
|
|
@ -22,7 +22,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.3.12.RELEASE</version>
|
<version>2.7.17</version>
|
||||||
<relativePath/>
|
<relativePath/>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -91,6 +91,10 @@ proxy:
|
|||||||
# url icon服务提供地址
|
# url icon服务提供地址
|
||||||
urlIconAddress: http://localhost:11001
|
urlIconAddress: http://localhost:11001
|
||||||
|
|
||||||
|
bing:
|
||||||
|
host: https://cn.bing.com
|
||||||
|
onePic: /HPImageArchive.aspx?format=js&idx=0&n=1
|
||||||
|
|
||||||
# 管理员用户id(因为目前尚未设计分角色权限系统,须指定管理员用户id)
|
# 管理员用户id(因为目前尚未设计分角色权限系统,须指定管理员用户id)
|
||||||
manageUserId: -1
|
manageUserId: -1
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE bookmark.host_icon (
|
||||||
|
id INT UNSIGNED auto_increment NOT NULL,
|
||||||
|
host varchar(300) NOT NULL COMMENT 'host',
|
||||||
|
iconPath varchar(330) NOT NULL,
|
||||||
|
CONSTRAINT host_icon_pk PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_0900_ai_ci;
|
||||||
|
CREATE INDEX host_icon_host_IDX USING BTREE ON bookmark.host_icon (host(20));
|
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE bookmark.global_config
|
||||||
|
(
|
||||||
|
code varchar(20) NOT NULL,
|
||||||
|
value varchar(100) NOT NULL COMMENT '值',
|
||||||
|
description varchar(100) NOT NULL COMMENT '描述',
|
||||||
|
CONSTRAINT global_config_pk PRIMARY KEY (code)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_0900_ai_ci
|
||||||
|
COMMENT='全局配置表';
|
||||||
|
|
||||||
|
insert into global_config
|
||||||
|
values ("pluginVersion", "0.1.1", "浏览器插件版本");
|
@ -0,0 +1,3 @@
|
|||||||
|
update global_config
|
||||||
|
set value='0.1.2'
|
||||||
|
where code = "pluginVersion";
|
@ -0,0 +1,22 @@
|
|||||||
|
create table search_engine
|
||||||
|
(
|
||||||
|
id int auto_increment
|
||||||
|
primary key,
|
||||||
|
userId int not null,
|
||||||
|
checked tinyint not null default 0,
|
||||||
|
name varchar(20) null,
|
||||||
|
url varchar(500) null,
|
||||||
|
icon varchar(20) null
|
||||||
|
) auto_increment=1001;
|
||||||
|
create index search_engine_userId_index
|
||||||
|
on search_engine (userId);
|
||||||
|
|
||||||
|
insert into search_engine(userId, checked, name, url, icon)
|
||||||
|
select userId, if(defaultSearchEngine = 'baidu', 1, 0), '百度', 'https://www.baidu.com/s?ie=UTF-8&wd=%s', 'icon-baidu'
|
||||||
|
from user;
|
||||||
|
insert into search_engine(userId, checked, name, url, icon)
|
||||||
|
select userId, if(defaultSearchEngine = 'bing', 1, 0), '必应', 'https://www.bing.com/search?q=%s', 'icon-bing'
|
||||||
|
from user;
|
||||||
|
insert into search_engine(userId, checked, name, url, icon)
|
||||||
|
select userId, if(defaultSearchEngine = 'google', 1, 0), '谷歌', 'https://www.google.com/search?q=%s', 'icon-google'
|
||||||
|
from user;
|
@ -0,0 +1,2 @@
|
|||||||
|
alter table bookmark
|
||||||
|
modify name varchar(2000) not null;
|
3
bookmark_front/.gitignore
vendored
3
bookmark_front/.gitignore
vendored
@ -3,6 +3,8 @@ node_modules
|
|||||||
/dist
|
/dist
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
public/files
|
||||||
|
public\static\bookmarkBrowserPlugin.zip
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
@ -22,3 +24,4 @@ pnpm-debug.log*
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
@ -20,11 +20,11 @@
|
|||||||
"vuex": "^3.4.0"
|
"vuex": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.4.0",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~4.4.0",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
"@vue/cli-plugin-router": "~4.4.0",
|
"@vue/cli-plugin-router": "~5.0.8",
|
||||||
"@vue/cli-plugin-vuex": "~4.4.0",
|
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||||
"@vue/cli-service": "~4.4.0",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
|
9549
bookmark_front/pnpm-lock.yaml
generated
Normal file
9549
bookmark_front/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,11 +7,20 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
|
mounted() {
|
||||||
|
window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
|
||||||
|
let script = document.createElement("script");
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.defer = true;
|
||||||
|
script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(script);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import "./global.less";
|
@import "./global.less";
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -20,6 +29,7 @@ body {
|
|||||||
background-color: @bgColor;
|
background-color: @bgColor;
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div :class="{ listShow: focused && list.length > 0 }" class="newSearch">
|
<div :class="{ listShow: focused && list.length > 0 }" class="newSearch">
|
||||||
<input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus" @blur="inputBlur" />
|
<input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus"
|
||||||
|
@blur="inputBlur" />
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-tooltip title="点击切换网页搜索">
|
<a-tooltip title="点击切换网页搜索">
|
||||||
<my-icon class="icon" style="margin-right: 0.5em" :type="searchIcon" />
|
<my-icon class="icon" style="margin-right: 0.5em" :type="checkedSearchEngine.icon"
|
||||||
|
@click="searchIconClick" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-menu slot="overlay" @click="searchEngineChange">
|
<a-menu slot="overlay" @click="searchEngineChange">
|
||||||
<a-menu-item key="google">谷歌</a-menu-item>
|
<a-menu-item v-for="item in searchEngineList" :key="item.id">{{ item.name }}</a-menu-item>
|
||||||
<a-menu-item key="bing">bing</a-menu-item>
|
|
||||||
<a-menu-item key="baidu">baidu</a-menu-item>
|
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<a-icon class="icon" type="search" @click="submit(true)" />
|
<a-icon class="icon" type="search" @click="submit(true)" />
|
||||||
@ -30,7 +30,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
<a-tooltip title="定位到书签树中" v-if="showLocation">
|
<a-tooltip title="定位到书签树中" v-if="showLocation">
|
||||||
<my-icon style="color: white; font-size: 1.3em" type="icon-et-location" @mousedown="location($event, item)" />
|
<my-icon style="color: white; font-size: 1.3em" type="icon-et-location"
|
||||||
|
@mousedown="location($event, item)" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="复制链接">
|
<a-tooltip title="复制链接">
|
||||||
<a-icon
|
<a-icon
|
||||||
@ -51,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a ref="targetA" style="left: 1000000px" target="_blank" />
|
<a ref="targetA" style="left: 1000000px" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -61,10 +62,11 @@ import { mapState } from "vuex";
|
|||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig";
|
import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig";
|
||||||
import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData";
|
import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Search",
|
name: "Search",
|
||||||
props: {
|
props: {
|
||||||
showLocation: Boolean, //是否显示定位等按钮
|
showLocation: Boolean //是否显示定位等按钮
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -74,19 +76,25 @@ export default {
|
|||||||
//上下选中
|
//上下选中
|
||||||
selectIndex: null,
|
selectIndex: null,
|
||||||
copyBoard: null, //剪贴板对象
|
copyBoard: null, //剪贴板对象
|
||||||
|
searchEngineList: [],
|
||||||
|
checkedSearchEngine: { icon: "icon-baidu", name: "百度", url: "https://www.baidu.com/s?ie=UTF-8&wd=%s" }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
//初始化clipboard
|
//初始化clipboard
|
||||||
this.copyBoard = new ClipboardJS(".search-copy-to-board", {
|
this.copyBoard = new ClipboardJS(".search-copy-to-board", {
|
||||||
text: function (trigger) {
|
text: function(trigger) {
|
||||||
return trigger.attributes.data.nodeValue;
|
return trigger.attributes.data.nodeValue;
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
this.copyBoard.on("success", (e) => {
|
this.copyBoard.on("success", (e) => {
|
||||||
this.$message.success("复制成功");
|
this.$message.success("复制成功");
|
||||||
e.clearSelection();
|
e.clearSelection();
|
||||||
});
|
});
|
||||||
|
if (this.$store.state.globalConfig.token != null) {
|
||||||
|
this.searchEngineList = await HttpUtil.get("/searchEngine/list");
|
||||||
|
this.checkedSearchEngine = this.searchEngineList.find(item => item.checked === 1);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
if (this.copyBoard != null) {
|
if (this.copyBoard != null) {
|
||||||
@ -95,26 +103,20 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("treeData", ["totalTreeData", HOME_PIN_BOOKMARK_ID_MAP]),
|
...mapState("treeData", ["totalTreeData", HOME_PIN_BOOKMARK_ID_MAP]),
|
||||||
...mapState("globalConfig", ["userInfo"]),
|
...mapState("globalConfig", ["userInfo"])
|
||||||
searchIcon() {
|
|
||||||
let search = this.userInfo != null ? this.userInfo.defaultSearchEngine : "baidu";
|
|
||||||
return search === "baidu" ? "icon-baidu" : search === "bing" ? "icon-bing" : "icon-google";
|
|
||||||
},
|
|
||||||
searchUrl() {
|
|
||||||
let search = this.userInfo && this.userInfo.defaultSearchEngine ? this.userInfo.defaultSearchEngine : "baidu";
|
|
||||||
return search === "baidu"
|
|
||||||
? "https://www.baidu.com/s?ie=UTF-8&wd="
|
|
||||||
: search === "bing"
|
|
||||||
? "https://www.bing.com/search?q="
|
|
||||||
: "https://www.google.com/search?q=";
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value(newVal, oldVal) {
|
value(newVal, oldVal) {
|
||||||
this.search(newVal);
|
this.search(newVal);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
searchIconClick() {
|
||||||
|
if (this.userInfo == null) {
|
||||||
|
this.searchEngineList = [];
|
||||||
|
this.$message.warning("未登录,请登录后操作");
|
||||||
|
}
|
||||||
|
},
|
||||||
search(content) {
|
search(content) {
|
||||||
console.log(content);
|
console.log(content);
|
||||||
let val = content.toLocaleLowerCase().trim();
|
let val = content.toLocaleLowerCase().trim();
|
||||||
@ -123,6 +125,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.list = this.dealSearch(val);
|
this.list = this.dealSearch(val);
|
||||||
}
|
}
|
||||||
|
this.selectIndex = null;
|
||||||
},
|
},
|
||||||
//下方列表点击
|
//下方列表点击
|
||||||
itemClick(index) {
|
itemClick(index) {
|
||||||
@ -134,7 +137,7 @@ export default {
|
|||||||
let url;
|
let url;
|
||||||
if (forceSearch || this.selectIndex == null) {
|
if (forceSearch || this.selectIndex == null) {
|
||||||
//说明使用网页搜索
|
//说明使用网页搜索
|
||||||
url = this.searchUrl + encodeURIComponent(this.value);
|
url = this.checkedSearchEngine.url.replace("%s", encodeURIComponent(this.value));
|
||||||
} else {
|
} else {
|
||||||
//说明跳转到书签
|
//说明跳转到书签
|
||||||
let bookmark = this.list[this.selectIndex];
|
let bookmark = this.list[this.selectIndex];
|
||||||
@ -175,15 +178,10 @@ export default {
|
|||||||
},
|
},
|
||||||
//修改默认搜索引擎
|
//修改默认搜索引擎
|
||||||
async searchEngineChange(item) {
|
async searchEngineChange(item) {
|
||||||
if (this.userInfo == null) {
|
|
||||||
this.$message.warning("未登录,请登录后操作");
|
let target = this.searchEngineList.find(one => one.id === item.key);
|
||||||
return;
|
await HttpUtil.post("/searchEngine/setChecked", null, { id: item.key });
|
||||||
}
|
this.checkedSearchEngine = target;
|
||||||
if (item.key !== this.userInfo.defaultSearchEngine) {
|
|
||||||
await HttpUtil.post("/baseInfo/updateSearchEngine", null, { defaultSearchEngine: item.key });
|
|
||||||
this.userInfo.defaultSearchEngine = item.key;
|
|
||||||
this.$store.commit(GLOBAL_CONFIG + "/" + USER_INFO, this.userInfo);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
//固定书签到首页
|
//固定书签到首页
|
||||||
async pinBookmark(event, { bookmarkId }) {
|
async pinBookmark(event, { bookmarkId }) {
|
||||||
@ -227,8 +225,8 @@ export default {
|
|||||||
}
|
}
|
||||||
console.log("阻止成功");
|
console.log("阻止成功");
|
||||||
return false;
|
return false;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -239,10 +237,12 @@ export default {
|
|||||||
@listActiveBgColor: #454545;
|
@listActiveBgColor: #454545;
|
||||||
.search {
|
.search {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.listShow {
|
.listShow {
|
||||||
border-bottom-left-radius: 0 !important;
|
border-bottom-left-radius: 0 !important;
|
||||||
border-bottom-right-radius: 0 !important;
|
border-bottom-right-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.newSearch {
|
.newSearch {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -251,6 +251,7 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
color: @textColor;
|
color: @textColor;
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: 0;
|
border: 0;
|
||||||
@ -259,11 +260,13 @@ export default {
|
|||||||
padding-left: 0.19rem;
|
padding-left: 0.19rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
padding: 0.1rem;
|
padding: 0.1rem;
|
||||||
padding-right: 0.19rem;
|
padding-right: 0.19rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: @textColor;
|
color: @textColor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -281,6 +284,7 @@ export default {
|
|||||||
border-bottom-left-radius: 0.18rem;
|
border-bottom-left-radius: 0.18rem;
|
||||||
border-bottom-right-radius: 0.18rem;
|
border-bottom-right-radius: 0.18rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.listItem {
|
.listItem {
|
||||||
font-size: 0.16rem;
|
font-size: 0.16rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -291,6 +295,7 @@ export default {
|
|||||||
margin: 0.05rem 0 0.05rem 0;
|
margin: 0.05rem 0 0.05rem 0;
|
||||||
padding: 0 0.19rem 0 0.19rem;
|
padding: 0 0.19rem 0 0.19rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
max-width: calc(100% - 2em);
|
max-width: calc(100% - 2em);
|
||||||
@ -298,17 +303,21 @@ export default {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icons {
|
.icons {
|
||||||
display: none;
|
display: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.listItem:hover {
|
.listItem:hover {
|
||||||
background-color: @listActiveBgColor;
|
background-color: @listActiveBgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemActive {
|
.itemActive {
|
||||||
background-color: @listActiveBgColor;
|
background-color: @listActiveBgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listItem:hover .icons {
|
.listItem:hover .icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
</a-form-model-item>
|
</a-form-model-item>
|
||||||
<template v-if="form.type !== 'file'">
|
<template v-if="form.type !== 'file'">
|
||||||
<a-form-model-item prop="name" label="名称" :required="false">
|
<a-form-model-item prop="name" label="名称" :required="false">
|
||||||
<a-input v-model="form.name" placeholder="名称" />
|
<a-input v-model="form.name" placeholder="名称" @pressEnter="submit" ref="inputName" />
|
||||||
</a-form-model-item>
|
</a-form-model-item>
|
||||||
<a-form-model-item v-if="form.type === 'bookmark'" prop="url" label="URL">
|
<a-form-model-item v-if="form.type === 'bookmark'" prop="url" label="URL">
|
||||||
<a-input v-model="form.url" placeholder="url" />
|
<a-input v-model="form.url" placeholder="url" @pressEnter="submit" />
|
||||||
</a-form-model-item>
|
</a-form-model-item>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button>
|
<a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button>
|
||||||
@ -21,6 +21,7 @@
|
|||||||
:data="{ path: form.path }"
|
:data="{ path: form.path }"
|
||||||
:headers="{ 'jwt-token': token }"
|
:headers="{ 'jwt-token': token }"
|
||||||
action="/bookmark/api/bookmark/uploadBookmarkFile"
|
action="/bookmark/api/bookmark/uploadBookmarkFile"
|
||||||
|
accept=".html,.db3"
|
||||||
@change="fileChange"
|
@change="fileChange"
|
||||||
>
|
>
|
||||||
<p class="ant-upload-drag-icon">
|
<p class="ant-upload-drag-icon">
|
||||||
@ -62,7 +63,7 @@ export default {
|
|||||||
file: null,
|
file: null,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
name: [{ required: true, min: 1, max: 1000, message: "名称长度为1-1000", trigger: "change" }],
|
name: [{ required: true, min: 1, max: 1000, message: "名称长度为1-200", trigger: "change" }],
|
||||||
url: [{ required: true, min: 1, message: "不能为空", trigger: "change" }],
|
url: [{ required: true, min: 1, message: "不能为空", trigger: "change" }],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -78,6 +79,12 @@ export default {
|
|||||||
}
|
}
|
||||||
this.token = this.$store.state.globalConfig.token;
|
this.token = this.$store.state.globalConfig.token;
|
||||||
this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
|
this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs.inputName) {
|
||||||
|
this.$refs.inputName.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<router-link to="/public/about">关于</router-link>
|
<router-link style="color: white" to="/public/about"><span class="text">关于</span></router-link>
|
||||||
|
<a-tooltip v-if="bgSrc">
|
||||||
|
<template #title>点击后鼠标右键-将图像另存为</template>
|
||||||
|
<a style="color: white; margin-left: 1em" :href="bgSrc" download="bing每日一图"><span class="text">下载壁纸</span></a>
|
||||||
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -8,6 +12,7 @@
|
|||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
name: "homeTop",
|
name: "homeTop",
|
||||||
|
props: ["bgSrc"],
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
@ -18,6 +23,11 @@ export default {
|
|||||||
.bottom {
|
.bottom {
|
||||||
height: 0.4rem;
|
height: 0.4rem;
|
||||||
padding: 0.1rem;
|
padding: 0.1rem;
|
||||||
text-align: right;
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,15 +6,18 @@
|
|||||||
/
|
/
|
||||||
<router-link to="/public/register">注册</router-link>
|
<router-link to="/public/register">注册</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else class="topAction">
|
||||||
|
<a-tooltip style="margin-right: 1em">
|
||||||
|
<template #title>书签管理</template>
|
||||||
|
<router-link to="/manage">
|
||||||
|
<a-icon class="bookmarkIcon" type="setting" />
|
||||||
|
</router-link>
|
||||||
|
</a-tooltip>
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<img :src="userInfo.icon" class="userIcon" />
|
<img :src="userInfo.icon" class="userIcon" />
|
||||||
</div>
|
</div>
|
||||||
<a-menu slot="overlay" :trigger="['hover', 'click']" @click="menuClick">
|
<a-menu slot="overlay" :trigger="['hover', 'click']" @click="menuClick">
|
||||||
<a-menu-item key="manage">
|
|
||||||
<router-link to="manage">书签管理</router-link>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="personSpace">
|
<a-menu-item key="personSpace">
|
||||||
<router-link to="/manage/personSpace/userInfo">个人中心</router-link>
|
<router-link to="/manage/personSpace/userInfo">个人中心</router-link>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@ -60,6 +63,19 @@ export default {
|
|||||||
|
|
||||||
.userIcon {
|
.userIcon {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
width: 2.5em;
|
||||||
|
height: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topAction {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.bookmarkIcon {
|
||||||
|
font-size: 2em;
|
||||||
|
background-color: rgb(74, 74, 74, 0.5);
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormModel,
|
FormModel,
|
||||||
Input,
|
Input,
|
||||||
Icon,
|
Icon,
|
||||||
message,
|
message,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Menu,
|
Menu,
|
||||||
Tree,
|
Tree,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Spin,
|
Spin,
|
||||||
notification,
|
notification,
|
||||||
Empty,
|
Empty,
|
||||||
Modal,
|
Modal,
|
||||||
Radio,
|
Radio,
|
||||||
Upload,
|
Upload,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Select,
|
Select,
|
||||||
Popover
|
Popover,
|
||||||
|
Breadcrumb,
|
||||||
|
Table
|
||||||
} from "ant-design-vue";
|
} from "ant-design-vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
|
|
||||||
const IconFont = Icon.createFromIconfontCN({
|
const IconFont = Icon.createFromIconfontCN({
|
||||||
scriptUrl: "//at.alicdn.com/t/font_1261825_1cgngjf5r4f.js"
|
scriptUrl: "//at.alicdn.com/t/c/font_1261825_v7m0rilm4hm.js"
|
||||||
});
|
});
|
||||||
Vue.use(Button);
|
Vue.use(Button);
|
||||||
Vue.use(FormModel);
|
Vue.use(FormModel);
|
||||||
@ -46,6 +48,8 @@ Vue.use(Popconfirm);
|
|||||||
Vue.use(AutoComplete);
|
Vue.use(AutoComplete);
|
||||||
Vue.use(Select);
|
Vue.use(Select);
|
||||||
Vue.use(Popover);
|
Vue.use(Popover);
|
||||||
|
Vue.use(Breadcrumb);
|
||||||
|
Vue.use(Table);
|
||||||
Vue.component("my-icon", IconFont);
|
Vue.component("my-icon", IconFont);
|
||||||
|
|
||||||
Vue.prototype.$message = message;
|
Vue.prototype.$message = message;
|
||||||
@ -54,9 +58,9 @@ Vue.prototype.$confirm = Modal.confirm;
|
|||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
window.vueInstance = new Vue({
|
window.vueInstance = new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
}).$mount("#app");
|
}).$mount("#app");
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,65 +1,69 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import VueRouter from "vue-router";
|
import VueRouter from "vue-router";
|
||||||
import * as vuex from "../store/index.js";
|
import * as vuex from "../store/index.js";
|
||||||
import { GLOBAL_CONFIG, SUPPORT_NO_LOGIN, TOKEN } from "@/store/modules/globalConfig";
|
import { GLOBAL_CONFIG, SUPPORT_NO_LOGIN, TOKEN, setToken } from "@/store/modules/globalConfig";
|
||||||
import { checkJwtValid } from "@/util/UserUtil";
|
import { checkJwtValid } from "@/util/UserUtil";
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: "/", component: () => import("@/views/home/index") },
|
{ path: "/", component: () => import("@/views/home/index") },
|
||||||
{
|
{ path: "/noHead/addBookmark", component: () => import("@/views/noHead/addBookmark/index") },
|
||||||
path: "/manage",
|
{
|
||||||
component: () => import("@/views/manage/index"),
|
path: "/manage",
|
||||||
children: [
|
component: () => import("@/views/manage/index"),
|
||||||
{ path: "", redirect: "/manage/bookmarkTree" },
|
children: [
|
||||||
{ path: "bookmarkTree", component: () => import("@/views/manage/bookmarkTree/index") },
|
{ path: "", redirect: "/manage/bookmarkTree" },
|
||||||
{ path: "personSpace/userInfo", component: () => import("@/views/manage/personSpace/index") },
|
{ path: "bookmarkTree", component: () => import("@/views/manage/bookmarkTree/index") },
|
||||||
]
|
{ path: "personSpace/userInfo", component: () => import("@/views/manage/personSpace/index") },
|
||||||
},
|
{ path: "sso/auth", component: () => import("@/views/manage/sso/auth/index") }
|
||||||
{
|
]
|
||||||
path: "/public",
|
},
|
||||||
component: () => import("@/views/public/index"),
|
{
|
||||||
children: [
|
path: "/public",
|
||||||
{ path: "login", component: () => import("@/views/public/login/index") },
|
component: () => import("@/views/public/index"),
|
||||||
{ path: "register", component: () => import("@/views/public/register/index") },
|
children: [
|
||||||
{ path: "resetPassword", component: () => import("@/views/public/passwordReset/index") },
|
{ path: "login", component: () => import("@/views/public/login/index") },
|
||||||
{ path: "oauth/github", component: () => import("@/views/public/oauth/github/index") },
|
{ path: "register", component: () => import("@/views/public/register/index") },
|
||||||
{ path: "about", component: () => import("@/views/public/about/index") },
|
{ path: "resetPassword", component: () => import("@/views/public/passwordReset/index") },
|
||||||
{ path: "404", component: () => import("@/views/public/notFound/index") },
|
{ path: "oauth/github", component: () => import("@/views/public/oauth/github/index") },
|
||||||
]
|
{ path: "about", component: () => import("@/views/public/about/index") },
|
||||||
},
|
{ path: "404", component: () => import("@/views/public/notFound/index") }
|
||||||
{ path: "*", redirect: "/public/404" }
|
]
|
||||||
|
},
|
||||||
|
{ path: "*", redirect: "/public/404" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: "history",
|
mode: "history",
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在此进行登录信息判断,以及重定向到登录页面
|
* 在此进行登录信息判断,以及重定向到登录页面
|
||||||
*/
|
*/
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
//进入主页面/管理页面时,确认已经进行初始化操作
|
if (to.query.token && checkJwtValid(to.query.token)) {
|
||||||
if (to.path === '/' || to.path.startsWith("/manage")) {
|
console.log("获取到页面token", to.query.token);
|
||||||
await vuex.loginInit();
|
await vuex.default.dispatch(GLOBAL_CONFIG + "/" + setToken, to.query.token);
|
||||||
}
|
}
|
||||||
let supportNoLogin = to.path === '/' || to.path.startsWith("/public");
|
//进入除/public以外的路由,确认已经进行初始化操作
|
||||||
vuex.default.commit(GLOBAL_CONFIG + "/" + SUPPORT_NO_LOGIN, supportNoLogin);
|
if (!to.path.startsWith("/public")) {
|
||||||
if (!supportNoLogin && !checkJwtValid(vuex.default.state[GLOBAL_CONFIG][TOKEN])) {
|
await vuex.loginInit();
|
||||||
//如不支持未登录进入,切jwt已过期,直接跳转到登录页面,并清理缓存
|
}
|
||||||
await vuex.default.dispatch("treeData/clear");
|
let supportNoLogin = to.path === "/" || to.path.startsWith("/public");
|
||||||
await vuex.default.dispatch("globalConfig/clear");
|
vuex.default.commit(GLOBAL_CONFIG + "/" + SUPPORT_NO_LOGIN, supportNoLogin);
|
||||||
next({
|
if (!supportNoLogin && !checkJwtValid(vuex.default.state[GLOBAL_CONFIG][TOKEN])) {
|
||||||
path: "/public/login?to=" + btoa(location.href),
|
//如不支持未登录进入,切jwt已过期,直接跳转到登录页面,并清理缓存
|
||||||
replace: true
|
await vuex.default.dispatch("treeData/clear");
|
||||||
});
|
await vuex.default.dispatch("globalConfig/clear");
|
||||||
} else {
|
next({
|
||||||
next();
|
path: "/public/login?to=" + btoa(to.fullPath),
|
||||||
}
|
replace: true
|
||||||
})
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -8,58 +8,61 @@ import { checkJwtValid } from "@/util/UserUtil";
|
|||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
let store = new Vuex.Store({
|
let store = new Vuex.Store({
|
||||||
state: {},
|
state: {},
|
||||||
mutations: {},
|
mutations: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
modules: {
|
modules: {
|
||||||
[globalConfig.GLOBAL_CONFIG]: globalConfig.store,
|
[globalConfig.GLOBAL_CONFIG]: globalConfig.store,
|
||||||
[treeData.TREE_DATA]: treeData.store
|
[treeData.TREE_DATA]: treeData.store
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let noLoginFinish = false;
|
let noLoginFinish = false;
|
||||||
|
|
||||||
//执行各自的非登陆初始化
|
//执行各自的非登陆初始化
|
||||||
(async () => {
|
(async () => {
|
||||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
|
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
|
||||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
//无需等待执行
|
||||||
noLoginFinish = true;
|
store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
||||||
|
noLoginFinish = true;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行各模块的登陆后初始化
|
* 执行各模块的登陆后初始化
|
||||||
*/
|
*/
|
||||||
export async function loginInit () {
|
export async function loginInit() {
|
||||||
if (!noLoginFinish) {
|
if (!noLoginFinish) {
|
||||||
await finishNoLogin();
|
await finishNoLogin();
|
||||||
}
|
}
|
||||||
console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]);
|
console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]);
|
||||||
if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) {
|
if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) {
|
||||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit);
|
//无需等待执行完毕
|
||||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit);
|
store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit);
|
||||||
}
|
store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit);
|
||||||
|
}
|
||||||
|
console.log("初始化完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推出登陆时需要清理的
|
* 推出登陆时需要清理的
|
||||||
*/
|
*/
|
||||||
export async function logoutClear () {
|
export async function logoutClear() {
|
||||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear);
|
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear);
|
||||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear);
|
await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保未登录前要初始化的初始化完了
|
* 确保未登录前要初始化的初始化完了
|
||||||
*/
|
*/
|
||||||
async function finishNoLogin () {
|
async function finishNoLogin() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let timer = setInterval(() => {
|
let timer = setInterval(() => {
|
||||||
if (noLoginFinish) {
|
if (noLoginFinish) {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
@ -11,95 +11,101 @@ export const IS_PHONE = "isPhone";
|
|||||||
|
|
||||||
export const noLoginInit = "noLoginInit";
|
export const noLoginInit = "noLoginInit";
|
||||||
export const loginInit = "loginInit";
|
export const loginInit = "loginInit";
|
||||||
|
/**
|
||||||
|
* 登出清除数据
|
||||||
|
*/
|
||||||
export const clear = "clear";
|
export const clear = "clear";
|
||||||
|
/**
|
||||||
|
* 设置token
|
||||||
|
*/
|
||||||
|
export const setToken = "setToken";
|
||||||
/**
|
/**
|
||||||
* 存储全局配置
|
* 存储全局配置
|
||||||
*/
|
*/
|
||||||
const state = {
|
const state = {
|
||||||
/**
|
/**
|
||||||
* 用户信息
|
* 用户信息
|
||||||
*/
|
*/
|
||||||
[USER_INFO]: null,
|
[USER_INFO]: null,
|
||||||
/**
|
/**
|
||||||
* token,null说明未获取登录凭证
|
* token,null说明未获取登录凭证
|
||||||
*/
|
*/
|
||||||
[TOKEN]: null,
|
[TOKEN]: null,
|
||||||
/**
|
/**
|
||||||
* 是否已经初始化完成,避免多次重复初始化
|
* 是否已经初始化完成,避免多次重复初始化
|
||||||
*/
|
*/
|
||||||
[IS_INIT]: false,
|
[IS_INIT]: false,
|
||||||
/**
|
/**
|
||||||
* 是否移动端
|
* 是否移动端
|
||||||
*/
|
*/
|
||||||
[IS_PHONE]: /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent),
|
[IS_PHONE]: /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent),
|
||||||
/**
|
/**
|
||||||
* 是否支持未登录进入页面
|
* 是否支持未登录进入页面
|
||||||
*/
|
*/
|
||||||
[SUPPORT_NO_LOGIN]: false,
|
[SUPPORT_NO_LOGIN]: false,
|
||||||
/**
|
/**
|
||||||
* 服务端全局配置
|
* 服务端全局配置
|
||||||
*/
|
*/
|
||||||
[SERVER_CONFIG]: {}
|
[SERVER_CONFIG]: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getters = {};
|
const getters = {};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
//未登录需要进行的初始化
|
//未登录需要进行的初始化
|
||||||
async [noLoginInit] ({ commit }) {
|
async [noLoginInit]({ commit }) {
|
||||||
commit(SERVER_CONFIG, await HttpUtil.get("/common/config/global"));
|
commit(SERVER_CONFIG, await HttpUtil.get("/common/config/global"));
|
||||||
let token = await localforage.getItem(TOKEN);
|
let token = await localforage.getItem(TOKEN);
|
||||||
if (token) {
|
if (token) {
|
||||||
commit(TOKEN, token);
|
commit(TOKEN, token);
|
||||||
window.jwtToken = token;
|
window.jwtToken = token;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//登陆后的,初始化数据
|
//登陆后的,初始化数据
|
||||||
async [loginInit] (context) {
|
async [loginInit](context) {
|
||||||
if (context.state.isInit) {
|
if (context.state.isInit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let userInfo = await HttpUtil.get("/user/currentUserInfo");
|
let userInfo = await HttpUtil.get("/user/currentUserInfo");
|
||||||
context.commit(USER_INFO, userInfo);
|
context.commit(USER_INFO, userInfo);
|
||||||
context.commit(IS_INIT, true);
|
context.commit(IS_INIT, true);
|
||||||
},
|
console.log("用户完了");
|
||||||
async setToken ({ commit }, token) {
|
},
|
||||||
await localforage.setItem(TOKEN, token);
|
async [setToken]({ commit }, token) {
|
||||||
window.jwtToken = token;
|
await localforage.setItem(TOKEN, token);
|
||||||
commit(TOKEN, token);
|
window.jwtToken = token;
|
||||||
},
|
commit(TOKEN, token);
|
||||||
//登出清除数据
|
},
|
||||||
async [clear] (context) {
|
async [clear](context) {
|
||||||
await localforage.removeItem(TOKEN);
|
await localforage.removeItem(TOKEN);
|
||||||
context.commit(USER_INFO, null);
|
context.commit(USER_INFO, null);
|
||||||
context.commit(TOKEN, null);
|
context.commit(TOKEN, null);
|
||||||
context.commit(IS_INIT, false);
|
context.commit(IS_INIT, false);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
[USER_INFO] (state, userInfo) {
|
[USER_INFO](state, userInfo) {
|
||||||
state[USER_INFO] = userInfo;
|
state[USER_INFO] = userInfo;
|
||||||
},
|
},
|
||||||
[TOKEN] (state, token) {
|
[TOKEN](state, token) {
|
||||||
state[TOKEN] = token;
|
state[TOKEN] = token;
|
||||||
},
|
},
|
||||||
[IS_INIT] (state, isInit) {
|
[IS_INIT](state, isInit) {
|
||||||
state[IS_INIT] = isInit;
|
state[IS_INIT] = isInit;
|
||||||
},
|
},
|
||||||
[SERVER_CONFIG] (state, serverConfig) {
|
[SERVER_CONFIG](state, serverConfig) {
|
||||||
state[SERVER_CONFIG] = serverConfig;
|
state[SERVER_CONFIG] = serverConfig;
|
||||||
},
|
},
|
||||||
[SUPPORT_NO_LOGIN] (state, val) {
|
[SUPPORT_NO_LOGIN](state, val) {
|
||||||
state[SUPPORT_NO_LOGIN] = val;
|
state[SUPPORT_NO_LOGIN] = val;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const store = {
|
export const store = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state,
|
state,
|
||||||
getters,
|
getters,
|
||||||
actions,
|
actions,
|
||||||
mutations
|
mutations
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,9 @@ import localforage from "localforage";
|
|||||||
import { checkJwtValid } from "@/util/UserUtil";
|
import { checkJwtValid } from "@/util/UserUtil";
|
||||||
import HttpUtil from "../../util/HttpUtil";
|
import HttpUtil from "../../util/HttpUtil";
|
||||||
|
|
||||||
|
/**书签版本检查间隔 */
|
||||||
|
const CHECK_INTERVAL = 5 * 60 * 1000;
|
||||||
|
// const CHECK_INTERVAL = 5 * 1000;
|
||||||
export const TREE_DATA = "treeData";
|
export const TREE_DATA = "treeData";
|
||||||
export const TOTAL_TREE_DATA = "totalTreeData";
|
export const TOTAL_TREE_DATA = "totalTreeData";
|
||||||
export const VERSION = "version";
|
export const VERSION = "version";
|
||||||
@ -19,7 +22,13 @@ export const refreshHomePinList = "refreshHomePinList";
|
|||||||
* 通过id获取书签数据
|
* 通过id获取书签数据
|
||||||
*/
|
*/
|
||||||
export const getById = "getById";
|
export const getById = "getById";
|
||||||
|
/**
|
||||||
|
* 登录前初始化
|
||||||
|
*/
|
||||||
export const noLoginInit = "noLoginInit";
|
export const noLoginInit = "noLoginInit";
|
||||||
|
/**
|
||||||
|
* 登陆后初始化
|
||||||
|
*/
|
||||||
export const loginInit = "loginInit";
|
export const loginInit = "loginInit";
|
||||||
export const refresh = "refresh";
|
export const refresh = "refresh";
|
||||||
export const clear = "clear";
|
export const clear = "clear";
|
||||||
@ -27,11 +36,19 @@ export const clear = "clear";
|
|||||||
* 删除书签数据
|
* 删除书签数据
|
||||||
*/
|
*/
|
||||||
export const deleteData = "deleteData";
|
export const deleteData = "deleteData";
|
||||||
|
/**
|
||||||
|
* 新增节点
|
||||||
|
*/
|
||||||
|
export const addNode = "addNode";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本检查定时调度
|
* 版本检查定时调度
|
||||||
*/
|
*/
|
||||||
let timer = null;
|
let timer = null;
|
||||||
|
/**
|
||||||
|
* 检查本地版本是否有更新
|
||||||
|
*/
|
||||||
|
let checkLocalDataTimer = null;
|
||||||
/**
|
/**
|
||||||
* 刷新书签确认弹窗是否展示
|
* 刷新书签确认弹窗是否展示
|
||||||
*/
|
*/
|
||||||
@ -41,321 +58,342 @@ let toastShow = false;
|
|||||||
* 书签树相关配置
|
* 书签树相关配置
|
||||||
*/
|
*/
|
||||||
const state = {
|
const state = {
|
||||||
//全部书签数据
|
//全部书签数据
|
||||||
[TOTAL_TREE_DATA]: {},
|
[TOTAL_TREE_DATA]: {},
|
||||||
//版本
|
//版本
|
||||||
[VERSION]: null,
|
[VERSION]: null,
|
||||||
//是否已经初始化书签数据
|
//是否已经初始化书签数据
|
||||||
[IS_INIT]: false,
|
[IS_INIT]: false,
|
||||||
// 是否正在加载数据
|
// 是否正在加载数据
|
||||||
[IS_INITING]: false,
|
[IS_INITING]: false,
|
||||||
[SHOW_REFRESH_TOAST]: false,
|
[SHOW_REFRESH_TOAST]: false,
|
||||||
[HOME_PIN_LIST]: [],
|
[HOME_PIN_LIST]: [],
|
||||||
[HOME_PIN_BOOKMARK_ID_MAP]: {}
|
[HOME_PIN_BOOKMARK_ID_MAP]: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
[getById]: state => id => {
|
[getById]: state => id => {
|
||||||
let arr = Object.values(state[TOTAL_TREE_DATA]);
|
let arr = Object.values(state[TOTAL_TREE_DATA]);
|
||||||
for (let i in arr) {
|
for (let i in arr) {
|
||||||
for (let j in arr[i]) {
|
for (let j in arr[i]) {
|
||||||
if (arr[i][j].bookmarkId === id) {
|
if (arr[i][j].bookmarkId === id) {
|
||||||
return arr[i][j];
|
return arr[i][j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
async [noLoginInit] () {
|
async [noLoginInit] () { },
|
||||||
|
async [loginInit] (context) {
|
||||||
},
|
if (context.state.isInit || context.state.isIniting) {
|
||||||
async [loginInit] (context) {
|
return;
|
||||||
if (context.state.isInit || context.state.isIniting) {
|
}
|
||||||
return;
|
await context.dispatch(refreshHomePinList);
|
||||||
}
|
context.commit(IS_INITING, true);
|
||||||
await context.dispatch(refreshHomePinList);
|
context.commit(TOTAL_TREE_DATA, await localforage.getItem(TOTAL_TREE_DATA));
|
||||||
context.commit(IS_INITING, true);
|
context.commit(VERSION, await localforage.getItem(VERSION));
|
||||||
context.commit(TOTAL_TREE_DATA, await localforage.getItem(TOTAL_TREE_DATA));
|
await treeDataCheck(context, true);
|
||||||
context.commit(VERSION, await localforage.getItem(VERSION));
|
context.commit(IS_INIT, true);
|
||||||
await treeDataCheck(context, true);
|
context.commit(IS_INITING, false);
|
||||||
context.commit(IS_INIT, true);
|
timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL);
|
||||||
context.commit(IS_INITING, false);
|
checkLocalDataTimer = setInterval(() => checkLocalData(context), 2000);
|
||||||
timer = setInterval(() => treeDataCheck(context, false), 5 * 60 * 1000);
|
},
|
||||||
// timer = setInterval(() => treeDataCheck(context, false), 5 * 1000);
|
/**
|
||||||
},
|
* 确保数据加载完毕
|
||||||
/**
|
*/
|
||||||
* 确保数据加载完毕
|
ensureDataOk (context) {
|
||||||
*/
|
return new Promise((resolve, reject) => {
|
||||||
ensureDataOk (context) {
|
let timer = setInterval(() => {
|
||||||
return new Promise((resolve, reject) => {
|
try {
|
||||||
let timer = setInterval(() => {
|
if (context.state[IS_INIT] && context.state[IS_INITING] == false) {
|
||||||
try {
|
clearInterval(timer);
|
||||||
if (context.state[IS_INIT] && context.state[IS_INITING] == false) {
|
resolve();
|
||||||
clearInterval(timer);
|
}
|
||||||
resolve();
|
} catch (err) {
|
||||||
}
|
reject(err);
|
||||||
} catch (err) {
|
}
|
||||||
reject(err);
|
}, 50);
|
||||||
}
|
});
|
||||||
}, 50);
|
},
|
||||||
});
|
//刷新缓存数据
|
||||||
},
|
async [refresh] (context) {
|
||||||
//刷新缓存数据
|
let treeData = await HttpUtil.get("/bookmark/currentUser");
|
||||||
async [refresh] (context) {
|
if (!treeData[""]) {
|
||||||
let treeData = await HttpUtil.get("/bookmark/currentUser");
|
treeData[""] = [];
|
||||||
if (!treeData[""]) {
|
}
|
||||||
treeData[""] = [];
|
Object.values(treeData).forEach(item =>
|
||||||
}
|
item.forEach(item1 => {
|
||||||
Object.values(treeData).forEach(item =>
|
item1.isLeaf = item1.type === 0;
|
||||||
item.forEach(item1 => {
|
item1.class = "treeNodeItem";
|
||||||
item1.isLeaf = item1.type === 0;
|
item1.scopedSlots = { title: "nodeTitle" };
|
||||||
item1.class = "treeNodeItem";
|
})
|
||||||
item1.scopedSlots = { title: "nodeTitle" };
|
);
|
||||||
})
|
let version = await HttpUtil.get("/user/version");
|
||||||
);
|
await context.dispatch("updateVersion", version);
|
||||||
let version = await HttpUtil.get("/user/version");
|
await context.dispatch(refreshHomePinList);
|
||||||
await context.dispatch("updateVersion", version);
|
context.commit(TOTAL_TREE_DATA, treeData);
|
||||||
context.commit(TOTAL_TREE_DATA, treeData);
|
await localforage.setItem(TOTAL_TREE_DATA, treeData);
|
||||||
await localforage.setItem(TOTAL_TREE_DATA, treeData);
|
},
|
||||||
},
|
//清除缓存数据
|
||||||
//清除缓存数据
|
async [clear] (context) {
|
||||||
async [clear] (context) {
|
context.commit(TOTAL_TREE_DATA, null);
|
||||||
context.commit(TOTAL_TREE_DATA, null);
|
context.commit(VERSION, null);
|
||||||
context.commit(VERSION, null);
|
context.commit(SHOW_REFRESH_TOAST, false);
|
||||||
context.commit(SHOW_REFRESH_TOAST, false);
|
context.commit(IS_INIT, false);
|
||||||
context.commit(IS_INIT, false);
|
context.commit(IS_INITING, false);
|
||||||
context.commit(IS_INITING, false);
|
context.commit(HOME_PIN_LIST, []);
|
||||||
context.commit(HOME_PIN_LIST, []);
|
if (timer != null) {
|
||||||
if (timer != null) {
|
clearInterval(timer);
|
||||||
clearInterval(timer);
|
}
|
||||||
}
|
if (checkLocalDataTimer != null) {
|
||||||
await localforage.removeItem(TOTAL_TREE_DATA);
|
clearInterval(checkLocalDataTimer);
|
||||||
await localforage.removeItem(VERSION);
|
}
|
||||||
},
|
await localforage.removeItem(TOTAL_TREE_DATA);
|
||||||
/**
|
await localforage.removeItem(VERSION);
|
||||||
* 移动节点
|
},
|
||||||
*/
|
/**
|
||||||
async moveNode (context, info) {
|
* 移动节点
|
||||||
let data = context.state[TOTAL_TREE_DATA];
|
*/
|
||||||
const target = info.node.dataRef;
|
async moveNode (context, info) {
|
||||||
const current = info.dragNode.dataRef;
|
let data = context.state[TOTAL_TREE_DATA];
|
||||||
//从原来位置中删除当前节点
|
const target = info.node.dataRef;
|
||||||
let currentList = data[current.path];
|
const current = info.dragNode.dataRef;
|
||||||
currentList.splice(
|
//从原来位置中删除当前节点
|
||||||
currentList.findIndex(item => item.bookmarkId === current.bookmarkId),
|
let currentList = data[current.path];
|
||||||
1
|
currentList.splice(
|
||||||
);
|
currentList.findIndex(item => item.bookmarkId === current.bookmarkId),
|
||||||
//请求体
|
1
|
||||||
const body = {
|
);
|
||||||
bookmarkId: current.bookmarkId,
|
//请求体
|
||||||
sourcePath: current.path,
|
const body = {
|
||||||
targetPath: "",
|
bookmarkId: current.bookmarkId,
|
||||||
//-1 表示排在最后
|
sourcePath: current.path,
|
||||||
sort: -1
|
targetPath: "",
|
||||||
};
|
//-1 表示排在最后
|
||||||
if (info.dropToGap) {
|
sort: -1
|
||||||
body.targetPath = target.path;
|
};
|
||||||
//移动到目标节点的上面或者下面
|
if (info.dropToGap) {
|
||||||
let targetList = data[target.path];
|
body.targetPath = target.path;
|
||||||
//目标节点index
|
//移动到目标节点的上面或者下面
|
||||||
let index = targetList.indexOf(target);
|
let targetList = data[target.path];
|
||||||
//移动节点相对于目标节点位置的增量
|
//目标节点index
|
||||||
let addIndex = info.dropPosition > index ? 1 : 0;
|
let index = targetList.indexOf(target);
|
||||||
body.sort = target.sort + addIndex;
|
//移动节点相对于目标节点位置的增量
|
||||||
targetList.splice(index + addIndex, 0, current);
|
let addIndex = info.dropPosition > index ? 1 : 0;
|
||||||
for (let i = index + 1; i < targetList.length; i++) {
|
body.sort = target.sort + addIndex;
|
||||||
targetList[i].sort += 1;
|
targetList.splice(index + addIndex, 0, current);
|
||||||
}
|
for (let i = index + 1; i < targetList.length; i++) {
|
||||||
} else {
|
targetList[i].sort += 1;
|
||||||
//移动到一个文件夹下面
|
}
|
||||||
body.targetPath = target.path + "." + target.bookmarkId;
|
} else {
|
||||||
let targetList = data[body.targetPath];
|
//移动到一个文件夹下面
|
||||||
if (!targetList) {
|
body.targetPath = target.path + "." + target.bookmarkId;
|
||||||
targetList = [];
|
let targetList = data[body.targetPath];
|
||||||
data[body.targetPath] = targetList;
|
if (!targetList) {
|
||||||
}
|
targetList = [];
|
||||||
body.sort = targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1;
|
data[body.targetPath] = targetList;
|
||||||
targetList.push(current);
|
}
|
||||||
}
|
body.sort = targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1;
|
||||||
//更新节点的path和对应子节点path
|
targetList.push(current);
|
||||||
current.path = body.targetPath;
|
}
|
||||||
current.sort = body.sort;
|
//更新节点的path和对应子节点path
|
||||||
//如果为文件夹还要更新所有子书签的path
|
current.path = body.targetPath;
|
||||||
if (body.sourcePath !== body.targetPath) {
|
current.sort = body.sort;
|
||||||
let keys = Object.keys(data);
|
//如果为文件夹还要更新所有子书签的path
|
||||||
//旧路径
|
if (body.sourcePath !== body.targetPath) {
|
||||||
let oldPath = body.sourcePath + "." + current.bookmarkId;
|
let keys = Object.keys(data);
|
||||||
//新路径
|
//旧路径
|
||||||
let newPath = body.targetPath + "." + current.bookmarkId;
|
let oldPath = body.sourcePath + "." + current.bookmarkId;
|
||||||
keys.forEach(item => {
|
//新路径
|
||||||
if (!item.startsWith(oldPath)) {
|
let newPath = body.targetPath + "." + current.bookmarkId;
|
||||||
return;
|
keys.forEach(item => {
|
||||||
}
|
if (!item.startsWith(oldPath)) {
|
||||||
let newPathStr = item.replace(oldPath, newPath);
|
return;
|
||||||
let list = data[item];
|
}
|
||||||
delete data[item];
|
let newPathStr = item.replace(oldPath, newPath);
|
||||||
data[newPathStr] = list;
|
let list = data[item];
|
||||||
list.forEach(item1 => (item1.path = newPathStr));
|
delete data[item];
|
||||||
});
|
data[newPathStr] = list;
|
||||||
}
|
list.forEach(item1 => (item1.path = newPathStr));
|
||||||
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
});
|
||||||
await context.dispatch("updateVersion", null);
|
}
|
||||||
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
||||||
return body;
|
await context.dispatch("updateVersion", null);
|
||||||
},
|
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||||
async [refreshHomePinList] ({ commit }) {
|
return body;
|
||||||
let list = await HttpUtil.get("/home/pin");
|
},
|
||||||
commit(HOME_PIN_LIST, list);
|
async [refreshHomePinList] ({ commit }) {
|
||||||
let map = {};
|
let list = await HttpUtil.get("/home/pin");
|
||||||
list.filter(item => item.id).forEach(item => map[item.bookmarkId] = true);
|
commit(HOME_PIN_LIST, list);
|
||||||
commit(HOME_PIN_BOOKMARK_ID_MAP, map);
|
let map = {};
|
||||||
|
list.filter(item => item.id).forEach(item => (map[item.bookmarkId] = true));
|
||||||
},
|
commit(HOME_PIN_BOOKMARK_ID_MAP, map);
|
||||||
/**
|
},
|
||||||
* 更新版本数据
|
/**
|
||||||
*/
|
* 更新版本数据
|
||||||
async updateVersion ({ commit, state }, version) {
|
*/
|
||||||
commit(VERSION, version == null ? state[VERSION] + 1 : version);
|
async updateVersion ({ commit, state }, version) {
|
||||||
await localforage.setItem(VERSION, state[VERSION]);
|
commit(VERSION, version == null ? state[VERSION] + 1 : version);
|
||||||
},
|
await localforage.setItem(VERSION, state[VERSION]);
|
||||||
/**
|
},
|
||||||
* 新增书签、文件夹
|
/**
|
||||||
*/
|
* 新增书签、文件夹
|
||||||
async addNode (context, { sourceNode, targetNode }) {
|
*/
|
||||||
if (sourceNode === null) {
|
async [addNode] (context, { sourceNode, targetNode }) {
|
||||||
if (context.state[TOTAL_TREE_DATA][""] === undefined) {
|
if (sourceNode === null) {
|
||||||
context.state[TOTAL_TREE_DATA][""] = [];
|
if (context.state[TOTAL_TREE_DATA][""] === undefined) {
|
||||||
}
|
context.state[TOTAL_TREE_DATA][""] = [];
|
||||||
context.state[TOTAL_TREE_DATA][""].push(targetNode);
|
}
|
||||||
} else {
|
context.state[TOTAL_TREE_DATA][""].push(targetNode);
|
||||||
if (sourceNode.children === undefined) {
|
} else {
|
||||||
sourceNode.children = [];
|
let path = sourceNode.path + "." + sourceNode.bookmarkId;
|
||||||
}
|
if (!context.state[TOTAL_TREE_DATA][path]) {
|
||||||
sourceNode.children.push(targetNode);
|
context.state[TOTAL_TREE_DATA][path] = [];
|
||||||
}
|
}
|
||||||
if (targetNode.type === 0) {
|
if (sourceNode.children === undefined) {
|
||||||
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
|
sourceNode.children = context.state[TOTAL_TREE_DATA][path];
|
||||||
}
|
}
|
||||||
targetNode.isLeaf = targetNode.type === 0;
|
sourceNode.children.push(targetNode);
|
||||||
targetNode.class = "treeNodeItem";
|
}
|
||||||
targetNode.scopedSlots = { title: "nodeTitle" };
|
if (targetNode.type === 1) {
|
||||||
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
|
||||||
await context.dispatch("updateVersion", null);
|
}
|
||||||
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
targetNode.isLeaf = targetNode.type === 0;
|
||||||
},
|
targetNode.class = "treeNodeItem";
|
||||||
/**
|
targetNode.scopedSlots = { title: "nodeTitle" };
|
||||||
* 删除节点数据
|
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
||||||
*/
|
await context.dispatch("updateVersion", null);
|
||||||
async [deleteData] (context, { pathList, bookmarkIdList }) {
|
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||||
//待删除的书签
|
},
|
||||||
let bookmarkIdSet = new Set();
|
/**
|
||||||
bookmarkIdList.forEach(item => bookmarkIdSet.add(item));
|
* 删除节点数据
|
||||||
//删除子节点
|
*/
|
||||||
pathList.forEach(item => {
|
async [deleteData] (context, { pathList, bookmarkIdList }) {
|
||||||
delete state[TOTAL_TREE_DATA][item];
|
//待删除的书签
|
||||||
Object.keys(context.state[TOTAL_TREE_DATA])
|
let bookmarkIdSet = new Set();
|
||||||
.filter(key => key.startsWith(item + "."))
|
bookmarkIdList.forEach(item => bookmarkIdSet.add(item));
|
||||||
.forEach(key => delete state[TOTAL_TREE_DATA][key]);
|
//删除子节点
|
||||||
bookmarkIdSet.add(parseInt(item.split(".").reverse()));
|
pathList.forEach(item => {
|
||||||
});
|
delete state[TOTAL_TREE_DATA][item];
|
||||||
//删除直接选中的节点
|
Object.keys(context.state[TOTAL_TREE_DATA])
|
||||||
Object.keys(context.state[TOTAL_TREE_DATA]).forEach(item => {
|
.filter(key => key.startsWith(item + "."))
|
||||||
let list = context.state[TOTAL_TREE_DATA][item];
|
.forEach(key => delete state[TOTAL_TREE_DATA][key]);
|
||||||
for (let i = list.length - 1; i >= 0; i--) {
|
bookmarkIdSet.add(parseInt(item.split(".").reverse()));
|
||||||
if (bookmarkIdSet.has(list[i].bookmarkId)) {
|
});
|
||||||
list.splice(i, 1);
|
//删除直接选中的节点
|
||||||
}
|
Object.keys(context.state[TOTAL_TREE_DATA]).forEach(item => {
|
||||||
}
|
let list = context.state[TOTAL_TREE_DATA][item];
|
||||||
});
|
for (let i = list.length - 1; i >= 0; i--) {
|
||||||
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
if (bookmarkIdSet.has(list[i].bookmarkId)) {
|
||||||
await context.dispatch("updateVersion", null);
|
list.splice(i, 1);
|
||||||
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
}
|
||||||
},
|
}
|
||||||
/**
|
});
|
||||||
* 编辑书签节点
|
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
|
||||||
*/
|
await context.dispatch("updateVersion", null);
|
||||||
async editNode ({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) {
|
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||||
node.name = newName;
|
},
|
||||||
node.url = newUrl;
|
/**
|
||||||
node.icon = newIcon;
|
* 编辑书签节点
|
||||||
commit(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
*/
|
||||||
await dispatch("updateVersion", null);
|
async editNode ({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) {
|
||||||
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
node.name = newName;
|
||||||
}
|
node.url = newUrl;
|
||||||
|
node.icon = newIcon;
|
||||||
|
commit(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||||
|
await dispatch("updateVersion", null);
|
||||||
|
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
[TOTAL_TREE_DATA]: (state, totalTreeData) => {
|
[TOTAL_TREE_DATA]: (state, totalTreeData) => {
|
||||||
state.totalTreeData = totalTreeData;
|
state.totalTreeData = totalTreeData;
|
||||||
},
|
},
|
||||||
[IS_INIT] (state, isInit) {
|
[IS_INIT] (state, isInit) {
|
||||||
state.isInit = isInit;
|
state.isInit = isInit;
|
||||||
},
|
},
|
||||||
[IS_INITING] (state, isIniting) {
|
[IS_INITING] (state, isIniting) {
|
||||||
state.isIniting = isIniting;
|
state.isIniting = isIniting;
|
||||||
},
|
},
|
||||||
[VERSION]: (state, version) => {
|
[VERSION]: (state, version) => {
|
||||||
state[VERSION] = version;
|
state[VERSION] = version;
|
||||||
},
|
},
|
||||||
[SHOW_REFRESH_TOAST]: (state, val) => {
|
[SHOW_REFRESH_TOAST]: (state, val) => {
|
||||||
state[SHOW_REFRESH_TOAST] = val;
|
state[SHOW_REFRESH_TOAST] = val;
|
||||||
},
|
},
|
||||||
[HOME_PIN_LIST]: (state, val) => {
|
[HOME_PIN_LIST]: (state, val) => {
|
||||||
state[HOME_PIN_LIST] = val;
|
state[HOME_PIN_LIST] = val;
|
||||||
},
|
},
|
||||||
[HOME_PIN_BOOKMARK_ID_MAP]: (state, val) => {
|
[HOME_PIN_BOOKMARK_ID_MAP]: (state, val) => {
|
||||||
state[HOME_PIN_BOOKMARK_ID_MAP] = val;
|
state[HOME_PIN_BOOKMARK_ID_MAP] = val;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查书签缓存是否最新
|
* 检查书签缓存是否最新
|
||||||
*
|
*
|
||||||
* @param {*} context
|
* @param {*} context
|
||||||
* @param {*} isFirst
|
* @param {*} isFirst
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function treeDataCheck (context, isFirst) {
|
async function treeDataCheck (context, isFirst) {
|
||||||
if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) {
|
if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let realVersion = await HttpUtil.get("/user/version");
|
let realVersion = await HttpUtil.get("/user/version");
|
||||||
if (realVersion !== context.state[VERSION]) {
|
if (realVersion !== context.state[VERSION]) {
|
||||||
if (SHOW_REFRESH_TOAST && !isFirst) {
|
if (context.state[SHOW_REFRESH_TOAST] && !isFirst) {
|
||||||
//如果在书签管理页面需要弹窗提示
|
//如果在书签管理页面需要弹窗提示
|
||||||
window.vueInstance.$confirm({
|
window.vueInstance.$confirm({
|
||||||
title: "书签数据有更新,是否立即刷新?",
|
title: "书签数据有更新,是否立即刷新?",
|
||||||
cancelText: "稍后提醒",
|
cancelText: "稍后提醒",
|
||||||
closable: false,
|
closable: false,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
onOk () {
|
onOk () {
|
||||||
toastShow = false;
|
toastShow = false;
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async resolve => {
|
||||||
await context.dispatch("refresh");
|
await context.dispatch(refresh);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onCancel () {
|
onCancel () {
|
||||||
toastShow = false;
|
toastShow = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
toastShow = true;
|
toastShow = true;
|
||||||
} else {
|
} else {
|
||||||
await context.dispatch(refresh);
|
await context.dispatch(refresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查本地缓存数据是否有更新
|
||||||
|
* @param {*} context
|
||||||
|
*/
|
||||||
|
async function checkLocalData (context) {
|
||||||
|
let data = await localforage.getItem(TOTAL_TREE_DATA);
|
||||||
|
let version = await localforage.getItem(VERSION);
|
||||||
|
if (!data || !version) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (version > context.state[VERSION]) {
|
||||||
|
console.log("从local缓存更新数据:", version);
|
||||||
|
context.commit(TOTAL_TREE_DATA, data);
|
||||||
|
context.commit(VERSION, version);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const store = {
|
export const store = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state,
|
state,
|
||||||
getters,
|
getters,
|
||||||
actions,
|
actions,
|
||||||
mutations
|
mutations
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,54 +12,54 @@ import router from "../router/index";
|
|||||||
* @param {*} redirect 接口返回未认证是否跳转到登陆
|
* @param {*} redirect 接口返回未认证是否跳转到登陆
|
||||||
* @returns 数据
|
* @returns 数据
|
||||||
*/
|
*/
|
||||||
async function request (url, method, params, body, isForm, redirect) {
|
async function request(url, method, params, body, isForm, redirect) {
|
||||||
let options = {
|
let options = {
|
||||||
url,
|
url,
|
||||||
baseURL: "/bookmark/api",
|
baseURL: "/bookmark/api",
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
headers: {
|
headers: {
|
||||||
"jwt-token": window.jwtToken
|
"jwt-token": window.jwtToken
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//如果是表单类型的请求,添加请求头
|
//如果是表单类型的请求,添加请求头
|
||||||
if (isForm) {
|
if (isForm) {
|
||||||
options.headers["Content-Type"] = "multipart/form-data";
|
options.headers["Content-Type"] = "multipart/form-data";
|
||||||
}
|
}
|
||||||
if (body) {
|
if (body) {
|
||||||
options.data = body;
|
options.data = body;
|
||||||
}
|
}
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
res = await http.default.request(options);
|
res = await http.default.request(options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
window.vueInstance.$message.error("网络连接异常");
|
window.vueInstance.$message.error("网络连接异常");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const { code, data, message } = res.data;
|
const { code, data, message } = res.data;
|
||||||
if (code === 1) {
|
if (code === 1) {
|
||||||
return data;
|
return data;
|
||||||
} else if (code === -1 && redirect) {
|
} else if (code === -1 && redirect) {
|
||||||
//未登陆,根据redirect参数判断是否需要跳转到登陆页
|
//未登陆,根据redirect参数判断是否需要跳转到登陆页
|
||||||
window.vueInstance.$message.error("您尚未登陆,请先登陆");
|
window.vueInstance.$message.error("您尚未登陆,请先登陆");
|
||||||
//跳转到登录页面需要清理缓存
|
//跳转到登录页面需要清理缓存
|
||||||
await this.$store.dispatch("treeData/clear");
|
await this.$store.dispatch("treeData/clear");
|
||||||
await this.$store.dispatch("globalConfig/clear");
|
await this.$store.dispatch("globalConfig/clear");
|
||||||
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
|
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
} else if (code === 0) {
|
} else if (code === 0) {
|
||||||
//通用异常,使用error提示
|
//通用异常,使用error提示
|
||||||
window.vueInstance.$notification.error({
|
window.vueInstance.$notification.error({
|
||||||
message: "异常",
|
message: "异常",
|
||||||
description: message
|
description: message
|
||||||
});
|
});
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
} else if (code === -2) {
|
} else if (code === -2) {
|
||||||
//表单异常,使用message提示
|
//表单异常,使用message提示
|
||||||
window.vueInstance.$message.error(message);
|
window.vueInstance.$message.error(message);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,8 +68,8 @@ async function request (url, method, params, body, isForm, redirect) {
|
|||||||
* @param {*} params url参数
|
* @param {*} params url参数
|
||||||
* @param {*} redirect 未登陆是否跳转到登陆页
|
* @param {*} redirect 未登陆是否跳转到登陆页
|
||||||
*/
|
*/
|
||||||
async function get (url, params = null, redirect = true) {
|
async function get(url, params = null, redirect = true) {
|
||||||
return request(url, "get", params, null, false, redirect);
|
return request(url, "get", params, null, false, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,8 +80,8 @@ async function get (url, params = null, redirect = true) {
|
|||||||
* @param {*} isForm 是否表单数据
|
* @param {*} isForm 是否表单数据
|
||||||
* @param {*} redirect 是否重定向
|
* @param {*} redirect 是否重定向
|
||||||
*/
|
*/
|
||||||
async function post (url, params, body, isForm = false, redirect = true) {
|
async function post(url, params, body, isForm = false, redirect = true) {
|
||||||
return request(url, "post", params, body, isForm, redirect);
|
return request(url, "post", params, body, isForm, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,8 +92,8 @@ async function post (url, params, body, isForm = false, redirect = true) {
|
|||||||
* @param {*} isForm 是否表单数据
|
* @param {*} isForm 是否表单数据
|
||||||
* @param {*} redirect 是否重定向
|
* @param {*} redirect 是否重定向
|
||||||
*/
|
*/
|
||||||
async function put (url, params, body, isForm = false, redirect = true) {
|
async function put(url, params, body, isForm = false, redirect = true) {
|
||||||
return request(url, "put", params, body, isForm, redirect);
|
return request(url, "put", params, body, isForm, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,13 +102,14 @@ async function put (url, params, body, isForm = false, redirect = true) {
|
|||||||
* @param {*} params url参数
|
* @param {*} params url参数
|
||||||
* @param {*} redirect 是否重定向
|
* @param {*} redirect 是否重定向
|
||||||
*/
|
*/
|
||||||
async function deletes (url, params = null, redirect = true) {
|
async function deletes(url, params = null, redirect = true) {
|
||||||
return request(url, "delete", params, null, redirect);
|
return request(url, "delete", params, null, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
post,
|
post,
|
||||||
put,
|
put,
|
||||||
delete: deletes
|
delete: deletes,
|
||||||
|
http
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<a href="pinObj.url" v-if="pinObj" class="pinBookmarkItem">
|
<a :href="pinObj.url" v-if="pinObj" class="pinBookmarkItem">
|
||||||
<img :src="pinObj.icon.length > 0 ? pinObj.icon : '/favicon.ico'" class="icon" />
|
<img :src="pinObj.icon.length > 0 ? pinObj.icon : '/favicon.ico'" class="icon" />
|
||||||
<span class="text" :title="pinObj.name">{{ pinObj.name }}</span>
|
<span class="text" :title="pinObj.name">{{ pinObj.name }}</span>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main" :style="{ backgroundImage: backgroundImg }">
|
||||||
<top />
|
<top />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<search :style="{ width: isPhone ? '100%' : '60%' }" />
|
<search :style="{ width: isPhone ? '100%' : '60%' }" />
|
||||||
<div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div>
|
<div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div>
|
||||||
</div>
|
</div>
|
||||||
<bottom />
|
<bottom :bgSrc="serverConfig.bingImgSrc" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import Bottom from "@/layout/home/Bottom.vue";
|
|||||||
import Search from "@/components/main/Search.vue";
|
import Search from "@/components/main/Search.vue";
|
||||||
import PinBookmark from "./PinBookmark.vue";
|
import PinBookmark from "./PinBookmark.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { GLOBAL_CONFIG, IS_PHONE } from "@/store/modules/globalConfig";
|
import { GLOBAL_CONFIG, IS_PHONE, SERVER_CONFIG } from "@/store/modules/globalConfig";
|
||||||
export default {
|
export default {
|
||||||
name: "HOME",
|
name: "HOME",
|
||||||
components: {
|
components: {
|
||||||
@ -28,7 +28,11 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(GLOBAL_CONFIG, [IS_PHONE, IS_PHONE]),
|
...mapState(GLOBAL_CONFIG, [IS_PHONE, SERVER_CONFIG]),
|
||||||
|
backgroundImg() {
|
||||||
|
let url = this.serverConfig.bingImgSrc ? this.serverConfig.bingImgSrc : "/static/img/homeBg.jpg";
|
||||||
|
return `url("${url}")`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
};
|
};
|
||||||
@ -38,9 +42,8 @@ export default {
|
|||||||
.main {
|
.main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: url("/static/img/homeBg.jpg") no-repeat center center;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-attachment: fixed;
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: calc(~"100vh" - 1.21rem);
|
height: calc(~"100vh" - 1.21rem);
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
>
|
>
|
||||||
<a-dropdown :trigger="['contextmenu']" slot="nodeTitle" slot-scope="rec">
|
<a-dropdown :trigger="['contextmenu']" slot="nodeTitle" slot-scope="rec">
|
||||||
<div class="titleContext">
|
<div class="titleContext" :title="rec.dataRef.url">
|
||||||
<a-icon type="folder" v-if="!rec.dataRef.isLeaf" />
|
<a-icon type="folder" v-if="!rec.dataRef.isLeaf" />
|
||||||
<img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" />
|
<img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" />
|
||||||
<a-icon type="book" v-else />
|
<a-icon type="book" v-else />
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-button type="primary" @click="addOne">新增</a-button>
|
||||||
|
<a-table :columns="columns" :data-source="list" :pagination="false">
|
||||||
|
<template v-for="col in ['name', 'url']" :slot="col" slot-scope="text, record, index">
|
||||||
|
<div :key="col">
|
||||||
|
<a-input v-if="record.isEdit" style="margin: -5px 0" :value="text"
|
||||||
|
@change="e => handleChange(e.target.value, record.id, col)" />
|
||||||
|
<template v-else>{{ text }}</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template slot="icon" slot-scope="text, record, index">
|
||||||
|
<div key="icon">
|
||||||
|
<a-select v-if="record.isEdit" :default-value="text" style="width: 120px"
|
||||||
|
@change="e => handleChange(e, record.id, 'icon')">
|
||||||
|
<a-select-option v-for="item in iconList" :key="item.icon" :value="item.icon">
|
||||||
|
<my-icon :type="item.icon" />
|
||||||
|
{{ item.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<template v-else>
|
||||||
|
<my-icon style="font-size: 1.2em" :type="text" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template slot="checked" slot-scope="text">
|
||||||
|
<span>{{ text === 1 ? "是" : "否" }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template slot="operation" slot-scope="text, record, index">
|
||||||
|
<div class="editable-row-operations">
|
||||||
|
<span v-if="record.isEdit">
|
||||||
|
<a @click="() => save(record.id)">保存</a>
|
||||||
|
<a @click="() => cancel(record.id)">取消</a>
|
||||||
|
</span>
|
||||||
|
<div v-else>
|
||||||
|
<a :disabled="currentEditCache" @click="() => edit(record.id)">编辑</a>
|
||||||
|
<a-popconfirm title="确认删除吗?" ok-text="是" cancel-text="否" @confirm="() => deleteOne(record.id)">
|
||||||
|
<a :disabled="currentEditCache">删除</a>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a :disabled="currentEditCache || record.checked===1" @click="() => setDefault(record.id)">设为默认</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import HttpUtil from "@/util/HttpUtil";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "manageSearchEngine",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
list: [],
|
||||||
|
currentEditCache: null,
|
||||||
|
iconList: [
|
||||||
|
{ icon: "icon-baidu", label: "百度" },
|
||||||
|
{ icon: "icon-bing", label: "必应" },
|
||||||
|
{ icon: "icon-google", label: "谷歌" },
|
||||||
|
{ icon: "icon-yandex", label: "yandex" },
|
||||||
|
{ icon: "icon-sogou", label: "搜狗" },
|
||||||
|
{ icon: "icon-yahoo", label: "雅虎" },
|
||||||
|
{ icon: "icon-qita", label: "其他" }
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: "名称",
|
||||||
|
dataIndex: "name",
|
||||||
|
width: "10em",
|
||||||
|
scopedSlots: { customRender: "name" }
|
||||||
|
}, {
|
||||||
|
title: "图标",
|
||||||
|
dataIndex: "icon",
|
||||||
|
width: "8em",
|
||||||
|
scopedSlots: { customRender: "icon" }
|
||||||
|
}, {
|
||||||
|
title: "路径(%s 会被替换为搜索内容)",
|
||||||
|
dataIndex: "url",
|
||||||
|
scopedSlots: { customRender: "url" }
|
||||||
|
}, {
|
||||||
|
title: "默认",
|
||||||
|
dataIndex: "checked",
|
||||||
|
width: "10em",
|
||||||
|
scopedSlots: { customRender: "checked" }
|
||||||
|
}, {
|
||||||
|
title: "操作",
|
||||||
|
width: "15em",
|
||||||
|
dataIndex: "operation",
|
||||||
|
scopedSlots: { customRender: "operation" }
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async created() {
|
||||||
|
await this.getData();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
//是否可点击删除
|
||||||
|
deleteOk() {
|
||||||
|
return this.list.filter(item => item.isEdit).length === 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addOne() {
|
||||||
|
let body = { id: -1, icon: "", name: "", url: "", checked: 0, isEdit: true };
|
||||||
|
this.list = [body, ...this.list];
|
||||||
|
this.currentEditCache = body;
|
||||||
|
|
||||||
|
},
|
||||||
|
handleChange(value, id, column) {
|
||||||
|
console.log(value, id, column);
|
||||||
|
const target = this.list.find(item => item.id === id);
|
||||||
|
target[column] = value;
|
||||||
|
this.list = [...this.list];
|
||||||
|
},
|
||||||
|
async edit(id) {
|
||||||
|
let target = this.list.find(item => item.id === id);
|
||||||
|
this.currentEditCache = { ...target };
|
||||||
|
target.isEdit = true;
|
||||||
|
this.list = [...this.list];
|
||||||
|
},
|
||||||
|
async save(id) {
|
||||||
|
let target = this.list.find(item => item.id === id);
|
||||||
|
if (target.id > 0) {
|
||||||
|
await HttpUtil.post("/searchEngine/edit", null, target);
|
||||||
|
} else {
|
||||||
|
await HttpUtil.post("/searchEngine/insert", null, target);
|
||||||
|
}
|
||||||
|
target.isEdit = false;
|
||||||
|
this.currentEditCache = null;
|
||||||
|
await this.getData();
|
||||||
|
},
|
||||||
|
cancel(id) {
|
||||||
|
let target = this.list.find(item => item.id === id);
|
||||||
|
Object.assign(target, this.currentEditCache);
|
||||||
|
target.isEdit = false;
|
||||||
|
this.list = [...this.list];
|
||||||
|
this.currentEditCache = null;
|
||||||
|
},
|
||||||
|
async deleteOne(id) {
|
||||||
|
await HttpUtil.post("/searchEngine/delete", null, { id });
|
||||||
|
this.list = await HttpUtil.get("/searchEngine/list");
|
||||||
|
},
|
||||||
|
async setDefault(id) {
|
||||||
|
await HttpUtil.post("/searchEngine/setChecked", null, { id });
|
||||||
|
this.list = await HttpUtil.get("/searchEngine/list");
|
||||||
|
},
|
||||||
|
async getData() {
|
||||||
|
this.list = await HttpUtil.get("/searchEngine/list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.icon {
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="userInfo">
|
<div v-if="userInfo" class="userInfo">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<img :src="userInfo.icon" class="full" />
|
<img :src="userInfo.icon" class="full" />
|
||||||
<label class="full">
|
<label class="full">
|
||||||
@ -12,7 +12,8 @@
|
|||||||
<div class="baseInfo">
|
<div class="baseInfo">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<a-tooltip title="点击修改" v-if="currentAction != 'name'">
|
<a-tooltip title="点击修改" v-if="currentAction != 'name'">
|
||||||
<span style="font-size: 2em; cursor: pointer" @click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
|
<span style="font-size: 2em; cursor: pointer"
|
||||||
|
@click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<div class="inputGroup" v-else-if="currentAction === 'name'">
|
<div class="inputGroup" v-else-if="currentAction === 'name'">
|
||||||
<a-input type="text" v-model="name" placeholder="修改昵称" />
|
<a-input type="text" v-model="name" placeholder="修改昵称" />
|
||||||
@ -26,7 +27,9 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<span style="width: 5em">密码</span>
|
<span style="width: 5em">密码</span>
|
||||||
<a-tooltip title="点击修改" v-if="currentAction != 'password'">
|
<a-tooltip title="点击修改" v-if="currentAction != 'password'">
|
||||||
<span style="cursor: pointer" @click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********" }}</span>
|
<span style="cursor: pointer"
|
||||||
|
@click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********"
|
||||||
|
}}</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<div class="inputGroup" v-else-if="currentAction === 'password'">
|
<div class="inputGroup" v-else-if="currentAction === 'password'">
|
||||||
<a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" />
|
<a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" />
|
||||||
@ -58,26 +61,24 @@
|
|||||||
<a-tooltip title="搜索框默认搜索引擎">
|
<a-tooltip title="搜索框默认搜索引擎">
|
||||||
<span style="width: 5em">搜索</span>
|
<span style="width: 5em">搜索</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="点击修改" v-if="currentAction != 'defaultSearchEngine'">
|
<div @click="showSearchEngineManage=true" style=" cursor: pointer;color:blue">管理搜索引擎</div>
|
||||||
<span style="cursor: pointer" @click="() => (this.currentAction = 'defaultSearchEngine')">{{ defaultSearchEngine }}</span>
|
|
||||||
</a-tooltip>
|
|
||||||
<div class="inputGroup" v-else-if="currentAction === 'defaultSearchEngine'">
|
|
||||||
<a-select :default-value="userInfo.defaultSearchEngine" style="width: 100%" @change="submit">
|
|
||||||
<a-select-option value="baidu">百度</a-select-option>
|
|
||||||
<a-select-option value="google">谷歌</a-select-option>
|
|
||||||
<a-select-option value="bing">Bing</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a-modal v-model="showSearchEngineManage" title="管理搜索引擎" :footer="null" width="70%">
|
||||||
|
<manage-search-engine />
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import manageSearchEngine from "./components/manageSearchEngine.vue";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import HttpUtil from "@/util/HttpUtil";
|
import HttpUtil from "@/util/HttpUtil";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "UserInfo",
|
name: "UserInfo",
|
||||||
|
components: { manageSearchEngine },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentAction: null, //当前操作,name,password,email
|
currentAction: null, //当前操作,name,password,email
|
||||||
@ -86,21 +87,11 @@ export default {
|
|||||||
password: "",
|
password: "",
|
||||||
rePassword: "",
|
rePassword: "",
|
||||||
email: "",
|
email: "",
|
||||||
|
showSearchEngineManage: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("globalConfig", ["userInfo"]),
|
...mapState("globalConfig", ["userInfo"])
|
||||||
defaultSearchEngine() {
|
|
||||||
switch (this.userInfo.defaultSearchEngine) {
|
|
||||||
case "baidu":
|
|
||||||
return "百度";
|
|
||||||
case "google":
|
|
||||||
return "谷歌";
|
|
||||||
case "bing":
|
|
||||||
return "Bing";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async changeIcon(e) {
|
async changeIcon(e) {
|
||||||
@ -127,21 +118,18 @@ export default {
|
|||||||
url = "/baseInfo/password";
|
url = "/baseInfo/password";
|
||||||
body = {
|
body = {
|
||||||
oldPassword: this.oldPassword,
|
oldPassword: this.oldPassword,
|
||||||
password: this.password,
|
password: this.password
|
||||||
};
|
};
|
||||||
} else if (this.currentAction === "email") {
|
} else if (this.currentAction === "email") {
|
||||||
url = "/baseInfo/email";
|
url = "/baseInfo/email";
|
||||||
body = { oldPassword: this.oldPassword, email: this.email };
|
body = { oldPassword: this.oldPassword, email: this.email };
|
||||||
} else if (this.currentAction === "defaultSearchEngine") {
|
|
||||||
url = "/baseInfo/updateSearchEngine";
|
|
||||||
body = { defaultSearchEngine: e };
|
|
||||||
}
|
}
|
||||||
await HttpUtil.post(url, null, body);
|
await HttpUtil.post(url, null, body);
|
||||||
await this.$store.dispatch("globalConfig/refreshUserInfo");
|
await this.$store.dispatch("globalConfig/refreshUserInfo");
|
||||||
this.$message.success("操作成功");
|
this.$message.success("操作成功");
|
||||||
this.currentAction = null;
|
this.currentAction = null;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -195,6 +183,7 @@ export default {
|
|||||||
border-bottom: 1px solid #ebebeb;
|
border-bottom: 1px solid #ebebeb;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.inputGroup {
|
.inputGroup {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
39
bookmark_front/src/views/manage/sso/auth/index.vue
Normal file
39
bookmark_front/src/views/manage/sso/auth/index.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ssoMain">{{ message }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { GLOBAL_CONFIG, TOKEN } from "@/store/modules/globalConfig";
|
||||||
|
export default {
|
||||||
|
name: "ssoPage",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: "loading"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("message", event => {
|
||||||
|
if (!event.data.code) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("收到content消息", event);
|
||||||
|
if (event.data.code == "setTokenOk") {
|
||||||
|
this.message = "登陆成功,3s后关闭本页面";
|
||||||
|
setTimeout(() => window.close(), 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let token = this.$store.state[GLOBAL_CONFIG][TOKEN];
|
||||||
|
window.postMessage({ code: "setToken", data: token }, "*");
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener("message");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ssoMain {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
266
bookmark_front/src/views/noHead/addBookmark/index.vue
Normal file
266
bookmark_front/src/views/noHead/addBookmark/index.vue
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ssoAddBookmark">
|
||||||
|
<div class="body">
|
||||||
|
<div>
|
||||||
|
<a-input placeholder="标题" v-model="form.name" @pressEnter="addBookmark" ref="nameInput" />
|
||||||
|
<a-input placeholder="网址" v-model="form.url" @pressEnter="addBookmark" />
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<div class="path">
|
||||||
|
<div>保存路径:</div>
|
||||||
|
<a-breadcrumb>
|
||||||
|
<a-breadcrumb-item class="breadItem"><span @click="breadClick(null)">根</span></a-breadcrumb-item>
|
||||||
|
<a-breadcrumb-item class="breadItem" v-for="item in breadList" :key="item.bookmarkId">
|
||||||
|
<span @click="breadClick(item)">{{ item.name.length > 4 ? item.name.substr(0, 3) + "..." : item.name }}</span>
|
||||||
|
</a-breadcrumb-item>
|
||||||
|
</a-breadcrumb>
|
||||||
|
</div>
|
||||||
|
<div class="folderList">
|
||||||
|
<div :class="{ item: true, bg: item.type == 1 }" v-for="item in dataList" :key="item.bookmarkId" :title="item.url">
|
||||||
|
<span class="text" @click="folderClick(item)">{{ item.name }}</span>
|
||||||
|
<a-popconfirm class="actionBar" placement="left" title="确认删除?" ok-text="是" cancel-text="否" @confirm="deleteOne(item, $event)">
|
||||||
|
<a-icon type="delete" />
|
||||||
|
</a-popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action">
|
||||||
|
<div v-if="showAddInput" style="display: flex">
|
||||||
|
<a-input v-model="addFolderName" style="width: 8em" @pressEnter="addFolder" ref="folderInput" />
|
||||||
|
<a-button shape="circle" icon="close" @click="showAddInput = false" />
|
||||||
|
<a-button type="primary" shape="circle" icon="check" @click="addFolder" />
|
||||||
|
</div>
|
||||||
|
<a-button v-else type="link" @click="showFolderInput">新建文件夹</a-button>
|
||||||
|
<div>
|
||||||
|
<a-button style="marging-right: 1em" type="" @click="closeIframe">取消</a-button>
|
||||||
|
<a-button type="primary" @click="addBookmark">{{ breadList.length === 0 ? "保存到根" : "保存" }}</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HttpUtil from "@/util/HttpUtil";
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import { TREE_DATA, TOTAL_TREE_DATA, addNode, deleteData } from "@/store/modules/treeData";
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
breadList: [],
|
||||||
|
showAddInput: false,
|
||||||
|
addFolderName: "",
|
||||||
|
form: {
|
||||||
|
name: null,
|
||||||
|
url: null,
|
||||||
|
icon: null,
|
||||||
|
iconUrl: null,
|
||||||
|
type: 0,
|
||||||
|
path: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(TREE_DATA, [TOTAL_TREE_DATA]),
|
||||||
|
dataList() {
|
||||||
|
let path = this.getCurrentPath();
|
||||||
|
return this.totalTreeData[path] ? this.totalTreeData[path] : [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
//接受父节点传递的书签信息
|
||||||
|
window.addEventListener("message", (event) => {
|
||||||
|
if (!event.data.code) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("收到content消息", event);
|
||||||
|
if (event.data.code == "addBookmarkAction") {
|
||||||
|
console.log("新增书签");
|
||||||
|
this.form.name = event.data.data.name;
|
||||||
|
this.form.url = event.data.data.url;
|
||||||
|
this.form.icon = event.data.data.icon;
|
||||||
|
this.form.iconUrl = event.data.data.iconUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("向父节点获取数据");
|
||||||
|
window.parent.postMessage({ code: "getBookmarkData", receiver: "content" }, "*");
|
||||||
|
this.$refs.nameInput.focus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeIframe() {
|
||||||
|
window.parent.postMessage({ code: "closeIframe", receiver: "content" }, "*");
|
||||||
|
},
|
||||||
|
//新增书签
|
||||||
|
async addBookmark() {
|
||||||
|
this.form.path = this.getCurrentPath();
|
||||||
|
let res = await HttpUtil.put("/bookmark", null, this.form);
|
||||||
|
this.$message.success("添加成功");
|
||||||
|
await this.$store.dispatch(TREE_DATA + "/" + addNode, {
|
||||||
|
sourceNode: this.breadList.length == 0 ? null : this.breadList[this.breadList.length - 1],
|
||||||
|
targetNode: res,
|
||||||
|
});
|
||||||
|
setTimeout(this.closeIframe, 500);
|
||||||
|
},
|
||||||
|
//面包屑点击
|
||||||
|
async breadClick(item) {
|
||||||
|
console.log(item);
|
||||||
|
//点击最后一个无效
|
||||||
|
if (item == null && this.breadList.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item === this.breadList[this.breadList.length - 1]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item == null) {
|
||||||
|
this.breadList = [];
|
||||||
|
} else {
|
||||||
|
let index = this.breadList.indexOf(item);
|
||||||
|
this.breadList = [...this.breadList.slice(0, index + 1)];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//点击文件夹
|
||||||
|
folderClick(item) {
|
||||||
|
this.form.path = item.path + "." + item.bookmarkId;
|
||||||
|
this.breadList.push(item);
|
||||||
|
},
|
||||||
|
//新增文件夹
|
||||||
|
async addFolder() {
|
||||||
|
let length = this.addFolderName.trim().length;
|
||||||
|
if (length == 0) {
|
||||||
|
this.$message.error("文件夹名称不为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (length > 200) {
|
||||||
|
this.$message.error("文件夹名称长度不能大于200");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let form = {
|
||||||
|
name: this.addFolderName,
|
||||||
|
path: this.getCurrentPath(),
|
||||||
|
type: 1,
|
||||||
|
url: "",
|
||||||
|
};
|
||||||
|
let res = await HttpUtil.put("/bookmark", null, form);
|
||||||
|
await this.$store.dispatch(TREE_DATA + "/" + addNode, {
|
||||||
|
sourceNode: this.breadList.length == 0 ? null : this.breadList[this.breadList.length - 1],
|
||||||
|
targetNode: res,
|
||||||
|
});
|
||||||
|
this.addFolderName = "";
|
||||||
|
this.showAddInput = false;
|
||||||
|
this.breadList = [...this.breadList];
|
||||||
|
this.$message.success("新增成功");
|
||||||
|
},
|
||||||
|
//获取当前文件夹路径
|
||||||
|
getCurrentPath() {
|
||||||
|
if (this.breadList.length == 0) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
let lastOne = this.breadList[this.breadList.length - 1];
|
||||||
|
return lastOne.path + "." + lastOne.bookmarkId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//删除一个
|
||||||
|
async deleteOne(item) {
|
||||||
|
let body = { pathList: [], bookmarkIdList: [] };
|
||||||
|
item.type == 0 ? body.bookmarkIdList.push(item.bookmarkId) : body.pathList.push(item.path + "." + item.bookmarkId);
|
||||||
|
await HttpUtil.post("/bookmark/batchDelete", null, body);
|
||||||
|
await this.$store.dispatch(TREE_DATA + "/" + deleteData, body);
|
||||||
|
this.$message.success("删除成功");
|
||||||
|
},
|
||||||
|
//显示输入框
|
||||||
|
showFolderInput() {
|
||||||
|
this.showAddInput = true;
|
||||||
|
console.log(this.$refs);
|
||||||
|
this.$nextTick(() => this.$refs.folderInput.focus());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ssoAddBookmark {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
background: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.list {
|
||||||
|
padding-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
.path {
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
.breadItem {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadItem:last-child {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folderList {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
height: 0;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
flex: 1;
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionBar {
|
||||||
|
display: none;
|
||||||
|
width: 2em;
|
||||||
|
align-items: center;
|
||||||
|
color: red;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg {
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgb(24, 144, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg:hover {
|
||||||
|
background: rgba(74, 74, 74, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover .actionBar {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
padding-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -5,13 +5,23 @@
|
|||||||
源码地址:
|
源码地址:
|
||||||
<a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a>
|
<a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div>
|
<div>
|
||||||
|
当前版本:{{ appVersion }}  <a v-if="showNewVersion"
|
||||||
|
href="https://github.com/FleyX/bookmark/blob/master/DEPLOY.md">最新版本:{{ latestVersion
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
使用教程:
|
使用教程:
|
||||||
<a href="https://github.com/FleyX/bookmark" target="_blank">点击跳转</a>
|
<a href="https://blog.fleyx.com/blog/detail/20220329" target="_blank">点击跳转</a>
|
||||||
</div> -->
|
</div>
|
||||||
|
<div>
|
||||||
|
浏览器插件:
|
||||||
|
<a href="/static/bookmarkBrowserPlugin.zip" download="浏览器插件.zip"
|
||||||
|
target="_blank">最新版本{{ serverConfig.map.pluginVersion }}(注意更新)</a>
|
||||||
|
,使用详情请参考使用教程
|
||||||
|
</div>
|
||||||
<div>交流反馈qq群:150056494,邮箱:fleyx20@outlook.com</div>
|
<div>交流反馈qq群:150056494,邮箱:fleyx20@outlook.com</div>
|
||||||
<div>
|
<div>
|
||||||
统计:
|
|
||||||
<a href="https://qiezi.fleyx.com" style="" target="_blank">
|
<a href="https://qiezi.fleyx.com" style="" target="_blank">
|
||||||
<div id="qieziStatisticHtmlHostPv" style="display: none">
|
<div id="qieziStatisticHtmlHostPv" style="display: none">
|
||||||
总访问次数:
|
总访问次数:
|
||||||
@ -29,17 +39,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
import { GLOBAL_CONFIG, SERVER_CONFIG } from "@/store/modules/globalConfig";
|
||||||
|
import httpUtil from "@/util/HttpUtil";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "about",
|
name: "about",
|
||||||
mounted() {
|
data() {
|
||||||
window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
|
return {
|
||||||
let script = document.createElement("script");
|
appVersion: "1.4", //应用版本
|
||||||
script.type = "text/javascript";
|
latestVersion: null,
|
||||||
script.defer = true;
|
showNewVersion: false
|
||||||
script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
|
};
|
||||||
document.getElementsByTagName("head")[0].appendChild(script);
|
|
||||||
},
|
},
|
||||||
};
|
computed: { ...mapState(GLOBAL_CONFIG, [SERVER_CONFIG]) },
|
||||||
|
async mounted() {
|
||||||
|
|
||||||
|
|
||||||
|
//获取最新版本
|
||||||
|
let res = await httpUtil.http.default.get("https://s3.fleyx.com/picbed/bookmark/config.json");
|
||||||
|
console.log(res);
|
||||||
|
this.latestVersion = res.data.latestAppVersion;
|
||||||
|
this.showNewVersion = this.checkVersion(this.appVersion, this.latestVersion);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkVersion: function(version, latestVersion) {
|
||||||
|
if (version === latestVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let versions = version.split(".");
|
||||||
|
let latestVersions = latestVersion.split(".");
|
||||||
|
for (let i = 0; i < versions.length; i++) {
|
||||||
|
if (i >= latestVersions.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let versionNum = parseInt(versions[i]);
|
||||||
|
let latestVersionNum = parseInt(latestVersions[i]);
|
||||||
|
if (versionNum !== latestVersionNum) {
|
||||||
|
return versionNum < latestVersionNum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="thirdPart">
|
<div class="thirdPart">
|
||||||
<span>第三方登陆</span>
|
<span>第三方登陆</span>
|
||||||
<a-tooltip v-if="serverConfig.proxyExist" title="github登陆" class="oneIcon" placement="bottom">
|
<a-tooltip title="github登陆" class="oneIcon" placement="bottom">
|
||||||
<a-icon type="github" @click="toGithub" style="font-size: 1.4em" />
|
<a-icon type="github" @click="toGithub" style="font-size: 1.4em" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -39,39 +39,44 @@ import Header from "@/components/public/Switch.vue";
|
|||||||
import httpUtil from "../../../util/HttpUtil.js";
|
import httpUtil from "../../../util/HttpUtil.js";
|
||||||
import { mapMutations, mapState } from "vuex";
|
import { mapMutations, mapState } from "vuex";
|
||||||
import HttpUtil from "../../../util/HttpUtil.js";
|
import HttpUtil from "../../../util/HttpUtil.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Login",
|
name: "Login",
|
||||||
components: {
|
components: {
|
||||||
Header,
|
Header
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState("globalConfig", ["serverConfig"]),
|
...mapState("globalConfig", ["serverConfig"])
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
str: "",
|
str: "",
|
||||||
password: "",
|
password: "",
|
||||||
rememberMe: false,
|
rememberMe: false
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
str: [
|
str: [
|
||||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||||
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" },
|
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" }
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
{ pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" },
|
{ pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" }
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
loading: false, //是否加载中
|
loading: false, //是否加载中
|
||||||
oauthLogining: false, //true:正在进行oauth后台操作
|
oauthLogining: false, //true:正在进行oauth后台操作
|
||||||
page: null, //oauth打开的页面实例
|
page: null, //oauth打开的页面实例
|
||||||
|
redirect: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
let _this = this;
|
let _this = this;
|
||||||
window.addEventListener("storage", this.storageDeal.bind(this));
|
window.addEventListener("storage", this.storageDeal.bind(this));
|
||||||
|
if (this.$route.query.to) {
|
||||||
|
this.redirect = atob(this.$route.query.to);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener("storage", this.storageDeal);
|
window.removeEventListener("storage", this.storageDeal);
|
||||||
@ -85,7 +90,7 @@ export default {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
let token = await httpUtil.post("/user/login", null, this.form);
|
let token = await httpUtil.post("/user/login", null, this.form);
|
||||||
await this.$store.dispatch("globalConfig/setToken", token);
|
await this.$store.dispatch("globalConfig/setToken", token);
|
||||||
this.$router.replace("/");
|
this.$router.replace(this.redirect ? this.redirect : "/");
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@ -126,8 +131,8 @@ export default {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -135,11 +140,13 @@ export default {
|
|||||||
.form {
|
.form {
|
||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
margin-bottom: 0.1rem;
|
margin-bottom: 0.1rem;
|
||||||
|
|
||||||
.reset {
|
.reset {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.thirdPart {
|
.thirdPart {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -81,12 +81,12 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit() {
|
async submit() {
|
||||||
let _this = this;
|
let _this = this;
|
||||||
this.$refs.registerForm.validate(async (status) => {
|
this.$refs.registerForm.validate(async (status) => {
|
||||||
if (status) {
|
if (status) {
|
||||||
let res = await httpUtil.put("/user", null, _this.form);
|
let res = await httpUtil.put("/user", null, _this.form);
|
||||||
this.$store.dispatch("globalConfig/setToken", res);
|
await this.$store.dispatch("globalConfig/setToken", res);
|
||||||
this.$router.replace("/");
|
this.$router.replace("/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
lintOnSave: false,
|
lintOnSave: false,
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
devtool: "source-map"
|
devtool: "source-map"
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
proxy: {
|
port: 4531,
|
||||||
"/bookmark/api": {
|
proxy: {
|
||||||
//这里最好有一个 /
|
"/bookmark/api": {
|
||||||
target: "http://localhost:8088", // 服务器端接口地址
|
//这里最好有一个 /
|
||||||
ws: true, //如果要代理 websockets,配置这个参数
|
target: "http://localhost:8088", // 服务器端接口地址
|
||||||
// 如果是https接口,需要配置这个参数
|
ws: true, //如果要代理 websockets,配置这个参数
|
||||||
changeOrigin: true, //是否跨域
|
// 如果是https接口,需要配置这个参数
|
||||||
pathRewrite: {
|
changeOrigin: true, //是否跨域
|
||||||
"^/bookmark/api": "/bookmark/api"
|
pathRewrite: {
|
||||||
}
|
"^/bookmark/api": "/bookmark/api"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
7
build.sh
Normal file → Executable file
7
build.sh
Normal file → Executable file
@ -3,8 +3,11 @@ base=$(cd "$(dirname "$0")";pwd)
|
|||||||
echo $base
|
echo $base
|
||||||
cd $base
|
cd $base
|
||||||
|
|
||||||
|
cd 浏览器插件/bookmarkBrowserPlugin
|
||||||
|
zip -q -r ../../bookmark_front/public/static/bookmarkBrowserPlugin.zip *
|
||||||
|
|
||||||
|
cd ../../
|
||||||
# 前端打包
|
# 前端打包
|
||||||
docker run -it --rm --user ${UID} -v $base/bookmark_front:/opt/front node:lts-buster-slim bash -c "cd /opt/front && yarn --registry https://registry.npm.taobao.org && yarn build"
|
docker run --rm --user ${UID} -v $base/bookmark_front:/opt/front node:16-slim bash -c "cd /opt/front && yarn --registry https://registry.npm.taobao.org && yarn build"
|
||||||
# 后端打包
|
# 后端打包
|
||||||
docker run -it --rm --user ${UID} -v $base/data/maven/mavenRep:/var/maven/.m2: -v $base/data/maven/settings.xml:/usr/share/maven/conf/settings.xml -v $base/bookMarkService:/code maven:3-openjdk-11-slim bash -c "cd /code && mvn clean install"
|
docker run --rm --user ${UID} -v $base/data/maven/mavenRep:/var/maven/.m2 -v $base/data/maven/settings.xml:/usr/share/maven/conf/settings.xml -v $base/bookMarkService:/code maven:3-openjdk-11-slim bash -c "cd /code && mvn clean install"
|
@ -14,6 +14,7 @@ services:
|
|||||||
- ./data/mysql/my.cnf:/etc/mysql/my.cnf
|
- ./data/mysql/my.cnf:/etc/mysql/my.cnf
|
||||||
- /etc/localtime:/etc/localtime
|
- /etc/localtime:/etc/localtime
|
||||||
- ./data/timezone:/etc/timezone
|
- ./data/timezone:/etc/timezone
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
|
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
|
||||||
- MYSQL_DATABASE=bookmark
|
- MYSQL_DATABASE=bookmark
|
||||||
@ -25,6 +26,7 @@ services:
|
|||||||
- /etc/localtime:/etc/localtime
|
- /etc/localtime:/etc/localtime
|
||||||
- ./data/timezone:/etc/timezone
|
- ./data/timezone:/etc/timezone
|
||||||
- ./data/redis:/data
|
- ./data/redis:/data
|
||||||
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- bookmark
|
- bookmark
|
||||||
|
|
||||||
@ -38,6 +40,7 @@ services:
|
|||||||
- ./bookmark_front/dist:/opt/dist
|
- ./bookmark_front/dist:/opt/dist
|
||||||
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
|
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||||
- ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public
|
- ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|
||||||
@ -60,6 +63,7 @@ services:
|
|||||||
- ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar
|
- ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar
|
||||||
- ${BOOKMARK_FILE_SAVE_PATH}:/opt/files
|
- ${BOOKMARK_FILE_SAVE_PATH}:/opt/files
|
||||||
working_dir: /opt/app
|
working_dir: /opt/app
|
||||||
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- -c
|
- -c
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
"@babel/plugin-proposal-optional-chaining"
|
|
||||||
],
|
|
||||||
"presets": [
|
|
||||||
["@babel/preset-env", {
|
|
||||||
"useBuiltIns": "usage",
|
|
||||||
"corejs": 3,
|
|
||||||
"targets": {
|
|
||||||
// https://jamie.build/last-2-versions
|
|
||||||
"browsers": ["> 0.25%", "not ie 11", "not op_mini all"]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
@ -1,33 +0,0 @@
|
|||||||
// https://eslint.org/docs/user-guide/configuring
|
|
||||||
// File taken from https://github.com/vuejs-templates/webpack/blob/1.3.1/template/.eslintrc.js, thanks.
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parserOptions: {
|
|
||||||
parser: 'babel-eslint'
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
webextensions: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
|
|
||||||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
|
|
||||||
'plugin:vue/essential',
|
|
||||||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
|
||||||
'standard',
|
|
||||||
// https://prettier.io/docs/en/index.html
|
|
||||||
'plugin:prettier/recommended'
|
|
||||||
],
|
|
||||||
// required to lint *.vue files
|
|
||||||
plugins: [
|
|
||||||
'vue'
|
|
||||||
],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
// allow async-await
|
|
||||||
'generator-star-spacing': 'off',
|
|
||||||
// allow debugger during development
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
|
||||||
}
|
|
||||||
}
|
|
4
浏览器插件/bookmark-chrome/.gitignore
vendored
4
浏览器插件/bookmark-chrome/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
/node_modules
|
|
||||||
/*.log
|
|
||||||
/dist
|
|
||||||
/dist-zip
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"printWidth": 180,
|
|
||||||
"trailingComma": "es5"
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bookmark-chrome",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A Vue.js web extension",
|
|
||||||
"author": "fanxb <fanxb.tl@gmail.com>",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint --ext .js,.vue src",
|
|
||||||
"prettier": "prettier \"src/**/*.{js,vue}\"",
|
|
||||||
"prettier:write": "npm run prettier -- --write",
|
|
||||||
"build": "cross-env NODE_ENV=production webpack --hide-modules",
|
|
||||||
"build:dev": "cross-env NODE_ENV=development webpack --hide-modules",
|
|
||||||
"build-zip": "node scripts/build-zip.js",
|
|
||||||
"watch": "npm run build -- --watch",
|
|
||||||
"watch:dev": "cross-env HMR=true npm run build:dev -- --watch"
|
|
||||||
},
|
|
||||||
"husky": {
|
|
||||||
"hooks": {
|
|
||||||
"pre-commit": "pretty-quick --staged"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^0.19.0",
|
|
||||||
"element-ui": "^2.12.0",
|
|
||||||
"vue": "^2.6.10",
|
|
||||||
"vue-router": "^3.0.1",
|
|
||||||
"vuex": "^3.0.1",
|
|
||||||
"webextension-polyfill": "^0.3.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/core": "^7.1.2",
|
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
|
||||||
"@babel/preset-env": "^7.1.0",
|
|
||||||
"@babel/runtime-corejs3": "^7.4.0",
|
|
||||||
"archiver": "^3.0.0",
|
|
||||||
"babel-eslint": "^10.0.1",
|
|
||||||
"babel-loader": "^8.0.2",
|
|
||||||
"copy-webpack-plugin": "^4.5.3",
|
|
||||||
"core-js": "^3.0.1",
|
|
||||||
"cross-env": "^5.2.0",
|
|
||||||
"css-loader": "^2.1.1",
|
|
||||||
"ejs": "^2.6.1",
|
|
||||||
"eslint": "^5.16.0",
|
|
||||||
"eslint-config-prettier": "^4.3.0",
|
|
||||||
"eslint-config-standard": "^12.0.0",
|
|
||||||
"eslint-friendly-formatter": "^4.0.1",
|
|
||||||
"eslint-loader": "^2.1.2",
|
|
||||||
"eslint-plugin-import": "^2.16.0",
|
|
||||||
"eslint-plugin-node": "^7.0.1",
|
|
||||||
"eslint-plugin-prettier": "^3.1.0",
|
|
||||||
"eslint-plugin-promise": "^4.1.1",
|
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
|
||||||
"eslint-plugin-vue": "^5.2.2",
|
|
||||||
"file-loader": "^1.1.11",
|
|
||||||
"husky": "^2.4.0",
|
|
||||||
"mini-css-extract-plugin": "^0.4.4",
|
|
||||||
"node-sass": "^4.9.3",
|
|
||||||
"prettier": "^1.17.1",
|
|
||||||
"pretty-quick": "^1.8.0",
|
|
||||||
"sass-loader": "^7.1.0",
|
|
||||||
"vue-loader": "^15.4.2",
|
|
||||||
"vue-template-compiler": "^2.6.10",
|
|
||||||
"web-ext-types": "^2.1.0",
|
|
||||||
"webpack": "^4.20.2",
|
|
||||||
"webpack-cli": "^3.1.2",
|
|
||||||
"webpack-extension-reloader": "^1.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const archiver = require('archiver');
|
|
||||||
|
|
||||||
const DEST_DIR = path.join(__dirname, '../dist');
|
|
||||||
const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');
|
|
||||||
|
|
||||||
const extractExtensionData = () => {
|
|
||||||
const extPackageJson = require('../package.json');
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: extPackageJson.name,
|
|
||||||
version: extPackageJson.version,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeDestZipDirIfNotExists = () => {
|
|
||||||
if (!fs.existsSync(DEST_ZIP_DIR)) {
|
|
||||||
fs.mkdirSync(DEST_ZIP_DIR);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildZip = (src, dist, zipFilename) => {
|
|
||||||
console.info(`Building ${zipFilename}...`);
|
|
||||||
|
|
||||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
||||||
const stream = fs.createWriteStream(path.join(dist, zipFilename));
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
archive
|
|
||||||
.directory(src, false)
|
|
||||||
.on('error', err => reject(err))
|
|
||||||
.pipe(stream);
|
|
||||||
|
|
||||||
stream.on('close', () => resolve());
|
|
||||||
archive.finalize();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const main = () => {
|
|
||||||
const { name, version } = extractExtensionData();
|
|
||||||
const zipFilename = `${name}-v${version}.zip`;
|
|
||||||
|
|
||||||
makeDestZipDirIfNotExists();
|
|
||||||
|
|
||||||
buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
|
|
||||||
.then(() => console.info('OK'))
|
|
||||||
.catch(console.err);
|
|
||||||
};
|
|
||||||
|
|
||||||
main();
|
|
@ -1,52 +0,0 @@
|
|||||||
import httpUtil from './util/httpUtil.js';
|
|
||||||
global.browser = require('webextension-polyfill');
|
|
||||||
|
|
||||||
window.envType = 'background';
|
|
||||||
window.token = localStorage.getItem('token');
|
|
||||||
|
|
||||||
let token = null;
|
|
||||||
let globalPort = null;
|
|
||||||
|
|
||||||
chrome.extension.onConnect.addListener(port => {
|
|
||||||
console.log(port);
|
|
||||||
globalPort = port;
|
|
||||||
port.onMessage.addListener(msg => {
|
|
||||||
switch (msg.type) {
|
|
||||||
case 'sendToken':
|
|
||||||
console.log(msg);
|
|
||||||
localStorage.setItem('token', msg.data);
|
|
||||||
window.token = msg.data;
|
|
||||||
token = msg.data;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('未知的数据', msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.contextMenus.create(
|
|
||||||
{
|
|
||||||
title: '添加到书签',
|
|
||||||
onclick: (info, tab) => {
|
|
||||||
console.log(info, tab);
|
|
||||||
httpUtil.put('/bookmark', {
|
|
||||||
type: 0,
|
|
||||||
path: '',
|
|
||||||
name: tab.title,
|
|
||||||
url: tab.url,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err => {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建一个标准命令
|
|
||||||
* @param {*} code code
|
|
||||||
* @param {*} data data
|
|
||||||
*/
|
|
||||||
function createMsg(code, data) {
|
|
||||||
return JSON.stringify({ code, data });
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bookmark-chrome",
|
|
||||||
"description": "A Vue.js web extension",
|
|
||||||
"version": null,
|
|
||||||
"manifest_version": 2,
|
|
||||||
"permissions": [
|
|
||||||
"contextMenus"
|
|
||||||
],
|
|
||||||
"icons": {
|
|
||||||
"48": "icons/icon_48.png",
|
|
||||||
"128": "icons/icon_128.png"
|
|
||||||
},
|
|
||||||
"browser_action": {
|
|
||||||
"default_title": "bookmark-chrome",
|
|
||||||
"default_popup": "popup/popup.html"
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"scripts": [
|
|
||||||
"background.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"options_ui": {
|
|
||||||
"page": "options/options.html",
|
|
||||||
"chrome_style": true
|
|
||||||
},
|
|
||||||
"content_scripts": [{
|
|
||||||
"matches": ["*://*/*"],
|
|
||||||
"js": ["static/sso.js"]
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p>Hello world!这是选项页</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'App',
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
p {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>bookmark-chrome - Options</title>
|
|
||||||
<link rel="stylesheet" href="options.css">
|
|
||||||
<% if (NODE_ENV === 'development') { %>
|
|
||||||
<!-- Load some resources only in development environment -->
|
|
||||||
<% } %>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script src="options.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
global.browser = require('webextension-polyfill');
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
|
||||||
new Vue({
|
|
||||||
el: '#app',
|
|
||||||
render: h => h(App),
|
|
||||||
});
|
|
@ -1,20 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app">
|
|
||||||
<router-view></router-view>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.app {
|
|
||||||
width: 600px;
|
|
||||||
height: 580px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Title</title>
|
|
||||||
<link rel="stylesheet" href="popup.css">
|
|
||||||
<% if (NODE_ENV === 'development') { %>
|
|
||||||
<!-- Load some resources only in development environment -->
|
|
||||||
<% } %>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script src="/static/js/localforage.min.js"></script>
|
|
||||||
<script src="popup.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,55 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import App from './App';
|
|
||||||
import store from '../store';
|
|
||||||
import router from './router';
|
|
||||||
import ElementUI from 'element-ui';
|
|
||||||
import 'element-ui/lib/theme-chalk/index.css';
|
|
||||||
import config from '../util/config';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
global.browser = require('webextension-polyfill');
|
|
||||||
Vue.prototype.$browser = global.browser;
|
|
||||||
Vue.use(ElementUI);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置axios
|
|
||||||
*/
|
|
||||||
axios.defaults.timeout = 15000;
|
|
||||||
axios.defaults.baseURL = config.baseUrl;
|
|
||||||
/**
|
|
||||||
* 请求拦截器
|
|
||||||
*/
|
|
||||||
axios.interceptors.request.use(
|
|
||||||
function(config) {
|
|
||||||
config.headers['jwt-token'] = window.token;
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
console.error(error);
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
|
||||||
res => {
|
|
||||||
if (res.data.code === -1) {
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
window.vueInstance.$router.replace('/public/login');
|
|
||||||
} else if (res.data.code === 1) {
|
|
||||||
return res.data.data;
|
|
||||||
} else {
|
|
||||||
Promise.reject(res);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
|
||||||
window.vueInstance = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
store,
|
|
||||||
router,
|
|
||||||
render: h => h(App),
|
|
||||||
});
|
|
@ -1,74 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="main">
|
|
||||||
<el-tree :props="props" :load="loadNode" accordion @node-click="nodeClick" lazy>
|
|
||||||
<span class="treeItem" slot-scope="{ node }">
|
|
||||||
<span>{{ node.label }}</span>
|
|
||||||
</span>
|
|
||||||
</el-tree>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios';
|
|
||||||
export default {
|
|
||||||
name: 'BookmarkTree',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
label: 'name',
|
|
||||||
children: 'children',
|
|
||||||
isLeaf: 'leaf',
|
|
||||||
},
|
|
||||||
treeObj: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async loadNode(node, resolve) {
|
|
||||||
if (window.treeData == null) {
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if ((window.treeData = await localforage.getItem('treeData')) == null) {
|
|
||||||
window.treeData = await axios.get('/bookmark/currentUser');
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
await localforage.setItem('treeData', window.treeData);
|
|
||||||
}
|
|
||||||
console.log(window.treeData);
|
|
||||||
}
|
|
||||||
console.log(node, resolve);
|
|
||||||
let list;
|
|
||||||
if (node.level === 0) {
|
|
||||||
list = window.treeData[''];
|
|
||||||
} else {
|
|
||||||
list = window.treeData[node.data.path + '.' + node.data.bookmarkId];
|
|
||||||
}
|
|
||||||
list.forEach(item => (item.leaf = item.type === 0));
|
|
||||||
console.log(list);
|
|
||||||
return resolve(list);
|
|
||||||
},
|
|
||||||
nodeClick(data) {
|
|
||||||
if (data.type === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let url = data.url;
|
|
||||||
if (!url.startsWith('http')) {
|
|
||||||
url = 'http://' + url;
|
|
||||||
}
|
|
||||||
console.log(url);
|
|
||||||
window.open(data.url);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.main {
|
|
||||||
width: 95%;
|
|
||||||
height: 500px;
|
|
||||||
overflow: auto;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.treeItem {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,92 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-popover placement="bottom-start" width="550" popper-class="popover" trigger="manual" :visible-arrow="false" v-model="isShow">
|
|
||||||
<div>
|
|
||||||
<div class="item" v-for="item in searchList" :key="item.bookmarkId">
|
|
||||||
<a target="_blank" :href="item.url">{{ item.name }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-input
|
|
||||||
ref="searchInput"
|
|
||||||
type="text"
|
|
||||||
placeholder="搜索"
|
|
||||||
v-model="searchContent"
|
|
||||||
clearable
|
|
||||||
:autofocus="true"
|
|
||||||
slot="reference"
|
|
||||||
@blur="clear"
|
|
||||||
@keyup.enter.native="searchKey"
|
|
||||||
@focus="focus"
|
|
||||||
>
|
|
||||||
<el-button slot="append" icon="el-icon-search" @click="searchKey"></el-button>
|
|
||||||
</el-input>
|
|
||||||
</el-popover>
|
|
||||||
<div class="searchResult"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios';
|
|
||||||
export default {
|
|
||||||
name: 'searchBookmark',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
searchContent: '',
|
|
||||||
// 是否展示结果列表
|
|
||||||
isShow: false,
|
|
||||||
// 是否有焦点
|
|
||||||
isFocus: false,
|
|
||||||
searchList: [],
|
|
||||||
timeOut: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
searchContent(newVal, oldVal) {
|
|
||||||
if (newVal.trim().length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (newVal.trim() !== oldVal) {
|
|
||||||
if (this.timeOut != null) {
|
|
||||||
clearTimeout(this.timeOut);
|
|
||||||
}
|
|
||||||
this.searchList = [];
|
|
||||||
this.timeOut = setTimeout(async () => {
|
|
||||||
this.searchList = await axios.get(`bookmark/searchUserBookmark?content=${newVal}`);
|
|
||||||
this.isShow = this.searchList.length > 0;
|
|
||||||
this.timeOut = null;
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs['searchInput'].focus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
searchKey() {
|
|
||||||
window.open('https://www.baidu.com/s?ie=UTF-8&wd=' + window.encodeURIComponent(this.searchContent));
|
|
||||||
},
|
|
||||||
focus() {
|
|
||||||
this.isFocus = true;
|
|
||||||
},
|
|
||||||
clear() {
|
|
||||||
this.isFocus = false;
|
|
||||||
if (this.timeOut != null) {
|
|
||||||
clearTimeout(this.timeOut);
|
|
||||||
}
|
|
||||||
this.searchContent = '';
|
|
||||||
this.searchList = [];
|
|
||||||
this.isShow = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
width: 500px;
|
|
||||||
padding: 5px;
|
|
||||||
border-bottom: 1px solid black;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||||||
import Vue from 'vue';
|
|
||||||
import VueRouter from 'vue-router';
|
|
||||||
import routes from './routes';
|
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
|
||||||
|
|
||||||
export default new VueRouter({
|
|
||||||
routes,
|
|
||||||
});
|
|
@ -1,50 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="head">
|
|
||||||
<img width="30%" src="/static/img/bookmarkLogo.png" alt="icon" />
|
|
||||||
<span>{{ personInfo.username }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- 书签检索 -->
|
|
||||||
<search />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios';
|
|
||||||
import Search from '../components/SearchBookmark';
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
Search,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
personInfo: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
window.token = localStorage.getItem('token');
|
|
||||||
if (!window.token) {
|
|
||||||
this.$router.replace('/public/login');
|
|
||||||
} else {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async init() {
|
|
||||||
let personInfo = await axios.get('/user/currentUserInfo');
|
|
||||||
window.personInfo = personInfo;
|
|
||||||
this.personInfo = personInfo;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 5% 0 5%;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="main">您还未登陆授权<br />
|
|
||||||
<el-button type="primary" @click="go">前往授权页面</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import config from '../../../../util/config';
|
|
||||||
export default {
|
|
||||||
name: 'login',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.checkToken();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
go() {
|
|
||||||
window.open(config.ssoUrl);
|
|
||||||
},
|
|
||||||
// 循环检测token是否生效
|
|
||||||
checkToken() {
|
|
||||||
console.log('检测token是否获取到了');
|
|
||||||
let token = localStorage.getItem('token');
|
|
||||||
if (token == null || token.length === 0) {
|
|
||||||
setTimeout(this.checkToken.bind(this), 1000);
|
|
||||||
} else {
|
|
||||||
this.$router.replace('/');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
@ -1,13 +0,0 @@
|
|||||||
import PageIndex from './pages/Index';
|
|
||||||
import Login from './pages/public/Login';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: PageIndex,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/public/login',
|
|
||||||
component: Login,
|
|
||||||
},
|
|
||||||
];
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
File diff suppressed because one or more lines are too long
@ -1,33 +0,0 @@
|
|||||||
console.log('注入了页面');
|
|
||||||
var port = chrome.extension.connect({ name: 'data' });
|
|
||||||
/**
|
|
||||||
* 接受background传来的消息
|
|
||||||
*/
|
|
||||||
port.onMessage.addListener(msg => {
|
|
||||||
console.log('收到消息:' + msg);
|
|
||||||
let obj = JSON.parse(msg);
|
|
||||||
switch (obj.code) {
|
|
||||||
case 'addBookmark':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('未知的命令:' + obj.code);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接收当前注入页面传来的消息
|
|
||||||
*/
|
|
||||||
window.addEventListener('message', function(event) {
|
|
||||||
if (event.data.type === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('接受到消息', event.data);
|
|
||||||
switch (event.data.type) {
|
|
||||||
case 'sendToken':
|
|
||||||
port.postMessage(event.data);
|
|
||||||
window.token = event.data;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('未知的事件', event);
|
|
||||||
}
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user