作者 lixiang

问题库以及自定义问答

  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { useMessage } from '/@/hooks/web/useMessage';
  3 +
  4 +const { createConfirm } = useMessage();
  5 +
  6 +enum Api {
  7 + list = '/question/embedding/list',
  8 + save = '/question/embedding/add',
  9 + edit = '/question/embedding/edit',
  10 + deleteOne = '/question/embedding/delete',
  11 + deleteBatch = '/question/embedding/deleteBatch',
  12 + importExcel = '/question/embedding/importZip',
  13 + uploadZip = '/question/embedding/uploadZip',
  14 +}
  15 +
  16 +export const getExportUrl = Api.importExcel;
  17 +export const getImportZipUrl = Api.uploadZip;
  18 +
  19 +export const list = async (params) => {
  20 + try {
  21 + const res = await defHttp.get({
  22 + url: Api.list,
  23 + params: { ...params, size: 1000 }
  24 + });
  25 +
  26 + if (res?.records && Array.isArray(res.records)) {
  27 + res.records = res.records.map(item => ({
  28 + ...item,
  29 + ...item.metadata,
  30 + question: item.question || '',
  31 + answer: item.answer || ''
  32 + }));
  33 + }
  34 + return res;
  35 + } catch (error) {
  36 + console.error("Error fetching question embeddings:", error);
  37 + return {
  38 + records: [],
  39 + total: 0,
  40 + size: 10,
  41 + current: 1,
  42 + pages: 0
  43 + };
  44 + }
  45 +}
  46 +
  47 +export const deleteOne = (params, handleSuccess) => {
  48 + return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
  49 + handleSuccess();
  50 + });
  51 +};
  52 +
  53 +export const batchDelete = (params, handleSuccess) => {
  54 + createConfirm({
  55 + iconType: 'warning',
  56 + title: '确认删除',
  57 + content: '是否删除选中问答数据',
  58 + okText: '确认',
  59 + cancelText: '取消',
  60 + onOk: () => {
  61 + return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
  62 + handleSuccess();
  63 + });
  64 + },
  65 + });
  66 +};
  67 +
  68 +export const saveOrUpdate = (params, isUpdate) => {
  69 + const url = isUpdate ? Api.edit : Api.save;
  70 + return defHttp.post({ url: url, params });
  71 +};
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +
  4 +export const columns: BasicColumn[] = [
  5 + {
  6 + title: 'ID',
  7 + align: 'center',
  8 + dataIndex: 'id',
  9 + width: 200,
  10 + },
  11 + {
  12 + title: '问题',
  13 + align: 'center',
  14 + dataIndex: 'question',
  15 + width: 250
  16 + },
  17 + {
  18 + title: '回答',
  19 + align: 'center',
  20 + dataIndex: 'answer',
  21 + width: 300
  22 + },
  23 + {
  24 + title: '原文',
  25 + align: 'center',
  26 + dataIndex: 'text',
  27 + width: 300
  28 + },
  29 + {
  30 + title: '元数据',
  31 + align: 'center',
  32 + dataIndex: 'metadata',
  33 + width: 200,
  34 + customRender: ({ text }) => JSON.stringify(text || {})
  35 + }
  36 +];
  37 +
  38 +export const searchFormSchema: FormSchema[] = [
  39 + {
  40 + field: 'question',
  41 + label: '问题',
  42 + component: 'Input',
  43 + colProps: { span: 8 }
  44 + },
  45 + {
  46 + field: 'answer',
  47 + label: '回答',
  48 + component: 'Input',
  49 + colProps: { span: 8 }
  50 + }
  51 +];
  52 +
  53 +export const formSchema: FormSchema[] = [
  54 + {
  55 + field: 'id',
  56 + label: 'ID',
  57 + component: 'Input',
  58 + show: false
  59 + },
  60 + {
  61 + field: 'question',
  62 + label: '问题',
  63 + component: 'InputTextArea',
  64 + required: true,
  65 + colProps: { span: 24 }
  66 + },
  67 + {
  68 + field: 'answer',
  69 + label: '回答',
  70 + component: 'InputTextArea',
  71 + required: true,
  72 + colProps: { span: 24 }
  73 + },
  74 + {
  75 + field: 'text',
  76 + label: '原文',
  77 + component: 'InputTextArea',
  78 + colProps: { span: 24 }
  79 + },
  80 + {
  81 + field: 'metadata',
  82 + label: '元数据',
  83 + component: 'InputTextArea',
  84 + colProps: { span: 24 }
  85 + }
  86 +];
  87 +
  88 +export const superQuerySchema = {
  89 + question: { title: '问题', order: 0, view: 'text', type: 'string' },
  90 + answer: { title: '回答', order: 1, view: 'text', type: 'string' }
  91 +};
  92 +
  93 +export function getBpmFormSchema(_formData): FormSchema[] {
  94 + return formSchema;
  95 +}
  1 +<template>
  2 + <div>
  3 + <BasicTable @register="registerTable" :rowSelection="rowSelection">
  4 + <template #tableTitle>
  5 + <a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined">新增</a-button>
  6 + <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
  7 + <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
  8 + <a-dropdown v-if="selectedRowKeys.length > 0">
  9 + <template #overlay>
  10 + <a-menu>
  11 + <a-menu-item key="1" @click="batchHandleDelete">
  12 + <Icon icon="ant-design:delete-outlined" />删除
  13 + </a-menu-item>
  14 + </a-menu>
  15 + </template>
  16 + <a-button>批量操作<Icon icon="mdi:chevron-down" /></a-button>
  17 + </a-dropdown>
  18 + <super-query :config="superQueryConfig" @search="handleSuperQuery" />
  19 + </template>
  20 + <template #action="{ record }">
  21 + <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
  22 + </template>
  23 + </BasicTable>
  24 + <QuestionEmbeddingModal @register="registerModal" @success="handleSuccess" />
  25 + </div>
  26 +</template>
  27 +
  28 +<script lang="ts" setup>
  29 +import { ref, reactive } from 'vue';
  30 +import { BasicTable, useTable, TableAction } from '/@/components/Table';
  31 +import { useModal } from '/@/components/Modal';
  32 +import { useListPage } from '/@/hooks/system/useListPage';
  33 +import QuestionEmbeddingModal from './components/QuestionEmbeddingModal.vue';
  34 +import { columns, searchFormSchema, superQuerySchema } from './QuestionEmbedding.data';
  35 +import { list, deleteOne, batchDelete, getImportZipUrl, getExportUrl } from './QuestionEmbedding.api';
  36 +
  37 +const queryParam = reactive<any>({});
  38 +const [registerModal, { openModal }] = useModal();
  39 +
  40 +const { tableContext, onExportXls, onImportXls } = useListPage({
  41 + tableProps: {
  42 + title: '问答向量库',
  43 + api: list,
  44 + columns,
  45 + formConfig: {
  46 + schemas: searchFormSchema,
  47 + autoSubmitOnEnter: true,
  48 + showAdvancedButton: true
  49 + },
  50 + actionColumn: {
  51 + width: 120,
  52 + fixed: 'right'
  53 + },
  54 + beforeFetch: (params) => Object.assign(params, queryParam)
  55 + },
  56 + exportConfig: {
  57 + name: '问答向量库',
  58 + url: getExportUrl
  59 + },
  60 + importConfig: {
  61 + url: getImportZipUrl,
  62 + success: handleSuccess
  63 + }
  64 +});
  65 +
  66 +const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
  67 +const superQueryConfig = reactive(superQuerySchema);
  68 +
  69 +function handleSuperQuery(params) {
  70 + Object.assign(queryParam, params);
  71 + reload();
  72 +}
  73 +
  74 +function handleAdd() {
  75 + openModal(true, { isUpdate: false, showFooter: true });
  76 +}
  77 +
  78 +function handleEdit(record) {
  79 + openModal(true, { record, isUpdate: true, showFooter: true });
  80 +}
  81 +
  82 +function handleDetail(record) {
  83 + openModal(true, { record, isUpdate: true, showFooter: false });
  84 +}
  85 +
  86 +async function handleDelete(record) {
  87 + await deleteOne({ id: record.id }, handleSuccess);
  88 +}
  89 +
  90 +async function batchHandleDelete() {
  91 + await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
  92 +}
  93 +
  94 +function handleSuccess() {
  95 + selectedRowKeys.value = [];
  96 + reload();
  97 +}
  98 +
  99 +function getTableAction(record) {
  100 + return [
  101 + { label: '编辑', onClick: handleEdit.bind(null, record) }
  102 + ];
  103 +}
  104 +
  105 +function getDropDownAction(record) {
  106 + return [
  107 + { label: '详情', onClick: handleDetail.bind(null, record) },
  108 + {
  109 + label: '删除',
  110 + popConfirm: {
  111 + title: '确认删除此问答?',
  112 + confirm: handleDelete.bind(null, record)
  113 + }
  114 + }
  115 + ];
  116 +}
  117 +</script>
  1 +<template>
  2 + <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
  3 + <BasicForm @register="registerForm" name="QuestionEmbeddingForm" />
  4 + </BasicModal>
  5 +</template>
  6 +
  7 +<script lang="ts" setup>
  8 +import { ref, computed, unref } from 'vue';
  9 +import { BasicModal, useModalInner } from '/@/components/Modal';
  10 +import { BasicForm, useForm } from '/@/components/Form/index';
  11 +import { formSchema } from '../QuestionEmbedding.data';
  12 +import { saveOrUpdate } from '../QuestionEmbedding.api';
  13 +
  14 +const emit = defineEmits(['register', 'success']);
  15 +const isUpdate = ref(true);
  16 +const isDetail = ref(false);
  17 +
  18 +const [registerForm, { setProps, resetFields, setFieldsValue, validate }] = useForm({
  19 + labelWidth: 150,
  20 + schemas: formSchema,
  21 + showActionButtonGroup: false,
  22 + baseColProps: { span: 24 }
  23 +});
  24 +
  25 +const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
  26 + await resetFields();
  27 + setModalProps({
  28 + confirmLoading: false,
  29 + showCancelBtn: !!data?.showFooter,
  30 + showOkBtn: !!data?.showFooter
  31 + });
  32 +
  33 + isUpdate.value = !!data?.isUpdate;
  34 + isDetail.value = !!data?.showFooter;
  35 +
  36 + if (unref(isUpdate)) {
  37 + await setFieldsValue({
  38 + ...data.record,
  39 + });
  40 + }
  41 +
  42 + setProps({ disabled: !data?.showFooter });
  43 +});
  44 +
  45 +const title = computed(() => (!unref(isUpdate) ? '新增问答' : !unref(isDetail) ? '问答详情' : '编辑问答'));
  46 +
  47 +async function handleSubmit() {
  48 + try {
  49 + const values = await validate();
  50 + setModalProps({ confirmLoading: true });
  51 + await saveOrUpdate(values, isUpdate.value);
  52 + closeModal();
  53 + emit('success');
  54 + } finally {
  55 + setModalProps({ confirmLoading: false });
  56 + }
  57 +}
  58 +</script>
