1+ <template >
2+ <v-slide-y-transition >
3+ <div v-if =" visible" class =" batch-operation-bar" >
4+ <v-toolbar
5+ class =" batch-operation-bar__toolbar"
6+ color =" surface"
7+ density =" comfortable"
8+ elevation =" 8"
9+ >
10+ <div class =" d-flex align-center flex-grow-1 ga-3 flex-wrap" >
11+ <div class =" d-flex align-center" >
12+ <v-icon size =" 18" class =" mr-2" >mdi-checkbox-multiple-marked</v-icon >
13+ <span class =" text-body-2" >已选择 {{ totalSelected }} 个插件</span >
14+ </div >
15+
16+ <v-divider vertical class =" mx-1" />
17+
18+ <div class =" d-flex align-center ga-2 flex-wrap" >
19+ <v-btn
20+ color =" success"
21+ size =" small"
22+ variant =" flat"
23+ :disabled =" isBusy || selectedInactive.length === 0"
24+ :loading =" isBusy"
25+ @click =" handleBatchEnable"
26+ >
27+ <v-icon start size =" 18" >mdi-play</v-icon >
28+ 批量启用
29+ <span v-if =" selectedInactive.length" class =" ml-1" >({{ selectedInactive.length }})</span >
30+ </v-btn >
31+
32+ <v-btn
33+ color =" warning"
34+ size =" small"
35+ variant =" flat"
36+ :disabled =" isBusy || selectedActive.length === 0"
37+ :loading =" isBusy"
38+ @click =" handleBatchDisable"
39+ >
40+ <v-icon start size =" 18" >mdi-pause</v-icon >
41+ 批量停用
42+ <span v-if =" selectedActive.length" class =" ml-1" >({{ selectedActive.length }})</span >
43+ </v-btn >
44+
45+ <v-btn
46+ color =" primary"
47+ size =" small"
48+ variant =" flat"
49+ :disabled =" isBusy || updatablePlugins.length === 0"
50+ :loading =" isBusy"
51+ @click =" handleBatchUpdate"
52+ >
53+ <v-icon start size =" 18" >mdi-update</v-icon >
54+ 批量更新
55+ <span class =" ml-1" >({{ updatablePlugins.length }})</span >
56+ </v-btn >
57+
58+ <v-btn
59+ color =" error"
60+ size =" small"
61+ variant =" flat"
62+ :disabled =" isBusy || uninstallablePlugins.length === 0"
63+ :loading =" isBusy"
64+ @click =" openUninstallConfirm"
65+ >
66+ <v-icon start size =" 18" >mdi-delete</v-icon >
67+ 批量卸载
68+ <span class =" ml-1" >({{ uninstallablePlugins.length }})</span >
69+ </v-btn >
70+ </div >
71+ </div >
72+
73+ <v-spacer />
74+
75+ <v-btn
76+ color =" grey"
77+ size =" small"
78+ variant =" text"
79+ :disabled =" isBusy"
80+ :loading =" isBusy"
81+ @click =" emit('clear-selection')"
82+ >
83+ 取消选择
84+ </v-btn >
85+ </v-toolbar >
86+
87+ <v-dialog v-model =" showUninstallDialog" max-width =" 520" >
88+ <v-card >
89+ <v-card-title class =" text-h6 d-flex align-center" >
90+ <v-icon color =" error" class =" mr-2" >mdi-delete-alert</v-icon >
91+ 确认卸载
92+ </v-card-title >
93+
94+ <v-card-text >
95+ <div >
96+ 确定要卸载选中的
97+ <strong >{{ pendingUninstallNames.length }}</strong >
98+ 个插件吗?此操作不可撤销。
99+ </div >
100+ <div class =" text-caption text-medium-emphasis mt-2" >
101+ 系统插件(reserved)不会出现在可卸载列表中。
102+ </div >
103+
104+ <v-alert
105+ v-if =" pendingUninstallNames.length === 0"
106+ type =" info"
107+ variant =" tonal"
108+ density =" compact"
109+ class =" mt-3"
110+ >
111+ 当前选择中没有可卸载插件。
112+ </v-alert >
113+
114+ <v-alert
115+ v-else
116+ type =" warning"
117+ variant =" tonal"
118+ density =" compact"
119+ class =" mt-3"
120+ >
121+ 将卸载 {{ pendingUninstallNames.length }} 个插件。
122+ </v-alert >
123+ </v-card-text >
124+
125+ <v-card-actions >
126+ <v-spacer />
127+ <v-btn
128+ color =" grey"
129+ variant =" text"
130+ :disabled =" isBusy"
131+ @click =" showUninstallDialog = false"
132+ >
133+ 取消
134+ </v-btn >
135+ <v-btn
136+ color =" error"
137+ variant =" elevated"
138+ :disabled =" isBusy || pendingUninstallNames.length === 0"
139+ :loading =" isBusy"
140+ @click =" confirmBatchUninstall"
141+ >
142+ 卸载
143+ </v-btn >
144+ </v-card-actions >
145+ </v-card >
146+ </v-dialog >
147+ </div >
148+ </v-slide-y-transition >
149+ </template >
150+
151+ <script setup lang="ts">
152+ import { computed , ref } from ' vue'
153+ import type { PluginSummary } from ' ./types'
154+
155+ const props = defineProps <{
156+ selectedInactive: PluginSummary []
157+ selectedActive: PluginSummary []
158+ busy? : boolean
159+ }>()
160+
161+ const emit = defineEmits <{
162+ (e : ' batch-enable' , plugins : PluginSummary []): void
163+ (e : ' batch-disable' , plugins : PluginSummary []): void
164+ (e : ' batch-update' , names : string []): void
165+ (e : ' batch-uninstall' , names : string []): void
166+ (e : ' clear-selection' ): void
167+ }>()
168+
169+ const isBusy = computed (() => props .busy ?? false )
170+
171+ // 总选中数量
172+ const totalSelected = computed (
173+ () => (props .selectedInactive ?.length ?? 0 ) + (props .selectedActive ?.length ?? 0 )
174+ )
175+
176+ // 是否显示(有选中项时显示)
177+ const visible = computed (() => totalSelected .value > 0 )
178+
179+ const allSelected = computed <PluginSummary []>(() => [
180+ ... (props .selectedInactive ?? []),
181+ ... (props .selectedActive ?? []),
182+ ])
183+
184+ // 可更新的插件(选中的且has_update为true)
185+ const updatablePlugins = computed (() => {
186+ const all = allSelected .value
187+ return all .filter ((p ) => Boolean (p .has_update ))
188+ })
189+
190+ // 可卸载的插件(选中的且非系统插件)
191+ const uninstallablePlugins = computed (() => {
192+ const all = allSelected .value
193+ return all .filter ((p ) => ! p .reserved )
194+ })
195+
196+ const handleBatchEnable = () => emit (' batch-enable' , props .selectedInactive ?? [])
197+ const handleBatchDisable = () => emit (' batch-disable' , props .selectedActive ?? [])
198+ const handleBatchUpdate = () => emit (' batch-update' , updatablePlugins .value .map ((p ) => p .name ))
199+
200+ const showUninstallDialog = ref (false )
201+ const pendingUninstallNames = ref <string []>([])
202+
203+ const openUninstallConfirm = () => {
204+ pendingUninstallNames .value = uninstallablePlugins .value .map ((p ) => p .name )
205+ showUninstallDialog .value = true
206+ }
207+
208+ const confirmBatchUninstall = () => {
209+ const names = pendingUninstallNames .value
210+ showUninstallDialog .value = false
211+ if (names .length === 0 ) return
212+ emit (' batch-uninstall' , names )
213+ }
214+ </script >
215+
216+ <style scoped>
217+ .batch-operation-bar {
218+ position : fixed ;
219+ left : 0 ;
220+ right : 0 ;
221+ bottom : 0 ;
222+ z-index : 1000 ;
223+ padding : 12px ;
224+ padding-bottom : calc (12px + env(safe-area-inset-bottom));
225+ pointer-events : none ;
226+ }
227+
228+ .batch-operation-bar__toolbar {
229+ pointer-events : auto ;
230+ border-radius : 14px ;
231+ box-shadow : 0 -6px 22px rgba (0 , 0 , 0 , 0.12 );
232+ border : 1px solid rgba (var (--v-border-color ), var (--v-border-opacity ));
233+ }
234+ </style >
0 commit comments