Skip to content

Commit d449d4e

Browse files
feat(dashboard): 拆分PR内容,专注于前端插件管理器界面修改,并解决与上游最新分支的冲突
1 parent 64e0183 commit d449d4e

26 files changed

+4951
-247
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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

Comments
 (0)