Forráskód Böngészése

Работа над проектом

Book Pauk 2 éve
szülő
commit
40d6b9c807

+ 117 - 73
client/components/Search/Search.vue

@@ -1,85 +1,108 @@
 <template>
-    <div class="root column fit">
-        <div class="col fit" style="position: relative">
-            <div v-show="loadingVisible" class="fit row justify-center items-center" style="position: absolute; background-color: rgba(0, 0, 0, 0.2); z-index: 1">
-                <div class="bg-white row justify-center items-center" style="width: 180px; height: 50px; border-radius: 10px; box-shadow: 2px 2px 10px #333333">
-                    <q-spinner color="primary" size="2em" />
-                    <div class="q-ml-sm">
-                        Поиск авторов...
-                    </div>
+    <div class="root column fit" style="position: relative">
+        <div v-show="loadingMessage" class="fit row justify-center items-center" style="position: absolute; background-color: rgba(0, 0, 0, 0.2); z-index: 1">
+            <div class="bg-white row justify-center items-center" style="width: 180px; height: 50px; border-radius: 10px; box-shadow: 2px 2px 10px #333333">
+                <q-spinner color="primary" size="2em" />
+                <div class="q-ml-sm">
+                    {{ loadingMessage }}
                 </div>
             </div>
+        </div>
 
-            <div ref="scroller" class="col fit column no-wrap" style="overflow: auto; position: relative" @scroll="onScroll">
-                <div ref="toolPanel" class="tool-panel column bg-green-11" style="position: sticky; top: 0;">
-                    <div class="header q-mx-md q-mt-sm 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">
+        <div ref="scroller" class="col fit column no-wrap" style="overflow: auto; position: relative" @scroll="onScroll">
+            <div ref="toolPanel" class="tool-panel column bg-green-11" style="position: sticky; top: 0; z-index: 10;">
+                <div class="header q-mx-md q-mt-xs row items-center justify-between">
+                    <div class="row items-center q-mr-xs" style="font-size: 150%;">
+                        <div class="q-py-xs q-px-sm bg-green-12" style="border: 1px solid #aaaaaa; border-radius: 6px">
                             {{ projectName }}
                         </div>
+                        <div class="q-ml-md q-mr-xs">
+                            Коллекция
+                        </div>
+                        <div class="clickable" @click="showCollectionInfo">
+                            {{ collection }}
+                        </div>
                     </div>
-                    <div class="row q-mx-md q-my-sm items-center">
-                        <q-input
-                            ref="authorInput" v-model="author" :maxlength="5000" :debounce="inputDebounce"
-                            class="bg-white q-mt-xs" style="width: 300px;" label="Автор" stack-label outlined dense clearable
-                        />
-                        <div class="q-mx-xs" />
-                        <q-input
-                            v-model="series" :maxlength="inputMaxLength" :debounce="inputDebounce"
-                            class="bg-white q-mt-xs" style="width: 200px;" label="Серия" stack-label outlined dense clearable
-                        />
-                        <div class="q-mx-xs" />
-                        <q-input
-                            v-model="title" :maxlength="inputMaxLength" :debounce="inputDebounce"
-                            class="bg-white q-mt-xs" style="width: 200px;" label="Название" stack-label outlined dense clearable
-                        />
-                        <div class="q-mx-xs" />
-                        <q-input
-                            v-model="genre" :maxlength="inputMaxLength" :debounce="inputDebounce"
-                            class="bg-white q-mt-xs" style="width: 200px;" label="Жанр" stack-label outlined dense clearable readonly
-                            @click="selectGenre"
-                        />
-                        <div class="q-mx-xs" />
-                        <q-input
-                            v-model="lang" :maxlength="inputMaxLength" :debounce="inputDebounce"
-                            class="bg-white q-mt-xs" style="width: 80px;" label="Язык" stack-label outlined dense clearable readonly
-                            @click="selectLang"
+                    <div class="row items-center" style="font-size: 120%;">
+                        <div class="q-mr-xs">
+                            На странице
+                        </div>
+                        <q-select
+                            v-model="limit" :options="limitOptions" class="bg-white"
+                            dropdown-icon="la la-angle-down la-sm"
+                            outlined dense emit-value map-options
                         />
