Vben
2021-06-09 55e9d9fc2953643cec95c74b6ed34b0e68641fb6
提交 | 用户 | age
dddda5 1 <template>
c774a6 2   <Teleport to="body">
V 3     <transition name="zoom-fade" mode="out-in">
4       <div :class="getClass" @click.stop v-if="visible">
ff2b12 5         <div :class="`${prefixCls}-content`" v-click-outside="handleClose">
V 6           <div :class="`${prefixCls}-input__wrapper`">
3c441a 7             <Input
ff2b12 8               :class="`${prefixCls}-input`"
V 9               :placeholder="t('common.searchText')"
1ae636 10               ref="inputRef"
ff2b12 11               allow-clear
V 12               @change="handleSearch"
13             >
14               <template #prefix>
15                 <SearchOutlined />
16               </template>
3c441a 17             </Input>
3ba828 18             <span :class="`${prefixCls}-cancel`" @click="handleClose">
V 19               {{ t('common.cancelText') }}
20             </span>
c774a6 21           </div>
ff2b12 22
V 23           <div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
24             {{ t('component.app.searchNotData') }}
25           </div>
970d40 26
ff2b12 27           <ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
V 28             <li
29               :ref="setRefs(index)"
30               v-for="(item, index) in searchResult"
31               :key="item.path"
32               :data-index="index"
33               @mouseenter="handleMouseenter"
34               @click="handleEnter"
35               :class="[
36                 `${prefixCls}-list__item`,
37                 {
38                   [`${prefixCls}-list__item--active`]: activeIndex === index,
39                 },
40               ]"
41             >
42               <div :class="`${prefixCls}-list__item-icon`">
3ba828 43                 <Icon :icon="item.icon || 'mdi:form-select'" :size="20" />
ff2b12 44               </div>
9edc28 45               <div :class="`${prefixCls}-list__item-text`">
V 46                 {{ item.name }}
47               </div>
ff2b12 48               <div :class="`${prefixCls}-list__item-enter`">
3ba828 49                 <Icon icon="ant-design:enter-outlined" :size="20" />
ff2b12 50               </div>
V 51             </li>
52           </ul>
53           <AppSearchFooter />
54         </div>
dddda5 55       </div>
c774a6 56     </transition>
V 57   </Teleport>
dddda5 58 </template>
V 59 <script lang="ts">
1ae636 60   import { defineComponent, computed, unref, ref, watch, nextTick } from 'vue';
236575 61   import { SearchOutlined } from '@ant-design/icons-vue';
V 62   import { Input } from 'ant-design-vue';
63   import AppSearchFooter from './AppSearchFooter.vue';
64   import Icon from '/@/components/Icon';
65   import clickOutside from '/@/directives/clickOutside';
dddda5 66   import { useDesign } from '/@/hooks/web/useDesign';
V 67   import { useRefs } from '/@/hooks/core/useRefs';
68   import { useMenuSearch } from './useMenuSearch';
69   import { useI18n } from '/@/hooks/web/useI18n';
c774a6 70   import { useAppInject } from '/@/hooks/web/useAppInject';
e6db0d 71
55e9d9 72   const props = {
V 73     visible: { type: Boolean },
74   };
970d40 75
dddda5 76   export default defineComponent({
V 77     name: 'AppSearchModal',
3c441a 78     components: { Icon, SearchOutlined, AppSearchFooter, Input },
9edc28 79     directives: {
V 80       clickOutside,
81     },
55e9d9 82     props,
9edc28 83     emits: ['close'],
d67772 84     setup(props, { emit }) {
dddda5 85       const scrollWrap = ref<ElRef>(null);
55e9d9 86       const inputRef = ref<Nullable<HTMLElement>>(null);
V 87
dddda5 88       const { t } = useI18n();
55e9d9 89       const { prefixCls } = useDesign('app-search-modal');
dddda5 90       const [refs, setRefs] = useRefs();
c774a6 91       const { getIsMobile } = useAppInject();
dddda5 92
1ae636 93       const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =
94         useMenuSearch(refs, scrollWrap, emit);
dddda5 95
d67772 96       const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0);
dddda5 97
c774a6 98       const getClass = computed(() => {
V 99         return [
100           prefixCls,
101           {
102             [`${prefixCls}--mobile`]: unref(getIsMobile),
103           },
104         ];
105       });
106
1ae636 107       watch(
d67772 108         () => props.visible,
55e9d9 109         (visible: boolean) => {
V 110           visible &&
1ae636 111             nextTick(() => {
112               unref(inputRef)?.focus();
113             });
114         }
115       );
116
d67772 117       function handleClose() {
V 118         searchResult.value = [];
119         emit('close');
120       }
121
dddda5 122       return {
V 123         t,
124         prefixCls,
c774a6 125         getClass,
dddda5 126         handleSearch,
V 127         searchResult,
128         activeIndex,
129         getIsNotData,
130         handleEnter,
131         setRefs,
132         scrollWrap,
133         handleMouseenter,
236575 134         handleClose,
1ae636 135         inputRef,
dddda5 136       };
V 137     },
138   });
139 </script>
140 <style lang="less" scoped>
141   @prefix-cls: ~'@{namespace}-app-search-modal';
c774a6 142   @footer-prefix-cls: ~'@{namespace}-app-search-footer';
dddda5 143   .@{prefix-cls} {
V 144     position: fixed;
145     top: 0;
146     left: 0;
c774a6 147     z-index: 800;
dddda5 148     display: flex;
V 149     width: 100%;
150     height: 100%;
151     padding-top: 50px;
2cdf2c 152     background-color: rgba(0, 0, 0, 0.25);
dddda5 153     justify-content: center;
c774a6 154
V 155     &--mobile {
156       padding: 0;
157
158       > div {
159         width: 100%;
160       }
161
162       .@{prefix-cls}-input {
163         width: calc(100% - 38px);
164       }
165
166       .@{prefix-cls}-cancel {
167         display: inline-block;
168       }
169
170       .@{prefix-cls}-content {
171         width: 100%;
172         height: 100%;
173         border-radius: 0;
174       }
175
176       .@{footer-prefix-cls} {
177         display: none;
178       }
179
180       .@{prefix-cls}-list {
181         height: calc(100% - 80px);
182         max-height: unset;
183
184         &__item {
185           &-enter {
186             opacity: 0 !important;
187           }
188         }
189       }
190     }
dddda5 191
V 192     &-content {
193       position: relative;
efbde0 194       width: 632px;
dddda5 195       margin: 0 auto auto auto;
2cdf2c 196       background-color: @component-background;
efbde0 197       border-radius: 16px;
V 198       box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
dddda5 199       flex-direction: column;
V 200     }
201
c774a6 202     &-input__wrapper {
V 203       display: flex;
204       padding: 14px 14px 0 14px;
205       justify-content: space-between;
206       align-items: center;
207     }
208
dddda5 209     &-input {
c774a6 210       width: 100%;
efbde0 211       height: 48px;
dddda5 212       font-size: 1.5em;
V 213       color: #1c1e21;
efbde0 214       border-radius: 6px;
dddda5 215
V 216       span[role='img'] {
217         color: #999;
218       }
219     }
220
c774a6 221     &-cancel {
V 222       display: none;
223       font-size: 1em;
224       color: #666;
225     }
226
dddda5 227     &-not-data {
V 228       display: flex;
229       width: 100%;
230       height: 100px;
231       font-size: 0.9;
232       color: rgb(150 159 175);
233       align-items: center;
234       justify-content: center;
235     }
236
237     &-list {
238       max-height: 472px;
239       padding: 0 14px;
240       padding-bottom: 20px;
241       margin: 0 auto;
242       margin-top: 14px;
243       overflow: auto;
244
245       &__item {
246         position: relative;
247         display: flex;
248         width: 100%;
249         height: 56px;
250         padding-bottom: 4px;
251         padding-left: 14px;
252         margin-top: 8px;
253         font-size: 14px;
254         color: @text-color-base;
255         cursor: pointer;
2cdf2c 256         background-color: @component-background;
dddda5 257         border-radius: 4px;
V 258         box-shadow: 0 1px 3px 0 #d4d9e1;
259         align-items: center;
260
e49072 261         > div:first-child,
V 262         > div:last-child {
263           display: flex;
264           align-items: center;
265         }
266
dddda5 267         &--active {
V 268           color: #fff;
2cdf2c 269           background-color: @primary-color;
dddda5 270
V 271           .@{prefix-cls}-list__item-enter {
272             opacity: 1;
273           }
274         }
275
276         &-icon {
277           width: 30px;
278         }
279
280         &-text {
281           flex: 1;
282         }
283
284         &-enter {
285           width: 30px;
286           opacity: 0;
287         }
288       }
289     }
290   }
291 </style>