Files
code-utils/frontend/src/components/PackCmdsModal.vue
2026-08-15 17:18:00 +08:00

132 lines
5.2 KiB
Vue

<script setup>
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { Package, Plus, Trash2, X, Terminal, Sparkles } from 'lucide-vue-next'
import { getPackCmds, setPackCmds } from '../packCmds'
import { call } from '../api'
const props = defineProps({
open: Boolean,
scope: { type: String, required: true }, // lp | proj
targetId: { type: [Number, String], required: true },
title: { type: String, default: '' },
dir: { type: String, default: '' }
})
const emit = defineEmits(['close', 'saved'])
const { t } = useI18n()
const rows = ref([])
const suggest = ref({ kind: '', label: '', items: [] })
const suggestLoading = ref(false)
watch(() => [props.open, props.scope, props.targetId, props.dir], async () => {
if (!props.open) return
const list = getPackCmds(props.scope, props.targetId)
rows.value = list.length
? list.map(x => ({ name: x.name || '', cmd: x.cmd || '' }))
: [{ name: '', cmd: '' }]
suggest.value = { kind: '', label: '', items: [] }
if (!props.dir) return
suggestLoading.value = true
try {
suggest.value = await call('SuggestPackCommands', props.dir) || { kind: '', label: '', items: [] }
} catch {
suggest.value = { kind: '', label: '', items: [] }
}
suggestLoading.value = false
}, { immediate: true })
const canSave = computed(() => rows.value.some(r => String(r.cmd || '').trim()))
const validCount = computed(() => rows.value.filter(r => String(r.cmd || '').trim()).length)
const suggestItems = computed(() => suggest.value?.items || [])
function addRow() {
rows.value.push({ name: '', cmd: '' })
}
function removeRow(i) {
rows.value.splice(i, 1)
if (!rows.value.length) rows.value.push({ name: '', cmd: '' })
}
function applySuggest(item) {
if (!item?.cmd) return
const empty = rows.value.findIndex(r => !String(r.cmd || '').trim())
const row = { name: item.name || '', cmd: item.cmd }
if (empty >= 0) rows.value[empty] = row
else rows.value.push(row)
}
function applyAllSuggest() {
for (const it of suggestItems.value) applySuggest(it)
}
function save() {
setPackCmds(props.scope, props.targetId, rows.value)
emit('saved')
emit('close')
}
</script>
<template>
<Teleport to="body">
<div v-if="open" class="overlay" @click.self="emit('close')">
<section class="modal pack-cmds-modal" @click.stop>
<header class="pack-cmds-head">
<div class="pack-cmds-brand">
<span class="pack-cmds-ico"><Package /></span>
<div>
<h2>{{ title || t('packCmdsTitle') }}</h2>
<p>{{ t('packCmdsHint') }}</p>
</div>
</div>
<button type="button" class="nm-close" :title="t('close')" @click="emit('close')"><X /></button>
</header>
<div class="pack-cmds-body">
<div v-if="dir" class="pack-suggest">
<div class="pack-suggest-top">
<b><Sparkles />{{ t('packSuggestTitle') }}</b>
<em v-if="suggestLoading">{{ t('packSuggestLoading') }}</em>
<em v-else-if="suggest.label" class="pack-suggest-kind">{{ suggest.label }}</em>
<button v-if="suggestItems.length" type="button" class="pack-suggest-all" @click="applyAllSuggest">{{ t('packSuggestAll') }}</button>
</div>
<div v-if="suggestItems.length" class="pack-suggest-chips">
<button v-for="(it, i) in suggestItems" :key="i" type="button" :title="it.cmd" @click="applySuggest(it)">
<span>{{ it.name }}</span>
<code>{{ it.cmd }}</code>
</button>
</div>
<p v-else-if="!suggestLoading" class="pack-suggest-empty">{{ t('packSuggestEmpty') }}</p>
</div>
<div class="pack-cmds-meta">
<b>{{ t('packCmdsList') }}</b>
<em>{{ t('packCmdsCount', { n: validCount }) }}</em>
</div>
<div class="pack-cmds-list">
<article v-for="(r, i) in rows" :key="i" class="pack-cmd-card">
<div class="pack-cmd-card-top">
<span class="pack-cmd-idx">{{ i + 1 }}</span>
<button type="button" class="pack-cmd-del" :title="t('delete')" @click="removeRow(i)"><Trash2 /></button>
</div>
<label class="pack-cmd-labeled">
<span>{{ t('packCmdNameLabel') }}</span>
<input v-model.trim="r.name" :placeholder="t('packCmdNamePh')" />
</label>
<label class="pack-cmd-labeled">
<span>{{ t('packCmdCmdLabel') }}</span>
<span class="pack-cmd-field">
<Terminal />
<input v-model.trim="r.cmd" class="pack-cmd-term" spellcheck="false" :placeholder="t('packCmdPh')" />
</span>
</label>
</article>
</div>
<button type="button" class="pack-cmd-add" @click="addRow"><Plus />{{ t('packCmdAdd') }}</button>
</div>
<footer class="pack-cmds-foot">
<button type="button" class="btn secondary" @click="emit('close')">{{ t('cancel') }}</button>
<button type="button" class="btn primary" :disabled="!canSave" @click="save">{{ t('save') }}</button>
</footer>
</section>
</div>
</Teleport>
</template>