feat:书签,搜索基本完成

This commit is contained in:
fanxb 2021-03-04 11:03:51 +08:00
parent f968a41303
commit 91d69aab6a
21 changed files with 336 additions and 210 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>bookmark-business</artifactId>
<groupId>com.fanxb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bookmark-business-api</artifactId>
</project>

View File

@ -0,0 +1,8 @@
package com.fanxb.bookmark.business.api;
public interface UserApi {
/**
* 版本自增
* @param userId 用户id
*/
void versionPlus(int userId);
}

View File

@ -0,0 +1,6 @@
/**
* 用于模块间的方法相互调用,具体使用如下
* 1. 首先在business模块下建立interface接口然后在个具体模块中实现
* 2. 最后诸如interface,就能实现同级模块间的直接调用
*/
package com.fanxb.bookmark.business.api;

View File

@ -12,6 +12,11 @@
<artifactId>bookmark-business-bookmark</artifactId>
<dependencies>
<dependency>
<groupId>com.fanxb</groupId>
<artifactId>bookmark-business-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--html文件解析-->
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>

View File

@ -35,10 +35,7 @@ public class PinyinUpdateConsumer implements RedisConsumer {
if (CollectionUtil.isEmpty(bookmarks)) {
return;
}
List<String> resList = pinYinService.changeStrings(bookmarks.stream().map(Bookmark::getName).collect(Collectors.toList()));
for (int i = 0, size = bookmarks.size(); i < size; i++) {
bookmarkDao.updateSearchKey(bookmarks.get(i).getBookmarkId(), resList.get(i));
}
//更新本用户书签更新版本
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_VERSION, bookmarks.get(0).getUserId());
}

View File