-                        <div class="q-mx-xs" />                
-                        <q-btn round dense style="height: 20px" color="info" icon="la la-question" @click="showSearchHelp" />
-
-                        <div class="q-mx-xs" />
-                        <div class="row items-center q-mt-xs">
-                            <div v-show="queryFound > 0">
-                                Найдено {{ totalFound }} авторов
-                            </div>
-                            <div v-show="queryFound == 0">
-                                Ничего не найдено
-                            </div>
+                    </div>
+                </div>
+                <div class="row q-mx-md q-mb-sm items-center">
+                    <q-input
+                        ref="authorInput" v-model="author" :maxlength="5000" :debounce="inputDebounce"
+                        class="bg-white q-mt-xs" style="width: 300px;" label="Автор" stack-label outlined dense clearable
+                    />
+                    <div class="q-mx-xs" />
+                    <q-input
+                        v-model="series" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                        class="bg-white q-mt-xs" style="width: 200px;" label="Серия" stack-label outlined dense clearable
+                    />
+                    <div class="q-mx-xs" />
+                    <q-input
+                        v-model="title" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                        class="bg-white q-mt-xs" style="width: 200px;" label="Название" stack-label outlined dense clearable
+                    />
+                    <div class="q-mx-xs" />
+                    <q-input
+                        v-model="genre" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                        class="bg-white q-mt-xs" style="width: 200px;" label="Жанр" stack-label outlined dense clearable readonly
+                        @click="selectGenre"
+                    />
+                    <div class="q-mx-xs" />
+                    <q-input
+                        v-model="lang" :maxlength="inputMaxLength" :debounce="inputDebounce"
+                        class="bg-white q-mt-xs" style="width: 80px;" label="Язык" stack-label outlined dense clearable readonly
+                        @click="selectLang"
+                    />
+                    <div class="q-mx-xs" />                
+                    <q-btn round dense style="height: 20px" color="info" icon="la la-question" @click="showSearchHelp" />
+
+                    <div class="q-mx-xs" />
+                    <div class="row items-center q-mt-xs">
+                        <div v-show="queryFound > 0">
+                            Найдено {{ totalFound }} авторов
                         </div>
-
-                        <div class="q-mx-xs" />
-                        <div class="col row justify-end q-mt-xs">
-                            <q-select
-                                v-model="limit" :options="limitOptions" class="bg-white"
-                                dropdown-icon="la la-angle-down la-sm"
-                                outlined dense emit-value map-options
-                            />
+                        <div v-show="queryFound == 0">
+                            Ничего не найдено
                         </div>
                     </div>
                 </div>
+            </div>
 
-                <div v-for="item in tableData" :key="item.key" style="border-bottom: 1px solid #aaaaaa">
-                    <div class="q-my-sm q-ml-md" style="font-size: 120%">
-                        {{ item.value }}
-                    </div>
+            <div v-show="totalPages > 1" class="row justify-center items-center q-ml-md q-my-xs" style="font-size: 120%">
+                <div class="q-mr-xs">
+                    Страница
+                </div>
+                <div class="bg-white">
+                    <NumInput v-model="page" :min="1" :max="totalPages" style="width: 150px" />
+                </div>
+                <div class="q-ml-xs">
+                    из {{ totalPages }}
+                </div>
+            </div>
+
+            <div v-for="item in tableData" :key="item.key" style="border-bottom: 1px solid #aaaaaa">
+                <div class="q-my-sm q-ml-md" style="font-size: 120%">
+                    {{ item.value }}
+                </div>
+            </div>
+
+            <div v-show="totalPages > 1" class="row justify-center items-center q-ml-md q-my-xs" style="font-size: 120%">
+                <div class="q-mr-xs">
+                    Страница
+                </div>
+                <div class="bg-white">
+                    <NumInput v-model="page" :min="1" :max="totalPages" style="width: 150px" />
+                </div>
+                <div class="q-ml-xs">
+                    из {{ totalPages }}
                 </div>
             </div>
         </div>
@@ -90,11 +113,14 @@
 //-----------------------------------------------------------------------------
 import vueComponent from '../vueComponent.js';
 
+import NumInput from '../share/NumInput.vue';
 import * as utils from '../../share/utils';
