Compare commits

..

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

103 changed files with 1414 additions and 11157 deletions

View File

@ -1,24 +0,0 @@
kind: pipeline
type: docker
name: bookmarkPublish
trigger:
branch:
- master
clone:
disable: true
steps:
- name: deploy
image: appleboy/drone-ssh
settings:
host:
from_secret: devHost
port: 22
user: root
key:
from_secret: privateSsh
command_timeout: 30m
script:
- cd /root/bookmark && git pull && bash build.sh && bash syncFile.sh

View File

@ -1,20 +1,10 @@
本程序基于 docker 来进行部署,使用 docker-compose 管理服务。
**注意,仅在 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` 启动服务。

100
README.md
View File

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

View File

@ -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>

View File

@ -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);
}

View File

@ -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);
}

View File

@ -3,7 +3,6 @@ package com.fanxb.bookmark.business.bookmark.service;
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
import com.fanxb.bookmark.business.bookmark.entity.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: 详情

View File

@ -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;
}
}

View File

@ -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>

View File

@ -1,47 +0,0 @@
package com.fanxb.bookmark.business.user.controller;
import com.fanxb.bookmark.business.user.dao.SearchEngineDao;
import com.fanxb.bookmark.business.user.entity.SearchEngine;
import com.fanxb.bookmark.business.user.service.SearchEngineService;
import com.fanxb.bookmark.common.entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/searchEngine")
public class SearchEngineController {
@Autowired
private SearchEngineService searchEngineService;
/**
* 列表查询
*/
@GetMapping("/list")
public Result list() {
return Result.success(searchEngineService.list());
}
@PostMapping("/insert")
public Result insert(@RequestBody SearchEngine body){
searchEngineService.insertOne(body);
return Result.success();
}
@PostMapping("/edit")
public Result edit(@RequestBody SearchEngine body){
searchEngineService.editOne(body);
return Result.success();
}
@PostMapping("/delete")
public Result delete(@RequestBody SearchEngine body){
searchEngineService.deleteOne(body.getId());
return Result.success();
}
@PostMapping("/setChecked")
public Result setChecked(@RequestBody SearchEngine body){
searchEngineService.setChecked(body.getId());
return Result.success();
}
}

View File

@ -1,8 +0,0 @@
package com.fanxb.bookmark.business.user.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fanxb.bookmark.business.user.entity.SearchEngine;
public interface SearchEngineDao extends BaseMapper<SearchEngine> {
}

View File

@ -1,6 +1,5 @@
package com.fanxb.bookmark.business.user.dao;
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);
/**
* 更新一个字段-一个条件

View File

@ -1,29 +0,0 @@
package com.fanxb.bookmark.business.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@TableName("search_engine")
public class SearchEngine {
@TableId(type = IdType.AUTO)
private Integer id;
private Integer userId;
private Integer checked;
/**
* 名称
*/
private String name;
/**
* url
*/
private String url;
/**
* 图标
*/
private String icon;
}

View File

@ -1,49 +0,0 @@
package com.fanxb.bookmark.business.user.service;
import com.fanxb.bookmark.business.user.entity.SearchEngine;
import java.util.List;
public interface SearchEngineService {
/**
* 列表查询
*/
List<SearchEngine> list();
/**
* delete one by id
*
* @param id id
*/
void deleteOne(int id);
/**
* insert one
*
* @param body body
*/
void insertOne(SearchEngine body);
/**
* edit one
*
* @param body body
*/
void editOne(SearchEngine body);
/**
* 设为默认搜索项
*
* @param id
*/
void setChecked(Integer id);
/**
* 新用户初始化
*
* @param userId userId
*/
void newUserInit(int userId);
}

View File

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

View File

@ -4,7 +4,6 @@ import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.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())) {

View File

@ -1,88 +0,0 @@
package com.fanxb.bookmark.business.user.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.support.ibatis.SpringIbatisBeanNameAutoProxyCreator;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fanxb.bookmark.business.user.dao.SearchEngineDao;
import com.fanxb.bookmark.business.user.dao.UserDao;
import com.fanxb.bookmark.business.user.entity.SearchEngine;
import com.fanxb.bookmark.business.user.service.SearchEngineService;
import com.fanxb.bookmark.common.entity.UserContext;
import com.fanxb.bookmark.common.entity.po.User;
import com.fanxb.bookmark.common.exception.CustomException;
import com.fanxb.bookmark.common.util.UserContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class SearchEngineServiceImpl implements SearchEngineService {
@Autowired
private SearchEngineDao searchEngineDao;
@Autowired
private UserDao userDao;
@Override
public List<SearchEngine> list() {
return searchEngineDao.selectList(new LambdaQueryWrapper<SearchEngine>().eq(SearchEngine::getUserId, UserContextHolder.get().getUserId()));
}
@Override
public void deleteOne(int id) {
SearchEngine engine = searchEngineDao.selectById(id);
if (engine.getUserId() != UserContextHolder.get().getUserId()) {
throw new CustomException("无法操作其他人数据");
}
if (engine.getChecked() == 1) {
throw new CustomException("默认搜索引擎无法删除");
}
searchEngineDao.deleteById(id);
}
@Override
public void insertOne(SearchEngine body) {
checkOne(body);
body.setId(null).setChecked(0).setUserId(UserContextHolder.get().getUserId());
searchEngineDao.insert(body);
}
private void checkOne(SearchEngine body) {
if (StrUtil.hasBlank(body.getIcon(), body.getUrl(), body.getName())) {
throw new CustomException("请填写完整");
}
if (!body.getUrl().contains("%s")) {
throw new CustomException("路径中必须包含%s");
}
}
@Override
public void editOne(SearchEngine body) {
SearchEngine engine = searchEngineDao.selectById(body.getId());
if (engine.getUserId() != UserContextHolder.get().getUserId()) {
throw new CustomException("无法操作其他人数据");
}
checkOne(body);
searchEngineDao.updateById(body);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void setChecked(Integer id) {
int userId = UserContextHolder.get().getUserId();
LambdaUpdateWrapper<SearchEngine> update = new LambdaUpdateWrapper<SearchEngine>().set(SearchEngine::getChecked, 0).eq(SearchEngine::getUserId, userId).eq(SearchEngine::getChecked, 1);
searchEngineDao.update(null, update);
update = new LambdaUpdateWrapper<SearchEngine>().set(SearchEngine::getChecked, 1).eq(SearchEngine::getId, id).eq(SearchEngine::getUserId, userId);
searchEngineDao.update(null, update);
}
@Override
public void newUserInit(int userId) {
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-baidu").setName("百度").setUrl("https://www.baidu.com/s?ie=UTF-8&wd=%s").setChecked(1));
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-bing").setName("必应").setUrl("https://www.bing.com/search?q=%s").setChecked(0));
searchEngineDao.insert(new SearchEngine().setUserId(userId).setIcon("icon-google").setName("谷歌").setUrl("https://www.google.com/search?q=%s").setChecked(0));
}
}

View File

@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
import com.fanxb.bookmark.business.api.BookmarkApi;
import com.fanxb.bookmark.business.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);

View File

@ -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>
<!-- &lt;!&ndash;邮件依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-mail</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;减负依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.projectlombok</groupId>-->
<!-- <artifactId>lombok</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash;json工具依赖&ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>fastjson</artifactId>-->
<!-- <version>1.2.83</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.elasticsearch.client</groupId>-->
<!-- <artifactId>elasticsearch-rest-high-level-client</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.hutool</groupId>-->
<!-- <artifactId>hutool-all</artifactId>-->
<!-- <version>5.8.21</version>-->
<!-- </dependency>-->
<!--单元测试-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<!--mysql jdbc依赖-->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <version>8.0.33</version>-->
<!-- </dependency>-->
<!--邮件依赖-->
<dependency>
<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>
<!--单元测试-->

View File

@ -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线程池成功");
}
}

View File

@ -1,14 +0,0 @@
package com.fanxb.bookmark.common.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fanxb.bookmark.common.entity.po.GlobalConfigPo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author fanxb
*/
@Mapper
public interface GlobalConfigDao extends BaseMapper<GlobalConfigPo> {
}

View File

@ -12,6 +12,7 @@ import java.util.List;
* 类功能详述
*
* @author fanxb
* @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);

View File

@ -1,20 +0,0 @@
package com.fanxb.bookmark.common.entity.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 全局配置表
*
* @author FleyX
*/
@Data
@TableName("global_config")
public class GlobalConfigPo {
@TableId(value = "code")
private String code;
private String value;
private String description;
}

View File

@ -2,10 +2,6 @@ package com.fanxb.bookmark.common.entity.po;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
/**
* 获取客户端
*

View File

@ -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>

View File

@ -1,13 +0,0 @@
CREATE TABLE bookmark.global_config
(
code varchar(20) NOT NULL,
value varchar(100) NOT NULL COMMENT '',
description varchar(100) NOT NULL COMMENT '描述',
CONSTRAINT global_config_pk PRIMARY KEY (code)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_0900_ai_ci
COMMENT='全局配置表';
insert into global_config
values ("pluginVersion", "0.1.1", "浏览器插件版本");

View File

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

View File

@ -1,22 +0,0 @@
create table search_engine
(
id int auto_increment
primary key,
userId int not null,
checked tinyint not null default 0,
name varchar(20) null,
url varchar(500) null,
icon varchar(20) null
) auto_increment=1001;
create index search_engine_userId_index
on search_engine (userId);
insert into search_engine(userId, checked, name, url, icon)
select userId, if(defaultSearchEngine = 'baidu', 1, 0), '百度', 'https://www.baidu.com/s?ie=UTF-8&wd=%s', 'icon-baidu'
from user;
insert into search_engine(userId, checked, name, url, icon)
select userId, if(defaultSearchEngine = 'bing', 1, 0), '必应', 'https://www.bing.com/search?q=%s', 'icon-bing'
from user;
insert into search_engine(userId, checked, name, url, icon)
select userId, if(defaultSearchEngine = 'google', 1, 0), '谷歌', 'https://www.google.com/search?q=%s', 'icon-google'
from user;

View File

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

View File

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

View File

@ -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",

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -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;

View File

@ -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;
}

View File

@ -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: {
/**

View File

@ -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>

View File

@ -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>

View File

@ -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");

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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
};

View File

@ -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']">

View File

@ -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>

View File

@ -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>

View File

@ -1,164 +0,0 @@
<template>
<div>
<a-button type="primary" @click="addOne">新增</a-button>
<a-table :columns="columns" :data-source="list" :pagination="false">
<template v-for="col in ['name', 'url']" :slot="col" slot-scope="text, record, index">
<div :key="col">
<a-input v-if="record.isEdit" style="margin: -5px 0" :value="text"
@change="e => handleChange(e.target.value, record.id, col)" />
<template v-else>{{ text }}</template>
</div>
</template>
<template slot="icon" slot-scope="text, record, index">
<div key="icon">
<a-select v-if="record.isEdit" :default-value="text" style="width: 120px"
@change="e => handleChange(e, record.id, 'icon')">
<a-select-option v-for="item in iconList" :key="item.icon" :value="item.icon">
<my-icon :type="item.icon" />
{{ item.label }}
</a-select-option>
</a-select>
<template v-else>
<my-icon style="font-size: 1.2em" :type="text" />
</template>
</div>
</template>
<template slot="checked" slot-scope="text">
<span>{{ text === 1 ? "是" : "否" }}</span>
</template>
<template slot="operation" slot-scope="text, record, index">
<div class="editable-row-operations">
<span v-if="record.isEdit">
<a @click="() => save(record.id)">保存</a>
&nbsp;<a @click="() => cancel(record.id)">取消</a>
</span>
<div v-else>
<a :disabled="currentEditCache" @click="() => edit(record.id)">编辑</a>&nbsp;
<a-popconfirm title="确认删除吗?" ok-text="" cancel-text="" @confirm="() => deleteOne(record.id)">
<a :disabled="currentEditCache">删除</a>
</a-popconfirm>&nbsp;
<a :disabled="currentEditCache || record.checked===1" @click="() => setDefault(record.id)">设为默认</a>
</div>
</div>
</template>
</a-table>
</div>
</template>
<script>
import HttpUtil from "@/util/HttpUtil";
export default {
name: "manageSearchEngine",
data() {
return {
list: [],
currentEditCache: null,
iconList: [
{ icon: "icon-baidu", label: "百度" },
{ icon: "icon-bing", label: "必应" },
{ icon: "icon-google", label: "谷歌" },
{ icon: "icon-yandex", label: "yandex" },
{ icon: "icon-sogou", label: "搜狗" },
{ icon: "icon-yahoo", label: "雅虎" },
{ icon: "icon-qita", label: "其他" }
],
columns: [
{
title: "名称",
dataIndex: "name",
width: "10em",
scopedSlots: { customRender: "name" }
}, {
title: "图标",
dataIndex: "icon",
width: "8em",
scopedSlots: { customRender: "icon" }
}, {
title: "路径(%s 会被替换为搜索内容)",
dataIndex: "url",
scopedSlots: { customRender: "url" }
}, {
title: "默认",
dataIndex: "checked",
width: "10em",
scopedSlots: { customRender: "checked" }
}, {
title: "操作",
width: "15em",
dataIndex: "operation",
scopedSlots: { customRender: "operation" }
}]
};
},
async created() {
await this.getData();
},
computed: {
//
deleteOk() {
return this.list.filter(item => item.isEdit).length === 0;
}
},
methods: {
addOne() {
let body = { id: -1, icon: "", name: "", url: "", checked: 0, isEdit: true };
this.list = [body, ...this.list];
this.currentEditCache = body;
},
handleChange(value, id, column) {
console.log(value, id, column);
const target = this.list.find(item => item.id === id);
target[column] = value;
this.list = [...this.list];
},
async edit(id) {
let target = this.list.find(item => item.id === id);
this.currentEditCache = { ...target };
target.isEdit = true;
this.list = [...this.list];
},
async save(id) {
let target = this.list.find(item => item.id === id);
if (target.id > 0) {
await HttpUtil.post("/searchEngine/edit", null, target);
} else {
await HttpUtil.post("/searchEngine/insert", null, target);
}
target.isEdit = false;
this.currentEditCache = null;
await this.getData();
},
cancel(id) {
let target = this.list.find(item => item.id === id);
Object.assign(target, this.currentEditCache);
target.isEdit = false;
this.list = [...this.list];
this.currentEditCache = null;
},
async deleteOne(id) {
await HttpUtil.post("/searchEngine/delete", null, { id });
this.list = await HttpUtil.get("/searchEngine/list");
},
async setDefault(id) {
await HttpUtil.post("/searchEngine/setChecked", null, { id });
this.list = await HttpUtil.get("/searchEngine/list");
},
async getData() {
this.list = await HttpUtil.get("/searchEngine/list");
}
}
};
</script>
<style lang="less" scoped>
.icon {
color: black;
cursor: pointer;
font-size: 1.3em;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<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;
}

View File

@ -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>

View File

@ -5,23 +5,18 @@
源码地址
<a href="https://github.com/FleyX/bookmark" target="_blank">github.com/FleyX/bookmark</a>
</div>
<div>
当前版本{{ appVersion }}&emsp;&emsp;<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>

View File

@ -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;

View File

@ -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"
}
}
}
}
};

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,15 @@
{
"plugins": [
"@babel/plugin-proposal-optional-chaining"
],
"presets": [
["@babel/preset-env", {
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
// https://jamie.build/last-2-versions
"browsers": ["> 0.25%", "not ie 11", "not op_mini all"]
}
}]
]
}

View File

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

View File

@ -0,0 +1,33 @@
// https://eslint.org/docs/user-guide/configuring
// File taken from https://github.com/vuejs-templates/webpack/blob/1.3.1/template/.eslintrc.js, thanks.
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true,
webextensions: true,
},
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/essential',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard',
// https://prettier.io/docs/en/index.html
'plugin:prettier/recommended'
],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

View File

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

View File

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

View File

@ -0,0 +1,68 @@
{
"name": "bookmark-chrome",
"version": "1.0.0",
"description": "A Vue.js web extension",
"author": "fanxb <fanxb.tl@gmail.com>",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js,.vue src",
"prettier": "prettier \"src/**/*.{js,vue}\"",
"prettier:write": "npm run prettier -- --write",
"build": "cross-env NODE_ENV=production webpack --hide-modules",
"build:dev": "cross-env NODE_ENV=development webpack --hide-modules",
"build-zip": "node scripts/build-zip.js",
"watch": "npm run build -- --watch",
"watch:dev": "cross-env HMR=true npm run build:dev -- --watch"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"dependencies": {
"axios": "^0.19.0",
"element-ui": "^2.12.0",
"vue": "^2.6.10",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"webextension-polyfill": "^0.3.1"
},
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
"@babel/preset-env": "^7.1.0",
"@babel/runtime-corejs3": "^7.4.0",
"archiver": "^3.0.0",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.2",
"copy-webpack-plugin": "^4.5.3",
"core-js": "^3.0.1",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"ejs": "^2.6.1",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.3.0",
"eslint-config-standard": "^12.0.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^2.1.2",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^5.2.2",
"file-loader": "^1.1.11",
"husky": "^2.4.0",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.9.3",
"prettier": "^1.17.1",
"pretty-quick": "^1.8.0",
"sass-loader": "^7.1.0",
"vue-loader": "^15.4.2",
"vue-template-compiler": "^2.6.10",
"web-ext-types": "^2.1.0",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-extension-reloader": "^1.1.0"
}
}

View File

@ -0,0 +1,53 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const DEST_DIR = path.join(__dirname, '../dist');
const DEST_ZIP_DIR = path.join(__dirname, '../dist-zip');
const extractExtensionData = () => {
const extPackageJson = require('../package.json');
return {
name: extPackageJson.name,
version: extPackageJson.version,
};
};
const makeDestZipDirIfNotExists = () => {
if (!fs.existsSync(DEST_ZIP_DIR)) {
fs.mkdirSync(DEST_ZIP_DIR);
}
};
const buildZip = (src, dist, zipFilename) => {
console.info(`Building ${zipFilename}...`);
const archive = archiver('zip', { zlib: { level: 9 } });
const stream = fs.createWriteStream(path.join(dist, zipFilename));
return new Promise((resolve, reject) => {
archive
.directory(src, false)
.on('error', err => reject(err))
.pipe(stream);
stream.on('close', () => resolve());
archive.finalize();
});
};
const main = () => {
const { name, version } = extractExtensionData();
const zipFilename = `${name}-v${version}.zip`;
makeDestZipDirIfNotExists();
buildZip(DEST_DIR, DEST_ZIP_DIR, zipFilename)
.then(() => console.info('OK'))
.catch(console.err);
};
main();

View File

@ -0,0 +1,52 @@
import httpUtil from './util/httpUtil.js';
global.browser = require('webextension-polyfill');
window.envType = 'background';
window.token = localStorage.getItem('token');
let token = null;
let globalPort = null;
chrome.extension.onConnect.addListener(port => {
console.log(port);
globalPort = port;
port.onMessage.addListener(msg => {
switch (msg.type) {
case 'sendToken':
console.log(msg);
localStorage.setItem('token', msg.data);
window.token = msg.data;
token = msg.data;
break;
default:
console.error('未知的数据', msg);
}
});
});
chrome.contextMenus.create(
{
title: '添加到书签',
onclick: (info, tab) => {
console.log(info, tab);
httpUtil.put('/bookmark', {
type: 0,
path: '',
name: tab.title,
url: tab.url,
});
},
},
err => {
console.log(err);
}
);
/**
* 构建一个标准命令
* @param {*} code code
* @param {*} data data
*/
function createMsg(code, data) {
return JSON.stringify({ code, data });
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,32 @@
{
"name": "bookmark-chrome",
"description": "A Vue.js web extension",
"version": null,
"manifest_version": 2,
"permissions": [
"contextMenus"
],
"icons": {
"48": "icons/icon_48.png",
"128": "icons/icon_128.png"
},
"browser_action": {
"default_title": "bookmark-chrome",
"default_popup": "popup/popup.html"
},
"background": {
"scripts": [
"background.js"
]
},
"options_ui": {
"page": "options/options.html",
"chrome_style": true
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["static/sso.js"]
}
]
}

View File

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

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>bookmark-chrome - Options</title>
<link rel="stylesheet" href="options.css">
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head>
<body>
<div id="app"></div>
<script src="options.js"></script>
</body>
</html>

View File

@ -0,0 +1,10 @@
import Vue from 'vue';
import App from './App';
global.browser = require('webextension-polyfill');
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App),
});

View File

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

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head>
<body>
<div id="app">
</div>
<script src="/static/js/localforage.min.js"></script>
<script src="popup.js"></script>
</body>
</html>

View File

@ -0,0 +1,55 @@
import Vue from 'vue';
import App from './App';
import store from '../store';
import router from './router';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import config from '../util/config';
import axios from 'axios';
global.browser = require('webextension-polyfill');
Vue.prototype.$browser = global.browser;
Vue.use(ElementUI);
/**
* 配置axios
*/
axios.defaults.timeout = 15000;
axios.defaults.baseURL = config.baseUrl;
/**
* 请求拦截器
*/
axios.interceptors.request.use(
function(config) {
config.headers['jwt-token'] = window.token;
return config;
},
function(error) {
console.error(error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
res => {
if (res.data.code === -1) {
localStorage.removeItem('token');
window.vueInstance.$router.replace('/public/login');
} else if (res.data.code === 1) {
return res.data.data;
} else {
Promise.reject(res);
}
},
error => {
return Promise.reject(error);
}
);
/* eslint-disable no-new */
window.vueInstance = new Vue({
el: '#app',
store,
router,
render: h => h(App),
});

View File

@ -0,0 +1,74 @@
<template>
<div class="main">
<el-tree :props="props" :load="loadNode" accordion @node-click="nodeClick" lazy>
<span class="treeItem" slot-scope="{ node }">
<span>{{ node.label }}</span>
</span>
</el-tree>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'BookmarkTree',
data() {
return {
props: {
label: 'name',
children: 'children',
isLeaf: 'leaf',
},
treeObj: null,
};
},
methods: {
async loadNode(node, resolve) {
if (window.treeData == null) {
// eslint-disable-next-line no-undef
if ((window.treeData = await localforage.getItem('treeData')) == null) {
window.treeData = await axios.get('/bookmark/currentUser');
// eslint-disable-next-line no-undef
await localforage.setItem('treeData', window.treeData);
}
console.log(window.treeData);
}
console.log(node, resolve);
let list;
if (node.level === 0) {
list = window.treeData[''];
} else {
list = window.treeData[node.data.path + '.' + node.data.bookmarkId];
}
list.forEach(item => (item.leaf = item.type === 0));
console.log(list);
return resolve(list);
},
nodeClick(data) {
if (data.type === 1) {
return;
}
let url = data.url;
if (!url.startsWith('http')) {
url = 'http://' + url;
}
console.log(url);
window.open(data.url);
},
},
};
</script>
<style scoped>
.main {
width: 95%;
height: 500px;
overflow: auto;
margin: 0 auto;
}
.treeItem {
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<div>
<el-popover placement="bottom-start" width="550" popper-class="popover" trigger="manual" :visible-arrow="false" v-model="isShow">
<div>
<div class="item" v-for="item in searchList" :key="item.bookmarkId">
<a target="_blank" :href="item.url">{{ item.name }}</a>
</div>
</div>
<el-input
ref="searchInput"
type="text"
placeholder="搜索"
v-model="searchContent"
clearable
:autofocus="true"
slot="reference"
@blur="clear"
@keyup.enter.native="searchKey"
@focus="focus"
>
<el-button slot="append" icon="el-icon-search" @click="searchKey"></el-button>
</el-input>
</el-popover>
<div class="searchResult"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'searchBookmark',
data() {
return {
searchContent: '',
//
isShow: false,
//
isFocus: false,
searchList: [],
timeOut: null,
};
},
watch: {
searchContent(newVal, oldVal) {
if (newVal.trim().length === 0) {
return;
}
if (newVal.trim() !== oldVal) {
if (this.timeOut != null) {
clearTimeout(this.timeOut);
}
this.searchList = [];
this.timeOut = setTimeout(async () => {
this.searchList = await axios.get(`bookmark/searchUserBookmark?content=${newVal}`);
this.isShow = this.searchList.length > 0;
this.timeOut = null;
}, 200);
}
},
},
mounted() {
this.$nextTick(() => {
this.$refs['searchInput'].focus();
});
},
methods: {
searchKey() {
window.open('https://www.baidu.com/s?ie=UTF-8&wd=' + window.encodeURIComponent(this.searchContent));
},
focus() {
this.isFocus = true;
},
clear() {
this.isFocus = false;
if (this.timeOut != null) {
clearTimeout(this.timeOut);
}
this.searchContent = '';
this.searchList = [];
this.isShow = false;
},
},
};
</script>
<style scoped>
.item {
width: 500px;
padding: 5px;
border-bottom: 1px solid black;
}
</style>

View File

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

View File

@ -0,0 +1,50 @@
<template>
<div>
<div class="head">
<img width="30%" src="/static/img/bookmarkLogo.png" alt="icon" />
<span>{{ personInfo.username }}</span>
</div>
<!-- 书签检索 -->
<search />
</div>
</template>
<script>
import axios from 'axios';
import Search from '../components/SearchBookmark';
export default {
components: {
Search,
},
data() {
return {
personInfo: {},
};
},
created() {
window.token = localStorage.getItem('token');
if (!window.token) {
this.$router.replace('/public/login');
} else {
this.init();
}
},
methods: {
async init() {
let personInfo = await axios.get('/user/currentUserInfo');
window.personInfo = personInfo;
this.personInfo = personInfo;
},
},
};
</script>
<style lang="scss" scoped>
.head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 5% 0 5%;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div class="main">您还未登陆授权<br />
<el-button type="primary" @click="go">前往授权页面</el-button>
</div>
</template>
<script>
import config from '../../../../util/config';
export default {
name: 'login',
data() {
return {
};
},
mounted() {
this.checkToken();
},
methods: {
go() {
window.open(config.ssoUrl);
},
// token
checkToken() {
console.log('检测token是否获取到了');
let token = localStorage.getItem('token');
if (token == null || token.length === 0) {
setTimeout(this.checkToken.bind(this), 1000);
} else {
this.$router.replace('/');
}
},
},
};
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
import PageIndex from './pages/Index';
import Login from './pages/public/Login';
export default [
{
path: '/',
component: PageIndex,
},
{
path: '/public/login',
component: Login,
},
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
console.log('注入了页面');
var port = chrome.extension.connect({ name: 'data' });
/**
* 接受background传来的消息
*/
port.onMessage.addListener(msg => {
console.log('收到消息:' + msg);
let obj = JSON.parse(msg);
switch (obj.code) {
case 'addBookmark':
break;
default:
console.error('未知的命令:' + obj.code);
}
});
/**
* 接收当前注入页面传来的消息
*/
window.addEventListener('message', function(event) {
if (event.data.type === undefined) {
return;
}
console.log('接受到消息', event.data);
switch (event.data.type) {
case 'sendToken':
port.postMessage(event.data);
window.token = event.data;
break;
default:
console.error('未知的事件', event);
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
import config from './config';
import axios from 'axios';
axios.defaults.timeout = 15000;
axios.defaults.baseURL = config.baseUrl;
axios.interceptors.request.use(
function(config) {
config.headers['jwt-token'] = window.token;
return config;
},
function(error) {
console.error(error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
res => {
if (res.data.code === -1) {
localStorage.removeItem('token');
if (window.envType === 'background') {
window.open(config.ssoUrl);
} else {
window.vueInstance.$router.replace('/public/login');
}
} else if (res.data.code === 1) {
return res.data.data;
} else {
Promise.reject(res);
}
},
error => {
return Promise.reject(error);
}
);
export default axios;

View File

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

View File

@ -4,18 +4,17 @@ chrome.runtime.onInstalled.addListener(() => {
title: '添加到书签',
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;
}

View File

@ -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",

View File

@ -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>

View File

@ -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