Compare commits

..

No commits in common. "master" and "1.2" have entirely different histories.
master ... 1.2

103 changed files with 1414 additions and 11157 deletions

View File

@ -1,24 +0,0 @@
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

View File

@ -1,20 +1,10 @@
本程序基于 docker 来进行部署,使用 docker-compose 管理服务。 本程序基于 docker 来进行部署,使用 docker-compose 管理服务。
**注意,仅在 x86 环境下测试,arm 下不保证可用性(目前测试可用)** 部署过程如下:
## 首次部署 **注意,仅在 x86 环境下测试**
0. 克隆代码`git clone https://github.com/FleyX/bookmark.git` 1. 安装新版的 docker 和 docker-compose(注意:以下操作均在项目根目录下执行)
1. 进入文件夹`cd bookmark` 2. 执行`build.sh`编译前后端代码
2. 安装新版的 docker,docker-compose,zip `apt install docker docker-compose zip`
3. 修改.env 文件中的参数,改为你的实际配置 3. 修改.env 文件中的参数,改为你的实际配置
4. 修改`浏览器插件/bookmarkBrowserPlugin/static/js/config.js`中的 bookmarkHost改为你的实际部署路径 4. root 权限运行 `docker-compose up -d` 启动服务。
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` 启动服务

100
README.md
View File

@ -1,56 +1,44 @@
![图片](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) 也可自己搭建,教程如下:
**为获得更好的体验,建议将主页设置为 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)
也可自己部署搭建,教程见:[docker-compose 部署](https://github.com/FleyX/bookmark/blob/master/DEPLOY.md) # 缘由
# 缘由 1. 主要用的是 chrome但是有时候需要用其他的浏览器Firefoxie 等。然后这些浏览器上没有书签,想进个网站还得打开 chrome 复制 url太麻烦。
1. 主要用的是 chrome但是有时候需要用其他的浏览器Firefoxie 等。然后这些浏览器上没有书签,想进个网站还得打开 chrome 复制 url太麻烦。 2. chrome 必须翻墙才能同步书签,体验不是那么好。
2. chrome 必须翻墙才能同步书签,体验不是那么好。 3. 如果书签全放在 chrome 上,相当于绑定死 chrome 浏览器了,很难迁移到别的优秀浏览器,比如 firfox 上。
3. 如果书签全放在 chrome 上,相当于绑定死 chrome 浏览器了,很难迁移到别的优秀浏览器,比如 firfox 上。 所以有了这样这样一个项目,建立一个和平台无关的书签管理器,可在任意平台使用。
所以有了这样这样一个项目,建立一个和平台无关的书签管理器,可在任意平台使用。 # 主要功能
# 主要功能 使用帮助见:[使用帮助](https://github.com/FleyX/bookmark/blob/master/HELP.md)
帮助文档:[点击跳转](https://blog.fleyx.com/blog/detail/20220329/) 1. 基础的书签增删改查功能。支持 chrome、firefox 等浏览器书签文件导入,导出。
- 支持从 chrome,edge,firefox 等浏览器导入书签数据。 ![](https://qiniupic.fleyx.com/blog/20220329214126.png?imageView2/2/w/1920)
- 支持从 OneEnv 导入书签数据
- 树型多级目录支持 2. 强大的书签检索功能,毫秒级的关键字检索。
- 支持导出标准 html 书签文件
- 强大的检索功能,支持拼音检索 ![](https://qiniupic.fleyx.com/blog/20220329214210.png?imageView2/2/w/1920)
- 支持浏览器插件,安装插件以后可右键添加书签
3. 首页功能,参考 bing 首页实现
# 更新日志
![](https://qiniupic.fleyx.com/blog/20220329214236.png?imageView2/2/w/1920)
## 1.4.1
4. 移动端支持,手机端也可使用(部分功能比如拖拽等无法使用)
- 修复书签名过长无法导入问题
![](https://qiniupic.fleyx.com/blog/20220329214312.png?imageView2/2/w/1920)
## 1.4
# TODO
- 优化首图加载逻辑
- 支持 OneEnv 备份文件导入 - 主页功能 Ok!
- 拼音检索 Ok!
## 1.3 - 书签导出 OK
- 侧边栏显示
![pic](https://s3.fleyx.com/picbed/2023/08/Snipaste_2023-08-13_15-01-20.png)
搜索引擎支持自定义[#43](https://github.com/FleyX/bookmark/issues/43)
位置:右上角个人中心-管理搜索引擎
# TODO
- [x] 主页功能
- [x] 拼音检索
- [x] 书签导出
- [x] 浏览器插件

View File

@ -22,18 +22,13 @@
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.15.3</version> <version>1.14.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.houbb</groupId> <groupId>com.github.houbb</groupId>
<artifactId>pinyin</artifactId> <artifactId>pinyin</artifactId>
<version>0.3.1</version> <version>0.3.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.44.1.0</version>
</dependency>
</dependencies> </dependencies>

View File

@ -69,7 +69,7 @@ public class BookmarkController {
*/ */
@RequestMapping("/uploadBookmarkFile") @RequestMapping("/uploadBookmarkFile")
public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("path") String path) throws Exception { public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("path") String path) throws Exception {
bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file, path); bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file.getInputStream(), path);
return Result.success(null); return Result.success(null);
} }

View File

@ -1,6 +1,9 @@
package com.fanxb.bookmark.business.bookmark.dao; package com.fanxb.bookmark.business.bookmark.dao;
import org.apache.ibatis.annotations.*; import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/** /**
* @author fanxb * @author fanxb
@ -25,15 +28,6 @@ public interface HostIconDao {
* @return {@link String} * @return {@link String}
* @author fanxb * @author fanxb
*/ */
@Select("select iconPath from host_icon where host=#{host} limit 1") @Select("select iconPath from host_icon where host=#{host}")
String selectByHost(String host); String selectByHost(String host);
/**
* 删除一条
*
* @param host host
* @author FleyX
*/
@Delete("delete from host_icon where host=#{host}")
void deleteByHost(String host);
} }

View File

@ -3,7 +3,6 @@ package com.fanxb.bookmark.business.bookmark.service;
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs; import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody; import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
import com.fanxb.bookmark.common.entity.po.Bookmark; import com.fanxb.bookmark.common.entity.po.Bookmark;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
@ -55,7 +54,7 @@ public interface BookmarkService {
* @author fanxb * @author fanxb
* @date 2019/7/9 18:44 * @date 2019/7/9 18:44
*/ */
void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception; void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception;
/** /**
* Description: 详情 * Description: 详情

View File

@ -1,9 +1,9 @@
package com.fanxb.bookmark.business.bookmark.service.impl; package com.fanxb.bookmark.business.bookmark.service.impl;
import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.*; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.HashUtil; import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.fanxb.bookmark.business.api.UserApi; import com.fanxb.bookmark.business.api.UserApi;
import com.fanxb.bookmark.business.bookmark.constant.FileConstant; import com.fanxb.bookmark.business.bookmark.constant.FileConstant;
@ -21,7 +21,6 @@ import com.fanxb.bookmark.common.constant.RedisConstant;
import com.fanxb.bookmark.common.entity.po.Bookmark; import com.fanxb.bookmark.common.entity.po.Bookmark;
import com.fanxb.bookmark.common.exception.CustomException; import com.fanxb.bookmark.common.exception.CustomException;
import com.fanxb.bookmark.common.util.*; import com.fanxb.bookmark.common.util.*;
import com.mysql.cj.conf.url.SingleConnectionUrl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -36,24 +35,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.awt.print.Book;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -62,6 +53,7 @@ import java.util.stream.Collectors;
* 类功能详述 * 类功能详述
* *
* @author fanxb * @author fanxb
* @date 2019/7/8 15:00
*/ */
@Service @Service
@Slf4j @Slf4j
@ -86,25 +78,18 @@ public class BookmarkServiceImpl implements BookmarkService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception { public void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception {
List<Bookmark> bookmarks = new ArrayList<>(); Document doc = Jsoup.parse(stream, "utf-8", "");
Elements elements = doc.select("html>body>dl>dt");
//获取当前层sort最大值 //获取当前层sort最大值
Integer sortBase = bookmarkDao.selectMaxSort(userId, path); Integer sortBase = bookmarkDao.selectMaxSort(userId, path);
if (sortBase == null) { if (sortBase == null) {
sortBase = 0; sortBase = 0;
} }
if (file.getOriginalFilename().endsWith(".db3")) { List<Bookmark> bookmarks = new ArrayList<>();
//处理db文件 for (int i = 0, length = elements.size(); i < length; i++) {
readFromOneEnv(bookmarks, userId, file, path, sortBase); dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
} else {
InputStream stream = file.getInputStream();
Document doc = Jsoup.parse(stream, "utf-8", "");
Elements elements = doc.select("html>body>dl>dt");
for (int i = 0, length = elements.size(); i < length; i++) {
dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
}
} }
//每一千条处理插入一次,批量更新搜索字段 //每一千条处理插入一次,批量更新搜索字段
List<Bookmark> tempList = new ArrayList<>(1000); List<Bookmark> tempList = new ArrayList<>(1000);
for (int i = 0; i < bookmarks.size(); i++) { for (int i = 0; i < bookmarks.size(); i++) {
@ -131,6 +116,7 @@ public class BookmarkServiceImpl implements BookmarkService {
* @param path 节点路径不包含自身 * @param path 节点路径不包含自身
* @param sort 当前层级中的排序序号 * @param sort 当前层级中的排序序号
* @author fanxb * @author fanxb
* @date 2019/7/8 14:49
*/ */
private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) { private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) {
if (!DT.equalsIgnoreCase(ele.tagName())) { if (!DT.equalsIgnoreCase(ele.tagName())) {
@ -139,7 +125,7 @@ public class BookmarkServiceImpl implements BookmarkService {
Element first = ele.child(0); Element first = ele.child(0);
if (A.equalsIgnoreCase(first.tagName())) { if (A.equalsIgnoreCase(first.tagName())) {
//说明为链接 //说明为链接
Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), "" Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), first.attr("icon")
, Long.parseLong(first.attr("add_date")) * 1000, sort); , Long.parseLong(first.attr("add_date")) * 1000, sort);
//存入数据库 //存入数据库
insertOne(node); insertOne(node);
@ -163,63 +149,13 @@ public class BookmarkServiceImpl implements BookmarkService {
} }
} }
/**
* 处理oneenv的导出
*
* @param bookmarks 书签列表
* @param userId 用户id
* @param file file
* @param path path
* @param sort sort
*/
private void readFromOneEnv(List<Bookmark> bookmarks, int userId, MultipartFile file, String path, int sort) {
String filePath = CommonConstant.fileSavePath + "/files/" + IdUtil.simpleUUID() + ".db3";
try {
file.transferTo(FileUtil.newFile(filePath));
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + filePath)) {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select * from on_categorys");
Map<Long, Bookmark> folderMap = new HashMap<>();
Map<Long, Integer> childSortBaseMap = new HashMap<>();
while (rs.next()) {
long addTime = rs.getLong("add_time");
Bookmark folder = new Bookmark(userId, path, StrUtil.nullToEmpty(rs.getString("name")), addTime == 0 ? System.currentTimeMillis() : addTime * 1000, sort++);
int childSortBase = 0;
if (insertOne(folder)) {
childSortBase = ObjectUtil.defaultIfNull(bookmarkDao.selectMaxSort(userId, path), 0);
}
long id = rs.getLong("id");
folderMap.put(id, folder);
childSortBaseMap.put(id, childSortBase);
}
rs.close();
rs = stat.executeQuery("select * from on_links");
while (rs.next()) {
long fId = rs.getLong("fid");
long addTime = rs.getLong("add_time");
int tempSort = childSortBaseMap.get(fId);
childSortBaseMap.put(fId, tempSort + 1);
Bookmark folder = folderMap.get(fId);
String curPath = folder == null ? "" : folder.getPath() + "." + folder.getBookmarkId();
Bookmark bookmark = new Bookmark(userId, curPath, StrUtil.nullToEmpty(rs.getString("title"))
, StrUtil.nullToEmpty(rs.getString("url")), "", addTime == 0 ? System.currentTimeMillis() : addTime * 1000, tempSort);
bookmarks.add(bookmark);
insertOne(bookmark);
}
rs.close();
stat.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** /**
* Description: 插入一条书签如果已经存在同名书签将跳过 * Description: 插入一条书签如果已经存在同名书签将跳过
* *
* @param node node * @param node node
* @return boolean 如果已经存在返回true否则false * @return boolean 如果已经存在返回true否则false
* @author fanxb * @author fanxb
* @date 2019/7/8 17:25
*/ */
private boolean insertOne(Bookmark node) { private boolean insertOne(Bookmark node) {
//先根据name,userId,parentId获取此节点id //先根据name,userId,parentId获取此节点id
@ -281,14 +217,11 @@ public class BookmarkServiceImpl implements BookmarkService {
bookmark.setUserId(userId); bookmark.setUserId(userId);
bookmark.setCreateTime(System.currentTimeMillis()); bookmark.setCreateTime(System.currentTimeMillis());
bookmark.setAddTime(bookmark.getCreateTime()); bookmark.setAddTime(bookmark.getCreateTime());
bookmark.setIcon(bookmark.getType() == 1 ? "" : getIconPath(bookmark.getUrl(), bookmark.getIcon(), bookmark.getIconUrl(), true)); bookmark.setIcon(getIconPath(bookmark.getUrl()));
//文件夹和书签都建立搜索key //文件夹和书签都建立搜索key
pinYinService.changeBookmark(bookmark); pinYinService.changeBookmark(bookmark);
bookmarkDao.insertOne(bookmark); bookmarkDao.insertOne(bookmark);
userApi.versionPlus(userId); userApi.versionPlus(userId);
if (StrUtil.isEmpty(bookmark.getIcon()) && bookmark.getType() == 0) {
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
}
return bookmark; return bookmark;
} }
@ -298,33 +231,13 @@ public class BookmarkServiceImpl implements BookmarkService {
bookmark.setUserId(userId); bookmark.setUserId(userId);
if (bookmark.getType() == 0) { if (bookmark.getType() == 0) {
pinYinService.changeBookmark(bookmark); pinYinService.changeBookmark(bookmark);
bookmark.setIcon(getIconPath(bookmark.getUrl(), null, null, true)); bookmark.setIcon(getIconPath(bookmark.getUrl()));
if (StrUtil.isEmpty(bookmark.getIcon())) {
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
}
} }
bookmarkDao.editBookmark(bookmark); bookmarkDao.editBookmark(bookmark);
userApi.versionPlus(userId); userApi.versionPlus(userId);
return bookmark.getIcon(); return bookmark.getIcon();
} }
/**
* 异步更新书签icon
*
* @param id 书签id
* @param url 书签url
* @param userId userId
*/
private void updateIconAsync(int id, String url, int userId) {
ThreadPoolUtil.execute(() -> {
String icon = getIconPath(url, null, null, false);
if (StrUtil.isEmpty(icon)) {
return;
}
bookmarkDao.updateIcon(id, icon);
});
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -374,10 +287,10 @@ public class BookmarkServiceImpl implements BookmarkService {
int size = 100; int size = 100;
int start = 0; int start = 0;
List<Bookmark> deal; List<Bookmark> deal;
while (!(deal = bookmarkDao.selectUserNoIcon(userId, start, size)).isEmpty()) { while ((deal = bookmarkDao.selectUserNoIcon(userId, start, size)).size() > 0) {
start += size; start += size;
deal.forEach(item -> { deal.forEach(item -> {
String icon = getIconPath(item.getUrl(), null, null, false); String icon = getIconPath(item.getUrl());
if (StrUtil.isNotEmpty(icon)) { if (StrUtil.isNotEmpty(icon)) {
bookmarkDao.updateIcon(item.getBookmarkId(), icon); bookmarkDao.updateIcon(item.getBookmarkId(), icon);
} }
@ -409,95 +322,58 @@ public class BookmarkServiceImpl implements BookmarkService {
} }
/** /**
* 获取icon,通过网络获取或者从base64还原 * 获取icon
* *
* @param url 书签url路径 * @param url url
* @param icon base64编码的icon
* @param iconUrl base64编码的文件文件名,用于获取文件名后缀
* @param quick 是否快速获取
* @return {@link String} * @return {@link String}
* @author fanxb * @author fanxb
*/ */
private String getIconPath(String url, String icon, String iconUrl, boolean quick) { private String getIconPath(String url) {
if (StrUtil.isEmpty(url)) {
return "";
}
String host; String host;
try { try {
URL urlObj = new URL(url); URL urlObj = new URL(url);
host = urlObj.getAuthority(); host = urlObj.getHost();
} catch (Exception e) { } catch (Exception e) {
log.warn("url无法解析出domain:{}", url); log.warn("url无法解析出domain:{}", url);
return ""; 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); String iconPath = hostIconDao.selectByHost(host);
if (iconPath != null) { if (iconPath != null) {
return iconPath; return iconPath;
} }
//再根据url解析 iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..64..256");
iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..128..256", quick);
if (StrUtil.isNotEmpty(iconPath)) { if (StrUtil.isNotEmpty(iconPath)) {
hostIconDao.insert(host, iconPath); hostIconDao.insert(host, iconPath);
} }
return iconPath; return iconPath;
} }
/** private String saveFile(String host, String url) {
* 保存文件到icon路径 try {
* try (Response res = HttpUtil.getClient(false).newCall(new Request.Builder().url(url)
* @param host host .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")
* @param url url .get().build()).execute()) {
* @param quick 是否快速获取,快速获取超时时间1s assert res.body() != null;
* @return {@link String} if (!HttpUtil.checkIsOk(res.code())) {
* @author FleyX throw new CustomException("请求错误:" + res.code());
*/ }
private String saveFile(String host, String url, boolean quick) { byte[] data = res.body().byteStream().readAllBytes();
try (Response res = (quick ? HttpUtil.getSHORT_CLIENT() : HttpUtil.getClient(false)).newCall(new Request.Builder().url(url) if (data.length > 0) {
.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") String iconUrl = res.request().url().toString();
.get().build()).execute()) { String fileName = URLEncoder.encode(host, StandardCharsets.UTF_8) + iconUrl.substring(iconUrl.lastIndexOf("."));
assert res.body() != null; String filePath = Paths.get(FileConstant.FAVICON_PATH, host.substring(0, 2), fileName).toString();
if (!HttpUtil.checkIsOk(res.code())) { FileUtil.writeBytes(data, Paths.get(CommonConstant.fileSavePath, filePath).toString());
throw new CustomException("请求错误:" + res.code()); return File.separator + filePath;
} else {
log.info("未获取到icon:{}", url);
}
} }
byte[] data = res.body().byteStream().readAllBytes();
if (data.length > 0) {
String iconUrl = new URL(res.request().url().toString()).getPath();
return saveToFile(iconUrl, host, data);
} else {
log.info("未获取到icon:{}", url);
}
} catch (SocketTimeoutException timeoutException) {
log.info("获取icon超时{}", host);
} catch (Exception e) { } catch (Exception e) {
log.error("url获取icon故障:{}", url, e); log.error("url获取icon故障:{}", url, e);
} }
return ""; return "";
} }
/**
* 保存到文件中
*
* @param iconUrl icon文件名
* @param host host
* @param b 数据
* @return {@link String}
* @author FleyX
*/
private String saveToFile(String iconUrl, String host, byte[] b) {
String fileName = host.replace(":", ".") + iconUrl.substring(iconUrl.lastIndexOf("."));
String filePath = Paths.get(FileConstant.FAVICON_PATH, host.replace("www", "").replaceAll("\\.", "").substring(0, 2), fileName).toString();
FileUtil.writeBytes(b, Paths.get(CommonConstant.fileSavePath, filePath).toString());
return File.separator + filePath;
}
} }

View File

@ -23,6 +23,12 @@
<artifactId>bookmark-common</artifactId> <artifactId>bookmark-common</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@ -1,47 +0,0 @@
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();
}
}

View File

@ -1,8 +0,0 @@
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> {
}

View File

@ -1,6 +1,5 @@
package com.fanxb.bookmark.business.user.dao; package com.fanxb.bookmark.business.user.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fanxb.bookmark.common.entity.po.User; import com.fanxb.bookmark.common.entity.po.User;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -17,7 +16,7 @@ import java.util.List;
* @date 2019/7/6 11:36 * @date 2019/7/6 11:36
*/ */
@Component @Component
public interface UserDao extends BaseMapper<User> { public interface UserDao {
/** /**
* Description: 新增一个用户 * Description: 新增一个用户
@ -183,6 +182,16 @@ public interface UserDao extends BaseMapper<User> {
@Select("select userId from user order by userId limit #{start},#{size}") @Select("select userId from user order by userId limit #{start},#{size}")
List<Integer> selectUserIdPage(@Param("start") int start, @Param("size") int size); List<Integer> selectUserIdPage(@Param("start") int start, @Param("size") int size);
/**
* 更新用户搜索引擎
*
* @param userId userId
* @param engine engine
* @author fanxb
* @date 2021/3/14
**/
@Update("update user set defaultSearchEngine=#{engine} where userId=#{userId}")
void updateSearchEngine(@Param("userId") int userId, @Param("engine") String engine);
/** /**
* 更新一个字段-一个条件 * 更新一个字段-一个条件

View File

@ -1,29 +0,0 @@
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;
}

View File

@ -1,49 +0,0 @@
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);
}

View File

@ -87,6 +87,6 @@ public class BaseInfoServiceImpl implements BaseInfoService {
@Override @Override
public void changeDefaultSearchEngine(User user) { public void changeDefaultSearchEngine(User user) {
userDao.updateById(user); userDao.updateSearchEngine(user.getUserId(), user.getDefaultSearchEngine());
} }
} }

View File

@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.fanxb.bookmark.business.user.dao.UserDao; import com.fanxb.bookmark.business.user.dao.UserDao;
import com.fanxb.bookmark.business.user.service.OauthService; import com.fanxb.bookmark.business.user.service.OauthService;
import com.fanxb.bookmark.business.user.service.SearchEngineService;
import com.fanxb.bookmark.business.user.service.UserService; import com.fanxb.bookmark.business.user.service.UserService;
import com.fanxb.bookmark.business.user.vo.OauthBody; import com.fanxb.bookmark.business.user.vo.OauthBody;
import com.fanxb.bookmark.common.constant.CommonConstant; import com.fanxb.bookmark.common.constant.CommonConstant;
@ -39,8 +38,6 @@ public class OauthServiceImpl implements OauthService {
private String githubClientId; private String githubClientId;
@Value("${OAuth.github.secret}") @Value("${OAuth.github.secret}")
private String githubSecret; private String githubSecret;
@Autowired
private SearchEngineService searchEngineService;
private final UserDao userDao; private final UserDao userDao;
private final UserService userService; private final UserService userService;
@ -108,7 +105,6 @@ public class OauthServiceImpl implements OauthService {
other.setLastLoginTime(System.currentTimeMillis()); other.setLastLoginTime(System.currentTimeMillis());
other.setVersion(0); other.setVersion(0);
userDao.addOne(other); userDao.addOne(other);
searchEngineService.newUserInit(other.getUserId());
return other; return other;
} else { } else {
if (!current.getEmail().equals(other.getEmail()) || !current.getGithubId().equals(other.getGithubId())) { if (!current.getEmail().equals(other.getEmail()) || !current.getGithubId().equals(other.getGithubId())) {

View File

@ -1,88 +0,0 @@
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));
}
}

View File

@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
import com.fanxb.bookmark.business.api.BookmarkApi; import com.fanxb.bookmark.business.api.BookmarkApi;
import com.fanxb.bookmark.business.user.constant.FileConstant; import com.fanxb.bookmark.business.user.constant.FileConstant;
import com.fanxb.bookmark.business.user.dao.UserDao; import com.fanxb.bookmark.business.user.dao.UserDao;
import com.fanxb.bookmark.business.user.service.SearchEngineService;
import com.fanxb.bookmark.business.user.service.UserService; import com.fanxb.bookmark.business.user.service.UserService;
import com.fanxb.bookmark.business.user.vo.LoginBody; import com.fanxb.bookmark.business.user.vo.LoginBody;
import com.fanxb.bookmark.business.user.vo.RegisterBody; import com.fanxb.bookmark.business.user.vo.RegisterBody;
@ -21,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File; import java.io.File;
@ -44,8 +42,6 @@ public class UserServiceImpl implements UserService {
* 登陆最大重试次数 * 登陆最大重试次数
*/ */
private static final int LOGIN_COUNT = 5; private static final int LOGIN_COUNT = 5;
@Autowired
private SearchEngineService searchEngineService;
private final UserDao userDao; private final UserDao userDao;
private final StringRedisTemplate redisTemplate; private final StringRedisTemplate redisTemplate;
@ -87,7 +83,6 @@ public class UserServiceImpl implements UserService {
* @author fanxb * @author fanxb
* @date 2019/7/6 11:30 * @date 2019/7/6 11:30
*/ */
@Transactional(rollbackFor = Exception.class)
public String register(RegisterBody body) { public String register(RegisterBody body) {
User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail()); User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail());
if (user != null) { if (user != null) {
@ -107,7 +102,6 @@ public class UserServiceImpl implements UserService {
user.setLastLoginTime(System.currentTimeMillis()); user.setLastLoginTime(System.currentTimeMillis());
user.setVersion(0); user.setVersion(0);
userDao.addOne(user); userDao.addOne(user);
searchEngineService.newUserInit(user.getUserId());
Map<String, String> data = new HashMap<>(1); Map<String, String> data = new HashMap<>(1);
data.put("userId", String.valueOf(user.getUserId())); data.put("userId", String.valueOf(user.getUserId()));
return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME); return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME);

View File

@ -37,10 +37,16 @@
<artifactId>commons-pool2</artifactId> <artifactId>commons-pool2</artifactId>
</dependency> </dependency>
<!--mybatis依赖-->
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>2.0.1</version>-->
<!-- </dependency>-->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version> <version>3.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
@ -53,66 +59,21 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.18</version> <version>1.1.18</version>
</dependency> </dependency>
<!--数据库版本管理--> <!--数据库版本管理-->
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId> <artifactId>flyway-core</artifactId>
<version>9.21.1</version> <version>5.2.4</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>9.21.1</version>
</dependency> </dependency>
<!--mysql jdbc依赖--> <!--mysql jdbc依赖-->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency> </dependency>
<!-- &lt;!&ndash;邮件依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-mail</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;减负依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;json工具依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>fastjson</artifactId>-->
<!-- <version>1.2.83</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.elasticsearch.client</groupId>-->
<!-- <artifactId>elasticsearch-rest-high-level-client</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.hutool</groupId>-->
<!-- <artifactId>hutool-all</artifactId>-->
<!-- <version>5.8.21</version>-->
<!-- </dependency>-->
<!--单元测试-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<!--mysql jdbc依赖-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <version>8.0.33</version>-->
<!-- </dependency>-->
<!--邮件依赖--> <!--邮件依赖-->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -128,7 +89,7 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
<version>1.2.83</version> <version>1.2.73</version>
</dependency> </dependency>
@ -140,7 +101,7 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.25</version> <version>5.2.3</version>
</dependency> </dependency>
<!--单元测试--> <!--单元测试-->

View File

@ -1,9 +1,6 @@
package com.fanxb.bookmark.common.configuration; package com.fanxb.bookmark.common.configuration;
import com.fanxb.bookmark.common.factory.CustomThreadFactory; import com.fanxb.bookmark.common.factory.CustomThreadFactory;
import com.fanxb.bookmark.common.factory.ThreadPoolFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@ -14,16 +11,15 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
* Created with IntelliJ IDEA * Created with IntelliJ IDEA
* *
* @author fanxb * @author fanxb
* @date 2020/1/26
*/ */
@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer { public class ScheduleConfig implements SchedulingConfigurer {
@Override @Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule")); ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule"));
scheduledTaskRegistrar.setScheduler(service); scheduledTaskRegistrar.setScheduler(service);
log.info("自定义schedule线程池成功");
} }
} }

View File

@ -1,14 +0,0 @@
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> {
}

View File

@ -12,6 +12,7 @@ import java.util.List;
* 类功能详述 * 类功能详述
* *
* @author fanxb * @author fanxb
* @date 2019/7/8 11:19
*/ */
@Data @Data
public class Bookmark { public class Bookmark {
@ -35,7 +36,6 @@ public class Bookmark {
private String name; private String name;
private String url = ""; private String url = "";
private String icon = ""; private String icon = "";
private String iconUrl;
private Integer sort; private Integer sort;
private String searchKey = ""; private String searchKey = "";
private Long addTime; private Long addTime;
@ -53,7 +53,7 @@ public class Bookmark {
this.setUserId(userId); this.setUserId(userId);
this.setPath(path); this.setPath(path);
this.setType(FOLDER_TYPE); this.setType(FOLDER_TYPE);
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name); this.setName(name);
this.setAddTime(addTime); this.setAddTime(addTime);
this.setSort(sort); this.setSort(sort);
this.setCreateTime(System.currentTimeMillis()); this.setCreateTime(System.currentTimeMillis());
@ -64,7 +64,7 @@ public class Bookmark {
this.setUserId(userId); this.setUserId(userId);
this.setPath(path); this.setPath(path);
this.setType(BOOKMARK_TYPE); this.setType(BOOKMARK_TYPE);
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name); this.setName(name);
this.setUrl(url); this.setUrl(url);
this.setIcon(icon); this.setIcon(icon);
this.setSort(sort); this.setSort(sort);

View File

@ -1,20 +0,0 @@
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;
}

View File

@ -2,10 +2,6 @@ package com.fanxb.bookmark.common.entity.po;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data; import lombok.Data;
@ -20,11 +16,9 @@ import java.util.Map;
* @date 2019/7/4 20:14 * @date 2019/7/4 20:14
*/ */
@Data @Data
@TableName("user")
public class User { public class User {
@TableId(type = IdType.AUTO) private int userId;
private Integer userId;
/** /**
* 第三方github登陆id,-1说明非github登陆 * 第三方github登陆id,-1说明非github登陆
*/ */
@ -36,7 +30,6 @@ public class User {
/** /**
* 是否未设置密码 * 是否未设置密码
*/ */
@TableField(exist = false)
private Boolean noPassword; private Boolean noPassword;
@JSONField(serialize = false) @JSONField(serialize = false)
private String password; private String password;
@ -49,10 +42,9 @@ public class User {
* 书签同步版本 * 书签同步版本
*/ */
private int version; private int version;
/** /**
* 默认搜索引擎 * 默认搜索引擎
*/ */
private Integer searchEngineId; private String defaultSearchEngine;
} }

View File

@ -2,8 +2,6 @@ package com.fanxb.bookmark.common.entity.vo;
import lombok.Data; import lombok.Data;
import java.util.Map;
/** /**
* 全局公共配置 * 全局公共配置
* *
@ -19,8 +17,4 @@ public class GlobalConfigVo {
* bing每日一图地址 * bing每日一图地址
*/ */
private String bingImgSrc; private String bingImgSrc;
/**
* 浏览器插件版本plugin
*/
private Map<String, String> map;
} }

View File

@ -2,32 +2,23 @@ package com.fanxb.bookmark.common.service.impl;
import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; 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.NumberConstant;
import com.fanxb.bookmark.common.constant.RedisConstant; 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.entity.vo.GlobalConfigVo;
import com.fanxb.bookmark.common.service.ConfigService; import com.fanxb.bookmark.common.service.ConfigService;
import com.fanxb.bookmark.common.util.HttpUtil; import com.fanxb.bookmark.common.util.HttpUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.util.Date;
import java.nio.file.Files; import java.util.HashMap;
import java.nio.file.StandardOpenOption; import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* @author fanxb * @author fanxb
@ -39,12 +30,10 @@ public class ConfigServiceImpl implements ConfigService {
private final StringRedisTemplate stringRedisTemplate; private final StringRedisTemplate stringRedisTemplate;
private final GlobalConfigDao globalConfigDao;
@Autowired @Autowired
public ConfigServiceImpl(StringRedisTemplate stringRedisTemplate, GlobalConfigDao globalConfigDao) { public ConfigServiceImpl(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate; this.stringRedisTemplate = stringRedisTemplate;
this.globalConfigDao = globalConfigDao;
} }
@Value("${bing.host}") @Value("${bing.host}")
@ -54,11 +43,9 @@ public class ConfigServiceImpl implements ConfigService {
@Override @Override
public GlobalConfigVo getGlobalConfig() { 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(); GlobalConfigVo vo = new GlobalConfigVo();
vo.setProxyExist(HttpUtil.getProxyExist());
vo.setBingImgSrc(getCacheBingImg()); vo.setBingImgSrc(getCacheBingImg());
vo.setMap(map);
return vo; return vo;
} }
@ -69,7 +56,9 @@ public class ConfigServiceImpl implements ConfigService {
return str; return str;
} }
str = getBingImg(); str = getBingImg();
stringRedisTemplate.opsForValue().set(RedisConstant.BING_IMG, str, 2, TimeUnit.HOURS); if (str != null) {
stringRedisTemplate.opsForValue().set(RedisConstant.BING_IMG, str, 2, TimeUnit.HOURS);
}
return str; return str;
} }
@ -77,19 +66,11 @@ public class ConfigServiceImpl implements ConfigService {
try { try {
JSONObject bingObj = HttpUtil.getObj(bingHost + bingUrl, null, false); JSONObject bingObj = HttpUtil.getObj(bingHost + bingUrl, null, false);
String path = bingObj.getJSONArray("images").getJSONObject(0).getString("url"); String path = bingObj.getJSONArray("images").getJSONObject(0).getString("url");
String picUrl = bingHost + path; return 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) { } catch (Exception e) {
log.error("获取bing每日一图错误{}", e.getLocalizedMessage(), e); log.error("获取bing每日一图错误{}", e.getLocalizedMessage(), e);
} }
return "/files/public/bing.jpg"; return null;
} }

View File

@ -53,14 +53,6 @@ public class HttpUtil {
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
.build(); .build();
/**
* 超时时间1s
*/
@Getter
private static final OkHttpClient SHORT_CLIENT = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS)
.readTimeout(1, TimeUnit.SECONDS)
.build();
/** /**
* 获取客户端 * 获取客户端
* *

View File

@ -22,7 +22,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version> <version>2.3.12.RELEASE</version>
<relativePath/> <relativePath/>
</parent> </parent>

View File

@ -1,13 +0,0 @@
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", "浏览器插件版本");

View File

@ -1,3 +0,0 @@
update global_config
set value='0.1.2'
where code = "pluginVersion";

View File

@ -1,22 +0,0 @@
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;

View File

@ -1,2 +0,0 @@
alter table bookmark
modify name varchar(2000) not null;

View File

@ -4,7 +4,6 @@ node_modules
package-lock.json package-lock.json
yarn.lock yarn.lock
public/files public/files
public\static\bookmarkBrowserPlugin.zip
# local env files # local env files
.env.local .env.local
@ -24,4 +23,3 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?

View File

@ -20,11 +20,11 @@
"vuex": "^3.4.0" "vuex": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-plugin-eslint": "~5.0.8", "@vue/cli-plugin-eslint": "~4.4.0",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~4.4.0",
"@vue/cli-plugin-vuex": "~5.0.8", "@vue/cli-plugin-vuex": "~4.4.0",
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "~4.4.0",
"@vue/eslint-config-airbnb": "^5.0.2", "@vue/eslint-config-airbnb": "^5.0.2",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -6,21 +6,12 @@
<script> <script>
export default { export default {
name: "App", name: "App"
mounted() {
window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
let script = document.createElement("script");
script.type = "text/javascript";
script.defer = true;
script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
document.getElementsByTagName("head")[0].appendChild(script);
}
}; };
</script> </script>
<style lang="less"> <style lang="less">
@import "./global.less"; @import "./global.less";
html, html,
body { body {
margin: 0; margin: 0;
@ -29,7 +20,6 @@ body {
background-color: @bgColor; background-color: @bgColor;
height: initial; height: initial;
} }
#app { #app {
font-family: Avenir, Helvetica, Arial, sans-serif; font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="search"> <div class="search">
<div :class="{ listShow: focused && list.length > 0 }" class="newSearch"> <div :class="{ listShow: focused && list.length > 0 }" class="newSearch">
<input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus" <input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus" @blur="inputBlur" />
@blur="inputBlur" />
<div class="action"> <div class="action">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<a-tooltip title="点击切换网页搜索"> <a-tooltip title="点击切换网页搜索">
<my-icon class="icon" style="margin-right: 0.5em" :type="checkedSearchEngine.icon" <my-icon class="icon" style="margin-right: 0.5em" :type="searchIcon" />
@click="searchIconClick" />
</a-tooltip> </a-tooltip>
<a-menu slot="overlay" @click="searchEngineChange"> <a-menu slot="overlay" @click="searchEngineChange">
<a-menu-item v-for="item in searchEngineList" :key="item.id">{{ item.name }}</a-menu-item> <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> </a-menu>
</a-dropdown> </a-dropdown>
<a-icon class="icon" type="search" @click="submit(true)" /> <a-icon class="icon" type="search" @click="submit(true)" />
@ -30,8 +30,7 @@
</div> </div>
<div class="icons"> <div class="icons">
<a-tooltip title="定位到书签树中" v-if="showLocation"> <a-tooltip title="定位到书签树中" v-if="showLocation">
<my-icon style="color: white; font-size: 1.3em" type="icon-et-location" <my-icon style="color: white; font-size: 1.3em" type="icon-et-location" @mousedown="location($event, item)" />
@mousedown="location($event, item)" />
</a-tooltip> </a-tooltip>
<a-tooltip title="复制链接"> <a-tooltip title="复制链接">
<a-icon <a-icon
@ -52,7 +51,7 @@
</div> </div>
</div> </div>
</div> </div>
<a ref="targetA" style="left: 1000000px" /> <a ref="targetA" style="left: 1000000px" target="_blank" />
</div> </div>
</template> </template>
@ -62,11 +61,10 @@ import { mapState } from "vuex";
import ClipboardJS from "clipboard"; import ClipboardJS from "clipboard";
import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig"; import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig";
import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData"; import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData";
export default { export default {
name: "Search", name: "Search",
props: { props: {
showLocation: Boolean // showLocation: Boolean, //
}, },
data() { data() {
return { return {
@ -76,25 +74,19 @@ export default {
// //
selectIndex: null, selectIndex: null,
copyBoard: null, // copyBoard: null, //
searchEngineList: [],
checkedSearchEngine: { icon: "icon-baidu", name: "百度", url: "https://www.baidu.com/s?ie=UTF-8&wd=%s" }
}; };
}, },
async mounted() { mounted() {
//clipboard //clipboard
this.copyBoard = new ClipboardJS(".search-copy-to-board", { this.copyBoard = new ClipboardJS(".search-copy-to-board", {
text: function(trigger) { text: function (trigger) {
return trigger.attributes.data.nodeValue; return trigger.attributes.data.nodeValue;
} },
}); });
this.copyBoard.on("success", (e) => { this.copyBoard.on("success", (e) => {
this.$message.success("复制成功"); this.$message.success("复制成功");
e.clearSelection(); e.clearSelection();
}); });
if (this.$store.state.globalConfig.token != null) {
this.searchEngineList = await HttpUtil.get("/searchEngine/list");
this.checkedSearchEngine = this.searchEngineList.find(item => item.checked === 1);
}
}, },
destroyed() { destroyed() {
if (this.copyBoard != null) { if (this.copyBoard != null) {
@ -103,20 +95,26 @@ export default {
}, },
computed: { computed: {
...mapState("treeData", ["totalTreeData", HOME_PIN_BOOKMARK_ID_MAP]), ...mapState("treeData", ["totalTreeData", HOME_PIN_BOOKMARK_ID_MAP]),
...mapState("globalConfig", ["userInfo"]) ...mapState("globalConfig", ["userInfo"]),
searchIcon() {
let search = this.userInfo != null ? this.userInfo.defaultSearchEngine : "baidu";
return search === "baidu" ? "icon-baidu" : search === "bing" ? "icon-bing" : "icon-google";
},
searchUrl() {
let search = this.userInfo && this.userInfo.defaultSearchEngine ? this.userInfo.defaultSearchEngine : "baidu";
return search === "baidu"
? "https://www.baidu.com/s?ie=UTF-8&wd="
: search === "bing"
? "https://www.bing.com/search?q="
: "https://www.google.com/search?q=";
},
}, },
watch: { watch: {
value(newVal, oldVal) { value(newVal, oldVal) {
this.search(newVal); this.search(newVal);
} },
}, },
methods: { methods: {
searchIconClick() {
if (this.userInfo == null) {
this.searchEngineList = [];
this.$message.warning("未登录,请登录后操作");
}
},
search(content) { search(content) {
console.log(content); console.log(content);
let val = content.toLocaleLowerCase().trim(); let val = content.toLocaleLowerCase().trim();
@ -125,7 +123,6 @@ export default {
} else { } else {
this.list = this.dealSearch(val); this.list = this.dealSearch(val);
} }
this.selectIndex = null;
}, },
// //
itemClick(index) { itemClick(index) {
@ -137,7 +134,7 @@ export default {
let url; let url;
if (forceSearch || this.selectIndex == null) { if (forceSearch || this.selectIndex == null) {
//使 //使
url = this.checkedSearchEngine.url.replace("%s", encodeURIComponent(this.value)); url = this.searchUrl + encodeURIComponent(this.value);
} else { } else {
// //
let bookmark = this.list[this.selectIndex]; let bookmark = this.list[this.selectIndex];
@ -178,10 +175,15 @@ export default {
}, },
// //
async searchEngineChange(item) { async searchEngineChange(item) {
if (this.userInfo == null) {
let target = this.searchEngineList.find(one => one.id === item.key); this.$message.warning("未登录,请登录后操作");
await HttpUtil.post("/searchEngine/setChecked", null, { id: item.key }); return;
this.checkedSearchEngine = target; }
if (item.key !== this.userInfo.defaultSearchEngine) {
await HttpUtil.post("/baseInfo/updateSearchEngine", null, { defaultSearchEngine: item.key });
this.userInfo.defaultSearchEngine = item.key;
this.$store.commit(GLOBAL_CONFIG + "/" + USER_INFO, this.userInfo);
}
}, },
// //
async pinBookmark(event, { bookmarkId }) { async pinBookmark(event, { bookmarkId }) {
@ -225,8 +227,8 @@ export default {
} }
console.log("阻止成功"); console.log("阻止成功");
return false; return false;
} },
} },
}; };
</script> </script>
@ -237,12 +239,10 @@ export default {
@listActiveBgColor: #454545; @listActiveBgColor: #454545;
.search { .search {
position: relative; position: relative;
.listShow { .listShow {
border-bottom-left-radius: 0 !important; border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important; border-bottom-right-radius: 0 !important;
} }
.newSearch { .newSearch {
display: flex; display: flex;
align-items: center; align-items: center;
@ -251,7 +251,6 @@ export default {
overflow: hidden; overflow: hidden;
font-size: 1.2em; font-size: 1.2em;
color: @textColor; color: @textColor;
.input { .input {
flex: 1; flex: 1;
border: 0; border: 0;
@ -260,13 +259,11 @@ export default {
padding-left: 0.19rem; padding-left: 0.19rem;
outline: none; outline: none;
} }
.action { .action {
padding: 0.1rem; padding: 0.1rem;
padding-right: 0.19rem; padding-right: 0.19rem;
display: flex; display: flex;
align-items: center; align-items: center;
.icon { .icon {
color: @textColor; color: @textColor;
cursor: pointer; cursor: pointer;
@ -284,7 +281,6 @@ export default {
border-bottom-left-radius: 0.18rem; border-bottom-left-radius: 0.18rem;
border-bottom-right-radius: 0.18rem; border-bottom-right-radius: 0.18rem;
overflow: hidden; overflow: hidden;
.listItem { .listItem {
font-size: 0.16rem; font-size: 0.16rem;
display: flex; display: flex;
@ -295,7 +291,6 @@ export default {
margin: 0.05rem 0 0.05rem 0; margin: 0.05rem 0 0.05rem 0;
padding: 0 0.19rem 0 0.19rem; padding: 0 0.19rem 0 0.19rem;
cursor: pointer; cursor: pointer;
.name { .name {
padding-right: 1em; padding-right: 1em;
max-width: calc(100% - 2em); max-width: calc(100% - 2em);
@ -303,21 +298,17 @@ export default {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icons { .icons {
display: none; display: none;
align-items: center; align-items: center;
} }
} }
.listItem:hover { .listItem:hover {
background-color: @listActiveBgColor; background-color: @listActiveBgColor;
} }
.itemActive { .itemActive {
background-color: @listActiveBgColor; background-color: @listActiveBgColor;
} }
.listItem:hover .icons { .listItem:hover .icons {
display: flex; display: flex;
} }

View File

@ -6,10 +6,10 @@
</a-form-model-item> </a-form-model-item>
<template v-if="form.type !== 'file'"> <template v-if="form.type !== 'file'">
<a-form-model-item prop="name" label="名称" :required="false"> <a-form-model-item prop="name" label="名称" :required="false">
<a-input v-model="form.name" placeholder="名称" @pressEnter="submit" ref="inputName" /> <a-input v-model="form.name" placeholder="名称" />
</a-form-model-item> </a-form-model-item>
<a-form-model-item v-if="form.type === 'bookmark'" prop="url" label="URL"> <a-form-model-item v-if="form.type === 'bookmark'" prop="url" label="URL">
<a-input v-model="form.url" placeholder="url" @pressEnter="submit" /> <a-input v-model="form.url" placeholder="url" />
</a-form-model-item> </a-form-model-item>
<div class="btns"> <div class="btns">
<a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button> <a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button>
@ -21,7 +21,6 @@
:data="{ path: form.path }" :data="{ path: form.path }"
:headers="{ 'jwt-token': token }" :headers="{ 'jwt-token': token }"
action="/bookmark/api/bookmark/uploadBookmarkFile" action="/bookmark/api/bookmark/uploadBookmarkFile"
accept=".html,.db3"
@change="fileChange" @change="fileChange"
> >
<p class="ant-upload-drag-icon"> <p class="ant-upload-drag-icon">
@ -63,7 +62,7 @@ export default {
file: null, file: null,
}, },
rules: { rules: {
name: [{ required: true, min: 1, max: 1000, message: "名称长度为1-200", trigger: "change" }], name: [{ required: true, min: 1, max: 1000, message: "名称长度为1-1000", trigger: "change" }],
url: [{ required: true, min: 1, message: "不能为空", trigger: "change" }], url: [{ required: true, min: 1, message: "不能为空", trigger: "change" }],
}, },
}; };
@ -79,12 +78,6 @@ export default {
} }
this.token = this.$store.state.globalConfig.token; this.token = this.$store.state.globalConfig.token;
this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : ""); this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
this.$nextTick(() => {
if (this.$refs.inputName) {
this.$refs.inputName.focus();
}
});
}, },
methods: { methods: {
/** /**

View File

@ -1,10 +1,6 @@
<template> <template>
<div class="bottom"> <div class="bottom">
<router-link style="color: white" to="/public/about"><span class="text">关于</span></router-link> <router-link to="/public/about">关于</router-link>
<a-tooltip v-if="bgSrc">
<template #title>点击后鼠标右键-将图像另存为</template>
<a style="color: white; margin-left: 1em" :href="bgSrc" download="bing每日一图"><span class="text">下载壁纸</span></a>
</a-tooltip>
</div> </div>
</template> </template>
@ -12,7 +8,6 @@
import { mapState } from "vuex"; import { mapState } from "vuex";
export default { export default {
name: "homeTop", name: "homeTop",
props: ["bgSrc"],
data() { data() {
return {}; return {};
}, },
@ -23,11 +18,6 @@ export default {
.bottom { .bottom {
height: 0.4rem; height: 0.4rem;
padding: 0.1rem; padding: 0.1rem;
text-align: center; text-align: right;
color: black;
.text {
color: rgba(255, 255, 255, 0.9);
}
} }
</style> </style>

View File

@ -6,18 +6,15 @@
/ /
<router-link to="/public/register">注册</router-link> <router-link to="/public/register">注册</router-link>
</div> </div>
<div v-else class="topAction"> <div v-else>
<a-tooltip style="margin-right: 1em">
<template #title>书签管理</template>
<router-link to="/manage">
<a-icon class="bookmarkIcon" type="setting" />
</router-link>
</a-tooltip>
<a-dropdown> <a-dropdown>
<div class="user"> <div class="user">
<img :src="userInfo.icon" class="userIcon" /> <img :src="userInfo.icon" class="userIcon" />
</div> </div>
<a-menu slot="overlay" :trigger="['hover', 'click']" @click="menuClick"> <a-menu slot="overlay" :trigger="['hover', 'click']" @click="menuClick">
<a-menu-item key="manage">
<router-link to="manage">书签管理</router-link>
</a-menu-item>
<a-menu-item key="personSpace"> <a-menu-item key="personSpace">
<router-link to="/manage/personSpace/userInfo">个人中心</router-link> <router-link to="/manage/personSpace/userInfo">个人中心</router-link>
</a-menu-item> </a-menu-item>
@ -66,16 +63,5 @@ export default {
width: 2.5em; width: 2.5em;
height: 2.5em; height: 2.5em;
} }
.topAction {
display: flex;
align-items: center;
.bookmarkIcon {
font-size: 2em;
background-color: rgb(74, 74, 74, 0.5);
color: rgba(255, 255, 255, 0.8);
}
}
} }
</style> </style>

View File

@ -1,34 +1,32 @@
import Vue from "vue"; import Vue from "vue";
import { import {
Button, Button,
FormModel, FormModel,
Input, Input,
Icon, Icon,
message, message,
Checkbox, Checkbox,
Dropdown, Dropdown,
Menu, Menu,
Tree, Tree,
Tooltip, Tooltip,
Spin, Spin,
notification, notification,
Empty, Empty,
Modal, Modal,
Radio, Radio,
Upload, Upload,
Popconfirm, Popconfirm,
AutoComplete, AutoComplete,
Select, Select,
Popover, Popover
Breadcrumb,
Table
} from "ant-design-vue"; } from "ant-design-vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import store from "./store"; import store from "./store";
const IconFont = Icon.createFromIconfontCN({ const IconFont = Icon.createFromIconfontCN({
scriptUrl: "//at.alicdn.com/t/c/font_1261825_v7m0rilm4hm.js" scriptUrl: "//at.alicdn.com/t/font_1261825_1cgngjf5r4f.js"
}); });
Vue.use(Button); Vue.use(Button);
Vue.use(FormModel); Vue.use(FormModel);
@ -48,8 +46,6 @@ Vue.use(Popconfirm);
Vue.use(AutoComplete); Vue.use(AutoComplete);
Vue.use(Select); Vue.use(Select);
Vue.use(Popover); Vue.use(Popover);
Vue.use(Breadcrumb);
Vue.use(Table);
Vue.component("my-icon", IconFont); Vue.component("my-icon", IconFont);
Vue.prototype.$message = message; Vue.prototype.$message = message;
@ -58,9 +54,9 @@ Vue.prototype.$confirm = Modal.confirm;
Vue.config.productionTip = false; Vue.config.productionTip = false;
window.vueInstance = new Vue({ window.vueInstance = new Vue({
router, router,
store, store,
render: h => h(App) render: h => h(App)
}).$mount("#app"); }).$mount("#app");

View File

@ -58,7 +58,7 @@ router.beforeEach(async (to, from, next) => {
await vuex.default.dispatch("treeData/clear"); await vuex.default.dispatch("treeData/clear");
await vuex.default.dispatch("globalConfig/clear"); await vuex.default.dispatch("globalConfig/clear");
next({ next({
path: "/public/login?to=" + btoa(to.fullPath), path: "/public/login?to=" + btoa(location.href),
replace: true replace: true
}); });
} else { } else {

View File

@ -8,61 +8,58 @@ import { checkJwtValid } from "@/util/UserUtil";
Vue.use(Vuex); Vue.use(Vuex);
let store = new Vuex.Store({ let store = new Vuex.Store({
state: {}, state: {},
mutations: {}, mutations: {},
actions: {}, actions: {},
modules: { modules: {
[globalConfig.GLOBAL_CONFIG]: globalConfig.store, [globalConfig.GLOBAL_CONFIG]: globalConfig.store,
[treeData.TREE_DATA]: treeData.store [treeData.TREE_DATA]: treeData.store
} }
}); });
let noLoginFinish = false; let noLoginFinish = false;
//执行各自的非登陆初始化 //执行各自的非登陆初始化
(async () => { (async () => {
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit); await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
//无需等待执行 await store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit); noLoginFinish = true;
noLoginFinish = true;
})(); })();
/** /**
* 执行各模块的登陆后初始化 * 执行各模块的登陆后初始化
*/ */
export async function loginInit() { export async function loginInit () {
if (!noLoginFinish) { if (!noLoginFinish) {
await finishNoLogin(); await finishNoLogin();
} }
console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]); console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]);
if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) { if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) {
//无需等待执行完毕 await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit);
store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit); await store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit);
store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit); }
}
console.log("初始化完成");
} }
/** /**
* 推出登陆时需要清理的 * 推出登陆时需要清理的
*/ */
export async function logoutClear() { export async function logoutClear () {
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear); await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear);
await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear); await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear);
} }
/** /**
* 确保未登录前要初始化的初始化完了 * 确保未登录前要初始化的初始化完了
*/ */
async function finishNoLogin() { async function finishNoLogin () {
return new Promise((resolve) => { return new Promise((resolve) => {
let timer = setInterval(() => { let timer = setInterval(() => {
if (noLoginFinish) { if (noLoginFinish) {
clearInterval(timer); clearInterval(timer);
resolve(); resolve();
} }
}, 100); }, 100);
}); })
} }
export default store; export default store;

View File

@ -69,7 +69,6 @@ const actions = {
let userInfo = await HttpUtil.get("/user/currentUserInfo"); let userInfo = await HttpUtil.get("/user/currentUserInfo");
context.commit(USER_INFO, userInfo); context.commit(USER_INFO, userInfo);
context.commit(IS_INIT, true); context.commit(IS_INIT, true);
console.log("用户完了");
}, },
async [setToken]({ commit }, token) { async [setToken]({ commit }, token) {
await localforage.setItem(TOKEN, token); await localforage.setItem(TOKEN, token);

View File

@ -22,13 +22,7 @@ export const refreshHomePinList = "refreshHomePinList";
* 通过id获取书签数据 * 通过id获取书签数据
*/ */
export const getById = "getById"; export const getById = "getById";
/**
* 登录前初始化
*/
export const noLoginInit = "noLoginInit"; export const noLoginInit = "noLoginInit";
/**
* 登陆后初始化
*/
export const loginInit = "loginInit"; export const loginInit = "loginInit";
export const refresh = "refresh"; export const refresh = "refresh";
export const clear = "clear"; export const clear = "clear";
@ -45,10 +39,6 @@ export const addNode = "addNode";
* 版本检查定时调度 * 版本检查定时调度
*/ */
let timer = null; let timer = null;
/**
* 检查本地版本是否有更新
*/
let checkLocalDataTimer = null;
/** /**
* 刷新书签确认弹窗是否展示 * 刷新书签确认弹窗是否展示
*/ */
@ -86,8 +76,8 @@ const getters = {
}; };
const actions = { const actions = {
async [noLoginInit] () { }, async [noLoginInit]() {},
async [loginInit] (context) { async [loginInit](context) {
if (context.state.isInit || context.state.isIniting) { if (context.state.isInit || context.state.isIniting) {
return; return;
} }
@ -99,12 +89,11 @@ const actions = {
context.commit(IS_INIT, true); context.commit(IS_INIT, true);
context.commit(IS_INITING, false); context.commit(IS_INITING, false);
timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL); timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL);
checkLocalDataTimer = setInterval(() => checkLocalData(context), 2000);
}, },
/** /**
* 确保数据加载完毕 * 确保数据加载完毕
*/ */
ensureDataOk (context) { ensureDataOk(context) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let timer = setInterval(() => { let timer = setInterval(() => {
try { try {
@ -119,7 +108,7 @@ const actions = {
}); });
}, },
//刷新缓存数据 //刷新缓存数据
async [refresh] (context) { async [refresh](context) {
let treeData = await HttpUtil.get("/bookmark/currentUser"); let treeData = await HttpUtil.get("/bookmark/currentUser");
if (!treeData[""]) { if (!treeData[""]) {
treeData[""] = []; treeData[""] = [];
@ -138,7 +127,7 @@ const actions = {
await localforage.setItem(TOTAL_TREE_DATA, treeData); await localforage.setItem(TOTAL_TREE_DATA, treeData);
}, },
//清除缓存数据 //清除缓存数据
async [clear] (context) { async [clear](context) {
context.commit(TOTAL_TREE_DATA, null); context.commit(TOTAL_TREE_DATA, null);
context.commit(VERSION, null); context.commit(VERSION, null);
context.commit(SHOW_REFRESH_TOAST, false); context.commit(SHOW_REFRESH_TOAST, false);
@ -148,16 +137,13 @@ const actions = {
if (timer != null) { if (timer != null) {
clearInterval(timer); clearInterval(timer);
} }
if (checkLocalDataTimer != null) {
clearInterval(checkLocalDataTimer);
}
await localforage.removeItem(TOTAL_TREE_DATA); await localforage.removeItem(TOTAL_TREE_DATA);
await localforage.removeItem(VERSION); await localforage.removeItem(VERSION);
}, },
/** /**
* 移动节点 * 移动节点
*/ */
async moveNode (context, info) { async moveNode(context, info) {
let data = context.state[TOTAL_TREE_DATA]; let data = context.state[TOTAL_TREE_DATA];
const target = info.node.dataRef; const target = info.node.dataRef;
const current = info.dragNode.dataRef; const current = info.dragNode.dataRef;
@ -225,7 +211,7 @@ const actions = {
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]); await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
return body; return body;
}, },
async [refreshHomePinList] ({ commit }) { async [refreshHomePinList]({ commit }) {
let list = await HttpUtil.get("/home/pin"); let list = await HttpUtil.get("/home/pin");
commit(HOME_PIN_LIST, list); commit(HOME_PIN_LIST, list);
let map = {}; let map = {};
@ -235,30 +221,26 @@ const actions = {
/** /**
* 更新版本数据 * 更新版本数据
*/ */
async updateVersion ({ commit, state }, version) { async updateVersion({ commit, state }, version) {
commit(VERSION, version == null ? state[VERSION] + 1 : version); commit(VERSION, version == null ? state[VERSION] + 1 : version);
await localforage.setItem(VERSION, state[VERSION]); await localforage.setItem(VERSION, state[VERSION]);
}, },
/** /**
* 新增书签文件夹 * 新增书签文件夹
*/ */
async [addNode] (context, { sourceNode, targetNode }) { async [addNode](context, { sourceNode, targetNode }) {
if (sourceNode === null) { if (sourceNode === null) {
if (context.state[TOTAL_TREE_DATA][""] === undefined) { if (context.state[TOTAL_TREE_DATA][""] === undefined) {
context.state[TOTAL_TREE_DATA][""] = []; context.state[TOTAL_TREE_DATA][""] = [];
} }
context.state[TOTAL_TREE_DATA][""].push(targetNode); context.state[TOTAL_TREE_DATA][""].push(targetNode);
} else { } else {
let path = sourceNode.path + "." + sourceNode.bookmarkId;
if (!context.state[TOTAL_TREE_DATA][path]) {
context.state[TOTAL_TREE_DATA][path] = [];
}
if (sourceNode.children === undefined) { if (sourceNode.children === undefined) {
sourceNode.children = context.state[TOTAL_TREE_DATA][path]; sourceNode.children = [];
} }
sourceNode.children.push(targetNode); sourceNode.children.push(targetNode);
} }
if (targetNode.type === 1) { if (targetNode.type === 0) {
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = []; context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
} }
targetNode.isLeaf = targetNode.type === 0; targetNode.isLeaf = targetNode.type === 0;
@ -271,7 +253,7 @@ const actions = {
/** /**
* 删除节点数据 * 删除节点数据
*/ */
async [deleteData] (context, { pathList, bookmarkIdList }) { async [deleteData](context, { pathList, bookmarkIdList }) {
//待删除的书签 //待删除的书签
let bookmarkIdSet = new Set(); let bookmarkIdSet = new Set();
bookmarkIdList.forEach(item => bookmarkIdSet.add(item)); bookmarkIdList.forEach(item => bookmarkIdSet.add(item));
@ -299,7 +281,7 @@ const actions = {
/** /**
* 编辑书签节点 * 编辑书签节点
*/ */
async editNode ({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) { async editNode({ dispatch, state, commit }, { node, newName, newUrl, newIcon }) {
node.name = newName; node.name = newName;
node.url = newUrl; node.url = newUrl;
node.icon = newIcon; node.icon = newIcon;
@ -313,10 +295,10 @@ const mutations = {
[TOTAL_TREE_DATA]: (state, totalTreeData) => { [TOTAL_TREE_DATA]: (state, totalTreeData) => {
state.totalTreeData = totalTreeData; state.totalTreeData = totalTreeData;
}, },
[IS_INIT] (state, isInit) { [IS_INIT](state, isInit) {
state.isInit = isInit; state.isInit = isInit;
}, },
[IS_INITING] (state, isIniting) { [IS_INITING](state, isIniting) {
state.isIniting = isIniting; state.isIniting = isIniting;
}, },
[VERSION]: (state, version) => { [VERSION]: (state, version) => {
@ -340,7 +322,7 @@ const mutations = {
* @param {*} isFirst * @param {*} isFirst
* @returns * @returns
*/ */
async function treeDataCheck (context, isFirst) { async function treeDataCheck(context, isFirst) {
if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) { if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) {
return; return;
} }
@ -354,14 +336,14 @@ async function treeDataCheck (context, isFirst) {
closable: false, closable: false,
keyboard: false, keyboard: false,
maskClosable: false, maskClosable: false,
onOk () { onOk() {
toastShow = false; toastShow = false;
return new Promise(async resolve => { return new Promise(async resolve => {
await context.dispatch(refresh); await context.dispatch(refresh);
resolve(); resolve();
}); });
}, },
onCancel () { onCancel() {
toastShow = false; toastShow = false;
} }
}); });
@ -372,24 +354,6 @@ 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 = { export const store = {
namespaced: true, namespaced: true,
state, state,

View File

@ -12,54 +12,54 @@ import router from "../router/index";
* @param {*} redirect 接口返回未认证是否跳转到登陆 * @param {*} redirect 接口返回未认证是否跳转到登陆
* @returns 数据 * @returns 数据
*/ */
async function request(url, method, params, body, isForm, redirect) { async function request (url, method, params, body, isForm, redirect) {
let options = { let options = {
url, url,
baseURL: "/bookmark/api", baseURL: "/bookmark/api",
method, method,
params, params,
headers: { headers: {
"jwt-token": window.jwtToken "jwt-token": window.jwtToken
} }
}; };
//如果是表单类型的请求,添加请求头 //如果是表单类型的请求,添加请求头
if (isForm) { if (isForm) {
options.headers["Content-Type"] = "multipart/form-data"; options.headers["Content-Type"] = "multipart/form-data";
} }
if (body) { if (body) {
options.data = body; options.data = body;
} }
let res; let res;
try { try {
res = await http.default.request(options); res = await http.default.request(options);
} catch (err) { } catch (err) {
window.vueInstance.$message.error("网络连接异常"); window.vueInstance.$message.error("网络连接异常");
console.error(err); console.error(err);
throw err; throw err;
} }
const { code, data, message } = res.data; const { code, data, message } = res.data;
if (code === 1) { if (code === 1) {
return data; return data;
} else if (code === -1 && redirect) { } else if (code === -1 && redirect) {
//未登陆根据redirect参数判断是否需要跳转到登陆页 //未登陆根据redirect参数判断是否需要跳转到登陆页
window.vueInstance.$message.error("您尚未登陆,请先登陆"); window.vueInstance.$message.error("您尚未登陆,请先登陆");
//跳转到登录页面需要清理缓存 //跳转到登录页面需要清理缓存
await this.$store.dispatch("treeData/clear"); await this.$store.dispatch("treeData/clear");
await this.$store.dispatch("globalConfig/clear"); await this.$store.dispatch("globalConfig/clear");
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`); router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
throw new Error(message); throw new Error(message);
} else if (code === 0) { } else if (code === 0) {
//通用异常使用error提示 //通用异常使用error提示
window.vueInstance.$notification.error({ window.vueInstance.$notification.error({
message: "异常", message: "异常",
description: message description: message
}); });
throw new Error(message); throw new Error(message);
} else if (code === -2) { } else if (code === -2) {
//表单异常使用message提示 //表单异常使用message提示
window.vueInstance.$message.error(message); window.vueInstance.$message.error(message);
throw new Error(message); throw new Error(message);
} }
} }
/** /**
@ -68,8 +68,8 @@ async function request(url, method, params, body, isForm, redirect) {
* @param {*} params url参数 * @param {*} params url参数
* @param {*} redirect 未登陆是否跳转到登陆页 * @param {*} redirect 未登陆是否跳转到登陆页
*/ */
async function get(url, params = null, redirect = true) { async function get (url, params = null, redirect = true) {
return request(url, "get", params, null, false, redirect); return request(url, "get", params, null, false, redirect);
} }
/** /**
@ -80,8 +80,8 @@ async function get(url, params = null, redirect = true) {
* @param {*} isForm 是否表单数据 * @param {*} isForm 是否表单数据
* @param {*} redirect 是否重定向 * @param {*} redirect 是否重定向
*/ */
async function post(url, params, body, isForm = false, redirect = true) { async function post (url, params, body, isForm = false, redirect = true) {
return request(url, "post", params, body, isForm, redirect); return request(url, "post", params, body, isForm, redirect);
} }
/** /**
@ -92,8 +92,8 @@ async function post(url, params, body, isForm = false, redirect = true) {
* @param {*} isForm 是否表单数据 * @param {*} isForm 是否表单数据
* @param {*} redirect 是否重定向 * @param {*} redirect 是否重定向
*/ */
async function put(url, params, body, isForm = false, redirect = true) { async function put (url, params, body, isForm = false, redirect = true) {
return request(url, "put", params, body, isForm, redirect); return request(url, "put", params, body, isForm, redirect);
} }
/** /**
@ -102,14 +102,13 @@ async function put(url, params, body, isForm = false, redirect = true) {
* @param {*} params url参数 * @param {*} params url参数
* @param {*} redirect 是否重定向 * @param {*} redirect 是否重定向
*/ */
async function deletes(url, params = null, redirect = true) { async function deletes (url, params = null, redirect = true) {
return request(url, "delete", params, null, redirect); return request(url, "delete", params, null, redirect);
} }
export default { export default {
get, get,
post, post,
put, put,
delete: deletes, delete: deletes
http
}; };

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<a :href="pinObj.url" v-if="pinObj" class="pinBookmarkItem"> <a :href="pinObj.url" v-if="pinObj" class="pinBookmarkItem" target="_blank">
<img :src="pinObj.icon.length > 0 ? pinObj.icon : '/favicon.ico'" class="icon" /> <img :src="pinObj.icon.length > 0 ? pinObj.icon : '/favicon.ico'" class="icon" />
<span class="text" :title="pinObj.name">{{ pinObj.name }}</span> <span class="text" :title="pinObj.name">{{ pinObj.name }}</span>
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">