@ -184,6 +184,14 @@ public interface BookmarkDao extends BaseMapper<Bookmark> {
@Update("update bookmark set searchKey=#{searchKey} where bookmarkId=#{bookmarkId}")
void updateSearchKey(@Param("bookmarkId") int bookmarkId, @Param("searchKey") String searchKey);
/**
* 批量更新searchKey
*
* @author fanxb
* @date 2020/3/22 22:08
*/
void updateSearchKeyBatch(List<Bookmark> list);
/**
* 分页获取所有的书签
*

View File

@ -2,6 +2,7 @@ package com.fanxb.bookmark.business.bookmark.service.impl;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSON;
import com.fanxb.bookmark.business.api.UserApi;
import com.fanxb.bookmark.business.bookmark.dao.BookmarkDao;
import com.fanxb.bookmark.business.bookmark.entity.BookmarkEs;
import com.fanxb.bookmark.business.bookmark.entity.MoveNodeBody;
@ -11,7 +12,6 @@ import com.fanxb.bookmark.business.bookmark.service.PinYinService;
import com.fanxb.bookmark.common.constant.EsConstant;
import com.fanxb.bookmark.common.constant.RedisConstant;
import com.fanxb.bookmark.common.entity.Bookmark;
import com.fanxb.bookmark.common.entity.redis.UserBookmarkUpdate;
import com.fanxb.bookmark.common.util.EsUtil;
import com.fanxb.bookmark.common.util.RedisUtil;
import com.fanxb.bookmark.common.util.UserContextHolder;
@ -43,16 +43,20 @@ import java.util.stream.Collectors;
@Slf4j
public class BookmarkServiceImpl implements BookmarkService {
@Autowired
private BookmarkDao bookmarkDao;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private PinYinService pinYinService;
private final BookmarkDao bookmarkDao;
private final StringRedisTemplate redisTemplate;
private final PinYinService pinYinService;
private final UserApi userApi;
private final EsUtil esUtil;
@Autowired
private EsUtil esUtil;
public BookmarkServiceImpl(BookmarkDao bookmarkDao, StringRedisTemplate redisTemplate, PinYinService pinYinService, UserApi userApi, EsUtil esUtil) {
this.bookmarkDao = bookmarkDao;
this.redisTemplate = redisTemplate;
this.pinYinService = pinYinService;
this.userApi = userApi;
this.esUtil = esUtil;
}
@Override
@Transactional(rollbackFor = Exception.class)
@ -77,10 +81,20 @@ public class BookmarkServiceImpl implements BookmarkService {
dealBookmark(userId, elements.get(i), path, sortBase + count + i - 1, bookmarks);
}
}
updateVersion(userId);
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, bookmarks);
RedisUtil.addToMq(RedisConstant.BOOKMARK_PINYIN_CHANGE, bookmarks);
//每一千条处理插入一次
List<Bookmark> tempList = new ArrayList<>(1000);
for(int i=0;i<bookmarks.size();i++){
tempList.add(bookmarks.get(i));
if(tempList.size()==1000 || i==bookmarks.size()-1){
List<String> resList = pinYinService.changeStrings(bookmarks.stream().map(Bookmark::getName).collect(Collectors.toList()));
for(int j=0;i<resList.size();i++){
tempList.get(j).setSearchKey(resList.get(j));
}
bookmarkDao.updateSearchKeyBatch(tempList);
tempList.clear();
}
}
userApi.versionPlus(userId);
}
/**
@ -179,7 +193,7 @@ public class BookmarkServiceImpl implements BookmarkService {
set.addAll(bookmarkIdList.stream().map(String::valueOf).collect(Collectors.toSet()));
}
RedisUtil.addToMq(RedisConstant.BOOKMARK_DELETE_ES, set);
updateVersion(userId);
userApi.versionPlus(userId);
}
@Override
@ -197,7 +211,7 @@ public class BookmarkServiceImpl implements BookmarkService {
if (bookmark.getType() == 0) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, Collections.singleton(bookmark));
}
updateVersion(userId);
userApi.versionPlus(userId);
return bookmark;
}
@ -210,7 +224,7 @@ public class BookmarkServiceImpl implements BookmarkService {
if (bookmark.getType() == 0) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_INSERT_ES, Collections.singleton(bookmark));
}
updateVersion(userId);
userApi.versionPlus(userId);
}
@ -231,7 +245,7 @@ public class BookmarkServiceImpl implements BookmarkService {
}
//更新被移动节点的path和sort
bookmarkDao.updatePathAndSort(userId, body.getBookmarkId(), body.getTargetPath(), body.getSort());
updateVersion(userId);
userApi.versionPlus(userId);
}
@Override
@ -256,16 +270,5 @@ public class BookmarkServiceImpl implements BookmarkService {
return bookmarkDao.selectPopular(UserContextHolder.get().getUserId(), num);
}
/**
* 功能描述: 向mq发送消息通知书签数据更新
*
* @param userId userId
* @author fanxb
* @date 2020/5/10 12:07
*/
private void updateVersion(int userId) {
RedisUtil.addToMq(RedisConstant.BOOKMARK_UPDATE_VERSION, userId);
}
}

View File

@ -111,5 +111,15 @@
limit ${start}, ${size}
</select>
<update id="updateSearchKeyBatch">
UPDATE `bookmark` a JOIN
(
<foreach collection="list" item="item" separator="union">
select #{item.bookmarkId} as bookmarkId,#{item.searchKey} as searchKey
</foreach>
) b USING(bookmarkId)
SET a.searchKey=b.searchKey;
</update>
</mapper>

View File

@ -14,6 +14,7 @@
<modules>
<module>user</module>
<module>bookmark</module>
<module>api</module>
</modules>
<dependencies>

View File

@ -11,5 +11,12 @@
<artifactId>bookmark-business-user</artifactId>
<dependencies>
<dependency>
<groupId>com.fanxb</groupId>
<artifactId>bookmark-business-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,21 @@
package com.fanxb.bookmark.business.user.apiImpl;
import com.fanxb.bookmark.business.api.UserApi;
import com.fanxb.bookmark.business.user.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserApiImpl implements UserApi {
private final UserDao userDao;
@Autowired
public UserApiImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void versionPlus(int userId) {
userDao.updateUserVersion(userId);
}
}

View File

@ -20,11 +20,11 @@ public class UserInfoUpdateConsumer implements RedisConsumer {
@Override
public void deal(String message) {
int userId = Integer.parseInt(message);
if (userId == -1) {
userDao.updateAllBookmarkUpdateVersion();
} else {
userDao.updateLastBookmarkUpdateTime(userId);
}
// int userId = Integer.parseInt(message);
// if (userId == -1) {
// userDao.updateAllBookmarkUpdateVersion();
// } else {
// userDao.updateLastBookmarkUpdateTime(userId);
// }
}
}

