Работа над проектом
This commit is contained in:
@@ -54,6 +54,16 @@ class Api {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBookList(authorId) {
|
||||||
|
const response = await this.request({action: 'get-book-list', authorId});
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
async getConfig() {
|
async getConfig() {
|
||||||
const response = await this.request({action: 'get-config'});
|
const response = await this.request({action: 'get-config'});
|
||||||
|
|
||||||
|
|||||||
@@ -83,23 +83,30 @@
|
|||||||
|
|
||||||
<!-- Формирование списка ------------------------------------------------------------------------>
|
<!-- Формирование списка ------------------------------------------------------------------------>
|
||||||
<div v-for="item in tableData" :key="item.key" class="column" :class="{'odd-author': item.num % 2}" style="font-size: 120%">
|
<div v-for="item in tableData" :key="item.key" class="column" :class="{'odd-author': item.num % 2}" style="font-size: 120%">
|
||||||
<div class="row items-center q-ml-md q-my-xs no-wrap">
|
<div class="row items-center q-ml-md q-mr-xs no-wrap">
|
||||||
<div class="clickable q-mr-sm q-pa-xs">
|
<div style="width: 30px">
|
||||||
<div v-if="!isExpanded(item)" @click="expandAuthor(item, true)">
|
<DivBtn v-if="tableData.length > 1" :icon-size="24" icon="la la-check-circle" @click="selectAuthor(item.author)">
|
||||||
<q-icon name="la la-plus-square" size="24px" />
|
<q-tooltip :delay="1500" anchor="bottom right" content-style="font-size: 80%">
|
||||||
</div>
|
Только этот автор
|
||||||
<div v-else @click="expandAuthor(item, false)">
|
</q-tooltip>
|
||||||
<q-icon name="la la-minus-square" size="24px" />
|
</DivBtn>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clickable" style="font-weight: bold" @click="authorClick(item.author)">
|
<div class="row items-center clickable q-mr-sm q-pa-xs" @click="expandAuthor(item)">
|
||||||
{{ item.name }}
|
<div v-if="!isExpanded(item)">
|
||||||
|
<q-icon name="la la-plus-square" size="28px" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<q-icon name="la la-minus-square" size="28px" />
|
||||||
|
</div>
|
||||||
|
<div class="q-ml-xs" style="font-weight: bold">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isExpanded(item) && item.books">
|
<div v-if="isExpanded(item) && item.books">
|
||||||
{{ item.books[0] }}
|
{{ item.books }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Формирование списка конец ------------------------------------------------------------------>
|
<!-- Формирование списка конец ------------------------------------------------------------------>
|
||||||
@@ -118,6 +125,8 @@ import vueComponent from '../vueComponent.js';
|
|||||||
const { reactive } = require('@vue/reactivity');
|
const { reactive } = require('@vue/reactivity');
|
||||||
|
|
||||||
import PageScroller from './PageScroller/PageScroller.vue';
|
import PageScroller from './PageScroller/PageScroller.vue';
|
||||||
|
import DivBtn from '../share/DivBtn.vue';
|
||||||
|
|
||||||
import * as utils from '../../share/utils';
|
import * as utils from '../../share/utils';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@@ -125,6 +134,7 @@ import _ from 'lodash';
|
|||||||
const componentOptions = {
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
PageScroller,
|
PageScroller,
|
||||||
|
DivBtn
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
config() {
|
config() {
|
||||||
@@ -301,7 +311,7 @@ class Search {
|
|||||||
this.page = 1;
|
this.page = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
authorClick(author) {
|
selectAuthor(author) {
|
||||||
this.author = `=${author}`;
|
this.author = `=${author}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,11 +319,11 @@ class Search {
|
|||||||
return this.expanded.indexOf(item.author) >= 0;
|
return this.expanded.indexOf(item.author) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
expandAuthor(item, expand = true) {
|
expandAuthor(item) {
|
||||||
const expanded = _.cloneDeep(this.expanded);
|
const expanded = _.cloneDeep(this.expanded);
|
||||||
const author = item.author;
|
const author = item.author;
|
||||||
|
|
||||||
if (expand) {
|
if (!this.isExpanded(item)) {
|
||||||
if (expanded.indexOf(author) < 0) {
|
if (expanded.indexOf(author) < 0) {
|
||||||
expanded.push(author);
|
expanded.push(author);
|
||||||
|
|
||||||
@@ -334,26 +344,39 @@ class Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBooks() {
|
async loadBooks(authorId) {
|
||||||
|
let inSearch = true;
|
||||||
|
(async() => {
|
||||||
|
await utils.sleep(500);
|
||||||
|
if (inSearch)
|
||||||
|
this.loadingMessage = 'Загрузка списка книг...';
|
||||||
|
})();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.api.getBookList(authorId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
inSearch = false;
|
||||||
|
this.loadingMessage = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBooks(item) {
|
async getBooks(item) {
|
||||||
if (item.books)
|
if (item.books)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await utils.sleep(1000);
|
item.books = await this.loadBooks(item.key);
|
||||||
item.books = [{name: 'book1'}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTableData() {
|
async updateTableData() {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
const authors = this.searchResult.author;
|
|
||||||
if (authors.length == 1) {
|
|
||||||
this.expandAuthor(authors[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandedSet = new Set(this.expanded);
|
const expandedSet = new Set(this.expanded);
|
||||||
|
const authors = this.searchResult.author;
|
||||||
|
|
||||||
let num = 0;
|
let num = 0;
|
||||||
for (const rec of authors) {
|
for (const rec of authors) {
|
||||||
@@ -373,6 +396,10 @@ class Search {
|
|||||||
result.push(item);
|
result.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.length == 1 && !this.isExpanded(result[0])) {
|
||||||
|
this.expandAuthor(result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
this.tableData = result;
|
this.tableData = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
client/components/share/DivBtn.vue
Normal file
54
client/components/share/DivBtn.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="btn" class="clickable row justify-center items-center">
|
||||||
|
<q-icon :name="icon" :size="`${iconSize}px`" />
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import vueComponent from '../vueComponent.js';
|
||||||
|
|
||||||
|
//import * as utils from '../../share/utils';
|
||||||
|
|
||||||
|
const componentOptions = {
|
||||||
|
watch: {
|
||||||
|
size() {
|
||||||
|
this.updateSizes();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class DivBtn {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
size: { type: Number, default: 24 },
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
iconSize: { type: Number, default: 14 },
|
||||||
|
round: { type: Boolean },
|
||||||
|
};
|
||||||
|
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.updateSizes();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSizes() {
|
||||||
|
this.$refs.btn.style.width = `${this.size}px`;
|
||||||
|
this.$refs.btn.style.height = `${this.size}px`;
|
||||||
|
if (this.round)
|
||||||
|
this.$refs.btn.style.borderRadius = `${this.size}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default vueComponent(DivBtn);
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.clickable-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -21,7 +21,7 @@ import {QIcon} from 'quasar/src/components/icon';
|
|||||||
//import {QSeparator} from 'quasar/src/components/separator';
|
//import {QSeparator} from 'quasar/src/components/separator';
|
||||||
//import {QList} from 'quasar/src/components/item';
|
//import {QList} from 'quasar/src/components/item';
|
||||||
//import {QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
|
//import {QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
|
||||||
//import {QTooltip} from 'quasar/src/components/tooltip';
|
import {QTooltip} from 'quasar/src/components/tooltip';
|
||||||
import {QSpinner} from 'quasar/src/components/spinner';
|
import {QSpinner} from 'quasar/src/components/spinner';
|
||||||
//import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
//import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
|
||||||
//import {QCheckbox} from 'quasar/src/components/checkbox';
|
//import {QCheckbox} from 'quasar/src/components/checkbox';
|
||||||
@@ -52,7 +52,7 @@ const components = {
|
|||||||
//QSeparator,
|
//QSeparator,
|
||||||
//QList,
|
//QList,
|
||||||
//QItem, QItemSection, QItemLabel,
|
//QItem, QItemSection, QItemLabel,
|
||||||
//QTooltip,
|
QTooltip,
|
||||||
QSpinner,
|
QSpinner,
|
||||||
//QTable, QTh, QTr, QTd,
|
//QTable, QTh, QTr, QTd,
|
||||||
//QCheckbox,
|
//QCheckbox,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const _ = require('lodash');
|
|||||||
const WorkerState = require('../core/WorkerState');//singleton
|
const WorkerState = require('../core/WorkerState');//singleton
|
||||||
const WebWorker = require('../core/WebWorker');//singleton
|
const WebWorker = require('../core/WebWorker');//singleton
|
||||||
const log = new (require('../core/AppLogger'))().log;//singleton
|
const log = new (require('../core/AppLogger'))().log;//singleton
|
||||||
//const utils = require('../core/utils');
|
const utils = require('../core/utils');
|
||||||
|
|
||||||
const cleanPeriod = 1*60*1000;//1 минута
|
const cleanPeriod = 1*60*1000;//1 минута
|
||||||
const closeSocketOnIdle = 5*60*1000;//5 минут
|
const closeSocketOnIdle = 5*60*1000;//5 минут
|
||||||
@@ -68,6 +68,8 @@ class WebSocketController {
|
|||||||
await this.getWorkerState(req, ws); break;
|
await this.getWorkerState(req, ws); break;
|
||||||
case 'search':
|
case 'search':
|
||||||
await this.search(req, ws); break;
|
await this.search(req, ws); break;
|
||||||
|
case 'get-book-list':
|
||||||
|
await this.getBookList(req, ws); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Action not found: ${req.action}`);
|
throw new Error(`Action not found: ${req.action}`);
|
||||||
@@ -122,6 +124,15 @@ class WebSocketController {
|
|||||||
|
|
||||||
this.send(result, req, ws);
|
this.send(result, req, ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBookList(req, ws) {
|
||||||
|
if (!utils.hasProp(req, 'authorId'))
|
||||||
|
throw new Error(`authorId is empty`);
|
||||||
|
|
||||||
|
const result = await this.webWorker.getBookList(req.authorId);
|
||||||
|
|
||||||
|
this.send(result, req, ws);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebSocketController;
|
module.exports = WebSocketController;
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class DbSearcher {
|
|||||||
//сначала попробуем найти в кеше
|
//сначала попробуем найти в кеше
|
||||||
const q = query;
|
const q = query;
|
||||||
const keyArr = [q.author, q.series, q.title, q.genre, q.lang];
|
const keyArr = [q.author, q.series, q.title, q.genre, q.lang];
|
||||||
const keyStr = keyArr.join('');
|
const keyStr = `query-${keyArr.join('')}`;
|
||||||
|
|
||||||
if (!keyStr) {//пустой запрос
|
if (!keyStr) {//пустой запрос
|
||||||
if (db.searchCache.authorIdsAll)
|
if (db.searchCache.authorIdsAll)
|
||||||
@@ -262,6 +262,42 @@ class DbSearcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBookList(authorId) {
|
||||||
|
if (this.closed)
|
||||||
|
throw new Error('DbSearcher closed');
|
||||||
|
|
||||||
|
this.searchFlag++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = this.db;
|
||||||
|
|
||||||
|
//выборка автора по authorId
|
||||||
|
const rows = await db.select({
|
||||||
|
table: 'author',
|
||||||
|
map: `(r) => ({author: r.author, bookId: r.bookId})`,
|
||||||
|
where: `@@id(${db.esc(authorId)})`
|
||||||
|
});
|
||||||
|
|
||||||
|
let author = '';
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
if (rows.length) {
|
||||||
|
author = rows[0].author;
|
||||||
|
|
||||||
|
//выборка книг по bookId
|
||||||
|
result = await db.select({
|
||||||
|
table: 'book',
|
||||||
|
//map: `(r) => ({})`,
|
||||||
|
where: `@@id(${db.esc(rows[0].bookId)})`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {author, books: result};
|
||||||
|
} finally {
|
||||||
|
this.searchFlag--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async periodicCleanCache() {
|
async periodicCleanCache() {
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
const cleanInterval = 5*1000;//this.config.cacheCleanInterval*60*1000;
|
const cleanInterval = 5*1000;//this.config.cacheCleanInterval*60*1000;
|
||||||
|
|||||||
@@ -195,7 +195,13 @@ class WebWorker {
|
|||||||
totalFound: result.totalFound,
|
totalFound: result.totalFound,
|
||||||
inpxHash: (config.inpxHash ? config.inpxHash : ''),
|
inpxHash: (config.inpxHash ? config.inpxHash : ''),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBookList(authorId) {
|
||||||
|
this.checkMyState();
|
||||||
|
|
||||||
|
return await this.dbSearcher.getBookList(authorId);
|
||||||
|
}
|
||||||
|
|
||||||
async logServerStats() {
|
async logServerStats() {
|
||||||
while (1) {// eslint-disable-line
|
while (1) {// eslint-disable-line
|
||||||
|
|||||||
Reference in New Issue
Block a user