| <template> | |
| <MoveablePanel | |
| class="search-panel" | |
| :width="330" | |
| :height="0" | |
| :left="-270" | |
| :top="90" | |
| > | |
| <div class="close-btn" @click="close()" @mousedown.stop><IconClose /></div> | |
| <Tabs | |
| :tabs="tabs" | |
| v-model:value="type" | |
| /> | |
| <div class="content" :class="type" @mousedown.stop> | |
| <Input class="input" v-model:value="searchWord" placeholder="输入查找内容" @enter="searchNext()" ref="searchInpRef"> | |
| <template #suffix> | |
| <span class="count">{{searchIndex + 1}}/{{searchResults.length}}</span> | |
| <Divider type="vertical" /> | |
| <span class="ignore-case" | |
| :class="{ 'active': modifiers === 'g' }" | |
| v-tooltip="'忽略大小写'" | |
| @click="toggleModifiers()" | |
| >Aa</span> | |
| <Divider type="vertical" /> | |
| <IconLeft class="next-btn left" @click="searchPrev()" v-tooltip="'上一个'" /> | |
| <IconRight class="next-btn right" @click="searchNext()" v-tooltip="'下一个'" /> | |
| </template> | |
| </Input> | |
| <Input class="input" v-model:value="replaceWord" placeholder="输入替换内容" @enter="replace()" v-if="type === 'replace'"></Input> | |
| <div class="footer" v-if="type === 'replace'"> | |
| <Button :disabled="!searchWord" style="margin-left: 5px;" @click="replace()">替换</Button> | |
| <Button :disabled="!searchWord" type="primary" style="margin-left: 5px;" @click="replaceAll()">全部替换</Button> | |
| </div> | |
| </div> | |
| </MoveablePanel> | |
| </template> | |
| <script lang="ts" setup> | |
| import { nextTick, onMounted, ref, watch } from 'vue' | |
| import { useMainStore } from '@/store' | |
| import useSearch from '@/hooks/useSearch' | |
| import MoveablePanel from '@/components/MoveablePanel.vue' | |
| import Tabs from '@/components/Tabs.vue' | |
| import Divider from '@/components/Divider.vue' | |
| import Input from '@/components/Input.vue' | |
| import Button from '@/components/Button.vue' | |
| type TypeKey = 'search' | 'replace' | |
| interface TabItem { | |
| key: TypeKey | |
| label: string | |
| } | |
| const mainStore = useMainStore() | |
| const { | |
| searchWord, | |
| replaceWord, | |
| searchResults, | |
| searchIndex, | |
| modifiers, | |
| searchNext, | |
| searchPrev, | |
| replace, | |
| replaceAll, | |
| toggleModifiers, | |
| } = useSearch() | |
| const type = ref<TypeKey>('search') | |
| const tabs: TabItem[] = [ | |
| { key: 'search', label: '查找' }, | |
| { key: 'replace', label: '替换' }, | |
| ] | |
| const close = () => { | |
| mainStore.setSearchPanelState(false) | |
| } | |
| const searchInpRef = ref<InstanceType<typeof Input>>() | |
| onMounted(() => { | |
| searchInpRef.value!.focus() | |
| }) | |
| watch(type, () => { | |
| nextTick(() => { | |
| searchInpRef.value!.focus() | |
| }) | |
| }) | |
| </script> | |
| <style lang="scss" scoped> | |
| .search-panel { | |
| font-size: 13px; | |
| } | |
| .content { | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| } | |
| .input { | |
| margin-top: 10px; | |
| } | |
| .count { | |
| font-size: 12px; | |
| margin-right: 8px; | |
| user-select: none; | |
| } | |
| .ignore-case { | |
| font-size: 12px; | |
| user-select: none; | |
| cursor: pointer; | |
| &.active { | |
| color: $themeColor; | |
| } | |
| } | |
| .next-btn { | |
| width: 22px; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin: 0 !important; | |
| user-select: none; | |
| cursor: pointer; | |
| &:hover { | |
| color: $themeColor; | |
| } | |
| } | |
| .footer { | |
| display: flex; | |
| justify-content: flex-end; | |
| align-items: center; | |
| margin-top: 10px; | |
| } | |
| .close-btn { | |
| width: 32px; | |
| height: 32px; | |
| position: absolute; | |
| top: 8px; | |
| right: 3px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: #666; | |
| font-size: 13px; | |
| cursor: pointer; | |
| } | |
| </style> |