瀏覽代碼

Merge branch 'master' into cpq-dev

chenlei1231 1 月之前
父節點
當前提交
04b696b440

二進制
src/assets/onlineChat/facebook-icon.png


二進制
src/assets/onlineChat/whatsapp-icon.png


+ 512 - 0
src/views/adweb/onlineChat/index.vue

@@ -0,0 +1,512 @@
+<template>
+  <div class="chat-container">
+    <div class="header">
+      <div class="toggle-buttons">
+        <button 
+          :class="['toggle-btn', { active: selectedPlatform === 'whatsapp' }]"
+          @click="selectPlatform('whatsapp')"
+        >
+          <img src="@/assets/onlineChat/whatsapp-icon.png" alt="">
+          WhatsApp
+        </button>
+        <button 
+          :class="['toggle-btn', { active: selectedPlatform === 'facebook' }]"
+          @click="selectPlatform('facebook')"
+        >
+          <img src="@/assets/onlineChat/facebook-icon.png" alt="">
+          Facebook
+        </button>
+      </div>
+    
+    </div>
+    
+    <!-- 主体内容区 -->
+    <div class="main-content">
+      <!-- 左侧面板 -->
+      <div class="left-panel">
+        <!-- 聊天列表 -->
+        <div class="chat-list">
+          <div 
+            v-for="chat in filteredChats" 
+            :key="chat.id"
+            :class="['chat-item', { active: selectedChatId === chat.id }]"
+            @click="selectChat(chat.id)"
+          >
+            <a-avatar :src="chat.avatar" />
+            <div class="chat-info">
+              <div class="chat-header">
+                <span class="name">{{ chat.name }}</span>
+                <span class="time">{{ chat.lastMessageTime }}</span>
+              </div>
+              <div class="last-message">{{ chat.lastMessage }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 右侧对话详情 -->
+      <div class="chat-detail" v-if="selectedChatId">
+        <!-- 对话头部信息 -->
+        <div class="chat-header">
+          <div class="chat-user-info">
+            <a-avatar :src="currentChat.avatar" />
+            <span class="user-name">{{ currentChat.name }}</span>
+          </div>
+        </div>
+
+        <!-- 聊天记录区域 -->
+        <div class="chat-messages">
+          <div 
+            v-for="message in currentChatMessages" 
+            :key="message.id"
+            :class="['message', message.type]"
+          >
+            <a-avatar :src="message.avatar" />
+            <div class="message-content">
+              <div class="message-info">
+                <span class="sender">{{ message.sender }}</span>
+                <span class="time">{{ message.time }}</span>
+              </div>
+              <div class="message-text">{{ message.content }}</div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 回复输入框 -->
+        <div class="chat-input">
+          <a-textarea
+            v-model:value="replyMessage"
+            placeholder="请输入消息..."
+            :auto-size="{ minRows: 2, maxRows: 5 }"
+          />
+          <a-button type="primary" @click="sendMessage">发送</a-button>
+        </div>
+      </div>
+      
+      <!-- 未选择对话时的提示 -->
+      <div class="no-chat-selected" v-else>
+        <MessageOutlined style="font-size: 48px" />
+        <p>快来选择联系人进入会话吧</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref, computed } from 'vue'
+import { 
+  DownOutlined,
+  SearchOutlined,
+  MessageOutlined
+} from '@ant-design/icons-vue'
+
+export default {
+  components: {
+    DownOutlined,
+    SearchOutlined,
+    MessageOutlined
+  },
+  data() {
+    return {
+      selectedPlatform: 'whatsapp', // 默认选中WhatsApp
+      showAccountDropdown: false,
+      currentAccount: {
+        id: 1,
+        avatar: '@/assets/onlineChat/whatsapp-icon.png',
+        name: 'Adweb AI'
+      },
+      accounts: [
+        {
+          id: 1,
+          avatar: '@/assets/onlineChat/whatsapp-icon.png',
+          name: 'Adweb AI'
+        },
+        {
+          id: 2,
+          avatar: '@/assets/onlineChat/whatsapp-icon.png',
+          name: 'Adweb AI 2'
+        }
+      ],
+      searchKeyword: '',
+      currentTab: 'all',
+      selectedChatId: null,
+      chats: [
+        {
+          id: 1,
+          name: 'John Doe',
+          avatar: '/path/to/avatar1.jpg',
+          lastMessage: 'Hello, how are you?',
+          lastMessageTime: '10:30'
+        },
+        {
+          id: 2,
+          name: 'Jane Smith',
+          avatar: '/path/to/avatar2.jpg',
+          lastMessage: 'See you tomorrow!',
+          lastMessageTime: '09:15'
+        }
+      ],
+      messages: {
+        1: [
+          {
+            id: 1,
+            type: 'received',
+            sender: 'John Doe',
+            avatar: '/path/to/avatar1.jpg',
+            content: 'Hello, how are you?',
+            time: '10:30'
+          },
+          {
+            id: 2,
+            type: 'sent',
+            sender: 'Me',
+            avatar: '/path/to/my-avatar.jpg',
+            content: 'I\'m good, thanks!',
+            time: '10:31'
+          }
+        ],
+        2: [
+          // messages for chat 2
+        ]
+      },
+      replyMessage: '',
+    }
+  },
+  methods: {
+    selectPlatform(platform) {
+      this.selectedPlatform = platform
+    },
+    selectChat(chatId) {
+      this.selectedChatId = chatId
+    },
+    switchAccount(account) {
+      this.currentAccount = account;
+      this.showAccountDropdown = false;
+    },
+    sendMessage() {
+      if (!this.replyMessage.trim()) return
+      
+      const newMessage = {
+        id: Date.now(),
+        type: 'sent',
+        sender: 'Me',
+        avatar: this.currentAccount.avatar,
+        content: this.replyMessage,
+        time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
+      }
+      
+      if (!this.messages[this.selectedChatId]) {
+        this.messages[this.selectedChatId] = []
+      }
+      this.messages[this.selectedChatId].push(newMessage)
+      this.replyMessage = ''
+    }
+  },
+  computed: {
+    filteredChats() {
+      return this.chats.filter(chat => 
+        chat.name.toLowerCase().includes(this.searchKeyword.toLowerCase())
+      )
+    },
+    currentChatMessages() {
+      return this.messages[this.selectedChatId] || []
+    },
+    currentChat() {
+      return this.chats.find(chat => chat.id === this.selectedChatId) || {}
+    }
+  }
+}
+</script>
+
+<style scoped>
+.chat-container {
+  padding: 20px;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.toggle-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.toggle-btn {
+  padding: 8px 16px;
+  border: 1px solid #dcdfe6;
+  background-color: #fff;
+  border-radius: 20px;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.toggle-btn:hover {
+  background-color: #f5f7fa;
+}
+
+.toggle-btn.active {
+  background-color: #409eff;
+  color: #fff;
+  border-color: #409eff;
+}
+
+/* 图标样式 */
+.icon-whatsapp,
+.icon-whatsapp-business,
+.icon-facebook {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  background-size: contain;
+  background-repeat: no-repeat;
+}
+
+.icon-whatsapp {
+  background-image: url('~@/assets/icons/whatsapp.svg');
+}
+
+.icon-whatsapp-business {
+  background-image: url('~@/assets/icons/whatsapp-business.svg');
+}
+
+.icon-facebook {
+  background-image: url('~@/assets/icons/facebook.svg');
+}
+
+.social-management-btn {
+  padding: 8px 16px;
+  border: 1px solid #dcdfe6;
+  background-color: #fff;
+  border-radius: 20px;
+  cursor: pointer;
+  transition: all 0.3s;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.social-management-btn:hover {
+  background-color: #f5f7fa;
+}
+
+.main-content {
+  display: flex;
+  height: calc(100vh - 220px);
+  margin-top: 20px;
+  background: #fff;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+}
+
+.left-panel {
+  width: 300px;
+  border-right: 1px solid #e4e7ed;
+  display: flex;
+  flex-direction: column;
+}
+
+.current-account {
+  padding: 16px;
+  border-bottom: 1px solid #e4e7ed;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+  position: relative;
+}
+
+.current-account .avatar {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+}
+
+.chat-list {
+  flex: 1;
+  overflow-y: auto;
+}
+
+.chat-item {
+  display: flex;
+  padding: 12px 16px;
+  cursor: pointer;
+  transition: background-color 0.3s;
+}
+
+.chat-item:hover {
+  background-color: #f5f7fa;
+}
+
+.chat-item.active {
+  background-color: #ecf5ff;
+}
+
+.chat-info {
+  margin-left: 12px;
+  flex: 1;
+}
+
+.chat-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 4px;
+}
+
+.chat-header .time {
+  font-size: 12px;
+  color: #909399;
+}
+
+.last-message {
+  font-size: 13px;
+  color: #606266;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.chat-detail {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.chat-header {
+  padding: 16px 20px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.chat-user-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.user-name {
+  font-size: 16px;
+  font-weight: 500;
+}
+
+.chat-messages {
+  flex: 1;
+  padding: 20px;
+  overflow-y: auto;
+}
+
+.chat-input {
+  padding: 16px 20px;
+  border-top: 1px solid #e4e7ed;
+  display: flex;
+  gap: 12px;
+  align-items: flex-end;
+}
+
+.chat-input .ant-textarea-wrapper {
+  flex: 1;
+}
+
+.chat-input .ant-btn {
+  height: 40px;
+  padding: 0 24px;
+}
+
+.message {
+  display: flex;
+  margin-bottom: 20px;
+}
+
+.message.sent {
+  flex-direction: row-reverse;
+}
+
+.message-content {
+  margin: 0 12px;
+  max-width: 60%;
+}
+
+.message.sent .message-content {
+  text-align: right;
+}
+
+.message-info {
+  margin-bottom: 4px;
+}
+
+.message-text {
+  padding: 12px;
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+
+.message.sent .message-text {
+  background: #409eff;
+  color: white;
+}
+
+.no-chat-selected {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+}
+
+.no-chat-selected .anticon {
+  font-size: 48px;
+  margin-bottom: 16px;
+  color: #bfbfbf;
+}
+
+/* 调整一些样式以匹配 Ant Design 风格 */
+.search-box .ant-input-affix-wrapper {
+  border-radius: 4px;
+}
+
+.chat-item .ant-avatar {
+  width: 40px;
+  height: 40px;
+}
+
+.dropdown-icon {
+  font-size: 12px;
+  color: #909399;
+  transition: transform 0.3s;
+}
+
+.dropdown-icon.is-open {
+  transform: rotate(180deg);
+}
+
+.account-dropdown {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  background: #fff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  z-index: 10;
+}
+
+.account-option {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  cursor: pointer;
+}
+
+.account-option:hover {
+  background-color: #f5f7fa;
+}
+</style>

+ 14 - 0
src/views/adweb/site/AdwebSite.api.ts

@@ -19,6 +19,8 @@ enum Api {
   gtmListUrl = 'gtm/add/list',
   getGoogleAdsAccount = '/marketing/googleads/account/get',
   bindGoogleAdsAccount = '/marketing/googleads/account/add', // 添加绑定接口地址
+  getFaceBookAccount = '/marketing/facebook/account/get',
+  bindFaceBookAccount = '/marketing/facebook/account/add',
 }
 
 /**
@@ -93,6 +95,18 @@ export const getGoogleAdsAccount = (params) => defHttp.get({ url: Api.getGoogleA
  */
 export const bindGoogleAdsAccount = (params) => defHttp.post({ url: Api.bindGoogleAdsAccount, params }, { joinParamsToUrl: true });
 
+/**
+ * 获取Google Ads账号信息
+ * @param params
+ */
+export const getFaceBookAccount = (params) => defHttp.get({ url: Api.getFaceBookAccount, params });
+
+/**
+ * 绑定Google Ads账号
+ * @param params
+ */
+export const bindFaceBookAccount = (params) => defHttp.post({ url: Api.bindFaceBookAccount, params }, { joinParamsToUrl: true });
+
 export const isSohoeb2b = async (params) => {
   const result = await getTenantById({ id: getAuthCache(TENANT_ID) });
 

+ 11 - 0
src/views/adweb/site/AdwebSiteList.vue

@@ -165,6 +165,7 @@
     <!--SEO流程-->
     <seo-process ref="seoProcessRef" :visible="seoProcessVisible" :title="processTitle" @close="closeProcess" @reload="reload" />
     <google-ads-modal ref="googleAdsModalRef" @success="reload" />
+    <facebook-modal ref="faceBookModalRef" @success="reload"></facebook-modal>
     <Sohoeb2bOrder ref="sohoeb2bOrderRef" @reload="reload" />
     <SiteRelease ref="siteReleaseRef" @reload="reload" />
   </div>
@@ -186,6 +187,7 @@
   import { useMessage } from '@/hooks/web/useMessage';
   import SiteSetEnquiry from '@/views/adweb/site/components/SiteSetEnquiry.vue';
   import GoogleAdsModal from './components/GoogleAdsModal.vue';
+  import FacebookModal from './components/FaceBookModal.vue';
   import { getAuthCache } from '@/utils/auth';
   import { TENANT_ID } from '@/enums/cacheEnum';
   import { getTenantById } from '@/views/system/tenant/tenant.api';
@@ -205,6 +207,7 @@
   const gtmRef = ref();
   const siteSetEnquiryRef = ref();
   const googleAdsModalRef = ref();
+  const faceBookModalRef = ref();
 
   // admin 判断和后端保持一致
   // src/main/java/org/jeecg/modules/adweb/system/service/impl/SysAdwebApiImpl.java L60
@@ -421,6 +424,10 @@
         {
           label: 'Google Ads',
           onClick: bindGoogleAds.bind(null, record),
+        },
+        {
+          label: 'FaceBook绑定',
+          onClick: addFaceBook.bind(null, record),
         }
       );
     }
@@ -497,6 +504,10 @@
   function bindGoogleAds(record) {
     googleAdsModalRef.value.init({ record });
   }
+
+  function addFaceBook(record) {
+    faceBookModalRef.value.init({ record });
+  }
 </script>
 
 <style lang="less" scoped>

+ 101 - 0
src/views/adweb/site/components/FacebookModal.vue

@@ -0,0 +1,101 @@
+<template>
+  <BasicDrawer v-bind="$attrs" @register="registerDrawer" title="绑定FaceBook帐号" @ok="handleSubmit" width="850" showFooter>
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { getFaceBookAccount, bindFaceBookAccount } from '../AdwebSite.api';
+
+  const emit = defineEmits(['success', 'register']);
+  const { createMessage } = useMessage();
+
+  const formSchema = [
+    {
+      field: 'FaceBookAccount',
+      label: 'FaceBook帐号',
+      component: 'Input',
+      required: true,
+    },
+    {
+      field: 'apiRefreshToken',
+      label: 'API Refresh Token',
+      component: 'Input',
+    },
+    {
+      field: 'siteCode',
+      label: '站点代码',
+      component: 'Input',
+      required: true,
+      show: false,
+    },
+  ];
+
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    labelWidth: 120,
+    schemas: formSchema,
+    showActionButtonGroup: false,
+  });
+
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner();
+
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      const params: Record<string, any> = {
+        customerId: values.FaceBookAccount,
+        siteCode: values.siteCode,
+      };
+      if (values.apiRefreshToken) {
+        params.refreshToken = values.apiRefreshToken;
+      }
+      await bindFaceBookAccount(params);
+      createMessage.success('FaceBook账号绑定成功');
+      emit('success');
+      closeDrawer();
+    } catch (error) {
+      createMessage.error('绑定FaceBook账号失败');
+      console.error(error);
+    }
+  }
+
+  // Add this method to handle initialization
+  const init = async (data: { record: Recordable }) => {
+    resetFields();
+    setDrawerProps({ visible: true, confirmLoading: true });
+
+    try {
+      // 将 siteCode 作为路径参数传递
+      const params = {
+        siteCode: data.record.code,
+      };
+      const res = await getFaceBookAccount(params);
+      if (res) {
+        console.log(data.record.code)
+        setFieldsValue({
+          FaceBookAccount: res.customerId,
+          apiRefreshToken: res.refreshToken,
+          siteCode: data.record.code,
+        });
+      }else {
+        setFieldsValue({
+          siteCode: data.record.code
+        });
+      }
+    } catch (error) {
+      createMessage.error('获取FaceBook账号信息失败');
+      console.error(error);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  };
+
+  // Expose the init method
+  defineExpose({
+    init,
+  });
+</script>