+ Visit vuejs.org to read the + documentation +
+ + + diff --git a/frontend/src/components/common/README.md b/frontend/src/components/common/README.md new file mode 100644 index 0000000..e5065b2 --- /dev/null +++ b/frontend/src/components/common/README.md @@ -0,0 +1,26 @@ +# 通用组件 (Common Components) + +## 目的 +存放可在整个应用中复用的通用组件。 + +## 内容 +- **布局组件**: 头部、侧边栏、底部等布局组件 +- **导航组件**: 菜单、面包屑、标签页等导航组件 +- **UI组件**: 按钮、输入框、模态框等基础UI组件 +- **加载组件**: 加载动画、骨架屏等状态组件 + +## 特点 +- 高度可复用 +- 无业务逻辑依赖 +- 支持主题定制 +- 响应式设计 + +## 示例组件 +```javascript +// AppHeader.vue - 应用头部 +// AppSidebar.vue - 侧边栏 +// LoadingSpinner.vue - 加载动画 +// ConfirmDialog.vue - 确认对话框 +// UserAvatar.vue - 用户头像 +// StatusBadge.vue - 状态标识 +``` diff --git a/frontend/src/components/editor/README.md b/frontend/src/components/editor/README.md new file mode 100644 index 0000000..442a01c --- /dev/null +++ b/frontend/src/components/editor/README.md @@ -0,0 +1,26 @@ +# 编辑器组件 (Editor Components) + +## 目的 +存放与实时文档编辑相关的专用组件。 + +## 内容 +- **文本编辑器**: 富文本编辑器、Markdown编辑器 +- **协作功能**: 实时光标、用户状态、编辑冲突处理 +- **编辑工具**: 工具栏、格式化按钮、插入功能 +- **预览组件**: 文档预览、版本对比等 + +## 特点 +- 实时协作编辑 +- 冲突检测和解决 +- 操作历史记录 +- 多用户光标显示 + +## 示例组件 +```javascript +// CollaborativeEditor.vue - 协作编辑器主组件 +// EditorToolbar.vue - 编辑器工具栏 +// UserCursor.vue - 用户光标指示器 +// EditHistory.vue - 编辑历史 +// DocumentPreview.vue - 文档预览 +// ConflictResolver.vue - 冲突解决器 +``` diff --git a/frontend/src/components/forms/README.md b/frontend/src/components/forms/README.md new file mode 100644 index 0000000..4d65243 --- /dev/null +++ b/frontend/src/components/forms/README.md @@ -0,0 +1,26 @@ +# 表单组件 (Form Components) + +## 目的 +存放与表单处理相关的专用组件。 + +## 内容 +- **输入组件**: 文本输入、密码输入、邮箱输入等 +- **选择组件**: 下拉选择、多选框、单选框等 +- **上传组件**: 文件上传、图片上传等 +- **验证组件**: 表单验证、错误提示等 + +## 特点 +- 统一的表单验证 +- 数据双向绑定 +- 错误状态处理 +- 自定义验证规则 + +## 示例组件 +```javascript +// FormInput.vue - 表单输入框 +// FormSelect.vue - 下拉选择 +// FormTextarea.vue - 文本域 +// FileUpload.vue - 文件上传 +// FormValidator.vue - 表单验证器 +// PasswordStrength.vue - 密码强度指示器 +``` diff --git a/frontend/src/composables/README.md b/frontend/src/composables/README.md new file mode 100644 index 0000000..0b8867e --- /dev/null +++ b/frontend/src/composables/README.md @@ -0,0 +1,197 @@ +# 组合式函数 (Composables) + +## 目的 +封装可复用的组合式逻辑,利用Vue 3的Composition API。 + +## 内容 +- **用户状态管理**: 用户认证和状态管理 +- **实时连接管理**: SignalR连接状态管理 +- **文档协作**: 实时文档编辑逻辑 +- **表单处理**: 表单验证和提交逻辑 + +## 特点 +- 响应式状态管理 +- 逻辑复用和组合 +- Vue 3 Composition API +- 易于测试和维护 + +## 示例 +```javascript +// composables/useAuth.js +import { ref, computed } from 'vue' +import { storage } from '@/utils/storage' +import ApiService from '@/services/api.service' + +const user = ref(null) +const token = ref(storage.get('auth_token')) + +export function useAuth() { + const isAuthenticated = computed(() => !!user.value) + + const login = async (credentials) => { + try { + const response = await ApiService.login(credentials) + user.value = response.user + token.value = response.token + storage.set('auth_token', response.token) + storage.set('user', response.user) + return { success: true } + } catch (error) { + return { success: false, error: error.message } + } + } + + const logout = () => { + user.value = null + token.value = null + storage.remove('auth_token') + storage.remove('user') + } + + const loadUserFromStorage = () => { + const storedUser = storage.get('user') + const storedToken = storage.get('auth_token') + if (storedUser && storedToken) { + user.value = storedUser + token.value = storedToken + } + } + + return { + user, + token, + isAuthenticated, + login, + logout, + loadUserFromStorage + } +} + +// composables/useSignalR.js +import { ref, onMounted, onUnmounted } from 'vue' +import SignalRService from '@/services/signalr.service' + +export function useSignalR() { + const isConnected = ref(false) + const connectionError = ref(null) + + const connect = async () => { + try { + await SignalRService.connect() + isConnected.value = true + connectionError.value = null + } catch (error) { + console.error('SignalR连接失败:', error) + connectionError.value = error.message + isConnected.value = false + } + } + + const disconnect = async () => { + try { + await SignalRService.disconnect() + isConnected.value = false + } catch (error) { + console.error('SignalR断开连接失败:', error) + } + } + + const on = (eventName, callback) => { + SignalRService.on(eventName, callback) + } + + const invoke = async (methodName, ...args) => { + try { + return await SignalRService.invoke(methodName, ...args) + } catch (error) { + console.error('SignalR调用失败:', error) + throw error + } + } + + onMounted(() => { + connect() + }) + + onUnmounted(() => { + disconnect() + }) + + return { + isConnected, + connectionError, + connect, + disconnect, + on, + invoke + } +} + +// composables/useCollaboration.js +import { ref, reactive } from 'vue' +import { useSignalR } from './useSignalR' + +export function useCollaboration() { + const { on, invoke } = useSignalR() + + const activeDocument = ref(null) + const connectedUsers = ref([]) + const documentChanges = reactive([]) + + const joinDocument = async (documentId) => { + try { + await invoke('JoinDocument', documentId) + activeDocument.value = documentId + } catch (error) { + console.error('加入文档失败:', error) + } + } + + const leaveDocument = async () => { + if (activeDocument.value) { + try { + await invoke('LeaveDocument', activeDocument.value) + activeDocument.value = null + connectedUsers.value = [] + } catch (error) { + console.error('离开文档失败:', error) + } + } + } + + const sendDocumentChange = async (change) => { + if (activeDocument.value) { + try { + await invoke('SendDocumentChange', activeDocument.value, change) + } catch (error) { + console.error('发送文档更改失败:', error) + } + } + } + + // 监听SignalR事件 + on('UserJoined', (userName) => { + console.log(`用户 ${userName} 加入了协作`) + // 更新连接用户列表 + }) + + on('UserLeft', (userName) => { + console.log(`用户 ${userName} 离开了协作`) + // 更新连接用户列表 + }) + + on('DocumentChanged', (change) => { + console.log('收到文档更改:', change) + documentChanges.push(change) + }) + + return { + activeDocument, + connectedUsers, + documentChanges, + joinDocument, + leaveDocument, + sendDocumentChange + } +} +``` diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..fda1e6e --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..e1eab52 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,8 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [], +}) + +export default router diff --git a/frontend/src/services/README.md b/frontend/src/services/README.md new file mode 100644 index 0000000..64e4b18 --- /dev/null +++ b/frontend/src/services/README.md @@ -0,0 +1,85 @@ +# 服务层 (Services) + +## 目的 +封装与后端API和外部服务的通信逻辑。 + +## 内容 +- **API服务**: HTTP请求的封装和处理 +- **SignalR服务**: 实时通信连接管理 +- **认证服务**: 用户登录和令牌管理 +- **文件服务**: 文件上传和下载处理 + +## 特点 +- 统一的API调用接口 +- 错误处理和重试机制 +- 请求拦截器和响应处理 +- 实时连接状态管理 + +## 示例 +```javascript +// api.service.js +import axios from 'axios' + +class ApiService { + constructor() { + this.baseURL = import.meta.env.VITE_API_BASE_URL + this.client = axios.create({ + baseURL: this.baseURL, + timeout: 10000 + }) + } + + async getUsers() { + const response = await this.client.get('/users') + return response.data + } + + async createUser(userData) { + const response = await this.client.post('/users', userData) + return response.data + } +} + +export default new ApiService() + +// signalr.service.js +import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr' + +class SignalRService { + constructor() { + this.connection = null + this.hubUrl = import.meta.env.VITE_SIGNALR_HUB_URL + } + + async connect() { + this.connection = new HubConnectionBuilder() + .withUrl(this.hubUrl + '/collaboration') + .configureLogging(LogLevel.Information) + .build() + + await this.connection.start() + console.log('SignalR连接已建立') + } + + async disconnect() { + if (this.connection) { + await this.connection.stop() + this.connection = null + } + } + + on(eventName, callback) { + if (this.connection) { + this.connection.on(eventName, callback) + } + } + + async invoke(methodName, ...args) { + if (this.connection) { + return await this.connection.invoke(methodName, ...args) + } + } +} + +export default new SignalRService() +``` diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js new file mode 100644 index 0000000..4625de7 --- /dev/null +++ b/frontend/src/services/api.js @@ -0,0 +1,44 @@ +// API服务基础配置 +import axios from 'axios' +import { storage } from '@/utils/storage' + +// 创建axios实例 +const apiClient = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 - 添加认证token +apiClient.interceptors.request.use( + (config) => { + const token = storage.get('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 - 处理通用错误 +apiClient.interceptors.response.use( + (response) => { + return response + }, + (error) => { + if (error.response?.status === 401) { + // Token过期,清除本地存储并重定向到登录页 + storage.remove('auth_token') + storage.remove('user') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export default apiClient diff --git a/frontend/src/services/signalr.js b/frontend/src/services/signalr.js new file mode 100644 index 0000000..cb0a54c --- /dev/null +++ b/frontend/src/services/signalr.js @@ -0,0 +1,93 @@ +// SignalR连接服务 +import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr' + +class SignalRService { + constructor() { + this.connection = null + this.hubUrl = import.meta.env.VITE_SIGNALR_HUB_URL + this.isConnected = false + } + + async connect(token = null) { + try { + const builder = new HubConnectionBuilder() + .withUrl(`${this.hubUrl}/collaboration`, { + accessTokenFactory: () => token + }) + .configureLogging(LogLevel.Information) + .withAutomaticReconnect() + + this.connection = builder.build() + + // 连接状态事件 + this.connection.onreconnecting(() => { + console.log('SignalR正在重连...') + this.isConnected = false + }) + + this.connection.onreconnected(() => { + console.log('SignalR重连成功') + this.isConnected = true + }) + + this.connection.onclose(() => { + console.log('SignalR连接已关闭') + this.isConnected = false + }) + + await this.connection.start() + this.isConnected = true + console.log('SignalR连接已建立') + + return true + } catch (error) { + console.error('SignalR连接失败:', error) + this.isConnected = false + return false + } + } + + async disconnect() { + if (this.connection) { + try { + await this.connection.stop() + this.connection = null + this.isConnected = false + console.log('SignalR连接已断开') + } catch (error) { + console.error('SignalR断开连接失败:', error) + } + } + } + + on(eventName, callback) { + if (this.connection) { + this.connection.on(eventName, callback) + } + } + + off(eventName, callback) { + if (this.connection) { + this.connection.off(eventName, callback) + } + } + + async invoke(methodName, ...args) { + if (this.connection && this.isConnected) { + try { + return await this.connection.invoke(methodName, ...args) + } catch (error) { + console.error(`SignalR调用 ${methodName} 失败:`, error) + throw error + } + } else { + throw new Error('SignalR连接未建立') + } + } + + getConnectionState() { + return this.connection?.state || 'Disconnected' + } +} + +export default new SignalRService() diff --git a/frontend/src/stores/README.md b/frontend/src/stores/README.md new file mode 100644 index 0000000..6e7bb8c --- /dev/null +++ b/frontend/src/stores/README.md @@ -0,0 +1,55 @@ +# 状态管理 (Stores) + +## 目的 +使用Pinia管理应用的全局状态。 + +## 内容 +- **用户状态**: 用户信息和认证状态管理 +- **文档状态**: 文档列表和当前文档状态 +- **协作状态**: 实时协作连接和用户状态 +- **UI状态**: 界面交互状态管理 + +## 特点 +- 响应式状态管理 +- 持久化存储支持 +- 组合式API风格 +- 模块化状态管理 + +## 状态文件 +```javascript +// auth.js - 用户认证状态管理 +export const useAuthStore = defineStore('auth', () => { + const user = ref(null) + const isAuthenticated = computed(() => !!user.value) + + const login = async (credentials) => { + // 登录逻辑 + } + + return { user, isAuthenticated, login } +}) + +// document.js - 文档状态管理 +export const useDocumentStore = defineStore('document', () => { + const documents = ref([]) + const currentDocument = ref(null) + + const fetchDocuments = async () => { + // 获取文档列表 + } + + return { documents, currentDocument, fetchDocuments } +}) + +// collaboration.js - 协作状态管理 +export const useCollaborationStore = defineStore('collaboration', () => { + const activeDocument = ref(null) + const connectedUsers = ref([]) + + const joinDocument = async (documentId) => { + // 加入文档协作 + } + + return { activeDocument, connectedUsers, joinDocument } +}) +``` diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js new file mode 100644 index 0000000..9031c3b --- /dev/null +++ b/frontend/src/stores/auth.js @@ -0,0 +1,116 @@ +// 用户认证状态管理 +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { storage } from '@/utils/storage' +import apiClient from '@/services/api' + +export const useAuthStore = defineStore('auth', () => { + const user = ref(null) + const token = ref(storage.get('auth_token')) + const loading = ref(false) + const error = ref(null) + + // 计算属性 + const isAuthenticated = computed(() => !!user.value && !!token.value) + const userName = computed(() => user.value?.name || '') + + // 登录 + const login = async (credentials) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.post('/auth/login', credentials) + const { user: userData, token: authToken } = response.data + + user.value = userData + token.value = authToken + + // 保存到本地存储 + storage.set('auth_token', authToken) + storage.set('user', userData) + + return { success: true } + } catch (err) { + error.value = err.response?.data?.message || '登录失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + // 注册 + const register = async (userData) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.post('/auth/register', userData) + const { user: newUser, token: authToken } = response.data + + user.value = newUser + token.value = authToken + + storage.set('auth_token', authToken) + storage.set('user', newUser) + + return { success: true } + } catch (err) { + error.value = err.response?.data?.message || '注册失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + // 登出 + const logout = () => { + user.value = null + token.value = null + storage.remove('auth_token') + storage.remove('user') + } + + // 从本地存储加载用户信息 + const loadUserFromStorage = () => { + const storedUser = storage.get('user') + const storedToken = storage.get('auth_token') + + if (storedUser && storedToken) { + user.value = storedUser + token.value = storedToken + } + } + + // 更新用户信息 + const updateUser = async (userData) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.put('/auth/profile', userData) + user.value = response.data + storage.set('user', response.data) + return { success: true } + } catch (err) { + error.value = err.response?.data?.message || '更新失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + return { + user, + token, + loading, + error, + isAuthenticated, + userName, + login, + register, + logout, + loadUserFromStorage, + updateUser + } +}) diff --git a/frontend/src/stores/collaboration.js b/frontend/src/stores/collaboration.js new file mode 100644 index 0000000..6531564 --- /dev/null +++ b/frontend/src/stores/collaboration.js @@ -0,0 +1,168 @@ +// 实时协作状态管理 +import { defineStore } from 'pinia' +import { ref, reactive } from 'vue' +import signalrService from '@/services/signalr' + +export const useCollaborationStore = defineStore('collaboration', () => { + const isConnected = ref(false) + const activeDocument = ref(null) + const connectedUsers = ref([]) + const documentChanges = reactive([]) + const userCursors = reactive(new Map()) + const loading = ref(false) + const error = ref(null) + + // 连接SignalR + const connect = async (token) => { + loading.value = true + error.value = null + + try { + const success = await signalrService.connect(token) + if (success) { + isConnected.value = true + setupEventHandlers() + } + return success + } catch (err) { + error.value = '连接失败' + isConnected.value = false + return false + } finally { + loading.value = false + } + } + + // 断开连接 + const disconnect = async () => { + try { + await signalrService.disconnect() + isConnected.value = false + activeDocument.value = null + connectedUsers.value = [] + documentChanges.splice(0) + userCursors.clear() + } catch (err) { + console.error('断开连接失败:', err) + } + } + + // 加入文档协作 + const joinDocument = async (documentId) => { + if (!isConnected.value) { + throw new Error('SignalR未连接') + } + + try { + await signalrService.invoke('JoinDocument', documentId) + activeDocument.value = documentId + return true + } catch (err) { + error.value = '加入文档失败' + return false + } + } + + // 离开文档协作 + const leaveDocument = async () => { + if (!activeDocument.value) return + + try { + await signalrService.invoke('LeaveDocument', activeDocument.value) + activeDocument.value = null + connectedUsers.value = [] + userCursors.clear() + } catch (err) { + console.error('离开文档失败:', err) + } + } + + // 发送文档更改 + const sendDocumentChange = async (change) => { + if (!activeDocument.value || !isConnected.value) { + throw new Error('未连接到文档') + } + + try { + await signalrService.invoke('SendDocumentChange', activeDocument.value, change) + return true + } catch (err) { + error.value = '发送更改失败' + return false + } + } + + // 发送光标位置 + const sendCursorPosition = async (position) => { + if (!activeDocument.value || !isConnected.value) return + + try { + await signalrService.invoke('SendCursorPosition', activeDocument.value, position) + } catch (err) { + console.error('发送光标位置失败:', err) + } + } + + // 设置事件处理器 + const setupEventHandlers = () => { + // 用户加入 + signalrService.on('UserJoined', (user) => { + console.log('用户加入:', user) + if (!connectedUsers.value.find(u => u.id === user.id)) { + connectedUsers.value.push(user) + } + }) + + // 用户离开 + signalrService.on('UserLeft', (userId) => { + console.log('用户离开:', userId) + connectedUsers.value = connectedUsers.value.filter(u => u.id !== userId) + userCursors.delete(userId) + }) + + // 文档更改 + signalrService.on('DocumentChanged', (change) => { + console.log('收到文档更改:', change) + documentChanges.push({ + ...change, + timestamp: new Date() + }) + }) + + // 光标位置更新 + signalrService.on('CursorMoved', (userId, position) => { + userCursors.set(userId, { + userId, + position, + timestamp: new Date() + }) + }) + + // 用户列表更新 + signalrService.on('ConnectedUsersUpdated', (users) => { + connectedUsers.value = users + }) + } + + // 清除错误 + const clearError = () => { + error.value = null + } + + return { + isConnected, + activeDocument, + connectedUsers, + documentChanges, + userCursors, + loading, + error, + connect, + disconnect, + joinDocument, + leaveDocument, + sendDocumentChange, + sendCursorPosition, + clearError + } +}) diff --git a/frontend/src/stores/counter.js b/frontend/src/stores/counter.js new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/frontend/src/stores/counter.js @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/frontend/src/stores/document.js b/frontend/src/stores/document.js new file mode 100644 index 0000000..0f0d2ed --- /dev/null +++ b/frontend/src/stores/document.js @@ -0,0 +1,187 @@ +// 文档管理状态 +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import apiClient from '@/services/api' + +export const useDocumentStore = defineStore('document', () => { + const documents = ref([]) + const currentDocument = ref(null) + const loading = ref(false) + const error = ref(null) + + // 计算属性 + const documentCount = computed(() => documents.value.length) + const hasDocuments = computed(() => documents.value.length > 0) + + // 获取所有文档 + const fetchDocuments = async () => { + loading.value = true + error.value = null + + try { + const response = await apiClient.get('/documents') + documents.value = response.data + return true + } catch (err) { + error.value = err.response?.data?.message || '获取文档列表失败' + return false + } finally { + loading.value = false + } + } + + // 获取单个文档 + const fetchDocument = async (id) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.get(`/documents/${id}`) + currentDocument.value = response.data + return response.data + } catch (err) { + error.value = err.response?.data?.message || '获取文档失败' + return null + } finally { + loading.value = false + } + } + + // 创建文档 + const createDocument = async (documentData) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.post('/documents', documentData) + const newDocument = response.data + documents.value.unshift(newDocument) + return { success: true, document: newDocument } + } catch (err) { + error.value = err.response?.data?.message || '创建文档失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + // 更新文档 + const updateDocument = async (id, documentData) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.put(`/documents/${id}`, documentData) + const updatedDocument = response.data + + // 更新列表中的文档 + const index = documents.value.findIndex(doc => doc.id === id) + if (index !== -1) { + documents.value[index] = updatedDocument + } + + // 更新当前文档 + if (currentDocument.value?.id === id) { + currentDocument.value = updatedDocument + } + + return { success: true, document: updatedDocument } + } catch (err) { + error.value = err.response?.data?.message || '更新文档失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + // 删除文档 + const deleteDocument = async (id) => { + loading.value = true + error.value = null + + try { + await apiClient.delete(`/documents/${id}`) + + // 从列表中移除 + documents.value = documents.value.filter(doc => doc.id !== id) + + // 如果是当前文档,清空 + if (currentDocument.value?.id === id) { + currentDocument.value = null + } + + return { success: true } + } catch (err) { + error.value = err.response?.data?.message || '删除文档失败' + return { success: false, error: error.value } + } finally { + loading.value = false + } + } + + // 搜索文档 + const searchDocuments = async (query) => { + loading.value = true + error.value = null + + try { + const response = await apiClient.get('/documents/search', { + params: { q: query } + }) + return response.data + } catch (err) { + error.value = err.response?.data?.message || '搜索文档失败' + return [] + } finally { + loading.value = false + } + } + + // 保存文档内容 + const saveDocumentContent = async (id, content) => { + try { + const response = await apiClient.patch(`/documents/${id}/content`, { + content + }) + + // 更新当前文档内容 + if (currentDocument.value?.id === id) { + currentDocument.value.content = content + currentDocument.value.updatedAt = response.data.updatedAt + } + + return true + } catch (err) { + console.error('保存文档内容失败:', err) + return false + } + } + + // 清除错误 + const clearError = () => { + error.value = null + } + + // 清除当前文档 + const clearCurrentDocument = () => { + currentDocument.value = null + } + + return { + documents, + currentDocument, + loading, + error, + documentCount, + hasDocuments, + fetchDocuments, + fetchDocument, + createDocument, + updateDocument, + deleteDocument, + searchDocuments, + saveDocumentContent, + clearError, + clearCurrentDocument + } +}) diff --git a/frontend/src/types/README.md b/frontend/src/types/README.md new file mode 100644 index 0000000..1cd9c85 --- /dev/null +++ b/frontend/src/types/README.md @@ -0,0 +1,63 @@ +# 类型定义 (Types) + +## 目的 +定义TypeScript类型和接口,确保类型安全。 + +## 内容 +- **API类型**: 后端API的请求和响应类型 +- **实体类型**: 业务实体的TypeScript定义 +- **组件Props**: Vue组件的属性类型定义 +- **事件类型**: SignalR事件的类型定义 + +## 特点 +- 类型安全保障 +- 代码智能提示 +- 编译时错误检查 +- 接口契约定义 + +## 示例 +```typescript +// types/user.ts +export interface User { + id: number + name: string + email: string + createdAt: string +} + +export interface LoginCredentials { + email: string + password: string +} + +export interface LoginResponse { + token: string + user: User +} + +// types/document.ts +export interface Document { + id: string + title: string + content: string + ownerId: number + collaborators: User[] + createdAt: string + updatedAt: string +} + +export interface DocumentChange { + type: 'insert' | 'delete' | 'replace' + position: number + content: string + userId: number +} + +// types/signalr.ts +export interface SignalREvents { + 'UserJoined': (userName: string) => void + 'UserLeft': (userName: string) => void + 'DocumentChanged': (change: DocumentChange) => void + 'CursorMoved': (userId: number, position: number) => void +} +``` diff --git a/frontend/src/utils/README.md b/frontend/src/utils/README.md new file mode 100644 index 0000000..4e6b667 --- /dev/null +++ b/frontend/src/utils/README.md @@ -0,0 +1,117 @@ +# 工具函数 (Utils) + +## 目的 +提供通用的工具函数和助手方法。 + +## 内容 +- **日期处理**: 日期格式化和计算工具 +- **验证工具**: 表单验证和数据校验 +- **存储工具**: 本地存储的封装 +- **格式化工具**: 数据格式化和转换 + +## 特点 +- 纯函数设计 +- 可复用性强 +- 易于测试 +- 模块化导出 + +## 示例 +```javascript +// utils/date.js +export const formatDate = (date) => { + const d = new Date(date) + return d.toLocaleDateString('zh-CN') +} + +export const formatRelativeTime = (date) => { + const now = new Date() + const target = new Date(date) + const diff = now.getTime() - target.getTime() + + if (diff < 60000) return '刚刚' + if (diff < 3600000) return `${Math.floor(diff / 60000)}分钟前` + if (diff < 86400000) return `${Math.floor(diff / 3600000)}小时前` + return formatDate(date) +} + +export const formatDateTime = (date) => { + const d = new Date(date) + return d.toLocaleString('zh-CN') +} + +// utils/validation.js +export const isValidEmail = (email) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) +} + +export const isValidPassword = (password) => { + return password && password.length >= 8 +} + +export const isRequired = (value) => { + return value !== null && value !== undefined && value !== '' +} + +// utils/storage.js +export const storage = { + get(key) { + try { + const item = localStorage.getItem(key) + return item ? JSON.parse(item) : null + } catch (error) { + console.error('读取本地存储失败:', error) + return null + } + }, + + set(key, value) { + try { + localStorage.setItem(key, JSON.stringify(value)) + } catch (error) { + console.error('设置本地存储失败:', error) + } + }, + + remove(key) { + try { + localStorage.removeItem(key) + } catch (error) { + console.error('删除本地存储失败:', error) + } + }, + + clear() { + try { + localStorage.clear() + } catch (error) { + console.error('清空本地存储失败:', error) + } + } +} + +// utils/debounce.js +export const debounce = (func, wait) => { + let timeout + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout) + func(...args) + } + clearTimeout(timeout) + timeout = setTimeout(later, wait) + } +} + +// utils/throttle.js +export const throttle = (func, limit) => { + let inThrottle + return function(...args) { + if (!inThrottle) { + func.apply(this, args) + inThrottle = true + setTimeout(() => inThrottle = false, limit) + } + } +} +``` diff --git a/frontend/src/utils/storage.js b/frontend/src/utils/storage.js new file mode 100644 index 0000000..894d6b3 --- /dev/null +++ b/frontend/src/utils/storage.js @@ -0,0 +1,56 @@ +// 本地存储工具 +export const storage = { + // 获取数据 + get(key) { + try { + const item = localStorage.getItem(key) + return item ? JSON.parse(item) : null + } catch (error) { + console.error('读取本地存储失败:', error) + return null + } + }, + + // 设置数据 + set(key, value) { + try { + localStorage.setItem(key, JSON.stringify(value)) + return true + } catch (error) { + console.error('设置本地存储失败:', error) + return false + } + }, + + // 删除数据 + remove(key) { + try { + localStorage.removeItem(key) + return true + } catch (error) { + console.error('删除本地存储失败:', error) + return false + } + }, + + // 清空所有数据 + clear() { + try { + localStorage.clear() + return true + } catch (error) { + console.error('清空本地存储失败:', error) + return false + } + }, + + // 检查是否存在 + has(key) { + return localStorage.getItem(key) !== null + }, + + // 获取所有键 + keys() { + return Object.keys(localStorage) + } +} diff --git a/frontend/src/views/auth/README.md b/frontend/src/views/auth/README.md new file mode 100644 index 0000000..3db31b5 --- /dev/null +++ b/frontend/src/views/auth/README.md @@ -0,0 +1,26 @@ +# 认证视图 (Auth Views) + +## 目的 +存放用户认证相关的页面视图。 + +## 内容 +- **登录页面**: 用户登录表单和处理逻辑 +- **注册页面**: 用户注册表单和验证 +- **密码重置**: 忘记密码和重置功能 +- **个人资料**: 用户信息查看和编辑 + +## 特点 +- 表单验证和错误处理 +- 响应式布局设计 +- 安全性考虑 +- 用户体验优化 + +## 页面文件 +```javascript +// LoginView.vue - 登录页面 +// RegisterView.vue - 注册页面 +// ForgotPasswordView.vue - 忘记密码页面 +// ResetPasswordView.vue - 重置密码页面 +// ProfileView.vue - 个人资料页面 +// ChangePasswordView.vue - 修改密码页面 +``` diff --git a/frontend/src/views/collaboration/README.md b/frontend/src/views/collaboration/README.md new file mode 100644 index 0000000..3324760 --- /dev/null +++ b/frontend/src/views/collaboration/README.md @@ -0,0 +1,26 @@ +# 协作视图 (Collaboration Views) + +## 目的 +存放实时协作编辑相关的页面视图。 + +## 内容 +- **协作编辑器**: 实时文档协作编辑页面 +- **协作会话**: 当前协作会话管理 +- **用户管理**: 协作用户的权限和状态管理 +- **版本历史**: 文档版本和变更历史 + +## 特点 +- 实时多用户协作 +- 冲突检测和解决 +- 用户状态实时显示 +- 版本控制和回滚 + +## 页面文件 +```javascript +// CollaborationEditorView.vue - 协作编辑器主页面 +// ActiveSessionView.vue - 当前活跃会话 +// CollaboratorsView.vue - 协作用户管理 +// VersionHistoryView.vue - 版本历史页面 +// ConflictResolutionView.vue - 冲突解决页面 +// ShareDocumentView.vue - 文档分享页面 +``` diff --git a/frontend/src/views/documents/README.md b/frontend/src/views/documents/README.md new file mode 100644 index 0000000..a7ff5f6 --- /dev/null +++ b/frontend/src/views/documents/README.md @@ -0,0 +1,26 @@ +# 文档管理视图 (Document Views) + +## 目的 +存放文档管理相关的页面视图。 + +## 内容 +- **文档列表**: 显示用户的所有文档 +- **文档创建**: 创建新文档的页面 +- **文档详情**: 查看文档信息和设置 +- **文档搜索**: 搜索和过滤文档 + +## 特点 +- 文档CRUD操作 +- 搜索和筛选功能 +- 分页和虚拟滚动 +- 文档权限管理 + +## 页面文件 +```javascript +// DocumentListView.vue - 文档列表页面 +// CreateDocumentView.vue - 创建文档页面 +// DocumentDetailView.vue - 文档详情页面 +// DocumentSearchView.vue - 文档搜索页面 +// DocumentSettingsView.vue - 文档设置页面 +// SharedDocumentsView.vue - 共享文档页面 +``` -- Gitee From 251a9978bcd2d43fef8e417f0a91fcb003c2420d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E8=82=B2=E6=9E=97?= <2921544609@qq.com> Date: Fri, 15 Aug 2025 13:29:26 +0800 Subject: [PATCH 002/120] =?UTF-8?q?=E6=90=AD=E5=BB=BA=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=A1=86=E6=9E=B6=E2=80=94=E2=80=94=E2=80=94=E5=89=8D=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/CollabApp.sln | 84 ++++++++ .../src/CollabApp.API/CollabApp.API.csproj | 25 +++ backend/src/CollabApp.API/CollabApp.API.http | 6 + .../src/CollabApp.API/Controllers/README.md | 38 ++++ backend/src/CollabApp.API/Hubs/README.md | 34 +++ .../src/CollabApp.API/Middleware/README.md | 44 ++++ backend/src/CollabApp.API/Program.cs | 41 ++++ .../Properties/launchSettings.json | 23 ++ backend/src/CollabApp.API/appsettings.json | 9 + .../CollabApp.Application.csproj | 18 ++ .../CollabApp.Application/Commands/README.md | 32 +++ .../src/CollabApp.Application/DTOs/README.md | 36 ++++ .../Interfaces/README.md | 30 +++ .../CollabApp.Application/Queries/README.md | 31 +++ .../CollabApp.Domain/CollabApp.Domain.csproj | 9 + .../src/CollabApp.Domain/Entities/README.md | 30 +++ .../CollabApp.Domain/Repositories/README.md | 28 +++ .../src/CollabApp.Domain/Services/README.md | 29 +++ .../CollabApp.Domain/ValueObjects/README.md | 35 ++++ .../CollabApp.Infrastructure.csproj | 21 ++ .../CollabApp.Infrastructure/Data/README.md | 30 +++ .../Repositories/README.md | 34 +++ .../Services/README.md | 34 +++ frontend/.gitattributes | 1 + frontend/.gitignore | 30 +++ frontend/.prettierrc.json | 6 + frontend/index.html | 13 ++ frontend/jsconfig.json | 8 + frontend/package.json | 32 +++ frontend/public/favicon.ico | Bin 0 -> 4286 bytes frontend/src/api/auth.js | 39 ++++ frontend/src/api/common.js | 89 ++++++++ frontend/src/api/game.js | 64 ++++++ .../src/{services/api.js => api/index.js} | 28 +-- frontend/src/api/ranking.js | 56 +++++ frontend/src/api/room.js | 63 ++++++ frontend/src/api/user.js | 40 ++++ frontend/src/components/common/README.md | 27 +-- frontend/src/components/editor/README.md | 27 +-- frontend/src/components/forms/README.md | 27 +-- frontend/src/composables/README.md | 197 ------------------ frontend/src/router/index.js | 3 +- frontend/src/router/routes.js | 1 + frontend/src/services/README.md | 85 -------- frontend/src/services/signalr.js | 93 --------- frontend/src/stores/README.md | 56 +---- frontend/src/stores/auth.js | 116 ----------- frontend/src/stores/collaboration.js | 168 --------------- frontend/src/stores/counter.js | 12 -- frontend/src/stores/document.js | 187 ----------------- frontend/src/types/README.md | 63 ------ frontend/src/utils/README.md | 118 +---------- frontend/src/utils/storage.js | 56 ----- frontend/src/views/auth/ForgotPassword.vue | 29 +++ frontend/src/views/auth/Login.vue | 28 +++ frontend/src/views/auth/README.md | 26 --- frontend/src/views/auth/Register.vue | 30 +++ frontend/src/views/collaboration/README.md | 26 --- frontend/src/views/documents/README.md | 26 --- frontend/src/views/game/Game.vue | 54 +++++ frontend/src/views/game/GameOver.vue | 53 +++++ frontend/src/views/game/GameRoom.vue | 49 +++++ frontend/src/views/home/Home.vue | 33 +++ frontend/src/views/lobby/Lobby.vue | 43 ++++ frontend/src/views/profile/Profile.vue | 62 ++++++ frontend/src/views/ranking/Ranking.vue | 62 ++++++ frontend/vite.config.js | 18 ++ frontend/vitest.config.js | 14 ++ 68 files changed, 1644 insertions(+), 1315 deletions(-) create mode 100644 backend/CollabApp.sln create mode 100644 backend/src/CollabApp.API/CollabApp.API.csproj create mode 100644 backend/src/CollabApp.API/CollabApp.API.http create mode 100644 backend/src/CollabApp.API/Controllers/README.md create mode 100644 backend/src/CollabApp.API/Hubs/README.md create mode 100644 backend/src/CollabApp.API/Middleware/README.md create mode 100644 backend/src/CollabApp.API/Program.cs create mode 100644 backend/src/CollabApp.API/Properties/launchSettings.json create mode 100644 backend/src/CollabApp.API/appsettings.json create mode 100644 backend/src/CollabApp.Application/CollabApp.Application.csproj create mode 100644 backend/src/CollabApp.Application/Commands/README.md create mode 100644 backend/src/CollabApp.Application/DTOs/README.md create mode 100644 backend/src/CollabApp.Application/Interfaces/README.md create mode 100644 backend/src/CollabApp.Application/Queries/README.md create mode 100644 backend/src/CollabApp.Domain/CollabApp.Domain.csproj create mode 100644 backend/src/CollabApp.Domain/Entities/README.md create mode 100644 backend/src/CollabApp.Domain/Repositories/README.md create mode 100644 backend/src/CollabApp.Domain/Services/README.md create mode 100644 backend/src/CollabApp.Domain/ValueObjects/README.md create mode 100644 backend/src/CollabApp.Infrastructure/CollabApp.Infrastructure.csproj create mode 100644 backend/src/CollabApp.Infrastructure/Data/README.md create mode 100644 backend/src/CollabApp.Infrastructure/Repositories/README.md create mode 100644 backend/src/CollabApp.Infrastructure/Services/README.md create mode 100644 frontend/.gitattributes create mode 100644 frontend/.gitignore create mode 100644 frontend/.prettierrc.json create mode 100644 frontend/index.html create mode 100644 frontend/jsconfig.json create mode 100644 frontend/package.json create mode 100644 frontend/public/favicon.ico create mode 100644 frontend/src/api/auth.js create mode 100644 frontend/src/api/common.js create mode 100644 frontend/src/api/game.js rename frontend/src/{services/api.js => api/index.js} (47%) create mode 100644 frontend/src/api/ranking.js create mode 100644 frontend/src/api/room.js create mode 100644 frontend/src/api/user.js delete mode 100644 frontend/src/composables/README.md create mode 100644 frontend/src/router/routes.js delete mode 100644 frontend/src/services/README.md delete mode 100644 frontend/src/services/signalr.js delete mode 100644 frontend/src/stores/auth.js delete mode 100644 frontend/src/stores/collaboration.js delete mode 100644 frontend/src/stores/counter.js delete mode 100644 frontend/src/stores/document.js delete mode 100644 frontend/src/types/README.md delete mode 100644 frontend/src/utils/storage.js create mode 100644 frontend/src/views/auth/ForgotPassword.vue create mode 100644 frontend/src/views/auth/Login.vue delete mode 100644 frontend/src/views/auth/README.md create mode 100644 frontend/src/views/auth/Register.vue delete mode 100644 frontend/src/views/collaboration/README.md delete mode 100644 frontend/src/views/documents/README.md create mode 100644 frontend/src/views/game/Game.vue create mode 100644 frontend/src/views/game/GameOver.vue create mode 100644 frontend/src/views/game/GameRoom.vue create mode 100644 frontend/src/views/home/Home.vue create mode 100644 frontend/src/views/lobby/Lobby.vue create mode 100644 frontend/src/views/profile/Profile.vue create mode 100644 frontend/src/views/ranking/Ranking.vue create mode 100644 frontend/vite.config.js create mode 100644 frontend/vitest.config.js diff --git a/backend/CollabApp.sln b/backend/CollabApp.sln new file mode 100644 index 0000000..48f1865 --- /dev/null +++ b/backend/CollabApp.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CollabApp.API", "src\CollabApp.API\CollabApp.API.csproj", "{5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CollabApp.Domain", "src\CollabApp.Domain\CollabApp.Domain.csproj", "{170263DD-CBBB-4106-9D78-A38A001F1F3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CollabApp.Application", "src\CollabApp.Application\CollabApp.Application.csproj", "{2505E022-6542-40FF-9725-1DA669A36A20}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CollabApp.Infrastructure", "src\CollabApp.Infrastructure\CollabApp.Infrastructure.csproj", "{78700058-9673-47E0-9993-2274A7BCD49C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|x64.ActiveCfg = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|x64.Build.0 = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|x86.ActiveCfg = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Debug|x86.Build.0 = Debug|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|Any CPU.Build.0 = Release|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|x64.ActiveCfg = Release|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|x64.Build.0 = Release|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|x86.ActiveCfg = Release|Any CPU + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F}.Release|x86.Build.0 = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|x64.ActiveCfg = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|x64.Build.0 = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|x86.ActiveCfg = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Debug|x86.Build.0 = Debug|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|Any CPU.Build.0 = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|x64.ActiveCfg = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|x64.Build.0 = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|x86.ActiveCfg = Release|Any CPU + {170263DD-CBBB-4106-9D78-A38A001F1F3B}.Release|x86.Build.0 = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|x64.ActiveCfg = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|x64.Build.0 = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|x86.ActiveCfg = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Debug|x86.Build.0 = Debug|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|Any CPU.Build.0 = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|x64.ActiveCfg = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|x64.Build.0 = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|x86.ActiveCfg = Release|Any CPU + {2505E022-6542-40FF-9725-1DA669A36A20}.Release|x86.Build.0 = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|x64.ActiveCfg = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|x64.Build.0 = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|x86.ActiveCfg = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Debug|x86.Build.0 = Debug|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|Any CPU.Build.0 = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|x64.ActiveCfg = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|x64.Build.0 = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|x86.ActiveCfg = Release|Any CPU + {78700058-9673-47E0-9993-2274A7BCD49C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5665D919-F5F1-41A6-AD0E-4DFAA9A7AC6F} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {170263DD-CBBB-4106-9D78-A38A001F1F3B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {2505E022-6542-40FF-9725-1DA669A36A20} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {78700058-9673-47E0-9993-2274A7BCD49C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal diff --git a/backend/src/CollabApp.API/CollabApp.API.csproj b/backend/src/CollabApp.API/CollabApp.API.csproj new file mode 100644 index 0000000..957da69 --- /dev/null +++ b/backend/src/CollabApp.API/CollabApp.API.csproj @@ -0,0 +1,25 @@ +