file-type

VS2019集成Gitee插件快速使用指南

7Z文件

下载需积分: 50 | 13.41MB | 更新于2025-08-08 | 191 浏览量 | 15 下载量 举报 收藏
download 立即下载
根据提供的文件信息,我们可以了解到一些关键的IT知识点。首先,文件标题和描述均提到“VS2019的Gitee插件.7z”,这暗示了我们即将探讨的内容涉及到Visual Studio 2019(简称VS2019),一款由微软开发的集成开发环境(IDE),以及Gitee,一个类似于GitHub的代码托管平台。 ### Visual Studio 2019 Visual Studio 2019是微软发布的一款面向开发者的功能强大的集成开发环境。它支持多种编程语言,比如C#、C++、VB.NET等,并且支持跨平台开发。Visual Studio 2019在用户界面和性能方面做了显著改进,包括新的启动窗口、更快的搜索功能、改进的调试体验等。 #### Visual Studio 2019的关键特性: 1. **项目模板**:提供了大量的项目模板,可以快速启动新项目。 2. **代码编辑器**:具有语法高亮、代码补全、重构等高级编辑功能。 3. **调试器**:提供了强大的调试工具,支持断点、步进、变量监视等。 4. **扩展管理器**:允许开发者安装和管理第三方插件,以扩展Visual Studio的功能。 5. **Git集成**:内置Git支持,可进行版本控制操作。 6. **跨平台开发**:支持.NET Core、UWP、Xamarin等多种开发平台。 ### Gitee Gitee是一个基于Git的代码托管和协作开发平台,它提供代码托管、项目管理、协作开发等功能。Gitee支持私有仓库,适合企业和团队对代码进行私密管理。 #### Gitee的关键特性: 1. **代码托管**:可以创建私有或公开的代码仓库。 2. **项目管理**:提供了任务分配、项目看板、里程碑规划等项目管理工具。 3. **文档与 wiki**:支持创建和编辑项目文档或Wiki。 4. **代码审查**:可以发起pull request,方便团队成员之间进行代码审查。 5. **持续集成**:集成了CI/CD工具,可自动构建和部署代码。 6. **安全与备份**:保障代码的安全性,并提供备份机制。 ### C# C#(发音为“C Sharp”)是一种由微软公司开发的面向对象的高级编程语言。它是.NET框架的主要开发语言,也被用于.NET Core和Xamarin等其他平台。C#提供了丰富的库支持,可以用来开发Windows客户端应用程序、Web应用程序、分布式组件、数据库应用等。 #### C#的关键特性: 1. **面向对象**:支持封装、继承和多态等面向对象的基本特性。 2. **类型安全**:具有严格的类型系统和内存管理机制。 3. **跨平台**:.NET Core支持跨平台开发,C#代码可以在不同操作系统上运行。 4. **LINQ**:语言集成查询(LINQ)允许开发者以统一的方式查询和操作数据。 5. **异步编程**:支持异步编程模型,方便处理并发和异步任务。 6. **最新的C#版本**:例如C# 8.0带来了模式匹配、可为空引用类型等新特性。 ### 插件开发与使用 插件是指可以添加到现有软件中以增强其功能的小型软件程序。在Visual Studio中,插件通常通过Visual Studio扩展(VSIX)格式来打包。开发者可以通过Visual Studio的扩展管理器来安装和管理这些插件。 #### 插件开发的关键点: 1. **创建VSIX包**:使用Visual Studio的模板创建扩展项目,打包为VSIX格式。 2. **扩展点**:使用Visual Studio SDK定义的扩展点来添加新功能。 3. **工具窗口**:可以创建自定义的工具窗口,以便与用户交互。 4. **命令绑定**:允许将插件的功能绑定到菜单项或工具栏。 5. **代码编辑器扩展**:可以添加对代码编辑器的特定支持,比如语法高亮、代码片段等。 6. **集成外部服务**:可以集成外部服务或系统,比如与Gitee的API集成,实现代码提交、拉取等操作。 ### 结论 根据给定的文件信息,我们可以看出“VS2019的Gitee插件.7z”是一个压缩包,包含了用于Visual Studio 2019的插件,该插件很可能是用来增强Visual Studio与Gitee之间的集成和协作功能。该插件的开发语言可能是C#,因为Visual Studio和.NET平台都广泛使用C#作为编程语言。通过这个插件,开发者可以更便捷地在Visual Studio IDE中与Gitee平台上的代码仓库进行交互,例如推送代码、创建拉取请求等。 需要注意的是,"VS2019的Gitee插件"的文件名实际上与标题描述重复,没有额外的文件名提供,但这不影响我们对知识点的详细解析。在实际使用中,开发者需要解压该文件,并按照适当的指南将其安装到Visual Studio 2019中,以实现对Gitee平台的支持。

