Skip to content

Commit 4e2d6d0

Browse files
Merge pull request #14 from q3cc/feature/plugin-market-enhancement
添加插件快捷复制安装命令
2 parents 7086cb4 + 11628b3 commit 4e2d6d0

1 file changed

Lines changed: 168 additions & 28 deletions

File tree

‎docs/.vitepress/theme/PluginMarket.vue‎

Lines changed: 168 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,16 @@
7979

8080
<div class="card-footer">
8181
<span class="version-tag">v{{ item.version }}</span>
82-
<button class="action-btn" @click.stop="openGithub(item)">
83-
查看源码
84-
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
85-
</button>
82+
<div class="footer-actions">
83+
<button class="action-btn copy-btn" @click.stop="handleCopy(item)" :class="{ 'copied': copiedStates[getCopyStateKey(item)] }">
84+
<span v-if="copiedStates[getCopyStateKey(item)]">✓ 已复制</span>
85+
<span v-else>📋 复制指令</span>
86+
</button>
87+
<button class="action-btn" @click.stop="openGithub(item)">
88+
源码
89+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>
90+
</button>
91+
</div>
8692
</div>
8793
</div>
8894
</div>
@@ -164,18 +170,48 @@
164170
</div>
165171
</div>
166172
</Transition>
173+
174+
<!-- Install Warning Modal -->
175+
<Transition name="modal-fade">
176+
<div v-if="warningVisible" class="modal-overlay" @click="warningVisible = false">
177+
<div class="modal-content warning-modal" @click.stop>
178+
<div class="modal-header">
179+
<h3>⚠️ 安装前必读</h3>
180+
<button class="modal-close" @click="warningVisible = false">
181+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
182+
</button>
183+
</div>
184+
<div class="modal-body">
185+
<p class="warning-text"><strong>当前 ToolDelta 框架尚未内置此功能。</strong></p>
186+
<p class="warning-text">使用快捷安装指令前,请确保您已手动安装了 <strong>"插件快捷安装"</strong> 插件。</p>
187+
<div class="command-preview">
188+
<code>{{ getInstallCmd(pendingCmdItem) }}</code>
189+
</div>
190+
</div>
191+
<div class="modal-footer">
192+
<button class="primary-btn full-width" @click="confirmWarning">
193+
我知道了,复制指令
194+
</button>
195+
</div>
196+
</div>
197+
</div>
198+
</Transition>
167199
</div>
168200
</template>
169201

