作者 lixiang

问题库以及自定义问答

@@ -16,22 +16,22 @@ export const sendMessage = async (params: { questionText: string }) => { @@ -16,22 +16,22 @@ export const sendMessage = async (params: { questionText: string }) => {
16 const res = await defHttp.get({ 16 const res = await defHttp.get({
17 url: Api.send, 17 url: Api.send,
18 params, 18 params,
  19 + timeout: 60000
19 }); 20 });
20 - return res; 21 + console.log("res...",res)
  22 + // 确保返回的数据结构正确
  23 + if (res ) {
  24 + return {
  25 + answer: res.answer || '',
  26 + similarity: res.similarity || 0,
  27 + fileName: res.fileName || '',
  28 + fileBase64: res.fileBase64 || null
  29 + };
  30 + }
  31 + return null;
21 } catch (error) { 32 } catch (error) {
22 console.error("Error sending message:", error); 33 console.error("Error sending message:", error);
23 createMessage.error('发送消息失败'); 34 createMessage.error('发送消息失败');
24 return null; 35 return null;
25 } 36 }
26 }; 37 };
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 <template> 1 <template>
2 <div class="chat-container"> 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) }}% 3 + <div class="chat-layout">
  4 + <!-- 聊天消息区域 -->
  5 + <div class="chat-messages" ref="messagesContainer">
  6 + <div v-for="(message, index) in messages" :key="index" :class="['message', message.type]">
  7 + <div class="message-content">
  8 + <div class="message-text" v-html="message.text"></div>
  9 + <div v-if="message.type === 'assistant' && message.similarity" class="message-meta">
  10 + 相似度: {{ (message.similarity * 100).toFixed(2) }}%
  11 + <a v-if="message.fileBase64" @click="showPreview(message)">预览文件</a>
  12 + </div>
  13 + </div>
  14 + </div>
  15 + <div v-if="loading" class="message assistant">
  16 + <div class="message-content">
  17 + <div class="message-text">思考中...</div>
10 </div> 18 </div>
11 </div> 19 </div>
12 </div> 20 </div>
13 - <div v-if="loading" class="message assistant">  
14 - <div class="message-content">  
15 - <div class="message-text">思考中...</div> 21 +
  22 + <!-- 文件预览区域 - 右侧 -->
  23 + <div v-if="previewContent" class="file-preview">
  24 + <div class="preview-header">
  25 + <span>文件预览</span>
  26 + <a-button type="text" @click="closePreview">
  27 + <template #icon><CloseOutlined /></template>
  28 + </a-button>
  29 + </div>
  30 + <div class="preview-content">
  31 + <div v-if="previewType === 'txt' || previewType === 'md'" class="text-preview">
  32 + <pre>{{ previewContent }}</pre>
  33 + </div>
  34 + <div v-else-if="previewType === 'doc' || previewType === 'docx'" class="doc-preview">
  35 + <!-- 使用Blob URL直接预览 -->
  36 + <iframe
  37 + :src="previewUrl"
  38 + frameborder="0"
  39 + style="width:100%; height:100%;"
  40 + ></iframe>
  41 + </div>
16 </div> 42 </div>
17 </div> 43 </div>
18 </div> 44 </div>
@@ -39,9 +65,9 @@ @@ -39,9 +65,9 @@
39 65
40 <script lang="ts" name="zdy-rag-chat" setup> 66 <script lang="ts" name="zdy-rag-chat" setup>
41 import { ref, reactive, nextTick } from 'vue'; 67 import { ref, reactive, nextTick } from 'vue';
42 -import { list, sendMessage as apiSendMessage } from './ZdyRag.api';  
43 -import { BasicTable, useTable } from '/@/components/Table'; 68 +import { sendMessage as apiSendMessage } from './ZdyRag.api';
44 import { useMessage } from '/@/hooks/web/useMessage'; 69 import { useMessage } from '/@/hooks/web/useMessage';
  70 +import { CloseOutlined } from '@ant-design/icons-vue';
45 71
46 const { createMessage } = useMessage(); 72 const { createMessage } = useMessage();
47 73
@@ -50,13 +76,17 @@ const messages = reactive([ @@ -50,13 +76,17 @@ const messages = reactive([
50 { 76 {
51 type: 'assistant', 77 type: 'assistant',
52 text: '您好!我是智能助手,请问有什么可以帮您?', 78 text: '您好!我是智能助手,请问有什么可以帮您?',
53 - similarity: null 79 + similarity: null,
  80 + fileBase64: null
54 } 81 }
55 ]); 82 ]);
56 83
57 const inputMessage = ref(''); 84 const inputMessage = ref('');
58 const loading = ref(false); 85 const loading = ref(false);
59 const messagesContainer = ref<HTMLElement>(); 86 const messagesContainer = ref<HTMLElement>();
  87 +const previewContent = ref('');
  88 +const previewType = ref('');
  89 +const previewUrl = ref('');