View File

@ -124,7 +124,7 @@ public interface UserDao {
* @date 2020/1/26 下午3:47
*/
@Update("update user set version=version+1 where userId=#{userId}")
void updateLastBookmarkUpdateTime(int userId);
void updateUserVersion(int userId);
/**
* 功能描述: 更新所有用户的更新时间

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.fanxb</groupId> <artifactId>bookMarkService</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>common</module> <module>business</module> <module>web</module> </modules> <properties> <project.build.sourceEncoding>utf-8</project.build.sourceEncoding> <project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding> <java.version>11</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> </parent> <dependencyManagement> <dependencies> <!--定义es版本号,因为spring-boot-start-parent中引入了es的依赖会导致es的版本问题无法正常使用high-level-client--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch --> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.2.0</version> </dependency> <!--&lt;!&ndash; https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-client &ndash;&gt;--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.2.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.fanxb</groupId> <artifactId>bookMarkService</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>common</module> <module>business</module> <module>web</module> <module>api</module> </modules> <properties> <project.build.sourceEncoding>utf-8</project.build.sourceEncoding> <project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding> <java.version>11</java.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> </parent> <dependencyManagement> <dependencies> <!--定义es版本号,因为spring-boot-start-parent中引入了es的依赖会导致es的版本问题无法正常使用high-level-client--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch --> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.2.0</version> </dependency> <!--&lt;!&ndash; https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-client &ndash;&gt;--> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.2.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>

View File

@ -3,12 +3,14 @@ module.exports = {
env: {
node: true
},
extends: ["plugin:vue/essential", "@vue/airbnb"],
extends: ["plugin:vue/essential"],
parserOptions: {
parser: "babel-eslint"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"max-len": [0, { code: 300 }],
"no-cycle": 0
}
};

View File

@ -11,6 +11,7 @@
"ant-design-vue": "^1.6.3",
"axios": "^0.19.2",
"babel-plugin-import": "^1.13.0",
"clipboard": "^2.0.6",
"core-js": "^3.6.5",
"localforage": "^1.7.4",
"vue": "^2.6.11",

View File

@ -16,13 +16,7 @@
</div>
</template>
<div v-else prop="file">
<a-upload-dragger
name="file"
:data="{ path: form.path }"
:headers="{ 'jwt-token': token }"
action="/bookmark/api/bookmark/uploadBookmarkFile"
@change="fileChange"
>
<a-upload-dragger name="file" :data="{ path: form.path }" :headers="{ 'jwt-token': token }" action="/bookmark/api/bookmark/uploadBookmarkFile" @change="fileChange">
<p class="ant-upload-drag-icon">
<a-icon type="inbox" />
</p>
@ -77,6 +71,9 @@ export default {
this.form.path = this.targetNode == null ? "" : this.targetNode.path + (this.isAdd ? "." + this.targetNode.bookmarkId : "");
},
methods: {
/**
* 文件提交不走这儿
*/
submit() {
//
this.loading = true;
@ -88,27 +85,29 @@ export default {
let res = null;
if (this.isAdd) {
res = await HttpUtil.put("/bookmark", null, this.form);
res.isLeaf = res.type === 0;
await this.$store.dispatch("treeData/addNode", { sourceNode: this.targetNode, targetNode: res });
} else {
this.form.bookmarkId = this.targetNode.bookmarkId;
await HttpUtil.post("/bookmark/updateOne", null, this.form);
this.targetNode.name = this.form.name;
this.targetNode.url = this.form.url;
await this.$store.dispatch("treeData/editNode", { node: this.targetNode, newName: this.form.name, newUrl: this.form.url });
}
this.$message.success("操作成功");
this.$emit("close", res);
this.$emit("close", false);
this.loading = false;
});
},
fileChange(info) {
if (info.file.status === "error") {
this.$notification.error({
message: "异常",
description: "文件内容无法解析,确保该文件为书签文件",
});
} else if (info.file.status === "done") {
this.$message.success("解析成功");
this.$emit("close", null);
console.log(info);
if (info.file.status === "done") {
if (info.file.response.code === 0) {
this.$notification.error({
message: "异常",
description: "文件内容无法解析,确保该文件为书签文件",
});
} else {
this.$message.success("解析成功");
this.$emit("close", true);
}
}
},
},

