Преглед изворни кода

Работа над разделом "Серии"

Book Pauk пре 2 година
родитељ
комит
cc5d5167a3

+ 20 - 16
client/components/Search/AuthorList/AuthorList.vue

@@ -75,16 +75,16 @@
                             </div>
 
                             <div
-                                v-if="book.allBooks && book.allBooks.length != book.seriesBooks.length"
+                                v-if="book.allBooksLoaded && book.allBooksLoaded.length != book.seriesBooks.length"
                                 class="row items-center q-my-sm"
                                 style="margin-left: 100px"
                             >
-                                <div v-if="book.showAllBooks && book.showMore" class="row items-center q-mr-md">
+                                <div v-if="book.showAllBooks && book.showMoreAll" class="row items-center q-mr-md">
                                     <i class="las la-ellipsis-h text-red" style="font-size: 40px"></i>
-                                    <q-btn class="q-ml-md" color="red" style="width: 200px" dense rounded no-caps @click="showMoreSeries(book)">
+                                    <q-btn class="q-ml-md" color="red" style="width: 200px" dense rounded no-caps @click="showMoreAll(book)">
                                         Показать еще (~{{ showMoreCount }})
                                     </q-btn>
-                                    <q-btn class="q-ml-sm" color="red" style="width: 200px" dense rounded no-caps @click="showMoreSeries(book, true)">
+                                    <q-btn class="q-ml-sm" color="red" style="width: 200px" dense rounded no-caps @click="showMoreAll(book, true)">
                                         Показать все ({{ (book.allBooksLoaded && book.allBooksLoaded.length) || '?' }})
                                     </q-btn>
                                 </div>
@@ -162,6 +162,18 @@ class AuthorList extends BaseList {
         return `+${this.hiddenCount} результат${utils.wordEnding(this.hiddenCount)} скрыт${utils.wordEnding(this.hiddenCount, 2)}`;
     }
 
