feat:网页段缓存书签数据完成

This commit is contained in:
fanxb 2020-01-28 01:51:17 +08:00
parent d95441d860
commit 3c93016bd3
8 changed files with 167 additions and 33 deletions

View File

@ -106,7 +106,7 @@ public class UserController {
/** /**
* 功能描述: 校验密码生成一个actionId * 功能描述: 校验密码生成一个actionId
* *
* @param password password * @param obj obj
* @return com.fanxb.bookmark.common.entity.Result * @return com.fanxb.bookmark.common.entity.Result
* @author fanxb * @author fanxb
* @date 2019/11/11 23:31 * @date 2019/11/11 23:31

View File

@ -22,4 +22,8 @@ public class User {
private String password; private String password;
private long createTime; private long createTime;
private long lastLoginTime; private long lastLoginTime;
/**
* 上次更新书签时间
*/
private long bookmarkChangeTime;
} }

View File

@ -34,5 +34,6 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<script src="/js/localforage.main.js"></script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,13 @@
import React from "react"; import React from "react";
import { Link, withRouter } from "react-router-dom"; import { Link, withRouter } from "react-router-dom";
import { Menu, Dropdown, Divider } from "antd"; import { Menu, Dropdown, Divider, Modal } from "antd";
import httpUtil from "../../util/httpUtil"; import httpUtil from "../../util/httpUtil";
import { connect } from "react-redux"; import { connect } from "react-redux";
import styles from "./index.module.less"; import styles from "./index.module.less";
import * as infoAction from "../../redux/action/LoginInfoAction"; import * as infoAction from "../../redux/action/LoginInfoAction";
import { checkCacheStatus, clearCache } from "../../util/cacheUtil";
const { confirm } = Modal;
function mapStateToProps(state) { function mapStateToProps(state) {
return state[infoAction.DATA_NAME]; return state[infoAction.DATA_NAME];
@ -20,13 +23,44 @@ function mapDispatchToProps(dispatch) {
class MainLayout extends React.Component { class MainLayout extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {}; this.state = {
timer: null,
//
showDialog: false
};
} }
async componentWillMount() { async componentWillMount() {
if (!this.props.username) { if (!this.props.username) {
let res = await httpUtil.get("/user/currentUserInfo"); let res = await httpUtil.get("/user/currentUserInfo");
this.props.changeUserInfo(res); this.props.changeUserInfo(res);
if (this.state.timer != null) {
clearInterval(this.state.timer);
}
await this.checkCache();
this.state.timer = setInterval(this.checkCache.bind(this), 5 * 60 * 1000);
}
}
async checkCache() {
//
if (this.state.showDialog) {
return;
}
let _this = this;
if (!(await checkCacheStatus())) {
this.state.showDialog = true;
confirm({
title: "缓存过期",
content: "书签数据有更新,是否立即刷新?",
onOk() {
_this.state.showDialog = false;
clearCache();
},
onCancel() {
_this.state.showDialog = false;
}
});
} }
} }
@ -40,7 +74,11 @@ class MainLayout extends React.Component {
); );
if (username != null) { if (username != null) {
return ( return (
<Dropdown overlay={menu} placement="bottomCenter" trigger={["hover", "click"]}> <Dropdown
overlay={menu}
placement="bottomCenter"
trigger={["hover", "click"]}
>
<span style={{ cursor: "pointer" }}> <span style={{ cursor: "pointer" }}>
<img className={styles.icon} src={icon} alt="icon" /> <img className={styles.icon} src={icon} alt="icon" />
{username} {username}
@ -77,20 +115,34 @@ class MainLayout extends React.Component {
<div className={styles.main}> <div className={styles.main}>
<div className={styles.header}> <div className={styles.header}>
<a href="/"> <a href="/">
<img style={{ width: "1.5rem" }} src="/img/bookmarkLogo.png" alt="logo" /> <img
style={{ width: "1.5rem" }}
src="/img/bookmarkLogo.png"
alt="logo"
/>
</a> </a>
{this.renderUserArea()} {this.renderUserArea()}
</div> </div>
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<div style={{ minHeight: `calc(${document.body.clientHeight}px - 1.45rem)` }} className={styles.content}> <div
style={{
minHeight: `calc(${document.body.clientHeight}px - 1.45rem)`
}}
className={styles.content}
>
{this.props.children} {this.props.children}
</div> </div>
<div className={styles.footer}> <div className={styles.footer}>
开源地址<a href="https://github.com/FleyX/bookmark">github.com/FleyX/bookmark</a> 开源地址
<a href="https://github.com/FleyX/bookmark">
github.com/FleyX/bookmark
</a>
</div> </div>
</div> </div>
); );
} }
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MainLayout)); export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(MainLayout)
);

