更新若干功能
This commit is contained in:
131
frontend/src/components/PackCmdsModal.vue
Normal file
131
frontend/src/components/PackCmdsModal.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user