|
|
<!-- 能源报告模板详情页(只读) -->
|
|
|
<template>
|
|
|
<div class="template-detail-page">
|
|
|
<!-- 顶部导航栏 -->
|
|
|
<div class="page-header">
|
|
|
<div class="header-left">
|
|
|
<ElButton text :icon="ArrowLeft" @click="goBack">返回</ElButton>
|
|
|
<span class="page-title">{{ template?.template_name || "加载中..." }}</span>
|
|
|
<ElTag v-if="template" type="info" effect="plain" size="small" class="readonly-tag">
|
|
|
只读模式
|
|
|
</ElTag>
|
|
|
</div>
|
|
|
<div class="header-right">
|
|
|
<ElButton type="primary" :icon="Edit" @click="goEdit">编辑模板</ElButton>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 三栏主体 -->
|
|
|
<div class="page-body">
|
|
|
<!-- 中间:文档预览(左+中由 FaDocxPreview 组件一体呈现,readonly 模式) -->
|
|
|
<div class="preview-wrapper">
|
|
|
<FaDocxPreview
|
|
|
v-if="template && docxSrc"
|
|
|
ref="previewRef"
|
|
|
:src="docxSrc"
|
|
|
:filename="template.original_name || template.template_name"
|
|
|
readonly
|
|
|
@ready="handlePreviewReady"
|
|
|
@heading-click="handleHeadingClick"
|
|
|
/>
|
|
|
<div v-else v-loading="loading" class="preview-loading">
|
|
|
<ElEmpty v-if="!loading" description="模板不存在" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧栏 -->
|
|
|
<aside class="right-panel">
|
|
|
<!-- 标注列表视图 -->
|
|
|
<div v-if="rightPanel === 'list'" class="panel-scroll">
|
|
|
<div class="panel-title">
|
|
|
<span>
|
|
|
<ElIcon><CollectionTag /></ElIcon>
|
|
|
标注列表
|
|
|
</span>
|
|
|
<span class="panel-count">共 {{ annotations.length }} 项</span>
|
|
|
</div>
|
|
|
<div v-if="annotations.length === 0" class="empty-panel">
|
|
|
<ElEmpty description="暂无标注" :image-size="80" />
|
|
|
</div>
|
|
|
<div v-else class="annotation-card-list">
|
|
|
<div
|
|
|
v-for="ann in annotations"
|
|
|
:key="ann.id"
|
|
|
class="annotation-card"
|
|
|
:class="{ active: activeId === ann.id }"
|
|
|
@click="focusAnnotation(ann)"
|
|
|
>
|
|
|
<div class="card-row">
|
|
|
<span class="label">章节</span>
|
|
|
<span class="chapter-link" :title="ann.chapter_name">
|
|
|
{{ ann.chapter_name }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="card-row">
|
|
|
<span class="label">关联规则</span>
|
|
|
<span class="rule-link" :title="ann.rule_name">
|
|
|
{{ ann.rule_name || "-" }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div v-if="ann.description" class="card-desc" :title="ann.description">
|
|
|
<span class="desc-text">{{ ann.description }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 标注详情视图(只读) -->
|
|
|
<div
|
|
|
v-else-if="rightPanel === 'detail' && activeAnnotation"
|
|
|
class="panel-scroll"
|
|
|
v-loading="detailLoading"
|
|
|
>
|
|
|
<div class="panel-title config-title">
|
|
|
<div class="selected-heading">
|
|
|
<span class="label">选中内容:</span>
|
|
|
<span class="heading-text">{{ activeAnnotation.chapter_name }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 关联规则 -->
|
|
|
<div class="config-section">
|
|
|
<div class="config-label">
|
|
|
关联规则:
|
|
|
<span class="rule-name">{{ activeAnnotation.rule_name || "未选择" }}</span>
|
|
|
</div>
|
|
|
<div v-if="activeAnnotation.rule_category_name" class="config-row">
|
|
|
<span class="sub-label">规则分类:</span>
|
|
|
<span>{{ activeAnnotation.rule_category_name }}</span>
|
|
|
</div>
|
|
|
<div v-if="activeAnnotation.rule_description" class="config-row">
|
|
|
<span class="sub-label">规则描述:</span>
|
|
|
<span>{{ activeAnnotation.rule_description }}</span>
|
|
|
</div>
|
|
|
<div v-if="activeAnnotation.rule_prompt_template" class="config-prompt">
|
|
|
<div class="prompt-header">
|
|
|
<span class="sub-label">提示词:</span>
|
|
|
<ElButton link size="small" :icon="View" @click="showRuleDetail">查看完整提示词</ElButton>
|
|
|
</div>
|
|
|
<pre>{{ truncatePrompt(activeAnnotation.rule_prompt_template) }}</pre>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 关联变量绑定(只读) -->
|
|
|
<div class="config-section">
|
|
|
<div class="config-label">关联变量绑定</div>
|
|
|
<div v-if="promptVariables.length === 0" class="no-var-hint">
|
|
|
该规则提示词中暂无变量
|
|
|
</div>
|
|
|
<div v-else class="var-bind-list">
|
|
|
<div
|
|
|
v-for="(varName, idx) in promptVariables"
|
|
|
:key="varName"
|
|
|
class="var-bind-row"
|
|
|
>
|
|
|
<span class="var-index">{{ idx + 1 }}</span>
|
|
|
<span class="var-name">{{ varName }}</span>
|
|
|
<div class="var-tags">
|
|
|
<template v-if="variableBindings[varName]?.length">
|
|
|
<ElTag
|
|
|
v-for="cn in variableBindings[varName]"
|
|
|
:key="cn"
|
|
|
size="small"
|
|
|
type="info"
|
|
|
effect="plain"
|
|
|
class="var-tag"
|
|
|
>
|
|
|
{{ cn }}
|
|
|
</ElTag>
|
|
|
</template>
|
|
|
<span v-else class="no-bind">未绑定</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="config-footer">
|
|
|
<ElButton @click="switchPanel('list')">关闭</ElButton>
|
|
|
</div>
|
|
|
</div>
|
|
|
</aside>
|
|
|
</div>
|
|
|
|
|
|
<!-- 规则详情弹窗 -->
|
|
|
<ElDialog
|
|
|
v-model="ruleDetailVisible"
|
|
|
title="规则详情"
|
|
|
width="600px"
|
|
|
destroy-on-close
|
|
|
>
|
|
|
<el-descriptions v-if="activeAnnotation" :column="1" border label-width="120px">
|
|
|
<el-descriptions-item label="规则名称">{{ activeAnnotation.rule_name }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="规则分类">
|
|
|
{{ activeAnnotation.rule_category_name || "-" }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="规则描述">{{ activeAnnotation.rule_description || "-" }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="提示词模板">
|
|
|
<pre class="detail-prompt">{{ activeAnnotation.rule_prompt_template }}</pre>
|
|
|
</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
<template #footer>
|
|
|
<ElButton type="primary" @click="ruleDetailVisible = false">关闭</ElButton>
|
|
|
</template>
|
|
|
</ElDialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
defineOptions({ name: "EnergyReportTemplateDetail" });
|
|
|
|
|
|
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
import { ArrowLeft, Edit, CollectionTag, View } from "@element-plus/icons-vue";
|
|
|
import FaDocxPreview from "@/components/business/fa-docx-preview/index.vue";
|
|
|
import type { DocxOutlineItem } from "@/components/business/fa-docx-preview/useDocxOutline";
|
|
|
import { ReportTemplateAPI, ChapterAnnotationAPI } from "@/api/module_report/report";
|
|
|
import type {
|
|
|
ReportTemplateItem,
|
|
|
ChapterAnnotationItem,
|
|
|
ChapterVariableDetail,
|
|
|
ReportType,
|
|
|
} from "@/api/module_report/report";
|
|
|
import { RuleAPI } from "@/api/module_rule/rule";
|
|
|
import type { RuleItem } from "@/api/module_rule/rule";
|
|
|
import { extractPromptVariables } from "../promptVars";
|
|
|
|
|
|
const route = useRoute();
|
|
|
const router = useRouter();
|
|
|
|
|
|
const templateId = computed(() => {
|
|
|
const v = route.query.id;
|
|
|
const n = Number(v);
|
|
|
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
|
});
|
|
|
|
|
|
/** 报告类型:1 能源报告 / 2 光伏报告(由路由参数传入) */
|
|
|
const reportType = computed<ReportType>(() => {
|
|
|
const t = route.query.report_type;
|
|
|
return t === "2" ? 2 : 1;
|
|
|
});
|
|
|
|
|
|
const loading = ref(false);
|
|
|
const detailLoading = ref(false);
|
|
|
const template = ref<ReportTemplateItem | null>(null);
|
|
|
const docxSrc = ref<string>("");
|
|
|
const previewRef = ref<InstanceType<typeof FaDocxPreview> | null>(null);
|
|
|
|
|
|
const outlineItems = ref<DocxOutlineItem[]>([]);
|
|
|
const annotations = ref<ChapterAnnotationItem[]>([]);
|
|
|
const activeId = ref<number | undefined>(undefined);
|
|
|
|
|
|
/** 当前查看的标注(含懒加载的规则详情) */
|
|
|
interface ActiveAnnotation {
|
|
|
id: number;
|
|
|
chapter_name: string;
|
|
|
rule_id: number;
|
|
|
rule_name?: string;
|
|
|
rule_category_name?: string;
|
|
|
rule_description?: string;
|
|
|
rule_prompt_template?: string;
|
|
|
}
|
|
|
|
|
|
const rightPanel = ref<"list" | "detail">("list");
|
|
|
const activeAnnotation = ref<ActiveAnnotation | null>(null);
|
|
|
/** 变量绑定:variable_name → chapter_names 数组(只读展示) */
|
|
|
const variableBindings = reactive<Record<string, string[]>>({});
|
|
|
|
|
|
const promptVariables = computed(() => {
|
|
|
if (!activeAnnotation.value?.rule_prompt_template) return [];
|
|
|
return extractPromptVariables(activeAnnotation.value.rule_prompt_template);
|
|
|
});
|
|
|
|
|
|
const ruleDetailVisible = ref(false);
|
|
|
|
|
|
async function loadTemplate() {
|
|
|
if (!templateId.value) return;
|
|
|
loading.value = true;
|
|
|
try {
|
|
|
const resp = await ReportTemplateAPI.detail(templateId.value, reportType.value);
|
|
|
const data = resp?.data?.data;
|
|
|
if (data) {
|
|
|
template.value = data;
|
|
|
docxSrc.value = data.file_url || "";
|
|
|
}
|
|
|
} catch (e) {
|
|
|
template.value = null;
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function loadAnnotations() {
|
|
|
if (!templateId.value) return;
|
|
|
try {
|
|
|
const resp = await ChapterAnnotationAPI.listByTemplate(templateId.value, reportType.value);
|
|
|
const list = resp?.data?.data;
|
|
|
annotations.value = Array.isArray(list) ? list : [];
|
|
|
} catch (e) {
|
|
|
annotations.value = [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function handlePreviewReady(payload: { outline: DocxOutlineItem[] }) {
|
|
|
outlineItems.value = payload.outline;
|
|
|
}
|
|
|
|
|
|
function handleHeadingClick(item: DocxOutlineItem) {
|
|
|
const exist = annotations.value.find((a) => a.chapter_name === item.text);
|
|
|
if (exist) {
|
|
|
focusAnnotation(exist);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** 通过章节名称查找大纲项 id(用于滚动定位) */
|
|
|
function findOutlineIdByChapterName(name: string): string | undefined {
|
|
|
return outlineItems.value.find((i) => i.text === name)?.id;
|
|
|
}
|
|
|
|
|
|
/** 点击标注卡片:加载规则详情和变量绑定,切换到详情视图 */
|
|
|
async function focusAnnotation(ann: ChapterAnnotationItem) {
|
|
|
activeId.value = ann.id;
|
|
|
activeAnnotation.value = {
|
|
|
id: ann.id,
|
|
|
chapter_name: ann.chapter_name,
|
|
|
rule_id: ann.rule_id,
|
|
|
rule_name: ann.rule_name,
|
|
|
};
|
|
|
rightPanel.value = "detail";
|
|
|
detailLoading.value = true;
|
|
|
|
|
|
// 滚动到对应章节
|
|
|
const outlineId = findOutlineIdByChapterName(ann.chapter_name);
|
|
|
if (outlineId) {
|
|
|
nextTick(() => {
|
|
|
previewRef.value?.scrollToHeading(outlineId);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 并行加载变量绑定和规则详情
|
|
|
const [varResp, ruleResp] = await Promise.all([
|
|
|
ChapterAnnotationAPI.getVariableConfig(ann.id),
|
|
|
RuleAPI.detail(ann.rule_id),
|
|
|
]);
|
|
|
|
|
|
const varData: ChapterVariableDetail | undefined = varResp?.data?.data;
|
|
|
const ruleData: RuleItem | undefined = ruleResp?.data?.data;
|
|
|
|
|
|
// 更新当前标注的规则详情
|
|
|
if (ruleData && activeAnnotation.value?.id === ann.id) {
|
|
|
activeAnnotation.value = {
|
|
|
...activeAnnotation.value,
|
|
|
rule_name: ruleData.name,
|
|
|
rule_category_name: ruleData.category_name || ruleData.category_name_snapshot,
|
|
|
rule_description: ruleData.description,
|
|
|
rule_prompt_template: ruleData.prompt_template,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
// 合并变量:从前端提取的完整变量列表 + 后端返回的已绑定变量
|
|
|
for (const key of Object.keys(variableBindings)) delete variableBindings[key];
|
|
|
const allVars = extractPromptVariables(ruleData?.prompt_template || "");
|
|
|
const apiVars = varData?.variables || [];
|
|
|
for (const v of allVars) {
|
|
|
const apiVar = apiVars.find((av) => av.variable_name === v);
|
|
|
variableBindings[v] = apiVar?.chapter_names ? [...apiVar.chapter_names] : [];
|
|
|
}
|
|
|
} catch (e) {
|
|
|
// 错误由全局拦截器处理
|
|
|
} finally {
|
|
|
detailLoading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function switchPanel(p: "list" | "detail") {
|
|
|
rightPanel.value = p;
|
|
|
if (p === "list") {
|
|
|
activeAnnotation.value = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function showRuleDetail() {
|
|
|
ruleDetailVisible.value = true;
|
|
|
}
|
|
|
|
|
|
function truncatePrompt(text: string): string {
|
|
|
if (!text) return "";
|
|
|
if (text.length <= 300) return text;
|
|
|
return text.slice(0, 300) + "...";
|
|
|
}
|
|
|
|
|
|
function goEdit() {
|
|
|
if (templateId.value) {
|
|
|
router.push({ name: "ReportTemplateEdit", query: { id: templateId.value, report_type: reportType.value } });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function goBack() {
|
|
|
router.back();
|
|
|
// router.push({ path: "/report/template", query: { report_type: reportType.value } });
|
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
|
await loadTemplate();
|
|
|
await loadAnnotations();
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.template-detail-page {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
height: 100%;
|
|
|
background: #f5f6f8;
|
|
|
}
|
|
|
|
|
|
.page-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
padding: 10px 16px;
|
|
|
background: #fff;
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.header-left {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 4px;
|
|
|
}
|
|
|
|
|
|
.page-title {
|
|
|
font-size: 15px;
|
|
|
font-weight: 600;
|
|
|
color: #111827;
|
|
|
margin-left: 8px;
|
|
|
}
|
|
|
|
|
|
.readonly-tag {
|
|
|
margin-left: 8px;
|
|
|
}
|
|
|
|
|
|
.page-body {
|
|
|
flex: 1;
|
|
|
min-height: 0;
|
|
|
display: flex;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.preview-wrapper {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
background: #fff;
|
|
|
border-right: 1px solid #e5e7eb;
|
|
|
}
|
|
|
|
|
|
.preview-loading {
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.right-panel {
|
|
|
width: 340px;
|
|
|
min-width: 340px;
|
|
|
background: #fff;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.panel-scroll {
|
|
|
flex: 1;
|
|
|
min-height: 0;
|
|
|
overflow-y: auto;
|
|
|
padding: 16px;
|
|
|
}
|
|
|
|
|
|
.panel-title {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 6px;
|
|
|
font-size: 14px;
|
|
|
font-weight: 600;
|
|
|
color: #111827;
|
|
|
margin-bottom: 12px;
|
|
|
padding-bottom: 10px;
|
|
|
border-bottom: 1px solid #f3f4f6;
|
|
|
}
|
|
|
|
|
|
.panel-count {
|
|
|
font-size: 12px;
|
|
|
font-weight: 400;
|
|
|
color: #9ca3af;
|
|
|
}
|
|
|
|
|
|
.empty-panel {
|
|
|
padding: 40px 0;
|
|
|
}
|
|
|
|
|
|
.annotation-card-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.annotation-card {
|
|
|
border: 1px solid #e5e7eb;
|
|
|
border-radius: 8px;
|
|
|
padding: 12px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.15s;
|
|
|
|
|
|
&:hover {
|
|
|
border-color: #409eff;
|
|
|
background: #f9fbff;
|
|
|
}
|
|
|
&.active {
|
|
|
border-color: #409eff;
|
|
|
background: #ecf5ff;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.card-row {
|
|
|
display: flex;
|
|
|
gap: 8px;
|
|
|
align-items: flex-start;
|
|
|
font-size: 13px;
|
|
|
margin-bottom: 6px;
|
|
|
}
|
|
|
|
|
|
.label {
|
|
|
color: #6b7280;
|
|
|
flex-shrink: 0;
|
|
|
min-width: 52px;
|
|
|
}
|
|
|
|
|
|
.chapter-link {
|
|
|
color: #111827;
|
|
|
font-weight: 500;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.rule-link {
|
|
|
color: #409eff;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.card-desc {
|
|
|
font-size: 12px;
|
|
|
color: #6b7280;
|
|
|
line-height: 1.5;
|
|
|
padding: 6px 8px;
|
|
|
background: #f9fafb;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.desc-text {
|
|
|
display: -webkit-box;
|
|
|
-webkit-line-clamp: 2;
|
|
|
-webkit-box-orient: vertical;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
/* ============ 标注详情面板(只读) ============ */
|
|
|
.config-title {
|
|
|
justify-content: flex-start;
|
|
|
}
|
|
|
|
|
|
.selected-heading {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
|
|
|
.heading-text {
|
|
|
font-weight: 600;
|
|
|
color: #409eff;
|
|
|
}
|
|
|
|
|
|
.config-section {
|
|
|
margin-bottom: 16px;
|
|
|
}
|
|
|
|
|
|
.config-label {
|
|
|
font-size: 13px;
|
|
|
font-weight: 600;
|
|
|
color: #111827;
|
|
|
margin-bottom: 8px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
|
|
|
.rule-name {
|
|
|
color: #409eff;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.config-row {
|
|
|
font-size: 13px;
|
|
|
color: #4b5563;
|
|
|
margin-bottom: 6px;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
|
|
|
.sub-label {
|
|
|
color: #6b7280;
|
|
|
margin-right: 4px;
|
|
|
}
|
|
|
|
|
|
.config-prompt {
|
|
|
background: #f9fafb;
|
|
|
border-radius: 6px;
|
|
|
padding: 10px;
|
|
|
margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
.prompt-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 6px;
|
|
|
}
|
|
|
|
|
|
.config-prompt pre {
|
|
|
margin: 0;
|
|
|
font-family: "SFMono-Regular", Consolas, monospace;
|
|
|
font-size: 12px;
|
|
|
line-height: 1.6;
|
|
|
color: #374151;
|
|
|
white-space: pre-wrap;
|
|
|
word-break: break-word;
|
|
|
max-height: 200px;
|
|
|
overflow: auto;
|
|
|
}
|
|
|
|
|
|
.no-var-hint {
|
|
|
font-size: 12px;
|
|
|
color: #9ca3af;
|
|
|
text-align: center;
|
|
|
padding: 16px 0;
|
|
|
}
|
|
|
|
|
|
.var-bind-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.var-bind-row {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.var-index {
|
|
|
width: 22px;
|
|
|
height: 22px;
|
|
|
border-radius: 50%;
|
|
|
background: #eff6ff;
|
|
|
color: #409eff;
|
|
|
font-size: 12px;
|
|
|
font-weight: 600;
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.var-name {
|
|
|
font-family: "SFMono-Regular", Consolas, monospace;
|
|
|
font-size: 12px;
|
|
|
color: #059669;
|
|
|
background: #ecfdf5;
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 3px;
|
|
|
min-width: 80px;
|
|
|
max-width: 140px;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
flex-shrink: 0;
|
|
|
margin-top: 1px;
|
|
|
}
|
|
|
|
|
|
.var-tags {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
gap: 4px;
|
|
|
align-items: center;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
.var-tag {
|
|
|
max-width: 100%;
|
|
|
}
|
|
|
|
|
|
.no-bind {
|
|
|
font-size: 12px;
|
|
|
color: #c0c4cc;
|
|
|
}
|
|
|
|
|
|
.config-footer {
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
gap: 8px;
|
|
|
padding-top: 12px;
|
|
|
border-top: 1px solid #f3f4f6;
|
|
|
}
|
|
|
|
|
|
.detail-prompt {
|
|
|
white-space: pre-wrap;
|
|
|
word-break: break-word;
|
|
|
font-family: "SFMono-Regular", Consolas, monospace;
|
|
|
font-size: 12px;
|
|
|
line-height: 1.6;
|
|
|
margin: 0;
|
|
|
padding: 8px;
|
|
|
background: #f9fafb;
|
|
|
border-radius: 4px;
|
|
|
max-height: 260px;
|
|
|
overflow: auto;
|
|
|
}
|
|
|
</style>
|