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 管理服务。
|
||||
|
||||
部署过程如下:
|
||||
**注意,仅在 x86 环境下测试,arm 下不保证可用性(目前测试可用)**
|
||||
|
||||
**注意,仅在 x86 环境下测试**
|
||||
## 首次部署
|
||||
|
||||
1. 安装新版的 docker 和 docker-compose(注意:以下操作均在项目根目录下执行)
|
||||
2. 执行`build.sh`编译前后端代码
|
||||
0. 克隆代码`git clone https://github.com/FleyX/bookmark.git`
|
||||
1. 进入文件夹`cd bookmark`
|
||||
2. 安装新版的 docker,docker-compose,zip `apt install docker docker-compose zip`
|
||||
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)
|
||||
|
46
README.md
46
README.md
@ -1,10 +1,12 @@
|
||||
本项目是一个云书签的项目,取名为:签签世界.
|
||||
![图片](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)
|
||||
|
||||
# 缘由
|
||||
|
||||
@ -18,27 +20,37 @@
|
||||
|
||||
# 主要功能
|
||||
|
||||
使用帮助见:[使用帮助](https://github.com/FleyX/bookmark/blob/master/HELP.md)
|
||||
帮助文档:[点击跳转](https://blog.fleyx.com/blog/detail/20220329/)
|
||||
|
||||
1. 基础的书签增删改查功能。支持 chrome、firefox 等浏览器书签文件导入,导出。
|
||||
- 支持从 chrome,edge,firefox 等浏览器导入书签数据。
|
||||
- 支持从 OneEnv 导入书签数据
|
||||
- 树型多级目录支持
|
||||
- 支持导出标准 html 书签文件
|
||||
- 强大的检索功能,支持拼音检索
|
||||
- 支持浏览器插件,安装插件以后可右键添加书签
|
||||
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214126.png?imageView2/2/w/1920)
|
||||
# 更新日志
|
||||
|
||||
2. 强大的书签检索功能,毫秒级的关键字检索。
|
||||
## 1.4.1
|
||||
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214210.png?imageView2/2/w/1920)
|
||||
- 修复书签名过长无法导入问题
|
||||
|
||||
3. 首页功能,参考 bing 首页实现
|
||||
## 1.4
|
||||
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214236.png?imageView2/2/w/1920)
|
||||
- 优化首图加载逻辑
|
||||
- 支持 OneEnv 备份文件导入
|
||||
|
||||
4. 移动端支持,手机端也可使用(部分功能比如拖拽等无法使用)
|
||||
## 1.3
|
||||
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214312.png?imageView2/2/w/1920)
|
||||
![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
|
||||
|
||||
- 主页功能 Ok!
|
||||
- 拼音检索 Ok!
|
||||
- 书签导出 OK!
|
||||
- 侧边栏显示
|
||||
- [x] 主页功能
|
||||
- [x] 拼音检索
|
||||
- [x] 书签导出
|
||||
- [x] 浏览器插件
|
||||
|
@ -22,13 +22,18 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.14.3</version>
|
||||
<version>1.15.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>pinyin</artifactId>
|
||||
<version>0.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.44.1.0</version>
|
||||
</dependency>
|
||||
</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")
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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.MoveNodeBody;
|
||||
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@ -54,7 +55,7 @@ public interface BookmarkService {
|
||||
* @author fanxb
|
||||
* @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: 详情
|
||||
|
@ -1,21 +1,30 @@
|
||||
package com.fanxb.bookmark.business.bookmark.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.codec.Base64Decoder;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
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.HostIconDao;
|
||||
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
|
||||
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.VisitNumPlus;
|
||||
import com.fanxb.bookmark.business.bookmark.service.BookmarkService;
|
||||
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.RedisConstant;
|
||||
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
||||
import com.fanxb.bookmark.common.exception.CustomException;
|
||||
import com.fanxb.bookmark.common.util.*;
|
||||
import com.mysql.cj.conf.url.SingleConnectionUrl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
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.stereotype.Service;
|
||||
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.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
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.stream.Collectors;
|
||||
|
||||
@ -39,7 +62,6 @@ import java.util.stream.Collectors;
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 15:00
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@ -51,29 +73,38 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
private final PinYinService pinYinService;
|
||||
private final UserApi userApi;
|
||||
private final EsUtil esUtil;
|
||||
private final HostIconDao hostIconDao;
|
||||
|
||||
@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.pinYinService = pinYinService;
|
||||
this.userApi = userApi;
|
||||
this.esUtil = esUtil;
|
||||
this.hostIconDao = hostIconDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception {
|
||||
Document doc = Jsoup.parse(stream, "utf-8", "");
|
||||
Elements elements = doc.select("html>body>dl>dt");
|
||||
public void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception {
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
//获取当前层sort最大值
|
||||
Integer sortBase = bookmarkDao.selectMaxSort(userId, path);
|
||||
if (sortBase == null) {
|
||||
sortBase = 0;
|
||||
}
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
if (file.getOriginalFilename().endsWith(".db3")) {
|
||||
//处理db文件
|
||||
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);
|
||||
for (int i = 0; i < bookmarks.size(); i++) {
|
||||
@ -100,7 +131,6 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
* @param path 节点路径,不包含自身
|
||||
* @param sort 当前层级中的排序序号
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 14:49
|
||||
*/
|
||||
private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) {
|
||||
if (!DT.equalsIgnoreCase(ele.tagName())) {
|
||||
@ -109,7 +139,7 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
Element first = ele.child(0);
|
||||
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);
|
||||
//存入数据库
|
||||
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: 插入一条书签,如果已经存在同名书签将跳过
|
||||
*
|
||||
* @param node node
|
||||
* @return boolean 如果已经存在返回true,否则false
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 17:25
|
||||
*/
|
||||
private boolean insertOne(Bookmark node) {
|
||||
//先根据name,userId,parentId获取此节点id
|
||||
@ -201,11 +281,14 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
bookmark.setUserId(userId);
|
||||
bookmark.setCreateTime(System.currentTimeMillis());
|
||||
bookmark.setAddTime(bookmark.getCreateTime());
|
||||
bookmark.setIcon(getIconBase64(bookmark.getUrl()));
|
||||
bookmark.setIcon(bookmark.getType() == 1 ? "" : getIconPath(bookmark.getUrl(), bookmark.getIcon(), bookmark.getIconUrl(), true));
|
||||
//文件夹和书签都建立搜索key
|
||||
pinYinService.changeBookmark(bookmark);
|
||||
bookmarkDao.insertOne(bookmark);
|
||||
userApi.versionPlus(userId);
|
||||
if (StrUtil.isEmpty(bookmark.getIcon()) && bookmark.getType() == 0) {
|
||||
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
|
||||
}
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
@ -215,13 +298,33 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
bookmark.setUserId(userId);
|
||||
if (bookmark.getType() == 0) {
|
||||
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);
|
||||
userApi.versionPlus(userId);
|
||||
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
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@ -271,10 +374,10 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
int size = 100;
|
||||
int start = 0;
|
||||
List<Bookmark> deal;
|
||||
while ((deal = bookmarkDao.selectUserNoIcon(userId, start, size)).size() > 0) {
|
||||
while (!(deal = bookmarkDao.selectUserNoIcon(userId, start, size)).isEmpty()) {
|
||||
start += size;
|
||||
deal.forEach(item -> {
|
||||
String icon = getIconBase64(item.getUrl());
|
||||
String icon = getIconPath(item.getUrl(), null, null, false);
|
||||
if (StrUtil.isNotEmpty(icon)) {
|
||||
bookmarkDao.updateIcon(item.getBookmarkId(), icon);
|
||||
}
|
||||
@ -305,24 +408,96 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
return resPath;
|
||||
}
|
||||
|
||||
private String getIconBase64(String url) {
|
||||
if (StrUtil.isEmpty(url)) {
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* 获取icon,通过网络获取,或者从base64还原
|
||||
*
|
||||
* @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 {
|
||||
URL urlObj = new URL(url);
|
||||
byte[] data = HttpUtil.download(urlIconAddress + "/icon?url=" + urlObj.getHost() + "&size=8..16..64", false);
|
||||
String base64 = new String(Base64.getEncoder().encode(data));
|
||||
if (StrUtil.isNotEmpty(base64)) {
|
||||
return "data:image/png;base64," + base64;
|
||||
} else {
|
||||
log.warn("url无法获取icon:{}", url);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
host = urlObj.getAuthority();
|
||||
} catch (Exception e) {
|
||||
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) {
|
||||
log.error("url获取icon故障:{}", url, e);
|
||||
}
|
||||
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>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</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>
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.fanxb.bookmark.business.user.constant;
|
||||
|
||||
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fanxb.bookmark.common.entity.po.User;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@ -16,7 +17,7 @@ import java.util.List;
|
||||
* @date 2019/7/6 11:36
|
||||
*/
|
||||
@Component
|
||||
public interface UserDao {
|
||||
public interface UserDao extends BaseMapper<User> {
|
||||
|
||||
/**
|
||||
* Description: 新增一个用户
|
||||
@ -182,16 +183,6 @@ public interface UserDao {
|
||||
@Select("select userId from user order by userId limit #{start},#{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
|
||||
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.fanxb.bookmark.business.user.dao.UserDao;
|
||||
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.vo.OauthBody;
|
||||
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||
@ -38,6 +39,8 @@ public class OauthServiceImpl implements OauthService {
|
||||
private String githubClientId;
|
||||
@Value("${OAuth.github.secret}")
|
||||
private String githubSecret;
|
||||
@Autowired
|
||||
private SearchEngineService searchEngineService;
|
||||
private final UserDao userDao;
|
||||
private final UserService userService;
|
||||
|
||||
@ -105,6 +108,7 @@ public class OauthServiceImpl implements OauthService {
|
||||
other.setLastLoginTime(System.currentTimeMillis());
|
||||
other.setVersion(0);
|
||||
userDao.addOne(other);
|
||||
searchEngineService.newUserInit(other.getUserId());
|
||||
return other;
|
||||
} else {
|
||||
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.user.constant.FileConstant;
|
||||
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.vo.LoginBody;
|
||||
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.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
@ -42,6 +44,8 @@ public class UserServiceImpl implements UserService {
|
||||
* 登陆最大重试次数
|
||||
*/
|
||||
private static final int LOGIN_COUNT = 5;
|
||||
@Autowired
|
||||
private SearchEngineService searchEngineService;
|
||||
|
||||
private final UserDao userDao;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
@ -83,6 +87,7 @@ public class UserServiceImpl implements UserService {
|
||||
* @author fanxb
|
||||
* @date 2019/7/6 11:30
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String register(RegisterBody body) {
|
||||
User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail());
|
||||
if (user != null) {
|
||||
@ -102,6 +107,7 @@ public class UserServiceImpl implements UserService {
|
||||
user.setLastLoginTime(System.currentTimeMillis());
|
||||
user.setVersion(0);
|
||||
userDao.addOne(user);
|
||||
searchEngineService.newUserInit(user.getUserId());
|
||||
Map<String, String> data = new HashMap<>(1);
|
||||
data.put("userId", String.valueOf(user.getUserId()));
|
||||
return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME);
|
||||
|
@ -37,16 +37,10 @@
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--mybatis依赖-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
|
||||
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
|
||||
<!-- <version>2.0.1</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<version>3.5.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -59,21 +53,66 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.1.18</version>
|
||||
<version>1.2.18</version>
|
||||
</dependency>
|
||||
|
||||
<!--数据库版本管理-->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<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>
|
||||
<!--mysql jdbc依赖-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -89,7 +128,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.73</version>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@ -101,7 +140,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.2.3</version>
|
||||
<version>5.8.25</version>
|
||||
</dependency>
|
||||
|
||||
<!--单元测试-->
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.fanxb.bookmark.common.configuration;
|
||||
|
||||
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.config.ScheduledTaskRegistrar;
|
||||
|
||||
@ -11,15 +14,16 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2020/1/26
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class ScheduleConfig implements SchedulingConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
|
||||
|
||||
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule"));
|
||||
scheduledTaskRegistrar.setScheduler(service);
|
||||
log.info("自定义schedule线程池成功");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,4 +13,8 @@ public class NumberConstant {
|
||||
* 2^10
|
||||
*/
|
||||
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) {
|
||||
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
|
||||
* @date 2019/7/8 11:19
|
||||
*/
|
||||
@Data
|
||||
public class Bookmark {
|
||||
@ -36,6 +35,7 @@ public class Bookmark {
|
||||
private String name;
|
||||
private String url = "";
|
||||
private String icon = "";
|
||||
private String iconUrl;
|
||||
private Integer sort;
|
||||
private String searchKey = "";
|
||||
private Long addTime;
|
||||
@ -53,7 +53,7 @@ public class Bookmark {
|
||||
this.setUserId(userId);
|
||||
this.setPath(path);
|
||||
this.setType(FOLDER_TYPE);
|
||||
this.setName(name);
|
||||
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||
this.setAddTime(addTime);
|
||||
this.setSort(sort);
|
||||
this.setCreateTime(System.currentTimeMillis());
|
||||
@ -64,7 +64,7 @@ public class Bookmark {
|
||||
this.setUserId(userId);
|
||||
this.setPath(path);
|
||||
this.setType(BOOKMARK_TYPE);
|
||||
this.setName(name);
|
||||
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||
this.setUrl(url);
|
||||
this.setIcon(icon);
|
||||
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.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 lombok.Data;
|
||||
|
||||
@ -16,9 +20,11 @@ import java.util.Map;
|
||||
* @date 2019/7/4 20:14
|
||||
*/
|
||||
@Data
|
||||
@TableName("user")
|
||||
public class User {
|
||||
|
||||
private int userId;
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer userId;
|
||||
/**
|
||||
* 第三方github登陆id,-1说明非github登陆
|
||||
*/
|
||||
@ -30,6 +36,7 @@ public class User {
|
||||
/**
|
||||
* 是否未设置密码
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean noPassword;
|
||||
@JSONField(serialize = false)
|
||||
private String password;
|
||||
@ -42,9 +49,10 @@ public class User {
|
||||
* 书签同步版本
|
||||
*/
|
||||
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);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("jwt解密失败:{},原因:{}", jwt, e.getMessage());
|
||||
log.info("jwt解密失败:{},原因:{}", jwt, e.getMessage());
|
||||
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;
|
||||
|
||||
import java.util.Map;
|
||||
import com.fanxb.bookmark.common.entity.vo.GlobalConfigVo;
|
||||
|
||||
/**
|
||||
* 全局配置相关
|
||||
@ -17,5 +17,12 @@ public interface ConfigService {
|
||||
* @author fanxb
|
||||
* @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;
|
||||
|
||||
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.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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.io.File;
|
||||
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
|
||||
* @date 2021-09-15-下午9:59
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final GlobalConfigDao globalConfigDao;
|
||||
|
||||
@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 Map<String, Object> getGlobalConfig() {
|
||||
Map<String, Object> res = new HashMap<>(1);
|
||||
res.put("proxyExist", HttpUtil.getProxyExist());
|
||||
return res;
|
||||
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.servlet.http.HttpServletRequest;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
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)
|
||||
.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");
|
||||
|
||||
@PostConstruct
|
||||
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);
|
||||
if (StrUtil.isNotBlank(proxyIp) && StrUtil.isNotBlank(proxyPort)) {
|
||||
builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp, Integer.parseInt(proxyPort))));
|
||||
proxyExist = true;
|
||||
}
|
||||
PROXY_CLIENT = builder.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.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();
|
||||
PROXY_CLIENT = builder.build();
|
||||
} else {
|
||||
throw new CustomException("下载出现问题:" + res.body().string());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CustomException(e);
|
||||
PROXY_CLIENT = CLIENT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,6 +267,8 @@ public class HttpUtil {
|
||||
}
|
||||
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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.12.RELEASE</version>
|
||||
<version>2.7.17</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
|
@ -91,6 +91,10 @@ proxy:
|
||||
# url icon服务提供地址
|
||||
urlIconAddress: http://localhost:11001
|
||||
|
||||
bing:
|
||||
host: https://cn.bing.com
|
||||
onePic: /HPImageArchive.aspx?format=js&idx=0&n=1
|
||||
|
||||
# 管理员用户id(因为目前尚未设计分角色权限系统,须指定管理员用户id)
|
||||
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
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
public/files
|
||||
public\static\bookmarkBrowserPlugin.zip
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
@ -22,3 +24,4 @@ pnpm-debug.log*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
@ -20,11 +20,11 @@
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-plugin-eslint": "~4.4.0",
|
||||
"@vue/cli-plugin-router": "~4.4.0",
|
||||
"@vue/cli-plugin-vuex": "~4.4.0",
|
||||
"@vue/cli-service": "~4.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.8",
|
||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"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>
|
||||
export default {
|
||||
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>
|
||||
|
||||
<style lang="less">
|
||||
@import "./global.less";
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
@ -20,6 +29,7 @@ body {
|
||||
background-color: @bgColor;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<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">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<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-menu slot="overlay" @click="searchEngineChange">
|
||||
<a-menu-item key="google">谷歌</a-menu-item>
|
||||
<a-menu-item key="bing">bing</a-menu-item>
|
||||
<a-menu-item key="baidu">baidu</a-menu-item>
|
||||
<a-menu-item v-for="item in searchEngineList" :key="item.id">{{ item.name }}</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
<a-icon class="icon" type="search" @click="submit(true)" />
|
||||
@ -30,7 +30,8 @@
|
||||
</div>
|
||||
<div class="icons">
|
||||
<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 title="复制链接">
|
||||
<a-icon
|
||||
@ -51,7 +52,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a ref="targetA" style="left: 1000000px" target="_blank" />
|
||||
<a ref="targetA" style="left: 1000000px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -61,10 +62,11 @@ import { mapState } from "vuex";
|
||||
import ClipboardJS from "clipboard";
|
||||
import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig";
|
||||
import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData";
|
||||
|
||||
export default {
|
||||
name: "Search",
|
||||
props: {
|
||||
showLocation: Boolean, //是否显示定位等按钮
|
||||
showLocation: Boolean //是否显示定位等按钮
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -74,19 +76,25 @@ export default {
|
||||
//上下选中
|
||||
selectIndex: null,
|
||||
copyBoard: null, //剪贴板对象
|
||||
searchEngineList: [],
|
||||
checkedSearchEngine: { icon: "icon-baidu", name: "百度", url: "https://www.baidu.com/s?ie=UTF-8&wd=%s" }
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
async mounted() {
|
||||
//初始化clipboard
|
||||
this.copyBoard = new ClipboardJS(".search-copy-to-board", {
|
||||
text: function(trigger) {
|
||||
return trigger.attributes.data.nodeValue;
|
||||
},
|
||||
}
|
||||
});
|
||||
this.copyBoard.on("success", (e) => {
|
||||
this.$message.success("复制成功");
|
||||
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() {
|
||||
if (this.copyBoard != null) {
|
||||
@ -95,26 +103,20 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState("treeData", ["totalTreeData", HOME_PIN_BOOKMARK_ID_MAP]),
|
||||
...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=";
|
||||
},
|
||||
...mapState("globalConfig", ["userInfo"])
|
||||
},
|
||||
watch: {
|
||||
value(newVal, oldVal) {
|
||||
this.search(newVal);
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
searchIconClick() {
|
||||
if (this.userInfo == null) {
|
||||
this.searchEngineList = [];
|
||||
this.$message.warning("未登录,请登录后操作");
|
||||
}
|
||||
},
|
||||
search(content) {
|
||||
console.log(content);
|
||||
let val = content.toLocaleLowerCase().trim();
|
||||
@ -123,6 +125,7 @@ export default {
|
||||
} else {
|
||||
this.list = this.dealSearch(val);
|
||||
}
|
||||
this.selectIndex = null;
|
||||
},
|
||||
//下方列表点击
|
||||
itemClick(index) {
|
||||
@ -134,7 +137,7 @@ export default {
|
||||
let url;
|
||||
if (forceSearch || this.selectIndex == null) {
|
||||
//说明使用网页搜索
|
||||
url = this.searchUrl + encodeURIComponent(this.value);
|
||||
url = this.checkedSearchEngine.url.replace("%s", encodeURIComponent(this.value));
|
||||
} else {
|
||||
//说明跳转到书签
|
||||
let bookmark = this.list[this.selectIndex];
|
||||
@ -175,15 +178,10 @@ export default {
|
||||
},
|
||||
//修改默认搜索引擎
|
||||
async searchEngineChange(item) {
|
||||
if (this.userInfo == null) {
|
||||
this.$message.warning("未登录,请登录后操作");
|
||||
return;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
let target = this.searchEngineList.find(one => one.id === item.key);
|
||||
await HttpUtil.post("/searchEngine/setChecked", null, { id: item.key });
|
||||
this.checkedSearchEngine = target;
|
||||
},
|
||||
//固定书签到首页
|
||||
async pinBookmark(event, { bookmarkId }) {
|
||||
@ -227,8 +225,8 @@ export default {
|
||||
}
|
||||
console.log("阻止成功");
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -239,10 +237,12 @@ export default {
|
||||
@listActiveBgColor: #454545;
|
||||
.search {
|
||||
position: relative;
|
||||
|
||||
.listShow {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.newSearch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -251,6 +251,7 @@ export default {
|
||||
overflow: hidden;
|
||||
font-size: 1.2em;
|
||||
color: @textColor;
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
border: 0;
|
||||
@ -259,11 +260,13 @@ export default {
|
||||
padding-left: 0.19rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 0.1rem;
|
||||
padding-right: 0.19rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
color: @textColor;
|
||||
cursor: pointer;
|
||||
@ -281,6 +284,7 @@ export default {
|
||||
border-bottom-left-radius: 0.18rem;
|
||||
border-bottom-right-radius: 0.18rem;
|
||||
overflow: hidden;
|
||||
|
||||
.listItem {
|
||||
font-size: 0.16rem;
|
||||
display: flex;
|
||||
@ -291,6 +295,7 @@ export default {
|
||||
margin: 0.05rem 0 0.05rem 0;
|
||||
padding: 0 0.19rem 0 0.19rem;
|
||||
cursor: pointer;
|
||||
|
||||
.name {
|
||||
padding-right: 1em;
|
||||
max-width: calc(100% - 2em);
|
||||
@ -298,17 +303,21 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icons {
|
||||
display: none;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.listItem:hover {
|
||||
background-color: @listActiveBgColor;
|
||||
}
|
||||
|
||||
.itemActive {
|
||||
background-color: @listActiveBgColor;
|
||||
}
|
||||
|
||||
.listItem:hover .icons {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
</a-form-model-item>
|
||||
<template v-if="form.type !== 'file'">
|
||||
<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 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>
|
||||
<div class="btns">
|
||||
<a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button>
|
||||
@ -21,6 +21,7 @@
|
||||
:data="{ path: form.path }"
|
||||
:headers="{ 'jwt-token': token }"
|
||||
action="/bookmark/api/bookmark/uploadBookmarkFile"
|
||||
accept=".html,.db3"
|
||||
@change="fileChange"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
@ -62,7 +63,7 @@ export default {
|
||||
file: null,
|
||||
},
|
||||
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" }],
|
||||
},
|
||||
};
|
||||
@ -78,6 +79,12 @@ export default {
|
||||
}
|
||||
this.token = this.$store.state.globalConfig.token;
|
||||
this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.inputName) {
|
||||
this.$refs.inputName.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
|
@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@ -8,6 +12,7 @@
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "homeTop",
|
||||
props: ["bgSrc"],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
@ -18,6 +23,11 @@ export default {
|
||||
.bottom {
|
||||
height: 0.4rem;
|
||||
padding: 0.1rem;
|
||||
text-align: right;
|
||||
text-align: center;
|
||||
color: black;
|
||||
|
||||
.text {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -6,15 +6,18 @@
|
||||
/
|
||||
<router-link to="/public/register">注册</router-link>
|
||||
</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>
|
||||
<div class="user">
|
||||
<img :src="userInfo.icon" class="userIcon" />
|
||||
</div>
|
||||
<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">
|
||||
<router-link to="/manage/personSpace/userInfo">个人中心</router-link>
|
||||
</a-menu-item>
|
||||
@ -60,6 +63,19 @@ export default {
|
||||
|
||||
.userIcon {
|
||||
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>
|
||||
|
@ -19,14 +19,16 @@ import {
|
||||
Popconfirm,
|
||||
AutoComplete,
|
||||
Select,
|
||||
Popover
|
||||
Popover,
|
||||
Breadcrumb,
|
||||
Table
|
||||
} from "ant-design-vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
|
||||
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(FormModel);
|
||||
@ -46,6 +48,8 @@ Vue.use(Popconfirm);
|
||||
Vue.use(AutoComplete);
|
||||
Vue.use(Select);
|
||||
Vue.use(Popover);
|
||||
Vue.use(Breadcrumb);
|
||||
Vue.use(Table);
|
||||
Vue.component("my-icon", IconFont);
|
||||
|
||||
Vue.prototype.$message = message;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
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";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{ path: "/", component: () => import("@/views/home/index") },
|
||||
{ path: "/noHead/addBookmark", component: () => import("@/views/noHead/addBookmark/index") },
|
||||
{
|
||||
path: "/manage",
|
||||
component: () => import("@/views/manage/index"),
|
||||
@ -15,6 +16,7 @@ const routes = [
|
||||
{ path: "", redirect: "/manage/bookmarkTree" },
|
||||
{ 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") }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -26,7 +28,7 @@ const routes = [
|
||||
{ path: "resetPassword", component: () => import("@/views/public/passwordReset/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: "404", component: () => import("@/views/public/notFound/index") }
|
||||
]
|
||||
},
|
||||
{ path: "*", redirect: "/public/404" }
|
||||
@ -41,25 +43,27 @@ const router = new VueRouter({
|
||||
* 在此进行登录信息判断,以及重定向到登录页面
|
||||
*/
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
//进入主页面/管理页面时,确认已经进行初始化操作
|
||||
if (to.path === '/' || to.path.startsWith("/manage")) {
|
||||
if (to.query.token && checkJwtValid(to.query.token)) {
|
||||
console.log("获取到页面token", to.query.token);
|
||||
await vuex.default.dispatch(GLOBAL_CONFIG + "/" + setToken, to.query.token);
|
||||
}
|
||||
//进入除/public以外的路由,确认已经进行初始化操作
|
||||
if (!to.path.startsWith("/public")) {
|
||||
await vuex.loginInit();
|
||||
}
|
||||
let supportNoLogin = to.path === '/' || to.path.startsWith("/public");
|
||||
let supportNoLogin = to.path === "/" || to.path.startsWith("/public");
|
||||
vuex.default.commit(GLOBAL_CONFIG + "/" + SUPPORT_NO_LOGIN, supportNoLogin);
|
||||
if (!supportNoLogin && !checkJwtValid(vuex.default.state[GLOBAL_CONFIG][TOKEN])) {
|
||||
//如不支持未登录进入,切jwt已过期,直接跳转到登录页面,并清理缓存
|
||||
await vuex.default.dispatch("treeData/clear");
|
||||
await vuex.default.dispatch("globalConfig/clear");
|
||||
next({
|
||||
path: "/public/login?to=" + btoa(location.href),
|
||||
path: "/public/login?to=" + btoa(to.fullPath),
|
||||
replace: true
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -22,7 +22,8 @@ let noLoginFinish = false;
|
||||
//执行各自的非登陆初始化
|
||||
(async () => {
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
|
||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
||||
//无需等待执行
|
||||
store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
||||
noLoginFinish = true;
|
||||
})();
|
||||
|
||||
@ -35,9 +36,11 @@ export async function loginInit () {
|
||||
}
|
||||
console.log(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("初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +62,7 @@ async function finishNoLogin () {
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export default store;
|
||||
|
@ -11,7 +11,14 @@ export const IS_PHONE = "isPhone";
|
||||
|
||||
export const noLoginInit = "noLoginInit";
|
||||
export const loginInit = "loginInit";
|
||||
/**
|
||||
* 登出清除数据
|
||||
*/
|
||||
export const clear = "clear";
|
||||
/**
|
||||
* 设置token
|
||||
*/
|
||||
export const setToken = "setToken";
|
||||
/**
|
||||
* 存储全局配置
|
||||
*/
|
||||
@ -62,19 +69,19 @@ const actions = {
|
||||
let userInfo = await HttpUtil.get("/user/currentUserInfo");
|
||||
context.commit(USER_INFO, userInfo);
|
||||
context.commit(IS_INIT, true);
|
||||
console.log("用户完了");
|
||||
},
|
||||
async setToken ({ commit }, token) {
|
||||
async [setToken]({ commit }, token) {
|
||||
await localforage.setItem(TOKEN, token);
|
||||
window.jwtToken = token;
|
||||
commit(TOKEN, token);
|
||||
},
|
||||
//登出清除数据
|
||||
async [clear](context) {
|
||||
await localforage.removeItem(TOKEN);
|
||||
context.commit(USER_INFO, null);
|
||||
context.commit(TOKEN, null);
|
||||
context.commit(IS_INIT, false);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
@ -95,7 +102,6 @@ const mutations = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const store = {
|
||||
namespaced: true,
|
||||
state,
|
||||
|
@ -2,6 +2,9 @@ import localforage from "localforage";
|
||||
import { checkJwtValid } from "@/util/UserUtil";
|
||||
import HttpUtil from "../../util/HttpUtil";
|
||||
|
||||
/**书签版本检查间隔 */
|
||||
const CHECK_INTERVAL = 5 * 60 * 1000;
|
||||
// const CHECK_INTERVAL = 5 * 1000;
|
||||
export const TREE_DATA = "treeData";
|
||||
export const TOTAL_TREE_DATA = "totalTreeData";
|
||||
export const VERSION = "version";
|
||||
@ -19,7 +22,13 @@ export const refreshHomePinList = "refreshHomePinList";
|
||||
* 通过id获取书签数据
|
||||
*/
|
||||
export const getById = "getById";
|
||||
/**
|
||||
* 登录前初始化
|
||||
*/
|
||||
export const noLoginInit = "noLoginInit";
|
||||
/**
|
||||
* 登陆后初始化
|
||||
*/
|
||||
export const loginInit = "loginInit";
|
||||
export const refresh = "refresh";
|
||||
export const clear = "clear";
|
||||
@ -27,11 +36,19 @@ export const clear = "clear";
|
||||
* 删除书签数据
|
||||
*/
|
||||
export const deleteData = "deleteData";
|
||||
/**
|
||||
* 新增节点
|
||||
*/
|
||||
export const addNode = "addNode";
|
||||
|
||||
/**
|
||||
* 版本检查定时调度
|
||||
*/
|
||||
let timer = null;
|
||||
/**
|
||||
* 检查本地版本是否有更新
|
||||
*/
|
||||
let checkLocalDataTimer = null;
|
||||
/**
|
||||
* 刷新书签确认弹窗是否展示
|
||||
*/
|
||||
@ -69,9 +86,7 @@ const getters = {
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async [noLoginInit] () {
|
||||
|
||||
},
|
||||
async [noLoginInit] () { },
|
||||
async [loginInit] (context) {
|
||||
if (context.state.isInit || context.state.isIniting) {
|
||||
return;
|
||||
@ -83,8 +98,8 @@ const actions = {
|
||||
await treeDataCheck(context, true);
|
||||
context.commit(IS_INIT, true);
|
||||
context.commit(IS_INITING, false);
|
||||
timer = setInterval(() => treeDataCheck(context, false), 5 * 60 * 1000);
|
||||
// timer = setInterval(() => treeDataCheck(context, false), 5 * 1000);
|
||||
timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL);
|
||||
checkLocalDataTimer = setInterval(() => checkLocalData(context), 2000);
|
||||
},
|
||||
/**
|
||||
* 确保数据加载完毕
|
||||
@ -118,6 +133,7 @@ const actions = {
|
||||
);
|
||||
let version = await HttpUtil.get("/user/version");
|
||||
await context.dispatch("updateVersion", version);
|
||||
await context.dispatch(refreshHomePinList);
|
||||
context.commit(TOTAL_TREE_DATA, treeData);
|
||||
await localforage.setItem(TOTAL_TREE_DATA, treeData);
|
||||
},
|
||||
@ -132,6 +148,9 @@ const actions = {
|
||||
if (timer != null) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
if (checkLocalDataTimer != null) {
|
||||
clearInterval(checkLocalDataTimer);
|
||||
}
|
||||
await localforage.removeItem(TOTAL_TREE_DATA);
|
||||
await localforage.removeItem(VERSION);
|
||||
},
|
||||
@ -210,9 +229,8 @@ const actions = {
|
||||
let list = await HttpUtil.get("/home/pin");
|
||||
commit(HOME_PIN_LIST, list);
|
||||
let map = {};
|
||||
list.filter(item => item.id).forEach(item => map[item.bookmarkId] = true);
|
||||
list.filter(item => item.id).forEach(item => (map[item.bookmarkId] = true));
|
||||
commit(HOME_PIN_BOOKMARK_ID_MAP, map);
|
||||
|
||||
},
|
||||
/**
|
||||
* 更新版本数据
|
||||
@ -224,19 +242,23 @@ const actions = {
|
||||
/**
|
||||
* 新增书签、文件夹
|
||||
*/
|
||||
async addNode (context, { sourceNode, targetNode }) {
|
||||
async [addNode] (context, { sourceNode, targetNode }) {
|
||||
if (sourceNode === null) {
|
||||
if (context.state[TOTAL_TREE_DATA][""] === undefined) {
|
||||
context.state[TOTAL_TREE_DATA][""] = [];
|
||||
}
|
||||
context.state[TOTAL_TREE_DATA][""].push(targetNode);
|
||||
} else {
|
||||
let path = sourceNode.path + "." + sourceNode.bookmarkId;
|
||||
if (!context.state[TOTAL_TREE_DATA][path]) {
|
||||
context.state[TOTAL_TREE_DATA][path] = [];
|
||||
}
|
||||
if (sourceNode.children === undefined) {
|
||||
sourceNode.children = [];
|
||||
sourceNode.children = context.state[TOTAL_TREE_DATA][path];
|
||||
}
|
||||
sourceNode.children.push(targetNode);
|
||||
}
|
||||
if (targetNode.type === 0) {
|
||||
if (targetNode.type === 1) {
|
||||
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
|
||||
}
|
||||
targetNode.isLeaf = targetNode.type === 0;
|
||||
@ -311,7 +333,6 @@ const mutations = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 检查书签缓存是否最新
|
||||
*
|
||||
@ -325,7 +346,7 @@ async function treeDataCheck (context, isFirst) {
|
||||
}
|
||||
let realVersion = await HttpUtil.get("/user/version");
|
||||
if (realVersion !== context.state[VERSION]) {
|
||||
if (SHOW_REFRESH_TOAST && !isFirst) {
|
||||
if (context.state[SHOW_REFRESH_TOAST] && !isFirst) {
|
||||
//如果在书签管理页面需要弹窗提示
|
||||
window.vueInstance.$confirm({
|
||||
title: "书签数据有更新,是否立即刷新?",
|
||||
@ -335,8 +356,8 @@ async function treeDataCheck (context, isFirst) {
|
||||
maskClosable: false,
|
||||
onOk () {
|
||||
toastShow = false;
|
||||
return new Promise(async (resolve) => {
|
||||
await context.dispatch("refresh");
|
||||
return new Promise(async resolve => {
|
||||
await context.dispatch(refresh);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
@ -351,6 +372,24 @@ async function treeDataCheck (context, isFirst) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查本地缓存数据是否有更新
|
||||
* @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 = {
|
||||
namespaced: true,
|
||||
state,
|
||||
@ -358,4 +397,3 @@ export const store = {
|
||||
actions,
|
||||
mutations
|
||||
};
|
||||
|
||||
|
@ -110,5 +110,6 @@ export default {
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: deletes
|
||||
delete: deletes,
|
||||
http
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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" />
|
||||
<span class="text" :title="pinObj.name">{{ pinObj.name }}</span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
|
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="main" :style="{ backgroundImage: backgroundImg }">
|
||||
<top />
|
||||
<div class="content">
|
||||
<search :style="{ width: isPhone ? '100%' : '60%' }" />
|
||||
<div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div>
|
||||
</div>
|
||||
<bottom />
|
||||
<bottom :bgSrc="serverConfig.bingImgSrc" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -15,7 +15,7 @@ import Bottom from "@/layout/home/Bottom.vue";
|
||||
import Search from "@/components/main/Search.vue";
|
||||
import PinBookmark from "./PinBookmark.vue";
|
||||
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 {
|
||||
name: "HOME",
|
||||
components: {
|
||||
@ -28,7 +28,11 @@ export default {
|
||||
return {};
|
||||
},
|
||||
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: {},
|
||||
};
|
||||
@ -38,9 +42,8 @@ export default {
|
||||
.main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url("/static/img/homeBg.jpg") no-repeat center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
|
||||
.content {
|
||||
height: calc(~"100vh" - 1.21rem);
|
||||
|
@ -65,7 +65,7 @@
|
||||
@drop="onDrop"
|
||||
>
|
||||
<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" />
|
||||
<img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" />
|
||||
<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>
|
||||
<div class="userInfo">
|
||||
<div v-if="userInfo" class="userInfo">
|
||||
<div class="icon">
|
||||
<img :src="userInfo.icon" class="full" />
|
||||
<label class="full">
|
||||
@ -12,7 +12,8 @@
|
||||
<div class="baseInfo">
|
||||
<div class="item">
|
||||
<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>
|
||||
<div class="inputGroup" v-else-if="currentAction === 'name'">
|
||||
<a-input type="text" v-model="name" placeholder="修改昵称" />
|
||||
@ -26,7 +27,9 @@
|
||||
<div class="item">
|
||||
<span style="width: 5em">密码</span>
|
||||
<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>
|
||||
<div class="inputGroup" v-else-if="currentAction === 'password'">
|
||||
<a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" />
|
||||
@ -58,26 +61,24 @@
|
||||
<a-tooltip title="搜索框默认搜索引擎">
|
||||
<span style="width: 5em">搜索</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="点击修改" v-if="currentAction != 'defaultSearchEngine'">
|
||||
<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 @click="showSearchEngineManage=true" style=" cursor: pointer;color:blue">管理搜索引擎</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal v-model="showSearchEngineManage" title="管理搜索引擎" :footer="null" width="70%">
|
||||
<manage-search-engine />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import manageSearchEngine from "./components/manageSearchEngine.vue";
|
||||
import { mapState } from "vuex";
|
||||
import HttpUtil from "@/util/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "UserInfo",
|
||||
components: { manageSearchEngine },
|
||||
data() {
|
||||
return {
|
||||
currentAction: null, //当前操作,name,password,email
|
||||
@ -86,21 +87,11 @@ export default {
|
||||
password: "",
|
||||
rePassword: "",
|
||||
email: "",
|
||||
showSearchEngineManage: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("globalConfig", ["userInfo"]),
|
||||
defaultSearchEngine() {
|
||||
switch (this.userInfo.defaultSearchEngine) {
|
||||
case "baidu":
|
||||
return "百度";
|
||||
case "google":
|
||||
return "谷歌";
|
||||
case "bing":
|
||||
return "Bing";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
...mapState("globalConfig", ["userInfo"])
|
||||
},
|
||||
methods: {
|
||||
async changeIcon(e) {
|
||||
@ -127,21 +118,18 @@ export default {
|
||||
url = "/baseInfo/password";
|
||||
body = {
|
||||
oldPassword: this.oldPassword,
|
||||
password: this.password,
|
||||
password: this.password
|
||||
};
|
||||
} else if (this.currentAction === "email") {
|
||||
url = "/baseInfo/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 this.$store.dispatch("globalConfig/refreshUserInfo");
|
||||
this.$message.success("操作成功");
|
||||
this.currentAction = null;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -195,6 +183,7 @@ export default {
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
|
||||
display: flex;
|
||||
|
||||
.inputGroup {
|
||||
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>
|
||||
</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>
|
||||
</div> -->
|
||||
<a href="https://blog.fleyx.com/blog/detail/20220329" target="_blank">点击跳转</a>
|
||||
</div>
|
||||
<div>
|
||||
浏览器插件:
|
||||
<a href="/static/bookmarkBrowserPlugin.zip" download="浏览器插件.zip"
|
||||
target="_blank">最新版本{{ serverConfig.map.pluginVersion }}(注意更新)</a>
|
||||
,使用详情请参考使用教程
|
||||
</div>
|
||||
<div>交流反馈qq群:150056494,邮箱:fleyx20@outlook.com</div>
|
||||
<div>
|
||||
统计:
|
||||
<a href="https://qiezi.fleyx.com" style="" target="_blank">
|
||||
<div id="qieziStatisticHtmlHostPv" style="display: none">
|
||||
总访问次数:
|
||||
@ -29,17 +39,51 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { GLOBAL_CONFIG, SERVER_CONFIG } from "@/store/modules/globalConfig";
|
||||
import httpUtil from "@/util/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "about",
|
||||
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);
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
appVersion: "1.4", //应用版本
|
||||
latestVersion: null,
|
||||
showNewVersion: false
|
||||
};
|
||||
},
|
||||
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>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="thirdPart">
|
||||
<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-tooltip>
|
||||
</div>
|
||||
@ -39,39 +39,44 @@ import Header from "@/components/public/Switch.vue";
|
||||
import httpUtil from "../../../util/HttpUtil.js";
|
||||
import { mapMutations, mapState } from "vuex";
|
||||
import HttpUtil from "../../../util/HttpUtil.js";
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
components: {
|
||||
Header,
|
||||
Header
|
||||
},
|
||||
computed: {
|
||||
...mapState("globalConfig", ["serverConfig"]),
|
||||
...mapState("globalConfig", ["serverConfig"])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
str: "",
|
||||
password: "",
|
||||
rememberMe: false,
|
||||
rememberMe: false
|
||||
},
|
||||
rules: {
|
||||
str: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" },
|
||||
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" }
|
||||
],
|
||||
password: [
|
||||
{ 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, //是否加载中
|
||||
oauthLogining: false, //true:正在进行oauth后台操作
|
||||
page: null, //oauth打开的页面实例
|
||||
redirect: null
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
let _this = this;
|
||||
window.addEventListener("storage", this.storageDeal.bind(this));
|
||||
if (this.$route.query.to) {
|
||||
this.redirect = atob(this.$route.query.to);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("storage", this.storageDeal);
|
||||
@ -85,7 +90,7 @@ export default {
|
||||
this.loading = true;
|
||||
let token = await httpUtil.post("/user/login", null, this.form);
|
||||
await this.$store.dispatch("globalConfig/setToken", token);
|
||||
this.$router.replace("/");
|
||||
this.$router.replace(this.redirect ? this.redirect : "/");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@ -126,8 +131,8 @@ export default {
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -135,11 +140,13 @@ export default {
|
||||
.form {
|
||||
margin: 0.3rem;
|
||||
margin-bottom: 0.1rem;
|
||||
|
||||
.reset {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.thirdPart {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -81,12 +81,12 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
async submit() {
|
||||
let _this = this;
|
||||
this.$refs.registerForm.validate(async (status) => {
|
||||
if (status) {
|
||||
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("/");
|
||||
}
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ module.exports = {
|
||||
devtool: "source-map"
|
||||
},
|
||||
devServer: {
|
||||
port: 4531,
|
||||
proxy: {
|
||||
"/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
|
||||
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
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./data/timezone:/etc/timezone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
|
||||
- MYSQL_DATABASE=bookmark
|
||||
@ -25,6 +26,7 @@ services:
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./data/timezone:/etc/timezone
|
||||
- ./data/redis:/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- bookmark
|
||||
|
||||
@ -38,6 +40,7 @@ services:
|
||||
- ./bookmark_front/dist:/opt/dist
|
||||
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
@ -60,6 +63,7 @@ services:
|
||||
- ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar
|
||||
- ${BOOKMARK_FILE_SAVE_PATH}:/opt/files
|
||||
working_dir: /opt/app
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- /bin/bash
|
||||
- -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