60 90
61 // 发送消息 91 // 发送消息
62 const sendMessage = async () => { 92 const sendMessage = async () => {
@@ -67,34 +97,102 @@ const sendMessage = async () => { @@ -67,34 +97,102 @@ const sendMessage = async () => {
67 messages.push({ 97 messages.push({
68 type: 'user', 98 type: 'user',
69 text: text, 99 text: text,
70 - similarity: null 100 + similarity: null,
  101 + fileBase64: null
71 }); 102 });
72 103
73 inputMessage.value = ''; 104 inputMessage.value = '';
74 loading.value = true; 105 loading.value = true;
  106 + closePreview();
75 107
76 try { 108 try {
77 // 调用API发送消息 109 // 调用API发送消息
78 const res = await apiSendMessage({ questionText: text }); 110 const res = await apiSendMessage({ questionText: text });
79 - console.log("res....",res)  
80 - if (res?.answer) {  
81 - messages.push({ 111 + console.log('API响应:', res);
  112 +
  113 + if (res) {
  114 + const newMessage = {
82 type: 'assistant', 115 type: 'assistant',
83 - text: res.answer,  
84 - similarity: res.similarity || null  
85 - }); 116 + text: formatAnswer(res.answer),
  117 + similarity: res.similarity || null,
  118 + fileBase64: res.fileBase64 || null,
  119 + fileName: res.fileName || ''
  120 + };
  121 + messages.push(newMessage);
  122 +
  123 + // 如果有文件内容,自动显示预览
  124 + if (res.fileBase64 && res.fileName) {
  125 + showPreview(newMessage);
  126 + }
86 } else { 127 } else {
87 - createMessage.error('获取回答失败'); 128 + createMessage.error('获取回答失败: 返回数据为空');
88 } 129 }
89 } catch (error) { 130 } catch (error) {
90 console.error('发送消息失败:', error); 131 console.error('发送消息失败:', error);
91 - createMessage.error('发送消息失败'); 132 + createMessage.error('发送消息失败: ' + (error as Error).message);
92 } finally { 133 } finally {
93 loading.value = false; 134 loading.value = false;
94 scrollToBottom(); 135 scrollToBottom();
95 } 136 }
96 }; 137 };
97 138
  139 +// 显示文件预览
  140 +const showPreview = (message) => {
  141 + if (!message.fileBase64 || !message.fileName) {
  142 + console.warn('无法预览: 缺少fileBase64或fileName');
  143 + return;
  144 + }
  145 +
  146 + try {
  147 + const fileExt = message.fileName.split('.').pop()?.toLowerCase() || '';
  148 + previewType.value = fileExt;
  149 +
  150 + if (fileExt === 'txt' || fileExt === 'md') {
  151 + // 对于文本文件,直接解码显示
  152 + previewContent.value = atob(message.fileBase64);
  153 + } else if (fileExt === 'doc' || fileExt === 'docx') {
  154 + // 对于Word文档,创建Blob对象并生成URL
  155 + const byteCharacters = atob(message.fileBase64);
  156 + const byteNumbers = new Array(byteCharacters.length);
  157 + for (let i = 0; i < byteCharacters.length; i++) {
  158 + byteNumbers[i] = byteCharacters.charCodeAt(i);
  159 + }
  160 + const byteArray = new Uint8Array(byteNumbers);
  161 + const blob = new Blob([byteArray], {
  162 + type: fileExt === 'docx' ?
  163 + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' :
  164 + 'application/msword'
  165 + });
  166 +
  167 + // 直接使用Blob URL
  168 + previewUrl.value = URL.createObjectURL(blob);
  169 + previewContent.value = 'doc-preview';
  170 + } else {
  171 + console.warn('不支持的文件类型:', fileExt);
  172 + createMessage.warning(`不支持预览 ${fileExt} 格式的文件`);
  173 + }
  174 + } catch (error) {
  175 + console.error('文件预览失败:', error);
  176 + createMessage.error('文件预览失败');
  177 + }
  178 +};
  179 +
  180 +// 关闭预览
  181 +const closePreview = () => {
  182 + previewContent.value = '';
  183 + previewType.value = '';
  184 + if (previewUrl.value) {
  185 + URL.revokeObjectURL(previewUrl.value);
  186 + previewUrl.value = '';
  187 + }
  188 +};
  189 +
  190 +// 格式化回答内容
  191 +const formatAnswer = (answer: string) => {
  192 + // 替换[n]为换行符,然后将换行符转换为<br>标签
  193 + return answer.replace(/\[n\]/g, '\n').replace(/\n/g, '<br>');
  194 +};
  195 +
98 // 滚动到底部 196 // 滚动到底部
99 const scrollToBottom = () => { 197 const scrollToBottom = () => {
100 nextTick(() => { 198 nextTick(() => {
@@ -110,23 +208,71 @@ const scrollToBottom = () => { @@ -110,23 +208,71 @@ const scrollToBottom = () => {
110 display: flex; 208 display: flex;
111 flex-direction: column; 209 flex-direction: column;
112 height: calc(100vh - 120px); 210 height: calc(100vh - 120px);
113 - max-width: 800px; 211 + max-width: 1200px; /* 增加宽度以容纳两个区域 */
114 margin: 0 auto; 212 margin: 0 auto;
115 padding: 20px; 213 padding: 20px;
116 background-color: #f5f5f5; 214 background-color: #f5f5f5;
117 border-radius: 8px; 215 border-radius: 8px;
118 } 216 }
119 217
120 -.chat-messages { 218 +.chat-layout {
  219 + display: flex;
121 flex: 1; 220 flex: 1;
  221 + gap: 20px;
  222 + overflow: hidden;
  223 + margin-bottom: 20px;
  224 +}
  225 +
  226 +.chat-messages {
  227 + flex: 2;
122 overflow-y: auto; 228 overflow-y: auto;
123 padding: 10px; 229 padding: 10px;
124 - margin-bottom: 20px;  
125 background-color: white; 230 background-color: white;
126 border-radius: 8px; 231 border-radius: 8px;
127 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 232 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
128 } 233 }
129 234
  235 +.file-preview {
  236 + flex: 1;
  237 + min-width: 350px; /* 确保预览区域有最小宽度 */
  238 + background-color: white;
  239 + border-radius: 8px;
  240 + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  241 + overflow: hidden;
  242 + display: flex;
  243 + flex-direction: column;
  244 +
  245 + .preview-header {
  246 + display: flex;
  247 + justify-content: space-between;
  248 + align-items: center;
  249 + padding: 10px 15px;
  250 + background-color: #f0f0f0;
  251 + border-bottom: 1px solid #e8e8e8;
  252 + }
  253 +
  254 + .preview-content {
  255 + flex: 1;
  256 + overflow: auto;
  257 +
  258 + .text-preview {
  259 + padding: 15px;
  260 + white-space: pre-wrap;
  261 + font-family: monospace;
  262 + }
  263 +
  264 + .doc-preview {
  265 + height: 100%;
  266 +
  267 + iframe {
  268 + width: 100%;
  269 + height: 100%;
  270 + border: none;
  271 + }
  272 + }
  273 + }
  274 +}
  275 +
130 .message { 276 .message {
131 margin-bottom: 15px; 277 margin-bottom: 15px;
132 display: flex; 278 display: flex;
@@ -163,6 +309,16 @@ const scrollToBottom = () => { @@ -163,6 +309,16 @@ const scrollToBottom = () => {
163 color: #666; 309 color: #666;
164 margin-top: 5px; 310 margin-top: 5px;
165 text-align: right; 311 text-align: right;
  312 +
  313 + a {
  314 + margin-left: 10px;
  315 + color: #1890ff;
  316 + cursor: pointer;
  317 +
  318 + &:hover {
  319 + text-decoration: underline;
  320 + }
  321 + }
166 } 322 }
167 323
168 .chat-input { 324 .chat-input {
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> 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 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 - }); 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 34
35 - const formDisabled = computed(()=>{  
36 - if(props.formData.disabled === false){  
37 - return false;  
38 - }  
39 - return true;  
40 - }); 35 +<!-- const formDisabled = computed(()=>{-->
  36 +<!-- if(props.formData.disabled === false){-->
  37 +<!-- return false;-->
  38 +<!-- }-->
  39 +<!-- return true;-->
  40 +<!-- });-->
41 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 - } 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 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 - } 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 60
61 - initFormData();  
62 -  
63 - return {  
64 - registerForm,  
65 - formDisabled,  
66 - submitForm,  
67 - }  
68 - }  
69 - });  
70 -</script> 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> 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 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> 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 66
67 -<style lang="less" scoped>  
68 - /** 时间和数字输入框样式 */  
69 - :deep(.ant-input-number) {  
70 - width: 100%;  
71 - } 67 +<!--<style lang="less" scoped>-->
  68 +<!-- /** 时间和数字输入框样式 */-->
  69 +<!-- :deep(.ant-input-number) {-->
  70 +<!-- width: 100%;-->
  71 +<!-- }-->
72 72
73 - :deep(.ant-calendar-picker) {  
74 - width: 100%;  
75 - }  
76 -</style> 73 +<!-- :deep(.ant-calendar-picker) {-->
  74 +<!-- width: 100%;-->
  75 +<!-- }-->
  76 +<!--</style>-->