View File

@ -24,11 +24,13 @@
.treeNodeItem {
span {
span {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
div {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}

View File

@ -44,7 +44,7 @@ const actions = {
}
context.commit("isIniting", true);
let data = await localforage.getItem(TOTAL_TREE_DATA);
if (data == null) {
if (!data) {
await context.dispatch("refresh");
} else {
context.commit(TOTAL_TREE_DATA, data);
@ -62,7 +62,6 @@ const actions = {
try {
if (context.state.isInit && context.state.isIniting == false) {
clearInterval(timer);
console.log(timer);
resolve();
}
} catch (err) {
@ -79,15 +78,15 @@ const actions = {
}
Object.values(treeData).forEach(item =>
item.forEach(item1 => {
item1.isLeaf = item.type === 0;
item1.isLeaf = item1.type === 0;
item1.class = "treeNodeItem";
item1.scopedSlots = { title: "nodeTitle" };
})
);
let userInfo = await httpUtil.get("/user/currentUserInfo");
await context.dispatch("updateVersion", userInfo.version);
context.commit(TOTAL_TREE_DATA, treeData);
await localforage.setItem(TOTAL_TREE_DATA, treeData);
let userInfo = await httpUtil.get("/user/currentUserInfo");
context.commit(VERSION, userInfo.version);
await localforage.setItem(VERSION, userInfo.version);
},
//清除缓存数据
async clear(context) {
@ -98,6 +97,9 @@ const actions = {
await localforage.removeItem(TOTAL_TREE_DATA);
await localforage.removeItem(VERSION);
},
/**
* 移动节点
*/
async moveNode(context, info) {
let data = context.state[TOTAL_TREE_DATA];
const target = info.node.dataRef;
@ -161,13 +163,85 @@ const actions = {
list.forEach(item1 => (item1.path = newPathStr));
});
}
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
await context.dispatch("updateVersion", null);
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
return body;
},
/**
* 更新版本数据
*/
async updateVersion({ commit, state }, version) {
commit(VERSION, version == null ? state[VERSION] + 1 : version);
await localforage.setItem(VERSION, state[VERSION]);
},
/**
* 新增书签文件夹
*/
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 {
if (sourceNode.children === undefined) {
sourceNode.children = [];
}
sourceNode.children.push(targetNode);
}
if (targetNode.type === 0) {
context.state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
}
targetNode.isLeaf = targetNode.type === 0;
targetNode.class = "treeNodeItem";
targetNode.scopedSlots = { title: "nodeTitle" };
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
await context.dispatch("updateVersion", null);
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
},
/**
* 删除节点数据
*/
async deleteData(context, { pathList, bookmarkIdList }) {
//待删除的书签
let bookmarkIdSet = new Set();
bookmarkIdList.forEach(item => bookmarkIdSet.add(item));
//删除子节点
pathList.forEach(item => {
delete state[TOTAL_TREE_DATA][item];
Object.keys(context.state[TOTAL_TREE_DATA])
.filter(key => key.startsWith(item + "."))
.forEach(key => delete state[TOTAL_TREE_DATA][key]);
bookmarkIdSet.add(parseInt(item.split(".").reverse()));
});
//删除直接选中的节点
Object.keys(context.state[TOTAL_TREE_DATA]).forEach(item => {
let list = context.state[TOTAL_TREE_DATA][item];
for (let i = list.length - 1; i >= 0; i--) {
if (bookmarkIdSet.has(list[i].bookmarkId)) {
list.splice(i, 1);
}
}
});
context.commit(TOTAL_TREE_DATA, context.state[TOTAL_TREE_DATA]);
await context.dispatch("updateVersion", null);
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
},
/**
* 编辑书签节点
*/
async editNode({ dispatch, state, commit }, { node, newName, newUrl }) {
node.name = newName;
node.url = newUrl;
commit(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
await dispatch("updateVersion", null);
await localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
}
};
const mutations = {
totalTreeData(state, totalTreeData) {
localforage.setItem(TOTAL_TREE_DATA, totalTreeData);
state.totalTreeData = totalTreeData;
},
isInit(state, isInit) {
@ -176,60 +250,9 @@ const mutations = {
isIniting(state, isIniting) {
state.isIniting = isIniting;
},
deleteData(state, { pathList, bookmarkIdList }) {
//待删除的书签
let bookmarkIdSet = new Set();
bookmarkIdList.forEach(item => bookmarkIdSet.add(item));
//删除子节点
pathList.forEach(item => {
delete state[TOTAL_TREE_DATA][item];
Object.keys(state[TOTAL_TREE_DATA])
.filter(key => key.startsWith(item + "."))
.forEach(key => delete state[TOTAL_TREE_DATA][key]);
bookmarkIdSet.add(parseInt(item.split(".").reverse()));
});
//删除直接选中的节点
Object.keys(state.totalTreeData).forEach(item => {
let list = state.totalTreeData[item];
for (let i = list.length - 1; i >= 0; i--) {
if (bookmarkIdSet.has(list[i].bookmarkId)) {
list.splice(i, 1);
}
}
});
localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
},
/**
* 新增书签文件夹
*/
addNode(state, { sourceNode, targetNode }) {
if (sourceNode === null) {
if (state[TOTAL_TREE_DATA][""] === undefined) {
state[TOTAL_TREE_DATA][""] = [];
}
state[TOTAL_TREE_DATA][""].push(targetNode);
} else {
if (sourceNode.children === undefined) {
sourceNode.children = [];
}
sourceNode.children.push(targetNode);
}
if (targetNode.type === 0) {
state[TOTAL_TREE_DATA][targetNode.path + "." + targetNode.bookmarkId] = [];
}
localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
},
/**
* 更新版本文件
*/
version(state, version) {
console.log("version:", version);
if (version == null && state.version != null) {
state.version = state.version + 1;
} else {
state.version = version;
}
localforage.setItem(VERSION, state[VERSION]);
state[VERSION] = version;
}
};

View File

@ -34,8 +34,9 @@ export default {
console.log("globalConfig加载完毕");
await this.$store.dispatch("treeData/init");
console.log("treeData加载完毕");
console.log("state数据:", this.$store.state);
await this.checkVersion();
this.timer = setInterval(this.checkVersion, 60 * 1000);
this.timer = setInterval(this.checkVersion, 10 * 1000);
},
destroyed() {
if (this.timer != null) {

View File

@ -11,13 +11,10 @@
<a-tooltip title="多选">
<a-button type="primary" shape="circle" icon="check" @click="switchMul" />
</a-tooltip>
<a-tooltip
v-if="
<a-tooltip v-if="
(checkedKeys.length === 0 && (currentSelect == null || currentSelect.type === 1)) ||
(checkedKeys.length === 1 && checkedNodes[0].type === 1)
"
title="添加书签"
>
" title="添加书签">
<a-button type="primary" shape="circle" icon="plus" @click="addData" />
</a-tooltip>
<a-tooltip v-if="currentSelect || checkedKeys.length === 1" title="编辑书签">
@ -26,38 +23,32 @@
<a-tooltip v-if="moveShow" title="移动书签">
<a-button type="primary" shape="circle" icon="scissor" />
</a-tooltip>
<a-popconfirm
v-if="checkedKeys.length > 0 || currentSelect"
title="此操作同时也会删除子节点数据,确认?"
ok-text="是"
cancel-text="否"
@confirm="deleteBookmarks"
>
<a-popconfirm v-if="checkedKeys.length > 0 || currentSelect" title="此操作同时也会删除子节点数据,确认?" ok-text="是" cancel-text="否" @confirm="deleteBookmarks">
<a-tooltip title="删除书签">
<a-button type="danger" shape="circle" icon="delete" />
</a-tooltip>
</a-popconfirm>
</div>
<a-empty v-if="treeData.length == 0 && loading == false" description="无数据,点击上方 + 新增"></a-empty>
<a-tree
v-else
:tree-data="treeData"
:loaded-keys="loadedKeys"
:selected-keys="currentSelect ? [currentSelect.bookmarkId] : []"
:load-data="loadData"
:checked-keys="checkedKeys"
:replace-fields="replaceFields"
:expandedKeys="expandedKeys"
@select="select"
@expand="expand"
@check="check"
blockNode
:checkable="mulSelect"
checkStrictly
:draggable="!isPhone"
@drop="onDrop"
@rightClick="rightClick"
/>
<a-empty v-if="treeData.length == 0" description="无数据,点击上方 + 新增"></a-empty>
<a-tree v-else :tree-data="treeData" :loaded-keys="loadedKeys" :selected-keys="currentSelect ? [currentSelect.bookmarkId] : []" :load-data="loadData" :checked-keys="checkedKeys" :replace-fields="replaceFields" :expandedKeys="expandedKeys" @select="select" @expand="expand" @check="check" blockNode :checkable="mulSelect" checkStrictly :draggable="!isPhone" @drop="onDrop">
<a-dropdown :trigger="['contextmenu']" slot="nodeTitle" slot-scope="rec">
<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" />
<a-icon type="book" v-else />
<span @click.prevent style="display:inline-block;min-width:50%;padding-left:0.4em">
{{rec.dataRef.name}}
</span>
</div>
<a-menu slot="overlay" @click="rightClick($event,rec.dataRef)">
<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 key="edit">编辑</a-menu-item>
<a-menu-item key="delete">删除</a-menu-item>
</a-menu>
</a-dropdown>
</a-tree>
<!-- 新增修改 -->
<a-modal v-model="addModal.show" :title="addModal.isAdd ? '新增' : '编辑'" :footer="null">
<add-bookmark v-if="addModal.show" :isAdd="addModal.isAdd" :targetNode="addModal.targetNode" @close="close" />
@ -70,32 +61,34 @@ import AddBookmark from "../../../../components/main/things/AddBookmark.vue";
import Search from "../../../../components/main/Search.vue";
import HttpUtil from "../../../../util/HttpUtil.js";
import { mapState, mapActions } from "vuex";
import ClipboardJS from "clipboard";
export default {
name: "BookmarkManage",
components: { AddBookmark, Search },
data() {
return {
treeData: [],
expandedKeys: [], //keys
checkedKeys: [], //keys
checkedNodes: [], //
loadedKeys: [], //
expandedKeys: [], // keys
checkedKeys: [], // keys
checkedNodes: [], //
loadedKeys: [], //
replaceFields: {
title: "name",
key: "bookmarkId",
},
mulSelect: false, //
currentSelect: null, //
loading: true, //loading
moveShow: false, //
//
mulSelect: false, //
currentSelect: null, //
loading: true, // loading
moveShow: false, //
//
addModal: {
show: false,
//null
// null
targetNode: null,
//
//
isAdd: false,
},
copyBoard: null, //
};
},
computed: {
@ -106,6 +99,21 @@ export default {
await this.$store.dispatch("treeData/ensureDataOk");
this.treeData = this.totalTreeData[""];
this.loading = false;
//clipboard
this.copyBoard = new ClipboardJS(".copy-to-board", {
text: function (trigger) {
return trigger.attributes.data.nodeValue;
},
});
this.copyBoard.on("success", (e) => {
this.$message.success("复制成功");
e.clearSelection();
});
},
destroyed() {
if (this.copyBoard != null) {
this.copyBoard.destroy();
}
},
methods: {
/**
@ -118,9 +126,7 @@ export default {
if (!this.totalTreeData[newPath]) {
this.totalTreeData[newPath] = [];
}
this.totalTreeData[newPath].forEach((item) => (item.isLeaf = item.type === 0));
data.children = this.totalTreeData[newPath];
this.treeData = [...this.treeData];
this.loadedKeys.push(data.bookmarkId);
resolve();
});
@ -130,7 +136,7 @@ export default {
this.loading = true;
await this.$store.dispatch("treeData/refresh");
}
this.treeData = [...this.totalTreeData[""]];
this.treeData = this.totalTreeData[""];
this.expandedKeys = [];
this.checkedKeys = [];
this.checkedNodes = [];
@ -140,7 +146,7 @@ export default {
},
expand(expandedKeys, { expanded, node }) {
if (expanded) {
let item = node.dataRef;
const item = node.dataRef;
this.expandedKeys = [
...item.path
.split(".")
@ -152,15 +158,8 @@ export default {
this.expandedKeys.pop();
}
},
rightClick({ node }) {
if (this.currentSelect === node.dataRef) {
this.currentSelect = null;
} else {
this.currentSelect = node.dataRef;
}
},
check(key, { checked, node }) {
let item = node.dataRef;
const item = node.dataRef;
if (checked) {
this.checkedKeys.push(item.bookmarkId);
this.checkedNodes.push(item);
@ -173,14 +172,14 @@ export default {
}
},
select(key, { selected, node }) {
let item = node.dataRef;
const item = node.dataRef;
if (item.type === 1) {
if (selected && this.mulSelect === false) {
this.currentSelect = item;
} else {
this.currentSelect = null;
}
let index = this.expandedKeys.indexOf(item.bookmarkId);
const index = this.expandedKeys.indexOf(item.bookmarkId);
if (index > -1) {
this.expandedKeys.splice(index, 1);
} else {
@ -197,6 +196,7 @@ export default {
window.open(item.url);
}
},
//
switchMul() {
if (this.mulSelect) {
this.mulSelect = false;
@ -207,9 +207,9 @@ export default {
}
},
async deleteBookmarks() {
//
const bookmarkIdList = [],
pathList = [];
//
const bookmarkIdList = [];
const pathList = [];
if (this.checkedNodes) {
this.checkedNodes.forEach((item) =>
item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId)
@ -230,10 +230,10 @@ export default {
pathList,
bookmarkIdList,
});
this.$store.commit("treeData/deleteData", { pathList, bookmarkIdList });
this.$store.dispatch("treeData/deleteData", { pathList, bookmarkIdList });
//
pathList.forEach((item) => {
let id = parseInt(item.split(".").reverse()[0]);
const id = parseInt(item.split(".").reverse()[0]);
let index = this.loadedKeys.indexOf(id);
if (index > -1) {
this.loadedKeys.splice(index, 1);
@ -243,7 +243,6 @@ export default {
this.expandedKeys.splice(index, 1);
}
});
this.$store.commit("treeData/version", null);
this.checkedNodes = [];
this.checkedKeys = [];
this.currentSelect = null;
@ -276,25 +275,14 @@ export default {
},
/**
* 关闭弹窗
* @param data data为null说明需要刷新书签树,不为浪即为修改/新增的对象
* @param isUpload 说明为上传书签文件需要刷新缓存数据
*/
async close(data) {
console.log(data);
if (this.addModal.isAdd) {
//
if (data == null) {
//
this.refresh(true);
} else {
//
this.$store.commit("treeData/addNode", { sourceNode: this.addModal.targetNode, targetNode: data });
this.treeData = [...this.totalTreeData[""]];
}
async close(isUpload) {
if (isUpload) {
this.refresh(true);
} else {
//
this.treeData = [...this.totalTreeData[""]];
this.treeData.__ob__.dep.notify();
}
this.$store.commit("treeData/version", null);
this.addModal = {
show: false,
targetNode: null,
@ -308,20 +296,46 @@ export default {
return;
}
this.loading = true;
let body = await this.$store.dispatch("treeData/moveNode", info);
const body = await this.$store.dispatch("treeData/moveNode", info);
try {
await HttpUtil.post("/bookmark/moveNode", null, body);
this.$message.success("移动完成");
this.treeData = [...this.totalTreeData[""]];
this.$store.commit("treeData/version", null);
this.treeData.__ob__.dep.notify();
} catch (error) {
console.error(error);
this.$message.error("后台移动失败将于2s后刷新页面以免前后台数据不一致");
// setTimeout(() => window.location.reload(), 2000);
setTimeout(() => this.refresh(true), 2000);
} finally {
this.loading = false;
}
},
//
async rightClick({ key }, item) {
if (key === "copy") {
return;
}
//
this.mulSelect = false;
this.checkedKeys = [];
this.checkedNodes = [];
this.currentSelect = item;
if (key === "add") {
this.addData();
} else if (key === "delete") {
this.$confirm({
title: "确认删除?",
content: "将删除当前节点和所有子节点,且不可恢复",
onOk: () => {
return new Promise(async (resolve, reject) => {
await this.deleteBookmarks();
resolve();
});
},
});
} else if (key === "edit") {
this.editData();
}
},
},
};
</script>
@ -338,4 +352,8 @@ export default {
font-size: 0.25rem;
font-weight: 600;
}
.titleContext {
display: flex;
align-items: center;
}
</style>