170202
<script setup>
171-
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
203+
import { ref, computed, onMounted, onUnmounted, watch, nextTick, reactive } from 'vue'
172204
173205
const itemList = ref([])
174206
const loading = ref(true)
175-
const pluginNameMap = ref({})
207+
const pluginNameMap = ref({}) // display name -> install id
208+
const pluginFolderMap = ref({}) // display name -> folder path for GitHub/assets
176209
const searchQuery = ref('')
177210
const modalVisible = ref(false)
178211
const currentItem = ref(null)
212+
const warningVisible = ref(false)
213+
const pendingCmdItem = ref(null)
214+
const copiedStates = reactive({}) // Use reactive for object property reactivity
179215
let modalResetTimer = null
180216
181217
// Minecraft Color Maps
@@ -312,6 +348,71 @@ const packageCount = computed(() => itemList.value.filter(i => i.isPackage).leng
312348
313349
watch(filteredList, () => nextTick(setupCardObserver), { flush: 'post' })
314350
351+
const getInstallCmd = (item) => {
352+
if (!item) return ''
353+
const installId = pluginNameMap.value[item.name]
354+
|| (item.isPackage ? item.originalName : item.pluginId)
355+
|| item.name
356+
return `plg add ${installId}`
357+
// return `plg add ${installId} -url https://pm.tooldelta.top`
358+
}
359+
360+
// Generate stable key for copiedStates to avoid collisions
361+
const getCopyStateKey = (item) => {
362+
if (!item) return ''
363+
const installId = pluginNameMap.value[item.name]
364+
|| (item.isPackage ? item.originalName : item.pluginId)
365+
|| item.name
366+
return `${item.isPackage ? 'pkg' : 'plugin'}_${installId}`
367+
}
368+
369+
const handleCopy = (item) => {
370+
try {
371+
if (typeof localStorage !== 'undefined' && localStorage.getItem('td_install_cmd_warning_shown')) {
372+
executeCopy(item)
373+
} else {
374+
pendingCmdItem.value = item
375+
warningVisible.value = true
376+
}
377+
} catch (err) {
378+
// localStorage might be disabled (Safari private mode, etc.)
379+
console.warn('localStorage unavailable, showing warning anyway:', err)
380+
pendingCmdItem.value = item
381+
warningVisible.value = true
382+
}
383+
}
384+
385+
const confirmWarning = () => {
386+
try {
387+
if (typeof localStorage !== 'undefined') {
388+
localStorage.setItem('td_install_cmd_warning_shown', 'true')
389+
}
390+
} catch (err) {
391+
console.warn('Could not save warning preference:', err)
392+
}
393+
warningVisible.value = false
394+
if (pendingCmdItem.value) {
395+
executeCopy(pendingCmdItem.value)
396+
pendingCmdItem.value = null
397+
}
398+
}
399+
400+
const executeCopy = async (item) => {
401+
const cmd = getInstallCmd(item)
402+
const stateKey = getCopyStateKey(item)
403+
try {
404+
await navigator.clipboard.writeText(cmd)
405+
copiedStates[stateKey] = true
406+
setTimeout(() => {
407+
copiedStates[stateKey] = false
408+
}, 2000)
409+
} catch (err) {
410+
console.error('Copy failed', err)
411+
// Fallback for older browsers
412+
alert('复制失败,请手动复制:' + cmd)
413+
}
414+
}
415+
315416
const openGithub = (item) => {
316417
if (!item) return
317418
const baseUrl = 'https://github.com/ToolDelta-Basic/PluginMarket/tree/main'
@@ -320,7 +421,7 @@ const openGithub = (item) => {
320421
const packageName = item.name.replace('[整合包] ', '')
321422
path = `/%5Bpkg%5D${encodeURIComponent(packageName)}`
322423
} else {
323-
const pluginFolderName = pluginNameMap.value[item.name]
424+
const pluginFolderName = pluginFolderMap.value[item.name]
324425
if (pluginFolderName) path = `/${encodeURIComponent(pluginFolderName)}`
325426
}
326427
if (path) window.open(baseUrl + path, '_blank')
@@ -354,40 +455,45 @@ onMounted(() => {
354455
return fetch('https://pm.tooldelta.top/plugin_ids_map.json')
355456
.then(r => r.json())
356457
.then(pluginIdsMap => {
357-
const packages = Object.entries(data.Packages).map(([key, val]) => ({
358-
name: `[整合包] ${key}`,
359-
originalName: key,
360-
author: val.author,
361-
version: val.version,
362-
description: val.description,
363-
isPackage: true,
364-
includedPlugins: (val['plugin-ids'] || []).map(id => {
365-
const p = data.MarketPlugins[id]
366-
return p
367-
? { name: p.name, author: p.author, version: p.version }
368-
: { name: id, author: '未知', version: '?' }
369-
})
370-
}))
458+
const packages = Object.entries(data.Packages).map(([key, val]) => {
459+
const displayName = `[整合包] ${key}`
460+
pluginNameMap.value[displayName] = key
461+
return {
462+
name: displayName,
463+
originalName: key,
464+
author: val.author,
465+
version: val.version,
466+
description: val.description,
467+
isPackage: true,
468+
includedPlugins: (val['plugin-ids'] || []).map(id => {
469+
const p = data.MarketPlugins[id]
470+
return p
471+
? { name: p.name, author: p.author, version: p.version }
472+
: { name: id, author: '未知', version: '?' }
473+
})
474+
}
475+
})
371476
372477
const plugins = []
373478
const descTasks = []
374479
375480
for (const pluginId in data.MarketPlugins) {
376481
const info = data.MarketPlugins[pluginId]
377-
const name = pluginIdsMap[pluginId]
378-
if (name) {
379-
pluginNameMap.value[info.name] = name
380-
plugins.push({ ...info, description: '加载中...', isPackage: false })
482+
const folderName = pluginIdsMap[pluginId]
483+
if (folderName) {
484+
pluginNameMap.value[info.name] = pluginId
485+
pluginFolderMap.value[info.name] = folderName
486+
plugins.push({ ...info, pluginId, description: '加载中...', isPackage: false })
381487
const targetIndex = packages.length + plugins.length - 1
382-
descTasks.push({ targetIndex, pluginName: name })
488+
descTasks.push({ targetIndex, folderName })
383489
}
384490
}
385491
386492
itemList.value = [...packages, ...plugins]
387493
loading.value = false
388494
389-
descTasks.forEach(({ targetIndex, pluginName }) => {
390-
fetch(`https://pm.tooldelta.top/${pluginName}/datas.json`)
495+
descTasks.forEach(({ targetIndex, folderName }) => {
496+
fetch(`https://pm.tooldelta.top/${folderName}/datas.json`)
391497
.then(r => r.json())
392498
.then(d => {
393499
const next = [...itemList.value]
@@ -638,6 +744,11 @@ onUnmounted(() => {
638744
border-radius: 0 0 12px 12px;
639745
}
640746
747+
.footer-actions {
748+
display: flex;
749+
gap: 8px;
750+
}
751+
641752
.version-tag {
642753
font-size: 12px;
643754
font-family: var(--vp-font-family-mono);
@@ -667,6 +778,10 @@ onUnmounted(() => {
667778
background: var(--vp-c-brand-dimm);
668779
}
669780
781+
.action-btn.copied {
782+
color: var(--vp-c-green-2, #10b981);
783+
}
784+
670785
/* Modal */
671786
.modal-overlay {
672787
position: fixed;
@@ -692,6 +807,10 @@ onUnmounted(() => {
692807
border: 1px solid var(--vp-c-divider);
693808
}
694809
810+
.warning-modal {
811+
max-width: 450px;
812+
}
813+
695814
.modal-header {
696815
padding: 24px;
697816
border-bottom: 1px solid var(--vp-c-divider);
@@ -899,6 +1018,27 @@ onUnmounted(() => {
8991018
background: var(--vp-c-brand-dark);
9001019
}
9011020
1021+
.full-width {
1022+
width: 100%;
1023+
justify-content: center;
1024+
}
1025+
1026+
.warning-text {
1027+
margin-bottom: 12px;
1028+
line-height: 1.6;
1029+
}
1030+
1031+
.command-preview {
1032+
margin-top: 16px;
1033+
padding: 12px;
1034+
background: var(--vp-c-bg-alt);
1035+
border-radius: 8px;
1036+
font-family: var(--vp-font-family-mono);
1037+
font-size: 13px;
1038+
word-break: break-all;
1039+
border: 1px solid var(--vp-c-divider);
1040+
}
1041+
9021042
/* Skeleton */
9031043
.skeleton-card {
9041044
cursor: default;

0 commit comments

Comments
 (0)