Quellcode durchsuchen

feat: 修改聊天框偏移

王晓东 vor 1 Woche
Ursprung
Commit
0507141ba8

+ 36 - 0
src/components/SliderAction/index.module.less

@@ -0,0 +1,36 @@
+.sliderDeleteAction {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.actions {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: row;
+  right: 0;
+  z-index: 1;
+  height: 100%;
+}
+
+.actionBtn {
+  width: 80px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 14px;
+  height: 100%;
+  background: #ff4d4f;
+  user-select: none;
+}
+
+.content {
+  background: #fff;
+  position: relative;
+  z-index: 2;
+  will-change: transform;
+  transition: transform 0.2s;
+}

+ 125 - 0
src/components/SliderAction/index.tsx

@@ -0,0 +1,125 @@
+import { View, Text } from "@tarojs/components";
+import { useRef, useState } from "react";
+import classNames from "classnames";
+import styles from "./index.module.less";
+
+interface TAction {
+  text: string|JSX.Element;
+  color?: string;
+  width?: number;
+  onClick: () => void;
+}
+
+interface SliderDeleteActionProps {
+  actions: TAction[]; // 操作按钮列表
+  children: React.ReactNode;
+  disabled?: boolean;
+}
+
+const THRESHOLD = 60; // 滑动阈值
+const DEFAULT_BUTTON_WIDTH = 80; // 默认每个按钮80px
+
+const SliderDeleteAction: React.FC<SliderDeleteActionProps> = ({
+  actions,
+  children,
+  disabled = false,
+}) => {
+  const [offset, setOffset] = useState(0);
+  const [open, setOpen] = useState(false);
+  const startXRef = useRef(0);
+  const touchingRef = useRef(false);
+
+  actions = actions.map(action => {
+    if(action.width === undefined){
+      action.width = DEFAULT_BUTTON_WIDTH
+    }
+    return action
+  })
+  // 计算所有按钮总宽度
+  const totalActionWidth = actions.reduce((prev, next)=> {
+    return prev + (next.width ?? DEFAULT_BUTTON_WIDTH)
+  }, 0); 
+
+  // 触摸开始
+  const handleTouchStart = (e) => {
+    if (disabled) return;
+    touchingRef.current = true;
+    startXRef.current = e.touches[0].clientX;
+  };
+
+  // 触摸移动
+  const handleTouchMove = (e) => {
+    if (!touchingRef.current || disabled) return;
+    const deltaX = e.touches[0].clientX - startXRef.current;
+    if (deltaX < 0) {
+      setOffset(Math.max(deltaX, -totalActionWidth));
+    } else if (open) {
+      setOffset(Math.min(deltaX - totalActionWidth, 0));
+    }
+  };
+
+  // 关闭
+  const closeSlider = ()=> {
+    setOffset(0);
+    setOpen(false);
+  }
+
+  // 触摸结束
+  const handleTouchEnd = () => {
+    if (disabled) return;
+    touchingRef.current = false;
+    if (Math.abs(offset) > THRESHOLD) {
+      setOffset(-totalActionWidth);
+      setOpen(true);
+    } else {
+      closeSlider()
+    }
+  };
+
+  // 点击内容关闭
+  const handleContentClick = () => {
+    if (open) {
+      closeSlider()
+    }
+  };
+
+  const handleActionClick = (action: TAction)=> {
+    action.onClick()
+    closeSlider()
+  }
+  
+  return (
+    <View className={styles.sliderDeleteAction}>
+      <View
+        className={styles.actions}
+        style={{ width: `${totalActionWidth}px`, right: 0 }}
+      >
+        {actions.map((action, idx) => (
+          <View
+            key={idx}
+            className={styles.actionBtn}
+            style={{ background: action.color || "#ff4d4f" }}
+            onClick={()=> handleActionClick(action)}
+          >
+            {action.text}
+          </View>
+        ))}
+      </View>
+      <View
+        className={classNames(styles.content, { [styles.open]: open })}
+        style={{
+          transform: `translateX(${offset}px)`,
+          transition: touchingRef.current ? "none" : "transform 0.2s",
+        }}
+        onTouchStart={handleTouchStart}
+        onTouchMove={handleTouchMove}
+        onTouchEnd={handleTouchEnd}
+        onClick={handleContentClick}
+      >
+        {children}
+      </View>
+    </View>
+  );
+};
+
+export default SliderDeleteAction;  

+ 1 - 1
src/pages/chat/components/chat-welcome/index.module.less

@@ -1,6 +1,6 @@
 .container{
   position: absolute;
-  bottom: 0px;
+  bottom: 16px;
   left: 16px;
   right: 16px;
   z-index: 1;

+ 2 - 2
src/pages/chat/components/input-bar/chatInput.ts

@@ -41,6 +41,7 @@ export const useChatInput = ({ agent, setShowWelcome, setDisabled, }: Props) =>
 
   const chatWithGpt = async (message: string, sessionId: string, msgUk: string) => {
     setShowWelcome?.(false)
+    setQuestions([])
     let currentRobotMsgUk = "";
     await delay(300);
     setDisabled?.(true);
@@ -144,9 +145,8 @@ export const useChatInput = ({ agent, setShowWelcome, setDisabled, }: Props) =>
           agentId: agent.agentId,
           sessionId,
         })
-
+        // todo: 如果用户快速输入需要将前面的问题答案丢弃,根据 currentRobotMessage.msgUk 来设置 questions
         if(isSuccess(response.status)){
-          console.log(response.data.questions, 444)
           setQuestions(response.data.questions)
         }
 

+ 6 - 3
src/pages/chat/components/keyboard.ts

@@ -1,7 +1,7 @@
 import Taro from "@tarojs/taro";
 import { useEffect, useState } from "react";
 
-export const useKeyboard = (scrollViewRef: React.MutableRefObject<any>, messageList:any[])=> {
+export const useKeyboard = (scrollViewRef: React.MutableRefObject<any>, messageList:any[], contentId: string, scrollViewId: string )=> {
   
   const [keyboardHeight, setKeyboardHeight] = useState(0);
   const [contentHeight, setContentHeight] = useState(0);
@@ -18,6 +18,7 @@ export const useKeyboard = (scrollViewRef: React.MutableRefObject<any>, messageL
       // 内容+键盘弹起高度 - 滚动容器高度
       return -(contentHeight + keyboardHeight - scrollViewHeight);
     }
+    return 0
   })();
 
   useEffect(() => {
@@ -42,18 +43,20 @@ export const useKeyboard = (scrollViewRef: React.MutableRefObject<any>, messageL
       const query = Taro.createSelectorQuery();
       // 获取聊天内容高度
       query
-        .select("#message-list")
+        .select(contentId)
         .boundingClientRect((rect: any) => {
           if (rect) {
+            console.log(rect.height,11)
             setContentHeight(rect.height);
           }
         })
         .exec();
       // 获取滚动容器高度
       query
-        .select("#scroll-view")
+        .select(scrollViewId)
         .boundingClientRect((rect: any) => {
           if (rect) {
+            console.log(rect.height,22)
             setScrollViewHeight(rect.height);
           }
         })

+ 17 - 8
src/pages/chat/index.tsx

@@ -43,7 +43,7 @@ const agent = useAgentStore((state) => {
   const [deepThink, setDeepThink] = useState(EAI_MODEL.DeepseekChat);
   const messageList = useTextChat((state) => state.list);
   
-  const {keyboardHeight, marginTopOffset} = useKeyboard(scrollViewRef, messageList)
+  const {keyboardHeight, marginTopOffset} = useKeyboard(scrollViewRef, messageList, '#messageList', '#scrollView')
   
 
   
@@ -105,8 +105,8 @@ const agent = useAgentStore((state) => {
   }, [agentId, isVisitor]);
 
   useEffect(() => {
-    setShowWelcome(!list.length);
-  }, [list]);
+    setShowWelcome(!messageList.length && !list.length);
+  }, [list, messageList]);
 
   useEffect(() => {
     if (pageIndex === 1) {
@@ -168,14 +168,16 @@ const agent = useAgentStore((state) => {
     };
     return <View className={style.topBg} style={bgImageStyle}></View>
   }
-
+  
   return (
     <PageCustom fullPage style={{ overflow: "hidden" }}>
-      <NavBarNormal blur leftColumn={renderNavLeft}></NavBarNormal>
+      <NavBarNormal blur leftColumn={renderNavLeft}>
+        {/* {marginTopOffset} */}
+      </NavBarNormal>
       {renderTopBg()}
       <View
         className="flex flex-col w-full h-full relative z-10 flex-1"
-        style={{ marginTop: `${marginTopOffset}px` }}
+        style={{ top: `${marginTopOffset}px` }}
       >
         <ScrollView
           ref={scrollViewRef}
@@ -209,8 +211,15 @@ const agent = useAgentStore((state) => {
           {(agent) &&  <RecommendQuestions agent={agent} />}
           </View>
         </ScrollView>
+        <View className="w-full h-54">
+          {/* 输入框高度占位块,todo: 改成动态获取输入框块高度 */}
+        </View>
         <View
-          className="w-full"
+          className="bottom-bar px-16 pt-12 z-50"
+          style={{
+            bottom: `${keyboardHeight}px`,
+          }}
+
         >
           {/* <View
             onClick={switchDeepThink}
@@ -222,7 +231,7 @@ const agent = useAgentStore((state) => {
           >
             深度思考(R1)
           </View> */}
-          <View className="px-16 py-12 bg-[#F5FAFF]">
+          <View className="bg-[#F5FAFF]">
           {agent && (
               <InputBar
                 aiModel={deepThink}

+ 0 - 3
src/pages/contact/index.config.ts

@@ -2,7 +2,4 @@ export default definePageConfig({
   navigationBarTitleText: '联系人',
   navigationStyle: 'custom',
   onReachBottomDistance: 20,
-  usingComponents: {
-    'slide-contact': '../../components/slide-contact/index',
-  }
 })

+ 61 - 52
src/pages/contact/index.tsx

@@ -7,16 +7,14 @@ import style from "./index.module.less";
 import { useEffect, useState } from "react";
 import ContactCard from "./components/contact-card/index";
 import { getContactList, setContactToTop } from "@/service/contact";
-import {
-  useLoadMoreInfinite,
-  type TResponseData,
-} from "@/utils/loadMoreInfinite";
+import { useLoadMoreInfinite } from "@/utils/loadMoreInfinite";
 import PageCustom from "@/components/page-custom/index";
 import { TContactItem } from "@/types/contact";
 import { isSuccess } from "@/utils";
 import { delContact } from "@/service/contact";
 import TabPageCheckLogin from "@/components/TabPageCheckLogin";
 import BlurContainer from "@/components/BlurContainer";
+import SliderAction from "@/components/SliderAction";
 
 export default function Index() {
   const [searchValue, setSearchValue] = useState("");
@@ -65,38 +63,6 @@ export default function Index() {
     setSize((prevSize) => prevSize + 1);
   };
 
-  const handleAction = async (e: any) => {
-    const detail = e.detail as { type: string; id: string };
-    console.log(detail);
-    // 置顶与取消置顶
-    if (detail.type === "pin" || detail.type === "unpin") {
-      const reseponse = await setContactToTop({
-        isTop: detail.type === "pin",
-        contactId: detail.id,
-      });
-      if (isSuccess(reseponse.status)) {
-        mutate();
-        return;
-      }
-    }
-    if (detail.type === "delete") {
-      handleDelete(detail.id);
-    }
-  };
-
-  useDidShow(() => {
-    mutate();
-  });
-  const onLoginEnd = () => {
-    mutate();
-  };
-
-  useEffect(() => {
-    if (data && pageIndex === 1) {
-      setTotalCount(data?.[0].totalCount || 0);
-    }
-  }, [data, pageIndex]);
-
   const handleDelete = (contactId: string | number) => {
     Taro.showModal({
       content: "😭 确认删除该联系人吗?",
@@ -115,27 +81,70 @@ export default function Index() {
     });
   };
 
+  const handlePin = async (isTop: boolean, contactId: string | number) => {
+    const reseponse = await setContactToTop({
+      isTop: !isTop,
+      contactId: contactId,
+    });
+    if (isSuccess(reseponse.status)) {
+      mutate();
+    }
+  };
+
+  useDidShow(() => {
+    mutate();
+  });
+  const onLoginEnd = () => {
+    mutate();
+  };
+
+  useEffect(() => {
+    if (data && pageIndex === 1) {
+      setTotalCount(data?.[0].totalCount || 0);
+    }
+  }, [data, pageIndex]);
+
+  const createSliderButtons = (item: TContactItem) => {
+    const onTopText = item.isTop ? (
+      <View>
+        <View>取消</View>
+        <View>置顶</View>
+      </View>
+    ) : (
+      "置顶"
+    );
+    return [
+      {
+        text: "删除",
+        color: "#FF8200",
+        onClick: () => {
+          handleDelete(item.contactId);
+        },
+      },
+      {
+        text: onTopText,
+        color: `${item.isTop ? "#FF4747" : "#327BF9"}`,
+        onClick: () => {
+          handlePin(item.isTop, item.contactId);
+        },
+      },
+    ];
+  };
 
   const renderContent = () => {
     if (list?.length) {
       return list.map((item) => (
         <View className={`rounded-12 overflow-hidden truncate`}>
-          <slide-contact
-            pid={item.contactId}
-            pinned={item.isTop}
-            onAction={handleAction}
-          >
-            <View className={``}>
-              <ContactCard
-                refresh={mutate}
-                deleteable={true}
-                key={item.contactId}
-                data={item}
-                fromContact
-                className={`${item.isTop ? "bg-[#EDF1FF]" : "bg-white"}`}
-              ></ContactCard>
-            </View>
-          </slide-contact>
+          <SliderAction actions={createSliderButtons(item)}>
+            <ContactCard
+              refresh={mutate}
+              deleteable={true}
+              key={item.contactId}
+              data={item}
+              fromContact
+              className={`${item.isTop ? "bg-[#EDF1FF]" : "bg-white"}`}
+            ></ContactCard>
+          </SliderAction>
         </View>
       ));
     }

+ 1 - 0
src/pages/knowledge/components/CorrectionTab/components/CorrectionList.tsx

@@ -9,6 +9,7 @@ import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
 import { deleteCorrection } from '@/service/correction'
 import { useModalStore } from "@/store/modalStore";
 import { isSuccess } from "@/utils";
+import SliderDeleteAction from "@/components/SliderAction";
 
 export interface Iprops {
   entId?: number|string