浏览代码

feat: 对接数据访问数据汇总

王晓东 1 月之前
父节点
当前提交
591a1f5da0
共有 31 个文件被更改,包括 444 次插入1308 次删除
  1. 0 5
      src/app.config.ts
  2. 8 9
      src/components/Picker/PickerSingleColumn.tsx
  3. 23 37
      src/components/component-list/components/card-contacts/index.tsx
  4. 26 65
      src/components/component-list/index.tsx
  5. 9 8
      src/components/contact-card/index.tsx
  6. 9 7
      src/pages/contact/components/contact-card/index.module.less
  7. 23 23
      src/pages/contact/components/contact-card/index.tsx
  8. 29 32
      src/pages/contact/index.tsx
  9. 0 73
      src/pages/dashboard/components/Picker/PickerSingleColumn.tsx
  10. 8 5
      src/pages/dashboard/components/VisitorCard/index.tsx
  11. 76 0
      src/pages/dashboard/components/VisitorList/index.tsx
  12. 64 47
      src/pages/dashboard/index.tsx
  13. 0 47
      src/pages/editor-pages/editor-chat/components/edit-character/components/textarea-form/index.tsx
  14. 0 237
      src/pages/editor-pages/editor-chat/components/edit-character/index.tsx
  15. 0 57
      src/pages/editor-pages/editor-chat/components/edit-rule/components/foldable-list/index.module.less
  16. 0 27
      src/pages/editor-pages/editor-chat/components/edit-rule/components/foldable-list/index.tsx
  17. 0 171
      src/pages/editor-pages/editor-chat/components/edit-rule/index.tsx
  18. 0 32
      src/pages/editor-pages/editor-chat/components/edit-voice/index.module.less
  19. 0 161
      src/pages/editor-pages/editor-chat/components/edit-voice/index.tsx
  20. 0 5
      src/pages/editor-pages/editor-chat/index.config.ts
  21. 0 10
      src/pages/editor-pages/editor-chat/index.module.less
  22. 0 71
      src/pages/editor-pages/editor-chat/index.tsx
  23. 0 0
      src/pages/editor-pages/editor-link-contact/components/AgentScrollList/index.module.less
  24. 66 0
      src/pages/editor-pages/editor-link-contact/components/AgentScrollList/index.tsx
  25. 59 130
      src/pages/editor-pages/editor-link-contact/index.tsx
  26. 0 4
      src/pages/editor-pages/editor-tel/index.tsx
  27. 3 1
      src/service/agent.ts
  28. 2 6
      src/service/contact.ts
  29. 7 36
      src/service/visitor.ts
  30. 29 0
      src/types/visitor.ts
  31. 3 2
      src/utils/loadMore.ts

+ 0 - 5
src/app.config.ts

