diff --git a/front/.gitignore b/front/.gitignore index 4a5ec65..66c8e91 100644 --- a/front/.gitignore +++ b/front/.gitignore @@ -21,4 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.vscode \ No newline at end of file +.vscode +yarn.lock \ No newline at end of file diff --git a/front/package.json b/front/package.json index 206adfd..a68aea4 100644 --- a/front/package.json +++ b/front/package.json @@ -7,6 +7,7 @@ "antd": "^3.19.8", "axios": "^0.19.0", "babel-plugin-import": "^1.12.0", + "clipboard": "^2.0.4", "customize-cra": "^0.2.14", "less": "^3.9.0", "query-string": "^6.8.1", @@ -42,4 +43,4 @@ "devDependencies": { "less-loader": "^5.0.0" } -} \ No newline at end of file +} diff --git a/front/public/index.html b/front/public/index.html index b1a5133..dcf6794 100644 --- a/front/public/index.html +++ b/front/public/index.html @@ -19,7 +19,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + 签签世界 @@ -34,5 +34,6 @@ To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> + diff --git a/front/src/App.jsx b/front/src/App.jsx index c61ac68..df81037 100644 --- a/front/src/App.jsx +++ b/front/src/App.jsx @@ -1,7 +1,9 @@ import React, { Component } from "react"; import { Route, Switch, Redirect } from "react-router-dom"; +import { message } from "antd"; import { withRouter } from "react-router-dom"; import { Provider } from "react-redux"; +import Clipboard from "clipboard"; import store from "./redux"; import NotFound from "./pages/public/notFound/NotFound"; @@ -17,6 +19,15 @@ class App extends Component { window.reactHistory = this.props.history; } + componentDidMount() { + //初始化clipboard + let clipboard = new Clipboard(".copy-to-board"); + clipboard.on("success", function(e) { + message.success("复制成功"); + e.clearSelection(); + }); + } + render() { const mainStyle = { fontSize: "0.14rem" diff --git a/front/src/pages/manage/OverView/AddModal.jsx b/front/src/pages/manage/OverView/AddModal.jsx new file mode 100644 index 0000000..ccfa831 --- /dev/null +++ b/front/src/pages/manage/OverView/AddModal.jsx @@ -0,0 +1,111 @@ +import React from "react"; +import { Modal, Form, Upload, Button, Radio, Input, Icon, message } from "antd"; +import httpUtil from "../../../util/httpUtil"; + +export default class AddModal extends React.Component { + constructor(props) { + super(props); + this.state = { + addType: 0, + addName: "", + addValue: "", + file: null + }; + } + + addOne = () => { + const { currentAddFolder, addToTree, closeModal } = this.props; + const path = currentAddFolder == null ? "" : currentAddFolder.path + "." + currentAddFolder.bookmarkId; + if (this.state.addType === 2) { + const form = new FormData(); + form.append("path", path); + form.append("file", this.state.file.originFileObj); + httpUtil + .put("/bookmark/uploadBookmarkFile", form, { + headers: { "Content-Type": "multipart/form-data" } + }) + .then(res => { + window.location.reload(); + }); + } else { + let body = { + type: this.state.addType, + path, + name: this.state.addName, + url: this.state.addValue + }; + httpUtil.put("/bookmark", body).then(res => { + addToTree(res); + message.success("加入成功"); + closeModal(); + }); + } + }; + + close = () => { + const { closeModal } = this.props; + this.setState({ file: null, addType: 0, addValue: "", addName: "" }); + closeModal(); + }; + + render() { + const { isShowModal } = this.props; + const { addType, addName, addValue } = this.state; + const formItemLayout = { + labelCol: { + xs: { span: 4 }, + sm: { span: 4 } + }, + wrapperCol: { + xs: { span: 20 }, + sm: { span: 20 } + } + }; + const uploadProps = { + accept: ".html,.htm", + onChange: e => { + this.setState({ file: e.fileList[0] }); + }, + beforeUpload: file => { + return false; + }, + fileList: [] + }; + return ( + +
+ + this.setState({ addType: e.target.value })}> + 书签 + 文件夹 + 上传书签html + + + {addType < 2 ? ( + + this.setState({ addName: e.target.value })} value={addName} /> + + ) : null} + {addType === 0 ? ( + + this.setState({ addValue: e.target.value })} /> + + ) : null} + {addType === 2 ? ( + + + + ) : null} +
+ +
+
+
+ ); + } +} diff --git a/front/src/pages/manage/OverView/function.js b/front/src/pages/manage/OverView/function.js index 0f1618c..6f1b8bc 100644 --- a/front/src/pages/manage/OverView/function.js +++ b/front/src/pages/manage/OverView/function.js @@ -1,5 +1,9 @@ import httpUtil from "../../../util/httpUtil"; -import { Modal, message } from "antd"; +import React from "react"; +import { Modal, Button, Tooltip, Tree } from "antd"; +import styles from "./index.module.less"; +import IconFont from "../../../components/IconFont"; +const { TreeNode } = Tree; /** * 选中的文件夹id列表 @@ -10,11 +14,6 @@ let folderIdList = []; */ let bookmarkIdList = []; -/** - * 新增书签的父节点node - */ -let parentNode = null; - /** * 展开/关闭 * @param {*} keys @@ -39,71 +38,108 @@ export function onCheck(keys, data) { this.setState({ checkedKeys: keys }); bookmarkIdList = []; folderIdList = []; - parentNode = null; data.checkedNodes.forEach(item => { const bookmark = item.props.dataRef; - parentNode = bookmark; bookmark.type === 0 ? bookmarkIdList.push(bookmark.bookmarkId) : folderIdList.push(bookmark.bookmarkId); }); } /** - * 弹出新增modal + * 渲染树节点中节点内容 + * @param {*} item */ -export function showAddModel() { - if (this.state.checkedKeys.length > 1) { - message.error("选中过多"); - return; - } else if (this.state.checkedKeys.length === 1) { - const id = this.state.checkedKeys[0]; - if (bookmarkIdList.indexOf(parseInt(id)) > -1) { - message.error("只能选择文件夹节点"); - return; - } - } - this.setState({ isShowModal: true }); +export function renderNodeContent(item) { + const { isEdit } = this.state; + // 节点内容后面的操作按钮 + const btns = ( +
+ {item.type === 0 ? ( +
+ ); + return ( + + + {item.name} + + {isEdit ? btns : null} + + ); } /** - * 新增书签 + * 渲染树节点 + * @param {*} items */ -export function addOne() { - console.log(1); - if (this.state.addType === 2) { - addHtmlFile(); - return; +export function renderTreeNodes(items) { + if (!(items && items.length >= 0)) { + return null; } - let body = { - type: this.state.addType, - path: parentNode == null ? "" : parentNode.path + "." + parentNode.bookmarkId, - name: this.state.addName, - url: this.state.addValue - }; - httpUtil.put("/bookmark", body).then(res => { - let arr; - if (parentNode == null) { - arr = this.data[""] ? this.data[""] : []; - } else { - arr = this.data[body.path] ? this.data[body.path] : []; + return items.map(item => { + const isLeaf = item.type === 0; + if (!isLeaf) { + return ( + } + isLeaf={isLeaf} + title={renderNodeContent.call(this, item)} + key={item.bookmarkId} + dataRef={item} + > + {renderTreeNodes.call(this, item.children)} + + ); } - arr.push(res); - if (this.state.treeData.length === 0) { - this.state.treeData.push(arr); - } - this.data[body.path] = arr; - this.setState({ treeData: [...this.state.treeData], addType: 0, addName: "", addValue: "", isShowModal: false }); + return ( + } + isLeaf={isLeaf} + title={renderNodeContent.call(this, item)} + key={item.bookmarkId} + dataRef={item} + /> + ); }); } -export function addHtmlFile() { - +/** + * 删除一个 + * @param {*} e + */ +export function deleteOne(item, e) { + e.stopPropagation(); + if (item.type === 0) { + deleteBookmark.call(this, [], [item.bookmarkId]); + } else { + deleteBookmark.call(this, [item.bookmarkId], []); + } } /** * 批量删除 */ export function batchDelete() { - console.log("1"); + deleteBookmark.call(this, folderIdList, bookmarkIdList); +} + +/** + * 删除书签 + * @param {*} folderIdList + * @param {*} bookmarkIdList + */ +function deleteBookmark(folderIdList, bookmarkIdList) { const _this = this; Modal.confirm({ title: "确认删除?", @@ -115,8 +151,8 @@ export function batchDelete() { .then(() => { //遍历节点树数据,并删除 const set = new Set(); - folderIdList.forEach(item => set.add(item)); - bookmarkIdList.forEach(item => set.add(item)); + folderIdList.forEach(item => set.add(parseInt(item))); + bookmarkIdList.forEach(item => set.add(parseInt(item))); deleteTreeData(_this.state.treeData, set); _this.setState({ treeData: [..._this.state.treeData], checkedKeys: [] }); resolve(); diff --git a/front/src/pages/manage/OverView/index.jsx b/front/src/pages/manage/OverView/index.jsx index 5820026..0e40c76 100644 --- a/front/src/pages/manage/OverView/index.jsx +++ b/front/src/pages/manage/OverView/index.jsx @@ -1,12 +1,10 @@ import React from "react"; -import { Icon, Tree, Empty, Button, Tooltip, Modal, Form, Input, Radio, Upload } from "antd"; +import { Tree, Empty, Button } from "antd"; import MainLayout from "../../../layout/MainLayout"; import httpUtil from "../../../util/httpUtil"; -import IconFont from "../../../components/IconFont"; import styles from "./index.module.less"; -import { batchDelete, onExpand, closeAll, onCheck, addOne, showAddModel } from "./function.js"; - -const { TreeNode } = Tree; +import { batchDelete, renderTreeNodes, onExpand, closeAll, onCheck } from "./function.js"; +import AddModal from "./AddModal"; export default class OverView extends React.Component { constructor(props) { @@ -17,131 +15,99 @@ export default class OverView extends React.Component { isLoading: true, checkedKeys: [], expandKeys: [], - //显示新增弹窗 + //是否显示新增书签弹窗 isShowModal: false, - //新增类别 - addType: 0, - addName: "", - addValue: "", - file: null + currentAddFolder: null }; } + /** + * 初始化第一级书签 + */ componentDidMount() { httpUtil - .get("/bookmark/currentUser") + .get("/bookmark/currentUser/path?path=") .then(res => { - this.data = res; - if (res[""]) { - this.setState({ treeData: res[""] }); - } - this.setState({ isLoading: false }); + this.setState({ treeData: res, isLoading: false }); }) .catch(() => this.setState({ isLoading: false })); } + /** + * 异步加载 + */ loadData = e => new Promise(resolve => { const item = e.props.dataRef; const newPath = item.path + "." + item.bookmarkId; - if (this.data[newPath]) { - item.children = this.data[newPath]; + httpUtil.get("/bookmark/currentUser/path?path=" + newPath).then(res => { + item.children = res; this.setState({ treeData: [...this.state.treeData] }); resolve(); - } else { - resolve(); - } + }); }); + /** + * 节点选择 + * @param {*} key + * @param {*} e + */ treeNodeSelect(key, e) { + if (e.nativeEvent.delegateTarget.name === "copy") { + return; + } const { expandKeys } = this.state; const item = e.node.props.dataRef; if (item.type === 0) { window.open(item.url); } else { - expandKeys.push(item.bookmarkId); + const id = item.bookmarkId.toString(); + const index = expandKeys.indexOf(id); + index === -1 ? expandKeys.push(id) : expandKeys.splice(index, 1); + this.setState({ expandKeys: [...expandKeys] }); } } /** - * 渲染树节点中节点内容 - * @param {*} item + * 将新增的数据加入到state中 */ - renderNodeContent(item) { - // const { isEdit } = this.state; - // const bts = ( - //
- //
- // ); - return ( - - - {item.name} - - {/* {isEdit ? bts : null} */} - - ); - } - - /** - * 渲染树节点 - * @param {*} items - */ - renderTreeNodes(items) { - if (!(items && items.length >= 0)) { - return null; - } - return items.map(item => { - const isLeaf = item.type === 0; - if (!isLeaf) { - return ( - } isLeaf={isLeaf} title={item.name} key={item.bookmarkId} dataRef={item}> - {this.renderTreeNodes(item.children)} - - ); + addToTree = node => { + const { currentAddFolder, treeData } = this.state; + if (currentAddFolder === null) { + treeData.push(node); + } else { + //存在children说明该子节点的孩子数据已经加载,需要重新将新的子书签加入进去 + if (currentAddFolder.children) { + currentAddFolder.children.push(node); + this.setState({ treeData: [...treeData] }); } - return ( - } - isLeaf={isLeaf} - title={this.renderNodeContent(item)} - key={item.bookmarkId} - dataRef={item} - /> - ); - }); - } + } + }; + + /** + * 关闭新增书签弹窗 + */ + closeAddModal = () => { + this.setState({ isShowModal: false }); + }; + + /** + * 新增书签 + */ + addOne = (item, e) => { + e.stopPropagation(); + this.setState({ currentAddFolder: item, isShowModal: true }); + }; render() { - const { isLoading, isEdit, treeData, expandKeys, checkedKeys, isShowModal, addType, addValue, addName } = this.state; - const formItemLayout = { - labelCol: { - xs: { span: 4 }, - sm: { span: 4 } - }, - wrapperCol: { - xs: { span: 20 }, - sm: { span: 20 } - } - }; - const uploadProps = { - accept: ".html,.htm", - onChange: e => { - this.setState({ file: e.fileList[0] }); - }, - beforeUpload: file => { - return false; - }, - fileList: [] - }; + const { isLoading, isEdit, treeData, expandKeys, checkedKeys, isShowModal, currentAddFolder } = this.state; return (
我的书签树 + {isEdit ? - ) : null}
- {/* {treeData.length ? ( */} - {this.renderTreeNodes(treeData)} + {renderTreeNodes.call(this, treeData)} - {/* ) : null} */} {isLoading === false && treeData.length === 0 ? : null} - - this.setState({ isShowModal: false })} footer={false}> -
- - this.setState({ addType: e.target.value })}> - 书签 - 文件夹 - 上传书签html - - - {addType < 2 ? ( - - this.setState({ addName: e.target.value })} value={addName} /> - - ) : null} - {addType === 0 ? ( - - this.setState({ addValue: e.target.value })} /> - - ) : null} - {addType === 2 ? ( - - - - ) : null} -
- -
-
-
+
); diff --git a/front/src/pages/manage/OverView/index.module.less b/front/src/pages/manage/OverView/index.module.less index e05ae03..ddcf867 100644 --- a/front/src/pages/manage/OverView/index.module.less +++ b/front/src/pages/manage/OverView/index.module.less @@ -28,7 +28,10 @@ .btns { display: inline-block; - position: relative; - top: -0.45em; + padding-left: 1em; + } + + .btns > button { + margin-right: 0.4em; } } diff --git a/front/src/pages/public/Login/index.jsx b/front/src/pages/public/Login/index.jsx index 20f7711..75a05c4 100644 --- a/front/src/pages/public/Login/index.jsx +++ b/front/src/pages/public/Login/index.jsx @@ -31,7 +31,11 @@ class Login extends Component { } valueChange = e => { - this.setState({ [e.target.name]: e.target.value }); + if (e.target.name === "rememberMe") { + this.setState({ rememberMe: e.target.checked }); + } else { + this.setState({ [e.target.name]: e.target.value }); + } }; submit = () => { @@ -81,7 +85,7 @@ class Login extends Component { placeholder="密码" />
- + 记住我 忘记密码