feat:搜索完成50%
This commit is contained in:
parent
97e015ac49
commit
a705cc76fb
@ -1,5 +1,5 @@
|
||||
{
|
||||
"printWidth": 200,
|
||||
"printWidth": 150,
|
||||
"singleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": true,
|
||||
|
144
bookmark_front/src/components/main/Search.vue
Normal file
144
bookmark_front/src/components/main/Search.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<a-input-search
|
||||
ref="searchInput"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
v-model="value"
|
||||
enter-button
|
||||
@change="search"
|
||||
@search="searchClick"
|
||||
allowClear
|
||||
@blur.prevent="inputBlur"
|
||||
@focus="inputFocus"
|
||||
/>
|
||||
<div v-if="focused" class="searchContent">
|
||||
<a-empty v-if="list.length == 0" />
|
||||
<div
|
||||
class="listItem"
|
||||
v-for="(item, index) in list"
|
||||
:key="item.bookmarkId"
|
||||
@mouseenter="mouseEnterOut(index, 'enter')"
|
||||
@mouseleave="mouseEnterOut(index, 'leave')"
|
||||
@mouseup="onMouse"
|
||||
@click="itemClick(item)"
|
||||
>
|
||||
<a style="min-width: 4em" :href="item.url" target="_blank">
|
||||
{{ item.name }}
|
||||
</a>
|
||||
<a-tooltip v-if="showActions && hoverIndex === index" title="定位到书签树中">
|
||||
<my-icon style="color: #40a9ff; font-size: 1.3em; cursor: pointer" type="icon-et-location" @click="location(item)" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HttpUtil from "@/util/HttpUtil";
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "Search",
|
||||
props: {
|
||||
//是否显示定位等按钮
|
||||
showActions: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: "",
|
||||
focused: false,
|
||||
list: [],
|
||||
//计时器结束列表的显示
|
||||
timer: null,
|
||||
hoverIndex: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState("treeData", ["totalTreeData"]),
|
||||
},
|
||||
methods: {
|
||||
search(content) {
|
||||
content = content.target.value;
|
||||
content = content.toLocaleLowerCase().trim();
|
||||
this.value = content;
|
||||
if (content === "") {
|
||||
this.list = [];
|
||||
return;
|
||||
}
|
||||
let time1 = Date.now();
|
||||
this.list = this.dealSearch(content);
|
||||
console.info("搜索耗时:" + (Date.now() - time1));
|
||||
},
|
||||
searchClick() {
|
||||
if (this.timer != null) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
},
|
||||
itemClick(item) {
|
||||
HttpUtil.post("/bookmark/visitNum", { id: item.bookmarkId });
|
||||
},
|
||||
inputBlur() {
|
||||
console.log("blur");
|
||||
this.timer = setTimeout(() => (this.focused = false), 300);
|
||||
},
|
||||
inputFocus() {
|
||||
this.focused = true;
|
||||
},
|
||||
onMouse(e) {
|
||||
if (this.timer != null) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.$refs["searchInput"].focus();
|
||||
},
|
||||
mouseEnterOut(item, type) {
|
||||
console.log(item, type);
|
||||
if (type === "enter") {
|
||||
this.hoverIndex = item;
|
||||
} else {
|
||||
this.hoverIndex = null;
|
||||
}
|
||||
},
|
||||
//定位到书签树中
|
||||
location(item) {
|
||||
this.$emit("location", item);
|
||||
},
|
||||
dealSearch(content) {
|
||||
let res = [];
|
||||
let arrs = Object.values(this.totalTreeData);
|
||||
for (let i1 = 0, length1 = arrs.length; i1 < length1; i1++) {
|
||||
for (let i2 = 0, length2 = arrs[i1].length; i2 < length2; i2++) {
|
||||
let item = arrs[i1][i2];
|
||||
if (item.type === 1) {
|
||||
continue;
|
||||
}
|
||||
if (item.searchKey.indexOf(content) > -1) {
|
||||
res.push(item);
|
||||
if (res.length >= 12) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
position: relative;
|
||||
.searchContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background: white;
|
||||
z-index: 1;
|
||||
.listItem {
|
||||
font-size: 0.16rem;
|
||||
display: flex;
|
||||
height: 0.3rem;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -16,7 +16,13 @@
|
||||
</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>
|
||||
|
@ -1,12 +1,35 @@
|
||||
import Vue from "vue";
|
||||
import { Button, FormModel, Input, Icon, message, Checkbox, Dropdown, Menu, Tree, Tooltip, Spin, notification, Empty, Modal, Radio, Upload } from "ant-design-vue";
|
||||
import {
|
||||
Button,
|
||||
FormModel,
|
||||
Input,
|
||||
Icon,
|
||||
message,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
Menu,
|
||||
Tree,
|
||||
Tooltip,
|
||||
Spin,
|
||||
notification,
|
||||
Empty,
|
||||
Modal,
|
||||
Radio,
|
||||
Upload,
|
||||
Popconfirm,
|
||||
AutoComplete,
|
||||
Select
|
||||
} from "ant-design-vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
|
||||
Vue.component(Button.name, Button);
|
||||
const IconFont = Icon.createFromIconfontCN({
|
||||
scriptUrl: "//at.alicdn.com/t/font_1261825_3wf60i93sdm.js"
|
||||
});
|
||||
Vue.use(Button);
|
||||
Vue.use(FormModel);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.use(Input);
|
||||
Vue.component(Icon.name, Icon);
|
||||
Vue.use(Checkbox);
|
||||
Vue.use(Dropdown);
|
||||
@ -18,9 +41,14 @@ Vue.use(Empty);
|
||||
Vue.use(Modal);
|
||||
Vue.use(Radio);
|
||||
Vue.use(Upload);
|
||||
Vue.use(Popconfirm);
|
||||
Vue.use(AutoComplete);
|
||||
Vue.use(Select);
|
||||
Vue.component("my-icon", IconFont);
|
||||
|
||||
Vue.prototype.$message = message;
|
||||
Vue.prototype.$notification = notification;
|
||||
Vue.prototype.$confirm = Modal.confirm;
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
window.vueInstance = new Vue({
|
||||
|
@ -1,7 +1,9 @@
|
||||
import localforage from "localforage";
|
||||
import httpUtil from "../../util/HttpUtil";
|
||||
import { getUesrInfo } from "../../util/UserUtil";
|
||||
|
||||
const TOTAL_TREE_DATA = "totalTreeData";
|
||||
const VERSION = "version";
|
||||
|
||||
/**
|
||||
* 书签树相关配置
|
||||
@ -9,11 +11,30 @@ const TOTAL_TREE_DATA = "totalTreeData";
|
||||
const state = {
|
||||
//全部书签数据
|
||||
[TOTAL_TREE_DATA]: {},
|
||||
version: null,
|
||||
isInit: false
|
||||
[VERSION]: null,
|
||||
isInit: false,
|
||||
/**
|
||||
* 是否正在加载数据
|
||||
*/
|
||||
isIniting: false
|
||||
};
|
||||
|
||||
const getters = {};
|
||||
const getters = {
|
||||
/**
|
||||
* 通过id获取节点数据
|
||||
*/
|
||||
getById: state => id => {
|
||||
let arr = Object.values(state[TOTAL_TREE_DATA]);
|
||||
for (let i in arr) {
|
||||
for (let j in arr[i]) {
|
||||
if (arr[i][j].bookmarkId === id) {
|
||||
return arr[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
//从缓存初始化数据
|
||||
@ -21,12 +42,34 @@ const actions = {
|
||||
if (context.state.isInit) {
|
||||
return;
|
||||
}
|
||||
context.commit("isIniting", true);
|
||||
let data = await localforage.getItem(TOTAL_TREE_DATA);
|
||||
if (data == null) {
|
||||
await context.dispatch("refresh");
|
||||
} else {
|
||||
context.commit(TOTAL_TREE_DATA, data);
|
||||
context.commit(VERSION, await localforage.getItem(VERSION));
|
||||
}
|
||||
context.commit("isIniting", false);
|
||||
context.commit("isInit", true);
|
||||
},
|
||||
/**
|
||||
* 确保数据加载完毕
|
||||
*/
|
||||
ensureDataOk(context) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let timer = setInterval(() => {
|
||||
try {
|
||||
if (context.state.isInit && context.state.isIniting == false) {
|
||||
clearInterval(timer);
|
||||
console.log(timer);
|
||||
resolve();
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
//刷新缓存数据
|
||||
async refresh(context) {
|
||||
@ -36,14 +79,19 @@ const actions = {
|
||||
}
|
||||
treeData[""].forEach(item => (item.isLeaf = item.type === 0));
|
||||
context.commit(TOTAL_TREE_DATA, treeData);
|
||||
localforage.setItem(TOTAL_TREE_DATA, treeData);
|
||||
await localforage.setItem(TOTAL_TREE_DATA, treeData);
|
||||
let userInfo = await httpUtil.get("/user/currentUserInfo");
|
||||
context.commit("version", userInfo.version);
|
||||
context.commit(VERSION, userInfo.version);
|
||||
await localforage.setItem(VERSION, userInfo.version);
|
||||
},
|
||||
//清除缓存数据
|
||||
async clear(context) {
|
||||
context.commit(TOTAL_TREE_DATA, {});
|
||||
context.commit(TOTAL_TREE_DATA, null);
|
||||
context.commit(VERSION, null);
|
||||
context.commit("isInit", false);
|
||||
context.commit("isIniting", false);
|
||||
await localforage.removeItem(TOTAL_TREE_DATA);
|
||||
await localforage.removeItem(VERSION);
|
||||
},
|
||||
async moveNode(context, info) {
|
||||
debugger;
|
||||
@ -121,6 +169,9 @@ const mutations = {
|
||||
isInit(state, isInit) {
|
||||
state.isInit = isInit;
|
||||
},
|
||||
isIniting(state, isIniting) {
|
||||
state.isIniting = isIniting;
|
||||
},
|
||||
deleteData(state, { pathList, bookmarkIdList }) {
|
||||
//待删除的书签
|
||||
let bookmarkIdSet = new Set();
|
||||
@ -164,12 +215,17 @@ const mutations = {
|
||||
}
|
||||
localforage.setItem(TOTAL_TREE_DATA, state[TOTAL_TREE_DATA]);
|
||||
},
|
||||
/**
|
||||
* 更新版本文件
|
||||
*/
|
||||
version(state, version) {
|
||||
if (version == null) {
|
||||
console.log("version:", version);
|
||||
if (version == null && state.version != null) {
|
||||
state.version = state.version + 1;
|
||||
} else {
|
||||
state.version = version;
|
||||
}
|
||||
localforage.setItem(VERSION, state[VERSION]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,7 @@ export async function clearToken() {
|
||||
* 本地获取用户信息
|
||||
*/
|
||||
export async function getUesrInfo() {
|
||||
return null;
|
||||
return window.vueInstance.$store.state.globalConfig.userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,13 +17,71 @@ import httpUtil from "../../util/HttpUtil";
|
||||
export default {
|
||||
name: "Home",
|
||||
components: { Top, Content, Bottom },
|
||||
async beforeCreate() {
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
//当前是第几轮
|
||||
count: 0,
|
||||
//计时经过多少轮后才处理
|
||||
total: 1,
|
||||
//弹窗是否处于展开状态
|
||||
isOpen: false,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
//数据初始化
|
||||
await this.$store.dispatch("globalConfig/init");
|
||||
//更新用户基本信息
|
||||
let userInfo = await httpUtil.get("/user/currentUserInfo");
|
||||
this.$store.commit("globalConfig/setUserInfo", userInfo);
|
||||
this.$store.commit("treeData/version", userInfo.version);
|
||||
console.log("globalConfig加载完毕");
|
||||
await this.$store.dispatch("treeData/init");
|
||||
console.log("treeData加载完毕");
|
||||
await this.checkVersion();
|
||||
this.timer = setInterval(this.checkVersion, 60 * 1000);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.timer != null) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 检查当前页面版本是否和服务器版本一致
|
||||
*/
|
||||
async checkVersion() {
|
||||
this.count++;
|
||||
if (this.count < this.total || this.isOpen) {
|
||||
return;
|
||||
}
|
||||
this.count = 0;
|
||||
let userInfo = await httpUtil.get("/user/currentUserInfo");
|
||||
this.$store.commit("globalConfig/setUserInfo", userInfo);
|
||||
const _this = this;
|
||||
if (this.$store.state.treeData.version < userInfo.version) {
|
||||
this.isOpen = true;
|
||||
this.$confirm({
|
||||
title: "书签数据有更新,是否立即刷新?",
|
||||
content: "点击确定将刷新整个页面,请注意!",
|
||||
cancelText: "五分钟后再提醒",
|
||||
closable: false,
|
||||
keyboard: false,
|
||||
maskClosable: false,
|
||||
onOk() {
|
||||
_this.isOpen = false;
|
||||
return new Promise(async (resolve) => {
|
||||
await _this.$store.dispatch("treeData/clear");
|
||||
window.location.reload();
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
_this.isOpen = false;
|
||||
_this.total = 5;
|
||||
},
|
||||
afterClose() {
|
||||
_this.isOpen = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading" :delay="300">
|
||||
<div>
|
||||
<div class="search">
|
||||
<search :showActions="true" @location="location" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<span class="myBookmark">我的书签</span>
|
||||
<a-tooltip title="刷新书签缓存">
|
||||
<a-button @click="refresh(true)" type="primary" shape="circle" icon="sync" />
|
||||
@ -8,7 +11,13 @@
|
||||
<a-tooltip title="多选">
|
||||
<a-button type="primary" shape="circle" icon="check" @click="switchMul" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="checkedKeys.length === 0 || (checkedKeys.length === 1 && checkedNodes[0].type === 1)" title="添加书签">
|
||||
<a-tooltip
|
||||
v-if="
|
||||
(checkedKeys.length === 0 && (currentSelect == null || currentSelect.type === 1)) ||
|
||||
(checkedKeys.length === 1 && checkedNodes[0].type === 1)
|
||||
"
|
||||
title="添加书签"
|
||||
>
|
||||
<a-button type="primary" shape="circle" icon="plus" @click="addData" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="currentSelect || checkedKeys.length === 1" title="编辑书签">
|
||||
@ -17,11 +26,19 @@
|
||||
<a-tooltip v-if="moveShow" title="移动书签">
|
||||
<a-button type="primary" shape="circle" icon="scissor" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="checkedKeys.length > 0 || currentSelect" title="删除书签" @click="deleteBookmarks">
|
||||
<a-button type="danger" shape="circle" icon="delete" />
|
||||
</a-tooltip>
|
||||
<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" description="无数据,点击上方 + 新增"></a-empty>
|
||||
<a-empty v-if="treeData.length == 0 && loading == false" description="无数据,点击上方 + 新增"></a-empty>
|
||||
<a-tree
|
||||
v-else
|
||||
:tree-data="treeData"
|
||||
@ -49,11 +66,12 @@
|
||||
|
||||
<script>
|
||||
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";
|
||||
export default {
|
||||
name: "BookmarkManage",
|
||||
components: { AddBookmark },
|
||||
components: { AddBookmark, Search },
|
||||
data() {
|
||||
return {
|
||||
treeData: [],
|
||||
@ -83,15 +101,18 @@ export default {
|
||||
...mapState("treeData", ["totalTreeData"]),
|
||||
...mapState("globalConfig", ["isPhone"]),
|
||||
},
|
||||
async beforeCreate() {
|
||||
await this.$store.dispatch("treeData/init");
|
||||
async mounted() {
|
||||
await this.$store.dispatch("treeData/ensureDataOk");
|
||||
this.treeData = this.totalTreeData[""];
|
||||
this.loading = false;
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载数据,兼容treeNode为id
|
||||
*/
|
||||
loadData(treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
const data = treeNode.dataRef;
|
||||
const data = typeof treeNode === "number" ? this.$store.getters["treeData/getById"](treeNode) : treeNode.dataRef;
|
||||
let newPath = data.path + "." + data.bookmarkId;
|
||||
if (!this.totalTreeData[newPath]) {
|
||||
this.totalTreeData[newPath] = [];
|
||||
@ -104,8 +125,8 @@ export default {
|
||||
});
|
||||
},
|
||||
async refresh(deleteCache) {
|
||||
this.loading = true;
|
||||
if (deleteCache) {
|
||||
this.loading = true;
|
||||
await this.$store.dispatch("treeData/refresh");
|
||||
}
|
||||
this.treeData = [...this.totalTreeData[""]];
|
||||
@ -182,15 +203,20 @@ export default {
|
||||
const bookmarkIdList = [],
|
||||
pathList = [];
|
||||
if (this.checkedNodes) {
|
||||
this.checkedNodes.forEach((item) => (item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId)));
|
||||
this.checkedNodes.forEach((item) =>
|
||||
item.type === 1 ? pathList.push(item.path + "." + item.bookmarkId) : bookmarkIdList.push(item.bookmarkId)
|
||||
);
|
||||
}
|
||||
if (this.currentSelect) {
|
||||
this.currentSelect.type === 1 ? pathList.push(this.currentSelect.path + "." + this.currentSelect.bookmarkId) : bookmarkIdList.push(this.currentSelect.bookmarkId);
|
||||
this.currentSelect.type === 1
|
||||
? pathList.push(this.currentSelect.path + "." + this.currentSelect.bookmarkId)
|
||||
: bookmarkIdList.push(this.currentSelect.bookmarkId);
|
||||
}
|
||||
if (pathList.length === 0 && bookmarkIdList.length === 0) {
|
||||
this.$message.warn("请选择后再进行操作");
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
await HttpUtil.post("/bookmark/batchDelete", null, {
|
||||
pathList,
|
||||
@ -226,6 +252,20 @@ export default {
|
||||
this.$set(this.addModal, "isAdd", true);
|
||||
this.$set(this.addModal, "targetNode", this.currentSelect || (this.checkedNodes.length > 0 ? this.checkedNodes[0] : null));
|
||||
},
|
||||
async location(item) {
|
||||
console.log(item);
|
||||
this.refresh(false);
|
||||
this.expandedKeys = item.path
|
||||
.split(".")
|
||||
.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));
|
||||
this.currentSelect = item;
|
||||
},
|
||||
/**
|
||||
* 关闭弹窗
|
||||
* @param data data为null说明需要刷新书签树,不为浪即为修改/新增的对象
|
||||
@ -279,6 +319,13 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search {
|
||||
}
|
||||
.actions {
|
||||
height: 0.42rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.myBookmark {
|
||||
font-size: 0.25rem;
|
||||
font-weight: 600;
|
||||
|
@ -9,7 +9,12 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Public"
|
||||
name: "Public",
|
||||
async created() {
|
||||
//进入注册、登录页需要清理掉所有的缓存数据
|
||||
await this.$store.dispatch("treeData/clear");
|
||||
await this.$store.dispatch("globalConfig/clear");
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user