whx 5 日 前
コミット
a8cd4982d7

+ 24 - 0
virgo.wzfrontend/aiChat/.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
virgo.wzfrontend/aiChat/.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 5 - 0
virgo.wzfrontend/aiChat/README.md

@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

+ 14 - 0
virgo.wzfrontend/aiChat/index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="zh">
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<link rel="icon" href="https://file-node.oss-cn-shanghai.aliyuncs.com/youji/f9617c7f80da485cb3cc72b6accc62ed">
+		<title>WorkArk AI</title>
+		<link rel="stylesheet" href="/src/style.css" />
+	</head>
+	<body>
+		<div id="app"></div>
+		<script type="module" src="/src/main.js"></script>
+	</body>
+</html>

ファイルの差分が大きいため隠しています
+ 2321 - 0
virgo.wzfrontend/aiChat/package-lock.json


+ 24 - 0
virgo.wzfrontend/aiChat/package.json

@@ -0,0 +1,24 @@
+{
+	"name": "aichat",
+	"private": true,
+	"version": "0.0.0",
+	"type": "module",
+	"scripts": {
+		"dev": "vite",
+		"build": "vite build",
+		"preview": "vite preview"
+	},
+	"dependencies": {
+		"axios": "^1.11.0",
+		"element-plus": "^2.10.4",
+		"pinia": "^3.0.3",
+		"vue": "^3.5.17",
+		"vue-router": "^4.5.1"
+	},
+	"devDependencies": {
+		"@vitejs/plugin-vue": "^6.0.0",
+		"sass": "^1.89.2",
+		"sass-loader": "^12.6.0",
+		"vite": "^7.0.4"
+	}
+}

ファイルの差分が大きいため隠しています
+ 2147 - 0
virgo.wzfrontend/aiChat/pnpm-lock.yaml


ファイルの差分が大きいため隠しています
+ 1 - 0
virgo.wzfrontend/aiChat/public/vite.svg


+ 13 - 0
virgo.wzfrontend/aiChat/src/App.vue

@@ -0,0 +1,13 @@
+<script setup>
+	import {
+		RouterView
+	} from 'vue-router'
+</script>
+
+<template>
+	<RouterView />
+</template>
+
+<style lang="scss">
+
+</style>

+ 15 - 0
virgo.wzfrontend/aiChat/src/api/index.js

@@ -0,0 +1,15 @@
+import request from '../utils/request'
+
+export const getPosts = () => {
+	return request({
+		url: '/posts',
+		method: 'get'
+	})
+}
+
+export const getUser = (id) => {
+	return request({
+		url: `/users/${id}`,
+		method: 'get'
+	})
+}

+ 205 - 0
virgo.wzfrontend/aiChat/src/api/login.js