@@ -12,20 +12,53 @@ enum Api { @@ -12,20 +12,53 @@ enum Api {
12 importExcel = '/embeddings/embeddings/importExcel', 12 importExcel = '/embeddings/embeddings/importExcel',
13 exportXls = '/embeddings/embeddings/exportXls', 13 exportXls = '/embeddings/embeddings/exportXls',
14 } 14 }
  15 +
15 /** 16 /**
16 * 导出api 17 * 导出api
17 * @param params 18 * @param params
18 */ 19 */
19 export const getExportUrl = Api.exportXls; 20 export const getExportUrl = Api.exportXls;
  21 +
20 /** 22 /**
21 * 导入api 23 * 导入api
22 */ 24 */
23 export const getImportUrl = Api.importExcel; 25 export const getImportUrl = Api.importExcel;
  26 +
24 /** 27 /**
25 * 列表接口 28 * 列表接口
26 * @param params 29 * @param params
27 */ 30 */
28 -export const list = (params) => defHttp.get({ url: Api.list, params }); 31 +export const list = async (params) => {
  32 + try {
  33 + // 设置足够大的size获取所有数据
  34 + const res = await defHttp.get({
  35 + url: Api.list,
  36 + params: { ...params, size: 1000 }
  37 + });
  38 +
  39 + if (res?.records && Array.isArray(res.records)) {
  40 + // 将metadata中的字段提取到顶层
  41 + res.records = res.records.map(item => ({
  42 + ...item,
  43 + ...item.metadata, // 将metadata中的字段展开
  44 + docName: item.metadata?.docName || '',
  45 + knowledgeId: item.metadata?.knowledgeId || '',
  46 + docId: item.metadata?.docId || '',
  47 + index: item.metadata?.index || '',
  48 + }));
  49 + }
  50 + return res;
  51 + } catch (error) {
  52 + console.error("Error fetching data:", error);
  53 + return {
  54 + records: [],
  55 + total: 0,
  56 + size: 10,
  57 + current: 1,
  58 + pages: 0
  59 + };
  60 + }
  61 +}
