Prechádzať zdrojové kódy

今日工作12.11 接口联调 1,获取OTC商家支付方式 2,C2C购买接入WebSocket,实现发送图片和文本,已读,历史记录 3,商家列表 4,OTC下单 当前OTC购买流程跑通 新增页面 1,我的理财-无数据时 2,认购-下单确认 3,配售-下单确认 4,认缴金额

jhaoG 3 týždňov pred
rodič
commit
bdb340f591

BIN
dist.zip


+ 34 - 0
src/api/otc.js

@@ -25,3 +25,37 @@ export function GetPayList(id) {
     method: "get",
   });
 }
+
+// 获取OTC下单
+export function BuyCoin(data) {
+  return request({
+    url: `/otc/otc_order/buy_coin/`,
+    method: "post",
+    data,
+  });
+}
+
+// 获取聊天id
+export function GetChatId(params) {
+  return request({
+    url: `/otc/chat/get_chat_id/`,
+    method: "get",
+    params,
+  });
+}
+
+// 获取商家消息列表
+export function GetOTCChat() {
+  return request({
+    url: "/otc/chat/",
+    method: "get",
+  });
+}
+
+// 获取商家消息详情
+export function GetOTCChatDetails(id) {
+  return request({
+    url: `/otc/chat/${id}/get_chat_details/?pageSize=999`,
+    method: "get",
+  });
+}

BIN
src/assets/icon/asset/send.png


BIN
src/assets/img/asset/head-img.png


BIN
src/assets/img/index/Cryptocurrency_hedge_funds_2_.png


+ 16 - 14
src/main.js

@@ -1,22 +1,18 @@
 import { createApp } from "vue";
 import App from "./App.vue";
-import 'amfe-flexible';
+import "amfe-flexible";
 import router from "./router";
-import 'vant/lib/index.css';
+import "vant/lib/index.css";
 import "./assets/less/index.less";
 import "./assets/h5-reset.css";
 
 // 引入 i18n(确保 locales/index.js 导出默认 i18n 实例和 loadLanguageAsync)
-import i18n, { loadLanguageAsync } from './locales/index.js';
+import i18n, { loadLanguageAsync } from "./locales/index.js";
 
-import {
-  Button, Popup, Form, Field, NavBar, Picker, Icon, Toast
-} from 'vant';
-
-import api from "./utils/api.js";
+import { Button, Popup, Form, Field, NavBar, Picker, Icon, Toast } from "vant";
 
 // ✅ 改为 sessionStorage
-const savedLang = sessionStorage.getItem('app-lang') || 'zh-CN';
+const savedLang = sessionStorage.getItem("app-lang") || "zh-CN";
 // console.log('🚀 [main.js] 启动语言:', savedLang);
 
 async function bootstrap() {
@@ -30,7 +26,7 @@ async function bootstrap() {
     // try { await loadLanguageAsync('zh-CN'); } catch (e) { console.error('❌ 回退中文也失败', e); }
   }
 
-  console.log('🚀 [main.js] 语言准备就绪,挂载 App...');
+  console.log("🚀 [main.js] 语言准备就绪,挂载 App...");
   const app = createApp(App);
 
   // 先挂载 i18n,确保组件内 t() 能拿到消息
@@ -41,14 +37,20 @@ async function bootstrap() {
   }
 
   // 注册 Vant 组件
-  app.use(Button).use(Popup).use(Form).use(Field).use(NavBar).use(Picker).use(Icon).use(Toast);
+  app
+    .use(Button)
+    .use(Popup)
+    .use(Form)
+    .use(Field)
+    .use(NavBar)
+    .use(Picker)
+    .use(Icon)
+    .use(Toast);
 
   // 再挂载路由和其它
   app.use(router);
-  app.config.globalProperties.$api = api;
-
   app.mount("#app");
-  console.log('✅ [main.js] App 挂载成功');
+  console.log("✅ [main.js] App 挂载成功");
 }
 
 bootstrap();

+ 6 - 1
src/router/index.js

@@ -57,7 +57,7 @@ import OTCMerchantDetails from "@/views/asset/otc/user/MerchantDetails.vue";
 import MyFollow from "@/views/asset/otc/user/MyFollow.vue";
 import LevelLimit from "@/views/asset/otc/user/LevelLimit.vue";
 import TransactionGuide from "@/views/asset/otc/user/TransactionGuide.vue";