@@ -0,0 +1,205 @@
+import request from '../utils/request'
+
+/* 
+ * 获取图片验证码
+ * 
+ */
+export function getImgCode() {
+	return request({
+		url: `/manager/pCode`,
+		method: 'get'
+	})
+}
+/* 
+ * 发送手机验证码
+ * 
+ */
+export function sendPhoneCode(phone, vCode) {
+	return request({
+		url: `/manager/send/${phone}/${vCode}`,
+		method: 'get'
+	})
+}
+/* 
+ * 登录
+ * 
+ */
+export function login(data) {
+	return request({
+		url: `/manager/login`,
+		method: 'post',
+		data: data
+	})
+}
+
+/* 
+ * 获取当前登录者信息
+ * @param null
+ * 
+ */
+export function getUserInfo(data) {
+	return request({
+		url: `/manager/userContext`,
+		method: 'get'
+	})
+}
+/* 
+ * 获取用户信息
+ * 
+ * 
+ */
+export function getUserInfoById(userId) {
+	return request({
+		url: `/manager/userInfo/${userId}`,
+		method: 'get'
+	})
+}
+/* 
+ * 切换组织
+ * @param null 
+ */
+export function selectOrangaized(data) {
+	return request({
+		url: `/manager/userContext/organization`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 切换项目
+ * @param null 
+ */
+export function selectProject(projectId) {
+	return request({
+		url: `/manager/userContext/project/${projectId}`,
+		method: 'put',
+	})
+}
+/* 
+ * 切换身份
+ * @param null 
+ */
+export function selectIdentity(identityId) {
+	return request({
+		url: `/manager/userContext/identity/${identityId}`,
+		method: 'put'
+	})
+}
+/* 
+ * 根据组织获取项目列表
+ * @param null 
+ * 
+ */
+export function getOrganizedProjectList(organizationId) {
+	return request({
+		url: `/api/project/getOrganization/${organizationId}/`,
+		method: 'get',
+	})
+}
+/* 
+ * 修改个人信息
+ * @param {Object} data = {loginName用户名|pwd密码|name昵称|sex性别|phone手机号|email邮箱|portrait头像|organizationId组织类型}
+ * 
+ */
+export function updateUserDetails(data) {
+	return request({
+		url: `/manager/userInfo/update`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 刷新上下文
+ * @param null 
+ * 
+ */
+export function refresh() {
+	return request({
+		url: `/manager/userContext/refresh`,
+		method: 'get',
+	})
+}
+/* 
+ * 获取项目组织下个人信息
+ * 
+ */
+export function getOperationUserInfo(data) {
+	return request({
+		url: `/api/info/get`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 新增项目组织下个人信息
+ * 
+ */
+export function insertOperationUserInfo(data) {
+	return request({
+		url: `/api/info`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 修改项目组织下个人信息
+ * 
+ */
+export function updateOperationUserInfo(data) {
+	return request({
+		url: `/api/info`,
+		method: 'put',
+		data: data
+	})
+}
+/* 
+ * 切换work组织
+ * @param null 
+ */
+export function selectWorkarkOrangaized(data) {
+	return request({
+		url: `/manager/userContext/workark/organization`,
+		method: 'post',
+		data: data
+	})
+}
+/* 
+ * 获取work组织
+ * @param null 
+ */
+export function getWorkarkOrangaized(data) {
+	return request({
+		url: `/manager/userContext/workark/organization`,
+		method: 'get'
+	})
+}
+/* 
+ * 获取邀请码
+ * 
+ */
+export function getInviteQr() {
+	return request({
+		url: `/manager/inviteQr`,
+		method: 'get'
+	})
+}
+/* 
+ * 获取邀请码
+ * 
+ */
+export function getInviteUserListByPage(currPage,pageSize) {
+	return request({
+		url: `/manager/inviteQr/${currPage}/${pageSize}`,
+		method: 'get'
+	})
+}
+/* 
+ * 获取个人账户佣金
+ * 
+ */
+export function getAccountBalance() {
+	return request({
+		url: `/api/accountBalance`,
+		method: 'get'
+	})
+}

+ 12 - 0
virgo.wzfrontend/aiChat/src/assets/scss/variables.scss

@@ -0,0 +1,12 @@
+/* just override what you need */
+@forward 'element-plus/theme-chalk/src/common/var.scss' with (
+  $colors: (
+    'primary': (
+      'base': #7a6dff,
+    ),
+  )
+);
+
+// If you just import on demand, you can ignore the following content.
+// 如果你想导入所有样式:
+@use "element-plus/theme-chalk/src/index.scss" as *;

+ 336 - 0
virgo.wzfrontend/aiChat/src/components/Login.vue

@@ -0,0 +1,336 @@
+<script setup>
+	import {
+		ref,
+		reactive,
+		onMounted,
+		onUnmounted
+	} from 'vue'
+	import {
+		Cellphone,
+		Picture,
+		Iphone,
+		Lock
+	} from '@element-plus/icons-vue'
+	import {
+		getImgCode,
+		sendPhoneCode,
+		login,
+		getUserInfo
+	} from '@/api/login'
+	import {
+		ElMessage
+	} from 'element-plus'
+
+	import {
+		useCommonStore,
+		useUserStore
+	} from '@/store'
+	const emits = defineEmits(['callback']);
+	const commonStore = useCommonStore();
+	const userStore = useUserStore()
+	const nowTab = ref({
+		id: 1,
+		name: '手机验证码登录'
+	})
+	const tabs = ref([{
+		id: 1,
+		name: '手机验证码登录'
+	}, {
+		id: 2,
+		name: '账号密码登录'
+	}])
+	const loginForm = reactive({
+		phone: '',
+		code: '',
+		phoneCode: '',
+		password: ''
+	})
+	const loginRules = ref({
+		code: [{
+			required: true,
+			message: '请输入图片验证码',
+			trigger: 'blur'
+		}],
+		phone: [{
+			required: true,
+			message: '请输入手机号',
+			trigger: 'blur'
+		}, {
+			validator: (rule, value, callback) => {
+				if (!/^1[123456789]\d{9}$/.test(value)) {
+					callback(new Error("请输入正确的手机号"));
+				} else {
+					callback();
+				}
+			},
+			trigger: 'blur'
+		}],
+		phoneCode: [{
+			required: true,
+			message: '请输入短信验证码',
+			trigger: 'blur'
+		}]
+	})
+	const codeName = ref('获取验证码');
+	onMounted(() => {
+		init();
+		if (commonStore.codeNumber != 60) codeReset();
+	})
+	const init = () => {
+		imgCodeFunc();
+	}
+	const codeImg = ref('')
+	const imgCodeFunc = async () => {
+		let imgCode = await getImgCode();
+		if (imgCode.state) codeImg.value = imgCode.data.pngBase64;
+	}
+	const loginFormRef = ref(null)
+	const getPhoneCode = () => {
+		loginFormRef.value.validateField('phone', valid => {
+			if (valid) {
+				loginFormRef.value.validateField('code', async valid => {
+					if (valid) {
+						let sendData = await sendPhoneCode(loginForm.phone, loginForm.code);
+						if (sendData.state) {
+							ElMessage({
+								message: '发送成功',
+								type: 'success',
+							});
+							codeReset();
+						}
+					}
+				})
+			}
+		})
+	}
+	const timer = ref(null);
+	const codeReset = () => {
+		//重置获取验证码倒计时
+		let codeNumber = commonStore.codeNumber;
+		codeNumber--;
+		handleCode(codeNumber);
+		timer.value = setInterval(() => {
+			codeNumber--;
+			handleCode(codeNumber);
+			if (codeNumber == 0) clearInterval(timer.value); //停止
+		}, 1000);
+	}
+	const handleCode = (codeNumber) => {
+		//code操作
+		codeName.value = codeNumber == 0 ? '获取验证码' : '重新获取' + codeNumber;
+		commonStore.setCodeNumber(codeNumber == 0 ? 60 : codeNumber);
+	}
+	onUnmounted(() => {
+		if (timer.value) clearInterval(timer.value);
+	})
+	const loginSubmit = () => {
+		if (loginLoading.value) return;
+		loginLoading.value = true;
+		loginFormRef.value.validate(valid => {
+			if (!valid) return loginLoading.value = false;
+			loginFunc();
+		})
+	}
+	const loginLoading = ref(false)
+	const loginFunc = async () => {
+		let postData = nowTab.id === 1 ? {
+			phone: loginForm.phone,
+			phoneCode: loginForm.phoneCode
+		} : {
+			phone: loginForm.phone,
+			password: loginForm.password
+		};
+		let loginData = await login(postData);
+		if (loginData.state) {
+			userStore.setToken(loginData.data.token);
+			console.log(userStore.token);
+			let userData = await getUserInfo();
+			if (userData.state) {
+				let user = userData.data;
+				userStore.setUserData(user);
+				setTimeout(() => {
+					successLogin();
+				}, 500)
+			} else {
+				loginLoading.value = false;
+			}
+		} else {
+			loginLoading.value = false;
+		}
+	}
+	const successLogin = () => {
+		loginLoading.value = false;
+		emits('callback')
+		ElMessage({
+			message: '登录成功',
+			type: 'success',
+		});
+	}
+</script>
+<template>
+	<div class="website-login-box">
+		<div class="login-form">
+			<div class="login-tab" :class="{
+					active1:nowTab.id === 1,
+					active2:nowTab.id === 2
+				}">
+				<div class="tab-item" :class="{
+					active:nowTab.id === tab.id
+				}" v-for="(tab, index) in tabs" :key="index" @click="nowTab = tab">
+					{{ tab.name }}
+				</div>
+			</div>
+			<el-form :model="loginForm" ref="loginFormRef" :rules="loginRules" status-icon label-position="left">
+				<el-form-item prop="phone">
+					<el-input type="text" :prefix-icon="Cellphone" v-model="loginForm.phone" placeholder="手机号"
+						maxlength="11">
+					</el-input>
+				</el-form-item>
+				<el-form-item prop="code" class="image-item" v-if="nowTab.id === 1">
+					<el-input type="text" :prefix-icon="Picture" v-model="loginForm.code" placeholder="图片验证码"
+						maxlength="4"></el-input>
+					<div class="code-image" @click="imgCodeFunc">
+						<div><img v-if="codeImg" :src="codeImg" alt="图片验证码" /></div>
+						<p class="color-blue">看不清?点击刷新</p>
+					</div>
+				</el-form-item>
+				<el-form-item prop="phoneCode" class="phone-code" v-if="nowTab.id === 1">
+					<el-input type="number" :prefix-icon="Iphone" v-model="loginForm.phoneCode" placeholder="短信验证码"
+						oninput="if(value.length > 6) value=value.slice(0, 6)">
+					</el-input>
+					<div class="get-code-btn">
+						<el-button type="primary" size="default" @click="getPhoneCode" :disabled="codeName !== '获取验证码'">
+							{{codeName}}
+						</el-button>
+					</div>
+				</el-form-item>
+				<el-form-item prop="password" v-if="nowTab.id === 2">
+					<el-input type="password" :prefix-icon="Lock" v-model="loginForm.password" :show-password="true"
+						placeholder="请输入密码">
+					</el-input>
+				</el-form-item>
+			</el-form>
+			<el-button class="submit" size="large" type="primary" @click="loginSubmit"
+				:loading="loginLoading">登录</el-button>
+		</div>
+	</div>
+</template>
+
+<style lang="scss" scoped>
+	.login-tab {
+		display: flex;
+		border: 1px solid #bebebe;
+		margin-bottom: 28px;
+		height: 36px;
+		border-radius: 4px;
+		align-items: center;
+		position: relative;
+
+		&:before {
+			content: '';
+			position: absolute;
+			background: #bebebe;
+			width: 159px;
+			height: 28px;
+			border-radius: 4px;
+			z-index: 8;
+			transition: left 0.3s;
+		}
+
+		&.active1:before {
+			left: 4px;
+		}
+
+		&.active2:before {
+			left: 212px;
+		}
+	}
+
+	.tab-item {
+		flex: 1;
+		text-align: center;
+		z-index: 9;
+		cursor: pointer;
+		transition: color 0.3s;
+
+		&.active {
+			color: #fff;
+		}
+	}
+
+	.website-login-box {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.login-form {
+		width: 100%;
+		padding: 15px;
+		box-sizing: border-box;
+	}
+
+	:deep(.el-form) {
+		display: block;
+
+		.el-form-item {
+			width: 100%;
+			padding: 0;
+			margin-bottom: 28px;
+			position: relative;
+		}
+
+		.el-input__inner {
+			height: 52px;
+			line-height: 52px;
+			font-size: 16px;
+		}
+
+
+
+		.el-input__prefix {
+			left: 16px;
+
+			.el-input__icon {
+				line-height: 52px;
+				font-size: 22px;
+			}
+		}
+
+		.image-item {
+			.el-form-item__content {
+				display: flex;
+			}
+
+			.el-input {
+				flex: 1;
+				width: 0;
+				margin-right: 12px;
+			}
+
+			.code-image {
+				width: 131px;
+				height: 50px;
+				cursor: pointer;
+			}
+
+			.color-blue {
+				font-size: 12px;
+				text-align: center;
+			}
+		}
+
+		.get-code-btn {
+			position: absolute;
+			right: 30px;
+		}
+	}
+
+	.submit {
+		width: 100%;
+		font-weight: 600;
+		font-size: 18px;
+	}
+</style>

+ 13 - 0
virgo.wzfrontend/aiChat/src/config/index.js

@@ -0,0 +1,13 @@
+const modeUrlObj = {
+	'production': { // 生产环境
+		baseURL: ''
+	},
+	'development': { // 开发环境
+		baseURL: '/apiV1'
+	},
+	'test': { // 测试环境
+		baseURL: ''
+	}
+}
+
+export default modeUrlObj[process.env.NODE_ENV]

+ 17 - 0
virgo.wzfrontend/aiChat/src/main.js

@@ -0,0 +1,17 @@
+import {
+	createApp
+} from 'vue'
+import App from './App.vue'
+import router from './router'
+// 引入SCSS
+import '@/assets/scss/variables.scss'
+import ElementPlus from 'element-plus'
+
+import pinia from './store'
+
+const app = createApp(App)
+app.use(pinia)
+app.use(router)
+app.use(ElementPlus)
+
+app.mount('#app')

+ 17 - 0
virgo.wzfrontend/aiChat/src/router/index.js

@@ -0,0 +1,17 @@
+import {
+	createRouter,
+	createWebHashHistory
+} from 'vue-router'
+
+const routes = [{
+	path: '/',
+	name: 'Home',
+	component: () => import('../views/Home.vue')
+}]
+
+const router = createRouter({
+	history: createWebHashHistory(import.meta.env.BASE_URL),
+	routes
+})
+
+export default router

+ 18 - 0
virgo.wzfrontend/aiChat/src/store/index.js

@@ -0,0 +1,18 @@
+import {
+	createPinia
+} from 'pinia'
+import registerPlugin from "./plugins/registerPlugin"
+import {
+	useCommonStore
+} from '@/store/modules/common.js'
+import {
+	useUserStore
+} from '@/store/modules/user.js'
+const pinia = createPinia();
+pinia.use(registerPlugin); // 使用插件
+export default pinia;
+
+export {
+	useUserStore,
+	useCommonStore
+}

+ 165 - 0
virgo.wzfrontend/aiChat/src/store/modules/chat.js

@@ -0,0 +1,165 @@
+import {
+	defineStore
+} from 'pinia'
+import {
+	ref,
+	computed
+} from 'vue'
+import {
+	nanoid
+} from 'nanoid'
+
+export const useChatStore = defineStore('chat', () => {
+	// 对话列表
+	const conversations = ref([{
+			id: 'conv1',
+			title: 'Vue3项目咨询',
+			createdAt: new Date(2023, 6, 15),
+			messages: [{
+					id: 'msg1',
+					content: '如何搭建Vue3项目?',
+					sender: 'user',
+					timestamp: new Date(2023, 6, 15, 10, 30)
+				},
+				{
+					id: 'msg2',
+					content: '您可以使用Vite创建Vue3项目:`npm create vite@latest my-vue-app -- --template vue`',
+					sender: 'ai',
+					timestamp: new Date(2023, 6, 15, 10, 32)
+				}
+			]
+		},
+		{
+			id: 'conv2',
+			title: '状态管理方案',
+			createdAt: new Date(2023, 6, 16),
+			messages: [{
+					id: 'msg1',
+					content: 'Vue3推荐的状态管理方案是什么?',
+					sender: 'user',
+					timestamp: new Date(2023, 6, 16, 14, 15)
+				},
+				{
+					id: 'msg2',
+					content: 'Pinia是Vue3官方推荐的状态管理库,它提供了更简洁的API和TypeScript支持。',
+					sender: 'ai',
+					timestamp: new Date(2023, 6, 16, 14, 17)
+				}
+			]
+		}
+	])
+
+	// 当前选中的对话ID
+	const activeConversationId = ref(conversations.value[0]?.id || null)
+
+	// 获取当前对话
+	const activeConversation = computed(() => {
+		return conversations.value.find(conv => conv.id === activeConversationId.value)
+	})
+
+	// 创建新对话
+	function createConversation() {
+		const newConv = {
+			id: nanoid(),
+			title: '新对话',
+			createdAt: new Date(),
+			messages: []
+		}
+		conversations.value.unshift(newConv)
+		activeConversationId.value = newConv.id
+		return newConv
+	}
+
+	// 删除对话
+	function deleteConversation(id) {
+		const index = conversations.value.findIndex(conv => conv.id === id)
+		if (index !== -1) {
+			conversations.value.splice(index, 1)
+			if (activeConversationId.value === id) {
+				activeConversationId.value = conversations.value[0]?.id || null
+			}
+		}
+	}
+
+	// 发送消息
+	function sendMessage(content) {
+		if (!activeConversationId.value || !content.trim()) return
+
+		const conversation = conversations.value.find(conv => conv.id === activeConversationId.value)
+		if (!conversation) return
+
+		// 添加用户消息
+		const userMessage = {
+			id: nanoid(),
+			content: content.trim(),
+			sender: 'user',
+			timestamp: new Date()
+		}
+		conversation.messages.push(userMessage)
+
+		// 更新对话标题(如果是第一条消息)
+		if (conversation.messages.length === 1) {
+			conversation.title = content.trim().substring(0, 20) + (content.length > 20 ? '...' : '')
+		}
+
+		// 模拟AI回复
+		simulateAIResponse(conversation)
+	}
+
+	// 模拟AI回复
+	function simulateAIResponse(conversation) {
+		setTimeout(() => {
+			const aiMessage = {
+				id: nanoid(),
+				content: generateAIResponse(conversation.messages[conversation.messages.length - 1]
+					.content),
+				sender: 'ai',
+				timestamp: new Date()
+			}
+			conversation.messages.push(aiMessage)
+		}, 1000 + Math.random() * 1000)
+	}
+
+	// 生成AI回复(模拟)
+	function generateAIResponse(userMessage) {
+		const responses = [
+			"我理解你的问题。Vue3提供了Composition API,它允许你更灵活地组织组件逻辑。",
+			"对于性能优化,建议使用Vue3的`<script setup>`语法糖和响应式API。",
+			"路由方面,Vue Router 4与Vue3完美集成,提供了动态路由和导航守卫功能。",
+			"状态管理方面,Pinia是Vue3的官方推荐,它比Vuex更简洁且支持TypeScript。",
+			"你可以使用Vite作为构建工具,它提供了极快的启动速度和热模块替换。",
+			"对于UI库,Element Plus、Vuetify 3和Quasar都是很好的选择。",
+			"要处理API请求,可以使用Axios并创建请求拦截器来处理身份验证。",
+			"建议使用Vue Devtools调试应用,它支持Vue3的新特性。",
+			"对于表单处理,VeeValidate或Vuelidate是不错的验证库选择。",
+			"要优化打包大小,可以使用Vite的代码分割和Tree Shaking功能。"
+		]
+
+		const keywordResponses = {
+			"路由": "Vue Router 4是Vue3的官方路由库,支持动态路由匹配和嵌套路由。",
+			"状态": "Pinia提供状态管理,具有响应式API和模块化设计。",
+			"组件": "Vue3的单文件组件(SFC)支持`<script setup>`语法,使代码更简洁。",
+			"请求": "可以使用Axios或Fetch API进行HTTP请求,建议封装为服务。",
+			"优化": "使用Vite构建、代码分割和异步组件可以提高性能。"
+		}
+
+		// 检查关键词
+		for (const [keyword, response] of Object.entries(keywordResponses)) {
+			if (userMessage.includes(keyword)) {
+				return response
+			}
+		}
+
+		// 随机回复
+		return responses[Math.floor(Math.random() * responses.length)]
+	}
+
+	return {
+		conversations,
+		activeConversationId,
+		activeConversation,
+		createConversation,
+		deleteConversation,
+		sendMessage
+	}
+})

+ 14 - 0
virgo.wzfrontend/aiChat/src/store/modules/common.js

@@ -0,0 +1,14 @@
+import {
+	defineStore
+} from 'pinia'
+
+export const useCommonStore = defineStore('common', {
+	state: () => ({
+		codeNumber: 60
+	}),
+	actions: {
+		setCodeNumber(codeNumber) {
+			this.codeNumber = codeNumber;
+		}
+	}
+})

+ 21 - 0
virgo.wzfrontend/aiChat/src/store/modules/user.js

@@ -0,0 +1,21 @@
+import {
+	defineStore
+} from 'pinia'
+
+export const useUserStore = defineStore('user', {
+	state: () => ({
+		userData: {},
+		token: null
+	}),
+	actions: {
+		setToken(token) {
+			this.token = token;
+		},
+		setUserData(userData) {
+			this.userData = userData;
+		},
+		logout() {
+			this.token = null;
+		}
+	}
+})

+ 24 - 0
virgo.wzfrontend/aiChat/src/store/plugins/registerPlugin.js

@@ -0,0 +1,24 @@
+const KEY_PREFIX = "PINIA:STATE:";
+
+export default function(context) {
+	const {
+		store
+	} = context;
+
+	//注册页面卸载和刷新行为事件
+	window.addEventListener("beforeunload", function() {
+		alert(JSON.stringify(store.$state));
+		sessionStorage.setItem(KEY_PREFIX + store.$id, JSON.stringify(store.$state));
+	});
+	// 读取数据
+	try {
+		const state = JSON.parse(sessionStorage.getItem(KEY_PREFIX + store.$id));
+		if (state) {
+			// 更新状态
+			store.$patch(state);
+			sessionStorage.removeItem(KEY_PREFIX + store.$id); //重新置完清空session
+		}
+	} catch (error) {
+		console.log("数据有误,无法存储");
+	}
+}

+ 175 - 0
virgo.wzfrontend/aiChat/src/style.css

@@ -0,0 +1,175 @@
+/* 禁用iPhone中Safari的字号自动调整 */
+* {
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
+	outline: none;
+	font-family: LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
+	font-weight: 400;
+	-webkit-font-smoothing: antialiased;
+	user-select: none;
+}
+
+html {
+	-webkit-text-size-adjust: 100%;
+	-ms-text-size-adjust: 100%;
+}
+
+/* 去除iPhone中默认的input样式 */
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input,
+button {
+	/* -webkit-appearance:none; */
+	resize: none;
+}
+
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+	-webkit-appearance: none;
+}
+
+button {
+	border: none;
+}
+
+/* 设置HTML5元素为块 */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+	display: block;
+}
+
+/* 图片自适应 */
+img {
+	/*max-width: 100%;*/
+	height: auto;
+	width: auto\9;
+	/* ie8 */
+	-ms-interpolation-mode: bicubic;
+	/*为了照顾ie图片缩放失真*/
+}
+
+/* 初始化 */
+body,
+div,
+ul,
+li,
+ol,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+input,
+textarea,
+select,
+p,
+dl,
+dt,
+dd,
+a,
+img,
+button,
+form,
+table,
+th,
+tr,
+td,
+tbody,
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+	margin: 0;
+	padding: 0;
+	border: none;
+	-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+	/*取消链接高亮*/
+	box-sizing: border-box;
+}
+
+
+em,
+i {
+	font-style: normal;
+}
+
+strong {
+	font-weight: normal;
+}
+
+.clearfix:after {
+	content: "";
+	display: block;
+	visibility: hidden;
+	height: 0;
+	clear: both;
+}
+
+.clearfix {
+	zoom: 1;
+}
+
+a {
+	text-decoration: none;
+}
+
+a:hover,
+a:active,
+a:visited {
+	text-decoration: none;
+}
+
+ul,
+ol {
+	list-style: none;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+	font-size: 100%;
+}
+
+img {
+	border: none;
+	vertical-align: middle;
+}
+
+body,
+html,
+#app {
+	width: 100%;
+	height: 100%;
+	margin: 0 auto;
+	position: relative;
+}
+
+.clear-both {
+	clear: both;
+}
+
+body {
+	font-size: 14px;
+}

+ 61 - 0
virgo.wzfrontend/aiChat/src/utils/request.js

@@ -0,0 +1,61 @@
+import axios from 'axios'
+import config from '@/config'
+import {
+	ElMessage
+} from 'element-plus'
+import {
+	useUserStore
+} from '@/store'
+const userStore = useUserStore();
+const baseURL = config.baseURL;
+const service = axios.create({
+	timeout: 6000000, // 请求超时时间
+})
+/* 消息提示 */
+const tip = (msg, type) => {
+	let types = type || 'warning';
+	if (msg == 'RET_INVALID_PASSWORD') {
+		msg = '账号密码有误'
+	}
+	if (msg == 'RET_INVALID_CODE') {
+		msg = '短信验证码有误'
+	}
+	ElMessage({
+		message: msg,
+		type: types,
+		duration: 2000
+	});
+}
+// request 拦截器
+service.interceptors.request.use(
+	config => {
+		let token = userStore.token;
+		config.headers['token'] = token;
+		config['url'] = baseURL + config.url;
+		return config
+	},
+	error => {
+		Promise.reject(error)
+	}
+)
+// response 拦截器
+service.interceptors.response.use(
+	response => {
+		const res = response.data;
+		let url = response.config.url;
+		if (!res.code) return res;
+		if (res.code != '200' && res.code != '20001') tip(res.message || res.msg);
+		return {
+			state: res.code == '200',
+			data: res.data,
+			msg: res.code == '200' ? 'success' : 'error'
+		}
+	},
+	error => { //请求返回错误
+		return {
+			state: false
+		}
+	}
+)
+
+export default service;

+ 343 - 0
virgo.wzfrontend/aiChat/src/views/Home.vue

@@ -0,0 +1,343 @@
+<script setup>
+	import {
+		Top,
+		Paperclip,
+		CircleCloseFilled,
+		Plus,
+		UserFilled
+	} from '@element-plus/icons-vue'
+	import {
+		ElMessage,
+		ElMessageBox
+	} from 'element-plus'
+	import {
+		ref,
+		watch,
+		computed,
+		onMounted
+	} from 'vue'
+	import {
+		useUserStore
+	} from '@/store'
+	import Login from '@/components/Login.vue'
+	const userStore = useUserStore()
+	const message = ref('');
+	const typeList = ref([{
+		id: 1,
+		name: '网站'
+	}, {
+		id: 2,
+		name: '移动端'
+	}]);
+	const typeValue = ref(1);
+	const generateTypeList = {
+		1: [{
+			id: 1,
+			name: '企业官网'
+		}, {
+			id: 2,
+			name: '电商网站'
+		}, {
+			id: 3,
+			name: '服务网站'
+		}],
+		2: [{
+			id: 1,
+			name: '企业介绍'
+		}, {
+			id: 2,
+			name: '物业服务'
+		}]
+	}
+	const generateValue = ref(1);
+	const generateList = computed(() => {
+		generateValue.value = 1;
+		return generateTypeList[typeValue.value];
+	});
+	const loading = ref(false);
+	const websiteURL = ref('');
+	const commandFunction = (command) => {
+		if (command === 'url') openUrl();
+	}
+	const openUrl = () => {
+		ElMessageBox.prompt('请输入参考网站网址', 'WorkArk.AI提示', {
+			confirmButtonText: '确认',
+			cancelButtonText: '取消',
+			inputPattern: /./,
+			inputErrorMessage: '请输入网址',
+			inputValue: websiteURL.value
+		}).then(({
+			value
+		}) => {
+			websiteURL.value = value;
+		}).catch(() => {});
+	}
+	const imageList = ref([{
+		id: 1,
+		url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
+	}])
+	const removeImage = (index) => {
+		ElMessageBox.confirm('是否删除该图片?', 'WorkArk.AI提示', {
+			confirmButtonText: '确认',
+			cancelButtonText: '取消',
+			type: 'warning',
+		}).then(() => {
+			imageList.value.splice(index, 1);
+		}).catch(() => {});
+	}
+	const loginVisible = ref(false);
+	const callback = () => {
+		loginVisible.value = false;
+		init();
+	}
+	const user = ref({});
+	const init = () => {
+		user.value = userStore.userData;
+		console.log(JSON.stringify(user.value));
+	}
+	onMounted(() => {
+		init();
+	})
+</script>
+
+<template>
+	<div class="home-container">
+		<div class="home-nav">
+			<div class="home-nav-left">
+				<img class="img"
+					src="https://file-node.oss-cn-shanghai.aliyuncs.com/youji/f9617c7f80da485cb3cc72b6accc62ed"
+					alt="logo.png" />
+				<span class="title">WorkArk AI</span>
+			</div>
+			<div class="home-nav-right">
+				<div class="item no-token" @click="loginVisible = true" v-if="!user.userId">
+					<el-avatar :size="36" :icon="UserFilled"></el-avatar>
+					<span class="name">登录/注册</span>
+				</div>
+				<div class="item no-token" v-else>
+					<el-avatar :size="36" :src="user.portrait"></el-avatar>
+					<span class="name">{{user.userName}}</span>
+				</div>
+			</div>
+		</div>
+		<div class="home-form">
+			<div class="form-title">
+				<h1 class="big-title"><span>Build By </span><span class="primary">WorkArk.AI</span></h1>
+				<p class="small-title">通过与AI聊天创建应用程序和网站</p>
+			</div>
+			<div class="form-box">
+				<el-input type="textarea" v-model="message" placeholder="生成一个信息科技企业官网" resize="none" :rows="5"
+					:autosize="{ minRows: 3, maxRows: 7 }">
+				</el-input>
+				<div class="form-submit">
+					<div class="form-operation">
+						<el-dropdown @command="commandFunction">
+							<el-button size="default" :icon="Paperclip" circle></el-button>
+							<template #dropdown>
+								<el-dropdown-menu>
+									<el-dropdown-item command="url">参考网站</el-dropdown-item>
+									<el-dropdown-item command="image">上传图片</el-dropdown-item>
+								</el-dropdown-menu>
+							</template>
+						</el-dropdown>
+						<el-button style="margin-left: 10px;" size="default" :icon="Plus" circle></el-button>
+					</div>
+					<el-button type="primary" size="large" :icon="Top" circle :loading="loading"></el-button>
+				</div>
+				<div class="operation-file" v-if="websiteURL || imageList.length > 0">
+					<div class="operation-url" v-if="websiteURL">
+						<el-tag type="info" closable effect="plain" round @close="websiteURL = ''">
+							{{websiteURL}}
+						</el-tag>
+					</div>
+					<div class="operation-image" v-if="imageList.length > 0">
+						<div class="image-item" v-for="(image,index) in imageList" :key="image.id">
+							<el-image style="width: 60px; height: 60px" :src="image.url" fit="cover" />
+							<el-icon class="image-icon" @click="removeImage(index)">
+								<CircleCloseFilled />
+							</el-icon>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<el-dialog v-model="loginVisible" title="登录" width="440px">
+			<login v-if="loginVisible" @callback="callback"></login>
+		</el-dialog>
+	</div>
+</template>
+<style lang="scss">
+	.home-container {
+		width: 100%;
+		height: 100%;
+		background-image: linear-gradient(135deg, #8ab1ff, #8834ef, #ff8a66);
+		display: flex;
+		flex-direction: column;
+
+		.home-nav {
+			background: rgba(255, 255, 255, 0.1);
+			height: 70px;
+			border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+			display: flex;
+			align-items: center;
+			padding: 0 24px;
+			justify-content: space-between;
+
+			.home-nav-left {
+				display: flex;
+				align-items: center;
+			}
+
+			.title {
+				font-weight: 600;
+				color: #000;
+				font-size: 20px;
+				margin-left: 15px;
+			}
+
+			.img {
+				width: 32px;
+				height: 32px;
+			}
+
+			.home-nav-right {
+				.item {
+					display: flex;
+					align-items: center;
+				}
+
+				.no-token {
+					cursor: pointer;
+				}
+
+				.name {
+					margin-left: 10px;
+				}
+
+				.el-avatar {
+					background: var(--el-color-primary);
+
+					img {
+						background: #fff;
+					}
+				}
+			}
+
+		}
+
+		.home-form {
+			padding: 24px;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			flex: 1;
+			height: 0;
+		}
+
+		.form-title {
+
+			.big-title {
+				text-align: center;
+
+				span {
+					font-size: 72px;
+					font-weight: bold;
+				}
+
+				.primary {
+					color: rgba(255, 255, 255, 0.4);
+				}
+			}
+
+			.img {
+				width: 44px;
+				height: 44px;
+			}
+
+			.small-title {
+				text-align: center;
+				font-size: 24px;
+				opacity: 0.8;
+				margin-top: 20px;
+			}
+
+		}
+
+		.form-box {
+			border: 1px solid #e2e8f0;
+			background: rgba(255, 255, 255, 0.8);
+			border-radius: 30px;
+			width: 100%;
+			max-width: 800px;
+			margin-top: 60px;
+			padding: 16px 16px 6px 16px;
+
+			.el-textarea__inner {
+				border: none;
+				background: transparent;
+				box-shadow: none;
+
+				&:hover {
+					box-shadow: none;
+				}
+			}
+
+			.form-submit {
+				margin-top: 10px;
+				display: flex;
+				align-items: center;
+
+				.form-operation {
+					flex: 1;
+					width: 0;
+					overflow: hidden;
+					display: flex;
+				}
+
+				.iconfont-item {
+					width: 30px;
+					height: 30px;
+					border: 1px solid var(--el-border-color);
+					background: #fff;
+					border-radius: 30px;
+					margin-right: 10px;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					cursor: pointer;
+					font-size: 16px;
+					color: #606266;
+					box-sizing: border-box;
+
+				}
+
+				.el-select__wrapper {
+					border-radius: 24px;
+				}
+			}
+
+			.operation-file {
+				padding: 10px 0;
+			}
+
+			.operation-image {
+				display: flex;
+			}
+
+			.image-item {
+				position: relative;
+			}
+
+			.image-icon {
+				position: absolute;
+				right: -6px;
+				top: -6px;
+				background: #fff;
+				border-radius: 6px;
+				cursor: pointer;
+				color: var(--el-color-error);
+			}
+		}
+	}
+</style>

+ 27 - 0
virgo.wzfrontend/aiChat/vite.config.js

@@ -0,0 +1,27 @@
+import {
+	defineConfig
+} from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path'
+
+// https://vite.dev/config/
+export default defineConfig({
+	base: './',
+	plugins: [vue()],
+	resolve: {
+		alias: {
+			'@': path.resolve(__dirname, './src')
+		}
+	},
+	server: {
+		proxy: {
+			'/apiV1': { // 配置需要代理的路径 --> 这里的意思是代理http://localhost:80/api/后的所有路由
+				target: 'https://www.workark.com', // 目标地址 --> 服务器地址
+				changeOrigin: true, // 允许跨域
+				ws: true, // 允许websocket代理
+				// 重写路径 --> 作用与vue配置pathRewrite作用相同
+				rewrite: (path) => path.replace(/^\/apiV1/, "")
+			}
+		},
+	}
+})