Работа над проектом
This commit is contained in:
@@ -156,6 +156,16 @@ class Api {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGenreTree() {
|
||||||
|
const response = await this.request({action: 'get-genre-tree'});
|
||||||
|
|
||||||
|
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'});
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,8 @@
|
|||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<SelectGenreDialog v-model="selectGenreDialogVisible" v-model:genre="genre" :genre-tree="genreTree" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -169,6 +171,7 @@ import vueComponent from '../vueComponent.js';
|
|||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
import PageScroller from './PageScroller/PageScroller.vue';
|
import PageScroller from './PageScroller/PageScroller.vue';
|
||||||
|
import SelectGenreDialog from './SelectGenreDialog/SelectGenreDialog.vue';
|
||||||
import authorBooksStorage from './authorBooksStorage';
|
import authorBooksStorage from './authorBooksStorage';
|
||||||
import DivBtn from '../share/DivBtn.vue';
|
import DivBtn from '../share/DivBtn.vue';
|
||||||
import Dialog from '../share/Dialog.vue';
|
import Dialog from '../share/Dialog.vue';
|
||||||
@@ -180,6 +183,7 @@ import _ from 'lodash';
|
|||||||
const componentOptions = {
|
const componentOptions = {
|
||||||
components: {
|
components: {
|
||||||
PageScroller,
|
PageScroller,
|
||||||
|
SelectGenreDialog,
|
||||||
Dialog,
|
Dialog,
|
||||||
DivBtn
|
DivBtn
|
||||||
},
|
},
|
||||||
@@ -236,6 +240,8 @@ class Search {
|
|||||||
loadingMessage = '';
|
loadingMessage = '';
|
||||||
loadingMessage2 = '';
|
loadingMessage2 = '';
|
||||||
settingsDialogVisible = false;
|
settingsDialogVisible = false;
|
||||||
|
selectGenreDialogVisible = false;
|
||||||
|
|
||||||
page = 1;
|
page = 1;
|
||||||
pageCount = 1;
|
pageCount = 1;
|
||||||
|
|
||||||
@@ -262,6 +268,8 @@ class Search {
|
|||||||
totalFound = 0;
|
totalFound = 0;
|
||||||
bookRowsOnPage = 100;
|
bookRowsOnPage = 100;
|
||||||
inpxHash = '';
|
inpxHash = '';
|
||||||
|
genreTree = [];
|
||||||
|
genreTreeInpxHash = '';
|
||||||
|
|
||||||
limitOptions = [
|
limitOptions = [
|
||||||
{label: '10', value: 10},
|
{label: '10', value: 10},
|
||||||
@@ -338,7 +346,7 @@ class Search {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectGenre() {
|
selectGenre() {
|
||||||
this.$root.stdDialog.alert('Выбор жанра');
|
this.selectGenreDialogVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectLang() {
|
selectLang() {
|
||||||
@@ -455,9 +463,8 @@ class Search {
|
|||||||
try {
|
try {
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
const key = `${authorId}-${this.inpxHash}`;
|
|
||||||
|
|
||||||
if (this.abCacheEnabled) {
|
if (this.abCacheEnabled) {
|
||||||
|
const key = `${authorId}-${this.inpxHash}`;
|
||||||
const data = await authorBooksStorage.getData(key);
|
const data = await authorBooksStorage.getData(key);
|
||||||
if (data) {
|
if (data) {
|
||||||
result = JSON.parse(data);
|
result = JSON.parse(data);
|
||||||
@@ -516,6 +523,33 @@ class Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateGenreTreeIfNeeded() {
|
||||||
|
try {
|
||||||
|
if (this.genreTreeInpxHash !== this.inpxHash) {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (this.abCacheEnabled) {
|
||||||
|
const key = `genre-tree-${this.inpxHash}`;
|
||||||
|
const data = await authorBooksStorage.getData(key);
|
||||||
|
if (data) {
|
||||||
|
result = JSON.parse(data);
|
||||||
|
} else {
|
||||||
|
result = await this.api.getGenreTree();
|
||||||
|
|
||||||
|
await authorBooksStorage.setData(key, JSON.stringify(result));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = await this.api.getGenreTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.genreTree = result.genreTree;
|
||||||
|
this.genreTreeInpxHash = result.inpxHash;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.stdDialog.alert(e.message, 'Ошибка');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateTableData() {
|
async updateTableData() {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
@@ -591,6 +625,7 @@ class Search {
|
|||||||
this.inpxHash = result.inpxHash;
|
this.inpxHash = result.inpxHash;
|
||||||
|
|
||||||
this.searchResult = result;
|
this.searchResult = result;
|
||||||
|
await this.updateGenreTreeIfNeeded();
|
||||||
await this.updateTableData();
|
await this.updateTableData();
|
||||||
this.scrollToTop();
|
this.scrollToTop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
172
client/components/Search/SelectGenreDialog/SelectGenreDialog.vue
Normal file
172
client/components/Search/SelectGenreDialog/SelectGenreDialog.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible">
|
||||||
|
<template #header>
|
||||||
|
<div class="row items-center">
|
||||||
|
<div style="font-size: 130%">
|
||||||
|
Выбрать жанры
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="col column" style="height: 500px; min-width: 400px">
|
||||||
|
<div class="row items-center top-panel bg-grey-3">
|
||||||
|
<q-input ref="search" v-model="search" class="col" outlined dense bg-color="white" placeholder="Найти" clearable />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col fit tree">
|
||||||
|
<div v-show="nodes.length" class="checkbox-tick-all">
|
||||||
|
<q-checkbox v-model="tickAll" size="36px" label="Выбрать все" @update:model-value="makeTickAll" />
|
||||||
|
</div>
|
||||||
|
<q-tree
|
||||||
|
v-model:ticked="ticked"
|
||||||
|
v-model:expanded="expanded"
|
||||||
|
class="q-my-xs"
|
||||||
|
:nodes="nodes"
|
||||||
|
node-key="key"
|
||||||
|
tick-strategy="leaf"
|
||||||
|
selected-color="black"
|
||||||
|
:filter="search"
|
||||||
|
no-nodes-label="Жанров нет"
|
||||||
|
no-results-label="Ничего не найдено"
|
||||||
|
>
|
||||||
|
</q-tree>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">
|
||||||
|
OK
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
import vueComponent from '../../vueComponent.js';
|
||||||
|
|
||||||
|
import Dialog from '../../share/Dialog.vue';
|
||||||
|
|
||||||
|
const componentOptions = {
|
||||||
|
components: {
|
||||||
|
Dialog
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue(newValue) {
|
||||||
|
this.dialogVisible = newValue;
|
||||||
|
if (newValue)
|
||||||
|
this.init();//no await
|
||||||
|
},
|
||||||
|
dialogVisible(newValue) {
|
||||||
|
this.$emit('update:modelValue', newValue);
|
||||||
|
},
|
||||||
|
genre() {
|
||||||
|
this.updateTicked();
|
||||||
|
},
|
||||||
|
ticked() {
|
||||||
|
this.checkAllTicked();
|
||||||
|
this.updateGenre();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class GenreSelectDialog {
|
||||||
|
_options = componentOptions;
|
||||||
|
_props = {
|
||||||
|
modelValue: Boolean,
|
||||||
|
genre: {type: String, value: ''},
|
||||||
|
genreTree: Array,
|
||||||
|
};
|
||||||
|
|
||||||
|
dialogVisible = false;
|
||||||
|
|
||||||
|
search = '';
|
||||||
|
ticked = [];
|
||||||
|
expanded = [];
|
||||||
|
tickAll = false;
|
||||||
|
allKeys = [];
|
||||||
|
|
||||||
|
created() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
get nodes() {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
this.allKeys = [];
|
||||||
|
for (const section of this.genreTree) {
|
||||||
|
const rkey = `r-${section.name}`;
|
||||||
|
const sec = {label: section.name, key: rkey, children: []};
|
||||||
|
|
||||||
|
for (const g of section.value) {
|
||||||
|
sec.children.push({label: g.name, key: g.value});
|
||||||
|
this.allKeys.push(g.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeTickAll() {
|
||||||
|
if (this.tickAll) {
|
||||||
|
const newTicked = [];
|
||||||
|
for (const key of this.allKeys) {
|
||||||
|
newTicked.push(key);
|
||||||
|
}
|
||||||
|
this.ticked = newTicked;
|
||||||
|
} else {
|
||||||
|
this.ticked = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAllTicked() {
|
||||||
|
const ticked = new Set(this.ticked);
|
||||||
|
|
||||||
|
let newTickAll = !!(this.nodes.length);
|
||||||
|
for (const key of this.allKeys) {
|
||||||
|
if (!ticked.has(key)) {
|
||||||
|
newTickAll = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.tickAll = newTickAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTicked() {
|
||||||
|
this.ticked = this.genre.split(',').filter(s => s);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGenre() {
|
||||||
|
this.$emit('update:genre', this.ticked.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
okClick() {
|
||||||
|
this.dialogVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default vueComponent(GenreSelectDialog);
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.top-panel {
|
||||||
|
height: 50px;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
padding: 0 10px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
padding: 0px 10px 10px 10px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-tick-all {
|
||||||
|
border-bottom: 1px solid #bbbbbb;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
padding: 5px 5px 2px 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -31,7 +31,7 @@ import {QSelect} from 'quasar/src/components/select';
|
|||||||
//import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
//import {QPopupProxy} from 'quasar/src/components/popup-proxy';
|
||||||
import {QDialog} from 'quasar/src/components/dialog';
|
import {QDialog} from 'quasar/src/components/dialog';
|
||||||
//import {QChip} from 'quasar/src/components/chip';
|
//import {QChip} from 'quasar/src/components/chip';
|
||||||
//import {QTree} from 'quasar/src/components/tree';
|
import {QTree} from 'quasar/src/components/tree';
|
||||||
//import {QVirtualScroll} from 'quasar/src/components/virtual-scroll';
|
//import {QVirtualScroll} from 'quasar/src/components/virtual-scroll';
|
||||||
|
|
||||||
//import {QExpansionItem} from 'quasar/src/components/expansion-item';
|
//import {QExpansionItem} from 'quasar/src/components/expansion-item';
|
||||||
@@ -63,7 +63,7 @@ const components = {
|
|||||||
//QPopupProxy,
|
//QPopupProxy,
|
||||||
QDialog,
|
QDialog,
|
||||||
//QChip,
|
//QChip,
|
||||||
//QTree,
|
QTree,
|
||||||
//QExpansionItem,
|
//QExpansionItem,
|
||||||
//QVirtualScroll,
|
//QVirtualScroll,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ class WebSocketController {
|
|||||||
await this.search(req, ws); break;
|
await this.search(req, ws); break;
|
||||||
case 'get-book-list':
|
case 'get-book-list':
|
||||||
await this.getBookList(req, ws); break;
|
await this.getBookList(req, ws); break;
|
||||||
|
case 'get-genre-tree':
|
||||||
|
await this.getGenreTree(req, ws); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Action not found: ${req.action}`);
|
throw new Error(`Action not found: ${req.action}`);
|
||||||
@@ -133,6 +135,12 @@ class WebSocketController {
|
|||||||
|
|
||||||
this.send(result, req, ws);
|
this.send(result, req, ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGenreTree(req, ws) {
|
||||||
|
const result = await this.webWorker.getGenreTree();
|
||||||
|
|
||||||
|
this.send(result, req, ws);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebSocketController;
|
module.exports = WebSocketController;
|
||||||
|
|||||||
@@ -275,7 +275,10 @@ class DbCreator {
|
|||||||
let genre = rec.genre || emptyFieldValue;
|
let genre = rec.genre || emptyFieldValue;
|
||||||
genre = rec.genre.split(',');
|
genre = rec.genre.split(',');
|
||||||
|
|
||||||
for (const g of genre) {
|
for (let g of genre) {
|
||||||
|
if (!g)
|
||||||
|
g = emptyFieldValue;
|
||||||
|
|
||||||
let genreRec;
|
let genreRec;
|
||||||
if (genreMap.has(g)) {
|
if (genreMap.has(g)) {
|
||||||
const genreId = genreMap.get(g);
|
const genreId = genreMap.get(g);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const os = require('os');
|
const os = require('os');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
const WorkerState = require('./WorkerState');
|
const WorkerState = require('./WorkerState');
|
||||||
const { JembaDbThread } = require('jembadb');
|
const { JembaDbThread } = require('jembadb');
|
||||||
@@ -9,6 +10,7 @@ const DbSearcher = require('./DbSearcher');
|
|||||||
const ayncExit = new (require('./AsyncExit'))();
|
const ayncExit = new (require('./AsyncExit'))();
|
||||||
const log = new (require('./AppLogger'))().log;//singleton
|
const log = new (require('./AppLogger'))().log;//singleton
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
|
const genreTree = require('./genres');
|
||||||
|
|
||||||
//server states
|
//server states
|
||||||
const ssNormal = 'normal';
|
const ssNormal = 'normal';
|
||||||
@@ -203,6 +205,43 @@ class WebWorker {
|
|||||||
return await this.dbSearcher.getBookList(authorId);
|
return await this.dbSearcher.getBookList(authorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getGenreTree() {
|
||||||
|
this.checkMyState();
|
||||||
|
|
||||||
|
const config = await this.dbConfig();
|
||||||
|
|
||||||
|
let result;
|
||||||
|
const db = this.db;
|
||||||
|
if (!db.wwCache.genres) {
|
||||||
|
const genres = _.cloneDeep(genreTree);
|
||||||
|
const last = genres[genres.length - 1];
|
||||||
|
|
||||||
|
const genreValues = new Set();
|
||||||
|
for (const section of genres) {
|
||||||
|
for (const g of section.value)
|
||||||
|
genreValues.add(g.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//добавим к жанрам те, что нашлись при парсинге
|
||||||
|
const rows = await db.select({table: 'genre', map: `(r) => ({value: r.value})`});
|
||||||
|
for (const row of rows) {
|
||||||
|
if (!genreValues.has(row.value))
|
||||||
|
last.value.push({name: row.value, value: row.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
result = {
|
||||||
|
genreTree: genres,
|
||||||
|
inpxHash: (config.inpxHash ? config.inpxHash : ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.wwCache.genres = result;
|
||||||
|
} else {
|
||||||
|
result = db.wwCache.genres;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async logServerStats() {
|
async logServerStats() {
|
||||||
while (1) {// eslint-disable-line
|
while (1) {// eslint-disable-line
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"
|
module.exports = `
|
||||||
#---------- Список жанров fb2 ----------
|
#---------- Список жанров fb2 ----------
|
||||||
0.1 Фантастика
|
0.1 Фантастика
|
||||||
0.2 Детективы и Триллеры
|
0.2 Детективы и Триллеры
|
||||||
@@ -276,4 +276,4 @@
|
|||||||
0.22.251 tbg_secondary;Учебники и пособия для среднего и специального образования
|
0.22.251 tbg_secondary;Учебники и пособия для среднего и специального образования
|
||||||
0.22.252 tbg_higher;Учебники и пособия ВУЗов
|
0.22.252 tbg_higher;Учебники и пособия ВУЗов
|
||||||
#---------- 2021-07-22 11:35:50.469539----------
|
#---------- 2021-07-22 11:35:50.469539----------
|
||||||
"
|
`;
|
||||||
53
server/core/genres/index.js
Normal file
53
server/core/genres/index.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const genresText = require('./genresText.js');
|
||||||
|
const genres = [];
|
||||||
|
|
||||||
|
const sec2index = {};
|
||||||
|
const lines = genresText.split('\n').map(l => l.trim());
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
let other;//прочее в конец
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line || line[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const p = line.indexOf(' ');
|
||||||
|
const num = line.substring(0, p).trim().split('.');
|
||||||
|
if (num.length < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const section = `${num[0]}.${num[1]}`;
|
||||||
|
if (section == '0.0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let name = line.substring(p + 1).trim();
|
||||||
|
|
||||||
|
if (num.length < 3) {//раздел
|
||||||
|
if (section == '0.20') {//прочее
|
||||||
|
other = {name, value: []};
|
||||||
|
} else {
|
||||||
|
if (sec2index[section] === undefined) {
|
||||||
|
if (!genres[index])
|
||||||
|
genres[index] = {name, value: []};
|
||||||
|
sec2index[section] = index;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {//подраздел
|
||||||
|
const n = name.split(';').map(l => l.trim());
|
||||||
|
|
||||||
|
if (section == '0.20') {//прочее
|
||||||
|
other.value.push({name: n[1], value: n[0]});
|
||||||
|
} else {
|
||||||
|
const i = sec2index[section];
|
||||||
|
if (i !== undefined) {
|
||||||
|
genres[i].value.push({name: n[1], value: n[0]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other)
|
||||||
|
genres.push(other);
|
||||||
|
|
||||||
|
module.exports = genres;
|
||||||
Reference in New Issue
Block a user