feat(aihr): align figma pages for release

This commit is contained in:
2026-07-14 02:04:36 +08:00
parent 922fe7ff29
commit 87360a25d3
51 changed files with 3575 additions and 629 deletions
+242
View File
@@ -0,0 +1,242 @@
<template>
<div class="prompt-page">
<div class="page-heading">
<div>
<h1>提示词模板管理</h1>
<p>统一维护训练、评分、复盘和准备卡模板</p>
</div>
<el-button type="danger" :icon="Plus" @click="createPrompt">新增模板</el-button>
</div>
<div class="prompt-layout">
<section class="list-card">
<h2>模板列表</h2>
<button
v-for="item in templates"
:key="item.id"
class="template-item"
:class="{ active: item.id === selectedId }"
@click="selectPrompt(item)"
>
<strong>{{ item.name }}</strong
><span>{{ item.code }}</span
><em>{{ item.enabled ? '已启用' : '已停用' }}</em>
</button>
<el-empty v-if="!loading && !templates.length" description="暂无模板" />
</section>
<section v-if="form" class="editor-card">
<div class="editor-heading">
<div>
<h2>编辑:{{ form.name }}</h2>
<p>版本 {{ form.version }} · 分类 {{ form.category }}</p>
</div>
<el-tag :type="form.enabled ? 'success' : 'info'">{{ form.enabled ? '启用' : '停用' }}</el-tag>
</div>
<div class="field-grid">
<el-form-item label="模板代码"><el-input v-model="form.code" :disabled="form.id > 0" /></el-form-item>
<el-form-item label="模板名称"><el-input v-model="form.name" /></el-form-item>
<el-form-item label="输出文体"><el-input v-model="form.outputFormat" /></el-form-item>
</div>
<el-form-item label="系统提示词"><el-input v-model="form.systemPrompt" type="textarea" :rows="3" /></el-form-item>
<el-form-item label="提示词模板"><el-input v-model="form.template" type="textarea" :rows="5" /></el-form-item>
<el-form-item label="模板变量"><el-input v-model="form.variables" placeholder='["scenario","dialogue","score"]' /></el-form-item>
<div class="check-card"><strong>变量校验</strong><span>保存前校验变量格式;未知变量不会发布到生产调用链。</span></div>
<div class="editor-actions">
<el-button @click="save(false)">保存草稿</el-button><el-button type="danger" :loading="saving" @click="save(true)">保存并启用</el-button>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import {
createPromptTemplate,
listPromptTemplates,
updatePromptTemplate,
type PromptTemplateRequest,
type PromptTemplateResponse
} from '@/api/aihr/practice';
const loading = ref(false);
const saving = ref(false);
const templates = ref<PromptTemplateResponse[]>([]);
const selectedId = ref(0);
const form = ref<PromptTemplateRequest & { id: number }>();
const clone = (item: PromptTemplateResponse): PromptTemplateRequest & { id: number } => ({ ...item });
const selectPrompt = (item: PromptTemplateResponse) => {
selectedId.value = item.id;
form.value = reactive(clone(item));
};
const load = async () => {
loading.value = true;
try {
const result = await listPromptTemplates();
templates.value = result.data ?? [];
if (!selectedId.value && templates.value[0]) selectPrompt(templates.value[0]);
} finally {
loading.value = false;
}
};
const createPrompt = () => {
selectedId.value = 0;
form.value = reactive({
id: 0,
code: '',
name: '新提示词模板',
category: 'practice',
outputFormat: 'summary_card',
systemPrompt: '',
template: '',
variables: '',
version: 'v1',
enabled: false,
description: ''
});
};
const save = async (enabled: boolean) => {
if (!form.value?.code.trim() || !form.value.name.trim() || !form.value.template.trim()) {
ElMessage.warning('请填写模板代码、名称和模板内容');
return;
}
saving.value = true;
try {
const payload = { ...form.value, enabled };
const result = form.value.id ? await updatePromptTemplate(form.value.id, payload) : await createPromptTemplate(payload);
ElMessage.success(enabled ? '模板已保存并启用' : '模板草稿已保存');
await load();
if (result.data) selectPrompt(result.data);
} finally {
saving.value = false;
}
};
onMounted(load);
</script>
<style scoped>
.prompt-page {
min-height: calc(100vh - 84px);
padding: 28px 32px 40px;
background: #f4f6fa;
color: #1d2635;
}
.page-heading,
.editor-heading,
.editor-actions {
display: flex;
align-items: center;
justify-content: space-between;
}
.page-heading {
margin-bottom: 22px;
}
h1,
h2,
p {
margin: 0;
}
h1 {
font-size: 28px;
font-weight: 600;
}
h2 {
font-size: 20px;
}
.page-heading p,
.editor-heading p {
margin-top: 8px;
color: #858e9e;
font-size: 13px;
}
.prompt-layout {
display: grid;
grid-template-columns: 410px minmax(0, 1fr);
gap: 18px;
align-items: start;
}
.list-card,
.editor-card {
min-height: 520px;
padding: 18px 16px;
border-radius: 8px;
background: #fff;
box-shadow: 0 8px 24px rgb(33 43 54 / 4%);
}
.list-card h2 {
margin-bottom: 16px;
}
.template-item {
position: relative;
display: block;
width: 100%;
margin-bottom: 9px;
padding: 15px 14px;
border: 0;
border-radius: 8px;
background: #fafbfc;
color: #253044;
text-align: left;
cursor: pointer;
}
.template-item.active {
background: #fff0f1;
color: #e60012;
}
.template-item strong,
.template-item span {
display: block;
}
.template-item span {
margin-top: 7px;
color: #858e9e;
font-size: 12px;
}
.template-item em {
position: absolute;
top: 20px;
right: 14px;
color: #16a05d;
font-size: 12px;
font-style: normal;
}
.editor-card {
padding: 20px;
}
.editor-heading {
align-items: flex-start;
margin-bottom: 18px;
}
.field-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.check-card {
display: flex;
flex-direction: column;
gap: 8px;
margin: 10px 0 14px;
padding: 16px;
border-radius: 8px;
background: #f7f9fb;
}
.check-card span {
color: #858e9e;
font-size: 12px;
}
.editor-actions {
gap: 10px;
}
.editor-actions .el-button:last-child {
flex: 1;
}
@media (max-width: 900px) {
.prompt-layout,
.field-grid {
grid-template-columns: 1fr;
}
}
</style>