29 62
30 /** 63 /**
31 * 删除单个 64 * 删除单个
@@ -35,6 +68,7 @@ export const deleteOne = (params, handleSuccess) => { @@ -35,6 +68,7 @@ export const deleteOne = (params, handleSuccess) => {
35 handleSuccess(); 68 handleSuccess();
36 }); 69 });
37 }; 70 };
  71 +
38 /** 72 /**
39 * 批量删除 73 * 批量删除
40 * @param params 74 * @param params
@@ -53,6 +87,7 @@ export const batchDelete = (params, handleSuccess) => { @@ -53,6 +87,7 @@ export const batchDelete = (params, handleSuccess) => {
53 }, 87 },
54 }); 88 });
55 }; 89 };
  90 +
56 /** 91 /**
57 * 保存或者更新 92 * 保存或者更新
58 * @param params 93 * @param params
1 import { BasicColumn } from '/@/components/Table'; 1 import { BasicColumn } from '/@/components/Table';
2 import { FormSchema } from '/@/components/Table'; 2 import { FormSchema } from '/@/components/Table';
3 -//列表数据 3 +
  4 +// 列表数据
4 export const columns: BasicColumn[] = [ 5 export const columns: BasicColumn[] = [
5 { 6 {
6 - title: 'name', 7 + title: 'ID',
  8 + align: 'center',
  9 + dataIndex: 'id',
  10 + width: 200,
  11 + },
  12 + {
  13 + title: '文本内容',
7 align: 'center', 14 align: 'center',
8 - dataIndex: 'name', 15 + dataIndex: 'text',
  16 + width: 300
  17 + },
  18 + {
  19 + title: '文件名称',
  20 + align: 'center',
  21 + dataIndex: 'docName',
  22 + width: 150
  23 + },
  24 + {
  25 + title: '知识ID',
  26 + align: 'center',
  27 + dataIndex: 'knowledgeId',
  28 + width: 150
  29 + },
  30 + {
  31 + title: '文档ID',
  32 + align: 'center',
  33 + dataIndex: 'docId',
  34 + width: 150
  35 + },
  36 + {
  37 + title: '索引',
  38 + align: 'center',
  39 + dataIndex: 'index',
  40 + width: 80
  41 + },
  42 + {
  43 + title: '相似度',
  44 + align: 'center',
  45 + dataIndex: 'similarity',
  46 + width: 100
9 }, 47 },
10 ]; 48 ];
11 -//查询数据 49 +
  50 +// 查询数据
12 export const searchFormSchema: FormSchema[] = []; 51 export const searchFormSchema: FormSchema[] = [];
13 -//表单数据 52 +
  53 +// 表单数据
14 export const formSchema: FormSchema[] = [ 54 export const formSchema: FormSchema[] = [
15 { 55 {
16 - label: 'name',  
17 - field: 'name', 56 + label: 'text',
  57 + field: 'text',
18 component: 'Input', 58 component: 'Input',
19 }, 59 },
20 - // TODO 主键隐藏字段,目前写死为ID  
21 { 60 {
22 label: '', 61 label: '',
23 field: 'id', 62 field: 'id',
@@ -28,7 +67,7 @@ export const formSchema: FormSchema[] = [ @@ -28,7 +67,7 @@ export const formSchema: FormSchema[] = [
28 67
29 // 高级查询数据 68 // 高级查询数据
30 export const superQuerySchema = { 69 export const superQuerySchema = {
31 - name: { title: 'name', order: 0, view: 'text', type: 'string' }, 70 + name: { title: 'text', order: 0, view: 'text', type: 'string' },
32 }; 71 };
33 72
34 /** 73 /**
@@ -36,6 +75,5 @@ export const superQuerySchema = { @@ -36,6 +75,5 @@ export const superQuerySchema = {
36 * @param param 75 * @param param
37 */ 76 */
38 export function getBpmFormSchema(_formData): FormSchema[] { 77 export function getBpmFormSchema(_formData): FormSchema[] {
39 - // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema  
40 return formSchema; 78 return formSchema;
41 } 79 }
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { useMessage } from '/@/hooks/web/useMessage';
  3 +
  4 +const { createMessage } = useMessage();
  5 +
  6 +enum Api {
  7 + send = '/airag/zdyRag/send',
  8 +}
  9 +
  10 +/**
  11 + * 发送消息
  12 + * @param params
  13 + */
  14 +export const sendMessage = async (params: { questionText: string }) => {
  15 + try {
  16 + const res = await defHttp.get({
  17 + url: Api.send,
  18 + params,
  19 + });
  20 + return res;
  21 + } catch (error) {
  22 + console.error("Error sending message:", error);
  23 + createMessage.error('发送消息失败');
  24 + return null;
  25 + }
  26 +};
  27 +
  28 +// export const sendMessage = (params) => {
  29 +// return defHttp.get({ url: Api.send, params }, { isTransformResponse: false });
  30 +// };
  31 +
  32 +// 保留其他API方法,但聊天界面可能不需要它们
  33 +export const list = async () => ({ records: [] });
  34 +export const deleteOne = async () => {};
  35 +export const batchDelete = async () => {};
  36 +export const getImportUrl = Api.send;
  37 +export const getExportUrl = Api.send;
  1 +import { BasicColumn } from '/@/components/Table';
  2 +import { FormSchema } from '/@/components/Table';
  3 +
  4 +// 列表数据
  5 +export const columns: BasicColumn[] = [
  6 + {
  7 + title: 'ID',
  8 + align: 'center',
  9 + dataIndex: 'id',
  10 + width: 200,
  11 + },
  12 + {
  13 + title: '文本内容',
  14 + align: 'center',
  15 + dataIndex: 'text',
  16 + width: 300
  17 + },
  18 + {
  19 + title: '文件名称',
  20 + align: 'center',
  21 + dataIndex: 'docName',
  22 + width: 150
  23 + },
  24 + {
  25 + title: '知识ID',
  26 + align: 'center',
  27 + dataIndex: 'knowledgeId',
  28 + width: 150
  29 + },
  30 + {
  31 + title: '文档ID',
  32 + align: 'center',
  33 + dataIndex: 'docId',
  34 + width: 150
  35 + },
  36 + {
  37 + title: '索引',
  38 + align: 'center',
  39 + dataIndex: 'index',
  40 + width: 80
  41 + },
  42 + {
  43 + title: '相似度',
  44 + align: 'center',
  45 + dataIndex: 'similarity',
  46 + width: 100
  47 + },
  48 +];
  49 +
  50 +// 查询数据
  51 +export const searchFormSchema: FormSchema[] = [];
  52 +
  53 +// 表单数据
  54 +export const formSchema: FormSchema[] = [
  55 + {
  56 + label: 'text',
  57 + field: 'text',
  58 + component: 'Input',
  59 + },
  60 + {
  61 + label: '',
  62 + field: 'id',
  63 + component: 'Input',
  64 + show: false,
  65 + },
  66 +];
  67 +
  68 +// 高级查询数据
  69 +export const superQuerySchema = {
  70 + name: { title: 'text', order: 0, view: 'text', type: 'string' },
  71 +};
  72 +
  73 +/**
  74 + * 流程表单调用这个方法获取formSchema
  75 + * @param param
  76 + */
  77 +export function getBpmFormSchema(_formData): FormSchema[] {
  78 + return formSchema;
  79 +}
  1 +<template>
  2 + <div class="chat-container">
  3 + <!-- 聊天消息区域 -->
  4 + <div class="chat-messages" ref="messagesContainer">
  5 + <div v-for="(message, index) in messages" :key="index" :class="['message', message.type]">
  6 + <div class="message-content">
  7 + <div class="message-text">{{ message.text }}</div>
  8 + <div v-if="message.type === 'assistant' && message.similarity" class="message-meta">
  9 + 相似度: {{ (message.similarity * 100).toFixed(2) }}%
  10 + </div>
  11 + </div>
  12 + </div>
  13 + <div v-if="loading" class="message assistant">
  14 + <div class="message-content">
  15 + <div class="message-text">思考中...</div>
  16 + </div>
  17 + </div>
  18 + </div>
  19 +
  20 + <!-- 输入区域 -->
  21 + <div class="chat-input">
  22 + <a-input
  23 + v-model:value="inputMessage"
  24 + placeholder="请输入您的问题..."
  25 + @pressEnter="sendMessage"
  26 + :disabled="loading"
  27 + />
  28 + <a-button
  29 + type="primary"
  30 + @click="sendMessage"
  31 + :loading="loading"
  32 + :disabled="!inputMessage.trim()"
  33 + >
  34 + 发送
  35 + </a-button>
  36 + </div>
  37 + </div>
  38 +</template>
  39 +
  40 +<script lang="ts" name="zdy-rag-chat" setup>
  41 +import { ref, reactive, nextTick } from 'vue';
  42 +import { list, sendMessage as apiSendMessage } from './ZdyRag.api';
  43 +import { BasicTable, useTable } from '/@/components/Table';
  44 +import { useMessage } from '/@/hooks/web/useMessage';
  45 +
  46 +const { createMessage } = useMessage();
  47 +
  48 +// 聊天消息数据
  49 +const messages = reactive([
  50 + {
  51 + type: 'assistant',
  52 + text: '您好!我是智能助手,请问有什么可以帮您?',
  53 + similarity: null
  54 + }
  55 +]);
  56 +
  57 +const inputMessage = ref('');
  58 +const loading = ref(false);
  59 +const messagesContainer = ref<HTMLElement>();
  60 +
  61 +// 发送消息
  62 +const sendMessage = async () => {
  63 + const text = inputMessage.value.trim();
  64 + if (!text || loading.value) return;
  65 +
  66 + // 添加用户消息
  67 + messages.push({
  68 + type: 'user',
  69 + text: text,
  70 + similarity: null
  71 + });
  72 +
  73 + inputMessage.value = '';
  74 + loading.value = true;
  75 +
  76 + try {
  77 + // 调用API发送消息
  78 + const res = await apiSendMessage({ questionText: text });
  79 + console.log("res....",res)
  80 + if (res?.answer) {
  81 + messages.push({
  82 + type: 'assistant',
  83 + text: res.answer,
  84 + similarity: res.similarity || null
  85 + });
  86 + } else {
  87 + createMessage.error('获取回答失败');
  88 + }
  89 + } catch (error) {
  90 + console.error('发送消息失败:', error);
  91 + createMessage.error('发送消息失败');
  92 + } finally {
  93 + loading.value = false;
  94 + scrollToBottom();
  95 + }
  96 +};
  97 +
  98 +// 滚动到底部
  99 +const scrollToBottom = () => {
  100 + nextTick(() => {
  101 + if (messagesContainer.value) {
  102 + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
  103 + }
  104 + });
  105 +};
  106 +</script>
  107 +
  108 +<style lang="less" scoped>
  109 +.chat-container {
  110 + display: flex;
  111 + flex-direction: column;
  112 + height: calc(100vh - 120px);
  113 + max-width: 800px;
  114 + margin: 0 auto;
  115 + padding: 20px;
  116 + background-color: #f5f5f5;
  117 + border-radius: 8px;
  118 +}
  119 +
  120 +.chat-messages {
  121 + flex: 1;
  122 + overflow-y: auto;
  123 + padding: 10px;
  124 + margin-bottom: 20px;
  125 + background-color: white;
  126 + border-radius: 8px;
  127 + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  128 +}
  129 +
  130 +.message {
  131 + margin-bottom: 15px;
  132 + display: flex;
  133 +
  134 + &.user {
  135 + justify-content: flex-end;
  136 +
  137 + .message-content {
  138 + background-color: #1890ff;
  139 + color: white;
  140 + border-radius: 18px 18px 0 18px;
  141 + }
  142 + }
  143 +
  144 + &.assistant {
  145 + justify-content: flex-start;
  146 +
  147 + .message-content {
  148 + background-color: #f0f0f0;
  149 + color: #333;
  150 + border-radius: 18px 18px 18px 0;
  151 + }
  152 + }
  153 +}
  154 +
  155 +.message-content {
  156 + max-width: 70%;
  157 + padding: 10px 15px;
  158 + word-wrap: break-word;
  159 +}
  160 +
  161 +.message-meta {
  162 + font-size: 12px;
  163 + color: #666;
  164 + margin-top: 5px;
  165 + text-align: right;
  166 +}
  167 +
  168 +.chat-input {
  169 + display: flex;
  170 + gap: 10px;
  171 +
  172 + :deep(.ant-input) {
  173 + flex: 1;
  174 + border-radius: 20px;
  175 + padding: 10px 15px;
  176 + }
  177 +
  178 + .ant-btn {
  179 + border-radius: 20px;
  180 + padding: 0 20px;
  181 + }
  182 +}
  183 +</style>
  1 +<template>
  2 + <div style="min-height: 400px">
  3 + <BasicForm @register="registerForm"></BasicForm>
  4 + <div style="width: 100%;text-align: center" v-if="!formDisabled">
  5 + <a-button @click="submitForm" pre-icon="ant-design:check" type="primary">提 交</a-button>
  6 + </div>
  7 + </div>
  8 +</template>
  9 +
  10 +<script lang="ts">
  11 + import {BasicForm, useForm} from '/@/components/Form/index';
  12 + import {computed, defineComponent} from 'vue';
  13 + import {defHttp} from '/@/utils/http/axios';
  14 + import { propTypes } from '/@/utils/propTypes';
  15 + import {getBpmFormSchema} from '../ZdyRag.data';
  16 + import {saveOrUpdate} from '../ZdyRag.api';
  17 +
  18 + export default defineComponent({
  19 + name: "TestForm",
  20 + components:{
  21 + BasicForm
  22 + },
  23 + props:{
  24 + formData: propTypes.object.def({}),
  25 + formBpm: propTypes.bool.def(true),
  26 + },
  27 + setup(props){
  28 + const [registerForm, { setFieldsValue, setProps, getFieldsValue }] = useForm({
  29 + labelWidth: 150,
  30 + schemas: getBpmFormSchema(props.formData),
  31 + showActionButtonGroup: false,
  32 + baseColProps: {span: 24}
  33 + });
  34 +
  35 + const formDisabled = computed(()=>{
  36 + if(props.formData.disabled === false){
  37 + return false;
  38 + }
  39 + return true;
  40 + });
  41 +
  42 + let formData = {};
  43 + const queryByIdUrl = '/test/test/queryById';
  44 + async function initFormData(){
  45 + let params = {id: props.formData.dataId};
  46 + const data = await defHttp.get({url: queryByIdUrl, params});
  47 + formData = {...data}
  48 + //设置表单的值
  49 + await setFieldsValue(formData);
  50 + //默认是禁用
  51 + await setProps({disabled: formDisabled.value})
  52 + }
  53 +
  54 + async function submitForm() {
  55 + let data = getFieldsValue();
  56 + let params = Object.assign({}, formData, data);
  57 + console.log('表单数据', params)
  58 + await saveOrUpdate(params, true)
  59 + }
  60 +
  61 + initFormData();
  62 +
  63 + return {
  64 + registerForm,
  65 + formDisabled,
  66 + submitForm,
  67 + }
  68 + }
  69 + });
  70 +</script>
  1 +<template>
  2 + <BasicModal v-bind="$attrs" @register="registerModal" destroyOnClose :title="title" :width="800" @ok="handleSubmit">
  3 + <BasicForm @register="registerForm" name="TestForm" />
  4 + </BasicModal>
  5 +</template>
  6 +
  7 +<script lang="ts" setup>
  8 + import {ref, computed, unref} from 'vue';
  9 + import {BasicModal, useModalInner} from '/@/components/Modal';
  10 + import {BasicForm, useForm} from '/@/components/Form/index';
  11 + import {formSchema} from '../ZdyRag.data';
  12 + import {saveOrUpdate} from '../ZdyRag.api';
  13 + // Emits声明
  14 + const emit = defineEmits(['register','success']);
  15 + const isUpdate = ref(true);
  16 + const isDetail = ref(false);
  17 + //表单配置
  18 + const [registerForm, { setProps,resetFields, setFieldsValue, validate, scrollToField }] = useForm({
  19 + labelWidth: 150,
  20 + schemas: formSchema,
  21 + showActionButtonGroup: false,
  22 + baseColProps: {span: 24}
  23 + });
  24 + //表单赋值
  25 + const [registerModal, {setModalProps, closeModal}] = useModalInner(async (data) => {
  26 + //重置表单
  27 + await resetFields();
  28 + setModalProps({confirmLoading: false,showCancelBtn:!!data?.showFooter,showOkBtn:!!data?.showFooter});
  29 + isUpdate.value = !!data?.isUpdate;
  30 + isDetail.value = !!data?.showFooter;
  31 + if (unref(isUpdate)) {
  32 + //表单赋值
  33 + await setFieldsValue({
  34 + ...data.record,
  35 + });
  36 + }
  37 + // 隐藏底部时禁用整个表单
  38 + setProps({ disabled: !data?.showFooter })
  39 + });
  40 + //设置标题
  41 + const title = computed(() => (!unref(isUpdate) ? '新增' : !unref(isDetail) ? '详情' : '编辑'));
  42 + //表单提交事件
  43 + async function handleSubmit(v) {
  44 + try {
  45 + let values = await validate();
  46 + setModalProps({confirmLoading: true});
  47 + //提交表单
  48 + await saveOrUpdate(values, isUpdate.value);
  49 + //关闭弹窗
  50 + closeModal();
  51 + //刷新列表
  52 + emit('success');
  53 + } catch ({ errorFields }) {
  54 + if (errorFields) {
  55 + const firstField = errorFields[0];
  56 + if (firstField) {
  57 + scrollToField(firstField.name, { behavior: 'smooth', block: 'center' });
  58 + }
  59 + }
  60 + return Promise.reject(errorFields);
  61 + } finally {
  62 + setModalProps({confirmLoading: false});
  63 + }
  64 + }
  65 +</script>
  66 +
  67 +<style lang="less" scoped>
  68 + /** 时间和数字输入框样式 */
  69 + :deep(.ant-input-number) {
  70 + width: 100%;
  71 + }
  72 +
  73 + :deep(.ant-calendar-picker) {
  74 + width: 100%;
  75 + }
  76 +</style>