Files
EasyFlow/easyflow-ui-admin/app/src/views/ai/documentCollection/DocumentCollectionModal.vue
陈子默 d244a0404d fix: 收口管理端页面权限与工作流运行授权
- 页面选项接口改用所属页面权限并返回最小数据视图

- 统一校验工作流引用、租户、状态与定时任务执行主体

- 补充聊天记录权限迁移和权限隔离回归测试
2026-08-07 12:51:21 +08:00

403 lines
11 KiB
Vue

<script setup lang="ts">
import type { FormInstance } from 'element-plus';
import { computed, onMounted, ref } from 'vue';
import { EasyFlowFormModal } from '@easyflow/common-ui';
import { InfoFilled } from '@element-plus/icons-vue';
import {
ElForm,
ElFormItem,
ElIcon,
ElInput,
ElMessage,
ElOption,
ElSelect,
ElSwitch,
ElTooltip,
} from 'element-plus';
import { api } from '#/api/request';
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
import { $t } from '#/locales';
const emit = defineEmits(['reload']);
const embeddingLlmList = ref<any>([]);
const rerankerLlmList = ref<any>([]);
const getEmbeddingLlmListData = async () => {
try {
const url = `/api/v1/documentCollection/modelList?modelType=embeddingModel`;
const res = await api.get(url, {});
if (res.errorCode === 0) {
embeddingLlmList.value = res.data;
}
} catch (error) {
ElMessage.error($t('message.apiError'));
console.error('获取嵌入模型列表失败:', error);
}
};
const getRerankerLlmListData = async () => {
try {
const res = await api.get(
'/api/v1/documentCollection/modelList?modelType=rerankModel',
);
rerankerLlmList.value = res.data;
} catch (error) {
ElMessage.error($t('message.apiError'));
console.error('获取重排模型列表失败:', error);
}
};
onMounted(async () => {
await getEmbeddingLlmListData();
await getRerankerLlmListData();
});
const saveForm = ref<FormInstance>();
const dialogVisible = ref(false);
const isAdd = ref(true);
const defaultEntity = {
collectionType: 'DOCUMENT',
alias: '',
deptId: '',
icon: '',
title: '',
categoryId: '',
description: '',
slug: '',
vectorStoreEnable: false,
vectorStoreType: '',
vectorStoreCollection: '',
vectorStoreConfig: '',
vectorEmbedModelId: '',
dimensionOfVectorModel: undefined,
options: {
canUpdateEmbeddingModel: true,
rerankEnable: false,
},
rerankModelId: '',
searchEngineEnable: false,
englishName: '',
visibilityScope: 'PRIVATE',
};
const normalizeEntity = (raw: any = {}) => {
const options = {
canUpdateEmbeddingModel: true,
...raw.options,
};
if (options.rerankEnable === undefined || options.rerankEnable === null) {
options.rerankEnable = !!raw.rerankModelId;
}
return {
...defaultEntity,
...raw,
options,
};
};
const entity = ref<any>(normalizeEntity(defaultEntity));
const btnLoading = ref(false);
const categoryOptions = ref<any[]>([]);
let categoryOptionsLoaded = false;
const rules = ref({
collectionType: [
{ required: true, message: $t('message.required'), trigger: 'change' },
],
deptId: [
{ required: true, message: $t('message.required'), trigger: 'blur' },
],
englishName: [
{ required: true, message: $t('message.required'), trigger: 'blur' },
],
description: [
{ required: true, message: $t('message.required'), trigger: 'blur' },
],
title: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
vectorEmbedModelId: [
{ required: true, message: $t('message.required'), trigger: 'blur' },
],
});
const visibilityScopeOptions = computed(() => [
{
label: $t('documentCollection.visibilityScopePrivate'),
value: 'PRIVATE',
},
{
label: $t('documentCollection.visibilityScopeDept'),
value: 'DEPT',
},
{
label: $t('documentCollection.visibilityScopePublic'),
value: 'PUBLIC',
},
]);
const collectionTypeList = [
{
label: $t('documentCollection.collectionTypeDocument'),
value: 'DOCUMENT',
},
{
label: $t('documentCollection.collectionTypeFaq'),
value: 'FAQ',
},
];
async function openDialog(row: any = {}) {
if (row.id) {
isAdd.value = false;
entity.value = normalizeEntity(row);
} else {
isAdd.value = true;
entity.value = normalizeEntity(defaultEntity);
}
dialogVisible.value = true;
if (!categoryOptionsLoaded) {
try {
const res = await api.get(
'/api/v1/documentCollectionCategory/visibleList',
);
categoryOptions.value = Array.isArray(res.data) ? res.data : [];
categoryOptionsLoaded = true;
} catch {
categoryOptions.value = [];
}
}
}
async function save() {
try {
const valid = await saveForm.value?.validate();
if (!valid) return;
if (!entity.value.options) {
entity.value.options = {};
}
entity.value.options.rerankEnable = !!entity.value.options.rerankEnable;
btnLoading.value = true;
const res = await api.post(
isAdd.value
? '/api/v1/documentCollection/save'
: '/api/v1/documentCollection/update',
entity.value,
);
if (res.errorCode === 0) {
ElMessage.success(res.message || $t('message.saveSuccess'));
emit('reload');
closeDialog();
} else {
ElMessage.error(res.message || $t('message.saveFail'));
}
} catch (error) {
ElMessage.error($t('message.saveFail'));
console.error('保存失败:', error);
} finally {
btnLoading.value = false;
}
}
function closeDialog() {
saveForm.value?.resetFields();
isAdd.value = true;
entity.value = normalizeEntity(defaultEntity);
dialogVisible.value = false;
}
defineExpose({
openDialog,
});
</script>
<template>
<EasyFlowFormModal
v-model:open="dialogVisible"
:title="isAdd ? $t('button.add') : $t('button.edit')"
:before-close="closeDialog"
:centered="true"
:confirm-loading="btnLoading"
:confirm-text="$t('button.save')"
:submitting="btnLoading"
width="xl"
@confirm="save"
>
<ElForm
ref="saveForm"
:model="entity"
status-icon
:rules="rules"
label-position="top"
class="easyflow-modal-form easyflow-modal-form--compact"
>
<ElFormItem
prop="icon"
:label="$t('documentCollection.icon')"
style="display: flex; align-items: center"
>
<UploadAvatar v-model="entity.icon" />
</ElFormItem>
<ElFormItem prop="title" :label="$t('documentCollection.title')">
<ElInput
v-model.trim="entity.title"
:placeholder="$t('documentCollection.placeholder.title')"
/>
</ElFormItem>
<ElFormItem
prop="collectionType"
:label="$t('documentCollection.collectionType')"
>
<ElSelect v-model="entity.collectionType">
<ElOption
v-for="item in collectionTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
prop="categoryId"
:label="$t('documentCollection.categoryId')"
>
<ElSelect v-model="entity.categoryId" clearable filterable>
<ElOption
v-for="category in categoryOptions"
:key="category.id"
:label="category.categoryName"
:value="category.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
prop="visibilityScope"
:label="$t('documentCollection.visibilityScope')"
>
<ElSelect v-model="entity.visibilityScope" class="w-full">
<ElOption
v-for="item in visibilityScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem prop="alias" :label="$t('documentCollection.alias')">
<ElInput v-model.trim="entity.alias" />
</ElFormItem>
<ElFormItem
prop="englishName"
:label="$t('documentCollection.englishName')"
>
<ElInput v-model.trim="entity.englishName" />
</ElFormItem>
<ElFormItem
prop="description"
:label="$t('documentCollection.description')"
>
<ElInput
v-model.trim="entity.description"
:rows="4"
type="textarea"
:placeholder="$t('documentCollection.placeholder.description')"
/>
</ElFormItem>
<ElFormItem prop="vectorEmbedModelId">
<template #label>
<span style="display: flex; align-items: center">
{{ $t('documentCollection.vectorEmbedLlmId') }}
<ElTooltip
:content="$t('documentCollection.vectorEmbedModelTips')"
placement="top"
effect="light"
>
<ElIcon
style="
margin-left: 4px;
font-size: 14px;
color: #909399;
cursor: pointer;
"
>
<InfoFilled />
</ElIcon>
</ElTooltip>
</span>
</template>
<ElSelect
v-model="entity.vectorEmbedModelId"
:disabled="!entity?.options?.canUpdateEmbeddingModel"
:placeholder="$t('documentCollection.placeholder.embedLlm')"
>
<ElOption
v-for="item in embeddingLlmList"
:key="item.id"
:label="item.title"
:value="item.id || ''"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
prop="dimensionOfVectorModel"
:label="$t('documentCollection.dimensionOfVectorModel')"
>
<template #label>
<span style="display: flex; align-items: center">
{{ $t('documentCollection.dimensionOfVectorModel') }}
<ElTooltip
:content="$t('documentCollection.dimensionOfVectorModelTips')"
placement="top"
effect="light"
>
<ElIcon
style="
margin-left: 4px;
font-size: 14px;
color: #909399;
cursor: pointer;
"
>
<InfoFilled />
</ElIcon>
</ElTooltip>
</span>
</template>
<ElInput
:disabled="!entity?.options?.canUpdateEmbeddingModel"
v-model.trim="entity.dimensionOfVectorModel"
type="number"
/>
</ElFormItem>
<ElFormItem
prop="options.rerankEnable"
:label="$t('documentCollection.rerankEnable')"
>
<ElSwitch v-model="entity.options.rerankEnable" />
</ElFormItem>
<ElFormItem
prop="rerankModelId"
:label="$t('documentCollection.rerankLlmId')"
>
<ElSelect
v-model="entity.rerankModelId"
clearable
:placeholder="$t('documentCollection.placeholder.rerankLlm')"
>
<ElOption
v-for="item in rerankerLlmList"
:key="item.id"
:label="item.title"
:value="item.id || ''"
/>
</ElSelect>
</ElFormItem>
</ElForm>
</EasyFlowFormModal>
</template>
<style scoped></style>