11<script lang="ts" setup>
2- import { nextTick , onMounted , reactive , ref , watch } from ' vue'
2+ import { computed , nextTick , onMounted , reactive , ref , watch } from ' vue'
33import { Code , Search } from ' @icon-park/vue-next'
4- import type { Widget } from ' @widget-js/core'
5- import { AppApi , BrowserWindowApi , ElectronUtils , WidgetApi , WidgetPackageApi } from ' @widget-js/core'
4+ import type { Category , Widget } from ' @widget-js/core'
5+ import { AppApi , BrowserWindowApi , ElectronUtils , NotificationApi , WidgetApi , WidgetPackageApi } from ' @widget-js/core'
66
7- import { useDebounceFn , useElementSize } from ' @vueuse/core'
7+ import { useDebounceFn , useDropZone , useStorage , useWindowSize } from ' @vueuse/core'
88import type { WidgetSearchOptions } from ' @widget-js/web-api'
99import { WebWidget } from ' @widget-js/web-api'
1010import { useI18n } from ' vue-i18n'
11+ import { ElNotification } from ' element-plus'
12+ import consola from ' consola'
1113import SearchItem from ' @/views/add/SearchItem.vue'
1214import { WebWidgetApi } from ' @/api/WebWidgetApi'
1315import WidgetTags from ' @/views/add/WidgetTags.vue'
1416import FeatureWallList from ' @/views/add/feature/FeatureWallList.vue'
17+ import DropMask from ' @/views/add/DropMask.vue'
1518
1619const { t } = useI18n ()
1720const keyword = ref (' ' )
1821const selectedCategory = ref (' ' )
1922const windowRef = ref ()
2023const widgets = reactive <Widget []>([])
2124const loading = ref (true )
22- const { height } = useElementSize ( windowRef )
25+ const { height } = useWindowSize ( )
2326onMounted (async () => {
2427 await nextTick ()
2528 document .title = t (' search.title' )
2629})
2730
28- BrowserWindowApi .setup ({ width: 750 , height: 850 , center: true })
29-
3031async function search() {
3132 loading .value = true
3233 if (selectedCategory .value == ' debug' ) {
@@ -47,13 +48,26 @@ async function search() {
4748 }
4849
4950 const version = await AppApi .getVersion ()
51+ const keywordStr = keyword .value .trim ()
5052 const options: WidgetSearchOptions = {
5153 page: 1 ,
5254 pageSize: 50 ,
5355 category: selectedCategory .value ,
54- keyword: keyword . value . trim () ,
56+ keyword: keywordStr ,
5557 }
5658 options .appVersion = version
59+ let localWidgets = (await WidgetApi .getWidgets ()).filter (it => ! it .disabled )
60+
61+ if (selectedCategory .value ) {
62+ localWidgets = localWidgets .filter (it => it .categories && it .categories .includes (selectedCategory .value as Category ))
63+ }
64+ if (keywordStr ) {
65+ localWidgets = localWidgets .filter ((it ) => {
66+ const title = JSON .stringify (it .title )
67+ const description = JSON .stringify (it .description )
68+ return title .includes (keywordStr ) || description .includes (keywordStr )
69+ })
70+ }
5771 WebWidgetApi .search (options )
5872 .then ((res ) => {
5973 widgets .splice (0 , widgets .length )
@@ -62,6 +76,16 @@ async function search() {
6276 .map (it => WebWidget .fromObject (it ))
6377 .filter (it => it .name != ' cn.widgetjs.widgets.dynamic_island' ),
6478 )
79+ for (const localWidget of localWidgets ) {
80+ if (widgets .some (it => it .name == localWidget .name )) {
81+ continue
82+ }
83+ widgets .push (WebWidget .fromObject (localWidget ))
84+ }
85+ })
86+ .catch (() => {
87+ widgets .splice (0 , widgets .length )
88+ widgets .push (... localWidgets )
6589 })
6690 .finally (() => {
6791 loading .value = false
@@ -78,29 +102,58 @@ search()
78102function goDevPage() {
79103 BrowserWindowApi .openUrl (' https://widgetjs.cn/guide/' , { external: true })
80104}
105+
106+ const bodyRef = ref <HTMLDivElement >()
107+ const showKnowButton = useStorage (' tip-drop-mask-button' , true )
108+
109+ const { isOverDropZone } = useDropZone (bodyRef , {
110+ onDrop : async (files : File [] | null , _ : DragEvent ) => {
111+ if (files && files .length > 0 ) {
112+ const file = files [0 ]
113+ try {
114+ consola .info (` 开始安装组件包: ` , file )
115+ await WidgetPackageApi .install (file .path )
116+ await NotificationApi .success (' 安装成功' )
117+ window .location .reload ()
118+ }
119+ catch (e ) {
120+ ElNotification ({
121+ title: ' 安装失败' ,
122+ message: e .message ,
123+ type: ' error' ,
124+ })
125+ }
126+ }
127+ },
128+ dataTypes: [' application/x-zip-compressed' ],
129+ multiple: false ,
130+ })
131+
132+ const isShowMask = computed (() => {
133+ return isOverDropZone .value || showKnowButton .value
134+ })
81135 </script >
82136
83137<template >
84138 <div :class =" { browser: !ElectronUtils.hasElectronApi() }" >
85- <widget-base-dialog ref =" windowRef" class =" search-window" :body-padding =" 0" :title =" t('search.title')" >
139+ <WidgetBaseDialog ref =" windowRef" class =" search-window" :body-padding =" 0" :title =" t('search.title')" >
86140 <template #body >
87- <div class =" body" >
88- <el-row justify = " center " class =" px-4 pt-2" >
141+ <div ref = " bodyRef " class =" dialog- body" >
142+ <div class =" px-4 pt-2 w-full flex gap -2" >
89143 <el-input
90144 v-model =" keyword"
91145 size =" large"
92- clearable
93- class =" round-border"
146+ class =" round-border flex-1"
94147 :placeholder =" t('search.placeholder')"
95148 @keydown.enter =" search"
96149 >
97150 <template #prefix >
98151 <Search />
99152 </template >
100153 </el-input >
101- </el-row >
154+ </div >
102155 <WidgetTags v-model =" selectedCategory" class =" px-4 pt-2" @change =" search" />
103- <el-scrollbar :height =" height - 150 " >
156+ <el-scrollbar :height =" height - 140 " >
104157 <el-row v-loading =" loading" justify =" start" class =" px-4" >
105158 <template v-if =" selectedCategory == ' wish' " >
106159 <FeatureWallList />
@@ -125,13 +178,14 @@ function goDevPage() {
125178 </template >
126179 </el-row >
127180 </el-scrollbar >
181+ <DropMask v-show =" isShowMask" />
128182 </div >
129183 </template >
130- </widget-base-dialog >
184+ </WidgetBaseDialog >
131185 </div >
132186</template >
133187
134- <style lang="scss">
188+ <style lang="scss" scoped >
135189.browser {
136190 background-color : #e7e7e7 ;
137191 height : 100vh ;
@@ -146,7 +200,9 @@ function goDevPage() {
146200@import ' @/assets/scss/theme.scss' ;
147201
148202.dialog-wrapper {
149- .body {
203+ .dialog-body {
204+ position : relative ;
205+ height : calc (100vh - 42px );
150206 background-color : $fill-color-default ;
151207 }
152208}
0 commit comments