fix: language deps style

This commit is contained in:
engigu
2026-03-02 21:59:16 +08:00
parent d8f1b8b50c
commit 156a384fee
5 changed files with 142 additions and 82 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./web/tsconfig.app.json"
},
{
"path": "./web/tsconfig.node.json"
}
]
}
+4 -1
View File
@@ -404,7 +404,10 @@ onMounted(async () => {
<!-- 终端对话框 --> <!-- 终端对话框 -->
<Dialog v-model:open="showTerminalDialog" @update:open="(val) => !val && handleTerminalClose()"> <Dialog v-model:open="showTerminalDialog" @update:open="(val) => !val && handleTerminalClose()">
<DialogContent class="max-w-4xl h-[600px] p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl"> <DialogContent
class="w-[calc(100%-2rem)] sm:max-w-[90vw] lg:max-w-4xl xl:max-w-5xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl"
:show-close-button="false" @interact-outside="(e) => e.preventDefault()"
@escape-key-down="(e) => e.preventDefault()">
<DialogHeader class="sr-only"> <DialogHeader class="sr-only">
<DialogTitle>{{ terminalTitle }}</DialogTitle> <DialogTitle>{{ terminalTitle }}</DialogTitle>
<DialogDescription>正在执行依赖安装指令</DialogDescription> <DialogDescription>正在执行依赖安装指令</DialogDescription>
+2 -1
View File
@@ -616,7 +616,8 @@ onUnmounted(() => {
<Dialog v-model:open="showTerminalDialog"> <Dialog v-model:open="showTerminalDialog">
<DialogContent <DialogContent
class="w-[calc(100%-2rem)] sm:max-w-3xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden !bg-[#1e1e1e] border-[#3c3c3c]" class="w-[calc(100%-2rem)] sm:max-w-3xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden !bg-[#1e1e1e] border-[#3c3c3c]"
:show-close-button="false" @openAutoFocus.prevent> :show-close-button="false" @openAutoFocus.prevent
@interact-outside="(e) => e.preventDefault()" @escape-key-down="(e) => e.preventDefault()">
<div class="flex items-center justify-between px-3 sm:px-4 py-2 border-b border-[#3c3c3c]"> <div class="flex items-center justify-between px-3 sm:px-4 py-2 border-b border-[#3c3c3c]">
<span class="text-xs sm:text-sm font-medium text-gray-300">运行脚本</span> <span class="text-xs sm:text-sm font-medium text-gray-300">运行脚本</span>
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white hover:bg-white/10" <Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white hover:bg-white/10"
+107 -76
View File
@@ -64,6 +64,7 @@ const filteredVersions = computed(() => {
const showTerminalDialog = ref(false) const showTerminalDialog = ref(false)
const terminalCommand = ref('') const terminalCommand = ref('')
const isInstallSuccess = ref(false)
const filteredLanguages = computed(() => { const filteredLanguages = computed(() => {
if (!searchQuery.value) return languages.value if (!searchQuery.value) return languages.value
@@ -169,6 +170,7 @@ function startInstall() {
function runInTerminal(command: string) { function runInTerminal(command: string) {
terminalCommand.value = command terminalCommand.value = command
isInstallSuccess.value = false
showTerminalDialog.value = true showTerminalDialog.value = true
} }
@@ -177,6 +179,21 @@ function confirmDelete(lang: MiseLanguage) {
runInTerminal(cmd) runInTerminal(cmd)
} }
async function handleTerminalClose() {
showTerminalDialog.value = false
// 无论成功失败,都触发环境同步并刷新列表
syncing.value = true
try {
await api.mise.sync()
toast.success('环境同步完成')
} catch (e) {
toast.error('环境同步失败: ' + e)
} finally {
syncing.value = false
}
await loadLanguages()
}
function getLangIcon(plugin: string) { function getLangIcon(plugin: string) {
const name = plugin.toLowerCase().trim() const name = plugin.toLowerCase().trim()
const mapping: Record<string, string> = { const mapping: Record<string, string> = {
@@ -278,56 +295,61 @@ onMounted(loadLanguages)
<Globe class="h-12 w-12 mx-auto mb-2 opacity-10" /> <Globe class="h-12 w-12 mx-auto mb-2 opacity-10" />
{{ searchQuery ? '未找到匹配的语言' : '未发现已安装的语言' }} {{ searchQuery ? '未找到匹配的语言' : '未发现已安装的语言' }}
</div> </div>
<div v-else v-for="lang in filteredLanguages" :key="lang.plugin + lang.version" <template v-else>
class="flex flex-col sm:flex-row sm:items-center justify-between px-4 py-4 hover:bg-muted/50 transition-colors gap-4"> <div v-for="lang in filteredLanguages" :key="lang.plugin + lang.version"
<div class="flex items-center gap-4 min-w-0"> class="flex flex-col sm:flex-row sm:items-center justify-between px-4 py-4 hover:bg-muted/50 transition-colors gap-4">
<div class="flex items-center gap-4 min-w-0">
<div
class="h-9 w-9 sm:h-10 sm:w-10 rounded-full bg-primary/10 flex items-center justify-center font-bold text-primary uppercase overflow-hidden shrink-0">
<template v-if="getLangIcon(lang.plugin)">
<div class="w-full h-full bg-white/80 p-2 flex items-center justify-center">
<img :src="getLangIcon(lang.plugin)" :alt="lang.plugin"
class="w-full h-full object-contain" />
</div>
</template>
<template v-else>
{{ lang.plugin.length > 2 ? lang.plugin.substring(0, 2) : lang.plugin }}
</template>
</div>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2">
<span class="font-bold capitalize truncate">{{ lang.plugin }}</span>
<Badge variant="outline" class="font-mono whitespace-nowrap">{{ lang.version }}
</Badge>
</div>
<div class="text-xs text-muted-foreground mt-1 space-y-0.5">
<div class="font-mono opacity-60 truncate" :title="lang.source">来源: {{ lang.source
}}
</div>
<div v-if="lang.installed_at" class="opacity-50">
添加日期: {{ lang.installed_at }}
</div>
</div>
</div>
</div>
<div <div
class="h-9 w-9 sm:h-10 sm:w-10 rounded-full bg-primary/10 flex items-center justify-center font-bold text-primary uppercase overflow-hidden shrink-0"> class="flex items-center gap-2 sm:ml-auto w-full sm:w-auto overflow-x-auto pb-1 sm:pb-0 hide-scrollbar">
<template v-if="getLangIcon(lang.plugin)"> <Button v-if="SUPPORTED_DEPS_LANGS.includes(lang.plugin)" variant="outline" size="sm"
<div class="w-full h-full bg-white/80 p-2 flex items-center justify-center"> class="whitespace-nowrap flex-1 sm:flex-none"
<img :src="getLangIcon(lang.plugin)" :alt="lang.plugin" @click="$router.push(`/dependencies?language=${lang.plugin}&version=${lang.version}`)">
class="w-full h-full object-contain" /> 依赖管理
</div> </Button>
</template> <Badge v-else variant="secondary"
<template v-else> class="h-8 opacity-60 flex-1 sm:flex-none justify-center whitespace-nowrap">
{{ lang.plugin.length > 2 ? lang.plugin.substring(0, 2) : lang.plugin }} 不支持管理
</template> </Badge>
</div> <Button variant="outline" size="sm" class="whitespace-nowrap flex-1 sm:flex-none"
<div class="min-w-0 flex-1"> @click="handleVerify(lang)">
<div class="flex items-center gap-2"> 环境验证
<span class="font-bold capitalize truncate">{{ lang.plugin }}</span> </Button>
<Badge variant="outline" class="font-mono whitespace-nowrap">{{ lang.version }}</Badge> <Button variant="ghost" size="icon"
</div> class="text-destructive h-8 w-8 shrink-0 ml-auto sm:ml-0" @click="confirmDelete(lang)"
<div class="text-xs text-muted-foreground mt-1 space-y-0.5"> title="卸载">
<div class="font-mono opacity-60 truncate" :title="lang.source">来源: {{ lang.source }} <Trash2 class="h-4 w-4" />
</div> </Button>
<div v-if="lang.installed_at" class="opacity-50">
添加日期: {{ lang.installed_at }}
</div>
</div>
</div> </div>
</div> </div>
<div </template>
class="flex items-center gap-2 sm:ml-auto w-full sm:w-auto overflow-x-auto pb-1 sm:pb-0 hide-scrollbar">
<Button v-if="SUPPORTED_DEPS_LANGS.includes(lang.plugin)" variant="outline" size="sm"
class="whitespace-nowrap flex-1 sm:flex-none"
@click="$router.push(`/dependencies?language=${lang.plugin}&version=${lang.version}`)">
依赖管理
</Button>
<Badge v-else variant="secondary"
class="h-8 opacity-60 flex-1 sm:flex-none justify-center whitespace-nowrap">
不支持管理
</Badge>
<Button variant="outline" size="sm" class="whitespace-nowrap flex-1 sm:flex-none"
@click="handleVerify(lang)">
环境验证
</Button>
<Button variant="ghost" size="icon" class="text-destructive h-8 w-8 shrink-0 ml-auto sm:ml-0"
@click="confirmDelete(lang)" title="卸载">
<Trash2 class="h-4 w-4" />
</Button>
</div>
</div>
</div> </div>
</div> </div>
@@ -368,25 +390,28 @@ onMounted(loadLanguages)
class="py-6 text-center text-xs text-muted-foreground"> class="py-6 text-center text-xs text-muted-foreground">
未找到匹配插件 未找到匹配插件
</div> </div>
<button v-else v-for="p in filteredPlugins" :key="p" <template v-else>
@click="() => { newLangPlugin = p; openPluginPopover = false }" <button v-for="p in filteredPlugins" :key="p"
class="w-full flex items-center px-2 py-1.5 text-sm rounded-sm hover:bg-muted text-left transition-colors group"> @click="() => { newLangPlugin = p; openPluginPopover = false }"
<div class="w-full flex items-center px-2 py-1.5 text-sm rounded-sm hover:bg-muted text-left transition-colors group">
class="mr-2 h-4 w-4 shrink-0 flex items-center justify-center relative"> <div
<div v-if="getLangIcon(p)" class="mr-2 h-4 w-4 shrink-0 flex items-center justify-center relative">
class="w-full h-full rounded-sm bg-white/80 overflow-hidden p-0.5"> <div v-if="getLangIcon(p)"
<img :src="getLangIcon(p)" class="w-full h-full object-contain" /> class="w-full h-full rounded-sm bg-white/80 overflow-hidden p-0.5">
<img :src="getLangIcon(p)"
class="w-full h-full object-contain" />
</div>
<div v-else
class="w-full h-full flex items-center justify-center bg-primary/10 rounded-sm text-[8px] font-bold uppercase">
{{ p.substring(0, 2) }}
</div>
<Check v-if="newLangPlugin === p"
class="absolute -right-2 -top-1 h-3 w-3 text-primary bg-background rounded-full border shadow-sm" />
</div> </div>
<div v-else <span :class="{ 'font-bold text-primary': newLangPlugin === p }">{{ p
class="w-full h-full flex items-center justify-center bg-primary/10 rounded-sm text-[8px] font-bold uppercase"> }}</span>
{{ p.substring(0, 2) }} </button>
</div> </template>
<Check v-if="newLangPlugin === p"
class="absolute -right-2 -top-1 h-3 w-3 text-primary bg-background rounded-full border shadow-sm" />
</div>
<span :class="{ 'font-bold text-primary': newLangPlugin === p }">{{ p
}}</span>
</button>
</div> </div>
</ScrollArea> </ScrollArea>
</PopoverContent> </PopoverContent>
@@ -425,13 +450,15 @@ onMounted(loadLanguages)
class="py-6 text-center text-xs text-muted-foreground"> class="py-6 text-center text-xs text-muted-foreground">
未找到匹配版本 未找到匹配版本
</div> </div>
<button v-else v-for="v in filteredVersions" :key="v" <template v-else>
@click="() => { newLangVersion = v; openVersionPopover = false }" <button v-for="v in filteredVersions" :key="v"
class="w-full flex items-center px-2 py-1.5 text-sm rounded-sm hover:bg-muted text-left transition-colors"> @click="() => { newLangVersion = v; openVersionPopover = false }"
<Check class="w-full flex items-center px-2 py-1.5 text-sm rounded-sm hover:bg-muted text-left transition-colors">
:class="cn('mr-2 h-3.5 w-3.5', newLangVersion === v ? 'opacity-100' : 'opacity-0')" /> <Check
{{ v }} :class="cn('mr-2 h-3.5 w-3.5', newLangVersion === v ? 'opacity-100' : 'opacity-0')" />
</button> {{ v }}
</button>
</template>
</div> </div>
</ScrollArea> </ScrollArea>
</PopoverContent> </PopoverContent>
@@ -446,8 +473,11 @@ onMounted(loadLanguages)
</Dialog> </Dialog>
<!-- 终端对话框 --> <!-- 终端对话框 -->
<Dialog v-model:open="showTerminalDialog" @update:open="(val) => !val && loadLanguages()"> <Dialog v-model:open="showTerminalDialog">
<DialogContent class="max-w-4xl h-[600px] p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl"> <DialogContent
class="w-[calc(100%-2rem)] sm:max-w-[90vw] lg:max-w-4xl xl:max-w-5xl h-[60vh] sm:h-[70vh] flex flex-col p-0 overflow-hidden bg-[#1e1e1e] border-none shadow-2xl"
:show-close-button="false" @interact-outside="(e) => e.preventDefault()"
@escape-key-down="(e) => e.preventDefault()">
<DialogHeader class="sr-only"> <DialogHeader class="sr-only">
<DialogTitle>终端执行</DialogTitle> <DialogTitle>终端执行</DialogTitle>
<DialogDescription>正在执行 mise 相关指令</DialogDescription> <DialogDescription>正在执行 mise 相关指令</DialogDescription>
@@ -459,12 +489,13 @@ onMounted(loadLanguages)
<span class="text-xs font-medium text-gray-300">正在安装 / 执行: {{ terminalCommand }}</span> <span class="text-xs font-medium text-gray-300">正在安装 / 执行: {{ terminalCommand }}</span>
</div> </div>
<Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white" <Button variant="ghost" size="icon" class="h-6 w-6 text-gray-400 hover:text-white"
@click="showTerminalDialog = false"> @click="handleTerminalClose">
<X class="h-4 w-4" /> <X class="h-4 w-4" />
</Button> </Button>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<XTerminal v-if="showTerminalDialog" :font-size="13" :initial-command="terminalCommand" /> <XTerminal v-if="showTerminalDialog" :font-size="13" :initial-command="terminalCommand"
@success="isInstallSuccess = true" @failed="isInstallSuccess = false" />
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
+18 -4
View File
@@ -2,10 +2,20 @@
"extends": "@vue/tsconfig/tsconfig.dom.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"], "target": "ESNext",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"types": [
"vite/client"
],
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": [
"src/*"
]
}, },
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
@@ -14,5 +24,9 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
}, },
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] "include": [
} "src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}