feat:web端书签缓存功能完成

This commit is contained in:
fanxb 2020-01-31 00:33:18 +08:00
parent 3c93016bd3
commit 8ece106c03
4 changed files with 220 additions and 175 deletions

View File

@ -4,6 +4,7 @@ import httpUtil from "../../../util/httpUtil";
import * as action from "../../../redux/action/BookmarkTreeOverview";
import { connect } from "react-redux";
import { addNode } from "../../../util/cacheUtil";
function mapStateToProps(state) {
return state[action.DATA_NAME];
@ -48,7 +49,11 @@ class AddModal extends React.Component {
const { currentEditNode } = nextProps;
if (currentEditNode != null) {
this.type = "edit";
this.setState({ addType: currentEditNode.type === 0 ? "bookmark" : "folder", name: currentEditNode.name, url: currentEditNode.url });
this.setState({
addType: currentEditNode.type === 0 ? "bookmark" : "folder",
name: currentEditNode.name,
url: currentEditNode.url
});
} else {
this.type = "add";
this.setState({ addType: "bookmark", name: "", url: "", file: null });
@ -69,7 +74,7 @@ class AddModal extends React.Component {
changeValue(e) {
const name = e.target.name;
const value = e.target.value
const value = e.target.value;
this.checkValue(name, value);
this.setState({ [name]: value });
}
@ -96,7 +101,9 @@ class AddModal extends React.Component {
url: url,
type
};
let isOk = this.checkValue("name", name) && (type === 0 ? this.checkValue("url", url) : true);
let isOk =
this.checkValue("name", name) &&
(type === 0 ? this.checkValue("url", url) : true);
if (!isOk) {
return;
}
@ -116,9 +123,17 @@ class AddModal extends React.Component {
/**
* 新增一个节点
*/
addOne() {
const { currentAddFolder, updateTreeData, closeModal, treeData } = this.props;
const path = currentAddFolder == null ? "" : currentAddFolder.path + "." + currentAddFolder.bookmarkId;
async addOne() {
const {
currentAddFolder,
updateTreeData,
closeModal,
treeData
} = this.props;
const path =
currentAddFolder == null
? ""
: currentAddFolder.path + "." + currentAddFolder.bookmarkId;
const { name, url, file, addType } = this.state;
if (addType === "file") {
if (file == null) {
@ -145,29 +160,22 @@ class AddModal extends React.Component {
name,
url
};
let isOk = this.checkValue("name", name) && (body.type === 0 ? this.checkValue("url", url) : true);
let isOk =
this.checkValue("name", name) &&
(body.type === 0 ? this.checkValue("url", url) : true);
if (!isOk) {
return;
}
this.setState({ isLoading: true });
httpUtil
.put("/bookmark", body)
.then(res => {
// addToTree(res);
let res = await httpUtil.put("/bookmark", body);
message.success("加入成功");
if (currentAddFolder === null) {
await addNode(currentAddFolder, res);
if (!currentAddFolder) {
treeData.push(res);
} else {
//children
if (currentAddFolder.children) {
currentAddFolder.children.push(res);
}
}
updateTreeData(treeData);
closeModal();
this.setState({ isLoading: false });
})
.catch(() => this.setState({ isLoading: false }));
}
}
@ -211,11 +219,20 @@ class AddModal extends React.Component {
}
};
return (
<Modal destroyOnClose title={type === "add" ? "新增" : "编辑"} visible={isShowModal} onCancel={closeModal} footer={false}>
<Modal
destroyOnClose
title={type === "add" ? "新增" : "编辑"}
visible={isShowModal}
onCancel={closeModal}
footer={false}
>
<Form {...formItemLayout}>
{type === "add" ? (
<Form.Item label="类别">
<Radio.Group defaultValue="bookmark" onChange={e => this.setState({ addType: e.target.value })}>
<Radio.Group
defaultValue="bookmark"
onChange={e => this.setState({ addType: e.target.value })}
>
<Radio value="bookmark">书签</Radio>
<Radio value="folder">文件夹</Radio>
<Radio value="file">上传书签html</Radio>
@ -223,13 +240,31 @@ class AddModal extends React.Component {
</Form.Item>
) : null}
{addType === "bookmark" || addType === "folder" ? (
<Form.Item label="名称" validateStatus={nameHelp === "" ? "success" : "error"} help={nameHelp}>
<Input type="text" name="name" onChange={this.changeValue.bind(this)} value={name} />
<Form.Item
label="名称"
validateStatus={nameHelp === "" ? "success" : "error"}
help={nameHelp}
>
<Input
type="text"
name="name"
onChange={this.changeValue.bind(this)}
value={name}
/>
</Form.Item>
) : null}
{addType === "bookmark" ? (
<Form.Item label="URL" validateStatus={urlHelp === "" ? "success" : "error"} help={urlHelp}>
<Input type="text" name="url" value={url} onChange={this.changeValue.bind(this)} />
<Form.Item
label="URL"
validateStatus={urlHelp === "" ? "success" : "error"}
help={urlHelp}
>
<Input
type="text"
name="url"
value={url}
onChange={this.changeValue.bind(this)}
/>
</Form.Item>
) : null}
{addType === "file" ? this.renderFileUpload() : null}
@ -244,7 +279,4 @@ class AddModal extends React.Component {
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddModal);
export default connect(mapStateToProps, mapDispatchToProps)(AddModal);

View File

@ -4,10 +4,12 @@ import { Modal, Button, Tree, message, Menu, Dropdown } from "antd";
import styles from "./index.module.less";
import IconFont from "../../../components/IconFont";
import { stopTransfer } from "../../../util/eventUtil";
import { deleteNodes, moveNode } from "../../../util/cacheUtil";
const { TreeNode } = Tree;
function menuVisible(item, visible) {
if (visible) { window.copyUrl = item.url;
if (visible) {
window.copyUrl = item.url;
}
this.props.changeCurrentClickItem(item);
}
@ -127,11 +129,7 @@ export function renderTreeNodes(items) {
* @param {*} e
*/
export function deleteOne(item) {
if (item.type === 0) {
deleteBookmark.call(this, [], [item.bookmarkId]);
} else {
deleteBookmark.call(this, [item.bookmarkId], []);
}
deleteBookmark.call(this, [item]);
}
/**
@ -139,15 +137,8 @@ export function deleteOne(item) {
*/
export function batchDelete() {
const { checkedNodes } = this.props;
const folderIdList = [],
bookmarkIdList = [];
checkedNodes.forEach(item => {
const data = item.props.dataRef;
data.type === 0
? bookmarkIdList.push(data.bookmarkId)
: folderIdList.push(data.bookmarkId);
});
deleteBookmark.call(this, folderIdList, bookmarkIdList);
deleteBookmark.call(this, checkedNodes);
}
/**
@ -155,8 +146,18 @@ export function batchDelete() {
* @param {*} folderIdList
* @param {*} bookmarkIdList
*/
function deleteBookmark(folderIdList, bookmarkIdList) {
function deleteBookmark(nodeList) {
const { updateTreeData, treeData, changeCheckedKeys } = this.props;
const folderIdList = [],
bookmarkIdList = [],
dataNodeList = [];
nodeList.forEach(item => {
const data = item.props ? item.props.dataRef : item;
dataNodeList.push(data);
data.type === 0
? bookmarkIdList.push(data.bookmarkId)
: folderIdList.push(data.bookmarkId);
});
Modal.confirm({
title: "确认删除?",
content: "删除后,无法找回",
@ -166,10 +167,8 @@ function deleteBookmark(folderIdList, bookmarkIdList) {
.post("/bookmark/batchDelete", { folderIdList, bookmarkIdList })
.then(() => {
//遍历节点树数据,并删除
const set = new Set();
folderIdList.forEach(item => set.add(parseInt(item)));
bookmarkIdList.forEach(item => set.add(parseInt(item)));
deleteTreeData(treeData, set);
deleteNodes(dataNodeList);
// deleteTreeData(treeData, set);
changeCheckedKeys([], null);
updateTreeData([...treeData]);
resolve();
@ -180,28 +179,11 @@ function deleteBookmark(folderIdList, bookmarkIdList) {
});
}
/**
* 递归删除已经被删除的数据
* @param {*} treeData
*/
function deleteTreeData(treeData, set) {
for (let i = 0, length = treeData.length; i < length; i++) {
const item = treeData[i];
if (set.has(treeData[i].bookmarkId)) {
treeData.splice(i, 1);
length--;
i--;
} else if (item.children && item.children.length > 0) {
deleteTreeData(item.children, set);
}
}
}
/**
* 节点拖拽
* @param {*} info
*/
export function onDrop(info) {
export async function onDrop(info) {
const { treeData, updateTreeData, loadedKeys, changeLoadedKeys } = this.props;
const target = info.node.props.dataRef;
if (!info.dropToGap && target.type === 0) {
@ -209,57 +191,13 @@ export function onDrop(info) {
return;
}
this.setState({ isLoading: true });
const current = info.dragNode.props.dataRef;
const body = {
bookmarkId: current.bookmarkId,
sourcePath: current.path,
targetPath: "",
//-1 表示排在最后
sort: -1
};
//从原来所属的节点列表中删除当前节点
const currentBelowList =
current.path === "" ? treeData : getBelowList(treeData, current);
currentBelowList.splice(currentBelowList.indexOf(current), 1);
if (info.dropToGap) {
body.targetPath = target.path;
const targetBelowList =
target.path === "" ? treeData : getBelowList(treeData, target);
const index = targetBelowList.indexOf(target);
if (info.dropPosition > index) {
body.sort = target.sort + 1;
insertToArray(current, index + 1, targetBelowList);
} else {
body.sort = target.sort;
insertToArray(current, index, targetBelowList);
}
} else {
//移动到一个文件夹内,该文件夹可能还未加载
body.targetPath = target.path + "." + target.bookmarkId;
//存在children说明已经加载
if (target.children) {
const length = target.children.length;
body.sort = length > 0 ? target.children[length - 1].sort + 1 : 1;
target.children.push(current);
} else if (current.type === 1 && current.children) {
let body =await moveNode(info);
//目标未加载且当前节点为已经展开的目录情况下需要把当前节点从已加载列表中移除,否则在目标节点中展开时会不显示当前节点的子节点
loadedKeys.splice(loadedKeys.indexOf(current.bookmarkId.toString()), 1);
let index = loadedKeys.indexOf(body.bookmarkId.toString());
if (index > -1) {
loadedKeys.splice(index, 1);
changeLoadedKeys(loadedKeys);
}
}
if (body.sort !== -1) {
//说明目标节点已经加载,需要更新当前节点信息
current.path = body.targetPath;
current.sort = body.sort;
//如果当前节点和目标节点不在一个层级中需要更新当前子节点的path信息
if (body.sourcePath !== body.targetPath) {
updateChildrenPath(
current.children,
body.sourcePath + "." + body.bookmarkId,
body.targetPath + "." + body.bookmarkId
);
}
}
httpUtil
.post("/bookmark/moveNode", body)
.then(res => {
@ -273,54 +211,3 @@ export function onDrop(info) {
setTimeout(window.location.reload, 2000);
});
}
/**
* 递归获取node所属list
* @param {*} tree 树结构
* @param {*} node node
*/
function getBelowList(treeList, node) {
for (let i in treeList) {
let item = treeList[i];
if (item.type === 1) {
if (item.path + "." + item.bookmarkId === node.path) {
return item.children;
} else if (item.children) {
let res = getBelowList(item.children, node);
//如果找到了,那么返回
if (res) return res;
}
}
}
}
/**
* 往数组中插入一个节点并让后面节点的sort+1
* @param {*} item 节点
* @param {*} index 插入位置
* @param {*} arr 目标数组
*/
function insertToArray(item, index, arr) {
const length = arr.length;
let i = length;
for (; i > index; i--) {
arr[i] = arr[i - 1];
arr[i].sort++;
}
arr[i] = item;
}
/**
* 更新修改节点的子节点节点path信息
* @param {*} children 孩子数组
* @param {*} oldPath 旧path
* @param {*} newPath 新path
*/
function updateChildrenPath(children, oldPath, newPath) {
if (children && children.length > 0) {
children.forEach(item => {
item.path = item.path.replace(oldPath, newPath);
updateChildrenPath(item.children, oldPath, newPath);
});
}
}

View File

@ -46,7 +46,7 @@ class OverView extends React.Component {
async componentDidMount() {
this.props.refresh();
await cacheBookmarkData();
this.props.updateTreeData(getBookmarkList(""));
this.props.updateTreeData([...getBookmarkList("")]);
this.props.changeIsInit(true);
}

View File

@ -53,3 +53,129 @@ export async function clearCache() {
await localforage.removeItem(TREE_LIST_TIME_KEY);
window.location.reload();
}
/**
* 更新本地缓存数据的时间
*/
export async function updateCurrentChangeTime() {
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
await localforage.setItem(TREE_LIST_KEY, window[TREE_LIST_KEY]);
}
/**
* 新增一个节点数据
* @param {*} currentNode
* @param {*} node
*/
export async function addNode(currentNode, node) {
let treeDataMap = window[TREE_LIST_KEY];
if (currentNode) {
let key = currentNode.path + "." + currentNode.bookmarkId;
if (!treeDataMap[key]) {
treeDataMap[key] = [];
}
treeDataMap[key].push(node);
} else {
treeDataMap[""].push(node);
}
await updateCurrentChangeTime();
}
/**
* 批量删除节点
* @param {*} folderIdList 删除的文件夹id
* @param {*} bookmarkIdList 删除的书签id
*/
export async function deleteNodes(nodeList) {
let data = window[TREE_LIST_KEY];
nodeList.forEach(item => {
let list = data[item.path];
let index = list.findIndex(one => one.bookmarkId === item.bookmarkId);
if (index > -1) {
list.splice(index, 1);
}
//如果是文件夹还是把他的子节点删除
if (item.type === 1) {
let key = item.path + "." + item.bookmarkId;
Object.keys(data).forEach(one => {
if (one.startsWith(key)) {
delete data[one];
}
});
}
});
await updateCurrentChangeTime();
}
/**
* 节点拖拽方法
* @param {*} info
*/
export async function moveNode(info) {
debugger;
let data = window[TREE_LIST_KEY];
const target = info.node.props.dataRef;
const current = info.dragNode.props.dataRef;
//从原来位置中删除当前节点
let currentList = data[current.path];
currentList.splice(
currentList.findIndex(item => item.bookmarkId === current.bookmarkId),
1
);
//请求体
const body = {
bookmarkId: current.bookmarkId,
sourcePath: current.path,
targetPath: "",
//-1 表示排在最后
sort: -1
};
if (info.dropToGap) {
body.targetPath = target.path;
//移动到目标节点的上面或者下面
let targetList = data[target.path];
//目标节点index
let index = targetList.indexOf(target);
//移动节点相对于目标节点位置的增量
let addIndex = info.dropPosition > index ? 1 : 0;
body.sort = target.sort + addIndex;
targetList.splice(addIndex, 0, current);
for (let i = index + 1; i < targetList.length; i++) {
targetList[i].sort += 1;
}
} else {
//移动到一个文件夹下面
body.targetPath = target.path + "." + target.bookmarkId;
let targetList = data[body.targetPath];
if (!targetList) {
targetList = [];
data[body.targetPath] = targetList;
}
body.sort =
targetList.length > 0 ? targetList[targetList.length - 1].sort + 1 : 1;
targetList.push(current);
}
//更新节点的path和对应子节点path
current.path = body.targetPath;
current.sort = body.sort;
//如果为文件夹还要更新所有子书签的path
if (body.sourcePath !== body.targetPath) {
let keys = Object.keys(data);
//旧路径
let oldPath = body.sourcePath + current.bookmarkId;
//新路径
let newPath = body.targetPath + current.bookmarkId;
keys.forEach(item => {
if (!item.startsWith(oldPath)) {
return;
}
let newPathStr = item.replace(oldPath, newPath);
let list = data[item];
delete data[item];
list.forEach(item1 => (item1.path = newPathStr));
});
}
await updateCurrentChangeTime();
return body;
}