示例图:
image.png
<template>
<div class="chat-container">
<!-- 消息区 -->
<div ref="messageBox" class="messages">
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.role]">
<div class="bubble">
<div v-if="msg.role === 'assistan' && msg.isStreaming">
{{msg.content}}<span class="cursor">...</span>
</div>
<markdown-it-vue v-else :content="msg.content"></markdown-it-vue>
</div>
</div>
</div>
<!-- 输入区 -->
<div class="input-area">
<textarea v-model="input" @keydown.enter.exact.prevent="submit" placeholder="向DeepSeek提问..."></textarea>
<button @click="submit" :disabled="isLoading">
{{ isLoading ? '生成中...' : '发送' }}
</button>
</div>
</div>
</template>
<script>
import axios from 'axios'
import MarkdownItVue from 'markdown-it-vue';
export default {
components: {
MarkdownItVue
},
data() {
return {
DEEPSEEK_API_KEY: process.env.VUE_APP_API_KEY,
messages: [],
content: '',
input: '',
isLoading: false,
messageBox: null
}
},
mounted() {
this.messageBox = this.$refs.messageBox
},
methods: {
async submit() {
if (!this.input.trim() || this.isLoading) return
// 思考中 对话框
const assistantMsg = {
role: 'assistan',
content: '正在思考',
isStreaming: true
}
try {
this.isLoading = true
// 输入消息框
const userMsg = {
role: 'user',
content: this.input
}
this.messages.push(userMsg)
// 思考中 对话写入
this.messages.push(assistantMsg)
this.input = ''
this.$nextTick() // 确保DOM更
// let url = '/test.json'
let url = 'https://api.deepseek.com/chat/completions'
const response = await fetch(url, {
method: 'POST', // 或者 'PUT'
model: "deepseek-reasoner",
headers: {
'Authorization': `Bearer ${this.DEEPSEEK_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: "deepseek-reasoner",
temperature: 0.7,
messages: [{
role: "user",
content: userMsg.content
}],
stream: true
}) // 将数据对象转换为JSON字符串
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const reader = response.body.getReader()
const decoder = new TextDecoder()
const contentChunks = []
// 对话框数据写入
const contentMsg = {
role: 'assistant',
content: '|'
}
this.messages.push(contentMsg)
this.$nextTick()
while (true) {
const {done,value} = await reader.read()
if (done) break
const chunk = decoder.decode(value)
const lines = chunk.split('\n').filter(line => line.trim())
// 返回的数据 循环读取
for (const line of lines) {
if (line === ': keep-alive') { // 数据开始读取判断
continue;
}
try {
let newStr = line.replace('data: ', '')
if (newStr === '[DONE]') break // 数据到 DONE 结束循环
let dataL = JSON.parse(newStr);
if (dataL.choices && dataL.choices[0] && dataL.choices[0].delta && dataL.choices[0].delta.content) {
//数据写入
contentChunks.push(dataL.choices[0].delta.content)
console.log('contentChunksArr', contentChunks)
const string = contentChunks.join('');
console.log('string', string)
this.$nextTick(() => {
contentMsg.content = string
// 自动滚动到底部
this.messageBox.scrollTop = this.messageBox.scrollHeight
})
}
} catch (parseError) {
console.warn('Non-JSON response:', parseError)
}
}
}
} catch (error) {
console.error('API Error:', error)
this.messages.push({
role: 'system',
content: '请求失败,请检查网络连接或API配置'
})
} finally {
console.log('finally')
this.isLoading = false
assistantMsg.isStreaming = false
assistantMsg.content = '思考完成'
}
}
}
}
</script>
<style scoped>
.chat-container {
max-width: 800px;
margin: 0 auto;
height: 100vh;
display: flex;
flex-direction: column;
}
.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.message {
margin: 10px 0;
}
.message.assistant {
text-align: left;
}
.message.user {
text-align: right;
}
.bubble {
display: inline-block;
padding: 12px 18px;
border-radius: 15px;
max-width: 80%;
background: #f0f2f5;
/* 助手消息背景 */
}
.user .bubble {
background: #1890ff;
/* 用户消息背景 */
color: white;
}
.cursor {
animation: blink 1s infinite;
}
@keyframes blink {
50% {
opacity: 0
}
}
.input-area {
padding: 20px;
border-top: 1px solid #eee;
display: flex;
gap: 10px;
}
textarea {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
resize: none;
height: 80px;
}
button {
padding: 0 24px;
background: #1890ff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
</style>
.env 配置

image.png
VUE_APP_API_KEY = your api-key
