SelectExtSearchDialog.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. <template>
  2. <Dialog ref="dialog" v-model="dialogVisible">
  3. <template #header>
  4. <div class="row items-center">
  5. <div style="font-size: 110%">
  6. Расширенный поиск
  7. </div>
  8. <DivBtn class="q-ml-sm text-grey-5 bg-yellow-1" :size="28" :icon-size="24" icon="la la-question" round @click.stop.prevent="showSearchHelp">
  9. <template #tooltip>
  10. <q-tooltip :delay="1500" anchor="bottom middle" content-style="font-size: 80%" max-width="400px">
  11. Памятка
  12. </q-tooltip>
  13. </template>
  14. </DivBtn>
  15. </div>
  16. </template>
  17. <div ref="box" class="column q-mt-xs overflow-auto" style="max-width: 660px; padding: 0px 10px 10px 10px;">
  18. <div class="row">
  19. <div v-for="f in recStruct" :key="f.field" class="row">
  20. <div class="q-mx-xs" />
  21. <q-input
  22. v-model="search[f.field]" :maxlength="5000"
  23. class="q-mt-xs" style="width: 150px;" :label="`(${f.type}) ${f.field}`"
  24. :bg-color="bgColor[f.field] || 'white'"
  25. stack-label outlined dense clearable
  26. @keydown="onKeyDown"
  27. >
  28. <q-tooltip v-if="search[f.field]" :delay="500" anchor="bottom middle" content-style="font-size: 80%" max-width="400px">
  29. {{ search[f.field] }}
  30. </q-tooltip>
  31. </q-input>
  32. </div>
  33. </div>
  34. <div class="row q-mt-xs q-ml-sm" style="color: red" v-html="error" />
  35. </div>
  36. <template #footer>
  37. <q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps :disabled="error !== ''" @click="apply">
  38. Применить
  39. </q-btn>
  40. </template>
  41. </Dialog>
  42. </template>
  43. <script>
  44. //-----------------------------------------------------------------------------
  45. import vueComponent from '../../vueComponent.js';
  46. import Dialog from '../../share/Dialog.vue';
  47. import DivBtn from '../../share/DivBtn.vue';
  48. import _ from 'lodash';
  49. const componentOptions = {
  50. components: {
  51. Dialog,
  52. DivBtn,
  53. },
  54. watch: {
  55. modelValue(newValue) {
  56. this.dialogVisible = newValue;
  57. },
  58. dialogVisible(newValue) {
  59. this.$emit('update:modelValue', newValue);
  60. },
  61. extSearch: {
  62. handler(newValue) {
  63. this.search = _.cloneDeep(newValue);
  64. },
  65. deep: true,
  66. },
  67. search: {
  68. handler() {
  69. this.validate();
  70. },
  71. deep: true,
  72. },
  73. }
  74. };
  75. class SelectExtSearchDialog {
  76. _options = componentOptions;
  77. _props = {
  78. modelValue: Boolean,
  79. extSearch: Object,
  80. };
  81. dialogVisible = false;
  82. search = {};
  83. bgColor = {};
  84. error = '';
  85. created() {
  86. this.commit = this.$store.commit;
  87. }
  88. mounted() {
  89. }
  90. get config() {
  91. return this.$store.state.config;
  92. }
  93. get recStruct() {
  94. if (this.config.dbConfig && this.config.dbConfig.inpxInfo.recStruct)
  95. return this.config.dbConfig.inpxInfo.recStruct;
  96. else
  97. return [];
  98. }
  99. validate() {
  100. const validNumValue = (n) => {
  101. const validChars = new Set('0123456789.'.split(''));
  102. for (const c of n.split(''))
  103. if (!validChars.has(c))
  104. return false;
  105. const v = n.split('..');
  106. if ( isNaN(parseInt(v[0] || '0', 10)) || isNaN(parseInt(v[1] || '0', 10)) )
  107. return false;
  108. return true;
  109. };
  110. let error = [];
  111. const s = this.search;
  112. for (const f of this.recStruct) {
  113. if (f.type == 'N' && s[f.field] && !validNumValue(s[f.field])) {
  114. error.push(`Недопустимое значение поля ${f.field}`);
  115. this.bgColor[f.field] = 'red-2';
  116. } else {
  117. this.bgColor[f.field] = '';//default
  118. }
  119. }
  120. this.error = error.join('<br>');
  121. }
  122. showSearchHelp() {
  123. let info = `<div style="min-width: 250px" />`;
  124. info += `
  125. <p>
  126. Расширенный поиск ведется непосредственно по значениям атрибутов записей описания книг.
  127. Атрибуты можно увидеть, если включить опцию "Показывать JSON".
  128. Названия атрибутов (кроме "_uid" и "id") соответствуют названиям полей струкутры записей из inpx-файла.
  129. На поисковые значения действуют те же правила, что и для разделов "Авторы", "Серии", "Книги".
  130. <br><br>
  131. Для строковых значений (S):
  132. <ul>
  133. <li>
  134. без префикса: значение трактуется, как "начинается с"
  135. </li>
  136. <li>
  137. префикс "=": поиск по точному совпадению
  138. </li>
  139. <li>
  140. префикс "*": поиск подстроки в строке
  141. </li>
  142. <li>
  143. префикс "#": поиск подстроки в строке, но только среди начинающихся не с латинского или кириллического символа
  144. </li>
  145. <li>
  146. префикс "~": поиск по регулярному выражению
  147. </li>
  148. <li>
  149. префикс "?": поиск пустых значений или тех, что начинаются с этого символа
  150. </li>
  151. </ul>
  152. Для числовых значений (N):
  153. <ul>
  154. <li>
  155. число N: поиск по точному совпадению
  156. </li>
  157. <li>
  158. диапазон N..M: поиск по диапазону числовых значений, включая N и M. Например, поисковое значение 1024..2048 в поле "size"
  159. найдет все ссылки на файлы размером от 1КБ до 2КБ.
  160. </li>
  161. </ul>
  162. </p>
  163. `;
  164. this.$root.stdDialog.alert(info, 'Памятка', {iconName: 'la la-info-circle'});
  165. }
  166. onKeyDown(event) {
  167. if (event.code == 'Enter')
  168. this.apply();
  169. }
  170. apply() {
  171. this.validate();
  172. if (!this.error) {
  173. this.$emit('update:extSearch', _.cloneDeep(this.search));
  174. this.dialogVisible = false;
  175. }
  176. }
  177. }
  178. export default vueComponent(SelectExtSearchDialog);
  179. //-----------------------------------------------------------------------------
  180. </script>
  181. <style scoped>
  182. </style>