View File

@ -5,7 +5,7 @@
<search :style="{ width: isPhone ? '100%' : '60%' }" /> <search :style="{ width: isPhone ? '100%' : '60%' }" />
<div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div> <div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div>
</div> </div>
<bottom :bgSrc="serverConfig.bingImgSrc" /> <bottom />
</div> </div>
</template> </template>

View File

@ -15,7 +15,7 @@
<a-tooltip <a-tooltip
v-if=" v-if="
(checkedKeys.length === 0 && (currentSelect == null || currentSelect.type === 1)) || (checkedKeys.length === 0 && (currentSelect == null || currentSelect.type === 1)) ||
(checkedKeys.length === 1 && checkedNodes[0].type === 1) (checkedKeys.length === 1 && checkedNodes[0].type === 1)
" "
title="添加书签" title="添加书签"
> >
@ -65,7 +65,7 @@
@drop="onDrop" @drop="onDrop"
> >
<a-dropdown :trigger="['contextmenu']" slot="nodeTitle" slot-scope="rec"> <a-dropdown :trigger="['contextmenu']" slot="nodeTitle" slot-scope="rec">
<div class="titleContext" :title="rec.dataRef.url"> <div class="titleContext">
<a-icon type="folder" v-if="!rec.dataRef.isLeaf" /> <a-icon type="folder" v-if="!rec.dataRef.isLeaf" />
<img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" /> <img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" />
<a-icon type="book" v-else /> <a-icon type="book" v-else />
@ -77,7 +77,7 @@
<a-menu-item v-if="!rec.dataRef.isLeaf" key="add">新增</a-menu-item> <a-menu-item v-if="!rec.dataRef.isLeaf" key="add">新增</a-menu-item>
<a-menu-item v-else key="copy" class="copy-to-board" :data="rec.dataRef.url">复制URL</a-menu-item> <a-menu-item v-else key="copy" class="copy-to-board" :data="rec.dataRef.url">复制URL</a-menu-item>
<a-menu-item v-if="rec.dataRef.isLeaf" key="pin"> <a-menu-item v-if="rec.dataRef.isLeaf" key="pin">
{{ homePinList.filter((item) => item.id && item.bookmarkId == rec.dataRef.bookmarkId).length > 0 ? "从首页移除" : "固定到首页" }} {{ homePinList.filter(item => item.id && item.bookmarkId == rec.dataRef.bookmarkId).length > 0 ? "从首页移除" : "固定到首页" }}
</a-menu-item> </a-menu-item>
<a-menu-item key="edit">编辑</a-menu-item> <a-menu-item key="edit">编辑</a-menu-item>
<a-menu-item key="delete">删除</a-menu-item> <a-menu-item key="delete">删除</a-menu-item>
@ -114,7 +114,7 @@ export default {
loadedKeys: [], // loadedKeys: [], //
replaceFields: { replaceFields: {
title: "name", title: "name",
key: "bookmarkId", key: "bookmarkId"
}, },
mulSelect: false, // mulSelect: false, //
currentSelect: null, // currentSelect: null, //
@ -126,19 +126,19 @@ export default {
// null // null
targetNode: null, targetNode: null,
// //
isAdd: false, isAdd: false
}, },
copyBoard: null, // copyBoard: null //
}; };
}, },
computed: { computed: {
...mapState("treeData", ["totalTreeData", HOME_PIN_LIST]), ...mapState("treeData", ["totalTreeData", HOME_PIN_LIST]),
...mapState("globalConfig", ["isPhone"]), ...mapState("globalConfig", ["isPhone"])
}, },
watch: { watch: {
totalTreeData(newVal, oldVal) { totalTreeData(newVal, oldVal) {
this.resetData(); this.resetData();
}, }
}, },
async mounted() { async mounted() {
this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, true); this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, true);
@ -147,14 +147,21 @@ export default {
this.loading = false; this.loading = false;
//clipboard //clipboard
this.copyBoard = new ClipboardJS(".copy-to-board", { this.copyBoard = new ClipboardJS(".copy-to-board", {
text: function (trigger) { text: function(trigger) {
return trigger.attributes.data.nodeValue; return trigger.attributes.data.nodeValue;
}, }
}); });
this.copyBoard.on("success", (e) => { this.copyBoard.on("success", e => {
this.$message.success("复制成功"); this.$message.success("复制成功");
e.clearSelection(); e.clearSelection();
}); });
window.onblur = e => {
console.log("窗口非激活");
};
window.onfocus = e => {
console.log("窗口激活");
};
}, },
beforeDestroy() { beforeDestroy() {
this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, false); this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, false);
@ -168,7 +175,7 @@ export default {
*/ */
loadData(treeNode) { loadData(treeNode) {
console.log("加载数据", treeNode); console.log("加载数据", treeNode);
return new Promise((resolve) => { return new Promise(resolve => {
const data = typeof treeNode === "number" ? this.$store.getters["treeData/getById"](treeNode) : treeNode.dataRef; const data = typeof treeNode === "number" ? this.$store.getters["treeData/getById"](treeNode) : treeNode.dataRef;
let newPath = data.path + "." + data.bookmarkId; let newPath = data.path + "." + data.bookmarkId;
if (!this.totalTreeData[newPath]) { if (!this.totalTreeData[newPath]) {
@ -212,9 +219,9 @@ export default {
this.expandedKeys = [ this.expandedKeys = [
...item.path ...item.path
.split(".") .split(".")
.filter((item) => item.length > 0) .filter(item => item.length > 0)
.map((item) => parseInt(item)), .map(item => parseInt(item)),
item.bookmarkId, item.bookmarkId
]; ];
} else { } else {
this.expandedKeys.pop(); this.expandedKeys.pop();
@ -228,7 +235,7 @@ export default {
} else { } else {
this.checkedKeys.splice(this.checkedKeys.indexOf(item.bookmarkId), 1); this.checkedKeys.splice(this.checkedKeys.indexOf(item.bookmarkId), 1);
this.checkedNodes.splice( this.checkedNodes.splice(
this.checkedNodes.findIndex((item1) => item1.bookmarkId === item.bookmarkId), this.checkedNodes.findIndex(item1 => item1.bookmarkId === item.bookmarkId),
1 1
); );
} }
@ -248,9 +255,9 @@ export default {
this.expandedKeys = [ this.expandedKeys = [
...item.path ...item.path
.split(".") .split(".")
.filter((item) => item.length > 0) .filter(item => item.length > 0)
.map((item) => parseInt(item)), .map(item => parseInt(item)),
item.bookmarkId, item.bookmarkId
]; ];
} }
} else { } else {
@ -273,7 +280,7 @@ export default {
const bookmarkIdList = []; const bookmarkIdList = [];
const pathList = []; const pathList = [];
if (this.checkedNodes) { if (this.checkedNodes) {
this.checkedNodes.forEach((item) => this.checkedNodes.forEach(item =>
item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId) item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId)
); );
} }
@ -290,7 +297,7 @@ export default {
await HttpUtil.post("/bookmark/batchDelete", null, { pathList, bookmarkIdList }); await HttpUtil.post("/bookmark/batchDelete", null, { pathList, bookmarkIdList });
await this.$store.dispatch(TREE_DATA + "/" + deleteData, { pathList, bookmarkIdList }); await this.$store.dispatch(TREE_DATA + "/" + deleteData, { pathList, bookmarkIdList });
// //
pathList.forEach((item) => { pathList.forEach(item => {
const id = parseInt(item.split(".").reverse()[0]); const id = parseInt(item.split(".").reverse()[0]);
let index = this.loadedKeys.indexOf(id); let index = this.loadedKeys.indexOf(id);
if (index > -1) { if (index > -1) {
@ -322,13 +329,13 @@ export default {
this.refresh(false); this.refresh(false);
this.expandedKeys = item.path this.expandedKeys = item.path
.split(".") .split(".")
.filter((one) => one.length > 0) .filter(one => one.length > 0)
.map((one) => parseInt(one)); .map(one => parseInt(one));
this.loadedKeys = item.path this.loadedKeys = item.path
.split(".") .split(".")
.filter((one) => one.length > 0) .filter(one => one.length > 0)
.map((one) => parseInt(one)); .map(one => parseInt(one));
this.expandedKeys.forEach(async (one) => await this.loadData(one)); this.expandedKeys.forEach(async one => await this.loadData(one));
this.currentSelect = item; this.currentSelect = item;
}, },
/** /**
@ -344,7 +351,7 @@ export default {
this.addModal = { this.addModal = {
show: false, show: false,
targetNode: null, targetNode: null,
isAdd: false, isAdd: false
}; };
}, },
async onDrop(info) { async onDrop(info) {
@ -388,12 +395,12 @@ export default {
await this.deleteBookmarks(); await this.deleteBookmarks();
resolve(); resolve();
}); });
}, }
}); });
} else if (key === "edit") { } else if (key === "edit") {
this.editData(); this.editData();
} else if (key === "pin") { } else if (key === "pin") {
let pin = this.homePinList.filter((one) => one.id && one.bookmarkId == item.bookmarkId); let pin = this.homePinList.filter(one => one.id && one.bookmarkId == item.bookmarkId);
if (pin.length > 0) { if (pin.length > 0) {
await HttpUtil.delete("/home/pin", { id: pin[0].id }); await HttpUtil.delete("/home/pin", { id: pin[0].id });
} else { } else {
@ -411,8 +418,8 @@ export default {
dealList(root, map[""], map); dealList(root, map[""], map);
let content = exportFileHead + root.outerHTML; let content = exportFileHead + root.outerHTML;
downloadFile(moment().format("YYYY-MM-DD") + "导出书签.html", content); downloadFile(moment().format("YYYY-MM-DD") + "导出书签.html", content);
}, }
}, }
}; };
</script> </script>

View File

@ -1,164 +0,0 @@
<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>
&nbsp;<a @click="() => cancel(record.id)">取消</a>
</span>
<div v-else>
<a :disabled="currentEditCache" @click="() => edit(record.id)">编辑</a>&nbsp;
<a-popconfirm title="确认删除吗?" ok-text="" cancel-text="" @confirm="() => deleteOne(record.id)">
<a :disabled="currentEditCache">删除</a>
</a-popconfirm>&nbsp;
<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>

View File

@ -1,5 +1,5 @@
<template> <template>
<div v-if="userInfo" class="userInfo"> <div class="userInfo">
<div class="icon"> <div class="icon">
<img :src="userInfo.icon" class="full" /> <img :src="userInfo.icon" class="full" />
<label class="full"> <label class="full">
@ -12,8 +12,7 @@
<div class="baseInfo"> <div class="baseInfo">
<div class="item"> <div class="item">
<a-tooltip title="点击修改" v-if="currentAction != 'name'"> <a-tooltip title="点击修改" v-if="currentAction != 'name'">
<span style="font-size: 2em; cursor: pointer" <span style="font-size: 2em; cursor: pointer" @click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
@click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
</a-tooltip> </a-tooltip>
<div class="inputGroup" v-else-if="currentAction === 'name'"> <div class="inputGroup" v-else-if="currentAction === 'name'">
<a-input type="text" v-model="name" placeholder="修改昵称" /> <a-input type="text" v-model="name" placeholder="修改昵称" />
@ -27,9 +26,7 @@
<div class="item"> <div class="item">
<span style="width: 5em">密码</span> <span style="width: 5em">密码</span>
<a-tooltip title="点击修改" v-if="currentAction != 'password'"> <a-tooltip title="点击修改" v-if="currentAction != 'password'">
<span style="cursor: pointer" <span style="cursor: pointer" @click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********" }}</span>
@click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********"
}}</span>
</a-tooltip> </a-tooltip>
<div class="inputGroup" v-else-if="currentAction === 'password'"> <div class="inputGroup" v-else-if="currentAction === 'password'">
<a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" /> <a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" />
@ -61,24 +58,26 @@
<a-tooltip title="搜索框默认搜索引擎"> <a-tooltip title="搜索框默认搜索引擎">
<span style="width: 5em">搜索</span> <span style="width: 5em">搜索</span>
</a-tooltip> </a-tooltip>
<div @click="showSearchEngineManage=true" style=" cursor: pointer;color:blue">管理搜索引擎</div> <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> </div>
</div> </div>
<a-modal v-model="showSearchEngineManage" title="管理搜索引擎" :footer="null" width="70%">
<manage-search-engine />
</a-modal>
</div> </div>
</template> </template>
<script> <script>
import manageSearchEngine from "./components/manageSearchEngine.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import HttpUtil from "@/util/HttpUtil"; import HttpUtil from "@/util/HttpUtil";
export default { export default {
name: "UserInfo", name: "UserInfo",
components: { manageSearchEngine },
data() { data() {
return { return {
currentAction: null, //,name,password,email currentAction: null, //,name,password,email
@ -87,11 +86,21 @@ export default {
password: "", password: "",
rePassword: "", rePassword: "",
email: "", email: "",
showSearchEngineManage: false
}; };
}, },
computed: { computed: {
...mapState("globalConfig", ["userInfo"]) ...mapState("globalConfig", ["userInfo"]),
defaultSearchEngine() {
switch (this.userInfo.defaultSearchEngine) {
case "baidu":
return "百度";
case "google":
return "谷歌";
case "bing":
return "Bing";
}
return "";
},
}, },
methods: { methods: {
async changeIcon(e) { async changeIcon(e) {
@ -118,18 +127,21 @@ export default {
url = "/baseInfo/password"; url = "/baseInfo/password";
body = { body = {
oldPassword: this.oldPassword, oldPassword: this.oldPassword,
password: this.password password: this.password,
}; };
} else if (this.currentAction === "email") { } else if (this.currentAction === "email") {
url = "/baseInfo/email"; url = "/baseInfo/email";
body = { oldPassword: this.oldPassword, email: this.email }; body = { oldPassword: this.oldPassword, email: this.email };
} else if (this.currentAction === "defaultSearchEngine") {
url = "/baseInfo/updateSearchEngine";
body = { defaultSearchEngine: e };
} }
await HttpUtil.post(url, null, body); await HttpUtil.post(url, null, body);
await this.$store.dispatch("globalConfig/refreshUserInfo"); await this.$store.dispatch("globalConfig/refreshUserInfo");
this.$message.success("操作成功"); this.$message.success("操作成功");
this.currentAction = null; this.currentAction = null;
} },
} },
}; };
</script> </script>
@ -183,7 +195,6 @@ export default {
border-bottom: 1px solid #ebebeb; border-bottom: 1px solid #ebebeb;
display: flex; display: flex;
.inputGroup { .inputGroup {
flex: 1; flex: 1;
} }

View File

@ -1,76 +1,27 @@
<template> <template>
<div class="ssoAddBookmark"> <div class="ssoAddBookmark">
<div class="body"> 正在添加请稍后
<div> <!-- <button @click="closeIframe">关闭</button> -->
<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> </div>
</template> </template>
<script> <script>
import HttpUtil from "@/util/HttpUtil"; import HttpUtil from "@/util/HttpUtil";
import { mapState } from "vuex"; import { TREE_DATA, addNode } from "@/store/modules/treeData";
import { TREE_DATA, TOTAL_TREE_DATA, addNode, deleteData } from "@/store/modules/treeData";
export default { export default {
data() { data() {
return { return {
breadList: [],
showAddInput: false,
addFolderName: "",
form: { form: {
name: null, name: null,
url: null, url: null,
icon: null,
iconUrl: null,
type: 0, type: 0,
path: "", path: ""
}, }
}; };
}, },
computed: {
...mapState(TREE_DATA, [TOTAL_TREE_DATA]),
dataList() {
let path = this.getCurrentPath();
return this.totalTreeData[path] ? this.totalTreeData[path] : [];
},
},
mounted() { mounted() {
// //
window.addEventListener("message", (event) => { window.addEventListener("message", event => {
if (!event.data.code) { if (!event.data.code) {
return; return;
} }
@ -79,13 +30,11 @@ export default {
console.log("新增书签"); console.log("新增书签");
this.form.name = event.data.data.name; this.form.name = event.data.data.name;
this.form.url = event.data.data.url; this.form.url = event.data.data.url;
this.form.icon = event.data.data.icon; this.addBookmark();
this.form.iconUrl = event.data.data.iconUrl;
} }
}); });
console.log("向父节点获取数据"); console.log("向父节点获取数据");
window.parent.postMessage({ code: "getBookmarkData", receiver: "content" }, "*"); window.parent.postMessage({ code: "getBookmarkData", receiver: "content" }, "*");
this.$refs.nameInput.focus();
}, },
methods: { methods: {
closeIframe() { closeIframe() {
@ -93,174 +42,22 @@ export default {
}, },
// //
async addBookmark() { async addBookmark() {
this.form.path = this.getCurrentPath();
let res = await HttpUtil.put("/bookmark", null, this.form); let res = await HttpUtil.put("/bookmark", null, this.form);
this.$message.success("添加成功"); this.$message.success("添加成功");
await this.$store.dispatch(TREE_DATA + "/" + addNode, { await this.$store.dispatch(TREE_DATA + "/" + addNode, { sourceNode: null, targetNode: res });
sourceNode: this.breadList.length == 0 ? null : this.breadList[this.breadList.length - 1],
targetNode: res,
});
setTimeout(this.closeIframe, 500); 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> </script>
<style lang="less" scoped> <style lang="less" scoped>
.ssoAddBookmark { .ssoAddBookmark {
display: flex; display: flex;
flex-direction: column; justify-content: center;
padding: 0.5em; align-items: center;
padding-bottom: 1em;
background: white; background: white;
width: 100%; width: 100%;
height: 100vh; 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> </style>

View File

@ -5,23 +5,18 @@
源码地址 源码地址
<a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a> <a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a>
</div> </div>
<div>
当前版本{{ appVersion }}&emsp;&emsp;<a v-if="showNewVersion"
href="https://github.com/FleyX/bookmark/blob/master/DEPLOY.md">最新版本{{ latestVersion
}}</a>
</div>
<div> <div>
使用教程 使用教程
<a href="https://blog.fleyx.com/blog/detail/20220329" target="_blank">点击跳转</a> <a href="https://blog.fleyx.com/blog/detail/20220329" target="_blank">点击跳转</a>
</div> </div>
<div> <div>
浏览器插件 浏览器插件
<a href="/static/bookmarkBrowserPlugin.zip" download="浏览器插件.zip" <a href="/static/bookmarkBrowserPlugin.7z" download="浏览器插件.7z" target="_blank">点击下载</a>
target="_blank">最新版本{{ serverConfig.map.pluginVersion }}(注意更新)</a>
,使用详情请参考使用教程 ,使用详情请参考使用教程
</div> </div>
<div>交流反馈qq群150056494,邮箱fleyx20@outlook.com</div> <div>交流反馈qq群150056494,邮箱fleyx20@outlook.com</div>
<div> <div>
统计
<a href="https://qiezi.fleyx.com" style="" target="_blank"> <a href="https://qiezi.fleyx.com" style="" target="_blank">
<div id="qieziStatisticHtmlHostPv" style="display: none"> <div id="qieziStatisticHtmlHostPv" style="display: none">
总访问次数: 总访问次数:
@ -39,51 +34,17 @@
</template> </template>
<script> <script>
import { mapState } from "vuex";
import { GLOBAL_CONFIG, SERVER_CONFIG } from "@/store/modules/globalConfig";
import httpUtil from "@/util/HttpUtil";
export default { export default {
name: "about", name: "about",
data() { mounted() {
return { window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
appVersion: "1.4", // let script = document.createElement("script");
latestVersion: null, script.type = "text/javascript";
showNewVersion: false script.defer = true;
}; script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
}, document.getElementsByTagName("head")[0].appendChild(script);
computed: { ...mapState(GLOBAL_CONFIG, [SERVER_CONFIG]) },
async mounted() {
//
let res = await httpUtil.http.default.get("https://s3.fleyx.com/picbed/bookmark/config.json");
console.log(res);
this.latestVersion = res.data.latestAppVersion;
this.showNewVersion = this.checkVersion(this.appVersion, this.latestVersion);
},
methods: {
checkVersion: function(version, latestVersion) {
if (version === latestVersion) {
return false;
}
let versions = version.split(".");
let latestVersions = latestVersion.split(".");
for (let i = 0; i < versions.length; i++) {
if (i >= latestVersions.length) {
return false;
}
let versionNum = parseInt(versions[i]);
let latestVersionNum = parseInt(latestVersions[i]);
if (versionNum !== latestVersionNum) {
return versionNum < latestVersionNum;
}
}
return true;
}
} }
} };
;
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -24,7 +24,7 @@
</div> </div>
<div class="thirdPart"> <div class="thirdPart">
<span>第三方登陆</span> <span>第三方登陆</span>
<a-tooltip title="github登陆" class="oneIcon" placement="bottom"> <a-tooltip v-if="serverConfig.proxyExist" title="github登陆" class="oneIcon" placement="bottom">
<a-icon type="github" @click="toGithub" style="font-size: 1.4em" /> <a-icon type="github" @click="toGithub" style="font-size: 1.4em" />
</a-tooltip> </a-tooltip>
</div> </div>
@ -39,44 +39,39 @@ import Header from "@/components/public/Switch.vue";
import httpUtil from "../../../util/HttpUtil.js"; import httpUtil from "../../../util/HttpUtil.js";
import { mapMutations, mapState } from "vuex"; import { mapMutations, mapState } from "vuex";
import HttpUtil from "../../../util/HttpUtil.js"; import HttpUtil from "../../../util/HttpUtil.js";
export default { export default {
name: "Login", name: "Login",
components: { components: {
Header Header,
}, },
computed: { computed: {
...mapState("globalConfig", ["serverConfig"]) ...mapState("globalConfig", ["serverConfig"]),
}, },
data() { data() {
return { return {
form: { form: {
str: "", str: "",
password: "", password: "",
rememberMe: false rememberMe: false,
}, },
rules: { rules: {
str: [ str: [
{ required: true, message: "请输入用户名", trigger: "blur" }, { required: true, message: "请输入用户名", trigger: "blur" },
{ min: 1, max: 50, message: "最短1最长50", trigger: "change" } { min: 1, max: 50, message: "最短1最长50", trigger: "change" },
], ],
password: [ password: [
{ required: true, message: "请输入密码", trigger: "blur" }, { required: true, message: "请输入密码", trigger: "blur" },
{ pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" } { pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" },
] ],
}, },
loading: false, // loading: false, //
oauthLogining: false, //true:oauth oauthLogining: false, //true:oauth
page: null, //oauth page: null, //oauth
redirect: null
}; };
}, },
async created() { async created() {
let _this = this; let _this = this;
window.addEventListener("storage", this.storageDeal.bind(this)); window.addEventListener("storage", this.storageDeal.bind(this));
if (this.$route.query.to) {
this.redirect = atob(this.$route.query.to);
}
}, },
destroyed() { destroyed() {
window.removeEventListener("storage", this.storageDeal); window.removeEventListener("storage", this.storageDeal);
@ -90,7 +85,7 @@ export default {
this.loading = true; this.loading = true;
let token = await httpUtil.post("/user/login", null, this.form); let token = await httpUtil.post("/user/login", null, this.form);
await this.$store.dispatch("globalConfig/setToken", token); await this.$store.dispatch("globalConfig/setToken", token);
this.$router.replace(this.redirect ? this.redirect : "/"); this.$router.replace("/");
} finally { } finally {
this.loading = false; this.loading = false;
} }
@ -131,8 +126,8 @@ export default {
} finally { } finally {
this.loading = false; this.loading = false;
} }
} },
} },
}; };
</script> </script>
@ -140,13 +135,11 @@ export default {
.form { .form {
margin: 0.3rem; margin: 0.3rem;
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
.reset { .reset {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
} }
.thirdPart { .thirdPart {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View File

@ -1,21 +1,20 @@
module.exports = { module.exports = {
lintOnSave: false, lintOnSave: false,
configureWebpack: { configureWebpack: {
devtool: "source-map" devtool: "source-map"
}, },
devServer: { devServer: {
port: 4531, proxy: {
proxy: { "/bookmark/api": {
"/bookmark/api": { //这里最好有一个 /
//这里最好有一个 / target: "http://localhost:8088", // 服务器端接口地址
target: "http://localhost:8088", // 服务器端接口地址 ws: true, //如果要代理 websockets配置这个参数
ws: true, //如果要代理 websockets配置这个参数 // 如果是https接口需要配置这个参数
// 如果是https接口需要配置这个参数 changeOrigin: true, //是否跨域
changeOrigin: true, //是否跨域 pathRewrite: {
pathRewrite: { "^/bookmark/api": "/bookmark/api"
"^/bookmark/api": "/bookmark/api" }
} }
} }
} }
}
}; };

View File

@ -3,11 +3,8 @@ base=$(cd "$(dirname "$0")";pwd)
echo $base echo $base
cd $base cd $base
cd 浏览器插件/bookmarkBrowserPlugin
zip -q -r ../../bookmark_front/public/static/bookmarkBrowserPlugin.zip *
cd ../../
# 前端打包 # 前端打包
docker run --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 --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/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"

View File

@ -14,7 +14,6 @@ services:
- ./data/mysql/my.cnf:/etc/mysql/my.cnf - ./data/mysql/my.cnf:/etc/mysql/my.cnf
- /etc/localtime:/etc/localtime - /etc/localtime:/etc/localtime
- ./data/timezone:/etc/timezone - ./data/timezone:/etc/timezone
restart: unless-stopped
environment: environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=bookmark - MYSQL_DATABASE=bookmark
@ -26,7 +25,6 @@ services:
- /etc/localtime:/etc/localtime - /etc/localtime:/etc/localtime
- ./data/timezone:/etc/timezone - ./data/timezone:/etc/timezone
- ./data/redis:/data - ./data/redis:/data
restart: unless-stopped
networks: networks:
- bookmark - bookmark
@ -40,7 +38,6 @@ services:
- ./bookmark_front/dist:/opt/dist - ./bookmark_front/dist:/opt/dist
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf - ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
- ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public - ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public
restart: unless-stopped
ports: ports:
- 8080:8080 - 8080:8080
@ -63,7 +60,6 @@ services:
- ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar - ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar
- ${BOOKMARK_FILE_SAVE_PATH}:/opt/files - ${BOOKMARK_FILE_SAVE_PATH}:/opt/files
working_dir: /opt/app working_dir: /opt/app
restart: unless-stopped
command: command:
- /bin/bash - /bin/bash
- -c - -c

View File

@ -0,0 +1,15 @@
{
"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"]
}
}]
]
}

View File

@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -0,0 +1,33 @@
// 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'
}
}

View File

@ -0,0 +1,4 @@
/node_modules
/*.log
/dist
/dist-zip

View File

@ -0,0 +1,5 @@
{
"singleQuote": true,
"printWidth": 180,
"trailingComma": "es5"
}

View File

@ -0,0 +1,68 @@
{
"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"
}
}

View File

@ -0,0 +1,53 @@
#!/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();

View File

@ -0,0 +1,52 @@
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.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,32 @@
{
"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"]
}
]
}

View File

@ -0,0 +1,17 @@
<template>
<div>
<p>Hello world!这是选项页</p>
</div>
</template>
<script>
export default {
name: 'App',
};
</script>
<style scoped>
p {
font-size: 20px;
}
</style>

View File

@ -0,0 +1,15 @@
<!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>

View File

@ -0,0 +1,10 @@
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),
});

View File

@ -0,0 +1,20 @@
<template>
<div class="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style>
.app {
width: 600px;
height: 580px;
}
</style>

View File

@ -0,0 +1,18 @@
<!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>

View File

@ -0,0 +1,55 @@
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),
});

View File

@ -0,0 +1,74 @@
<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>

View File

@ -0,0 +1,92 @@
<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>

View File

@ -0,0 +1,9 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
Vue.use(VueRouter);
export default new VueRouter({
routes,
});

View File

@ -0,0 +1,50 @@
<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>

View File

@ -0,0 +1,37 @@
<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>

View File

@ -0,0 +1,13 @@
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.

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
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);
}
});

View File

@ -0,0 +1,5 @@
import * as types from './mutation-types'
export const setFoo = ({commit}, payload) => {
commit(types.UPDATE_FOO, payload)
}

View File

@ -0,0 +1 @@
export const foo = (state) => state.foo

View File

@ -0,0 +1,17 @@
import Vue from 'vue';
import Vuex from 'vuex';
import * as getters from './getters';
import mutations from './mutations';
import * as actions from './actions';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
foo: 'bar',
},
getters,
mutations,
actions,
});

View File

@ -0,0 +1 @@
export const UPDATE_FOO = 'UPDATE_FOO'

View File

@ -0,0 +1,7 @@
import * as types from './mutation-types'
export default {
[types.UPDATE_FOO] (state, payload) {
state.foo = payload
}
}

View File

@ -0,0 +1,16 @@
// const baseUri = ;
var baseUri;
if (process.env.NODE_ENV === 'development') {
baseUri = 'http://localhost:3000';
// baseUri = 'https://bm.tapme.top';
} else {
baseUri = 'https://bm.tapme.top';
}
const config = {
baseUrl: baseUri + '/bookmark/api',
ssoUrl: baseUri + '/userSpace/ssoAuth',
};
export default config;

View File

@ -0,0 +1,37 @@
import config from './config';
import axios from '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');
if (window.envType === 'background') {
window.open(config.ssoUrl);
} else {
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);
}
);
export default axios;

View File

@ -0,0 +1,123 @@
const webpack = require('webpack');
const ejs = require('ejs');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtensionReloader = require('webpack-extension-reloader');
const { VueLoaderPlugin } = require('vue-loader');
const { version } = require('./package.json');
const config = {
mode: process.env.NODE_ENV,
context: __dirname + '/src',
entry: {
background: './background.js',
'popup/popup': './popup/popup.js',
'options/options': './options/options.js',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
},
resolve: {
extensions: ['.js', '.vue'],
},
module: {
rules: [
{
test: /\.vue$/,
loaders: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /\.sass$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader?indentedSyntax'],
},
{
test: /\.(png|jpg|jpeg|gif|svg|ico)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/images/',
emitFile: false,
},
},
{
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/fonts/',
emitFile: false,
},
},
],
},
plugins: [
new webpack.DefinePlugin({
global: 'window',
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new CopyWebpackPlugin([
{ from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
{ from: 'static', to: 'static' },
{ from: 'static/fonts', to: 'fonts' },
{ from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml },
{ from: 'options/options.html', to: 'options/options.html', transform: transformHtml },
{
from: 'manifest.json',
to: 'manifest.json',
transform: content => {
const jsonContent = JSON.parse(content);
jsonContent.version = version;
if (config.mode === 'development') {
jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'";
}
return JSON.stringify(jsonContent, null, 2);
},
},
]),
],
};
if (config.mode === 'production') {
config.plugins = (config.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
},
}),
]);
}
if (process.env.HMR === 'true') {
config.plugins = (config.plugins || []).concat([
new ExtensionReloader({
manifest: __dirname + '/src/manifest.json',
}),
]);
}
function transformHtml(content) {
return ejs.render(content.toString(), {
...process.env,
});
}
module.exports = config;

View File

@ -4,18 +4,17 @@ chrome.runtime.onInstalled.addListener(() => {
title: '添加到书签', title: '添加到书签',
id: "addBookmark", id: "addBookmark",
}, },
() => console.debug("创建右键菜单成功") () => console.log("创建右键菜单成功")
); );
}); });
chrome.contextMenus.onClicked.addListener(async function (info, tab) { chrome.contextMenus.onClicked.addListener(async function (info, tab) {
console.debug(info, tab); console.log(info, tab);
let body = { let body = {
name: tab.title, name: tab.title,
url: tab.url, url: tab.url,
iconUrl: tab.favIconUrl
}; };
sendToContent(tab.id, { code: "addBookmark", data: body, token: await getVal("token") }); sendToContent(tab.id, { code: "addBookmark", data: body, token: await getVal("token") });
}); });
@ -27,15 +26,14 @@ chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => {
return; return;
} }
sendResponse("ok"); sendResponse("ok");
console.debug("收到消息:", data, sender); console.log("收到消息:", data, sender);
if (data.code == 'setToken') { if (data.code == 'setToken') {
await setVal("token", data.data); await setVal("token", data.data);
// sendToContent // sendToContent
await sendToContent(sender.tab.id, { code: "setTokenOk" }); await sendToContent(sender.tab.id, { code: "setTokenOk" });
} else if (data.code == 'getToken') { } else if (data.code == 'getToken') {
let token = await getVal("token"); let token = await getVal("token");
sendToPopup({ code: "setToken", data: await getVal("token") });
sendToPopup({ code: "setToken", data: token });
} else if (data.code == "clearToken") { } else if (data.code == "clearToken") {
await clearVal("token"); await clearVal("token");
} }
@ -47,10 +45,10 @@ chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => {
* @param {*} data * @param {*} data
*/ */
function sendToContent (tabId, data) { function sendToContent (tabId, data) {
console.debug(tabId, data); console.log(tabId, data);
data.receiver = "content"; data.receiver = "content";
chrome.tabs.sendMessage(tabId, data, res => { chrome.tabs.sendMessage(tabId, data, res => {
console.debug("接受响应", res); console.log("接受响应", res);
}) })
} }
@ -60,7 +58,7 @@ function sendToContent (tabId, data) {
*/ */
function sendToPopup (data) { function sendToPopup (data) {
data.receiver = "popup"; data.receiver = "popup";
chrome.runtime.sendMessage(data, res => console.debug(res)); chrome.runtime.sendMessage(data, res => console.log(res));
} }
/** /**
@ -72,7 +70,7 @@ function sendToPopup (data) {
function setVal (key, val) { function setVal (key, val) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
chrome.storage.local.set({ [key]: val }, function () { chrome.storage.local.set({ [key]: val }, function () {
console.debug("设置值成功:", key, val) console.log("设置值成功:", key, val)
resolve(); resolve();
}) })
}) })
@ -85,13 +83,8 @@ function setVal (key, val) {
*/ */
function getVal (key) { function getVal (key) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
chrome.storage.local.get([key], async function (res) { chrome.storage.local.get([key], function (res) {
if (key === 'token' && !checkTokenValid(res[key])) { console.log("取值成功", res);
console.debug("token过期");
await clearVal("token");
res[key] = null;
}
console.debug("取值成功", res);
resolve(res[key]); resolve(res[key]);
}) })
}) })
@ -100,28 +93,8 @@ function getVal (key) {
function clearVal (key) { function clearVal (key) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
chrome.storage.local.remove(key, function () { chrome.storage.local.remove(key, function () {
console.debug("remove成功", key); console.log("remove成功", key);
resolve(); resolve();
}) })
}) })
}
/**
* 检查token是否有效
* @param {*} token
* @returns
*/
function checkTokenValid (token) {
try {
if (token && token.trim().length > 0) {
//检查token是否还有效
let content = JSON.parse(atob(token.split(".")[1]));
if (content.exp > Date.now() / 1000) {
return true;
}
}
} catch (err) {
console.error(token, err);
}
return false;
} }

View File

@ -4,15 +4,8 @@
"version": "1.0", "version": "1.0",
"manifest_version": 3, "manifest_version": 3,
"permissions": ["contextMenus", "storage"], "permissions": ["contextMenus", "storage"],
"chrome_url_overrides": {
"newtab": "tab/index.html"
},
"action": { "action": {
"default_popup": "popup/index.html", "default_popup": "popup/index.html"
"default_icon": {
"48": "static/icons/favicon.png",
"128": "static/icons/favicon.png"
}
}, },
"icons": { "icons": {
"48": "static/icons/favicon.png", "48": "static/icons/favicon.png",

View File

@ -8,7 +8,7 @@
html, html,
body { body {
padding: 0.2em; padding: 0.2em;
min-width: 8em; width: 8em;
} }
#content { #content {
color: red; color: red;
@ -16,18 +16,14 @@
</style> </style>
</head> </head>
<body> <body>
<a id="login" href="https://bm.fleyx.com/userSpace/ssoAuth" target="_blank">点击登录</a> <a id="login" href="https://fleyx.com/userSpace/ssoAuth" target="_blank">点击登录</a>
<div id="action" style="display: none"> <div id="action" style="display: none">
<button id="logout">退出登陆</button> <button id="logout">退出登陆</button>
<!-- <button title="同步云端书签到浏览器(覆盖本地)">同步云端书签</button> --> <!-- <button title="同步云端书签到浏览器(覆盖本地)">同步云端书签</button> -->
<!-- <button title="同步浏览器书签到云端(覆盖云端)">同步浏览器</button> --> <!-- <button title="同步浏览器书签到云端(覆盖云端)">同步浏览器</button> -->
</div> </div>
<p id="content"></p> <p id="content"></p>
<div class="bottom"> <div class="bottom">插件版本:<span id="version"></span></div>
<div>插件版本:<span id="version"></span></div>
<div>最新版本:<a href="" id="newestVersion">最新版本</a></div>
<div><a id="about" target="_blank">关于</a></span></div>
</div>
<script type="text/javascript" src="/static/js/axios.min.js"></script> <script type="text/javascript" src="/static/js/axios.min.js"></script>
<script type="text/javascript" src="/static/js/config.js"></script> <script type="text/javascript" src="/static/js/config.js"></script>
<script type="text/javascript" src="/popup/index.js"></script> <script type="text/javascript" src="/popup/index.js"></script>

View File

@ -1,5 +1,5 @@
console.debug("asdf"); console.log("asdf");
console.debug(bookmarkHost); console.log(bookmarkHost);
var token; var token;
var login = document.getElementById("login"); var login = document.getElementById("login");
@ -9,20 +9,14 @@ var action = document.getElementById("action");
//初始化 //初始化
login.href = bookmarkHost + "/manage/sso/auth"; login.href = bookmarkHost + "/manage/sso/auth";
document.getElementById("version").innerText = version; document.getElementById("version").innerText = version;
document.getElementById("about").href = bookmarkHost + "/public/about";
sendToBg("getToken", null); sendToBg("getToken", null);
let newestBlock = document.getElementById("newestVersion");
newestBlock.href = bookmarkHost + "/static/bookmarkBrowserPlugin.zip";
let res = await axios.get("/common/config/global");
console.debug(res);
newestBlock.innerText = res.data.data.map.pluginVersion;
})(); })();
/** /**
* 退出登陆 * 退出登陆
*/ */
document.getElementById("logout").addEventListener("click", () => { document.getElementById("logout").addEventListener("click", () => {
console.debug("click"); console.log("click");
sendToBg("clearToken", null); sendToBg("clearToken", null);
action.style.display = "none"; action.style.display = "none";
login.style.display = "block"; login.style.display = "block";
@ -33,7 +27,7 @@ document.getElementById("logout").addEventListener("click", () => {
* @param {*} data * @param {*} data
*/ */
function sendToBg (code, data) { function sendToBg (code, data) {
chrome.runtime.sendMessage({ code, data, receiver: "background" }, res => console.debug(res)); chrome.runtime.sendMessage({ code, data, receiver: "background" }, res => console.log(res));
} }
@ -43,7 +37,7 @@ chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => {
return; return;
} }
sendResponse("ok"); sendResponse("ok");
console.debug("popup收到消息", data); console.log("popup收到消息", data);
if (data.code == 'setToken') { if (data.code == 'setToken') {
token = data.data; token = data.data;
if (token) { if (token) {

Some files were not shown because too many files have changed in this diff Show More