-
+import OTCCustomService from "@/views/asset/otc/message/CustomerService.vue";
 import CommonFunctionsPopup from "@/views/bitcoin/CommonFunctionsPopup/CommonFunctionsPopup.vue"; // 子组件路径
 import TradeRules from "@/views/bitcoin/CommonFunctionsPopup/GeneralLevel2/TradeRules.vue"; // 新建
 import TradeLayout from "@/views/bitcoin/TradeLayout.vue"; // 新建的公共父组件
@@ -607,6 +607,11 @@ const routes = [
     name: "transactionGuide",
     component: TransactionGuide,
   },
+  {
+    path: "/OTCCustomService/:chatId/:otcId",
+    name: "OTCCustomService",
+    component: OTCCustomService,
+  },
 ];
 
 const router = createRouter({

+ 0 - 6
src/utils/api.js

@@ -1,6 +0,0 @@
-import user from '../api/user'
-
-// 接口文件
-export default {
-  ...user,
-};

+ 45 - 12
src/views/asset/dialog/C2CBuy.vue

@@ -33,6 +33,7 @@
       <div class="number-input">
         <input
           type="text"
+          v-model="buyCount"
           class="input pf400 fs14 fc333333"
           placeholder="请输入购买数量" />
         <div class="all pf400 fs14 fc333333">USDT <span class="fcDF384C">全部</span></div>
@@ -42,40 +43,72 @@
         <div>付款方式</div>
         <div class="account-right">
           <div class="color"></div>
-          银行卡
+          <span v-if="payList.type == 1">银行卡</span>
+          <span v-if="payList.type == 2">支付宝</span>
+          <span v-if="payList.type == 3">微信</span>
         </div>
       </div>
       <div class="account pf500 fs14 fc999999">
         <div>付款账号</div>
         <div class="account-right fcDF384C" @click="goAddBankAccount">
-          请选择
+          {{ payList.card_no }}
           <img src="@/assets/icon/asset/right-arrow-black.svg" alt="" />
         </div>
       </div>
-      <div class="sure-btn pf600 fs14 fcFFFFFF" @click="goAddzfbAccount">确定购买</div>
+      <div class="sure-btn pf600 fs14 fcFFFFFF" @click="sureBuy">确定购买</div>
     </div>
   </div>
 </template>
 <script setup>
   import { useRoute, useRouter } from "vue-router";
-  import { GetPayList } from "@/api/otc";
-  import { watch } from "vue";
+  import { GetPayList, BuyCoin } from "@/api/otc";
+  import { watch, ref } from "vue";
 
   const router = useRouter();
+  const props = defineProps(["otcId", "chatId", "orderId"]);
+  const emits = defineEmits(["sellAndBuyClose"]);
 
-  const props = defineProps(["OTCId"]);
+  const payList = ref({});
+  const otcId = ref();
+  const chatId = ref();
+  const orderId = ref();
+  const buyCount = ref(0);
 
   watch(
-    () => props.OTCId,
+    () => props.otcId,
     async (newVal, oldVal) => {
-      const data1 = await GetPayList(newVal);
-      console.log(12, data1);
+      otcId.value = newVal;
+      console.log(otcId.value);
+    }
+  );
+
+  watch(
+    () => props.orderId,
+    async (newVal, oldVal) => {
+      orderId.value = newVal;
     }
   );
-  const emits = defineEmits(["sellAndBuyClose"]);
 
-  const goAddzfbAccount = () => {
-    router.push("/addzfbAccount");
+  watch(
+    () => props.chatId,
+    async (newVal, oldVal) => {
+      chatId.value = newVal;
+      console.log(chatId.value);
+    }
+  );
+
+  const sureBuy = async () => {
+    const params = {
+      pid: orderId.value,
+      count: buyCount.value,
+    };
+    const data = await BuyCoin(params);
+    if (data == "success") {
+      router.push({
+        name: "OTCCustomService",
+        params: { chatId: chatId.value, otcId: otcId.value },
+      });
+    }
   };
 
   const goAddBankAccount = () => {

+ 304 - 0
src/views/asset/otc/message/CustomerService.vue

@@ -0,0 +1,304 @@
+<template>
+  <HeaderNav headerText="商家昵称" headerRouter="/" />
+  <div class="customer-service">
+    <!-- 聊天消息区域 -->
+    <div class="chat-box" ref="chatBoxRef">
+      <div
+        v-for="msg in msgList"
+        :key="msg.id"
+        class="msg-item"
+        :class="{ self: msg.isSelf }">
+        <img class="avatar" :src="msg.isSelf ? userAvatar : serviceAvatar" />
+
+        <div class="msg-content">
+          <!-- 文本消息 -->
+          <div v-if="msg.messageType === 'text'" class="text">
+            {{ msg.text }}
+          </div>
+
+          <!-- 图片消息 -->
+          <img
+            v-else-if="msg.messageType === 'image'"
+            class="img-msg"
+            :src="msg.text"
+            alt="图片消息" />
+
+          <!-- 时间/已读显示 -->
+          <div class="time">
+            <!-- 最后一条我的消息 已读 -->
+            <template v-if="msg.isSelf && msg.read && msg.id === lastSelfMsgId">
+              已读
+            </template>
+
+            <!-- 其他显示时间 -->
+            <template v-else>
+              {{ formatTime(msg.time) }}
+            </template>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部输入栏 -->
+    <div class="bottom-bar">
+      <input
+        v-model="inputValue"
+        type="text"
+        placeholder="请输入内容..."
+        class="input pf500 fs14" />
+      <img
+        src="@/assets/icon/asset/send.png"
+        class="send-btn"
+        @click="sendMessage"
+        alt="" />
+    </div>
+  </div>
+</template>
+<script setup>
+  import { ref, onMounted, nextTick, onUnmounted, computed } from "vue";
+  import { useRoute } from "vue-router";
+  import { GetOTCChatDetails } from "@/api/otc";
+  import HeaderNav from "@/views/index/components/HeaderNav.vue";
+
+  const route = useRoute();
+
+  // 用户头像(自己)
+  const userAvatar = require("@/assets/img/asset/head-img.png");
+  // 商家头像(对方)
+  const serviceAvatar = require("@/assets/img/index/user/default-head.png");
+
+  let ws = null;
+  const msgList = ref([]);
+  const inputValue = ref("");
+
+  const chatBoxRef = ref(null);
+
+  // 自动滚动到底部
+  function scrollToBottom() {
+    nextTick(() => {
+      if (chatBoxRef.value) {
+        chatBoxRef.value.scrollTop = chatBoxRef.value.scrollHeight;
+      }
+    });
+  }
+
+  // 格式化时间
+  function formatTime(t) {
+    if (!t) return "";
+    const d = new Date(t);
+    return `${d.getHours().toString().padStart(2, "0")}:${d
+      .getMinutes()
+      .toString()
+      .padStart(2, "0")}`;
+  }
+
+  // 最后一条自己的消息 ID(用于显示“已读”)
+  const lastSelfMsgId = computed(() => {
+    const selfMsgs = msgList.value.filter((m) => m.isSelf);
+    return selfMsgs.length ? selfMsgs[selfMsgs.length - 1].id : null;
+  });
+
+  async function loadHistory() {
+    const res = await GetOTCChatDetails(route.params.chatId);
+    msgList.value = res.list.map((item) => ({
+      id: item.id,
+      text: item.context,
+      time: item.update_time,
+      read: item.is_read,
+      isSelf: !item.is_otc, // true=右侧(自己),false=左侧(商家)
+      messageType: "text",
+    }));
+
+    scrollToBottom();
+  }
+
+  /* ==========================================================
+    ② 发送消息
+   ========================================================== */
+  function sendMessage() {
+    if (!inputValue.value.trim()) return;
+
+    const msg = {
+      id: Date.now(),
+      otc: route.params.otcId,
+      text: inputValue.value,
+      time: Date.now(),
+      isSelf: true,
+      type: "chat",
+      messageType: "text",
+      read: false,
+    };
+
+    msgList.value.push(msg);
+
+    // WebSocket 发送
+    if (ws && ws.readyState === WebSocket.OPEN) {
+      ws.send(JSON.stringify(msg));
+    }
+
+    inputValue.value = "";
+    scrollToBottom();
+  }
+
+  /* ==========================================================
+    ③ WebSocket 处理
+   ========================================================== */
+  const initWebSocket = () => {
+    const token = localStorage.getItem("token");
+    ws = new WebSocket(`ws://63.141.230.43:57676/ws/custom_chat/?token=${token}`);
+
+    ws.onopen = () => {
+      console.log("WebSocket 已连接");
+    };
+
+    ws.onmessage = (e) => {
+      let data = {};
+      try {
+        data = JSON.parse(e.data);
+      } catch {
+        return;
+      }
+
+      console.log("收到数据:", data);
+
+      /* --- 已读处理 --- */
+      if (data.message?.type === "read") {
+        const lastSelf = [...msgList.value].reverse().find((m) => m.isSelf && !m.read);
+        if (lastSelf) lastSelf.read = true;
+        return;
+      }
+
+      /* --- 普通消息 --- */
+      const text = data.message?.text || "";
+      const isImage = /^https?:\/\/.+\.(png|jpg|jpeg|gif|webp)(\?.*)?$/i.test(text);
+
+      msgList.value.push({
+        id: Date.now(),
+        text,
+        time: Date.now(),
+        isSelf: false,
+        messageType: isImage ? "image" : "text",
+        read: false,
+      });
+
+      scrollToBottom();
+    };
+
+    ws.onerror = () => {
+      console.error("WebSocket 错误");
+    };
+
+    ws.onclose = () => {
+      console.log("WebSocket 已关闭");
+    };
+  };
+
+  onMounted(() => {
+    loadHistory(); // ← 添加历史记录加载
+    initWebSocket(); // ← WebSocket 初始化
+  });
+
+  onUnmounted(() => {
+    ws && ws.close();
+  });
+</script>
+
+<style lang="less" scoped>
+  .customer-service {
+    display: flex;
+    flex-direction: column;
+    padding-top: 50px;
+    width: 375px;
+    height: 100vh;
+    box-sizing: border-box;
+  }
+
+  /* 聊天内容区域 */
+  .chat-box {
+    flex: 1;
+    overflow-y: auto;
+    padding: 12px;
+    background: #f5f5f5;
+  }
+
+  .msg-item {
+    display: flex;
+    margin-bottom: 12px;
+
+    &.self {
+      flex-direction: row-reverse;
+
+      .msg-content {
+        align-items: flex-end;
+      }
+
+      .text {
+        background: #4dabf7;
+        color: #fff;
+      }
+    }
+  }
+
+  .avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+  }
+
+  .msg-content {
+    display: flex;
+    flex-direction: column;
+    margin: 0 10px;
+
+    .text {
+      max-width: 70vw;
+      background: #ffffff;
+      padding: 8px 12px;
+      border-radius: 8px;
+      line-height: 1.5;
+      font-size: 14px;
+    }
+
+    .img-msg {
+      max-width: 180px;
+      max-height: 400px;
+      display: block;
+      border-radius: 10px;
+    }
+
+    .time {
+      margin-top: 4px;
+      font-size: 12px;
+      color: #999;
+    }
+  }
+
+  /* 底部栏 */
+  .bottom-bar {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    padding-left: 16px;
+    padding-right: 16px;
+    border-top: 1px solid #ddd;
+    height: 70px;
+    box-sizing: border-box;
+
+    .input {
+      padding-left: 12px;
+      width: 283px;
+      height: 48px;
+      border: 1px solid #ddd;
+      border-radius: 40px;
+      outline: none;
+      box-sizing: border-box;
+    }
+
+    .send-btn {
+      margin-left: 12px;
+      width: 48px;
+      height: 48px;
+    }
+  }
+</style>

+ 23 - 3
src/views/asset/otc/message/Index.vue

@@ -12,20 +12,40 @@
       </div>
       <div class="service-right pf500 fs12 fc999999">11/07 22:55:50</div>
     </div>
-    <div class="merchant-service margin-top17" v-for="(item, index) in 3" :key="index">
+    <div
+      class="merchant-service margin-top17"
+      v-for="(item, index) in OTCChatData"
+      :key="index">
       <div class="service-left">
         <img src="@/assets/img/asset/Avatar.svg" class="platform-head" alt="" />
         <div class="left-info">
-          <div class="info-name pf500 fs18 fc061237">商家昵称</div>
+          <div class="info-name pf500 fs18 fc061237">{{ item.otc_name }}</div>
           <div class="info-message pf500 fs12 fc999999">消息内容文本显示</div>
         </div>
       </div>
-      <div class="service-right pf500 fs12 fc999999">11/07 22:55:50</div>
+      <div class="service-right pf500 fs12 fc999999">{{ item.update_time }}</div>
     </div>
   </div>
 </template>
 <script setup>
   import HeaderNav from "../../../index/components/HeaderNav.vue";
+  import { GetOTCChat, GetOTCChatDetails } from "@/api/otc";
+  import { ref, onMounted } from "vue";
+
+  const OTCChatData = ref();
+
+  const getOTCChatData = async () => {
+    const data = await GetOTCChat();
+    OTCChatData.value = data.list;
+    console.log(OTCChatData.value);
+
+    const data1 = await GetOTCChatDetails(OTCChatData.value[0].otc);
+    console.log(2, data1);
+  };
+
+  onMounted(async () => {
+    await getOTCChatData();
+  });
 </script>
 <style lang="less" scoped>
   .message-index {

+ 48 - 11
src/views/asset/otc/transaction/C2C.vue

@@ -75,13 +75,19 @@
           <div class="func-pay-way pf400 fs10 fc999999">
             <div class="color"></div>
             <!-- 1.银行卡 2.支付宝 3.微信 -->
-            <div v-if="item.pay_type == 1">银行卡</div>
-            <div v-if="item.pay_type == 2">支付宝</div>
-            <div v-if="item.pay_type == 3">微信</div>
+            <div v-if="item.status == 1">银行卡</div>
+            <div v-if="item.status == 2">支付宝</div>
+            <div v-if="item.status == 3">微信</div>
           </div>
           <div class="func-main">
-            <div class="func-chat pf500 fs12 fcDF384C">聊天</div>
-            <div class="func-buy pf500 fs12 fcFFFFFF" @click="OTCBuy(item.otc)">购买</div>
+            <div
+              class="func-chat pf500 fs12 fcDF384C"
+              @click="getChatIdData(item.id, item.otc)">
+              聊天
+            </div>
+            <div class="func-buy pf500 fs12 fcFFFFFF" @click="OTCBuy(item.id, item.otc)">
+              购买
+            </div>
           </div>
         </div>
       </div>
@@ -91,7 +97,9 @@
     <Filter v-show="filterFlag" @filterClose="filterClose"></Filter>
     <C2CBuy
       v-show="sellAndBuyFlag"
-      :OTCId="OTCId"
+      :otcId="otcId"
+      :chatId="chatId"
+      :orderId="orderId"
       @sellAndBuyClose="sellAndBuyClose"></C2CBuy>
     <!-- 暂无收款方式,还没点击的地方 -->
     <NotPaymentWay
@@ -114,7 +122,7 @@
 <script setup>
   import { ref, onMounted } from "vue";
   import { useRoute, useRouter } from "vue-router";
-  import { GetOTCPendingOrder } from "@/api/otc";
+  import { GetOTCPendingOrder, GetChatId } from "@/api/otc";
   import PaymentWay from "../../dialog/NotPaymentWay.vue";
   import Amount from "../../dialog/Amount.vue";
   import Filter from "../../dialog/Filter.vue";
@@ -127,11 +135,40 @@
 
   const router = useRouter();
 
-  const OTCId = ref();
+  const otcId = ref();
+  const chatId = ref();
+  const orderId = ref();
 
-  const OTCBuy = (otcId) => {
-    OTCId.value = otcId;
-    sellAndBuyFlag.value = true;
+  // item.id 6, item.otc 1
+  const OTCBuy = async (id, otc) => {
+    otcId.value = otc;
+    orderId.value = id;
+
+    const params = {
+      otc_id: otc,
+    };
+    const data = await GetChatId(params);
+    if (data) {
+      chatId.value = data;
+      sellAndBuyFlag.value = true;
+    }
+  };
+
+  const getChatIdData = async (id, otc) => {
+    otcId.value = id;
+
+    const params = {
+      otc_id: otc,
+    };
+    const data = await GetChatId(params);
+    if (data) {
+      chatId.value = data;
+
+      router.push({
+        name: "OTCCustomService",
+        params: { chatId: chatId.value, otcId: otc },
+      });
+    }
   };
 
   const goMerchantDetails = () => {

+ 96 - 0
src/views/index/dialog/PlacementOrder.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="place-order">
+    <div class="apply-mask" @click="emits('placementOrderClose')"></div>
+    <div class="apply-content">
+      <div class="slide-line"></div>
+      <div class="apply-text pf600 fs18 fc121212">下单确认</div>
+      <div class="coin pf500 fs16 fc121212">BTCION</div>
+      <div class="price">
+        <div class="pf500 fs14 fc666666">单价</div>
+        <div class="pf500 fs12 fc666666">1000.05 USDT</div>
+      </div>
+      <div class="sure-btn pf600 fs14 fcFFFFFF">配售</div>
+    </div>
+  </div>
+</template>
+<script setup>
+  const emits = defineEmits(["placementOrderClose"]);
+</script>
+<style lang="less" scoped>
+  .place-order {
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    width: 100%;
+    min-height: 100vh;
+
+    .apply-mask {
+      position: fixed;
+      left: 0;
+      top: 0;
+      z-index: -1;
+      width: 100%;
+      min-height: 100vh;
+      background: rgba(0, 0, 0, 0.5);
+    }
+
+    .apply-content {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      width: 375px;
+      height: 200px;
+      background: #ffffff;
+      border-radius: 24px 24px 0px 0px;
+
+      .slide-line {
+        margin-top: 12px;
+        width: 32px;
+        height: 4px;
+        border-radius: 2px;
+        opacity: 0.4;
+        background: rgba(0, 0, 0, 0.5);
+      }
+
+      .apply-text {
+        margin-top: 21px;
+        width: 345px;
+        height: 24px;
+        line-height: 24px;
+        letter-spacing: 0.2px;
+      }
+
+      .coin {
+        margin-top: 13px;
+        width: 345px;
+        height: 24px;
+        line-height: 24px;
+      }
+
+      .price {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 7px;
+        width: 345px;
+        height: 18px;
+      }
+
+      .sure-btn {
+        margin-top: 17px;
+        width: 311px;
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        border-radius: 100px;
+        background: #df384c;
+      }
+    }
+  }
+</style>

+ 88 - 0
src/views/index/dialog/Subscribed.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="subscribed">
+    <div class="apply-mask" @click="emits('subscribedClose')"></div>
+    <div class="apply-content">
+      <div class="slide-line"></div>
+      <div class="apply-text pf600 fs18 fc121212">认缴金额</div>
+      <input type="text" class="input pf400 fs14 fc333333" placeholder="输入认缴金额" />
+
+      <div class="sure-btn pf600 fs14 fcFFFFFF">确认</div>
+    </div>
+  </div>
+</template>
+<script setup>
+  const emits = defineEmits(["subscribedClose"]);
+</script>
+<style lang="less" scoped>
+  .subscribed {
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    width: 100%;
+    min-height: 100vh;
+
+    .apply-mask {
+      position: fixed;
+      left: 0;
+      top: 0;
+      z-index: -1;
+      width: 100%;
+      min-height: 100vh;
+      background: rgba(0, 0, 0, 0.5);
+    }
+
+    .apply-content {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      width: 375px;
+      height: 200px;
+      background: #ffffff;
+      border-radius: 24px 24px 0px 0px;
+
+      .slide-line {
+        margin-top: 12px;
+        width: 32px;
+        height: 4px;
+        border-radius: 2px;
+        opacity: 0.4;
+        background: rgba(0, 0, 0, 0.5);
+      }
+
+      .apply-text {
+        margin-top: 21px;
+        width: 345px;
+        height: 24px;
+        line-height: 24px;
+        letter-spacing: 0.2px;
+      }
+
+      .input {
+        padding-left: 11px;
+        margin-top: 12px;
+        width: 345px;
+        height: 45px;
+        box-sizing: border-box;
+        border-radius: 6px;
+        border: none;
+        outline: none;
+        background: #f5f5f5;
+      }
+
+      .sure-btn {
+        margin-top: 17px;
+        width: 311px;
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        border-radius: 100px;
+        background: #df384c;
+      }
+    }
+  }
+</style>

+ 96 - 0
src/views/index/dialog/SubscriptionOrder.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="subscription-order">
+    <div class="apply-mask" @click="emits('subscriptionOrderClose')"></div>
+    <div class="apply-content">
+      <div class="slide-line"></div>
+      <div class="apply-text pf600 fs18 fc121212">下单确认</div>
+      <div class="coin pf500 fs16 fc121212">BTCION</div>
+      <div class="price">
+        <div class="pf500 fs14 fc666666">单价</div>
+        <div class="pf500 fs12 fc666666">1000.05 USDT</div>
+      </div>
+      <div class="sure-btn pf600 fs14 fcFFFFFF">认购</div>
+    </div>
+  </div>
+</template>
+<script setup>
+  const emits = defineEmits(["subscriptionOrderClose"]);
+</script>
+<style lang="less" scoped>
+  .subscription-order {
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    width: 100%;
+    min-height: 100vh;
+
+    .apply-mask {
+      position: fixed;
+      left: 0;
+      top: 0;
+      z-index: -1;
+      width: 100%;
+      min-height: 100vh;
+      background: rgba(0, 0, 0, 0.5);
+    }
+
+    .apply-content {
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      width: 375px;
+      height: 200px;
+      background: #ffffff;
+      border-radius: 24px 24px 0px 0px;
+
+      .slide-line {
+        margin-top: 12px;
+        width: 32px;
+        height: 4px;
+        border-radius: 2px;
+        opacity: 0.4;
+        background: rgba(0, 0, 0, 0.5);
+      }
+
+      .apply-text {
+        margin-top: 21px;
+        width: 345px;
+        height: 24px;
+        line-height: 24px;
+        letter-spacing: 0.2px;
+      }
+
+      .coin {
+        margin-top: 13px;
+        width: 345px;
+        height: 24px;
+        line-height: 24px;
+      }
+
+      .price {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 7px;
+        width: 345px;
+        height: 18px;
+      }
+
+      .sure-btn {
+        margin-top: 17px;
+        width: 311px;
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        border-radius: 100px;
+        background: #45b26b;
+      }
+    }
+  }
+</style>

+ 29 - 1
src/views/index/financial/MyFinancial.vue

@@ -1,7 +1,7 @@
 <template>
   <!-- 我的理财 -->
   <HeaderNav headerText="我的理财"></HeaderNav>
-  <div class="my-financial">
+  <div class="my-financial" v-if="WealthOrderListLength > 0">
     <div class="coin-item" v-for="(item, index) in WealthOrderList" :key="index">
       <div class="item-name">
         <img src="@/assets/icon/coin/bnb.svg" alt="" />
@@ -48,6 +48,11 @@
       </div>
     </div>
   </div>
+  <div class="financial-null" v-else>
+    <img src="@/assets/img/index/Cryptocurrency_hedge_funds_2_.png" alt="" />
+    <div class="order-null pf500 fs14 fc1F2937">暂无订单</div>
+    <div class="pf400 fs12 fcA9A9A9">快去理财创造收益吧</div>
+  </div>
 </template>
 <script setup>
   import HeaderNav from "../components/HeaderNav.vue";
@@ -55,11 +60,13 @@
   import { ref, onMounted } from "vue";
 
   const WealthOrderList = ref();
+  const WealthOrderListLength = ref(0);
 
   const GetWealthOrderListData = async () => {
     const data = await GetWealthOrderList();
     if (data) {
       WealthOrderList.value = data;
+      WealthOrderListLength.value = WealthOrderList.value.length;
     }
   };
 
@@ -163,4 +170,25 @@
       }
     }
   }
+
+  .financial-null {
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: center;
+    margin-top: 48px;
+    width: 100%;
+
+    img {
+      margin-top: 130px;
+      width: 142px;
+      height: 95px;
+    }
+
+    .order-null {
+      margin-top: 10px;
+      height: 22px;
+      line-height: 22px;
+    }
+  }
 </style>

+ 10 - 1
src/views/index/ico/LotteryResult.vue

@@ -19,9 +19,18 @@
         <div class="item-operate pf400 fs12 fc2C3131">2025-11-04 14:25:06</div>
       </div>
     </div>
+    <Subscribed v-show="subscribedFlag"></Subscribed>
   </div>
 </template>
-<script setup></script>
+<script setup>
+  import Subscribed from "../dialog/Subscribed.vue";
+  import { ref } from "vue";
+
+  const subscribedFlag = ref(false);
+  const subscribedClose = () => {
+    subscribedFlag.value = false;
+  };
+</script>
 <style lang="less" scoped>
   .lottery-result {
     width: 349px;

+ 12 - 1
src/views/index/ico/Placement.vue

@@ -14,7 +14,9 @@
             {{ Number(item.issue_price).toFixed(2) }}
           </div>
           <div class="item-operate">
-            <div class="text pf500 fs12 fcFFFFFF">配售</div>
+            <div class="text pf500 fs12 fcFFFFFF" @click="placementOrderFlag = true">
+              配售
+            </div>
           </div>
         </div>
       </div>
@@ -25,15 +27,24 @@
         class="img-null"
         alt="" />
     </div>
+    <PlacementOrder
+      v-show="placementOrderFlag"
+      @placementOrderClose="placementOrderClose"></PlacementOrder>
   </div>
 </template>
 <script setup>
   import { ref, onMounted } from "vue";
   import { GetIcoIco } from "@/api/index";
+  import PlacementOrder from "../dialog/PlacementOrder.vue";
 
   const icoData = ref();
   const subscriptionData = ref();
 
+  const placementOrderFlag = ref(false);
+  const placementOrderClose = () => {
+    placementOrderFlag.value = false;
+  };
+
   const GetIcoIcoData = async () => {
     // 1,认购 2,配售
     const params = {

+ 12 - 1
src/views/index/ico/Subscription.vue

@@ -14,7 +14,9 @@
             {{ Number(item.issue_price).toFixed(2) }}
           </div>
           <div class="item-operate">
-            <div class="text pf500 fs12 fcFFFFFF">认购</div>
+            <div class="text pf500 fs12 fcFFFFFF" @click="subscriptionOrderFlag = true">
+              认购
+            </div>
           </div>
         </div>
       </div>
@@ -25,15 +27,24 @@
         class="img-null"
         alt="" />
     </div>
+    <SubscriptionOrder
+      v-show="subscriptionOrderFlag"
+      @subscriptionOrderClose="subscriptionOrderClose"></SubscriptionOrder>
   </div>
 </template>
 <script setup>
   import { ref, onMounted } from "vue";
   import { GetIcoIco } from "@/api/index";
+  import SubscriptionOrder from "../dialog/SubscriptionOrder.vue";
 
   const icoData = ref();
   const subscriptionData = ref();
 
+  const subscriptionOrderFlag = ref(false);
+  const subscriptionOrderClose = () => {
+    subscriptionOrderFlag.value = false;
+  };
+
   const GetIcoIcoData = async () => {
     // 1,认购 2,配售
     const params = {

+ 10 - 9
vue.config.js

@@ -1,19 +1,14 @@
-// vue.config.js
 const { defineConfig } = require("@vue/cli-service");
 
 module.exports = defineConfig({
   transpileDependencies: true,
-  //跨域
-  // --- 核心配置开始 ---
   devServer: {
     proxy: {
       "/api": {
-        // ⚠️【重要】这里必须改成你真实的后端地址!
-        // 如果后端在本地,可能是 http://localhost:8000
-        // 如果是线上测试服,可能是 http://47.100.xx.xx
-        //'http://63.141.230.43:57676',
-        // 'http://backend.66linknow.com'
-        target: "https://test2.66linknow.com", // ✅ 必须加上协议
+        // http://63.141.230.43:57676
+        // http://backend.66linknow.com
+        // https://test2.66linknow.com
+        target: "https://test2.66linknow.com",
         changeOrigin: true, // 允许跨域
       },
       // 2.【新增】WebSocket 代理配置
@@ -23,6 +18,12 @@ module.exports = defineConfig({
         ws: true, // ⚠️ 开启 WebSocket 支持
         // 这里是否需要 pathRewrite 取决于后端路径有没有 /ws
       },
+      "/ws/custom_chat": {
+        target: "wss://test2.66linknow.com", // 后端 IP
+        changeOrigin: true,
+        ws: true, // ⚠️ 开启 WebSocket 支持
+        // 这里是否需要 pathRewrite 取决于后端路径有没有 /ws
+      },
     },
   },