Bläddra i källkod

增强更新通知组件,添加5秒倒计时功能,允许用户在倒计时结束后关闭通知。同时,优化关闭按钮的样式和交互逻辑,确保用户体验更流畅。

刘博博 1 månad sedan
förälder
incheckning
2a750f3f2a

+ 36 - 4
src/help/components/UpdateNotification/UpdateNotification.tsx

@@ -15,6 +15,8 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
   const [content, setContent] = useState('');
   const [isAnimating, setIsAnimating] = useState(false);
   const [previewImage, setPreviewImage] = useState<string | null>(null);
+  const [countdown, setCountdown] = useState(5); // 5秒倒计时
+  const [canClose, setCanClose] = useState(false); // 是否可以关闭
 
   useEffect(() => {
     // 检查是否被禁用
@@ -49,6 +51,9 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
           setTimeout(() => {
             setVisible(true);
             setIsAnimating(true);
+            // 重置倒计时
+            setCountdown(5);
+            setCanClose(false);
           }, 800);
         })
         .catch((error) => {
@@ -57,7 +62,22 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
     }
   }, [version]);
 
+  // 倒计时逻辑
+  useEffect(() => {
+    if (visible && countdown > 0) {
+      const timer = setTimeout(() => {
+        setCountdown(countdown - 1);
+      }, 1000);
+      return () => clearTimeout(timer);
+    } else if (visible && countdown === 0) {
+      setCanClose(true);
+    }
+  }, [visible, countdown]);
+
   const handleClose = () => {
+    // 只有倒计时结束后才允许关闭
+    if (!canClose) return;
+    
     setIsAnimating(false);
     setTimeout(() => {
       setVisible(false);
@@ -80,6 +100,7 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
       <div 
         className={`update-notification-mask ${isAnimating ? 'show' : ''}`}
         onClick={handleClose}
+        style={{ cursor: canClose ? 'pointer' : 'not-allowed' }}
       />
       
       {/* 弹窗 */}
@@ -91,6 +112,10 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
           <CloseOutlined 
             className="update-notification-close" 
             onClick={handleClose}
+            style={{ 
+              cursor: canClose ? 'pointer' : 'not-allowed',
+              opacity: canClose ? 1 : 0.5
+            }}
           />
         </div>
         <div className="update-notification-content">
@@ -126,16 +151,23 @@ const UpdateNotification: React.FC<UpdateNotificationProps> = ({ version }) => {
           </ReactMarkdown>
         </div>
         <div className="update-notification-footer">
-          <button className="update-notification-button" onClick={handleClose}>
-            关闭
+          <button 
+            className="update-notification-button" 
+            onClick={handleClose}
+            disabled={!canClose}
+            style={{ 
+              cursor: canClose ? 'pointer' : 'not-allowed',
+              opacity: canClose ? 1 : 0.6
+            }}
+          >
+            {canClose ? '已知晓' : `${countdown}秒后可关闭`}
           </button>
           <div 
             className="footer-notice" 
             onClick={() => window.open('/help/update-history', '_blank')}
             style={{ cursor: 'pointer' }}
           >
-            <ExclamationCircleOutlined className="info-icon" />
-            <span className="notice-text">版本公告将在帮助文档中</span>
+            <span className="notice-text">!版本公告将在帮助文档中</span>
           </div>
         </div>
       </div>

+ 408 - 261
src/pages/deepseek/knowledgeLib/slice/detail/index.tsx

@@ -1,304 +1,451 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { generatePath, useLocation, useParams } from 'react-router-dom';
-import { Form, Input, Button, message, Upload, UploadProps } from 'antd';
-import { apis } from '@/apis';
-import router from '@/router';
-import { ArrowLeftOutlined } from '@ant-design/icons';
-import config, { getHeaders } from '@/apis/config';
+import * as React from "react";
+import { observer } from "mobx-react";
+import { generatePath, useLocation, useParams } from "react-router-dom";
+import { Form, Input, Button, message, Upload, UploadProps, Spin } from "antd";
+import { apis } from "@/apis";
+import router from "@/router";
+import { ArrowLeftOutlined } from "@ant-design/icons";
+import config, { getHeaders } from "@/apis/config";
 const { TextArea } = Input;
-import { useState, useRef, useEffect } from 'react';
-import { EyeOutlined, CloseOutlined } from '@ant-design/icons';
+import { useState, useRef, useEffect } from "react";
+import { EyeOutlined, CloseOutlined } from "@ant-design/icons";
 const FormItem = Form.Item;
 // 引入富文本
 import MarkdownIt from "markdown-it";
 import MdEditor from "react-markdown-editor-lite";
 import "react-markdown-editor-lite/lib/index.css";
-import './style.less';
+import "./style.less";
 
 // 初始化 markdown-it
 const mdParser = new MarkdownIt({
   html: true,
-  typographer: true
+  typographer: true,
 });
 const ar2 = [
   {
-    name:'我是图片1',
-    url:'https://jkeckms.ryuiso.com/chat-bg.jpg'
+    name: "我是图片1",
+    url: "https://jkeckms.ryuiso.com/chat-bg.jpg",
   },
   {
-    name:'我是图片2',
-    url:'https://jkeckms.ryuiso.com/chat-bg.jpg'
+    name: "我是图片2",
+    url: "https://jkeckms.ryuiso.com/chat-bg.jpg",
   },
   {
-    name:'我是图片3',
-    url:'https://jkeckms.ryuiso.com/chat-bg.jpg'
-  }
-]
+    name: "我是图片3",
+    url: "https://jkeckms.ryuiso.com/chat-bg.jpg",
+  },
+];
 interface MdImg {
   name: string;
   url: string;
 }
 
 const SliceDetail: React.FC = () => {
-    const [form] = Form.useForm();
-    const params = useParams();
-    const location = useLocation();
-    // console.log('location------',location);
-    const { text = '', page } = location.state??{};
-    const [listLoading, setListLoading] = React.useState(false);
-    const [mdImgUrlList, setMdImgUrlList] = useState<MdImg[]>();
-
-
+  const [form] = Form.useForm();
+  const params = useParams();
+  const location = useLocation();
+  // console.log('location------',location);
+  const { text = "", page } = location.state ?? {};
+  const [listLoading, setListLoading] = React.useState(false);
+  const [mdImgUrlList, setMdImgUrlList] = useState<MdImg[]>();
+  const [sliceList, setSliceList] = useState<string[]>([]); // 存储切片ID列表
+  const [currentIndex, setCurrentIndex] = useState<number>(-1); // 当前切片在列表中的索引
+  const [switchLoading, setSwitchLoading] = useState(false); // 切换切片时的loading状态
 
-    const appApi = {
-        fetchList: async () => {
-            setListLoading(true);
-            try {
-                if (!params.sliceId || !params.knowledgeId) {
-                    throw new Error('参数错误');
-                }
-                const res = await apis.fetchTakaiSliceDetail(params.sliceId, params.knowledgeId);
-                const info = res.data.data;
-                form.setFieldsValue({
-                    slice_id: info.slice_id,
-                    slice_text: info.slice_text,
-                    document_id: info.document_id,
-                })
-                setContentMd(info.slice_text || '');
-                setMdImgUrlList(info.imageList || []);
-            } catch (error) {
-                console.error(error);
-            } finally {
-                setListLoading(false);
-            }
+  const appApi = {
+    fetchList: async () => {
+      setListLoading(true);
+      try {
+        if (!params.sliceId || !params.knowledgeId) {
+          throw new Error("参数错误");
         }
-    };
-
-    const init = async () => {
-        if (params.sliceId && params.sliceId !== 'new') {
-            await appApi.fetchList();
+        const res = await apis.fetchTakaiSliceDetail(
+          params.sliceId,
+          params.knowledgeId
+        );
+        const info = res.data.data;
+        form.setFieldsValue({
+          slice_id: info.slice_id,
+          slice_text: info.slice_text,
+          document_id: info.document_id,
+        });
+        setContentMd(info.slice_text || "");
+        setMdImgUrlList(info.imageList || []);
+      } catch (error) {
+        console.error(error);
+      } finally {
+        setListLoading(false);
+      }
+    },
+    // 获取切片列表用于导航
+    fetchSliceList: async () => {
+      try {
+        if (!params.knowledgeId || !params.documentId) {
+          return;
         }
-    }
-
-    React.useEffect(() => {
-        init();
-    }, [])
+        const res = await apis.fetchTakaiSliceList({
+          knowledge_id: params.knowledgeId,
+          document_id: params.documentId,
+          text: text || "",
+          pageSize: 10000, // 获取所有切片
+          pageNum: 1,
+        });
+        const ids = res.rows.map((item: any) => item.sliceId);
+        setSliceList(ids);
 
-    const [cursorEndPosition, setCursorEndPosition] = React.useState<number>(0);
+        // 找到当前切片的索引
+        if (params.sliceId && params.sliceId !== "new") {
+          const index = ids.findIndex((id: string) => id === params.sliceId);
+          setCurrentIndex(index);
+        }
+      } catch (error) {
+        console.error("获取切片列表失败:", error);
+      }
+    },
+  };
 
-    // 上传图片配置
-    const uploadImageConfig: UploadProps = {
-        name: 'files',
-        action: config.baseURL + `/deepseek/api/uploadSliceImage/${params.knowledgeId}/${params.documentId}`,
-        method: 'POST',
-        headers: getHeaders(),
-        accept: ['.png', '.jpg', '.jpeg'].join(','),
-        multiple: true,
-        maxCount: 5,
-        showUploadList: false,
-    };
+  const init = async () => {
+    if (params.sliceId && params.sliceId !== "new") {
+      await appApi.fetchList();
+    }
+    await appApi.fetchSliceList();
+    setSwitchLoading(false); // 加载完成后关闭loading
+  };
 
-    // 处理markdown编辑器
-    const [contentMd, setContentMd] = React.useState('');
-    // 在这里做关键字替换
-    const customRender = (text: string)=> {
-        let html = mdParser.render(text);
+  React.useEffect(() => {
+    init();
+  }, [params.sliceId]);
 
-        // 比如:把 "我是图片" 替换成一张图片
-        mdImgUrlList?.forEach(item=>{
-            html = html.replace(new RegExp(item.name, 'g'), `<img src="${item.url}" alt="${item.name}" />`);
-        })
-        // html = html.replace(/我是图片/g, `<img src="https://jkeckms.ryuiso.com/chat-bg.jpg" alt="我是图片" />`);
-        // 你还可以做更多规则,比如匹配 [img:xxx] 这种自定义语法
-        // html = html.replace(/\[img:(.+?)\]/g, (_, name) => `<img src="/images/${name}.png" />`);
-        form.setFieldsValue({ slice_text: text });
-        return html;
-    }
+  const [cursorEndPosition, setCursorEndPosition] = React.useState<number>(0);
 
+  // 上传图片配置
+  const uploadImageConfig: UploadProps = {
+    name: "files",
+    action:
+      config.baseURL +
+      `/deepseek/api/uploadSliceImage/${params.knowledgeId}/${params.documentId}`,
+    method: "POST",
+    headers: getHeaders(),
+    accept: [".png", ".jpg", ".jpeg"].join(","),
+    multiple: true,
+    maxCount: 5,
+    showUploadList: false,
+  };
 
+  // 处理markdown编辑器
+  const [contentMd, setContentMd] = React.useState("");
+  // 在这里做关键字替换
+  const customRender = (text: string) => {
+    let html = mdParser.render(text);
 
+    // 比如:把 "我是图片" 替换成一张图片
+    mdImgUrlList?.forEach((item) => {
+      html = html.replace(
+        new RegExp(item.name, "g"),
+        `<img src="${item.url}" alt="${item.name}" />`
+      );
+    });
+    // html = html.replace(/我是图片/g, `<img src="https://jkeckms.ryuiso.com/chat-bg.jpg" alt="我是图片" />`);
+    // 你还可以做更多规则,比如匹配 [img:xxx] 这种自定义语法
+    // html = html.replace(/\[img:(.+?)\]/g, (_, name) => `<img src="/images/${name}.png" />`);
+    form.setFieldsValue({ slice_text: text });
+    return html;
+  };
 
+  // 导航到指定切片
+  const navigateToSlice = (sliceId: string) => {
+    const path = generatePath(
+      "/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId/:sliceId",
+      {
+        knowledgeId: params.knowledgeId as string,
+        createBy: params.createBy as string,
+        documentId: params.documentId as string,
+        embeddingId: params.embeddingId as string,
+        sliceId: sliceId,
+      }
+    );
+    router.navigate({ pathname: path }, { state: { text: text, page } });
+  };
 
+  // 上一步
+  const handlePrevious = () => {
+    if (currentIndex > 0) {
+      setSwitchLoading(true);
+      const prevSliceId = sliceList[currentIndex - 1];
+      navigateToSlice(prevSliceId);
+    }
+  };
 
-    return (
-        <div>
-            <div className='questionAnswerList'>
-                <div style={{ height: '100%', marginLeft: '10px' }}>
-                    <Form
-                        style={{ paddingTop: '20px', height: '100%' }}
-                        form={form}
-                        layout='vertical'
-                    >
-                        <Form.Item
-                            // name="slice_text"
-                            rules={[{ required: true, message: '内容不能为空' }]}
-                        >
-                            <MdEditor
-                                config={{
-                                    view:{
-                                        menu:false
-                                    }
-                                }}
-                                value={contentMd}
-                                style={{ height: "450px",width:'98%' }}
-                                renderHTML={customRender}
-                                onChange={({ text }) => setContentMd(text)}
-                                onBlur={(e: React.FocusEvent<HTMLTextAreaElement>) => {
-                                    const ta = e.target as HTMLTextAreaElement;
-                                    const start = ta.selectionStart;
-                                    const end = ta.selectionEnd;
-                                    setCursorEndPosition(end);
-                                    // setCursor({ start, end });
-                                    // console.log('blur cursor', start, end, ta.value);
-                                }}
-                            />
-                        </Form.Item>
-                        {false && <FormItem
-                            name="slice_text"
-                            rules={[{ required: true, message: '切片内容不能为空' }]}>
-                            <TextArea
-                                style={{
-                                    width: '50%',
-                                    height: '200px',
-                                    overflow: 'auto',
-                                    resize: 'both',
-                                    boxSizing: 'border-box',
+  // 下一步
+  const handleNext = () => {
+    if (currentIndex < sliceList.length - 1) {
+      setSwitchLoading(true);
+      const nextSliceId = sliceList[currentIndex + 1];
+      navigateToSlice(nextSliceId);
+    }
+  };
 
-                                }}
-                                placeholder=""
-                                autoSize={{ minRows: 20, maxRows: 5000 }}
-                                onBlur={(e) => {
-                                    const target = e.target as HTMLTextAreaElement;
-                                    // 更新光标终点位置
-                                    setCursorEndPosition(target.selectionEnd);
-                                }}
-                            />
-                        </FormItem>}
-                        <Upload
-                            {...uploadImageConfig}
-                            onChange={(info) => {
-                                const insertToSliceText = (text: string) => {
-                                    const slice_text = contentMd
-                                    // 获取当前光标位置
-                                    const position = cursorEndPosition;
+  return (
+    <div style={{ position: "relative" }}>
+      <Spin
+        spinning={switchLoading}
+        size="large"
+        style={{
+          position: "fixed",
+          top: "50%",
+          left: "50%",
+          transform: "translate(-50%, -50%)",
+          zIndex: 9999,
+        }}
+      >
+        <div style={{ width: 0, height: 0 }} />
+      </Spin>
+      <div className="questionAnswerList">
+        <div style={{ height: "100%", marginLeft: "10px" }}>
+          <Form
+            style={{ paddingTop: "20px", height: "100%" }}
+            form={form}
+            layout="vertical"
+          >
+            <Form.Item
+              // name="slice_text"
+              rules={[{ required: true, message: "内容不能为空" }]}
+            >
+              <MdEditor
+                config={{
+                  view: {
+                    menu: false,
+                  },
+                }}
+                value={contentMd}
+                style={{ height: "450px", width: "98%" }}
+                renderHTML={customRender}
+                onChange={({ text }) => setContentMd(text)}
+                onBlur={(e: React.FocusEvent<HTMLTextAreaElement>) => {
+                  const ta = e.target as HTMLTextAreaElement;
+                  const start = ta.selectionStart;
+                  const end = ta.selectionEnd;
+                  setCursorEndPosition(end);
+                  // setCursor({ start, end });
+                  // console.log('blur cursor', start, end, ta.value);
+                }}
+              />
+            </Form.Item>
+            {false && (
+              <FormItem
+                name="slice_text"
+                rules={[{ required: true, message: "切片内容不能为空" }]}
+              >
+                <TextArea
+                  style={{
+                    width: "50%",
+                    height: "200px",
+                    overflow: "auto",
+                    resize: "both",
+                    boxSizing: "border-box",
+                  }}
+                  placeholder=""
+                  autoSize={{ minRows: 20, maxRows: 5000 }}
+                  onBlur={(e) => {
+                    const target = e.target as HTMLTextAreaElement;
+                    // 更新光标终点位置
+                    setCursorEndPosition(target.selectionEnd);
+                  }}
+                />
+              </FormItem>
+            )}
+            <div
+              style={{
+                display: "flex",
+                justifyContent: "space-between",
+                alignItems: "center",
+              }}
+            >
+              <div>
+                <Upload
+                  {...uploadImageConfig}
+                  onChange={(info) => {
+                    const insertToSliceText = (text: string) => {
+                      const slice_text = contentMd;
+                      // 获取当前光标位置
+                      const position = cursorEndPosition;
 
-                                    let newValue = '';
+                      let newValue = "";
 
-                                    if (!slice_text) {
-                                        newValue = text;
-                                    } else {
-                                        newValue = slice_text.slice(0, position) + text + slice_text.slice(position);
-                                    }
-                                    setContentMd(newValue);
-                                    form.setFieldsValue({ slice_text: newValue });
-                                }
-                                const file = info.file;
-                                if (file.status === 'done') {// 上传成功
-                                    const { code, msg, data } = file.response;
-                                    if (code === 200) {
-                                        const text = data&&data[0]?.name||'';
-                                        insertToSliceText(text);
-                                        const flagList = mdImgUrlList || [];
-                                        const flagList1 = [...flagList,...data];
-                                        setMdImgUrlList(flagList1)
-                                        message.success('上传成功');
-                                    } else {
-                                        message.error(msg);
-                                    }
-                                } else if (file.status === 'error') {// 上传失败
-                                    // 检查是否是504超时错误
-                                    const response = file.response;
-                                    if (response?.status === 504 || file.error?.includes('timeout')) {
-                                        message.error('上传图片超时,请修改图片后再上传');
-                                    } else {
-                                        message.error('上传失败');
-                                    }
-                                }
-                            }}
-                        >
-                            <Button type='primary'>
-                                解析图片
-                            </Button>
-                        </Upload>
-                        <Button
-                            style={{ margin: '0 16px' }}
-                            type='primary'
-                            icon={<ArrowLeftOutlined />}
-                            onClick={() => {
-                                const path = generatePath('/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId', {
-                                    knowledgeId: params.knowledgeId as string,
-                                    documentId: params.documentId as string,
-                                    createBy: params.createBy as string,
-                                    embeddingId: params.embeddingId as string,
-                                });
-                                router.navigate({ pathname: path }, 
-                                    { state: { text: text, page } }
-                                );
-                            }}
-                        >
-                            返回
-                        </Button>
-                        <Button
-                            type='primary'
-                            onClick={() => {
-                                form.validateFields().then(async (values) => {
-                                    values = {
-                                        slice_text:contentMd
-                                    }
-                                    // 验证参数是否存在
-                                    if (!params.knowledgeId || !params.sliceId) {
-                                        message.error('知识库ID或切片ID无效');
-                                        return;
-                                    }
-                                    const data = values;
-                                    const info = {
-                                        knowledgeId: params.knowledgeId,
-                                        sliceId: params.sliceId === null ? '' : params.sliceId,
-                                        sliceText: btoa(unescape(encodeURIComponent(values.slice_text))),
-                                        documentId: params.documentId,
-                                    };
-                                    let res = null;
-                                    if (params.sliceId && params.sliceId !== 'null' && params.sliceId !== 'new') {
-                                        // 编辑应用
-                                        res = await apis.modifyTakaiSliceInfo(info as any);
-                                    } else {
-                                        res = await apis.addTakaiSlice(info as any);
-                                    }
-                                    if (res.code === 200 && res.data === 1) {
-                                        if (params.sliceId && params.sliceId !== 'null' && params.sliceId !== 'new') {
-                                            message.success('修改成功');
-                                        } else {
-                                            message.success('新增成功');
-                                        }
-                                    } else {
-                                        if (params.sliceId && params.sliceId !== 'null' && params.sliceId !== 'new') {
-                                            message.error('修改失败');
-                                        } else {
-                                            message.error('新增失败');
-                                        }
-                                    }
-                                    const path = generatePath('/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId', {
-                                        knowledgeId: params.knowledgeId as string,
-                                        createBy: params.createBy as string,
-                                        documentId: params.documentId as string,
-                                        embeddingId: params.embeddingId as string,
-                                    });
-                                    router.navigate({ pathname: path }, {
-                                         state: { text: text, page } 
-                                        });
-                                }).catch((error) => {
-                                    console.error(error);
-                                });
-                            }}
-                        >
-                            保存
-                        </Button>
-                    </Form>
-                </div>
+                      if (!slice_text) {
+                        newValue = text;
+                      } else {
+                        newValue =
+                          slice_text.slice(0, position) +
+                          text +
+                          slice_text.slice(position);
+                      }
+                      setContentMd(newValue);
+                      form.setFieldsValue({ slice_text: newValue });
+                    };
+                    const file = info.file;
+                    if (file.status === "done") {
+                      // 上传成功
+                      const { code, msg, data } = file.response;
+                      if (code === 200) {
+                        const text = (data && data[0]?.name) || "";
+                        insertToSliceText(text);
+                        const flagList = mdImgUrlList || [];
+                        const flagList1 = [...flagList, ...data];
+                        setMdImgUrlList(flagList1);
+                        message.success("上传成功");
+                      } else {
+                        message.error(msg);
+                      }
+                    } else if (file.status === "error") {
+                      // 上传失败
+                      // 检查是否是504超时错误
+                      const response = file.response;
+                      if (
+                        response?.status === 504 ||
+                        file.error?.includes("timeout")
+                      ) {
+                        message.error("上传图片超时,请修改图片后再上传");
+                      } else {
+                        message.error("上传失败");
+                      }
+                    }
+                  }}
+                >
+                  <Button type="primary">解析图片</Button>
+                </Upload>
+                <Button
+                  style={{ margin: "0 16px" }}
+                  type="primary"
+                  icon={<ArrowLeftOutlined />}
+                  onClick={() => {
+                    const path = generatePath(
+                      "/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId",
+                      {
+                        knowledgeId: params.knowledgeId as string,
+                        documentId: params.documentId as string,
+                        createBy: params.createBy as string,
+                        embeddingId: params.embeddingId as string,
+                      }
+                    );
+                    router.navigate(
+                      { pathname: path },
+                      { state: { text: text, page } }
+                    );
+                  }}
+                >
+                  返回
+                </Button>
+                <Button
+                  type="primary"
+                  onClick={() => {
+                    form
+                      .validateFields()
+                      .then(async (values) => {
+                        values = {
+                          slice_text: contentMd,
+                        };
+                        // 验证参数是否存在
+                        if (!params.knowledgeId || !params.sliceId) {
+                          message.error("知识库ID或切片ID无效");
+                          return;
+                        }
+                        const data = values;
+                        const info = {
+                          knowledgeId: params.knowledgeId,
+                          sliceId:
+                            params.sliceId === null ? "" : params.sliceId,
+                          sliceText: btoa(
+                            unescape(encodeURIComponent(values.slice_text))
+                          ),
+                          documentId: params.documentId,
+                        };
+                        let res = null;
+                        if (
+                          params.sliceId &&
+                          params.sliceId !== "null" &&
+                          params.sliceId !== "new"
+                        ) {
+                          // 编辑应用
+                          res = await apis.modifyTakaiSliceInfo(info as any);
+                        } else {
+                          res = await apis.addTakaiSlice(info as any);
+                        }
+                        if (res.code === 200 && res.data === 1) {
+                          if (
+                            params.sliceId &&
+                            params.sliceId !== "null" &&
+                            params.sliceId !== "new"
+                          ) {
+                            message.success("修改成功");
+                          } else {
+                            message.success("新增成功");
+                          }
+                        } else {
+                          if (
+                            params.sliceId &&
+                            params.sliceId !== "null" &&
+                            params.sliceId !== "new"
+                          ) {
+                            message.error("修改失败");
+                          } else {
+                            message.error("新增失败");
+                          }
+                        }
+                        const path = generatePath(
+                          "/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId",
+                          {
+                            knowledgeId: params.knowledgeId as string,
+                            createBy: params.createBy as string,
+                            documentId: params.documentId as string,
+                            embeddingId: params.embeddingId as string,
+                          }
+                        );
+                        router.navigate(
+                          { pathname: path },
+                          {
+                            state: { text: text, page },
+                          }
+                        );
+                      })
+                      .catch((error) => {
+                        console.error(error);
+                      });
+                  }}
+                >
+                  保存
+                </Button>
+                <Button
+                  style={{ marginLeft: 16 }}
+                  type="default"
+                  disabled={
+                    currentIndex <= 0 ||
+                    params.sliceId === "new" ||
+                    switchLoading
+                  }
+                  onClick={handlePrevious}
+                >
+                  上一步
+                </Button>
+                <Button
+                  style={{ marginLeft: 8 }}
+                  type="default"
+                  disabled={
+                    currentIndex >= sliceList.length - 1 ||
+                    params.sliceId === "new" ||
+                    switchLoading
+                  }
+                  onClick={handleNext}
+                >
+                  下一步
+                </Button>
+              </div>
             </div>
+          </Form>
         </div>
-    )
+      </div>
+    </div>
+  );
 };
 
-export default observer(SliceDetail);
+export default observer(SliceDetail);

+ 19 - 3
src/pages/deepseek/questionAnswer/list/index.tsx

@@ -659,7 +659,8 @@ const QuestionAnswerList: React.FC = () => {
                 <Select
                   placeholder='请选择'
                   allowClear
-                  // style={ { width: 200 } }
+                  style={{ width: 200 }}
+                  popupMatchSelectWidth={200}
                   onChange={(value) => {
                     // 项目类型选择器自动提交逻辑
                     const currentProjectId = form.getFieldValue('projectId');
@@ -669,8 +670,15 @@ const QuestionAnswerList: React.FC = () => {
                 >
                   {
                     appProjectList.map((item, index) => {
-                      return <Option value={item.value} key={index}>
-                        {item.label}
+                      return <Option value={item.value} key={index} title={item.label}>
+                        <div style={{
+                          overflow: 'hidden',
+                          textOverflow: 'ellipsis',
+                          whiteSpace: 'nowrap',
+                          width: '100%'
+                        }}>
+                          {item.label}
+                        </div>
                       </Option>
                     })
                   }
@@ -691,6 +699,14 @@ const QuestionAnswerList: React.FC = () => {
                 options={appProjectList}
                 placeholder="请选择项目"
                 showSearch
+                style={{ width: 200 }}
+                maxTagTextLength={10}
+                popupClassName="cascader-dropdown-fixed-width"
+                dropdownRender={(menus) => (
+                  <div style={{ maxWidth: 200 }}>
+                    {menus}
+                  </div>
+                )}
                 onChange={(value) => {
                   // 项目选择器自动提交逻辑
                   const currentProTypeId = form.getFieldValue('proTypeId');

+ 29 - 0
src/pages/deepseek/questionAnswer/list/style.less

@@ -74,6 +74,35 @@
   .ant-select-item-option-active {
     background-color: rgba(0, 123, 255, 0.1);
   }
+
+  // 下拉选项固定宽度并显示省略号
+  .ant-select-item-option-content {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+  }
+}
+
+// Cascader下拉框固定宽度样式
+.cascader-dropdown-fixed-width {
+  .ant-cascader-menu {
+    max-width: 200px;
+    min-width: 200px;
+  }
+
+  .ant-cascader-menu-item {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    padding-right: 24px;
+  }
+
+  .ant-cascader-menu-item-content {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
 }