feat(web): prompt-templates view + chat/admin routes + nav + isAdmin guard

This commit is contained in:
2026-05-04 20:15:49 +08:00
parent 372af06b01
commit ff283013da
4 changed files with 284 additions and 1 deletions
@@ -0,0 +1,184 @@
<template>
<AppShell>
<div class="space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-lg font-semibold">
Prompt 模板
</h2>
<NButton
type="primary"
@click="openCreate"
>
+ 新增
</NButton>
</div>
<NDataTable
:data="templates"
:columns="columns"
:row-key="rowKey"
/>
<NModal
v-model:show="showForm"
preset="card"
:title="editing ? '编辑模板' : '新建模板'"
style="width: 640px"
>
<NForm>
<NFormItem label="名称">
<NInput v-model:value="form.name" />
</NFormItem>
<NFormItem label="作用域">
<NRadioGroup
v-model:value="form.scope"
:disabled="!!editing"
>
<NRadio value="user">
个人仅自己可见
</NRadio>
<NRadio
v-if="auth.isAdmin"
value="system"
>
系统所有用户可见
</NRadio>
</NRadioGroup>
</NFormItem>
<NFormItem label="内容">
<NInput
v-model:value="form.content"
type="textarea"
:rows="10"
/>
</NFormItem>
</NForm>
<template #footer>
<div class="flex justify-end gap-2">
<NButton @click="showForm = false">
取消
</NButton>
<NButton
type="primary"
:loading="saving"
@click="save"
>
保存
</NButton>
</div>
</template>
</NModal>
</div>
</AppShell>
</template>
<script setup lang="ts">
import { h, onMounted, ref } from 'vue'
import {
NButton,
NDataTable,
NForm,
NFormItem,
NInput,
NModal,
NRadio,
NRadioGroup,
useDialog,
useMessage,
} from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
import AppShell from '@/layouts/AppShell.vue'
import { chatApi, type PromptTemplate, type TemplateScope } from '@/api/chat'
import { useAuthStore } from '@/stores/auth'
const auth = useAuthStore()
const templates = ref<PromptTemplate[]>([])
const showForm = ref(false)
const editing = ref<PromptTemplate | null>(null)
const saving = ref(false)
const msg = useMessage()
const dlg = useDialog()
const form = ref<{ name: string; content: string; scope: TemplateScope }>({
name: '',
content: '',
scope: 'user',
})
const columns: DataTableColumns<PromptTemplate> = [
{ title: '名称', key: 'name' },
{ title: '作用域', key: 'scope' },
{
title: '操作',
key: 'actions',
render: (row) =>
h('div', { class: 'flex gap-1' }, [
h(
NButton,
{ size: 'small', onClick: () => openEdit(row) },
{ default: () => '编辑' },
),
h(
NButton,
{ size: 'small', type: 'error', onClick: () => del(row) },
{ default: () => '删除' },
),
]),
},
]
const rowKey = (row: PromptTemplate) => row.id
onMounted(reload)
async function reload() {
templates.value = await chatApi.listTemplates()
}
function openCreate() {
editing.value = null
form.value = { name: '', content: '', scope: 'user' }
showForm.value = true
}
function openEdit(t: PromptTemplate) {
editing.value = t
form.value = { name: t.name, content: t.content, scope: t.scope }
showForm.value = true
}
async function save() {
saving.value = true
try {
if (editing.value) {
await chatApi.updateTemplate(editing.value.id, {
name: form.value.name,
content: form.value.content,
})
} else {
await chatApi.createTemplate({
name: form.value.name,
content: form.value.content,
scope: form.value.scope,
})
}
showForm.value = false
await reload()
} catch (e) {
msg.error(e instanceof Error ? e.message : String(e))
} finally {
saving.value = false
}
}
function del(t: PromptTemplate) {
dlg.warning({
title: `删除 "${t.name}"?`,
positiveText: '删除',
negativeText: '取消',
onPositiveClick: async () => {
await chatApi.deleteTemplate(t.id)
await reload()
},
})
}
</script>