+    get foundCountMessage() {
+        return `Найден${utils.wordEnding(this.list.totalFound, 2)} ${this.list.totalFound} автор${utils.wordEnding(this.list.totalFound)}`;
+    }    
+
+    isFoundSeriesBook(seriesItem, seriesBook) {
+        if (!seriesItem.booksSet) {
+            seriesItem.booksSet = new Set(seriesItem.seriesBooks.map(b => b.id));
+        }
+
+        return seriesItem.booksSet.has(seriesBook.id);
+    }
+
     getBookCount(item) {
         let result = '';
         if (!this.showCounts || item.count === undefined)
@@ -183,14 +195,6 @@ class AuthorList extends BaseList {
         return `(${result})`;
     }
 
-    isFoundSeriesBook(seriesItem, seriesBook) {
-        if (!seriesItem.booksSet) {
-            seriesItem.booksSet = new Set(seriesItem.seriesBooks.map(b => b.id));
-        }
-
-        return seriesItem.booksSet.has(seriesBook.id);
-    }
-
     async expandAuthor(item) {
         const expanded = _.cloneDeep(this.expandedAuthor);
         const key = item.author;
@@ -263,13 +267,13 @@ class AuthorList extends BaseList {
                     if (index === undefined) {
                         index = books.length;
                         books.push(reactive({
-                            key: `${item.author}-${book.series}`,
+                            key: book.series,
                             type: 'series',
                             series: book.series,
-                            allBooksLoaded: null,
-                            allBooks: null,
+                            allBooksLoaded: false,
+                            allBooks: false,
                             showAllBooks: false,
-                            showMore: false,
+                            showMoreAll: false,
 
                             seriesBooks: [],
                         }));

+ 17 - 17
client/components/Search/BaseList.js

@@ -296,20 +296,21 @@ export default class BaseList {
     }
 
     async getSeriesBooks(seriesItem) {
-        //асинхронно подгружаем все книги серии, блокируем повторный вызов
-        if (seriesItem.allBooksLoaded === null) {
-            seriesItem.allBooksLoaded = undefined;
-            (async() => {
-                seriesItem.allBooksLoaded = await this.loadSeriesBooks(seriesItem.series);
-
-                if (seriesItem.allBooksLoaded) {
-                    seriesItem.allBooksLoaded = seriesItem.allBooksLoaded.filter(book => (this.showDeleted || !book.del));
-                    this.sortSeriesBooks(seriesItem.allBooksLoaded);
-                    this.showMoreSeries(seriesItem);
-                } else {
-                    seriesItem.allBooksLoaded = null;
-                }
-            })();
+        //блокируем повторный вызов
+        if (seriesItem.seriesBookLoading)
+            return;
+        seriesItem.seriesBookLoading = true;
+
+        try {
+            seriesItem.allBooksLoaded = await this.loadSeriesBooks(seriesItem.series);
+
+            if (seriesItem.allBooksLoaded) {
+                seriesItem.allBooksLoaded = seriesItem.allBooksLoaded.filter(book => (this.showDeleted || !book.del));
+                this.sortSeriesBooks(seriesItem.allBooksLoaded);
+                this.showMoreAll(seriesItem);
+            }
+        } finally {
+            seriesItem.seriesBookLoading = false;
         }
     }
 
@@ -423,7 +424,7 @@ export default class BaseList {
         }
     }
 
-    showMoreSeries(seriesItem, all = false) {
+    showMoreAll(seriesItem, all = false) {
         if (seriesItem.allBooksLoaded) {
             const currentLen = (seriesItem.allBooks ? seriesItem.allBooks.length : 0);
             let books;
@@ -433,7 +434,7 @@ export default class BaseList {
                 books = seriesItem.allBooksLoaded.slice(0, currentLen + this.showMoreCount);
             }
 
-            seriesItem.showMore = (books.length < seriesItem.allBooksLoaded.length);
+            seriesItem.showMoreAll = (books.length < seriesItem.allBooksLoaded.length);
             seriesItem.allBooks = books;
         }
     }
@@ -446,5 +447,4 @@ export default class BaseList {
             return (dserno ? dserno : (dtitle ? dtitle : dext));
         });
     }
-
 }

+ 7 - 7
client/components/Search/Search.vue

@@ -101,7 +101,7 @@
                     <div class="q-mx-xs" />
                     <div class="row items-center q-mt-xs">
                         <div v-show="list.queryFound > 0">
-                            {{ foundAuthorsMessage }}
+                            {{ foundCountMessage }}
                         </div>
                         <div v-show="list.queryFound == 0">
                             Ничего не найдено
@@ -115,7 +115,7 @@
             </div>
 
             <!-- Формирование списка ------------------------------------------------------------------------>
-            <component :is="selectedListComponent" v-if="selectedListComponent" :list="list" :search="search" :genre-map="genreMap" @list-event="listEvent" />
+            <component :is="selectedListComponent" v-if="selectedListComponent" ref="list" :list="list" :search="search" :genre-map="genreMap" @list-event="listEvent" />
             <!-- Формирование списка конец ------------------------------------------------------------------>
 
             <div class="row justify-center">
@@ -251,8 +251,10 @@ const componentOptions = {
             handler(newValue) {
                 this.updateGenreTreeIfNeeded();
 
-                if (this.prevList.totalFound != newValue.totalFound)
+                if (this.prevList.totalFound != newValue.totalFound) {
                     this.updatePageCount();
+                    this.foundCountMessage = this.$refs.list.foundCountMessage;
+                }
 
                 this.prevList = _.cloneDeep(newValue);
             },
@@ -278,6 +280,8 @@ class Search {
     collection = '';
     projectName = '';
 
+    foundCountMessage = '';
+
     settingsDialogVisible = false;
     selectGenreDialogVisible = false;
     selectLangDialogVisible = false;
@@ -667,10 +671,6 @@ class Search {
         this.lastScrollTop = 0;
     }
 
-    get foundAuthorsMessage() {
-        return `Найден${utils.wordEnding(this.list.totalFound, 2)} ${this.list.totalFound} автор${utils.wordEnding(this.list.totalFound)}`;
-    }
-
     updatePageCount() {
         const prevPageCount = this.pageCount;
 

+ 127 - 94
client/components/Search/SeriesList/SeriesList.vue

@@ -8,9 +8,9 @@
         <!-- Формирование списка ------------------------------------------------------------------------>
         <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-mr-xs no-wrap">
-                <div class="row items-center clickable2 q-py-xs no-wrap" @click="expandAuthor(item)">
+                <div class="row items-center clickable2 q-py-xs no-wrap" @click="expandSeries(item)">
                     <div style="min-width: 30px">
-                        <div v-if="!isExpandedAuthor(item)">
+                        <div v-if="!isExpandedSeries(item)">
                             <q-icon name="la la-plus-square" size="28px" />
                         </div>
                         <div v-else>
@@ -19,8 +19,8 @@
                     </div>
                 </div>
 
-                <div class="clickable2 q-ml-xs q-py-sm text-green-10 text-bold" @click="selectAuthor(item.author)">
-                    {{ item.name }}                            
+                <div class="clickable2 q-ml-xs q-py-sm text-bold" @click="selectSeries(item.series)">
+                    Серия: {{ item.series }}
                 </div>
 
                 <div class="q-ml-sm text-bold" style="color: #555">
@@ -35,82 +35,57 @@
                 </div>
             </div>
 
-            <div v-if="isExpandedAuthor(item) && item.books">
-                <div v-for="book in item.books" :key="book.key" class="book-row column">
-                    <!-- серия книг -->
-                    <div v-if="book.type == 'series'" class="column">
-                        <div class="row items-center q-mr-xs no-wrap text-grey-9">
-                            <div class="row items-center clickable2 q-py-xs no-wrap" @click="expandSeries(book)">
-                                <div style="min-width: 30px">
-                                    <div v-if="!isExpandedSeries(book)">
-                                        <q-icon name="la la-plus-square" size="28px" />
-                                    </div>
-                                    <div v-else>
-                                        <q-icon name="la la-minus-square" size="28px" />
-                                    </div>
-                                </div>
-                            </div>
-
-                            <div class="clickable2 q-ml-xs q-py-sm text-bold" @click="selectSeries(book.series)">
-                                Серия: {{ book.series }}
-                            </div>
-                        </div>
+            <div v-if="isExpandedSeries(item) && item.books">
+                <div v-if="item.showAllBooks" class="book-row column">
+                    <BookView
+                        v-for="seriesBook in item.allBooks" :key="seriesBook.id"
+                        :book="seriesBook" :genre-map="genreMap"
+                        show-author
+                        :show-read-link="showReadLink"
+                        :title-color="isFoundSeriesBook(item, seriesBook) ? 'text-blue-10' : 'text-red'"
+                        @book-event="bookEvent"
+                    />
+                </div>
+                <div v-else class="book-row column">
+                    <BookView 
+                        v-for="seriesBook in item.books" :key="seriesBook.key"
+                        show-author
+                        :book="seriesBook" :genre-map="genreMap" :show-read-link="showReadLink" @book-event="bookEvent"
+                    />
+                </div>
 
-                        <div v-if="isExpandedSeries(book) && book.seriesBooks">
-                            <div v-if="book.showAllBooks" class="book-row column">
-                                <BookView
-                                    v-for="seriesBook in book.allBooks" :key="seriesBook.id"
-                                    :book="seriesBook" :genre-map="genreMap"
-                                    show-author
-                                    :show-read-link="showReadLink"
-                                    :title-color="isFoundSeriesBook(book, seriesBook) ? 'text-blue-10' : 'text-red'"
-                                    @book-event="bookEvent"
-                                />
-                            </div>
-                            <div v-else class="book-row column">
-                                <BookView 
-                                    v-for="seriesBook in book.seriesBooks" :key="seriesBook.key"
-                                    :book="seriesBook" :genre-map="genreMap" :show-read-link="showReadLink" @book-event="bookEvent"
-                                />
-                            </div>
-
-                            <div
-                                v-if="book.allBooks && book.allBooks.length != book.seriesBooks.length"
-                                class="row items-center q-my-sm"
-                                style="margin-left: 100px"
-                            >
-                                <div v-if="book.showAllBooks && book.showMore" class="row items-center q-mr-md">
-                                    <i class="las la-ellipsis-h text-red" style="font-size: 40px"></i>
-                                    <q-btn class="q-ml-md" color="red" style="width: 200px" dense rounded no-caps @click="showMoreSeries(book)">
-                                        Показать еще (~{{ showMoreCount }})
-                                    </q-btn>
-                                    <q-btn class="q-ml-sm" color="red" style="width: 200px" dense rounded no-caps @click="showMoreSeries(book, true)">
-                                        Показать все ({{ (book.allBooksLoaded && book.allBooksLoaded.length) || '?' }})
-                                    </q-btn>
-                                </div>
-
-                                <div v-if="book.showAllBooks" class="row items-center clickable2 text-blue-10" @click="book.showAllBooks = false">
-                                    <q-icon class="la la-long-arrow-alt-up" size="28px" />
-                                    Только найденные книги
-                                </div>
-                                <div v-else class="row items-center clickable2 text-red" @click="book.showAllBooks = true">
-                                    <q-icon class="la la-long-arrow-alt-down" size="28px" />
-                                    Все книги серии
-                                </div>
-                            </div>
-                        </div>
+                <div
+                    v-if="item.allBooksLoaded && item.allBooksLoaded.length != item.booksLoaded.length"
+                    class="row items-center q-my-sm"
+                    style="margin-left: 100px"
+                >
+                    <div v-if="item.showAllBooks && item.showMoreAll" class="row items-center q-mr-md">
+                        <i class="las la-ellipsis-h text-red" style="font-size: 40px"></i>
+                        <q-btn class="q-ml-md" color="red" style="width: 200px" dense rounded no-caps @click="showMoreAll(item)">
+                            Показать еще (~{{ showMoreCount }})
+                        </q-btn>
+                        <q-btn class="q-ml-sm" color="red" style="width: 200px" dense rounded no-caps @click="showMoreAll(item, true)">
+                            Показать все ({{ (item.allBooksLoaded && item.allBooksLoaded.length) || '?' }})
+                        </q-btn>
                     </div>
-                    <!-- книга без серии -->
-                    <BookView v-else :book="book" :genre-map="genreMap" :show-read-link="showReadLink" @book-event="bookEvent" />
-                </div>
 
-                <div v-if="isExpandedAuthor(item) && item.books && !item.books.length" class="book-row row items-center">
-                    <q-icon class="la la-meh q-mr-xs" size="24px" />
-                    По каждому из заданных критериев у этой серии были найдены разные книги, но нет полного совпадения                    
+                    <div v-if="item.showAllBooks" class="row items-center clickable2 text-blue-10" @click="item.showAllBooks = false">
+                        <q-icon class="la la-long-arrow-alt-up" size="28px" />
+                        Только найденные книги
+                    </div>
+                    <div v-else class="row items-center clickable2 text-red" @click="item.showAllBooks = true">
+                        <q-icon class="la la-long-arrow-alt-down" size="28px" />
+                        Все книги серии
+                    </div>
                 </div>
             </div>
 
-            <div v-if="isExpandedAuthor(item) && item.showMore" class="row items-center book-row q-mb-sm">
+            <div v-if="isExpandedSeries(item) && item.books && !item.books.length" class="book-row row items-center">
+                <q-icon class="la la-meh q-mr-xs" size="24px" />
+                По каждому из заданных критериев у этой серии были найдены разные книги, но нет полного совпадения                    
+            </div>
+
+            <div v-if="isExpandedSeries(item) && item.showMore" class="row items-center book-row q-mb-sm">
                 <i class="las la-ellipsis-h text-blue-10" style="font-size: 40px"></i>
                 <q-btn class="q-ml-md" color="primary" style="width: 200px" dense rounded no-caps @click="showMore(item)">
                     Показать еще (~{{ showMoreCount }})
@@ -141,6 +116,67 @@ import * as utils from '../../../share/utils';
 import _ from 'lodash';
 
 class SeriesList extends BaseList {
+    get foundCountMessage() {
+        return `Найден${utils.wordEnding(this.list.totalFound, 4)} ${this.list.totalFound} сери${utils.wordEnding(this.list.totalFound, 1)}`;
+    }
+
+    isFoundSeriesBook(seriesItem, seriesBook) {
+        if (!seriesItem.booksSet) {
+            seriesItem.booksSet = new Set(seriesItem.books.map(b => b.id));
+        }
+
+        return seriesItem.booksSet.has(seriesBook.id);
+    }
+
+    getBookCount(item) {
+        let result = '';
+        if (!this.showCounts || item.count === undefined)
+            return result;
+
+        if (item.books) {
+            result = `${item.books.length}/${item.count}`;
+        } else 
+            result = `#/${item.count}`;
+
+        return `(${result})`;
+    }
+
+    async getSeriesBooks(seriesItem) {
+        if (seriesItem.count > this.maxItemCount) {
+            seriesItem.bookLoading = true;
+            await this.$nextTick();
+        }
+
+        try {
+            await super.getSeriesBooks(seriesItem);
+
+            if (seriesItem.allBooksLoaded) {
+                const prepareBook = (book) => {
+                    return Object.assign(
+                        {
+                            key: book.id,
+                            type: 'book',
+                        },
+                        book
+                    );
+                };
+
+                const filtered = this.filterBooks(seriesItem.allBooksLoaded);
+
+                //объединение по сериям
+                const books = [];
+                for (const book of filtered) {
+                    books.push(prepareBook(book));
+                }
+
+                seriesItem.booksLoaded = books;
+                this.showMore(seriesItem);
+            }
+        } finally {
+            seriesItem.bookLoading = false;
+        }
+    }
+
     async updateTableData() {
         let result = [];
 
@@ -148,47 +184,44 @@ class SeriesList extends BaseList {
         const series = this.searchResult.series;
         if (!series)
             return;
-/*
-        let num = 0;
-        this.hiddenCount = 0;
-        for (const rec of authors) {
-            this.cachedAuthors[rec.author] = rec;
 
+        let num = 0;
+        for (const rec of series) {
             const count = (this.showDeleted ? rec.bookCount + rec.bookDelCount : rec.bookCount);
-            if (!count) {
-                this.hiddenCount++;
-                continue;
-            }
 
             const item = reactive({
-                key: rec.id,
+                key: rec.series,
+                series: rec.series,
                 num,
-                author: rec.author,
-                name: rec.author.replace(/,/g, ', '),
                 count,
+                bookLoading: false,
+
+                allBooksLoaded: false,
+                allBooks: false,
+                showAllBooks: false,
+                showMoreAll: false,
+
                 booksLoaded: false,
                 books: false,
-                bookLoading: false,
                 showMore: false,
             });
             num++;
 
-            if (expandedSet.has(item.author)) {
-                if (authors.length > 1 || item.count > this.maxItemCount)
-                    this.getBooks(item);//no await
+            if (expandedSet.has(item.series)) {
+                if (series.length > 1 || item.count > this.maxItemCount)
+                    this.getSeriesBooks(item);//no await
                 else 
-                    await this.getBooks(item);
+                    await this.getSeriesBooks(item);
             }
 
             result.push(item);
         }
 
-        if (result.length == 1 && !this.isExpandedAuthor(result[0])) {
-            this.expandAuthor(result[0]);
+        if (result.length == 1 && !this.isExpandedSeries(result[0])) {
+            this.expandSeries(result[0]);
         }
 
         this.tableData = result;
-*/        
     }
 
     async refresh() {

+ 2 - 1
client/share/utils.js

@@ -38,7 +38,8 @@ export function wordEnding(num, type = 0) {
         ['ов', '', 'а', 'а', 'а', 'ов', 'ов', 'ов', 'ов', 'ов'],
         ['й', 'я', 'и', 'и', 'и', 'й', 'й', 'й', 'й', 'й'],
         ['о', '', 'о', 'о', 'о', 'о', 'о', 'о', 'о', 'о'],
-        ['ий', 'ие', 'ия', 'ия', 'ия', 'ий', 'ий', 'ий', 'ий', 'ий']
+        ['ий', 'ие', 'ия', 'ия', 'ия', 'ий', 'ий', 'ий', 'ий', 'ий'],
+        ['о', 'а', 'о', 'о', 'о', 'о', 'о', 'о', 'о', 'о'],
     ];
     const deci = num % 100;
     if (deci > 10 && deci < 20) {

+ 1 - 1
server/controllers/WebSocketController.js

@@ -163,7 +163,7 @@ class WebSocketController {
     }
 
     async getSeriesBookList(req, ws) {
-        const result = await this.webWorker.getSeriesBookList(req.series, req.seriesId);
+        const result = await this.webWorker.getSeriesBookList(req.series);
 
         this.send(result, req, ws);
     }

+ 6 - 5
server/core/DbSearcher.js

@@ -483,11 +483,11 @@ class DbSearcher {
         }
     }
 
-    async getSeriesBookList(series, seriesId) {
+    async getSeriesBookList(series) {
         if (this.closed)
             throw new Error('DbSearcher closed');
 
-        if (!series && !seriesId)
+        if (!series)
             return {books: ''};
 
         this.searchFlag++;
@@ -500,15 +500,16 @@ class DbSearcher {
             //выборка серии по названию серии
             let rows = await db.select({
                 table: 'series',
-                where: `@@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)})`
+                rawResult: true,
+                where: `return Array.from(@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)}))`
             });
 
             let books;
-            if (rows.length) {
+            if (rows.length && rows[0].rawResult.length) {
                 //выборка книг серии
                 rows = await db.select({
                     table: 'series_book',
-                    where: `@@id(${rows[0].id})`
+                    where: `@@id(${rows[0].rawResult[0]})`
                 });
 
                 if (rows.length)

+ 2 - 2
server/core/WebWorker.js

@@ -277,10 +277,10 @@ class WebWorker {
         return await this.dbSearcher.getAuthorBookList(authorId);
     }
 
-    async getSeriesBookList(series, seriesId) {
+    async getSeriesBookList(series) {
         this.checkMyState();
 
-        return await this.dbSearcher.getSeriesBookList(series, seriesId);
+        return await this.dbSearcher.getSeriesBookList(series);
     }
 
     async getGenreTree() {