View File

@ -7,8 +7,7 @@ import { stopTransfer } from "../../../util/eventUtil";
const { TreeNode } = Tree; const { TreeNode } = Tree;
function menuVisible(item, visible) { function menuVisible(item, visible) {
if (visible) { if (visible) { window.copyUrl = item.url;
window.copyUrl = item.url;
} }
this.props.changeCurrentClickItem(item); this.props.changeCurrentClickItem(item);
} }
@ -56,7 +55,11 @@ export function renderNodeContent(item) {
return ( return (
<React.Fragment> <React.Fragment>
{/* 触发右键菜单 */} {/* 触发右键菜单 */}
<Dropdown overlay={menu} trigger={["contextMenu"]} onVisibleChange={menuVisible.bind(this, item)}> <Dropdown
overlay={menu}
trigger={["contextMenu"]}
onVisibleChange={menuVisible.bind(this, item)}
>
{item.type === 0 ? ( {item.type === 0 ? (
<a href={item.url} className={styles.nodeContent}> <a href={item.url} className={styles.nodeContent}>
{item.name} {item.name}
@ -66,8 +69,18 @@ export function renderNodeContent(item) {
)} )}
</Dropdown> </Dropdown>
{isEdit ? ( {isEdit ? (
<Dropdown overlay={menu} trigger={["click"]} onVisibleChange={menuVisible.bind(this, item)}> <Dropdown
<Button size="small" onClick={stopTransfer.bind(this)} type="primary" icon="menu" shape="circle" /> overlay={menu}
trigger={["click"]}
onVisibleChange={menuVisible.bind(this, item)}
>
<Button
size="small"
onClick={stopTransfer.bind(this)}
type="primary"
icon="menu"
shape="circle"
/>
</Dropdown> </Dropdown>
) : null} ) : null}
</React.Fragment> </React.Fragment>
@ -130,7 +143,9 @@ export function batchDelete() {
bookmarkIdList = []; bookmarkIdList = [];
checkedNodes.forEach(item => { checkedNodes.forEach(item => {
const data = item.props.dataRef; const data = item.props.dataRef;
data.type === 0 ? bookmarkIdList.push(data.bookmarkId) : folderIdList.push(data.bookmarkId); data.type === 0
? bookmarkIdList.push(data.bookmarkId)
: folderIdList.push(data.bookmarkId);
}); });
deleteBookmark.call(this, folderIdList, bookmarkIdList); deleteBookmark.call(this, folderIdList, bookmarkIdList);
} }
@ -203,11 +218,13 @@ export function onDrop(info) {
sort: -1 sort: -1
}; };
//从原来所属的节点列表中删除当前节点 //从原来所属的节点列表中删除当前节点
const currentBelowList = current.path === "" ? treeData : getBelowList(treeData, current); const currentBelowList =
current.path === "" ? treeData : getBelowList(treeData, current);
currentBelowList.splice(currentBelowList.indexOf(current), 1); currentBelowList.splice(currentBelowList.indexOf(current), 1);
if (info.dropToGap) { if (info.dropToGap) {
body.targetPath = target.path; body.targetPath = target.path;
const targetBelowList = target.path === "" ? treeData : getBelowList(treeData, target); const targetBelowList =
target.path === "" ? treeData : getBelowList(treeData, target);
const index = targetBelowList.indexOf(target); const index = targetBelowList.indexOf(target);
if (info.dropPosition > index) { if (info.dropPosition > index) {
body.sort = target.sort + 1; body.sort = target.sort + 1;
@ -236,7 +253,11 @@ export function onDrop(info) {
current.sort = body.sort; current.sort = body.sort;
//如果当前节点和目标节点不在一个层级中需要更新当前子节点的path信息 //如果当前节点和目标节点不在一个层级中需要更新当前子节点的path信息
if (body.sourcePath !== body.targetPath) { if (body.sourcePath !== body.targetPath) {
updateChildrenPath(current.children, body.sourcePath + "." + body.bookmarkId, body.targetPath + "." + body.bookmarkId); updateChildrenPath(
current.children,
body.sourcePath + "." + body.bookmarkId,
body.targetPath + "." + body.bookmarkId
);
} }
} }
httpUtil httpUtil

View File

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { Tree, Empty, Button, Spin } from "antd"; import { Tree, Empty, Button, Spin } from "antd";
import MainLayout from "../../../layout/MainLayout"; import MainLayout from "../../../layout/MainLayout";
import httpUtil from "../../../util/httpUtil";
import styles from "./index.module.less"; import styles from "./index.module.less";
import { batchDelete, renderTreeNodes, onDrop } from "./function.js"; import { batchDelete, renderTreeNodes, onDrop } from "./function.js";
import { cacheBookmarkData, getBookmarkList } from "../../../util/cacheUtil";
import AddModal from "./AddModal"; import AddModal from "./AddModal";
import Search from "../../../components/Search"; import Search from "../../../components/Search";
@ -43,15 +43,11 @@ class OverView extends React.Component {
/** /**
* 初始化第一级书签 * 初始化第一级书签
*/ */
componentDidMount() { async componentDidMount() {
this.props.refresh(); this.props.refresh();
httpUtil await cacheBookmarkData();
.get("/bookmark/currentUser/path?path=") this.props.updateTreeData(getBookmarkList(""));
.then(res => {
this.props.updateTreeData(res);
this.props.changeIsInit(true); this.props.changeIsInit(true);
})
.catch(() => this.props.changeIsInit(true));
} }
/** /**
@ -62,14 +58,12 @@ class OverView extends React.Component {
return new Promise(resolve => { return new Promise(resolve => {
const item = e.props.dataRef; const item = e.props.dataRef;
const newPath = item.path + "." + item.bookmarkId; const newPath = item.path + "." + item.bookmarkId;
httpUtil.get("/bookmark/currentUser/path?path=" + newPath).then(res => { item.children = getBookmarkList(newPath);
item.children = res;
this.props.updateTreeData([...treeData]); this.props.updateTreeData([...treeData]);
loadedKeys.push(item.bookmarkId.toString()); loadedKeys.push(item.bookmarkId.toString());
this.props.changeLoadedKeys(loadedKeys); this.props.changeLoadedKeys(loadedKeys);
resolve(); resolve();
}); });
});
}; };
/** /**
* 节点选择 * 节点选择

View File

@ -0,0 +1,55 @@
/* eslint-disable no-undef */
import httpUtil from "./httpUtil";
/**
* 缓存工具类
*/
/**
* 全部书签数据key
*/
export const TREE_LIST_KEY = "treeListData";
/**
* 获取全部书签时间
*/
export const TREE_LIST_TIME_KEY = "treeListDataTime";
/**
* 缓存书签数据
*/
export async function cacheBookmarkData() {
let res = await localforage.getItem(TREE_LIST_KEY);
if (!res) {
res = await httpUtil.get("/bookmark/currentUser");
await localforage.setItem(TREE_LIST_KEY, res);
await localforage.setItem(TREE_LIST_TIME_KEY, Date.now());
}
window[TREE_LIST_KEY] = res;
}
/**
* 获取缓存数据
* @param {*} path path
*/
export function getBookmarkList(path) {
return window[TREE_LIST_KEY][path];
}
/**
* 检查缓存情况
* @return 返回true说明未过期否则说明过期了
*/
export async function checkCacheStatus() {
let date = await localforage.getItem(TREE_LIST_TIME_KEY, Date.now());
let userInfo = await httpUtil.get("/user/currentUserInfo");
return !date || date > userInfo.bookmarkChangeTime;
}
/**
* 清楚缓存数据
*/
export async function clearCache() {
await localforage.removeItem(TREE_LIST_KEY);
await localforage.removeItem(TREE_LIST_TIME_KEY);
window.location.reload();
}