|
@@ -0,0 +1,245 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 主弹窗:展示环境变量列表 -->
|
|
|
|
|
+ <el-dialog v-model="visible" title="环境变量" width="520px" append-to-body>
|
|
|
|
|
+ <div class="space-y-12px">
|
|
|
|
|
+ <div>环境变量用以配置智能体运行时所需的环境变量,例如 API_KEY、SECRET_KEY 等。</div>
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <el-button type="primary" link @click="onAddClick">
|
|
|
|
|
+ <span class="text-13px">+ 新增变量</span>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="envList.length === 0" class="text-12px text-gray-400">
|
|
|
|
|
+ 当前暂无环境变量,点击「新增变量」添加。
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="space-y-8px max-h-360px overflow-y-auto pr-4px">
|
|
|
|
|
+ <el-card
|
|
|
|
|
+ v-for="(item, index) in envList"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ shadow="never"
|
|
|
|
|
+ class="env-item-card"
|
|
|
|
|
+ body-class="p-6px!"
|
|
|
|
|
+ header-class="p-6px!"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <div class="text-14px text-gray-700 font-medium flex items-center gap-4px">
|
|
|
|
|
+ <Icon icon="eos-icons:env" class="w-12px h-12px" color="#7839ee" />
|
|
|
|
|
+ {{ item.name || '未命名变量' }}
|
|
|
|
|
+ <span class="ml-2 text-12px text-gray-400">
|
|
|
|
|
+ {{ item.type === 'number' ? 'number' : 'string' }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex items-center gap-4px">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ link
|
|
|
|
|
+ class="mt-4px flex-shrink-0"
|
|
|
|
|
+ @click="onEditClick(index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ link
|
|
|
|
|
+ class="mt-4px flex-shrink-0"
|
|
|
|
|
+ @click="removeEnv(index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 删除
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <div class="text-12px text-gray-500 break-all">
|
|
|
|
|
+ {{ item.value }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 编辑 / 新增 子弹窗:单个变量表单 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="editVisible"
|
|
|
|
|
+ :title="editingIndex === -1 ? '新增环境变量' : '编辑环境变量'"
|
|
|
|
|
+ width="520px"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="editFormRef"
|
|
|
|
|
+ :model="editForm"
|
|
|
|
|
+ :rules="editFormRules"
|
|
|
|
|
+ label-width="80px"
|
|
|
|
|
+ label-position="top"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item label="变量名" prop="name">
|
|
|
|
|
+ <el-input v-model="editForm.name" placeholder="例如 API_KEY" clearable />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="变量类型" prop="type">
|
|
|
|
|
+ <el-select v-model="editForm.type" placeholder="请选择变量类型" class="w-full">
|
|
|
|
|
+ <el-option label="String" value="string" />
|
|
|
|
|
+ <el-option label="Number" value="number" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <el-form-item label="变量值" prop="value">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="editForm.value"
|
|
|
|
|
+ :type="editForm.type === 'number' ? 'number' : 'text'"
|
|
|
|
|
+ placeholder="请输入变量值"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <div class="flex justify-end gap-8px">
|
|
|
|
|
+ <el-button @click="cancelEdit">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="saveEdit">保存</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { computed, reactive, ref, watch } from 'vue'
|
|
|
|
|
+import { Icon } from '@repo/ui'
|
|
|
|
|
+import type { FormInstance } from 'element-plus'
|
|
|
|
|
+
|
|
|
|
|
+interface AgentEnvVar {
|
|
|
|
|
+ name: string
|
|
|
|
|
+ value: string
|
|
|
|
|
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const props = defineProps<{
|
|
|
|
|
+ modelValue: boolean
|
|
|
|
|
+ /** 当前的环境变量列表,可选(预填用) */
|
|
|
|
|
+ value?: AgentEnvVar[]
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const emit = defineEmits<{
|
|
|
|
|
+ (e: 'update:modelValue', value: boolean): void
|
|
|
|
|
+ (e: 'change', value: AgentEnvVar[]): void
|
|
|
|
|
+}>()
|
|
|
|
|
+
|
|
|
|
|
+const visible = computed({
|
|
|
|
|
+ get() {
|
|
|
|
|
+ return props.modelValue
|
|
|
|
|
+ },
|
|
|
|
|
+ set(val: boolean) {
|
|
|
|
|
+ emit('update:modelValue', val)
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const defaultItem: AgentEnvVar = {
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ type: 'string',
|
|
|
|
|
+ value: ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const envList = ref<AgentEnvVar[]>([])
|
|
|
|
|
+
|
|
|
|
|
+// 子弹窗编辑状态
|
|
|
|
|
+const editVisible = ref(false)
|
|
|
|
|
+const editingIndex = ref<number>(-1)
|
|
|
|
|
+const editForm = reactive<AgentEnvVar>({ ...defaultItem })
|
|
|
|
|
+const editFormRef = ref<FormInstance>()
|
|
|
|
|
+const editFormRules = {
|
|
|
|
|
+ // 名称不能为空且不能重复
|
|
|
|
|
+ name: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ trigger: 'blur',
|
|
|
|
|
+ validator: (_rule: any, value: string, callback: any) => {
|
|
|
|
|
+ if (!value) {
|
|
|
|
|
+ callback(new Error('请输入变量名'))
|
|
|
|
|
+ }
|
|
|
|
|
+ if (envList.value.some((item) => item.name === value)) {
|
|
|
|
|
+ callback(new Error('变量名不能重复'))
|
|
|
|
|
+ }
|
|
|
|
|
+ callback()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ type: [{ required: true, message: '请选择变量类型', trigger: 'blur' }],
|
|
|
|
|
+ value: [{ required: true, message: '请输入变量值', trigger: 'blur' }]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+watch(
|
|
|
|
|
+ () => props.value,
|
|
|
|
|
+ (val) => {
|
|
|
|
|
+ if (Array.isArray(val) && val.length > 0) {
|
|
|
|
|
+ envList.value = val.map((item) => ({
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ type: item.type,
|
|
|
|
|
+ value: item.value ?? ''
|
|
|
|
|
+ }))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ envList.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ { immediate: true, deep: false }
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+function onAddClick() {
|
|
|
|
|
+ editingIndex.value = -1
|
|
|
|
|
+ editForm.name = ''
|
|
|
|
|
+ editForm.type = 'string'
|
|
|
|
|
+ editForm.value = ''
|
|
|
|
|
+ editVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onEditClick(index: number) {
|
|
|
|
|
+ const target = envList.value[index]
|
|
|
|
|
+ editingIndex.value = index
|
|
|
|
|
+ editForm.name = target?.name ?? ''
|
|
|
|
|
+ editForm.type = target?.type ?? 'string'
|
|
|
|
|
+ editForm.value = target?.value ?? ''
|
|
|
|
|
+ editVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function cancelEdit() {
|
|
|
|
|
+ editVisible.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function saveEdit() {
|
|
|
|
|
+ if (!editFormRef.value) return
|
|
|
|
|
+ const isValid = await editFormRef.value.validate()
|
|
|
|
|
+ if (!isValid) return
|
|
|
|
|
+
|
|
|
|
|
+ const normalized: AgentEnvVar = {
|
|
|
|
|
+ name: editForm.name.trim(),
|
|
|
|
|
+ type: editForm.type,
|
|
|
|
|
+ value: editForm.value ?? ''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 不允许空变量名
|
|
|
|
|
+ if (!normalized.name) {
|
|
|
|
|
+ editVisible.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (editingIndex.value === -1) {
|
|
|
|
|
+ envList.value.push({ ...normalized })
|
|
|
|
|
+ } else if (envList.value[editingIndex.value]) {
|
|
|
|
|
+ envList.value[editingIndex.value] = { ...normalized }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ emit('change', envList.value)
|
|
|
|
|
+ editVisible.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeEnv(index: number) {
|
|
|
|
|
+ envList.value.splice(index, 1)
|
|
|
|
|
+ emit('change', envList.value)
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.env-item-card:hover {
|
|
|
|
|
+ border-color: #e5e7eb;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|