+
 //import _ from 'lodash';
 
 const componentOptions = {
     components: {
+        NumInput,
     },
     watch: {
         config() {
@@ -115,9 +141,16 @@ const componentOptions = {
         lang() {
             this.refresh();
         },
+        page() {
+            this.refresh();
+        },
         limit() {
+            this.updatePageCount();
             this.refresh();
         },
+        totalFound() {
+            this.updatePageCount();
+        }
     },
 };
 class Search {
@@ -125,7 +158,9 @@ class Search {
     collection = '';
     projectName = '';
 
-    loadingVisible = false;
+    loadingMessage = '';
+    page = 1;
+    totalPages = 1;
 
     //input field consts 
     inputMaxLength = 1000;
@@ -220,6 +255,13 @@ class Search {
         this.lastScrollTop = curScrollTop;    
     }
 
+    updatePageCount() {
+        this.totalPages = Math.floor(this.totalFound/this.limit);
+        this.totalPages = (this.totalPages < 1 ? 1 : this.totalPages);
+        if (this.page > this.totalPages)
+            this.page = 1;
+    }
+
     async updateTableData() {
         let result = [];
 
@@ -231,6 +273,8 @@ class Search {
     }
 
     async refresh() {
+        const offset = (this.page - 1)*this.limit;
+
         const newQuery = {
             author: this.author,
             series: this.series,
@@ -238,6 +282,7 @@ class Search {
             genre: this.genre,
             lang:  this.lang,
             limit: this.limit,
+            offset,
         };
 
         this.queryExecute = newQuery;
@@ -255,7 +300,7 @@ class Search {
                 (async() => {
                     await utils.sleep(500);
                     if (inSearch)
-                        this.loadingVisible = true;
+                        this.loadingMessage = 'Поиск авторов...';
                 })();
 
                 try {
@@ -271,7 +316,7 @@ class Search {
                     return;
                 } finally {
                     inSearch = false;
-                    this.loadingVisible = false;
+                    this.loadingMessage = '';
                 }
             }
         } finally {
@@ -293,7 +338,6 @@ export default vueComponent(Search);
 }
 
 .header {
-    font-size: 150%;
     min-height: 30px;
 }
 

+ 188 - 0
client/components/share/NumInput.vue

@@ -0,0 +1,188 @@
+<template>
+    <q-input
+        v-model="filteredValue"
+        outlined dense
+        input-style="text-align: center"
+        class="no-mp"
+        :class="(error ? 'error' : '')"
+        :disable="disable"
+    >
+        <slot></slot>
+        <template #prepend>
+            <q-icon
+                v-ripple="validate(modelValue - step)" 
+                :class="(validate(modelValue - step) ? '' : 'disable')" 
+                name="la la-minus-circle" 
+                class="button" 
+                @click="minus"
+                @mousedown.prevent.stop="onMouseDown($event, 'minus')"
+                @mouseup.prevent.stop="onMouseUp"
+                @mouseout.prevent.stop="onMouseUp"
+                @touchstart.stop="onTouchStart($event, 'minus')"
+                @touchend.stop="onTouchEnd"
+                @touchcancel.prevent.stop="onTouchEnd"
+            />
+        </template>
+        <template #append>
+            <q-icon
+                v-ripple="validate(modelValue + step)"
+                :class="(validate(modelValue + step) ? '' : 'disable')"
+                name="la la-plus-circle"
+                class="button"
+                @click="plus"
+                @mousedown.prevent.stop="onMouseDown($event, 'plus')"
+                @mouseup.prevent.stop="onMouseUp"
+                @mouseout.prevent.stop="onMouseUp"
+                @touchstart.stop="onTouchStart($event, 'plus')"
+                @touchend.stop="onTouchEnd"
+                @touchcancel.prevent.stop="onTouchEnd"
+            />
+        </template>
+    </q-input>
+</template>
+
+<script>
+//-----------------------------------------------------------------------------
+import vueComponent from '../vueComponent.js';
+
+import * as utils from '../../share/utils';
+
+const componentOptions = {
+    watch: {
+        filteredValue: function(newValue) {
+            if (this.validate(newValue)) {
+                this.error = false;
+                this.$emit('update:modelValue', this.string2number(newValue));
+            } else {
+                this.error = true;
+            }
+        },
+        modelValue: function(newValue) {
+            this.filteredValue = newValue;
+        },
+    }
+};
+class NumInput {
+    _options = componentOptions;
+    _props = {
+        modelValue: Number,
+        min: { type: Number, default: -Number.MAX_VALUE },
+        max: { type: Number, default: Number.MAX_VALUE },
+        step: { type: Number, default: 1 },
+        digits: { type: Number, default: 0 },
+        disable: Boolean
+    };
+
+    filteredValue = 0;
+    error = false;
+
+    created() {
+        this.filteredValue = this.modelValue;
+    }
+
+    string2number(value) {
+        return Number.parseFloat(Number.parseFloat(value).toFixed(this.digits));
+    }
+
+    validate(value) {
+        let n = this.string2number(value);
+        if (isNaN(n))
+            return false;
+        if (n < this.min)
+            return false;
+        if (n > this.max)
+            return false;
+        return true;
+    }
+
+    plus() {
+        const newValue = this.modelValue + this.step;
+        if (this.validate(newValue))
+            this.filteredValue = newValue;
+    }
+
+    minus() {
+        const newValue = this.modelValue - this.step;
+        if (this.validate(newValue))
+            this.filteredValue = newValue;
+    }
+
+    onMouseDown(event, way) {
+        this.startClickRepeat = true;
+        this.clickRepeat = false;
+
+        if (event.button == 0) {
+            (async() => {
+                await utils.sleep(300);
+                if (this.startClickRepeat) {
+                    this.clickRepeat = true;
+                    while (this.clickRepeat) {
+                        if (way == 'plus') {
+                            this.plus();
+                        } else {
+                            this.minus();
+                        }
+                        await utils.sleep(50);
+                    }
+                }
+            })();
+        }
+    }
+
+    onMouseUp() {
+        if (this.inTouch)
+            return;
+        this.startClickRepeat = false;
+        this.clickRepeat = false;
+    }
+
+    onTouchStart(event, way) {
+        if (!this.$root.isMobileDevice)
+            return;
+        if (event.touches.length == 1) {
+            this.inTouch = true;
+            this.onMouseDown({button: 0}, way);
+        }
+    }
+
+    onTouchEnd() {
+        if (!this.$root.isMobileDevice)
+            return;
+        this.inTouch = false;
+        this.onMouseUp();
+    }
+}
+
+export default vueComponent(NumInput);
+//-----------------------------------------------------------------------------
+</script>
+
+<style scoped>
+.no-mp {
+    margin: 0;
+    padding: 0;
+}
+
+.button {
+    font-size: 130%;
+    border-radius: 20px;
+    color: #bbb;
+    cursor: pointer;
+}
+
+.button:hover {
+    color: #616161;
+    background-color: #efebe9;
+}
+
+.error {
+    background-color: #ffabab;
+    border-radius: 3px;
+}
+
+.disable, .disable:hover {
+    cursor: not-allowed;
+    color: #bbb;
+    background-color: white;
+}
+</style>

+ 2 - 2
client/quasar.js

@@ -67,10 +67,10 @@ const components = {
 };
 
 //directives 
-//import Ripple from 'quasar/src/directives/Ripple';
+import Ripple from 'quasar/src/directives/Ripple';
 import ClosePopup from 'quasar/src/directives/ClosePopup';
 
-const directives = {/*Ripple, */ClosePopup};
+const directives = {Ripple, ClosePopup};
 
 //plugins
 //import AppFullscreen from 'quasar/src/plugins/AppFullscreen';

+ 2 - 1
server/core/DbSearcher.js

@@ -240,12 +240,13 @@ class DbSearcher {
 
             const totalFound = authorIds.length;
             const limit = (query.limit ? query.limit : 1000);
+            const offset = (query.offset ? query.offset : 0);
 
             //выборка найденных авторов
             let result = await db.select({
                 table: 'author',
                 map: `(r) => ({id: r.id, author: r.author})`,
-                where: `@@id(${db.esc(authorIds.slice(0, limit))})`
+                where: `@@id(${db.esc(authorIds.slice(offset, offset + limit))})`
             });
 
             return {result, totalFound};