feat:搜索完成50%

This commit is contained in:
fanxb 2020-10-05 23:36:50 +08:00
parent 97e015ac49
commit a705cc76fb
10 changed files with 376 additions and 8809 deletions

View File

@ -1,5 +1,5 @@
{
"printWidth": 200,
"printWidth": 150,
"singleQuote": false,
"bracketSpacing": true,
"jsxBracketSameLine": true,

View 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>

View File

@ -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>

View File

@ -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({

View File

@ -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]);
}
};

View File

@ -25,7 +25,7 @@ export async function clearToken() {
* 本地获取用户信息
*/
export async function getUesrInfo() {
return null;
return window.vueInstance.$store.state.globalConfig.userInfo;
}
/**

View File

@ -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>

View File

@ -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;

View File

@ -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