相关推荐

filetype

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>高德地图</title> <script src="https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.6.4.js"></script> <style> #map-container { width: 100%; height: 584px; border-radius: 22px; z-index: 4; } .amap-marker img { display: none; /* 隐藏默认图标 */ } </style> </head> <body>
<script> // 安全配置 window._AMapSecurityConfig = { securityJsCode: 'cf331d54f67e306ca5c0b17f7122d559' }; </script> <script src="https://webapi.amap.com/maps?v=2.0&key=f3323c24769b10828db07f07c0fcec0d&plugin=AMap.MarkerCluster"></script> <script> // 确保UniApp通信桥接准备就绪 function setupUniBridge() { return new Promise(resolve => { if (window.uni && typeof uni.postMessage === 'function') { return resolve(); } document.addEventListener('UniAppJSBridgeReady', resolve); }); } // 向UniApp发送消息 async function sendMessageToUniApp(data) { await setupUniBridge(); if (window.uni && typeof uni.postMessage === 'function') { uni.postMessage({ data: [JSON.stringify(data)] // 必须包装在数组中 }); } else if (window.parent) { window.parent.postMessage({ type: 'webviewMessage', data: JSON.stringify(data) }, '*'); } } // 解析URL参数 const params = new URLSearchParams(location.search); const points = JSON.parse(params.get('points') || '[]'); const markers = JSON.parse(params.get('markers') || '[]'); // 初始化地图 const map = new AMap.Map('map-container', { zoom: 10, center: points.length ? [points[0].lng, points[0].lat] : [116.397428, 39.90923], showBuildingBlock: true }); // 1. 绘制路线 if (points.length > 0) { const path = points.map(p => [p.lng, p.lat]); new AMap.Polyline({ path: path, strokeColor: "#32CD32", strokeWeight: 5, map: map }); // 添加起点终点标记 points.forEach((point, i) => { new AMap.Marker({ position: [point.lng, point.lat], offset: new AMap.Pixel(-10, -10), map: map }); }); } // 2. 初始化聚合点(使用官方推荐方式) let cluster; // 存储聚合实例 function initCluster() { // 转换数据格式为高德官方要求的格式 const clusterPoints = markers.map(marker => ({ lnglat: [marker.lng, marker.lat], // 注意属性名是lnglat title: marker.title, originalData: marker })); // 销毁旧实例(如果存在) if (cluster) { cluster.setMap(null); } // 创建聚合实例 cluster = new AMap.MarkerCluster(map, clusterPoints, { gridSize: 80, // 聚合网格像素大小 maxZoom: 18, // 最大聚合级别 minClusterSize: 2, // 最小聚合数量 // 自定义聚合点样式(官方推荐方式) renderClusterMarker: function(context) { const count = context.count; const div = document.createElement('div'); div.style.background = '#FF9A44'; div.style.borderRadius = '50%'; div.style.width = '40px'; div.style.height = '40px'; div.style.display = 'flex'; div.style.alignItems = 'center'; div.style.justifyContent = 'center'; div.style.color = 'white'; div.style.fontWeight = 'bold'; div.style.zIndex = '99'; div.innerHTML = count; return div; }, // 自定义非聚合点样式 renderMarker: function(context) { const markerData = context.data[0].originalData; const marker = new AMap.Marker({ position: [markerData.lng, markerData.lat], content: `
${markerData.title}
`, offset: new AMap.Pixel(-20, -40) }); // 绑定点击事件 marker.on('click', async function(e) { await sendMessageToUniApp({ type: 'markerClick', data: markerData }); }); return marker; } }); // 绑定聚合点点击事件 cluster.on('click', async function(e) { await sendMessageToUniApp({ type: 'clusterClick', data: { count: e.clusterData.count, center: e.clusterData.center, markers: e.clusterData.markers } }); }); } // 3. 地图加载完成后初始化聚合点 map.on('complete', function() { if (markers.length > 0) { initCluster(); } }); </script> </body> </html> 聚合点还是默认图标,没有失效自定义样式

filetype

map.html?key=f3323c24769b10828db07f07c0fcec0d&points=%5B%7B%22lng%22%3A106.235589%2C%22lat%22%3A38.45766%2C%22name%22%3A%22%E8%B5%B7%E7%82%B9%22%7D%2C%7B%22lng%22%3A106.287761%2C%22lat%22%3A38.4710595%2C%22name%22%3A%22%E9%BC%93%E6%A5%BC%22%7D%5D&markers=%5B%7B%22lng%22%3A106.28776197503275%2C%22lat%22%3A38.47105952636814%2C%22title%22%3A%22%E9%BC%93%E6%A5%BC%22%7D%2C%7B%22lng%22%3A106.01313200136352%2C%22lat%22%3A38.434956774850576%2C%22title%22%3A%22%E8%A5%BF%E5%A4%8F%E9%99%B5%22%7D%2C%7B%22lng%22%3A106.235068%2C%22lat%22%3A38.484468%2C%22title%22%3A%22%E5%AE%81%E5%A4%8F%E5%8D%9A%E7%89%A9%E9%A6%86%22%7D%2C%7B%22lng%22%3A106.123554%2C%22lat%22%3A38.231086%2C%22title%22%3A%22%E5%AE%81%E5%A4%8F%E5%B7%A5%E5%A7%94%E7%BA%AA%E5%BF%B5%E9%A6%86%22%7D%2C%7B%22lng%22%3A105.975629%2C%22lat%22%3A38.244193%2C%22title%22%3A%22%E9%97%BD%E5%AE%81%E6%96%B0%E8%B2%8C%E5%B1%95%E7%A4%BA%E4%B8%AD%E5%BF%83%22%7D%2C%7B%22lng%22%3A106.381172%2C%22lat%22%3A38.566882%2C%22title%22%3A%22%E7%81%B5%E6%AD%A6%E5%B8%82%E5%9B%BD%E5%AE%B6%E7%94%9F%E6%80%81%E6%96%87%E6%98%8E%E6%95%99%E8%82%B2%E5%9F%BA%E5%9C%B0%22%7D%2C%7B%22lng%22%3A106.584342%2C%22lat%22%3A38.159979%2C%22title%22%3A%22%E5%9B%BD%E5%AE%B6%E8%83%BD%E6%BA%90%E9%9B%86%E5%9B%A2%E5%AE%81%E5%A4%8F%E7%85%A4%E4%B8%9A%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8%E5%AE%9E%E8%B7%B5%E6%95%99%E8%82%B2%E5%9F%BA%E5%9C%B0%22%7D%5D:135 Uncaught SyntaxError: Identifier 'data' has already been declared (at map.html?key=f3323c24769b10828db07f07c0fcec0d&points=%5B%7B%22lng%22%3A106.235589%2C%22lat%22%3A38.45766%2C%22name%22%3A%22%E8%B5%B7%E7%82%B9%22%7D%2C%7B%22lng%22%3A106.287761%2C%22lat%22%3A38.4710595%2C%22name%22%3A%22%E9%BC%93%E6%A5%BC%22%7D%5D&markers=%5B%7B%22lng%22%3A106.28776197503275%2C%22lat%22%3A38.47105952636814%2C%22title%22%3A%22%E9%BC%93%E6%A5%BC%22%7D%2C%7B%22lng%22%3A106.01313200136352%2C%22lat%22%3A38.434956774850576%2C%22title%22%3A%22%E8%A5%BF%E5%A4%8F%E9%99%B5%22%7D%2C%7B%22lng%22%3A106.235068%2C%22lat%22%3A38.484468%2C%22title%22%3A%22%E5%AE%81%E5%A4%8F%E5%8D%9A%E7%89%A9%E9%A6%86%22%7D%2C%7B%22lng%22%3A106.123554%2C%22lat%22%3A38.231086%2C%22title%22%3A%22%E5%AE%81%E5%A4%8F%E5%B7%A5%E5%A7%94%E7%BA%AA%E5%BF%B5%E9%A6%86%22%7D%2C%7B%22lng%22%3A105.975629%2C%22lat%22%3A38.244193%2C%22title%22%3A%22%E9%97%BD%E5%AE%81%E6%96%B0%E8%B2%8C%E5%B1%95%E7%A4%BA%E4%B8%AD%E5%BF%83%22%7D%2C%7B%22lng%22%3A106.381172%2C%22lat%22%3A38.566882%2C%22title%22%3A%22%E7%81%B5%E6%AD%A6%E5%B8%82%E5%9B%BD%E5%AE%B6%E7%94%9F%E6%80%81%E6%96%87%E6%98%8E%E6%95%99%E8%82%B2%E5%9F%BA%E5%9C%B0%22%7D%2C%7B%22lng%22%3A106.584342%2C%22lat%22%3A38.159979%2C%22title%22%3A%22%E5%9B%BD%E5%AE%B6%E8%83%BD%E6%BA%90%E9%9B%86%E5%9B%A2%E5%AE%81%E5%A4%8F%E7%85%A4%E4%B8%9A%E6%9C%89%E9%99%90%E5%85%AC%E5%8F%B8%E5%AE%9E%E8%B7%B5%E6%95%99%E8%82%B2%E5%9F%BA%E5%9C%B0%22%7D%5D:135:11)

filetype

<script setup lang="ts"> import { nextTick, ref } from 'vue'; // @ts-ignore - These variables are used in the template import { marked as originalMarked } from 'marked'; // @ts-ignore - These variables are used in the template import hljs from 'highlight.js'; import 'highlight.js/styles/github.css'; import { useMessage, useToast } from 'wot-design-uni'; import send from '/static/icons/send.svg'; import { onLoad } from '@dcloudio/uni-app'; // 添加页面生命周期钩子 // @ts-ignore - These variables are used in the template import { getModellist } from '@/api/user.js'; const marked: any = originalMarked; const inputBottom = ref('15rpx'); // 输入框底部间距 const keyboardHeight = ref(0); // 键盘高度 const message = useMessage(); const toast = useToast(); const picker = ref<any>(null); // 添加选择器引用 // 监听键盘高度变化 uni.onKeyboardHeightChange((res) => { keyboardHeight.value = res.height; // 转换为px单位(小程序环境使用px) inputBottom.value = `${res.height}px`; }); // 在组件卸载时取消监听 onUnmounted(() => { uni.offKeyboardHeightChange(); }); // 配置marked选项 marked.setOptions({ highlight: (code: string, lang: string) => { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (err) { console.warn('代码高亮失败:', err); return code; } } return code; }, gfm: true, breaks: true, headerIds: false, mangle: false }); function renderMarkdown(content: string) { try { // 在小程序中,我们需要确保返回的是字符串 const html = marked(content); // 处理代码块的样式类,确保返回字符串 return String(html).replace(/
<code/g, '
<code class="hljs"');
  } catch (err) {
    console.error('Markdown渲染失败:', err);
    return content;
  }
}

interface Message {
  role: 'user' | 'assistant';
  content: string;
  typing?: boolean;
}

const messages = ref<Message[]>([
  {
    role: 'assistant',
    content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?'
  }
]);

const inputMessage = ref('');
const loading = ref(false);
const scrollTop = ref(0);
const messageListRef = ref<HTMLElement | null>(null);

// 防抖函数
function debounce(fn: Function, delay: number) {
  let timer: number | null = null;
  return function (this: any, ...args: any[]) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 节流函数
function throttle(fn: Function, delay: number) {
  let lastTime = 0;
  let timer: number | null = null;
  return function (this: any, ...args: any[]) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = now;
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = Date.now();
        timer = null;
      }, remaining);
    }
  };
}

// 滚动到底部的函数
const scrollToBottom = throttle(async () => {
  if (!messageListRef.value) return;

  const lastMessage = messages.value[messages.value.length - 1];
  if (lastMessage?.typing) return; // 打字过程中不触发滚动

  await nextTick();
  const query = uni.createSelectorQuery();
  query
    .select('.message-list')
    .boundingClientRect((data: any) => {
      if (data) {
        scrollTop.value = data.height;
      }
    })
    .exec();
}, 200); // 增加节流时间,减少滚动更新频率

// 监听消息列表变化,自动滚动到底部
watch(
  () => messages.value.length,
  () => {
    if (messages.value[messages.value.length - 1]?.typing) return;
    scrollToBottom();
  }
);
// 监听最后一条消息的内容变化(用于打字效果)
watch(
  () => messages.value[messages.value.length - 1]?.content,
  async () => {
    const lastMessage = messages.value[messages.value.length - 1];
    if (lastMessage?.typing) {
      // 在打字过程中,每次内容变化都尝试滚动
      await nextTick();
      const query = uni.createSelectorQuery();
      query
        .select('.message-list')
        .boundingClientRect((data: any) => {
          if (data && data.height > scrollTop.value) {
            scrollTop.value = data.height;
          }
        })
        .exec();
    }
  }
);

// 模拟打字效果的函数
async function typeMessage(fullContent: string) {
  try {
    // 获取最后一条消息的引用
    const lastMessage = messages.value[messages.value.length - 1];
    // 初始化内容并标记为正在输入
    lastMessage.content = '';
    lastMessage.typing = true;
    // 确保滚动到底部
    await nextTick();
    await scrollToBottom();
    // 逐步更新内容
    const chars = Array.from(fullContent);
    let currentContent = '';
    const batchSize = 1;

    for (let i = 0; i < chars.length; i += batchSize) {
      currentContent += chars.slice(i, i + batchSize).join('');
      lastMessage.content = currentContent; // 直接更新最后一条消息
      await new Promise((resolve) => setTimeout(resolve, 10));
      await scrollToBottom();
    }

    // 最终状态更新
    lastMessage.content = fullContent;
    lastMessage.typing = false;
    await nextTick();
    await scrollToBottom();
  } catch (error) {
    console.error('打字效果执行失败:', error);
    const lastMessage = messages.value[messages.value.length - 1];
    lastMessage.content = fullContent;
    lastMessage.typing = false;
    await scrollToBottom();
  }
}

async function sendMessage() {
  if (!inputMessage.value.trim()) return;
  const userContent = inputMessage.value;

  // 添加用户消息
  messages.value.push({
    role: 'user',
    content: userContent
  });

  // 清空输入框
  inputMessage.value = '';
  loading.value = true;

  try {
    // 创建初始的助手消息
    const assistantMessage: Message = {
      role: 'assistant',
      content: '',
      typing: true
    };
    messages.value.push(assistantMessage);
    await nextTick();

    const token = uni.getStorageSync('token');
    // 发送请求
    const { data } = await uni.request({
      url: 'https://qa.mini.xmaas.cn/chat/completions',
      method: 'POST',
      header: {
        logic: 'check',
        Authorization: `${token}`,
        'Content-Type': 'application/json'
      },
      data: {
        position: 0,
        model: pickerValue.value,
        stream: true,
        messages: [{ role: 'user', content: userContent }]
      }
    });

    // 处理流式数据
    const streamData = data as string;
    const chunks = streamData
      .split('\n\n') // 分割事件
      .filter((chunk) => chunk.trim().startsWith('data:')); // 过滤有效数据

    let fullContent = '';
    for (const chunk of chunks) {
      try {
        const jsonStr = chunk.replace(/^data:/, '').trim();
        if (!jsonStr) continue;

        const eventData = JSON.parse(jsonStr);
        const contentChunk = eventData.choices[0]?.delta?.content || '';

        // 处理Unicode转义字符
        const decodedContent = unescape(contentChunk.replace(/\\u/g, '%u'));
        fullContent += decodedContent;
      } catch (err) {
        console.error('解析数据块失败:', err);
      }
    }

    // 更新最后一条消息内容,并触发打字效果
    messages.value[messages.value.length - 1].content = fullContent;
    await typeMessage(fullContent); // 使用逐字打印效果

    // 结束打字状态
    messages.value[messages.value.length - 1].typing = false;
  } catch (error) {
    console.error('请求失败:', error);
    messages.value[messages.value.length - 1].content = '回答生成失败,请稍后重试';
  } finally {
    loading.value = false;
    await scrollToBottom();
  }
}

function loadMoreMessages() {
  // TODO: 实现加载更多历史消息
  console.log('加载更多消息');
}

// ... existing code ...
function handleBack() {
  uni.navigateBack({
    delta: 1,
    fail: () => {
      // 如果返回失败(比如没有上一页),则跳转到首页
      uni.switchTab({
        url: '/pages/index'
      });
    }
  });
}

//清空对话

// 修改 handClear 函数
async function handClear() {
  const token = uni.getStorageSync('token');
  try {
    // 1. 调用清空接口
    await uni.request({
      url: 'https://qa.mini.xmaas.cn/message/deleteByChatId',
      method: 'DELETE',
      header: {
        'Content-Type': 'application/json',
        Authorization: `${token}`
      }
    });

    // 2. 重置本地消息状态为初始欢迎消息
    messages.value = [
      {
        role: 'assistant',
        content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?'
      }
    ];

    // 3. 重置滚动位置到顶部
    scrollTop.value = 0;
    // 4. 显示成功提示(已经在 beforeConfirm 中处理)
  } catch (error) {
    console.error('清空消息失败:', error);
    toast.error('清空消息失败');
  }
}

function beforeConfirm() {
  message
    .confirm({
      msg: '是否删除',
      title: '提示',
      beforeConfirm: ({ resolve }) => {
        toast.loading('删除中...');
        setTimeout(() => {
          toast.close();
          handClear();
          resolve(true);
          toast.success('删除成功');
        }, 2000);
      }
    })
    .then(() => {})
    .catch((error) => {
      console.log(error);
    });
}

const columns = ref<Record<string, any>>([]);

const pickerValue = ref<string>('');

// 添加获取模型列表的函数
async function fetchModelList() {
  try {
    const data = await getModellist();
    if (data && data.models && data.models.length > 0) {
      // 将接口数据映射为选择器需要的格式
      columns.value = data.models.map((model: any) => ({
        value: model.name,
        label: model.name
      }));

      // 设置默认选中第一个模型
      if (columns.value.length > 0) {
        pickerValue.value = columns.value[0].value;
      }
    }
  } catch (error) {}
}

function handleChange({ value }: any) {
  pickerValue.value = value;
}

// 添加按钮点击处理函数
function handleButtonClick() {
  picker.value?.open();
}

function pauseLoading() {
  console.log(22222222222);
}

// 添加历史消息获取函数
async function fetchHistoryMessages() {
  const token = uni.getStorageSync('token');
  try {
    loading.value = true;
    const { data }: any = await uni.request({
      url: 'https://qa.mini.xmaas.cn/message/findByChatId',
      method: 'GET',
      header: {
        'Content-Type': 'application/json',
        Authorization: `${token}`
      }
    });

    // 处理接口返回的数据
    if (data && data.messages && data.messages.length > 0) {
      // 按时间排序(确保消息顺序正确)
      const sortedMessages = data.messages.sort((a: any, b: any) => a.created_time - b.created_time);
      messages.value = [];

      // 转换为需要的格式
      sortedMessages.forEach((msg: any) => {
        messages.value.push({
          role: msg.model_id === 0 ? 'user' : 'assistant',
          content: msg.content,
          typing: false // 历史消息不需要打字效果
        });
      });

      // 滚动到底部
      await nextTick();
      scrollToBottom();
    } else {
      // 没有历史消息时显示欢迎语
      setWelcomeMessage();
    }
  } catch (error) {
    console.error('获取历史消息失败:', error);
    toast.error('加载历史消息失败');
    // 请求失败时也显示欢迎语
    setWelcomeMessage();
  } finally {
    loading.value = false;
  }
}

function setWelcomeMessage() {
  messages.value = [
    {
      role: 'assistant',
      content: '你好,我是你的智能助手。有什么问题我可以帮你解答吗?'
    }
  ];
}

// 在页面加载时获取历史消息
onLoad(() => {
  fetchHistoryMessages();
  fetchModelList();
});

//长按复制
function handleCopy(e: any) {
  // 确保是复制操作
  if (e.detail.action === 'copy') {
    // 获取消息内容
    const content = e.currentTarget.dataset.content;
    // 复制到剪贴板
    uni.setClipboardData({
      data: content,
      success: () => {
        // 使用您现有的 toast 组件
        toast.success('复制成功');
      },
      fail: () => {
        toast.error('复制失败');
      }
    });
  }
}

// +++ 添加长按事件处理 +++
function handleLongPress(e: any, content: string) {
  console.log('长按事件触发', content);
}
</script>

<template>
  <view class="chat-container">
    <wd-navbar safe-area-inset-top placeholder left-arrow fixed :bordered="false" @click-left="handleBack">
      <template #right>
        <view class="custom-right">
          <wd-icon name="clear" size="22px" @click="beforeConfirm" />
        </view>
      </template>
    </wd-navbar>

    <scroll-view
      ref="messageListRef"
      scroll-y
      class="chat-messages"
      :scroll-top="scrollTop"
      :scroll-with-animation="false"
      :scroll-anchoring="true"
      :enhanced="true"
      :bounces="false"
      @scrolltoupper="loadMoreMessages"
    >
      <view class="message-list">
<view 
  v-for="(message, index) in messages" 
  :key="index" 
  class="message-item"
  :class="[message.role, { typing: message.typing }]"
>
  <view v-if="message.role === 'assistant'" class="message-avatar">
    <image src="/https/wenku.csdn.net/static/svg/Hara.svg" mode="aspectFill" style="width: 44rpx; height: 44rpx" />
    <text>Hara</text>
  </view>
  
  <view class="message-content">
    
    <view 
      v-if="!message.typing"
      @longpress="(e) => handleLongPress(e, message.content)"
    >
      <rich-text 
        :nodes="renderMarkdown(message.content)" 
        :data-content="message.content"
      />
    </view>
    
    <rich-text 
      v-else 
      :nodes="renderMarkdown(message.content)" 
    />
    
    <view v-if="message.typing" class="typing-indicator">
      <view class="dot" />
      <view class="dot" />
      <view class="dot" />
    </view>
  </view>
</view>
      </view>
    </scroll-view>
    <wd-select-picker
      ref="picker"
      v-model="pickerValue"
      :columns="columns"
      @change="handleChange"
      custom-class="hidden-picker"
      custom-style="z-index:120"
      type="radio"
      :show-confirm="false"
    ></wd-select-picker>
    <view class="button_picker" style="margin-bottom: 45rpx; margin-left: 25rpx">
      <wd-button type="success" @click="handleButtonClick" custom-class="handbtn">
        <text style="font-family: PingFang SC">{{ pickerValue }}</text>
        <wd-icon name="arrow-down" size="16px" custom-style="transform: translateY(3rpx)"></wd-icon>
      </wd-button>
    </view>

    <view class="chat-input safe-area-bottom" :style="{ bottom: inputBottom }">
      <input
        v-model="inputMessage"
        type="text"
        placeholder="向你的专属知识库提问吧~"
        :rows="2"
        class="message-textarea"
        :disabled="loading"
        :adjust-position="false"
        @keypress.enter.prevent="sendMessage"
      />
      

      <image
        :src="loading ? '/static/icons/Pause.svg' : !inputMessage.trim() ? '/static/icons/ic_send.svg' : '/static/icons/arrow.svg'"
        @click="loading ? pauseLoading() : inputMessage.trim() ? sendMessage() : null"
        style="width: 56rpx; height: 56rpx"
      />
    </view>
  </view>
</template>

<style lang="scss" scoped>
:deep(.handbtn) {
  background-color: #d8f2f3 !important;
  color: #14c3c9 !important;
  width: auto !important;
  border-radius: 16rpx !important;
  font-family: 'PingFang SC';
  display: flex;
  align-items: center;
}

.button_picker {
  margin-bottom: 90rpx;
  margin-left: 80rpx;
}
/* 隐藏原生选择器控件 */
:deep(.hidden-picker) {
  .wd-select-picker__field {
    display: none !important;
  }

  .data-v-d4a8410a.wd-icon.wd-icon-check {
    color: #14c3c9 !important;
  }

  .data-v-aa3a6253.wd-button.is-primary.is-large.is-round.is-block {
    background-color: #7f6ce0 !important;
  }
}

.custom-right {
  display: flex;
  align-items: center;
  /* 垂直居中 */
  gap: 10rpx;
  /* 元素间距 */
  padding-right: 160rpx;
  /* 右侧距离 */
  margin-right: 20rpx;
}

:deep(.wd-navbar) {
  background-color: #f4f4f5 !important; // 添加背景色
}

:deep(.wd-navbar__title) {
  color: #000000 !important;
}

:deep(.wd-icon-arrow-left) {
  color: #000000 !important;
}

.chat-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f4f4f5;
  position: relative;
}

.chat-messages {
  flex: 0.9;
  box-sizing: border-box;
  padding: 20rpx 0 20rpx 20rpx;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.message-list {
  padding-bottom: 20rpx;
}

.message-item {
  display: flex;
  flex-direction: column;
  margin-bottom: 48rpx;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.3s, transform 0.3s;
  // padding: 0 20rpx;

  &.typing {
    .message-content {
      min-width: 120rpx;
    }
  }

  &.user {
    flex-direction: row-reverse;
    .message-content {
      background-color: #272727;
      border-radius: 24rpx 24rpx 24rpx 24rpx;
      padding: 20rpx;
      color: #fff;
      box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
      max-width: 80%;
      :deep(pre),
      :deep(code) {
        background-color: rgba(255, 255, 255, 0.1);
        color: #fff;
      }
    }
  }

  &.assistant .message-content {
    max-width: 100%;
    // background-color: #fff;
    padding: 0 !important;
    // border-radius: 4rpx 20rpx 20rpx 20rpx;
  }
}

.message-avatar {
  width: 140rpx;
  flex-shrink: 0;
  margin: 0;
  display: flex;
  align-items: center;

  image {
    width: 100%;
    height: 100%;
    border-radius: 50%;
  }

  text {
    margin-left: 8rpx;
    color: 737373;
    font-weight: 400;
    font-size: 28rpx;
  }
}

.message-content {
  // max-width: 80%;
  margin: 0 20rpx;
  font-size: 28rpx;
  word-break: break-word;
  overflow-wrap: break-word;

  :deep(pre) {
    background-color: #f6f8fa;
    padding: 16rpx;
    border-radius: 6rpx;
    overflow-x: auto;
    margin: 16rpx 0;
    white-space: pre-wrap;
    word-wrap: break-word;
  }

  :deep(code) {
    font-family: Consolas, Monaco, 'Andale Mono', monospace;
    font-size: 24rpx;
    padding: 4rpx 8rpx;
    background-color: #f6f8fa;
    border-radius: 4rpx;
    white-space: pre-wrap;
    word-wrap: break-word;
  }

  :deep(p) {
    margin: 16rpx 0;
  }

  :deep(ul),
  :deep(ol) {
    padding-left: 32rpx;
    margin: 16rpx 0;
  }

  :deep(table) {
    border-collapse: collapse;
    margin: 16rpx 0;
    width: 100%;
  }

  :deep(th),
  :deep(td) {
    border: 2rpx solid #dfe2e5;
    padding: 12rpx 16rpx;
  }

  :deep(th) {
    background-color: #f6f8fa;
  }

  :deep(a) {
    color: #0366d6;
    text-decoration: none;

    &:hover {
      text-decoration: underline;
    }
  }

  :deep(img) {
    max-width: 100%;
    height: auto;
  }

  :deep(blockquote) {
    margin: 16rpx 0;
    padding: 0 16rpx;
    color: #6a737d;
    border-left: 4rpx solid #dfe2e5;
  }
}

.chat-input {
  padding: 24rpx 40rpx;
  position: fixed;
  left: 20rpx;
  right: 20rpx;
  bottom: 15rpx;
  display: flex;
  align-items: center;
  gap: 20rpx;
  z-index: 100;
  transition: bottom 0.3s ease; // 添加过渡动画
  border-radius: 16rpx;
  border: 2rpx solid var(--Extra-Shallow-Theme, #f4f3fc);
  background: var(--White, #fff);
  box-shadow: 0px 8rpx 20rpx 0px rgba(62, 46, 136, 0.17);
  margin-bottom: 45rpx;

  .message-textarea {
    flex: 1;
  }
}

.typing-indicator {
  display: flex;
  align-items: center;
  gap: 8rpx;
  padding: 8rpx 0;

  .dot {
    width: 8rpx;
    height: 8rpx;
    background-color: #999;
    border-radius: 50%;
    animation: typing 1.4s infinite;

    &:nth-child(2) {
      animation-delay: 0.2s;
    }

    &:nth-child(3) {
      animation-delay: 0.4s;
    }
  }
}

@keyframes typing {
  0%,
  60%,
  100% {
    transform: translateY(0);
    opacity: 0.3;
  }

  30% {
    transform: translateY(-4rpx);
    opacity: 1;
  }
}
</style>
markdown 输出的消息不能复制咋办

有时爱瞎折腾
  • 粉丝: 54
上传资源 快速赚钱