Compare commits
No commits in common. "master" and "1.2" have entirely different histories.
24
.drone.yml
24
.drone.yml
@ -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
|
20
DEPLOY.md
20
DEPLOY.md
@ -1,20 +1,10 @@
|
||||
本程序基于 docker 来进行部署,使用 docker-compose 管理服务。
|
||||
|
||||
**注意,仅在 x86 环境下测试,arm 下不保证可用性(目前测试可用)**
|
||||
部署过程如下:
|
||||
|
||||
## 首次部署
|
||||
**注意,仅在 x86 环境下测试**
|
||||
|
||||
0. 克隆代码`git clone https://github.com/FleyX/bookmark.git`
|
||||
1. 进入文件夹`cd bookmark`
|
||||
2. 安装新版的 docker,docker-compose,zip `apt install docker docker-compose zip`
|
||||
1. 安装新版的 docker 和 docker-compose(注意:以下操作均在项目根目录下执行)
|
||||
2. 执行`build.sh`编译前后端代码
|
||||
3. 修改.env 文件中的参数,改为你的实际配置
|
||||
4. 修改`浏览器插件/bookmarkBrowserPlugin/static/js/config.js`中的 bookmarkHost,改为你的实际部署路径
|
||||
5. 修改`浏览器插件/bookmarkBrowserPlugin/tab/index.html`中的`<meta http-equiv="Refresh" content="0;url=https://bm.fleyx.com" />`,将 url 改为你的实际部署地址
|
||||
6. 执行`build.sh`编译前后端代码 `bash build.sh`
|
||||
7. root 权限运行 `docker-compose up -d` 启动服务。
|
||||
|
||||
## 更新系统
|
||||
|
||||
0. 代码库更新`cd bookmark;git pull`
|
||||
1. 执行`build.sh`编译前后端代码 `bash build.sh`
|
||||
2. root 权限运行 `docker-compose restart` 启动服务
|
||||
4. root 权限运行 `docker-compose up -d` 启动服务。
|
||||
|
46
README.md
46
README.md
@ -1,12 +1,10 @@
|
||||
![图片](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)
|
||||
|
||||
# 缘由
|
||||
|
||||
@ -20,37 +18,27 @@
|
||||
|
||||
# 主要功能
|
||||
|
||||
帮助文档:[点击跳转](https://blog.fleyx.com/blog/detail/20220329/)
|
||||
使用帮助见:[使用帮助](https://github.com/FleyX/bookmark/blob/master/HELP.md)
|
||||
|
||||
- 支持从 chrome,edge,firefox 等浏览器导入书签数据。
|
||||
- 支持从 OneEnv 导入书签数据
|
||||
- 树型多级目录支持
|
||||
- 支持导出标准 html 书签文件
|
||||
- 强大的检索功能,支持拼音检索
|
||||
- 支持浏览器插件,安装插件以后可右键添加书签
|
||||
1. 基础的书签增删改查功能。支持 chrome、firefox 等浏览器书签文件导入,导出。
|
||||
|
||||
# 更新日志
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214126.png?imageView2/2/w/1920)
|
||||
|
||||
## 1.4.1
|
||||
2. 强大的书签检索功能,毫秒级的关键字检索。
|
||||
|
||||
- 修复书签名过长无法导入问题
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214210.png?imageView2/2/w/1920)
|
||||
|
||||
## 1.4
|
||||
3. 首页功能,参考 bing 首页实现
|
||||
|
||||
- 优化首图加载逻辑
|
||||
- 支持 OneEnv 备份文件导入
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214236.png?imageView2/2/w/1920)
|
||||
|
||||
## 1.3
|
||||
4. 移动端支持,手机端也可使用(部分功能比如拖拽等无法使用)
|
||||
|
||||
![pic](https://s3.fleyx.com/picbed/2023/08/Snipaste_2023-08-13_15-01-20.png)
|
||||
|
||||
搜索引擎支持自定义[#43](https://github.com/FleyX/bookmark/issues/43)
|
||||
|
||||
位置:右上角个人中心-管理搜索引擎
|
||||
![](https://qiniupic.fleyx.com/blog/20220329214312.png?imageView2/2/w/1920)
|
||||
|
||||
# TODO
|
||||
|
||||
- [x] 主页功能
|
||||
- [x] 拼音检索
|
||||
- [x] 书签导出
|
||||
- [x] 浏览器插件
|
||||
- 主页功能 Ok!
|
||||
- 拼音检索 Ok!
|
||||
- 书签导出 OK!
|
||||
- 侧边栏显示
|
||||
|
@ -22,18 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.15.3</version>
|
||||
<version>1.14.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.houbb</groupId>
|
||||
<artifactId>pinyin</artifactId>
|
||||
<version>0.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.44.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -69,7 +69,7 @@ public class BookmarkController {
|
||||
*/
|
||||
@RequestMapping("/uploadBookmarkFile")
|
||||
public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("path") String path) throws Exception {
|
||||
bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file, path);
|
||||
bookmarkService.parseBookmarkFile(UserContextHolder.get().getUserId(), file.getInputStream(), path);
|
||||
return Result.success(null);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
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
|
||||
@ -25,15 +28,6 @@ public interface HostIconDao {
|
||||
* @return {@link String}
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* 删除一条
|
||||
*
|
||||
* @param host host
|
||||
* @author FleyX
|
||||
*/
|
||||
@Delete("delete from host_icon where host=#{host}")
|
||||
void deleteByHost(String host);
|
||||
}
|
||||
|
@ -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.MoveNodeBody;
|
||||
import com.fanxb.bookmark.common.entity.po.Bookmark;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@ -55,7 +54,7 @@ public interface BookmarkService {
|
||||
* @author fanxb
|
||||
* @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: 详情
|
||||
|
@ -1,9 +1,9 @@
|
||||
package com.fanxb.bookmark.business.bookmark.service.impl;
|
||||
|
||||
import cn.hutool.core.codec.Base64Decoder;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fanxb.bookmark.business.api.UserApi;
|
||||
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.exception.CustomException;
|
||||
import com.fanxb.bookmark.common.util.*;
|
||||
import com.mysql.cj.conf.url.SingleConnectionUrl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
@ -36,24 +35,16 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.awt.print.Book;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -62,6 +53,7 @@ import java.util.stream.Collectors;
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 15:00
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@ -86,25 +78,18 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void parseBookmarkFile(int userId, MultipartFile file, String path) throws Exception {
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
public void parseBookmarkFile(int userId, InputStream stream, String path) throws Exception {
|
||||
Document doc = Jsoup.parse(stream, "utf-8", "");
|
||||
Elements elements = doc.select("html>body>dl>dt");
|
||||
//获取当前层sort最大值
|
||||
Integer sortBase = bookmarkDao.selectMaxSort(userId, path);
|
||||
if (sortBase == null) {
|
||||
sortBase = 0;
|
||||
}
|
||||
if (file.getOriginalFilename().endsWith(".db3")) {
|
||||
//处理db文件
|
||||
readFromOneEnv(bookmarks, userId, file, path, sortBase);
|
||||
} else {
|
||||
InputStream stream = file.getInputStream();
|
||||
Document doc = Jsoup.parse(stream, "utf-8", "");
|
||||
Elements elements = doc.select("html>body>dl>dt");
|
||||
for (int i = 0, length = elements.size(); i < length; i++) {
|
||||
dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
|
||||
}
|
||||
List<Bookmark> bookmarks = new ArrayList<>();
|
||||
for (int i = 0, length = elements.size(); i < length; i++) {
|
||||
dealBookmark(userId, elements.get(i), path, sortBase + i, bookmarks);
|
||||
}
|
||||
|
||||
//每一千条处理插入一次,批量更新搜索字段
|
||||
List<Bookmark> tempList = new ArrayList<>(1000);
|
||||
for (int i = 0; i < bookmarks.size(); i++) {
|
||||
@ -131,6 +116,7 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
* @param path 节点路径,不包含自身
|
||||
* @param sort 当前层级中的排序序号
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 14:49
|
||||
*/
|
||||
private void dealBookmark(int userId, Element ele, String path, int sort, List<Bookmark> bookmarks) {
|
||||
if (!DT.equalsIgnoreCase(ele.tagName())) {
|
||||
@ -139,7 +125,7 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
Element first = ele.child(0);
|
||||
if (A.equalsIgnoreCase(first.tagName())) {
|
||||
//说明为链接
|
||||
Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), ""
|
||||
Bookmark node = new Bookmark(userId, path, first.ownText(), first.attr("href"), first.attr("icon")
|
||||
, Long.parseLong(first.attr("add_date")) * 1000, sort);
|
||||
//存入数据库
|
||||
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: 插入一条书签,如果已经存在同名书签将跳过
|
||||
*
|
||||
* @param node node
|
||||
* @return boolean 如果已经存在返回true,否则false
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 17:25
|
||||
*/
|
||||
private boolean insertOne(Bookmark node) {
|
||||
//先根据name,userId,parentId获取此节点id
|
||||
@ -281,14 +217,11 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
bookmark.setUserId(userId);
|
||||
bookmark.setCreateTime(System.currentTimeMillis());
|
||||
bookmark.setAddTime(bookmark.getCreateTime());
|
||||
bookmark.setIcon(bookmark.getType() == 1 ? "" : getIconPath(bookmark.getUrl(), bookmark.getIcon(), bookmark.getIconUrl(), true));
|
||||
bookmark.setIcon(getIconPath(bookmark.getUrl()));
|
||||
//文件夹和书签都建立搜索key
|
||||
pinYinService.changeBookmark(bookmark);
|
||||
bookmarkDao.insertOne(bookmark);
|
||||
userApi.versionPlus(userId);
|
||||
if (StrUtil.isEmpty(bookmark.getIcon()) && bookmark.getType() == 0) {
|
||||
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
|
||||
}
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
@ -298,33 +231,13 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
bookmark.setUserId(userId);
|
||||
if (bookmark.getType() == 0) {
|
||||
pinYinService.changeBookmark(bookmark);
|
||||
bookmark.setIcon(getIconPath(bookmark.getUrl(), null, null, true));
|
||||
if (StrUtil.isEmpty(bookmark.getIcon())) {
|
||||
updateIconAsync(bookmark.getBookmarkId(), bookmark.getUrl(), userId);
|
||||
}
|
||||
bookmark.setIcon(getIconPath(bookmark.getUrl()));
|
||||
}
|
||||
bookmarkDao.editBookmark(bookmark);
|
||||
userApi.versionPlus(userId);
|
||||
return bookmark.getIcon();
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步更新书签icon
|
||||
*
|
||||
* @param id 书签id
|
||||
* @param url 书签url
|
||||
* @param userId userId
|
||||
*/
|
||||
private void updateIconAsync(int id, String url, int userId) {
|
||||
ThreadPoolUtil.execute(() -> {
|
||||
String icon = getIconPath(url, null, null, false);
|
||||
if (StrUtil.isEmpty(icon)) {
|
||||
return;
|
||||
}
|
||||
bookmarkDao.updateIcon(id, icon);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@ -374,10 +287,10 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
int size = 100;
|
||||
int start = 0;
|
||||
List<Bookmark> deal;
|
||||
while (!(deal = bookmarkDao.selectUserNoIcon(userId, start, size)).isEmpty()) {
|
||||
while ((deal = bookmarkDao.selectUserNoIcon(userId, start, size)).size() > 0) {
|
||||
start += size;
|
||||
deal.forEach(item -> {
|
||||
String icon = getIconPath(item.getUrl(), null, null, false);
|
||||
String icon = getIconPath(item.getUrl());
|
||||
if (StrUtil.isNotEmpty(icon)) {
|
||||
bookmarkDao.updateIcon(item.getBookmarkId(), icon);
|
||||
}
|
||||
@ -409,95 +322,58 @@ public class BookmarkServiceImpl implements BookmarkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取icon,通过网络获取,或者从base64还原
|
||||
* 获取icon
|
||||
*
|
||||
* @param url 书签url路径
|
||||
* @param icon base64编码的icon
|
||||
* @param iconUrl base64编码的文件,文件名,用于获取文件名后缀
|
||||
* @param quick 是否快速获取
|
||||
* @param url url
|
||||
* @return {@link String}
|
||||
* @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;
|
||||
try {
|
||||
URL urlObj = new URL(url);
|
||||
host = urlObj.getAuthority();
|
||||
host = urlObj.getHost();
|
||||
} catch (Exception e) {
|
||||
log.warn("url无法解析出domain:{}", url);
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isNotBlank(icon)) {
|
||||
//优先从base64还原出图片
|
||||
try {
|
||||
byte[] b = Base64Decoder.decode(icon.substring(icon.indexOf(",") + 1));
|
||||
String iconPath = saveToFile(iconUrl, host, b);
|
||||
hostIconDao.deleteByHost(host);
|
||||
hostIconDao.insert(host, iconPath);
|
||||
return iconPath;
|
||||
} catch (Exception e) {
|
||||
log.error("解析base64获取icon故障:{}", iconUrl, e);
|
||||
}
|
||||
}
|
||||
|
||||
String iconPath = hostIconDao.selectByHost(host);
|
||||
if (iconPath != null) {
|
||||
return iconPath;
|
||||
}
|
||||
//再根据url解析
|
||||
iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..128..256", quick);
|
||||
iconPath = saveFile(host, urlIconAddress + "/icon?url=" + host + "&size=16..64..256");
|
||||
if (StrUtil.isNotEmpty(iconPath)) {
|
||||
hostIconDao.insert(host, iconPath);
|
||||
}
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件到icon路径
|
||||
*
|
||||
* @param host host
|
||||
* @param url url
|
||||
* @param quick 是否快速获取,快速获取超时时间1s
|
||||
* @return {@link String}
|
||||
* @author FleyX
|
||||
*/
|
||||
private String saveFile(String host, String url, boolean quick) {
|
||||
try (Response res = (quick ? HttpUtil.getSHORT_CLIENT() : HttpUtil.getClient(false)).newCall(new Request.Builder().url(url)
|
||||
.header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36")
|
||||
.get().build()).execute()) {
|
||||
assert res.body() != null;
|
||||
if (!HttpUtil.checkIsOk(res.code())) {
|
||||
throw new CustomException("请求错误:" + res.code());
|
||||
private String saveFile(String host, String url) {
|
||||
try {
|
||||
try (Response res = HttpUtil.getClient(false).newCall(new Request.Builder().url(url)
|
||||
.header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36")
|
||||
.get().build()).execute()) {
|
||||
assert res.body() != null;
|
||||
if (!HttpUtil.checkIsOk(res.code())) {
|
||||
throw new CustomException("请求错误:" + res.code());
|
||||
}
|
||||
byte[] data = res.body().byteStream().readAllBytes();
|
||||
if (data.length > 0) {
|
||||
String iconUrl = res.request().url().toString();
|
||||
String fileName = URLEncoder.encode(host, StandardCharsets.UTF_8) + iconUrl.substring(iconUrl.lastIndexOf("."));
|
||||
String filePath = Paths.get(FileConstant.FAVICON_PATH, host.substring(0, 2), fileName).toString();
|
||||
FileUtil.writeBytes(data, Paths.get(CommonConstant.fileSavePath, filePath).toString());
|
||||
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) {
|
||||
log.error("url获取icon故障:{}", url, e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存到文件中
|
||||
*
|
||||
* @param iconUrl icon文件名
|
||||
* @param host host
|
||||
* @param b 数据
|
||||
* @return {@link String}
|
||||
* @author FleyX
|
||||
*/
|
||||
private String saveToFile(String iconUrl, String host, byte[] b) {
|
||||
String fileName = host.replace(":", ".") + iconUrl.substring(iconUrl.lastIndexOf("."));
|
||||
String filePath = Paths.get(FileConstant.FAVICON_PATH, host.replace("www", "").replaceAll("\\.", "").substring(0, 2), fileName).toString();
|
||||
FileUtil.writeBytes(b, Paths.get(CommonConstant.fileSavePath, filePath).toString());
|
||||
return File.separator + filePath;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,12 @@
|
||||
<artifactId>bookmark-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -1,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();
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package com.fanxb.bookmark.business.user.dao;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fanxb.bookmark.common.entity.po.User;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
@ -17,7 +16,7 @@ import java.util.List;
|
||||
* @date 2019/7/6 11:36
|
||||
*/
|
||||
@Component
|
||||
public interface UserDao extends BaseMapper<User> {
|
||||
public interface UserDao {
|
||||
|
||||
/**
|
||||
* Description: 新增一个用户
|
||||
@ -183,6 +182,16 @@ public interface UserDao extends BaseMapper<User> {
|
||||
@Select("select userId from user order by userId limit #{start},#{size}")
|
||||
List<Integer> selectUserIdPage(@Param("start") int start, @Param("size") int size);
|
||||
|
||||
/**
|
||||
* 更新用户搜索引擎
|
||||
*
|
||||
* @param userId userId
|
||||
* @param engine engine
|
||||
* @author fanxb
|
||||
* @date 2021/3/14
|
||||
**/
|
||||
@Update("update user set defaultSearchEngine=#{engine} where userId=#{userId}")
|
||||
void updateSearchEngine(@Param("userId") int userId, @Param("engine") String engine);
|
||||
|
||||
/**
|
||||
* 更新一个字段-一个条件
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -87,6 +87,6 @@ public class BaseInfoServiceImpl implements BaseInfoService {
|
||||
|
||||
@Override
|
||||
public void changeDefaultSearchEngine(User user) {
|
||||
userDao.updateById(user);
|
||||
userDao.updateSearchEngine(user.getUserId(), user.getDefaultSearchEngine());
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fanxb.bookmark.business.user.dao.UserDao;
|
||||
import com.fanxb.bookmark.business.user.service.OauthService;
|
||||
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||
import com.fanxb.bookmark.business.user.service.UserService;
|
||||
import com.fanxb.bookmark.business.user.vo.OauthBody;
|
||||
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||
@ -39,8 +38,6 @@ public class OauthServiceImpl implements OauthService {
|
||||
private String githubClientId;
|
||||
@Value("${OAuth.github.secret}")
|
||||
private String githubSecret;
|
||||
@Autowired
|
||||
private SearchEngineService searchEngineService;
|
||||
private final UserDao userDao;
|
||||
private final UserService userService;
|
||||
|
||||
@ -108,7 +105,6 @@ public class OauthServiceImpl implements OauthService {
|
||||
other.setLastLoginTime(System.currentTimeMillis());
|
||||
other.setVersion(0);
|
||||
userDao.addOne(other);
|
||||
searchEngineService.newUserInit(other.getUserId());
|
||||
return other;
|
||||
} else {
|
||||
if (!current.getEmail().equals(other.getEmail()) || !current.getGithubId().equals(other.getGithubId())) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.fanxb.bookmark.business.api.BookmarkApi;
|
||||
import com.fanxb.bookmark.business.user.constant.FileConstant;
|
||||
import com.fanxb.bookmark.business.user.dao.UserDao;
|
||||
import com.fanxb.bookmark.business.user.service.SearchEngineService;
|
||||
import com.fanxb.bookmark.business.user.service.UserService;
|
||||
import com.fanxb.bookmark.business.user.vo.LoginBody;
|
||||
import com.fanxb.bookmark.business.user.vo.RegisterBody;
|
||||
@ -21,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
@ -44,8 +42,6 @@ public class UserServiceImpl implements UserService {
|
||||
* 登陆最大重试次数
|
||||
*/
|
||||
private static final int LOGIN_COUNT = 5;
|
||||
@Autowired
|
||||
private SearchEngineService searchEngineService;
|
||||
|
||||
private final UserDao userDao;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
@ -87,7 +83,6 @@ public class UserServiceImpl implements UserService {
|
||||
* @author fanxb
|
||||
* @date 2019/7/6 11:30
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String register(RegisterBody body) {
|
||||
User user = userDao.selectByUsernameOrEmail(body.getUsername(), body.getEmail());
|
||||
if (user != null) {
|
||||
@ -107,7 +102,6 @@ public class UserServiceImpl implements UserService {
|
||||
user.setLastLoginTime(System.currentTimeMillis());
|
||||
user.setVersion(0);
|
||||
userDao.addOne(user);
|
||||
searchEngineService.newUserInit(user.getUserId());
|
||||
Map<String, String> data = new HashMap<>(1);
|
||||
data.put("userId", String.valueOf(user.getUserId()));
|
||||
return JwtUtil.encode(data, CommonConstant.jwtSecret, LONG_EXPIRE_TIME);
|
||||
|
@ -37,10 +37,16 @@
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--mybatis依赖-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
|
||||
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
|
||||
<!-- <version>2.0.1</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.3.2</version>
|
||||
<version>3.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -53,66 +59,21 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.2.18</version>
|
||||
<version>1.1.18</version>
|
||||
</dependency>
|
||||
|
||||
<!--数据库版本管理-->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>9.21.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
<version>9.21.1</version>
|
||||
<version>5.2.4</version>
|
||||
</dependency>
|
||||
<!--mysql jdbc依赖-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <!–邮件依赖–>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-starter-mail</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <!–减负依赖–>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.projectlombok</groupId>-->
|
||||
<!-- <artifactId>lombok</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<!-- <!–json工具依赖–>-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba</groupId>-->
|
||||
<!-- <artifactId>fastjson</artifactId>-->
|
||||
<!-- <version>1.2.83</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.elasticsearch.client</groupId>-->
|
||||
<!-- <artifactId>elasticsearch-rest-high-level-client</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.hutool</groupId>-->
|
||||
<!-- <artifactId>hutool-all</artifactId>-->
|
||||
<!-- <version>5.8.21</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!--单元测试-->
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
|
||||
<!--mysql jdbc依赖-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>mysql</groupId>-->
|
||||
<!-- <artifactId>mysql-connector-java</artifactId>-->
|
||||
<!-- <version>8.0.33</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!--邮件依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -128,7 +89,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
<version>1.2.73</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@ -140,7 +101,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.25</version>
|
||||
<version>5.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<!--单元测试-->
|
||||
|
@ -1,9 +1,6 @@
|
||||
package com.fanxb.bookmark.common.configuration;
|
||||
|
||||
import com.fanxb.bookmark.common.factory.CustomThreadFactory;
|
||||
import com.fanxb.bookmark.common.factory.ThreadPoolFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
@ -14,16 +11,15 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
* Created with IntelliJ IDEA
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2020/1/26
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class ScheduleConfig implements SchedulingConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
|
||||
|
||||
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(5, new CustomThreadFactory("schedule"));
|
||||
scheduledTaskRegistrar.setScheduler(service);
|
||||
log.info("自定义schedule线程池成功");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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> {
|
||||
}
|
@ -12,6 +12,7 @@ import java.util.List;
|
||||
* 类功能详述:
|
||||
*
|
||||
* @author fanxb
|
||||
* @date 2019/7/8 11:19
|
||||
*/
|
||||
@Data
|
||||
public class Bookmark {
|
||||
@ -35,7 +36,6 @@ public class Bookmark {
|
||||
private String name;
|
||||
private String url = "";
|
||||
private String icon = "";
|
||||
private String iconUrl;
|
||||
private Integer sort;
|
||||
private String searchKey = "";
|
||||
private Long addTime;
|
||||
@ -53,7 +53,7 @@ public class Bookmark {
|
||||
this.setUserId(userId);
|
||||
this.setPath(path);
|
||||
this.setType(FOLDER_TYPE);
|
||||
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||
this.setName(name);
|
||||
this.setAddTime(addTime);
|
||||
this.setSort(sort);
|
||||
this.setCreateTime(System.currentTimeMillis());
|
||||
@ -64,7 +64,7 @@ public class Bookmark {
|
||||
this.setUserId(userId);
|
||||
this.setPath(path);
|
||||
this.setType(BOOKMARK_TYPE);
|
||||
this.setName(name.length() > 2000 ? name.substring(0, 1999) : name);
|
||||
this.setName(name);
|
||||
this.setUrl(url);
|
||||
this.setIcon(icon);
|
||||
this.setSort(sort);
|
||||
|
@ -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;
|
||||
}
|
@ -2,10 +2,6 @@ package com.fanxb.bookmark.common.entity.po;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
@ -20,11 +16,9 @@ import java.util.Map;
|
||||
* @date 2019/7/4 20:14
|
||||
*/
|
||||
@Data
|
||||
@TableName("user")
|
||||
public class User {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer userId;
|
||||
private int userId;
|
||||
/**
|
||||
* 第三方github登陆id,-1说明非github登陆
|
||||
*/
|
||||
@ -36,7 +30,6 @@ public class User {
|
||||
/**
|
||||
* 是否未设置密码
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean noPassword;
|
||||
@JSONField(serialize = false)
|
||||
private String password;
|
||||
@ -49,10 +42,9 @@ public class User {
|
||||
* 书签同步版本
|
||||
*/
|
||||
private int version;
|
||||
|
||||
/**
|
||||
* 默认搜索引擎
|
||||
*/
|
||||
private Integer searchEngineId;
|
||||
private String defaultSearchEngine;
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ package com.fanxb.bookmark.common.entity.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局公共配置
|
||||
*
|
||||
@ -19,8 +17,4 @@ public class GlobalConfigVo {
|
||||
* bing每日一图地址
|
||||
*/
|
||||
private String bingImgSrc;
|
||||
/**
|
||||
* 浏览器插件版本plugin
|
||||
*/
|
||||
private Map<String, String> map;
|
||||
}
|
||||
|
@ -2,32 +2,23 @@ package com.fanxb.bookmark.common.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.fanxb.bookmark.common.constant.CommonConstant;
|
||||
import com.fanxb.bookmark.common.constant.NumberConstant;
|
||||
import com.fanxb.bookmark.common.constant.RedisConstant;
|
||||
import com.fanxb.bookmark.common.dao.GlobalConfigDao;
|
||||
import com.fanxb.bookmark.common.entity.po.GlobalConfigPo;
|
||||
import com.fanxb.bookmark.common.entity.vo.GlobalConfigVo;
|
||||
import com.fanxb.bookmark.common.service.ConfigService;
|
||||
import com.fanxb.bookmark.common.util.HttpUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author fanxb
|
||||
@ -39,12 +30,10 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final GlobalConfigDao globalConfigDao;
|
||||
|
||||
@Autowired
|
||||
public ConfigServiceImpl(StringRedisTemplate stringRedisTemplate, GlobalConfigDao globalConfigDao) {
|
||||
public ConfigServiceImpl(StringRedisTemplate stringRedisTemplate) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.globalConfigDao = globalConfigDao;
|
||||
}
|
||||
|
||||
@Value("${bing.host}")
|
||||
@ -54,11 +43,9 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
|
||||
@Override
|
||||
public GlobalConfigVo getGlobalConfig() {
|
||||
List<GlobalConfigPo> pos = globalConfigDao.selectByMap(Collections.emptyMap());
|
||||
Map<String, String> map = pos.stream().collect(Collectors.toMap(GlobalConfigPo::getCode, GlobalConfigPo::getValue));
|
||||
GlobalConfigVo vo = new GlobalConfigVo();
|
||||
vo.setProxyExist(HttpUtil.getProxyExist());
|
||||
vo.setBingImgSrc(getCacheBingImg());
|
||||
vo.setMap(map);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@ -69,7 +56,9 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
return str;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -77,19 +66,11 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
try {
|
||||
JSONObject bingObj = HttpUtil.getObj(bingHost + bingUrl, null, false);
|
||||
String path = bingObj.getJSONArray("images").getJSONObject(0).getString("url");
|
||||
String picUrl = bingHost + path;
|
||||
Request request = new Request.Builder().url(picUrl).build();
|
||||
try (Response res = HttpUtil.getClient(false).newCall(request).execute()) {
|
||||
byte[] bytes = res.body().bytes();
|
||||
String filePath = CommonConstant.fileSavePath + "/files/public/bing.jpg";
|
||||
FileUtil.writeBytes(bytes, filePath);
|
||||
} catch (Exception e) {
|
||||
log.error("获取bing每日一图错误:{}", e.getLocalizedMessage(), e);
|
||||
}
|
||||
return bingHost + path;
|
||||
} catch (Exception e) {
|
||||
log.error("获取bing每日一图错误:{}", e.getLocalizedMessage(), e);
|
||||
}
|
||||
return "/files/public/bing.jpg";
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -53,14 +53,6 @@ public class HttpUtil {
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 超时时间1s
|
||||
*/
|
||||
@Getter
|
||||
private static final OkHttpClient SHORT_CLIENT = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS)
|
||||
.readTimeout(1, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 获取客户端
|
||||
*
|
||||
|
@ -22,7 +22,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.17</version>
|
||||
<version>2.3.12.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
|
@ -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", "浏览器插件版本");
|
@ -1,3 +0,0 @@
|
||||
update global_config
|
||||
set value='0.1.2'
|
||||
where code = "pluginVersion";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
alter table bookmark
|
||||
modify name varchar(2000) not null;
|
2
bookmark_front/.gitignore
vendored
2
bookmark_front/.gitignore
vendored
@ -4,7 +4,6 @@ node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
public/files
|
||||
public\static\bookmarkBrowserPlugin.zip
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
@ -24,4 +23,3 @@ pnpm-debug.log*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
@ -20,11 +20,11 @@
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.8",
|
||||
"@vue/cli-plugin-vuex": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-plugin-eslint": "~4.4.0",
|
||||
"@vue/cli-plugin-router": "~4.4.0",
|
||||
"@vue/cli-plugin-vuex": "~4.4.0",
|
||||
"@vue/cli-service": "~4.4.0",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
|
9549
bookmark_front/pnpm-lock.yaml
generated
9549
bookmark_front/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
bookmark_front/public/static/bookmarkBrowserPlugin.7z
Normal file
BIN
bookmark_front/public/static/bookmarkBrowserPlugin.7z
Normal file
Binary file not shown.
@ -6,21 +6,12 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "App",
|
||||
mounted() {
|
||||
window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
|
||||
let script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.defer = true;
|
||||
script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
|
||||
document.getElementsByTagName("head")[0].appendChild(script);
|
||||
}
|
||||
name: "App"
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "./global.less";
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
@ -29,7 +20,6 @@ body {
|
||||
background-color: @bgColor;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<div :class="{ listShow: focused && list.length > 0 }" class="newSearch">
|
||||
<input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus"
|
||||
@blur="inputBlur" />
|
||||
<input ref="searchInput" class="input" type="text" v-model="value" @keydown="keyPress" @focus="inputFocus" @blur="inputBlur" />
|
||||
<div class="action">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-tooltip title="点击切换网页搜索">
|
||||
<my-icon class="icon" style="margin-right: 0.5em" :type="checkedSearchEngine.icon"
|
||||
@click="searchIconClick" />
|
||||
<my-icon class="icon" style="margin-right: 0.5em" :type="searchIcon" />
|
||||
</a-tooltip>
|
||||
<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-dropdown>
|
||||
<a-icon class="icon" type="search" @click="submit(true)" />
|
||||
@ -30,8 +30,7 @@
|
||||
</div>
|
||||
<div class="icons">
|
||||
<a-tooltip title="定位到书签树中" v-if="showLocation">
|
||||
<my-icon style="color: white; font-size: 1.3em" type="icon-et-location"
|
||||
@mousedown="location($event, item)" />
|
||||
<my-icon style="color: white; font-size: 1.3em" type="icon-et-location" @mousedown="location($event, item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="复制链接">
|
||||
<a-icon
|
||||
@ -52,7 +51,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a ref="targetA" style="left: 1000000px" />
|
||||
<a ref="targetA" style="left: 1000000px" target="_blank" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -62,11 +61,10 @@ import { mapState } from "vuex";
|
||||
import ClipboardJS from "clipboard";
|
||||
import { GLOBAL_CONFIG, USER_INFO } from "@/store/modules/globalConfig";
|
||||
import { TREE_DATA, refreshHomePinList, HOME_PIN_BOOKMARK_ID_MAP } from "@/store/modules/treeData";
|
||||
|
||||
export default {
|
||||
name: "Search",
|
||||
props: {
|
||||
showLocation: Boolean //是否显示定位等按钮
|
||||
showLocation: Boolean, //是否显示定位等按钮
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -76,25 +74,19 @@ export default {
|
||||
//上下选中
|
||||
selectIndex: null,
|
||||
copyBoard: null, //剪贴板对象
|
||||
searchEngineList: [],
|
||||
checkedSearchEngine: { icon: "icon-baidu", name: "百度", url: "https://www.baidu.com/s?ie=UTF-8&wd=%s" }
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
mounted() {
|
||||
//初始化clipboard
|
||||
this.copyBoard = new ClipboardJS(".search-copy-to-board", {
|
||||
text: function(trigger) {
|
||||
text: function (trigger) {
|
||||
return trigger.attributes.data.nodeValue;
|
||||
}
|
||||
},
|
||||
});
|
||||
this.copyBoard.on("success", (e) => {
|
||||
this.$message.success("复制成功");
|
||||
e.clearSelection();
|
||||
});
|
||||
if (this.$store.state.globalConfig.token != null) {
|
||||
this.searchEngineList = await HttpUtil.get("/searchEngine/list");
|
||||
this.checkedSearchEngine = this.searchEngineList.find(item => item.checked === 1);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.copyBoard != null) {
|
||||
@ -103,20 +95,26 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...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: {
|
||||
value(newVal, oldVal) {
|
||||
this.search(newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
searchIconClick() {
|
||||
if (this.userInfo == null) {
|
||||
this.searchEngineList = [];
|
||||
this.$message.warning("未登录,请登录后操作");
|
||||
}
|
||||
},
|
||||
search(content) {
|
||||
console.log(content);
|
||||
let val = content.toLocaleLowerCase().trim();
|
||||
@ -125,7 +123,6 @@ export default {
|
||||
} else {
|
||||
this.list = this.dealSearch(val);
|
||||
}
|
||||
this.selectIndex = null;
|
||||
},
|
||||
//下方列表点击
|
||||
itemClick(index) {
|
||||
@ -137,7 +134,7 @@ export default {
|
||||
let url;
|
||||
if (forceSearch || this.selectIndex == null) {
|
||||
//说明使用网页搜索
|
||||
url = this.checkedSearchEngine.url.replace("%s", encodeURIComponent(this.value));
|
||||
url = this.searchUrl + encodeURIComponent(this.value);
|
||||
} else {
|
||||
//说明跳转到书签
|
||||
let bookmark = this.list[this.selectIndex];
|
||||
@ -178,10 +175,15 @@ export default {
|
||||
},
|
||||
//修改默认搜索引擎
|
||||
async searchEngineChange(item) {
|
||||
|
||||
let target = this.searchEngineList.find(one => one.id === item.key);
|
||||
await HttpUtil.post("/searchEngine/setChecked", null, { id: item.key });
|
||||
this.checkedSearchEngine = target;
|
||||
if (this.userInfo == null) {
|
||||
this.$message.warning("未登录,请登录后操作");
|
||||
return;
|
||||
}
|
||||
if (item.key !== this.userInfo.defaultSearchEngine) {
|
||||
await HttpUtil.post("/baseInfo/updateSearchEngine", null, { defaultSearchEngine: item.key });
|
||||
this.userInfo.defaultSearchEngine = item.key;
|
||||
this.$store.commit(GLOBAL_CONFIG + "/" + USER_INFO, this.userInfo);
|
||||
}
|
||||
},
|
||||
//固定书签到首页
|
||||
async pinBookmark(event, { bookmarkId }) {
|
||||
@ -225,8 +227,8 @@ export default {
|
||||
}
|
||||
console.log("阻止成功");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -237,12 +239,10 @@ export default {
|
||||
@listActiveBgColor: #454545;
|
||||
.search {
|
||||
position: relative;
|
||||
|
||||
.listShow {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.newSearch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -251,7 +251,6 @@ export default {
|
||||
overflow: hidden;
|
||||
font-size: 1.2em;
|
||||
color: @textColor;
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
border: 0;
|
||||
@ -260,13 +259,11 @@ export default {
|
||||
padding-left: 0.19rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 0.1rem;
|
||||
padding-right: 0.19rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
color: @textColor;
|
||||
cursor: pointer;
|
||||
@ -284,7 +281,6 @@ export default {
|
||||
border-bottom-left-radius: 0.18rem;
|
||||
border-bottom-right-radius: 0.18rem;
|
||||
overflow: hidden;
|
||||
|
||||
.listItem {
|
||||
font-size: 0.16rem;
|
||||
display: flex;
|
||||
@ -295,7 +291,6 @@ export default {
|
||||
margin: 0.05rem 0 0.05rem 0;
|
||||
padding: 0 0.19rem 0 0.19rem;
|
||||
cursor: pointer;
|
||||
|
||||
.name {
|
||||
padding-right: 1em;
|
||||
max-width: calc(100% - 2em);
|
||||
@ -303,21 +298,17 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icons {
|
||||
display: none;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.listItem:hover {
|
||||
background-color: @listActiveBgColor;
|
||||
}
|
||||
|
||||
.itemActive {
|
||||
background-color: @listActiveBgColor;
|
||||
}
|
||||
|
||||
.listItem:hover .icons {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
</a-form-model-item>
|
||||
<template v-if="form.type !== 'file'">
|
||||
<a-form-model-item prop="name" label="名称" :required="false">
|
||||
<a-input v-model="form.name" placeholder="名称" @pressEnter="submit" ref="inputName" />
|
||||
<a-input v-model="form.name" placeholder="名称" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item v-if="form.type === 'bookmark'" prop="url" label="URL">
|
||||
<a-input v-model="form.url" placeholder="url" @pressEnter="submit" />
|
||||
<a-input v-model="form.url" placeholder="url" />
|
||||
</a-form-model-item>
|
||||
<div class="btns">
|
||||
<a-button type="primary" @click="submit" :loading="loading" :disabled="loading">提交</a-button>
|
||||
@ -21,7 +21,6 @@
|
||||
:data="{ path: form.path }"
|
||||
:headers="{ 'jwt-token': token }"
|
||||
action="/bookmark/api/bookmark/uploadBookmarkFile"
|
||||
accept=".html,.db3"
|
||||
@change="fileChange"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
@ -63,7 +62,7 @@ export default {
|
||||
file: null,
|
||||
},
|
||||
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" }],
|
||||
},
|
||||
};
|
||||
@ -79,12 +78,6 @@ export default {
|
||||
}
|
||||
this.token = this.$store.state.globalConfig.token;
|
||||
this.form.path = !this.targetNode ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.inputName) {
|
||||
this.$refs.inputName.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
|
@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div class="bottom">
|
||||
<router-link style="color: white" to="/public/about"><span class="text">关于</span></router-link>
|
||||
<a-tooltip v-if="bgSrc">
|
||||
<template #title>点击后鼠标右键-将图像另存为</template>
|
||||
<a style="color: white; margin-left: 1em" :href="bgSrc" download="bing每日一图"><span class="text">下载壁纸</span></a>
|
||||
</a-tooltip>
|
||||
<router-link to="/public/about">关于</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -12,7 +8,6 @@
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "homeTop",
|
||||
props: ["bgSrc"],
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
@ -23,11 +18,6 @@ export default {
|
||||
.bottom {
|
||||
height: 0.4rem;
|
||||
padding: 0.1rem;
|
||||
text-align: center;
|
||||
color: black;
|
||||
|
||||
.text {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
@ -6,18 +6,15 @@
|
||||
/
|
||||
<router-link to="/public/register">注册</router-link>
|
||||
</div>
|
||||
<div v-else class="topAction">
|
||||
<a-tooltip style="margin-right: 1em">
|
||||
<template #title>书签管理</template>
|
||||
<router-link to="/manage">
|
||||
<a-icon class="bookmarkIcon" type="setting" />
|
||||
</router-link>
|
||||
</a-tooltip>
|
||||
<div v-else>
|
||||
<a-dropdown>
|
||||
<div class="user">
|
||||
<img :src="userInfo.icon" class="userIcon" />
|
||||
</div>
|
||||
<a-menu slot="overlay" :trigger="['hover', 'click']" @click="menuClick">
|
||||
<a-menu-item key="manage">
|
||||
<router-link to="manage">书签管理</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="personSpace">
|
||||
<router-link to="/manage/personSpace/userInfo">个人中心</router-link>
|
||||
</a-menu-item>
|
||||
@ -66,16 +63,5 @@ export default {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
}
|
||||
|
||||
.topAction {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bookmarkIcon {
|
||||
font-size: 2em;
|
||||
background-color: rgb(74, 74, 74, 0.5);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,34 +1,32 @@
|
||||
import Vue from "vue";
|
||||
import {
|
||||
Button,
|
||||
FormModel,
|
||||
Input,
|
||||
Icon,
|
||||
message,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Tree,
|
||||
Tooltip,
|
||||
Spin,
|
||||
notification,
|
||||
Empty,
|
||||
Modal,
|
||||
Radio,
|
||||
Upload,
|
||||
Popconfirm,
|
||||
AutoComplete,
|
||||
Select,
|
||||
Popover,
|
||||
Breadcrumb,
|
||||
Table
|
||||
Button,
|
||||
FormModel,
|
||||
Input,
|
||||
Icon,
|
||||
message,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Tree,
|
||||
Tooltip,
|
||||
Spin,
|
||||
notification,
|
||||
Empty,
|
||||
Modal,
|
||||
Radio,
|
||||
Upload,
|
||||
Popconfirm,
|
||||
AutoComplete,
|
||||
Select,
|
||||
Popover
|
||||
} from "ant-design-vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
|
||||
const IconFont = Icon.createFromIconfontCN({
|
||||
scriptUrl: "//at.alicdn.com/t/c/font_1261825_v7m0rilm4hm.js"
|
||||
scriptUrl: "//at.alicdn.com/t/font_1261825_1cgngjf5r4f.js"
|
||||
});
|
||||
Vue.use(Button);
|
||||
Vue.use(FormModel);
|
||||
@ -48,8 +46,6 @@ Vue.use(Popconfirm);
|
||||
Vue.use(AutoComplete);
|
||||
Vue.use(Select);
|
||||
Vue.use(Popover);
|
||||
Vue.use(Breadcrumb);
|
||||
Vue.use(Table);
|
||||
Vue.component("my-icon", IconFont);
|
||||
|
||||
Vue.prototype.$message = message;
|
||||
@ -58,9 +54,9 @@ Vue.prototype.$confirm = Modal.confirm;
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
window.vueInstance = new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
||||
|
||||
|
||||
|
@ -58,7 +58,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
await vuex.default.dispatch("treeData/clear");
|
||||
await vuex.default.dispatch("globalConfig/clear");
|
||||
next({
|
||||
path: "/public/login?to=" + btoa(to.fullPath),
|
||||
path: "/public/login?to=" + btoa(location.href),
|
||||
replace: true
|
||||
});
|
||||
} else {
|
||||
|
@ -8,61 +8,58 @@ import { checkJwtValid } from "@/util/UserUtil";
|
||||
Vue.use(Vuex);
|
||||
|
||||
let store = new Vuex.Store({
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
modules: {
|
||||
[globalConfig.GLOBAL_CONFIG]: globalConfig.store,
|
||||
[treeData.TREE_DATA]: treeData.store
|
||||
}
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
modules: {
|
||||
[globalConfig.GLOBAL_CONFIG]: globalConfig.store,
|
||||
[treeData.TREE_DATA]: treeData.store
|
||||
}
|
||||
});
|
||||
|
||||
let noLoginFinish = false;
|
||||
|
||||
//执行各自的非登陆初始化
|
||||
(async () => {
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
|
||||
//无需等待执行
|
||||
store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
||||
noLoginFinish = true;
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.noLoginInit);
|
||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.noLoginInit);
|
||||
noLoginFinish = true;
|
||||
})();
|
||||
|
||||
/**
|
||||
* 执行各模块的登陆后初始化
|
||||
*/
|
||||
export async function loginInit() {
|
||||
if (!noLoginFinish) {
|
||||
await finishNoLogin();
|
||||
}
|
||||
console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]);
|
||||
if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) {
|
||||
//无需等待执行完毕
|
||||
store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit);
|
||||
store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit);
|
||||
}
|
||||
console.log("初始化完成");
|
||||
export async function loginInit () {
|
||||
if (!noLoginFinish) {
|
||||
await finishNoLogin();
|
||||
}
|
||||
console.log(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN]);
|
||||
if (checkJwtValid(store.state[globalConfig.GLOBAL_CONFIG][globalConfig.TOKEN])) {
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.loginInit);
|
||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.loginInit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推出登陆时需要清理的
|
||||
*/
|
||||
export async function logoutClear() {
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear);
|
||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear);
|
||||
export async function logoutClear () {
|
||||
await store.dispatch(globalConfig.GLOBAL_CONFIG + "/" + globalConfig.clear);
|
||||
await store.dispatch(treeData.TREE_DATA + "/" + treeData.clear);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保未登录前要初始化的初始化完了
|
||||
*/
|
||||
async function finishNoLogin() {
|
||||
return new Promise((resolve) => {
|
||||
let timer = setInterval(() => {
|
||||
if (noLoginFinish) {
|
||||
clearInterval(timer);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
async function finishNoLogin () {
|
||||
return new Promise((resolve) => {
|
||||
let timer = setInterval(() => {
|
||||
if (noLoginFinish) {
|
||||
clearInterval(timer);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
})
|
||||
}
|
||||
|
||||
export default store;
|
||||
|
@ -69,7 +69,6 @@ const actions = {
|
||||
let userInfo = await HttpUtil.get("/user/currentUserInfo");
|
||||
context.commit(USER_INFO, userInfo);
|
||||
context.commit(IS_INIT, true);
|
||||
console.log("用户完了");
|
||||
},
|
||||
async [setToken]({ commit }, token) {
|
||||
await localforage.setItem(TOKEN, token);
|
||||
|
@ -22,13 +22,7 @@ export const refreshHomePinList = "refreshHomePinList";
|
||||
* 通过id获取书签数据
|
||||
*/
|
||||
export const getById = "getById";
|
||||
/**
|
||||
* 登录前初始化
|
||||
*/
|
||||
export const noLoginInit = "noLoginInit";
|
||||
/**
|
||||
* 登陆后初始化
|
||||
*/
|
||||
export const loginInit = "loginInit";
|
||||
export const refresh = "refresh";
|
||||
export const clear = "clear";
|
||||
@ -45,10 +39,6 @@ export const addNode = "addNode";
|
||||
* 版本检查定时调度
|
||||
*/
|
||||
let timer = null;
|
||||
/**
|
||||
* 检查本地版本是否有更新
|
||||
*/
|
||||
let checkLocalDataTimer = null;
|
||||
/**
|
||||
* 刷新书签确认弹窗是否展示
|
||||
*/
|
||||
@ -86,8 +76,8 @@ const getters = {
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async [noLoginInit] () { },
|
||||
async [loginInit] (context) {
|
||||
async [noLoginInit]() {},
|
||||
async [loginInit](context) {
|
||||
if (context.state.isInit || context.state.isIniting) {
|
||||
return;
|
||||
}
|
||||
@ -99,12 +89,11 @@ const actions = {
|
||||
context.commit(IS_INIT, true);
|
||||
context.commit(IS_INITING, false);
|
||||
timer = setInterval(() => treeDataCheck(context, false), CHECK_INTERVAL);
|
||||
checkLocalDataTimer = setInterval(() => checkLocalData(context), 2000);
|
||||
},
|
||||
/**
|
||||
* 确保数据加载完毕
|
||||
*/
|
||||
ensureDataOk (context) {
|
||||
ensureDataOk(context) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timer = setInterval(() => {
|
||||
try {
|
||||
@ -119,7 +108,7 @@ const actions = {
|
||||
});
|
||||
},
|
||||
//刷新缓存数据
|
||||
async [refresh] (context) {
|
||||
async [refresh](context) {
|
||||
let treeData = await HttpUtil.get("/bookmark/currentUser");
|
||||
if (!treeData[""]) {
|
||||
treeData[""] = [];
|
||||
@ -138,7 +127,7 @@ const actions = {
|
||||
await localforage.setItem(TOTAL_TREE_DATA, treeData);
|
||||
},
|
||||
//清除缓存数据
|
||||
async [clear] (context) {
|
||||
async [clear](context) {
|
||||
context.commit(TOTAL_TREE_DATA, null);
|
||||
context.commit(VERSION, null);
|
||||
context.commit(SHOW_REFRESH_TOAST, false);
|
||||
@ -148,16 +137,13 @@ const actions = {
|
||||
if (timer != null) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
if (checkLocalDataTimer != null) {
|
||||
clearInterval(checkLocalDataTimer);
|
||||
}
|
||||
await localforage.removeItem(TOTAL_TREE_DATA);
|
||||
await localforage.removeItem(VERSION);
|
||||
},
|
||||
/**
|
||||
* 移动节点
|
||||
*/
|
||||
async moveNode (context, info) {
|
||||
async moveNode(context, info) {
|
||||
let data = context.state[TOTAL_TREE_DATA];
|
||||
const target = info.node.dataRef;
|
||||
const current = info.dragNode.dataRef;
|
||||
@ -225,7 +211,7 @@ const actions = {
|
||||
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||
return body;
|
||||
},
|
||||
async [refreshHomePinList] ({ commit }) {
|
||||
async [refreshHomePinList]({ commit }) {
|
||||
let list = await HttpUtil.get("/home/pin");
|
||||
commit(HOME_PIN_LIST, list);
|
||||
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);
|
||||
await localforage.setItem(VERSION, state[VERSION]);
|
||||
},
|
||||
/**
|
||||
* 新增书签、文件夹
|
||||
*/
|
||||
async [addNode] (context, { sourceNode, targetNode }) {
|
||||
async [addNode](context, { sourceNode, targetNode }) {
|
||||
if (sourceNode === null) {
|
||||
if (context.state[TOTAL_TREE_DATA][""] === undefined) {
|
||||
context.state[TOTAL_TREE_DATA][""] = [];
|
||||
}
|
||||
context.state[TOTAL_TREE_DATA][""].push(targetNode);
|
||||
} else {
|
||||
let path = sourceNode.path + "." + sourceNode.bookmarkId;
|
||||
if (!context.state[TOTAL_TREE_DATA][path]) {
|
||||
context.state[TOTAL_TREE_DATA][path] = [];
|
||||
}
|
||||
if (sourceNode.children === undefined) {
|
||||
sourceNode.children = context.state[TOTAL_TREE_DATA][path];
|
||||
sourceNode.children = [];
|
||||
}
|
||||
sourceNode.children.push(targetNode);
|
||||
}
|
||||
if (targetNode.type === 1) {
|
||||
if (targetNode.type === 0) {
|
||||
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
|
||||
}
|
||||
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();
|
||||
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.url = newUrl;
|
||||
node.icon = newIcon;
|
||||
@ -313,10 +295,10 @@ const mutations = {
|
||||
[TOTAL_TREE_DATA]: (state, totalTreeData) => {
|
||||
state.totalTreeData = totalTreeData;
|
||||
},
|
||||
[IS_INIT] (state, isInit) {
|
||||
[IS_INIT](state, isInit) {
|
||||
state.isInit = isInit;
|
||||
},
|
||||
[IS_INITING] (state, isIniting) {
|
||||
[IS_INITING](state, isIniting) {
|
||||
state.isIniting = isIniting;
|
||||
},
|
||||
[VERSION]: (state, version) => {
|
||||
@ -340,7 +322,7 @@ const mutations = {
|
||||
* @param {*} isFirst
|
||||
* @returns
|
||||
*/
|
||||
async function treeDataCheck (context, isFirst) {
|
||||
async function treeDataCheck(context, isFirst) {
|
||||
if (toastShow || !checkJwtValid(context.rootState.globalConfig.token)) {
|
||||
return;
|
||||
}
|
||||
@ -354,14 +336,14 @@ async function treeDataCheck (context, isFirst) {
|
||||
closable: false,
|
||||
keyboard: false,
|
||||
maskClosable: false,
|
||||
onOk () {
|
||||
onOk() {
|
||||
toastShow = false;
|
||||
return new Promise(async resolve => {
|
||||
await context.dispatch(refresh);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
onCancel () {
|
||||
onCancel() {
|
||||
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 = {
|
||||
namespaced: true,
|
||||
state,
|
||||
|
@ -12,54 +12,54 @@ import router from "../router/index";
|
||||
* @param {*} redirect 接口返回未认证是否跳转到登陆
|
||||
* @returns 数据
|
||||
*/
|
||||
async function request(url, method, params, body, isForm, redirect) {
|
||||
let options = {
|
||||
url,
|
||||
baseURL: "/bookmark/api",
|
||||
method,
|
||||
params,
|
||||
headers: {
|
||||
"jwt-token": window.jwtToken
|
||||
}
|
||||
};
|
||||
//如果是表单类型的请求,添加请求头
|
||||
if (isForm) {
|
||||
options.headers["Content-Type"] = "multipart/form-data";
|
||||
}
|
||||
if (body) {
|
||||
options.data = body;
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await http.default.request(options);
|
||||
} catch (err) {
|
||||
window.vueInstance.$message.error("网络连接异常");
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
const { code, data, message } = res.data;
|
||||
if (code === 1) {
|
||||
return data;
|
||||
} else if (code === -1 && redirect) {
|
||||
//未登陆,根据redirect参数判断是否需要跳转到登陆页
|
||||
window.vueInstance.$message.error("您尚未登陆,请先登陆");
|
||||
//跳转到登录页面需要清理缓存
|
||||
await this.$store.dispatch("treeData/clear");
|
||||
await this.$store.dispatch("globalConfig/clear");
|
||||
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
|
||||
throw new Error(message);
|
||||
} else if (code === 0) {
|
||||
//通用异常,使用error提示
|
||||
window.vueInstance.$notification.error({
|
||||
message: "异常",
|
||||
description: message
|
||||
});
|
||||
throw new Error(message);
|
||||
} else if (code === -2) {
|
||||
//表单异常,使用message提示
|
||||
window.vueInstance.$message.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
async function request (url, method, params, body, isForm, redirect) {
|
||||
let options = {
|
||||
url,
|
||||
baseURL: "/bookmark/api",
|
||||
method,
|
||||
params,
|
||||
headers: {
|
||||
"jwt-token": window.jwtToken
|
||||
}
|
||||
};
|
||||
//如果是表单类型的请求,添加请求头
|
||||
if (isForm) {
|
||||
options.headers["Content-Type"] = "multipart/form-data";
|
||||
}
|
||||
if (body) {
|
||||
options.data = body;
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await http.default.request(options);
|
||||
} catch (err) {
|
||||
window.vueInstance.$message.error("网络连接异常");
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
const { code, data, message } = res.data;
|
||||
if (code === 1) {
|
||||
return data;
|
||||
} else if (code === -1 && redirect) {
|
||||
//未登陆,根据redirect参数判断是否需要跳转到登陆页
|
||||
window.vueInstance.$message.error("您尚未登陆,请先登陆");
|
||||
//跳转到登录页面需要清理缓存
|
||||
await this.$store.dispatch("treeData/clear");
|
||||
await this.$store.dispatch("globalConfig/clear");
|
||||
router.replace(`/public/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`);
|
||||
throw new Error(message);
|
||||
} else if (code === 0) {
|
||||
//通用异常,使用error提示
|
||||
window.vueInstance.$notification.error({
|
||||
message: "异常",
|
||||
description: message
|
||||
});
|
||||
throw new Error(message);
|
||||
} else if (code === -2) {
|
||||
//表单异常,使用message提示
|
||||
window.vueInstance.$message.error(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,8 +68,8 @@ async function request(url, method, params, body, isForm, redirect) {
|
||||
* @param {*} params url参数
|
||||
* @param {*} redirect 未登陆是否跳转到登陆页
|
||||
*/
|
||||
async function get(url, params = null, redirect = true) {
|
||||
return request(url, "get", params, null, false, redirect);
|
||||
async function get (url, params = null, redirect = true) {
|
||||
return request(url, "get", params, null, false, redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,8 +80,8 @@ async function get(url, params = null, redirect = true) {
|
||||
* @param {*} isForm 是否表单数据
|
||||
* @param {*} redirect 是否重定向
|
||||
*/
|
||||
async function post(url, params, body, isForm = false, redirect = true) {
|
||||
return request(url, "post", params, body, isForm, redirect);
|
||||
async function post (url, params, body, isForm = false, redirect = true) {
|
||||
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 {*} redirect 是否重定向
|
||||
*/
|
||||
async function put(url, params, body, isForm = false, redirect = true) {
|
||||
return request(url, "put", params, body, isForm, redirect);
|
||||
async function put (url, params, body, isForm = false, redirect = true) {
|
||||
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 {*} redirect 是否重定向
|
||||
*/
|
||||
async function deletes(url, params = null, redirect = true) {
|
||||
return request(url, "delete", params, null, redirect);
|
||||
async function deletes (url, params = null, redirect = true) {
|
||||
return request(url, "delete", params, null, redirect);
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: deletes,
|
||||
http
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: deletes
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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" />
|
||||
<span class="text" :title="pinObj.name">{{ pinObj.name }}</span>
|
||||
<a-dropdown :trigger="['click']">
|
||||
|
@ -5,7 +5,7 @@
|
||||
<search :style="{ width: isPhone ? '100%' : '60%' }" />
|
||||
<div :style="{ width: isPhone ? '100%' : '70%' }"><pin-bookmark /></div>
|
||||
</div>
|
||||
<bottom :bgSrc="serverConfig.bingImgSrc" />
|
||||
<bottom />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
<a-tooltip
|
||||
v-if="
|
||||
(checkedKeys.length === 0 && (currentSelect == null || currentSelect.type === 1)) ||
|
||||
(checkedKeys.length === 1 && checkedNodes[0].type === 1)
|
||||
(checkedKeys.length === 1 && checkedNodes[0].type === 1)
|
||||
"
|
||||
title="添加书签"
|
||||
>
|
||||
@ -65,7 +65,7 @@
|
||||
@drop="onDrop"
|
||||
>
|
||||
<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" />
|
||||
<img v-else-if="rec.dataRef.icon.length > 0" :src="rec.dataRef.icon" style="width: 16px" />
|
||||
<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-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">
|
||||
{{ 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 key="edit">编辑</a-menu-item>
|
||||
<a-menu-item key="delete">删除</a-menu-item>
|
||||
@ -114,7 +114,7 @@ export default {
|
||||
loadedKeys: [], // 已加载数据
|
||||
replaceFields: {
|
||||
title: "name",
|
||||
key: "bookmarkId",
|
||||
key: "bookmarkId"
|
||||
},
|
||||
mulSelect: false, // 多选框是否显示
|
||||
currentSelect: null, // 当前树的选择项
|
||||
@ -126,19 +126,19 @@ export default {
|
||||
// 新增、修改目标数据,null说明向根节点增加数据
|
||||
targetNode: null,
|
||||
// 是否为新增动作
|
||||
isAdd: false,
|
||||
isAdd: false
|
||||
},
|
||||
copyBoard: null, //剪贴板对象
|
||||
copyBoard: null //剪贴板对象
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("treeData", ["totalTreeData", HOME_PIN_LIST]),
|
||||
...mapState("globalConfig", ["isPhone"]),
|
||||
...mapState("globalConfig", ["isPhone"])
|
||||
},
|
||||
watch: {
|
||||
totalTreeData(newVal, oldVal) {
|
||||
this.resetData();
|
||||
},
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, true);
|
||||
@ -147,14 +147,21 @@ export default {
|
||||
this.loading = false;
|
||||
//初始化clipboard
|
||||
this.copyBoard = new ClipboardJS(".copy-to-board", {
|
||||
text: function (trigger) {
|
||||
text: function(trigger) {
|
||||
return trigger.attributes.data.nodeValue;
|
||||
},
|
||||
}
|
||||
});
|
||||
this.copyBoard.on("success", (e) => {
|
||||
this.copyBoard.on("success", e => {
|
||||
this.$message.success("复制成功");
|
||||
e.clearSelection();
|
||||
});
|
||||
|
||||
window.onblur = e => {
|
||||
console.log("窗口非激活");
|
||||
};
|
||||
window.onfocus = e => {
|
||||
console.log("窗口激活");
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$store.commit(TREE_DATA + "/" + SHOW_REFRESH_TOAST, false);
|
||||
@ -168,7 +175,7 @@ export default {
|
||||
*/
|
||||
loadData(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;
|
||||
let newPath = data.path + "." + data.bookmarkId;
|
||||
if (!this.totalTreeData[newPath]) {
|
||||
@ -212,9 +219,9 @@ export default {
|
||||
this.expandedKeys = [
|
||||
...item.path
|
||||
.split(".")
|
||||
.filter((item) => item.length > 0)
|
||||
.map((item) => parseInt(item)),
|
||||
item.bookmarkId,
|
||||
.filter(item => item.length > 0)
|
||||
.map(item => parseInt(item)),
|
||||
item.bookmarkId
|
||||
];
|
||||
} else {
|
||||
this.expandedKeys.pop();
|
||||
@ -228,7 +235,7 @@ export default {
|
||||
} else {
|
||||
this.checkedKeys.splice(this.checkedKeys.indexOf(item.bookmarkId), 1);
|
||||
this.checkedNodes.splice(
|
||||
this.checkedNodes.findIndex((item1) => item1.bookmarkId === item.bookmarkId),
|
||||
this.checkedNodes.findIndex(item1 => item1.bookmarkId === item.bookmarkId),
|
||||
1
|
||||
);
|
||||
}
|
||||
@ -248,9 +255,9 @@ export default {
|
||||
this.expandedKeys = [
|
||||
...item.path
|
||||
.split(".")
|
||||
.filter((item) => item.length > 0)
|
||||
.map((item) => parseInt(item)),
|
||||
item.bookmarkId,
|
||||
.filter(item => item.length > 0)
|
||||
.map(item => parseInt(item)),
|
||||
item.bookmarkId
|
||||
];
|
||||
}
|
||||
} else {
|
||||
@ -273,7 +280,7 @@ export default {
|
||||
const bookmarkIdList = [];
|
||||
const pathList = [];
|
||||
if (this.checkedNodes) {
|
||||
this.checkedNodes.forEach((item) =>
|
||||
this.checkedNodes.forEach(item =>
|
||||
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 this.$store.dispatch(TREE_DATA + "/" + deleteData, { pathList, bookmarkIdList });
|
||||
//删除已经被删除的数据
|
||||
pathList.forEach((item) => {
|
||||
pathList.forEach(item => {
|
||||
const id = parseInt(item.split(".").reverse()[0]);
|
||||
let index = this.loadedKeys.indexOf(id);
|
||||
if (index > -1) {
|
||||
@ -322,13 +329,13 @@ export default {
|
||||
this.refresh(false);
|
||||
this.expandedKeys = item.path
|
||||
.split(".")
|
||||
.filter((one) => one.length > 0)
|
||||
.map((one) => parseInt(one));
|
||||
.filter(one => one.length > 0)
|
||||
.map(one => parseInt(one));
|
||||
this.loadedKeys = item.path
|
||||
.split(".")
|
||||
.filter((one) => one.length > 0)
|
||||
.map((one) => parseInt(one));
|
||||
this.expandedKeys.forEach(async (one) => await this.loadData(one));
|
||||
.filter(one => one.length > 0)
|
||||
.map(one => parseInt(one));
|
||||
this.expandedKeys.forEach(async one => await this.loadData(one));
|
||||
this.currentSelect = item;
|
||||
},
|
||||
/**
|
||||
@ -344,7 +351,7 @@ export default {
|
||||
this.addModal = {
|
||||
show: false,
|
||||
targetNode: null,
|
||||
isAdd: false,
|
||||
isAdd: false
|
||||
};
|
||||
},
|
||||
async onDrop(info) {
|
||||
@ -388,12 +395,12 @@ export default {
|
||||
await this.deleteBookmarks();
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
} else if (key === "edit") {
|
||||
this.editData();
|
||||
} 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) {
|
||||
await HttpUtil.delete("/home/pin", { id: pin[0].id });
|
||||
} else {
|
||||
@ -411,8 +418,8 @@ export default {
|
||||
dealList(root, map[""], map);
|
||||
let content = exportFileHead + root.outerHTML;
|
||||
downloadFile(moment().format("YYYY-MM-DD") + "导出书签.html", content);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -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>
|
||||
<a @click="() => cancel(record.id)">取消</a>
|
||||
</span>
|
||||
<div v-else>
|
||||
<a :disabled="currentEditCache" @click="() => edit(record.id)">编辑</a>
|
||||
<a-popconfirm title="确认删除吗?" ok-text="是" cancel-text="否" @confirm="() => deleteOne(record.id)">
|
||||
<a :disabled="currentEditCache">删除</a>
|
||||
</a-popconfirm>
|
||||
<a :disabled="currentEditCache || record.checked===1" @click="() => setDefault(record.id)">设为默认</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import HttpUtil from "@/util/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "manageSearchEngine",
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
currentEditCache: null,
|
||||
iconList: [
|
||||
{ icon: "icon-baidu", label: "百度" },
|
||||
{ icon: "icon-bing", label: "必应" },
|
||||
{ icon: "icon-google", label: "谷歌" },
|
||||
{ icon: "icon-yandex", label: "yandex" },
|
||||
{ icon: "icon-sogou", label: "搜狗" },
|
||||
{ icon: "icon-yahoo", label: "雅虎" },
|
||||
{ icon: "icon-qita", label: "其他" }
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
title: "名称",
|
||||
dataIndex: "name",
|
||||
width: "10em",
|
||||
scopedSlots: { customRender: "name" }
|
||||
}, {
|
||||
title: "图标",
|
||||
dataIndex: "icon",
|
||||
width: "8em",
|
||||
scopedSlots: { customRender: "icon" }
|
||||
}, {
|
||||
title: "路径(%s 会被替换为搜索内容)",
|
||||
dataIndex: "url",
|
||||
scopedSlots: { customRender: "url" }
|
||||
}, {
|
||||
title: "默认",
|
||||
dataIndex: "checked",
|
||||
width: "10em",
|
||||
scopedSlots: { customRender: "checked" }
|
||||
}, {
|
||||
title: "操作",
|
||||
width: "15em",
|
||||
dataIndex: "operation",
|
||||
scopedSlots: { customRender: "operation" }
|
||||
}]
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.getData();
|
||||
},
|
||||
computed: {
|
||||
//是否可点击删除
|
||||
deleteOk() {
|
||||
return this.list.filter(item => item.isEdit).length === 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addOne() {
|
||||
let body = { id: -1, icon: "", name: "", url: "", checked: 0, isEdit: true };
|
||||
this.list = [body, ...this.list];
|
||||
this.currentEditCache = body;
|
||||
|
||||
},
|
||||
handleChange(value, id, column) {
|
||||
console.log(value, id, column);
|
||||
const target = this.list.find(item => item.id === id);
|
||||
target[column] = value;
|
||||
this.list = [...this.list];
|
||||
},
|
||||
async edit(id) {
|
||||
let target = this.list.find(item => item.id === id);
|
||||
this.currentEditCache = { ...target };
|
||||
target.isEdit = true;
|
||||
this.list = [...this.list];
|
||||
},
|
||||
async save(id) {
|
||||
let target = this.list.find(item => item.id === id);
|
||||
if (target.id > 0) {
|
||||
await HttpUtil.post("/searchEngine/edit", null, target);
|
||||
} else {
|
||||
await HttpUtil.post("/searchEngine/insert", null, target);
|
||||
}
|
||||
target.isEdit = false;
|
||||
this.currentEditCache = null;
|
||||
await this.getData();
|
||||
},
|
||||
cancel(id) {
|
||||
let target = this.list.find(item => item.id === id);
|
||||
Object.assign(target, this.currentEditCache);
|
||||
target.isEdit = false;
|
||||
this.list = [...this.list];
|
||||
this.currentEditCache = null;
|
||||
},
|
||||
async deleteOne(id) {
|
||||
await HttpUtil.post("/searchEngine/delete", null, { id });
|
||||
this.list = await HttpUtil.get("/searchEngine/list");
|
||||
},
|
||||
async setDefault(id) {
|
||||
await HttpUtil.post("/searchEngine/setChecked", null, { id });
|
||||
this.list = await HttpUtil.get("/searchEngine/list");
|
||||
},
|
||||
async getData() {
|
||||
this.list = await HttpUtil.get("/searchEngine/list");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.icon {
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="userInfo" class="userInfo">
|
||||
<div class="userInfo">
|
||||
<div class="icon">
|
||||
<img :src="userInfo.icon" class="full" />
|
||||
<label class="full">
|
||||
@ -12,8 +12,7 @@
|
||||
<div class="baseInfo">
|
||||
<div class="item">
|
||||
<a-tooltip title="点击修改" v-if="currentAction != 'name'">
|
||||
<span style="font-size: 2em; cursor: pointer"
|
||||
@click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
|
||||
<span style="font-size: 2em; cursor: pointer" @click="() => (this.currentAction = 'name')">{{ userInfo.username }}</span>
|
||||
</a-tooltip>
|
||||
<div class="inputGroup" v-else-if="currentAction === 'name'">
|
||||
<a-input type="text" v-model="name" placeholder="修改昵称" />
|
||||
@ -27,9 +26,7 @@
|
||||
<div class="item">
|
||||
<span style="width: 5em">密码</span>
|
||||
<a-tooltip title="点击修改" v-if="currentAction != 'password'">
|
||||
<span style="cursor: pointer"
|
||||
@click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********"
|
||||
}}</span>
|
||||
<span style="cursor: pointer" @click="() => (this.currentAction = 'password')">{{ userInfo.noPassword ? "设置密码" : "**********" }}</span>
|
||||
</a-tooltip>
|
||||
<div class="inputGroup" v-else-if="currentAction === 'password'">
|
||||
<a-input type="password" v-model="oldPassword" placeholder="旧密码(如无置空)" />
|
||||
@ -61,24 +58,26 @@
|
||||
<a-tooltip title="搜索框默认搜索引擎">
|
||||
<span style="width: 5em">搜索</span>
|
||||
</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>
|
||||
|
||||
<a-modal v-model="showSearchEngineManage" title="管理搜索引擎" :footer="null" width="70%">
|
||||
<manage-search-engine />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import manageSearchEngine from "./components/manageSearchEngine.vue";
|
||||
import { mapState } from "vuex";
|
||||
import HttpUtil from "@/util/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "UserInfo",
|
||||
components: { manageSearchEngine },
|
||||
data() {
|
||||
return {
|
||||
currentAction: null, //当前操作,name,password,email
|
||||
@ -87,11 +86,21 @@ export default {
|
||||
password: "",
|
||||
rePassword: "",
|
||||
email: "",
|
||||
showSearchEngineManage: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("globalConfig", ["userInfo"])
|
||||
...mapState("globalConfig", ["userInfo"]),
|
||||
defaultSearchEngine() {
|
||||
switch (this.userInfo.defaultSearchEngine) {
|
||||
case "baidu":
|
||||
return "百度";
|
||||
case "google":
|
||||
return "谷歌";
|
||||
case "bing":
|
||||
return "Bing";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async changeIcon(e) {
|
||||
@ -118,18 +127,21 @@ export default {
|
||||
url = "/baseInfo/password";
|
||||
body = {
|
||||
oldPassword: this.oldPassword,
|
||||
password: this.password
|
||||
password: this.password,
|
||||
};
|
||||
} else if (this.currentAction === "email") {
|
||||
url = "/baseInfo/email";
|
||||
body = { oldPassword: this.oldPassword, email: this.email };
|
||||
} else if (this.currentAction === "defaultSearchEngine") {
|
||||
url = "/baseInfo/updateSearchEngine";
|
||||
body = { defaultSearchEngine: e };
|
||||
}
|
||||
await HttpUtil.post(url, null, body);
|
||||
await this.$store.dispatch("globalConfig/refreshUserInfo");
|
||||
this.$message.success("操作成功");
|
||||
this.currentAction = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -183,7 +195,6 @@ export default {
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
|
||||
display: flex;
|
||||
|
||||
.inputGroup {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -1,76 +1,27 @@
|
||||
<template>
|
||||
<div class="ssoAddBookmark">
|
||||
<div class="body">
|
||||
<div>
|
||||
<a-input placeholder="标题" v-model="form.name" @pressEnter="addBookmark" ref="nameInput" />
|
||||
<a-input placeholder="网址" v-model="form.url" @pressEnter="addBookmark" />
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="path">
|
||||
<div>保存路径:</div>
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item class="breadItem"><span @click="breadClick(null)">根</span></a-breadcrumb-item>
|
||||
<a-breadcrumb-item class="breadItem" v-for="item in breadList" :key="item.bookmarkId">
|
||||
<span @click="breadClick(item)">{{ item.name.length > 4 ? item.name.substr(0, 3) + "..." : item.name }}</span>
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<div class="folderList">
|
||||
<div :class="{ item: true, bg: item.type == 1 }" v-for="item in dataList" :key="item.bookmarkId" :title="item.url">
|
||||
<span class="text" @click="folderClick(item)">{{ item.name }}</span>
|
||||
<a-popconfirm class="actionBar" placement="left" title="确认删除?" ok-text="是" cancel-text="否" @confirm="deleteOne(item, $event)">
|
||||
<a-icon type="delete" />
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<div v-if="showAddInput" style="display: flex">
|
||||
<a-input v-model="addFolderName" style="width: 8em" @pressEnter="addFolder" ref="folderInput" />
|
||||
<a-button shape="circle" icon="close" @click="showAddInput = false" />
|
||||
<a-button type="primary" shape="circle" icon="check" @click="addFolder" />
|
||||
</div>
|
||||
<a-button v-else type="link" @click="showFolderInput">新建文件夹</a-button>
|
||||
<div>
|
||||
<a-button style="marging-right: 1em" type="" @click="closeIframe">取消</a-button>
|
||||
<a-button type="primary" @click="addBookmark">{{ breadList.length === 0 ? "保存到根" : "保存" }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
正在添加,请稍后!!!
|
||||
<!-- <button @click="closeIframe">关闭</button> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HttpUtil from "@/util/HttpUtil";
|
||||
import { mapState } from "vuex";
|
||||
import { TREE_DATA, TOTAL_TREE_DATA, addNode, deleteData } from "@/store/modules/treeData";
|
||||
import { TREE_DATA, addNode } from "@/store/modules/treeData";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
breadList: [],
|
||||
showAddInput: false,
|
||||
addFolderName: "",
|
||||
form: {
|
||||
name: null,
|
||||
url: null,
|
||||
icon: null,
|
||||
iconUrl: null,
|
||||
type: 0,
|
||||
path: "",
|
||||
},
|
||||
path: ""
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(TREE_DATA, [TOTAL_TREE_DATA]),
|
||||
dataList() {
|
||||
let path = this.getCurrentPath();
|
||||
return this.totalTreeData[path] ? this.totalTreeData[path] : [];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
//接受父节点传递的书签信息
|
||||
window.addEventListener("message", (event) => {
|
||||
window.addEventListener("message", event => {
|
||||
if (!event.data.code) {
|
||||
return;
|
||||
}
|
||||
@ -79,13 +30,11 @@ export default {
|
||||
console.log("新增书签");
|
||||
this.form.name = event.data.data.name;
|
||||
this.form.url = event.data.data.url;
|
||||
this.form.icon = event.data.data.icon;
|
||||
this.form.iconUrl = event.data.data.iconUrl;
|
||||
this.addBookmark();
|
||||
}
|
||||
});
|
||||
console.log("向父节点获取数据");
|
||||
window.parent.postMessage({ code: "getBookmarkData", receiver: "content" }, "*");
|
||||
this.$refs.nameInput.focus();
|
||||
},
|
||||
methods: {
|
||||
closeIframe() {
|
||||
@ -93,174 +42,22 @@ export default {
|
||||
},
|
||||
//新增书签
|
||||
async addBookmark() {
|
||||
this.form.path = this.getCurrentPath();
|
||||
let res = await HttpUtil.put("/bookmark", null, this.form);
|
||||
this.$message.success("添加成功");
|
||||
await this.$store.dispatch(TREE_DATA + "/" + addNode, {
|
||||
sourceNode: this.breadList.length == 0 ? null : this.breadList[this.breadList.length - 1],
|
||||
targetNode: res,
|
||||
});
|
||||
await this.$store.dispatch(TREE_DATA + "/" + addNode, { sourceNode: null, targetNode: res });
|
||||
setTimeout(this.closeIframe, 500);
|
||||
},
|
||||
//面包屑点击
|
||||
async breadClick(item) {
|
||||
console.log(item);
|
||||
//点击最后一个无效
|
||||
if (item == null && this.breadList.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (item === this.breadList[this.breadList.length - 1]) {
|
||||
return;
|
||||
}
|
||||
if (item == null) {
|
||||
this.breadList = [];
|
||||
} else {
|
||||
let index = this.breadList.indexOf(item);
|
||||
this.breadList = [...this.breadList.slice(0, index + 1)];
|
||||
}
|
||||
},
|
||||
//点击文件夹
|
||||
folderClick(item) {
|
||||
this.form.path = item.path + "." + item.bookmarkId;
|
||||
this.breadList.push(item);
|
||||
},
|
||||
//新增文件夹
|
||||
async addFolder() {
|
||||
let length = this.addFolderName.trim().length;
|
||||
if (length == 0) {
|
||||
this.$message.error("文件夹名称不为空");
|
||||
return;
|
||||
}
|
||||
if (length > 200) {
|
||||
this.$message.error("文件夹名称长度不能大于200");
|
||||
return;
|
||||
}
|
||||
let form = {
|
||||
name: this.addFolderName,
|
||||
path: this.getCurrentPath(),
|
||||
type: 1,
|
||||
url: "",
|
||||
};
|
||||
let res = await HttpUtil.put("/bookmark", null, form);
|
||||
await this.$store.dispatch(TREE_DATA + "/" + addNode, {
|
||||
sourceNode: this.breadList.length == 0 ? null : this.breadList[this.breadList.length - 1],
|
||||
targetNode: res,
|
||||
});
|
||||
this.addFolderName = "";
|
||||
this.showAddInput = false;
|
||||
this.breadList = [...this.breadList];
|
||||
this.$message.success("新增成功");
|
||||
},
|
||||
//获取当前文件夹路径
|
||||
getCurrentPath() {
|
||||
if (this.breadList.length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
let lastOne = this.breadList[this.breadList.length - 1];
|
||||
return lastOne.path + "." + lastOne.bookmarkId;
|
||||
}
|
||||
},
|
||||
//删除一个
|
||||
async deleteOne(item) {
|
||||
let body = { pathList: [], bookmarkIdList: [] };
|
||||
item.type == 0 ? body.bookmarkIdList.push(item.bookmarkId) : body.pathList.push(item.path + "." + item.bookmarkId);
|
||||
await HttpUtil.post("/bookmark/batchDelete", null, body);
|
||||
await this.$store.dispatch(TREE_DATA + "/" + deleteData, body);
|
||||
this.$message.success("删除成功");
|
||||
},
|
||||
//显示输入框
|
||||
showFolderInput() {
|
||||
this.showAddInput = true;
|
||||
console.log(this.$refs);
|
||||
this.$nextTick(() => this.$refs.folderInput.focus());
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ssoAddBookmark {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5em;
|
||||
padding-bottom: 1em;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: white;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.list {
|
||||
padding-top: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
|
||||
.path {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
font-size: 0.9em;
|
||||
|
||||
.breadItem {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.breadItem:last-child {
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
.folderList {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 0;
|
||||
margin-left: 0.5em;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.actionBar {
|
||||
display: none;
|
||||
width: 2em;
|
||||
align-items: center;
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
cursor: pointer;
|
||||
color: rgb(24, 144, 255);
|
||||
}
|
||||
|
||||
.bg:hover {
|
||||
background: rgba(74, 74, 74, 0.3);
|
||||
}
|
||||
|
||||
.item:hover .actionBar {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
padding-top: 1em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -5,23 +5,18 @@
|
||||
源码地址:
|
||||
<a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a>
|
||||
</div>
|
||||
<div>
|
||||
当前版本:{{ appVersion }}  <a v-if="showNewVersion"
|
||||
href="https://github.com/FleyX/bookmark/blob/master/DEPLOY.md">最新版本:{{ latestVersion
|
||||
}}</a>
|
||||
</div>
|
||||
<div>
|
||||
使用教程:
|
||||
<a href="https://blog.fleyx.com/blog/detail/20220329" target="_blank">点击跳转</a>
|
||||
</div>
|
||||
<div>
|
||||
浏览器插件:
|
||||
<a href="/static/bookmarkBrowserPlugin.zip" download="浏览器插件.zip"
|
||||
target="_blank">最新版本{{ serverConfig.map.pluginVersion }}(注意更新)</a>
|
||||
<a href="/static/bookmarkBrowserPlugin.7z" download="浏览器插件.7z" target="_blank">点击下载</a>
|
||||
,使用详情请参考使用教程
|
||||
</div>
|
||||
<div>交流反馈qq群:150056494,邮箱:fleyx20@outlook.com</div>
|
||||
<div>
|
||||
统计:
|
||||
<a href="https://qiezi.fleyx.com" style="" target="_blank">
|
||||
<div id="qieziStatisticHtmlHostPv" style="display: none">
|
||||
总访问次数:
|
||||
@ -39,51 +34,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { GLOBAL_CONFIG, SERVER_CONFIG } from "@/store/modules/globalConfig";
|
||||
import httpUtil from "@/util/HttpUtil";
|
||||
|
||||
export default {
|
||||
name: "about",
|
||||
data() {
|
||||
return {
|
||||
appVersion: "1.4", //应用版本
|
||||
latestVersion: null,
|
||||
showNewVersion: false
|
||||
};
|
||||
},
|
||||
computed: { ...mapState(GLOBAL_CONFIG, [SERVER_CONFIG]) },
|
||||
async mounted() {
|
||||
|
||||
|
||||
//获取最新版本
|
||||
let res = await httpUtil.http.default.get("https://s3.fleyx.com/picbed/bookmark/config.json");
|
||||
console.log(res);
|
||||
this.latestVersion = res.data.latestAppVersion;
|
||||
this.showNewVersion = this.checkVersion(this.appVersion, this.latestVersion);
|
||||
},
|
||||
methods: {
|
||||
checkVersion: function(version, latestVersion) {
|
||||
if (version === latestVersion) {
|
||||
return false;
|
||||
}
|
||||
let versions = version.split(".");
|
||||
let latestVersions = latestVersion.split(".");
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
if (i >= latestVersions.length) {
|
||||
return false;
|
||||
}
|
||||
let versionNum = parseInt(versions[i]);
|
||||
let latestVersionNum = parseInt(latestVersions[i]);
|
||||
if (versionNum !== latestVersionNum) {
|
||||
return versionNum < latestVersionNum;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
mounted() {
|
||||
window.qieziStatisticKey = "b74c4b571b644782a837433209827874";
|
||||
let script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.defer = true;
|
||||
script.src = "https://qiezi.fleyx.com/qiezijs/1.0/qiezi_statistic.min.js";
|
||||
document.getElementsByTagName("head")[0].appendChild(script);
|
||||
}
|
||||
}
|
||||
;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="thirdPart">
|
||||
<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-tooltip>
|
||||
</div>
|
||||
@ -39,44 +39,39 @@ import Header from "@/components/public/Switch.vue";
|
||||
import httpUtil from "../../../util/HttpUtil.js";
|
||||
import { mapMutations, mapState } from "vuex";
|
||||
import HttpUtil from "../../../util/HttpUtil.js";
|
||||
|
||||
export default {
|
||||
name: "Login",
|
||||
components: {
|
||||
Header
|
||||
Header,
|
||||
},
|
||||
computed: {
|
||||
...mapState("globalConfig", ["serverConfig"])
|
||||
...mapState("globalConfig", ["serverConfig"]),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
str: "",
|
||||
password: "",
|
||||
rememberMe: false
|
||||
rememberMe: false,
|
||||
},
|
||||
rules: {
|
||||
str: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" }
|
||||
{ min: 1, max: 50, message: "最短1,最长50", trigger: "change" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" }
|
||||
]
|
||||
{ pattern: "^\\w{6,18}$", message: "密码为6-18位数字,字母,下划线组合", trigger: "change" },
|
||||
],
|
||||
},
|
||||
loading: false, //是否加载中
|
||||
oauthLogining: false, //true:正在进行oauth后台操作
|
||||
page: null, //oauth打开的页面实例
|
||||
redirect: null
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
let _this = this;
|
||||
window.addEventListener("storage", this.storageDeal.bind(this));
|
||||
if (this.$route.query.to) {
|
||||
this.redirect = atob(this.$route.query.to);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("storage", this.storageDeal);
|
||||
@ -90,7 +85,7 @@ export default {
|
||||
this.loading = true;
|
||||
let token = await httpUtil.post("/user/login", null, this.form);
|
||||
await this.$store.dispatch("globalConfig/setToken", token);
|
||||
this.$router.replace(this.redirect ? this.redirect : "/");
|
||||
this.$router.replace("/");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@ -131,8 +126,8 @@ export default {
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -140,13 +135,11 @@ export default {
|
||||
.form {
|
||||
margin: 0.3rem;
|
||||
margin-bottom: 0.1rem;
|
||||
|
||||
.reset {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.thirdPart {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -1,21 +1,20 @@
|
||||
module.exports = {
|
||||
lintOnSave: false,
|
||||
configureWebpack: {
|
||||
devtool: "source-map"
|
||||
},
|
||||
devServer: {
|
||||
port: 4531,
|
||||
proxy: {
|
||||
"/bookmark/api": {
|
||||
//这里最好有一个 /
|
||||
target: "http://localhost:8088", // 服务器端接口地址
|
||||
ws: true, //如果要代理 websockets,配置这个参数
|
||||
// 如果是https接口,需要配置这个参数
|
||||
changeOrigin: true, //是否跨域
|
||||
pathRewrite: {
|
||||
"^/bookmark/api": "/bookmark/api"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lintOnSave: false,
|
||||
configureWebpack: {
|
||||
devtool: "source-map"
|
||||
},
|
||||
devServer: {
|
||||
proxy: {
|
||||
"/bookmark/api": {
|
||||
//这里最好有一个 /
|
||||
target: "http://localhost:8088", // 服务器端接口地址
|
||||
ws: true, //如果要代理 websockets,配置这个参数
|
||||
// 如果是https接口,需要配置这个参数
|
||||
changeOrigin: true, //是否跨域
|
||||
pathRewrite: {
|
||||
"^/bookmark/api": "/bookmark/api"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
7
build.sh
7
build.sh
@ -3,11 +3,8 @@ base=$(cd "$(dirname "$0")";pwd)
|
||||
echo $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"
|
@ -14,7 +14,6 @@ services:
|
||||
- ./data/mysql/my.cnf:/etc/mysql/my.cnf
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./data/timezone:/etc/timezone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
|
||||
- MYSQL_DATABASE=bookmark
|
||||
@ -26,7 +25,6 @@ services:
|
||||
- /etc/localtime:/etc/localtime
|
||||
- ./data/timezone:/etc/timezone
|
||||
- ./data/redis:/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- bookmark
|
||||
|
||||
@ -40,7 +38,6 @@ services:
|
||||
- ./bookmark_front/dist:/opt/dist
|
||||
- ./data/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ${BOOKMARK_FILE_SAVE_PATH}/files/public:/opt/files/public
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
@ -63,7 +60,6 @@ services:
|
||||
- ./bookMarkService/web/target/bookmark-web-1.0-SNAPSHOT.jar:/opt/app/service.jar
|
||||
- ${BOOKMARK_FILE_SAVE_PATH}:/opt/files
|
||||
working_dir: /opt/app
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
|
15
浏览器插件/bookmark-chrome/.babelrc
Normal file
15
浏览器插件/bookmark-chrome/.babelrc
Normal 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"]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
8
浏览器插件/bookmark-chrome/.editorConfig
Normal file
8
浏览器插件/bookmark-chrome/.editorConfig
Normal file
@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
33
浏览器插件/bookmark-chrome/.eslintrc.js
Normal file
33
浏览器插件/bookmark-chrome/.eslintrc.js
Normal 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'
|
||||
}
|
||||
}
|
4
浏览器插件/bookmark-chrome/.gitignore
vendored
Normal file
4
浏览器插件/bookmark-chrome/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
/*.log
|
||||
/dist
|
||||
/dist-zip
|
5
浏览器插件/bookmark-chrome/.prettierrc
Normal file
5
浏览器插件/bookmark-chrome/.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 180,
|
||||
"trailingComma": "es5"
|
||||
}
|
68
浏览器插件/bookmark-chrome/package.json
Normal file
68
浏览器插件/bookmark-chrome/package.json
Normal 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"
|
||||
}
|
||||
}
|
53
浏览器插件/bookmark-chrome/scripts/build-zip.js
Normal file
53
浏览器插件/bookmark-chrome/scripts/build-zip.js
Normal 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();
|
52
浏览器插件/bookmark-chrome/src/background.js
Normal file
52
浏览器插件/bookmark-chrome/src/background.js
Normal 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 });
|
||||
}
|
BIN
浏览器插件/bookmark-chrome/src/icons/icon.xcf
Normal file
BIN
浏览器插件/bookmark-chrome/src/icons/icon.xcf
Normal file
Binary file not shown.
BIN
浏览器插件/bookmark-chrome/src/icons/icon_128.png
Normal file
BIN
浏览器插件/bookmark-chrome/src/icons/icon_128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
BIN
浏览器插件/bookmark-chrome/src/icons/icon_48.png
Normal file
BIN
浏览器插件/bookmark-chrome/src/icons/icon_48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
32
浏览器插件/bookmark-chrome/src/manifest.json
Normal file
32
浏览器插件/bookmark-chrome/src/manifest.json
Normal 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"]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
17
浏览器插件/bookmark-chrome/src/options/App.vue
Normal file
17
浏览器插件/bookmark-chrome/src/options/App.vue
Normal 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>
|
15
浏览器插件/bookmark-chrome/src/options/options.html
Normal file
15
浏览器插件/bookmark-chrome/src/options/options.html
Normal 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>
|
10
浏览器插件/bookmark-chrome/src/options/options.js
Normal file
10
浏览器插件/bookmark-chrome/src/options/options.js
Normal 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),
|
||||
});
|
20
浏览器插件/bookmark-chrome/src/popup/App.vue
Normal file
20
浏览器插件/bookmark-chrome/src/popup/App.vue
Normal 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>
|
18
浏览器插件/bookmark-chrome/src/popup/popup.html
Normal file
18
浏览器插件/bookmark-chrome/src/popup/popup.html
Normal 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>
|
55
浏览器插件/bookmark-chrome/src/popup/popup.js
Normal file
55
浏览器插件/bookmark-chrome/src/popup/popup.js
Normal 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),
|
||||
});
|
@ -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>
|
@ -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>
|
9
浏览器插件/bookmark-chrome/src/popup/router/index.js
Normal file
9
浏览器插件/bookmark-chrome/src/popup/router/index.js
Normal 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,
|
||||
});
|
50
浏览器插件/bookmark-chrome/src/popup/router/pages/Index.vue
Normal file
50
浏览器插件/bookmark-chrome/src/popup/router/pages/Index.vue
Normal 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>
|
@ -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>
|
13
浏览器插件/bookmark-chrome/src/popup/router/routes.js
Normal file
13
浏览器插件/bookmark-chrome/src/popup/router/routes.js
Normal 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,
|
||||
},
|
||||
];
|
BIN
浏览器插件/bookmark-chrome/src/static/fonts/element-icons.ttf
Normal file
BIN
浏览器插件/bookmark-chrome/src/static/fonts/element-icons.ttf
Normal file
Binary file not shown.
BIN
浏览器插件/bookmark-chrome/src/static/fonts/element-icons.woff
Normal file
BIN
浏览器插件/bookmark-chrome/src/static/fonts/element-icons.woff
Normal file
Binary file not shown.
BIN
浏览器插件/bookmark-chrome/src/static/img/bookmarkLogo.png
Normal file
BIN
浏览器插件/bookmark-chrome/src/static/img/bookmarkLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
7
浏览器插件/bookmark-chrome/src/static/js/localforage.min.js
vendored
Normal file
7
浏览器插件/bookmark-chrome/src/static/js/localforage.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
33
浏览器插件/bookmark-chrome/src/static/sso.js
Normal file
33
浏览器插件/bookmark-chrome/src/static/sso.js
Normal 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);
|
||||
}
|
||||
});
|
5
浏览器插件/bookmark-chrome/src/store/actions.js
Normal file
5
浏览器插件/bookmark-chrome/src/store/actions.js
Normal file
@ -0,0 +1,5 @@
|
||||
import * as types from './mutation-types'
|
||||
|
||||
export const setFoo = ({commit}, payload) => {
|
||||
commit(types.UPDATE_FOO, payload)
|
||||
}
|
1
浏览器插件/bookmark-chrome/src/store/getters.js
Normal file
1
浏览器插件/bookmark-chrome/src/store/getters.js
Normal file
@ -0,0 +1 @@
|
||||
export const foo = (state) => state.foo
|
17
浏览器插件/bookmark-chrome/src/store/index.js
Normal file
17
浏览器插件/bookmark-chrome/src/store/index.js
Normal 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,
|
||||
});
|
1
浏览器插件/bookmark-chrome/src/store/mutation-types.js
Normal file
1
浏览器插件/bookmark-chrome/src/store/mutation-types.js
Normal file
@ -0,0 +1 @@
|
||||
export const UPDATE_FOO = 'UPDATE_FOO'
|
7
浏览器插件/bookmark-chrome/src/store/mutations.js
Normal file
7
浏览器插件/bookmark-chrome/src/store/mutations.js
Normal file
@ -0,0 +1,7 @@
|
||||
import * as types from './mutation-types'
|
||||
|
||||
export default {
|
||||
[types.UPDATE_FOO] (state, payload) {
|
||||
state.foo = payload
|
||||
}
|
||||
}
|
16
浏览器插件/bookmark-chrome/src/util/config.js
Normal file
16
浏览器插件/bookmark-chrome/src/util/config.js
Normal 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;
|
0
浏览器插件/bookmark-chrome/src/util/const.js
Normal file
0
浏览器插件/bookmark-chrome/src/util/const.js
Normal file
37
浏览器插件/bookmark-chrome/src/util/httpUtil.js
Normal file
37
浏览器插件/bookmark-chrome/src/util/httpUtil.js
Normal 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;
|
123
浏览器插件/bookmark-chrome/webpack.config.js
Normal file
123
浏览器插件/bookmark-chrome/webpack.config.js
Normal 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;
|
@ -4,18 +4,17 @@ chrome.runtime.onInstalled.addListener(() => {
|
||||
title: '添加到书签',
|
||||
id: "addBookmark",
|
||||
},
|
||||
() => console.debug("创建右键菜单成功")
|
||||
() => console.log("创建右键菜单成功")
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
chrome.contextMenus.onClicked.addListener(async function (info, tab) {
|
||||
console.debug(info, tab);
|
||||
console.log(info, tab);
|
||||
let body = {
|
||||
name: tab.title,
|
||||
url: tab.url,
|
||||
iconUrl: tab.favIconUrl
|
||||
};
|
||||
sendToContent(tab.id, { code: "addBookmark", data: body, token: await getVal("token") });
|
||||
});
|
||||
@ -27,15 +26,14 @@ chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => {
|
||||
return;
|
||||
}
|
||||
sendResponse("ok");
|
||||
console.debug("收到消息:", data, sender);
|
||||
console.log("收到消息:", data, sender);
|
||||
if (data.code == 'setToken') {
|
||||
await setVal("token", data.data);
|
||||
// sendToContent
|
||||
await sendToContent(sender.tab.id, { code: "setTokenOk" });
|
||||
} else if (data.code == 'getToken') {
|
||||
let token = await getVal("token");
|
||||
|
||||
sendToPopup({ code: "setToken", data: token });
|
||||
sendToPopup({ code: "setToken", data: await getVal("token") });
|
||||
} else if (data.code == "clearToken") {
|
||||
await clearVal("token");
|
||||
}
|
||||
@ -47,10 +45,10 @@ chrome.runtime.onMessage.addListener(async (data, sender, sendResponse) => {
|
||||
* @param {*} data
|
||||
*/
|
||||
function sendToContent (tabId, data) {
|
||||
console.debug(tabId, data);
|
||||
console.log(tabId, data);
|
||||
data.receiver = "content";
|
||||
chrome.tabs.sendMessage(tabId, data, res => {
|
||||
console.debug("接受响应", res);
|
||||
console.log("接受响应", res);
|
||||
})
|
||||
}
|
||||
|
||||
@ -60,7 +58,7 @@ function sendToContent (tabId, data) {
|
||||
*/
|
||||
function sendToPopup (data) {
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.set({ [key]: val }, function () {
|
||||
console.debug("设置值成功:", key, val)
|
||||
console.log("设置值成功:", key, val)
|
||||
resolve();
|
||||
})
|
||||
})
|
||||
@ -85,13 +83,8 @@ function setVal (key, val) {
|
||||
*/
|
||||
function getVal (key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.get([key], async function (res) {
|
||||
if (key === 'token' && !checkTokenValid(res[key])) {
|
||||
console.debug("token过期");
|
||||
await clearVal("token");
|
||||
res[key] = null;
|
||||
}
|
||||
console.debug("取值成功", res);
|
||||
chrome.storage.local.get([key], function (res) {
|
||||
console.log("取值成功", res);
|
||||
resolve(res[key]);
|
||||
})
|
||||
})
|
||||
@ -100,28 +93,8 @@ function getVal (key) {
|
||||
function clearVal (key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.remove(key, function () {
|
||||
console.debug("remove成功", key);
|
||||
console.log("remove成功", key);
|
||||
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;
|
||||
}
|
@ -4,15 +4,8 @@
|
||||
"version": "1.0",
|
||||
"manifest_version": 3,
|
||||
"permissions": ["contextMenus", "storage"],
|
||||
"chrome_url_overrides": {
|
||||
"newtab": "tab/index.html"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup/index.html",
|
||||
"default_icon": {
|
||||
"48": "static/icons/favicon.png",
|
||||
"128": "static/icons/favicon.png"
|
||||
}
|
||||
"default_popup": "popup/index.html"
|
||||
},
|
||||
"icons": {
|
||||
"48": "static/icons/favicon.png",
|
||||
|
@ -8,7 +8,7 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0.2em;
|
||||
min-width: 8em;
|
||||
width: 8em;
|
||||
}
|
||||
#content {
|
||||
color: red;
|
||||
@ -16,18 +16,14 @@
|
||||
</style>
|
||||
</head>
|
||||
<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">
|
||||
<button id="logout">退出登陆</button>
|
||||
<!-- <button title="同步云端书签到浏览器(覆盖本地)">同步云端书签</button> -->
|
||||
<!-- <button title="同步浏览器书签到云端(覆盖云端)">同步浏览器</button> -->
|
||||
</div>
|
||||
<p id="content"></p>
|
||||
<div class="bottom">
|
||||
<div>插件版本:<span id="version"></span></div>
|
||||
<div>最新版本:<a href="" id="newestVersion">最新版本</a></div>
|
||||
<div><a id="about" target="_blank">关于</a></span></div>
|
||||
</div>
|
||||
<div class="bottom">插件版本:<span id="version"></span></div>
|
||||
<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="/popup/index.js"></script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
console.debug("asdf");
|
||||
console.debug(bookmarkHost);
|
||||
console.log("asdf");
|
||||
console.log(bookmarkHost);
|
||||
|
||||
var token;
|
||||
var login = document.getElementById("login");
|
||||
@ -9,20 +9,14 @@ var action = document.getElementById("action");
|
||||
//初始化
|
||||
login.href = bookmarkHost + "/manage/sso/auth";
|
||||
document.getElementById("version").innerText = version;
|
||||
document.getElementById("about").href = bookmarkHost + "/public/about";
|
||||
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", () => {
|
||||
console.debug("click");
|
||||
console.log("click");
|
||||
sendToBg("clearToken", null);
|
||||
action.style.display = "none";
|
||||
login.style.display = "block";
|
||||
@ -33,7 +27,7 @@ document.getElementById("logout").addEventListener("click", () => {
|
||||
* @param {*} 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;
|
||||
}
|
||||
sendResponse("ok");
|
||||
console.debug("popup收到消息:", data);
|
||||
console.log("popup收到消息:", data);
|
||||
if (data.code == 'setToken') {
|
||||
token = data.data;
|
||||
if (token) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user