Работа над клиентской частью
This commit is contained in:
59
client/components/Api/Api.vue
Normal file
59
client/components/Api/Api.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import vueComponent from '../vueComponent.js';
|
||||||
|
|
||||||
|
import wsc from './webSocketConnection';
|
||||||
|
//import _ from 'lodash';
|
||||||
|
|
||||||
|
const componentOptions = {
|
||||||
|
components: {
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
class Api {
|
||||||
|
_options = componentOptions;
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.commit = this.$store.commit;
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(params) {
|
||||||
|
return await wsc.message(await wsc.send(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query) {
|
||||||
|
const response = await this.request({action: 'search', query});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async config() {
|
||||||
|
const response = await this.request({action: 'get-config'});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default vueComponent(Api);
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
3
client/components/Api/webSocketConnection.js
Normal file
3
client/components/Api/webSocketConnection.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import WebSocketConnection from '../../../server/core/WebSocketConnection';
|
||||||
|
|
||||||
|
export default new WebSocketConnection();
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fit row">
|
<div class="fit row">
|
||||||
|
<Api ref="api" />
|
||||||
<Notify ref="notify" />
|
<Notify ref="notify" />
|
||||||
<StdDialog ref="stdDialog" />
|
<StdDialog ref="stdDialog" />
|
||||||
|
|
||||||
@@ -19,10 +20,12 @@ import vueComponent from './vueComponent.js';
|
|||||||
import Notify from './share/Notify.vue';
|
import Notify from './share/Notify.vue';
|
||||||
import StdDialog from './share/StdDialog.vue';
|
import StdDialog from './share/StdDialog.vue';
|
||||||
|
|
||||||
|
import Api from './Api/Api.vue';
|
||||||
import Search from './Search/Search.vue';
|
import Search from './Search/Search.vue';
|
||||||
|
|
||||||
const componentOptions = {
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
|
Api,
|
||||||
Notify,
|
Notify,
|
||||||
StdDialog,
|
StdDialog,
|
||||||
|
|
||||||
@@ -36,6 +39,8 @@ class App {
|
|||||||
_options = componentOptions;
|
_options = componentOptions;
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.commit = this.$store.commit;
|
||||||
|
|
||||||
//root route
|
//root route
|
||||||
let cachedRoute = '';
|
let cachedRoute = '';
|
||||||
let cachedPath = '';
|
let cachedPath = '';
|
||||||
@@ -49,6 +54,7 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$root.isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
this.$root.isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
|
||||||
|
this.$root.setAppTitle = this.setAppTitle;
|
||||||
|
|
||||||
//global keyHooks
|
//global keyHooks
|
||||||
this.keyHooks = [];
|
this.keyHooks = [];
|
||||||
@@ -80,10 +86,26 @@ class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$root.api = this.$refs.api;
|
||||||
this.$root.notify = this.$refs.notify;
|
this.$root.notify = this.$refs.notify;
|
||||||
this.$root.stdDialog = this.$refs.stdDialog;
|
this.$root.stdDialog = this.$refs.stdDialog;
|
||||||
|
|
||||||
this.setAppTitle();
|
this.setAppTitle();
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
try {
|
||||||
|
const api = this.$root.api;
|
||||||
|
const config = await api.config();
|
||||||
|
this.commit('setConfig', config);
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get config() {
|
||||||
|
return this.$store.state.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rootRoute() {
|
get rootRoute() {
|
||||||
|
|||||||
@@ -1,6 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="root row fit">
|
<div class="root column fit">
|
||||||
<div>Search</div>
|
<div class="tool-panel bg-green-11">
|
||||||
|
<div class="header q-mx-md q-mt-xs row justify-between items-center">
|
||||||
|
<div class="row items-center">
|
||||||
|
<div class="q-mr-xs">
|
||||||
|
Коллекция:
|
||||||
|
</div>
|
||||||
|
<div class="clickable" @click="showCollectionInfo">
|
||||||
|
{{ collection }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-ml-md">
|
||||||
|
{{ projectName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mx-md q-my-sm items-center">
|
||||||
|
<q-input ref="authorInput" v-model="author" maxlength="1000" class="bg-white" style="width: 300px;" label="Автор" stack-label outlined dense clearable />
|
||||||
|
<div class="q-mx-xs" />
|
||||||
|
<q-input v-model="series" maxlength="1000" class="bg-white" style="width: 200px;" label="Серия" stack-label outlined dense clearable />
|
||||||
|
<div class="q-mx-xs" />
|
||||||
|
<q-input v-model="title" maxlength="1000" class="bg-white" style="width: 200px;" label="Название" stack-label outlined dense clearable />
|
||||||
|
<div class="q-mx-xs" />
|
||||||
|
<q-input v-model="genre" maxlength="1000" class="bg-white" style="width: 200px;" label="Жанр" stack-label outlined dense clearable readonly />
|
||||||
|
<div class="q-mx-xs" />
|
||||||
|
<q-input v-model="lang" maxlength="1000" class="bg-white" style="width: 80px;" label="Язык" stack-label outlined dense clearable readonly />
|
||||||
|
<div class="q-mx-xs" />
|
||||||
|
<q-btn round dense style="height: 20px" color="info" icon="la la-question" @click="showSearchHelp" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col fit" style="overflow: auto">
|
||||||
|
{{ config }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,18 +45,98 @@ const componentOptions = {
|
|||||||
components: {
|
components: {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
config() {
|
||||||
|
this.makeTitle();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
class Search {
|
class Search {
|
||||||
_options = componentOptions;
|
_options = componentOptions;
|
||||||
|
collection = '';
|
||||||
|
projectName = '';
|
||||||
|
|
||||||
|
//search fields
|
||||||
|
author = '';
|
||||||
|
series = '';
|
||||||
|
title = '';
|
||||||
|
genre = '';
|
||||||
|
lang = '';
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.commit = this.$store.commit;
|
this.commit = this.$store.commit;
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.api = this.$root.api;
|
||||||
|
|
||||||
|
this.$refs.authorInput.focus();
|
||||||
|
|
||||||
|
this.refresh();//no await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get config() {
|
||||||
|
return this.$store.state.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeTitle() {
|
||||||
|
const collection = this.config.dbConfig.inpxInfo.collection.split('\n');
|
||||||
|
this.collection = collection[0].trim();
|
||||||
|
|
||||||
|
this.projectName = `${this.config.name} v${this.config.version}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSearchHelp() {
|
||||||
|
this.$root.stdDialog.alert(`
|
||||||
|
<p>
|
||||||
|
Здесь должна быть подсказка<br>
|
||||||
|
</p>
|
||||||
|
`, 'Подсказка', {iconName: 'la la-info-circle'});
|
||||||
|
}
|
||||||
|
|
||||||
|
showCollectionInfo() {
|
||||||
|
this.$root.stdDialog.alert(`
|
||||||
|
<p>
|
||||||
|
Здесь должна быть информация о коллекции<br>
|
||||||
|
</p>
|
||||||
|
`, 'Статистика по коллекции', {iconName: 'la la-info-circle'});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTableData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
const newQuery = {
|
||||||
|
author: this.author,
|
||||||
|
series: this.series,
|
||||||
|
title: this.title,
|
||||||
|
genre: this.genre,
|
||||||
|
lang: this.lang,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.queryExecute = newQuery;
|
||||||
|
|
||||||
|
if (this.refreshing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.refreshing = true;
|
||||||
|
try {
|
||||||
|
while (this.queryExecute) {
|
||||||
|
const query = this.queryExecute;
|
||||||
|
this.queryExecute = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.searchResult = await this.api.search(query);
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTableData();//no await
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.refreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default vueComponent(Search);
|
export default vueComponent(Search);
|
||||||
@@ -35,4 +146,18 @@ export default vueComponent(Search);
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.root {
|
.root {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-panel {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 150%;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
color: blue;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
53
client/share/LockQueue.js
Normal file
53
client/share/LockQueue.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
class LockQueue {
|
||||||
|
constructor(queueSize) {
|
||||||
|
this.queueSize = queueSize;
|
||||||
|
this.freed = true;
|
||||||
|
this.waitingQueue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//async
|
||||||
|
get(take = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.freed) {
|
||||||
|
if (take)
|
||||||
|
this.freed = false;
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.waitingQueue.length < this.queueSize) {
|
||||||
|
this.waitingQueue.push({resolve, reject});
|
||||||
|
} else {
|
||||||
|
reject(new Error('Lock queue is too long'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ret() {
|
||||||
|
if (this.waitingQueue.length) {
|
||||||
|
this.waitingQueue.shift().resolve();
|
||||||
|
} else {
|
||||||
|
this.freed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//async
|
||||||
|
wait() {
|
||||||
|
return this.get(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
retAll() {
|
||||||
|
while (this.waitingQueue.length) {
|
||||||
|
this.waitingQueue.shift().resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errAll(error = 'rejected') {
|
||||||
|
while (this.waitingQueue.length) {
|
||||||
|
this.waitingQueue.shift().reject(new Error(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LockQueue;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// initial state
|
// initial state
|
||||||
const state = {
|
const state = {
|
||||||
apiError: null,
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
@@ -11,8 +11,8 @@ const actions = {};
|
|||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
const mutations = {
|
const mutations = {
|
||||||
setApiError(state, value) {
|
setConfig(state, value) {
|
||||||
state.apiError = value;
|
state.config = value;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user