@@ -35,13 +35,8 @@ export default defineAppConfig({
     'pages/editor-pages/editor-channels/index',
     
     
-    
-    
-    'pages/choose-contact/index',
-    
     'pages/editor-pages/editor-media/index',
     
-    'pages/editor-pages/editor-chat/index',
   
     'pages/editor-pages/editor-link/index',
     'pages/editor-pages/editor-mini-program/index',

+ 8 - 9
src/components/Picker/PickerSingleColumn.tsx

@@ -7,29 +7,28 @@ export interface IndexProps {
   selected: string
   showPicker: boolean
   setShowPicker: (show:boolean) => void
-  onChange: (value: string) => void
+  onChange?: (value: string) => void
+  onPicked: (value: string) => void
   children: JSX.Element|JSX.Element[]
 }
 
-const Index = ({selected, showPicker, setShowPicker,onChange, options, headerTitle = '请选择', children }: IndexProps) => {
-  
-  
+const Index = ({selected, showPicker, setShowPicker,onChange,onPicked, options, headerTitle = '请选择', children }: IndexProps) => {
   
   
+  let current = selected
+
 
   // 处理选择变化
   const handleChange = (e:any) => {
     const { value } = e.detail
-    onChange(options[value[0]])
+    current = options[value]
+    onChange && onChange(options[value[0]])
   }
 
   // 确认选择
   const handleConfirm = () => {
     setShowPicker(false)
-    Taro.showToast({
-      title: `已选择: ${selected}`,
-      icon: 'none'
-    })
+    onPicked(current)
   }
   return <View>
     {children}

+ 23 - 37
src/components/component-list/components/card-contacts/index.tsx

@@ -35,57 +35,42 @@ export default ({
   onClick,
   onStyleChanged,
 }: Props) => {
-  let cancelTokenSource = axios.CancelToken.source();
-  const [contacts, setContacts] = useState<ICharacter[]>([]);
-  const fetchList = async () => {
-    cancelTokenSource.cancel('Previous request canceled')
-    if (!component.data?.value) {
-      return;
-    }
-    // 重新创建一个新的取消令牌源
-    cancelTokenSource = axios.CancelToken.source();
-    const res = await batchGet(component.data?.value, {
-      cancelToken: cancelTokenSource.token
-    });
+  
+  const contacts = component.data.values
 
-    if (res.code === 0) {
-      Taro.nextTick(()=> {
-        setContacts(res.data);
-      })
-    }
-  };
-  const nav2Profile = (profileId: string)=> {
-    if(!editMode){
-      Taro.navigateTo({
-        url: "/pages/profile/index?profileId=" + profileId,
-      });
-    }
-  }
 
   // 非编辑模式且垂直平铺,背景显示为白色
   const bgStyle = (component.data?.layout === "mini" || editMode) ? style.bgMini : style.bgFull
 
-  useEffect(() => {
-    fetchList();
-    return ()=> {
-      cancelTokenSource.cancel('Previous request canceled')
+  
+  const renderEmpty = ()=> {
+    if(contacts.length){
+      return <></>
     }
-  }, [component.id, component?.data?.value]);
-
+    return <View className="px-16"><View className="component-card-empty">
+      <View className="component-card-content">
+        <View className="component-card-empty-figure"></View>
+        <View className="component-card-empty-tips">点击配置视频号视频</View>
+      </View>
+    </View>
+    </View>
+  }
   const renderList = () => {
+    
+
     if (component.data?.layout === "mini") {
       return (
         <View className="grid grid-cols-3 gap-x-18 gap-y-20 px-16">
           {contacts.map((item) => {
             return (
-              <View className="flex flex-col gap-8" onClick={() => nav2Profile(item.profileId)}>
+              <View className="flex flex-col gap-8" onClick={() => {}}>
                 <View className={style.avatar}>
                   {item.avatar && (
                     <Image src={item.avatar} mode='aspectFill' className={style.avatar} lazyLoad></Image>
                   )}
                 </View>
                 <View className={`truncate ${style.nickName}`}>
-                  {item.alias ?? item.name}
+                  {item.name}
                 </View>
               </View>
             );
@@ -94,16 +79,16 @@ export default ({
       );
     }
 
+    // 
     return (
       <View className={`flex flex-col gap-8 ${editMode ? 'px-16' : ''}`}>
-        
         {contacts.map((item) => {
           return (
             <ContactCard
               className={ bgStyle }
-              key={item.profileId}
+              key={item.agentId}
               data={item}
-              onClick={() => nav2Profile(item.profileId)}
+              onClick={() => {}}
               renderRight={() => {
                 return (
                   <Image
@@ -133,7 +118,7 @@ export default ({
   return (
     <>
       <WidgetCard
-        key={component.id}
+        key={component.data.id}
         enabled={component.enabled}
         onSwitchChanged={(checked) => onSwitchChanged(component, checked)}
         index={index}
@@ -150,6 +135,7 @@ export default ({
           }
         }}
       >
+        {renderEmpty()}
         {renderList()}
       </WidgetCard>
     </>

+ 26 - 65
src/components/component-list/index.tsx

@@ -188,31 +188,31 @@ export default ({ components, editMode = false }: Props) => {
         }
         
         // 联系人
-        // if(c.type === "bluebook"){
-        //   return {
-        //     component: c,
-        //     renderer: (
-        //       <CardContacts
-        //         index={index}
-        //         key={c.id}
-        //         component={c}
-        //         components={components}
-        //         onSwitchChanged={handleSwitchChanged}
-        //         editMode={editMode}
-        //         onDelete={handleDelete}
-        //         onMove={handleSort}
-        //         onStyleChanged={changeStyle}
-        //         onClick={() =>
-        //           editMode &&
-        //           handleNavigate(
-        //             c,
-        //             `/pages/editor-pages/editor-link-contact/index`
-        //           )
-        //         }
-        //       ></CardContacts>
-        //     ),
-        //   };
-        // }
+        if(c.type === "bluebook"){
+          return {
+            component: c,
+            renderer: (
+              <CardContacts
+                index={index}
+                key={c.data.id}
+                component={c}
+                components={components}
+                onSwitchChanged={handleSwitchChanged}
+                editMode={editMode}
+                onDelete={handleDelete}
+                onMove={handleSort}
+                onStyleChanged={changeStyle}
+                onClick={() =>
+                  editMode &&
+                  handleNavigate(
+                    c,
+                    `/pages/editor-pages/editor-link-contact/index`
+                  )
+                }
+              ></CardContacts>
+            ),
+          };
+        }
         
         // 社交媒体组件
         if (c?.type && SocialMediaType[c?.type]) {
@@ -337,46 +337,7 @@ export default ({ components, editMode = false }: Props) => {
 
         
 
-        // if (c.type === "email") {
-        //   return {
-        //     component: c,
-        //     renderer: (
-        //       <>
-        //         <WidgetCard
-        //           index={index}
-        //           enabled={c.enabled}
-        //           onSwitchChanged={(checked) => handleSwitchChanged(c, checked)}
-        //           components={components}
-        //           editMode={editMode}
-        //           onDelete={() => handleDelete(c)}
-        //           onMove={(direction) => handleSort(c, direction)}
-        //           onChanged={()=> {
-        //             if(c.data?.layout === 'center'){
-        //               changeStyle({...c, data: {...c.data, layout: 'left'}})
-        //             }else{
-        //               changeStyle({...c, data: {...c.data, layout: 'center'}})
-        //             }
-        //           }}
-        //         >
-        //           <WidgetTypeRow
-        //             editMode={editMode}
-        //             onClick={() =>
-        //               editMode &&
-        //               handleNavigate(
-        //                 c,
-        //                 "/pages/editor-pages/editor-email/index"
-        //               )
-        //             }
-        //             type={IconMail()}
-        //             layout={c.data.layout}
-        //           >
-        //             {c.data.text}
-        //           </WidgetTypeRow>
-        //         </WidgetCard>
-        //       </>
-        //     ),
-        //   };
-        // }
+        
         // 电话
         if (c.type === "tel") {
           return {

+ 9 - 8
src/components/contact-card/index.tsx

@@ -1,9 +1,10 @@
 import { View, Image } from '@tarojs/components'
 import style from './index.module.less'
-import { IContactModel,ICharacter } from '@/types/index'
+import { TContactItem } from '@/types/contact'
+import { TAgent } from '@/types/agent'
 interface Props {
-  data: IContactModel|ICharacter
-  onClick?: (data: IContactModel|ICharacter)=>void
+  data: TContactItem
+  onClick?: (data: TContactItem|TAgent)=>void
   renderRight?: ()=> JSX.Element | JSX.Element[]
   className?: string
 }
@@ -11,15 +12,15 @@ const Index = ({onClick, data, className, renderRight}: Props)=> {
   return (
     <View className={`${style.contactCard} ${className}`} onClick={()=>{onClick?.(data)}}>
       <View className={style.avatar}>
-      {data.avatar && <Image src={data.avatar} className={style.avatarImg} mode="aspectFill"></Image>}
+      {data.avatarUrl && <Image src={data.avatarUrl} className={style.avatarImg} mode="aspectFill"></Image>}
       </View>
       <View className={`${style.infoColumn} truncate`}>
         <View className={style.nameRow}>
-          <View className={`${style.nickName} truncate`}>{data.alias ?? data.name}</View>
-          {data.badges?.map(item=><View className={style.nameMarkup}>{item}</View>)}
+          <View className={`${style.nickName} truncate`}>{data.name}</View>
+          {/* {data.badges?.map(item=><View className={style.nameMarkup}>{item}</View>)} */}
         </View>
-        <View className={`${style.subInfo} truncate`}>{data.company}</View>
-        <View className={`${style.subInfo} truncate`}>{data.job}</View>
+        <View className={`${style.subInfo} truncate`}>{data?.entName}</View>
+        <View className={`${style.subInfo} truncate`}>{data?.position}</View>
       </View>
       {renderRight && renderRight()}
     </View>

+ 9 - 7
src/pages/contact/components/contact-card/index.module.less

@@ -17,6 +17,7 @@
   overflow: hidden;
 }
 .infoColumn{
+  width: 100%;
   display: flex;
   flex-direction: column;
   gap: 4px;
@@ -33,17 +34,18 @@
   font-weight: 500;
   line-height: 20px;
 }
-.nameMarkup{
+.tipCnt{
   display: flex;
-  padding: 2px 4px;
   justify-content: center;
   align-items: center;
-  font-size: 11px;
+  font-size: 10px;
   font-style: normal;
-  font-weight: 600;
-  line-height: 14px;
-  border-radius: 4px;
-  background: #CBF706;
+  height: 14px;
+  padding: 2px 4px;
+  flex-shrink: 0;
+  color: white;
+  border-radius: 14px;
+  background: red;
 }
 
 .subInfo{

+ 23 - 23
src/pages/contact/components/contact-card/index.tsx

@@ -12,41 +12,41 @@ interface Props {
 const Index = ({data, deleteable, refresh, fromContact}: Props)=> {
   const handleClick = (data: TContactItem)=>{
     console.log(data, fromContact)
-    // if(fromContact){
-    //   Taro.navigateTo({url: '/pages/profile/index?fromContact=true&profileId='+data.contactProfileId})
-    //   return   
-    // }
-    // Taro.navigateTo({url: '/pages/profile/index?profileId='+data.contactProfileId})
+    Taro.navigateTo({
+      url: "/pages/profile/index?agentId=" + data.agentId,
+    });
   }
-  const handleLongPress = (e, profileId: string) => {
+  const handleLongPress = (e, contactId: string|number) => {
     // console.log("longpress");
-    // if(!deleteable){
-    //   return;
-    // }
-    // e.stopPropagation();
-    // Taro.showModal({
-    //   content: "😭 确认删除该联系人吗?",
-    //   async success(result) {
-    //     if (result.confirm) {
-    //       await delContact(profileId);
-    //       refresh();
-    //     }
-    //   },
-    // });
+    if(!deleteable){
+      return;
+    }
+    e.stopPropagation();
+    Taro.showModal({
+      content: "😭 确认删除该联系人吗?",
+      async success(result) {
+        if (result.confirm) {
+          await delContact(contactId);
+          refresh();
+        }
+      },
+    });
   };
 
   return (
-    <View className={style.contactCard} onClick={()=>{handleClick(data)}} onLongPress={(e) => handleLongPress(e, data.agentId)}>
+    <View className={style.contactCard} onClick={()=>{handleClick(data)}} onLongPress={(e) => handleLongPress(e, data.contactId)}>
       <View className={style.avatar}>
       {data.avatarUrl && <Image src={data.avatarUrl} mode="aspectFill" className={style.avatar}></Image>}
       </View>
       <View className={style.infoColumn}>
         <View className={style.nameRow}>
           <View className={style.nickName}>{data.name}</View>
-          {/* {data.badges?.map(item=><View className={style.nameMarkup}>{item}</View>)} */}
         </View>
-        <View className={style.subInfo}>{data.lastChatMsg}</View>
-          {/* <View className={style.subInfo}>{data.job}</View> */}
+        <View className='flex items-center w-full'>
+          <View className={`flex-1 ${style.subInfo}`}>{data.lastChatMsg}</View>
+          {(!!data?.tipCnt) && <View className={style.tipCnt}>{data.tipCnt} </View>}
+        </View>
+        
       </View>
     </View>
   )

+ 29 - 32
src/pages/contact/index.tsx

@@ -6,18 +6,23 @@ import SearchBar from "@/components/search-bar/index";
 import style from "./index.module.less";
 import { useEffect, useState } from "react";
 import ContactCard from "./components/contact-card/index";
-import { getContactList, searchContact } from "@/service/contact";
-import { useDebounceWithParams } from "@/utils/index";
+import { getContactList, setContactToTop } from "@/service/contact";
 import { useLoadMore } from "@/utils/loadMore";
 import PageCustom from "@/components/page-custom/index";
 import { TContactItem } from "@/types/contact";
+import { isSuccess } from "@/utils";
 
 export default function Index() {
   const [searchValue, setSearchValue] = useState("");
   const [list, setList] = useState<TContactItem[]>([]);
 
-  const fetcher = async ([_url, nextId, page, pageSize]) => {
-    const res = await getContactList({ startId: nextId, pageSize });
+  const fetcher = async ([_url, nextId, page, pageSize, _keyword]) => {
+    let keyword = _keyword
+    // if(_keyword !== undefined){
+    //   keyword = decodeURIComponent(_keyword)
+    //   console.log(keyword,1111)
+    // }
+    const res = await getContactList({ startId: nextId, pageSize, keyword });
     return res.data;
   };
   const { data, loadMore, refetch } = useLoadMore<{
@@ -27,22 +32,9 @@ export default function Index() {
   }>({
     url: '/blue-aiagent/api/v1/my/contacts',
     fetcher,
+    params: [searchValue]
   });
 
-  // 搜索
-  const debounceSearch = useDebounceWithParams(async (searchText) => {
-    if (searchText === "") {
-      console.log("searchValue is empty", searchText);
-      return;
-    }
-    // const res = await searchContact(searchText);
-    // if (res.code === 0 && res.data) {
-    //   setList(res.data);
-    // } else {
-    //   setList([]);
-    // }
-  }, 500);
-
   const resetFetchList = () => {
     setList([]);
     refetch();
@@ -50,19 +42,11 @@ export default function Index() {
 
   const handleSearchBarChanged = (v: string) => {
     setSearchValue(v);
-    if (!v.length) {
-      resetFetchList();
-      return;
-    }
-
-    Taro.nextTick(() => {
-      debounceSearch(v);
-    });
+    setList([])
   };
 
   const handleClear = () => {
     setSearchValue("");
-    resetFetchList();
   };
 
   useReachBottom(() => {
@@ -81,8 +65,21 @@ export default function Index() {
   }, [data]);
 
 
-  const handleHello = (e: any) => {
-    console.log(e.detail);
+  const handleHello = 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)){
+        resetFetchList();
+        return
+      }
+    }
+    
   };
 
   const renderContent = () => {
@@ -90,15 +87,15 @@ export default function Index() {
       return list.map((item) => (
         // @ts-ignore
         <slide-delete
-          pid={item.agentId}
+          pid={item.contactId}
           pinned={item.isTop}
           onAction={handleHello}
         >
           <View className={`${item.isTop ? "bg-gray" : "bg-white"}`}>
             <ContactCard
               refresh={resetFetchList}
-              deleteable
-              key={item.agentId}
+              deleteable={!item.isEnt}
+              key={item.contactId}
               data={item}
               fromContact
             ></ContactCard>

+ 0 - 73
src/pages/dashboard/components/Picker/PickerSingleColumn.tsx

@@ -1,73 +0,0 @@
-import { View, PickerView, PickerViewColumn  } from "@tarojs/components"
-import Taro from "@tarojs/taro"
-
-export interface IndexProps {
-  options: string[]
-  headerTitle?: string
-  selected: string
-  showPicker: boolean
-  setShowPicker: (show:boolean) => void
-  onChange: (value: string) => void
-  children: JSX.Element|JSX.Element[]
-}
-
-const Index = ({selected, showPicker, setShowPicker,onChange, options, headerTitle = '请选择', children }: IndexProps) => {
-  
-  
-  
-  
-
-  // 处理选择变化
-  const handleChange = (e:any) => {
-    const { value } = e.detail
-    onChange(options[value[0]])
-  }
-
-  // 确认选择
-  const handleConfirm = () => {
-    setShowPicker(false)
-    Taro.showToast({
-      title: `已选择: ${selected}`,
-      icon: 'none'
-    })
-  }
-  return <View>
-    {children}
-    {showPicker && (
-            <View className='picker-modal'>
-              <View className='picker-header'>
-                <View className='picker-cancel' onClick={() => setShowPicker(false)}>
-                  取消
-                </View>
-                <View className='picker-title'>{headerTitle}</View>
-                <View 
-                  className='picker-confirm' 
-                  onClick={handleConfirm}
-                  style={{ color: '#317CFA' }} // 直接修改确认按钮颜色
-                >
-                  确定
-                </View>
-              </View>
-
-              {/* 单列 PickerView */}
-              <PickerView
-                className='picker-view'
-                indicatorStyle='height: 50px;' // 选中项高亮样式
-                style='width: 100%; height: 300px;'
-                onChange={handleChange}
-                value={[options.indexOf(selected)]} // 初始选中项索引
-              >
-                <PickerViewColumn>
-                  {options.map((item) => (
-                    <View key={item} className='picker-item'>
-                      {item}
-                    </View>
-                  ))}
-                </PickerViewColumn>
-              </PickerView>
-            </View>
-          )}
-  </View>
-}
-
-export default Index;

+ 8 - 5
src/pages/dashboard/components/VisitorCard/index.tsx

@@ -1,11 +1,12 @@
-import { View, Text } from "@tarojs/components";
+import { View, Text, Image } from "@tarojs/components";
 import DigitalCardBasic from "@/components/DigitalCard/DigitalCardBasic";
 
 export interface IndexProps {
   data: {
     certificated?: boolean
     name: string
-    position: string
+    position?: string
+    avatarUrl?: string
     company: string
     agentName: string
     visitNum: number
@@ -19,11 +20,13 @@ const Index = ({ data }: IndexProps) => {
   
   return <View className="bg-white rounded-12 p-12">
   <View className="flex items-start gap-12">
-    <View className="flex items-start bg-pink rounded-full overflow-hidden">
-      <View className="w-56 h-56 bg-gray-3 rounded-full"></View>
+    <View className="flex items-star rounded-full overflow-hidden">
+      <View className="w-56 h-56 bg-gray-3 rounded-full">
+        {(!!data.avatarUrl) && <Image src={data.avatarUrl} mode="aspectFill" className="w-56 h-56 bg-gray-3 rounded-full"></Image>}
+      </View>
     </View>
     <View className="flex flex-col gap-8">
-      <DigitalCardBasic name={data.name} position={data.position} company={data.company} certificated />
+      <DigitalCardBasic name={data.name} position={data.position ?? ''} company={data.company} certificated />
       <View className="text-14 font-medium leading-22">第 <Text className="text-yellow">{data.visitNum}</Text> 次访问了你的智能体【{data.agentName}】</View>
       <View className="flex-center text-12 leading-20">
         <View className="flex-1"><Text className="text-primary">{data.chatNum}</Text> 轮对话</View>

+ 76 - 0
src/pages/dashboard/components/VisitorList/index.tsx

@@ -0,0 +1,76 @@
+import { View, Text } from "@tarojs/components";
+import Taro, { useReachBottom } from "@tarojs/taro";
+import VisitorCard from "../VisitorCard";
+import { useEffect, useState } from "react";
+
+import { TVisitorAgent } from "@/types/visitor";
+import { useLoadMore } from "@/utils/loadMore";
+import { getVisitorList } from "@/service/visitor";
+
+interface IProps {
+  agentId: string | number;
+}
+
+export default ({ agentId }: IProps) => {
+  const [list, setList] = useState<TVisitorAgent[]>([]);
+
+  const fetcher = async ([_url, nextId, page, pageSize, agentId]) => {
+    const res = await getVisitorList({ startId: nextId, pageSize, agentId });
+    return res.data;
+  };
+  const { data, loadMore, refetch } = useLoadMore<{
+    data?: TVisitorAgent[];
+    nextId?: string;
+    totalCount?: number;
+  }>({
+    url: "/blue-aiagent/api/v1/my/contacts",
+    fetcher,
+    params: [agentId],
+  });
+
+  useReachBottom(() => {
+    loadMore();
+  });
+
+  useEffect(() => {
+    if (data?.data) {
+      setList([...list, ...data.data]);
+    }
+  }, [data]);
+
+  return (
+    <View>
+      <View className="mb-8 text-14 leading-22 font-medium">访问详情</View>
+      <View className="flex flex-col gap-8">
+        {list.map((item) => {
+          return (
+            <VisitorCard
+              data={{
+                name: item.name ?? "",
+                company: "",
+                avatarUrl: item.avatarUrl,
+                position: item.position ?? "",
+                agentName: item.myAgentName ?? "",
+                chatNum: item.chatRound,
+                visitNum: item.visitTimes,
+                disLikeNum: item.dislikeCnt,
+                visitTime: item.visitTimes,
+              }}
+            ></VisitorCard>
+          );
+        })}
+
+        {/* <VisitorCard data={{
+            name: '李四',
+            company: '北京茗视光眼科医院管理有限公司',
+            position: '商务经理',
+            agentName: '张医生',
+            chatNum: 10,
+            visitNum: 7,
+            disLikeNum: 2,
+            visitTime: 5
+          }}></VisitorCard> */}
+      </View>
+    </View>
+  );
+};

+ 64 - 47
src/pages/dashboard/index.tsx

@@ -4,75 +4,92 @@ import NavBarNormal from "@/components/nav-bar-normal/index";
 import PageCustom from "@/components/page-custom/index";
 import IconArrowDownRounded from '@/components/icon/IconArrowDownRounded';
 import DataCard from './components/DataCard'
-import VisitorCard from "./components/VisitorCard";
-import  PickerSingleColumn from "./components/Picker/PickerSingleColumn";
-import { useState } from "react";
+import VisitorList from "./components/VisitorList";
+import  PickerSingleColumn from "@/components/Picker/PickerSingleColumn";
+import { useEffect, useState } from "react";
+import { useAgentStore } from "@/store/agentStore";
+import { getVisitorSummary, type TVisitorSummary } from '@/service/visitor'
+import { isSuccess } from "@/utils";
 
 export default () => {
   
+  const {agents, fetchAgents} = useAgentStore()
+  
   
-  // 当前选中的值
-  const options = ['张三', '李四']
   // 是否显示选择器
   const [showPicker, setShowPicker] = useState(false)
+  const [summary, setSummary] = useState<TVisitorSummary>()
 
   // 当前选中的值
-  const [selected, setSelected] = useState(options[0])
+  const [selected, setSelected] = useState<string>();
+  const currentAgent = agents.find( _agent => _agent.name === selected)
 
-  const handleChange = (value: string) => {
+  const onPicked = (value: string) => {
     setSelected(value)
   }
 
+  useEffect(()=> {
+    fetchAgents()
+  }, [])
+
+  useEffect(() => {
+    if (agents?.length && !selected) {
+      setSelected(agents[0].name)
+    }
+  }, [agents, selected])
+
+
+  const fetchInitData = async () => {
+    const currentAgent = agents.find( _agent => _agent.name === selected)
+    if(!currentAgent){
+      return
+    }
+    const response = await getVisitorSummary(currentAgent.agentId)
+    if(isSuccess(response.status)){
+      setSummary(response.data)
+    }
+  }
+
+  useEffect(()=> {
+    fetchInitData()
+  }, [])
+
+
+
+  if(!agents || !selected) {
+    return <View>...</View>
+  }
+
+  const options = agents.map(_agent => _agent.name)
+
+
   return (
     <PageCustom>
       <NavBarNormal leftColumn={()=> <Text>数据分析</Text>}>
       </NavBarNormal>
       <View className="w-full pt-8 px-16 pb-40">
-        
-        
-        <PickerSingleColumn options={options} selected={selected} onChange={handleChange} showPicker={showPicker} setShowPicker={setShowPicker}>
-          <View className="flex items-center gap-2 bg-white rounded-12 p-12 mb-16" onClick={() => setShowPicker(true)}>
-            <View className="flex-1 text-14 leading-22 text-gray-45">{selected}</View>
-            <View className="flex-center">
-              <IconArrowDownRounded/>
+        {(!!options.length && !!selected) && 
+          <PickerSingleColumn options={options} selected={selected} onPicked={onPicked} showPicker={showPicker} setShowPicker={setShowPicker}>
+            <View className="flex items-center gap-2 bg-white rounded-12 p-12 mb-16" onClick={() => setShowPicker(true)}>
+              <View className="flex-1 text-14 leading-22 text-gray-45">{selected}</View>
+              <View className="flex-center">
+                <IconArrowDownRounded/>
+              </View>
             </View>
-          </View>
-        </PickerSingleColumn>
-        
+          </PickerSingleColumn>
+        }
 
         <View className="grid grid-cols-2 gap-8 bg-white rounded-12 p-12 mb-12">
-          <DataCard text="今日访问次数" unitText="次" value={588} />
-          <DataCard text="今日对话人数" unitText="人" value={588} />
-          <DataCard text="累计访问次数" unitText="次" value={588} />
-          <DataCard text="累计对话人数" unitText="人" value={588} />
-          <DataCard text="好评" unitText="条" value={20} arrow />
-          <DataCard text="待处理差评" unitText="条" value={2} arrow />
+          <DataCard text="今日访问次数" unitText="次" value={summary?.todayPv ?? 0} />
+          <DataCard text="今日对话人数" unitText="人" value={summary?.todayUv ?? 0} />
+          <DataCard text="累计访问次数" unitText="次" value={summary?.sumPv ?? 0} />
+          <DataCard text="累计对话人数" unitText="人" value={summary?.sumPv ?? 0} />
+          <DataCard text="好评" unitText="条" value={summary?.unreadLikeCnt ?? 0} arrow />
+          <DataCard text="待处理差评" unitText="条" value={summary?.unprocessedDislikeCnt ?? 0} arrow />
         </View>
 
-        <View className="mb-8 text-14 leading-22 font-medium">访问详情</View>
-        <View className="flex flex-col gap-8">
-          <VisitorCard data={{
-              name: '洪三',
-              company: '北京茗视光眼科医院管理有限公司',
-              position: 'CEO',
-              agentName: '张医生',
-              chatNum: 10,
-              visitNum: 7,
-              disLikeNum: 2,
-              visitTime: 5
-            }}></VisitorCard>
-          
-          <VisitorCard data={{
-              name: '李四',
-              company: '北京茗视光眼科医院管理有限公司',
-              position: '商务经理',
-              agentName: '张医生',
-              chatNum: 10,
-              visitNum: 7,
-              disLikeNum: 2,
-              visitTime: 5
-            }}></VisitorCard>
-        </View>
+        {currentAgent?.agentId && <VisitorList agentId={currentAgent.agentId}></VisitorList>}
+
       </View>
       
     </PageCustom>

+ 0 - 47
src/pages/editor-pages/editor-chat/components/edit-character/components/textarea-form/index.tsx

@@ -1,47 +0,0 @@
-import { View } from "@tarojs/components";
-import WemetaTextarea from "@/components/wemeta-textarea/index";
-import { useState } from "react";
-interface Props {
-  title: string
-  placeholder?: string
-  value: string
-  cursorSpacing?: number
-  maxlength?: number
-  bgColor?: string
-  key: string,
-  extra?: () => JSX.Element | JSX.Element[] | undefined
-  onAdd: (value: string) => void
-  onBlur?: () => void
-}
-export default ({
-  value,
-  title,
-  placeholder = "请输入...",
-  onBlur,
-  maxlength = 10000,
-  key,
-  onAdd,
-}: Props) => {
-  const [text, setText] = useState(value)
-  const handleAdd = () => {
-    onAdd(text)
-  }
-  
-  return (
-    <>
-      <View className="text-16 leading-32 font-medium text-black pl-4 py-8 mb-8">{title}</View>
-      <View className="flex flex-col gap-16">
-        <WemetaTextarea
-          value={text}
-          onInput={(value: any) => setText(value)}
-          onBlur={onBlur}
-          placeholder={placeholder}
-          cursorSpacing={100}
-          maxlength={maxlength}
-          bgColor='#F5F6F7'
-        />
-        <View className="button-rounded-big" onClick={handleAdd}>添加</View>
-      </View>
-    </>
-  );
-};

+ 0 - 237
src/pages/editor-pages/editor-chat/components/edit-character/index.tsx

@@ -1,237 +0,0 @@
-import IconArrow from "@/components/icon/icon-arrow/index";
-import Popup from "@/components/popup/popup/index";
-import WemetaRadioBlock from "@/components/wemeta-radio-block/index";
-import WemetaTextarea from "@/components/wemeta-textarea/index";
-import IconVoice from "@/images/svgs/icon_voice.svg";
-import { useCharacterStore } from "@/store/characterStore";
-import { ICharacter } from "@/types";
-import { Image, Text, View } from "@tarojs/components";
-import Taro, { useDidShow } from "@tarojs/taro";
-import { forwardRef, useImperativeHandle, useState } from "react";
-import editorStyle from "../../../editor.module.less";
-
-export interface IChatBasicComponent {
-  save: () => void;
-}
-
-interface Props {
-  value: ICharacter;
-  setValue: (value: ICharacter) => void;
-}
-export default forwardRef<IChatBasicComponent, Props>(({value, setValue}: Props, ref) => {
-  const { saveCharacter } = useCharacterStore();
-  const currentCharacter = useCharacterStore((state) => state.character);
-  const [greetingShow, setGreetingShow] = useState(false);
-  const [personalityShow, setPersonalityShow] = useState(false);
-  const [greeting, setGreeting] = useState(currentCharacter?.greeting ?? "");
-  const [personality, setPersonality] = useState(
-    currentCharacter?.personality ?? "",
-  );
-
-  // const [value, setValue] = useState(currentCharacter);
-
-  let [radioValue, setRadioValue] = useState(
-    currentCharacter?.style ?? "热情服务",
-  );
-
-  const radioList = [
-    {
-      text: "简洁明了",
-      value: "简洁明了",
-      checked: false,
-    },
-    {
-      text: "热情服务",
-      value: "热情服务",
-      checked: false,
-    },
-    {
-      text: "社交高手",
-      value: "社交高手",
-      checked: false,
-    },
-    {
-      text: "严谨专业",
-      value: "严谨专业",
-      checked: false,
-    },
-  ];
-
-  const setValueByKey = (key: string, v: string) => {
-    if (!value) {
-      return;
-    }
-    let newValue = { ...value };
-    newValue[key] = v;
-    // setValue(newValue);
-  };
-
-  const saveTextByKey = (key: string, v: string) => {
-    if (!value) {
-      return;
-    }
-    let newValue = { ...value };
-    newValue[key] = v;
-    setValue(newValue);
-    saveCharacter({ ...newValue });
-  };
-
-  const onRadioChange = (val: string) => {
-    setRadioValue(val);
-    setValueByKey("style", val);
-    currentCharacter?.profileId &&
-      saveCharacter({ profileId: currentCharacter.profileId, style: val });
-    return false;
-  };
-
-  const handleSave = () => {
-    console.log(value, "save");
-    value && saveCharacter({ ...value });
-  };
-  const handleNavToVoiceTabsPage = () => {
-    Taro.navigateTo({
-      url: "/pages/voice-tabs/index?profileId=" + currentCharacter?.profileId,
-    });
-  };
-
-  useImperativeHandle(ref, () => {
-    return {
-      save: handleSave,
-    };
-  });
-
-  const renderBasic = () => {
-    if (!currentCharacter || !value) {
-      return <></>;
-    }
-    return (
-      <>
-        <View
-          className={editorStyle.formItem}
-          onClick={() => {
-            currentCharacter?.greeting &&
-              setGreeting(currentCharacter.greeting);
-            setGreetingShow(true);
-          }}
-        >
-          <View className={editorStyle.formItemLabel}>开场白</View>
-          <View className='flex items-center bg-white rounded-20 px-16 py-28'>
-            <View className='flex-1 text-justify'>
-              {value.greeting ? (
-                value.greeting
-              ) : (
-                <Text className='text-gray-45'>添加开场白</Text>
-              )}
-            </View>
-            <IconArrow></IconArrow>
-          </View>
-        </View>
-
-        <View
-          className={editorStyle.formItem}
-          onClick={() => {
-            currentCharacter?.personality &&
-              setPersonality(currentCharacter.personality);
-            setPersonalityShow(true);
-          }}
-        >
-          <View className={editorStyle.formItemLabel}>我的人设</View>
-          <View className='flex items-center bg-white rounded-20 px-16 py-28'>
-            <View className='flex-1 text-justify '>
-              {value.personality ? (
-                value.personality
-              ) : (
-                <Text className='text-gray-45'>添加我的人设</Text>
-              )}
-            </View>
-            <IconArrow></IconArrow>
-          </View>
-        </View>
-
-        <View
-          className={editorStyle.formItem}
-          onClick={handleNavToVoiceTabsPage}
-        >
-          <View className={editorStyle.formItemLabel}>对话声音</View>
-          <View className='flex items-center bg-white rounded-20 px-16 py-28'>
-            <View
-              className='flex items-center justify-center w-36 h-36 rounded-8'
-              style={{
-                background: "#CBF706",
-              }}
-            >
-              <Image src={IconVoice} className='w-16 h-16'></Image>
-            </View>
-            <View className='flex-1 text-justify ml-10 '>
-              {currentCharacter.voiceAlias ?? "系统声音"}
-            </View>
-            <IconArrow></IconArrow>
-          </View>
-        </View>
-        <View className={editorStyle.formItem}>
-          <View className={editorStyle.formItemLabel}>对话风格</View>
-          <WemetaRadioBlock
-            radioList={radioList}
-            onChange={onRadioChange}
-            value={radioValue}
-          ></WemetaRadioBlock>
-        </View>
-      </>
-    );
-  };
-
-  return (
-    <View className={`relative pb-56 ${editorStyle.transitionBottom}`}>
-      <View className={editorStyle.formContainer}>{renderBasic()}</View>
-
-      <Popup title='填写开场白' show={greetingShow} setShow={setGreetingShow}>
-        <View className='flex flex-col gap-16 pb-38'>
-          <WemetaTextarea
-            value={greeting}
-            onInput={(val: string) => setGreeting(val)}
-            placeholder='将作为开启聊天的第一句话,示例:你好,很高兴遇见你...建议不超过100字'
-            cursorSpacing={500}
-            maxlength={100}
-            bgColor='#F5F6F7'
-            extraClass='leading-30'
-          />
-          <View
-            className='button-rounded-big'
-            onClick={() => {
-              saveTextByKey("greeting", greeting);
-              setGreetingShow(false);
-            }}
-          >
-            添加
-          </View>
-        </View>
-      </Popup>
-
-      <Popup
-        title='填写我的人设'
-        show={personalityShow}
-        setShow={setPersonalityShow}
-      >
-        <View className='flex flex-col gap-16 pb-38'>
-          <WemetaTextarea
-            value={personality}
-            onInput={(val: any) => setPersonality(val)}
-            placeholder='示例:你是一位幽默风趣的科技区UP 主,了解前沿科技,善于运用生动且有趣的语言,表达对科技的独家理解。...'
-            cursorSpacing={500}
-            maxlength={500}
-            bgColor='#F5F6F7'
-          />
-          <View
-            className='button-rounded-big'
-            onClick={() => {
-              saveTextByKey("personality", personality);
-              setPersonalityShow(false);
-            }}
-          >
-            添加
-          </View>
-        </View>
-      </Popup>
-    </View>
-  );
-});

+ 0 - 57
src/pages/editor-pages/editor-chat/components/edit-rule/components/foldable-list/index.module.less

@@ -1,57 +0,0 @@
-
-.foldedBar{
-  border-top: 1px solid rgba(#000 ,.05);
-  padding: 8px 16px;
-  display: flex;
-  align-items: center;
-}
-.foldedTitle{
-  flex: 1;
-  font-size: 15px;
-  line-height: 30px;
-  font-weight: 500;
-}
-.list{
-  interpolate-size: allow-keywords;
-  width: 100%;
-  transition: height .2s ease;
-  overflow: hidden;
-}
-.listItem{
-  padding: 8px 16px;
-  display: flex;
-  align-items: center;
-}
-.num{
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: var(--color-primary);
-  width: 20px;
-  height: 20px;
-  border-radius: 100%;
-  color: black;
-  font-size: 12px;
-  line-height: 12px;
-  font-weight: 500;
-}
-.text{
-  margin-left: 4px;
-  flex: 1;
-  font-weight: 400;
-  font-size: 15px;
-  line-height: 30px;
-  color: rgba(#000, .65);
-}
-
-.deleteButton{
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 24px;
-  height: 24px;
-  font-size: 24px;
-  color: #C4C4C4;
-}
-
-

+ 0 - 27
src/pages/editor-pages/editor-chat/components/edit-rule/components/foldable-list/index.tsx

@@ -1,27 +0,0 @@
-import { View } from '@tarojs/components'
-import style from './index.module.less'
-import IconArrow from '@/components/icon/icon-arrow/index'
-import { useState } from 'react';
-interface Props {
-  isFold?: boolean
-  dataList: string[];
-  deleteFunc: (item: string)=> void;
-}
-export default ({dataList, deleteFunc, isFold = false}: Props)=> {
-  const [fold, setFold] = useState(isFold)
-  return <>
-    <View className={style.foldedBar} onClick={()=> setFold(!fold)}>
-      <View className={style.foldedTitle}>已添加 {dataList.length} 个</View>
-      <IconArrow></IconArrow>
-    </View>
-    <View className={style.list} style={{height: fold ? '0px': 'max-content'}}>
-      {dataList.map((item, index)=> {
-        return <View className={style.listItem}>
-          <View className={style.num}>{index+1}</View>
-          <View className={style.text}>{item}</View>
-          <View className={`iconfont icon-icon_24_delet icon-24 ${style.deleteButton}`}  onClick={()=> deleteFunc(item)}></View>
-        </View>
-      })}
-    </View>
-  </>
-}

+ 0 - 171
src/pages/editor-pages/editor-chat/components/edit-rule/index.tsx

@@ -1,171 +0,0 @@
-import { forwardRef, useState } from "react";
-import { View, Text } from "@tarojs/components";
-import editorStyle from "../../../editor.module.less";
-import WemetaTextarea from "@/components/wemeta-textarea/index";
-import Popup from "@/components/popup/popup/index";
-import { useCharacterStore } from "@/store/characterStore";
-import ButtonCardAdd from "@/components/button-card-add";
-import FoldableList from "./components/foldable-list/index";
-interface Props {}
-export interface IChatAdvancedComponent {
-  saveChild: () => void;
-}
-export default forwardRef(({}: Props) => {
-  const { saveCharacter } = useCharacterStore();
-  const currentCharacter = useCharacterStore((state) => state.character);
-  const [keyword, setKeyword] = useState("");
-  const [question, setQuestion] = useState("");
-  const [show, setShow] = useState(false);
-  const [questionShow, setQuestionShow] = useState(false);
-
-  const handleAddKeyword = () => {
-    if (!keyword.trim().length) {
-      return;
-    }
-    setShow(false);
-    if (currentCharacter) {
-      console.log("currentCharacter", currentCharacter, keyword);
-      const banWords = currentCharacter.banWords ?? [];
-      saveCharacter({
-        profileId: currentCharacter.profileId,
-        banWords: [...banWords, keyword],
-      });
-    }
-  };
-
-  const deleteBanWord = (word: string) => {
-    if (currentCharacter) {
-      let banWords = currentCharacter.banWords ?? [];
-      banWords = banWords.filter((item) => {
-        return item !== word;
-      });
-      saveCharacter({
-        profileId: currentCharacter.profileId,
-        banWords: [...banWords],
-      });
-    }
-  };
-  const deleteQuestion = (qa: string) => {
-    if (currentCharacter) {
-      let banQuestions = currentCharacter.banQuestions ?? [];
-      banQuestions = banQuestions.filter((item) => {
-        return item !== qa;
-      });
-      saveCharacter({
-        profileId: currentCharacter.profileId,
-        banQuestions: [...banQuestions],
-      });
-    }
-  };
-
-  const handleAddQuestion = () => {
-    if (!question.trim().length) {
-      return true;
-    }
-    if (currentCharacter) {
-      const banQuestions = currentCharacter.banQuestions ?? [];
-      saveCharacter({
-        profileId: currentCharacter.profileId,
-        banQuestions: [...banQuestions, question],
-      });
-    }
-    setQuestionShow(false);
-  };
-
-  const showKeywordPopup = () => {
-    setKeyword("");
-    setShow(true);
-  };
-
-  const showQuestionPopup = () => {
-    setQuestion("");
-    setQuestionShow(true);
-  };
-
-  const renderContent = () => {
-    if (currentCharacter) {
-      return (
-        <View className={editorStyle.formContainer}>
-          <View className={editorStyle.formItem}>
-            <View className={editorStyle.formItemLabel}>
-              禁止 AI 输出的关键词
-            </View>
-            <ButtonCardAdd text="添加关键词" onClick={showKeywordPopup}>
-            {currentCharacter?.banWords && (
-                <FoldableList
-                  dataList={currentCharacter.banWords}
-                  deleteFunc={deleteBanWord}
-                />
-              )}
-            </ButtonCardAdd>
-          </View>
-          <View className="flex flex-col gap-12">
-            <View className={editorStyle.formItem}>
-              <View className={editorStyle.formItemLabel}>
-                禁止 AI 回答的问题
-              </View>
-              <ButtonCardAdd text="添加问题" onClick={showQuestionPopup}>
-              {currentCharacter.banQuestions && (
-                  <FoldableList
-                    dataList={currentCharacter.banQuestions}
-                    deleteFunc={deleteQuestion}
-                  />
-                )}
-              </ButtonCardAdd>
-            </View>
-          </View>
-        </View>
-      );
-    }
-    return <></>;
-  };
-
-  return (
-    <View>
-      <Popup title="添加禁止 AI 输出的关键词" show={show} setShow={setShow}>
-        <View className="flex flex-col gap-16">
-          <WemetaTextarea
-            value={keyword}
-            onInput={(value: any) => setKeyword(value)}
-            placeholder="请输入不超过6个字的关键词..."
-            cursorSpacing={200}
-            maxlength={6}
-            bgColor="#F5F6F7"
-          />
-          <View className="button-rounded-big" onClick={handleAddKeyword}>
-            添加
-          </View>
-          <View className="mt-44 mb-48 text-14 leading-22 font-medium text-gray-65 p-12 rounded-20 bg-gray-f8">
-            🌟<Text className="font-medium">小绿提示:</Text>
-            好比给AI设定了“红灯停”的规则,让它知道有些话题是敏感或不恰当的,需要避免。
-          </View>
-        </View>
-      </Popup>
-
-      <Popup
-        title="添加禁止 AI 回答的问题  (限20字符)"
-        show={questionShow}
-        setShow={setQuestionShow}
-      >
-        <View className="flex flex-col gap-16">
-          <WemetaTextarea
-            value={question}
-            onInput={(value: any) => setQuestion(value)}
-            placeholder="请输入不超过20个字的问题..."
-            cursorSpacing={200}
-            maxlength={20}
-            bgColor="#F5F6F7"
-          />
-          <View className="button-rounded-big" onClick={handleAddQuestion}>
-            添加
-          </View>
-          <View className="mt-44 mb-48 pb-28 text-14 leading-22 font-medium text-gray-65 p-12 rounded-20 bg-gray-f8">
-            🌟<Text className="font-medium">小绿提示:</Text>
-            就像是给AI立了规矩,帮助它知道什么话不能说,什么事不能做
-          </View>
-        </View>
-      </Popup>
-      {renderContent()}
-    </View>
-  );
-});

+ 0 - 32
src/pages/editor-pages/editor-chat/components/edit-voice/index.module.less

@@ -1,32 +0,0 @@
-.listIcon{
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 36px;
-  height: 36px;
-  border-radius: 8px;
-}
-.iconImage{
-  width: 18px;
-  height: 18px;
-}
-.listIconClone{
-  .listIcon();
-  background-color: var(--color-bg-primary);
-}
-.listIconSystem{
-  .listIcon();
-  background-color: #FFD86D;
-}
-.ListItemText{
-  font-size: 15px;
-  font-weight: 500;
-  line-height: 24px;
-  color: #000;
-}
-.ListItemValue{
-  font-size: 15px;
-  font-weight: 400;
-  line-height: 24px;
-  color: rgba(#000, .65);
-}

+ 0 - 161
src/pages/editor-pages/editor-chat/components/edit-voice/index.tsx

@@ -1,161 +0,0 @@
-import { useEffect, forwardRef, useState, useImperativeHandle } from "react";
-import { View, Image, Text } from "@tarojs/components";
-import CardList from "@/components/list/card-list/index";
-import CardListItem from "@/components/list/card-list-item/index";
-import editorStyle from "../../../editor.module.less";
-import style from "./index.module.less";
-import { useCharacterStore } from "@/store/characterStore";
-import { ICharacter } from "@/types";
-import IconClone from "@/images/icon-renshengkelong-18.png";
-import IconVoice from "@/images/icon-voice-18.png";
-import WemetaRadio from "@/components/wemeta-radio/index";
-import Taro from "@tarojs/taro";
-import { useAppStore } from "@/store/appStore";
-import { navToVip } from '@/utils/'
-
-interface Props {
-  value: ICharacter;
-}
-export interface IChatBasicComponent {
-  save: () => void;
-}
-export default forwardRef<IChatBasicComponent, Props>(({value}: Props, ref) => {
-  const { saveCharacter, fetchVoiceCloneHistory } = useCharacterStore();
-  const voiceList = useCharacterStore((state) => state.voiceList);
-  const currentCharacter = useCharacterStore((state) => state.character);
-  const [mode, setMode] = useState(!!currentCharacter?.voice ? 0 : 1 );
-  const isIos = useAppStore((state) => state.isIos);
-  const createVoiceNameText = () => {
-    const i = voiceList.findIndex((item)=> item.voiceName === currentCharacter?.voice)
-    if(i === -1) return ''
-    return `克隆声音 0${i+1}`
-  }
-  const save = async (voice: string) => {
-    console.log("save character voice: ", voice);
-    
-    if (!currentCharacter) {
-      return;
-    }
-    // 保存 voice 至 character
-    await saveCharacter({
-      profileId: currentCharacter.profileId,
-      voice: voice,
-    });
-  };
-
-  const changeMode = (value: number) => {
-    setMode(value);
-    if(value === 1){
-      save('')
-    }else{
-      const voiceName = voiceList?.[0]?.voiceName
-      if(voiceName){
-        save(voiceName)
-      }
-    }
-  };
-
-  const handleSave = () => {
-    console.log(value, "save");
-    value && saveCharacter({ ...value });
-  };
-  
-  const nav2SystemVoicePage = ()=> {
-    console.log({url: '/pages/system-voice/index?profileId='+currentCharacter?.profileId})
-    // Taro.navigateTo({url:`/pages/system-voice/index?profileId?=${}`})
-    Taro.navigateTo({url: '/pages/system-voice/index?profileId='+currentCharacter?.profileId})
-  }
-
-  const nav2Voice = ()=> {
-    Taro.navigateTo({url: '/pages/voice/index?profileId='+currentCharacter?.profileId})
-  }
-  const nav2Member = ()=> {
-    if(isIos){
-      Taro.showModal({
-        content: '根据微信规定,iOS平台暂不提供支付功能。',
-        showCancel: false,
-      });
-      
-    } else {
-      navToVip()
-    }
-  }
-
-  useImperativeHandle(ref, () => {
-    return {
-      save: handleSave,
-    };
-  });
-
-  useEffect(() => {
-    fetchVoiceCloneHistory(value.profileId);
-  }, [value.profileId]);
-
-  return (
-    <View>
-      <View className={editorStyle.formContainer}>
-        <View className={editorStyle.formItem}>
-          <View className={editorStyle.formItemLabel}>对话音色</View>
-          <CardList>
-            <CardListItem
-              underline
-              arrow
-              rightRenderer={() => (
-                <View className={style.ListItemValue}>{createVoiceNameText()}</View>
-              )}
-              onClick={nav2Voice}
-            >
-              <View className="flex items-center gap-8">
-                <View className={style.listIconClone}>
-                  <Image src={IconClone} className={style.iconImage}></Image>
-                </View>
-                <View className={style.ListItemText}>克隆声音</View>
-              </View>
-            </CardListItem>
-            <CardListItem
-              arrow
-              rightRenderer={() => (
-                <View className={style.ListItemValue}>{currentCharacter?.defaultSystemVoice}</View>
-              )}
-              onClick={nav2SystemVoicePage}
-            >
-              <View className="flex items-center gap-8">
-                <View className={style.listIconSystem}>
-                  <Image src={IconVoice} className={style.iconImage}></Image>
-                </View>
-                <View className={style.ListItemText}>系统声音</View>
-              </View>
-            </CardListItem>
-          </CardList>
-        </View>
-
-        <View className={editorStyle.formItem}>
-          <View className={editorStyle.formItemLabel}>模式选择</View>
-          <CardList>
-            <CardListItem
-              underline
-              rightRenderer={() => <WemetaRadio checked={mode === 0} />}
-              onClick={() => changeMode(0)}
-            >
-              <View className="flex flex-col gap-6 pr-36">
-                <View className={style.ListItemText}>优先使用克隆声音</View>
-                <View className="text-12 leading-22 text-gray-65">
-                  当前每天可使用克隆音色对话10分钟,超出限定时长后,自动使用系统音色,{" "}
-                  <Text className="primary-color-dark font-medium" onClick={nav2Member}>
-                    前往升级
-                  </Text>
-                </View>
-              </View>
-            </CardListItem>
-            <CardListItem
-              rightRenderer={() => <WemetaRadio checked={mode === 1} />}
-              onClick={() => changeMode(1)}
-            >
-              <View className={style.ListItemText}>仅使用系统声音</View>
-            </CardListItem>
-          </CardList>
-        </View>
-      </View>
-    </View>
-  );
-});

+ 0 - 5
src/pages/editor-pages/editor-chat/index.config.ts

@@ -1,5 +0,0 @@
-export default definePageConfig({
-  navigationBarTitleText: '章节标题',
-  "usingComponents": {},
-  navigationStyle: 'custom'
-})

+ 0 - 10
src/pages/editor-pages/editor-chat/index.module.less

@@ -1,10 +0,0 @@
-.pageTitle{
-  color: #000;
-  text-align: center;
-  font-size: 24px;
-  font-style: normal;
-  font-weight: 500;
-  line-height: 40px;
-  padding-bottom: 29px;
-}
-

+ 0 - 71
src/pages/editor-pages/editor-chat/index.tsx

@@ -1,71 +0,0 @@
-/**
- * 对话
- */
-
-import { View } from '@tarojs/components'
-import { useRef, useState, } from 'react'
-
-import NavBarNormal from '@/components/nav-bar-normal/index'
-import PageCustom from '@/components/page-custom/index'
-import WemetaTabs from '@/components/wemeta-tabs/index'
-
-import { useCharacterStore } from "@/store/characterStore"
-import Taro, { useLoad } from '@tarojs/taro'
-import editorStyle from '../editor.module.less'
-import EditCharacter, { IChatBasicComponent } from './components/edit-character/index'
-import EditRule from './components/edit-rule/index'
-
-
-export default function Index () {
-  const $instance = Taro.getCurrentInstance();
-  const { profileId } = $instance.router?.params || {};
-  const { fetchCharacter } = useCharacterStore();
-  const currentCharacter = useCharacterStore((state)=> state.character);
-
-  const [value, setValue] =  useState(currentCharacter);
-  const chatBasicRef = useRef<IChatBasicComponent>(null)
-
-  const handleNavBack = ()=> {
-    if (chatBasicRef.current) {
-      // chatBasicRef.current.save(); // 调用子组件的保存方法
-      profileId && fetchCharacter(profileId)
-    }
-  }
-
-  const tabList = [
-    {
-      key: '1',
-      label: '基础',
-      children: <View>{value && <EditCharacter ref={chatBasicRef} value={value} setValue={setValue}></EditCharacter>}</View>
-    },
-    // {
-    //   key: '2',
-    //   label: '声音',
-    //   children: (
-    //     <View>{value && <EditVoice value={value} />}</View>
-    //   )
-    // },
-    {
-      key: '3',
-      label: '规则',
-      children: (
-        <EditRule />
-      )
-    },
-  ]
-  useLoad(()=> {
-    console.log(profileId, $instance)
-    profileId && fetchCharacter(profileId)
-  })
-
-  return (
-    <PageCustom bgColor='global-light-green-bg'>
-      <NavBarNormal onNavBack={handleNavBack}>对话</NavBarNormal>
-      <View className='flex flex-col items-center w-full'>
-        <View className={`${editorStyle.container}`}>
-          {currentCharacter && <WemetaTabs full list={tabList} current='1'></WemetaTabs>}
-        </View>
-      </View>
-    </PageCustom>
-  )
-}

+ 0 - 0
src/pages/editor-pages/editor-link-contact/components/AgentScrollList/index.module.less


+ 66 - 0
src/pages/editor-pages/editor-link-contact/components/AgentScrollList/index.tsx

@@ -0,0 +1,66 @@
+import Taro, { useReachBottom,} from "@tarojs/taro";
+import { View, Text } from "@tarojs/components";
+import style from "./index.module.less";
+import { useEffect, useState } from "react";
+import { getContactList } from "@/service/contact";
+import { useLoadMore } from "@/utils/loadMore";
+import { TContactItem } from "@/types/contact";
+
+
+
+export default function Index() {
+  const [list, setList] = useState<TContactItem[]>([]);
+
+  const fetcher = async ([_url, nextId, page, pageSize, _keyword]) => {
+    let keyword = _keyword
+    const res = await getContactList({ startId: nextId, pageSize, keyword });
+    return res.data;
+  };
+  const { data, loadMore, refetch } = useLoadMore<{
+    data?: TContactItem[]
+    nextId?: string,
+    totalCount?: number
+  }>({
+    url: '/blue-aiagent/api/v1/my/contacts',
+    fetcher,
+  });
+
+  useReachBottom(() => {
+    // 加载更多数据
+    loadMore();
+  });
+
+  useEffect(() => {
+    if (data?.data) {
+      setList([...list, ...data.data]);
+    }
+  }, [data]);
+
+
+  const handleHello = async (e: any) => {
+    
+    
+  };
+
+  const renderContent = () => {
+    if (list?.length) {
+      return list.map((item) => (
+        <View className='bg-white'>
+          {item.name}
+        </View>
+      ));
+    }
+    return (
+      <View className="flex flex-col pt-56 items-center">
+        <View className="data-empty"></View>
+        <View className="text-12 text-gray-45 text-center leading-24 mt-12">
+          暂无数据
+        </View>
+      </View>
+    );
+  };
+
+  return (
+    <View>{renderContent()}</View>
+  );
+}

+ 59 - 130
src/pages/editor-pages/editor-link-contact/index.tsx

@@ -4,56 +4,24 @@ import Taro, { useRouter } from "@tarojs/taro";
 import PageCustom from "@/components/page-custom/index";
 import NavBarNormal from "@/components/nav-bar-normal/index";
 import ButtonCardAdd from "@/components/button-card-add";
-import WemetaTabs from "@/components/wemeta-tabs/index";
-import WemetaRadioGroup from "@/components/wemeta-radio-group/index";
 import ContactCard from "@/components/contact-card/index";
 import editorStyle from "../editor.module.less";
-import { useCharacterStore } from "@/store/characterStore";
-import { useComponentStore } from "@/store/componentStore";
-import { usePageStore } from "@/store/pageStore";
-import { IContactModel } from "@/types";
-import { batchGet } from "@/service/character";
-import { axios, CancelTokenSource } from "taro-axios";
-import { APP_NAME_TEXT } from '@/config'
+import Popup from "@/components/popup/popup";
+import WemetaTabs from "@/components/wemeta-tabs/index";
+import BottomBar from "@/components/BottomBar";
+import { TContactItem } from "@/types/contact";
+import AgentScrollList from './components/AgentScrollList'
 
 export default function Index() {
   const router = useRouter();
   const { id } = router.params;
-  const deltaNum = id ? 1 : 2;
-  let currentComponent = useComponentStore((state) => state.currentComponent);
-  const character = useCharacterStore((state) => state.character);
-  const picked = usePageStore((state) => state.picked);
-  const { setPicked, setTmpPicked, clear } = usePageStore();
-  const { saveComponent } = useComponentStore();
-  const loading = useComponentStore((state) => state.loading);
-  const { value, layout } = currentComponent?.data ?? {};
+  const [picked, setPicked] = useState<TContactItem[]>([]);
   
-  const [radioValue, setRadioValue] = useState(layout ?? "mini");
-  const profileId =
-    currentComponent?.characterProfileId ?? character?.profileId;
-  const radioList = [
-    {
-      text: "缩略宫格",
-      value: "mini",
-      checked: false,
-    },
-    {
-      text: "大图平铺",
-      value: "full",
-      checked: false,
-    },
-  ];
-  const onRadioChange = (value: string) => {
-    setRadioValue(value);
-    return false;
-  };
-
-  const deleteFunc = (item: IContactModel) => {
-    const newList = picked.filter(
-      (_item) => _item.contactProfileId !== item.contactProfileId
-    );
-    setTmpPicked([...newList]);
-    setPicked(newList);
+  // 是否显示选择器
+  const [show, setShow] = useState(false);
+  
+  const deleteFunc = () => {
+    
   };
 
   const renderContactList = () => {
@@ -72,13 +40,13 @@ export default function Index() {
         {picked.map((item) => {
           return (
             <ContactCard
-              key={item.profileId}
+              key={item.contactId}
               data={item}
               renderRight={() => {
                 return (
                   <View
                     className={`iconfont icon-icon_24_delet icon-24`}
-                    onClick={() => deleteFunc(item)}
+                    onClick={() => deleteFunc()}
                   ></View>
                 );
               }}
@@ -89,114 +57,75 @@ export default function Index() {
     );
   };
 
+  
+
+  const handleSubmit = async () => {
+    
+  };
+  
+
+  
   const tabList = [
     {
       key: "1",
-      label: "内容",
-      children: renderContactList(),
+      label: "我创建的",
+      children: (
+        <View >
+          <AgentScrollList></AgentScrollList>
+        </View>
+      ),
     },
     {
       key: "2",
-      label: "版式",
+      label: "我的联系⼈",
       children: (
-        <View>
-          <WemetaRadioGroup
-            radioList={radioList}
-            value={radioValue}
-            onChange={onRadioChange}
-          ></WemetaRadioGroup>
+        <View >
+          
+        </View>
+      ),
+    },
+    {
+      key: "3",
+      label: "企业同事",
+      children: (
+        <View >
+          
         </View>
       ),
     },
   ];
 
-  const handleSave = async () => {
-    const profileIds = [...picked].map((item) => {
-      return item.profileId ?? item.contactProfileId;
-    });
-    if (loading || !profileIds.length) {
-      return;
-    }
-    const c = {
-      data: {
-        value: profileIds,
-        layout: radioValue,
-      },
-      enabled: currentComponent?.enabled ?? true,
-      id: currentComponent?.id,
-      name: currentComponent?.name ?? "联系人",
-      characterProfileId:
-        currentComponent?.characterProfileId ?? character?.profileId,
-      type: "contactList",
-    };
-    console.log(c);
-    await saveComponent(c);
-  };
 
-  const handleNavBack = async () => {
-    await handleSave();
-    clear();
-  };
+  const handleClick = () => {
 
-  
-
-  const nav2ChooseContact = () => {
-    Taro.navigateTo({
-      url: `/pages/choose-contact/index?id=${id}&profileId=${profileId}`,
-    });
-  };
-  
-
-  const fetchList = async (cts: CancelTokenSource) => {
-    const profileIds = currentComponent?.data?.value as string[] | undefined;
-    if (!profileIds) {
-      return;
-    }
-    
-    
-    const res = await batchGet(profileIds, {
-      cancelToken: cts.token
-    });
-    if (res.code === 0) {
-      const picked = res.data.map((item) => {
-        return {
-          ...item,
-          contactProfileId: item.profileId,
-        };
-      });
-      //
-      setTmpPicked([...picked]);
-      setPicked(picked);
-    }
-  };
-  useEffect(() => {
-    let cts = axios.CancelToken.source();
-    if (id) {
-      fetchList(cts);
-    }
-    return ()=> {
-      return cts.cancel('batchGet request canceled')
-    }
-  }, [id]);
+  }
 
   return (
     <PageCustom>
-      <NavBarNormal
-        backText="保存"
-        navDelta={deltaNum}
-        onNavBack={handleNavBack}
-      >
-        {`${APP_NAME_TEXT}用户`}
-      </NavBarNormal>
+      <NavBarNormal>智能体</NavBarNormal>
       <View className="flex flex-col items-center w-full">
         <View className={editorStyle.container}>
           <ButtonCardAdd
-            text={`选择展示的${APP_NAME_TEXT}`}
-            onClick={nav2ChooseContact}
+            text={`选择展示的智能体`}
+            onClick={handleClick}
           ></ButtonCardAdd>
-          <WemetaTabs list={tabList} current="1"></WemetaTabs>
+          {renderContactList()}
         </View>
       </View>
+      <Popup setShow={setShow} show={show} title="知识库-链接">
+        <WemetaTabs current="1" list={tabList}></WemetaTabs>
+        <BottomBar>
+          <View
+            className="button-rounded button-primary flex-1"
+            onClick={handleSubmit}
+          >
+            引用
+          </View>
+        </BottomBar>
+      </Popup>
+      <BottomBar>
+        <View className="button-rounded button-primary flex-1" onClick={handleSubmit}>保存</View>
+      </BottomBar>
     </PageCustom>
   );
 }

+ 0 - 4
src/pages/editor-pages/editor-tel/index.tsx

@@ -3,11 +3,7 @@ import { View } from "@tarojs/components";
 
 import PageCustom from "@/components/page-custom/index";
 import NavBarNormal from "@/components/nav-bar-normal/index";
-import editorStyle from "../editor.module.less";
-import WemetaRadioGroup from "@/components/wemeta-radio-group/index";
 import { useComponentStore } from "@/store/componentStore";
-import { useCharacterStore } from "@/store/characterStore";
-import Taro, { useUnload } from "@tarojs/taro";
 import WemetaTextarea from "@/components/wemeta-textarea/index";
 import BottomBar from "@/components/BottomBar";
 import { EComponentType } from "@/consts/enum";

+ 3 - 1
src/service/agent.ts

@@ -77,8 +77,10 @@ export const postVisitorLog = (data: {
   agentId: string,
   loginId: string,
 }) => {
-  return request.post<TAgentDetail>(`${bluebookAiAgent}api/v1//agent/visitor/log`, data)
+  return request.post<TAgentDetail>(`${bluebookAiAgent}api/v1/agent/visitor/log`, data)
 }
 
 
 
+
+

+ 2 - 6
src/service/contact.ts

@@ -11,7 +11,7 @@ import { TContactItem } from '@/types/contact'
 
 
 // 置顶与取消置顶
-export const setToTop = (data: {
+export const setContactToTop = (data: {
   contactId: number|string,
   isTop: boolean
 }) => {
@@ -32,12 +32,8 @@ export const getContactList = (data: {
     ...data
   }})
 }
-// 
-export const searchContact = (str: string) => {
-  return request.get(`/v1/contact/search`, {params: {s: str}})
-}
 
 // 删除联系人
-export const delContact = (contactId: string) => {
+export const delContact = (contactId: string|number) => {
   return request.delete(`${bluebookAiAgent}api/v1/my/contact/${contactId}`)
 }

+ 7 - 36
src/service/visitor.ts

@@ -3,6 +3,8 @@ import {
 } from '@/xiaolanbenlib/api/index'
 import request from '@/xiaolanbenlib/module/axios.js'
 
+import { TVisitorAgent } from '@/types/visitor'
+
 
 
 // 编辑差评记录的答案消息--编辑后则自动标识为已处理
@@ -114,21 +116,7 @@ export const getVisitorLikeMessages = (data: {
 // 访客数据列表--列表会按我的智能体明细展示
 export type TVisitorListResponse = {
   "data": [
-    {
-      "agentId": string,
-      "agentStatus": string,
-      "avatarUrl": string,
-      "chatRound": number,
-      "dislikeCnt": number,
-      "isEnt": false,
-      "lastChatTime": "2025-05-21T07:09:45.823Z",
-      "myAgentId": string,
-      "myAgentName": string,
-      "name": string,
-      "position": string,
-      "visitTimes": number,
-      "visitorId": number
-    }
+    
   ],
   "nextId": string,
   "totalCount": number
@@ -144,22 +132,7 @@ export const getVisitorList = (data: {
 
 // 访客访问详情--访客聊天记录
 export type TVisitorMessagesResponse = {
-  "data": [
-    {
-      "agentId": string,
-      "visitorId": string,
-      "content": string,
-      "dislikeReason": string,
-      "isDislike": false,
-      "isLike": false,
-      "isStreaming": false,
-      "msgId": number,
-      "msgTime": "2025-05-21T07:09:45.826Z",
-      "msgUk": string,
-      "originalAgentId": string,
-      "role": string
-    }
-  ],
+  "data": TVisitorAgent[],
   "nextId": string,
   "totalCount": number
 }
@@ -199,7 +172,7 @@ export const getVisitorMessagesByMsgId = (data: {
 
 
 // 访问数据汇总
-export type TVisitorSummaryResponse = {
+export type TVisitorSummary = {
   sumChatUv?: number// 总聊天UV ,
   sumPv?: number// 总PV ,
   sumUv?: number// 总UV ,
@@ -209,10 +182,8 @@ export type TVisitorSummaryResponse = {
   unprocessedDislikeCnt?: number// 未处理差评记录数 ,
   unreadLikeCnt?: number// 未查看点赞记录数
 }
-export const getVisitorSummary = (data: {
-  "agentId": string,
-}) => {
-  return request.get<TVisitorSummaryResponse>(`${bluebookAiAgent}api/v1/my/visitor/summary`, {params: data})
+export const getVisitorSummary = (agentId: string) => {
+  return request.get<TVisitorSummary>(`${bluebookAiAgent}api/v1/my/visitor/summary`, {params: {agentId}})
 }
 
 

+ 29 - 0
src/types/visitor.ts

@@ -0,0 +1,29 @@
+// TVisitorAgent 访客详情
+// agentId (string, optional): 
+
+
+
+
+
+
+
+
+
+
+
+
+export type TVisitorAgent = {
+  "agentId": string, // 访客智能体ID -- 如果客户的所有智能体都无效时,则可能为 null ,
+  "agentStatus": string, // agentStatus (string, optional): 访客智能体状态 -- normal 正常/ deleted 已删除 ,
+  "avatarUrl": string, // avatarUrl (string, optional): 访客头像地址 ,
+  "chatRound": number, // chatRound (integer, optional): 会话轮数 ,
+  "dislikeCnt": number,// dislikeCnt (integer, optional): 差评数 ,
+  "isEnt": false, // isEnt (boolean, optional): 是否企业认证联系人 ,
+  "lastChatTime": string,// lastChatTime (string, optional): 最新的对话时间 ,
+  "myAgentId": string, // myAgentId (string, optional): 访问我的智能体ID ,
+  "myAgentName": string,// myAgentName (string, optional): 访问我的智能体名称 ,
+  "name": string, // name (string, optional): 访客名称 -- 最后一次访问的快照信息 ,
+  "position": string,// position (string, optional): 访客职位 ,
+  "visitTimes": number,// visitTimes (integer, optional): 访问访问我的指定智能体次数 ,
+  "visitorId": number// visitorId (integer, optional): 访客ID
+}

+ 3 - 2
src/utils/loadMore.ts

@@ -1,10 +1,11 @@
 import { useEffect, useState } from "react";
 import useSWR from "swr";
-export const useLoadMore = <T>({url, fetcher, startId,  pageSize = 10} : {
+export const useLoadMore = <T>({url, fetcher, startId,  pageSize = 10, params = []} : {
   url: string,
   fetcher: any,
   startId?: number,
   pageSize?: number,
+  params?: any[]
 
 }) => {
   const [page, setPage]  = useState(1)
@@ -13,7 +14,7 @@ export const useLoadMore = <T>({url, fetcher, startId,  pageSize = 10} : {
   // 使用 useSWR
   const { data, error, isLoading, mutate } = useSWR<T>(
     // key 数组,当这些值变化时会重新请求
-    [url, nextId, page, pageSize],
+    [url, nextId, page, pageSize, ...params],
     fetcher
   )