• 基于vue3+pinia2仿ChatGPT聊天实例|vite4.x仿chatgpt界面


    使用vue3+pinia2开发仿制chatgpt界面聊天实例Vue3-Chatgpt

    基于Vue3.x+Pinia2+VueRouter+Vue3-Markdown等技术构建仿ChatGPT网页端聊天程序。支持经典+分栏界面布局、light/dark模式、全屏+半屏显示、Markdown语法解析、侧边栏隐藏等功能。

    在这里插入图片描述
    在这里插入图片描述

    技术框架

    • 编辑工具:Cursor
    • 框架技术:Vue3+Vite4.x+Pinia2
    • 组件库:VEPlus (基于vue3桌面端组件库)
    • 国际化多语言:vue-i18n^9.2.2
    • 代码高亮:highlight.js^11.7.0
    • 本地存储:pinia-plugin-persistedstate^3.1.0
    • markdown解析:vue3-markdown-it

    在这里插入图片描述
    在这里插入图片描述

    项目目录结构

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    vite.config.js配置

    import { defineConfig, loadEnv } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import { resolve } from 'path'
    import { parseEnv } from './src/utils/env'
    
    // https://vitejs.dev/config/
    export default defineConfig(({ mode }) => {
    	const viteEnv = loadEnv(mode, process.cwd())
    	const env = parseEnv(viteEnv)
    
    	return {
    		plugins: [vue()],
    
    		// base: '/',
    		// mode: 'development', // development|production
    
    		/*构建选项*/
    		build: {
    			// minify: 'esbuild', // 打包方式 esbuild(打包快)|terser
    			// chunkSizeWarningLimit: 2000, // 打包大小警告
    			// rollupOptions: {
    			// 	output: {
    			// 		chunkFileNames: 'assets/js/[name]-[hash].js',
    			// 		entryFileNames: 'assets/js/[name]-[hash].js',
    			// 		assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
    			// 	}
    			// }
    		},
    		esbuild: {
    			// 打包去除 console.log 和 debugger
    			drop: env.VITE_DROP_CONSOLE ? ['console', 'debugger'] : []
    		},
    
    		/*开发服务器选项*/
    		server: {
    			// 端口
    			port: env.VITE_PORT,
    			// 是否浏览器自动打开
    			open: env.VITE_OPEN,
    			// 开启https
    			https: env.VITE_HTTPS,
    			// 代理配置
    			proxy: {
    				// ...
    			}
    		},
    
    		resolve: {
    			// 设置别名
    			alias: {
    				'@': resolve(__dirname, 'src'),
    				'@assets': resolve(__dirname, 'src/assets'),
    				'@components': resolve(__dirname, 'src/components'),
    				'@views': resolve(__dirname, 'src/views'),
    				// 解决vue-i18n警告提示:You are running the esm-bundler build of vue-i18n.
    				'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
    			}
    		}
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    main.js主入口

    import { createApp } from 'vue'
    import App from './App.vue'
    
    // 引入Router和Store
    import Router from './router'
    import Store from './store'
    
    // 引入插件配置
    import Plugins from './plugins'
    
    const app = createApp(App)
    
    app
    .use(Router)
    .use(Store)
    .use(Plugins)
    .mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    vue3.x组件库

    项目中使用的组件库是基于vue3自定义UI组件库Ve-plus。一个支持40+组件的轻量级组件库。
    在这里插入图片描述

    在这里插入图片描述
    安装组件

    yarn add ve-plus
    npm i ve-plus --save
    
    • 1
    • 2

    https://blog.csdn.net/yanxinyun1990/article/details/129312570

    整体布局

    项目支持2种布局模式,整体分为顶栏+侧边栏+主体内容三大模块构成。
    在这里插入图片描述

    在这里插入图片描述

    <div class="ve__layout-body flex1 flexbox">
    	<!-- //中间栏 -->
    	<div class="ve__layout-menus flexbox" :class="{'hidden': store.config.collapse}">
    		<aside class="ve__layout-aside flexbox flex-col">
    			<ChatNew />
    			<Scrollbar class="flex1" autohide size="4" gap="1">
    				<ChatList />
    			</Scrollbar>
    			<ExtraLink />
    			<Collapse />
    		</aside>
    	</div>
    
    	<!-- //右边栏 -->
    	<div class="ve__layout-main flex1 flexbox flex-col">
    		<!-- 主内容区 -->
    		<Main />
    	</div>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    <template>
    	<div class="vegpt__editor">
    		<div class="vegpt__editor-inner">
    			<Flex :gap="0">
    				<Popover placement="top" trigger="click" width="150">
    					<Button class="btn" type="link" icon="ve-icon-yuyin1" v-tooltip="{content: '发送语音', theme: 'light', arrow: false}"></Button>
    					<template #content>
    						<div class="flexbox flex-alignc flex-col" style="padding: 15px 0;">
    							<Icon name="ve-icon-yuyin" size="40" color="#0fa27e" />
    							<p class="fs-12 mb-15 c-999">网络不给力</p>
    							<Button size="small"><i style="background:#f00;border-radius:50%;box-shadow:0 1px 2px #999;margin-right:5px;height:8px;width:8px;"></i>开始讲话</Button>
    						</div>
    					</template>
    				</Popover>
    				<Button class="btn" type="link" v-tooltip="{content: '发送图片', theme: 'light', arrow: false}">
    					<Icon name="ve-icon-photo" size="16" cursor />
    					<input ref="uploadImgRef" type="file" title="" accept="image/*" @change="handleUploadImage" />
    				</Button>
    				<Input
    					class="flex1"
    					ref="editorRef"
    					v-model="editorText"
    					type="textarea"
    					:autosize="{maxRows: 4}"
    					clearable
    					placeholder="Prompt..."
    					@keydown="handleKeydown"
    					@clear="handleClear"
    					style="margin: 0 5px;"
    				/>
    				<Button class="btn" type="link" icon="ve-icon-submit" @click="handleSubmit"></Button>
    			</Flex>
    		</div>
    	</div>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    import { ref, watch } from 'vue'
    import { guid } from '@/utils'
    import { chatStore } from '@/store/modules/chat'
    
    const props = defineProps({
    	value: { type: [String, Number] }
    })
    const emit = defineEmits(['clear'])
    
    const chatState = chatStore()
    
    const uploadImgRef = ref()
    const editorRef = ref()
    const editorText = ref(props.value)
    
    // ...
    
    // 发送会话
    const handleSubmit = () => {
    	editorRef.value.focus()
    	if(!editorText.value) return
    
    	let data = {
    		type: 'text',
    		role: 'User',
    		key: guid(),
    		content: editorText.value
    	}
    	chatState.addSession(data)
    	// 清空
    	editorText.value = ''
    }
    const handleKeydown = (e) => {
    	// ctrl+enter
    	if(e.ctrlKey && e.keyCode == 13) {
    		handleSubmit()
    	}
    }
    const handleClear = () => {
    	emit('clear')
    }
    // 选择图片
    const handleUploadImage = () => {
    	let file = uploadImgRef.value.files[0]
    	if(!file) return
    	let size = Math.floor(file.size / 1024)
    	console.log(size)
    	if(size > 2*1024) {
    		Message.danger('图片大小不能超过2M')
    		uploadImgRef.value.value = ''
    		return false
    	}
    	let reader = new FileReader()
    	reader.readAsDataURL(file)
    	reader.onload = function() {
    		let img = this.result
    
    		let data = {
    			type: 'image',
    			role: 'User',
    			key: guid(),
    			content: img
    		}
    		chatState.addSession(data)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    在这里插入图片描述

    /**
     * 聊天状态管理
     * @author YXY  Q:282310962
     */
    
    import { defineStore } from 'pinia'
    import { guid, isEmpty } from '@/utils'
    
    export const chatStore = defineStore('chat', {
    	state: () => ({
    		// 聊天会话记录
    		sessionId: '',
    		session: []
    	}),
    	getters: {},
    	actions: {
    		// 创建新会话
    		createSession(ssid) {
    			this.sessionId = ssid
    			this.session.push({
    				sessionId: ssid,
    				title: '',
    				data: []
    			})
    		},
    
    		// 新增会话
    		addSession(message) {
    			// 判断当前会话uuid是否存在,不存在创建新会话
    			if(!this.sessionId) {
    				const ssid = guid()
    				this.createSession(ssid)
    			}
    			this.session.map(item => {
    				if(item.sessionId == this.sessionId) {
    					if(!item.title) {
    						item.title = message.content
    					}
    					item.data.push(message)
    				}
    			})
    			// ...
    		},
    
    		// 获取会话
    		getSession() {
    			return this.session.find(item => item.sessionId == this.sessionId)
    		},
    
    		// 移除会话
    		removeSession(ssid) {
    			const index = this.session.findIndex(item => item?.sessionId === ssid)
    			if(index > -1) {
    				this.session.splice(index, 1)
    			}
    			this.sessionId = ''
    		},
    		// 删除某一条会话
    		deleteSession(ssid) {
    			// ...
    		},
    
    		// 清空会话
    		clearSession() {
    			this.session = []
    			this.sessionId = ''
    		}
    	},
    	// 本地持久化存储(默认存储localStorage)
    	persist: true
    	/* persist: {
    		// key: 'chatStore', // 不设置则是默认app
    		storage: localStorage,
    		paths: ['aa', 'bb'] // 设置缓存键
    	} */
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    好了,基于vue3+vite4+pinia2开发模仿chatgpt聊天就分享到这里。

    Tauri-Vue3聊天实例|Tauri跨端聊天
    uniapp-ttlive短视频聊天|uniapp+uview仿抖音实例

    在这里插入图片描述

  • 相关阅读:
    TIA博途中通过SCATTER指令实现将字节BYTE拆分成单个位的具体方法示例
    docker 增加cpu线程数
    FFmpeg--packet数据包和frame数据帧的区别
    http协议详解(一)
    时间复杂度和空间复杂度详解
    【HDLBits 刷题 8】Circuits(4)Sequential Logic---Shifts Registers & More Circuits
    从一线撤回二三线城市的程序员们,最后都怎么样了?
    房贷结清后抵押注销及个税App专项扣除费修改
    c/c++开发时的VsCode插件
    Maven了解&运用配置
  • 原文地址:https://blog.csdn.net/yanxinyun1990/article/details/130539403