作者 dong

资料管理界面新增查询编辑功能添加了确定所属知识库的功能,按钮管理界面的增删改查及界面按钮开关实时操作开启和关闭功能

... ... @@ -12,16 +12,16 @@
/>
</Teleport>
<!-- update-end--author:liaozhiyang---date:20240517---for:【TV360X-35】富文本,图片上传遮挡其他按钮 -->
<Editor :id="tinymceId" ref="elRef" :disabled="disabled" :init="initOptions" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline"></Editor>
<Editor :id="tinymceId" ref="elRef" :disabled="disabled" :init="initOptions" :style="{ visibility: 'hidden' }" v-if="!initOptions.inline" />
<slot v-else></slot>
<ProcessMask ref="processMaskRef" :show="showUploadMask"/>
<ProcessMask ref="processMaskRef" :show="showUploadMask" />
</div>
</template>
<script lang="ts">
import type { RawEditorOptions } from 'tinymce';
import tinymce from 'tinymce/tinymce';
import Editor from '@tinymce/tinymce-vue'
import Editor from '@tinymce/tinymce-vue';
import 'tinymce/themes/silver';
import 'tinymce/icons/default/icons';
import 'tinymce/models/dom';
... ... @@ -83,27 +83,27 @@
},
showImageUpload: {
type: Boolean,
default: true,
default: false,
},
showUploadMask: {
type: Boolean,
default: false,
default: true,
},
//是否聚焦
autoFocus:{
autoFocus: {
type: Boolean,
default: true,
}
},
};
export default defineComponent({
name: 'Tinymce',
components: { ImgUpload,Editor,ProcessMask },
components: { ImgUpload, Editor, ProcessMask },
inheritAttrs: false,
props: tinymceProps as any,
emits: ['change', 'update:modelValue', 'inited', 'init-error'],
setup(props, { emit, attrs }) {
console.log("---Tinymce---初始化---")
console.log('---Tinymce---初始化---');
const editorRef = ref<Nullable<any>>(null);
const fullscreen = ref(false);
... ... @@ -155,6 +155,8 @@
language: langName.value,
branding: false,
default_link_target: '_blank',
forced_root_block: false,
force_br_newlines: true, // 换行使用<br>
link_title: false,
object_resizing: true,
toolbar_mode: 'sliding',
... ... @@ -166,26 +168,26 @@
skin_url: publicPath + 'resource/tinymce/skins/ui/' + skinName.value,
images_upload_handler: (blobInfo, process) =>
new Promise((resolve, reject) => {
let params = {
file: blobInfo.blob(),
filename: blobInfo.filename(),
data: { biz: 'jeditor', jeditor: '1' },
};
const uploadSuccess = (res) => {
if (res.success) {
if (res.message == 'local') {
const img = 'data:image/jpeg;base64,' + blobInfo.base64();
resolve(img);
let params = {
file: blobInfo.blob(),
filename: blobInfo.filename(),
data: { biz: 'jeditor', jeditor: '1' },
};
const uploadSuccess = (res) => {
if (res.success) {
if (res.message == 'local') {
const img = 'data:image/jpeg;base64,' + blobInfo.base64();
resolve(img);
} else {
let img = getFileAccessHttpUrl(res.message);
resolve(img);
}
} else {
let img = getFileAccessHttpUrl(res.message);
resolve(img);
}
} else {
reject('上传失败!');
}
};
uploadFile(params, uploadSuccess);
}),
}
};
uploadFile(params, uploadSuccess);
}),
content_css: publicPath + 'resource/tinymce/skins/ui/' + skinName.value + '/content.min.css',
...options,
setup: (editor: any) => {
... ... @@ -217,7 +219,7 @@
if (!editor) {
return;
}
editor?.setMode && editor.setMode(attrs.disabled ? 'readonly' : 'design');
editor?.setMode && editor.setMode(attrs.disabled ? 'readonly' : 'design');
}
);
... ... @@ -339,12 +341,12 @@
* @param fileList
*/
function handleLoading(fileLength,showMask){
if(fileLength && fileLength > 0){
if (fileLength && fileLength > 0) {
setTimeout(() => {
props?.showUploadMask && processMaskRef.value.calcProcess(fileLength)
},100)
}else{
props?.showUploadMask && (processMaskRef.value.showMask = showMask);
props?.showUploadMask && processMaskRef.value.calcProcess(fileLength);
}, 100);
} else {
props?.showUploadMask && (processMaskRef.value.showMask = showMask);
}
}
function getUploadingImgName(name: string) {
... ... @@ -421,7 +423,7 @@
targetElem,
handleLoading,
processMaskRef
processMaskRef,
};
},
});
... ... @@ -441,7 +443,7 @@
visibility: hidden;
}
.tox:not(.tox-tinymce-inline) .tox-editor-header {
padding:0;
padding: 0;
}
// update-begin--author:liaozhiyang---date:20240527---for:【TV360X-329】富文本禁用状态下工具栏划过边框丢失
.tox .tox-tbtn--disabled,
... ... @@ -456,7 +458,9 @@
html[data-theme='dark'] {
.@{prefix-cls} {
.tox .tox-edit-area__iframe {background-color: #141414;}
.tox .tox-edit-area__iframe {
background-color: #141414;
}
}
}
</style>
... ...
... ... @@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import {useDesign} from "@/hooks/web/useDesign";
import { useDesign } from '@/hooks/web/useDesign';
const props = defineProps({
backColor: {
... ... @@ -67,44 +67,42 @@
</script>
<style lang="less">
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-tinymce-process-mask';
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-tinymce-process-mask';
.@{prefix-cls} {
& {
position: absolute; /* 或者使用固定定位等其他方式 */
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); /* 半透明遮罩 */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
z-index: 99;
}
.@{prefix-cls} {
& {
position: absolute; /* 或者使用固定定位等其他方式 */
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); /* 半透明遮罩 */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
z-index: 99;
}
.progress-bar-rear {
width: 100px; /* 进度条宽度 */
height: 10px; /* 进度条高度 */
background-color: v-bind(rearColor); /* 进度条颜色 */
border-radius: 4px;
}
.progress-bar-rear {
width: 100px; /* 进度条宽度 */
height: 10px; /* 进度条高度 */
background-color: v-bind(rearColor); /* 进度条颜色 */
border-radius: 4px;
}
.progress-bar-front {
height: 10px; /* 进度条高度 */
background-color: v-bind(frontColor); /* 进度条颜色 */
border-radius: 4px;
}
.progress-bar-front {
height: 10px; /* 进度条高度 */
background-color: v-bind(frontColor); /* 进度条颜色 */
border-radius: 4px;
}
.value {
color: #fff;
margin-left: 5px;
font-size: 16px;
font-weight: 600;
.value {
color: #fff;
margin-left: 5px;
font-size: 16px;
font-weight: 600;
}
}
}
</style>
... ...
... ... @@ -12,8 +12,6 @@ export const toolbar =
export const simplePlugins = 'lists image link fullscreen';
export const simpleToolbar = [
'undo redo styles bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent lists image link fullscreen',
];
export const simpleToolbar = ['styles bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent lists image link '];
export const menubar = 'file edit insert view format table';
... ...
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/airagbutton/airagButton/list',
save = '/airagbutton/airagButton/add',
edit = '/airagbutton/airagButton/edit',
deleteOne = '/airagbutton/airagButton/delete',
deleteBatch = '/airagbutton/airagButton/deleteBatch',
importExcel = '/airagbutton/airagButton/importExcel',
exportXls = '/airagbutton/airagButton/exportXls',
}
/**
* 导出api
* @param params
*/
export const getExportUrl = Api.exportXls;
/**
* 导入api
*/
export const getImportUrl = Api.importExcel;
/**
* 列表接口
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除单个
*/
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 批量删除
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
createConfirm({
iconType: 'warning',
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或者更新
* @param params
* @param isUpdate - 是否为更新操作
*/
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
... ...
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { Switch } from 'ant-design-vue';
import { h, reactive } from 'vue';
import { saveOrUpdate } from '@/views/super/airag/airagbutton/AiragButton.api';
//列表数据
export const columns: BasicColumn[] = [
{
title: '按钮名称',
align: 'center',
dataIndex: 'buttonName',
},
{
title: '按钮开关',
align: 'center',
dataIndex: 'buttonSwitch',
customRender: ({ text, record }) => {
return h(Switch, {
checked: text === 'Y',
loading: switchLoading[record.id],
onChange: (checked: boolean) => handleSwitchChange(checked, record),
});
},
},
{
title: '按钮值',
align: 'center',
dataIndex: 'buttonValues',
},
];
// 定义开关状态映射
const switchLoading = reactive<Record<string, boolean>>({});
// 处理开关状态变化
async function handleSwitchChange(checked: boolean, record: Recordable) {
const newValue = checked ? 'Y' : 'N';
const oldValue = record.buttonSwitch;
// 立即更新本地状态
record.buttonSwitch = newValue;
switchLoading[record.id] = true;
try {
// 调用API更新后端
await saveOrUpdate(
{
id: record.id,
buttonSwitch: newValue,
},
true
);
} catch {
// 出错时回滚状态
record.buttonSwitch = oldValue;
} finally {
switchLoading[record.id] = false;
}
}
//查询数据
export const searchFormSchema: FormSchema[] = [
{
label: '按钮名称',
field: 'buttonName',
component: 'JInput',
},
{
label: '按钮值',
field: 'buttonValues',
component: 'JInput',
},
];
//表单数据
export const formSchema: FormSchema[] = [
{
label: '按钮名称',
field: 'buttonName',
component: 'Input',
required: true,
},
{
label: '按钮开关',
field: 'buttonSwitch',
component: 'JSwitch',
required: true,
componentProps: {},
},
{
label: '按钮值',
field: 'buttonValues',
component: 'Input',
required: true,
},
// TODO 主键隐藏字段,目前写死为ID
{
label: '',
field: 'id',
component: 'Input',
show: false,
},
];
// 高级查询数据
export const superQuerySchema = {
buttonName: { title: '按钮名称', order: 0, view: 'text', type: 'string' },
buttonSwitch: { title: '按钮开关', order: 1, view: 'switch', type: 'string' },
buttonValues: { title: '按钮值', order: 2, view: 'text', type: 'string' },
};
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[] {
// 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
return formSchema;
}
... ...
<template>
<div>
<!--引用表格-->
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'airagbutton:airag_button:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
<a-button type="primary" v-auth="'airagbutton:airag_button:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls">
导出</a-button
>
<j-upload-button type="primary" v-auth="'airagbutton:airag_button:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls"
>导入</j-upload-button
>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />
删除
</a-menu-item>
</a-menu>
</template>
<a-button v-auth="'airagbutton:airag_button:deleteBatch'"
>批量操作
<Icon icon="mdi:chevron-down" />
</a-button>
</a-dropdown>
<!-- 高级查询 -->
<super-query :config="superQueryConfig" @search="handleSuperQuery" />
</template>
<!--操作栏-->
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
</template>
<!--字段回显插槽-->
<template #bodyCell="{ column, record, index, text }"> </template>
</BasicTable>
<!-- 表单区域 -->
<AiragButtonModal @register="registerModal" @success="handleSuccess" />
</div>
</template>
<script lang="ts" name="airagbutton-airagButton" setup>
import { ref, reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import AiragButtonModal from './components/AiragButtonModal.vue';
import { columns, searchFormSchema, superQuerySchema } from './AiragButton.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, saveOrUpdate } from './AiragButton.api';
import { useUserStore } from '/@/store/modules/user';
const queryParam = reactive<any>({});
const checkedKeys = ref<Array<string | number>>([]);
const userStore = useUserStore();
//注册model
const [registerModal, { openModal }] = useModal();
//注册table数据
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '按钮表单',
api: list,
columns,
canResize: false,
formConfig: {
//labelWidth: 120,
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
fieldMapToNumber: [],
fieldMapToTime: [],
},
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: (params) => {
return Object.assign(params, queryParam);
},
},
exportConfig: {
name: '按钮表单',
url: getExportUrl,
params: queryParam,
},
importConfig: {
url: getImportUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
// 高级查询配置
const superQueryConfig = reactive(superQuerySchema);
/**
* 高级查询事件
*/
function handleSuperQuery(params) {
Object.keys(params).map((k) => {
queryParam[k] = params[k];
});
reload();
}
/**
* 新增事件
*/
function handleAdd() {
openModal(true, {
isUpdate: false,
showFooter: true,
});
}
/**
* 编辑事件
*/
function handleEdit(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: true,
});
}
/**
* 详情
*/
function handleDetail(record: Recordable) {
openModal(true, {
record,
isUpdate: true,
showFooter: false,
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
/**
* 成功回调
*/
function handleSuccess() {
(selectedRowKeys.value = []) && reload();
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: 'airagbutton:airag_button:edit',
},
];
}
/**
* 下拉操作栏
*/
function getDropDownAction(record) {
return [
{
label: '详情',
onClick: handleDetail.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
placement: 'topLeft',
},
auth: 'airagbutton:airag_button:delete',
},
];
}
</script>
<style lang="less" scoped>
:deep(.ant-picker),
:deep(.ant-input-number) {
width: 100%;
}
</style>
... ...
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm" />
<div style="width: 100%; text-align: center" v-if="!formDisabled">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button>
</div>
</div>
</template>
<script lang="ts">
import { BasicForm, useForm } from '/@/components/Form/index';
import { computed, defineComponent } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { propTypes } from '/@/utils/propTypes';
import { getBpmFormSchema } from '../AiragButton.data';
import { saveOrUpdate } from '../AiragButton.api';
export default defineComponent({
name: 'AiragButtonForm',
components: {
BasicForm,
},
props: {
formData: propTypes.object.def({}),
formBpm: propTypes.bool.def(true),
},
setup(props) {
const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
labelWidth: 150,
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
const formDisabled = computed(() => {
if (props.formData.disabled === false) {
return false;
}
return true;
});
let formData = {};
const queryByIdUrl = '/airagbutton/airagButton/queryById';
async function initFormData() {
let params = { id: props.formData.dataId };
const data = await defHttp.get({ url: queryByIdUrl, params });
formData = { ...data };
//设置表单的值
await setFieldsValue(formData);
//默认是禁用
await setProps({ disabled: formDisabled.value });
}
async function submitForm() {
let data = getFieldsValue();
let params = Object.assign({}, formData, data);
console.log('表单数据', params);
await saveOrUpdate(params, true);
}
initFormData();
return {
registerForm,
formDisabled,
submitForm,
};
},
});
</script>
... ...
<template>
<BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
<BasicForm @register="registerForm" name="AiragButtonForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../AiragButton.data';
import { saveOrUpdate } from '../AiragButton.api';
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
//表单配置
const [registerForm, { setProps, resetFields, setFieldsValue, validate, scrollToField }] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false, showCancelBtn: !!data?.showFooter, showOkBtn: !!data?.showFooter });
isUpdate.value = !!data?.isUpdate;
isDetail.value = !!data?.showFooter;
if (unref(isUpdate)) {
//表单赋值
await setFieldsValue({
...data.record,
});
}
// 隐藏底部时禁用整个表单
setProps({ disabled: !data?.showFooter });
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
//表单提交事件
async function handleSubmit(v) {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} catch ({ errorFields }) {
if (errorFields) {
const firstField = errorFields[0];
if (firstField) {
scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
}
}
return Promise.reject(errorFields);
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style lang="less" scoped>
/** 时间和数字输入框样式 */
:deep(.ant-input-number) {
width: 100%;
}
:deep(.ant-calendar-picker) {
width: 100%;
}
</style>
... ...
... ... @@ -20,29 +20,29 @@ export const list = async (params) => {
try {
const res = await defHttp.get({
url: Api.list,
params: { ...params, size: 1000 }
params: { ...params, size: 1000 },
});
if (res?.records && Array.isArray(res.records)) {
res.records = res.records.map(item => ({
res.records = res.records.map((item) => ({
...item,
...item.metadata,
question: item.question || '',
answer: item.answer || ''
answer: item.answer || '',
}));
}
return res;
} catch (error) {
console.error("Error fetching question embeddings:", error);
console.error('Error fetching question embeddings:', error);
return {
records: [],
total: 0,
size: 10,
current: 1,
pages: 0
pages: 0,
};
}
}
};
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
... ...
... ... @@ -12,27 +12,27 @@ export const columns: BasicColumn[] = [
title: '问题',
align: 'center',
dataIndex: 'question',
width: 250
width: 250,
},
{
title: '回答',
align: 'center',
dataIndex: 'answer',
width: 300
width: 300,
},
{
title: '原文',
align: 'center',
dataIndex: 'text',
width: 300
width: 300,
},
{
title: '元数据',
align: 'center',
dataIndex: 'metadata',
width: 200,
customRender: ({ text }) => JSON.stringify(text || {})
}
customRender: ({ text }) => JSON.stringify(text || {}),
},
];
export const searchFormSchema: FormSchema[] = [
... ... @@ -40,14 +40,14 @@ export const searchFormSchema: FormSchema[] = [
field: 'question',
label: '问题',
component: 'Input',
colProps: { span: 8 }
colProps: { span: 8 },
},
{
field: 'answer',
label: '回答',
component: 'Input',
colProps: { span: 8 }
}
colProps: { span: 8 },
},
];
export const formSchema: FormSchema[] = [
... ... @@ -55,39 +55,39 @@ export const formSchema: FormSchema[] = [
field: 'id',
label: 'ID',
component: 'Input',
show: false
show: false,
},
{
field: 'question',
label: '问题',
component: 'InputTextArea',
required: true,
colProps: { span: 24 }
colProps: { span: 24 },
},
{
field: 'answer',
label: '回答',
component: 'InputTextArea',
required: true,
colProps: { span: 24 }
colProps: { span: 24 },
},
{
field: 'text',
label: '原文',
component: 'InputTextArea',
colProps: { span: 24 }
colProps: { span: 24 },
},
{
field: 'metadata',
label: '元数据',
component: 'InputTextArea',
colProps: { span: 24 }
}
colProps: { span: 24 },
},
];
export const superQuerySchema = {
question: { title: '问题', order: 0, view: 'text', type: 'string' },
answer: { title: '回答', order: 1, view: 'text', type: 'string' }
answer: { title: '回答', order: 1, view: 'text', type: 'string' },
};
export function getBpmFormSchema(_formData): FormSchema[] {
... ...
... ... @@ -8,9 +8,7 @@
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" />删除
</a-menu-item>
<a-menu-item key="1" @click="batchHandleDelete"> <Icon icon="ant-design:delete-outlined" />删除 </a-menu-item>
</a-menu>
</template>
<a-button>批量操作<Icon icon="mdi:chevron-down" /></a-button>
... ... @@ -26,92 +24,90 @@
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue';
import { columns, searchFormSchema, superQuerySchema } from './QuestionEmbedding.data';
import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl } from './QuestionEmbedding.api';
import { reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue';
import { columns, searchFormSchema, superQuerySchema } from './QuestionEmbedding.data';
import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl } from './QuestionEmbedding.api';
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const queryParam = reactive<any>({});
const [registerModal, { openModal }] = useModal();
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '问答向量库',
api: list,
columns,
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: '问答向量库',
api: list,
columns,
formConfig: {
schemas: searchFormSchema,
autoSubmitOnEnter: true,
showAdvancedButton: true,
},
actionColumn: {
width: 120,
fixed: 'right',
},
beforeFetch: (params) => Object.assign(params, queryParam),
},
actionColumn: {
width: 120,
fixed: 'right'
exportConfig: {
name: '问答向量库',
url: getExportUrl,
},
beforeFetch: (params) => Object.assign(params, queryParam)
},
exportConfig: {
name: '问答向量库',
url: getExportUrl
},
importConfig: {
url: getImportZipUrl,
success: handleSuccess
}
});
importConfig: {
url: getImportZipUrl,
success: handleSuccess,
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const superQueryConfig = reactive(superQuerySchema);
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const superQueryConfig = reactive(superQuerySchema);
function handleSuperQuery(params) {
Object.assign(queryParam, params);
reload();
}
function handleSuperQuery(params) {
Object.assign(queryParam, params);
reload();
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
function handleAdd() {
openModal(true, { isUpdate: false, showFooter: true });
}
function handleEdit(record) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
function handleEdit(record) {
openModal(true, { record, isUpdate: true, showFooter: true });
}
function handleDetail(record) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
function handleDetail(record) {
openModal(true, { record, isUpdate: true, showFooter: false });
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function handleDelete(record) {
await deleteOne({ id: record.id }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
}
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
function handleSuccess() {
selectedRowKeys.value = [];
reload();
}
function getTableAction(record) {
return [
{ label: '编辑', onClick: handleEdit.bind(null, record) }
];
}
function getTableAction(record) {
return [{ label: '编辑', onClick: handleEdit.bind(null, record) }];
}
function getDropDownAction(record) {
return [
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
popConfirm: {
title: '确认删除此问答?',
confirm: handleDelete.bind(null, record)
}
}
];
}
function getDropDownAction(record) {
return [
{ label: '详情', onClick: handleDetail.bind(null, record) },
{
label: '删除',
popConfirm: {
title: '确认删除此问答?',
confirm: handleDelete.bind(null, record),
},
},
];
}
</script>
... ...
... ... @@ -5,54 +5,46 @@
</template>
<script lang="ts" setup>
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../QuestionEmbedding.data';
import { saveOrUpdate } from '../QuestionEmbedding.api';
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 }
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
setModalProps({
confirmLoading: false,
showCancelBtn: !!data?.showFooter,
showOkBtn: !!data?.showFooter
import { ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../QuestionEmbedding.data';
import { saveOrUpdate } from '../QuestionEmbedding.api';
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const isDetail = ref(false);
const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 150,
schemas: formSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 },
});
isUpdate.value = !!data?.isUpdate;
isDetail.value = !!data?.showFooter;
if (unref(isUpdate)) {
await setFieldsValue({
...data.record,
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
await resetFields();
setModalProps({
confirmLoading: false,
showCancelBtn: !!data?.showFooter,
showOkBtn: !!data?.showFooter,
});
isUpdate.value = !!data?.isUpdate;
isDetail.value = !!data?.showFooter;
if (unref(isUpdate)) {
await setFieldsValue({
...data.record,
});
}
setProps({ disabled: !data?.showFooter });
});
const title = computed(() => (!unref(isUpdate) ? '新增问答' : !unref(isDetail) ? '问答详情' : '编辑问答'));
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, isUpdate.value);
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
setProps({ disabled: !data?.showFooter });
});
const title = computed(() => (!unref(isUpdate) ? '新增问答' : !unref(isDetail) ? '问答详情' : '编辑问答'));
async function handleSubmit() {
try {
const values = await validate();
setModalProps({ confirmLoading: true });
await saveOrUpdate(values, isUpdate.value);
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
... ...
... ... @@ -39,18 +39,76 @@
</template>
<script lang="ts" name="test-test" setup>
import { ref, reactive } from 'vue';
import { BasicTable, TableAction } from '/@/components/Table';
import { ref, reactive, computed, onMounted } from 'vue';
import { BasicColumn, BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import { useListPage } from '/@/hooks/system/useListPage';
import TestModal from './components/TestModal.vue';
import { columns, searchFormSchema, superQuerySchema } from './Test.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './Test.api';
import { searchFormSchema, superQuerySchema } from './Test.data';
import { list, batchDelete, getImportUrl, getExportUrl, deleteOne, listknowledge } from './Test.api';
import { useUserStore } from '/@/store/modules/user';
import JUploadButton from "@/components/Button/src/JUploadButton.vue";
import JUploadButton from '@/components/Button/src/JUploadButton.vue';
import { columns as defaultColumns } from './Test.data'; // 导入默认列配置
const queryParam = reactive<any>({});
const checkedKeys = ref<Array<string | number>>([]);
const userStore = useUserStore();
// 添加知识库名称映射
const knowledgeMap = ref<Record<string, string>>({});
// 加载知识库列表
async function loadKnowledgeMap() {
try {
const res = await listknowledge({});
const map: Record<string, string> = {};
res.forEach((item) => {
if (item.id && item.name) {
map[item.id] = item.name;
}
});
knowledgeMap.value = map;
} catch (e) {
console.error('加载知识库列表失败', e);
}
}
onMounted(() => {
loadKnowledgeMap();
});
// 创建符合 BasicColumn 类型的知识库名称列
const knowledgeColumn: BasicColumn = {
title: '知识库名称',
align: 'center',
dataIndex: 'knowledgeName',
width: 150,
customRender: ({ record }) => {
try {
const metadata = typeof record.metadata === 'string' ? JSON.parse(record.metadata) : record.metadata;
const knowledgeId = metadata?.knowledgeId;
if (knowledgeId && knowledgeMap.value[knowledgeId]) {
return knowledgeMap.value[knowledgeId];
} else if (knowledgeId) {
return `未知知识库(${knowledgeId})`;
} else {
return '无知识库';
}
} catch (e) {
return '元数据解析失败';
}
},
};
// 创建完整的列配置
const tableColumns = computed<BasicColumn[]>(() => {
// 从默认列配置中过滤掉可能存在的旧知识库列
const filteredColumns = defaultColumns.filter((col) => col.dataIndex !== 'name' && col.title !== '知识库名称');
// 添加新的知识库列
return [...filteredColumns, knowledgeColumn];
});
//注册model
const [registerModal, { openModal }] = useModal();
//注册table数据
... ... @@ -58,7 +116,7 @@
tableProps: {
title: 'test',
api: list,
columns,
columns: tableColumns,
canResize: false,
formConfig: {
//labelWidth: 120,
... ... @@ -73,6 +131,13 @@
fixed: 'right',
},
beforeFetch: (params) => {
// 处理知识库查询参数
if (params.knowledgeId) {
// 直接使用对象而不是JSON字符串
queryParam.metadata = { knowledgeId: params.knowledgeId };
} else {
delete queryParam.metadata;
}
return Object.assign(params, queryParam);
},
},
... ...
... ... @@ -5,6 +5,7 @@ const { createConfirm } = useMessage();
enum Api {
list = '/embeddings/embeddings/list',
listknowledge = '/embeddings/embeddings/listknowledge',
save = '/embeddings/embeddings/add',
edit = '/embeddings/embeddings/edit',
deleteOne = '/embeddings/embeddings/delete',
... ... @@ -29,6 +30,7 @@ export const getImportUrl = Api.importWord;
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
export const listknowledge = (params) => defHttp.get({ url: Api.listknowledge, params });
/**
* 删除单个
... ...
import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table';
import { h } from 'vue';
import { listknowledge } from '@/views/super/airag/test/Test.api';
//import { Tooltip } from 'ant-design-vue';
//列表数据
... ... @@ -43,15 +44,15 @@ export const columns: BasicColumn[] = [
align: 'center',
dataIndex: 'text',
},
/* {
title: '知识库ID',
{
title: '知识库名称',
align: 'center',
dataIndex: 'knowledgeId',
dataIndex: 'knowledgeName',
width: 150,
customRender: ({ record }) => {
/*customRender: ({ record }) => {
return renderMetadataField(record, 'knowledgeId');
},
},
},*/
} /*
{
title: '文档名称',
align: 'center',
... ... @@ -109,7 +110,7 @@ export const columns: BasicColumn[] = [
return h(Tooltip, { title: displayText }, () => h('span', shortText));
}
},
},*/
},*/,
];
function renderMetadataField(record: any, fieldName: string) {
... ... @@ -125,7 +126,6 @@ function renderMetadataField(record: any, fieldName: string) {
if (value === undefined || value === null) {
return h('span', { style: 'color: #999' }, '无');
}
// 根据字段类型渲染
switch (fieldName) {
case 'knowledgeId':
... ... @@ -150,6 +150,21 @@ export const searchFormSchema: FormSchema[] = [
field: 'text',
component: 'Input',
},
// 新增知识库选择字段
{
label: '知识库',
field: 'knowledgeId', // 注意:这里使用knowledgeId作为字段名
component: 'ApiSelect',
componentProps: {
api: listknowledge, // 使用知识库接口
labelField: 'name', // 显示知识库名称
valueField: 'id', // 提交知识库ID
showSearch: true,
filterOption: (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
},
},
];
//表单数据
export const formSchema: FormSchema[] = [
... ... @@ -166,7 +181,23 @@ export const formSchema: FormSchema[] = [
label: '文本内容',
field: 'text',
required: true,
component: 'InputTextArea',
component: 'JEditor',
},
// 新增知识库选择字段
{
label: '知识库',
field: 'knowledgeId', // 注意:这里使用knowledgeId作为字段名
component: 'ApiSelect',
required: true,
componentProps: {
api: listknowledge, // 使用知识库接口
labelField: 'name', // 显示知识库名称
valueField: 'id', // 提交知识库ID
showSearch: true,
filterOption: (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
},
},
// TODO 主键隐藏字段,目前写死为ID
{
... ...
... ... @@ -36,6 +36,9 @@
try {
const metadata = typeof formData.metadata === 'string' ? JSON.parse(formData.metadata) : formData.metadata;
formData.docName = metadata.docName || '未命名';
// 关键修复:设置知识库ID
formData.knowledgeId = metadata.knowledgeId;
} catch (e) {
console.error('元数据解析失败', e);
formData.docName = '元数据格式错误';
... ... @@ -54,11 +57,15 @@
try {
let values = await validate();
const metadata = {
knowledgeId: values.knowledgeId,
};
// 仅保留需要的字段,其他由后端自动生成
const payload = {
id: values.id,
docName: values.docName, // 文件名称
text: values.text, // 文件内容
metadata: metadata,
};
setModalProps({ confirmLoading: true });
//提交表单
... ...